@event4u/agent-config 12.1.0 → 14.0.0
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/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +164 -186
- package/README.md +1 -1
- package/dist/agent-src/commands/cost/report.md +23 -0
- package/dist/agent-src/commands/evals/roadmap.json +8 -0
- package/dist/agent-src/commands/fix/ci.md +39 -1
- package/dist/agent-src/commands/pr/create.md +65 -4
- package/dist/agent-src/commands/roadmap/next.md +28 -5
- package/dist/agent-src/commands/work.md +16 -0
- package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +47 -0
- package/dist/agent-src/contexts/execution/contract-decision-sheet.md +94 -0
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +30 -0
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +21 -8
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +55 -8
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +42 -0
- package/dist/agent-src/contexts/execution/subagent-routing.md +148 -33
- package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +29 -0
- package/dist/agent-src/contexts/subagent-configuration.md +69 -7
- package/dist/agent-src/rules/autonomous-execution.md +1 -1
- package/dist/agent-src/rules/command-suggestion-policy.md +33 -0
- package/dist/agent-src/rules/design-fidelity.md +1 -1
- package/dist/agent-src/rules/design-review-after-ui-write.md +36 -14
- package/dist/agent-src/rules/evaluator-independence.md +27 -5
- package/dist/agent-src/rules/git-history-discipline.md +5 -1
- package/dist/agent-src/rules/missing-skill-recovery.md +89 -0
- package/dist/agent-src/rules/ui-audit-gate.md +34 -12
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +33 -0
- package/dist/agent-src/scripts/gate_execute.ts +395 -0
- package/dist/agent-src/scripts/resume_probe.ts +422 -0
- package/dist/agent-src/scripts/roadmap_gates.ts +138 -4
- package/dist/agent-src/scripts/update_roadmap_progress.ts +70 -4
- package/dist/agent-src/skills/ai-council/SKILL.md +61 -0
- package/dist/agent-src/skills/authz-review/SKILL.md +4 -0
- package/dist/agent-src/skills/design-system-capture/SKILL.md +4 -0
- package/dist/agent-src/skills/design-system-capture/references/snapshot-preference-order.md +77 -0
- package/dist/agent-src/skills/experiment-loop/SKILL.md +116 -0
- package/dist/agent-src/skills/experiment-loop/evals/output-schema.yml +27 -0
- package/dist/agent-src/skills/experiment-loop/evals/triggers.json +17 -0
- package/dist/agent-src/skills/experiment-loop/references/pivot-ladder.md +54 -0
- package/dist/agent-src/skills/experiment-loop/references/protocol.md +61 -0
- package/dist/agent-src/skills/experiment-loop/references/register-format.md +66 -0
- package/dist/agent-src/skills/merge-conflicts/SKILL.md +4 -0
- package/dist/agent-src/skills/prompt-optimizer/SKILL.md +17 -1
- package/dist/agent-src/skills/roadmap-management/SKILL.md +2 -2
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +5 -5
- package/dist/agent-src/skills/subagent-orchestration/schemas/team-review-status.json +8 -3
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +4 -0
- package/dist/agent-src/skills/threat-modeling/SKILL.md +3 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +43 -3
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-K34QTSD5.js → chunk-7PZ4FSZB.js} +368 -191
- package/dist/cli-delegate/{chunk-55WZN5VF.js → chunk-EVTY7ISR.js} +2 -2
- package/dist/cli-delegate/{chunk-O52B4F5S.js → chunk-F3YUF7SA.js} +1 -1
- package/dist/cli-delegate/{chunk-LKZ5DOYW.js → chunk-XURJ4AQC.js} +11 -3
- package/dist/cli-delegate/cmd_conformance.js +224 -104
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +73 -30
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +7 -5
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +6 -4
- package/dist/hooks/dispatch.js +2653 -1365
- package/dist/install/install.mjs +12 -4
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +2679 -887
- package/dist/router.json +1 -1
- package/dist/scripts/condense.js +1 -1
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +2 -2
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/ui/assets/{index-B0aMJnuz.js → index-C1pBRm1f.js} +2 -2
- package/dist/ui/assets/index-C1pBRm1f.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/guidelines/design-fidelity-mechanics.md +21 -0
- package/docs/guidelines/design-handover-extraction.md +87 -0
- package/docs/guidelines/prompt-templates.md +24 -1
- package/package.json +1 -1
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +33 -0
- package/src/agent-src/scripts/gate_execute.ts +395 -0
- package/src/agent-src/scripts/resume_probe.ts +422 -0
- package/src/agent-src/scripts/roadmap_gates.ts +138 -4
- package/src/agent-src/scripts/update_roadmap_progress.ts +70 -4
- package/src/config/ci-local-parity.yml +13 -0
- package/src/config/evaluator-budgets.json +9 -8
- package/src/config/gate-coverage.yml +140 -12
- package/src/config/gate-violation-baselines.json +9 -3
- package/src/config/hook-token-budget.json +8 -1
- package/src/config/rule-activation-census.json +41 -0
- package/src/config/rule-stub-ceilings.json +52 -0
- package/src/scripts/_dispatch.bash +4 -0
- package/src/scripts/_lib/bench_ab_complexity.ts +439 -0
- package/src/scripts/_lib/bench_ab_safety_tier.ts +142 -0
- package/src/scripts/_lib/bench_ab_scoring_v2.ts +28 -3
- package/src/scripts/_lib/bench_ab_search_adherence.ts +423 -0
- package/src/scripts/_lib/bench_ab_size_claim.ts +206 -0
- package/src/scripts/_lib/bench_ab_workspace.ts +111 -0
- package/src/scripts/_lib/evaluator_contract.ts +159 -0
- package/src/scripts/_lib/gh_transient.ts +80 -0
- package/src/scripts/_lib/release_material.ts +16 -5
- package/src/scripts/_lib/review_independence.ts +111 -0
- package/src/scripts/_lib/session_register.ts +14 -0
- package/src/scripts/_lib/skill_catalogue.ts +278 -5
- package/src/scripts/_lib/skill_catalogue_series.ts +411 -0
- package/src/scripts/_lib/tier_budget_routing.ts +31 -331
- package/src/scripts/_lib/turn_end_refusals.ts +482 -0
- package/src/scripts/ai_council/_py_json.ts +96 -0
- package/src/scripts/ai_council/cli_call_budget.ts +357 -0
- package/src/scripts/ai_council/clients.ts +101 -164
- package/src/scripts/ai_council/config.ts +25 -11
- package/src/scripts/ai_council/probe_store.ts +196 -0
- package/src/scripts/ai_council/qualification.ts +425 -0
- package/src/scripts/ai_council/qualification_wiring.ts +243 -0
- package/src/scripts/ai_council/quorum_wiring.ts +211 -0
- package/src/scripts/ai_team/team_dispatch.ts +40 -6
- package/src/scripts/bench_ab_v2_complexity.ts +252 -0
- package/src/scripts/bench_ab_v2_run.ts +3 -42
- package/src/scripts/bench_ab_v2_safety.ts +296 -0
- package/src/scripts/bench_ab_v2_search.ts +343 -0
- package/src/scripts/bench_ab_v2_stats.ts +191 -0
- package/src/scripts/build_archive_index.ts +383 -0
- package/src/scripts/capture_skill_catalogue.ts +369 -4
- package/src/scripts/check_evaluator_schema.ts +249 -0
- package/src/scripts/check_pr_ci_current.ts +409 -0
- package/src/scripts/check_review_schema.ts +195 -0
- package/src/scripts/check_rule_activation_census.ts +451 -0
- package/src/scripts/check_rule_stub_ceiling.ts +1128 -0
- package/src/scripts/check_source_size_budget.ts +428 -0
- package/src/scripts/check_suppression_hygiene.ts +5 -1
- package/src/scripts/condense.ts +1 -1
- package/src/scripts/cost/backfill_rates.mjs +298 -0
- package/src/scripts/cost/budget.mjs +12 -32
- package/src/scripts/cost_summary.ts +25 -0
- package/src/scripts/council_cli.ts +141 -191
- package/src/scripts/dispatch_r2_reviewer.ts +8 -1
- package/src/scripts/external_sources_denylist.json +1 -0
- package/src/scripts/hook_manifest.yaml +99 -6
- package/src/scripts/hooks/concern_registry.ts +6 -0
- package/src/scripts/hooks/dispatch_hook.ts +43 -1
- package/src/scripts/hooks/interruption_ledger_hook.ts +256 -0
- package/src/scripts/hooks/ship_diff_volume_hook.ts +180 -0
- package/src/scripts/hooks/skill_route_hook.ts +226 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +119 -26
- package/src/scripts/hooks/ui_route_nudge_hook.ts +6 -3
- package/src/scripts/hooks_doctor.ts +112 -0
- package/src/scripts/interruption_report.ts +443 -0
- package/src/scripts/lint_design_antipattern_parity.ts +40 -6
- package/src/scripts/lint_evidence_artifacts.ts +359 -0
- package/src/scripts/lint_harvest_provenance.ts +35 -1
- package/src/scripts/lint_roadmap_blockers.ts +65 -0
- package/src/scripts/lint_roadmap_complexity.ts +7 -1
- package/src/scripts/lint_rule_skill_pack_reach.ts +43 -0
- package/src/scripts/lint_skill_router_head.ts +21 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +26 -3
- package/src/scripts/mcp_server/tool_catalog_source.ts +6 -24
- package/src/scripts/mcp_server/tools.ts +87 -0
- package/src/scripts/release.ts +43 -58
- package/src/scripts/routing_doctor.ts +50 -13
- package/src/scripts/rule_activation_census.ts +292 -0
- package/src/scripts/schemas/command.schema.json +1 -1
- package/src/scripts/schemas/evaluator-output.schema.json +60 -0
- package/src/scripts/schemas/review-findings.schema.json +31 -0
- package/src/scripts/schemas/skill.schema.json +18 -1
- package/src/scripts/self_review_gate.ts +6 -0
- package/src/scripts/session_register_hook.ts +30 -1
- package/src/scripts/sync_pr_branch.ts +271 -0
- package/src/scripts/test_council_qualification.ts +213 -0
- package/src/scripts/trigger_coverage.ts +224 -3
- package/src/server/schemas/settings.ts +2 -2
- package/dist/ui/assets/index-B0aMJnuz.js.map +0 -1
- package/src/config/budget-routing.json +0 -7
package/dist/ui/index.html
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
document.documentElement.setAttribute('data-theme', theme);
|
|
28
28
|
})();
|
|
29
29
|
</script>
|
|
30
|
-
<script type="module" crossorigin src="/assets/index-
|
|
30
|
+
<script type="module" crossorigin src="/assets/index-C1pBRm1f.js"></script>
|
|
31
31
|
<link rel="stylesheet" crossorigin href="/assets/index-jSfMW8_t.css">
|
|
32
32
|
</head>
|
|
33
33
|
<body>
|
|
@@ -45,6 +45,27 @@ Two consequences worth stating because they are the ones agents get backwards:
|
|
|
45
45
|
|
|
46
46
|
Regression witness: `daf-source-over-screenshot`.
|
|
47
47
|
|
|
48
|
+
## URL / live-page handover — extraction into files, before the first UI write
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
A HANDOVER THAT ARRIVES AS A URL IS STILL RUNG 2, NOT RUNG 4.
|
|
52
|
+
EXTRACT THROUGH THE USER'S CONNECTED BROWSER TOOLS INTO FILES
|
|
53
|
+
BEFORE ANY UI WRITE. A SCREENSHOT TAKEN DURING EXTRACTION
|
|
54
|
+
CARRIES QA DUTY ONLY — IT IS NEVER THE THING YOU BUILD FROM.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
A published artifact link, a builder's share link, a staging or `localhost`
|
|
58
|
+
page: rung 2 above is reachable, so rung 4 is not the fallback it looks like.
|
|
59
|
+
Extraction lands in the existing `design-system.json` contract under the
|
|
60
|
+
`.claude/design-system/` prefix the rule already routes on — no new artifact
|
|
61
|
+
shape — and the offline `/design-system:import` adapter consumes it. The package
|
|
62
|
+
ships the contract, the adapter and the instructions; it ships no crawler,
|
|
63
|
+
Playwright runtime or font-bundler.
|
|
64
|
+
|
|
65
|
+
Where the extraction goes, what consumes it, the retrieval order that keeps the
|
|
66
|
+
source alive across sessions, the producer sentence, and the honest coverage
|
|
67
|
+
gap: [`design-handover-extraction`](design-handover-extraction.md).
|
|
68
|
+
|
|
48
69
|
## Adopt the code — re-derivation is a deviation
|
|
49
70
|
|
|
50
71
|
Where the artifact's own markup, CSS, or JS is stack-compatible, **adapting that
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Design Handover — URL / live-page extraction
|
|
2
|
+
|
|
3
|
+
> Where a URL handover gets extracted to, what consumes it, and in what order it is read back
|
|
4
|
+
|
|
5
|
+
_Split out of [`design-fidelity-mechanics`](design-fidelity-mechanics.md) § URL / live-page handover, which carries the obligation surface and routes here. The split is the per-file depth ceiling doing its job: the mechanics file sat at 14.2k of its 16k budget, and compressing this material to fit would have cost the parts that make it actionable._
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
A HANDOVER THAT ARRIVES AS A URL IS STILL RUNG 2, NOT RUNG 4.
|
|
9
|
+
EXTRACT THROUGH THE USER'S CONNECTED BROWSER TOOLS INTO FILES
|
|
10
|
+
BEFORE ANY UI WRITE. A SCREENSHOT TAKEN DURING EXTRACTION
|
|
11
|
+
CARRIES QA DUTY ONLY — IT IS NEVER THE THING YOU BUILD FROM.
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
When the artifact is handed over as a **URL** rather than a file — a published
|
|
15
|
+
artifact link, a builder's share link, a staging or `localhost` page — rung 2 of
|
|
16
|
+
the [data-basis ladder](design-fidelity-mechanics.md#data-basis-ladder) is
|
|
17
|
+
reachable and rung 4 is not the fallback it looks like. Pull the DOM,
|
|
18
|
+
stylesheets, scripts and assets through the browser tools the user has
|
|
19
|
+
connected, and land them as files first.
|
|
20
|
+
|
|
21
|
+
## Where the extraction lands
|
|
22
|
+
|
|
23
|
+
Into `design-system.json` under `.claude/design-system/` — the `path_prefix` the
|
|
24
|
+
`design-fidelity` rule already routes on. That file is an **existing contract**,
|
|
25
|
+
specified in
|
|
26
|
+
[`design-system-json.md`](../../src/skills/design-system-capture/references/design-system-json.md);
|
|
27
|
+
this guideline defines no format of its own. Raw source files land beside it,
|
|
28
|
+
and screenshots taken while extracting land in a references directory next to
|
|
29
|
+
them.
|
|
30
|
+
|
|
31
|
+
## What consumes it
|
|
32
|
+
|
|
33
|
+
[`/design-system:import`](../../src/domains/engineering-base/design-system/import/command.md)
|
|
34
|
+
runs the extraction output through the three-lane adapter
|
|
35
|
+
(`native` / `dtcg` / `dembrandt`) into the contract shape, then hands it to
|
|
36
|
+
`design-system-capture`'s per-field import. The adapter is **offline** — it
|
|
37
|
+
reads a file and writes the contract shape; it never fetches, crawls, or
|
|
38
|
+
launches a browser.
|
|
39
|
+
|
|
40
|
+
## The lock boundary — stated here because this is where an agent would cross it
|
|
41
|
+
|
|
42
|
+
The package ships the contract, the adapter, and the instructions. It does
|
|
43
|
+
**not** ship the crawler, the Playwright runtime, or a font-bundler. Extraction
|
|
44
|
+
runs through the user's own connected tooling, and its output is *observed, not
|
|
45
|
+
authoritative*: every field is confirmed by the human, and a value conflicting
|
|
46
|
+
with a registered brand token is flagged rather than applied
|
|
47
|
+
(`brand-source-of-truth`).
|
|
48
|
+
|
|
49
|
+
## Retrieval order — so the source survives the session
|
|
50
|
+
|
|
51
|
+
Check in this order and stop at the first hit. The point is that the next
|
|
52
|
+
session reads a file instead of re-screenshotting the same page.
|
|
53
|
+
|
|
54
|
+
| # | Source | Why it ranks here |
|
|
55
|
+
|---|---|---|
|
|
56
|
+
| 1 | The project's own `design-system.json` | Already confirmed by a human in an earlier session — the only rung whose values someone signed off on |
|
|
57
|
+
| 2 | A previous extraction artifact under `.claude/design-system/` | Observed and file-backed; re-reading it costs nothing and re-extracting may drift |
|
|
58
|
+
| 3 | The live page | Authoritative for *today's* page, but only reachable while it is up and the tooling is connected |
|
|
59
|
+
|
|
60
|
+
**Persistence is not restated here.** Skip-if-exists unless explicitly forced,
|
|
61
|
+
and never silently discarding a prior confirmed decision, are owned by
|
|
62
|
+
[`/design-system:generate`](../../src/domains/engineering-base/design-system/generate/command.md)
|
|
63
|
+
— *"never overwrite a confirmed `DESIGN.md`"*, *"Never persist silently."* This
|
|
64
|
+
guideline states the order; that command states what happens on a collision, and
|
|
65
|
+
stating it twice is how the two drift apart.
|
|
66
|
+
|
|
67
|
+
## The producer sentence
|
|
68
|
+
|
|
69
|
+
The documented easy path is a connected extractor MCP, attached once and reused.
|
|
70
|
+
Where none is available, the manual Chrome-DevTools-MCP channel is the fallback,
|
|
71
|
+
and where neither is reachable the handover degrades to rung 3 or 4 — which the
|
|
72
|
+
ladder requires you to **name**, not to pass off as a source-based build.
|
|
73
|
+
|
|
74
|
+
## Coverage — stated honestly
|
|
75
|
+
|
|
76
|
+
No fixture scores this class yet. `daf-source-over-screenshot` scores the rung
|
|
77
|
+
choice on an *attached* artifact, not a URL handover, and it is itself recorded
|
|
78
|
+
as SKIPPED on 2026-08-13 for want of a page-reaching capture primitive. Closing
|
|
79
|
+
the gap needs a fixture whose host actually has one; claiming a regression
|
|
80
|
+
witness before then would be the fabrication this package's evidence discipline
|
|
81
|
+
exists to prevent.
|
|
82
|
+
|
|
83
|
+
## See also
|
|
84
|
+
|
|
85
|
+
- [`design-fidelity-mechanics`](design-fidelity-mechanics.md) — the data-basis ladder this operationalises, and the adopt-the-code duty that governs what you do with the extracted source.
|
|
86
|
+
- [`design-fidelity`](../../src/rules/design-fidelity.md) — the rule; a provided artifact is the spec.
|
|
87
|
+
- [`design-system-capture`](../../src/skills/design-system-capture/SKILL.md) — the per-field import the adapter hands off to.
|
|
@@ -138,9 +138,32 @@ Given an existing good output, reconstruct the prompt that would
|
|
|
138
138
|
produce it. Used to mine prompts from public LLM artifacts. Not a
|
|
139
139
|
shaping template — a forensic one.
|
|
140
140
|
|
|
141
|
+
## Viral-prompt anti-patterns — what this catalogue deliberately omits
|
|
142
|
+
|
|
143
|
+
Prompt collections that circulate socially optimise for looking impressive
|
|
144
|
+
on the way in, not for what the target model can act on. None of the
|
|
145
|
+
templates above encodes the three patterns below, and that is a decision
|
|
146
|
+
rather than an oversight — a template is a slot structure, and these three
|
|
147
|
+
are slots the model cannot fill honestly.
|
|
148
|
+
|
|
149
|
+
| Anti-pattern | Why no template carries it |
|
|
150
|
+
|---|---|
|
|
151
|
+
| **Grandiose persona** — "world's foremost", "legendary", "10× expert" | Every template's role slot takes a *capability* (`RTF` Role, `CRISPE` Capacity, `CO-STAR` Style). A superlative is not a capability: it spends tokens and changes nothing the model does. |
|
|
152
|
+
| **Presupposed canon** — "the 7 laws of X", "the standard X framework", for a body of knowledge that may not exist | A presupposition in the context slot is answered by confabulation, and the invented canon inherits the authority of the frame. `Few-Shot` exists precisely so that a real pattern is *shown* rather than asserted. |
|
|
153
|
+
| **Scope stuffing** — analysis *and* strategy *and* copy *and* a checklist in one prompt | Each template has one Expectation / Objective / Format slot by construction. Four deliverables in one prompt produce four shallow ones; split them, or name the primary. |
|
|
154
|
+
|
|
155
|
+
The catalogue's own position: a template earns its place by making a
|
|
156
|
+
request *checkable*, not by making it sound authoritative. Where a rough
|
|
157
|
+
prompt carries one of these three, the
|
|
158
|
+
[`prompt-optimizer`](../../.agent-src.uncondensed/skills/prompt-optimizer/SKILL.md)
|
|
159
|
+
strips it in its de-inflation sub-step before any template is picked —
|
|
160
|
+
and strips nothing else, because everything outside these three is a
|
|
161
|
+
requirement the author meant. This section is own analysis; it records a
|
|
162
|
+
decision about this catalogue rather than adopting an outside claim.
|
|
163
|
+
|
|
141
164
|
## Rejection note
|
|
142
165
|
|
|
143
|
-
|
|
166
|
+
An external prompt-collection claims that only five techniques
|
|
144
167
|
are "safe" for production prompting:
|
|
145
168
|
|
|
146
169
|
- few-shot
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@event4u/agent-config",
|
|
3
3
|
"mcpName": "io.github.event4u-app/agent-config",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "14.0.0",
|
|
5
5
|
"description": "Governed skills, rules and commands for AI coding tools (Claude Code, Cursor, Windsurf, Copilot) — every claim machine-checked, including \"zero runtime daemon\".",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"private": false,
|
|
@@ -403,6 +403,38 @@ function _regen_dashboard(root: string, dry_run: boolean): void {
|
|
|
403
403
|
}
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
+
/**
|
|
407
|
+
* Rebuild the archive index after a move, when the builder is present.
|
|
408
|
+
*
|
|
409
|
+
* The sweep is the one operation that changes what `agents/roadmaps/archive/`
|
|
410
|
+
* contains, so it is the one operation that can leave the generated index
|
|
411
|
+
* stale — and the drift gate on that index runs in the same PR the sweep runs
|
|
412
|
+
* in. Regenerating here keeps the two from disagreeing by construction rather
|
|
413
|
+
* than by remembering.
|
|
414
|
+
*
|
|
415
|
+
* `build_archive_index` is package-internal (`src/scripts/`), while this sweep
|
|
416
|
+
* is projected into consumer installs that carry no such script. Absent
|
|
417
|
+
* builder → nothing to regenerate, and that is a normal consumer state, not a
|
|
418
|
+
* failure. The spawn is best-effort for the same reason `_regen_dashboard` is:
|
|
419
|
+
* a sweep that archived correctly must not report failure because a
|
|
420
|
+
* regeneration could not run.
|
|
421
|
+
*/
|
|
422
|
+
function _regen_archive_index(root: string, dry_run: boolean): void {
|
|
423
|
+
if (dry_run) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const script = path.join(root, 'src', 'scripts', 'build_archive_index.ts');
|
|
427
|
+
if (!_isFile(script)) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
_runTwin(root, script);
|
|
431
|
+
for (const rel of ['agents/roadmaps/archive/INDEX.md', 'agents/roadmaps/archive/index.json']) {
|
|
432
|
+
if (_isFile(path.join(root, rel))) {
|
|
433
|
+
_run(['git', 'add', '--', rel], root);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
406
438
|
/** Spawn the dashboard generator twin (tsx) with cwd=root, mirroring `[sys.executable, script]`. */
|
|
407
439
|
function _runTwin(root: string, script: string): void {
|
|
408
440
|
// Python uses `sys.executable` — the interpreter already running this
|
|
@@ -491,6 +523,7 @@ function main(argv?: readonly string[]): number {
|
|
|
491
523
|
return 0;
|
|
492
524
|
}
|
|
493
525
|
_regen_dashboard(root, ns.dry_run);
|
|
526
|
+
_regen_archive_index(root, ns.dry_run);
|
|
494
527
|
const verb = ns.dry_run ? 'Would archive' : 'Archived';
|
|
495
528
|
for (const rec of archived) {
|
|
496
529
|
process.stdout.write(
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agent-config gates --execute <id>` — the acting half of the gate layer.
|
|
3
|
+
*
|
|
4
|
+
* `gates` renders decisions. This resolves the ones that were never decisions:
|
|
5
|
+
* a class-0 gate is a command whose OUTPUT IS THE UNBLOCK, and leaving it for a
|
|
6
|
+
* human to type is the couriering `road-to-gate-autonomy` exists to remove.
|
|
7
|
+
*
|
|
8
|
+
* ## Deliberately thin, on the sweep's own number
|
|
9
|
+
*
|
|
10
|
+
* Step 1.2 pre-registered that at least 40 % of open blockers would be class 0
|
|
11
|
+
* or 1 and measured **24.5 %** (`agents/evidence/analysis/gate-class-sweep-2026-08-17.md`).
|
|
12
|
+
* The roadmap's honest-null clause then fixes this module's size: it ships for
|
|
13
|
+
* the six class-0 entries and the render path, and it is not the lever that
|
|
14
|
+
* drains the estate. Building a general execution engine against a premise the
|
|
15
|
+
* evidence weakened would be the over-build the null exists to prevent.
|
|
16
|
+
*
|
|
17
|
+
* ## What it will not do
|
|
18
|
+
*
|
|
19
|
+
* - **No sweep.** One blocker per invocation, named explicitly. A blanket
|
|
20
|
+
* `--execute` over the estate would run N authored commands on one keypress,
|
|
21
|
+
* and the blast radius of a misclassification would be the whole tree
|
|
22
|
+
* instead of one entry.
|
|
23
|
+
* - **No resolve on failure.** A non-zero exit reports and changes nothing.
|
|
24
|
+
* A gate marked resolved by a command that failed is worse than an open one.
|
|
25
|
+
* - **No invented ledger.** Class 1 needs a standing budget, and the shape of
|
|
26
|
+
* that budget is an OPEN maintainer decision (`b-gate-budget-preauth`).
|
|
27
|
+
* Until it is taken there is no ledger, so class 1 takes the
|
|
28
|
+
* render-instead-of-run path — which is the behaviour the blocker itself
|
|
29
|
+
* prescribes for a missing ledger, not a stub of the decision.
|
|
30
|
+
* - **No class-3 change.** The class-3 branch executes nothing and writes
|
|
31
|
+
* nothing, so `agent-config gates` renders a class-3 entry exactly as it did
|
|
32
|
+
* before this module existed. `--execute` on one returns a one-line notice
|
|
33
|
+
* and points back at that renderer; it does not call it.
|
|
34
|
+
* - **No run without `--confirm`.** `Run:` is arbitrary shell read out of a
|
|
35
|
+
* markdown field. Without the flag the command is echoed and refused, so
|
|
36
|
+
* the operator sees the exact string first — the this-turn, names-the-object
|
|
37
|
+
* confirmation `non-destructive-by-default` requires. A command carrying a
|
|
38
|
+
* Hard-Floor action is refused even WITH the flag: class 0 means reversible,
|
|
39
|
+
* so such an entry is misclassified rather than merely dangerous.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
import * as fs from 'node:fs';
|
|
43
|
+
import * as path from 'node:path';
|
|
44
|
+
import { spawnSync } from 'node:child_process';
|
|
45
|
+
|
|
46
|
+
import { collect, blocker_class, type Blocker } from './update_roadmap_progress.js';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Where a class-1 receipt ledger would live once the budget decision is taken.
|
|
50
|
+
*
|
|
51
|
+
* Named here so the absent-ledger branch has something concrete to report, and
|
|
52
|
+
* deliberately NOT created: the path is a consequence of the decision, and
|
|
53
|
+
* writing it now would answer a question that is still the maintainer's.
|
|
54
|
+
*/
|
|
55
|
+
const LEDGER_REL = path.join('agents', 'runtime', 'state', 'gate-budget-ledger.jsonl');
|
|
56
|
+
|
|
57
|
+
type Outcome = 'resolved' | 'failed' | 'rendered' | 'not-found';
|
|
58
|
+
|
|
59
|
+
interface ExecuteResult {
|
|
60
|
+
outcome: Outcome;
|
|
61
|
+
/** Human-facing report — always populated, always the whole answer. */
|
|
62
|
+
report: string;
|
|
63
|
+
/** Exit code for the CLI: 0 unless the gate was named and could not run. */
|
|
64
|
+
code: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface Located {
|
|
68
|
+
blocker: Blocker;
|
|
69
|
+
/** Absolute path of the roadmap holding it. */
|
|
70
|
+
file: string;
|
|
71
|
+
rel: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function locate(roadmapRoot: string, id: string): Located | null {
|
|
75
|
+
for (const r of collect(roadmapRoot)) {
|
|
76
|
+
for (const b of r.open_blockers) {
|
|
77
|
+
if (b.id === id) {
|
|
78
|
+
// `r.path` is the absolute path the collector already resolved.
|
|
79
|
+
// Rebuilding it from `basename(r.rel)` discarded the directory,
|
|
80
|
+
// so a roadmap in any subdirectory the collector does not
|
|
81
|
+
// exclude was either written to a same-named top-level file or
|
|
82
|
+
// threw — and either way AFTER the authored command had run.
|
|
83
|
+
return { blocker: b, file: r.path, rel: r.rel };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Commands a class-0 gate may never carry, whatever the entry claims.
|
|
92
|
+
*
|
|
93
|
+
* Class 0 is defined as deterministic, free and **reversible**. A command that
|
|
94
|
+
* pushes, merges, deploys, or deletes is none of those, so its presence is a
|
|
95
|
+
* misclassification — and the right answer to a misclassification is refusal,
|
|
96
|
+
* not a confirmation prompt that makes the operator the last line of defence.
|
|
97
|
+
* These are Hard-Floor actions under `non-destructive-by-default`, which no
|
|
98
|
+
* setting, roadmap or flag lifts.
|
|
99
|
+
*
|
|
100
|
+
* A denylist is the wrong shape for a *security boundary* and the right shape
|
|
101
|
+
* for a *classification check*, which is what this is: the tree's roadmap files
|
|
102
|
+
* are reviewed content, so this catches the authoring mistake rather than an
|
|
103
|
+
* attacker. The blast-radius control is `--confirm` below, not this list.
|
|
104
|
+
*/
|
|
105
|
+
const HARD_FLOOR_RE =
|
|
106
|
+
/(^|[;&|]\s*)(git\s+(?:push|merge|reset\s+--hard|rebase)\b|gh\s+(?:pr\s+merge|release)\b|terraform\s+apply\b|kubectl\s+apply\b|npm\s+publish\b|rm\s+-[rRf]+|DROP\s+TABLE\b|TRUNCATE\b)/i;
|
|
107
|
+
|
|
108
|
+
function hardFloorReason(command: string): string | null {
|
|
109
|
+
const m = HARD_FLOOR_RE.exec(command);
|
|
110
|
+
return m ? (m[2] as string).trim() : null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Strip the backticks an authored `Run:` field carries. */
|
|
114
|
+
function commandOf(b: Blocker): string {
|
|
115
|
+
const raw = b.run.trim();
|
|
116
|
+
const fenced = /^`([^`]+)`$/.exec(raw);
|
|
117
|
+
return (fenced ? (fenced[1] as string) : raw).trim();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The class-2 consent line — one question, its recommendation, its default.
|
|
122
|
+
*
|
|
123
|
+
* This is a FEED, not a surface: `road-to-user-out-of-the-loop` Phase 1 owns
|
|
124
|
+
* the decision sheet, and this roadmap only hands it a line. Keeping the shape
|
|
125
|
+
* to one line is the contract — a class-2 gate that cannot state a one-line
|
|
126
|
+
* question and a default is reclassified to 3, never verbosified.
|
|
127
|
+
*/
|
|
128
|
+
function consentLine(b: Blocker, why?: string): string {
|
|
129
|
+
const head = why ? `${b.id} — ${why}` : b.id;
|
|
130
|
+
const rec = b.recommendation.trim();
|
|
131
|
+
const q = b.question.trim();
|
|
132
|
+
const lines = [`CONSENT · ${head}`];
|
|
133
|
+
// An entry with no `Question:` has no question, and borrowing `Blocks:`
|
|
134
|
+
// silently put a "what this holds up" sentence in the "what is being
|
|
135
|
+
// decided" slot — two different facts. Say which one is on screen.
|
|
136
|
+
lines.push(
|
|
137
|
+
q !== ''
|
|
138
|
+
? ` Question: ${q}`
|
|
139
|
+
: ` Question: (none authored — this is the Blocks line) ${b.blocks.trim()}`,
|
|
140
|
+
);
|
|
141
|
+
lines.push(
|
|
142
|
+
` Recommendation: ${rec || '(none recorded — ask for one before deciding)'}`,
|
|
143
|
+
);
|
|
144
|
+
// The default is derived, not constant: an entry carrying a recommendation
|
|
145
|
+
// has one, an entry without one has nothing to default TO, and printing
|
|
146
|
+
// the same sentence for both told the reader nothing either way.
|
|
147
|
+
lines.push(
|
|
148
|
+
rec !== ''
|
|
149
|
+
? ' Default if you say nothing: the recommendation above is NOT applied; the gate stays open.'
|
|
150
|
+
: ' Default if you say nothing: the gate stays open, and no recommendation exists to fall back on.',
|
|
151
|
+
);
|
|
152
|
+
// Risk 5 of the roadmap, made observable rather than merely written down:
|
|
153
|
+
// the defect being fixed is reading load, and a mechanism that emits
|
|
154
|
+
// paragraphs recreates it under a new name. The remedy the taxonomy names
|
|
155
|
+
// is reclassification to 3, never a longer line — so the overflow is
|
|
156
|
+
// reported at the entry rather than silently rendered.
|
|
157
|
+
if (rec.length > PARAGRAPH_CHARS) {
|
|
158
|
+
lines.push(
|
|
159
|
+
` note: this recommendation is ${rec.length} chars — a class-2 gate that ` +
|
|
160
|
+
'cannot state a one-line question and a default is reclassified to 3, ' +
|
|
161
|
+
'not verbosified.',
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
return lines.join('\n') + '\n';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Where "one line" stops being one line.
|
|
169
|
+
*
|
|
170
|
+
* A stated default, not a measured optimum: two full terminal lines at the 78
|
|
171
|
+
* column the rest of this command wraps to. *Revisit-if* a real decision sheet
|
|
172
|
+
* lands and its own width contradicts this.
|
|
173
|
+
*/
|
|
174
|
+
const PARAGRAPH_CHARS = 156;
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Append the run's evidence under the blocker and flip its status.
|
|
178
|
+
*
|
|
179
|
+
* In-file, at the blocker, which is the done-note discipline every phase in
|
|
180
|
+
* this tree already uses — an unblock recorded anywhere else is an unblock the
|
|
181
|
+
* next reader of the roadmap cannot see.
|
|
182
|
+
*/
|
|
183
|
+
function appendEvidence(file: string, id: string, command: string, output: string, when: string): boolean {
|
|
184
|
+
const text = fs.readFileSync(file, 'utf-8');
|
|
185
|
+
// The id is regex-escaped: ids are parsed with `(.+?)`, so a metacharacter
|
|
186
|
+
// is legal in the tree and an unescaped interpolation threw an uncaught
|
|
187
|
+
// SyntaxError — after the authored command had already run.
|
|
188
|
+
const head = new RegExp(`^###[ \\t]+blocker:[ \\t]*${escapeRe(id)}[ \\t]*$`, 'im');
|
|
189
|
+
const m = head.exec(text);
|
|
190
|
+
if (!m) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
const bodyStart = m.index + m[0].length;
|
|
194
|
+
// The body ends at the next blocker OR the next `##` section. Bounding it
|
|
195
|
+
// only at the next `### blocker:` made the LAST blocker's body run to the
|
|
196
|
+
// end of the file — the common shape, since `## Blockers` is followed by
|
|
197
|
+
// `## Risk Register` — so the evidence bullet landed after the risk table
|
|
198
|
+
// instead of under the blocker, which is the opposite of what this
|
|
199
|
+
// function's whole point is.
|
|
200
|
+
const rest = text.slice(bodyStart);
|
|
201
|
+
const nextHead = /^###[ \t]+blocker:/im.exec(rest);
|
|
202
|
+
const nextSection = /^##[ \t]+\S/m.exec(rest);
|
|
203
|
+
const ends = [nextHead?.index, nextSection?.index].filter((i): i is number => i !== undefined);
|
|
204
|
+
const bodyEnd = ends.length ? bodyStart + Math.min(...ends) : text.length;
|
|
205
|
+
const body = text.slice(bodyStart, bodyEnd);
|
|
206
|
+
|
|
207
|
+
const statusRe = /^-[ \t]*\*\*Status:\*\*[ \t]*.*$/im;
|
|
208
|
+
if (!statusRe.test(body)) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
const firstLine = output.split(/\r?\n/).find((l) => l.trim() !== '') ?? '(no output)';
|
|
212
|
+
const flipped = body.replace(
|
|
213
|
+
statusRe,
|
|
214
|
+
`- **Status:** resolved ${when} — class-0 auto-run`,
|
|
215
|
+
);
|
|
216
|
+
const evidence =
|
|
217
|
+
`- **Unblock evidence (${when}):** ran \`${command}\` — exit 0. ` +
|
|
218
|
+
`First line of output: ${firstLine.trim()}\n`;
|
|
219
|
+
const withEvidence = flipped.replace(/\s*$/, '\n') + evidence;
|
|
220
|
+
fs.writeFileSync(file, text.slice(0, bodyStart) + withEvidence + text.slice(bodyEnd), 'utf-8');
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** `YYYY-MM-DD` for the evidence note. Injected so tests are deterministic. */
|
|
225
|
+
function today(now: Date): string {
|
|
226
|
+
return now.toISOString().slice(0, 10);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function escapeRe(s: string): string {
|
|
230
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
interface ExecuteOptions {
|
|
234
|
+
/**
|
|
235
|
+
* Actually run the class-0 command.
|
|
236
|
+
*
|
|
237
|
+
* Default false, and that default is the control. `Run:` is an arbitrary
|
|
238
|
+
* shell string read out of a markdown file; without this flag the command
|
|
239
|
+
* is ECHOED and refused, so the operator sees exactly what would run before
|
|
240
|
+
* anything runs. `non-destructive-by-default` requires the confirmation to
|
|
241
|
+
* be this-turn and to name the exact object, and the echoed command is that
|
|
242
|
+
* object.
|
|
243
|
+
*/
|
|
244
|
+
confirm?: boolean;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function execute(
|
|
248
|
+
roadmapRoot: string,
|
|
249
|
+
id: string,
|
|
250
|
+
now: Date,
|
|
251
|
+
opts: ExecuteOptions = {},
|
|
252
|
+
): ExecuteResult {
|
|
253
|
+
const found = locate(roadmapRoot, id);
|
|
254
|
+
if (!found) {
|
|
255
|
+
return {
|
|
256
|
+
outcome: 'not-found',
|
|
257
|
+
report: `No open blocker with id '${id}'. Run \`agent-config gates --all\` for the list.\n`,
|
|
258
|
+
code: 1,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
const { blocker, file, rel } = found;
|
|
262
|
+
const cls = blocker_class(blocker);
|
|
263
|
+
|
|
264
|
+
if (cls === '3') {
|
|
265
|
+
// Byte-identical to today by construction: nothing is executed and
|
|
266
|
+
// nothing is written. The gate is rendered by the caller.
|
|
267
|
+
return {
|
|
268
|
+
outcome: 'rendered',
|
|
269
|
+
report: `${id} is class 3 — human-only. Nothing to execute; see \`agent-config gates\`.\n`,
|
|
270
|
+
code: 0,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
if (cls === '2') {
|
|
274
|
+
return { outcome: 'rendered', report: consentLine(blocker), code: 0 };
|
|
275
|
+
}
|
|
276
|
+
if (cls === '1') {
|
|
277
|
+
const ledger = path.join(path.dirname(path.dirname(roadmapRoot)), LEDGER_REL);
|
|
278
|
+
if (!fs.existsSync(ledger)) {
|
|
279
|
+
// The blocker's own prescription for a missing ledger: render the
|
|
280
|
+
// consent line INSTEAD of running. Not a degraded mode — the
|
|
281
|
+
// budget shape is an open decision and this is what open means.
|
|
282
|
+
return {
|
|
283
|
+
outcome: 'rendered',
|
|
284
|
+
report: consentLine(
|
|
285
|
+
blocker,
|
|
286
|
+
'class 1 with no budget ledger, so it renders instead of running',
|
|
287
|
+
),
|
|
288
|
+
code: 0,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
outcome: 'rendered',
|
|
293
|
+
report:
|
|
294
|
+
`${id} is class 1 and a ledger exists at ${LEDGER_REL}, but spend execution ` +
|
|
295
|
+
'is gated on `b-gate-budget-preauth`, which is open. Rendering instead of running.\n',
|
|
296
|
+
code: 0,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const command = commandOf(blocker);
|
|
301
|
+
if (command === '') {
|
|
302
|
+
// Unreachable through a linted tree — `lint_roadmap_blockers` fails a
|
|
303
|
+
// class-0 entry with no `Run:` — but reachable through an unlinted one,
|
|
304
|
+
// and guessing a command here is the one thing that must not happen.
|
|
305
|
+
return {
|
|
306
|
+
outcome: 'failed',
|
|
307
|
+
report: `${id} is class 0 but carries no **Run:** command. Nothing was executed.\n`,
|
|
308
|
+
code: 1,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const floor = hardFloorReason(command);
|
|
313
|
+
if (floor !== null) {
|
|
314
|
+
// Refused outright, not offered for confirmation. Class 0 is defined
|
|
315
|
+
// as reversible; a push, merge, deploy or delete is not, so this is a
|
|
316
|
+
// misclassified entry and the fix is to re-author the class — making
|
|
317
|
+
// the operator wave it through would move a Hard Floor onto a keypress.
|
|
318
|
+
return {
|
|
319
|
+
outcome: 'failed',
|
|
320
|
+
report:
|
|
321
|
+
`${id} is class 0 but its **Run:** command contains \`${floor}\`, which is a ` +
|
|
322
|
+
'Hard-Floor action under non-destructive-by-default. Class 0 means ' +
|
|
323
|
+
'deterministic, free and REVERSIBLE, so this entry is misclassified. ' +
|
|
324
|
+
'Nothing was executed; re-author the class.\n',
|
|
325
|
+
code: 1,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (opts.confirm !== true) {
|
|
330
|
+
// Echo-before-run. The command is arbitrary shell read out of a
|
|
331
|
+
// markdown field, so the operator sees the exact string before it runs
|
|
332
|
+
// and the confirmation is this-turn rather than implied by the id.
|
|
333
|
+
return {
|
|
334
|
+
outcome: 'rendered',
|
|
335
|
+
report:
|
|
336
|
+
`${id} is class 0 and would run, from the repo root:\n\n` +
|
|
337
|
+
` ${command}\n\n` +
|
|
338
|
+
'Nothing has been executed. Re-run with `--confirm` to execute it and ' +
|
|
339
|
+
'record the evidence at the blocker.\n',
|
|
340
|
+
code: 0,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const r = spawnSync(command, {
|
|
345
|
+
shell: true,
|
|
346
|
+
encoding: 'utf-8',
|
|
347
|
+
cwd: path.dirname(path.dirname(roadmapRoot)),
|
|
348
|
+
timeout: 120_000,
|
|
349
|
+
});
|
|
350
|
+
const output = `${r.stdout ?? ''}${r.stderr ?? ''}`;
|
|
351
|
+
if (r.error !== undefined || r.status !== 0) {
|
|
352
|
+
// `r.error` is read, not just the status: a spawn failure or a
|
|
353
|
+
// maxBuffer overflow leaves status null with empty output, and
|
|
354
|
+
// "exited null" with no diagnostic is illegible on the one path in
|
|
355
|
+
// this command that writes.
|
|
356
|
+
const why =
|
|
357
|
+
r.error !== undefined
|
|
358
|
+
? `could not run (${r.error.message})`
|
|
359
|
+
: `exited ${r.status ?? 'null'}`;
|
|
360
|
+
return {
|
|
361
|
+
outcome: 'failed',
|
|
362
|
+
report:
|
|
363
|
+
`${id}: \`${command}\` ${why}. The gate is unchanged — ` +
|
|
364
|
+
'a blocker resolved by a failed command is worse than an open one.\n' +
|
|
365
|
+
output.split(/\r?\n/).slice(0, 10).join('\n') +
|
|
366
|
+
'\n',
|
|
367
|
+
code: 1,
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
const when = today(now);
|
|
371
|
+
if (!appendEvidence(file, id, command, output, when)) {
|
|
372
|
+
return {
|
|
373
|
+
outcome: 'failed',
|
|
374
|
+
report:
|
|
375
|
+
`${id}: \`${command}\` succeeded, but its entry in ${rel} could not be ` +
|
|
376
|
+
'rewritten (no Status field found). Nothing was changed.\n',
|
|
377
|
+
code: 1,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
return {
|
|
381
|
+
outcome: 'resolved',
|
|
382
|
+
report:
|
|
383
|
+
`${id} resolved. Ran \`${command}\` (exit 0); evidence appended in ${rel} ` +
|
|
384
|
+
`and the status flipped to resolved ${when}.\n` +
|
|
385
|
+
// The dashboard is derived from the file this just rewrote, so it
|
|
386
|
+
// is now stale. Saying so is the same follow-up `renderResumed`
|
|
387
|
+
// already prints for its own file-move suggestion.
|
|
388
|
+
'Run `agent-config roadmap:progress` — the dashboard is derived from the ' +
|
|
389
|
+
'file this rewrote and is now stale.\n',
|
|
390
|
+
code: 0,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
export { execute, consentLine, commandOf, locate, appendEvidence, LEDGER_REL };
|
|
395
|
+
export type { ExecuteResult, Outcome };
|