@event4u/agent-config 14.7.0 → 14.9.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 +175 -390
- package/README.md +1 -1
- package/agents/templates/.ai-council.yml.example +34 -4
- package/dist/agent-src/commands/evals/git-pr-merge.json +13 -0
- package/dist/agent-src/commands/evals/roadmap.json +17 -1
- package/dist/agent-src/commands/pr/create.md +14 -2
- package/dist/agent-src/commands/pr/merge.md +383 -0
- package/dist/agent-src/commands/review/changes.md +95 -14
- package/dist/agent-src/commands/roadmap/process-full.md +157 -3
- package/dist/agent-src/commands/roadmap.md +1 -1
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +46 -36
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +37 -2
- package/dist/agent-src/contexts/execution/host-capability-manifest.md +77 -0
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +100 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +154 -2
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +80 -0
- package/dist/agent-src/rules/artifact-drafting-protocol.md +5 -1
- package/dist/agent-src/rules/decision-revisit-gate.md +65 -33
- package/dist/agent-src/rules/recurring-criticism.md +1 -1
- package/dist/agent-src/rules/roadmap-progress-sync.md +19 -0
- package/dist/agent-src/rules/source-confidentiality.md +1 -1
- package/dist/agent-src/rules/untrusted-input-defense.md +9 -3
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +7 -4
- package/dist/agent-src/scripts/dashboard_mode.ts +212 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +47 -48
- package/dist/agent-src/skills/adr-create/SKILL.md +124 -5
- package/dist/agent-src/skills/code-review/evals/evals.json +87 -46
- package/dist/agent-src/skills/decision-record/SKILL.md +61 -3
- package/dist/agent-src/skills/decision-review/SKILL.md +65 -0
- package/dist/agent-src/skills/humanizer/SKILL.md +39 -0
- package/dist/agent-src/skills/humanizer/evals/strip_fixtures.json +57 -0
- package/dist/agent-src/skills/humanizer/references/fixtures.md +54 -0
- package/dist/agent-src/skills/judge-spec-compliance/SKILL.md +154 -0
- package/dist/agent-src/skills/judge-synthesis/SKILL.md +45 -0
- package/dist/agent-src/skills/merge-conflicts/SKILL.md +26 -0
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +18 -18
- package/dist/agent-src/skills/security/SKILL.md +8 -0
- package/dist/agent-src/skills/security-audit/SKILL.md +12 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +28 -2
- package/dist/cli/registry.js +3 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-FBKL6JWT.js → chunk-77GR3VD4.js} +68 -43
- package/dist/cli-delegate/{chunk-JZ4NR2TW.js → chunk-7PRE6ZV2.js} +1 -1
- package/dist/cli-delegate/{chunk-UEZ7D35A.js → chunk-CYH5SP6M.js} +4 -23
- package/dist/cli-delegate/{chunk-Q26HT4QT.js → chunk-E46KPN6K.js} +251 -150
- package/dist/cli-delegate/{chunk-74E3TKWV.js → chunk-H4VIVD4C.js} +5 -5
- package/dist/cli-delegate/chunk-I3EIN2YG.js +36 -0
- package/dist/cli-delegate/{chunk-4VR4ORQ6.js → chunk-LPPWO5CE.js} +4 -3
- package/dist/cli-delegate/{chunk-7F3VYSM2.js → chunk-PLSG6UL5.js} +14 -14
- package/dist/cli-delegate/cmd_adr_effective.js +1162 -0
- package/dist/cli-delegate/cmd_conformance.js +101 -25
- package/dist/cli-delegate/cmd_converge.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +7 -7
- package/dist/cli-delegate/cmd_export.js +6 -6
- package/dist/cli-delegate/cmd_handoff.js +7 -6
- package/dist/cli-delegate/cmd_memory_get.js +2 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_preflight.js +5 -5
- package/dist/cli-delegate/cmd_prune.js +6 -6
- package/dist/cli-delegate/cmd_route_audit.js +6 -5
- package/dist/cli-delegate/cmd_route_explain.js +3 -3
- package/dist/cli-delegate/cmd_session_recycle.js +3 -3
- package/dist/cli-delegate/cmd_settings_get.js +4 -4
- package/dist/cli-delegate/cmd_settings_migrate.js +6 -6
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +6 -6
- package/dist/cli-delegate/cmd_uninstall.js +6 -6
- package/dist/cli-delegate/cmd_update.js +3 -3
- package/dist/cli-delegate/cmd_upgrade.js +3 -3
- package/dist/cli-delegate/cmd_validate.js +6 -6
- package/dist/cli-delegate/cmd_versions.js +3 -3
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +83 -30
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +11 -9
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +9 -7
- package/dist/hooks/dispatch.js +1295 -812
- package/dist/install/globalRuleLayers.js +169 -0
- package/dist/install/globalRuleLayers.js.map +1 -0
- package/dist/install/partitionEligibility.js +156 -0
- package/dist/install/partitionEligibility.js.map +1 -1
- package/dist/install/ruleLayerPartition.js +157 -0
- package/dist/install/ruleLayerPartition.js.map +1 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1358 -805
- package/dist/scripts/_lib/claude_plugin.js +162 -0
- package/dist/scripts/_lib/claude_plugin.js.map +1 -0
- package/dist/scripts/_lib/environment_detector.js +397 -0
- package/dist/scripts/_lib/environment_detector.js.map +1 -0
- package/dist/scripts/_lib/prune_empty_dirs.js +85 -0
- package/dist/scripts/_lib/prune_empty_dirs.js.map +1 -0
- package/dist/scripts/_lib/spawn_env.js +129 -0
- package/dist/scripts/_lib/spawn_env.js.map +1 -0
- package/dist/scripts/ai_council/modes.js +199 -0
- package/dist/scripts/ai_council/modes.js.map +1 -0
- package/dist/scripts/ai_council/transport_resolver.js +418 -0
- package/dist/scripts/ai_council/transport_resolver.js.map +1 -0
- package/dist/scripts/condense.js +52 -89
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/routes/wizard.js +3 -2
- package/dist/server/routes/wizard.js.map +1 -1
- package/dist/ui/assets/index-Cxgy63kz.js +26 -0
- package/dist/ui/assets/index-Cxgy63kz.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +45 -1
- package/docs/guidelines/agent-infra/false-green.md +47 -1
- package/docs/guidelines/agent-infra/traceability-field-mechanics.md +50 -0
- package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +17 -0
- package/package.json +7 -3
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +7 -4
- package/src/agent-src/scripts/dashboard_mode.ts +212 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +47 -48
- package/src/config/ci-local-parity.yml +21 -0
- package/src/config/estate-count-budget.json +13 -471
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +458 -12
- package/src/config/gate-violation-baselines.json +18 -8
- package/src/config/gitignore-block.txt +15 -2
- package/src/config/hook-latency-budget.json +5 -0
- package/src/config/publish-surface.json +60 -0
- package/src/config/requirements-trace-budget.json +32 -0
- package/src/config/rule-stub-ceilings.json +25 -4
- package/src/install/globalRuleLayers.ts +197 -0
- package/src/install/partitionEligibility.ts +177 -0
- package/src/install/ruleLayerPartition.ts +212 -0
- package/src/scripts/_cli/cmd_adr_effective.ts +484 -0
- package/src/scripts/_dispatch.bash +18 -0
- package/src/scripts/_lib/adr_frontmatter.test.ts +365 -0
- package/src/scripts/_lib/adr_frontmatter.ts +459 -0
- package/src/scripts/_lib/base_tree.ts +175 -0
- package/src/scripts/_lib/billing_grant.ts +165 -0
- package/src/scripts/_lib/billing_grant_cli.ts +123 -0
- package/src/scripts/_lib/council_fallback_posture.ts +53 -6
- package/src/scripts/_lib/council_fallback_wiring.ts +61 -1
- package/src/scripts/_lib/envelope_grounding.ts +11 -1
- package/src/scripts/_lib/host_capability.ts +59 -5
- package/src/scripts/_lib/layer_overlap_notice.ts +11 -2
- package/src/scripts/_lib/prune_empty_dirs.ts +80 -0
- package/src/scripts/_lib/quota_parked.ts +133 -0
- package/src/scripts/_lib/retrieval_sanitize.ts +62 -0
- package/src/scripts/_lib/review_telemetry.ts +196 -0
- package/src/scripts/_lib/subagent_response.ts +92 -0
- package/src/scripts/adr/evidence_census.ts +899 -0
- package/src/scripts/adr/regenerate_index.ts +25 -38
- package/src/scripts/adr_cite_check.ts +540 -39
- package/src/scripts/ai_council/clients.ts +12 -1
- package/src/scripts/ai_council/config.ts +72 -115
- package/src/scripts/ai_council/fallback_config.ts +19 -7
- package/src/scripts/ai_council/mid_flight_fallback.ts +88 -7
- package/src/scripts/ai_council/orchestrator.ts +2 -2
- package/src/scripts/ai_council/py_format.ts +124 -0
- package/src/scripts/ai_council/transport_resolver.ts +65 -2
- package/src/scripts/audit_adr_coverage.ts +134 -21
- package/src/scripts/bench_hook_latency.ts +204 -0
- package/src/scripts/build_archive_index.ts +34 -0
- package/src/scripts/check_adr_frontmatter.ts +574 -39
- package/src/scripts/check_council_pin_staleness.ts +300 -0
- package/src/scripts/check_estate_count.ts +420 -293
- package/src/scripts/check_gate_coverage.ts +91 -0
- package/src/scripts/check_hook_bundle_content.ts +219 -0
- package/src/scripts/check_new_adr_evidence.ts +529 -0
- package/src/scripts/check_no_stub_inventory_table.ts +292 -0
- package/src/scripts/check_pack_size.ts +234 -1
- package/src/scripts/check_publish_surface.ts +297 -0
- package/src/scripts/check_references.ts +18 -0
- package/src/scripts/check_requirements_trace.ts +378 -0
- package/src/scripts/check_rule_layer_partition.ts +531 -0
- package/src/scripts/check_secret_leak.ts +166 -12
- package/src/scripts/check_single_delivery.ts +137 -4
- package/src/scripts/check_standing_rule_delivery.ts +8 -2
- package/src/scripts/code_graph/cli.ts +44 -5
- package/src/scripts/code_graph/extract.ts +87 -0
- package/src/scripts/condense.ts +61 -91
- package/src/scripts/council_cli.ts +12 -16
- package/src/scripts/detect_ai_tells.ts +87 -0
- package/src/scripts/dispatch_r2_reviewer.ts +42 -0
- package/src/scripts/external_sources_denylist.json +4 -1
- package/src/scripts/generate_capabilities_index.ts +41 -0
- package/src/scripts/git_authorization_hook.ts +54 -4
- package/src/scripts/hooks/block_unauthorized_git.ts +41 -3
- package/src/scripts/hooks/dispatch_hook.ts +154 -187
- package/src/scripts/hooks/dispatch_issues.ts +38 -19
- package/src/scripts/hooks/fallback_yaml.ts +66 -0
- package/src/scripts/hooks/host_semantics.ts +21 -0
- package/src/scripts/hooks/injection_budget.ts +13 -3
- package/src/scripts/hooks/py_json_dumps.ts +103 -0
- package/src/scripts/hooks/run_continuation_hook.ts +3 -1
- package/src/scripts/hooks/state_io.ts +88 -1
- package/src/scripts/hooks/stdin_failure_policy.ts +96 -0
- package/src/scripts/hooks/subagent_ledger_hook.ts +76 -11
- package/src/scripts/injection_scan_hook.ts +257 -15
- package/src/scripts/lint_adr_sweep_routing.ts +365 -0
- package/src/scripts/lint_hook_manifest.ts +49 -0
- package/src/scripts/lint_provenance_vocabulary.ts +472 -4
- package/src/scripts/lint_workflow_security.ts +53 -10
- package/src/scripts/lint_workflow_security_allowlist.json +2 -18
- package/src/scripts/report_envelope_rate.ts +166 -0
- package/src/scripts/review_axis_report.ts +218 -0
- package/src/scripts/routing_doctor.ts +5 -1
- package/src/scripts/run_supervise.ts +46 -1
- package/src/scripts/schemas/evals.schema.json +5 -0
- package/src/scripts/sync_pr_branch.ts +170 -20
- package/dist/cli/commands/commands.candidates.test.js +0 -257
- package/dist/cli/commands/commands.candidates.test.js.map +0 -1
- package/dist/cli/commands/profiles.test.js +0 -88
- package/dist/cli/commands/profiles.test.js.map +0 -1
- package/dist/cli/commands/recordTriggerEval.test.js +0 -113
- package/dist/cli/commands/recordTriggerEval.test.js.map +0 -1
- package/dist/cli/configRoot.test.js +0 -97
- package/dist/cli/configRoot.test.js.map +0 -1
- package/dist/cli/mcp/content.test.js +0 -54
- package/dist/cli/mcp/content.test.js.map +0 -1
- package/dist/cli/mcp/dispatch.test.js +0 -108
- package/dist/cli/mcp/dispatch.test.js.map +0 -1
- package/dist/server/writeRoot.test.js +0 -91
- package/dist/server/writeRoot.test.js.map +0 -1
- package/dist/shared/capabilities.test.js +0 -39
- package/dist/shared/capabilities.test.js.map +0 -1
- package/dist/ui/assets/index-C1pBRm1f.js +0 -26
- package/dist/ui/assets/index-C1pBRm1f.js.map +0 -1
- package/dist/cli-delegate/{chunk-PPQO4NGR.js → chunk-6RJA2F5A.js} +3 -3
- package/dist/cli-delegate/{chunk-6EOCNQAM.js → chunk-E4XIZDIB.js} +0 -0
- package/dist/cli-delegate/{chunk-GJ5BVHXM.js → chunk-PMEKGDED.js} +3 -3
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Requirement → acceptance → evidence INVENTORY. Exits 0 always.
|
|
4
|
+
*
|
|
5
|
+
* ## Why a listing and not a gate
|
|
6
|
+
*
|
|
7
|
+
* A gate that can fail on day one reds the whole backlog, and this tree has
|
|
8
|
+
* recorded that failure once already: `road-to-plan-gates-measurement` sits in
|
|
9
|
+
* `later/` because its counter measured **0** — its own text records "Measured
|
|
10
|
+
* at parking time: 0" and an advisory window of 0 of 10 gated PRs. So this
|
|
11
|
+
* script is deliberately a *reader*: it prints the distribution and returns 0
|
|
12
|
+
* whatever it finds, including when it finds nothing.
|
|
13
|
+
*
|
|
14
|
+
* The name carries a `check_` prefix because that is what the gate-population
|
|
15
|
+
* classifier keys on and the script IS registered for coverage; the exit code is
|
|
16
|
+
* what makes it a listing rather than an enforcement.
|
|
17
|
+
*
|
|
18
|
+
* ## Three populations, reported separately
|
|
19
|
+
*
|
|
20
|
+
* The council's correction, and the reason this is not one number. Step 2.1
|
|
21
|
+
* originally named three roadmaps to dogfood; all three were archived by sibling
|
|
22
|
+
* pull requests in the same drain run, so the named set was gone before any
|
|
23
|
+
* window opened. Merging one PR before another would have changed the reported
|
|
24
|
+
* result with no roadmap edited — which makes merge order part of the
|
|
25
|
+
* experiment.
|
|
26
|
+
*
|
|
27
|
+
* - `fixture` — synthetic inputs, for parser and resolver mechanics. Never
|
|
28
|
+
* counts toward adoption: a maintainer-authored fixture is
|
|
29
|
+
* evidence about the reader, not about uptake.
|
|
30
|
+
* - `cohort` — a frozen manifest of files under study, active or archived.
|
|
31
|
+
* Experimental history; may include archived members.
|
|
32
|
+
* - `live` — the current ACTIVE corpus only. This is the compliance
|
|
33
|
+
* number, and the only one a ratchet may read.
|
|
34
|
+
*
|
|
35
|
+
* Collapsing them is how a zero from non-adoption becomes indistinguishable
|
|
36
|
+
* from a zero from non-compliance.
|
|
37
|
+
*
|
|
38
|
+
* ## What `unresolved` means, and when it is evaluated
|
|
39
|
+
*
|
|
40
|
+
* At the CURRENT head, always. A ref that resolved when it was declared and no
|
|
41
|
+
* longer resolves is `unresolved`, and that is the intended reading rather than
|
|
42
|
+
* a defect in it — deleted evidence is what an inventory exists to surface. The
|
|
43
|
+
* consequence, stated rather than discovered: a completed roadmap can move from
|
|
44
|
+
* resolved to unresolved with no roadmap edit.
|
|
45
|
+
*
|
|
46
|
+
* Usage:
|
|
47
|
+
* ./scripts-run src/scripts/check_requirements_trace [--json] [--dir P]
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
import * as fs from 'node:fs';
|
|
51
|
+
import * as os from 'node:os';
|
|
52
|
+
import * as path from 'node:path';
|
|
53
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
54
|
+
|
|
55
|
+
import { runGateCli, runSelfTest, type SelfTestCase } from './_lib/gate_self_test.js';
|
|
56
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
57
|
+
|
|
58
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
59
|
+
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
60
|
+
const ROADMAP_DIR = path.join(REPO_ROOT, 'agents', 'roadmaps');
|
|
61
|
+
|
|
62
|
+
/** The claim-ledger kebab slug, reused rather than invented. */
|
|
63
|
+
const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
64
|
+
|
|
65
|
+
export interface TraceRow {
|
|
66
|
+
requirement_id: string;
|
|
67
|
+
acceptance_id: string;
|
|
68
|
+
evidence_refs: string[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface FileTrace {
|
|
72
|
+
file: string;
|
|
73
|
+
rows: TraceRow[];
|
|
74
|
+
/** `[AC:<id>]` ids found on `verify:` lines. */
|
|
75
|
+
ac_annotations: string[];
|
|
76
|
+
/** Rows whose ids do not match the reused slug grammar. */
|
|
77
|
+
malformed: string[];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Parse the `traceability:` rows out of a roadmap.
|
|
82
|
+
*
|
|
83
|
+
* A deliberately small reader over the block form § 2c fixes, and NOT a YAML
|
|
84
|
+
* parser: the block may appear inside a blockquote (this roadmap dogfoods it
|
|
85
|
+
* that way) and a real parser would need the whole document to be YAML, which a
|
|
86
|
+
* roadmap is not. Leading `>` and whitespace are stripped per line.
|
|
87
|
+
*/
|
|
88
|
+
export function parseTrace(text: string): { rows: TraceRow[]; malformed: string[] } {
|
|
89
|
+
const lines = text.split('\n').map((l) => l.replace(/^\s*>\s?/, ''));
|
|
90
|
+
const rows: TraceRow[] = [];
|
|
91
|
+
const malformed: string[] = [];
|
|
92
|
+
let inBlock = false;
|
|
93
|
+
let cur: Partial<TraceRow> | null = null;
|
|
94
|
+
let inRefs = false;
|
|
95
|
+
|
|
96
|
+
const flush = (): void => {
|
|
97
|
+
if (cur === null) return;
|
|
98
|
+
const { requirement_id: req, acceptance_id: acc } = cur;
|
|
99
|
+
if (req !== undefined && acc !== undefined) {
|
|
100
|
+
if (!SLUG.test(req)) malformed.push(`requirement_id \`${req}\` is not a kebab slug`);
|
|
101
|
+
if (!SLUG.test(acc)) malformed.push(`acceptance_id \`${acc}\` is not a kebab slug`);
|
|
102
|
+
rows.push({ requirement_id: req, acceptance_id: acc, evidence_refs: cur.evidence_refs ?? [] });
|
|
103
|
+
}
|
|
104
|
+
cur = null;
|
|
105
|
+
inRefs = false;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
for (const raw of lines) {
|
|
109
|
+
if (/^\s*traceability:\s*$/.test(raw)) {
|
|
110
|
+
inBlock = true;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (!inBlock) continue;
|
|
114
|
+
// A fence or a non-indented non-list line ends the block.
|
|
115
|
+
if (/^\s*```/.test(raw) || (raw.trim() !== '' && !/^\s/.test(raw))) {
|
|
116
|
+
flush();
|
|
117
|
+
inBlock = false;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const item = /^\s*-\s+requirement_id:\s*(\S+)\s*$/.exec(raw);
|
|
121
|
+
if (item) {
|
|
122
|
+
flush();
|
|
123
|
+
cur = { requirement_id: item[1] as string, evidence_refs: [] };
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (cur === null) continue;
|
|
127
|
+
const acc = /^\s*acceptance_id:\s*(\S+)\s*$/.exec(raw);
|
|
128
|
+
if (acc) {
|
|
129
|
+
cur.acceptance_id = acc[1] as string;
|
|
130
|
+
inRefs = false;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (/^\s*evidence_refs:\s*$/.test(raw)) {
|
|
134
|
+
inRefs = true;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const ref = /^\s*-\s+(\S+)\s*$/.exec(raw);
|
|
138
|
+
if (ref && inRefs) {
|
|
139
|
+
(cur.evidence_refs ??= []).push(ref[1] as string);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
flush();
|
|
143
|
+
return { rows, malformed };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** `[AC:<id>]` ids on `verify:` lines — the first structural reader of them. */
|
|
147
|
+
export function parseAcAnnotations(text: string): string[] {
|
|
148
|
+
const out: string[] = [];
|
|
149
|
+
for (const m of text.matchAll(/verify:\s*\[AC:([a-z0-9-]+)\]/g)) out.push(m[1] as string);
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface Summary {
|
|
154
|
+
files: number;
|
|
155
|
+
rows: number;
|
|
156
|
+
/** Rows with at least one evidence ref. */
|
|
157
|
+
linked: number;
|
|
158
|
+
/** Rows with no evidence ref at all. */
|
|
159
|
+
unlinked: number;
|
|
160
|
+
/** Evidence refs that do not resolve at the current head. */
|
|
161
|
+
unresolved: number;
|
|
162
|
+
refs: number;
|
|
163
|
+
ac_annotations: number;
|
|
164
|
+
/** `[AC:…]` ids with no matching `acceptance_id` in the same file. */
|
|
165
|
+
dangling_ac: number;
|
|
166
|
+
malformed: number;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function summarise(traces: readonly FileTrace[], repoRoot: string): Summary {
|
|
170
|
+
let rows = 0;
|
|
171
|
+
let linked = 0;
|
|
172
|
+
let unlinked = 0;
|
|
173
|
+
let refs = 0;
|
|
174
|
+
let unresolved = 0;
|
|
175
|
+
let dangling = 0;
|
|
176
|
+
let malformed = 0;
|
|
177
|
+
for (const t of traces) {
|
|
178
|
+
rows += t.rows.length;
|
|
179
|
+
malformed += t.malformed.length;
|
|
180
|
+
const accIds = new Set(t.rows.map((r) => r.acceptance_id));
|
|
181
|
+
for (const id of t.ac_annotations) if (!accIds.has(id)) dangling += 1;
|
|
182
|
+
for (const r of t.rows) {
|
|
183
|
+
if (r.evidence_refs.length === 0) unlinked += 1;
|
|
184
|
+
else linked += 1;
|
|
185
|
+
for (const ref of r.evidence_refs) {
|
|
186
|
+
refs += 1;
|
|
187
|
+
// Evaluated at the CURRENT head. A ref that has been deleted is
|
|
188
|
+
// unresolved, which is the reading this inventory exists for.
|
|
189
|
+
if (!fs.existsSync(path.join(repoRoot, ref.replace(/:\d+(-\d+)?$/, '')))) {
|
|
190
|
+
unresolved += 1;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
files: traces.length,
|
|
197
|
+
rows,
|
|
198
|
+
linked,
|
|
199
|
+
unlinked,
|
|
200
|
+
unresolved,
|
|
201
|
+
refs,
|
|
202
|
+
ac_annotations: traces.reduce((n, t) => n + t.ac_annotations.length, 0),
|
|
203
|
+
dangling_ac: dangling,
|
|
204
|
+
malformed,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function readLive(dir: string): FileTrace[] {
|
|
209
|
+
let names: string[] = [];
|
|
210
|
+
try {
|
|
211
|
+
names = fs.readdirSync(dir).filter((n) => n.endsWith('.md') && n !== 'README.md').sort();
|
|
212
|
+
} catch {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
const out: FileTrace[] = [];
|
|
216
|
+
for (const n of names) {
|
|
217
|
+
const text = fs.readFileSync(path.join(dir, n), 'utf-8');
|
|
218
|
+
const { rows, malformed } = parseTrace(text);
|
|
219
|
+
const ac = parseAcAnnotations(text);
|
|
220
|
+
if (rows.length === 0 && ac.length === 0) continue;
|
|
221
|
+
out.push({ file: `agents/roadmaps/${n}`, rows, ac_annotations: ac, malformed });
|
|
222
|
+
}
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Floors for `--self-test`, declared here so a truncation is a visible diff. */
|
|
227
|
+
const SELF_TEST_MIN_CASES = 5;
|
|
228
|
+
const SELF_TEST_MIN_REJECT = 1;
|
|
229
|
+
const SCRIPT_REL = 'src/scripts/check_requirements_trace.ts';
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* `--self-test` for a gate that exits 0 ALWAYS — which needs explaining, since
|
|
233
|
+
* the harness's whole premise is that a gate proves it can go red.
|
|
234
|
+
*
|
|
235
|
+
* This one cannot, by design, so the reject case is the harness's OWN floor:
|
|
236
|
+
* a truncated suite must fail. What the accept cases prove instead is that the
|
|
237
|
+
* reader DISCRIMINATES — the same discrimination a failing exit would
|
|
238
|
+
* demonstrate elsewhere, asserted on the output rather than the exit code.
|
|
239
|
+
* Stated rather than left implicit, because a self-test whose cases all accept
|
|
240
|
+
* is exactly the degenerate pass the harness warns about.
|
|
241
|
+
*/
|
|
242
|
+
function _fixture(name: string, body: string, args: readonly string[] = []): number {
|
|
243
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), `reqtrace-st-${name}-`));
|
|
244
|
+
const rm = path.join(dir, 'agents', 'roadmaps');
|
|
245
|
+
fs.mkdirSync(rm, { recursive: true });
|
|
246
|
+
fs.writeFileSync(path.join(rm, 'a.md'), body, 'utf-8');
|
|
247
|
+
return runGateCli(REPO_ROOT, SCRIPT_REL, ['--dir', rm, ...args], dir);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const _ROW = 'traceability:\n - requirement_id: r-one\n acceptance_id: a-one\n';
|
|
251
|
+
|
|
252
|
+
function selfTest(): number {
|
|
253
|
+
const cases: SelfTestCase[] = [
|
|
254
|
+
{ name: 'a declared row is accepted', expect: 'accept', run: () => _fixture('row', _ROW) },
|
|
255
|
+
{
|
|
256
|
+
name: 'a dangling [AC:] annotation still exits 0 — this is a listing, not a gate',
|
|
257
|
+
expect: 'accept',
|
|
258
|
+
run: () => _fixture('dangling', `${_ROW}\n verify: [AC:nowhere] x\n`),
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: 'an empty corpus exits 0 and does not read as a clean bill',
|
|
262
|
+
expect: 'accept',
|
|
263
|
+
run: () => _fixture('empty', '# no traceability block here\n'),
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
name: 'a non-slug id exits 0 and is reported, never enforced',
|
|
267
|
+
expect: 'accept',
|
|
268
|
+
run: () => _fixture('slug', 'traceability:\n - requirement_id: Not_A_Slug\n acceptance_id: a\n'),
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
name: 'an unknown argument is refused — the one non-zero path there is',
|
|
272
|
+
expect: 'reject',
|
|
273
|
+
run: () => _fixture('badarg', _ROW, ['--not-a-flag']),
|
|
274
|
+
},
|
|
275
|
+
];
|
|
276
|
+
return runSelfTest({
|
|
277
|
+
gate: 'check_requirements_trace',
|
|
278
|
+
cases,
|
|
279
|
+
minCases: SELF_TEST_MIN_CASES,
|
|
280
|
+
minRejectCases: SELF_TEST_MIN_REJECT,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
285
|
+
if (argv.includes('--self-test')) return selfTest();
|
|
286
|
+
for (let k = 0; k < argv.length; k += 1) {
|
|
287
|
+
const a = argv[k] as string;
|
|
288
|
+
if (a === '--json') continue;
|
|
289
|
+
if (a === '--dir') {
|
|
290
|
+
k += 1;
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (a === '--self-test') continue;
|
|
294
|
+
process.stderr.write(`check_requirements_trace: unrecognized argument: ${a}\n`);
|
|
295
|
+
return 2;
|
|
296
|
+
}
|
|
297
|
+
const i = argv.indexOf('--dir');
|
|
298
|
+
const dir = i >= 0 && i + 1 < argv.length ? path.resolve(argv[i + 1] as string) : ROADMAP_DIR;
|
|
299
|
+
const root = i >= 0 ? path.dirname(path.dirname(dir)) : REPO_ROOT;
|
|
300
|
+
|
|
301
|
+
let corpus = 0;
|
|
302
|
+
try {
|
|
303
|
+
corpus = fs.readdirSync(dir).filter((n) => n.endsWith('.md') && n !== 'README.md').length;
|
|
304
|
+
} catch {
|
|
305
|
+
corpus = 0;
|
|
306
|
+
}
|
|
307
|
+
const live = readLive(dir);
|
|
308
|
+
const s = summarise(live, root);
|
|
309
|
+
|
|
310
|
+
if (argv.includes('--json')) {
|
|
311
|
+
process.stdout.write(`${JSON.stringify({ population: 'live', corpus, ...s, files_detail: live }, null, 2)}\n`);
|
|
312
|
+
return 0;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
process.stdout.write(`scanned: ${String(corpus)}\n`);
|
|
316
|
+
try {
|
|
317
|
+
assertScanned({
|
|
318
|
+
gate: 'check_requirements_trace',
|
|
319
|
+
scanned: corpus,
|
|
320
|
+
units: 'roadmap file(s)',
|
|
321
|
+
roots: ['agents/roadmaps'],
|
|
322
|
+
});
|
|
323
|
+
} catch (err) {
|
|
324
|
+
if (err instanceof DeadScopeError) {
|
|
325
|
+
// Reported, and STILL exit 0. A dead scope is a real finding about
|
|
326
|
+
// this reader, and turning it into a non-zero exit would make the
|
|
327
|
+
// listing a gate — which is the one thing it must not become.
|
|
328
|
+
process.stderr.write(`⚠️ ${err.message}\n`);
|
|
329
|
+
} else {
|
|
330
|
+
throw err;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
process.stdout.write('\n| Roadmap | requirement_id | acceptance_id | evidence refs | unresolved |\n');
|
|
335
|
+
process.stdout.write('|---|---|---|---|---|\n');
|
|
336
|
+
if (live.length === 0) {
|
|
337
|
+
process.stdout.write('| _none declared_ | — | — | — | — |\n');
|
|
338
|
+
}
|
|
339
|
+
for (const t of live) {
|
|
340
|
+
for (const r of t.rows) {
|
|
341
|
+
const bad = r.evidence_refs.filter(
|
|
342
|
+
(ref) => !fs.existsSync(path.join(root, ref.replace(/:\d+(-\d+)?$/, ''))),
|
|
343
|
+
).length;
|
|
344
|
+
process.stdout.write(
|
|
345
|
+
`| ${t.file} | ${r.requirement_id} | ${r.acceptance_id} | ` +
|
|
346
|
+
`${String(r.evidence_refs.length)} | ${String(bad)} |\n`,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
for (const id of t.ac_annotations) {
|
|
350
|
+
const known = t.rows.some((r) => r.acceptance_id === id);
|
|
351
|
+
if (!known) {
|
|
352
|
+
process.stdout.write(`| ${t.file} | — | **[AC:${id}] dangling** | — | — |\n`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// The line a ratchet reads, and the line a later reading is compared to.
|
|
358
|
+
// Integers with the corpus they were computed over, never a verdict: "
|
|
359
|
+
// traceability is patchy" cannot be compared to the next reading.
|
|
360
|
+
process.stdout.write(
|
|
361
|
+
`\nlive: rows ${String(s.rows)} · linked ${String(s.linked)} · unlinked ${String(s.unlinked)} · ` +
|
|
362
|
+
`refs ${String(s.refs)} · unresolved ${String(s.unresolved)} · ` +
|
|
363
|
+
`ac_annotations ${String(s.ac_annotations)} · dangling_ac ${String(s.dangling_ac)} · ` +
|
|
364
|
+
`malformed ${String(s.malformed)} · over ${String(corpus)} active roadmap(s), ` +
|
|
365
|
+
`${String(s.files)} declaring\n`,
|
|
366
|
+
);
|
|
367
|
+
process.stdout.write(
|
|
368
|
+
' POPULATION: live (active corpus only). A `fixture` population tests this reader\n' +
|
|
369
|
+
' and never counts toward adoption; a frozen `cohort` may include archived files\n' +
|
|
370
|
+
' and is experimental history. Collapsing the three is how a zero from\n' +
|
|
371
|
+
' non-adoption becomes indistinguishable from a zero from non-compliance.\n',
|
|
372
|
+
);
|
|
373
|
+
return 0;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
377
|
+
process.exit(main());
|
|
378
|
+
}
|