@event4u/agent-config 14.13.0 → 14.14.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.
Files changed (101) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +116 -387
  5. package/dist/agent-src/commands/analyze/inbox.md +36 -0
  6. package/dist/agent-src/rules/code-comment-discipline.md +6 -0
  7. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
  8. package/dist/agent-src/scripts/guarded_baseline.ts +22 -7
  9. package/dist/agent-src/scripts/stubs_due.ts +118 -2
  10. package/dist/agent-src/scripts/update_roadmap_progress.ts +8 -8
  11. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  12. package/dist/cli-delegate/cmd_conformance.js +38 -1
  13. package/dist/discovery/deprecation-report.md +1 -1
  14. package/dist/discovery/discovery-manifest.json +4 -4
  15. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  16. package/dist/discovery/discovery-manifest.summary.md +1 -1
  17. package/dist/discovery/dormancy-report.md +1 -1
  18. package/dist/discovery/orphan-report.md +1 -1
  19. package/dist/discovery/packs.json +2 -2
  20. package/dist/discovery/trust-report.md +1 -1
  21. package/dist/discovery/workspaces.json +2 -2
  22. package/dist/hooks/dispatch.js +1438 -711
  23. package/dist/mcp/registry-manifest.json +2 -2
  24. package/dist/mcp/server.json +2 -2
  25. package/dist/mcp/server.mjs +112 -17
  26. package/docs/guidelines/agent-infra/guarded-baseline.md +1 -1
  27. package/docs/guidelines/code-clarity.md +18 -0
  28. package/package.json +1 -1
  29. package/src/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
  30. package/src/agent-src/scripts/guarded_baseline.ts +22 -7
  31. package/src/agent-src/scripts/stubs_due.ts +118 -2
  32. package/src/agent-src/scripts/update_roadmap_progress.ts +8 -8
  33. package/src/config/estate-count-budget.json +20 -1
  34. package/src/config/gate-coverage.yml +150 -0
  35. package/src/config/gate-violation-baselines.json +8 -3
  36. package/src/config/preamble-payload-budget.json +19 -11
  37. package/src/config/rule-stub-ceilings.json +14 -7
  38. package/src/scripts/_dispatch.bash +0 -4
  39. package/src/scripts/_lib/activation_ladder.ts +47 -3
  40. package/src/scripts/_lib/activation_receipt_producer.ts +341 -0
  41. package/src/scripts/_lib/adr_frontmatter.ts +26 -0
  42. package/src/scripts/_lib/candidate_pair_delta.ts +349 -0
  43. package/src/scripts/_lib/carrier_status.ts +118 -0
  44. package/src/scripts/_lib/cascade_stage_enumeration.ts +92 -0
  45. package/src/scripts/_lib/corpus_manifest.ts +726 -0
  46. package/src/scripts/_lib/delivery_arm_experiment.ts +18 -2
  47. package/src/scripts/_lib/estate_carrier_cases.ts +72 -0
  48. package/src/scripts/_lib/evaluation_cascade.ts +452 -0
  49. package/src/scripts/_lib/evolution_roi.ts +562 -0
  50. package/src/scripts/_lib/harness_evolution_guards.ts +10 -0
  51. package/src/scripts/_lib/lexical_shortlist.ts +215 -0
  52. package/src/scripts/_lib/llm_candidate_proposer.ts +461 -0
  53. package/src/scripts/_lib/llm_proposer_transport.ts +153 -0
  54. package/src/scripts/_lib/pathology_archive.ts +393 -0
  55. package/src/scripts/_lib/promotion_capability.ts +315 -0
  56. package/src/scripts/_lib/promotion_evidence.ts +414 -0
  57. package/src/scripts/_lib/promotion_review.ts +294 -0
  58. package/src/scripts/_lib/prompt_shape.ts +92 -0
  59. package/src/scripts/_lib/release_highlights.ts +70 -1
  60. package/src/scripts/_lib/release_material.ts +125 -2
  61. package/src/scripts/_lib/routing_corpus.ts +307 -0
  62. package/src/scripts/_lib/routing_index_input.ts +104 -0
  63. package/src/scripts/_lib/rule_injection.ts +22 -1
  64. package/src/scripts/_lib/semantic_noop.ts +167 -0
  65. package/src/scripts/activation_receipt.ts +258 -0
  66. package/src/scripts/ai_council/cross_exam.ts +128 -0
  67. package/src/scripts/ai_council/explain_route.ts +157 -0
  68. package/src/scripts/ai_council/information_gain.ts +293 -0
  69. package/src/scripts/ai_council/leakage_corpus.ts +385 -0
  70. package/src/scripts/ai_council/leakage_patterns.ts +453 -0
  71. package/src/scripts/ai_council/minority_retention.ts +171 -0
  72. package/src/scripts/ai_council/recouncil_savings.ts +299 -0
  73. package/src/scripts/ai_council/replay_route.ts +223 -0
  74. package/src/scripts/ai_council/reviewer_assignment.ts +279 -0
  75. package/src/scripts/ai_council/routing_training_row.ts +197 -0
  76. package/src/scripts/ai_council/synthesis_strategy.ts +159 -0
  77. package/src/scripts/check_adr_frontmatter.ts +4 -9
  78. package/src/scripts/check_estate_count.ts +205 -24
  79. package/src/scripts/check_release_highlights.ts +96 -13
  80. package/src/scripts/check_roadmap_trackable.ts +17 -2
  81. package/src/scripts/corpus_manifest.ts +235 -0
  82. package/src/scripts/evolution_lab.ts +181 -8
  83. package/src/scripts/hook_manifest.json +1 -1
  84. package/src/scripts/hook_manifest.yaml +28 -6
  85. package/src/scripts/hooks/comment_discipline_hook.ts +146 -0
  86. package/src/scripts/hooks/concern_registry.ts +2 -0
  87. package/src/scripts/hooks/turn_end_gate_hook.ts +9 -1
  88. package/src/scripts/language_mirror_hook.ts +14 -2
  89. package/src/scripts/lint_carrier_integrity.ts +820 -0
  90. package/src/scripts/lint_code_comments.ts +643 -0
  91. package/src/scripts/lint_plan_risk_register.ts +39 -19
  92. package/src/scripts/lint_promotion_paths.ts +779 -0
  93. package/src/scripts/llm_propose.ts +256 -0
  94. package/src/scripts/measure_delivery_sets.ts +361 -0
  95. package/src/scripts/measure_routing_signal.ts +307 -0
  96. package/src/scripts/model_rule_injection.ts +19 -3
  97. package/src/scripts/probe_council_retention.ts +495 -0
  98. package/src/scripts/release.ts +21 -37
  99. package/src/scripts/release_drill.ts +143 -0
  100. package/src/scripts/release_env.ts +35 -0
  101. package/src/scripts/release_publication.ts +84 -0
@@ -487,6 +487,7 @@ The question is never "what does the file say" but **"what does it become here"*
487
487
  | reference material read on demand | a `guideline` or `context` |
488
488
  | a measured finding | a `decision-record`/ADR, not a rule |
489
489
  | a defect claim | a roadmap item, once verified |
490
+ | a claim that maps onto an existing stub | **not a fresh artifact — a stub-blocked finding, see below** |
490
491
  | a consolidation omitting a parent | **not an artifact — a discharge, see below** |
491
492
 
492
493
  **An omitted parent is discharged, never left silent.** When Phase 2's `lineage`
@@ -511,6 +512,41 @@ A `ghost:` reading — a declared parent with no matching file — has the same
511
512
  discharges plus a fourth that is really a correction: fix the name. A lineage
512
513
  naming a plan nobody can open is the cheaper half of the same defect.
513
514
 
515
+ **A survivor that maps onto an existing stub is never resolved into a verdict
516
+ line.** When a verified claim lands on something `agents/roadmaps/stubs/` already
517
+ holds, the mapping is neither "a defect claim → a roadmap item" nor "nothing to
518
+ do": the plan exists, and something is holding it. Before this row the table had
519
+ no entry for that state, so a run that found one had no prescribed output and
520
+ wrote a summary sentence instead. The round that produced this row closed exactly
521
+ so — *"Not a neglected guard: the cost of an unmade owner decision. On the owner's
522
+ desk."* That sentence is accurate, and it is why nine feedback rounds on one guard
523
+ produced no executable item: it names a state and hands nobody anything to do.
524
+
525
+ The run's output MUST carry all four of these, per stub matched. Three of them
526
+ are the difference between a finding and a shrug, and the fourth is already
527
+ computed upstream:
528
+
529
+ 1. **The stub path** — `agents/roadmaps/stubs/<slug>.md`, so a reader opens it
530
+ instead of searching for it.
531
+ 2. **Its blocker slug** — the `### blocker: <slug>` holding it, named. Where the
532
+ hold is recorded outside any blocker section, say so in those words and name
533
+ the section; "waiting on a decision" with nothing named is the verdict line
534
+ again, one word longer.
535
+ 3. **Its age in days** — days since the file first appeared under
536
+ `agents/roadmaps/stubs/`, from
537
+ `git log --diff-filter=A --format=%ad --date=short -- <path> | tail -1`. A
538
+ stub parked eleven days and one parked two hundred are different findings and
539
+ a reader cannot tell them apart from the path.
540
+ 4. **The recurrence count from Phase 4c** — how many earlier rounds raised the
541
+ same subject. A first arrival and a ninth are different findings, 4c has
542
+ already counted it, and dropping it here is what let one subject arrive nine
543
+ times as if each were new.
544
+
545
+ Missing any of the four leaves the survivor undischarged. This row does not
546
+ decide what happens to the stub — promotion is the estate's question and this
547
+ command does not answer it — it only forbids resolving the survivor into prose
548
+ that names no artifact.
549
+
514
550
  Three hard defaults, from this repo's own scar tissue:
515
551
 
516
552
  - **Extend before you create.** Run the four-surface overlap scan from
@@ -22,6 +22,9 @@ collision_ok:
22
22
  "implement": "fresh implementation is where over-commenting lands"
23
23
  # obligation: line 5
24
24
  obligation_frequency: "per-edit"
25
+ enforced_by:
26
+ - "validator:src/scripts/lint_code_comments.ts"
27
+ - "hook:comment-discipline"
25
28
  ---
26
29
 
27
30
  # Code Comment Discipline
@@ -37,6 +40,9 @@ NO DOCBLOCK THAT ONLY MIRRORS THE NATIVE SIGNATURE.
37
40
  DOCBLOCKS EARN THEIR PLACE ONLY WITH MACHINE-RELEVANT PRECISION
38
41
  (GENERICS, ARRAY SHAPES, NON-TRIVIAL UNIONS) OR GENUINE WHY-CONTEXT.
39
42
  WHEN IN DOUBT: NO COMMENT. SHORTER IS BETTER. NONE IS OFTEN BEST.
43
+ A COMMENT NAMING WHERE THE CODE CAME FROM — A ROADMAP PATH, A PHASE AND STEP,
44
+ A PROTOTYPE — IS EVIDENCE IN THE WRONG PLACE. PROVENANCE IS NOT A WHY.
45
+ A ROADMAP CANNOT LICENSE A COMMENT THIS RULE FORBIDS.
40
46
  ```
41
47
 
42
48
  ## What a comment is FOR
@@ -12,8 +12,8 @@
12
12
  * `urp.collect()` completion criterion, and exit code 0. snake_case kept.
13
13
  *
14
14
  * A roadmap that has reached `count_open == 0` and `count_deferred == 0` is
15
- * **complete**. This sweep moves it to `agents/roadmaps/archive/`, rewrites
16
- * inbound references (`agents/roadmaps/<x>.md` → `agents/roadmaps/archive/<x>.md`)
15
+ * **complete**. This sweep moves it to `agents/roadmaps/archive/`, rewrites code-comment-allow provenance-comment -- the sweep's own operand
16
+ * inbound references (`agents/roadmaps/<x>.md` → `agents/roadmaps/archive/<x>.md`) code-comment-allow provenance-comment -- the rewrite this sweep performs
17
17
  * across tracked files so links never break, and regenerates the dashboard.
18
18
  *
19
19
  * It replaces the old **merge-gate** (keep one item open + a manual post-merge
@@ -136,7 +136,7 @@ function _branch_touched_paths(root: string, base: string): Set<string> | null {
136
136
  *
137
137
  * Two classes, and the second is why this predicate exists at all:
138
138
  *
139
- * - `agents/evidence/**` — a committed record of what was true, or of what a
139
+ * - `agents/evidence/**` — a committed record of what was true, or of what a code-comment-allow provenance-comment -- the predicate's operand set
140
140
  * reviewer was shown, at one point in time. Retroactively editing it makes it
141
141
  * claim something that did not happen, which is the failure
142
142
  * `evaluator-independence` exists to prevent ("the prompt ships with the
@@ -146,7 +146,7 @@ function _branch_touched_paths(root: string, base: string): Set<string> | null {
146
146
  * AND makes `git apply` target a path the diff never created.
147
147
  *
148
148
  * Measured 2026-08-11: archiving one roadmap rewrote four frozen artifacts
149
- * under `agents/evidence/reviews/`, including the `diff.patch` headers of a
149
+ * under `agents/evidence/reviews/`, including the `diff.patch` headers of a code-comment-allow provenance-comment -- names what the predicate protects
150
150
  * recorded review input. Nothing caught it — the sweep reported it as
151
151
  * "4 ref(s) migrated", which reads like success.
152
152
  */
@@ -346,7 +346,7 @@ function _escapeRe(v: string): string {
346
346
  * bare `- <name>` item inside the `relates:` block, which is a declaration the
347
347
  * author made deliberately. A mention anywhere else in the file does not count.
348
348
  */
349
- function _relatesTo(text: string, slug: string): boolean {
349
+ export function _relatesTo(text: string, slug: string): boolean {
350
350
  const lines = text.split('\n');
351
351
  const start = lines.findIndex((l) => /^relates:/.test(l));
352
352
  if (start === -1) return false;
@@ -654,7 +654,7 @@ function _regen_dashboard(root: string, dry_run: boolean): void {
654
654
  /**
655
655
  * Rebuild the archive index after a move, when the builder is present.
656
656
  *
657
- * The sweep is the one operation that changes what `agents/roadmaps/archive/`
657
+ * The sweep is the one operation that changes what `agents/roadmaps/archive/` code-comment-allow provenance-comment -- the directory this function writes
658
658
  * contains, so it is the one operation that can leave the generated index
659
659
  * stale — and the drift gate on that index runs in the same PR the sweep runs
660
660
  * in. Regenerating here keeps the two from disagreeing by construction rather
@@ -226,9 +226,18 @@ export function guardedBaselineProblems(items: readonly GuardedBaselineItem[]):
226
226
  *
227
227
  * HONEST SCOPE. Only a PATH trigger is machine-checkable: a token containing `/`
228
228
  * is resolved against the repo root and stale iff it exists. A bare symbol name
229
- * cannot be decided from a path, so it is returned as `unverifiable` rather than
230
- * silently reading as "not stale" — an unchecked trigger that looks checked is
231
- * the failure this whole state exists to avoid.
229
+ * cannot be decided from a path, so a record carrying ONLY symbol tokens is
230
+ * returned as `unverifiable` rather than silently reading as "not stale" — an
231
+ * unchecked trigger that looks checked is the failure this whole state exists
232
+ * to avoid.
233
+ *
234
+ * A record whose trigger carries AT LEAST ONE path token is decided by that
235
+ * path and is NOT reported as unverifiable, even when a companion symbol token
236
+ * sits beside it. Reporting it would be the mirror of the failure above: a
237
+ * checked trigger that looks unchecked. Measured 2026-09-01 — three of the four
238
+ * lines the dashboard printed as "not machine-checkable" carried a path token
239
+ * and were therefore already decidable, which is noise that trains a reader to
240
+ * skip the section where the one genuinely undecidable trigger also lives.
232
241
  */
233
242
  export function guardedBaselineStaleness(
234
243
  items: readonly GuardedBaselineItem[],
@@ -241,16 +250,22 @@ export function guardedBaselineStaleness(
241
250
  if (trigger === '') {
242
251
  continue;
243
252
  }
244
- for (const token of trigger.split(/[\s,]+/).filter((t) => t !== '')) {
245
- if (!token.includes('/')) {
246
- unverifiable.push(`line ${it.line} (${it.label}): recheck_when \`${token}\``);
247
- } else if (fs.existsSync(path.join(repo_root, token))) {
253
+ const tokens = trigger.split(/[\s,]+/).filter((t) => t !== '');
254
+ const paths = tokens.filter((t) => t.includes('/'));
255
+ for (const token of paths) {
256
+ if (fs.existsSync(path.join(repo_root, token))) {
248
257
  stale.push(
249
258
  `line ${it.line} (${it.label}): recheck_when \`${token}\` now exists — ` +
250
259
  're-verify against the real mechanism',
251
260
  );
252
261
  }
253
262
  }
263
+ if (paths.length > 0) {
264
+ continue;
265
+ }
266
+ for (const token of tokens) {
267
+ unverifiable.push(`line ${it.line} (${it.label}): recheck_when \`${token}\``);
268
+ }
254
269
  }
255
270
  return { stale, unverifiable };
256
271
  }
@@ -66,6 +66,48 @@ export interface StubRecord {
66
66
  owner_decisions: number;
67
67
  overdue: boolean;
68
68
  days_overdue: number | null;
69
+ /** Frontmatter `design_validated:` — a citation, or null when absent. */
70
+ design_validated: string | null;
71
+ /** Frontmatter `capability_gap:` — `none` means the stub declares no gap. */
72
+ capability_gap: string | null;
73
+ /** Frontmatter `blocker_class:` — `estate` | `budget` | `product`. */
74
+ blocker_class: string | null;
75
+ /** Frontmatter `blocker_opened:` — ISO date the hold began, or null. */
76
+ blocker_opened: string | null;
77
+ /** Releases published since `blocker_opened`, or null when it is absent. */
78
+ releases_since_blocked: number | null;
79
+ /** All three membership conditions hold. See {@link BLOCKED_QUICKWIN_FIELDS}. */
80
+ blocked_quickwin: boolean;
81
+ }
82
+
83
+ /**
84
+ * The three frontmatter keys that decide fourth-bucket membership.
85
+ *
86
+ * Read as SCALARS, never as substrings of the body. A substring match over
87
+ * prose is what put a validated fix and a waiting preference into one bucket
88
+ * in the first place; doing it with three substrings instead of one would be
89
+ * worse, not better. A stub missing any of the three is not a member and falls
90
+ * through to the OWNER bucket exactly as before — absence is never a member.
91
+ */
92
+ export const BLOCKED_QUICKWIN_FIELDS = ['design_validated', 'capability_gap', 'blocker_class'] as const;
93
+
94
+ /** Blocker classes that make a stub a BLOCKED QUICK-WIN rather than a product wait. */
95
+ export const QUICKWIN_BLOCKER_CLASSES = ['estate', 'budget'] as const;
96
+
97
+ /**
98
+ * All three conditions, read from frontmatter only.
99
+ *
100
+ * A recorded design validation, an explicit `capability_gap: none`, and an open
101
+ * blocker that is an estate or budget decision rather than a product one.
102
+ */
103
+ export function is_blocked_quickwin(
104
+ design_validated: string | null,
105
+ capability_gap: string | null,
106
+ blocker_class: string | null,
107
+ ): boolean {
108
+ if (design_validated === null || design_validated === '') return false;
109
+ if (capability_gap !== 'none') return false;
110
+ return (QUICKWIN_BLOCKER_CLASSES as readonly string[]).includes(blocker_class ?? '');
69
111
  }
70
112
 
71
113
  function _isDir(p: string): boolean {
@@ -188,7 +230,48 @@ function _parseIso(s: string): Date | null {
188
230
 
189
231
  /** Read every stub and classify it. `today` is injectable so tests are stable. */
190
232
  export function scan(root: string, today: string): StubRecord[] {
191
- return scan_dir(path.join(root, STUB_DIR), today);
233
+ return scan_dir(path.join(root, STUB_DIR), today, release_dates(root));
234
+ }
235
+
236
+ /**
237
+ * Changelog files that carry released sections, newest era first.
238
+ *
239
+ * BOTH are read, and that is the whole point of the pair. The era split moved
240
+ * older releases into the archive, so a count taken over the current file alone
241
+ * silently reports zero for anything before the split — measured: 14.11.0 lives
242
+ * only in the archive, and grepping `CHANGELOG.md` for it returns nothing.
243
+ */
244
+ export const CHANGELOG_ERAS = ['CHANGELOG.md', 'docs/archive/CHANGELOG-pre-14.12.0.md'] as const;
245
+
246
+ /** `## [x.y.z](…) (YYYY-MM-DD)` — a released section heading with its date. */
247
+ const RELEASE_HEADING = /^## \[\d+\.\d+\.\d+\][^\n]*\((\d{4}-\d{2}-\d{2})\)\s*$/gm;
248
+
249
+ /** Every released section's date across both eras, unsorted. */
250
+ export function release_dates(root: string): string[] {
251
+ const out: string[] = [];
252
+ for (const rel of CHANGELOG_ERAS) {
253
+ let text: string;
254
+ try {
255
+ text = fs.readFileSync(path.join(root, rel), 'utf8');
256
+ } catch {
257
+ continue;
258
+ }
259
+ for (const m of text.matchAll(RELEASE_HEADING)) {
260
+ if (m[1] !== undefined) out.push(m[1]);
261
+ }
262
+ }
263
+ return out;
264
+ }
265
+
266
+ /**
267
+ * Releases published strictly AFTER the day the hold began.
268
+ *
269
+ * Strictly after, because a release cut on the same day cannot have carried a
270
+ * fix the hold was blocking — counting it would inflate the figure the
271
+ * council's falsifier is read against.
272
+ */
273
+ export function releases_since(opened: string, dates: readonly string[]): number {
274
+ return dates.filter((d) => d > opened).length;
192
275
  }
193
276
 
194
277
  /**
@@ -198,7 +281,11 @@ export function scan(root: string, today: string): StubRecord[] {
198
281
  * immediately re-join — a fragile round trip past exactly the directory both
199
282
  * callers care about.
200
283
  */
201
- export function scan_dir(base: string, today: string): StubRecord[] {
284
+ export function scan_dir(
285
+ base: string,
286
+ today: string,
287
+ releaseDates: readonly string[] = [],
288
+ ): StubRecord[] {
202
289
  if (!_isDir(base)) return [];
203
290
  const now = _parseIso(today);
204
291
  const out: StubRecord[] = [];
@@ -222,6 +309,10 @@ export function scan_dir(base: string, today: string): StubRecord[] {
222
309
  const review_by = _scalar(front, 'review_by');
223
310
  const reviewed_at = _scalar(front, 'reviewed_at');
224
311
  const probeVal = _scalar(front, 'probe');
312
+ const design_validated = _scalar(front, 'design_validated');
313
+ const capability_gap = _scalar(front, 'capability_gap');
314
+ const blocker_class = _scalar(front, 'blocker_class');
315
+ const blocker_opened = _scalar(front, 'blocker_opened');
225
316
  const shape: StubShape = text.includes(TRANSFER_MARKER) ? 'transfer' : 'orgmode';
226
317
  let overdue = false;
227
318
  let days_overdue: number | null = null;
@@ -242,6 +333,13 @@ export function scan_dir(base: string, today: string): StubRecord[] {
242
333
  owner_decisions: count_owner_decisions(text),
243
334
  overdue,
244
335
  days_overdue,
336
+ design_validated,
337
+ capability_gap,
338
+ blocker_class,
339
+ blocker_opened,
340
+ releases_since_blocked:
341
+ blocker_opened === null ? null : releases_since(blocker_opened, releaseDates),
342
+ blocked_quickwin: is_blocked_quickwin(design_validated, capability_gap, blocker_class),
245
343
  });
246
344
  }
247
345
  return out;
@@ -251,6 +349,8 @@ export interface Counts {
251
349
  overdue: number;
252
350
  owner_decisions: number;
253
351
  missing_review_by: number;
352
+ /** ADDITIVE 2026-09-01. Existing keys are unchanged in name, type and meaning. */
353
+ blocked_quickwin: number;
254
354
  total: number;
255
355
  }
256
356
 
@@ -259,6 +359,7 @@ export function counts(records: StubRecord[]): Counts {
259
359
  overdue: records.filter((r) => r.overdue).length,
260
360
  owner_decisions: records.reduce((s, r) => s + r.owner_decisions, 0),
261
361
  missing_review_by: records.filter((r) => r.review_by === null).length,
362
+ blocked_quickwin: records.filter((r) => r.blocked_quickwin).length,
262
363
  total: records.length,
263
364
  };
264
365
  }
@@ -328,6 +429,21 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
328
429
  }
329
430
  }
330
431
 
432
+ const quickwins = records.filter((r) => r.blocked_quickwin);
433
+ if (quickwins.length > 0) {
434
+ process.stdout.write(
435
+ `\n 🔓 ${quickwins.length} BLOCKED QUICK-WIN(S) — design validated, no capability gap,\n` +
436
+ ' held by an estate or budget decision rather than a product one:\n',
437
+ );
438
+ for (const r of quickwins) {
439
+ const since =
440
+ r.releases_since_blocked === null
441
+ ? 'no `blocker_opened:`'
442
+ : `${String(r.releases_since_blocked)} release(s) since ${r.blocker_opened}`;
443
+ process.stdout.write(` ${r.file} (${r.blocker_class}, ${since})\n`);
444
+ }
445
+ }
446
+
331
447
  const noProbe = records.filter((r) => !r.probe_none && !r.has_probe_heading);
332
448
  if (noProbe.length > 0) {
333
449
  process.stdout.write(
@@ -12,7 +12,7 @@
12
12
  * `archive_completed_roadmaps` can consume it the same way Python does. snake_case
13
13
  * is kept on the public surface.
14
14
  *
15
- * Scans every roadmap under `agents/roadmaps/` (excluding `archive/`, `skipped/`,
15
+ * Scans every roadmap under `agents/roadmaps/` (excluding `archive/`, `skipped/`, code-comment-allow provenance-comment -- the path is this script's operand, not where the code came from
16
16
  * `template.md`, `README.md`, `open-questions*.md`), counts checkbox states per
17
17
  * phase, and writes a dashboard at `agents/roadmaps-progress.md` (outside the
18
18
  * `roadmaps/` folder to keep it clean) with:
@@ -96,7 +96,7 @@ const EXCLUDE_DIRS: ReadonlySet<string> = new Set(['archive', 'skipped', 'stubs'
96
96
 
97
97
  // FRONTMATTER_RE = re.compile(r"\A---\n(.*?)\n---\s*\n", re.DOTALL)
98
98
  const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---[ \t\n\r\f\v]*\n/;
99
- const DRAFT_VALUES: ReadonlySet<string> = new Set(['draft']);
99
+ const UNSCHEDULED_VALUES: ReadonlySet<string> = new Set(['draft', 'carrier']);
100
100
 
101
101
  const MERGE_GATED_RE = /merge-gated/i;
102
102
  // PR_NUM_RE = re.compile(r"pr\s*[=#:]?\s*#?\s*(\d+)", re.IGNORECASE)
@@ -289,8 +289,8 @@ function _stripQuotes(s: string): string {
289
289
  return out;
290
290
  }
291
291
 
292
- function is_draft(fm: Record<string, string>): boolean {
293
- return DRAFT_VALUES.has((fm['status'] ?? '').toLowerCase());
292
+ function is_unscheduled(fm: Record<string, string>): boolean {
293
+ return UNSCHEDULED_VALUES.has((fm['status'] ?? '').toLowerCase());
294
294
  }
295
295
 
296
296
  /**
@@ -752,7 +752,7 @@ function collect(roadmap_root: string): RoadmapStats[] {
752
752
  continue;
753
753
  }
754
754
  const text = fs.readFileSync(p, { encoding: 'utf-8' });
755
- if (is_draft(parse_frontmatter(text))) {
755
+ if (is_unscheduled(parse_frontmatter(text))) {
756
756
  continue;
757
757
  }
758
758
  const stats = parse_roadmap(p, roadmap_root);
@@ -820,7 +820,7 @@ function collect_parked(roadmap_root: string): ParkedRoadmap[] {
820
820
  } catch {
821
821
  continue;
822
822
  }
823
- if (is_draft(parse_frontmatter(text))) {
823
+ if (is_unscheduled(parse_frontmatter(text))) {
824
824
  continue;
825
825
  }
826
826
  const cell = resume_cell(text);
@@ -1305,7 +1305,7 @@ function _parseArgs(argv: readonly string[]): Args {
1305
1305
  }
1306
1306
 
1307
1307
  /**
1308
- * When the default cwd carries no `agents/roadmaps/`, fall back to the git
1308
+ * When the default cwd carries no `agents/roadmaps/`, fall back to the git code-comment-allow provenance-comment -- the path is this script's operand, not where the code came from
1309
1309
  * toplevel — hook and IDE invocations often run from a subdirectory, and a
1310
1310
  * cwd-only resolution silently skipped the dashboard there. An explicit
1311
1311
  * `--repo-root` always wins; a cwd that has the directory is used as-is
@@ -1479,7 +1479,7 @@ export {
1479
1479
  TITLE_RE,
1480
1480
  FRONTMATTER_RE,
1481
1481
  parse_frontmatter,
1482
- is_draft,
1482
+ is_unscheduled,
1483
1483
  is_roadmap_candidate,
1484
1484
  count_checkboxes,
1485
1485
  parse_blockers,
@@ -39,7 +39,7 @@ schema_version: 1
39
39
  # CI guard: a release bump of `package.json` must update this value
40
40
  # in lockstep — see scripts/check_template_pin_drift.ts (road-to-
41
41
  # portable-runtime-and-update-check P3.3).
42
- agent_config_version: "14.13.0"
42
+ agent_config_version: "14.14.0"
43
43
 
44
44
  # --- Project identity ---
45
45
  project:
@@ -215,6 +215,43 @@ function humanAuthoredLead(text) {
215
215
  }
216
216
  return kept.join("\n");
217
217
  }
218
+ var REGION_OPEN = /^\s*<([A-Za-z][\w-]*)>\s*$/;
219
+ var REGION_NOTE = /\b(content (above|below) is from|treat (it|this|that|the (content|text|above|following)) as data)\b/i;
220
+ function stripInjectedRegions(text) {
221
+ const lines = text.split("\n");
222
+ const kept = [];
223
+ for (let i = 0; i < lines.length; i++) {
224
+ const line = lines[i] ?? "";
225
+ const open = REGION_OPEN.exec(line);
226
+ if (open === null) {
227
+ kept.push(line);
228
+ continue;
229
+ }
230
+ const close = new RegExp(`^\\s*</${open[1] ?? ""}>\\s*$`);
231
+ let end = -1;
232
+ for (let j = i + 1; j < lines.length; j++) {
233
+ if (close.test(lines[j] ?? "")) {
234
+ end = j;
235
+ break;
236
+ }
237
+ }
238
+ if (end === -1) {
239
+ kept.push(line);
240
+ continue;
241
+ }
242
+ if (kept.length > 0 && REGION_NOTE.test(kept[kept.length - 1] ?? "")) {
243
+ kept.pop();
244
+ }
245
+ i = end;
246
+ while (i + 1 < lines.length && (lines[i + 1] ?? "").trim() !== "") {
247
+ if (!REGION_NOTE.test(lines[i + 1] ?? "")) {
248
+ break;
249
+ }
250
+ i++;
251
+ }
252
+ }
253
+ return kept.join("\n");
254
+ }
218
255
 
219
256
  // src/scripts/language_mirror_hook.ts
220
257
  var EXIT_ALLOW = 0;
@@ -261,7 +298,7 @@ function instructionText(prompt) {
261
298
  return kept.join("\n");
262
299
  }
263
300
  function classify(prompt) {
264
- const text = instructionText(prompt);
301
+ const text = instructionText(stripInjectedRegions(prompt));
265
302
  const lead = humanAuthoredLead(text);
266
303
  const leadVerdict = _score(lead);
267
304
  if (leadVerdict.language !== "und") {
@@ -1,6 +1,6 @@
1
1
  # Discovery — Deprecation Report
2
2
 
3
- - Generated: `2026-08-31T12:02:28Z`
3
+ - Generated: `2026-09-02T14:21:19Z`
4
4
  - Deprecated artefacts: **0**
5
5
 
6
6
  _None. Tree is clean._
@@ -360,7 +360,7 @@
360
360
  },
361
361
  {
362
362
  "category": "command",
363
- "checksum": "sha256:2279bdd2064f11bb4f0a18bbc13b818f2208959d15845a1aacd44e3b09d49752",
363
+ "checksum": "sha256:566e4d523e131bdf5e0a28a5bf0c544805956c646b03d2a45a9061c8268d4d49",
364
364
  "install": {
365
365
  "default": true,
366
366
  "removable": false
@@ -5660,7 +5660,7 @@
5660
5660
  },
5661
5661
  {
5662
5662
  "category": "rule",
5663
- "checksum": "sha256:5564d2f97795177c1447749a8fab43f61656a928ebe8d1b14716bc83ceaa07c3",
5663
+ "checksum": "sha256:a6ae2d34f3972a509ba275be77c31d75856877dc166962f72db5c2fd6236c2a9",
5664
5664
  "install": {
5665
5665
  "default": true,
5666
5666
  "removable": false
@@ -14760,7 +14760,7 @@
14760
14760
  "workspaces": []
14761
14761
  }
14762
14762
  ],
14763
- "checksum": "sha256:4c8e3d49e0a995eed75ef73200239bdd111f02bab4ea8522b329c0a95fdf4397",
14763
+ "checksum": "sha256:741fc18e76a73a2474604b840ac9c9431ad140c3c674cde381985e4b5eaad810",
14764
14764
  "deprecations": [
14765
14765
  {
14766
14766
  "key": "tier",
@@ -14909,7 +14909,7 @@
14909
14909
  "reason": "scaffold template for ticket-bundle tickets (ADR-101); carries no runtime frontmatter — emit-tickets fills it in per ticket"
14910
14910
  }
14911
14911
  ],
14912
- "generated_at": "2026-08-31T12:02:28Z",
14912
+ "generated_at": "2026-09-02T14:21:19Z",
14913
14913
  "packs": [
14914
14914
  {
14915
14915
  "artefact_count": 128,
@@ -1 +1 @@
1
- 7e4e2017bb44a944d8c221cc6c81275e3215a85e055cbbf163f32bbc635cb107 discovery-manifest.json
1
+ e89b20c3c1bb6136e6c752e18629851eb22679b0ae3f127bff36f17b3356a898 discovery-manifest.json
@@ -1,6 +1,6 @@
1
1
  # Discovery Manifest — Summary
2
2
 
3
- - Generated: `2026-08-31T12:02:28Z`
3
+ - Generated: `2026-09-02T14:21:19Z`
4
4
  - Scanner: `de0a22b4ac4c`
5
5
  - Artefacts: **623**
6
6
  - Unassigned: **0**
@@ -1,6 +1,6 @@
1
1
  # Discovery — Dormancy Report
2
2
 
3
- - Generated: `2026-08-31T12:02:28Z`
3
+ - Generated: `2026-09-02T14:21:19Z`
4
4
  - Window: **6 months** without a commit
5
5
 
6
6
  > Dormancy triggers **review, never auto-deprecation**
@@ -1,6 +1,6 @@
1
1
  # Discovery — Orphan Report
2
2
 
3
- - Generated: `2026-08-31T12:02:28Z`
3
+ - Generated: `2026-09-02T14:21:19Z`
4
4
  - Orphan artefacts: **0**
5
5
 
6
6
  > An orphan is an artefact whose declared pack has no other members.
@@ -1,6 +1,6 @@
1
1
  {
2
- "checksum": "sha256:4c8e3d49e0a995eed75ef73200239bdd111f02bab4ea8522b329c0a95fdf4397",
3
- "generated_at": "2026-08-31T12:02:28Z",
2
+ "checksum": "sha256:741fc18e76a73a2474604b840ac9c9431ad140c3c674cde381985e4b5eaad810",
3
+ "generated_at": "2026-09-02T14:21:19Z",
4
4
  "packs": [
5
5
  {
6
6
  "always_on": false,
@@ -1,6 +1,6 @@
1
1
  # Discovery — Trust Report
2
2
 
3
- - Generated: `2026-08-31T12:02:28Z`
3
+ - Generated: `2026-09-02T14:21:19Z`
4
4
  - Workspaces tracked: **10**
5
5
  - Human-review-required artefacts: **3**
6
6
 
@@ -1,6 +1,6 @@
1
1
  {
2
- "checksum": "sha256:4c8e3d49e0a995eed75ef73200239bdd111f02bab4ea8522b329c0a95fdf4397",
3
- "generated_at": "2026-08-31T12:02:28Z",
2
+ "checksum": "sha256:741fc18e76a73a2474604b840ac9c9431ad140c3c674cde381985e4b5eaad810",
3
+ "generated_at": "2026-09-02T14:21:19Z",
4
4
  "scanner_version": "de0a22b4ac4c",
5
5
  "workspaces": [
6
6
  {