@yemi33/minions 0.1.2370 → 0.1.2371
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/minions.js +38 -11
- package/dashboard/js/render-other.js +2 -30
- package/dashboard/js/render-work-items.js +0 -34
- package/dashboard/js/settings.js +12 -27
- package/dashboard/pages/tools.html +1 -1
- package/dashboard.js +80 -257
- package/docs/README.md +2 -3
- package/docs/architecture.excalidraw +2 -2
- package/docs/auto-discovery.md +3 -3
- package/docs/completion-reports.md +0 -121
- package/docs/copilot-cli-schema.md +9 -17
- package/docs/deprecated.json +0 -51
- package/docs/design-state-storage.md +4 -4
- package/docs/harness-propagation.md +33 -263
- package/docs/human-vs-automated.md +1 -1
- package/docs/named-agents.md +0 -2
- package/docs/plan-lifecycle.md +5 -6
- package/docs/qa-runbook-lifecycle.md +10 -25
- package/docs/shared-lifecycle-module-map.md +2 -10
- package/engine/ado-comment.js +5 -11
- package/engine/ado.js +10 -5
- package/engine/cleanup.js +16 -36
- package/engine/cli.js +13 -175
- package/engine/comment-format.js +8 -182
- package/engine/db/index.js +60 -10
- package/engine/db/migrations/018-sql-only-cutover.js +56 -0
- package/engine/dispatch-store.js +0 -114
- package/engine/dispatch.js +1 -6
- package/engine/gh-comment.js +2 -10
- package/engine/github.js +8 -6
- package/engine/lifecycle.js +58 -173
- package/engine/llm.js +9 -11
- package/engine/managed-spawn.js +1 -6
- package/engine/memory-store.js +8 -6
- package/engine/metrics-store.js +4 -128
- package/engine/pipeline.js +4 -7
- package/engine/playbook.js +8 -196
- package/engine/prd-store.js +115 -141
- package/engine/preflight.js +8 -55
- package/engine/projects.js +34 -41
- package/engine/pull-requests-store.js +9 -234
- package/engine/qa-runs.js +4 -15
- package/engine/qa-sessions.js +5 -17
- package/engine/queries.js +23 -103
- package/engine/routing.js +1 -1
- package/engine/runtimes/claude.js +1 -2
- package/engine/runtimes/codex.js +0 -2
- package/engine/runtimes/copilot.js +1 -4
- package/engine/shared-branch-pr-reconcile.js +2 -5
- package/engine/shared.js +174 -533
- package/engine/small-state-store.js +12 -647
- package/engine/spawn-agent.js +5 -96
- package/engine/state-operations.js +166 -0
- package/engine/supervisor.js +0 -1
- package/engine/watch-actions.js +2 -3
- package/engine/watches-store.js +2 -128
- package/engine/work-items-store.js +3 -254
- package/engine.js +102 -334
- package/package.json +2 -2
- package/playbooks/build-fix-complex.md +0 -4
- package/playbooks/fix.md +0 -4
- package/playbooks/implement-shared.md +0 -4
- package/playbooks/implement.md +0 -4
- package/playbooks/plan-to-prd.md +16 -11
- package/playbooks/plan.md +0 -4
- package/playbooks/review.md +0 -6
- package/playbooks/shared-rules.md +0 -65
- package/docs/harness-transparency.md +0 -199
- package/docs/project-skills.md +0 -193
- package/engine/discover-project-skills.js +0 -673
- package/engine/playbook-intents.js +0 -76
package/docs/named-agents.md
CHANGED
|
@@ -43,7 +43,6 @@ The full per-agent config shape supported under `config.agents.<id>`:
|
|
|
43
43
|
| `model` | string | Per-agent model override (omitted → runtime picks its own default). |
|
|
44
44
|
| `maxBudgetUsd` | number | Per-spawn USD cap. **`0` is a valid cap** (read-only / dry-run agents), not "unlimited". |
|
|
45
45
|
| `bareMode` | boolean | Run Claude in `--bare` mode (Claude-runtime knob). |
|
|
46
|
-
| `hermeticHarness` | boolean | Strip user-scope skill/command/MCP roots from the worktree; `--add-dir` resolves to `[minionsDir]` only. |
|
|
47
46
|
| `workspace_manifest` | object | Optional permission scoping — see below. |
|
|
48
47
|
|
|
49
48
|
### Three-tier resolution chain
|
|
@@ -57,7 +56,6 @@ The resolver helpers live in [`engine/shared.js`](../engine/shared.js):
|
|
|
57
56
|
| `resolveAgentModel(agent, engine)` | `agent.model` → `engine.defaultModel` → `undefined` (adapter omits `--model`, CLI uses its own default). |
|
|
58
57
|
| `resolveAgentMaxBudget(agent, engine)` | `agent.maxBudgetUsd` → `engine.maxBudgetUsd` → `undefined` (no cap). Nullish-coalesced so `0` is honored. |
|
|
59
58
|
| `resolveAgentBareMode(agent, engine)` | `agent.bareMode` → `engine.claudeBareMode` → `false`. Strict null check so a per-agent `false` overrides an engine `true`. |
|
|
60
|
-
| `resolveAgentHermeticHarness(agent, engine)` | `agent.hermeticHarness` → `engine.hermeticHarness` → `false`. Same strict-override semantics. |
|
|
61
59
|
|
|
62
60
|
Empty string / `null` / `undefined` all count as "unset" for the string knobs, so an agent
|
|
63
61
|
inherits the fleet default rather than blanking the value.
|
package/docs/plan-lifecycle.md
CHANGED
|
@@ -116,18 +116,18 @@ The verify agent and the user can then build and run from these paths.
|
|
|
116
116
|
|
|
117
117
|
## Manual Archive Lifecycle
|
|
118
118
|
|
|
119
|
-
Archiving is a deliberate dashboard action, not a post-verify side effect. When a PRD completes, `checkPlanCompletion()` writes the completion summary, persists `status: "completed"` plus `_completionNotified
|
|
119
|
+
Archiving is a deliberate dashboard action, not a post-verify side effect. When a PRD completes, `checkPlanCompletion()` writes the completion summary, persists `status: "completed"` plus `_completionNotified` in SQL, and creates the verify work item. The PRD remains active and its source plan remains in `plans/` so humans can review the guide, inspect the final state, resume stale work, or reopen items before hiding the plan from active views.
|
|
120
120
|
|
|
121
121
|
The verify agent does not call `archivePlan()`. After the testing guide and any follow-up PR work are satisfactory, use the dashboard archive action, which calls `POST /api/plans/archive` with the active PRD or plan filename.
|
|
122
122
|
|
|
123
123
|
Manual archive behavior:
|
|
124
124
|
|
|
125
|
-
1.
|
|
125
|
+
1. The dashboard updates the SQL PRD row with `archived: true`, `status: "archived"`, and `archivedAt`.
|
|
126
126
|
2. If the PRD has `source_plan`, the matching `plans/<source>.md` is moved to `plans/archive/<source>.md`.
|
|
127
127
|
3. Pending or queued work items linked to the archived PRD are cancelled with `_cancelledBy: "plan-archived"`; completed work items remain as history.
|
|
128
128
|
4. Plan worktrees are cleaned up by the archive handler/lifecycle cleanup path.
|
|
129
129
|
|
|
130
|
-
Use unarchive only when the archived PRD/plan should reappear in active views. Reopening completed work should happen before archive, or after unarchive, so the materializer can see the active PRD
|
|
130
|
+
Use unarchive only when the archived PRD/plan should reappear in active views. Reopening completed work should happen before archive, or after unarchive, so the materializer can see the active SQL PRD.
|
|
131
131
|
|
|
132
132
|
## Human Feedback on PRs
|
|
133
133
|
|
|
@@ -151,10 +151,9 @@ After PRs are created, humans can leave comments containing `@minions` to trigge
|
|
|
151
151
|
| File | Purpose |
|
|
152
152
|
|------|---------|
|
|
153
153
|
| `plans/*.md` | Source plans awaiting review or linked to active PRDs |
|
|
154
|
-
| `
|
|
154
|
+
| SQL `prds` + `prd_items` | Active and archived PRDs with materializable items |
|
|
155
155
|
| `plans/archive/` | Manually archived plan Markdown files |
|
|
156
|
-
| `prd
|
|
157
|
-
| `prd/archive/` | Legacy: PRDs archived before Phase 10 (still scanned for filename-collision detection) |
|
|
156
|
+
| `prd/` | Legacy upgrade input only; migration 018 imports and retires these files |
|
|
158
157
|
| `playbooks/verify.md` | Verification task playbook |
|
|
159
158
|
| `playbooks/implement.md` | Implementation playbook |
|
|
160
159
|
| `playbooks/plan-to-prd.md` | Plan → PRD conversion playbook |
|
|
@@ -17,7 +17,7 @@ engine-assigned `id`). `target` is a name from `/api/managed-processes` or
|
|
|
17
17
|
|
|
18
18
|
## Run-record path
|
|
19
19
|
|
|
20
|
-
`
|
|
20
|
+
The SQL `qa_runs` table stores newest-first, capped run records. Each run:
|
|
21
21
|
`{ id, runbookId, runbookName, target, status, startedAt, completedAt, workItemId, agentId, artifacts }`.
|
|
22
22
|
`status` ∈ `pending|dispatched|running|passed|failed|error`. Created by
|
|
23
23
|
`POST /api/qa/runbooks/run` (`{ id }`), which dispatches a `qa-validate`
|
|
@@ -27,29 +27,14 @@ and clears the interval on page navigation via the `switchPage` wrapper in
|
|
|
27
27
|
`dashboard/js/qa.js` (matches `_stopPlanPoll`/`_stopMeetingPoll` pattern in
|
|
28
28
|
`dashboard/js/state.js`).
|
|
29
29
|
|
|
30
|
-
### Persistence
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
`
|
|
37
|
-
(`
|
|
38
|
-
on the sidecar path so cross-process writers serialize. Reads go through
|
|
39
|
-
`small-state-store.readQaRuns` / `readQaSessions`, which compare the SQL
|
|
40
|
-
content-hash to the JSON sidecar and rehydrate SQL from JSON when an
|
|
41
|
-
external editor (operator hand-edit, manual rollback) has diverged.
|
|
42
|
-
|
|
43
|
-
The JSON mirror is gated by `engine.qaDualWriteJson` (default true). Once
|
|
44
|
-
operators trust SQL as the source of truth and have migrated external
|
|
45
|
-
tooling off the JSON files, flip it false via Dashboard → Settings →
|
|
46
|
-
Advanced → "QA: dual-write JSON sidecars" to halve write I/O. With the
|
|
47
|
-
toggle off, `qa-runs.json` / `qa-sessions.json` stop receiving updates
|
|
48
|
-
(stale snapshot at the moment of the flip), so don't disable until any
|
|
49
|
-
external readers have moved to the SQL tables or the dashboard `/api/qa/*`
|
|
50
|
-
endpoints. The qa-runs cap (`QA_RUNS_MAX_RECORDS=2000`) and qa-sessions
|
|
51
|
-
cap (`QA_SESSIONS_MAX_RECORDS=500`) still apply, enforced in-memory at
|
|
52
|
-
createRun/createSession time.
|
|
30
|
+
### Persistence
|
|
31
|
+
|
|
32
|
+
SQLite (`engine/state.db`, tables `qa_runs` and `qa_sessions`) is the sole
|
|
33
|
+
source of truth. Every mutation goes through `shared.mutateQaRuns` or
|
|
34
|
+
`shared.mutateQaSessions` inside a transaction. Legacy JSON is imported only
|
|
35
|
+
during upgrade or through the explicit offline recovery command. The qa-runs
|
|
36
|
+
cap (`QA_RUNS_MAX_RECORDS=2000`) and qa-sessions cap
|
|
37
|
+
(`QA_SESSIONS_MAX_RECORDS=500`) remain enforced at creation time.
|
|
53
38
|
|
|
54
39
|
### Deleting runs
|
|
55
40
|
|
|
@@ -253,7 +238,7 @@ errors are mapped to HTTP via `_qaSessionsErrorToStatus`:
|
|
|
253
238
|
DRAFT agent. Path is sandboxed by `_isSafeSessionId` (kebab-case ≤ 64
|
|
254
239
|
chars, no `..`, no separators) before every read/write that maps id →
|
|
255
240
|
path. Gitignored.
|
|
256
|
-
- **Managed-spawn**:
|
|
241
|
+
- **Managed-spawn**: SQL managed-process entry with
|
|
257
242
|
`name='qa-session-<sessionId>'`. Cleaned up by `/kill` or by the
|
|
258
243
|
session's `cancelSession` if the operator opts for the cancel-without-
|
|
259
244
|
spawn-kill path.
|
|
@@ -105,7 +105,6 @@ because either is a bug:
|
|
|
105
105
|
|---|---|---|
|
|
106
106
|
| minions root pointer | `readMinionsRootPointer`, `saveMinionsRootPointer` | shared.js |
|
|
107
107
|
| dashboard port file | `readDashboardPortFile`, `writeDashboardPortFile` | shared.js |
|
|
108
|
-
| agent Copilot home dir | `resolveAgentCopilotHome`, `ensureAgentCopilotHome` | shared.js |
|
|
109
108
|
| canonical PR id | `parseCanonicalPrId`, `getCanonicalPrId` | shared.js |
|
|
110
109
|
| PR record | `findPrRecord`, `normalizePrRecord` | shared.js |
|
|
111
110
|
| worktree owner marker | `writeWorktreeOwnerMarker`, `hasWorktreeOwnerMarker` | shared.js |
|
|
@@ -214,7 +213,7 @@ means zero such files were found), **Proposed target module**.
|
|
|
214
213
|
| `redactPromptDescription` | 447-458 | Redactor applied to WI description text before it is injected into an agent // prompt. Applies the standard log redactor (_redactString) first, then catches // any remaining Authorization: Bearer header — including alrea | yes | `engine.js` | engine/shared.js (keep) |
|
|
215
214
|
| `log` | 459-531 | Log. | yes | `engine/abandoned-pr-reconciliation.js`, `engine/ado-git-auth.js`, `engine/ado-status.js`, `engine/ado.js`, `engine/cc-worker-pool.js`, `engine/check-status.js` +72 more | engine/shared.js (keep) — logging/timestamp primitives |
|
|
216
215
|
| `_currentLogPath` | 532-547 | Resolve the log file path at write time, not at module load time. `LOG_PATH` is captured eagerly when shared.js first loads. In production this is fine — there's a single shared.js instance per process. In tests the requ | no | _internal-only (no callers found outside shared.js)_ | engine/shared.js (keep) |
|
|
217
|
-
| `_currentLogDbPath` | 548-559 | Resolve the SQL state.db path at write time
|
|
216
|
+
| `_currentLogDbPath` | 548-559 | Resolve the SQL state.db path at write time with test/home isolation. | no | _internal-only (no callers found outside shared.js)_ | engine/shared.js (keep) |
|
|
218
217
|
| `resolveEngineCacheDir` | 560-593 | MINIONS_TEST_DIR-aware engine dir for cache files (runtime caps, models, // spawn-debug.log). Returns `<MINIONS_TEST_DIR>/engine` under tests; otherwise // the caller's source-adjacent fallback (typically `__dirname`-der | yes | `engine/runtimes/claude.js`, `engine/runtimes/codex.js`, `engine/runtimes/copilot.js` | engine/shared.js (keep) |
|
|
219
218
|
| `openUrlInBrowser` | 594-645 | Cross-platform URL opener. Uses execSync so failures fall through the // try/catch and the caller sees them. Dashboard self-open and `minions dash` // / `minions restart` post-health open all funnel through here. // // E | yes | `test/unit/dashboard-port-resolution.test.js`, `dashboard.js`, `engine.js` | engine/shared.js (keep) |
|
|
220
219
|
| `_bumpBrowserOpenMetric` | 646-662 | Bump per-reason counter under metrics._engine.browserOpens. Best-effort — // metrics.json writes must never throw out of openUrlInBrowser. Schema: // _engine.browserOpens.<reason> = { count, suppressedCount, lastAt } | no | _internal-only (no callers found outside shared.js)_ | engine/shared.js (keep) |
|
|
@@ -324,7 +323,6 @@ means zero such files were found), **Proposed target module**.
|
|
|
324
323
|
| `resolveAgentBareMode` | 3974-3991 | Resolve whether this agent should run in Claude `--bare` mode. Priority: 1. `agent.bareMode` — per-agent override (boolean) 2. `engine.claudeBareMode` — fleet default 3. `false` — hardcoded fallback Strict undefined/null | yes | `test/unit.test.js`, `engine.js` | engine/shared.js (keep) |
|
|
325
324
|
| `_normalizeMcpServerList` | 3992-4005 | P-mcp-storm — Resolve the list of MCP server names to disable for spawned // Copilot agents. The engine FILTERS these out of the agent's seeded // COPILOT_HOME mcp-config (engine.js `_applyAgentCopilotHome`) — a server a | no | _internal-only (no callers found outside shared.js)_ | engine/shared.js (keep) |
|
|
326
325
|
| `resolveCopilotAgentDisabledMcpServers` | 4006-4030 | Resolve copilot agent disabled mcp servers. | yes | `test/unit/agent-copilot-home.test.js` | engine/shared.js (keep) |
|
|
327
|
-
| `resolveAgentHermeticHarness` | 4031-4062 | P-49e1c8b7 — Resolve whether this agent should run with a hermetic harness. Priority (mirrors `resolveAgentBareMode`): 1. `agent.hermeticHarness` — per-agent override (boolean) 2. `engine.hermeticHarness` — fleet default | yes | `engine/spawn-agent.js`, `engine.js` | engine/shared.js (keep) |
|
|
328
326
|
| `applyLegacyCcModelMigration` | 4063-4082 | If `config.engine.ccModel` is set but `config.engine.defaultModel` is unset, mutate the in-memory `config.engine` so `defaultModel` mirrors `ccModel` and log a one-time deprecation notice. Does NOT write to disk. Returns | yes | `engine/cli.js`, `test/unit.test.js` | engine/shared.js (keep) |
|
|
329
327
|
| `_resetLegacyCcModelMigrationFlag` | 4083-4113 | Test helper: reset the dedup flag so repeated tests can re-trigger the log. | yes | _internal-only (no callers found outside shared.js)_ | engine/shared.js (keep) |
|
|
330
328
|
| `runtimeConfigWarnings` | 4114-4194 | Inspect runtime fleet config and return warning entries for misconfiguration. Warnings emitted: - Unknown CLI: any `cli` value (per-agent, ccCli, defaultCli) not in `registeredRuntimes`. Each unknown value produces one e | yes | `engine/preflight.js` | engine/process-utils.js (new) — process spawn/pid/tmp-dir lifecycle helpers |
|
|
@@ -337,13 +335,11 @@ means zero such files were found), **Proposed target module**.
|
|
|
337
335
|
| `_qaDualWriteEnabled` | 4673-4681 | Phase 8 — QA state mutators. Both qa-runs and qa-sessions are top-level JSON arrays (mutator receives the array; mutates in place or returns a replacement). The store diffs by `id`. Falls back to mutateJsonFileLocked on | no | _internal-only (no callers found outside shared.js)_ | engine/shared.js (keep) |
|
|
338
336
|
| `_qaMutator` | 4682-4720 | Qa mutator. | no | _internal-only (no callers found outside shared.js)_ | engine/shared.js (keep) |
|
|
339
337
|
| `mutateWatches` | 4721-4741 | Route a watches mutation through the SQL store. Same shape as mutateWorkItems / mutatePullRequests: mutator receives the watches array, mutates in place or returns a replacement, and the store diffs by id. SQL-canonical | yes | `engine/watches.js` | engine/shared.js (keep) |
|
|
340
|
-
| `mutateMetrics` | 4742-4763 | Route a metrics mutation through the SQL store
|
|
338
|
+
| `mutateMetrics` | 4742-4763 | Route a metrics mutation through the SQL store. | yes | `engine/cleanup.js`, `engine/llm.js`, `test/unit/auto-recovery.test.js`, `engine.js` | engine/shared.js (keep) |
|
|
341
339
|
| `trackReviewMetric` | 4764-4784 | Update per-agent review metrics (prsApproved/prsRejected). Only writes for configured agents. | yes | `engine/ado.js`, `engine/github.js`, `engine/queries.js`, `test/unit.test.js` | engine/shared.js (keep) |
|
|
342
340
|
| `queuePlanToPrd` | 4785-4822 | Queue a plan-to-prd work item with dedup check inside lock. Returns { queued, alreadyQueued, id, item } so callers (e.g. POST /api/plans/execute) can surface the new or existing work item id back to the dashboard. Return | yes | `test/unit.test.js`, `dashboard.js` | engine/shared.js (keep) |
|
|
343
341
|
| `extractPlanDeclaredProject` | 4823-4845 | Extract plan declared project. | yes | `dashboard.js`, `engine.js` | engine/shared.js (keep) |
|
|
344
342
|
| `extractPlanTargetProjects` | 4846-4974 | P-7a3f1c08: Parse a cross-repo plan's list of target projects. // Primary signal: `<!-- minions:targetProjects=a, b, c -->` HTML comment. // Fallback (human): ` Projects: a, b \| c` markdown line in the first 80 lines. // | yes | `dashboard.js`, `engine.js` | engine/shared.js (keep) |
|
|
345
|
-
| `normalizeHarnessUsed` | 4975-5027 | normalizeHarnessUsed(raw) — validate/clamp an untrusted harnessUsed-shaped // object into the canonical { skills, mcpServers, commands, docs } form. // Drops malformed entries (non-objects, or missing the kind's required | yes | _internal-only (no callers found outside shared.js)_ | engine/shared.js (keep) |
|
|
346
|
-
| `groundHarnessUsed` | 5028-5099 | groundHarnessUsed(harnessUsed, propagated) — Stage 2 grounding cross-check // (P-c6f5e8b3). Normalizes an agent's self-reported `harnessUsed` and annotates // every surviving entry with a strict-boolean `grounded` field | yes | `engine/cli.js` | engine/shared.js (keep) |
|
|
347
343
|
| `_bumpPruneDefaultClaudeStrip` | 5100-5114 | Bump rolling-daily strip counter for docs/deprecated.json#prune-default-claude-config // removal gate (>=30 consecutive days of zero strips). Best-effort; mirrors // engine/cleanup.js:1200-1213 _engine.legacyStatusMigrat | no | _internal-only (no callers found outside shared.js)_ | engine/shared.js (keep) |
|
|
348
344
|
| `pruneDefaultClaudeConfig` | 5115-5185 | Prune default claude config. | yes | `test/unit.test.js`, `dashboard.js`, `minions.js` | engine/shared.js (keep) |
|
|
349
345
|
| `_isStringArrayOrNull` | 5186-5202 | Is string array or null. | no | _internal-only (no callers found outside shared.js)_ | engine/shared.js (keep) |
|
|
@@ -398,14 +394,11 @@ means zero such files were found), **Proposed target module**.
|
|
|
398
394
|
| `parseWorktreePorcelain` | 6139-6169 | Parse `git worktree list --porcelain` output. Pure — callers run the subprocess (sync `execSilent` or async `execAsync`) and feed stdout in. Returns `[{path, head, branch, bare, detached, locked, prunable}]`. `branch` st | yes | `engine/cleanup.js`, `engine/worktree-gc.js`, `test/unit.test.js`, `engine.js` | engine/worktree-utils.js (new) — general worktree path/branch/owner-marker helpers (worktree-pool.js/worktree-gc.js already own pool sizing + GC sweep; this new module would take the shared low-level helpers those two currently import from shared.js) |
|
|
399
395
|
| `assertWorktreeOutsideProject` | 6170-6203 | Throws when `worktreePath` would land inside (or equal) `projectRoot`. Called by the engine spawn path before `git worktree add`, and by the cleanup sweep that audits already-registered worktrees per linked project. The | yes | `test/unit.test.js`, `dashboard.js`, `engine.js` | engine/worktree-utils.js (new) — general worktree path/branch/owner-marker helpers (worktree-pool.js/worktree-gc.js already own pool sizing + GC sweep; this new module would take the shared low-level helpers those two currently import from shared.js) |
|
|
400
396
|
| `resolveProjectRootDir` | 6204-6259 | Resolve the project root directory used as the parent for git worktree paths during dispatch. Centralizes the fallback that engine spawnAgent used to do inline (`project.localPath ? path.resolve(project.localPath) : path | yes | `engine/create-pr-worktree.js`, `test/unit.test.js`, `engine.js` | engine/shared.js (keep) |
|
|
401
|
-
| `resolveAgentCopilotHome` | 6260-6286 | Resolve a stable, repo-EXTERNAL COPILOT_HOME for spawned copilot agents. Copilot loads `$COPILOT_HOME/mcp-config.json` and SPAWNS every server in it on every process start. `--disable-mcp-server <name>` only hides a serv | yes | `test/unit/agent-copilot-home.test.js`, `engine.js` | engine/shared.js (keep) |
|
|
402
397
|
| `resolveAgentTempBaseDir` | 6287-6308 | Derive a scratch/temp base dir on the same volume agent work already lives on — the configured worktree location (engine.worktreeRoot, default `../worktrees`). Agents (copilot/node/git + JVM-based MCP servers) write heav | yes | `test/unit/agent-temp-dir.test.js`, `engine.js` | engine/shared.js (keep) |
|
|
403
|
-
| `ensureAgentCopilotHome` | 6309-6412 | Seed the agent COPILOT_HOME's mcp-config (copy of `~/.copilot/mcp-config.json` // MINUS `engine.copilotAgentDisabledMcpServers`) and return the home path. This // is the single source of truth for "what MCP servers may a | yes | `test/unit/agent-copilot-home.test.js`, `dashboard.js`, `engine.js` | engine/shared.js (keep) |
|
|
404
398
|
| `resolveSpawnPaths` | 6413-6505 | Resolve the agent's working directory and (when needed) the parent dir for git worktree placement. Decouples the two concerns that spawnAgent used to conflate (W-mp73x32w000l143d): 1. cwd — where the agent process actual | yes | `engine/live-checkout.js`, `test/unit.test.js`, `engine.js` | engine/shared.js (keep) |
|
|
405
399
|
| `_applyWorkdirOrThrow` | 6506-6518 | ── meta.workdir helpers (P-714ef144) // // Per-WI `meta.workdir` is a relative POSIX subpath under the spawn base // (project.localPath for read-only / live; worktree root for mutating). // When set, the engine lands the | no | _internal-only (no callers found outside shared.js)_ | engine/shared.js (keep) |
|
|
406
400
|
| `validateWorkItemWorkdir` | 6519-6561 | Validate work item workdir. | yes | `dashboard.js`, `engine.js` | engine/shared.js (keep) |
|
|
407
401
|
| `applyWorkdir` | 6562-6586 | Apply workdir. | yes | `engine.js` | engine/shared.js (keep) |
|
|
408
|
-
| `filterProjectHarnessDirsForWorkdir` | 6587-6650 | Filter project harness dirs for workdir. | yes | `engine.js` | engine/shared.js (keep) |
|
|
409
402
|
| `isAllowedOrigin` | 6651-6686 | Returns true if the origin-like header value (either an `Origin` header value such as `http://localhost:7331` or a full `Referer` URL) belongs to the local dashboard allowlist. Port-agnostic. Returns false for null/undef | yes | `test/unit.test.js`, `dashboard.js` | engine/shared.js (keep) |
|
|
410
403
|
| `isAllowedDashboardOrigin` | 6687-6705 | Returns true if the given `Origin` header value should receive an `Access-Control-Allow-Origin` echo on GET/HEAD responses. STRICTER than `isAllowedOrigin` — that helper is the mutating-request gate (allows any localhost | yes | `test/unit.test.js`, `dashboard.js` | engine/shared.js (keep) |
|
|
411
404
|
| `buildSecurityHeaders` | 6706-6721 | Returns the baseline set of security response headers to apply on every HTTP response from the dashboard. Values match OWASP defaults for a same-origin SPA served from 127.0.0.1. The HTML entry-point response intentional | yes | `test/unit.test.js`, `dashboard.js` | engine/shared.js (keep) |
|
|
@@ -609,7 +602,6 @@ Same columns as above, relative to `lifecycle.js`.
|
|
|
609
602
|
| `parseAgentOutput` | 4020-4024 | Agent Output Parsing | yes | `engine/timeout.js`, `test/unit.test.js`, `engine.js` | engine/lifecycle.js (keep) |
|
|
610
603
|
| `hasActionableFailureClass` | 4025-4047 | Has actionable failure class. | no | _internal-only (no callers found outside lifecycle.js)_ | engine/lifecycle.js (keep) |
|
|
611
604
|
| `handleInjectionFlag` | 4048-4134 | F5 (W-mpeklod3000we69c): handle agent-reported injection attempts. The agent set `securityFlags.injectionAttempt: true` in its completion report after spotting attacker-controlled instructions inside an `<UNTRUSTED-INPUT | no | _internal-only (no callers found outside lifecycle.js)_ | engine/lifecycle.js (keep) |
|
|
612
|
-
| `resolveHarnessPropagated` | 4135-4149 | resolveHarnessPropagated(dispatchItem) — P-c6f5e8b3. Locate the spawn-time // `_harnessPropagated` manifest for a dispatch. The in-memory dispatchItem // (spawnAgent's closure object) normally carries it directly; after | yes | `engine.js` | engine/lifecycle.js (keep) |
|
|
613
605
|
| `parseCompletionReportFile` | 4150-4172 | Parse completion report file. | yes | `engine/timeout.js`, `test/unit.test.js` | engine/lifecycle.js (keep) |
|
|
614
606
|
| `normalizeCompletionArtifacts` | 4173-4198 | Normalize completion artifacts. | yes | _internal-only (no callers found outside lifecycle.js)_ | engine/lifecycle.js (keep) |
|
|
615
607
|
| `normalizeArtifactPath` | 4199-4212 | Normalize artifact path. | no | _internal-only (no callers found outside lifecycle.js)_ | engine/lifecycle.js (keep) |
|
package/engine/ado-comment.js
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* engine/ado-comment.js — Azure DevOps mirror of engine/gh-comment.js.
|
|
3
3
|
*
|
|
4
4
|
* Posts a minions PR-comment thread to ADO with the same hidden marker, the
|
|
5
|
-
* same
|
|
6
|
-
* link as the GitHub path — because all three are produced by the ONE neutral
|
|
5
|
+
* same deterministic brand link as the GitHub path — because both are produced by the ONE neutral
|
|
7
6
|
* builder `engine/comment-format.js#buildMinionsCommentBody`. This module owns
|
|
8
7
|
* only the ADO transport (REST `…/threads` POST) and ADO-specific input
|
|
9
8
|
* validation (orgBase / project / repositoryId / prNumber).
|
|
@@ -13,7 +12,7 @@
|
|
|
13
12
|
* unlinked on ADO (the GitHub backstop in buildMinionsCommentBody never ran).
|
|
14
13
|
* Routing ADO comments through this poster gives ADO the same guarantees as
|
|
15
14
|
* GitHub: marker present (so pollers classify the comment as agent-authored),
|
|
16
|
-
*
|
|
15
|
+
* brand trailer hyperlinked.
|
|
17
16
|
*
|
|
18
17
|
* Auth: a bearer token from `az account get-access-token` (via
|
|
19
18
|
* engine/ado-token.js#acquireAdoToken), threaded as `Authorization: Bearer`.
|
|
@@ -34,7 +33,7 @@
|
|
|
34
33
|
* changes) stay `active` so they block/notify the author.
|
|
35
34
|
*/
|
|
36
35
|
|
|
37
|
-
const { buildMinionsCommentBody
|
|
36
|
+
const { buildMinionsCommentBody } = require('./comment-format');
|
|
38
37
|
const { acquireAdoToken } = require('./ado-token');
|
|
39
38
|
|
|
40
39
|
const ADO_API_VERSION = '7.1';
|
|
@@ -95,7 +94,7 @@ async function _defaultAcquireToken() {
|
|
|
95
94
|
/**
|
|
96
95
|
* Post a minions PR comment to an Azure DevOps pull request.
|
|
97
96
|
*
|
|
98
|
-
* Mirrors engine/gh-comment.js#postPrComment: builds the marked +
|
|
97
|
+
* Mirrors engine/gh-comment.js#postPrComment: builds the marked +
|
|
99
98
|
* brand-linked body via the shared builder, then POSTs a new active thread.
|
|
100
99
|
*
|
|
101
100
|
* @param {object} args
|
|
@@ -107,8 +106,6 @@ async function _defaultAcquireToken() {
|
|
|
107
106
|
* @param {string} args.agentId minions agent id (marker)
|
|
108
107
|
* @param {string} args.kind comment kind (marker)
|
|
109
108
|
* @param {string} [args.workItemId] originating work-item id (marker)
|
|
110
|
-
* @param {object} [args.harnessUsed] grounded harnessUsed record (folded into body)
|
|
111
|
-
* @param {object} [args.skillSkipped] skipped-project-skill record `{name, reason}` (folded into body, visible)
|
|
112
109
|
* @param {number} [args.timeoutMs=30000]
|
|
113
110
|
* @param {Function} [args.acquireToken] () => Promise<string> — injectable token source
|
|
114
111
|
* @param {Function} [args.fetchImpl=fetch] injectable fetch
|
|
@@ -123,8 +120,6 @@ async function postAdoPrComment({
|
|
|
123
120
|
agentId,
|
|
124
121
|
kind,
|
|
125
122
|
workItemId,
|
|
126
|
-
harnessUsed,
|
|
127
|
-
skillSkipped,
|
|
128
123
|
resolved = false,
|
|
129
124
|
timeoutMs = 30000,
|
|
130
125
|
acquireToken = _defaultAcquireToken,
|
|
@@ -136,7 +131,7 @@ async function postAdoPrComment({
|
|
|
136
131
|
_validatePrNumber(prNumber);
|
|
137
132
|
|
|
138
133
|
// buildMinionsCommentBody validates marker fields and throws on bad input.
|
|
139
|
-
const finalBody = buildMinionsCommentBody({ agentId, kind, workItemId, body
|
|
134
|
+
const finalBody = buildMinionsCommentBody({ agentId, kind, workItemId, body });
|
|
140
135
|
|
|
141
136
|
const token = await acquireToken();
|
|
142
137
|
if (!token || typeof token !== 'string') {
|
|
@@ -186,7 +181,6 @@ module.exports = {
|
|
|
186
181
|
// Re-export the neutral builder so ADO callers have a single import surface,
|
|
187
182
|
// mirroring engine/gh-comment.js.
|
|
188
183
|
buildMinionsCommentBody,
|
|
189
|
-
buildSkippedSkillSection,
|
|
190
184
|
// Internal validators exported for tests.
|
|
191
185
|
_validateOrgBase,
|
|
192
186
|
_validateProject,
|
package/engine/ado.js
CHANGED
|
@@ -409,11 +409,13 @@ function applyAdoPrMetadata(pr, prData) {
|
|
|
409
409
|
updated = true;
|
|
410
410
|
}
|
|
411
411
|
|
|
412
|
-
// W-mpej044m00076d63:
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
//
|
|
416
|
-
|
|
412
|
+
// W-mpej044m00076d63 / W-mrezh0yb0007f733: keep `pr.created` aligned with
|
|
413
|
+
// ADO's real creationDate. The platform is authoritative for when a PR was
|
|
414
|
+
// opened, so adopt its value whenever the stored one is missing, a legacy
|
|
415
|
+
// date-only stamp, or a link/attach-time `now()` default (the bug where
|
|
416
|
+
// manually-linked PRs showed a "created today" date). `shouldAdoptPlatformCreated`
|
|
417
|
+
// compares instants so equivalent representations don't churn. Idempotent.
|
|
418
|
+
if (shared.shouldAdoptPlatformCreated(pr.created, prData.creationDate)) {
|
|
417
419
|
pr.created = prData.creationDate;
|
|
418
420
|
updated = true;
|
|
419
421
|
}
|
|
@@ -2606,6 +2608,9 @@ async function fetchAdoPrMetadata(prNum, adoOrg, adoProj, adoRepo, project) {
|
|
|
2606
2608
|
description: pr.description || '',
|
|
2607
2609
|
branch: stripRefsHeads(pr.sourceRefName),
|
|
2608
2610
|
author: pr.createdBy?.displayName || '',
|
|
2611
|
+
// W-mrezh0yb0007f733 — surface ADO's real creationDate so manual-link paths
|
|
2612
|
+
// can seed `pr.created` with the true value instead of link-time now().
|
|
2613
|
+
created: pr.creationDate || '',
|
|
2609
2614
|
};
|
|
2610
2615
|
}
|
|
2611
2616
|
|
package/engine/cleanup.js
CHANGED
|
@@ -1388,23 +1388,13 @@ async function runCleanup(config, verbose = false) {
|
|
|
1388
1388
|
}
|
|
1389
1389
|
// PRD items (missing_features[].status)
|
|
1390
1390
|
try {
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
prdDirEntries = fs.readdirSync(PRD_DIR);
|
|
1394
|
-
} catch (e) {
|
|
1395
|
-
log('warn', `migrate PRD statuses: failed to read ${PRD_DIR} — ${e.message}`);
|
|
1396
|
-
prdDirEntries = [];
|
|
1397
|
-
}
|
|
1398
|
-
const prdFiles = prdDirEntries.filter(f => f.endsWith('.json'));
|
|
1391
|
+
const prdStore = require('./prd-store');
|
|
1392
|
+
const prdFiles = prdStore.listPrdRows().filter(row => !row.archived).map(row => row.filename);
|
|
1399
1393
|
for (const pf of prdFiles) {
|
|
1400
1394
|
const prdPath = path.join(PRD_DIR, pf);
|
|
1395
|
+
const prd = prdStore.readPrd(prdPath);
|
|
1401
1396
|
let migrated = 0;
|
|
1402
|
-
|
|
1403
|
-
// safeJsonNoRestore: PRDs are terminal artifacts. If the active PRD
|
|
1404
|
-
// was archived but a stale .backup sidecar remains, never resurrect
|
|
1405
|
-
// it during a routine migration sweep (W-mouptdh1000h9f39).
|
|
1406
|
-
const prd = safeJsonNoRestore(prdPath);
|
|
1407
|
-
if (!prd?.missing_features) return;
|
|
1397
|
+
if (prd?.missing_features) {
|
|
1408
1398
|
for (const feat of prd.missing_features) {
|
|
1409
1399
|
if (LEGACY_DONE_ALIASES.has(feat.status)) {
|
|
1410
1400
|
feat.status = shared.WI_STATUS.DONE;
|
|
@@ -1414,8 +1404,8 @@ async function runCleanup(config, verbose = false) {
|
|
|
1414
1404
|
migrated++;
|
|
1415
1405
|
}
|
|
1416
1406
|
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1407
|
+
}
|
|
1408
|
+
if (migrated > 0) prdStore.writePrd(prdPath, prd);
|
|
1419
1409
|
if (migrated > 0) {
|
|
1420
1410
|
log('info', `Migrated ${migrated} legacy PRD item status(es) in ${pf}`);
|
|
1421
1411
|
}
|
|
@@ -1425,36 +1415,27 @@ async function runCleanup(config, verbose = false) {
|
|
|
1425
1415
|
// Reset orphaned PRD item statuses — dispatched/failed with no matching work item (#779)
|
|
1426
1416
|
cleaned.orphanedPrdStatuses = 0;
|
|
1427
1417
|
try {
|
|
1428
|
-
const wiIds = new Set(
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
for (const wi of items) { if (wi?.id) wiIds.add(wi.id); }
|
|
1432
|
-
}
|
|
1433
|
-
try {
|
|
1434
|
-
const centralWi = safeJsonArr(path.join(MINIONS_DIR, 'work-items.json'));
|
|
1435
|
-
for (const wi of centralWi) { if (wi?.id) wiIds.add(wi.id); }
|
|
1436
|
-
} catch { /* optional */ }
|
|
1418
|
+
const wiIds = new Set(
|
|
1419
|
+
queries.getWorkItems(config, { enrich: false }).map(wi => wi?.id).filter(Boolean)
|
|
1420
|
+
);
|
|
1437
1421
|
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
catch { orphanPrdEntries = []; }
|
|
1422
|
+
const prdStore = require('./prd-store');
|
|
1423
|
+
const orphanPrdEntries = prdStore.listPrdRows().filter(row => !row.archived).map(row => row.filename);
|
|
1441
1424
|
for (const pf of orphanPrdEntries) {
|
|
1442
1425
|
const prdPath = path.join(PRD_DIR, pf);
|
|
1443
|
-
|
|
1444
|
-
// from a stale .backup sidecar (W-mouptdh1000h9f39).
|
|
1445
|
-
const peek = safeJsonNoRestore(prdPath);
|
|
1426
|
+
const peek = prdStore.readPrd(prdPath);
|
|
1446
1427
|
if (!peek?.missing_features) continue;
|
|
1447
1428
|
let reset = 0;
|
|
1448
|
-
|
|
1449
|
-
|
|
1429
|
+
const prd = peek;
|
|
1430
|
+
if (prd?.missing_features) {
|
|
1450
1431
|
for (const feat of prd.missing_features) {
|
|
1451
1432
|
if ((feat.status === shared.WI_STATUS.DISPATCHED || feat.status === shared.WI_STATUS.FAILED) && !wiIds.has(feat.id)) {
|
|
1452
1433
|
feat.status = shared.WI_STATUS.PENDING;
|
|
1453
1434
|
reset++;
|
|
1454
1435
|
}
|
|
1455
1436
|
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1437
|
+
}
|
|
1438
|
+
if (reset > 0) prdStore.writePrd(prdPath, prd);
|
|
1458
1439
|
if (reset > 0) {
|
|
1459
1440
|
log('info', `Reset ${reset} orphaned PRD item status(es) → pending in ${pf}`);
|
|
1460
1441
|
cleaned.orphanedPrdStatuses += reset;
|
|
@@ -1669,7 +1650,6 @@ async function runCleanup(config, verbose = false) {
|
|
|
1669
1650
|
* that were written before that guard existed. */
|
|
1670
1651
|
function scrubStaleMetrics() {
|
|
1671
1652
|
const metricsPath = path.join(ENGINE_DIR, 'metrics.json');
|
|
1672
|
-
if (!fs.existsSync(metricsPath)) return;
|
|
1673
1653
|
mutateJsonFileLocked(metricsPath, metrics => {
|
|
1674
1654
|
for (const key of Object.keys(metrics)) {
|
|
1675
1655
|
if (key.startsWith('temp-') || key === 'agent1' || key.startsWith('_test')) {
|