@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,202 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Every roadmap producer ends in closure — mechanically, not by convention.
|
|
4
|
+
*
|
|
5
|
+
* A command that produces a plan and hands it back with open decisions in it
|
|
6
|
+
* has moved those decisions into execution, where meeting one costs a run.
|
|
7
|
+
* The entrance list used to be hard-coded in three command bodies, so the
|
|
8
|
+
* other six producers were simply not in it and nothing said so.
|
|
9
|
+
*
|
|
10
|
+
* The declaration is now the command's own: `produces_roadmap: true` in the
|
|
11
|
+
* frontmatter. This gate reds a command that declares it and does not end in
|
|
12
|
+
* the closure pass, which is the half a hard-coded list could never check.
|
|
13
|
+
*
|
|
14
|
+
* What counts as ending in closure: the body references the closure pass by
|
|
15
|
+
* one of its two names — the `/challenge-me closure` sub-command, or the
|
|
16
|
+
* `closure_scan` detector it runs. Deliberately a reference check rather than
|
|
17
|
+
* a prose check: prose drifts, and a gate that guesses at intent from wording
|
|
18
|
+
* is the detector that gets weakened until it finds nothing.
|
|
19
|
+
*
|
|
20
|
+
* Exit codes: 0 clean · 1 violations · 2 internal error / bad argv.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import * as fs from 'node:fs';
|
|
24
|
+
import * as path from 'node:path';
|
|
25
|
+
import process from 'node:process';
|
|
26
|
+
import { fileURLToPath } from 'node:url';
|
|
27
|
+
|
|
28
|
+
import { assertScanned, DeadScopeError } from './_lib/scan_scope.js';
|
|
29
|
+
|
|
30
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
31
|
+
const _PROG = 'lint_roadmap_producers';
|
|
32
|
+
|
|
33
|
+
/** The frontmatter declaration a producer carries. */
|
|
34
|
+
export const DECLARATION_RE = /^produces_roadmap:\s*true\s*$/m;
|
|
35
|
+
|
|
36
|
+
/** Either name of the closure pass discharges the obligation. */
|
|
37
|
+
export const CLOSURE_REFS: readonly RegExp[] = [
|
|
38
|
+
/\/challenge-me\s+closure/,
|
|
39
|
+
/challenge-me\/closure/,
|
|
40
|
+
/\bclosure_scan\b/,
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
export interface Violation {
|
|
44
|
+
readonly file: string;
|
|
45
|
+
readonly reason: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function _frontmatter(text: string): string {
|
|
49
|
+
if (!text.startsWith('---')) return '';
|
|
50
|
+
const end = text.indexOf('\n---', 3);
|
|
51
|
+
return end < 0 ? '' : text.slice(0, end);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function declaresProducer(text: string): boolean {
|
|
55
|
+
return DECLARATION_RE.test(_frontmatter(text));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function endsInClosure(text: string): boolean {
|
|
59
|
+
return CLOSURE_REFS.some((re) => re.test(text));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function checkFile(rel: string, text: string): Violation[] {
|
|
63
|
+
if (!declaresProducer(text)) return [];
|
|
64
|
+
if (endsInClosure(text)) return [];
|
|
65
|
+
return [
|
|
66
|
+
{
|
|
67
|
+
file: rel,
|
|
68
|
+
reason:
|
|
69
|
+
'declares `produces_roadmap: true` and does not end in the closure pass — ' +
|
|
70
|
+
'a producer hands back an execution contract, not a plan with open decisions ' +
|
|
71
|
+
'in it. Reference `/challenge-me closure` (or the `closure_scan` detector) ' +
|
|
72
|
+
'as the command’s last step.',
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function _walk(dir: string, out: string[]): void {
|
|
78
|
+
let entries: fs.Dirent[];
|
|
79
|
+
try {
|
|
80
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
81
|
+
} catch {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
85
|
+
const full = path.join(dir, e.name);
|
|
86
|
+
if (e.isDirectory()) _walk(full, out);
|
|
87
|
+
else if (e.isFile() && e.name === 'command.md') out.push(full);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function listCommands(root: string): string[] {
|
|
92
|
+
const out: string[] = [];
|
|
93
|
+
_walk(root, out);
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function _repoRoot(): string {
|
|
98
|
+
return path.resolve(path.dirname(_HERE), '..', '..');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function _usage(): string {
|
|
102
|
+
return (
|
|
103
|
+
`usage: ${_PROG} [-h] [--quiet] [--json] [--self-test]\n\n` +
|
|
104
|
+
`Every command declaring \`produces_roadmap: true\` ends in the closure pass.\n`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function main(argv?: readonly string[]): number {
|
|
109
|
+
const args = argv ?? process.argv.slice(2);
|
|
110
|
+
let quiet = false;
|
|
111
|
+
let json = false;
|
|
112
|
+
let selfTest = false;
|
|
113
|
+
const unknown: string[] = [];
|
|
114
|
+
for (const a of args) {
|
|
115
|
+
if (a === '-h' || a === '--help') {
|
|
116
|
+
process.stdout.write(_usage());
|
|
117
|
+
return 0;
|
|
118
|
+
} else if (a === '--quiet') quiet = true;
|
|
119
|
+
else if (a === '--json') json = true;
|
|
120
|
+
else if (a === '--self-test') selfTest = true;
|
|
121
|
+
else unknown.push(a);
|
|
122
|
+
}
|
|
123
|
+
if (unknown.length) {
|
|
124
|
+
process.stderr.write(`${_usage()}${_PROG}: error: unrecognized arguments: ${unknown.join(' ')}\n`);
|
|
125
|
+
return 2;
|
|
126
|
+
}
|
|
127
|
+
if (selfTest) return _selfTest();
|
|
128
|
+
|
|
129
|
+
const repo = _repoRoot();
|
|
130
|
+
const root = path.join(repo, 'src', 'domains');
|
|
131
|
+
const files = listCommands(root);
|
|
132
|
+
const producers = files.filter((f) => declaresProducer(fs.readFileSync(f, 'utf-8')));
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
// The corpus is the DECLARED producers, not every command: a tree where
|
|
136
|
+
// nobody declares the key is a tree where the declaration was lost, and
|
|
137
|
+
// that is exactly the silent-disarm this assertion exists to catch.
|
|
138
|
+
assertScanned({
|
|
139
|
+
gate: _PROG,
|
|
140
|
+
scanned: producers.length,
|
|
141
|
+
units: 'roadmap producer(s)',
|
|
142
|
+
roots: ['src/domains'],
|
|
143
|
+
});
|
|
144
|
+
} catch (e) {
|
|
145
|
+
if (e instanceof DeadScopeError) {
|
|
146
|
+
process.stderr.write(`❌ ${e.message}\n`);
|
|
147
|
+
return 2;
|
|
148
|
+
}
|
|
149
|
+
throw e;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const violations: Violation[] = [];
|
|
153
|
+
for (const f of producers) {
|
|
154
|
+
violations.push(...checkFile(path.relative(repo, f), fs.readFileSync(f, 'utf-8')));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (json) {
|
|
158
|
+
process.stdout.write(JSON.stringify(violations, null, 2) + '\n');
|
|
159
|
+
return violations.length ? 1 : 0;
|
|
160
|
+
}
|
|
161
|
+
if (violations.length) {
|
|
162
|
+
process.stderr.write(`❌ ${_PROG}: ${violations.length} producer(s) do not end in closure\n`);
|
|
163
|
+
for (const v of violations) process.stderr.write(` ${v.file} — ${v.reason}\n`);
|
|
164
|
+
return 1;
|
|
165
|
+
}
|
|
166
|
+
if (!quiet) {
|
|
167
|
+
process.stdout.write(
|
|
168
|
+
`✅ ${_PROG}: ${producers.length} roadmap producer(s), all ending in closure\n`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
return 0;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Both directions in-process: a producer without closure reds, one with it does not. */
|
|
175
|
+
function _selfTest(): number {
|
|
176
|
+
const withClosure = '---\nname: x\nproduces_roadmap: true\n---\n\nRun `/challenge-me closure`.\n';
|
|
177
|
+
const without = '---\nname: x\nproduces_roadmap: true\n---\n\nHand the plan back.\n';
|
|
178
|
+
const notAProducer = '---\nname: x\n---\n\nHand the plan back.\n';
|
|
179
|
+
const failures: string[] = [];
|
|
180
|
+
if (checkFile('a.md', without).length === 0) failures.push('a producer without closure did not red');
|
|
181
|
+
if (checkFile('b.md', withClosure).length !== 0) failures.push('a producer with closure red');
|
|
182
|
+
if (checkFile('c.md', notAProducer).length !== 0) failures.push('a non-producer was judged');
|
|
183
|
+
if (failures.length) {
|
|
184
|
+
for (const f of failures) process.stderr.write(`❌ ${_PROG} --self-test: ${f}\n`);
|
|
185
|
+
return 1;
|
|
186
|
+
}
|
|
187
|
+
process.stdout.write(`✅ ${_PROG} --self-test: both directions hold\n`);
|
|
188
|
+
return 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function _isCliEntry(): boolean {
|
|
192
|
+
if (!process.argv[1]) return false;
|
|
193
|
+
try {
|
|
194
|
+
return fs.realpathSync(_HERE) === fs.realpathSync(path.resolve(process.argv[1]));
|
|
195
|
+
} catch {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (_isCliEntry() || process.argv[1] === _HERE) {
|
|
201
|
+
process.exitCode = main();
|
|
202
|
+
}
|
|
@@ -38,6 +38,7 @@ const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
|
38
38
|
|
|
39
39
|
const OVERRIDES_FILE = path.join(REPO_ROOT, 'docs', 'contracts', 'iron-law-overrides.txt');
|
|
40
40
|
const TREND_FILE = path.join(REPO_ROOT, 'agents', 'runtime', '.rule-budget-history.jsonl');
|
|
41
|
+
const AUTO_BUDGET_FILE = path.join(REPO_ROOT, 'src', 'config', 'auto-rule-budget.json');
|
|
41
42
|
|
|
42
43
|
// Council R2 amendments (2026-05-06) — see docs/contracts/kernel-membership.md § 5.1.
|
|
43
44
|
// Per-rule cap raised 1.5k → 2.5k; warning band raised 1.2k → 2.0k.
|
|
@@ -59,6 +60,8 @@ export interface RuleMeasure {
|
|
|
59
60
|
lines: number;
|
|
60
61
|
tokens_gpt: number;
|
|
61
62
|
tokens_claude: number;
|
|
63
|
+
/** Iron-Law headings in the body — the obligation count a char count hides. */
|
|
64
|
+
iron_laws: number;
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
export interface Aggregate {
|
|
@@ -80,6 +83,7 @@ export interface Aggregate {
|
|
|
80
83
|
per_rule_hard: number;
|
|
81
84
|
per_rule_target: number;
|
|
82
85
|
per_rule_override_ceiling: number;
|
|
86
|
+
iron_law_total: number;
|
|
83
87
|
oversize_rules: RuleMeasure[];
|
|
84
88
|
top5_largest: RuleMeasure[];
|
|
85
89
|
}
|
|
@@ -130,9 +134,32 @@ export function measure_rule(p: string): RuleMeasure {
|
|
|
130
134
|
lines: _count(body, '\n'),
|
|
131
135
|
tokens_gpt: token_count.gpt_tokens(body).tokens,
|
|
132
136
|
tokens_claude: token_count.claude_tokens(body).tokens,
|
|
137
|
+
iron_laws: count_iron_laws(body),
|
|
133
138
|
};
|
|
134
139
|
}
|
|
135
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Count Iron-Law HEADINGS in a rule body.
|
|
143
|
+
*
|
|
144
|
+
* road-to-design-fidelity-proof Phase 3.2. A rule is not an instruction:
|
|
145
|
+
* `token-efficiency` carries five Iron Laws in one file, so a file count and a
|
|
146
|
+
* char count both understate what a model is asked to hold at once. The
|
|
147
|
+
* instruction-following literature measures against the number of SIMULTANEOUS
|
|
148
|
+
* constraints, and an Iron-Law heading is the closest proxy this tree can count
|
|
149
|
+
* deterministically.
|
|
150
|
+
*
|
|
151
|
+
* Heading forms accepted, matching `preservation-guard`'s own definition: any
|
|
152
|
+
* level, optional `The`, singular or plural, optional trailing number or
|
|
153
|
+
* qualifier (`## Iron Law 2`, `## Iron Law — Gate`). A prose mention is NOT a
|
|
154
|
+
* heading, and a heading that merely contains the words later (`## Why the Iron
|
|
155
|
+
* Law exists`) is not one either — the phrase must open the heading text, or
|
|
156
|
+
* every retrospective paragraph would inflate the count.
|
|
157
|
+
*/
|
|
158
|
+
export function count_iron_laws(body: string): number {
|
|
159
|
+
const re = /^#{1,6}[ \t]+(?:The[ \t]+)?Iron[ \t]+Laws?\b/gim;
|
|
160
|
+
return (body.match(re) ?? []).length;
|
|
161
|
+
}
|
|
162
|
+
|
|
136
163
|
/**
|
|
137
164
|
* Collect rule measurements from every source root (multi-root aware).
|
|
138
165
|
* Deduplicates on logical rule id (stem) — first root wins. Mirrors
|
|
@@ -198,6 +225,7 @@ export function aggregate(rules: RuleMeasure[]): Aggregate {
|
|
|
198
225
|
per_rule_hard: PER_RULE_HARD,
|
|
199
226
|
per_rule_target: PER_RULE_TARGET,
|
|
200
227
|
per_rule_override_ceiling: PER_RULE_OVERRIDE_CEILING,
|
|
228
|
+
iron_law_total: _sum(rules.map((r) => r.iron_laws)),
|
|
201
229
|
oversize_rules: rules
|
|
202
230
|
.filter((r) => r.chars > PER_RULE_HARD)
|
|
203
231
|
.sort((a, b) => _cmp([-a.chars, a.id], [-b.chars, b.id])),
|
|
@@ -324,6 +352,59 @@ export function kernel_budget_check(
|
|
|
324
352
|
return [0, out];
|
|
325
353
|
}
|
|
326
354
|
|
|
355
|
+
export interface AutoBudgetConfig {
|
|
356
|
+
baseline_chars: number;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Read the auto-bucket baseline. Throws rather than defaulting: a ratchet that
|
|
360
|
+
* silently falls back to "no bound" is a permanently green gate. */
|
|
361
|
+
export function load_auto_budget(file: string = AUTO_BUDGET_FILE): AutoBudgetConfig {
|
|
362
|
+
const raw = JSON.parse(fs.readFileSync(file, 'utf-8')) as { baseline_chars?: unknown };
|
|
363
|
+
if (typeof raw.baseline_chars !== 'number') {
|
|
364
|
+
throw new Error(`${file}: baseline_chars must be a number`);
|
|
365
|
+
}
|
|
366
|
+
return { baseline_chars: raw.baseline_chars };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Enforce the `auto`-bucket growth ratchet. Returns `[exit_code, report_lines]`.
|
|
371
|
+
*
|
|
372
|
+
* road-to-design-fidelity-proof Phase 3.1. `kernel_budget_check` above bounds
|
|
373
|
+
* nine rules; this bounds the other 107, which carry ~93 % of the estate's rule
|
|
374
|
+
* prose and grew against nothing. Shrink-only: growth fails, a shrink passes and
|
|
375
|
+
* says how much slack it created, so the number walks down rather than becoming
|
|
376
|
+
* unused headroom.
|
|
377
|
+
*
|
|
378
|
+
* The Iron-Law total is REPORTED, never gated. No measurement in this tree says
|
|
379
|
+
* how many simultaneous obligations this layer can carry, and a bound nobody
|
|
380
|
+
* measured would publish a guess as a finding.
|
|
381
|
+
*/
|
|
382
|
+
export function auto_budget_check(agg: Aggregate, cfg: AutoBudgetConfig): [number, string[]] {
|
|
383
|
+
const out: string[] = [];
|
|
384
|
+
const delta = agg.auto_chars - cfg.baseline_chars;
|
|
385
|
+
const signed = delta >= 0 ? `+${delta}` : String(delta);
|
|
386
|
+
out.push(
|
|
387
|
+
`auto-bucket: ${agg.auto_chars} / ${cfg.baseline_chars} chars ` +
|
|
388
|
+
`(${agg.auto_count} rules, ${signed} vs baseline)`,
|
|
389
|
+
);
|
|
390
|
+
out.push(`iron-law total: ${agg.iron_law_total} heading(s) — reported, not gated`);
|
|
391
|
+
out.push('');
|
|
392
|
+
if (delta > 0) {
|
|
393
|
+
out.push(`\u274c auto budget check: the auto bucket grew ${signed} chars`);
|
|
394
|
+
out.push(
|
|
395
|
+
' The baseline may only move DOWN. Walk the bucket back, or state the ' +
|
|
396
|
+
'compensating reduction — raising it to clear this is config weakening.',
|
|
397
|
+
);
|
|
398
|
+
return [1, out];
|
|
399
|
+
}
|
|
400
|
+
out.push(
|
|
401
|
+
delta === 0
|
|
402
|
+
? '\u2705 auto budget check: pass (at the baseline)'
|
|
403
|
+
: `\u2705 auto budget check: pass (${signed} chars of slack — ratchet it down)`,
|
|
404
|
+
);
|
|
405
|
+
return [0, out];
|
|
406
|
+
}
|
|
407
|
+
|
|
327
408
|
/**
|
|
328
409
|
* Append a daily snapshot to agents/runtime/.rule-budget-history.jsonl.
|
|
329
410
|
* Idempotent per UTC day. Mirrors `trend_append`. Returns `[exit, msg]`.
|
|
@@ -363,19 +444,22 @@ export function trend_append(agg: Aggregate): [number, string] {
|
|
|
363
444
|
interface Args {
|
|
364
445
|
json: boolean;
|
|
365
446
|
kernelBudgetCheck: boolean;
|
|
447
|
+
autoBudgetCheck: boolean;
|
|
366
448
|
trendAppend: boolean;
|
|
367
449
|
}
|
|
368
450
|
|
|
369
451
|
function parse_args(argv: string[]): Args {
|
|
370
452
|
let json = false;
|
|
371
453
|
let kernelBudgetCheck = false;
|
|
454
|
+
let autoBudgetCheck = false;
|
|
372
455
|
let trendAppend = false;
|
|
373
456
|
for (const a of argv) {
|
|
374
457
|
if (a === '--json') json = true;
|
|
375
458
|
else if (a === '--kernel-budget-check') kernelBudgetCheck = true;
|
|
459
|
+
else if (a === '--auto-budget-check') autoBudgetCheck = true;
|
|
376
460
|
else if (a === '--trend-append') trendAppend = true;
|
|
377
461
|
}
|
|
378
|
-
return { json, kernelBudgetCheck, trendAppend };
|
|
462
|
+
return { json, kernelBudgetCheck, autoBudgetCheck, trendAppend };
|
|
379
463
|
}
|
|
380
464
|
|
|
381
465
|
export function main(argv: string[] = process.argv.slice(2)): number {
|
|
@@ -384,10 +468,22 @@ export function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
384
468
|
const rules = collect();
|
|
385
469
|
const agg = aggregate(rules);
|
|
386
470
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
471
|
+
// Both budget flags may be passed in one invocation, and the CI step does
|
|
472
|
+
// exactly that — a second task step would be a new gate `check_ci_local_parity`
|
|
473
|
+
// has to be told about, for two checks over one measurement.
|
|
474
|
+
if (args.kernelBudgetCheck || args.autoBudgetCheck) {
|
|
475
|
+
let code = 0;
|
|
476
|
+
if (args.kernelBudgetCheck) {
|
|
477
|
+
const overrides = load_overrides();
|
|
478
|
+
const [kCode, report] = kernel_budget_check(rules, agg, overrides);
|
|
479
|
+
process.stdout.write(report.join('\n') + '\n');
|
|
480
|
+
code = Math.max(code, kCode);
|
|
481
|
+
}
|
|
482
|
+
if (args.autoBudgetCheck) {
|
|
483
|
+
const [aCode, report] = auto_budget_check(agg, load_auto_budget());
|
|
484
|
+
process.stdout.write(report.join('\n') + '\n');
|
|
485
|
+
code = Math.max(code, aCode);
|
|
486
|
+
}
|
|
391
487
|
return code;
|
|
392
488
|
}
|
|
393
489
|
|
|
@@ -48,6 +48,13 @@ function titleOf(slug: string): string {
|
|
|
48
48
|
return slug.replace(/-/g, ' ');
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// The two heading markers the release-holds block emits, interpolated rather
|
|
52
|
+
// than written literally: `lint_code_comments` strips indentation before
|
|
53
|
+
// matching, so a template-literal line beginning `## ` reads to it as a
|
|
54
|
+
// markdown heading left in source. The emitted text is unchanged.
|
|
55
|
+
const H2 = '#'.repeat(2) + ' ';
|
|
56
|
+
const H3 = '#'.repeat(3) + ' ';
|
|
57
|
+
|
|
51
58
|
export function skeleton(slug: string, complexity: 'lightweight' | 'structural', date: string): string {
|
|
52
59
|
return `---
|
|
53
60
|
complexity: ${complexity}
|
|
@@ -73,6 +80,36 @@ could tell whether it happened.
|
|
|
73
80
|
verify: the check that proves it — a command, a test, an observable
|
|
74
81
|
state. Never "looks right".
|
|
75
82
|
|
|
83
|
+
<!-- Release holds — emitted commented out, because the default is that there is
|
|
84
|
+
not one. Uncomment ONLY if an intermediate tree state of this roadmap must
|
|
85
|
+
not be published. Template rule 28 is the contract; the authoring order is
|
|
86
|
+
re-sequence -> guard -> hold, and a hold is the last resort, never the
|
|
87
|
+
first tool.
|
|
88
|
+
|
|
89
|
+
Record the outcome either way. A roadmap that considered a hold and reached
|
|
90
|
+
rung 1 or 2 instead writes the one-line \`resequenced:\` / \`guarded:\` note
|
|
91
|
+
below and deletes the entry; that note is a counted outcome, not a comment.
|
|
92
|
+
|
|
93
|
+
resequenced: <one line — the broken intermediate state, and the phase cut
|
|
94
|
+
that removed it, so no window was ever needed.>
|
|
95
|
+
|
|
96
|
+
${H2}Release holds
|
|
97
|
+
|
|
98
|
+
${H3}hold: <kebab-id>
|
|
99
|
+
- **Channel:** all (all | latest; omitted parses to all)
|
|
100
|
+
- **Opened by:** <phase.step> (the checkbox whose [x] opens the window)
|
|
101
|
+
- **Cleared by:** <phase.step> (the checkbox whose [x] closes it)
|
|
102
|
+
- **State:** <one sentence naming what is broken in the tree while open.>
|
|
103
|
+
- **Why not a guard:** <why the guard rung failed, concretely. Mandatory —
|
|
104
|
+
an entry without it is malformed and reddens CI.>
|
|
105
|
+
|
|
106
|
+
Both named steps carry an inline HTML-comment marker on the checkbox line
|
|
107
|
+
itself — \`opens-hold: <kebab-id>\` on the opener, \`clears-hold: <kebab-id>\`
|
|
108
|
+
on the clearer — so the binding is readable from the checkbox and not only
|
|
109
|
+
from this section. The clearing step MUST carry a \`verify:\` field: a hold
|
|
110
|
+
cleared by an unverified flip is a hold cleared by assertion.
|
|
111
|
+
-->
|
|
112
|
+
|
|
76
113
|
## Risk Register
|
|
77
114
|
<!-- risk-review: v1 | reviewed: ${date} | reviewer: claude/host -->
|
|
78
115
|
|
|
@@ -18,7 +18,6 @@
|
|
|
18
18
|
* [--rules-carried 32] [--rules-used 5] \
|
|
19
19
|
* [--work-tokens 41000] [--floor-provenance measured|estimated] \
|
|
20
20
|
* [--capsule-emitted true|false] [--capsule-entries 9] \
|
|
21
|
-
* [--watermark-step 8] [--saturation-step 4] [--trigger-arm-earlier watermark|saturation|tie] \
|
|
22
21
|
* [--dir <audit-dir>] [--dry-run]
|
|
23
22
|
*
|
|
24
23
|
* Read by `src/scripts/orchestration_savings_report.ts`.
|
|
@@ -42,7 +41,6 @@ import {
|
|
|
42
41
|
type LineOutcome,
|
|
43
42
|
type RecordInput,
|
|
44
43
|
type RouteTaken,
|
|
45
|
-
type TriggerArm,
|
|
46
44
|
} from './_lib/orchestration_record.js';
|
|
47
45
|
|
|
48
46
|
// Exported so other writers of the same audit-log-v1 orchestration line
|
|
@@ -174,9 +172,6 @@ export function main(argv: string[] = process.argv.slice(2)): number {
|
|
|
174
172
|
floor_provenance: str(flags, 'floor-provenance') as Provenance | undefined,
|
|
175
173
|
capsule_emitted: bool(flags, 'capsule-emitted'),
|
|
176
174
|
capsule_entries: int(flags, 'capsule-entries'),
|
|
177
|
-
watermark_step: int(flags, 'watermark-step'),
|
|
178
|
-
saturation_step: int(flags, 'saturation-step'),
|
|
179
|
-
trigger_arm_earlier: str(flags, 'trigger-arm-earlier') as TriggerArm | undefined,
|
|
180
175
|
phase: str(flags, 'phase') as LinePhase | undefined,
|
|
181
176
|
outcome: str(flags, 'outcome') as LineOutcome | undefined,
|
|
182
177
|
confidence_band: str(flags, 'confidence-band') as Band | undefined,
|
package/src/scripts/release.ts
CHANGED
|
@@ -122,9 +122,7 @@ import {
|
|
|
122
122
|
import {
|
|
123
123
|
HEAD_LABELS,
|
|
124
124
|
type MixObligation,
|
|
125
|
-
|
|
126
|
-
derive_category_hits,
|
|
127
|
-
render_derived_head_values,
|
|
125
|
+
derive_head_prefill,
|
|
128
126
|
} from './_lib/release_highlights.js';
|
|
129
127
|
import { canPrompt, promptLine } from './_lib/tty_prompt.js';
|
|
130
128
|
import { preflightPosition } from './_lib/release_position.js';
|
|
@@ -137,6 +135,11 @@ import {
|
|
|
137
135
|
release_notes_from_section,
|
|
138
136
|
render_mix_response,
|
|
139
137
|
} from './_lib/release_material.js';
|
|
138
|
+
import {
|
|
139
|
+
assert_major_migration_section,
|
|
140
|
+
assert_scheduled_deprecations_clear,
|
|
141
|
+
} from './_lib/release_migration_gate.js';
|
|
142
|
+
import { releaseHoldPreflight } from './_lib/release_holds.js';
|
|
140
143
|
|
|
141
144
|
// `__doc__.splitlines()[0]` in `_parse_args` — the argparse description. Kept
|
|
142
145
|
// as a referenceable constant so the first docstring line is preserved exactly.
|
|
@@ -144,7 +147,6 @@ const MODULE_DOC_FIRST_LINE = 'End-to-end release automation for `event4u/agent-
|
|
|
144
147
|
|
|
145
148
|
const _HERE = fileURLToPath(import.meta.url);
|
|
146
149
|
|
|
147
|
-
|
|
148
150
|
import {
|
|
149
151
|
ArgparseExit,
|
|
150
152
|
AUGMENT_MARKETPLACE_JSON,
|
|
@@ -169,7 +171,6 @@ import {
|
|
|
169
171
|
_rstrip,
|
|
170
172
|
_splitlines,
|
|
171
173
|
} from './release_env.js';
|
|
172
|
-
import type { RunResult } from './release_publication.js';
|
|
173
174
|
// Re-export surface for the names tests import from `release.js` and that moved
|
|
174
175
|
// into the publication unit — eleven since the test-count-trend unit followed. `export ... from` rather than a bare import,
|
|
175
176
|
// because these are not USED here — an unused import would be dropped and the
|
|
@@ -226,7 +227,6 @@ import {
|
|
|
226
227
|
// existing block resolve. This is the whole of step 1.1's "re-export shape that
|
|
227
228
|
// keeps callers unaffected": no caller and no test import path changes.
|
|
228
229
|
|
|
229
|
-
|
|
230
230
|
// ---------------------------------------------------------------------------
|
|
231
231
|
// Parity helpers — code-point length, comma grouping, regex escape, JSON
|
|
232
232
|
// ---------------------------------------------------------------------------
|
|
@@ -304,8 +304,7 @@ export { RELEASE_HEAD_CAP_LINES } from './_lib/release_material.js';
|
|
|
304
304
|
*/
|
|
305
305
|
function _derive_head_prefill(prev: string | null): Record<string, string> {
|
|
306
306
|
try {
|
|
307
|
-
|
|
308
|
-
return render_derived_head_values(derive_category_hits(span));
|
|
307
|
+
return derive_head_prefill(prev, 'HEAD', REPO_ROOT);
|
|
309
308
|
} catch (err) {
|
|
310
309
|
process.stderr.write(
|
|
311
310
|
`warning: could not derive release-head highlights (${(err as Error).message}); ` +
|
|
@@ -698,93 +697,6 @@ function set_template_pin(p: string, version: string): void {
|
|
|
698
697
|
fs.writeFileSync(p, lines.join('\n'), 'utf-8');
|
|
699
698
|
}
|
|
700
699
|
|
|
701
|
-
/**
|
|
702
|
-
* Refuse a MAJOR cut that carries a scheduled deprecation due at or before it.
|
|
703
|
-
*
|
|
704
|
-
* **The trigger is the shape of the TARGET, not a comparison with the current
|
|
705
|
-
* version.** A major target is `X.0.0` — and that one predicate covers every
|
|
706
|
-
* path to a major: the `--as major` flag, an explicit `--version 13.0.0`,
|
|
707
|
-
* auto-detection from a `feat!:` commit, AND `--resume`, where
|
|
708
|
-
* `_detect_in_flight_target()` returns the already-bumped `package.json`
|
|
709
|
-
* version so `target === current` and any current-vs-target comparison
|
|
710
|
-
* silently returns. Resume was the fourth path an earlier version of this
|
|
711
|
-
* guard missed while its own comment claimed three paths converged.
|
|
712
|
-
*
|
|
713
|
-
* Refusing a resumed release can strand a partially-completed one, and that is
|
|
714
|
-
* the deliberate trade: a stranded release is recoverable by fixing the table
|
|
715
|
-
* and resuming again, whereas a major shipped over a missed commitment is the
|
|
716
|
-
* failure this whole surface exists to prevent.
|
|
717
|
-
*
|
|
718
|
-
* The target version is PASSED to the gate. Without it the gate falls back to
|
|
719
|
-
* `package.json`, which at the cut to N still reads N-1 — so a row committed
|
|
720
|
-
* to N reads as one major early and passes, and only rows already a major late
|
|
721
|
-
* could ever be refused. That is the lateness being prevented, so measuring
|
|
722
|
-
* against the shipped version would have made the refusal fire exactly one
|
|
723
|
-
* major too late, forever.
|
|
724
|
-
*
|
|
725
|
-
* Runs under `--dry-run` too, unlike the rest of preflight: this check is one
|
|
726
|
-
* subprocess reading two files, so the "keep a preview fast" rationale that
|
|
727
|
-
* excludes the ~15s test-trend collection does not apply — and a preview that
|
|
728
|
-
* reports green for the single condition that will refuse the real run is the
|
|
729
|
-
* case an operator runs a preview to discover. It REPORTS there rather than
|
|
730
|
-
* dying: `--dry-run` exiting 0 before `execute()` and before `preflight()` is a
|
|
731
|
-
* contract this file's own tests assert, and an earlier revision of this guard
|
|
732
|
-
* broke it by sitting above the dry-run branch with no preview mode.
|
|
733
|
-
*
|
|
734
|
-
* @param runner Seam for the gate invocation. Production passes nothing and
|
|
735
|
-
* gets the real `run`; tests inject a stub, because the alternative — reaching
|
|
736
|
-
* this branch only by mutating `docs/MIGRATION.md` — would make the refusal
|
|
737
|
-
* path testable exclusively through a tracked-file edit.
|
|
738
|
-
*/
|
|
739
|
-
export function assert_scheduled_deprecations_clear(
|
|
740
|
-
target: string,
|
|
741
|
-
runner: (args: readonly string[]) => RunResult = (args) => run(args, { check: false, capture: true }),
|
|
742
|
-
opts: { previewOnly?: boolean } = {},
|
|
743
|
-
): void {
|
|
744
|
-
const [, minor, patch] = parse_version(target);
|
|
745
|
-
if (minor !== 0 || patch !== 0) {
|
|
746
|
-
return;
|
|
747
|
-
}
|
|
748
|
-
const res = runner([
|
|
749
|
-
'./scripts-run',
|
|
750
|
-
'src/scripts/lint_scheduled_deprecations',
|
|
751
|
-
'--cutting',
|
|
752
|
-
target,
|
|
753
|
-
]);
|
|
754
|
-
if (res.returncode === 0) {
|
|
755
|
-
return;
|
|
756
|
-
}
|
|
757
|
-
process.stderr.write(res.stdout);
|
|
758
|
-
process.stderr.write(res.stderr);
|
|
759
|
-
if (res.returncode !== 1) {
|
|
760
|
-
// The gate reserves 1 for a finding and 2 for a usage/environment
|
|
761
|
-
// failure. Diagnosing the latter as "the table has an overdue row"
|
|
762
|
-
// sends the releaser to edit a file that is not the problem.
|
|
763
|
-
die(
|
|
764
|
-
`refusing the ${target} cut: the scheduled-deprecations check could not run ` +
|
|
765
|
-
`(exit ${String(res.returncode)}). That is an environment or usage failure, not a ` +
|
|
766
|
-
'finding in docs/MIGRATION.md — fix the invocation or the checkout, then re-run.',
|
|
767
|
-
);
|
|
768
|
-
}
|
|
769
|
-
if (opts.previewOnly === true) {
|
|
770
|
-
// The preview's job is to SHOW what the real run will refuse. Dying
|
|
771
|
-
// here would break the `--dry-run exits 0` contract asserted elsewhere
|
|
772
|
-
// in this file's tests — trading a documented exit code for a message
|
|
773
|
-
// that has already been printed above.
|
|
774
|
-
process.stderr.write(
|
|
775
|
-
`\n(dry-run) the ${target} cut WOULD BE REFUSED for the reason above. ` +
|
|
776
|
-
'Previewing only; exit code unchanged.\n',
|
|
777
|
-
);
|
|
778
|
-
return;
|
|
779
|
-
}
|
|
780
|
-
die(
|
|
781
|
-
`refusing the ${target} cut: the scheduled-deprecations table in docs/MIGRATION.md ` +
|
|
782
|
-
'has a row due at or before this major, or one that cannot be resolved. Act on ' +
|
|
783
|
-
"it — perform the removal in its own change, or revise the row's commitment and " +
|
|
784
|
-
'record why the surface stays — then re-run.',
|
|
785
|
-
);
|
|
786
|
-
}
|
|
787
|
-
|
|
788
700
|
// ─── preflight ────────────────────────────────────────────────────────────────
|
|
789
701
|
|
|
790
702
|
/**
|
|
@@ -878,6 +790,7 @@ function preflight(target: string, opts: { resume?: boolean; ci?: boolean } = {}
|
|
|
878
790
|
die(`tag '${target}' already exists; nothing to release`);
|
|
879
791
|
}
|
|
880
792
|
}
|
|
793
|
+
releaseHoldPreflight(REPO_ROOT, die);
|
|
881
794
|
}
|
|
882
795
|
|
|
883
796
|
// ─── plan ─────────────────────────────────────────────────────────────────────
|
|
@@ -1109,9 +1022,9 @@ function execute(
|
|
|
1109
1022
|
if (pr_merged) {
|
|
1110
1023
|
_step(4, total, 'PR already merged — skip push');
|
|
1111
1024
|
} else {
|
|
1112
|
-
//
|
|
1113
|
-
//
|
|
1114
|
-
//
|
|
1025
|
+
// push_release_branch reads the remote FIRST — it skips the push when
|
|
1026
|
+
// the remote is already at head (a no-op push still runs the pre-push
|
|
1027
|
+
// hook, which then refuses over pending CI) and merges one that moved.
|
|
1115
1028
|
// Not a `_step`: a second `[4/11]` makes the cited evidence anchors ambiguous.
|
|
1116
1029
|
process.stdout.write(' · verifying release gates locally (`task release:verify -- --cheap`)\n');
|
|
1117
1030
|
run(local_release_gate_argv());
|
|
@@ -1673,6 +1586,7 @@ function main(argv: readonly string[] | null = null): number {
|
|
|
1673
1586
|
head: _derive_head_prefill(prev),
|
|
1674
1587
|
mix: measure_mix_obligation(target, prev),
|
|
1675
1588
|
});
|
|
1589
|
+
assert_major_migration_section(target, full, { previewOnly: args.dry_run });
|
|
1676
1590
|
|
|
1677
1591
|
// Era-split planning — gate on the POST-release view (2026-07-07 fix).
|
|
1678
1592
|
// The drift test measures the era AFTER this release's section is
|
|
@@ -1777,6 +1691,8 @@ export {
|
|
|
1777
1691
|
bump_version,
|
|
1778
1692
|
commits_since,
|
|
1779
1693
|
infer_bump,
|
|
1694
|
+
assert_major_migration_section,
|
|
1695
|
+
assert_scheduled_deprecations_clear,
|
|
1780
1696
|
render_changelog_entry,
|
|
1781
1697
|
_changelog_line,
|
|
1782
1698
|
_cap_body,
|