@event4u/agent-config 14.2.0 → 14.4.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 +286 -218
- package/agents/templates/.ai-council.yml.example +35 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +83 -12
- package/dist/agent-src/rules/roadmap-progress-sync.md +48 -4
- package/dist/agent-src/scripts/resume_probe.ts +154 -9
- package/dist/agent-src/scripts/roadmap_gates.ts +210 -6
- package/dist/agent-src/skills/roadmap-management/SKILL.md +9 -4
- package/dist/agent-src/templates/agent-settings.md +70 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/telemetry/remote.ts +571 -0
- package/dist/agent-src/templates/scripts/telemetry/settings.ts +222 -44
- package/dist/cli/python/workspace_drive.js +8 -3
- package/dist/cli/python/workspace_drive.js.map +1 -1
- package/dist/cli/python/workspace_hosts.js +16 -12
- package/dist/cli/python/workspace_hosts.js.map +1 -1
- package/dist/cli/registry.js +2 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-F3YUF7SA.js → chunk-3UCJWMMP.js} +1 -1
- package/dist/cli-delegate/chunk-F7DQWYR5.js +69 -0
- package/dist/cli-delegate/{chunk-7PZ4FSZB.js → chunk-FBKL6JWT.js} +61 -2
- package/dist/cli-delegate/{chunk-MNWRDMR5.js → chunk-PKOBQADP.js} +1 -1
- package/dist/cli-delegate/cmd_conformance.js +26 -11
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +3 -3
- package/dist/cli-delegate/cmd_route_audit.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +17 -3
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +4 -4
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.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 +3684 -1719
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1070 -408
- package/dist/scripts/_lib/host_launch.js +66 -0
- package/dist/scripts/_lib/host_launch.js.map +1 -0
- package/dist/server/io/yamlIO.js +175 -3
- package/dist/server/io/yamlIO.js.map +1 -1
- package/dist/server/routes/wizard.js +20 -1
- package/dist/server/routes/wizard.js.map +1 -1
- package/dist/shared/settingsConsent.js +25 -0
- package/dist/shared/settingsConsent.js.map +1 -1
- package/docs/contracts/settings-classes.md +11 -0
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +2 -1
- package/package.json +1 -1
- package/src/agent-src/scripts/resume_probe.ts +154 -9
- package/src/agent-src/scripts/roadmap_gates.ts +210 -6
- package/src/agent-src/templates/scripts/telemetry/remote.ts +571 -0
- package/src/agent-src/templates/scripts/telemetry/settings.ts +222 -44
- package/src/config/estate-count-budget.json +120 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +32 -4
- package/src/config/gate-violation-baselines.json +63 -63
- package/src/config/gitignore-block.txt +5 -0
- package/src/config/hook-latency-budget.json +40 -1
- package/src/config/hook-token-budget.json +16 -0
- package/src/config/rule-stub-ceilings.json +11 -3
- package/src/scripts/_dispatch.bash +42 -1
- package/src/scripts/_lib/council_fallback_posture.ts +81 -0
- package/src/scripts/_lib/council_fallback_wiring.ts +126 -0
- package/src/scripts/_lib/council_settings_block.ts +152 -0
- package/src/scripts/_lib/headless_invocation.ts +285 -0
- package/src/scripts/_lib/host_launch.ts +72 -0
- package/src/scripts/_lib/map_to_object.ts +16 -0
- package/src/scripts/_lib/roadmap_checkboxes.ts +88 -0
- package/src/scripts/_lib/run_checkpoint.ts +409 -0
- package/src/scripts/_lib/session_register.ts +32 -1
- package/src/scripts/_lib/stdin.ts +35 -1
- package/src/scripts/_lib/turn_end_refusals.ts +12 -4
- package/src/scripts/_lib/unattended_guard.ts +371 -0
- package/src/scripts/ai_council/config.ts +27 -1
- package/src/scripts/ai_council/events_log.ts +12 -1
- package/src/scripts/ai_council/fallback_config.ts +114 -0
- package/src/scripts/ai_council/mid_flight_fallback.ts +295 -0
- package/src/scripts/ai_council/orchestrator.ts +251 -292
- package/src/scripts/ai_council/py_parity.ts +139 -0
- package/src/scripts/ai_council/response_render.ts +99 -0
- package/src/scripts/ai_council/spend_gate.ts +123 -0
- package/src/scripts/ai_council/transport_resolver.ts +81 -13
- package/src/scripts/bench_hook_injection.ts +100 -6
- package/src/scripts/bench_hook_latency.ts +442 -16
- package/src/scripts/check_estate_count.ts +924 -0
- package/src/scripts/compile_hook_manifest.ts +67 -0
- package/src/scripts/council_cli.ts +100 -125
- package/src/scripts/decision_memo.ts +325 -0
- package/src/scripts/hook_manifest.json +1 -0
- package/src/scripts/hook_manifest.yaml +172 -25
- package/src/scripts/hooks/concern_registry.ts +6 -0
- package/src/scripts/hooks/dispatch_hook.ts +183 -10
- package/src/scripts/hooks/dispatch_issues.ts +26 -0
- package/src/scripts/hooks/end_review_nudge_hook.ts +97 -24
- package/src/scripts/hooks/hook_stdin.ts +111 -5
- package/src/scripts/hooks/injection_budget.ts +595 -0
- package/src/scripts/hooks/payload_stub.ts +446 -0
- package/src/scripts/hooks/run_continuation_hook.ts +627 -0
- package/src/scripts/hooks/session_eol_hook.ts +52 -0
- package/src/scripts/hooks/telemetry_usage_hook.ts +339 -0
- package/src/scripts/hooks/tool_result_bytes_hook.ts +27 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +45 -7
- package/src/scripts/hooks_doctor.ts +16 -2
- package/src/scripts/interruption_report.ts +330 -13
- package/src/scripts/lint_hook_manifest.ts +167 -0
- package/src/scripts/roadmap_progress_hook.ts +254 -46
- package/src/scripts/run_supervise.ts +623 -0
- package/src/scripts/session_register_hook.ts +108 -8
- package/src/scripts/sessions_cli.ts +18 -6
- package/src/scripts/telemetry_disclosure_hook.ts +244 -0
- package/src/server/io/yamlIO.ts +167 -3
- package/src/shared/settingsConsent.ts +49 -2
- package/dist/cli-delegate/chunk-MMJL4TIX.js +0 -18
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
* `telemetry.artifact_engagement` namespace from `.agent-settings.yml`.
|
|
6
6
|
* Tolerates a missing file, a missing section, and a missing YAML parser —
|
|
7
7
|
* the default-off doctrine means "everything unparseable means disabled".
|
|
8
|
+
*
|
|
9
|
+
* Three namespaces live here, all under `telemetry:` and all default-off:
|
|
10
|
+
* `artifact_engagement` (the CLI's own log), `tier_usage` (the documented
|
|
11
|
+
* signal contract), and `remote` (road-to-org-telemetry Phase 1 — the
|
|
12
|
+
* org-pack-enabled Class-A usage record). They share one doctrine and one
|
|
13
|
+
* set of coercion helpers; they share no state.
|
|
8
14
|
*/
|
|
9
15
|
import { createRequire } from 'node:module';
|
|
10
16
|
import * as fs from 'node:fs';
|
|
@@ -100,32 +106,49 @@ function _isFile(p: string): boolean {
|
|
|
100
106
|
}
|
|
101
107
|
}
|
|
102
108
|
|
|
103
|
-
/**
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Read one `telemetry.<key>` section out of a settings file.
|
|
111
|
+
*
|
|
112
|
+
* Every branch that cannot produce a section returns the empty one with
|
|
113
|
+
* `present: false` — a missing file, unparseable YAML, a missing `telemetry:`
|
|
114
|
+
* mapping, and a missing sub-key are deliberately indistinguishable here.
|
|
115
|
+
* That IS the default-off doctrine: unparseable means disabled, and a reader
|
|
116
|
+
* that could tell those cases apart would invite a caller to treat one of
|
|
117
|
+
* them as consent.
|
|
118
|
+
*/
|
|
119
|
+
function _read_telemetry_section(
|
|
120
|
+
p: string,
|
|
121
|
+
key: string,
|
|
122
|
+
): { section: Record<string, unknown>; present: boolean } {
|
|
123
|
+
if (!_isFile(p)) {
|
|
124
|
+
return { section: {}, present: false };
|
|
125
|
+
}
|
|
126
|
+
let raw: unknown = {};
|
|
127
|
+
try {
|
|
128
|
+
raw = YAML.parse(fs.readFileSync(p, 'utf-8'), { version: '1.1' });
|
|
129
|
+
if (raw === null || raw === undefined) {
|
|
116
130
|
raw = {};
|
|
117
131
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
132
|
+
} catch {
|
|
133
|
+
raw = {};
|
|
134
|
+
}
|
|
135
|
+
if (!_isPlainObject(raw)) {
|
|
136
|
+
return { section: {}, present: false };
|
|
137
|
+
}
|
|
138
|
+
const tele = raw['telemetry'];
|
|
139
|
+
if (!_isPlainObject(tele)) {
|
|
140
|
+
return { section: {}, present: false };
|
|
141
|
+
}
|
|
142
|
+
const sub = tele[key];
|
|
143
|
+
if (!_isPlainObject(sub)) {
|
|
144
|
+
return { section: {}, present: false };
|
|
128
145
|
}
|
|
146
|
+
return { section: sub, present: true };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Return parsed telemetry settings — never raises on missing data. */
|
|
150
|
+
export function read_settings(p: string): TelemetrySettings {
|
|
151
|
+
const { section, present: section_present } = _read_telemetry_section(p, 'artifact_engagement');
|
|
129
152
|
|
|
130
153
|
const record = _isPlainObject(section['record']) ? section['record'] : {};
|
|
131
154
|
const output = _isPlainObject(section['output']) ? section['output'] : {};
|
|
@@ -164,27 +187,7 @@ export class TierUsageSettings {
|
|
|
164
187
|
|
|
165
188
|
/** Return parsed tier-usage settings — never raises on missing data. */
|
|
166
189
|
export function read_tier_usage_settings(p: string): TierUsageSettings {
|
|
167
|
-
|
|
168
|
-
if (_isFile(p)) {
|
|
169
|
-
let raw: unknown = {};
|
|
170
|
-
try {
|
|
171
|
-
raw = YAML.parse(fs.readFileSync(p, 'utf-8'), { version: '1.1' });
|
|
172
|
-
if (raw === null || raw === undefined) {
|
|
173
|
-
raw = {};
|
|
174
|
-
}
|
|
175
|
-
} catch {
|
|
176
|
-
raw = {};
|
|
177
|
-
}
|
|
178
|
-
if (_isPlainObject(raw)) {
|
|
179
|
-
const tele = raw['telemetry'];
|
|
180
|
-
if (_isPlainObject(tele)) {
|
|
181
|
-
const tu = tele['tier_usage'];
|
|
182
|
-
if (_isPlainObject(tu)) {
|
|
183
|
-
section = tu;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
190
|
+
const { section } = _read_telemetry_section(p, 'tier_usage');
|
|
188
191
|
|
|
189
192
|
const output = _isPlainObject(section['output']) ? section['output'] : {};
|
|
190
193
|
const retier = _isPlainObject(section['retier']) ? section['retier'] : {};
|
|
@@ -208,3 +211,178 @@ export function read_tier_usage_settings(p: string): TierUsageSettings {
|
|
|
208
211
|
min_distinct_users: _coerce_int(retier['min_distinct_users'], defaults.min_distinct_users),
|
|
209
212
|
});
|
|
210
213
|
}
|
|
214
|
+
|
|
215
|
+
// ── telemetry.remote — org-pack Class-A usage records ───────────────────
|
|
216
|
+
//
|
|
217
|
+
// road-to-org-telemetry Phase 1. Same default-off doctrine as the two
|
|
218
|
+
// namespaces above, with one addition the others do not need: this is the
|
|
219
|
+
// only namespace whose records are intended to LEAVE the machine (Phase 2
|
|
220
|
+
// transports them; Phase 1 writes them locally and nothing else). So
|
|
221
|
+
// `enabled: true` alone is deliberately not enough to switch it on.
|
|
222
|
+
//
|
|
223
|
+
// `flush: never` AND RETENTION, TOGETHER. `never` means no transport, so the
|
|
224
|
+
// local file is the only store — and the retention budget below then decides
|
|
225
|
+
// what survives. A record evicted by retention under `flush: never` is a
|
|
226
|
+
// record that is never sent anywhere. That is the intended trade of a growth
|
|
227
|
+
// budget rather than an oversight, and it is stated here so an operator meets
|
|
228
|
+
// it in the settings surface instead of inferring it from a short file. An
|
|
229
|
+
// org that needs the full history configures a flush; an org that wants a
|
|
230
|
+
// longer local history raises `retention.max_age_days`.
|
|
231
|
+
|
|
232
|
+
export const DEFAULT_REMOTE_LOG_PATH = '.agent-telemetry.jsonl';
|
|
233
|
+
export const DEFAULT_REMOTE_FLUSH = 'session-end';
|
|
234
|
+
export const ALLOWED_REMOTE_FLUSH = ['session-end', 'never'] as const;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Growth budget for the local record log (`scale-discipline` R-A7).
|
|
238
|
+
*
|
|
239
|
+
* Mirrors `DEFAULT_RETENTION_MAX_AGE_DAYS` / `DEFAULT_RETENTION_MAX_BYTES`
|
|
240
|
+
* in `remote.ts`, which is where the measured basis for both numbers is
|
|
241
|
+
* written down. Duplicated as literals rather than imported because this
|
|
242
|
+
* module is the settings surface and must stay readable on its own; the
|
|
243
|
+
* pairing is asserted by a test rather than left to a comment.
|
|
244
|
+
*/
|
|
245
|
+
export const DEFAULT_REMOTE_RETENTION = {
|
|
246
|
+
max_age_days: 90,
|
|
247
|
+
max_bytes: 2 * 1024 * 1024,
|
|
248
|
+
} as const;
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Smallest legal retention override, per key.
|
|
252
|
+
*
|
|
253
|
+
* A budget below these is not a stricter budget, it is a broken one: a
|
|
254
|
+
* `max_bytes` under one record line empties the log on every append, and a
|
|
255
|
+
* `max_age_days` of zero prunes everything written before this instant. Both
|
|
256
|
+
* are the "retains nothing" outcome the zero-rejection above refuses, reached
|
|
257
|
+
* by a value that is technically positive. 4 KiB is one full record clear of
|
|
258
|
+
* the largest line measured (286 bytes).
|
|
259
|
+
*/
|
|
260
|
+
export const MIN_RETENTION_MAX_AGE_DAYS = 1;
|
|
261
|
+
export const MIN_RETENTION_MAX_BYTES = 4096;
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* The three fields that must ALL carry a value — on top of `enabled` —
|
|
265
|
+
* before a single record is written. `endpoint` and `org_id` name where the
|
|
266
|
+
* data goes and on whose authority; `salt` is the org-pack secret without
|
|
267
|
+
* which the user hash would be a plain hash of a login name — i.e.
|
|
268
|
+
* reversible by dictionary.
|
|
269
|
+
*
|
|
270
|
+
* None of the four has a usable default, and that is the point: an
|
|
271
|
+
* external clone of this repository carries the key NAMES and no values,
|
|
272
|
+
* so it cannot reach `active` by copying the tree.
|
|
273
|
+
*/
|
|
274
|
+
export const REMOTE_REQUIRED_FIELDS = ['endpoint', 'org_id', 'salt'] as const;
|
|
275
|
+
|
|
276
|
+
export class RemoteTelemetrySettings {
|
|
277
|
+
/** The raw `enabled:` value. NOT the switch — see `active`. */
|
|
278
|
+
readonly enabled: boolean;
|
|
279
|
+
readonly endpoint: string;
|
|
280
|
+
readonly org_id: string;
|
|
281
|
+
/** Org-pack secret. Never written into a record, never logged. */
|
|
282
|
+
readonly salt: string;
|
|
283
|
+
readonly flush: string;
|
|
284
|
+
readonly log_path: string;
|
|
285
|
+
/**
|
|
286
|
+
* Growth budget for `log_path`. Always present — an install that
|
|
287
|
+
* declares no `retention:` gets the defaults rather than no policy,
|
|
288
|
+
* because "unbounded" is the state R-A7 forbids and a settings file is
|
|
289
|
+
* not a place to opt back into it.
|
|
290
|
+
*/
|
|
291
|
+
readonly retention_max_age_days: number;
|
|
292
|
+
readonly retention_max_bytes: number;
|
|
293
|
+
|
|
294
|
+
constructor(init: {
|
|
295
|
+
enabled: boolean;
|
|
296
|
+
endpoint: string;
|
|
297
|
+
org_id: string;
|
|
298
|
+
salt: string;
|
|
299
|
+
flush: string;
|
|
300
|
+
log_path: string;
|
|
301
|
+
retention_max_age_days: number;
|
|
302
|
+
retention_max_bytes: number;
|
|
303
|
+
}) {
|
|
304
|
+
this.enabled = init.enabled;
|
|
305
|
+
this.endpoint = init.endpoint;
|
|
306
|
+
this.org_id = init.org_id;
|
|
307
|
+
this.salt = init.salt;
|
|
308
|
+
this.flush = init.flush;
|
|
309
|
+
this.log_path = init.log_path;
|
|
310
|
+
this.retention_max_age_days = init.retention_max_age_days;
|
|
311
|
+
this.retention_max_bytes = init.retention_max_bytes;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Which required fields are absent. Empty array + `enabled` ⇒ `active`.
|
|
316
|
+
* Exposed so a doctor command can say WHICH field is missing without
|
|
317
|
+
* printing the salt, rather than reporting a bare "disabled".
|
|
318
|
+
*/
|
|
319
|
+
get missing(): string[] {
|
|
320
|
+
const out: string[] = [];
|
|
321
|
+
for (const field of REMOTE_REQUIRED_FIELDS) {
|
|
322
|
+
if (!this[field]) {
|
|
323
|
+
out.push(field);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return out;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** The real switch: opted in AND fully configured by the org pack. */
|
|
330
|
+
get active(): boolean {
|
|
331
|
+
return this.enabled && this.missing.length === 0;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Return parsed remote-telemetry settings — never raises on missing data.
|
|
337
|
+
*
|
|
338
|
+
* Fail-closed on every axis. A missing file, unparseable YAML, a missing
|
|
339
|
+
* section, a missing field, or an unknown `flush` value all resolve to a
|
|
340
|
+
* settings object whose `active` is false.
|
|
341
|
+
*/
|
|
342
|
+
export function read_remote_settings(p: string): RemoteTelemetrySettings {
|
|
343
|
+
const { section } = _read_telemetry_section(p, 'remote');
|
|
344
|
+
const output = _isPlainObject(section['output']) ? section['output'] : {};
|
|
345
|
+
const retention = _isPlainObject(section['retention']) ? section['retention'] : {};
|
|
346
|
+
|
|
347
|
+
// A retention override must be a positive integer AND at least the floor
|
|
348
|
+
// for its key. Zero and negatives are rejected to the default rather than
|
|
349
|
+
// honoured: `max_age_days: 0` reads like "keep nothing" but would in
|
|
350
|
+
// practice mean "prune on every append", and neither is a growth budget an
|
|
351
|
+
// operator should be able to set by typing one character.
|
|
352
|
+
//
|
|
353
|
+
// The floor exists because rejecting only `0` left the strictly worse
|
|
354
|
+
// value reachable. `max_bytes: 1` is a positive integer, passes a
|
|
355
|
+
// >0 test, and is smaller than one ~270-byte record — so every append
|
|
356
|
+
// prunes the file back to empty and the log silently retains nothing,
|
|
357
|
+
// which is exactly the outcome the zero-check was written to refuse.
|
|
358
|
+
// `MIN_RETENTION_MAX_BYTES` is one full record over the largest observed
|
|
359
|
+
// line so the smallest legal setting still keeps something.
|
|
360
|
+
const _coerce_bounded_int = (value: unknown, def: number, floor: number): number => {
|
|
361
|
+
if (typeof value === 'boolean') {
|
|
362
|
+
return def;
|
|
363
|
+
}
|
|
364
|
+
if (typeof value === 'number' && Number.isInteger(value) && value >= floor) {
|
|
365
|
+
return value;
|
|
366
|
+
}
|
|
367
|
+
return def;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
return new RemoteTelemetrySettings({
|
|
371
|
+
enabled: _coerce_bool(section['enabled'], false),
|
|
372
|
+
endpoint: _coerce_str(section['endpoint'], ''),
|
|
373
|
+
org_id: _coerce_str(section['org_id'], ''),
|
|
374
|
+
salt: _coerce_str(section['salt'], ''),
|
|
375
|
+
flush: _coerce_str(section['flush'], DEFAULT_REMOTE_FLUSH, ALLOWED_REMOTE_FLUSH),
|
|
376
|
+
log_path: _coerce_path(output['path'], DEFAULT_REMOTE_LOG_PATH),
|
|
377
|
+
retention_max_age_days: _coerce_bounded_int(
|
|
378
|
+
retention['max_age_days'],
|
|
379
|
+
DEFAULT_REMOTE_RETENTION.max_age_days,
|
|
380
|
+
MIN_RETENTION_MAX_AGE_DAYS,
|
|
381
|
+
),
|
|
382
|
+
retention_max_bytes: _coerce_bounded_int(
|
|
383
|
+
retention['max_bytes'],
|
|
384
|
+
DEFAULT_REMOTE_RETENTION.max_bytes,
|
|
385
|
+
MIN_RETENTION_MAX_BYTES,
|
|
386
|
+
),
|
|
387
|
+
});
|
|
388
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Roadmap-estate ratchet (road-to-estate-drawdown T2 + T3). The estate does not walk down by itself: new roadmaps arrive faster than old ones terminate, and until this file existed nothing in CI objected \u2014 lint_roadmap_family_cap bounds concurrency WITHIN one road-to-<family>-* family, and no gate read the estate as a whole. The baseline below is the MEASURED estate at registration, not a target: a hard target gate would be red on the day it lands and would teach the reader to ignore it. It fails on GROWTH instead, so the number can only walk down. T1's proposed ceilings are recorded under `target` and are explicitly NOT registered \u2014 they are the maintainer's to set.",
|
|
3
|
+
"schema_version": 1,
|
|
4
|
+
"registered_at": "2026-08-18",
|
|
5
|
+
"owner": "maintainer",
|
|
6
|
+
"review_by": "2027-08-18",
|
|
7
|
+
"metric": {
|
|
8
|
+
"basis": "src/scripts/check_estate_count.ts, using collect() from update_roadmap_progress \u2014 the same parser the dashboard is generated from, imported rather than re-implemented",
|
|
9
|
+
"active_roadmaps": "non-draft roadmap candidates at the top level of agents/roadmaps/, as collect() counts them. Verified 2026-08-18: this is exactly the number the dashboard header prints as 'N open roadmaps'.",
|
|
10
|
+
"later_roadmaps": "roadmap-shaped *.md files under agents/roadmaps/later/ \u2014 the same is_roadmap_candidate name filter the active side uses, so later/README.md is not counted. Counted from the filesystem because parked roadmaps sit outside collect()'s corpus by design; they are estate all the same, and a drawdown that moved everything into later/ and called the number down would be the burial T1's quality anchor exists to prevent.",
|
|
11
|
+
"open_blockers": "open blockers summed over the active tree AND later/. This deliberately does NOT equal the dashboard header's blocker count, which is active-only: counting only the active tree meant PARKING a roadmap dropped this number without resolving anything, and the ratchet then printed 'free tightening' over a burial. AC-1 is phrased on this metric, so it has to survive a move that resolves nothing. archive/ and skipped/ stay out \u2014 those are terminal, and a blocker in a skipped roadmap is not open work.",
|
|
12
|
+
"not_gated": [
|
|
13
|
+
"the needs-you subset of open blockers \u2014 it moves when a blocker changes OWNER, which is not estate growth, so gating it would red on a reclassification that changed nothing about the size of the estate",
|
|
14
|
+
"agents/roadmaps/skipped/ and archive/ \u2014 these are the terminal destinations a drawdown sends files to; gating their growth would penalise the wanted direction",
|
|
15
|
+
"agents/roadmaps/stubs/ as a COUNT \u2014 a stub is not yet a roadmap, so it is not estate. It IS recognised as an offset destination and as an addition source, because un-stubbing is the documented promotion path and T3 has to be able to charge it."
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
"baseline": {
|
|
19
|
+
"active_roadmaps": 33,
|
|
20
|
+
"later_roadmaps": 50,
|
|
21
|
+
"open_blockers": 70
|
|
22
|
+
},
|
|
23
|
+
"baseline_history": [
|
|
24
|
+
{
|
|
25
|
+
"at": "2026-08-18",
|
|
26
|
+
"active_roadmaps": 38,
|
|
27
|
+
"later_roadmaps": 44,
|
|
28
|
+
"open_blockers": 49,
|
|
29
|
+
"why": "registered with the gate, at the measured estate. Note the roadmap's own \u00a7 0 quotes 37 active / 44 later / 38 blockers against a mid-August pin; the estate moved between that draft and this registration, which is the arrival rate T3 exists to bound rather than a correction to the roadmap."
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"at": "2026-08-18",
|
|
33
|
+
"active_roadmaps": 38,
|
|
34
|
+
"later_roadmaps": 43,
|
|
35
|
+
"open_blockers": 67,
|
|
36
|
+
"why": "Two metrics REDEFINED before the gate ever ran on a second commit, both on R2 findings against this same change, so these are re-measurements rather than raises. later_roadmaps 44 -> 43: the first count included later/README.md, which the active side's own name filter excludes \u2014 an off-by-one that made the baseline describe 43 roadmaps plus a README. open_blockers 49 -> 67: the first count spanned the active tree only, so PARKING a roadmap dropped the gated number without resolving anything and the ratchet printed 'free tightening' over a burial. It now spans active + later/ (49 + 18), which is why the number rose while the estate did not. The cost is stated rather than hidden: this metric no longer equals the dashboard header, and the first version claimed that parity as a virtue."
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"at": "2026-08-18",
|
|
40
|
+
"active_roadmaps": 38,
|
|
41
|
+
"later_roadmaps": 43,
|
|
42
|
+
"open_blockers": 69,
|
|
43
|
+
"why": "open_blockers 67 -> 69, and this is a correction of a baseline that never described the trunk rather than room made for new growth. 67 was measured at 554b032aa, the commit that registered this gate (PR #1420). PR #1418 merged AFTER it and carried road-to-per-turn-hook-economy from one open blocker to three \u2014 verified by counting that one file at both refs, 1 at 554b032aa and 3 at 851568b5c, which is the entire delta. #1418's own CI ran before the gate existed, so nothing objected at the time and the trunk arrived at 69 with the file still claiming 67. The consequence was not a loose ratchet but a permanently red one: the count half compares the live tree against this baseline irrespective of the diff, so EVERY subsequent PR failed on growth it had not caused, and the first to notice was an evidence-only PR that adds no blocker at all. Raising to the measured trunk value restores the property the gate is for \u2014 it fails on growth from here, and 69 is now the number that must walk down. The two blockers themselves are untouched and remain open work on road-to-per-turn-hook-economy; closing or parking someone else's blockers to buy the old number back would have been a terminal disposition of another session's work, which is exactly what this ratchet must never incentivise."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"at": "2026-08-18",
|
|
47
|
+
"active_roadmaps": 38,
|
|
48
|
+
"later_roadmaps": 43,
|
|
49
|
+
"open_blockers": 72,
|
|
50
|
+
"why": "RAISED by 3, on top of the 67 -> 69 correction above rather than beside it. All three are road-to-per-turn-hook-economy Phase 2, and each records a finding that would otherwise rot: b-payload-read-parse-dominates (two independent measurements now say D-2's cost is the dispatcher's own read+parse of the payload, once per event, not the per-concern churn the roadmap blamed \u2014 so the phase's AC is refuted and the null is published), b-payload-mis-nested-readers (two concerns read tool payload keys off the envelope root; one is a security scanner that works only via a whole-envelope fallback, the other returns 0 on every dispatcher-path call today), and b-stop-async-split-prerequisites (step 5.3 deferred, carrying the five prerequisites its classification surfaced). A blocker that puts a maintainer decision in front of the maintainer is the estate growing in the direction this ratchet exists FOR, which is why the raise is taken rather than the findings being dropped into prose where nothing tracks them. My first version of this entry claimed two of five as unattributed drift; the trunk's correction identified the same two and named their cause, so that half is withdrawn rather than restated."
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"at": "2026-08-19",
|
|
54
|
+
"active_roadmaps": 37,
|
|
55
|
+
"later_roadmaps": 43,
|
|
56
|
+
"open_blockers": 71,
|
|
57
|
+
"why": "LOWERED by 1 on each gated metric, in the direction this ratchet exists for. road-to-stop-gate-honesty reached zero open steps and zero deferred items and was archived, which is a terminal disposition rather than a park; its `b-detector-demotion-bars` was resolved with the decision recorded at the entry, not moved elsewhere. Both numbers are therefore measurements of a smaller estate, not room bought by relocating work \u2014 later_roadmaps is unchanged at 43, which is the check that distinguishes the two. Walked down because the count gate reported both as free tightening and its unit test asserts the committed budget equals the live tree: a baseline left above a lower measurement is headroom a later change can spend without any gate objecting, which is the loose-ratchet failure the 67 -> 69 entry above records from the other direction."
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"at": "2026-08-19",
|
|
61
|
+
"active_roadmaps": 38,
|
|
62
|
+
"later_roadmaps": 43,
|
|
63
|
+
"open_blockers": 72,
|
|
64
|
+
"why": "RAISED by 1 on active_roadmaps and open_blockers, and the direction is stated plainly because this ratchet exists to make exactly this visible. Both come from one change that routes a MEASURED hook-latency regression to an owner: the blocker b-concern-load-taxes-every-slot on road-to-per-turn-hook-economy, and road-to-slot-scoped-concern-loading for the structural half. The trunk is red on the pre-registered latency gate \u2014 main measured pre_tool_use p50 176 ms against a derivation window of 111-148, a local A/B at the gate own n=50 reproduced +12.6 percent, and the same-run control was flat, so it is cost growth rather than runner spread. The estate grew because the defect had nowhere to live, not because scope was added. NOT offset deliberately: the only offset available here would be archiving an unrelated roadmap to buy room, which bundles an unreviewed disposition into a latency-gate PR. The roadmap carries estate_offset_exempt with that reason and its own Honest null, so it closes on a measurement in Phase 0 if module load turns out to be a minor share \u2014 which is the walk back down this raise is underwritten by. later_roadmaps unchanged at 43, the check that distinguishes a real addition from work relocated into later/."
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"at": "2026-08-19",
|
|
68
|
+
"active_roadmaps": 37,
|
|
69
|
+
"later_roadmaps": 43,
|
|
70
|
+
"open_blockers": 71,
|
|
71
|
+
"why": "WALKED BACK DOWN, reversing the raise recorded one entry above IN THE SAME PR. That raise was explicitly underwritten by the new roadmap own Honest null \u2014 close on the Phase 0 measurement if the premise does not hold \u2014 and the null fired on the first measurement it took: a bundle carrying only the 11 pre_tool_use concerns loads in 24 ms against the full 45-concern bundle at 23 ms, so slot-scoping the dispatcher would have bought minus one millisecond. The bundle is dominated by its shared dependency graph, not by per-concern code. road-to-slot-scoped-concern-loading is therefore archived with that result published rather than left open, and b-concern-load-taxes-every-slot is resolved because the real cause was found and fixed in the same change: dispatch_hook parsed the 61 kB YAML manifest on every dispatch (8 ms module load plus 12 ms parse) and now reads a precompiled 14.7 kB JSON sibling, taking pre_tool_use p50 from 103 ms to 81 ms. Both numbers are measurements of a smaller estate, not room bought by relocating work \u2014 later_roadmaps is unchanged at 43, which is the check that distinguishes the two."
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"at": "2026-08-19",
|
|
75
|
+
"active_roadmaps": 39,
|
|
76
|
+
"later_roadmaps": 43,
|
|
77
|
+
"open_blockers": 72,
|
|
78
|
+
"why": "RAISED by 2 active and 1 blocker ON TOP of the same-day lowering recorded directly above, for the two roadmaps the long-horizon branch installed from an inbox handover and then executed in the same PR. The arithmetic is stated because the first version of this entry got it wrong: it read 38 -> 40 and 72 -> 73 against the pre-merge trunk, and `origin/main` had meanwhile MEASURED 37 and 71 by archiving road-to-stop-gate-honesty and resolving its blocker. Carrying 40/73 through the merge would have silently pocketed that branch\u2019s drawdown as headroom, which is the loose-ratchet failure the 67 -> 69 entry records from the other direction. 37 + 2 and 71 + 1 is the honest merged value. Both roadmaps carry an `estate_offset_exempt` line, so the one-in-one-out check passes on its own terms; what still fires is the raw COUNT half, which is diff-blind by design. The honest shape of the raise: road-to-council-api-fallback arrives at 16 of 17 steps done and would have ARCHIVED in this PR \u2014 that archive is the offset the check wanted. It does not archive, because Iron Law 3 refuses a silent archive while a `[~]` item is unresolved and that resolution belongs to the user, so this is a raise one user decision turns back into 38. road-to-long-horizon-execution stays open on an OBSERVATION of a live killed-and-resumed run: not work any change-set can contain, and not something to buy back by archiving an unrelated roadmap, which would move the number without moving the estate. The +1 blocker is `team-loop-benchmark-spend`, which puts a funded-or-published-null decision in front of the maintainer \u2014 the direction this ratchet exists FOR."
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"at": "2026-08-19",
|
|
82
|
+
"active_roadmaps": 36,
|
|
83
|
+
"later_roadmaps": 49,
|
|
84
|
+
"open_blockers": 73,
|
|
85
|
+
"why": "road-to-estate-drawdown Phase 2 triage batch 1 \u2014 the first batch of the sweep this ratchet was registered to make stick. RE-APPENDED a SECOND time, after a second merge of origin/main, and every number below is re-measured on the merged tree rather than carried over from either previous reading. The trunk moved twice under this branch: first PR #1429 (a raise to 38/43/72 then its own walk back down to 37/43/71), then the long-horizon branch installing two roadmaps (37 -> 39 active, 71 -> 72 blockers). Both of those entries stand above this one in trunk order; this entry states only what THIS branch does to the numbers they left. Six of the ten files in the batch took the verdict PARK-PROBEABLE and moved to later/ with a rewritten resume condition. active_roadmaps 39 -> 36 is WALKED DOWN, and the delta is three rather than six because three of the six carried `status: draft` and were never in collect()'s corpus to begin with \u2014 the same asymmetry that explains the blocker delta below. later_roadmaps 43 -> 49 is RAISED by all six, because every parked file is counted there whatever its status was. The raise is exactly the move this metric exists to make visible: parking is not closing, and a drawdown that moved everything into later/ and called the number down is the burial T1's quality anchor forbids. It is taken rather than avoided because all six are the later/ definition verbatim \u2014 open work that cannot proceed now, gated on an external trigger, not abandoned \u2014 and every one keeps its open-step count plus a resume condition a script or a named command can test. ONE of the six is decidable by resume_probe and reports `unmet` (domain-pack-extraction-when-triggered); the other five are recorded with a per-row reason in agents/decisions/estate-triage-dispositions.yml, because their conditions name an external fact, a content bar, or a host capability rather than a file's existence. It was two until the R2 round showed the second was decidable only through a field-split bypass, and removing that bypass took the count down \u2014 a probe that decides fewer notes honestly is worth more than one that decides more by an authoring trick. open_blockers 72 -> 73 is +1 with one cause, stated because it looks like drift and is not: road-to-product-bets carried `status: draft`, so collect() excluded it and its one blocker was invisible to this metric; parking it into later/ brought that blocker into the counted corpus. No blocker was opened, closed or moved \u2014 a draft's blocker became countable. Nothing in this batch was archived, so no closure needed a migration note; the two DECISION-SHEET verdicts route to the existing consolidated sheet and the two EXECUTE verdicts leave their files in the active tree."
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"at": "2026-08-19",
|
|
89
|
+
"active_roadmaps": 34,
|
|
90
|
+
"later_roadmaps": 50,
|
|
91
|
+
"open_blockers": 73,
|
|
92
|
+
"why": "The two Iron-Law-3 holdouts resolved on a converged AI-council verdict \u2014 anthropic + openai, 2 of 2 present, one round, 2026-08-19 \u2014 and both roadmaps archived. active_roadmaps 36 -> 34 is WALKED DOWN by exactly those two archivals; later_roadmaps 49 -> 50 is RAISED by one, the follow-up carrier the council required for the second of them. open_blockers is UNCHANGED at 73, which is the check that says nothing was bought by relocating an open decision: both archived files carried only resolved blockers, and the new carrier carries none. The two cases were not the same case, and the council said so on both. road-to-inbox-harvest-2026-08-d-top-band-model-economy's holdout was an acceptance CRITERION that is unsatisfiable as written and contradicted in that form by ADR-035; both seats returned archive-with-no-follow-up, because a contradictory criterion is a specification defect rather than future work and a roadmap spawned from it could never close. They split on the glyph and `[-]` won on the sharper argument, verified against the tree before adoption: `[x]` would assert 'no .md names a vendor model', and 160 .md files do. road-to-council-api-fallback's holdout was a real proposal gated on an operational trigger, so it moved to a carrier rather than being archived with it \u2014 later/road-to-council-api-quota-source-split.md, with the step migrated verbatim including its 'do not build ahead of that evidence' instruction. Both seats independently REFUSED that roadmap's claim about itself that its deferral 'does not gate archival': a roadmap does not get to exempt itself from a repository-wide preservation rule, or the gate becomes discretionary exactly where it is structural. The claim is left standing in the archived file rather than deleted, because the claim is the finding. The carrier's resume condition is an existence predicate over a named evidence file, which resume_probe decides \u2014 chosen over the other seat's `gh issue list` probe because the tree has a probe for one and not the other, and both seats named parked-roadmap clutter as the failure mode of a follow-up whose trigger nothing can observe."
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"at": "2026-08-19",
|
|
96
|
+
"active_roadmaps": 33,
|
|
97
|
+
"later_roadmaps": 50,
|
|
98
|
+
"open_blockers": 71,
|
|
99
|
+
"why": "WALKED DOWN by 1 active and 2 blockers: road-to-inbox-harvest-residuals reached a terminal state and archived. Its last two open items, R1 and R2, were both blocked on a call rather than on effort, and both are now `[-]` against named locks \u2014 one of the three terminal states that roadmap's own Exit clause allows. later_roadmaps is UNCHANGED at 50, which is the check that says this is a closure and not a park: nothing was relocated to buy the number. The two blockers resolved are the roadmap's own, each on the option the blocker itself recommended \u2014 deferred-finding-decision-reopen on (b), cancel R2 against the in-source decline; spent-inbox-artifacts-await-deletion on (c), keep-reason recorded, the branch that deletes nothing. Neither was closed to make room: the AI council was polled first on whether an agent may record a disposition against a blocker whose declared Owner is the maintainer, and both seats converged that observing a named trigger did NOT fire closes the loop while the reserved call is whether to REOPEN \u2014 which is exactly what was not done. R2's trigger was probed rather than assumed (`check_review_dispositions`: 17 archived records, all terminal, zero findings, which is the trigger's negation), and R1's cancellation had to be RE-GROUNDED because the lock the step cited says the opposite of what the step claimed: \u00a7 2.7 of plan-review-gates.md opens with 'never an edit ban on the live artefact', and that same file already carried the refutation of the broader reading in writing. The lock that does hold is narrower \u2014 \u00a7 2.7 freezes only the 16 superseded round-review records, and the contract declined a corpus-migration event for this class when it made its v1 fields optional. A cancellation resting on a lock the cited contract denies would have been a burial wearing a citation, which is the failure this ratchet's own history keeps recording from the count side."
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"at": "2026-08-19",
|
|
103
|
+
"active_roadmaps": 33,
|
|
104
|
+
"later_roadmaps": 50,
|
|
105
|
+
"open_blockers": 70,
|
|
106
|
+
"why": "WALKED DOWN by 1 blocker: worktree-claim-root-split resolved in the change that found it. The blocker recorded a measured defect \u2014 the roadmap claim and the stop-slot concern resolved different roots, so run-continuation was inert in every worktree session \u2014 and the claim now lives in the git common dir beside the session register. Active and later are unchanged and net zero by construction: road-to-long-horizon-execution archived and road-to-run-continuation-observation was created in the same change to carry its last deferred criterion, per the preservation test the same change added to roadmap-progress-sync. Walked here rather than banked because the gate reported the slack itself, and a baseline above the measurement is headroom a later change spends with nothing objecting."
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
"target": {
|
|
110
|
+
"_comment": "T1's proposed ceilings, kept as the destination. PROPOSALS, NOT REGISTERED: the roadmap says in as many words that both numbers and the window belong to the maintainer, and no date is pinned in this file. Nothing reads these values \u2014 `one_in_one_out.applies_above_active` is the only threshold the gate evaluates.",
|
|
111
|
+
"active_roadmaps": 15,
|
|
112
|
+
"open_blockers": 12,
|
|
113
|
+
"window": "roughly six weeks from campaign start (drafted; unregistered)"
|
|
114
|
+
},
|
|
115
|
+
"one_in_one_out": {
|
|
116
|
+
"_comment": "T3. `applies_above_active: null` means UNCONDITIONAL. T3 phrases the condition as 'while the active count sits above target', and T1 says the target belongs to the maintainer \u2014 so rather than inventing a ceiling so the condition could be evaluated, null states that no ceiling is registered and the lint therefore applies. Registering a number here is how it gets a threshold; registering one above the live count is how it is turned off.",
|
|
117
|
+
"applies_above_active": null,
|
|
118
|
+
"exemption": "estate_offset_exempt: <reason> in the added roadmap's own frontmatter. The key lives in the file rather than in this config for the reason RATCHET_RESET_KEY gives for living inside its baseline JSON: the claim shows up in the diff of the change that makes it."
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -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": 101,
|
|
62
|
+
"last_measured": 101,
|
|
63
|
+
"last_measured_at": "2026-08-19",
|
|
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. 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. 97 -> 98 on 2026-08-15: the `workspace:doctor` verb (road-to-inbox-harvest-2026-08-c-workspace-identity Phase 3 — the read-only report over the new `workspaceIdentity()` resolver) registers in the same PR that adds the surface, per this record's own contract. It exists for the same inference-from-a-filename shape as the council:status and gates entries above, one layer down: the tree answered \"where am I\" in eight ad-hoc places across six files, and the 12.0.0 span shipped the same worktree misclassification twice, so no verb could be asked which checkout is the main one. Read-only by construction — the roadmap's Non-goals put worktree disposal out of scope and its Risk 3 names \"the pressure read becomes a disposal path by accident\" as the failure to design against, so the command carries no --fix and no --prune. Re-measured twice via the pinned method on the unchanged tree: 98/98. 98 -> 99 on 2026-08-17: the `council:quota` verb (road-to-council-quota-accounting-truth Phase 2) registers in the same PR that adds the surface, per this record's own contract. Exactly the inference-from-a-filename shape as the council:status entry above, one layer down: `cmd_quota` had existed in council_cli.ts and been reachable only via `scripts-run`, so the documented recovery path for an exhausted shared counter -- read the usage, reset one provider -- did not resolve through `agent-config`, and an operator debugging a quota failure met `unknown command`. The verb is read-only apart from the pre-existing `--reset <provider>`, which already required `--confirm`. Re-measured twice via the pinned method on the unchanged tree: 99/99."
|
|
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. 97 -> 98 on 2026-08-15: the `workspace:doctor` verb (road-to-inbox-harvest-2026-08-c-workspace-identity Phase 3 — the read-only report over the new `workspaceIdentity()` resolver) registers in the same PR that adds the surface, per this record's own contract. It exists for the same inference-from-a-filename shape as the council:status and gates entries above, one layer down: the tree answered \"where am I\" in eight ad-hoc places across six files, and the 12.0.0 span shipped the same worktree misclassification twice, so no verb could be asked which checkout is the main one. Read-only by construction — the roadmap's Non-goals put worktree disposal out of scope and its Risk 3 names \"the pressure read becomes a disposal path by accident\" as the failure to design against, so the command carries no --fix and no --prune. Re-measured twice via the pinned method on the unchanged tree: 98/98. 98 -> 99 on 2026-08-17: the `council:quota` verb (road-to-council-quota-accounting-truth Phase 2) registers in the same PR that adds the surface, per this record's own contract. Exactly the inference-from-a-filename shape as the council:status entry above, one layer down: `cmd_quota` had existed in council_cli.ts and been reachable only via `scripts-run`, so the documented recovery path for an exhausted shared counter -- read the usage, reset one provider -- did not resolve through `agent-config`, and an operator debugging a quota failure met `unknown command`. The verb is read-only apart from the pre-existing `--reset <provider>`, which already required `--confirm`. Re-measured twice via the pinned method on the unchanged tree: 99/99. 99 -> 100 on 2026-08-19: the `decision:memo` verb (road-to-long-horizon-execution Phase 2.2, sequencing UOTL Phase 4.3 -- the decision-memo channel) registers in the same PR that adds the surface, per this record's own contract. It exists because the question ladder gained a rung between \"halt and ask\" and \"decide silently\": a resolution the run makes without contacting the operator is only legitimate if it is reviewable afterwards, and there was no surface that wrote such a record with a fixed shape. Write-and-list only, local-only (agents/runtime/ is gitignored), and it gates nothing -- the locked decision classes are refused at the config schema, never in the ledger. 100 -> 101 in the same PR: `run:supervise` (Phase 3.1 -- the out-of-process watcher for runs whose session died with open steps left). It is the operator-facing half of session continuity: nothing anywhere had the job of noticing that a claimed run stopped moving, so a roadmap 40% done and a roadmap finished looked identical from outside the dead session. Report-only by default and it NEVER merges, pushes or closes -- the auto-merge the reference design carries is a named rejection in the roadmap's harvest section, not an unbuilt feature. Re-measured twice via the pinned method on the unchanged tree: 101/101."
|
|
66
66
|
},
|
|
67
67
|
"mcp_public_tool_count": {
|
|
68
68
|
"max": 20,
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
# visible, or it repeats the sin it exists to catch.
|
|
41
41
|
# canary optional mutation recipe (Phase 7) — see CANARY below
|
|
42
42
|
#
|
|
43
|
-
# COVERAGE — THE HONEST DENOMINATOR (re-measured 2026-08-
|
|
43
|
+
# COVERAGE — THE HONEST DENOMINATOR (re-measured 2026-08-18)
|
|
44
44
|
# ----------------------------------------------------------
|
|
45
|
-
#
|
|
45
|
+
# 267 `lint_*` / `check_*` / `audit_*` scripts live under `src/scripts/`
|
|
46
46
|
# (the count is computed, not asserted — `check_gate_coverage.count_gate_scripts`).
|
|
47
|
-
# **
|
|
48
|
-
# CI-identical argv, and
|
|
47
|
+
# **42 of them emit the machine-readable `scanned: <N>` line under their
|
|
48
|
+
# CI-identical argv, and 41 of those 42 are listed below.** The manifest is
|
|
49
49
|
# therefore complete with respect to its own contract for every gate that can
|
|
50
50
|
# carry a floor, and covers ~15 % of the population.
|
|
51
51
|
#
|
|
@@ -564,6 +564,34 @@ gates:
|
|
|
564
564
|
reported, never silently skipped, so a collapse below the floor means
|
|
565
565
|
the roadmaps root moved, not that the corpus went clean.
|
|
566
566
|
|
|
567
|
+
- id: check_estate_count
|
|
568
|
+
argv: []
|
|
569
|
+
min_scanned: 20
|
|
570
|
+
corpus: "active + parked roadmap files (81 at baseline: 38 active, 43 in later/)"
|
|
571
|
+
status: enforced
|
|
572
|
+
canary:
|
|
573
|
+
class: contract-violation
|
|
574
|
+
path: "agents/roadmaps/road-to-canary-estate-count.md"
|
|
575
|
+
content: |
|
|
576
|
+
# Roadmap: canary
|
|
577
|
+
|
|
578
|
+
## Phase 1
|
|
579
|
+
|
|
580
|
+
- [ ] **1.1** a step
|
|
581
|
+
note: >
|
|
582
|
+
road-to-estate-drawdown T2. Two halves, one floor: the count half walks the
|
|
583
|
+
committed baseline DOWN only and always has a corpus, so it can carry a
|
|
584
|
+
floor; the one-in-one-out half (T3) is diff-scoped and legitimately weighs
|
|
585
|
+
nothing on a branch that adds no roadmap, which is why the floor counts
|
|
586
|
+
roadmap FILES rather than diff entries.
|
|
587
|
+
The floor is 20 rather than something near today's 81 because THIS gate's
|
|
588
|
+
corpus is the thing it exists to shrink: T1's proposed ceiling is 15 active,
|
|
589
|
+
and a successful campaign would land near 15 + a drained `later/` — a floor
|
|
590
|
+
of 50 would have failed on the day the drawdown worked, which is the exact
|
|
591
|
+
failure this file's own § 3 warns about, pointed the wrong way. 20 still
|
|
592
|
+
cannot survive a collapse: at that point the gate's own `reportScanned`
|
|
593
|
+
fires first. (Set to 50 in the first version, corrected on an R2 finding.)
|
|
594
|
+
|
|
567
595
|
- id: check_completion_review
|
|
568
596
|
argv: ["--advisory", "--base", "origin/main"]
|
|
569
597
|
min_scanned: 1
|