@event4u/agent-config 14.6.0 → 14.7.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 +260 -7
- package/README.md +1 -1
- package/dist/agent-src/commands/analyze/inbox.md +264 -10
- package/dist/agent-src/commands/analyze.md +1 -1
- package/dist/agent-src/commands/roadmap/process-full.md +116 -30
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +31 -0
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +135 -104
- package/dist/agent-src/rules/context-hygiene.md +2 -0
- package/dist/agent-src/rules/decision-revisit-gate.md +3 -0
- package/dist/agent-src/rules/fix-what-you-see.md +108 -0
- package/dist/agent-src/rules/no-roadmap-references.md +0 -5
- package/dist/agent-src/rules/recurring-criticism.md +110 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
- package/dist/agent-src/rules/rule-type-governance.md +0 -2
- package/dist/agent-src/rules/self-repair-loop.md +17 -0
- package/dist/agent-src/rules/skill-quality.md +0 -2
- package/dist/agent-src/rules/source-confidentiality.md +37 -7
- package/dist/agent-src/scripts/archival_sweep.ts +117 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +19 -3
- package/dist/agent-src/scripts/gate_budget.ts +282 -0
- package/dist/agent-src/scripts/gate_execute.ts +72 -31
- package/dist/agent-src/scripts/roadmap_gates.ts +74 -3
- package/dist/agent-src/scripts/update_roadmap_progress.ts +251 -7
- package/dist/agent-src/skills/ai-council/SKILL.md +48 -807
- package/dist/agent-src/skills/ai-council/references/advanced-modes.md +320 -0
- package/dist/agent-src/skills/ai-council/references/cost-and-redaction.md +99 -0
- package/dist/agent-src/skills/ai-council/references/execution-modes.md +162 -0
- package/dist/agent-src/skills/ai-council/references/output-and-synthesis.md +125 -0
- package/dist/agent-src/skills/ai-council/references/procedure.md +143 -0
- package/dist/agent-src/skills/quality-tools/SKILL.md +34 -326
- package/dist/agent-src/skills/quality-tools/references/js-ts-tools.md +113 -0
- package/dist/agent-src/skills/quality-tools/references/php-tools.md +222 -0
- package/dist/agent-src/skills/roadmap-management/SKILL.md +15 -409
- package/dist/agent-src/skills/roadmap-management/references/archival.md +226 -0
- package/dist/agent-src/skills/roadmap-management/references/authoring.md +107 -0
- package/dist/agent-src/skills/roadmap-management/references/dashboard.md +47 -0
- package/dist/agent-src/skills/roadmap-management/references/lifecycle.md +58 -0
- package/dist/agent-src/skills/skill-writing/SKILL.md +34 -570
- package/dist/agent-src/skills/skill-writing/references/procedure.md +275 -0
- package/dist/agent-src/skills/skill-writing/references/read-only-scripts.md +18 -0
- package/dist/agent-src/skills/skill-writing/references/section-patterns.md +298 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +45 -2
- package/dist/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
- package/dist/agent-src/templates/scripts/telemetry/remote.ts +160 -7
- package/dist/agent-src/templates/scripts/telemetry/transport.ts +169 -0
- package/dist/cli/registry.js +1 -1
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/chunk-2GNWGMAL.js +172 -0
- package/dist/cli-delegate/{chunk-RLN73PR5.js → chunk-4VR4ORQ6.js} +10 -7
- package/dist/cli-delegate/chunk-6EOCNQAM.js +402 -0
- package/dist/cli-delegate/{chunk-XURJ4AQC.js → chunk-74E3TKWV.js} +381 -280
- package/dist/cli-delegate/{chunk-3UCJWMMP.js → chunk-7F3VYSM2.js} +4 -4
- package/dist/cli-delegate/{chunk-BKO2AZ7Z.js → chunk-B2ERU2VA.js} +1 -1
- package/dist/cli-delegate/{chunk-NXFWFAYV.js → chunk-G23AT6GD.js} +1 -0
- package/dist/cli-delegate/{chunk-63WSP6YZ.js → chunk-GJ5BVHXM.js} +72 -165
- package/dist/cli-delegate/{chunk-P6LQDIWK.js → chunk-JZ4NR2TW.js} +338 -0
- package/dist/cli-delegate/chunk-MWRD5YOH.js +100 -0
- package/dist/cli-delegate/{chunk-GRRPEPRN.js → chunk-T7L2QK3K.js} +24 -5
- package/dist/cli-delegate/{chunk-EVTY7ISR.js → chunk-YM4GZDJG.js} +9 -1
- package/dist/cli-delegate/cmd_conformance.js +459 -709
- package/dist/cli-delegate/cmd_doctor.js +6 -6
- package/dist/cli-delegate/cmd_explain.js +1 -1
- package/dist/cli-delegate/cmd_export.js +7 -6
- package/dist/cli-delegate/cmd_handoff.js +6 -6
- package/dist/cli-delegate/cmd_packs_active.js +1 -1
- package/dist/cli-delegate/cmd_preflight.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +7 -6
- package/dist/cli-delegate/cmd_session_recycle.js +840 -30
- package/dist/cli-delegate/cmd_settings_migrate.js +7 -6
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +7 -6
- package/dist/cli-delegate/cmd_uninstall.js +9 -7
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +7 -6
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +68 -25
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +10 -8
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +8 -6
- package/dist/hooks/dispatch.js +2901 -2053
- package/dist/install/claudePathsPlan.js +187 -0
- package/dist/install/claudePathsPlan.js.map +1 -0
- package/dist/install/hostLayerFingerprint.js +136 -0
- package/dist/install/hostLayerFingerprint.js.map +1 -0
- package/dist/install/install.mjs +553 -425
- package/dist/install/partitionEligibility.js +276 -0
- package/dist/install/partitionEligibility.js.map +1 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +1744 -1191
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_src.js +41 -0
- package/dist/scripts/_lib/agent_src.js.map +1 -1
- package/dist/scripts/_lib/install_layout.js +83 -0
- package/dist/scripts/_lib/install_layout.js.map +1 -0
- package/dist/scripts/_lib/installed_lock.js +444 -0
- package/dist/scripts/_lib/installed_lock.js.map +1 -0
- package/dist/scripts/condense.js +13 -164
- package/dist/scripts/condense.js.map +1 -1
- package/dist/server/schemas/settings.js +4 -0
- package/dist/server/schemas/settings.js.map +1 -1
- package/docs/contracts/settings-classes.md +12 -6
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +8 -3
- package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +26 -0
- package/docs/guidelines/agent-infra/memory-access.md +17 -0
- package/docs/guidelines/agent-infra/recurring-criticism-mechanics.md +96 -0
- package/package.json +1 -1
- package/src/agent-src/scripts/archival_sweep.ts +117 -0
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +19 -3
- package/src/agent-src/scripts/gate_budget.ts +282 -0
- package/src/agent-src/scripts/gate_execute.ts +72 -31
- package/src/agent-src/scripts/roadmap_gates.ts +74 -3
- package/src/agent-src/scripts/update_roadmap_progress.ts +251 -7
- package/src/agent-src/templates/scripts/telemetry/flush_sender.mjs +163 -0
- package/src/agent-src/templates/scripts/telemetry/remote.ts +160 -7
- package/src/agent-src/templates/scripts/telemetry/transport.ts +169 -0
- package/src/config/agent-settings.template.yml +27 -2
- package/src/config/ci-local-parity.yml +18 -0
- package/src/config/estate-count-budget.json +339 -3
- package/src/config/gate-violation-baselines.json +41 -41
- package/src/config/rule-activation-census.json +14 -6
- package/src/config/rule-stub-ceilings.json +19 -6
- package/src/install/claudePathsPlan.ts +196 -0
- package/src/install/hostLayerFingerprint.ts +145 -0
- package/src/install/partitionEligibility.ts +325 -0
- package/src/scripts/_cli/cmd_session_recycle.ts +9 -1
- package/src/scripts/_dispatch.bash +6 -1
- package/src/scripts/_lib/ac_heading.ts +30 -0
- package/src/scripts/_lib/agent_src.ts +42 -0
- package/src/scripts/_lib/bench_ab_pinned_repo.ts +162 -0
- package/src/scripts/_lib/bench_ab_workspace.ts +10 -1
- package/src/scripts/_lib/envelope_grounding.ts +38 -8
- package/src/scripts/_lib/installed_lock.ts +50 -5
- package/src/scripts/_lib/md_section.ts +129 -0
- package/src/scripts/_lib/scope_guard.sh +26 -3
- package/src/scripts/_lib/self_repair.ts +19 -0
- package/src/scripts/_lib/self_repair_class_b.ts +171 -0
- package/src/scripts/ai_council/cli_help.ts +55 -0
- package/src/scripts/before_complete_hook.ts +153 -28
- package/src/scripts/bench_ab_v2_complexity.ts +27 -7
- package/src/scripts/check_references.ts +60 -0
- package/src/scripts/check_release_published.ts +32 -11
- package/src/scripts/check_single_delivery.ts +59 -15
- package/src/scripts/ci_settle.ts +170 -0
- package/src/scripts/condense.ts +20 -170
- package/src/scripts/council_cli.ts +3 -3
- package/src/scripts/dispatch_r2_reviewer.ts +6 -14
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +77 -14
- package/src/scripts/hooks/block_no_verify.ts +61 -10
- package/src/scripts/hooks/block_unauthorized_git.ts +1 -1
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/rtk_wrap_hook.ts +8 -3
- package/src/scripts/hooks/run_continuation_hook.ts +8 -5
- package/src/scripts/hooks/ship_diff_volume_hook.ts +81 -15
- package/src/scripts/hooks/source_first_gate_hook.ts +464 -0
- package/src/scripts/hooks/state_io.ts +782 -20
- package/src/scripts/hooks/subagent_ledger_hook.ts +44 -15
- package/src/scripts/hooks/telemetry_flush_hook.ts +139 -0
- package/src/scripts/hooks/telemetry_self_repair.ts +128 -0
- package/src/scripts/hooks/telemetry_usage_hook.ts +22 -4
- package/src/scripts/hooks/turn_end_gate_hook.ts +81 -15
- package/src/scripts/install.ts +16 -11
- package/src/scripts/language_mirror_hook.ts +561 -25
- package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
- package/src/scripts/lint_plan_risk_register.ts +4 -3
- package/src/scripts/lint_skill_router_head.ts +4 -4
- package/src/scripts/mcp_server/tools.ts +2 -2
- package/src/scripts/new_roadmap.ts +136 -0
- package/src/scripts/pack_mcp_content.ts +20 -5
- package/src/scripts/release.ts +37 -38
- package/src/scripts/report_layer_overlap.ts +6 -0
- package/src/scripts/self_repair_hook.ts +37 -5
- package/src/scripts/session_register_hook.ts +11 -0
- package/src/scripts/skill_usage_collect.ts +11 -1
- package/src/scripts/skill_usage_report.ts +278 -8
- package/src/scripts/sync_pr_branch.ts +7 -0
- package/src/server/schemas/settings.ts +8 -0
- package/dist/cli-delegate/chunk-EJITJBJX.js +0 -172
- package/dist/cli-delegate/chunk-PZWRPRWP.js +0 -38
- /package/dist/cli-delegate/{chunk-RYNADUFG.js → chunk-LWTYTX6F.js} +0 -0
|
@@ -29,13 +29,36 @@
|
|
|
29
29
|
* correctness fact rather than a token fact, and it needs a different
|
|
30
30
|
* remedy, so it is never folded into the overlap number.
|
|
31
31
|
*
|
|
32
|
-
* REPORT BY DEFAULT, ENFORCE ON REQUEST — and that is not timidity.
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
32
|
+
* REPORT BY DEFAULT, ENFORCE ON REQUEST — and that is not timidity. A blocking
|
|
33
|
+
* default would red every run on a defect a given machine may not be able to
|
|
34
|
+
* fix, which teaches readers to ignore it — the exact failure this estate has
|
|
35
|
+
* recorded twice already.
|
|
36
|
+
*
|
|
37
|
+
* The reason used to be that `road-to-single-delivery` Phase 2 was HALTED on
|
|
38
|
+
* blocker `partition-current-layer-undecidable`. That is no longer true and the
|
|
39
|
+
* sentence is corrected rather than left standing: the blocker reads
|
|
40
|
+
* `**Status:** resolved`, its roadmap is archived, and the partition shipped
|
|
41
|
+
* under ADR-236 Phase 2. What replaced the halt is a per-machine activation —
|
|
42
|
+
* the partition withholds artefacts only where the host layer is verified
|
|
43
|
+
* against `installed.lock`, so a machine whose install predates the fingerprint
|
|
44
|
+
* keeps the full projection BY DESIGN and the overlap this gate reports there is
|
|
45
|
+
* the fail-safe working, not a regression.
|
|
46
|
+
*
|
|
47
|
+
* WHERE THIS CHECK IS MEANINGFUL — read this before quoting one of its numbers
|
|
48
|
+
* (road-to-session-closeout 3.3). It is a DEVELOPER-MACHINE check. It reads two
|
|
49
|
+
* host layers off the filesystem, and in CI both are absent: a fresh checkout
|
|
50
|
+
* carries no `<project>/.claude/**` (zero tracked files under `.claude/`) and no
|
|
51
|
+
* `~/.claude/**` because no CI leg installs at user scope. So in CI this gate
|
|
52
|
+
* finds zero layers and has nothing to compare — which is reported as `absent`
|
|
53
|
+
* rather than as a pass, per the paragraph below, but is still not a statement
|
|
54
|
+
* about any developer's machine.
|
|
55
|
+
*
|
|
56
|
+
* How large that gap is, measured on ONE tree in ONE session on 2026-08-20:
|
|
57
|
+
* `check_standing_rule_delivery` read the same repository twice and reported
|
|
58
|
+
* 115 781 tok with one layer installed and 209 767 tok with two — a 1.8x
|
|
59
|
+
* difference produced entirely by which layers existed where. Any figure from
|
|
60
|
+
* this gate or that one describes the machine it ran on. Cite it with the layer
|
|
61
|
+
* counts attached, never as a repository property.
|
|
39
62
|
*
|
|
40
63
|
* A LAYER THAT DOES NOT EXIST IS NOT A PASS. Where a layer is absent there is no
|
|
41
64
|
* overlap to find, and saying "invariant holds" would be a gate reading nothing
|
|
@@ -51,6 +74,17 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
51
74
|
|
|
52
75
|
import { reportScanned } from './_lib/scan_scope.js';
|
|
53
76
|
|
|
77
|
+
// ledger-exempt: this gate has no skip semantics to account for. `_lib/gate_ledger`
|
|
78
|
+
// exists so a gate that PASSES OVER targets says which and why; here the target set
|
|
79
|
+
// is four fixed artefact types, every one of them appears in the output, and a type
|
|
80
|
+
// whose layer is missing is printed as `absent` rather than quietly dropped — see
|
|
81
|
+
// the `absent` field and the `types_compared=N of 4` ledger line, which together
|
|
82
|
+
// already carry what a per-target ledger would. Adding one would restate the render
|
|
83
|
+
// in a second format, and the gate's own docstring warns that the degenerate pass
|
|
84
|
+
// here is a marker with no argument behind it. If a future version gains a real skip
|
|
85
|
+
// path — a type it declines to compare for a reason — this exemption is void and the
|
|
86
|
+
// ledger is the right answer.
|
|
87
|
+
|
|
54
88
|
const _HERE = fileURLToPath(import.meta.url);
|
|
55
89
|
const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
56
90
|
|
|
@@ -336,6 +370,7 @@ export function main(argv?: readonly string[]): number {
|
|
|
336
370
|
let globalRoot = path.join(os.homedir(), '.claude');
|
|
337
371
|
let projectRoot = path.join(REPO_ROOT, '.claude');
|
|
338
372
|
let enforce = false;
|
|
373
|
+
let quiet = false;
|
|
339
374
|
|
|
340
375
|
const value = (i: number): string | null => {
|
|
341
376
|
const v = args[i + 1];
|
|
@@ -354,13 +389,20 @@ export function main(argv?: readonly string[]): number {
|
|
|
354
389
|
i += 1;
|
|
355
390
|
} else if (a === '--enforce') {
|
|
356
391
|
enforce = true;
|
|
392
|
+
} else if (a === '--quiet') {
|
|
393
|
+
// R2 finding: the full per-type table is ~30 lines, and preflight is a
|
|
394
|
+
// `silent: true` task. A gate that floods a quiet chain is a gate people
|
|
395
|
+
// learn to scroll past, which is the same end state as an unbound one.
|
|
396
|
+
quiet = true;
|
|
357
397
|
} else if (a === '--help' || a === '-h') {
|
|
358
398
|
process.stdout.write(
|
|
359
|
-
'usage: check_single_delivery [--global DIR] [--project DIR] [--enforce]\n' +
|
|
399
|
+
'usage: check_single_delivery [--global DIR] [--project DIR] [--enforce] [--quiet]\n' +
|
|
360
400
|
'\n' +
|
|
361
|
-
'Reports by default and exits 0: the
|
|
362
|
-
'
|
|
363
|
-
'
|
|
401
|
+
'Reports by default and exits 0: the partition SHIPPED (ADR-236, Phase 2)\n' +
|
|
402
|
+
'but activates per machine — only where a verified host layer exists, so a\n' +
|
|
403
|
+
'checkout without one keeps the full projection BY DESIGN and reports overlap.\n' +
|
|
404
|
+
'--enforce exits 1 on any overlap; registering it would fail every machine\n' +
|
|
405
|
+
'that has not re-run `agent-config install`, which is why it stays opt-in.\n',
|
|
364
406
|
);
|
|
365
407
|
return 0;
|
|
366
408
|
} else if (a !== undefined) {
|
|
@@ -379,7 +421,7 @@ export function main(argv?: readonly string[]): number {
|
|
|
379
421
|
}
|
|
380
422
|
|
|
381
423
|
const v = evaluate(globalRoot, projectRoot);
|
|
382
|
-
process.stdout.write(`${render(v, globalRoot, projectRoot)}\n`);
|
|
424
|
+
if (!quiet) process.stdout.write(`${render(v, globalRoot, projectRoot)}\n`);
|
|
383
425
|
// Dead-scope assertion via the shared primitive rather than a hand-rolled
|
|
384
426
|
// `scanned:` line: `check_gate_coverage`'s hardening ratchet accepts an
|
|
385
427
|
// emitted line only from a gate registered in the enforced manifest, and this
|
|
@@ -451,9 +493,11 @@ export function main(argv?: readonly string[]): number {
|
|
|
451
493
|
}
|
|
452
494
|
process.stdout.write(
|
|
453
495
|
`⚠️ check_single_delivery: ${detail}. Reported, not enforced — ` +
|
|
454
|
-
'
|
|
455
|
-
'
|
|
456
|
-
'
|
|
496
|
+
'the partition shipped (ADR-236 Phase 2) but activates per machine: it withholds ' +
|
|
497
|
+
'artefacts only where the host layer is verified against `installed.lock`, so a ' +
|
|
498
|
+
'machine whose install predates the fingerprint keeps the full projection BY DESIGN ' +
|
|
499
|
+
'and this overlap is the fail-safe working. Run `agent-config install` to enable it ' +
|
|
500
|
+
'here; use --enforce where every machine is known to be verified.\n',
|
|
457
501
|
);
|
|
458
502
|
return 0;
|
|
459
503
|
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Wait for a PR's checks to settle, and never report a verdict it did not read.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS. Agents write this loop by hand, and the exit condition is
|
|
6
|
+
* the part that is easy to get wrong. Measured 2026-08-20:
|
|
7
|
+
*
|
|
8
|
+
* until ! gh pr checks 1459 | grep -q "pending"; do sleep 60; done
|
|
9
|
+
*
|
|
10
|
+
* The API then failed. Its error text — `error connecting to api.github.com` —
|
|
11
|
+
* contains no `pending`, so the loop exited, the run reported a settle that had
|
|
12
|
+
* not happened, and the claim had to be retracted to the user in the next
|
|
13
|
+
* reply. The bug is not the sleep or the grep: it is that **absence of the word
|
|
14
|
+
* `pending` was treated as evidence of completion**, and an error is neither
|
|
15
|
+
* settled nor pending.
|
|
16
|
+
*
|
|
17
|
+
* The rule this encodes, and the reason it belongs in one place rather than in
|
|
18
|
+
* every agent's head: `not settled` and `settled` are different from `could not
|
|
19
|
+
* read`, and only the first two may end a wait.
|
|
20
|
+
*
|
|
21
|
+
* Exit codes: 0 = settled, every check succeeded (or was skipped) ·
|
|
22
|
+
* 1 = settled, at least one check failed ·
|
|
23
|
+
* 2 = did not settle within the budget, or the API could not be
|
|
24
|
+
* read — NEVER a verdict, and the reason is printed.
|
|
25
|
+
*
|
|
26
|
+
* Usage: ./scripts-run src/scripts/ci_settle <pr> [--timeout-min N] [--interval-sec N]
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { spawnSync } from 'node:child_process';
|
|
30
|
+
|
|
31
|
+
/** What one poll told us. `unreadable` is deliberately not a verdict. */
|
|
32
|
+
export type PollState =
|
|
33
|
+
| { kind: 'unreadable'; reason: string }
|
|
34
|
+
| { kind: 'pending'; total: number; done: number }
|
|
35
|
+
| { kind: 'settled'; failing: string[]; total: number };
|
|
36
|
+
|
|
37
|
+
/** Markers that mean the API did not answer — not that nothing is pending. */
|
|
38
|
+
const _UNREADABLE_MARKERS = [
|
|
39
|
+
'error connecting to',
|
|
40
|
+
'could not resolve host',
|
|
41
|
+
'connection refused',
|
|
42
|
+
'timeout',
|
|
43
|
+
'timed out',
|
|
44
|
+
'bad gateway',
|
|
45
|
+
'service unavailable',
|
|
46
|
+
'rate limit',
|
|
47
|
+
'gh auth login',
|
|
48
|
+
'not logged into',
|
|
49
|
+
'HTTP 5',
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Classify one `gh pr view --json statusCheckRollup` result.
|
|
54
|
+
*
|
|
55
|
+
* Pure, so the failure this file exists to prevent is testable without a
|
|
56
|
+
* network: feed it an API error and it must answer `unreadable`, never
|
|
57
|
+
* `settled`.
|
|
58
|
+
*/
|
|
59
|
+
export function classifyPoll(stdout: string, stderr: string, status: number | null): PollState {
|
|
60
|
+
const combined = `${stdout}\n${stderr}`.toLowerCase();
|
|
61
|
+
for (const marker of _UNREADABLE_MARKERS) {
|
|
62
|
+
if (combined.includes(marker.toLowerCase())) {
|
|
63
|
+
return { kind: 'unreadable', reason: marker };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (status !== 0) {
|
|
67
|
+
return { kind: 'unreadable', reason: `gh exited ${String(status)}` };
|
|
68
|
+
}
|
|
69
|
+
let rows: { name?: string; conclusion?: string | null; status?: string | null }[];
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(stdout) as { statusCheckRollup?: unknown };
|
|
72
|
+
const roll = parsed.statusCheckRollup;
|
|
73
|
+
if (!Array.isArray(roll)) {
|
|
74
|
+
return { kind: 'unreadable', reason: 'no statusCheckRollup in response' };
|
|
75
|
+
}
|
|
76
|
+
rows = roll as typeof rows;
|
|
77
|
+
} catch {
|
|
78
|
+
return { kind: 'unreadable', reason: 'unparseable JSON' };
|
|
79
|
+
}
|
|
80
|
+
if (rows.length === 0) {
|
|
81
|
+
// Zero checks is not "settled green" — a run that has not registered
|
|
82
|
+
// its checks yet looks exactly like this.
|
|
83
|
+
return { kind: 'pending', total: 0, done: 0 };
|
|
84
|
+
}
|
|
85
|
+
const terminal = (r: (typeof rows)[number]): boolean =>
|
|
86
|
+
typeof r.conclusion === 'string' && r.conclusion.length > 0;
|
|
87
|
+
const done = rows.filter(terminal).length;
|
|
88
|
+
if (done < rows.length) {
|
|
89
|
+
return { kind: 'pending', total: rows.length, done };
|
|
90
|
+
}
|
|
91
|
+
const bad = new Set(['FAILURE', 'TIMED_OUT', 'CANCELLED', 'ACTION_REQUIRED', 'STARTUP_FAILURE']);
|
|
92
|
+
const failing = rows
|
|
93
|
+
.filter((r) => bad.has(String(r.conclusion).toUpperCase()))
|
|
94
|
+
.map((r) => String(r.name ?? '?'));
|
|
95
|
+
return { kind: 'settled', failing, total: rows.length };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function poll(pr: string): PollState {
|
|
99
|
+
const r = spawnSync('gh', ['pr', 'view', pr, '--json', 'statusCheckRollup'], {
|
|
100
|
+
encoding: 'utf-8',
|
|
101
|
+
timeout: 60_000,
|
|
102
|
+
});
|
|
103
|
+
return classifyPoll(r.stdout ?? '', r.stderr ?? '', r.status);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function sleepSync(seconds: number): void {
|
|
107
|
+
// Deliberately synchronous: this is a CLI whose whole job is to block.
|
|
108
|
+
spawnSync(process.execPath, ['-e', `setTimeout(()=>{}, ${String(seconds * 1000)})`], {
|
|
109
|
+
timeout: (seconds + 5) * 1000,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function main(argv: readonly string[]): number {
|
|
114
|
+
const positional = argv.filter((a) => !a.startsWith('--'));
|
|
115
|
+
const pr = positional[0];
|
|
116
|
+
if (pr === undefined) {
|
|
117
|
+
process.stderr.write('usage: ci_settle <pr> [--timeout-min N] [--interval-sec N]\n');
|
|
118
|
+
return 2;
|
|
119
|
+
}
|
|
120
|
+
const num = (flag: string, dflt: number): number => {
|
|
121
|
+
const i = argv.indexOf(flag);
|
|
122
|
+
if (i === -1) return dflt;
|
|
123
|
+
const v = argv[i + 1];
|
|
124
|
+
const n = v === undefined ? NaN : parseInt(v, 10);
|
|
125
|
+
return Number.isFinite(n) && n > 0 ? n : dflt;
|
|
126
|
+
};
|
|
127
|
+
const timeoutMin = num('--timeout-min', 45);
|
|
128
|
+
const intervalSec = num('--interval-sec', 60);
|
|
129
|
+
|
|
130
|
+
const deadline = Date.now() + timeoutMin * 60_000;
|
|
131
|
+
let unreadableStreak = 0;
|
|
132
|
+
|
|
133
|
+
for (;;) {
|
|
134
|
+
const state = poll(pr);
|
|
135
|
+
if (state.kind === 'settled') {
|
|
136
|
+
if (state.failing.length === 0) {
|
|
137
|
+
process.stdout.write(`ci_settle: SETTLED GREEN — ${String(state.total)} check(s)\n`);
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
process.stdout.write(
|
|
141
|
+
`ci_settle: SETTLED RED — ${String(state.failing.length)} of ${String(state.total)} failing:\n` +
|
|
142
|
+
state.failing.map((f) => ` ${f}\n`).join(''),
|
|
143
|
+
);
|
|
144
|
+
return 1;
|
|
145
|
+
}
|
|
146
|
+
if (state.kind === 'unreadable') {
|
|
147
|
+
unreadableStreak += 1;
|
|
148
|
+
process.stdout.write(
|
|
149
|
+
`ci_settle: could not read the API (${state.reason}) — NOT a verdict, still waiting ` +
|
|
150
|
+
`[${String(unreadableStreak)}]\n`,
|
|
151
|
+
);
|
|
152
|
+
} else {
|
|
153
|
+
unreadableStreak = 0;
|
|
154
|
+
process.stdout.write(
|
|
155
|
+
`ci_settle: pending — ${String(state.done)}/${String(state.total)} settled\n`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (Date.now() >= deadline) {
|
|
159
|
+
process.stdout.write(
|
|
160
|
+
`ci_settle: DID NOT SETTLE within ${String(timeoutMin)} min — no verdict is claimed.\n`,
|
|
161
|
+
);
|
|
162
|
+
return 2;
|
|
163
|
+
}
|
|
164
|
+
sleepSync(intervalSec);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (process.argv[1] !== undefined && process.argv[1].endsWith('ci_settle.ts')) {
|
|
169
|
+
process.exitCode = main(process.argv.slice(2));
|
|
170
|
+
}
|
package/src/scripts/condense.ts
CHANGED
|
@@ -251,7 +251,20 @@ function _stripChars(s: string, chars: string): string {
|
|
|
251
251
|
// module-level CLI self-invoke into its bundle. Re-exported here because four
|
|
252
252
|
// in-repo consumers import it from `condense.js`.
|
|
253
253
|
export { rule_in_scope } from '../install/ruleInScope.js';
|
|
254
|
+
// Re-exported for in-repo consumers; definitions moved beside emit_host_rules_cli.ts (Phase 5).
|
|
255
|
+
export {
|
|
256
|
+
CLAUDE_PATHS_PATTERN_BUDGET,
|
|
257
|
+
_claude_paths_plan,
|
|
258
|
+
_escape_claude_bracket,
|
|
259
|
+
_expanded_pattern_count,
|
|
260
|
+
_has_non_path_trigger,
|
|
261
|
+
_is_unresolved_placeholder,
|
|
262
|
+
derive_trigger_globs,
|
|
263
|
+
type ClaudePathsPlan,
|
|
264
|
+
} from '../install/claudePathsPlan.js';
|
|
254
265
|
import { rule_in_scope } from '../install/ruleInScope.js';
|
|
266
|
+
import { isExclusivelyPackageOnly, partitionActive, setPartitionAnnounce } from '../install/partitionEligibility.js'; // ADR-236
|
|
267
|
+
import { _claude_paths_plan, derive_trigger_globs } from '../install/claudePathsPlan.js';
|
|
255
268
|
|
|
256
269
|
const _HERE = path.dirname(fileURLToPath(import.meta.url)); // <repo>/src/scripts
|
|
257
270
|
const _DEFAULT_PROJECT_ROOT = path.resolve(_HERE, '..', '..');
|
|
@@ -304,6 +317,7 @@ function _deriveState(root: string): ModuleState {
|
|
|
304
317
|
}
|
|
305
318
|
|
|
306
319
|
export const MODULE_STATE: ModuleState = _deriveState(_DEFAULT_PROJECT_ROOT);
|
|
320
|
+
setPartitionAnnounce(success); // ADR-236: the mode line must be visible at the default level
|
|
307
321
|
|
|
308
322
|
/**
|
|
309
323
|
* Test seam — reassign one or more module-state fields, mirroring the pytest
|
|
@@ -1118,6 +1132,7 @@ function _scoped_rule_basenames(): string[] {
|
|
|
1118
1132
|
// is its fourth consumer, after the router compiler, the dist writer, and
|
|
1119
1133
|
// check_sync. Same predicate in all four; that is the point.
|
|
1120
1134
|
.filter((p) => !skip_compile_disabled_rule(`rules/${path.basename(p)}`))
|
|
1135
|
+
.filter((p) => !partitionActive(MODULE_STATE.PROJECT_ROOT) || isExclusivelyPackageOnly(p))
|
|
1121
1136
|
.map((p) => path.basename(p))
|
|
1122
1137
|
.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
1123
1138
|
}
|
|
@@ -1322,32 +1337,6 @@ function _yaml_scalar(value: string): string {
|
|
|
1322
1337
|
return JSON.stringify(value);
|
|
1323
1338
|
}
|
|
1324
1339
|
|
|
1325
|
-
/**
|
|
1326
|
-
* Derive host-native activation globs from a rule's `triggers:` frontmatter
|
|
1327
|
-
* (road-to-request-scoped-rule-load Phase 2). `file_pattern` triggers map
|
|
1328
|
-
* verbatim; `path_prefix` triggers map to `<prefix>**`. Keyword / phrase /
|
|
1329
|
-
* command triggers produce no glob — those rules stay
|
|
1330
|
-
* description-activated (Cursor Agent-Requested / Windsurf model_decision).
|
|
1331
|
-
*/
|
|
1332
|
-
export function derive_trigger_globs(meta: Record<string, unknown>): string[] {
|
|
1333
|
-
const triggers = meta['triggers'];
|
|
1334
|
-
if (!Array.isArray(triggers)) {
|
|
1335
|
-
return [];
|
|
1336
|
-
}
|
|
1337
|
-
const globs: string[] = [];
|
|
1338
|
-
for (const t of triggers) {
|
|
1339
|
-
if (t === null || typeof t !== 'object' || Array.isArray(t)) continue;
|
|
1340
|
-
const obj = t as Record<string, unknown>;
|
|
1341
|
-
if (typeof obj['file_pattern'] === 'string' && obj['file_pattern']) {
|
|
1342
|
-
globs.push(obj['file_pattern']);
|
|
1343
|
-
} else if (typeof obj['path_prefix'] === 'string' && obj['path_prefix']) {
|
|
1344
|
-
const prefix = obj['path_prefix'] as string;
|
|
1345
|
-
globs.push(prefix.endsWith('/') ? `${prefix}**` : `${prefix}**`);
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
// De-dup, keep declaration order (deterministic output).
|
|
1349
|
-
return [...new Set(globs)];
|
|
1350
|
-
}
|
|
1351
1340
|
|
|
1352
1341
|
export function _emit_cursor_mdc(source: string, target: string): void {
|
|
1353
1342
|
const [meta, body] = _parse_frontmatter(_readText(source));
|
|
@@ -1390,151 +1379,8 @@ export function _emit_windsurf_rule(source: string, target: string): void {
|
|
|
1390
1379
|
_writeText(target, lines.join('\n'));
|
|
1391
1380
|
}
|
|
1392
1381
|
|
|
1393
|
-
/**
|
|
1394
|
-
* A brace group the host expands, versus an agent-config placeholder that only
|
|
1395
|
-
* LOOKS like one.
|
|
1396
|
-
*
|
|
1397
|
-
* `path_prefix: "{module_root}/"` in `roadmap-ci-steps-policy` is resolved by
|
|
1398
|
-
* this suite from `modules.root_paths`. Emitted verbatim into a host `paths:`
|
|
1399
|
-
* list it is a brace group with no comma, which the host expands to the literal
|
|
1400
|
-
* string `{module_root}` — a pattern that matches no file. The rule would then
|
|
1401
|
-
* reach the model **never** instead of on-demand, and nothing would say so: the
|
|
1402
|
-
* probed contract records this exact degradation as silent
|
|
1403
|
-
* (`claude-code-rules-dir-contract.md` § constraints).
|
|
1404
|
-
*
|
|
1405
|
-
* Dropping the pattern is fail-safe in the one direction that matters. A rule
|
|
1406
|
-
* left with no pattern at all carries no `paths:` key and loads
|
|
1407
|
-
* unconditionally — the status quo, not a regression. A rule carrying a
|
|
1408
|
-
* no-op pattern is invisible.
|
|
1409
|
-
*
|
|
1410
|
-
* The discriminator is the comma, not a placeholder allowlist: `{ts,tsx}` is a
|
|
1411
|
-
* real alternation, `{module_root}` cannot be one.
|
|
1412
|
-
*/
|
|
1413
|
-
export function _is_unresolved_placeholder(pattern: string): boolean {
|
|
1414
|
-
for (const m of pattern.matchAll(/\{([^}]*)\}/g)) {
|
|
1415
|
-
if (!(m[1] as string).includes(',')) {
|
|
1416
|
-
return true;
|
|
1417
|
-
}
|
|
1418
|
-
}
|
|
1419
|
-
return false;
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
/**
|
|
1423
|
-
* `[` opens a bracket expression for the host. An unparseable one makes that
|
|
1424
|
-
* pattern match nothing while the rule's other patterns keep working — so a
|
|
1425
|
-
* literal `[` in a trigger is escaped rather than trusted.
|
|
1426
|
-
*/
|
|
1427
|
-
export function _escape_claude_bracket(pattern: string): string {
|
|
1428
|
-
return pattern.replace(/\[/g, '\\[');
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
/**
|
|
1432
|
-
* Expanded-pattern cost of one glob: the product of its brace alternations.
|
|
1433
|
-
* The host budgets a rule's whole `paths:` list at 1,000 expanded patterns and
|
|
1434
|
-
* 4 MiB, and a list over budget is used UNEXPANDED — braces then match
|
|
1435
|
-
* literally, i.e. nothing. Counting keeps the emitter under the cliff instead
|
|
1436
|
-
* of discovering it as a silent no-op.
|
|
1437
|
-
*/
|
|
1438
|
-
export function _expanded_pattern_count(pattern: string): number {
|
|
1439
|
-
let n = 1;
|
|
1440
|
-
for (const m of pattern.matchAll(/\{([^}]*)\}/g)) {
|
|
1441
|
-
n *= (m[1] as string).split(',').length;
|
|
1442
|
-
}
|
|
1443
|
-
return n;
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
/** Host-documented budget for one rule's whole `paths:` list. */
|
|
1447
|
-
export const CLAUDE_PATHS_PATTERN_BUDGET = 1000;
|
|
1448
|
-
|
|
1449
|
-
export interface ClaudePathsPlan {
|
|
1450
|
-
/** Patterns to emit under `paths:`, in declaration order. */
|
|
1451
|
-
globs: string[];
|
|
1452
|
-
/** Patterns deliberately not emitted, with the reason a reader can act on. */
|
|
1453
|
-
dropped: { pattern: string; reason: 'unresolved-placeholder' | 'over-budget' | 'mixed-triggers' }[];
|
|
1454
|
-
}
|
|
1455
1382
|
|
|
1456
|
-
/**
|
|
1457
|
-
* Does this rule declare any trigger that is NOT path-shaped?
|
|
1458
|
-
*
|
|
1459
|
-
* Load-bearing for `_claude_paths_plan` below, because one `triggers:` list
|
|
1460
|
-
* means two different things on two hosts. Cursor and Windsurf treat globs as
|
|
1461
|
-
* ADDITIVE — a rule with empty globs still reaches the agent through its
|
|
1462
|
-
* description (see `_emit_cursor_mdc`'s own comment). Claude Code treats
|
|
1463
|
-
* `paths:` as EXCLUSIVE: with it the rule loads on a path match; without it the
|
|
1464
|
-
* rule loads unconditionally. The emitter fed both semantics from one list, so
|
|
1465
|
-
* a rule that declared keywords *and* one path glob silently became
|
|
1466
|
-
* path-gated-only on Claude.
|
|
1467
|
-
*/
|
|
1468
|
-
export function _has_non_path_trigger(meta: Record<string, unknown>): boolean {
|
|
1469
|
-
const triggers = meta['triggers'];
|
|
1470
|
-
if (!Array.isArray(triggers)) return false;
|
|
1471
|
-
for (const t of triggers) {
|
|
1472
|
-
if (t === null || typeof t !== 'object' || Array.isArray(t)) continue;
|
|
1473
|
-
const obj = t as Record<string, unknown>;
|
|
1474
|
-
if (typeof obj['keyword'] === 'string' && obj['keyword']) return true;
|
|
1475
|
-
if (typeof obj['phrase'] === 'string' && obj['phrase']) return true;
|
|
1476
|
-
}
|
|
1477
|
-
return false;
|
|
1478
|
-
}
|
|
1479
1383
|
|
|
1480
|
-
/**
|
|
1481
|
-
* Derive the host's `paths:` list for one rule, with every drop recorded.
|
|
1482
|
-
*
|
|
1483
|
-
* Kernel / always-apply rules get an EMPTY list on purpose: absent `paths:` is
|
|
1484
|
-
* how this host says "load unconditionally", which is exactly what an Iron-Law
|
|
1485
|
-
* rule needs. Scoping one would be a correctness regression dressed as a byte
|
|
1486
|
-
* saving — and the same contract notes path-scoped rules are not re-injected
|
|
1487
|
-
* after `/compact`, so an obligation that must survive compaction cannot be
|
|
1488
|
-
* path-scoped at all.
|
|
1489
|
-
*/
|
|
1490
|
-
export function _claude_paths_plan(meta: Record<string, unknown>): ClaudePathsPlan {
|
|
1491
|
-
const always_apply = Boolean(meta['alwaysApply'] || meta['type'] === 'always');
|
|
1492
|
-
if (always_apply) {
|
|
1493
|
-
return { globs: [], dropped: [] };
|
|
1494
|
-
}
|
|
1495
|
-
const globs: string[] = [];
|
|
1496
|
-
const dropped: ClaudePathsPlan['dropped'] = [];
|
|
1497
|
-
|
|
1498
|
-
// A rule that ALSO declares keyword / phrase triggers gets no `paths:` at
|
|
1499
|
-
// all, and therefore keeps loading unconditionally on Claude Code.
|
|
1500
|
-
//
|
|
1501
|
-
// Emitting `paths:` here would narrow the rule to a path match and discard
|
|
1502
|
-
// every keyword the author wrote, because this host reads the list as the
|
|
1503
|
-
// whole gate rather than as one way in. Measured before this guard: 25 of
|
|
1504
|
-
// 110 projected rules carried `paths:` and 19 of them also carried keyword
|
|
1505
|
-
// triggers — `design-fidelity` lost 21 of them, in exactly the pasted-
|
|
1506
|
-
// screenshot and capability-URL handovers its own routing section names as
|
|
1507
|
-
// the classes it exists to catch. Nothing in the tree recorded a decision
|
|
1508
|
-
// to make that trade, which is why it is treated as a defect.
|
|
1509
|
-
//
|
|
1510
|
-
// The drop is REPORTED rather than silent, so a rule whose obligation
|
|
1511
|
-
// really is path-bound is visible as a candidate for removing its keyword
|
|
1512
|
-
// triggers — the narrowing then becomes an authoring decision instead of an
|
|
1513
|
-
// emitter side effect. Cursor and Windsurf are untouched: globs are
|
|
1514
|
-
// additive there, so the same rule keeps both routes.
|
|
1515
|
-
if (_has_non_path_trigger(meta)) {
|
|
1516
|
-
for (const raw of derive_trigger_globs(meta)) {
|
|
1517
|
-
dropped.push({ pattern: raw, reason: 'mixed-triggers' });
|
|
1518
|
-
}
|
|
1519
|
-
return { globs, dropped };
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
let spent = 0;
|
|
1523
|
-
for (const raw of derive_trigger_globs(meta)) {
|
|
1524
|
-
if (_is_unresolved_placeholder(raw)) {
|
|
1525
|
-
dropped.push({ pattern: raw, reason: 'unresolved-placeholder' });
|
|
1526
|
-
continue;
|
|
1527
|
-
}
|
|
1528
|
-
const cost = _expanded_pattern_count(raw);
|
|
1529
|
-
if (spent + cost > CLAUDE_PATHS_PATTERN_BUDGET) {
|
|
1530
|
-
dropped.push({ pattern: raw, reason: 'over-budget' });
|
|
1531
|
-
continue;
|
|
1532
|
-
}
|
|
1533
|
-
spent += cost;
|
|
1534
|
-
globs.push(_escape_claude_bracket(raw));
|
|
1535
|
-
}
|
|
1536
|
-
return { globs, dropped };
|
|
1537
|
-
}
|
|
1538
1384
|
|
|
1539
1385
|
/**
|
|
1540
1386
|
* Emit a Claude Code rule carrying the host's OWN activation key.
|
|
@@ -1823,6 +1669,7 @@ function _render_native_model_md(src_md: string, tier: string): string {
|
|
|
1823
1669
|
}
|
|
1824
1670
|
|
|
1825
1671
|
export function generate_claude_skills(active_skill_names: ReadonlySet<string> | null = null): number {
|
|
1672
|
+
if (partitionActive(MODULE_STATE.PROJECT_ROOT)) active_skill_names = new Set<string>();
|
|
1826
1673
|
if (!_exists(MODULE_STATE.SKILLS_SOURCE)) {
|
|
1827
1674
|
process.stderr.write(' ⚠️ dist/agent-src/skills/ not found — skipping skills\n');
|
|
1828
1675
|
return 0;
|
|
@@ -1836,7 +1683,9 @@ export function generate_claude_skills(active_skill_names: ReadonlySet<string> |
|
|
|
1836
1683
|
skills = skills.filter((s) => active_skill_names.has(s));
|
|
1837
1684
|
}
|
|
1838
1685
|
const skill_set = new Set(skills);
|
|
1839
|
-
const command_slugs =
|
|
1686
|
+
const command_slugs = partitionActive(MODULE_STATE.PROJECT_ROOT)
|
|
1687
|
+
? new Set<string>()
|
|
1688
|
+
: new Set([...iterCommands()].map(([, slug]) => slug));
|
|
1840
1689
|
|
|
1841
1690
|
_mkdirp(MODULE_STATE.CLAUDE_SKILLS_DIR);
|
|
1842
1691
|
const auto = _read_model_auto_switch() === 'auto';
|
|
@@ -1997,6 +1846,7 @@ export function generate_claude_project_commands(
|
|
|
1997
1846
|
}
|
|
1998
1847
|
|
|
1999
1848
|
export function generate_claude_commands(active_command_slugs: ReadonlySet<string> | null = null): number {
|
|
1849
|
+
if (partitionActive(MODULE_STATE.PROJECT_ROOT)) active_command_slugs = new Set<string>();
|
|
2000
1850
|
if (!_isDir(path.join(MODULE_STATE.PROJECT_ROOT, 'src', 'domains'))) {
|
|
2001
1851
|
process.stderr.write(' ⚠️ src/domains/ not found — skipping commands\n');
|
|
2002
1852
|
return 0;
|
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
plan_advisor_swap,
|
|
64
64
|
} from './ai_council/advisors.js';
|
|
65
65
|
import { format_install_hints } from './ai_council/cli_hints.js';
|
|
66
|
+
import { renderSubHelp } from './ai_council/cli_help.js';
|
|
66
67
|
import {
|
|
67
68
|
type AdvisorConfig,
|
|
68
69
|
type CouncilConfig,
|
|
@@ -2507,6 +2508,7 @@ function cmd_run(
|
|
|
2507
2508
|
const [extra_calls, extra_usd] = _consensus_cost_delta(ai_cfg, question.mode, estimates, billable.length);
|
|
2508
2509
|
const [ch_calls, ch_usd] = _chairman_cost_delta(ai_cfg, estimates);
|
|
2509
2510
|
const [pr_extra_calls, pr_extra_usd] = _peer_review_cost_delta(ai_cfg, args, estimates, billable.length);
|
|
2511
|
+
if (!args.confirm) _stdout('council:run · DRY PASS — estimate only, no seat is contacted. Add --confirm to run.\n');
|
|
2510
2512
|
_stdout(
|
|
2511
2513
|
`council:run · mode=${question.mode} · members=${members.length} ` +
|
|
2512
2514
|
`(billable=${billable.length})\n`,
|
|
@@ -3728,10 +3730,8 @@ function _parseArgs(argv: string[]): Args {
|
|
|
3728
3730
|
const rest = argv.slice(1);
|
|
3729
3731
|
const usage = _usageFor(first);
|
|
3730
3732
|
const { positionals, opts, requiredOpts } = _specsFor(first);
|
|
3731
|
-
|
|
3732
|
-
// Sub-help.
|
|
3733
3733
|
if (rest.includes('-h') || rest.includes('--help')) {
|
|
3734
|
-
_stdout(usage);
|
|
3734
|
+
_stdout(renderSubHelp(first, usage, { positionals, opts, requiredOpts }));
|
|
3735
3735
|
process.exitCode = 0;
|
|
3736
3736
|
throw new _ArgExit();
|
|
3737
3737
|
}
|
|
@@ -51,6 +51,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
51
51
|
// (validator → dispatcher for the scope hash, dispatcher → validator for
|
|
52
52
|
// relevance) is safe: neither module calls the other at module-evaluation
|
|
53
53
|
// time, and each CLI entry guard only fires for its own argv[1].
|
|
54
|
+
import { isAcceptanceCriteriaHeading } from './_lib/ac_heading.js';
|
|
54
55
|
import { gitEnv } from './_lib/git_env.js';
|
|
55
56
|
import { completionReviewDisabled } from './_lib/planning_settings.js';
|
|
56
57
|
import { artifactRelevance } from './check_completion_review.js';
|
|
@@ -492,20 +493,11 @@ export function extractAcceptanceCriteria(roadmapText: string): string {
|
|
|
492
493
|
const lines = roadmapText.split('\n');
|
|
493
494
|
let start = -1;
|
|
494
495
|
for (let i = 0; i < lines.length; i++) {
|
|
495
|
-
// Case-insensitive
|
|
496
|
-
//
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
|
|
500
|
-
//
|
|
501
|
-
// The end-anchor was the same defect wearing a different mask, and it was
|
|
502
|
-
// LIVE: `\s*$` rejected a heading carrying a qualifier, so
|
|
503
|
-
// `## Acceptance criteria (per phase, on promotion to ready)` and
|
|
504
|
-
// `## Acceptance criteria (anti-dump — the review-s own rule)` extracted
|
|
505
|
-
// nothing. Two roadmaps, measured 2026-08-18 — and they had been counted
|
|
506
|
-
// as "declares no criteria" by the very census that motivated this fix,
|
|
507
|
-
// which is how a matcher tuned to its own measurement hides its misses.
|
|
508
|
-
if (/^##\s+acceptance criteria\b/i.test(lines[i] as string)) {
|
|
496
|
+
// Case-insensitive and NOT end-anchored — both of those were live misses
|
|
497
|
+
// here (case: zcs-close R2 review 2026-08-09; the `(per phase, …)` qualifier:
|
|
498
|
+
// measured 2026-08-18). The predicate now lives in `_lib/ac_heading`, which
|
|
499
|
+
// records all three instances of the defect and why it is shared.
|
|
500
|
+
if (isAcceptanceCriteriaHeading(lines[i] as string)) {
|
|
509
501
|
start = i;
|
|
510
502
|
break;
|
|
511
503
|
}
|