@event4u/agent-config 14.1.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 +291 -181
- 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 +3773 -1731
- 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 +22 -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/skill_catalogue_series.ts +91 -0
- 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/dispatch_r2_reviewer.ts +169 -15
- 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/skill_route_hook.ts +172 -11
- 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/install-hooks.sh +26 -0
- 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
|
@@ -207,6 +207,23 @@ cli_call_budget:
|
|
|
207
207
|
# Warn once usage crosses this fraction of a cap (0..1).
|
|
208
208
|
warn_at: 0.8
|
|
209
209
|
|
|
210
|
+
# Mid-flight cli -> api fallback. When a CLI call fails partway through a
|
|
211
|
+
# pass, the seat can retry once on that provider's metered api rung instead
|
|
212
|
+
# of being lost. The classes where the CLI provably never reached the
|
|
213
|
+
# provider (missing binary, rejected auth, unsupported CLI, unservable
|
|
214
|
+
# model) fall through unconditionally; a timeout or a 5xx NEVER does, under
|
|
215
|
+
# any setting, because a half-completed call must not be paid for twice.
|
|
216
|
+
#
|
|
217
|
+
# `api_on_quota` is the one BILLING-CLASS decision in that design, which is
|
|
218
|
+
# why it is a key and not a rule. Both quota shapes are safe to retry — the
|
|
219
|
+
# local `cli_call_budget` cap refuses before anything is sent, and a
|
|
220
|
+
# provider plan quota is rejected at the request boundary — so the question
|
|
221
|
+
# is not double spend, it is that an unmetered subscription call becomes a
|
|
222
|
+
# USD one. Off by default; the retry still passes the `cost_budget` gates
|
|
223
|
+
# above. Turn it on if you would rather keep the seat than keep the $0.
|
|
224
|
+
fallback:
|
|
225
|
+
api_on_quota: false
|
|
226
|
+
|
|
210
227
|
# Quorum for a council pass to CONCLUDE (road-to-always-on-orchestration
|
|
211
228
|
# Phase 3.3). Default "majority" resolves to ceil(n / 2) enabled members —
|
|
212
229
|
# a SIMPLE majority, deliberately not "more than half": at n=2 that is
|
|
@@ -499,6 +516,23 @@ necessity_classifier:
|
|
|
499
516
|
#
|
|
500
517
|
# See docs/contracts/ai-council-config.md for the trigger lists and the
|
|
501
518
|
# Iron-Law clause.
|
|
519
|
+
# `second_model` — the OPTIONAL local rung between the agent and the
|
|
520
|
+
# council (UOTL Phase 4.1). It exists because the council used to be the
|
|
521
|
+
# only rung before a halt: a medium-impact question stopped the run
|
|
522
|
+
# whenever no council was configured or its quota was gone.
|
|
523
|
+
#
|
|
524
|
+
# Name a provider whose LOCAL CLI runs under subscription auth —
|
|
525
|
+
# anthropic, openai or gemini. That set is narrower than the five
|
|
526
|
+
# providers `members:` accepts, on purpose: xai and perplexity ship
|
|
527
|
+
# community CLI wrappers that consume an API key, so routing a
|
|
528
|
+
# resolution there would spend USD on the rung whose whole point is not
|
|
529
|
+
# to. The rung MUST book against the same `cli_call_budget` counter above
|
|
530
|
+
# — one subscription, one counter. That is an obligation on the rung, not
|
|
531
|
+
# a guard that already exists: nothing reads this key at runtime yet, so
|
|
532
|
+
# setting it declares intent rather than activating a bounded mechanism.
|
|
533
|
+
#
|
|
534
|
+
# The key is REFUSED on high_impact and user_required, not ignored. Those
|
|
535
|
+
# classes reach you, and a silently-dropped key would read as configured.
|
|
502
536
|
decision_resolution:
|
|
503
537
|
enabled: true
|
|
504
538
|
classes:
|
|
@@ -511,6 +545,7 @@ decision_resolution:
|
|
|
511
545
|
medium_impact:
|
|
512
546
|
mode: council
|
|
513
547
|
confidence_threshold: 0.6
|
|
548
|
+
# second_model: gemini # uncomment to add the local rung
|
|
514
549
|
high_impact:
|
|
515
550
|
mode: user # LOCKED — Iron Law
|
|
516
551
|
confidence_threshold: 0.6
|
|
@@ -277,11 +277,39 @@ For each open step in the working set (scope-bound — see wrapper):
|
|
|
277
277
|
summary named the members). `high_impact` / `user_required`
|
|
278
278
|
classifications still escalate to the user per
|
|
279
279
|
[`ask-when-uncertain`](../../rules/ask-when-uncertain.md).
|
|
280
|
+
- **Second-model rung, when the class declares one**
|
|
281
|
+
(`decision_resolution.classes.<cls>.second_model`, UOTL Phase 4.1)
|
|
282
|
+
→ one local vendor-CLI pass under subscription auth. **The rung is
|
|
283
|
+
MODEL-CARRIED: the key is declared and schema-validated, and no
|
|
284
|
+
TypeScript path reads it at runtime** — so "bounded by the
|
|
285
|
+
`cli_call_budget` counter" is what the rung must respect, never
|
|
286
|
+
something a booking consumer enforces (`cli_call_budget.ts` declares
|
|
287
|
+
its consumer set closed at two, and this is not one of them). Stated
|
|
288
|
+
rather than implied after R2 round 4 finding 4 found the coupling
|
|
289
|
+
asserted on three surfaces at once. Available
|
|
290
|
+
to `trivial` / `low_impact` / `medium_impact` only; the config
|
|
291
|
+
schema REFUSES the key on `high_impact` and `user_required`, so no
|
|
292
|
+
locked question can reach it.
|
|
280
293
|
- **Council off / not configured** → halt, surface once, wait.
|
|
281
294
|
Resume on next turn. An execution contract cannot enable a
|
|
282
295
|
council that has no configured members — the contract summary
|
|
283
296
|
says so upfront, and in-run ambiguity halts (never silent
|
|
284
297
|
guessing).
|
|
298
|
+
- **No self-adversarial fallback.** With neither a council nor a
|
|
299
|
+
second-model rung available, the ambiguity halt STANDS. The gap is
|
|
300
|
+
never filled by the agent arguing both sides of the question to
|
|
301
|
+
itself: a monologue produces a verdict with no independent
|
|
302
|
+
observer, which is the failure
|
|
303
|
+
[`evaluator-independence`](../../rules/evaluator-independence.md)
|
|
304
|
+
exists over, and reads as convergence to whoever finds it later.
|
|
305
|
+
Halting costs one turn; a manufactured verdict costs the trust in
|
|
306
|
+
every other verdict the run produced.
|
|
307
|
+
- **A resolution taken WITHOUT contacting the user is recorded** —
|
|
308
|
+
`agent-config decision:memo write --run <id> …` (question, chosen
|
|
309
|
+
option, reasoning, resolver, confidence). The run's PR description
|
|
310
|
+
links the directory. Not a gate: the memo is what makes an
|
|
311
|
+
autonomous resolution reviewable after the fact, which is the
|
|
312
|
+
condition under which it is legitimate at all.
|
|
285
313
|
5. **Atomic flip — same reply, every step.**
|
|
286
314
|
Flip the checkbox in `agents/roadmaps/<file>.md`: `[x]` done ·
|
|
287
315
|
`[~]` partial · `[-]` skipped. **Non-skippable, non-batchable**
|
|
@@ -345,20 +373,56 @@ For each open step in the working set (scope-bound — see wrapper):
|
|
|
345
373
|
file is the source of truth between regens.
|
|
346
374
|
7. Run quality pipeline if cadence is `per_step`.
|
|
347
375
|
|
|
376
|
+
### 5d. Resuming a run whose previous session died
|
|
377
|
+
|
|
378
|
+
A session above the recycle threshold and inside a running contract leaves a
|
|
379
|
+
**deterministic checkpoint** at
|
|
380
|
+
`agents/runtime/state/checkpoints/<run>.json` — open / done / parked counts,
|
|
381
|
+
the next open step, and the commit the tree was on. Every field is recomputed
|
|
382
|
+
from the roadmap on disk rather than summarised, which is what makes the next
|
|
383
|
+
line possible.
|
|
384
|
+
|
|
385
|
+
```
|
|
386
|
+
A RESUMED RUN RE-VERIFIES THE CHECKPOINT BEFORE ACTING ON IT.
|
|
387
|
+
RESUME BY EVIDENCE, NEVER BY BOOKKEEPING.
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
The first act of a resumed run is to look its checkpoint up **by roadmap slug**
|
|
391
|
+
— `latestCheckpointFor(repoRoot, slug)` — and then `verifyCheckpoint` it against
|
|
392
|
+
the current tree. The slug, not the run id: a relaunched session has a NEW
|
|
393
|
+
session id, so the run-id-keyed `readCheckpoint` cannot find the checkpoint the
|
|
394
|
+
DYING session wrote, and this instruction named an unreachable path until the
|
|
395
|
+
R2 review's finding 7. The slug is the one key a resumed run holds by
|
|
396
|
+
definition, because claiming the same roadmap is what makes it a resume.
|
|
397
|
+
|
|
398
|
+
The per-field report then names WHICH claim went stale, which is what a bare
|
|
399
|
+
"stale/fresh" verdict cannot. **A disagreement is not an error**: work landing
|
|
400
|
+
between the checkpoint and the resume is the normal case (a human committed, a
|
|
401
|
+
sibling worktree moved, the dying session finished a step it never recorded),
|
|
402
|
+
and the `actual` column is what to resume from. Treating progress as corruption
|
|
403
|
+
would refuse every healthy resume.
|
|
404
|
+
|
|
405
|
+
`agent-config run:supervise --once` reports which runs died with open steps
|
|
406
|
+
left. It never merges, pushes, or closes anything — that boundary is a named
|
|
407
|
+
rejection, not a missing feature.
|
|
408
|
+
|
|
348
409
|
### Halt conditions
|
|
349
410
|
|
|
350
411
|
- Hard-Floor trigger ([`non-destructive-by-default`](../../rules/non-destructive-by-default.md))
|
|
351
412
|
- Security-sensitive path ([`security-sensitive-stop`](../../rules/security-sensitive-stop.md))
|
|
352
413
|
- Step reveals work outside the roadmap's scope
|
|
353
414
|
- Test failure or quality red on `per_step`
|
|
354
|
-
- Council off
|
|
355
|
-
|
|
356
|
-
this halt
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
with
|
|
361
|
-
|
|
415
|
+
- Council off, no second-model rung, and true ambiguity — under an
|
|
416
|
+
**accepted execution contract**
|
|
417
|
+
([§ 3](#3-pre-scan--execution-contract-or-commit-step-ask)) this halt
|
|
418
|
+
exists only when neither rung is available: the contract auto-enables
|
|
419
|
+
council for the run, so in-run open questions resolve silently
|
|
420
|
+
(`high_impact` / `user_required` classifications still escalate per
|
|
421
|
+
[`ask-when-uncertain`](../../rules/ask-when-uncertain.md)); with a
|
|
422
|
+
class-declared `second_model` the local rung runs first. With neither,
|
|
423
|
+
true ambiguity halts — never silent guessing, and never a
|
|
424
|
+
self-adversarial monologue in place of the missing observer (the
|
|
425
|
+
open-question handling in § 5 states that rung by rung).
|
|
362
426
|
|
|
363
427
|
An accepted execution contract **never lifts a Hard Floor** or any of
|
|
364
428
|
the other halts above — it removes redundant *asks* (git shape,
|
|
@@ -438,10 +502,17 @@ The loop MUST:
|
|
|
438
502
|
five choices: follow-up (draft), follow-up (ready + blocked),
|
|
439
503
|
keep-in-archive (intentional drop), restore to `[ ]`, convert
|
|
440
504
|
to `[-]` cancelled.
|
|
441
|
-
3.
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
505
|
+
3. **Route by the preservation test**, per
|
|
506
|
+
[`roadmap-progress-sync § Who resolves it`](../../rules/roadmap-progress-sync.md):
|
|
507
|
+
a disposition that keeps the criterion ALIVE in the active estate
|
|
508
|
+
(fix now · carry item + blocker into a follow-up created in the SAME
|
|
509
|
+
change · merge into existing active work · restore to `[ ]`) may be
|
|
510
|
+
resolved by the council, recorded at the item. A disposition that
|
|
511
|
+
drops, weakens or permanently accepts the loss of it — cancel to
|
|
512
|
+
`[-]`, keep-in-archive, scope cut — reaches the **user**, and the
|
|
513
|
+
autonomous mandate (`/work`, `/roadmap:process-full`, "decide for
|
|
514
|
+
me") does **not** lift that half. In doubt: user. A council verdict
|
|
515
|
+
naming a follow-up that does not exist yet fails closed to the user.
|
|
445
516
|
4. On picks 1 / 2 → run the "Spawn follow-up from deferred items"
|
|
446
517
|
procedure in [`roadmap-management`](../../skills/roadmap-management/SKILL.md).
|
|
447
518
|
On picks 3 / 4 / 5 → apply the change, re-evaluate the decision
|
|
@@ -46,12 +46,56 @@ IS A RULE VIOLATION, NOT AN OVERSIGHT.
|
|
|
46
46
|
|
|
47
47
|
```
|
|
48
48
|
A ROADMAP WITH `[~]` DEFERRED ITEMS NEVER AUTO-ARCHIVES SILENTLY.
|
|
49
|
-
SURFACE EVERY DEFERRED STEP.
|
|
49
|
+
SURFACE EVERY DEFERRED STEP. RESOLVE IT — VIA THE COUNCIL WHERE THE
|
|
50
|
+
DISPOSITION PRESERVES THE ITEM, VIA THE USER WHERE IT DOES NOT.
|
|
50
51
|
A SILENT ARCHIVE THAT BURIES PLANNED-FOR-LATER WORK
|
|
51
52
|
IS A RULE VIOLATION, NOT A CONVENIENCE.
|
|
52
53
|
```
|
|
53
54
|
|
|
54
|
-
Closure check fires (`count_open == 0` and `count_deferred > 0`) → enumerate every `[~]` step, present the numbered-options resolution menu (per [`user-interaction`](user-interaction.md)), and only after the
|
|
55
|
+
Closure check fires (`count_open == 0` and `count_deferred > 0`) → enumerate every `[~]` step, present the numbered-options resolution menu (per [`user-interaction`](user-interaction.md)), and only after the deferrals are resolved does the `git mv` to `archive/` run. Full option menu + migration mechanics: guideline + [`roadmap-management`](../skills/roadmap-management/SKILL.md).
|
|
56
|
+
|
|
57
|
+
### Who resolves it — the preservation test
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
THE COUNCIL MAY RESOLVE A `[~]` ONLY WHERE THE CHOSEN DISPOSITION KEEPS THE
|
|
61
|
+
ITEM ALIVE IN THE ACTIVE ESTATE. ANYTHING THAT DROPS, WEAKENS, OR PERMANENTLY
|
|
62
|
+
ACCEPTS THE LOSS OF IT REACHES THE USER — ALWAYS, AND NO MANDATE LIFTS THAT.
|
|
63
|
+
IN DOUBT, IT IS A USER DECISION. THE COUNCIL ADVISES ON HOW AND WHEN;
|
|
64
|
+
THE OWNER DECIDES WHETHER.
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
One question decides the route, and it is answerable from the option itself
|
|
68
|
+
rather than from judgement: **does this disposition keep the criterion active
|
|
69
|
+
in the estate?**
|
|
70
|
+
|
|
71
|
+
| Disposition | Route |
|
|
72
|
+
|---|---|
|
|
73
|
+
| Fix the blocker now, in this change or its own PR | council |
|
|
74
|
+
| Carry item **and** blocker into a named follow-up roadmap created in the SAME change and estate-ratchet compliant | council |
|
|
75
|
+
| Merge the item into existing active work that already covers it | council |
|
|
76
|
+
| Restore to `[ ]` in this roadmap | council |
|
|
77
|
+
| Convert to `[-]` cancelled | **user** |
|
|
78
|
+
| Weaken the criterion, cut its scope, or accept the breakage permanently | **user** |
|
|
79
|
+
| Keep-in-archive (an intentional drop) | **user** |
|
|
80
|
+
| The item carries a `high_impact` / `user_required` classification | **user** |
|
|
81
|
+
|
|
82
|
+
- **"Immediately active" is not a promise.** A follow-up counts only if created
|
|
83
|
+
in the same change and estate-ratchet compliant. A verdict naming a roadmap
|
|
84
|
+
that does not exist yet **fails closed to the user**.
|
|
85
|
+
- **Recorded, or it did not happen.** At the item: criterion verbatim ·
|
|
86
|
+
blocker id · every option · verdict + one-sentence rationale · dissent ·
|
|
87
|
+
destination when carried · what closes it. A verdict with no record is a
|
|
88
|
+
silent drop wearing a procedure.
|
|
89
|
+
- **The residual hole, stated not papered over.** Both authoring seats named
|
|
90
|
+
it in their own strongest counter: a carried follow-up can still become an
|
|
91
|
+
indefinite deferral, so this test bounds *who decides*, not *whether the work
|
|
92
|
+
happens*. Only fix-now discharges it. A carried item untouched at the next
|
|
93
|
+
task boundary is raised again per [`active-remediation`](active-remediation.md).
|
|
94
|
+
- **Why it changed.** Adopted 2026-08-19, unanimous 2/2 council (blind peer
|
|
95
|
+
review). The prior text — *"Wait for the user"* — handed back a fully
|
|
96
|
+
analysed choice with four costed options, the low-value interruption
|
|
97
|
+
[`no-cheap-questions`](no-cheap-questions.md) forbids. The gate protects the
|
|
98
|
+
item, not the maintainer's attention.
|
|
55
99
|
|
|
56
100
|
## Later disposition — blocked-for-later roadmaps are parked, never left active
|
|
57
101
|
|
|
@@ -95,9 +139,9 @@ Before sending any reply that landed roadmap work:
|
|
|
95
139
|
4. Did `count_open` reach 0?
|
|
96
140
|
- **No (real open work remains)** → continue normally.
|
|
97
141
|
- **Yes + `count_deferred == 0`** → the roadmap is **complete**. Archive it — `git mv` to `archive/` + migrate inbound refs + regen, same reply — or let the next `/create-pr` § 1c sweep do it deterministically. Either way it must never be pushed to the trunk unarchived (§ PR-gate; the `--check` backstop enforces it).
|
|
98
|
-
- **Yes + `count_deferred > 0`** → STOP. Run the Iron Law 3 deferred-resolution flow
|
|
142
|
+
- **Yes + `count_deferred > 0`** → STOP. Run the Iron Law 3 deferred-resolution flow: surface every item, apply the preservation test above, and resolve — council where the disposition keeps the item alive, user where it does not. Archive only after resolution, and only with the resolution recorded at the item.
|
|
99
143
|
|
|
100
|
-
Any "no" at step 2 → reply is incomplete. Do not send. A skipped step 3 regen is fine when cadence permits — checkbox truth lives in the markdown file. Skipping the deferred-resolution gate at step 4 is **never** acceptable; it is the canonical "lost-information" failure mode this rule exists to prevent.
|
|
144
|
+
Any "no" at step 2 → reply is incomplete. Do not send. A skipped step 3 regen is fine when cadence permits — checkbox truth lives in the markdown file. Skipping the deferred-resolution gate at step 4 is **never** acceptable; it is the canonical "lost-information" failure mode this rule exists to prevent. Note what the council path does and does not change here: it changes WHO resolves a preserving disposition, never whether the gate runs, never whether the resolution is recorded, and never the user's ownership of a drop.
|
|
101
145
|
|
|
102
146
|
Body migrated to `guideline:agent-infra/roadmap-progress-mechanics` (per P4 of `road-to-kernel-and-router.md`) — glyph semantics, regen cadence, deferred-resolution menu, later-disposition procedure, PR-gate prose, plus the long-form failure-mode catalog, Copilot fallback, and hook + CI defence-in-depth.
|
|
103
147
|
Trigger-set above activates this routing on demand, independent of the discipline profile (ADR-110).
|
|
@@ -16,14 +16,19 @@
|
|
|
16
16
|
*
|
|
17
17
|
* ## What it can and cannot decide, stated plainly
|
|
18
18
|
*
|
|
19
|
-
* A resume condition is prose.
|
|
20
|
-
*
|
|
19
|
+
* A resume condition is prose. TWO shapes are machine-decidable — a roadmap
|
|
20
|
+
* reference, and a single repo-relative file path under an existence predicate
|
|
21
|
+
* — and the rest are not, so the verdict set has three values rather than two:
|
|
21
22
|
*
|
|
22
23
|
* - `fired` — every roadmap the condition names has archived, or the
|
|
23
|
-
* named step in a still-active roadmap is ticked
|
|
24
|
+
* named step in a still-active roadmap is ticked; or the
|
|
25
|
+
* single path it names under `exists` is there.
|
|
24
26
|
* - `unmet` — at least one named roadmap is still active with the
|
|
25
|
-
* named step (or the whole file) still open
|
|
26
|
-
*
|
|
27
|
+
* named step (or the whole file) still open; or the single
|
|
28
|
+
* path it names under `exists` is absent.
|
|
29
|
+
* - `undecidable` — the condition names neither, names both (a conjunction
|
|
30
|
+
* this probe can only half-weigh), names two of either, or
|
|
31
|
+
* states a predicate other than existence over its path.
|
|
27
32
|
*
|
|
28
33
|
* `undecidable` is reported as its own count and never folded into "nothing
|
|
29
34
|
* fired". A probe that reads 44 files, understands 12 of them and announces
|
|
@@ -84,6 +89,31 @@ const COMPOUND_RE = /\bboth\b|\band\b|\([1-9]\)|\([a-e]\)|`[ \t]*\+|\+[ \t]*`/i;
|
|
|
84
89
|
const FIELD_LABEL_RE = /\*\*[^*]+:\*\*/;
|
|
85
90
|
/** A roadmap slug as written in prose, with or without the `.md` suffix. */
|
|
86
91
|
const ROADMAP_REF_RE = /\broad-to-[a-z0-9][a-z0-9-]*\b/g;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* A repo-relative path named inside backticks — the second decidable form.
|
|
95
|
+
*
|
|
96
|
+
* `PARK-PROBEABLE` (`road-to-estate-drawdown` Phase 2) means the resume
|
|
97
|
+
* condition can be tested by a script, and until this existed the probe could
|
|
98
|
+
* decide exactly one phrasing: a roadmap slug. Measured 2026-08-19 on the live
|
|
99
|
+
* estate, 42 of 44 park notes were `undecidable`, and the batch-1 sweep could
|
|
100
|
+
* only add six more — a verdict whose name promised a probe that did not read
|
|
101
|
+
* the conditions it was producing.
|
|
102
|
+
*
|
|
103
|
+
* Deliberately narrow in three ways, because a false FIRED un-parks live work:
|
|
104
|
+
* the path must be **inside backticks** (prose that merely contains a slash is
|
|
105
|
+
* not a condition), it must start at a **known top-level repo directory** and
|
|
106
|
+
* end in a **file extension**, and the clause must name **exactly one** — two
|
|
107
|
+
* paths are a conjunction this function cannot weigh, and `COMPOUND_RE` does
|
|
108
|
+
* not catch a comma.
|
|
109
|
+
*
|
|
110
|
+
* The extension requirement is what keeps a *directory* out. `agents/evidence/`
|
|
111
|
+
* exists in every checkout, so "recorded under `agents/evidence/`" is not a
|
|
112
|
+
* condition a probe can decide — and since `_exists` tests `isFile()`, matching
|
|
113
|
+
* it would have reported "does not exist" about a directory that does. A
|
|
114
|
+
* directory-shaped condition stays `undecidable`, which is the honest verdict.
|
|
115
|
+
*/
|
|
116
|
+
const REPO_PATH_RE = /`((?:agents|docs|src|tests|internal|scripts|evals|\.github)\/[A-Za-z0-9._\-/]*[A-Za-z0-9_\-]\.[A-Za-z0-9]{2,5})`/g;
|
|
87
117
|
/**
|
|
88
118
|
* A step reference: `P2.1`, `Phase 2.1`, or a bare `2.1`.
|
|
89
119
|
*
|
|
@@ -231,6 +261,37 @@ function _blankFencedCode(text: string): string {
|
|
|
231
261
|
);
|
|
232
262
|
}
|
|
233
263
|
|
|
264
|
+
/**
|
|
265
|
+
* The head of `s` up to its first sentence terminator OUTSIDE backticks.
|
|
266
|
+
*
|
|
267
|
+
* A naive character-class scan that stops at any dot cuts inside `foo.md` or
|
|
268
|
+
* inside a step id like `3.3`, which truncates the clause mid-span and hides
|
|
269
|
+
* the very path or step the condition names. Measured 2026-08-19: three of the six park notes written by
|
|
270
|
+
* `road-to-estate-drawdown` Phase 2 batch 1 lost their backticked path this
|
|
271
|
+
* way, and the probe then reported "names no roadmap this tree can resolve"
|
|
272
|
+
* about a condition that named a file.
|
|
273
|
+
*/
|
|
274
|
+
function _sentenceHead(s: string): string {
|
|
275
|
+
// An ODD backtick count means the span never closes, and toggling on it
|
|
276
|
+
// would leave `inCode` stuck true for the whole remainder — the commentary
|
|
277
|
+
// paragraph then becomes the clause, and a path mentioned only in that
|
|
278
|
+
// commentary could decide the verdict. Fall back to the plain scan, which
|
|
279
|
+
// cuts at the first terminator and is the conservative direction.
|
|
280
|
+
const balanced = (s.match(/`/g) ?? []).length % 2 === 0;
|
|
281
|
+
let inCode = false;
|
|
282
|
+
for (let i = 0; i < s.length; i++) {
|
|
283
|
+
const ch = s[i] as string;
|
|
284
|
+
if (ch === '`' && balanced) {
|
|
285
|
+
inCode = !inCode;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
if (!inCode && (ch === '.' || ch === '—' || ch === '–')) {
|
|
289
|
+
return s.slice(0, i);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return s;
|
|
293
|
+
}
|
|
294
|
+
|
|
234
295
|
/**
|
|
235
296
|
* The condition CLAUSE inside the condition text.
|
|
236
297
|
*
|
|
@@ -260,13 +321,63 @@ function conditionClause(condition: string): string {
|
|
|
260
321
|
// (`**Resume when** that pruning lands`), so take the span plus the
|
|
261
322
|
// remainder of its sentence — up to an em dash or a full stop.
|
|
262
323
|
const after = condition.slice(m.index + m[0].length);
|
|
263
|
-
|
|
264
|
-
return `${span}${tail}`;
|
|
324
|
+
return `${span}${_sentenceHead(after)}`;
|
|
265
325
|
}
|
|
266
326
|
}
|
|
267
327
|
return condition;
|
|
268
328
|
}
|
|
269
329
|
|
|
330
|
+
/**
|
|
331
|
+
* The clause's predicate is EXISTENCE, not some property of the file's content.
|
|
332
|
+
*
|
|
333
|
+
* Without this the path branch would answer a question the condition did not
|
|
334
|
+
* ask. Measured 2026-08-19 while writing it: `road-to-catalogue-host-fit` waits
|
|
335
|
+
* until `agents/evidence/metrics/skill-catalogue.jsonl` *holds at least 20
|
|
336
|
+
* observations*; the file exists today with 7 lines, so an existence test would
|
|
337
|
+
* have reported FIRED and un-parked a roadmap whose bar is 13 observations away.
|
|
338
|
+
* A content bar is decidable — by `capture_skill_catalogue --cadence` — but not
|
|
339
|
+
* by this probe, and `undecidable` is the honest verdict for it.
|
|
340
|
+
*
|
|
341
|
+
* The predicate is bound POSITIONALLY to the path rather than tested as a word
|
|
342
|
+
* anywhere in the clause, because word-presence is not a predicate: `\`x.md\` no
|
|
343
|
+
* longer exists` would have reported FIRED while the file was still there, and
|
|
344
|
+
* `blocked until a workaround exists, see \`docs/x.md\`` would have paired the
|
|
345
|
+
* word with a path it does not govern. Both shapes are regression tests.
|
|
346
|
+
*
|
|
347
|
+
* KNOWN LIMIT, stated rather than papered over: this probe reads the condition
|
|
348
|
+
* AFTER `_truncateAtNextField`, so a content bar an author moves into a sibling
|
|
349
|
+
* bolded field is invisible to it and the note decides as bare existence. That
|
|
350
|
+
* is an authoring bypass no check here can see — the guard is that a resume
|
|
351
|
+
* condition states its whole bar inside the condition, and it is model-carried.
|
|
352
|
+
*/
|
|
353
|
+
const EXISTENCE_PREDICATE_RE = /`(?:agents|docs|src|tests|internal|scripts|evals|\.github)\/[A-Za-z0-9._\-/]+`[ \t]*(?:currently[ \t]+)?exists\b/i;
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* The single repo-relative path a condition names, or `null`.
|
|
357
|
+
*
|
|
358
|
+
* `null` for zero paths (nothing to test) and for two or more (a conjunction
|
|
359
|
+
* this probe cannot weigh — reporting FIRED on the first would be the
|
|
360
|
+
* partial-match resume the compound guard already refuses).
|
|
361
|
+
*/
|
|
362
|
+
function referencedPath(condition: string): string | null {
|
|
363
|
+
if (!EXISTENCE_PREDICATE_RE.test(condition)) {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
const paths = _pathsIn(condition);
|
|
367
|
+
return paths.length === 1 ? (paths[0] as string) : null;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** Every distinct repo-relative file path a clause names, predicate or not. */
|
|
371
|
+
function _pathsIn(condition: string): string[] {
|
|
372
|
+
REPO_PATH_RE.lastIndex = 0;
|
|
373
|
+
const found = new Set<string>();
|
|
374
|
+
let m: RegExpExecArray | null;
|
|
375
|
+
while ((m = REPO_PATH_RE.exec(condition)) !== null) {
|
|
376
|
+
found.add(m[1] as string);
|
|
377
|
+
}
|
|
378
|
+
return [...found].sort();
|
|
379
|
+
}
|
|
380
|
+
|
|
270
381
|
/** Roadmap slugs named by the condition, minus the parked file's own slug. */
|
|
271
382
|
function referencedRoadmaps(condition: string, ownSlug: string): string[] {
|
|
272
383
|
const out = new Set<string>();
|
|
@@ -310,7 +421,10 @@ function stepIsDone(text: string, stepId: string): boolean | null {
|
|
|
310
421
|
* tree rather than the live estate — a probe whose only test is the live tree
|
|
311
422
|
* stops testing anything the day the live tree changes.
|
|
312
423
|
*/
|
|
313
|
-
function probeLater(roadmapRoot: string): ResumeFinding[] {
|
|
424
|
+
function probeLater(roadmapRoot: string, repoRootArg?: string): ResumeFinding[] {
|
|
425
|
+
// Defaults to the repo that owns `agents/roadmaps/`. Passed explicitly by
|
|
426
|
+
// the tests, whose fixture tree IS the roadmap root and has no repo above it.
|
|
427
|
+
const repoRoot = repoRootArg ?? path.resolve(roadmapRoot, '..', '..');
|
|
314
428
|
const laterDir = path.join(roadmapRoot, 'later');
|
|
315
429
|
let entries: string[];
|
|
316
430
|
try {
|
|
@@ -353,13 +467,43 @@ function probeLater(roadmapRoot: string): ResumeFinding[] {
|
|
|
353
467
|
continue;
|
|
354
468
|
}
|
|
355
469
|
const refs = referencedRoadmaps(clause, ownSlug);
|
|
470
|
+
// A clause naming a roadmap AND a path is a conjunction, and deciding it
|
|
471
|
+
// on the roadmap half alone is the partial-match resume `COMPOUND_RE`
|
|
472
|
+
// already refuses for two roadmaps. `COMPOUND_RE` does not close it —
|
|
473
|
+
// a comma joins the two without an `and`, which is the same hole the
|
|
474
|
+
// two-paths rule in `referencedPath` exists for. Refused symmetrically.
|
|
475
|
+
if (refs.length > 0 && _pathsIn(clause).length > 0) {
|
|
476
|
+
findings.push({
|
|
477
|
+
file: `later/${name}`,
|
|
478
|
+
condition,
|
|
479
|
+
refs,
|
|
480
|
+
verdict: 'undecidable',
|
|
481
|
+
why: 'names a roadmap and a repo path — a conjunction the probe can only half-weigh',
|
|
482
|
+
});
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
356
485
|
if (refs.length === 0) {
|
|
486
|
+
// Second decidable form: a single repo-relative path. `exists` is
|
|
487
|
+
// the whole test — the condition says the artefact will appear, so
|
|
488
|
+
// its appearance IS the firing, and no content check is implied.
|
|
489
|
+
const rel = referencedPath(clause);
|
|
490
|
+
if (rel !== null) {
|
|
491
|
+
const there = _exists(path.join(repoRoot, rel));
|
|
492
|
+
findings.push({
|
|
493
|
+
file: `later/${name}`,
|
|
494
|
+
condition,
|
|
495
|
+
refs,
|
|
496
|
+
verdict: there ? 'fired' : 'unmet',
|
|
497
|
+
why: there ? `${rel} exists` : `${rel} does not exist`,
|
|
498
|
+
});
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
357
501
|
findings.push({
|
|
358
502
|
file: `later/${name}`,
|
|
359
503
|
condition,
|
|
360
504
|
refs,
|
|
361
505
|
verdict: 'undecidable',
|
|
362
|
-
why: 'the condition names no roadmap this tree can resolve',
|
|
506
|
+
why: 'the condition names no roadmap or repo path this tree can resolve',
|
|
363
507
|
});
|
|
364
508
|
continue;
|
|
365
509
|
}
|
|
@@ -414,6 +558,7 @@ export {
|
|
|
414
558
|
conditionClause,
|
|
415
559
|
extractCondition,
|
|
416
560
|
probeLater,
|
|
561
|
+
referencedPath,
|
|
417
562
|
referencedRoadmaps,
|
|
418
563
|
roadmapDisposition,
|
|
419
564
|
stepIsDone,
|