@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
|
@@ -1,20 +1,4 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "Legitimate workflow security findings. Hard-capped at 20 entries (lint_workflow_security.py ALLOWLIST_CAP). Exceeding the cap signals the linter is wrong, not the content
|
|
3
|
-
"findings": [
|
|
4
|
-
{
|
|
5
|
-
"workflow": "bench-drift.yml",
|
|
6
|
-
"rule": "mutable-action-tag",
|
|
7
|
-
"reason": "marocchino/sticky-pull-request-comment@v2 — sticky PR comment for bench drift reporting; no code execution, output-only action. SHA-pinning tracked in road-to-security-hardening P3."
|
|
8
|
-
},
|
|
9
|
-
{
|
|
10
|
-
"workflow": "skill-lint.yml",
|
|
11
|
-
"rule": "mutable-action-tag",
|
|
12
|
-
"reason": "marocchino/sticky-pull-request-comment@v2 — sticky PR comment for skill-lint reporting; same rationale as bench-drift.yml. SHA-pinning tracked in road-to-security-hardening P3."
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"workflow": "cloud-release.yml",
|
|
16
|
-
"rule": "mutable-action-tag",
|
|
17
|
-
"reason": "softprops/action-gh-release@v2 — creates GitHub releases; runs only on push to main (not on pull_request_target), no untrusted input. SHA-pinning tracked in road-to-security-hardening P3."
|
|
18
|
-
}
|
|
19
|
-
]
|
|
2
|
+
"_comment": "Legitimate workflow security findings. Hard-capped at 20 entries (lint_workflow_security.py ALLOWLIST_CAP). Exceeding the cap signals the linter is wrong, not the content \u2014 tighten the heuristic, do not grow this list (autonomous-execution allowlist-growth antipattern). Each entry MUST cite why the finding is acceptable in `reason`. EMPTIED 2026-08-22 (road-to-ci-supply-chain-integrity 0.2). It held three mutable-action-tag exemptions for marocchino/sticky-pull-request-comment and softprops/action-gh-release. All three are now pinned to full commit SHAs, so the findings they excused no longer exist and the entries matched nothing \u2014 lint_workflow_security reports \"0 allowlisted\". The roadmap step asked to REPAIR their `reason` text, each of which closed by naming an archived roadmap phase as the tracker for SHA-pinning; that phase is a git-discipline hook and its file mentions pinning nowhere, so the pointer dangled. Deleting a dead exemption is the honest repair \u2014 rewriting the reason on an entry that excuses nothing would have kept the allowlist looking load-bearing. The dangling pointer is also WHY the three survived: each one read as deferred rather than open. The full trace lives in the archived roadmap's completion note.",
|
|
3
|
+
"findings": []
|
|
20
4
|
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Publish `valid_envelope_rate` — three numbers, never one.
|
|
4
|
+
*
|
|
5
|
+
* `road-to-subagent-envelope-adoption` step 1.2: a rate with no denominator is
|
|
6
|
+
* a claim, not a measurement. So every line carries the rate, the window
|
|
7
|
+
* bounds, the stop count, AND the path it read — because the ledger is
|
|
8
|
+
* gitignored and machine-local, so a figure with no path is not reproducible
|
|
9
|
+
* even in principle.
|
|
10
|
+
*
|
|
11
|
+
* Named `report_` and not `check_` deliberately: this publishes a number and
|
|
12
|
+
* gates nothing. `gate_population.matchesGatePattern` classifies on a
|
|
13
|
+
* `lint|check|audit|skill|verify_` prefix, and a reporting script wearing a gate
|
|
14
|
+
* prefix would be required to carry a coverage floor it has no business having.
|
|
15
|
+
*
|
|
16
|
+
* ## Rows this counts, and rows it refuses
|
|
17
|
+
*
|
|
18
|
+
* Only `subagent_stop` rows carrying a POST-SPLIT `envelope_parse` value. The
|
|
19
|
+
* retired `absent` value collapsed `no_message` and `no_envelope` into one
|
|
20
|
+
* bucket, so a rate computed across the boundary would mix two vocabularies and
|
|
21
|
+
* report a number that is not about either. Rows with `absent` are excluded and
|
|
22
|
+
* counted separately, so the exclusion is visible rather than silent.
|
|
23
|
+
*
|
|
24
|
+
* Exit 0 always: a report that fails is a gate, and this is not one.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import * as fs from 'node:fs';
|
|
28
|
+
import * as path from 'node:path';
|
|
29
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
30
|
+
|
|
31
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
32
|
+
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
33
|
+
const LEDGER_DIR = path.join(REPO_ROOT, 'agents', 'runtime', 'state', 'subagent-ledger');
|
|
34
|
+
|
|
35
|
+
export interface RateReport {
|
|
36
|
+
ledger_paths: string[];
|
|
37
|
+
stops: number;
|
|
38
|
+
ok: number;
|
|
39
|
+
rate: number;
|
|
40
|
+
window_start: string | null;
|
|
41
|
+
window_end: string | null;
|
|
42
|
+
by_verdict: Record<string, number>;
|
|
43
|
+
excluded_absent: number;
|
|
44
|
+
by_agent_type: Record<string, number>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function computeRate(lines: readonly string[], paths: readonly string[]): RateReport {
|
|
48
|
+
const by: Record<string, number> = {};
|
|
49
|
+
const types: Record<string, number> = {};
|
|
50
|
+
let stops = 0;
|
|
51
|
+
let absent = 0;
|
|
52
|
+
let start: string | null = null;
|
|
53
|
+
let end: string | null = null;
|
|
54
|
+
for (const line of lines) {
|
|
55
|
+
let r: Record<string, unknown>;
|
|
56
|
+
try {
|
|
57
|
+
r = JSON.parse(line) as Record<string, unknown>;
|
|
58
|
+
} catch {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (r['event'] !== 'subagent_stop') continue;
|
|
62
|
+
const verdict = r['envelope_parse'];
|
|
63
|
+
if (typeof verdict !== 'string') continue;
|
|
64
|
+
if (verdict === 'absent') {
|
|
65
|
+
absent += 1;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
stops += 1;
|
|
69
|
+
by[verdict] = (by[verdict] ?? 0) + 1;
|
|
70
|
+
const at = typeof r['agent_type'] === 'string' ? r['agent_type'] : '(null)';
|
|
71
|
+
types[at] = (types[at] ?? 0) + 1;
|
|
72
|
+
const ts = r['ts'];
|
|
73
|
+
if (typeof ts === 'string') {
|
|
74
|
+
if (start === null || ts < start) start = ts;
|
|
75
|
+
if (end === null || ts > end) end = ts;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const ok = by['ok'] ?? 0;
|
|
79
|
+
return {
|
|
80
|
+
ledger_paths: [...paths],
|
|
81
|
+
stops,
|
|
82
|
+
ok,
|
|
83
|
+
// 0/0 is 0, not NaN: an empty ledger has a rate of zero valid envelopes
|
|
84
|
+
// and a denominator that says so on the same line.
|
|
85
|
+
rate: stops === 0 ? 0 : ok / stops,
|
|
86
|
+
window_start: start,
|
|
87
|
+
window_end: end,
|
|
88
|
+
by_verdict: by,
|
|
89
|
+
excluded_absent: absent,
|
|
90
|
+
by_agent_type: types,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function readLedger(dir: string): { lines: string[]; paths: string[] } {
|
|
95
|
+
let names: string[] = [];
|
|
96
|
+
try {
|
|
97
|
+
names = fs.readdirSync(dir).filter((n) => n.endsWith('.jsonl')).sort();
|
|
98
|
+
} catch {
|
|
99
|
+
return { lines: [], paths: [] };
|
|
100
|
+
}
|
|
101
|
+
const lines: string[] = [];
|
|
102
|
+
const paths: string[] = [];
|
|
103
|
+
for (const n of names) {
|
|
104
|
+
const abs = path.join(dir, n);
|
|
105
|
+
paths.push(path.relative(REPO_ROOT, abs).split(path.sep).join('/'));
|
|
106
|
+
lines.push(...fs.readFileSync(abs, 'utf-8').split('\n'));
|
|
107
|
+
}
|
|
108
|
+
return { lines, paths };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
112
|
+
// `--dir` so the report is reproducible against a ledger that is not this
|
|
113
|
+
// checkout's. The ledger is gitignored and machine-local, so without it the
|
|
114
|
+
// only readable ledger is the one beside the running copy — and a worktree
|
|
115
|
+
// has none, which would make the figure unobtainable exactly where the work
|
|
116
|
+
// happens.
|
|
117
|
+
const i = argv.indexOf('--dir');
|
|
118
|
+
const dir = i >= 0 && i + 1 < argv.length ? path.resolve(argv[i + 1] as string) : LEDGER_DIR;
|
|
119
|
+
const { lines, paths } = readLedger(dir);
|
|
120
|
+
const r = computeRate(lines, paths);
|
|
121
|
+
if (argv.includes('--json')) {
|
|
122
|
+
process.stdout.write(`${JSON.stringify(r, null, 2)}\n`);
|
|
123
|
+
return 0;
|
|
124
|
+
}
|
|
125
|
+
if (r.ledger_paths.length === 0) {
|
|
126
|
+
// Not a failure, and not a rate either. The ledger is gitignored, so a
|
|
127
|
+
// clean clone has none — reporting 0 % there would be a number about
|
|
128
|
+
// nothing.
|
|
129
|
+
process.stdout.write(
|
|
130
|
+
`valid_envelope_rate: NO LEDGER at ${dir} — ` +
|
|
131
|
+
'the ledger is gitignored and machine-local, so this is expected in a fresh ' +
|
|
132
|
+
'clone and is not a measurement of zero.\n',
|
|
133
|
+
);
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
const pct = (r.rate * 100).toFixed(2);
|
|
137
|
+
process.stdout.write(
|
|
138
|
+
`valid_envelope_rate: ${pct}% — ${String(r.ok)} ok of ${String(r.stops)} stops, ` +
|
|
139
|
+
`window ${r.window_start ?? '?'} → ${r.window_end ?? '?'}, ` +
|
|
140
|
+
`read from ${r.ledger_paths.join(', ')}\n`,
|
|
141
|
+
);
|
|
142
|
+
const verdicts = Object.entries(r.by_verdict)
|
|
143
|
+
.sort((a, b) => b[1] - a[1])
|
|
144
|
+
.map(([k, v]) => `${k} ${String(v)}`)
|
|
145
|
+
.join(' · ');
|
|
146
|
+
process.stdout.write(` verdicts: ${verdicts}\n`);
|
|
147
|
+
process.stdout.write(
|
|
148
|
+
` excluded (retired \`absent\` vocabulary): ${String(r.excluded_absent)}\n`,
|
|
149
|
+
);
|
|
150
|
+
const comp = Object.entries(r.by_agent_type)
|
|
151
|
+
.sort((a, b) => b[1] - a[1])
|
|
152
|
+
.map(([k, v]) => `${k} ${String(v)}`)
|
|
153
|
+
.join(' · ');
|
|
154
|
+
process.stdout.write(` agent-type composition: ${comp}\n`);
|
|
155
|
+
process.stdout.write(
|
|
156
|
+
' CAVEAT: machine-local. This ledger is one machine\'s drain traffic, so the\n' +
|
|
157
|
+
' composition above is the shape of THIS traffic and the rate does not\n' +
|
|
158
|
+
' generalise. Published with the composition rather than alone, so the limit\n' +
|
|
159
|
+
' is readable instead of hidden.\n',
|
|
160
|
+
);
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
165
|
+
process.exit(main());
|
|
166
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Report the spec axis's measured effect on the review surface.
|
|
4
|
+
*
|
|
5
|
+
* The question this answers, and the only one: over the observation window,
|
|
6
|
+
* how often did the spec axis change the recommendation the craft judges would
|
|
7
|
+
* have produced alone? The honest answers include **zero**, and **"nothing was
|
|
8
|
+
* recorded"** — which is a third answer, not a variant of zero. A report that
|
|
9
|
+
* printed `0 changed` over an empty ledger would read as evidence the axis is
|
|
10
|
+
* useless, when it is evidence that nothing has been observed yet; those two
|
|
11
|
+
* states are separated everywhere below.
|
|
12
|
+
*
|
|
13
|
+
* Three populations, never collapsed (the same model `check_pack_size` and
|
|
14
|
+
* `check_requirements_trace` use):
|
|
15
|
+
* observed — lines in the window
|
|
16
|
+
* comparable — lines where the axis ran, so a counterfactual exists
|
|
17
|
+
* changed — comparable lines where it flipped the recommendation
|
|
18
|
+
*
|
|
19
|
+
* `comparable` is the denominator, never `observed`: a run where the axis was
|
|
20
|
+
* unreachable carries `spec_axis_effect: null` and contributes to neither
|
|
21
|
+
* numerator nor denominator. Dividing by `observed` would report a low rate for
|
|
22
|
+
* an axis that was never given the chance to act, which is the mis-attribution
|
|
23
|
+
* this split exists to prevent.
|
|
24
|
+
*
|
|
25
|
+
* Exit code is 0 on every outcome including the empty one. This is a REPORT,
|
|
26
|
+
* not a gate — there is no threshold it could honestly enforce with the window
|
|
27
|
+
* this repo has, and a gate over an unmeasured surface fails for the wrong
|
|
28
|
+
* reason. `--self-test` exercises the pure summariser and exits non-zero on a
|
|
29
|
+
* mismatch.
|
|
30
|
+
*/
|
|
31
|
+
import * as fs from 'node:fs';
|
|
32
|
+
import * as path from 'node:path';
|
|
33
|
+
|
|
34
|
+
import type { ReviewLine } from './_lib/review_telemetry.js';
|
|
35
|
+
|
|
36
|
+
export interface ReviewAxisSummary {
|
|
37
|
+
observed: number;
|
|
38
|
+
/** Lines where the axis ran, i.e. a counterfactual exists. */
|
|
39
|
+
comparable: number;
|
|
40
|
+
changed: number;
|
|
41
|
+
unchanged: number;
|
|
42
|
+
/** Lines where the axis could not run at all. */
|
|
43
|
+
unreachable: number;
|
|
44
|
+
/** Reviews that ran with no criteria supplied — the axis reached, nothing to measure against. */
|
|
45
|
+
no_criteria: number;
|
|
46
|
+
/** Criteria handed over and unreadable — an error state, never folded into no_criteria. */
|
|
47
|
+
unparseable: number;
|
|
48
|
+
/** Earliest / latest `at` seen, or null over an empty window. */
|
|
49
|
+
window_start: string | null;
|
|
50
|
+
window_end: string | null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Pure summariser — the unit under `--self-test`. */
|
|
54
|
+
export function summarise(lines: readonly ReviewLine[]): ReviewAxisSummary {
|
|
55
|
+
const s: ReviewAxisSummary = {
|
|
56
|
+
observed: 0,
|
|
57
|
+
comparable: 0,
|
|
58
|
+
changed: 0,
|
|
59
|
+
unchanged: 0,
|
|
60
|
+
unreachable: 0,
|
|
61
|
+
no_criteria: 0,
|
|
62
|
+
unparseable: 0,
|
|
63
|
+
window_start: null,
|
|
64
|
+
window_end: null,
|
|
65
|
+
};
|
|
66
|
+
for (const l of lines) {
|
|
67
|
+
s.observed += 1;
|
|
68
|
+
if (s.window_start === null || l.at < s.window_start) s.window_start = l.at;
|
|
69
|
+
if (s.window_end === null || l.at > s.window_end) s.window_end = l.at;
|
|
70
|
+
if (l.spec_axis_reach === 'unreachable') s.unreachable += 1;
|
|
71
|
+
if (l.criteria_source === 'not_provided') s.no_criteria += 1;
|
|
72
|
+
if (l.criteria_source === 'supplied_unparseable') s.unparseable += 1;
|
|
73
|
+
if (l.spec_axis_effect === 'changed') {
|
|
74
|
+
s.comparable += 1;
|
|
75
|
+
s.changed += 1;
|
|
76
|
+
} else if (l.spec_axis_effect === 'unchanged') {
|
|
77
|
+
s.comparable += 1;
|
|
78
|
+
s.unchanged += 1;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return s;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Render the summary as the prose a human reads. Empty window says so in words. */
|
|
85
|
+
export function render(s: ReviewAxisSummary): string {
|
|
86
|
+
const out: string[] = [];
|
|
87
|
+
if (s.observed === 0) {
|
|
88
|
+
out.push('NO WINDOW — 0 reviews recorded, so the spec axis has no measured effect in either direction.');
|
|
89
|
+
out.push('This is not "the axis changed nothing": nothing has been observed yet. Rerun once reviews have run.');
|
|
90
|
+
return out.join('\n');
|
|
91
|
+
}
|
|
92
|
+
out.push(`window: ${String(s.window_start)} … ${String(s.window_end)}`);
|
|
93
|
+
out.push(`observed: ${s.observed} review(s)`);
|
|
94
|
+
if (s.comparable === 0) {
|
|
95
|
+
out.push(
|
|
96
|
+
'comparable: 0 — the axis ran in none of them, so it changed nothing BY CONSTRUCTION, ' +
|
|
97
|
+
'which is not evidence about the axis.',
|
|
98
|
+
);
|
|
99
|
+
} else {
|
|
100
|
+
const pct = ((s.changed / s.comparable) * 100).toFixed(1);
|
|
101
|
+
out.push(`comparable: ${s.comparable} (the axis ran; a counterfactual exists)`);
|
|
102
|
+
out.push(`changed the recommendation: ${s.changed} of ${s.comparable} (${pct}%)`);
|
|
103
|
+
if (s.changed === 0) {
|
|
104
|
+
out.push('The honest reading of 0: the axis ran and never flipped a verdict over this window.');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
out.push(
|
|
108
|
+
`not comparable: ${s.unreachable} unreachable · ${s.no_criteria} ran with no criteria · ` +
|
|
109
|
+
`${s.unparseable} with unreadable criteria (an error state, counted apart)`,
|
|
110
|
+
);
|
|
111
|
+
return out.join('\n');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function readLedger(dir: string): ReviewLine[] {
|
|
115
|
+
if (!fs.existsSync(dir)) return [];
|
|
116
|
+
const lines: ReviewLine[] = [];
|
|
117
|
+
for (const f of fs.readdirSync(dir).sort()) {
|
|
118
|
+
if (!f.endsWith('.jsonl')) continue;
|
|
119
|
+
for (const raw of fs.readFileSync(path.join(dir, f), 'utf8').split('\n')) {
|
|
120
|
+
const t = raw.trim();
|
|
121
|
+
if (!t) continue;
|
|
122
|
+
try {
|
|
123
|
+
const o = JSON.parse(t) as ReviewLine;
|
|
124
|
+
if (o && o.schema === 'review-axis-v1') lines.push(o);
|
|
125
|
+
} catch {
|
|
126
|
+
// A malformed line is skipped, never guessed at.
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return lines;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function selfTest(): number {
|
|
134
|
+
const at = '2026-08-22T00:00:00Z';
|
|
135
|
+
const base = {
|
|
136
|
+
schema: 'review-axis-v1' as const,
|
|
137
|
+
at,
|
|
138
|
+
judges_declared: 6,
|
|
139
|
+
judges_ran: 6,
|
|
140
|
+
criteria_count: 2,
|
|
141
|
+
spec_missing: 0,
|
|
142
|
+
spec_partial: 0,
|
|
143
|
+
recommendation: 'proceed' as const,
|
|
144
|
+
};
|
|
145
|
+
const cases: { name: string; lines: ReviewLine[]; want: Partial<ReviewAxisSummary>; wantText?: string }[] = [
|
|
146
|
+
{ name: 'empty window is not zero-effect', lines: [], want: { observed: 0, comparable: 0 }, wantText: 'NO WINDOW' },
|
|
147
|
+
{
|
|
148
|
+
name: 'unreachable contributes to neither population',
|
|
149
|
+
lines: [
|
|
150
|
+
{
|
|
151
|
+
...base,
|
|
152
|
+
spec_axis_reach: 'unreachable',
|
|
153
|
+
criteria_source: 'not_provided',
|
|
154
|
+
criteria_count: 0,
|
|
155
|
+
spec_axis_effect: null,
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
want: { observed: 1, comparable: 0, changed: 0, unreachable: 1 },
|
|
159
|
+
wantText: 'BY CONSTRUCTION',
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: 'changed and unchanged both land in comparable',
|
|
163
|
+
lines: [
|
|
164
|
+
{ ...base, spec_axis_reach: 'reachable_with_criteria', criteria_source: 'supplied', spec_axis_effect: 'changed' },
|
|
165
|
+
{ ...base, spec_axis_reach: 'reachable_with_criteria', criteria_source: 'supplied', spec_axis_effect: 'unchanged' },
|
|
166
|
+
],
|
|
167
|
+
want: { observed: 2, comparable: 2, changed: 1, unchanged: 1 },
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: 'unparseable is counted apart from no_criteria',
|
|
171
|
+
lines: [
|
|
172
|
+
{
|
|
173
|
+
...base,
|
|
174
|
+
spec_axis_reach: 'reachable_no_criteria',
|
|
175
|
+
criteria_source: 'supplied_unparseable',
|
|
176
|
+
criteria_count: 0,
|
|
177
|
+
spec_axis_effect: 'unchanged',
|
|
178
|
+
},
|
|
179
|
+
],
|
|
180
|
+
want: { observed: 1, no_criteria: 0, unparseable: 1, comparable: 1 },
|
|
181
|
+
},
|
|
182
|
+
];
|
|
183
|
+
let pass = 0;
|
|
184
|
+
let fail = 0;
|
|
185
|
+
for (const c of cases) {
|
|
186
|
+
const got = summarise(c.lines);
|
|
187
|
+
const bad = Object.entries(c.want).filter(([k, v]) => got[k as keyof ReviewAxisSummary] !== v);
|
|
188
|
+
const text = render(got);
|
|
189
|
+
const textBad = c.wantText !== undefined && !text.includes(c.wantText);
|
|
190
|
+
if (bad.length === 0 && !textBad) {
|
|
191
|
+
pass += 1;
|
|
192
|
+
} else {
|
|
193
|
+
fail += 1;
|
|
194
|
+
console.error(`❌ ${c.name}`);
|
|
195
|
+
for (const [k, v] of bad) console.error(` ${k}: want ${String(v)}, got ${String(got[k as keyof ReviewAxisSummary])}`);
|
|
196
|
+
if (textBad) console.error(` prose missing: ${String(c.wantText)}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
process.stdout.write(`review_axis_report --self-test: ${pass} pass, ${fail} fail\n`);
|
|
200
|
+
return fail === 0 ? 0 : 1;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function main(argv: string[]): number {
|
|
204
|
+
if (argv.includes('--self-test')) return selfTest();
|
|
205
|
+
const dirIdx = argv.indexOf('--dir');
|
|
206
|
+
const dir =
|
|
207
|
+
dirIdx >= 0 && argv[dirIdx + 1] !== undefined
|
|
208
|
+
? String(argv[dirIdx + 1])
|
|
209
|
+
: path.join(process.cwd(), 'agents', 'runtime', 'state', 'review-axis');
|
|
210
|
+
const lines = readLedger(dir);
|
|
211
|
+
process.stdout.write(render(summarise(lines)) + '\n');
|
|
212
|
+
process.stdout.write(`scanned: ${lines.length}\n`);
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (process.argv[1] !== undefined && process.argv[1].includes('review_axis_report')) {
|
|
217
|
+
process.exit(main(process.argv.slice(2)));
|
|
218
|
+
}
|
|
@@ -276,7 +276,11 @@ export function render_standing_delivery(m: StandingDeliveryMeasure | null): str
|
|
|
276
276
|
lines.push(
|
|
277
277
|
`⚠️ Standing delivery: both rule layers active — ${m.overlap_rules} rule(s) delivered twice ` +
|
|
278
278
|
`(~${m.overlap_tokens} tok). This session's host receives ${m.received_tokens} rule tokens; ` +
|
|
279
|
-
`
|
|
279
|
+
`single-layer delivery would be ~${scoped} — run \`agent-config install\` to arm the ` +
|
|
280
|
+
`ADR-236 partition on this machine (it fingerprints the host layer; the next ` +
|
|
281
|
+
`\`task generate-tools\` then withholds the globally-owned artefacts). The former advice ` +
|
|
282
|
+
`here, \`install --layer=<global|project>\`, was layer suppression — declined by ADR-226 ` +
|
|
283
|
+
`and superseded by the partition.`,
|
|
280
284
|
);
|
|
281
285
|
if (m.divergent_rules > 0) {
|
|
282
286
|
// Suppressing a layer whose copies differ drops whichever obligations only
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
roadmapPath,
|
|
52
52
|
} from './_lib/run_checkpoint.js';
|
|
53
53
|
import { readBudget } from './_lib/unattended_guard.js';
|
|
54
|
+
import { findQuotaParked, type QuotaParkedMarker } from './_lib/quota_parked.js';
|
|
54
55
|
import {
|
|
55
56
|
LIVE_SPAWN_REFUSAL,
|
|
56
57
|
planResume,
|
|
@@ -80,6 +81,7 @@ export type Disposition =
|
|
|
80
81
|
| 'no-roadmap'
|
|
81
82
|
| 'roadmap-unreadable'
|
|
82
83
|
| 'complete'
|
|
84
|
+
| 'quota-parked'
|
|
83
85
|
| 'budget-exhausted'
|
|
84
86
|
| 'relaunchable';
|
|
85
87
|
|
|
@@ -246,6 +248,21 @@ export function classify(
|
|
|
246
248
|
reason: 'no open steps remain — the run finished, the session just never said so',
|
|
247
249
|
};
|
|
248
250
|
}
|
|
251
|
+
// Before the relaunch cap, and the order is load-bearing in one direction
|
|
252
|
+
// only: a parked run that has also spent its relaunches should still read
|
|
253
|
+
// as parked, because "waiting for a reset" is the cause and "spent three
|
|
254
|
+
// relaunches" is a consequence of it. Reported the other way round, an
|
|
255
|
+
// operator sees a run that failed repeatedly rather than one that keeps
|
|
256
|
+
// meeting the same wall.
|
|
257
|
+
const parked = findQuotaParked(repoRoot, rec.session_id);
|
|
258
|
+
if (parked !== null) {
|
|
259
|
+
return {
|
|
260
|
+
...base,
|
|
261
|
+
disposition: 'quota-parked',
|
|
262
|
+
open_steps: counts.open,
|
|
263
|
+
reason: quotaParkedReason(parked, counts.open),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
249
266
|
if (base.relaunches >= MAX_RELAUNCHES_PER_RUN) {
|
|
250
267
|
return {
|
|
251
268
|
...base,
|
|
@@ -264,6 +281,23 @@ export function classify(
|
|
|
264
281
|
};
|
|
265
282
|
}
|
|
266
283
|
|
|
284
|
+
/**
|
|
285
|
+
* The one sentence a human acts on for a parked run.
|
|
286
|
+
*
|
|
287
|
+
* It states the reset time is unknown rather than omitting it, and rather than
|
|
288
|
+
* guessing an interval. There is no reset-time parser and there will not be one
|
|
289
|
+
* until a verified error string exists to pin a fixture against — see
|
|
290
|
+
* `later/road-to-billing-cliff-detection.md`. An omitted unknown reads as "no
|
|
291
|
+
* wait needed"; a guessed interval reads as a fact.
|
|
292
|
+
*/
|
|
293
|
+
export function quotaParkedReason(m: QuotaParkedMarker, open: number): string {
|
|
294
|
+
return (
|
|
295
|
+
`plan quota exhausted on ${m.providers.join(', ')} since ${m.parked_at} — ` +
|
|
296
|
+
`${open} open step(s) remain; the run is waiting, not broken. ` +
|
|
297
|
+
`Reset time unknown (no parser: no verified error string yet).`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
267
301
|
export interface ScanOptions {
|
|
268
302
|
readonly now?: Date;
|
|
269
303
|
}
|
|
@@ -434,10 +468,21 @@ export function digest(repoRoot: string, candidates: readonly Candidate[], now:
|
|
|
434
468
|
|
|
435
469
|
const dead = candidates.filter((c) => c.disposition !== 'alive');
|
|
436
470
|
const relaunchable = candidates.filter((c) => c.disposition === 'relaunchable');
|
|
471
|
+
const quotaParked = candidates.filter((c) => c.disposition === 'quota-parked');
|
|
437
472
|
lines.push(
|
|
438
473
|
` sessions: ${candidates.length} registered · ${candidates.length - dead.length} alive · ` +
|
|
439
|
-
`${relaunchable.length} relaunchable
|
|
474
|
+
`${relaunchable.length} relaunchable` +
|
|
475
|
+
(quotaParked.length > 0 ? ` · ${quotaParked.length} quota-parked` : ''),
|
|
440
476
|
);
|
|
477
|
+
// Named on its own line, not folded into the relaunchable count, because
|
|
478
|
+
// the two call for opposite responses: a relaunchable run wants a session,
|
|
479
|
+
// a parked one wants time. Suppressed at zero so the ordinary digest does
|
|
480
|
+
// not carry a line about a state nothing is in.
|
|
481
|
+
for (const c of quotaParked) {
|
|
482
|
+
lines.push(
|
|
483
|
+
` parked: ${c.roadmap ?? '-'} — ${c.reason}`,
|
|
484
|
+
);
|
|
485
|
+
}
|
|
441
486
|
|
|
442
487
|
const budget = readBudget(repoRoot, now);
|
|
443
488
|
lines.push(
|
|
@@ -48,6 +48,11 @@
|
|
|
48
48
|
"minLength": 1,
|
|
49
49
|
"description": "The input fixture handed to the skill under test."
|
|
50
50
|
},
|
|
51
|
+
"_why": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"minLength": 1,
|
|
54
|
+
"description": "Optional rationale: why this fixture exists at all. Annotation, never input — nothing reads it at run time, and it carries the same underscore prefix as the top-level `_calibration` to say so. Added because the alternative, on scenarios whose rationale IS the negative-control argument for the pair above them, was to delete the argument to satisfy additionalProperties."
|
|
55
|
+
},
|
|
51
56
|
"environment": {
|
|
52
57
|
"type": "string",
|
|
53
58
|
"minLength": 1,
|