@event4u/agent-config 9.29.0 → 9.30.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 +108 -7
- package/README.md +1 -0
- package/dist/agent-src/commands/analyze/inbox.md +22 -1
- package/dist/agent-src/commands/explain-run.md +31 -1
- package/dist/agent-src/commands/team/delegate.md +1 -0
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +4 -1
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +17 -0
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +11 -1
- package/dist/agent-src/rules/preservation-guard.md +9 -0
- package/dist/agent-src/scripts/roadmap_gates.ts +312 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +72 -4
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +10 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli/registry.js +2 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-OJM3MBJM.js → chunk-BTJNRUX3.js} +21 -34
- package/dist/cli-delegate/{chunk-MMXA5GY7.js → chunk-DDUNNFYX.js} +14 -0
- package/dist/cli-delegate/{chunk-NZ5N2XQL.js → chunk-EQY4FLJB.js} +1 -1
- package/dist/cli-delegate/{chunk-RCIA67XN.js → chunk-IYTGIG2Y.js} +44 -1
- package/dist/cli-delegate/chunk-MMJL4TIX.js +18 -0
- package/dist/cli-delegate/chunk-O3CO5BFB.js +161 -0
- package/dist/cli-delegate/{chunk-REKCMYOU.js → chunk-PFBV6XRT.js} +36 -20
- package/dist/cli-delegate/chunk-RVGBH4XK.js +142 -0
- package/dist/cli-delegate/cmd_conformance.js +2798 -95
- package/dist/cli-delegate/cmd_doctor.js +2 -1
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +86 -6
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_route_audit.js +2 -1
- package/dist/cli-delegate/cmd_session_recycle.js +177 -0
- package/dist/cli-delegate/cmd_settings_get.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +2 -2
- 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 +7 -7
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +1904 -575
- package/dist/install/install.mjs +14 -0
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +3760 -726
- package/dist/server/schemas/settings.js +6 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/ui/assets/index-B5qGYOJo.js +26 -0
- package/dist/ui/assets/index-B5qGYOJo.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/contracts/settings-classes.md +21 -5
- package/package.json +1 -1
- package/src/agent-src/scripts/roadmap_gates.ts +312 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +72 -4
- package/src/config/agent-settings.template.yml +36 -0
- package/src/config/dispatch-economy-metrics.json +61 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/hook-token-budget.json +91 -0
- package/src/config/recycle-threshold-budget.json +12 -0
- package/src/config/rule-enforcement-baseline.json +86 -0
- package/src/scripts/_cli/cmd_conformance.ts +17 -2
- package/src/scripts/_cli/cmd_session_recycle.ts +226 -0
- package/src/scripts/_dispatch.bash +23 -0
- package/src/scripts/_lib/judgment_ladder.ts +254 -2
- package/src/scripts/_lib/orchestration_record.ts +41 -10
- package/src/scripts/_lib/recycle_envelope_paths.ts +35 -0
- package/src/scripts/_lib/self_repair.ts +194 -0
- package/src/scripts/_lib/session_eol.ts +230 -0
- package/src/scripts/_lib/session_role.ts +42 -0
- package/src/scripts/_lib/subagent_capsule.ts +159 -0
- package/src/scripts/_lib/subagent_response.ts +75 -0
- package/src/scripts/ai_council/clients.ts +50 -1
- package/src/scripts/ask_transport.ts +221 -0
- package/src/scripts/bench_hook_injection.ts +234 -0
- package/src/scripts/check_adr_frontmatter.ts +84 -40
- package/src/scripts/check_branch_freshness.ts +1 -0
- package/src/scripts/check_corpus_staleness.ts +18 -4
- package/src/scripts/check_detector_corpus.ts +17 -0
- package/src/scripts/check_gitignore_freshness.ts +42 -8
- package/src/scripts/check_hook_bundle_freshness.ts +20 -0
- package/src/scripts/check_kernel_prefix_stability.ts +1 -0
- package/src/scripts/check_standing_rule_delivery.ts +73 -32
- package/src/scripts/check_static_layer_stability.ts +154 -0
- package/src/scripts/check_suppression_hygiene.ts +48 -0
- package/src/scripts/dispatch_economy_report.ts +448 -0
- package/src/scripts/dispatch_r2_reviewer.ts +15 -2
- package/src/scripts/explain_run.ts +206 -4
- package/src/scripts/generate_settings_reference.ts +1 -0
- package/src/scripts/handoff_context_hook.ts +112 -4
- package/src/scripts/hook_manifest.yaml +117 -10
- package/src/scripts/hooks/concern_registry.ts +8 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +40 -3
- package/src/scripts/hooks/dispatch_hook.ts +39 -2
- package/src/scripts/hooks/edit_shape_hook.ts +303 -0
- package/src/scripts/hooks/orchestration_record_hook.ts +19 -1
- package/src/scripts/hooks/reread_guard_hook.ts +314 -0
- package/src/scripts/hooks/rtk_wrap_hook.ts +133 -14
- package/src/scripts/hooks/session_eol_hook.ts +268 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +507 -0
- package/src/scripts/install-hooks.sh +1 -1
- package/src/scripts/language_mirror_hook.ts +89 -0
- package/src/scripts/lint_decision_impact.ts +14 -1
- package/src/scripts/lint_hook_manifest.ts +82 -0
- package/src/scripts/lint_readme_serial_comma.ts +1 -0
- package/src/scripts/lint_rule_enforcement_declaration.ts +347 -0
- package/src/scripts/measure_turn_end_gate.ts +194 -0
- package/src/scripts/orchestration_record.ts +4 -1
- package/src/scripts/rebuild_hook_bundle.ts +1 -0
- package/src/scripts/report_conformance_funnel.ts +284 -0
- package/src/scripts/report_imperative_density.ts +1 -0
- package/src/scripts/routing_doctor.ts +97 -0
- package/src/scripts/self_repair_cli.ts +51 -3
- package/src/scripts/session_eol_report.ts +261 -0
- package/src/server/schemas/settings.ts +14 -0
- package/src/templates/compact-instructions.md +40 -0
- package/dist/ui/assets/index-JDAF00bC.js +0 -26
- package/dist/ui/assets/index-JDAF00bC.js.map +0 -1
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-B5qGYOJo.js"></script>
|
|
31
31
|
<link rel="stylesheet" crossorigin href="/assets/index-jSfMW8_t.css">
|
|
32
32
|
</head>
|
|
33
33
|
<body>
|
|
@@ -22,14 +22,26 @@ raise a spend ceiling, empty an allowlist, or switch off a gate.
|
|
|
22
22
|
| **B — consent** | the agent, but only after ONE just-in-time ask that the user answered | **once**, then persisted with provenance. |
|
|
23
23
|
| **C — guarded** | the GUI's write route or a hand-edit, by a human | never asked by the agent; **every agent write path refuses it.** |
|
|
24
24
|
|
|
25
|
+
### The provenance sidecar's record shape
|
|
26
|
+
|
|
27
|
+
"Persisted with provenance" means one record per dotted key in
|
|
28
|
+
`settings/.agent-settings.provenance.json`, written by `settings:set` beside
|
|
29
|
+
the value: `{ "source": "auto-detected" | "jit-answer" | "manual" | "gui",
|
|
30
|
+
"at": "<ISO timestamp>" }`. For a class-B key the `at` stamp IS the grant
|
|
31
|
+
time — the consent reader (`consentVerdict`) grants on `jit-answer` / `gui` /
|
|
32
|
+
`manual` provenance, so source + timestamp together are the audit record of
|
|
33
|
+
who consented and when. `expires_at` / `revoked_at` / contract-version fields
|
|
34
|
+
stay deliberately unbuilt until a reader needs them — a field nothing reads is
|
|
35
|
+
surface without a consumer.
|
|
36
|
+
|
|
25
37
|
## What the fence covers — and what it does not
|
|
26
38
|
|
|
27
39
|
**It governs writes, not reads.** Every class is readable by everything. A C
|
|
28
40
|
class does not hide a value; it refuses to let a non-human change it.
|
|
29
41
|
|
|
30
|
-
**It governs writes, not asks.**
|
|
31
|
-
enum (`tokens.rich_skills`, `subagents.
|
|
32
|
-
`
|
|
42
|
+
**It governs writes, not asks.** Four settings ship an `ask` value in their own
|
|
43
|
+
enum (`tokens.rich_skills`, `subagents.adversarial_council`, `worktrees.mode`,
|
|
44
|
+
`decision_engine.on_block`).
|
|
33
45
|
Those are C-class here, and that is not a contradiction: the class says who may
|
|
34
46
|
*persist* a new value; the `ask` value says what happens *at runtime* when the
|
|
35
47
|
setting is already set to `ask`. A C-class key set to `ask` still produces a
|
|
@@ -168,8 +180,8 @@ dispatch runs, not WHETHER the layer exists, so they keep their own C rows.
|
|
|
168
180
|
|---|---|
|
|
169
181
|
| A — preference | 27 |
|
|
170
182
|
| B — consent | 3 |
|
|
171
|
-
| C — guarded |
|
|
172
|
-
| **Total** | **
|
|
183
|
+
| C — guarded | 111 |
|
|
184
|
+
| **Total** | **141** |
|
|
173
185
|
|
|
174
186
|
The total is every leaf in the template, where *leaf* means anything that is not
|
|
175
187
|
a **non-empty** map. An empty map (like the former `subagents.host_capabilities: {}`) is a real
|
|
@@ -269,6 +281,7 @@ Rows follow template order, so a diff against the template reads straight down.
|
|
|
269
281
|
| `subagents.model_map.high` | C | `""` | names an external model endpoint |
|
|
270
282
|
| `subagents.implementer_model` | C | `""` | names an external model endpoint |
|
|
271
283
|
| `subagents.judge_model` | C | `""` | names an external model endpoint |
|
|
284
|
+
| `subagents.model_ceiling` | C | `""` | session-wide subagent model cap (exported as `CLAUDE_CODE_SUBAGENT_MODEL` by suite-owned CLI spawn wrappers), which is spend and quality |
|
|
272
285
|
| `subagents.max_parallel` | C | `3` | parallelism cap, and therefore a spend rate |
|
|
273
286
|
| `subagents.adversarial_council` | C | `"off"` | governs a paid verification step |
|
|
274
287
|
| `worktrees.mode` | C | `ask` | standing permission for autonomous worktree creation |
|
|
@@ -312,6 +325,9 @@ Rows follow template order, so a diff against the template reads straight down.
|
|
|
312
325
|
| `hooks.rtk_wrap.enabled` | C | `false` | configures code that runs on every tool call |
|
|
313
326
|
| `hooks.design_slop.enabled` | C | `false` | configures code that runs on every tool call |
|
|
314
327
|
| `hooks.code_graph.enabled` | C | `false` | configures code that runs on every tool call |
|
|
328
|
+
| `hooks.turn_end_gate.enabled` | C | `false` | arms the only concern that can REFUSE a turn-end; an agent must never be able to switch its own delivery gate on |
|
|
329
|
+
| `hooks.turn_end_gate.promissory` | C | `true` | which refusal the turn-end gate may raise; inert while the master switch is off |
|
|
330
|
+
| `hooks.turn_end_gate.language` | C | `true` | which refusal the turn-end gate may raise; inert while the master switch is off |
|
|
315
331
|
| `decision_engine.surface_traces` | C | `false` | the decision engine’s own black box; the agent must not be able to close it |
|
|
316
332
|
| `decision_engine.min_confidence` | C | `"off"` | the confidence gate |
|
|
317
333
|
| `decision_engine.block_on_risk` | C | `"off"` | the risk-class gate |
|
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": "9.
|
|
4
|
+
"version": "9.30.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,
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* `agent-config gates` — the open decisions that need the user, as actions.
|
|
4
|
+
*
|
|
5
|
+
* The dashboard (`update_roadmap_progress`) already parses every `## Blockers`
|
|
6
|
+
* entry and already counts them per roadmap. What it does not do is separate
|
|
7
|
+
* the blockers the *user* must resolve from the ones the maintainer or an
|
|
8
|
+
* external party owns: the count links into a per-roadmap breakdown, so
|
|
9
|
+
* answering "what do I have to do" means opening every anchor and filtering
|
|
10
|
+
* `Owner:` by hand across the whole file. The fields exist, are CI-enforced by
|
|
11
|
+
* `lint_roadmap_blockers`, and never reach the user in a form they can act on.
|
|
12
|
+
*
|
|
13
|
+
* This command is that missing projection, and nothing more: same parser
|
|
14
|
+
* (`parse_blockers` / `collect`, imported — never re-implemented), filtered by
|
|
15
|
+
* owner, ordered by how much each blocker unblocks, rendered action-first.
|
|
16
|
+
*
|
|
17
|
+
* Deliberately NOT a gate: it exits 0 whether or not decisions are pending.
|
|
18
|
+
* Blocking a pipeline on "the user has an open decision" would turn a
|
|
19
|
+
* visibility aid into a second thing to fight.
|
|
20
|
+
*
|
|
21
|
+
* Invocation (from project root):
|
|
22
|
+
* ./agent-config gates # decisions owned by the user
|
|
23
|
+
* ./agent-config gates --all # every open blocker, grouped by owner
|
|
24
|
+
* ./agent-config gates --json # machine-readable
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import * as path from 'node:path';
|
|
28
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
29
|
+
import * as fs from 'node:fs';
|
|
30
|
+
import { spawnSync } from 'node:child_process';
|
|
31
|
+
|
|
32
|
+
import {
|
|
33
|
+
collect,
|
|
34
|
+
blocker_needs_user as needsUser,
|
|
35
|
+
type Blocker,
|
|
36
|
+
} from './update_roadmap_progress.js';
|
|
37
|
+
|
|
38
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
39
|
+
|
|
40
|
+
/** Hard wrap for the rendered body — keeps output readable in a narrow pane. */
|
|
41
|
+
const WIDTH = 78;
|
|
42
|
+
|
|
43
|
+
interface Entry {
|
|
44
|
+
blocker: Blocker;
|
|
45
|
+
roadmapRel: string;
|
|
46
|
+
/** Open steps in the roadmap this blocker sits in — the "unblocks" weight. */
|
|
47
|
+
openSteps: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Rejoin wrapped continuation lines into one step each.
|
|
52
|
+
*
|
|
53
|
+
* `parse_blockers` returns `todo` as raw stripped lines, so a step that wraps
|
|
54
|
+
* over three source lines arrives as three entries. A new step starts at an
|
|
55
|
+
* ordered marker (`1.`, `2)`) or a bullet; everything else continues the one
|
|
56
|
+
* before it.
|
|
57
|
+
*/
|
|
58
|
+
function regroupTodo(todo: readonly string[]): string[] {
|
|
59
|
+
const steps: string[] = [];
|
|
60
|
+
for (const line of todo) {
|
|
61
|
+
const startsStep = /^(\d+[.)]|[-*+])\s/.test(line);
|
|
62
|
+
if (startsStep || steps.length === 0) {
|
|
63
|
+
steps.push(line);
|
|
64
|
+
} else {
|
|
65
|
+
steps[steps.length - 1] = `${steps[steps.length - 1] as string} ${line}`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return steps;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Wrap `text` to `width`, indenting every line after the first by `indent`. */
|
|
72
|
+
function wrap(text: string, width: number, indent: string): string[] {
|
|
73
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
74
|
+
if (words.length === 0) {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
const lines: string[] = [];
|
|
78
|
+
let cur = '';
|
|
79
|
+
for (const w of words) {
|
|
80
|
+
const candidate = cur === '' ? w : `${cur} ${w}`;
|
|
81
|
+
if (candidate.length > width && cur !== '') {
|
|
82
|
+
lines.push(cur);
|
|
83
|
+
cur = w;
|
|
84
|
+
} else {
|
|
85
|
+
cur = candidate;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (cur !== '') {
|
|
89
|
+
lines.push(cur);
|
|
90
|
+
}
|
|
91
|
+
return lines.map((l, i) => (i === 0 ? l : indent + l));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Column width for the label gutter — sized to the longest label in use
|
|
96
|
+
* (`Blocked until:`, 14 chars) plus one separating space, so no value ever
|
|
97
|
+
* runs flush against its own label.
|
|
98
|
+
*/
|
|
99
|
+
const LABEL_W = 15;
|
|
100
|
+
|
|
101
|
+
/** A labelled block: ` Label: value`, wrapped and hanging-indented. */
|
|
102
|
+
function field(label: string, value: string): string[] {
|
|
103
|
+
const pad = ' ' + label.padEnd(LABEL_W);
|
|
104
|
+
const indent = ' '.repeat(pad.length);
|
|
105
|
+
const lines = wrap(value, WIDTH - pad.length, indent);
|
|
106
|
+
if (lines.length === 0) {
|
|
107
|
+
return [];
|
|
108
|
+
}
|
|
109
|
+
return [pad + lines[0], ...lines.slice(1)];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function collectEntries(roadmapRoot: string): Entry[] {
|
|
113
|
+
const entries: Entry[] = [];
|
|
114
|
+
for (const r of collect(roadmapRoot)) {
|
|
115
|
+
for (const b of r.open_blockers) {
|
|
116
|
+
entries.push({ blocker: b, roadmapRel: r.rel, openSteps: r.open_ });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Most-unblocking first; ties broken deterministically so two runs on an
|
|
120
|
+
// unchanged tree print byte-identical output.
|
|
121
|
+
entries.sort(
|
|
122
|
+
(a, b) =>
|
|
123
|
+
b.openSteps - a.openSteps ||
|
|
124
|
+
a.roadmapRel.localeCompare(b.roadmapRel) ||
|
|
125
|
+
a.blocker.id.localeCompare(b.blocker.id),
|
|
126
|
+
);
|
|
127
|
+
return entries;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The parser's legacy fallback synthesises one blocker per `> Blocked until …`
|
|
132
|
+
* note under the fixed id `legacy`. That id is an implementation detail and
|
|
133
|
+
* says nothing to a reader, and its text is a *condition* ("blocked until X"),
|
|
134
|
+
* not an imperative — so both the heading and the field label are renamed for
|
|
135
|
+
* this one parser-defined case rather than dressed up as a step list.
|
|
136
|
+
*/
|
|
137
|
+
function isLegacy(b: Blocker): boolean {
|
|
138
|
+
return b.id === 'legacy';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function renderEntry(e: Entry, index: number): string[] {
|
|
142
|
+
const head = `${index} · ${isLegacy(e.blocker) ? 'blocked-until note' : e.blocker.id}`;
|
|
143
|
+
const tail =
|
|
144
|
+
e.openSteps > 0 ? `unblocks: ${e.openSteps} step${e.openSteps !== 1 ? 's' : ''}` : '';
|
|
145
|
+
const dashes = Math.max(3, WIDTH - head.length - tail.length - 6);
|
|
146
|
+
const out: string[] = [
|
|
147
|
+
'',
|
|
148
|
+
`── ${head} ${'─'.repeat(dashes)} ${tail}`.trimEnd(),
|
|
149
|
+
...field('Roadmap:', e.roadmapRel),
|
|
150
|
+
...field('Blocks:', e.blocker.blocks),
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
const doLabel = isLegacy(e.blocker) ? 'Blocked until:' : 'Do this:';
|
|
154
|
+
const steps = regroupTodo(e.blocker.todo);
|
|
155
|
+
if (steps.length === 0) {
|
|
156
|
+
out.push(
|
|
157
|
+
...field(
|
|
158
|
+
doLabel,
|
|
159
|
+
'(no steps recorded — the blocker entry in the roadmap needs a ' +
|
|
160
|
+
'**What to do:** list)',
|
|
161
|
+
),
|
|
162
|
+
);
|
|
163
|
+
} else {
|
|
164
|
+
out.push(...field(doLabel, steps[0] as string));
|
|
165
|
+
for (const s of steps.slice(1)) {
|
|
166
|
+
// Steps 2..n have no label of their own — align them under the
|
|
167
|
+
// first step's text, i.e. the same gutter `field()` produces.
|
|
168
|
+
const indent = ' '.repeat(LABEL_W + 2);
|
|
169
|
+
const lines = wrap(s, WIDTH - LABEL_W - 2, indent);
|
|
170
|
+
out.push(...lines.map((l, i) => (i === 0 ? indent + l : l)));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
out.push(...field('Done when:', e.blocker.resolvedWhen));
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function render(entries: readonly Entry[], all: boolean): string {
|
|
178
|
+
const mine = entries.filter((e) => needsUser(e.blocker.owner));
|
|
179
|
+
const others = entries.filter((e) => !needsUser(e.blocker.owner));
|
|
180
|
+
const shown = all ? entries : mine;
|
|
181
|
+
const lines: string[] = [];
|
|
182
|
+
|
|
183
|
+
if (mine.length === 0) {
|
|
184
|
+
lines.push(
|
|
185
|
+
others.length === 0
|
|
186
|
+
? 'No open blockers at all.'
|
|
187
|
+
: `Nothing is waiting on you. ${others.length} open blocker` +
|
|
188
|
+
`${others.length !== 1 ? 's' : ''} sit with maintainer/external` +
|
|
189
|
+
`${all ? '' : ' — see --all'}.`,
|
|
190
|
+
);
|
|
191
|
+
} else {
|
|
192
|
+
const head =
|
|
193
|
+
`${mine.length} decision${mine.length !== 1 ? 's' : ''} need${mine.length === 1 ? 's' : ''} you`;
|
|
194
|
+
const rest =
|
|
195
|
+
others.length > 0
|
|
196
|
+
? ` · ${others.length} more with maintainer/external${all ? '' : ' (--all)'}`
|
|
197
|
+
: '';
|
|
198
|
+
lines.push(head + rest);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
let n = 0;
|
|
202
|
+
for (const e of shown) {
|
|
203
|
+
n += 1;
|
|
204
|
+
lines.push(...renderEntry(e, n));
|
|
205
|
+
}
|
|
206
|
+
if (shown.length > 0) {
|
|
207
|
+
lines.push('');
|
|
208
|
+
}
|
|
209
|
+
return lines.join('\n') + '\n';
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function renderJson(entries: readonly Entry[], all: boolean): string {
|
|
213
|
+
const pick = all ? entries : entries.filter((e) => needsUser(e.blocker.owner));
|
|
214
|
+
return (
|
|
215
|
+
JSON.stringify(
|
|
216
|
+
{
|
|
217
|
+
needsYou: entries.filter((e) => needsUser(e.blocker.owner)).length,
|
|
218
|
+
other: entries.filter((e) => !needsUser(e.blocker.owner)).length,
|
|
219
|
+
blockers: pick.map((e) => ({
|
|
220
|
+
id: e.blocker.id,
|
|
221
|
+
roadmap: e.roadmapRel,
|
|
222
|
+
owner: e.blocker.owner,
|
|
223
|
+
needsYou: needsUser(e.blocker.owner),
|
|
224
|
+
blocks: e.blocker.blocks,
|
|
225
|
+
unblocksSteps: e.openSteps,
|
|
226
|
+
todo: regroupTodo(e.blocker.todo),
|
|
227
|
+
resolvedWhen: e.blocker.resolvedWhen,
|
|
228
|
+
})),
|
|
229
|
+
},
|
|
230
|
+
null,
|
|
231
|
+
2,
|
|
232
|
+
) + '\n'
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function _isDir(p: string): boolean {
|
|
237
|
+
try {
|
|
238
|
+
return fs.statSync(p).isDirectory();
|
|
239
|
+
} catch {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Same repo-root resolution as the dashboard: honour the cwd when it holds
|
|
246
|
+
* `agents/roadmaps`, otherwise fall back to the git toplevel (monorepo
|
|
247
|
+
* sub-project support).
|
|
248
|
+
*/
|
|
249
|
+
function _resolveRepoRoot(start: string): string {
|
|
250
|
+
if (_isDir(path.join(start, 'agents', 'roadmaps'))) {
|
|
251
|
+
return start;
|
|
252
|
+
}
|
|
253
|
+
try {
|
|
254
|
+
const r = spawnSync('git', ['rev-parse', '--show-toplevel'], {
|
|
255
|
+
cwd: start,
|
|
256
|
+
encoding: 'utf-8',
|
|
257
|
+
timeout: 10_000,
|
|
258
|
+
});
|
|
259
|
+
const top = (r.stdout || '').trim();
|
|
260
|
+
if (r.status === 0 && top !== '' && _isDir(path.join(top, 'agents', 'roadmaps'))) {
|
|
261
|
+
return top;
|
|
262
|
+
}
|
|
263
|
+
} catch {
|
|
264
|
+
/* not a git repo / git missing — keep the cwd default */
|
|
265
|
+
}
|
|
266
|
+
return start;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function main(argv?: readonly string[]): number {
|
|
270
|
+
const args = argv ?? process.argv.slice(2);
|
|
271
|
+
const all = args.includes('--all');
|
|
272
|
+
const json = args.includes('--json');
|
|
273
|
+
|
|
274
|
+
const repoRoot = _resolveRepoRoot(process.cwd());
|
|
275
|
+
const roadmapRoot = path.join(repoRoot, 'agents', 'roadmaps');
|
|
276
|
+
if (!_isDir(roadmapRoot)) {
|
|
277
|
+
process.stdout.write(
|
|
278
|
+
json ? '{"needsYou":0,"other":0,"blockers":[]}\n' : 'No roadmaps directory — nothing to report.\n',
|
|
279
|
+
);
|
|
280
|
+
return 0;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const entries = collectEntries(roadmapRoot);
|
|
284
|
+
process.stdout.write(json ? renderJson(entries, all) : render(entries, all));
|
|
285
|
+
return 0;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function _isCliEntry(): boolean {
|
|
289
|
+
if (process.argv[1] === undefined) {
|
|
290
|
+
return false;
|
|
291
|
+
}
|
|
292
|
+
const argvPath = path.resolve(process.argv[1]);
|
|
293
|
+
if (import.meta.url === pathToFileURL(argvPath).href) {
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
// A symlinked invocation (`.augment/scripts` → `dist/agent-src/scripts`, or
|
|
297
|
+
// macOS /var → /private/var) makes the raw URLs differ: import.meta.url is
|
|
298
|
+
// the resolved real path while argv[1] keeps the symlink. Compare realpaths
|
|
299
|
+
// so the entry guard still fires through the projection.
|
|
300
|
+
try {
|
|
301
|
+
return fs.realpathSync(_HERE) === fs.realpathSync(argvPath);
|
|
302
|
+
} catch {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (_isCliEntry()) {
|
|
308
|
+
process.exitCode = main();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export { main, needsUser, regroupTodo, wrap, collectEntries, render, renderJson };
|
|
312
|
+
export type { Entry };
|
|
@@ -398,14 +398,32 @@ function _blockerField(slice: string, label: string): string | null {
|
|
|
398
398
|
return parts.length ? parts.join(' ') : null;
|
|
399
399
|
}
|
|
400
400
|
|
|
401
|
-
/**
|
|
401
|
+
/**
|
|
402
|
+
* The `- **What to do:**` text: the remainder of the marker line, plus every
|
|
403
|
+
* line under it up to the next `- **Field:**` marker.
|
|
404
|
+
*
|
|
405
|
+
* The marker line's own remainder is load-bearing and used to be dropped.
|
|
406
|
+
* Authors write both shapes — a numbered list under the marker, and prose that
|
|
407
|
+
* starts inline (`- **What to do:** the build work is done; only real …`) —
|
|
408
|
+
* and every *other* blocker field reads the inline part via `_blockerField`.
|
|
409
|
+
* Skipping it here truncated instructions mid-sentence in both the dashboard
|
|
410
|
+
* and `agent-config gates`, which is precisely the content those surfaces
|
|
411
|
+
* exist to deliver.
|
|
412
|
+
*/
|
|
402
413
|
function _blockerTodo(slice: string): string[] {
|
|
403
414
|
const lines = _splitlines(slice);
|
|
404
|
-
const
|
|
415
|
+
const markerRe = /^-[ \t]*\*\*What to do:\*\*[ \t]*(.*)$/i;
|
|
416
|
+
const startIdx = lines.findIndex((l) => markerRe.test(_strip(l)));
|
|
405
417
|
if (startIdx === -1) {
|
|
406
418
|
return [];
|
|
407
419
|
}
|
|
408
420
|
const out: string[] = [];
|
|
421
|
+
const inline = _stripComment(
|
|
422
|
+
(markerRe.exec(_strip(lines[startIdx] as string))?.[1] as string) ?? '',
|
|
423
|
+
);
|
|
424
|
+
if (inline) {
|
|
425
|
+
out.push(inline);
|
|
426
|
+
}
|
|
409
427
|
for (let i = startIdx + 1; i < lines.length; i++) {
|
|
410
428
|
const trimmed = _strip(lines[i] as string);
|
|
411
429
|
if (BLOCKER_FIELD_RE.test(trimmed) || trimmed.startsWith('#')) {
|
|
@@ -458,18 +476,56 @@ function parse_blockers(raw_text: string): Blocker[] {
|
|
|
458
476
|
}
|
|
459
477
|
const legacyMatch = LEGACY_BLOCKED_UNTIL_RE.exec(text);
|
|
460
478
|
if (legacyMatch) {
|
|
479
|
+
// The legacy note is a blockquote and routinely wraps over several
|
|
480
|
+
// `> ` lines. The regex captures one line, so the continuation used to
|
|
481
|
+
// be dropped and the instruction ended mid-sentence — the same defect
|
|
482
|
+
// `_blockerTodo` carried above. Walk the following quoted lines until
|
|
483
|
+
// the quote block ends.
|
|
484
|
+
const parts = [_strip(legacyMatch[1] as string)];
|
|
485
|
+
const allLines = _splitlines(text);
|
|
486
|
+
const noteIdx = allLines.findIndex((l) => LEGACY_BLOCKED_UNTIL_RE.test(_strip(l)));
|
|
487
|
+
for (let i = noteIdx + 1; noteIdx !== -1 && i < allLines.length; i++) {
|
|
488
|
+
const quoted = /^>[ \t]?(.*)$/.exec(_strip(allLines[i] as string));
|
|
489
|
+
if (!quoted) {
|
|
490
|
+
break;
|
|
491
|
+
}
|
|
492
|
+
const cont = _strip(quoted[1] as string);
|
|
493
|
+
if (cont === '') {
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
parts.push(cont);
|
|
497
|
+
}
|
|
461
498
|
blockers.push({
|
|
462
499
|
id: 'legacy',
|
|
463
500
|
status: 'open',
|
|
464
501
|
owner: 'user',
|
|
465
502
|
blocks: 'entire roadmap',
|
|
466
|
-
todo: [
|
|
503
|
+
todo: [parts.join(' ')],
|
|
467
504
|
resolvedWhen: 'condition described above clears',
|
|
468
505
|
});
|
|
469
506
|
}
|
|
470
507
|
return blockers;
|
|
471
508
|
}
|
|
472
509
|
|
|
510
|
+
/**
|
|
511
|
+
* Does this blocker need the human at the keyboard, rather than the maintainer
|
|
512
|
+
* or an external party?
|
|
513
|
+
*
|
|
514
|
+
* Owner values in the wild are not the clean `user | maintainer | external`
|
|
515
|
+
* enum the template comment suggests — real entries carry qualifiers
|
|
516
|
+
* (`user (billable spend)`, `user / maintainer`, `maintainer (security role)`).
|
|
517
|
+
* Word-boundary matching on `user` keeps the qualified forms and rejects
|
|
518
|
+
* `maintainer`. A shared `user / maintainer` blocker deliberately counts as the
|
|
519
|
+
* user's: showing one decision too many costs a line, hiding one costs the wait.
|
|
520
|
+
*
|
|
521
|
+
* Lives here rather than in `roadmap_gates` because it is owner *semantics*,
|
|
522
|
+
* not presentation — the dashboard header and `agent-config gates` must agree
|
|
523
|
+
* on the split, and two copies of this regex would drift.
|
|
524
|
+
*/
|
|
525
|
+
function blocker_needs_user(owner: string): boolean {
|
|
526
|
+
return /\buser\b/i.test(owner);
|
|
527
|
+
}
|
|
528
|
+
|
|
473
529
|
function parse_roadmap(p: string, roadmap_root: string): RoadmapStats | null {
|
|
474
530
|
const text = fs.readFileSync(p, { encoding: 'utf-8' });
|
|
475
531
|
const phase_matches: Array<{ start: number; end: number; g2: string; g3: string | undefined }> =
|
|
@@ -674,6 +730,14 @@ function render(roadmaps: RoadmapStats[], bundles: Bundle[] | null = null): stri
|
|
|
674
730
|
const pending = pending_iron_law_3(roadmaps);
|
|
675
731
|
const gated = merge_gated_pending(roadmaps);
|
|
676
732
|
const total_open_blockers = roadmaps.reduce((s, r) => s + r.open_blockers.length, 0);
|
|
733
|
+
// The count alone answers "is something blocked", never "is it on me" —
|
|
734
|
+
// and the per-roadmap anchors make the owner split a manual read across the
|
|
735
|
+
// whole file. Name the user's share here and point at the command that
|
|
736
|
+
// renders those entries as actions.
|
|
737
|
+
const user_open_blockers = roadmaps.reduce(
|
|
738
|
+
(s, r) => s + r.open_blockers.filter((b) => blocker_needs_user(b.owner)).length,
|
|
739
|
+
0,
|
|
740
|
+
);
|
|
677
741
|
const lines: string[] = [];
|
|
678
742
|
lines.push('# Roadmap Progress\n');
|
|
679
743
|
const header_meta =
|
|
@@ -682,7 +746,10 @@ function render(roadmaps: RoadmapStats[], bundles: Bundle[] | null = null): stri
|
|
|
682
746
|
' · [roadmaps/](roadmaps/) · [archive/](roadmaps/archive/) · ' +
|
|
683
747
|
'[skipped/](roadmaps/skipped/) · [later/](roadmaps/later/)' +
|
|
684
748
|
(total_open_blockers > 0
|
|
685
|
-
? ` · **${total_open_blockers}** open blocker${total_open_blockers !== 1 ? 's' : ''}`
|
|
749
|
+
? ` · **${total_open_blockers}** open blocker${total_open_blockers !== 1 ? 's' : ''}` +
|
|
750
|
+
(user_open_blockers > 0
|
|
751
|
+
? `, **${user_open_blockers}** need you → \`agent-config gates\``
|
|
752
|
+
: '')
|
|
686
753
|
: '') +
|
|
687
754
|
'\n';
|
|
688
755
|
lines.push(
|
|
@@ -1074,6 +1141,7 @@ export {
|
|
|
1074
1141
|
is_roadmap_candidate,
|
|
1075
1142
|
count_checkboxes,
|
|
1076
1143
|
parse_blockers,
|
|
1144
|
+
blocker_needs_user,
|
|
1077
1145
|
parse_roadmap,
|
|
1078
1146
|
bar,
|
|
1079
1147
|
collect,
|
|
@@ -786,6 +786,14 @@ subagents:
|
|
|
786
786
|
# (empty = one tier above implementer: opus if sonnet, sonnet if haiku)
|
|
787
787
|
judge_model: ""
|
|
788
788
|
|
|
789
|
+
# Session-wide model CEILING for subagents (token-economy-dispatch Phase
|
|
790
|
+
# 5.2). Empty (default) = no ceiling. When set (e.g. "claude-sonnet-4-5"),
|
|
791
|
+
# suite-owned CLI spawn wrappers export CLAUDE_CODE_SUBAGENT_MODEL to the
|
|
792
|
+
# sessions THEY launch — a spend cap on every subagent those sessions
|
|
793
|
+
# spawn. Class C: a human sets it; the agent never writes it and never
|
|
794
|
+
# infers one.
|
|
795
|
+
model_ceiling: ""
|
|
796
|
+
|
|
789
797
|
# Maximum number of parallel subagent invocations
|
|
790
798
|
# Integer, default 3. Set to 1 to serialize. Hard cap enforced by runtime.
|
|
791
799
|
max_parallel: 3
|
|
@@ -1182,6 +1190,34 @@ hooks:
|
|
|
1182
1190
|
injection_scan:
|
|
1183
1191
|
enabled: false
|
|
1184
1192
|
|
|
1193
|
+
# Stop-slot turn-end gate (road-to-conformance-round5 Phase 3). Default-OFF,
|
|
1194
|
+
# and it is the only concern in this file that can REFUSE a turn-end rather
|
|
1195
|
+
# than a tool call — so it soaks behind an explicit opt-in before it binds.
|
|
1196
|
+
#
|
|
1197
|
+
# Round 5 measured what it is for: both BLOCKING carriers reached zero
|
|
1198
|
+
# violations, neither ADVISORY carrier did, and 19 wrong-language replies
|
|
1199
|
+
# survived a pin that had fired 26-35 seconds earlier. A reminder at higher
|
|
1200
|
+
# frequency is the same failed mechanism running more often; a check at
|
|
1201
|
+
# delivery that can say no is a different one.
|
|
1202
|
+
#
|
|
1203
|
+
# enabled — master switch. false = the concern is a no-op costing one
|
|
1204
|
+
# settings read per turn-end.
|
|
1205
|
+
# promissory — detector A: the closing paragraph promises work not yet
|
|
1206
|
+
# performed while asking the user nothing (20 measured).
|
|
1207
|
+
# language — detector B: the user-visible prose is not in the pinned
|
|
1208
|
+
# language, with code, quotes, paths and identifiers already
|
|
1209
|
+
# excluded (19 measured).
|
|
1210
|
+
#
|
|
1211
|
+
# Both detectors default ON *within* the master switch, so opting in needs
|
|
1212
|
+
# one line, and either can be silenced without editing the hook manifest.
|
|
1213
|
+
# A refused turn continues in the same turn and is never refused twice —
|
|
1214
|
+
# re-entrancy is `stop_hook_active` plus a marker keyed on the last user
|
|
1215
|
+
# message, never on the reply.
|
|
1216
|
+
turn_end_gate:
|
|
1217
|
+
enabled: false
|
|
1218
|
+
promissory: true
|
|
1219
|
+
language: true
|
|
1220
|
+
|
|
1185
1221
|
# PreToolUse RTK-wrap nudge (token-saving Phase 3). Default-OFF. When enabled
|
|
1186
1222
|
# AND `rtk` is on PATH (a live probe — not a self-reported flag), warns (exit
|
|
1187
1223
|
# 2, never blocks) "re-run wrapped with rtk" before a single verbose CLI
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Registered metrics for road-to-token-economy-dispatch Phase 1 (registered BEFORE any reduction lever ships — the budget-ownership discipline of hook-latency-budget.json / preamble-payload-budget.json). Baseline anchor: road-to-cache-economy census 2026-07-30 (median subagent cold start 235.5k tokens written-or-uncached, C-1 cold-start dominance 69.7%). Thresholds are the roadmap's committed numbers; changing one is a PR citing evidence, never a drive-by edit.",
|
|
3
|
+
"schema_version": 1,
|
|
4
|
+
"registered_at": "2026-08-10",
|
|
5
|
+
"owner": "maintainer",
|
|
6
|
+
"review_by": "2026-11-10",
|
|
7
|
+
"metrics": {
|
|
8
|
+
"dispatch_floor": {
|
|
9
|
+
"definition": "Median init_tokens per role (worker, reviewer) and the COST-shaped init/work ratio per dispatched leg. init_tokens = billable_input of the leg's FIRST assistant record (raw — comparable to the cache-economy 235.5k baseline); work_tokens = leg total billable_input minus init_tokens. The RATIO is computed on weighted input units (read 0.1x, 5m write 1.25x, 1h write 2.0x — _lib/cc_transcript.ts weightedInputUnits), NOT raw billable_input: raw counts every later turn's cache-read re-transmission at full weight, which makes the 'more spent loading than working' threshold unmeetable by construction (measured live 2026-08-10: raw ratio 0.02 on the same legs). billable_input = input_tokens + cache_read_input_tokens + cache_creation_input_tokens (never usage.input_tokens alone).",
|
|
10
|
+
"source": "Primary: transcript ledger via src/scripts/_lib/cc_transcript.ts (per-agentId legs, message.id+requestId dedupe). Secondary: audit-log orchestration lines carrying init_tokens/work_tokens with floor_provenance=measured.",
|
|
11
|
+
"role_attribution": "HONEST LIMITATION until the role axis (Phase 2) ships: transcripts carry no session role, so all subagent legs report as role=worker; reviewer legs are attributed from audit-line agent_combo where present. The report states which attribution produced each number.",
|
|
12
|
+
"thresholds": {
|
|
13
|
+
"projection_mandatory_ratio": 1.0,
|
|
14
|
+
"_projection_mandatory": "sustained median init/work ratio > 1.0 for rung-1 workers over a review window makes Phase 3 (worker thin projection) mandatory, not optional",
|
|
15
|
+
"success_ratio_after_projection": 0.15,
|
|
16
|
+
"_success": "median ratio < 0.15 after Phase 3 ships is the roadmap's registered success criterion"
|
|
17
|
+
},
|
|
18
|
+
"baseline": {
|
|
19
|
+
"cold_start_median_tokens": 235500,
|
|
20
|
+
"cold_start_write_volume_share_pct": 69.7,
|
|
21
|
+
"at": "2026-07-30",
|
|
22
|
+
"source": "road-to-cache-economy first-party measurement (14-day window, host CC 2.1.220); re-measure on the post-9.29 tree is Phase 1's first report run"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"return_channel": {
|
|
26
|
+
"definition": "Orchestrator context growth per dispatch (roadmap Phase 6.3): serialized chars of the tool result returned into the parent context (return_channel_chars on sync completions, hook-carried). The failure this number exists to catch: the isolation win refunded through the return channel.",
|
|
27
|
+
"source": "orchestration_record_hook.ts on post_tool_use Agent/Task completions; a count derived from the result object, never its content.",
|
|
28
|
+
"thresholds": {
|
|
29
|
+
"envelope_cap_chars": 12000,
|
|
30
|
+
"_cap": "the committed MAX_ENVELOPE_CHARS from _lib/subagent_response.ts — a median return above it over the review window means transcript-shaped returns are flowing back and the envelope discipline is not holding"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"projection_quality": {
|
|
34
|
+
"definition": "Kill criterion for the worker thin projection (roadmap Phase 3.4, REGISTERED before the projection exists): worker verify-fail rate on projected sessions vs the pre-projection baseline.",
|
|
35
|
+
"source": "Orchestration lines' dispatch_outcome + verify_mode on role-marked sessions, compared against the same window pre-projection.",
|
|
36
|
+
"thresholds": {
|
|
37
|
+
"kill_margin_pp": 5,
|
|
38
|
+
"_kill": "verify-fail rate exceeding the pre-projection baseline by more than 5 percentage points over a 2-week review window reverts the projection for that rung and the regression is published (roadmap 3.4)"
|
|
39
|
+
},
|
|
40
|
+
"status": "armed-awaiting-projection: Phase 3.1-3.3 are data-window-gated (>= 2 weeks of rules_used); this criterion binds the moment a projection ships"
|
|
41
|
+
},
|
|
42
|
+
"ask_economy": {
|
|
43
|
+
"definition": "Rung-0.5 substitution and adoption (roadmap Phase 4.4): substitution = ask-routed lines / (ask-routed + rung-1 spawn lines); adoption = ask lines with dispatch_outcome DONE / all ask lines; escalation = the caller\u0027s follow-up SPAWN lines carrying route_taken=subagent + escalated=true + origin=dispatch-economy-2026 (the ask line itself cannot know it was insufficient).",
|
|
44
|
+
"source": "Audit-log orchestration lines with route_taken='ask' (written by src/scripts/ask_transport.ts, origin tag dispatch-economy-2026).",
|
|
45
|
+
"thresholds": {
|
|
46
|
+
"escalation_kill": 0.3,
|
|
47
|
+
"_kill": "if more than 30% of asks over the review window are re-asked or escalated to spawns, the rung's signals tighten or the rung dies — by evidence, in a PR (roadmap 4.4)"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"rules_efficiency": {
|
|
51
|
+
"definition": "rules_used / rules_carried per worker envelope (fields shipped by road-to-lean-agent-init, validated in _lib/orchestration_record.ts). Sustained low quota is simultaneously the Phase 3 cut-line evidence AND the demand-signal datum later/road-to-deferred-rule-retriever gate (2c) asks for — one measurement, two consumers.",
|
|
52
|
+
"source": "Audit-log orchestration lines (agents/runtime/state/audit/*.jsonl) where both fields are non-null.",
|
|
53
|
+
"thresholds": {
|
|
54
|
+
"low_quota_signal": 0.2,
|
|
55
|
+
"_low_quota": "a sustained median quota below 0.2 over the review window is the cut-line evidence Phase 3.1 requires (>= 2 weeks of data) and the retriever gate's demand signal"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"honest_null_consequence": "If the re-measured floor on the post-9.29 tree is materially smaller than the cache-economy baseline (roadmap step 1.4), phases 3-5 of road-to-token-economy-dispatch downgrade to blockers citing the null, and the null is published in docs/benchmark.md. A metric nobody re-runs by review_by is treated as the latency file's fossilization failure: the report command below is the re-verification procedure.",
|
|
60
|
+
"report_command": "./scripts-run src/scripts/dispatch_economy_report [--format text|json] [--root <transcripts>] [--audit-dir <dir>] [--max-age-days 14]"
|
|
61
|
+
}
|
|
@@ -58,11 +58,11 @@
|
|
|
58
58
|
"deterministic": false
|
|
59
59
|
},
|
|
60
60
|
"cli_help_command_count": {
|
|
61
|
-
"max":
|
|
62
|
-
"last_measured":
|
|
63
|
-
"last_measured_at": "2026-08-
|
|
61
|
+
"max": 97,
|
|
62
|
+
"last_measured": 97,
|
|
63
|
+
"last_measured_at": "2026-08-10",
|
|
64
64
|
"method": "registry enumeration — count of `{ name: '...', disposition` entries in src/cli/registry.ts (NOT --help prose parsing; methods historically disagreed 74 vs 76 vs 79)",
|
|
65
|
-
"baseline_note": "80 → 81 on 2026-08-03 (release 9.15.0 gate). This RECORDS growth that already shipped through its own reviewed PR; it does not authorize new surface. The +1 is f02db54c2 (fix(cli): register routing:doctor in the CLI registry) — the command existed, only its registry entry was missing, and the registering PR did not move this budget. The gate did its job again: breach WARNS on main and BLOCKS on release, which is the conscious-decision point rather than a silently raised cap. Re-measured 2026-08-03 via the pinned method: 81. Headroom stays zero by design — the next registry addition must move this number in its own PR. 81 → 82 on 2026-08-04: the `handoff` subcommand (road-to-agent-handoff-resume Phase 4) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 82. 82 → 84 on 2026-08-04: the `route:explain` + `route:audit` verbs (routing-correctness validation surface) register in the same PR that adds the surface. Re-measured via the pinned method: 84. 84 → 85 on 2026-08-05: the `settings:set` verb (road-to-zero-ceremony-settings Phase 2 — the one agent-reachable settings writer) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 85. 85 → 86 on 2026-08-06: the `conformance:behavior` verb (road-to-agent-behavior-conformance Phase 5 — replays the local transcript store through the four mechanised conformance checks) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 86. 86 → 88 on 2026-08-07: the `sessions:list` + `sessions:claim` verbs (road-to-parallel-session-coordination — the shell surface of the shared session register, needed because /roadmap:next is command markdown and can only reach the register through a shell call) register in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 88. 88 → 89 on 2026-08-08: the `council:status` verb registers in the same PR that adds the surface, per this record's own contract. It exists because an agent in a consumer project announced \"kein Council konfiguriert (keine .agent-settings.yml)\" and substituted a weaker path while the council was configured user-globally the whole time — there was no verb that answered availability, so the only route was an inference from a filename, and the intuitive filename is the one location ADR-104 guarantees is never consulted. Re-measured via the pinned method: 89. 89 → 91 on 2026-08-08: the `self-repair:status` + `self-repair:release` verbs (the queued-defect reader and the single Hard-Floor-gated outward step of the self-repair loop — the loop is deliberately unable to publish unless a human runs the release command) register in the same PR that adds the surface, per this record's own contract. Landed alongside the +1 council:status move in the same branch. Re-measured via the pinned method: 91. 91 → 94 on 2026-08-08: the `packs:active`, `settings:get` and `mcp:available` verbs (road-to-capability-answerability Phase 2 — the three capability probes for the gaps that had none) register in the same PR that adds the surface, per this record's own contract. Each answers a question that was previously only inferable from a filename: which packs are active and from which file, what a setting resolves to and which file set it, and what is declared in mcp.json versus what can actually launch. 94 -> 95 in the same PR: `brand:status` (Phase 2.4 — brand-layer presence, which the rule depended on and no surface could answer). Re-measured twice via the pinned method on the unchanged tree: 95/95."
|
|
65
|
+
"baseline_note": "80 → 81 on 2026-08-03 (release 9.15.0 gate). This RECORDS growth that already shipped through its own reviewed PR; it does not authorize new surface. The +1 is f02db54c2 (fix(cli): register routing:doctor in the CLI registry) — the command existed, only its registry entry was missing, and the registering PR did not move this budget. The gate did its job again: breach WARNS on main and BLOCKS on release, which is the conscious-decision point rather than a silently raised cap. Re-measured 2026-08-03 via the pinned method: 81. Headroom stays zero by design — the next registry addition must move this number in its own PR. 81 → 82 on 2026-08-04: the `handoff` subcommand (road-to-agent-handoff-resume Phase 4) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 82. 82 → 84 on 2026-08-04: the `route:explain` + `route:audit` verbs (routing-correctness validation surface) register in the same PR that adds the surface. Re-measured via the pinned method: 84. 84 → 85 on 2026-08-05: the `settings:set` verb (road-to-zero-ceremony-settings Phase 2 — the one agent-reachable settings writer) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 85. 85 → 86 on 2026-08-06: the `conformance:behavior` verb (road-to-agent-behavior-conformance Phase 5 — replays the local transcript store through the four mechanised conformance checks) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 86. 86 → 88 on 2026-08-07: the `sessions:list` + `sessions:claim` verbs (road-to-parallel-session-coordination — the shell surface of the shared session register, needed because /roadmap:next is command markdown and can only reach the register through a shell call) register in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 88. 88 → 89 on 2026-08-08: the `council:status` verb registers in the same PR that adds the surface, per this record's own contract. It exists because an agent in a consumer project announced \"kein Council konfiguriert (keine .agent-settings.yml)\" and substituted a weaker path while the council was configured user-globally the whole time — there was no verb that answered availability, so the only route was an inference from a filename, and the intuitive filename is the one location ADR-104 guarantees is never consulted. Re-measured via the pinned method: 89. 89 → 91 on 2026-08-08: the `self-repair:status` + `self-repair:release` verbs (the queued-defect reader and the single Hard-Floor-gated outward step of the self-repair loop — the loop is deliberately unable to publish unless a human runs the release command) register in the same PR that adds the surface, per this record's own contract. Landed alongside the +1 council:status move in the same branch. Re-measured via the pinned method: 91. 91 → 94 on 2026-08-08: the `packs:active`, `settings:get` and `mcp:available` verbs (road-to-capability-answerability Phase 2 — the three capability probes for the gaps that had none) register in the same PR that adds the surface, per this record's own contract. Each answers a question that was previously only inferable from a filename: which packs are active and from which file, what a setting resolves to and which file set it, and what is declared in mcp.json versus what can actually launch. 94 -> 95 in the same PR: `brand:status` (Phase 2.4 — brand-layer presence, which the rule depended on and no surface could answer). Re-measured twice via the pinned method on the unchanged tree: 95/95. 95 → 96 on 2026-08-10: the `session:recycle` verb (road-to-token-economy-recycling Phase 2.2 — the deterministic producer of the main-session recycle envelope; the recycle advisory names this exact verb, so it must exist as a shell surface) registers in the same PR that adds the surface, per this record's own contract. Re-measured via the pinned method: 96. 96 -> 97 on 2026-08-10: the `gates` verb registers in the same PR that adds the surface, per this record's own contract. It exists because the blocker fields the dashboard already parses (Owner, What to do, Resolved when — CI-enforced by lint_roadmap_blockers) had no owner-filtered reader: the dashboard counts the open blockers across a 600-line file and links each to a per-roadmap anchor, so \"which of these are mine\" was a manual read of every anchor. The same inference-from-a-filename shape as the council:status entry above, one layer up. This branch and the session:recycle branch each measured 96 independently and landed on the same number for different verbs; the merge makes it 97, re-measured twice via the pinned method on the merged tree: 97/97."
|
|
66
66
|
},
|
|
67
67
|
"mcp_public_tool_count": {
|
|
68
68
|
"max": 19,
|