@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.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +116 -387
- package/dist/agent-src/commands/analyze/inbox.md +36 -0
- package/dist/agent-src/rules/code-comment-discipline.md +6 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
- package/dist/agent-src/scripts/guarded_baseline.ts +22 -7
- package/dist/agent-src/scripts/stubs_due.ts +118 -2
- package/dist/agent-src/scripts/update_roadmap_progress.ts +8 -8
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/cli-delegate/cmd_conformance.js +38 -1
- 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 +1438 -711
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +112 -17
- package/docs/guidelines/agent-infra/guarded-baseline.md +1 -1
- package/docs/guidelines/code-clarity.md +18 -0
- package/package.json +1 -1
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +6 -6
- package/src/agent-src/scripts/guarded_baseline.ts +22 -7
- package/src/agent-src/scripts/stubs_due.ts +118 -2
- package/src/agent-src/scripts/update_roadmap_progress.ts +8 -8
- package/src/config/estate-count-budget.json +20 -1
- package/src/config/gate-coverage.yml +150 -0
- package/src/config/gate-violation-baselines.json +8 -3
- package/src/config/preamble-payload-budget.json +19 -11
- package/src/config/rule-stub-ceilings.json +14 -7
- package/src/scripts/_dispatch.bash +0 -4
- package/src/scripts/_lib/activation_ladder.ts +47 -3
- package/src/scripts/_lib/activation_receipt_producer.ts +341 -0
- package/src/scripts/_lib/adr_frontmatter.ts +26 -0
- package/src/scripts/_lib/candidate_pair_delta.ts +349 -0
- package/src/scripts/_lib/carrier_status.ts +118 -0
- package/src/scripts/_lib/cascade_stage_enumeration.ts +92 -0
- package/src/scripts/_lib/corpus_manifest.ts +726 -0
- package/src/scripts/_lib/delivery_arm_experiment.ts +18 -2
- package/src/scripts/_lib/estate_carrier_cases.ts +72 -0
- package/src/scripts/_lib/evaluation_cascade.ts +452 -0
- package/src/scripts/_lib/evolution_roi.ts +562 -0
- package/src/scripts/_lib/harness_evolution_guards.ts +10 -0
- package/src/scripts/_lib/lexical_shortlist.ts +215 -0
- package/src/scripts/_lib/llm_candidate_proposer.ts +461 -0
- package/src/scripts/_lib/llm_proposer_transport.ts +153 -0
- package/src/scripts/_lib/pathology_archive.ts +393 -0
- package/src/scripts/_lib/promotion_capability.ts +315 -0
- package/src/scripts/_lib/promotion_evidence.ts +414 -0
- package/src/scripts/_lib/promotion_review.ts +294 -0
- package/src/scripts/_lib/prompt_shape.ts +92 -0
- package/src/scripts/_lib/release_highlights.ts +70 -1
- package/src/scripts/_lib/release_material.ts +125 -2
- package/src/scripts/_lib/routing_corpus.ts +307 -0
- package/src/scripts/_lib/routing_index_input.ts +104 -0
- package/src/scripts/_lib/rule_injection.ts +22 -1
- package/src/scripts/_lib/semantic_noop.ts +167 -0
- package/src/scripts/activation_receipt.ts +258 -0
- package/src/scripts/ai_council/cross_exam.ts +128 -0
- package/src/scripts/ai_council/explain_route.ts +157 -0
- package/src/scripts/ai_council/information_gain.ts +293 -0
- package/src/scripts/ai_council/leakage_corpus.ts +385 -0
- package/src/scripts/ai_council/leakage_patterns.ts +453 -0
- package/src/scripts/ai_council/minority_retention.ts +171 -0
- package/src/scripts/ai_council/recouncil_savings.ts +299 -0
- package/src/scripts/ai_council/replay_route.ts +223 -0
- package/src/scripts/ai_council/reviewer_assignment.ts +279 -0
- package/src/scripts/ai_council/routing_training_row.ts +197 -0
- package/src/scripts/ai_council/synthesis_strategy.ts +159 -0
- package/src/scripts/check_adr_frontmatter.ts +4 -9
- package/src/scripts/check_estate_count.ts +205 -24
- package/src/scripts/check_release_highlights.ts +96 -13
- package/src/scripts/check_roadmap_trackable.ts +17 -2
- package/src/scripts/corpus_manifest.ts +235 -0
- package/src/scripts/evolution_lab.ts +181 -8
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +28 -6
- package/src/scripts/hooks/comment_discipline_hook.ts +146 -0
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +9 -1
- package/src/scripts/language_mirror_hook.ts +14 -2
- package/src/scripts/lint_carrier_integrity.ts +820 -0
- package/src/scripts/lint_code_comments.ts +643 -0
- package/src/scripts/lint_plan_risk_register.ts +39 -19
- package/src/scripts/lint_promotion_paths.ts +779 -0
- package/src/scripts/llm_propose.ts +256 -0
- package/src/scripts/measure_delivery_sets.ts +361 -0
- package/src/scripts/measure_routing_signal.ts +307 -0
- package/src/scripts/model_rule_injection.ts +19 -3
- package/src/scripts/probe_council_retention.ts +495 -0
- package/src/scripts/release.ts +21 -37
- package/src/scripts/release_drill.ts +143 -0
- package/src/scripts/release_env.ts +35 -0
- 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
|
|
230
|
-
* silently reading as "not stale" — an
|
|
231
|
-
* the failure this whole state exists
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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(
|
|
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
|
|
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
|
|
293
|
-
return
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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.
|
|
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") {
|
|
@@ -360,7 +360,7 @@
|
|
|
360
360
|
},
|
|
361
361
|
{
|
|
362
362
|
"category": "command",
|
|
363
|
-
"checksum": "sha256:
|
|
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:
|
|
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:
|
|
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-
|
|
14912
|
+
"generated_at": "2026-09-02T14:21:19Z",
|
|
14913
14913
|
"packs": [
|
|
14914
14914
|
{
|
|
14915
14915
|
"artefact_count": 128,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
e89b20c3c1bb6136e6c752e18629851eb22679b0ae3f127bff36f17b3356a898 discovery-manifest.json
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"checksum": "sha256:
|
|
3
|
-
"generated_at": "2026-
|
|
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
|
{
|
|
2
|
-
"checksum": "sha256:
|
|
3
|
-
"generated_at": "2026-
|
|
2
|
+
"checksum": "sha256:741fc18e76a73a2474604b840ac9c9431ad140c3c674cde381985e4b5eaad810",
|
|
3
|
+
"generated_at": "2026-09-02T14:21:19Z",
|
|
4
4
|
"scanner_version": "de0a22b4ac4c",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
{
|