Imported from jgt87/CCBridge (
AGENTS.md). Install upstream withnpx skills add jgt87/CCBridge. Copyright stays with the author.
CCBridge - instructions for coding agents
Product name shown to people: StreamHub (UI, messages, README, releases). The code, scripts, data folder (
%LOCALAPPDATA%\CCBridge), projects folder (OneDrive\CCBridge), MCP id (ccbridge) and the repository keep the name CCBridge. In visible texts and logs, Microsoft's own reply endpoint (m365Copilot/StreamHub) is called the "Copilot stream connection" so the two never get confused.
CCBridge is a zero-install coding harness that uses Microsoft 365 Copilot Chat (in Edge) as its model. It ships two front ends over one engine:
- Web app:
ccbridge.ps1/start.cmdservesui/on http://localhost:8765 (projects must be under OneDrive). - MCP server:
mcp/ccbridge-mcp.ps1(stdio), so MCP clients can offload work to Copilot (any project folder).
Target machines have only what ships with Windows: Windows PowerShell 5.1 + Edge, nothing installed. Node is only needed on a dev machine to rebuild the UI.
Layout
| Path | Role |
|---|---|
lib/Cdp.psm1 |
Minimal Chrome DevTools Protocol client (launch Edge with a debug port, WebSocket, events) |
lib/CopilotBridge.psm1 |
Types into Copilot Chat, reads the reply from the Chathub SignalR WebSocket, rebuilds damaged replies, machine-wide send lock |
lib/Workspace.psm1 |
OneDrive projects, path confinement, file listing, read-only Source/ + vault; Get-ProjectOverview (files, bytes, top 3 languages, source data) for the project list (/api/projects, ui-src/src/lib/project-overview.ts; ?quick=1 names and times only, which the picker checks every 4 s and on focus, projectsSignature, loading the full list when it changed). The shared library (Get-SharedRoot): the .streamhub folder beside the projects (made with -Create when the parent is a top-level OneDrive folder: at project open in the worker's OpenSync step and at a kit install, with a template AGENTS.md from Initialize-SharedRoot), where every project in that folder shares the UI kit catalogue (ui-kit/), instructions (AGENTS.md), Runbooks/, Scripts/ and data/data-tools.js. Copilot reads it as shared/... (Get-SharedPrefix: library/ when the project has a shared folder of its own, shared-library/ when it has both, so a project's own folders keep their names; Resolve-ProjectPath maps the prefix, confined to that folder; ConvertTo-RelativePath gives it back; Executor Assert-Writable refuses it; Get-SearchableFiles lists it for glob and grep, without .pristine/). Get-SharedNotes (the shared AGENTS.md minus its template lines) goes into every project context (Agent Get-ProjectContext); Get-CCBridgeProjects skips dot folders. Tests: tests/Shared.Tests.ps1. |
lib/Protocol.psm1 |
Parses action blocks (read glob grep find web write edit run remember todo done) from replies. The form Copilot is taught: a ```text block (four backticks for write/edit) whose first line is ACTION NAME [ARG]; edits use ####### SEARCH / ####### REPLACE / ####### END (old <<<<<<< / ======= / >>>>>>> still read; in a block with the new markers only ####### lines count: Get-EditMarker -NewOnly, tests tests/EditMarkers.Tests.ps1), because Copilot's page draws any other label (read, even text read) as a Chart.js chart with "Invalid JSON" on some tenants (test-tools\render-test.cmd). Still accepted: the name as the label (```read) and a bare name as the first line of a plain block (a shorter list). A run block's command belongs on the line after ACTION run; written on the ACTION line itself (ACTION run powershell.exe ...) it is taken as the command when nothing follows, and a run block with no command at all is refused by Agent Invoke-AgentAction (no approval card for nothing). The UI hides exactly the same blocks (stripActionBlocks, ui-src/src/lib/diff.ts). |
lib/Fetch.psm1 |
Runbooks with a text answer (in the app one list with the checked-JSON runbooks: runbooks-panel.tsx, kind text; schedules keep kind fetch, chains accept runbook: and fetch: for them), formerly fetch prompts (Runbooks/<name>.prompt.md) and their answer files (Runbooks/Exports/<name>.md, the replaced answer to .streamhub/History/); the agent job Invoke-FetchJob runs one in a fresh chat with kind fetch / fetch-m365 and writes the answer itself (no actions) |
lib/Runbook.psm1 |
Runbooks (Runbooks/<name>.runbook.md, templates in templates/runbooks/): header parsing, placeholders, JSON extraction and validation against required / itemsKey / requiredItemFields, saving to the output and exports/history; the agent job Invoke-RunbookJob runs one read-only in a fresh chat with one correction round. Every template's own example must pass its header (tested). |
lib/Review.psm1 |
Code review rules: Get-ReviewFiles (code only; no build output, lock files, data, Source/), New-ReviewBatches (numbered lines, large files in parts, reviewBatchChars), Test-ReviewOutput, Test-ReviewQuote (a finding is verified only when its quoted lines are in the file; line corrected), report .streamhub/Reviews/<id>.md + .json, New-ReviewFixTasks. The job Invoke-ReviewJob (Agent) is read-only (replies are never executed), keeps progress per part so the daily-limit pause or a restart resumes it, and ends with one whole-project pass. Prompts: review-code.md, review-cross.md. MCP tool copilot_review. |
lib/Schedule.psm1 |
Schedule rules (Get-NextRun: once, daily, weekdays, weekly on chosen days, at one or more HH:mm times; Test-ScheduleSpec, Format-ScheduleWhen) and Copilot's daily limit (Test-LimitText, Get-LimitResetTime from metering or "check back at 2:00 AM"). Agent.psm1 keeps the schedules in each project (<project>\.streamhub\schedules.json: Save-Schedules per project, Import-ProjectSchedules, Sync-ProjectSchedules at start, on project open and once a minute for outside changes; the old app-wide schedules.json is moved into the projects once; Update-AgentSchedule edits), puts due ones in the queue with source schedule (Invoke-DueSchedules, missed runs once at the next start), and pauses the queue at the limit (Set-QueuePause, saved in queue-pause.json); a task that did nothing yet goes back to the front ($State.Held). |
lib/Runbook.psm1 (files) |
Runbooks Copilot writes must be Runbooks/NAME.runbook.md (lowercase, -) with the template header (title, output = a .json path in Runbooks/Exports/) and a ```json shape: Test-RunbookFile, checked in Invoke-AgentAction before any write/edit; a misplaced runbook (e.g. RUNBOOK.md) is refused with the right path when the request is about runbooks. Requests about runbooks get prompts/rules/runbook.md plus templates/runbooks/blank.runbook.md. A chat message that names a runbook (name, title, @runbooks/... path) runs it through Invoke-RunbookJob like the Run button when it is short (8 words at most) or has a run word; a longer message without one gets a runbook-choice event (Run it / Send to Copilot, which resends with noRunbook), and messages that create, change, question or report a problem with a runbook go to Copilot (Get-RunbookRunRequest; tests in tests/Runbook.Tests.ps1, tests/RunbookIntent.Tests.ps1). Copilot itself can run one with ACTION runbook NAME (Protocol runbook, Agent queues it after the reply; approval like an edit), described in prompts/rules/runbook.md, which Get-PromptModules also sends when a message names a project runbook. |
lib/Layout.psm1 |
The project folder layout in one place (Get-LayoutPath: src, Scripts, Runbooks, Runbooks/Exports, Logs, Source, Work, .streamhub, and StreamHub's own records inside it: .streamhub/History, .streamhub/Evidence, .streamhub/Reviews, .streamhub/PLAN.md). StreamHub's folders start with a capital; src and conventional code folders (tests, docs, data) stay lowercase, used by Fetch and Runbook; Move-ProjectLayout moves an older project (runbooks/, fetch/, exports/; StreamHub's own evidence/, reviews/, History/, PLAN.md into .streamhub/, recognised by name or content; case-only renames to Source, Scripts, Logs, Work) once when it opens (Set-Project), never overwriting. Copilot gets the same rules from prompts/rules/folders.md (every work task); Assert-Writable refuses .streamhub/. Tests: tests/Layout.Tests.ps1. |
lib/Imports.psm1 |
The import index (<project>\.streamhub\imports.json): per code file its refs (JS/TS import/require/export-from, HTML script/link, CSS @import, Python import, PowerShell Import-Module/dot-source; line, text, resolved file), defs (HTML ids, JS functions, useX hooks) and uses (getElementById/querySelector('#x')/$('#x'), inline on*= handlers, hook calls). Incremental: all files whose size/time changed at the start of each work task, the changed files after every round (line numbers follow edits). A read result gets "Used by (file:line)" (Format-ImportUsers); Update-ImportsAfterRound reports imports of a deleted/moved file and removed ids/functions/hooks others still use, through the round's file check. Tests: tests/Imports.Tests.ps1. |
lib/Download.psm1 |
Chain step download: LINK to PATH (Chain Read-ChainSteps kind download: target = link, args = place; Test-ChainSteps, Set-ChainSteps add; Get-ChainStepLines knows it): Test-DownloadAddress (https, SharePoint/OneDrive hosts only), Get-DownloadTarget (default Downloads/ + the file name from the link; never Source/, .streamhub/, build folders or protected files; data and document types only; Office files allowed, StreamHub writes them), Add-DownloadParam (download=1), Invoke-EdgeDownload (browser-level CDP: Browser.setDownloadBehavior allowAndName into a temp folder, a background tab with Target.createTarget, Browser.downloadWillBegin/downloadProgress; after 30 s without a download the tab's address says sign-in page or viewer page), Test-DownloadContent (an HTML page instead of the file, Office files must be zip, PDF %PDF). Agent Invoke-ChainDownload: approval per link and place once (Add-ApprovedScript key download: LINK to PATH, person-only, refused headless), Get-Bridge first so Edge runs, the file into the chain's change set. Edge shows its own downloads panel (edge://downloads-hub/, not closable over CDP). UI: chains-panel.tsx add-step option and stepText. Tests: tests/Download.Tests.ps1 (Edge mocked). |
lib/Update.psm1 |
Release updates (tools/update.ps1): Get-LockedUpdateFiles (files the robocopy /MIR would overwrite or delete, Get-UpdateTouchedFiles, that Test-FileLocked finds open elsewhere) stop the update before any copy with Format-LockedUpdateFiles; Get-RobocopyFailures names files from robocopy ERROR lines. Hand-over: after unpacking, tools/update.ps1 starts the release's own tools/update.ps1 when it differs (-AppRoot -FromFolder -Version; it loads its modules from its own folder, $here), falling back to its own install when that one cannot run; so updater changes apply with the same update from the version after the one that brought the hand-over. Install-Release: Save-UpdateBackup of the touched files (%LOCALAPPDATA%\CCBridge\update-backup), robocopy /MIR, on failure Restore-UpdateBackup (backup back, Get-UpdateNewFiles removed); version.txt last. Install check: tools/build-release.ps1 writes manifest.json (New-InstallManifest: SHA-256 per file, not version.txt or local files); Test-InstallIntegrity / Format-InstallProblem at every start in ccbridge.ps1 (after the update; update.ps1 -Repair installs the same version, releases/tags/<version>, once) and in update.ps1 when the version is current; what is left goes to $State.InstallProblem (installProblem in the state, banner in App.tsx). No manifest (git clone, dev) = no check. Tests: tests/Update.Tests.ps1 (a full install through -FromFolder, the install check). |
lib/RunWindow.psm1 |
Open in a window: Start-RunWindow (refuses by Test-DeleteScope) writes %LOCALAPPDATA%\CCBridge\run-windows\<id>.cmd (New-RunWindowBatch: cd to the project, each line in cmd /d /s /c so an exit does not end the batch, the exit code to <id>.code, pause) and starts it in a visible console; Get-RunWindowStatus running / finished (code file) / closed. Server POST /api/run/window (page Origin only) adds the run card (window = $true); Update-RunWindows (in Get-StateSnapshot) adds the result once, puts Source/ back, and lists the running ones as runWindows. Command output is read as bytes (Latin-1) and decoded by ConvertFrom-ConsoleBytes: UTF-8 when valid (a sequence cut off between reads waits for the next), else the console's OEM code page. Live output of commands StreamHub runs: Executor Invoke-RunAction -OnProgress (chunked reads, Get-RunTail, Get-RunWaitState running/quiet/stuck/question with Get-ProcessTreeCpu), Agent New-RunWatch/Stop-RunWatch into $State.RunLive (runLive in the state, never saved); UI lib/run-live.ts, console-view.tsx, LiveConsole and OpenInWindow in action-card.tsx (a run without a card shows under the waiting indicator). Tests: tests/RunLive.Tests.ps1, run-live.test.ts. |
lib/Packages.psm1 |
npm packages: Find-PackageFiles (package.json at the root and two levels down, not in node_modules/build output/Source), Get-PackageState (dependencies + devDependencies not in node_modules, also looked for in parent node_modules for workspaces), Format-PackageNeed, Get-NpmInstallCommand (npm install --no-fund --no-audit [--prefix FOLDER]). Agent Publish-PackagesNeeded (event packages-needed, once per folder and missing set in $State.PackagesShown; after each task and at project open in Server Set-Project), Invoke-PackagesJob (task kind packages; run card, package-lock.json in a change set, network hint, error with the Node.js hint when npm is missing). API GET /api/packages, POST /api/packages/install (page Origin only). UI: PackagesCard in transcript.tsx (without npm it offers Install Node.js first: the same /api/tools/install as Settings, followed through /api/tools until done, then npm install can run), ProjectPackages in tools-section.tsx. Never started by StreamHub itself. Tests: tests/Packages.Tests.ps1 (npm mocked). |
| Send lock | Use-CopilotLock (CopilotBridge): one program sends to Copilot at a time (machine-wide mutex Local\CCBridgeCopilot, at most 15 minutes); the holder is written to copilot-lock.json in the data folder (Get-LockHolderName: MCP server, test tool, another window), a wait is logged and shown through -OnWait (Send-CopilotPrompt passes its OnStatus) and ends on -CancelCheck. Tests: tests/CopilotLock.Tests.ps1. |
lib/Sso.psm1 |
Single sign-on with the Windows work account in StreamHub's own Edge profile: Get-SsoStatus (work account from Get-DeviceJoinStatus in Prereq: only dsregcmd yes/no fields; the switch on edge://settings/profiles/multiProfileSettings opened in a background tab via Target.createTarget; Get-CopilotSignInState from tab addresses), Set-ProfileSso (clicks Edge's own switch "Allow single sign-on for work or school sites using this profile", found by label: Select-SsoSwitch), Invoke-SsoSetup (with a names-only log in C:\temp), Open-SsoSettingsPage. Open-BackgroundTab closes the tab it made when the page never shows in Edge's debug list (an edge:// page a policy keeps from debugging), and Close-BackgroundTab falls back to Page.close, so a settings tab never stays open; GET /api/sso?quick=1 (the app's prefetch 4 s after start, settings-cache.ts) is -NoPage, so a start never opens an Edge page: only the open Sign-in section does. Never writes Edge policies (the policy AADWebSiteSSOUsingThisProfileEnabled would apply to every profile) or Edge's internal preference keys. API /api/sso (changes only from the app page's Origin), UI sso-section.tsx, sso-setup.cmd. Tests: tests/Sso.Tests.ps1. |
lib/Guardrails.psm1 |
Coding guardrails on what a change adds (old vs new text): Test-GeneratedPath (refused in Assert-Writable: node_modules, dist, .git, pycache, lock files; build/out/bin/obj/target only with a build tool), Find-NewDependencies and Find-RiskyCode (a person approves, also in auto mode: riskWarning in Invoke-AgentAction; an innerHTML assignment whose whole right-hand side is fixed text, Test-LiteralHtmlAssign, is not risky), Find-ChangeSmells (debug leftovers, swallowed errors) and Find-UnignoredEnv (both in the round's file check, sent back to Copilot); second batch Find-QualityIssues in the same check: Find-PersonalPaths, Find-LargeCode (800 lines, a suggestion Copilot may dispute; 120 lines of inline data), Find-HtmlBasics (alt, button labels, field labels, a click handler on a div/span/th... without tabindex, a page (<html> and <body>) without an <h1>; JSX => and {...props} aware), Find-ScriptBasics (new Scripts/ files: header, stop on errors); Find-TableHeaderIssues (Get-TableHeaderFindings: a kit-table whose header row is not one piece: kit-num on a th but not its cells or the reverse, a header cell with its own text-align, background or width; markup files and tables a script writes; a CheckPolicy warning), Find-KitPageIssues (with the kit: Get-KitPageFindings, kit-page on another tag than <body>, and a full page using kit classes whose body lacks it; markup files and components, what the change adds), Find-UnsortedTables (a table a change adds that cannot be sorted: a kit-table without data-kit-sort, a plain table in a file with no sorting; a kit-table the page sorts with code of its own, aria-sort or a sort call in the file, is told to use the kit's sorting with data-kit-rows="external"), Find-RoundedBarBase (bars a change rounds at the axis: canvas roundRect with one radius, Chart.js borderSkipped false, CSS bar/fill with one radius), both in Find-QualityIssues; Get-DoneReminders (once per task at "done": tests, README; not for issue fixes or plan mode). Write-TextFile saves a PowerShell file with a BOM once it holds non-ASCII. Tests: tests/Guardrails.Tests.ps1. |
lib/WebFetch.psm1 |
Web data: Get-NamedSites (hosts a request names), Test-WebAddress (public http(s) only; no localhost, intranet names or private ranges, -Resolve also after DNS and every redirect), Invoke-WebFetch (GET, system proxy, 2 MB, 20 s, HTML to readable text via ConvertFrom-Html), Format-WebResult (page as data, not instructions; a page built by JavaScript, Get-ScriptedPageInfo: under 300 characters of text and script tags, gets a note to ask for an export instead of guessing, plus the JSON it embeds in application/json script blocks); ACTION web source (Agent $srcMode, Invoke-WebFetch -Source) returns the page, script or JSON as sent with Get-SourceAddresses (script/link addresses, quoted data addresses in code) first. Executor Get-WebDownloadCommand refuses a run command, or a project script it runs, that reads a public page itself (curl, wget, Invoke-WebRequest/RestMethod, WebClient, urllib, requests, fetch; local addresses and binary downloads excepted) and points to the web action; Get-CmdQuoteBreak refuses a command whose \" cmd.exe reads as the end of a quoted argument with a ` |
lib/Retention.psm1 |
Settings > Retention (retention.* in harness.json): Invoke-ProjectRetention (.streamhub/History per item, .streamhub/Evidence, .streamhub/Reviews as .md+.json pairs, .streamhub/Screenshots (page-<stamp>-* png + .layout.json + -changes.png as one), saved charts in Runbooks/Exports/ per name (NAME-chart-<stamp>[-N].png, one answer's charts as one item), undo backups with the newest always kept) at project open (Set-Project) and after each task (worker loop); Invoke-StateRetention at server start for every project state folder (undo backups, chat-history.jsonl lines). Count and days per item, 0 = no limit; never touches Source/, Logs/ or other files. Nothing expired reaches $apply as $null, which @() makes one item in 5.1: only items with paths count. Tests: tests/Retention.Tests.ps1. |
lib/Chain.psm1 |
Chains (Runbooks/NAME.chain.md, template templates/chains/blank.chain.md): Read-Chain / Read-ChainSteps (numbered lines runbook: NAME [with PATH, PATH], fetch: NAME, script: Scripts/NAME.ps1 WORDS; header title, stopOnError), Test-ChainSteps (unknown runbooks/fetches, scripts outside Scripts/ or of another type), Resolve-ChainScript (command per type; Test-ScriptArgs: plain words only), approvals by content in <project>\.streamhub\approved-scripts.json (Test-ScriptApproved, Add-ApprovedScript), New-ChainInputBlock (a runbook step's "with" files as data). A script also runs on its own: Invoke-ScriptJob (task kind script, API /api/scripts/run, schedules kind script, UI scripts-panel.tsx in Automation) through Invoke-ChainScript with $Chain.direct; the run card's preview is @{ path; exists; old = $null; new = script text } (the UI's asPreview also reads older plain-text previews). Set-ChainSteps (add a runbook or script step at the end, remove, move up/down; renumbers the step lines outside HTML comments, Get-ChainStepLines; checks a new step first), API /api/chains/steps. The job Invoke-ChainJob (Agent) runs the steps in order (pause pacing.chainStepSec between Copilot steps; a runbook/fetch step whose errors are all no-answer, Test-NoAnswerError, is run again in a new chat after each wait in pacing.chainRetrySec, Get-ChainRetryWaits, Wait-ChainPause stops on Stop) ($State.InChain keeps Busy/Stop across the runbook and fetch jobs); Invoke-ChainScript applies Test-DeleteScope, Get-CommandRisk on the command and the script text (person-only approval every time, refused when headless), setting chainScripts (approve-once / always-ask / off), one change set per chain, Restore-SourceData. API /api/chains, UI chains-panel.tsx, schedules kind chain. Shared: Get-Chains, Runbook Get-Runbooks and Fetch Get-FetchPrompts also list shared/Runbooks/ from the library beside the projects (shared = $true; a project file of the same name wins; outputs go to the project's Runbooks/Exports/; the jobs read them through Resolve-ProjectPath), and Resolve-ChainScript / Get-ProjectScripts take shared/Scripts/ scripts (run by their full path, approval by content per project as always). Tests: tests/Chain.Tests.ps1, tests/Shared.Tests.ps1. |
lib/Relink.psm1 |
After Move-ProjectLayout -Moves (every move as from/to/folder, case-only renames included; Get-DiskName reads the case on disk), Update-MovedReferences rewrites paths in the project's text files that point into a moved file or folder: Update-TextReferences finds path tokens (not URLs or longer names), resolves them like the code would (file folder, then project root; a leading / from the root), maps them with Get-MovedTarget and writes the new path in the same style (Get-RelativeRef). Skips Source/, .streamhub/, build output and Runbooks/Exports/; only to places that exist. Earlier versions go to relink-<time> in the project's state folder (not an Undo step: undoing only the links would break them). Called in Set-Project before the import index runs. Tests: tests/Relink.Tests.ps1 (Pester Should Match ignores case: use MatchExactly for case). |
lib/EdgeCache.psm1 |
Settings > Privacy > Clear Edge's cache: only cache folders of StreamHub's Edge profile (Cache, Code Cache, GPU/shader/Dawn caches, service worker caches, component_crx_cache), never cookies, storage, sessions or extensions. Request-EdgeCacheClear clears the web cache through Edge (Network.clearBrowserCache) and the folders when no Edge uses the profile (Test-EdgeProfileInUse); otherwise edge-cache-clear.flag makes Start-CdpEdge remove them before it starts Edge. API /api/edge-cache (clear only from the app page's Origin). Tests: tests/EdgeCache.Tests.ps1. |
lib/DataMirror.psm1 |
Data copies: a JS file that is only window.NAME = JSON; (or var/let/const; strict JSON, Test-StrictJsonText) paired with a JSON file (Generated-from header, else same name: same folder, data/, Runbooks/Exports/). Update-DataMirrors rewrites a copy that differs or has no header from its JSON (one change set), reports missing/invalid sources (Format-DataMirrorNotes); Sync-DataMirrors (Agent) after every task, at project open (by the worker at its next idle moment, $State.OpenSync set by Set-Project, so the open answers at once) and at Re-index; data files are converted only with -Import (at open only for a live data file the person chose) (/api/issues/reindex, not while $State.Busy). Assert-Writable refuses writes to a JS file with the Generated-from first line; Get-ReviewFiles skips data copies; the file:// fix text and rules/web.md say the copy follows the JSON. Setting dataCopies (toggle, default on; Test-DataCopiesOn in Config reads it from disk, so every runspace sees a change) turns sync, refusal, review skip and the Copilot wording off. Every change StreamHub makes to a project itself (data copies, layout moves, relinked files, Source/ put back) is an action card via Add-OwnChangeEvent (Agent; by = 'streamhub', verbs write/edit/move/restore; the summary is the card's own line, -Output holds the details, and the UI drops the summary line when the output says the same); at project open these come after Restore-ChatHistory. Tests: tests/DataMirror.Tests.ps1. |
lib/ChatScope.psm1 |
When the work moves to another part of the project: Get-FileArea (folder, two levels under src/app/lib/packages/...; (root)), Get-NamedFiles (path, name, name without extension 4+ chars and not generic, folder with slash), Get-RelatedFiles (one step through the import index), Test-ChatSwitch (switch when the chat worked on files, the message names files, none is related to or in the chat's areas, and it is not a follow-up: $script:Continue). Agent: $State.ChatFiles (reads, changes and named files; reset in Start-NewChat), Update-ChatScope before Start-NewChat in Invoke-AgentTurn (not for plain chat or the first message), Add-ChatScopeFiles; setting newChatOnSwitch. Tests: tests/ChatScope.Tests.ps1. |
lib/TestRunner.psm1 |
The project's own tests after a change, when AGENTS.md has no verify:: Find-TestCommand (Pester *.Tests.ps1, related by name or mention via Get-RelatedTests, else all; pytest or unittest discover when a real python is on PATH, Get-RealCommand skips the WindowsApps stub; npm test --silent with a real test script, npm and node_modules). Agent runs it in the done step like verify: (setting autoTests). Tests: tests/TestRunner.Tests.ps1. |
lib/Hooks.psm1 |
.streamhub/hooks.json (beforeAction/afterAction around every agent action, matched by action name or category (read, file-change, command, network, automation); bounded by hookTimeoutSec/hookMaxOutputChars; post-action failures are warnings; afterEdit with match and {file}, beforeDone, afterTask; _-keys are comments): Read-Hooks, Test-HookMatch, Get-HookCommand ({file} only for a name that passes Test-HookFileName: no $, backtick, quotes, `;& |
lib/TaskNotes.psm1 |
What Copilot writes down for the helper program to keep and check (fixed rules on its text, no extra message to set up): Done when (a build or change request, Prompts Test-BuildRequest, not issue fixes, plan mode, MCP review-by-caller or a Continue: Agent adds Get-DoneWhenAsk to the first message; Read-DoneWhen takes 2-6 '- ' lines under a 'Done when:' heading from a reply (Read-NoteBlock, outside code blocks); at done the question New-AcceptanceAsk rides with the review message or the screenshot message, else goes once on its own; Read-AcceptanceAnswers matches 'MET:' / 'NOT MET: ... - why' lines to the checks (by text, else in order); a status line with what is not met; the task report gets a 'Done when' section, Format-AcceptanceReport). Data notes (converted data files without notes or whose import hash changed, Get-DataNotesNeeded from the DataImport manifest: Get-DataNotesAsk with the first message of a non-chat turn; the 'Data notes:' block of a reply is saved per file hash in .streamhub/data-notes.json, Save-DataNotes; Format-DataNotes goes into every project context after the data lines). Plan coverage before done (Get-PlanFiles: the files an approved plan or Copilot's steps name to create or change, not ones to remove or temporary ones; Find-StubCode: functions a change left empty or only throwing "not implemented" or passing, new TODO/FIXME/placeholder notes, a new code file of three lines or fewer; Get-PlanCoverage / Format-PlanCoverage, Agent at most twice per task before the unloaded-files check; what is left goes into the task report). To-do and plan lists without approval (Test-TaskListFile: TODO.md, TO-DO.md, TASKS.md, PLAN.md in any folder, not .streamhub/PLAN.md): Agent Invoke-AgentAction writes and edits them without an approval card in every mode but Plan only, and the much-shorter write check skips them. Open to-do items at done (Get-OpenTodoItems: TODO/TASKS/PLAN.md at the root or in docs/, not .streamhub/; after $doneFinished, not for issue fixes, plan mode, review-by-caller or headless): a task-unfinished card with the items and Get-OpenTodoContinue as its Continue text (sent as a coding task); Done when checks answered NOT MET get the same card with Get-UnmetChecksContinue. Tie-breakers, where a fixed rule cannot tell from the words: look or feature (Prompts Test-LookUnclear: look words with a build word but no look verb, or a look request that also adds something; with the kit on and in the project the first message gets Get-RequestTypeAsk and the look rules, and Read-RequestType 'look'/'both' sets $State.UserLook, 'feature' clears it); build at once or plan first (ProjectSetup Test-BigUnclear: over half a big request's length or three listed asks, only in a project whose bigtask answer is clarify or plan; Agent Use-BigTaskPreference (a plan-first request gets its own PLAN.md section, planId; Server's plan-first route, the setup card's answer, makes the section too; a task with planBuild, which Server sets on every Approve and build, or the plan card's build text is never planned again) sets $State.ScopeAsk, the first message gets Get-ScopeAsk, and a reply with 'Scope: plan' and no write/edit/run ends the turn ($State.ScopeUpgrade) and queues the request again with bigByCopilot, which takes the project's clarify or plan path). Tests: tests/TaskNotes.Tests.ps1. |
lib/Handoff.psm1 |
The chat rollover (Agent Invoke-RolloverIfNeeded $State @{ changed; doneWhen; acceptance }): Get-HandoffAsk (a handoff under fixed headings: Objective, Requirements, Decisions, Done, Active, Blocked, Next steps, Relevant files), Get-HandoffMissing (a fixed rule on heading lines; three or more missing: Get-HandoffRetry once, within rolloverMargin), Format-HandoffFacts (what the helper program kept itself, added after Copilot's handoff: the request, plan mode, the files the turn's checkpoint changed, open to-do items, the Done when checks with MET / NOT MET / not answered, an active fix) and Get-HandoffOutlines (outlines of the changed code files). Tests: tests/RoundSavers.Tests.ps1. |
lib/LoopGuard.psm1 |
Going in circles (Agent round loop, after the explore check): Add-LoopFileVersion (fingerprints per file, seeded with the one taken at Copilot's read; a file back to an earlier version = a change undone or made again after a rollback), Get-RoundKey / Add-LoopRound (the last four rounds alternate A, B, A, B), Get-LoopNote the first time as a result block, the second time the message stops (code CIRCLE-LOOP). Tests: tests/RoundSavers.Tests.ps1. |
lib/RepoMap.psm1 |
Code map for the project context (Get-ProjectContext): Get-RepoMapRanking (named files, chat files, import users), Get-RepoMap (outline names with line numbers, 400 characters per file, setting repoMapChars). Tests: tests/RepoMap.Tests.ps1. |
lib/CheckPolicy.psm1 |
Handling of the round's file-check findings: Get-CheckLevel (error, or warning for $script:WarningPatterns and sources smell/quality/env), Get-CheckKey (path + message without numbers), ConvertTo-CheckFinding, Format-CheckResults (errors first, then likely mistakes, how to dispute), Get-Enforcement (setting enforcement light/standard/strict: error tries, warnings sent/blocking, test and hook tries), the false-alarm log .streamhub/check-disputes.json (Add-CheckDispute, Read-CheckDisputes; in Export diagnostics). Agent: findings with sources in the round check, left out when disputed this task ($State.CheckDisputes, action dispute), ignored (Test-CheckIgnored/Set-IgnoredCheck in Issues, API /api/checks/ignore, UI checks-card.tsx) or a warning already said; event checks (findings). Switches checks.generated, checks.tools, checks.powershell7, checks.quality via Test-CheckSwitch (Config, read from disk). Release gate in tools/build-release.ps1 (Pester + tools/scan-repo.ps1). Tests: tests/CheckPolicy.Tests.ps1. |
lib/AutoFix.psm1 |
Repair-MechanicalIssues (a one-file page's filled blocks are set aside as /*streamhub-block-N*/ and put back unchanged, so megabytes of inline data are never walked; Repair-MechanicalCore does the work; broken encoding sequences anywhere in a file but the one-file blocks, <meta charset="utf-8"> into a page without one; curly quotes, NBSP/zero-width, HTML entities in code via Get-CodeMask, but no quote or NBSP fixes in JSX/TSX/Vue/Svelte, whose text the mask does not know; // in CSS, not after ( (url(//host/x)); batch %i; mixed endings), applied in the round after the afterEdit hooks by enforcement (autoFix silent/tell/off), not applied when Get-NewFileIssues says it adds an error; raw mixed endings rewritten with the majority. Rollback (Agent): $roundSnap of files with errors from the last round ($fixTargets, seeded by $State.IssueFix), restored when the change adds an error, with a wider Invoke-ReadAction (line +-40, whole blocks) in the results; $rollbacks at most 2 per file. Tests: tests/AutoFix.Tests.ps1 (Copilot mocked). |
lib/Office.psm1 |
Office files without Office: ConvertFrom-OfficeFile (.docx via styles.xml names and numbering.xml, .pptx in sldIdLst order with notes through the slide rels, .xlsx with shared strings; Markdown out), ConvertTo-XlsxBytes / Write-XlsxFile (a workbook from CSV text: Read-CsvTable, ### NAME lines start sheets (Split-XlsxSheets), numbers, ISO dates and true/false typed, a bold frozen header row with filters; Test-OwnXlsx: only workbooks written here are rewritten; Executor Write-TextFile writes .xlsx through it), ConvertTo-DocxBytes / Write-DocxFile (a minimal package: styles, numbering per numbered list, tables with a header row, docProps/app.xml Application StreamHub), Test-OwnDocx, Get-OfficeWriteRefusal (only .docx; an existing one only when Test-OwnDocx). Executor: Read-TextFile returns Encoding office for .docx/.pptx/.xlsx, Write-TextFile writes a .docx path through Write-DocxFile, Assert-Writable applies the refusal, Test-TextReadable for undo/change-set texts, Invoke-ReadAction adds the label and sends old .doc/.ppt/.xls and PDFs (Get-OfficeKind old / pdf) to Copilot (Agent read result attach -> $fileAttach on the next message, at most 50 MB). Prompt module rules:office ($script:OfficePattern, trait office). Tests: tests/Office.Tests.ps1 (also XML/YAML outlines). |
lib/SecretFiles.psm1 |
Secret files: Get-SecretFileKind (keyvalue: .env*, .npmrc, .pypirc, .netrc, .git-credentials...; json: secrets.json, credentials.json, client_secret*.json; key: .pem/.key/.pfx/.p12/.jks/.ppk/.kdbx, id_*; example/sample/template/dist files are not secret), Hide-SecretValues (values as <hidden>; // lines are .npmrc keys, not comments), Get-SecretValues + Hide-KnownSecrets (Executor Hide-ProjectSecrets on every Invoke-RunAction output), Get-SecretWriteRefusal (in Assert-Writable). Executor Invoke-ReadAction and Invoke-GrepAction mask; Agent Resolve-AgentFiles refuses them; Get-ReviewFiles skips them. Images (Get-OfficeKind image) take the attachment route of PDFs. Tests: tests/SecretFiles.Tests.ps1 (also the C#/Java/Kotlin/Go/SQL/JSON/INI/TOML outlines in Get-FileOutline). |
lib/UiKit.psm1 |
The UI kit (templates/ui-kit/: tokens.css, kit.css, kit.js (behaviour by data-kit-*, DOM events kit:*), kit-examples.html, kit-charts.js (`KitCharts.bar |
Truncated - read the full file at https://github.com/jgt87/CCBridge/blob/c70500b5049ad631946c99b1efc98fcdb9aa8166/AGENTS.md.
