@event4u/agent-config 15.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 +301 -347
- 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 +77 -29
- 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 +97 -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/rules/user-interaction.md +31 -30
- 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/error-handling-patterns/SKILL.md +9 -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-NUHLI2RX.js +80 -0
- package/dist/cli-delegate/{chunk-HZ52BRGG.js → chunk-P6ZHP5W2.js} +15 -90
- package/dist/cli-delegate/{chunk-TYSY33MD.js → chunk-QJWAUMJW.js} +12 -2
- 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-2KKXJW25.js → chunk-S3VFXMTM.js} +1 -1
- 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 +918 -325
- package/dist/cli-delegate/cmd_doctor.js +7 -5
- package/dist/cli-delegate/cmd_explain.js +5 -3
- 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 +6 -6
- 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 +3 -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 +4 -4
- 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 +14 -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 +87 -40
- 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 +2601 -1073
- 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 +2008 -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 +1612 -667
- 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_lock.js +1 -1
- 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/io/yamlIO.js +50 -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 +28 -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/host-injection-effect.json +1 -1
- package/src/config/loop-surfaces.yaml +215 -0
- package/src/config/platform-anchor.json +3 -2
- package/src/config/preamble-payload-budget.json +12 -7
- package/src/config/preamble-payload-exceptions.json +28 -0
- 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/_dispatch.bash +3 -1
- 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/base_ref_payload.ts +315 -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/dropped_decision.ts +142 -0
- package/src/scripts/_lib/forge_protection.ts +267 -0
- package/src/scripts/_lib/hook_platform_keys.ts +27 -0
- package/src/scripts/_lib/installed_lock.ts +1 -1
- 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/measured_payload_ceiling.ts +424 -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/payload_catalogue_completeness.ts +211 -0
- package/src/scripts/_lib/release_findings_ingest.ts +324 -0
- 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/release_tag_dispatch.ts +63 -0
- package/src/scripts/_lib/review_baseline.ts +254 -0
- package/src/scripts/_lib/review_skipped_record.ts +61 -1
- package/src/scripts/_lib/rides_along.ts +102 -0
- package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
- package/src/scripts/_lib/standing_bound_ratchet.ts +216 -103
- package/src/scripts/_lib/test_delta.ts +166 -0
- package/src/scripts/_lib/test_provenance.ts +171 -0
- package/src/scripts/_lib/turn_end_refusals.ts +51 -4
- 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_beta_review_markers.ts +35 -4
- package/src/scripts/check_enforcement_matrix.ts +624 -0
- package/src/scripts/check_finding_dispositions.ts +167 -16
- package/src/scripts/check_gate_reachability.ts +173 -10
- package/src/scripts/check_generator_sync.ts +954 -0
- package/src/scripts/check_held_object_arrivals.ts +381 -0
- package/src/scripts/check_preamble_payload_budget.ts +474 -124
- package/src/scripts/check_release_highlights.ts +33 -1
- package/src/scripts/check_release_holds.ts +567 -0
- package/src/scripts/check_release_published.ts +1 -1
- package/src/scripts/check_reply_consistency.ts +86 -17
- 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 +127 -12
- package/src/scripts/hooks/block_kernel_rule_writes.ts +12 -2
- package/src/scripts/hooks/concern_registry.ts +6 -0
- package/src/scripts/hooks/design_pass_hook.ts +128 -3
- package/src/scripts/hooks/end_review_nudge_hook.ts +101 -21
- 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/review_baseline_hook.ts +130 -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/turn_end_gate_hook.ts +232 -14
- package/src/scripts/hooks_doctor.ts +89 -1
- package/src/scripts/hooks_status.ts +19 -0
- package/src/scripts/install.ts +107 -207
- package/src/scripts/interruption_report.ts +8 -2
- package/src/scripts/janitor.ts +15 -1
- 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/measure_turn_end_gate.ts +302 -5
- package/src/scripts/new_roadmap.ts +37 -0
- package/src/scripts/orchestration_record.ts +0 -5
- package/src/scripts/release.ts +66 -162
- package/src/scripts/release_drill.ts +460 -15
- package/src/scripts/release_env.ts +1 -1
- package/src/scripts/release_publication.ts +52 -1
- package/src/scripts/report_enforcement_drift.ts +566 -0
- package/src/scripts/report_held_object_arrivals.ts +456 -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/sync_agent_settings.ts +140 -18
- package/src/scripts/trigger_eval_grandfather.json +0 -1
- package/src/scripts/ui_conformance_probe.ts +622 -0
- package/src/server/io/yamlIO.ts +48 -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
|
@@ -82,6 +82,10 @@ function _strip_codespans(line: string): string {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/** Split on \n / \r\n / \r, dropping a single trailing empty element. */
|
|
85
|
+
export function splitAndMask(text: string): string[] {
|
|
86
|
+
return mask_fences(_splitlines(text));
|
|
87
|
+
}
|
|
88
|
+
|
|
85
89
|
function _splitlines(text: string): string[] {
|
|
86
90
|
if (text === '') return [];
|
|
87
91
|
const parts = text.split(/\r\n|\r|\n/);
|
|
@@ -143,6 +147,64 @@ export function find_option_blocks(text: string): OptionBlock[] {
|
|
|
143
147
|
return blocks;
|
|
144
148
|
}
|
|
145
149
|
|
|
150
|
+
/** A recommendation line found in a block's adjacency window. */
|
|
151
|
+
export interface RecommendationLine {
|
|
152
|
+
/** 1-based line of the recommendation. */
|
|
153
|
+
line: number;
|
|
154
|
+
/** The number it names. Not necessarily one of the block's options. */
|
|
155
|
+
num: number;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The recommendation line(s) sitting in ONE block's adjacency window.
|
|
160
|
+
*
|
|
161
|
+
* Extracted from `check_reply` rather than copied, because it has a second
|
|
162
|
+
* caller: `turn_end_gate_hook`'s `pending-decision` detector needs the same
|
|
163
|
+
* question this answers — is this numbered block an ASK, or is it a narrative
|
|
164
|
+
* list? Iron Law 1 says the recommendation line IS the ask, so a block without
|
|
165
|
+
* one was never a decision put to the user.
|
|
166
|
+
*
|
|
167
|
+
* That distinction is not decoration. Replayed over this repository's own 592
|
|
168
|
+
* assistant turns, a detector keyed on the block alone fired 29 times and 8 of
|
|
169
|
+
* those blocks carried no recommendation line anywhere near them — plans,
|
|
170
|
+
* findings lists, ordinary enumerations. Requiring the line removes that whole
|
|
171
|
+
* class, and it removes it by applying the rule the gate already cites rather
|
|
172
|
+
* than by adding a heuristic beside it.
|
|
173
|
+
*
|
|
174
|
+
* Returns every recommendation in the window, not the first: `check_reply`
|
|
175
|
+
* treats two as its own finding, and a caller that only wanted existence can
|
|
176
|
+
* read `.length > 0`.
|
|
177
|
+
*/
|
|
178
|
+
export function recommendationsUnder(
|
|
179
|
+
text: string,
|
|
180
|
+
block: OptionBlock,
|
|
181
|
+
maskedLines?: readonly string[],
|
|
182
|
+
): RecommendationLine[] {
|
|
183
|
+
// `maskedLines` lets a caller with several blocks over one text mask once
|
|
184
|
+
// instead of once per block. It is the SAME array `check_reply` and
|
|
185
|
+
// `find_option_blocks` compute; passing anything else silently changes the
|
|
186
|
+
// line numbers this returns, which is why it is an optimisation parameter
|
|
187
|
+
// and not part of the contract.
|
|
188
|
+
const lines = maskedLines ?? mask_fences(_splitlines(text));
|
|
189
|
+
const recs: RecommendationLine[] = [];
|
|
190
|
+
let inspected = 0;
|
|
191
|
+
for (let idx = block.endLine; idx < lines.length && inspected < REC_ADJACENCY_WINDOW; idx++) {
|
|
192
|
+
const line = lines[idx]!;
|
|
193
|
+
if (line.trim() === '') {
|
|
194
|
+
continue; // blank lines do not consume the adjacency window
|
|
195
|
+
}
|
|
196
|
+
if (HEADING_RE.test(line) || OPTION_LINE_RE.test(line)) {
|
|
197
|
+
break; // a new heading or a new options block ends this block's window
|
|
198
|
+
}
|
|
199
|
+
inspected += 1;
|
|
200
|
+
const m = REC_LINE_RE.exec(line);
|
|
201
|
+
if (m) {
|
|
202
|
+
recs.push({ line: idx + 1, num: parseInt(m[1]!, 10) });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return recs;
|
|
206
|
+
}
|
|
207
|
+
|
|
146
208
|
/**
|
|
147
209
|
* Core check — the exported function tests drive. Returns all findings for
|
|
148
210
|
* one reply draft; empty array = the draft is consistent.
|
|
@@ -166,22 +228,7 @@ export function check_reply(text: string): Finding[] {
|
|
|
166
228
|
// Per-block: exactly one adjacent recommendation line, number in range.
|
|
167
229
|
const blocks = find_option_blocks(text);
|
|
168
230
|
for (const block of blocks) {
|
|
169
|
-
const recs
|
|
170
|
-
let inspected = 0;
|
|
171
|
-
for (let idx = block.endLine; idx < lines.length && inspected < REC_ADJACENCY_WINDOW; idx++) {
|
|
172
|
-
const line = lines[idx]!;
|
|
173
|
-
if (line.trim() === '') {
|
|
174
|
-
continue; // blank lines do not consume the adjacency window
|
|
175
|
-
}
|
|
176
|
-
if (HEADING_RE.test(line) || OPTION_LINE_RE.test(line)) {
|
|
177
|
-
break; // a new heading or a new options block ends this block's window
|
|
178
|
-
}
|
|
179
|
-
inspected += 1;
|
|
180
|
-
const m = REC_LINE_RE.exec(line);
|
|
181
|
-
if (m) {
|
|
182
|
-
recs.push({ line: idx + 1, num: parseInt(m[1]!, 10) });
|
|
183
|
-
}
|
|
184
|
-
}
|
|
231
|
+
const recs = recommendationsUnder(text, block);
|
|
185
232
|
if (recs.length === 0) {
|
|
186
233
|
findings.push({
|
|
187
234
|
line: block.startLine,
|
|
@@ -421,7 +468,24 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
421
468
|
return 2;
|
|
422
469
|
}
|
|
423
470
|
|
|
471
|
+
// Bundle-safety: never auto-run when inlined into an esbuild bundle, where
|
|
472
|
+
// every module shares the bundle's `import.meta.url`.
|
|
473
|
+
//
|
|
474
|
+
// This module is not a hook, and until `turn_end_gate_hook` began importing
|
|
475
|
+
// `find_option_blocks` / `recommendationsUnder` from it, it was never inside
|
|
476
|
+
// one. It is now pulled into `dist/hooks/dispatch.js`, where without this guard
|
|
477
|
+
// the `import.meta.url === argvUrl` comparison below is TRUE for every bundled
|
|
478
|
+
// module — so importing this file would call `process.exit(main())` and take the
|
|
479
|
+
// whole dispatch down at import time. The build banner's `.__direct__` argv
|
|
480
|
+
// rewrite also prevents it, and a comment in the gate says so; a guard the other
|
|
481
|
+
// bundled entries all carry is the load-bearing half, and depending on the
|
|
482
|
+
// banner alone is depending on something no test pins.
|
|
483
|
+
declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
|
|
484
|
+
|
|
424
485
|
function _isCliEntry(): boolean {
|
|
486
|
+
if (typeof __AGENT_CONFIG_BUNDLE__ !== 'undefined' && __AGENT_CONFIG_BUNDLE__) {
|
|
487
|
+
return false;
|
|
488
|
+
}
|
|
425
489
|
if (process.argv[1] === undefined) {
|
|
426
490
|
return false;
|
|
427
491
|
}
|
|
@@ -442,7 +506,12 @@ function _isCliEntry(): boolean {
|
|
|
442
506
|
}
|
|
443
507
|
}
|
|
444
508
|
|
|
445
|
-
|
|
509
|
+
// The `|| process.argv[1] === _HERE` arm is the reason the bundle check lives in
|
|
510
|
+
// BOTH places: inside a bundle `_HERE` resolves to the bundle's own path and so
|
|
511
|
+
// does `process.argv[1]`, so this arm would be true and would route straight past
|
|
512
|
+
// a guard that only sat in `_isCliEntry`.
|
|
513
|
+
const _IN_BUNDLE = typeof __AGENT_CONFIG_BUNDLE__ !== 'undefined' && __AGENT_CONFIG_BUNDLE__;
|
|
514
|
+
if (!_IN_BUNDLE && (_isCliEntry() || process.argv[1] === _HERE)) {
|
|
446
515
|
process.exit(main());
|
|
447
516
|
}
|
|
448
517
|
|
|
@@ -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
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* check_test_delta — a code change with no credible test delta is a finding.
|
|
4
|
+
*
|
|
5
|
+
* `road-to-adversarial-verification-and-long-runs` Phase 2.4, first of two. The
|
|
6
|
+
* rule `test-first` states that a behaviour change gets a failing test first;
|
|
7
|
+
* nothing could see whether one arrived. This gate sees the cheap half: a diff
|
|
8
|
+
* that touches production code and touches no test at all.
|
|
9
|
+
*
|
|
10
|
+
* **What it deliberately cannot see.** Whether the test came FIRST, and whether
|
|
11
|
+
* it tests the thing that changed. Both are judgements about intent and content
|
|
12
|
+
* that no diff carries, and claiming them here would be the coverage inflation
|
|
13
|
+
* `evaluator-independence` exists over. This gate proves an ABSENCE, which is
|
|
14
|
+
* the one thing about a diff that is mechanically decidable.
|
|
15
|
+
*
|
|
16
|
+
* **The exemption is owner-set, and that is Risk 4's mitigation.** A gate that
|
|
17
|
+
* reds a legitimate PR invites the cheapest repair — widening the exemption until
|
|
18
|
+
* the gate finds nothing. So the escape is a PR LABEL a human applies, mirroring
|
|
19
|
+
* `check_kernel_rule_bundle`'s `bundled-always-rules-acknowledged`: it is visible
|
|
20
|
+
* on the PR, it is one click for the owner, and no agent can apply it by editing
|
|
21
|
+
* a file in the diff under review.
|
|
22
|
+
*
|
|
23
|
+
* Usage:
|
|
24
|
+
* check_test_delta [--files F …] [--base-ref REF] [--root DIR]
|
|
25
|
+
* [--label NAME] [--event-path P] [--quiet] [--self-test]
|
|
26
|
+
*
|
|
27
|
+
* Exit codes: 0 = pass (or exempted) · 1 = fail · 2 = usage error / dead scope.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { spawnSync } from 'node:child_process';
|
|
31
|
+
import * as fs from 'node:fs';
|
|
32
|
+
import * as os from 'node:os';
|
|
33
|
+
import * as path from 'node:path';
|
|
34
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
35
|
+
|
|
36
|
+
import { GateLedger } from './_lib/gate_ledger.js';
|
|
37
|
+
import { runGateCli, runSelfTest } from './_lib/gate_self_test.js';
|
|
38
|
+
import { reportScanned } from './_lib/scan_scope.js';
|
|
39
|
+
import { isCodePath, isTestPath } from './_lib/test_delta.js';
|
|
40
|
+
|
|
41
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
42
|
+
const REPO = path.resolve(path.dirname(_HERE), '..', '..');
|
|
43
|
+
|
|
44
|
+
/** The label an owner applies to accept a code change with no test delta. */
|
|
45
|
+
export const DEFAULT_LABEL = 'test-delta-acknowledged';
|
|
46
|
+
|
|
47
|
+
export interface DeltaVerdict {
|
|
48
|
+
readonly code: string[];
|
|
49
|
+
readonly tests: string[];
|
|
50
|
+
readonly scanned: number;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Classify a changed-file list. Pure — the whole decision, without I/O. */
|
|
54
|
+
export function classify(files: readonly string[]): DeltaVerdict {
|
|
55
|
+
return {
|
|
56
|
+
code: files.filter(isCodePath),
|
|
57
|
+
tests: files.filter(isTestPath),
|
|
58
|
+
scanned: files.length,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The finding, or `null`. Separated from rendering so a test reads the decision. */
|
|
63
|
+
export function findingFor(v: DeltaVerdict): string | null {
|
|
64
|
+
if (v.code.length === 0) return null;
|
|
65
|
+
if (v.tests.length > 0) return null;
|
|
66
|
+
const shown = v.code.slice(0, 5).join(', ');
|
|
67
|
+
const more = v.code.length > 5 ? `, +${String(v.code.length - 5)} more` : '';
|
|
68
|
+
return (
|
|
69
|
+
`${String(v.code.length)} code path(s) changed and no test path did: ${shown}${more}. ` +
|
|
70
|
+
`A behaviour change gets a test (src/rules/test-first.md). If this change genuinely ` +
|
|
71
|
+
`has no testable behaviour, apply the \`${DEFAULT_LABEL}\` label to the PR — that is ` +
|
|
72
|
+
`an owner decision, deliberately not a file an agent can edit inside this diff.`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function gitChangedFiles(baseRef: string, root: string): string[] | null {
|
|
77
|
+
const r = spawnSync('git', ['diff', '--name-only', `${baseRef}...HEAD`], {
|
|
78
|
+
cwd: root,
|
|
79
|
+
encoding: 'utf8',
|
|
80
|
+
});
|
|
81
|
+
if (r.status !== 0) return null;
|
|
82
|
+
return r.stdout
|
|
83
|
+
.split('\n')
|
|
84
|
+
.map((s) => s.trim())
|
|
85
|
+
.filter((s) => s !== '');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function resolveBaseRef(explicit: string | null, root: string): string {
|
|
89
|
+
if (explicit !== null) return explicit;
|
|
90
|
+
for (const ref of ['origin/main', 'origin/master', 'main', 'master']) {
|
|
91
|
+
const r = spawnSync('git', ['rev-parse', '--verify', '--quiet', ref], {
|
|
92
|
+
cwd: root,
|
|
93
|
+
encoding: 'utf8',
|
|
94
|
+
});
|
|
95
|
+
if (r.status === 0) return ref;
|
|
96
|
+
}
|
|
97
|
+
return 'HEAD~1';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** PR labels from the GitHub event JSON, or `[]` when there is none to read. */
|
|
101
|
+
export function labelsFrom(eventPath: string | null): string[] {
|
|
102
|
+
if (eventPath === null || eventPath === '') return [];
|
|
103
|
+
try {
|
|
104
|
+
const raw = JSON.parse(fs.readFileSync(eventPath, 'utf8')) as Record<string, unknown>;
|
|
105
|
+
const pr = raw['pull_request'] as { labels?: unknown } | undefined;
|
|
106
|
+
const labels = Array.isArray(pr?.labels) ? pr.labels : [];
|
|
107
|
+
return labels
|
|
108
|
+
.map((l) => (l as { name?: unknown }).name)
|
|
109
|
+
.filter((n): n is string => typeof n === 'string');
|
|
110
|
+
} catch {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function usage(): string {
|
|
116
|
+
return (
|
|
117
|
+
'usage: check_test_delta [--files F …] [--base-ref REF] [--root DIR]\n' +
|
|
118
|
+
' [--label NAME] [--event-path P] [--quiet] [--self-test]\n'
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
123
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
124
|
+
process.stdout.write(usage());
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
if (argv.includes('--self-test')) return selfTest();
|
|
128
|
+
|
|
129
|
+
const quiet = argv.includes('--quiet');
|
|
130
|
+
const at = (flag: string): string | null => {
|
|
131
|
+
const i = argv.indexOf(flag);
|
|
132
|
+
return i >= 0 ? (argv[i + 1] ?? null) : null;
|
|
133
|
+
};
|
|
134
|
+
const root = at('--root') ?? REPO;
|
|
135
|
+
const label = at('--label') ?? DEFAULT_LABEL;
|
|
136
|
+
const eventPath = at('--event-path') ?? process.env['GITHUB_EVENT_PATH'] ?? null;
|
|
137
|
+
|
|
138
|
+
let files: string[];
|
|
139
|
+
const fi = argv.indexOf('--files');
|
|
140
|
+
if (fi >= 0) {
|
|
141
|
+
files = argv.slice(fi + 1).filter((a) => !a.startsWith('--'));
|
|
142
|
+
} else {
|
|
143
|
+
const fromGit = gitChangedFiles(resolveBaseRef(at('--base-ref'), root), root);
|
|
144
|
+
if (fromGit === null) {
|
|
145
|
+
process.stderr.write('check_test_delta: git diff failed\n');
|
|
146
|
+
return 2;
|
|
147
|
+
}
|
|
148
|
+
files = fromGit;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Per-path accounting, so "no code changed" and "code changed and nothing
|
|
152
|
+
// was looked at" stop printing the same green line. Every changed path is a
|
|
153
|
+
// planned target: a code path completes or fails, and everything else is out
|
|
154
|
+
// of scope by classification rather than silently skipped.
|
|
155
|
+
const ledger = new GateLedger('check_test_delta');
|
|
156
|
+
ledger.plan(files.map((f) => `path:${f}`));
|
|
157
|
+
|
|
158
|
+
const verdict = classify(files);
|
|
159
|
+
const finding0 = findingFor(verdict);
|
|
160
|
+
for (const f of files) {
|
|
161
|
+
const target = `path:${f}`;
|
|
162
|
+
if (!isCodePath(f)) {
|
|
163
|
+
ledger.outOfScope(target, 'not_applicable_kind');
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (finding0 === null) ledger.complete(target);
|
|
167
|
+
else ledger.fail(target, `${f}: no test path changed alongside it`);
|
|
168
|
+
}
|
|
169
|
+
ledger.report();
|
|
170
|
+
|
|
171
|
+
reportScanned({
|
|
172
|
+
gate: 'check_test_delta',
|
|
173
|
+
scanned: verdict.scanned,
|
|
174
|
+
units: 'changed path(s)',
|
|
175
|
+
roots: ['<the diff against the base ref>'],
|
|
176
|
+
// A diff with nothing in it is a real state — a docs-only branch, or a
|
|
177
|
+
// re-run against an already-merged base — and it is not this gate's job
|
|
178
|
+
// to red on it. Declared rather than silently passing a zero.
|
|
179
|
+
allowEmpty: 'EMPTY_VALID: a diff with no changed files carries no code change to gate',
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const finding = finding0;
|
|
183
|
+
if (finding === null) {
|
|
184
|
+
if (!quiet) {
|
|
185
|
+
process.stdout.write(
|
|
186
|
+
`✅ check_test_delta: ${String(verdict.code.length)} code path(s), ` +
|
|
187
|
+
`${String(verdict.tests.length)} test path(s)\n`,
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
return 0;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (labelsFrom(eventPath).includes(label)) {
|
|
194
|
+
process.stdout.write(
|
|
195
|
+
`✅ check_test_delta: exempted by the \`${label}\` label — ` +
|
|
196
|
+
'an owner accepted a code change with no test delta.\n',
|
|
197
|
+
);
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
process.stderr.write(`❌ check_test_delta: ${finding}\n`);
|
|
202
|
+
return 1;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function selfTest(): number {
|
|
206
|
+
const run = (files: Record<string, string>, args: readonly string[] = []): number => {
|
|
207
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'ctd-'));
|
|
208
|
+
try {
|
|
209
|
+
return runGateCli(
|
|
210
|
+
REPO,
|
|
211
|
+
'src/scripts/check_test_delta.ts',
|
|
212
|
+
['--root', dir, '--quiet', ...args],
|
|
213
|
+
dir,
|
|
214
|
+
);
|
|
215
|
+
} finally {
|
|
216
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
217
|
+
void files;
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
const eventWith = (labels: string[]): string => {
|
|
221
|
+
const f = path.join(
|
|
222
|
+
fs.mkdtempSync(path.join(os.tmpdir(), 'ctd-ev-')),
|
|
223
|
+
'event.json',
|
|
224
|
+
);
|
|
225
|
+
fs.writeFileSync(f, JSON.stringify({ pull_request: { labels: labels.map((name) => ({ name })) } }));
|
|
226
|
+
return f;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
return runSelfTest({
|
|
230
|
+
gate: 'check_test_delta',
|
|
231
|
+
minCases: 6,
|
|
232
|
+
minRejectCases: 2,
|
|
233
|
+
cases: [
|
|
234
|
+
{
|
|
235
|
+
name: 'code change with no test delta is REJECTED',
|
|
236
|
+
expect: 'reject',
|
|
237
|
+
run: () => run({}, ['--files', 'src/scripts/thing.ts']),
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: 'several code changes with no test delta are REJECTED',
|
|
241
|
+
expect: 'reject',
|
|
242
|
+
run: () => run({}, ['--files', 'src/a.ts', 'src/b.ts', 'docs/x.md']),
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: 'code change WITH a test delta passes',
|
|
246
|
+
expect: 'accept',
|
|
247
|
+
run: () => run({}, ['--files', 'src/scripts/thing.ts', 'tests/scripts/thing.test.ts']),
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: 'a co-located *.test.ts counts as the test delta',
|
|
251
|
+
expect: 'accept',
|
|
252
|
+
run: () => run({}, ['--files', 'src/a.ts', 'src/a.test.ts']),
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
name: 'docs-only change is not a code change',
|
|
256
|
+
expect: 'accept',
|
|
257
|
+
run: () => run({}, ['--files', 'docs/x.md', 'README.md']),
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: 'a generated projection is not an independent code change',
|
|
261
|
+
expect: 'accept',
|
|
262
|
+
run: () => run({}, ['--files', 'dist/agent-src/scripts/thing.ts']),
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: 'the owner label exempts a real finding',
|
|
266
|
+
expect: 'accept',
|
|
267
|
+
run: () =>
|
|
268
|
+
run({}, [
|
|
269
|
+
'--files',
|
|
270
|
+
'src/scripts/thing.ts',
|
|
271
|
+
'--event-path',
|
|
272
|
+
eventWith([DEFAULT_LABEL]),
|
|
273
|
+
]),
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
name: 'an unrelated label does NOT exempt it',
|
|
277
|
+
expect: 'reject',
|
|
278
|
+
run: () =>
|
|
279
|
+
run({}, [
|
|
280
|
+
'--files',
|
|
281
|
+
'src/scripts/thing.ts',
|
|
282
|
+
'--event-path',
|
|
283
|
+
eventWith(['documentation']),
|
|
284
|
+
]),
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/* c8 ignore start — CLI entry, exercised by the self-test rather than in-process. */
|
|
291
|
+
if (import.meta.url === pathToFileURL(path.resolve(process.argv[1] ?? '')).href) {
|
|
292
|
+
process.exit(main());
|
|
293
|
+
}
|
|
294
|
+
/* c8 ignore stop */
|