@event4u/agent-config 16.0.0 → 16.1.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 +297 -137
- package/CONTRIBUTING.md +9 -0
- package/README.md +2 -2
- package/dist/agent-src/commands/analyze/inbox.md +26 -0
- package/dist/agent-src/commands/analyze/roadmap-repos.md +27 -0
- package/dist/agent-src/commands/challenge-me/closure.md +178 -0
- package/dist/agent-src/commands/challenge-me.md +9 -2
- package/dist/agent-src/commands/council/default.md +25 -5
- package/dist/agent-src/commands/feature/plan.md +28 -1
- package/dist/agent-src/commands/feature/roadmap.md +28 -1
- package/dist/agent-src/commands/implement-ticket.md +27 -0
- package/dist/agent-src/commands/jira-ticket.md +27 -0
- package/dist/agent-src/commands/pr/create.md +29 -0
- package/dist/agent-src/commands/pr/merge.md +60 -9
- package/dist/agent-src/commands/roadmap/ai-council.md +7 -1
- package/dist/agent-src/commands/roadmap/create.md +74 -27
- package/dist/agent-src/commands/roadmap/materialize.md +27 -0
- package/dist/agent-src/commands/roadmap/process-full.md +123 -18
- package/dist/agent-src/commands/team.md +22 -0
- package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
- package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +47 -0
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +15 -15
- package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
- package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
- package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
- package/dist/agent-src/rules/autonomous-execution.md +15 -8
- package/dist/agent-src/rules/delegation-policy.md +23 -45
- package/dist/agent-src/rules/design-fidelity.md +32 -37
- package/dist/agent-src/rules/evaluator-independence.md +43 -49
- package/dist/agent-src/rules/test-first.md +41 -0
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
- package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
- package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
- package/dist/agent-src/skills/database/data/manifest.json +4 -4
- package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
- package/dist/agent-src/skills/design-review/SKILL.md +50 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +14 -4
- package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
- package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
- package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
- package/dist/agent-src/templates/agent-settings.md +1 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +188 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- package/dist/cli-delegate/check_update_banner.js +40 -2
- package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
- package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
- package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
- package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
- package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
- package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-FXYUYU4N.js} +57 -13
- package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-GJCBB2UG.js} +168 -10
- package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-HDVVGPQW.js} +873 -539
- package/dist/cli-delegate/{chunk-H32DGUJH.js → chunk-P6ZHP5W2.js} +1 -1
- package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
- package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
- package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-UNCRJBT3.js} +31 -7
- package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
- package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +90 -56
- package/dist/cli-delegate/cmd_doctor.js +7 -5
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +9 -7
- package/dist/cli-delegate/cmd_fleet.js +4 -2
- package/dist/cli-delegate/cmd_handoff.js +4 -4
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_preflight.js +4 -2
- package/dist/cli-delegate/cmd_prune.js +10 -8
- package/dist/cli-delegate/cmd_route_audit.js +2 -2
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_get.js +28 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +10 -8
- package/dist/cli-delegate/cmd_uninstall.js +16 -13
- 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 +12 -10
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +85 -38
- 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 +1832 -1007
- package/dist/install/claudeRuleRewrite.js +14 -3
- package/dist/install/conflict.js +60 -10
- package/dist/install/conflictTracker.js +218 -0
- package/dist/install/fsPrimitives.js +54 -0
- package/dist/install/install.mjs +2007 -1481
- package/dist/install/manifestFiles.js +259 -0
- package/dist/install/preflight.js +14 -2
- package/dist/install/preserve.js +157 -0
- package/dist/install/recordedOwnership.js +132 -0
- package/dist/install/txlog.js +11 -2
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +398 -39
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +3 -2
- package/dist/scripts/_lib/fs_atomic.js +167 -0
- package/dist/scripts/_lib/installed_tools.js +460 -0
- package/dist/scripts/_lib/lean_projection_mode.js +3 -2
- package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
- package/dist/server/routes/install.js +8 -0
- package/dist/server/routes/wizard.js +9 -3
- package/dist/server/schemas/settings.js +17 -5
- package/dist/shared/missionExecution.js +97 -0
- package/dist/ui/assets/{index-D0jaCRPM.js → index-DH1xc62v.js} +17 -17
- package/dist/ui/index.html +1 -1
- package/docs/MIGRATION.md +103 -0
- package/docs/contracts/settings-classes.md +26 -7
- package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/docs/guidelines/design-fidelity-routing.md +29 -0
- package/package.json +1 -1
- package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- package/src/config/agent-settings.template.yml +139 -27
- package/src/config/auto-rule-budget.json +16 -0
- package/src/config/ci-local-parity.yml +18 -5
- package/src/config/continuity-surface.json +21 -0
- package/src/config/gate-coverage.yml +146 -13
- package/src/config/gate-violation-baselines.json +6 -6
- package/src/config/hook-token-budget.json +2 -0
- package/src/config/loop-surfaces.yaml +215 -0
- package/src/config/platform-anchor.json +3 -2
- package/src/config/release-gate-locality.yml +11 -0
- package/src/config/routing-coverage-seed.json +18 -4
- package/src/config/rule-enforcement-baseline.json +0 -1
- package/src/install/claudeRuleRewrite.ts +16 -2
- package/src/install/conflict.ts +65 -9
- package/src/install/conflictTracker.ts +299 -0
- package/src/install/fsPrimitives.ts +54 -0
- package/src/install/manifestFiles.ts +312 -0
- package/src/install/preflight.ts +16 -2
- package/src/install/preserve.ts +205 -0
- package/src/install/recordedOwnership.ts +149 -0
- package/src/install/txlog.ts +11 -2
- package/src/install/types.ts +19 -4
- package/src/scripts/_cli/cmd_conformance.ts +88 -15
- package/src/scripts/_cli/cmd_doctor.ts +4 -3
- package/src/scripts/_cli/cmd_route_explain.ts +9 -2
- package/src/scripts/_cli/cmd_settings_get.ts +44 -0
- package/src/scripts/_cli/doctor_execution.ts +90 -0
- package/src/scripts/_lib/activation_payload.ts +247 -0
- package/src/scripts/_lib/agent_settings.ts +4 -2
- package/src/scripts/_lib/authority_path.ts +102 -0
- package/src/scripts/_lib/cascade_base.ts +94 -0
- package/src/scripts/_lib/changelog_eras.ts +7 -2
- package/src/scripts/_lib/continuation_ladder.ts +149 -2
- package/src/scripts/_lib/council_transport.ts +129 -0
- package/src/scripts/_lib/delivery_ready.ts +112 -0
- package/src/scripts/_lib/forge_protection.ts +267 -0
- package/src/scripts/_lib/hook_platform_keys.ts +27 -0
- package/src/scripts/_lib/json_python_parity.ts +127 -0
- package/src/scripts/_lib/lean_projection_mode.ts +14 -3
- package/src/scripts/_lib/loop_surfaces.ts +382 -0
- package/src/scripts/_lib/mission_record.ts +190 -0
- package/src/scripts/_lib/obligation_frequency.ts +217 -0
- package/src/scripts/_lib/obligations.ts +402 -0
- package/src/scripts/_lib/orchestration_record.ts +11 -20
- package/src/scripts/_lib/release_highlights.ts +199 -10
- package/src/scripts/_lib/release_holds.ts +493 -0
- package/src/scripts/_lib/release_migration_gate.ts +184 -0
- package/src/scripts/_lib/rides_along.ts +102 -0
- package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
- package/src/scripts/_lib/test_delta.ts +166 -0
- package/src/scripts/_lib/test_provenance.ts +171 -0
- package/src/scripts/_lib/typed_op_grant.ts +246 -0
- package/src/scripts/_lib/typed_op_watch.ts +185 -0
- package/src/scripts/ai_council/config.ts +8 -11
- package/src/scripts/ai_council/fallback_config.ts +5 -3
- package/src/scripts/ai_council/ownership_classes.ts +118 -0
- package/src/scripts/ask_block_census.ts +285 -15
- package/src/scripts/check_enforcement_matrix.ts +624 -0
- package/src/scripts/check_gate_reachability.ts +173 -10
- package/src/scripts/check_generator_sync.ts +954 -0
- package/src/scripts/check_preamble_payload_budget.ts +75 -2
- package/src/scripts/check_release_highlights.ts +33 -1
- package/src/scripts/check_release_holds.ts +567 -0
- package/src/scripts/check_rule_projection_integrity.ts +22 -12
- package/src/scripts/check_test_delta.ts +294 -0
- package/src/scripts/check_test_weakening.ts +299 -0
- package/src/scripts/closure_scan.ts +333 -0
- package/src/scripts/compile_hook_manifest.ts +43 -3
- package/src/scripts/council_record_shape.ts +209 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +88 -9
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/design_pass_hook.ts +128 -3
- package/src/scripts/hooks/evidence_independence.ts +96 -0
- package/src/scripts/hooks/memory_recall_hook.ts +540 -0
- package/src/scripts/hooks/obligation_settle_hook.ts +182 -0
- package/src/scripts/hooks/rule_inject_hook.ts +49 -1
- package/src/scripts/hooks/run_continuation_hook.ts +17 -20
- package/src/scripts/hooks_doctor.ts +85 -0
- package/src/scripts/hooks_status.ts +19 -0
- package/src/scripts/install.ts +106 -206
- package/src/scripts/janitor.ts +7 -0
- package/src/scripts/lint_decision_classes.ts +464 -0
- package/src/scripts/lint_hook_manifest.ts +11 -0
- package/src/scripts/lint_major_migration_sections.ts +450 -0
- package/src/scripts/lint_plan_risk_register.ts +47 -3
- package/src/scripts/lint_roadmap_blockers.ts +61 -0
- package/src/scripts/lint_roadmap_producers.ts +202 -0
- package/src/scripts/measure_rule_budget.ts +101 -5
- package/src/scripts/new_roadmap.ts +37 -0
- package/src/scripts/orchestration_record.ts +0 -5
- package/src/scripts/release.ts +14 -98
- package/src/scripts/release_drill.ts +267 -1
- package/src/scripts/release_publication.ts +51 -0
- package/src/scripts/report_enforcement_drift.ts +566 -0
- package/src/scripts/report_host_injection_effect.ts +3 -11
- package/src/scripts/report_standing_payload_by_host.ts +26 -152
- package/src/scripts/schemas/agent-settings.schema.json +23 -0
- package/src/scripts/schemas/command.schema.json +4 -0
- package/src/scripts/scope_growth.ts +219 -0
- package/src/scripts/trigger_eval_grandfather.json +0 -1
- package/src/scripts/ui_conformance_probe.ts +622 -0
- package/src/server/schemas/settings.ts +31 -7
- package/src/shared/missionExecution.ts +126 -0
- package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
#!/usr/bin/env -S npx tsx
|
|
2
|
+
/**
|
|
3
|
+
* check_release_holds — the one evaluator every release boundary reads.
|
|
4
|
+
*
|
|
5
|
+
* Template rule 28. Four modes, and the split matters because two of them run
|
|
6
|
+
* on completely different paths:
|
|
7
|
+
*
|
|
8
|
+
* --lint a malformed declaration reddens NORMAL CI, so a broken hold
|
|
9
|
+
* cannot fail open by being unparseable.
|
|
10
|
+
* --status list every declared hold and its state. Read-only.
|
|
11
|
+
* --require-safe the RELEASE-path question: may a cut be published now?
|
|
12
|
+
* --selftest every row of rule 28's state table, including the
|
|
13
|
+
* not-evaluable row, asserted against fixtures.
|
|
14
|
+
*
|
|
15
|
+
* Exit codes: 0 = clean / safe · 1 = a violation or a refusal · 2 = usage.
|
|
16
|
+
*
|
|
17
|
+
* The load-bearing behaviour, and the one worth re-reading before changing
|
|
18
|
+
* anything here: `not-evaluable` REFUSES, on every channel. An evaluator that
|
|
19
|
+
* cannot read a declaration has learned nothing about whether the tree is safe,
|
|
20
|
+
* and "learned nothing" is not "safe". There is deliberately no flag, label or
|
|
21
|
+
* trailer anywhere in this file that turns a refusal into a pass.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import * as fs from 'node:fs';
|
|
25
|
+
import * as os from 'node:os';
|
|
26
|
+
import * as path from 'node:path';
|
|
27
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
evaluateFile,
|
|
31
|
+
evaluateHolds,
|
|
32
|
+
lifecycleViolations,
|
|
33
|
+
refuses,
|
|
34
|
+
refusalReport,
|
|
35
|
+
REFUSING_DIRS,
|
|
36
|
+
type CutChannel,
|
|
37
|
+
type Hold,
|
|
38
|
+
} from './_lib/release_holds.js';
|
|
39
|
+
import { REPO_ROOT } from './lint_roadmap_blockers.js';
|
|
40
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
41
|
+
import { runGateCli, runSelfTest, type SelfTestCase } from './_lib/gate_self_test.js';
|
|
42
|
+
|
|
43
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The folders a release question must read.
|
|
47
|
+
*
|
|
48
|
+
* Wider than `lint_roadmap_blockers`' active-only glob on purpose: rule 28's
|
|
49
|
+
* per-folder lifecycle says a window does not disappear by moving the file, and
|
|
50
|
+
* a `later/` roadmap still refuses. `archive/` and `skipped/` are included
|
|
51
|
+
* because the move INTO them is refused while a window is open — a file that
|
|
52
|
+
* reached them with an open window is a defect this must still see, not one it
|
|
53
|
+
* should be blind to.
|
|
54
|
+
*/
|
|
55
|
+
const HOLD_DIRS = ['', 'later', 'archive', 'skipped', 'stubs'] as const;
|
|
56
|
+
|
|
57
|
+
function globRoadmaps(root = REPO_ROOT): string[] {
|
|
58
|
+
const out: string[] = [];
|
|
59
|
+
for (const sub of HOLD_DIRS) {
|
|
60
|
+
const dir = path.join(root, 'agents', 'roadmaps', sub);
|
|
61
|
+
let entries: fs.Dirent[];
|
|
62
|
+
try {
|
|
63
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
64
|
+
} catch {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
for (const e of entries) {
|
|
68
|
+
if (e.isFile() && e.name.endsWith('.md')) {
|
|
69
|
+
out.push(path.join(dir, e.name));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return out.sort();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function collect(root = REPO_ROOT): Hold[] {
|
|
77
|
+
return globRoadmaps(root).flatMap((f) => evaluateFile(f));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function rel(p: string): string {
|
|
81
|
+
return path.relative(REPO_ROOT, p).split(path.sep).join('/');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function cmdLint(root: string): number {
|
|
85
|
+
// A broken glob is this gate's one silent failure mode: with zero files it
|
|
86
|
+
// would print "0 declared hold(s), all well-formed" and exit 0, certifying
|
|
87
|
+
// a coverage that does not exist. "Zero holds" is a real and normal state;
|
|
88
|
+
// "zero FILES under agents/roadmaps" never is.
|
|
89
|
+
try {
|
|
90
|
+
assertScanned({
|
|
91
|
+
gate: 'check_release_holds',
|
|
92
|
+
scanned: globRoadmaps(root).length,
|
|
93
|
+
units: 'roadmap file(s)',
|
|
94
|
+
roots: ['agents/roadmaps'],
|
|
95
|
+
});
|
|
96
|
+
} catch (e) {
|
|
97
|
+
if (e instanceof DeadScopeError) {
|
|
98
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
throw e;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const files = globRoadmaps(root);
|
|
105
|
+
const holds = collect(root);
|
|
106
|
+
const bad = holds.filter((h) => h.state === 'not-evaluable');
|
|
107
|
+
|
|
108
|
+
// The lifecycle half. A move already made is what a gate can see: if a file
|
|
109
|
+
// with a live window is sitting in `archive/` or `skipped/`, the refused
|
|
110
|
+
// move happened, and reddening CI is how the move is refused in practice.
|
|
111
|
+
const lifecycle = files.flatMap((f) => {
|
|
112
|
+
const hs = evaluateFile(f);
|
|
113
|
+
if (hs.length === 0) {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
let text: string;
|
|
117
|
+
try {
|
|
118
|
+
text = fs.readFileSync(f, 'utf8');
|
|
119
|
+
} catch {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
return lifecycleViolations(f, hs, text);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
process.stdout.write(`scanned: ${files.length}\n`);
|
|
126
|
+
if (bad.length === 0 && lifecycle.length === 0) {
|
|
127
|
+
process.stdout.write(
|
|
128
|
+
`✅ check-release-holds: ${holds.length} declared hold(s), all well-formed, ` +
|
|
129
|
+
`lifecycle clean\n`,
|
|
130
|
+
);
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
if (bad.length > 0) {
|
|
134
|
+
process.stderr.write('❌ check-release-holds: malformed declaration(s):\n');
|
|
135
|
+
for (const h of bad) {
|
|
136
|
+
for (const why of h.malformed) {
|
|
137
|
+
process.stderr.write(` - ${rel(h.file)} · hold \`${h.id}\`: ${why}\n`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (lifecycle.length > 0) {
|
|
142
|
+
process.stderr.write('❌ check-release-holds: lifecycle violation(s):\n');
|
|
143
|
+
for (const v of lifecycle) {
|
|
144
|
+
process.stderr.write(` - ${rel(v.file)} · hold \`${v.holdId}\`: ${v.reason}\n`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Answer the move BEFORE it happens, so the loop asks rather than discovers.
|
|
152
|
+
*
|
|
153
|
+
* `--can-move <file> <archive|skipped|later>`; exit 0 permits, 1 refuses. The
|
|
154
|
+
* `--lint` gate above is the backstop that catches a move made without asking.
|
|
155
|
+
*/
|
|
156
|
+
function cmdCanMove(file: string, dest: string): number {
|
|
157
|
+
if (!(REFUSING_DIRS as readonly string[]).includes(dest) && dest !== 'later') {
|
|
158
|
+
process.stderr.write(`❌ --can-move destination must be archive, skipped or later\n`);
|
|
159
|
+
return 2;
|
|
160
|
+
}
|
|
161
|
+
const holds = evaluateFile(file);
|
|
162
|
+
const live = holds.filter((h) => h.state === 'open' || h.state === 'not-evaluable');
|
|
163
|
+
if (live.length === 0) {
|
|
164
|
+
process.stdout.write(`✅ no live window — the move to \`${dest}/\` is permitted\n`);
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
167
|
+
if (dest === 'later') {
|
|
168
|
+
let text = '';
|
|
169
|
+
try {
|
|
170
|
+
text = fs.readFileSync(file, 'utf8');
|
|
171
|
+
} catch {
|
|
172
|
+
/* an unreadable file is already not-evaluable above */
|
|
173
|
+
}
|
|
174
|
+
const problems = lifecycleViolations(
|
|
175
|
+
path.join(REPO_ROOT, 'agents', 'roadmaps', 'later', path.basename(file)),
|
|
176
|
+
holds,
|
|
177
|
+
text,
|
|
178
|
+
);
|
|
179
|
+
if (problems.length === 0) {
|
|
180
|
+
process.stdout.write(
|
|
181
|
+
`✅ the move to \`later/\` is permitted — the window stays listed and still refuses\n`,
|
|
182
|
+
);
|
|
183
|
+
return 0;
|
|
184
|
+
}
|
|
185
|
+
for (const v of problems) {
|
|
186
|
+
process.stderr.write(`❌ hold \`${v.holdId}\`: ${v.reason}\n`);
|
|
187
|
+
}
|
|
188
|
+
return 1;
|
|
189
|
+
}
|
|
190
|
+
for (const h of live) {
|
|
191
|
+
process.stderr.write(
|
|
192
|
+
`❌ REFUSED: hold \`${h.id}\` is ${h.state} — a roadmap with a live window may not ` +
|
|
193
|
+
`move to \`${dest}/\`. Finish \`${h.clearedBy || '?'}\`, or move it to \`later/\`.\n`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
return 1;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function cmdStatus(root: string): number {
|
|
200
|
+
const holds = collect(root);
|
|
201
|
+
if (holds.length === 0) {
|
|
202
|
+
process.stdout.write('No release holds declared. Every cut is permitted.\n');
|
|
203
|
+
return 0;
|
|
204
|
+
}
|
|
205
|
+
for (const h of holds) {
|
|
206
|
+
process.stdout.write(
|
|
207
|
+
`${h.state.padEnd(14)} ${h.id.padEnd(28)} channel=${h.channel.padEnd(6)} ` +
|
|
208
|
+
`opens=${h.openedBy || '-'} clears=${h.clearedBy || '-'} ${rel(h.file)}\n`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
return 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function cmdRequireSafe(root: string, cut: CutChannel): number {
|
|
215
|
+
const report = refusalReport(collect(root), cut);
|
|
216
|
+
if (report === null) {
|
|
217
|
+
process.stdout.write(`✅ release-holds: safe to cut (channel ${cut})\n`);
|
|
218
|
+
return 0;
|
|
219
|
+
}
|
|
220
|
+
process.stderr.write(`❌ ${report}\n`);
|
|
221
|
+
return 1;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Rule 28's state table, one fixture per row, plus the two channel cases. */
|
|
225
|
+
function cmdSelftest(): number {
|
|
226
|
+
// Built line-by-line rather than as one template literal on purpose: a
|
|
227
|
+
// heredoc whose lines begin with `##` reads to `lint_code_comments` as a
|
|
228
|
+
// markdown heading left in source, and the fixture would redden a gate it
|
|
229
|
+
// has nothing to do with.
|
|
230
|
+
const base = (open: string, clear: string, channel = 'all', extra = '') =>
|
|
231
|
+
[
|
|
232
|
+
'',
|
|
233
|
+
'## Phase 1',
|
|
234
|
+
`- [${open}] **1.1 opener** <!-- opens-hold: probe -->`,
|
|
235
|
+
" verify: the opener's own check",
|
|
236
|
+
`- [${clear}] **1.2 clearer** <!-- clears-hold: probe -->`,
|
|
237
|
+
' verify: the check that proves the state is repaired',
|
|
238
|
+
'',
|
|
239
|
+
'## Release holds',
|
|
240
|
+
'',
|
|
241
|
+
'### hold: probe',
|
|
242
|
+
`- **Channel:** ${channel}`,
|
|
243
|
+
'- **Opened by:** 1.1',
|
|
244
|
+
'- **Cleared by:** 1.2',
|
|
245
|
+
'- **State:** the surface is half wired while this is open.',
|
|
246
|
+
'- **Why not a guard:** the entry point is reachable and cannot be made inert.',
|
|
247
|
+
extra,
|
|
248
|
+
].join('\n');
|
|
249
|
+
|
|
250
|
+
type Case = { name: string; text: string; state: string; all: boolean; latest: boolean };
|
|
251
|
+
const cases: Case[] = [
|
|
252
|
+
{ name: 'unopened permits', text: base(' ', ' '), state: 'unopened', all: false, latest: false },
|
|
253
|
+
{ name: 'open/all refuses every cut', text: base('x', ' '), state: 'open', all: true, latest: true },
|
|
254
|
+
{ name: 'cleared permits', text: base('x', 'x'), state: 'cleared', all: false, latest: false },
|
|
255
|
+
{
|
|
256
|
+
name: 'open/latest refuses stable, permits prerelease',
|
|
257
|
+
text: base('x', ' ', 'latest'),
|
|
258
|
+
state: 'open',
|
|
259
|
+
all: true,
|
|
260
|
+
latest: false,
|
|
261
|
+
},
|
|
262
|
+
// The load-bearing row. Each of these is a DIFFERENT way to be
|
|
263
|
+
// unreadable, and every one of them must refuse on BOTH channels.
|
|
264
|
+
{
|
|
265
|
+
name: 'not-evaluable: missing `Why not a guard:`',
|
|
266
|
+
text: base('x', ' ').replace(/- \*\*Why not a guard:\*\*.*\n/, ''),
|
|
267
|
+
state: 'not-evaluable',
|
|
268
|
+
all: true,
|
|
269
|
+
latest: true,
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: 'not-evaluable: unknown channel',
|
|
273
|
+
text: base('x', ' ', 'someday'),
|
|
274
|
+
state: 'not-evaluable',
|
|
275
|
+
all: true,
|
|
276
|
+
latest: true,
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: 'not-evaluable: clearer carries no `verify:`',
|
|
280
|
+
text: base('x', ' ').replace(
|
|
281
|
+
' verify: the check that proves the state is repaired\n',
|
|
282
|
+
'',
|
|
283
|
+
),
|
|
284
|
+
state: 'not-evaluable',
|
|
285
|
+
all: true,
|
|
286
|
+
latest: true,
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: 'not-evaluable: marker is not on a checkbox',
|
|
290
|
+
text: base('x', ' ').replace(
|
|
291
|
+
'- [ ] **1.2 clearer** <!-- clears-hold: probe -->',
|
|
292
|
+
'The clearer is step 1.2 <!-- clears-hold: probe -->',
|
|
293
|
+
),
|
|
294
|
+
state: 'not-evaluable',
|
|
295
|
+
all: true,
|
|
296
|
+
latest: true,
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: 'not-evaluable: hold entry names a version (rule 13)',
|
|
300
|
+
text: base('x', ' ', 'all', '- **Note:** ships in 15.1.0\n'),
|
|
301
|
+
state: 'not-evaluable',
|
|
302
|
+
all: true,
|
|
303
|
+
latest: true,
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: 'not-evaluable: duplicate hold id',
|
|
307
|
+
text: `${base('x', ' ')}\n### hold: probe\n- **Channel:** all\n`,
|
|
308
|
+
state: 'not-evaluable',
|
|
309
|
+
all: true,
|
|
310
|
+
latest: true,
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: 'not-evaluable: marker with no entry',
|
|
314
|
+
text: '## Phase 1\n- [x] **1.1 opener** <!-- opens-hold: orphan -->\n',
|
|
315
|
+
state: 'not-evaluable',
|
|
316
|
+
all: true,
|
|
317
|
+
latest: true,
|
|
318
|
+
},
|
|
319
|
+
];
|
|
320
|
+
|
|
321
|
+
// The two NEGATIVE cases. Without them a check that refused everything
|
|
322
|
+
// would pass every row above, which is the tautology this guards against.
|
|
323
|
+
const negatives: { name: string; text: string }[] = [
|
|
324
|
+
{ name: 'a roadmap with no holds section declares nothing', text: '## Phase 1\n- [ ] **1.1 x**\n' },
|
|
325
|
+
{
|
|
326
|
+
name: 'a fenced documentation example is not a declaration',
|
|
327
|
+
text: '```markdown\n## Release holds\n\n### hold: doc-example\n- **Channel:** all\n```\n',
|
|
328
|
+
},
|
|
329
|
+
];
|
|
330
|
+
|
|
331
|
+
// Lifecycle, rule 28's per-folder half. Asserted on the SAME fixture text
|
|
332
|
+
// under three different paths, so the only variable is the folder.
|
|
333
|
+
const openText = base('x', ' ');
|
|
334
|
+
const lifecycle: { name: string; file: string; text: string; want: number }[] = [
|
|
335
|
+
{
|
|
336
|
+
name: 'archive/ refuses a live window',
|
|
337
|
+
file: 'agents/roadmaps/archive/probe.md',
|
|
338
|
+
text: openText,
|
|
339
|
+
want: 1,
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
name: 'skipped/ refuses a live window',
|
|
343
|
+
file: 'agents/roadmaps/skipped/probe.md',
|
|
344
|
+
text: openText,
|
|
345
|
+
want: 1,
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
name: 'later/ refuses when entry_condition.what does not name the hold',
|
|
349
|
+
file: 'agents/roadmaps/later/probe.md',
|
|
350
|
+
text: `---\nstatus: later\nentry_condition:\n what: something else entirely\n when: later\n who: maintainer\n---\n${openText}`,
|
|
351
|
+
want: 1,
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
name: 'later/ permits when entry_condition.what names the hold',
|
|
355
|
+
file: 'agents/roadmaps/later/probe.md',
|
|
356
|
+
text: `---\nstatus: later\nentry_condition:\n what: the probe hold stays open until 1.2 lands\n when: later\n who: maintainer\n---\n${openText}`,
|
|
357
|
+
want: 0,
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
name: 'the active root permits a live window',
|
|
361
|
+
file: 'agents/roadmaps/probe.md',
|
|
362
|
+
text: openText,
|
|
363
|
+
want: 0,
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
name: 'archive/ permits a CLEARED window',
|
|
367
|
+
file: 'agents/roadmaps/archive/probe.md',
|
|
368
|
+
text: base('x', 'x'),
|
|
369
|
+
want: 0,
|
|
370
|
+
},
|
|
371
|
+
];
|
|
372
|
+
|
|
373
|
+
let failed = 0;
|
|
374
|
+
for (const c of cases) {
|
|
375
|
+
const holds = evaluateHolds(c.text, 'selftest.md');
|
|
376
|
+
const h = holds[0];
|
|
377
|
+
const ok =
|
|
378
|
+
h !== undefined &&
|
|
379
|
+
h.state === c.state &&
|
|
380
|
+
refuses(h, 'all') === c.all &&
|
|
381
|
+
refuses(h, 'latest') === c.latest;
|
|
382
|
+
if (!ok) {
|
|
383
|
+
failed += 1;
|
|
384
|
+
process.stderr.write(
|
|
385
|
+
` ❌ ${c.name}: got state=${h?.state ?? '<none>'} ` +
|
|
386
|
+
`all=${h ? refuses(h, 'all') : '-'} latest=${h ? refuses(h, 'latest') : '-'}; ` +
|
|
387
|
+
`want state=${c.state} all=${c.all} latest=${c.latest}\n`,
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
for (const n of negatives) {
|
|
392
|
+
const holds = evaluateHolds(n.text, 'selftest.md');
|
|
393
|
+
if (holds.length !== 0) {
|
|
394
|
+
failed += 1;
|
|
395
|
+
process.stderr.write(` ❌ ${n.name}: got ${holds.length} hold(s), want 0\n`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
for (const l of lifecycle) {
|
|
400
|
+
const got = lifecycleViolations(l.file, evaluateHolds(l.text, l.file), l.text).length;
|
|
401
|
+
if (got !== l.want) {
|
|
402
|
+
failed += 1;
|
|
403
|
+
process.stderr.write(` ❌ ${l.name}: got ${got} violation(s), want ${l.want}\n`);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const total = cases.length + negatives.length + lifecycle.length;
|
|
408
|
+
if (failed > 0) {
|
|
409
|
+
process.stderr.write(`❌ check-release-holds --selftest: ${failed}/${total} failed\n`);
|
|
410
|
+
return 1;
|
|
411
|
+
}
|
|
412
|
+
process.stdout.write(
|
|
413
|
+
`✅ check-release-holds --selftest: ${total}/${total} — every state-table row ` +
|
|
414
|
+
`(including not-evaluable, 7 ways), both negatives, and all six lifecycle cases\n`,
|
|
415
|
+
);
|
|
416
|
+
return 0;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* The house self-test: fixture trees on disk, this gate's real CLI, exit codes.
|
|
421
|
+
*
|
|
422
|
+
* Two accept cases are not padding. A gate that refused everything would pass
|
|
423
|
+
* every reject case, and the one that matters most is the VALID OPEN WINDOW:
|
|
424
|
+
* normal CI must stay green on exactly the tree state the release path refuses,
|
|
425
|
+
* and nothing else in this suite would notice if `--lint` started reddening it.
|
|
426
|
+
*/
|
|
427
|
+
function cmdConventionSelfTest(): number {
|
|
428
|
+
const script = 'src/scripts/check_release_holds.ts';
|
|
429
|
+
const mk = (name: string, files: Record<string, string>): string => {
|
|
430
|
+
const root = fs.mkdtempSync(path.join(os.tmpdir(), `crh-${name}-`));
|
|
431
|
+
for (const [rel, body] of Object.entries(files)) {
|
|
432
|
+
const abs = path.join(root, rel);
|
|
433
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
434
|
+
fs.writeFileSync(abs, body, 'utf8');
|
|
435
|
+
}
|
|
436
|
+
return root;
|
|
437
|
+
};
|
|
438
|
+
const hold = (open: string, clear: string, extra = ''): string =>
|
|
439
|
+
[
|
|
440
|
+
'## Phase 1',
|
|
441
|
+
`- [${open}] **1.1 opener** <!-- opens-hold: probe -->`,
|
|
442
|
+
' verify: the opener check',
|
|
443
|
+
`- [${clear}] **1.2 clearer** <!-- clears-hold: probe -->`,
|
|
444
|
+
' verify: ./scripts-run src/scripts/check_surface_whole',
|
|
445
|
+
'',
|
|
446
|
+
'## Release holds',
|
|
447
|
+
'',
|
|
448
|
+
'### hold: probe',
|
|
449
|
+
'- **Channel:** all',
|
|
450
|
+
'- **Opened by:** 1.1',
|
|
451
|
+
'- **Cleared by:** 1.2',
|
|
452
|
+
'- **State:** the surface is half wired while this is open.',
|
|
453
|
+
`- **Why not a guard:** the entry point cannot be made inert.${extra}`,
|
|
454
|
+
'',
|
|
455
|
+
].join('\n');
|
|
456
|
+
const at = (root: string, args: string[]): number =>
|
|
457
|
+
runGateCli(REPO_ROOT, script, [...args, '--root', root], REPO_ROOT);
|
|
458
|
+
|
|
459
|
+
const cases: SelfTestCase[] = [
|
|
460
|
+
{
|
|
461
|
+
name: 'a malformed declaration reddens --lint',
|
|
462
|
+
expect: 'reject',
|
|
463
|
+
run: () =>
|
|
464
|
+
at(
|
|
465
|
+
mk('bad', {
|
|
466
|
+
'agents/roadmaps/r.md': hold('x', ' ').replace(
|
|
467
|
+
/- \*\*Why not a guard:\*\*.*\n/,
|
|
468
|
+
'',
|
|
469
|
+
),
|
|
470
|
+
}),
|
|
471
|
+
['--lint'],
|
|
472
|
+
),
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
name: 'an open window refuses --require-safe',
|
|
476
|
+
expect: 'reject',
|
|
477
|
+
run: () => at(mk('open', { 'agents/roadmaps/r.md': hold('x', ' ') }), ['--require-safe']),
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
name: 'a live window sitting in archive/ reddens --lint',
|
|
481
|
+
expect: 'reject',
|
|
482
|
+
run: () => at(mk('arch', { 'agents/roadmaps/archive/r.md': hold('x', ' ') }), ['--lint']),
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: 'a dead scan scope reddens rather than certifying an empty corpus',
|
|
486
|
+
expect: 'reject',
|
|
487
|
+
run: () => at(mk('dead', { 'README.md': 'no roadmaps here\n' }), ['--lint']),
|
|
488
|
+
},
|
|
489
|
+
{
|
|
490
|
+
name: 'a clean corpus passes --lint',
|
|
491
|
+
expect: 'accept',
|
|
492
|
+
run: () =>
|
|
493
|
+
at(mk('clean', { 'agents/roadmaps/r.md': '## Phase 1\n- [ ] **1.1 x**\n' }), ['--lint']),
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
name: 'a VALID OPEN window passes --lint — normal CI stays green on it',
|
|
497
|
+
expect: 'accept',
|
|
498
|
+
run: () => at(mk('valid', { 'agents/roadmaps/r.md': hold('x', ' ') }), ['--lint']),
|
|
499
|
+
},
|
|
500
|
+
];
|
|
501
|
+
return runSelfTest({ gate: 'check_release_holds', cases, minCases: 6, minRejectCases: 4 });
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function usage(): number {
|
|
505
|
+
process.stderr.write(
|
|
506
|
+
'usage: check_release_holds (--lint | --status | --require-safe [--channel latest|all]\n' +
|
|
507
|
+
' | --can-move <file> <archive|skipped|later>\n' +
|
|
508
|
+
' | --selftest | --self-test) [--root <dir>]\n' +
|
|
509
|
+
' --channel all (default) a stable X.Y.Z cut — every open hold refuses\n' +
|
|
510
|
+
' --channel latest a -next.N prerelease — only `Channel: all` holds refuse\n',
|
|
511
|
+
);
|
|
512
|
+
return 2;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function main(argv: string[] = process.argv.slice(2)): number {
|
|
516
|
+
const ri = argv.indexOf('--root');
|
|
517
|
+
const root = ri === -1 ? REPO_ROOT : path.resolve(argv[ri + 1] ?? '.');
|
|
518
|
+
// `--self-test` is the house convention (`_lib/gate_self_test`): it drives
|
|
519
|
+
// this CLI against fixture trees on disk and asserts the exit code, so what
|
|
520
|
+
// it proves is that the BINARY still rejects. `--selftest` is the unit-level
|
|
521
|
+
// state-table suite. They are complementary, not duplicates -- one can pass
|
|
522
|
+
// while the other fails, which is the whole reason both exist.
|
|
523
|
+
if (argv.includes('--self-test') && process.env['GATE_SELF_TEST_CHILD'] !== '1') {
|
|
524
|
+
return cmdConventionSelfTest();
|
|
525
|
+
}
|
|
526
|
+
if (argv.includes('--selftest')) {
|
|
527
|
+
return cmdSelftest();
|
|
528
|
+
}
|
|
529
|
+
if (argv.includes('--lint')) {
|
|
530
|
+
return cmdLint(root);
|
|
531
|
+
}
|
|
532
|
+
if (argv.includes('--status')) {
|
|
533
|
+
return cmdStatus(root);
|
|
534
|
+
}
|
|
535
|
+
if (argv.includes('--require-safe')) {
|
|
536
|
+
const i = argv.indexOf('--channel');
|
|
537
|
+
const raw = i === -1 ? 'all' : argv[i + 1];
|
|
538
|
+
if (raw !== 'all' && raw !== 'latest') {
|
|
539
|
+
process.stderr.write(`❌ --channel must be \`all\` or \`latest\`, got \`${raw ?? ''}\`\n`);
|
|
540
|
+
return usage();
|
|
541
|
+
}
|
|
542
|
+
return cmdRequireSafe(root, raw);
|
|
543
|
+
}
|
|
544
|
+
const mv = argv.indexOf('--can-move');
|
|
545
|
+
if (mv !== -1) {
|
|
546
|
+
const file = argv[mv + 1];
|
|
547
|
+
const dest = argv[mv + 2];
|
|
548
|
+
if (file === undefined || dest === undefined) {
|
|
549
|
+
return usage();
|
|
550
|
+
}
|
|
551
|
+
return cmdCanMove(path.resolve(file), dest);
|
|
552
|
+
}
|
|
553
|
+
return usage();
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function _isCliEntry(): boolean {
|
|
557
|
+
if (process.argv[1] === undefined) {
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
return import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (_isCliEntry() || process.argv[1] === _HERE) {
|
|
564
|
+
process.exit(main());
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export { collect, globRoadmaps, main, HOLD_DIRS };
|
|
@@ -6,22 +6,32 @@
|
|
|
6
6
|
* WHY THIS GATE EXISTS (road-to-conformance-round5 Phase 1, measured 2026-08-07)
|
|
7
7
|
* ---------------------------------------------------------------------------
|
|
8
8
|
* `docs/contracts/rule-router.md` (§ "The router earns its place as a
|
|
9
|
-
* **compile-time** artifact")
|
|
10
|
-
* loads `dist/router.json` at runtime, so **projection is the only
|
|
11
|
-
* mechanism there is
|
|
9
|
+
* **compile-time** artifact") used to state that under every shipped default
|
|
10
|
+
* nothing loads `dist/router.json` at runtime, so **projection is the only
|
|
11
|
+
* reach mechanism there is**. Both halves of that have since been corrected,
|
|
12
|
+
* and the gate outlived both:
|
|
12
13
|
*
|
|
13
14
|
* CORRECTED 2026-08-26. This docblock used to cite that section's "measured:
|
|
14
15
|
* zero consumers under `src/scripts/hooks/`", and that measurement is stale: the
|
|
15
16
|
* `rule-inject` concern reads the router via `_lib/rule_injection.ts:76-79` and
|
|
16
|
-
* is bound on three slots.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
17
|
+
* is bound on three slots. What no longer holds is the stronger claim that no
|
|
18
|
+
* such mechanism exists.
|
|
19
|
+
*
|
|
20
|
+
* CORRECTED AGAIN 2026-09-12. The 2026-08-26 note rescued the rationale with
|
|
21
|
+
* "the concern is DEFAULT-OFF … so on a shipped default, projection IS still
|
|
22
|
+
* the only reach mechanism". That rescue is now false and the clause is gone:
|
|
23
|
+
* since ADR-267 the shipped template carries `lean_projection.mode: delivery`
|
|
24
|
+
* with `hosts: [claude-code]`, so on the shipped default that one host does
|
|
25
|
+
* reach rule bodies at runtime. The gate never depended on the rescue — its two
|
|
26
|
+
* assertions below are about the emit plan and the entry mtimes, and a thinned
|
|
27
|
+
* tree of stubs is exactly as load-bearing as a tree of bodies: a missing entry
|
|
28
|
+
* is a rule the model neither reads nor has a pointer to, in either mode.
|
|
29
|
+
*
|
|
30
|
+
* Both are stated rather than quietly re-worded, because a gate whose stated
|
|
31
|
+
* reason rests on a refuted premise is exactly the decoration ADR-127 rejects:
|
|
32
|
+
* a non-kernel rule activates by the model's judgment over text already in
|
|
33
|
+
* context, and the per-tool rule tree is what puts it there. That makes the
|
|
34
|
+
* state of the projection load-bearing, and it was broken:
|
|
25
35
|
*
|
|
26
36
|
* - `.claude/rules/` in the working checkout held **92** entries. A
|
|
27
37
|
* regeneration in a clean worktree produces **108**. The tree was last
|