@event4u/agent-config 9.13.0 → 9.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +113 -67
- package/README.md +9 -1
- package/dist/agent-src/commands/agents/audit.md +7 -7
- package/dist/agent-src/commands/agents/optimize.md +1 -1
- package/dist/agent-src/commands/analyze/reference-repo.md +1 -1
- package/dist/agent-src/commands/chat-history.md +1 -0
- package/dist/agent-src/commands/context.md +1 -0
- package/dist/agent-src/commands/council.md +1 -1
- package/dist/agent-src/commands/ghostwriter.md +1 -1
- package/dist/agent-src/commands/memory.md +2 -1
- package/dist/agent-src/commands/module.md +1 -0
- package/dist/agent-src/commands/optimize/agents-dir.md +1 -1
- package/dist/agent-src/commands/optimize/deep.md +150 -0
- package/dist/agent-src/commands/optimize/skills.md +5 -5
- package/dist/agent-src/commands/optimize.md +12 -6
- package/dist/agent-src/commands/override.md +1 -0
- package/dist/agent-src/commands/prediction-pool.md +4 -4
- package/dist/agent-src/commands/research/deep.md +1 -1
- package/dist/agent-src/commands/roadmap.md +2 -1
- package/dist/agent-src/commands/rule-compliance-audit.md +1 -1
- package/dist/agent-src/commands/tdd.md +1 -0
- package/dist/agent-src/commands/tests.md +1 -0
- package/dist/agent-src/commands/upstream-contribute.md +5 -5
- package/dist/agent-src/commands/video/from-script.md +1 -1
- package/dist/agent-src/commands/video/from-song.md +1 -1
- package/dist/agent-src/commands/video/scene.md +1 -1
- package/dist/agent-src/commands/video/stitch.md +1 -1
- package/dist/agent-src/commands/video/storyboard.md +1 -1
- package/dist/agent-src/commands/worktree/create.md +2 -1
- package/dist/agent-src/contexts/augment-infrastructure.md +1 -1
- package/dist/agent-src/contexts/contracts/artifact-engagement-flow.md +5 -5
- package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +3 -3
- package/dist/agent-src/contexts/contracts/emergency-triage-block.md +2 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +4 -3
- package/dist/agent-src/contexts/execution/subagent-routing.md +19 -0
- package/dist/agent-src/contexts/override-system.md +1 -1
- package/dist/agent-src/ghostwriter/fictional-fixture-v1.md +1 -1
- package/dist/agent-src/rules/augment-edit-discipline.md +1 -1
- package/dist/agent-src/rules/autonomous-execution.md +2 -1
- package/dist/agent-src/rules/brand-consistency.md +1 -0
- package/dist/agent-src/rules/brand-source-of-truth.md +1 -0
- package/dist/agent-src/rules/code-comment-discipline.md +2 -0
- package/dist/agent-src/rules/communication-through-line.md +1 -0
- package/dist/agent-src/rules/content-quoting-floor.md +1 -0
- package/dist/agent-src/rules/cross-source-consistency.md +0 -1
- package/dist/agent-src/rules/downstream-changes.md +1 -0
- package/dist/agent-src/rules/external-code-graph-interop.md +1 -0
- package/dist/agent-src/rules/external-reference-deep-dive.md +1 -0
- package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
- package/dist/agent-src/rules/finance-safety-floor.md +0 -1
- package/dist/agent-src/rules/icon-consistency.md +1 -0
- package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
- package/dist/agent-src/rules/improve-before-implement.md +1 -1
- package/dist/agent-src/rules/invite-challenge.md +1 -0
- package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
- package/dist/agent-src/rules/markdown-safe-codeblocks.md +1 -0
- package/dist/agent-src/rules/media-governance-routing.md +4 -3
- package/dist/agent-src/rules/media-sync-ground-truth.md +1 -0
- package/dist/agent-src/rules/no-attribution-footers.md +1 -0
- package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +1 -0
- package/dist/agent-src/rules/no-pr-progress-comments.md +1 -0
- package/dist/agent-src/rules/onboarding-gate.md +1 -0
- package/dist/agent-src/rules/output-discipline.md +1 -0
- package/dist/agent-src/rules/prefer-enums-over-literals.md +1 -0
- package/dist/agent-src/rules/preservation-guard.md +1 -0
- package/dist/agent-src/rules/question-not-instruction.md +1 -0
- package/dist/agent-src/rules/roadmap-ci-steps-policy.md +1 -0
- package/dist/agent-src/rules/role-mode-adherence.md +1 -0
- package/dist/agent-src/rules/runtime-safety.md +1 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +1 -0
- package/dist/agent-src/rules/session-canary.md +9 -2
- package/dist/agent-src/rules/source-confidentiality.md +1 -0
- package/dist/agent-src/rules/source-of-truth.md +1 -0
- package/dist/agent-src/rules/spreadsheet-source-quality.md +1 -0
- package/dist/agent-src/rules/think-before-action.md +1 -0
- package/dist/agent-src/rules/token-budget-discipline.md +1 -0
- package/dist/agent-src/rules/token-efficiency.md +1 -0
- package/dist/agent-src/rules/token-optimizer-maintenance.md +5 -5
- package/dist/agent-src/rules/tool-safety.md +1 -0
- package/dist/agent-src/rules/untrusted-input-defense.md +2 -0
- package/dist/agent-src/rules/user-interaction.md +1 -0
- package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
- package/dist/agent-src/skills/adr-create/SKILL.md +1 -1
- package/dist/agent-src/skills/check-refs/SKILL.md +1 -1
- package/dist/agent-src/skills/competitive-positioning/SKILL.md +1 -1
- package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
- package/dist/agent-src/skills/context-authoring/SKILL.md +1 -1
- package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +1 -1
- package/dist/agent-src/skills/description-assist/SKILL.md +1 -1
- package/dist/agent-src/skills/github-ci/SKILL.md +1 -0
- package/dist/agent-src/skills/guideline-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/lint-skills/SKILL.md +1 -1
- package/dist/agent-src/skills/readme-reviewer/SKILL.md +1 -1
- package/dist/agent-src/skills/roadmap-management/SKILL.md +8 -1
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-writing/SKILL.md +4 -4
- package/dist/agent-src/skills/token-optimizer/SKILL.md +4 -4
- package/dist/agent-src/skills/upstream-contribute/SKILL.md +6 -6
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +25 -0
- package/dist/agent-src/templates/AGENTS.md +1 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/command.md +1 -1
- package/dist/agent-src/templates/copilot-instructions.md +3 -3
- package/dist/agent-src/templates/rule.md +2 -2
- package/dist/agent-src/templates/scripts/work_engine/orchestration.ts +1 -1
- package/dist/agent-src/templates/skill-archive-note.md +5 -5
- package/dist/agent-src/templates/skill.md +1 -1
- package/dist/cli/agent-config.js +66 -19
- package/dist/cli/agent-config.js.map +1 -1
- package/dist/cli/main.js +9 -4
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/python/workspace_drive.js +2 -0
- package/dist/cli/python/workspace_drive.js.map +1 -1
- package/dist/cli/python/workspace_drive_health.js +2 -0
- package/dist/cli/python/workspace_drive_health.js.map +1 -1
- package/dist/cli/python/workspace_hosts.js +2 -0
- package/dist/cli/python/workspace_hosts.js.map +1 -1
- package/dist/cli/python/workspace_sessions.js +2 -0
- package/dist/cli/python/workspace_sessions.js.map +1 -1
- package/dist/cli/registry.js +1 -0
- package/dist/cli/registry.js.map +1 -1
- package/dist/cli-delegate/{chunk-A3HJ6NTI.js → chunk-63WSP6YZ.js} +2 -1
- package/dist/cli-delegate/{chunk-EJYIGZ3T.js → chunk-BKO2AZ7Z.js} +1 -1
- package/dist/cli-delegate/{chunk-QFKQLQ7X.js → chunk-H574KOUH.js} +340 -462
- package/dist/cli-delegate/chunk-NXFWFAYV.js +339 -0
- package/dist/cli-delegate/{chunk-TEGJU2JX.js → chunk-ZXXJLL5K.js} +2 -2
- package/dist/cli-delegate/cmd_conformance.js +29 -23
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_export.js +4 -4
- package/dist/cli-delegate/cmd_preflight.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +4 -4
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +163 -103
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +6 -5
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +5 -4
- package/dist/hooks/dispatch.js +729 -430
- package/dist/install/install.mjs +700 -625
- package/dist/install/rule_scope.js +18 -2
- package/dist/install/rule_scope.js.map +1 -1
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +564 -424
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/claude_settings_hooks.js +30 -5
- package/dist/scripts/_lib/claude_settings_hooks.js.map +1 -1
- package/dist/scripts/_lib/scoped_projection.js +223 -0
- package/dist/scripts/_lib/scoped_projection.js.map +1 -0
- package/dist/scripts/_lib/surface_tiers.js +125 -0
- package/dist/scripts/_lib/surface_tiers.js.map +1 -0
- package/dist/scripts/_lib/token_count.js +166 -0
- package/dist/scripts/_lib/token_count.js.map +1 -0
- package/dist/scripts/_lib/trigger_eval_floors.js +8 -0
- package/dist/scripts/_lib/trigger_eval_floors.js.map +1 -1
- package/dist/scripts/condense.js +139 -9
- package/dist/scripts/condense.js.map +1 -1
- package/dist/scripts/project_thin_rules.js +450 -0
- package/dist/scripts/project_thin_rules.js.map +1 -0
- package/dist/server/routes/workspace.js +2 -0
- package/dist/server/routes/workspace.js.map +1 -1
- package/dist/server/schemas/settings.js +9 -3
- package/dist/server/schemas/settings.js.map +1 -1
- package/dist/ui/assets/index-DJY_vQVn.js +40 -0
- package/dist/ui/assets/index-DJY_vQVn.js.map +1 -0
- package/dist/ui/index.html +1 -1
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
- package/package.json +11 -11
- package/src/agent-src/templates/AGENTS.md +1 -1
- package/src/agent-src/templates/scripts/work_engine/orchestration.ts +1 -1
- package/src/config/agent-settings.template.yml +59 -11
- package/src/config/ci-local-parity.yml +8 -0
- package/src/config/discovery/unassigned-artefacts.yml +23 -25
- package/src/config/evaluator-budgets.json +35 -5
- package/src/config/gate-coverage.yml +147 -0
- package/src/config/gate-violation-baselines.json +25 -0
- package/src/install/rule_scope.ts +27 -2
- package/src/scripts/_cli/cmd_conformance.ts +13 -24
- package/src/scripts/_dispatch.bash +10 -0
- package/src/scripts/_lib/claude_settings_hooks.ts +30 -4
- package/src/scripts/_lib/exec_evidence.ts +5 -0
- package/src/scripts/_lib/gate_baseline.ts +213 -0
- package/src/scripts/_lib/scoped_projection.ts +262 -0
- package/src/scripts/_lib/tier_budget_routing.ts +234 -0
- package/src/scripts/_lib/tool_adapter_registry.ts +8 -1
- package/src/scripts/_lib/trigger_eval_floors.ts +9 -0
- package/src/scripts/_lib/trigger_routers.ts +17 -5
- package/src/scripts/adr/regenerate_index.ts +4 -3
- package/src/scripts/ai-video/lib/parse-blueprint.sh +1 -1
- package/src/scripts/ai_council/compile_corpus.ts +1 -1
- package/src/scripts/audit_cloud_compatibility.ts +37 -2
- package/src/scripts/audit_initial_context.ts +53 -19
- package/src/scripts/audit_likelihood.ts +31 -2
- package/src/scripts/audit_user_type_axis.ts +58 -11
- package/src/scripts/bench_hook_latency.ts +172 -41
- package/src/scripts/build_mcp_catalog.ts +12 -1
- package/src/scripts/check_always_budget.ts +122 -11
- package/src/scripts/check_artefact_count_messaging.ts +128 -15
- package/src/scripts/check_augment_description_cap.ts +24 -3
- package/src/scripts/check_ci_strict_superset.ts +150 -0
- package/src/scripts/check_cluster_patterns.ts +100 -0
- package/src/scripts/check_consumer_scope_flip.ts +26 -4
- package/src/scripts/check_evaluator_budgets.ts +198 -19
- package/src/scripts/check_gate_coverage.ts +337 -1
- package/src/scripts/check_generator_output_coverage.ts +2 -1
- package/src/scripts/check_iron_law_prominence.ts +1 -1
- package/src/scripts/check_kernel_rule_bundle.ts +1 -1
- package/src/scripts/check_no_new_legacy_path.ts +167 -3
- package/src/scripts/check_pack_size.ts +18 -4
- package/src/scripts/check_portability.ts +37 -7
- package/src/scripts/check_references.ts +19 -3
- package/src/scripts/check_release_pr_shape.ts +13 -0
- package/src/scripts/check_token_regression.ts +9 -2
- package/src/scripts/condense.ts +155 -12
- package/src/scripts/cost/budget.mjs +63 -2
- package/src/scripts/count_scoped_projection.ts +104 -0
- package/src/scripts/evaluator_umbrella.sh +34 -1
- package/src/scripts/generate_capability_matrix.ts +8 -0
- package/src/scripts/generate_router_coverage_from_matrix.ts +155 -0
- package/src/scripts/hook_manifest.yaml +17 -3
- package/src/scripts/hooks/block_config_weakening.ts +337 -0
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/probe_throttle.ts +75 -0
- package/src/scripts/install-hooks.sh +24 -2
- package/src/scripts/install.ts +15 -92
- package/src/scripts/lint_agents_md.ts +9 -3
- package/src/scripts/lint_archived_skills.ts +1 -1
- package/src/scripts/lint_artefact_frontmatter.ts +185 -37
- package/src/scripts/lint_command_routing.ts +21 -52
- package/src/scripts/lint_command_verbs.ts +47 -28
- package/src/scripts/lint_context_spine_usage.ts +38 -12
- package/src/scripts/lint_framework_leakage.ts +25 -6
- package/src/scripts/lint_handoffs.ts +49 -2
- package/src/scripts/lint_media_policy_linkage.ts +62 -30
- package/src/scripts/lint_namespace.ts +92 -7
- package/src/scripts/lint_new_skill_gate.ts +39 -31
- package/src/scripts/lint_originality_shingles.ts +7 -1
- package/src/scripts/lint_override_kernel_guard.ts +21 -3
- package/src/scripts/lint_pack_boundaries.ts +461 -91
- package/src/scripts/lint_pack_dependencies.ts +24 -57
- package/src/scripts/lint_persona_governance.ts +1 -1
- package/src/scripts/lint_role_experiences.ts +34 -5
- package/src/scripts/lint_trigger_precision.ts +142 -0
- package/src/scripts/mcp_server/consumer_tool_catalog.json +12 -201
- package/src/scripts/mcp_server/tools.ts +1 -1
- package/src/scripts/prepack-check.mjs +40 -1
- package/src/scripts/prepack_router_targets.d.mts +7 -0
- package/src/scripts/prepack_router_targets.mjs +93 -0
- package/src/scripts/print_required_checks.ts +72 -18
- package/src/scripts/profile_staleness_hook.ts +12 -0
- package/src/scripts/record_evaluator_measurements.mjs +105 -0
- package/src/scripts/recruit_preflight.sh +1 -1
- package/src/scripts/release.ts +105 -1
- package/src/scripts/router_target_paths.d.mts +4 -0
- package/src/scripts/router_target_paths.mjs +69 -0
- package/src/scripts/router_telemetry.ts +52 -1
- package/src/scripts/routing_doctor.ts +485 -0
- package/src/scripts/rule_trigger_eval.ts +403 -0
- package/src/scripts/schemas/rule.schema.json +5 -1
- package/src/scripts/session_canary_hook.ts +96 -5
- package/src/scripts/skill_linter.ts +90 -42
- package/src/scripts/smoke/skills.sh +1 -1
- package/src/scripts/sweep_dead_scan_roots.ts +356 -10
- package/src/scripts/trigger_coverage.ts +13 -2
- package/src/scripts/update_counts.ts +91 -12
- package/src/scripts/validate_telegraph_carveouts.ts +1 -1
- package/src/scripts/wrapper_freshness_hook.ts +14 -0
- package/src/server/schemas/settings.ts +19 -5
- package/dist/cli-delegate/chunk-TJIAM5FW.js +0 -133
- package/dist/ui/assets/index-BpSQt5vm.js +0 -40
- package/dist/ui/assets/index-BpSQt5vm.js.map +0 -1
|
@@ -91,6 +91,20 @@ concerns:
|
|
|
91
91
|
script: src/scripts/hooks/block_kernel_rule_writes.ts
|
|
92
92
|
args: []
|
|
93
93
|
fail_closed: true
|
|
94
|
+
# road-to-renewal-leverage Phase 2 — "fix the code, not the config".
|
|
95
|
+
# PreToolUse guard over the recorded allowlist-growth antipattern
|
|
96
|
+
# (autonomous-execution § Antipattern): warns as an allowlist grows within a
|
|
97
|
+
# session and BLOCKS past the cap that rule already states in words (>20
|
|
98
|
+
# entries in one session = the linter is wrong). Baselines and budget
|
|
99
|
+
# thresholds warn only — whether a rising count is weakening or a legitimate
|
|
100
|
+
# ratchet reset is not decidable from the edit alone. Council 2026-08-02
|
|
101
|
+
# rejected inventing a "fix-loop is active" predicate; the SESSION is the
|
|
102
|
+
# window the recorded rule already names. fail_closed: false — a malformed
|
|
103
|
+
# envelope must never block an unrelated edit.
|
|
104
|
+
block-config-weakening:
|
|
105
|
+
script: src/scripts/hooks/block_config_weakening.ts
|
|
106
|
+
args: []
|
|
107
|
+
fail_closed: false
|
|
94
108
|
# token-saving Phase 3 — PreToolUse RTK-wrap nudge. DETERMINISTIC: keys off a
|
|
95
109
|
# live `which rtk` probe (not the self-reported flag). When rtk is installed
|
|
96
110
|
# and the agent is about to run a single verbose CLI command that is NOT
|
|
@@ -211,7 +225,7 @@ platforms:
|
|
|
211
225
|
session_start: [chat-history, hot-context, session-canary, first-run-gate, onboarding-gate, verify-before-complete, minimal-safe-diff, profile-staleness, wrapper-freshness, surface-probe]
|
|
212
226
|
session_end: [chat-history, memory-learn]
|
|
213
227
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
214
|
-
pre_tool_use: [block-no-verify, block-kernel-rule-writes, rtk-wrap, design-slop, code-graph-nudge]
|
|
228
|
+
pre_tool_use: [block-no-verify, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, code-graph-nudge]
|
|
215
229
|
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
|
|
216
230
|
|
|
217
231
|
claude:
|
|
@@ -219,7 +233,7 @@ platforms:
|
|
|
219
233
|
session_end: [chat-history, memory-learn]
|
|
220
234
|
stop: [chat-history, hot-context, verify-before-complete, team-review-gate]
|
|
221
235
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
222
|
-
pre_tool_use: [block-no-verify, block-kernel-rule-writes, rtk-wrap, design-slop, code-graph-nudge]
|
|
236
|
+
pre_tool_use: [block-no-verify, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, code-graph-nudge]
|
|
223
237
|
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
|
|
224
238
|
|
|
225
239
|
# Cowork — the Claude desktop app's local-agent-mode runtime, built
|
|
@@ -241,7 +255,7 @@ platforms:
|
|
|
241
255
|
session_end: [chat-history, memory-learn]
|
|
242
256
|
stop: [chat-history, hot-context, verify-before-complete]
|
|
243
257
|
user_prompt_submit: [chat-history, verify-before-complete, minimal-safe-diff]
|
|
244
|
-
pre_tool_use: [block-no-verify, block-kernel-rule-writes, rtk-wrap, design-slop, code-graph-nudge]
|
|
258
|
+
pre_tool_use: [block-no-verify, block-kernel-rule-writes, block-config-weakening, rtk-wrap, design-slop, code-graph-nudge]
|
|
245
259
|
post_tool_use: [chat-history, roadmap-progress, context-hygiene, verify-before-complete, minimal-safe-diff, injection-scan, pr-url-reminder]
|
|
246
260
|
|
|
247
261
|
# Phase 7.5 — Cursor. `.cursor/hooks.json` (project) is read by the
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// MIGRATE: precompiled-hook-layer — carry this hook when road-to-credible-install
|
|
3
|
+
// Phase 1 precompiles the hook path (touch-once preserved at migration).
|
|
4
|
+
/**
|
|
5
|
+
* PreToolUse guard: "fix the code, not the config."
|
|
6
|
+
*
|
|
7
|
+
* The recorded antipattern (`autonomous-execution` § Antipattern —
|
|
8
|
+
* allowlist-growth as silent budget bypass): during a fix loop the agent
|
|
9
|
+
* weakens the gate instead of the code, growing a lint allowlist entry by
|
|
10
|
+
* entry until the linter no longer objects. The rule already states the
|
|
11
|
+
* threshold in words —
|
|
12
|
+
*
|
|
13
|
+
* ALLOWLIST > 20 ENTRIES IN ONE SESSION = THE LINTER IS WRONG.
|
|
14
|
+
* STOP. PROPOSE LINTER REDESIGN OR REMOVAL.
|
|
15
|
+
*
|
|
16
|
+
* — and two linters cite it in their own failure messages. Nothing enforced
|
|
17
|
+
* it: by the time CI sees the diff the session has already spent its budget
|
|
18
|
+
* on the wrong fix. This hook moves that stated threshold to tool-call time.
|
|
19
|
+
*
|
|
20
|
+
* Deliberately NOT built (council 2026-08-02, anthropic/claude-sonnet-4-5 +
|
|
21
|
+
* openai/gpt-4o):
|
|
22
|
+
*
|
|
23
|
+
* - **No invented "fix-loop is active" predicate.** Both members rejected it
|
|
24
|
+
* as speculative — no such state exists in the repo and defining entry/exit
|
|
25
|
+
* invites false positives in both directions. The SESSION is the window the
|
|
26
|
+
* recorded rule already names, so the session id off the hook envelope is
|
|
27
|
+
* the predicate, and it is not an invention.
|
|
28
|
+
* - **No blocking on baselines or budgets.** Whether a violation-baseline
|
|
29
|
+
* count rising is weakening or a legitimate ratchet reset after a refactor
|
|
30
|
+
* is not mechanically decidable from the edit alone. Those surfaces warn
|
|
31
|
+
* (exit 2) and stay a review decision; only allowlist growth — mechanical,
|
|
32
|
+
* countable, and already carrying a stated numeric threshold — blocks.
|
|
33
|
+
*
|
|
34
|
+
* Cumulative within a session, per allowlist file. Small additions warn from
|
|
35
|
+
* the first one so the count is visible while it is still cheap to change
|
|
36
|
+
* course; crossing the recorded cap blocks.
|
|
37
|
+
*
|
|
38
|
+
* Exit codes (docs/contracts/hook-architecture-v1.md):
|
|
39
|
+
* 0 — allow
|
|
40
|
+
* 1 — block
|
|
41
|
+
* 2 — warn
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
import * as fs from 'node:fs';
|
|
45
|
+
import * as path from 'node:path';
|
|
46
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
47
|
+
|
|
48
|
+
import { EDIT_TOOLS } from '../minimal_safe_diff_hook.js';
|
|
49
|
+
import { readHookStdin } from './hook_stdin.js';
|
|
50
|
+
|
|
51
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
52
|
+
|
|
53
|
+
const EXIT_ALLOW = 0;
|
|
54
|
+
const EXIT_BLOCK = 1;
|
|
55
|
+
const EXIT_WARN = 2;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The stated cap from `autonomous-execution` § Antipattern. Crossing it in one
|
|
59
|
+
* session is defined there as the third validation-target failure — the point
|
|
60
|
+
* at which the linter, not the content, is wrong.
|
|
61
|
+
*/
|
|
62
|
+
export const SESSION_ENTRY_CAP = 20;
|
|
63
|
+
|
|
64
|
+
/** Warn from this many cumulative added entries so the count stays visible. */
|
|
65
|
+
export const SESSION_WARN_AT = 5;
|
|
66
|
+
|
|
67
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue };
|
|
68
|
+
type JsonObject = { [k: string]: JsonValue };
|
|
69
|
+
|
|
70
|
+
function _isObject(v: JsonValue | undefined): v is JsonObject {
|
|
71
|
+
return v !== null && v !== undefined && typeof v === 'object' && !Array.isArray(v);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const _PATH_KEYS: readonly string[] = ['file_path', 'path', 'target_file', 'filePath', 'notebook_path'];
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Config surfaces this guard recognises.
|
|
78
|
+
*
|
|
79
|
+
* `allowlist` is the blocking class — an entry count is a plain number and the
|
|
80
|
+
* recorded rule already fixes its threshold. `advisory` covers the surfaces
|
|
81
|
+
* where "weakening" needs context a tool call does not carry (a baseline count
|
|
82
|
+
* may legitimately reset after a refactor); those warn and never block.
|
|
83
|
+
*/
|
|
84
|
+
export type ConfigKind = 'allowlist' | 'advisory' | null;
|
|
85
|
+
|
|
86
|
+
/** Classify a target path into a config surface, or null for everything else. */
|
|
87
|
+
export function classify_target(p: string): ConfigKind {
|
|
88
|
+
const posix = p.split(path.sep).join('/');
|
|
89
|
+
const base = posix.split('/').pop() ?? '';
|
|
90
|
+
if (/_allowlist\.(json|txt)$/.test(base) || /^allowlist[-_.]/.test(base)) {
|
|
91
|
+
return 'allowlist';
|
|
92
|
+
}
|
|
93
|
+
if (/(^|-)baselines?\.json$/.test(base)) {
|
|
94
|
+
return 'advisory';
|
|
95
|
+
}
|
|
96
|
+
if (/-budget(s)?\.(json|ya?ml)$/.test(base) || base === 'budgets.yml') {
|
|
97
|
+
return 'advisory';
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Count allowlist entries in a blob.
|
|
104
|
+
*
|
|
105
|
+
* JSON: every string leaf in the structure (arrays of paths, and
|
|
106
|
+
* `{"rule": ["path", …]}` maps alike) — parsing beats a line count because a
|
|
107
|
+
* reformat would otherwise read as growth. Falls back to non-blank,
|
|
108
|
+
* non-comment lines for `.txt` allowlists and for JSON fragments that do not
|
|
109
|
+
* parse on their own (an `Edit` payload is rarely a whole document).
|
|
110
|
+
*/
|
|
111
|
+
export function count_entries(text: string): number {
|
|
112
|
+
const trimmed = text.trim();
|
|
113
|
+
if (trimmed) {
|
|
114
|
+
try {
|
|
115
|
+
let n = 0;
|
|
116
|
+
const walk = (v: unknown): void => {
|
|
117
|
+
if (typeof v === 'string') {
|
|
118
|
+
n += 1;
|
|
119
|
+
} else if (Array.isArray(v)) {
|
|
120
|
+
v.forEach(walk);
|
|
121
|
+
} else if (v !== null && typeof v === 'object') {
|
|
122
|
+
Object.values(v as Record<string, unknown>).forEach(walk);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
walk(JSON.parse(trimmed));
|
|
126
|
+
return n;
|
|
127
|
+
} catch {
|
|
128
|
+
/* not a standalone JSON document — fall through */
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return text
|
|
132
|
+
.split(/\r?\n/)
|
|
133
|
+
.map((l) => l.trim())
|
|
134
|
+
.filter((l) => l.length > 0 && !l.startsWith('#') && !l.startsWith('//')).length;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Entries this single tool call adds to an allowlist.
|
|
139
|
+
*
|
|
140
|
+
* `Write` carries the whole new document, so the delta is measured against
|
|
141
|
+
* what is on disk. `Edit` carries a replacement pair, so the delta is the pair
|
|
142
|
+
* itself. Negative results (a shrinking allowlist — the direction this guard
|
|
143
|
+
* wants) collapse to 0: removals must never bank credit against a later
|
|
144
|
+
* addition.
|
|
145
|
+
*/
|
|
146
|
+
export function added_entries(
|
|
147
|
+
tool_input: JsonObject,
|
|
148
|
+
on_disk: string | null,
|
|
149
|
+
): number {
|
|
150
|
+
const content = tool_input['content'];
|
|
151
|
+
if (typeof content === 'string') {
|
|
152
|
+
const before = on_disk === null ? 0 : count_entries(on_disk);
|
|
153
|
+
return Math.max(0, count_entries(content) - before);
|
|
154
|
+
}
|
|
155
|
+
const oldS = tool_input['old_string'] ?? tool_input['oldStr'] ?? tool_input['old_str'];
|
|
156
|
+
const newS = tool_input['new_string'] ?? tool_input['newStr'] ?? tool_input['new_str'];
|
|
157
|
+
if (typeof newS === 'string') {
|
|
158
|
+
const before = typeof oldS === 'string' ? count_entries(oldS) : 0;
|
|
159
|
+
return Math.max(0, count_entries(newS) - before);
|
|
160
|
+
}
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface Decision {
|
|
165
|
+
action: 'allow' | 'warn' | 'block';
|
|
166
|
+
reason: string;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Decide from the cumulative session total for one allowlist file. */
|
|
170
|
+
export function decide(kind: ConfigKind, rel_path: string, session_total: number, added: number): Decision {
|
|
171
|
+
if (kind === null || added <= 0) {
|
|
172
|
+
return { action: 'allow', reason: '' };
|
|
173
|
+
}
|
|
174
|
+
if (kind === 'advisory') {
|
|
175
|
+
return {
|
|
176
|
+
action: 'warn',
|
|
177
|
+
reason:
|
|
178
|
+
`${rel_path}: this edit loosens a gate threshold. Whether that is a ` +
|
|
179
|
+
'legitimate ratchet reset or the config being bent around a failing ' +
|
|
180
|
+
'check is a review decision — state which in the diff.',
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (session_total > SESSION_ENTRY_CAP) {
|
|
184
|
+
return {
|
|
185
|
+
action: 'block',
|
|
186
|
+
reason:
|
|
187
|
+
`${rel_path}: ${session_total} allowlist entries added this session ` +
|
|
188
|
+
`(cap ${SESSION_ENTRY_CAP}). Per autonomous-execution § Antipattern — ` +
|
|
189
|
+
'allowlist-growth as silent budget bypass, crossing this cap means the ' +
|
|
190
|
+
'LINTER is wrong, not the content. Fix the code or redesign the check; ' +
|
|
191
|
+
'do not expand the allowlist further.\n' +
|
|
192
|
+
' Legitimate override is a human action outside this session: land the ' +
|
|
193
|
+
'linter redesign, or remove the `block-config-weakening` entry in ' +
|
|
194
|
+
'src/scripts/hook_manifest.yaml.',
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (session_total >= SESSION_WARN_AT) {
|
|
198
|
+
return {
|
|
199
|
+
action: 'warn',
|
|
200
|
+
reason:
|
|
201
|
+
`${rel_path}: ${session_total} allowlist entries added this session ` +
|
|
202
|
+
`(cap ${SESSION_ENTRY_CAP}). Fix the code, not the config.`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
return { action: 'allow', reason: '' };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// --- session state -------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
function _state_file(root: string): string {
|
|
211
|
+
return path.join(root, 'agents', 'runtime', 'state', 'config-weakening.json');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Read the cumulative count, add `added`, persist, and return the new total. */
|
|
215
|
+
export function bump_session(root: string, session: string, key: string, added: number): number {
|
|
216
|
+
const f = _state_file(root);
|
|
217
|
+
let data: Record<string, Record<string, number>> = {};
|
|
218
|
+
try {
|
|
219
|
+
data = JSON.parse(fs.readFileSync(f, 'utf-8')) as Record<string, Record<string, number>>;
|
|
220
|
+
} catch {
|
|
221
|
+
data = {};
|
|
222
|
+
}
|
|
223
|
+
const bucket = data[session] ?? {};
|
|
224
|
+
const total = (bucket[key] ?? 0) + added;
|
|
225
|
+
bucket[key] = total;
|
|
226
|
+
data[session] = bucket;
|
|
227
|
+
try {
|
|
228
|
+
fs.mkdirSync(path.dirname(f), { recursive: true });
|
|
229
|
+
fs.writeFileSync(f, `${JSON.stringify(data, null, 2)}\n`, 'utf-8');
|
|
230
|
+
} catch {
|
|
231
|
+
/* state is an optimisation, never a gate — a read-only tree still decides */
|
|
232
|
+
}
|
|
233
|
+
return total;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// --- entry ---------------------------------------------------------------
|
|
237
|
+
|
|
238
|
+
function _extract(envelope: JsonObject): { tool: string; paths: string[]; ti: JsonObject | null } {
|
|
239
|
+
const payload = _isObject(envelope['payload']) ? envelope['payload'] : envelope;
|
|
240
|
+
const nameVal =
|
|
241
|
+
payload['tool_name'] ?? payload['toolName'] ?? payload['tool'] ?? envelope['tool_name'] ?? envelope['tool'];
|
|
242
|
+
const tool = typeof nameVal === 'string' ? nameVal : '';
|
|
243
|
+
const ti = _isObject(payload['tool_input'])
|
|
244
|
+
? payload['tool_input']
|
|
245
|
+
: _isObject(envelope['tool_input'])
|
|
246
|
+
? envelope['tool_input']
|
|
247
|
+
: null;
|
|
248
|
+
const paths: string[] = [];
|
|
249
|
+
if (ti !== null) {
|
|
250
|
+
for (const key of _PATH_KEYS) {
|
|
251
|
+
const v = ti[key];
|
|
252
|
+
if (typeof v === 'string' && v) {
|
|
253
|
+
paths.push(v);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return { tool, paths, ti };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function main(): number {
|
|
261
|
+
let envelope: JsonValue;
|
|
262
|
+
try {
|
|
263
|
+
const raw = readHookStdin();
|
|
264
|
+
envelope = raw.trim() ? (JSON.parse(raw) as JsonValue) : {};
|
|
265
|
+
} catch {
|
|
266
|
+
return EXIT_ALLOW;
|
|
267
|
+
}
|
|
268
|
+
if (!_isObject(envelope)) {
|
|
269
|
+
return EXIT_ALLOW;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const { tool, paths, ti } = _extract(envelope);
|
|
273
|
+
if (!tool || !EDIT_TOOLS.has(tool) || ti === null) {
|
|
274
|
+
return EXIT_ALLOW;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const cwd = envelope['cwd'];
|
|
278
|
+
const pr = envelope['workspace_root'] ?? envelope['project_root'];
|
|
279
|
+
const root = typeof cwd === 'string' && cwd ? cwd : typeof pr === 'string' && pr ? pr : '.';
|
|
280
|
+
const sidRaw = envelope['session_id'] ?? envelope['sessionId'];
|
|
281
|
+
const session = typeof sidRaw === 'string' && sidRaw ? sidRaw : 'default';
|
|
282
|
+
|
|
283
|
+
for (const p of paths) {
|
|
284
|
+
const kind = classify_target(p);
|
|
285
|
+
if (kind === null) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const abs = path.isAbsolute(p) ? p : path.join(root, p);
|
|
289
|
+
let on_disk: string | null = null;
|
|
290
|
+
try {
|
|
291
|
+
on_disk = fs.readFileSync(abs, 'utf-8');
|
|
292
|
+
} catch {
|
|
293
|
+
on_disk = null;
|
|
294
|
+
}
|
|
295
|
+
const added = added_entries(ti, on_disk);
|
|
296
|
+
if (added <= 0) {
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
const rel = path.relative(root, abs) || p;
|
|
300
|
+
const total = kind === 'allowlist' ? bump_session(root, session, rel, added) : added;
|
|
301
|
+
const d = decide(kind, rel, total, added);
|
|
302
|
+
if (d.action === 'block') {
|
|
303
|
+
process.stderr.write(`block-config-weakening: BLOCKED — ${d.reason}\n`);
|
|
304
|
+
return EXIT_BLOCK;
|
|
305
|
+
}
|
|
306
|
+
if (d.action === 'warn') {
|
|
307
|
+
process.stdout.write(`${JSON.stringify({ decision: 'warn', reason: d.reason })}\n`);
|
|
308
|
+
return EXIT_WARN;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return EXIT_ALLOW;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Bundle-safety: never auto-run when inlined into an esbuild bundle, where
|
|
315
|
+
// every module shares the bundle's `import.meta.url`.
|
|
316
|
+
declare const __AGENT_CONFIG_BUNDLE__: boolean | undefined;
|
|
317
|
+
function _isCliEntry(): boolean {
|
|
318
|
+
if (typeof __AGENT_CONFIG_BUNDLE__ !== 'undefined' && __AGENT_CONFIG_BUNDLE__) {
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
321
|
+
if (process.argv[1] === undefined) {
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
|
|
325
|
+
if (import.meta.url === argvUrl) {
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
try {
|
|
329
|
+
return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(path.resolve(process.argv[1]));
|
|
330
|
+
} catch {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (_isCliEntry() || process.argv[1] === _HERE) {
|
|
336
|
+
process.exit(main());
|
|
337
|
+
}
|
|
@@ -42,6 +42,7 @@ import { main as prUrlReminderMain } from '../pr_url_reminder_hook.js';
|
|
|
42
42
|
import { main as sessionCanaryMain } from '../session_canary_hook.js';
|
|
43
43
|
import { main as blockNoVerifyMain } from './block_no_verify.js';
|
|
44
44
|
import { main as blockKernelRuleWritesMain } from './block_kernel_rule_writes.js';
|
|
45
|
+
import { main as blockConfigWeakeningMain } from './block_config_weakening.js';
|
|
45
46
|
import { main as rtkWrapMain } from './rtk_wrap_hook.js';
|
|
46
47
|
import { main as designSlopMain } from './design_slop_hook.js';
|
|
47
48
|
import { main as codeGraphNudgeMain } from './code_graph_nudge_hook.js';
|
|
@@ -69,6 +70,7 @@ export const CONCERN_REGISTRY: Readonly<Record<string, ConcernMain>> = {
|
|
|
69
70
|
'src/scripts/session_canary_hook.ts': sessionCanaryMain as ConcernMain,
|
|
70
71
|
'src/scripts/hooks/block_no_verify.ts': blockNoVerifyMain as ConcernMain,
|
|
71
72
|
'src/scripts/hooks/block_kernel_rule_writes.ts': blockKernelRuleWritesMain as ConcernMain,
|
|
73
|
+
'src/scripts/hooks/block_config_weakening.ts': blockConfigWeakeningMain as ConcernMain,
|
|
72
74
|
'src/scripts/hooks/rtk_wrap_hook.ts': rtkWrapMain as ConcernMain,
|
|
73
75
|
'src/scripts/hooks/design_slop_hook.ts': designSlopMain as ConcernMain,
|
|
74
76
|
'src/scripts/hooks/code_graph_nudge_hook.ts': codeGraphNudgeMain as ConcernMain,
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* probe_throttle — daily throttle for session_start probe concerns
|
|
3
|
+
* (road-to-hook-latency-repair Phase 2, SessionStart fan-out lever).
|
|
4
|
+
*
|
|
5
|
+
* `profile-staleness`, `wrapper-freshness`, and `surface-probe` are
|
|
6
|
+
* defense-in-depth probes whose findings do not change within a day; running
|
|
7
|
+
* them on EVERY session_start pays their cost per session for no new
|
|
8
|
+
* information. Each probe checks a per-concern stamp file under
|
|
9
|
+
* `agents/runtime/state/probe-throttle/` and skips when the stamp's mtime is
|
|
10
|
+
* from the same LOCAL calendar day (a skipped probe re-runs tomorrow).
|
|
11
|
+
*
|
|
12
|
+
* Deliberate simplicity (roadmap lock — "an mtime file, not a daemon"):
|
|
13
|
+
* - The stamp carries NO semantics beyond its mtime. Deleting the file or
|
|
14
|
+
* the directory only changes WHEN a probe re-runs, never what it answers.
|
|
15
|
+
* - Same-day equality is skew-safe: a stamp dated in the future or the past
|
|
16
|
+
* is simply "not today", so the probe runs and re-stamps — there is no
|
|
17
|
+
* lockout state (council 2026-08-03).
|
|
18
|
+
* - Fail-open both ways: an unreadable stamp means "run the probe"; an
|
|
19
|
+
* unwritable state dir means the probe simply runs again next session.
|
|
20
|
+
* - Replay runs (AGENT_CONFIG_REPLAY=1) bypass the throttle and never
|
|
21
|
+
* write stamps — fixture re-execution and the latency bench must be
|
|
22
|
+
* deterministic, not dependent on yesterday's stamps (state_io replay
|
|
23
|
+
* contract).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import fs from 'node:fs';
|
|
27
|
+
import path from 'node:path';
|
|
28
|
+
|
|
29
|
+
import { is_replay_mode } from './state_io.js';
|
|
30
|
+
|
|
31
|
+
const STATE_SUBDIR = ['agents', 'runtime', 'state', 'probe-throttle'] as const;
|
|
32
|
+
|
|
33
|
+
function _stampPath(projectRoot: string, concern: string): string {
|
|
34
|
+
return path.join(projectRoot, ...STATE_SUBDIR, `${concern}.stamp`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Local-calendar-day key (YYYY-MM-DD in local time, not UTC). */
|
|
38
|
+
function _localDay(d: Date): string {
|
|
39
|
+
const y = d.getFullYear();
|
|
40
|
+
const m = String(d.getMonth() + 1).padStart(2, '0');
|
|
41
|
+
const day = String(d.getDate()).padStart(2, '0');
|
|
42
|
+
return `${y}-${m}-${day}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* True when the probe already ran today (same LOCAL calendar day as the
|
|
47
|
+
* stamp's mtime) and may be skipped. Replay mode and any read problem
|
|
48
|
+
* return false — the probe runs.
|
|
49
|
+
*/
|
|
50
|
+
export function probeRanToday(projectRoot: string, concern: string): boolean {
|
|
51
|
+
if (is_replay_mode()) return false;
|
|
52
|
+
try {
|
|
53
|
+
const st = fs.statSync(_stampPath(projectRoot, concern));
|
|
54
|
+
return _localDay(st.mtime) === _localDay(new Date());
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Record that the probe ran now. Fail-silent — an unwritable state dir just
|
|
62
|
+
* means the probe runs again next session. No-op in replay mode.
|
|
63
|
+
*/
|
|
64
|
+
export function stampProbeRun(projectRoot: string, concern: string): void {
|
|
65
|
+
if (is_replay_mode()) return;
|
|
66
|
+
try {
|
|
67
|
+
const p = _stampPath(projectRoot, concern);
|
|
68
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
69
|
+
// Content is a human-readable timestamp for debugging only — the
|
|
70
|
+
// mtime is the sole semantic carrier.
|
|
71
|
+
fs.writeFileSync(p, `${new Date().toISOString()}\n`);
|
|
72
|
+
} catch {
|
|
73
|
+
/* fail-silent */
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -266,10 +266,20 @@ write_chat_history_hook "post-rewrite" "git:post-rewrite"
|
|
|
266
266
|
# `.augment/`, etc. projections until they remember to run `task sync`.
|
|
267
267
|
# These hooks bridge that gap: fast idempotent re-projection.
|
|
268
268
|
#
|
|
269
|
+
# The same staleness bites the compiled CLI: the Claude/agent hooks run
|
|
270
|
+
# `./agent-config` → `dist/cli/` (gitignored tsc output). A pull that adds
|
|
271
|
+
# or moves a TS module leaves `dist/` stale and EVERY subsequent tool-use
|
|
272
|
+
# hook fails with ERR_MODULE_NOT_FOUND until someone rebuilds. So when the
|
|
273
|
+
# range touches TS sources, rebuild via `npm run build:cli` — tsc only,
|
|
274
|
+
# deliberately NOT the full `npm run build`: the install-bundle step
|
|
275
|
+
# rewrites the committed `dist/install/install.mjs` and has produced
|
|
276
|
+
# spurious tracked diffs from worktrees before.
|
|
277
|
+
#
|
|
269
278
|
# Bypass: `git pull --no-verify` does not exist, but devs can disable the
|
|
270
279
|
# hooks per-command via `git -c core.hooksPath=/dev/null ...` or by
|
|
271
280
|
# editing the file. Runtime ~200 ms when nothing relevant changed
|
|
272
|
-
# (path-diff check exits early); ~2 s on full re-projection
|
|
281
|
+
# (path-diff check exits early); ~2 s on full re-projection; ~2 s extra
|
|
282
|
+
# for the tsc rebuild when TS sources changed.
|
|
273
283
|
|
|
274
284
|
append_auto_sync_block() {
|
|
275
285
|
local name="$1"
|
|
@@ -294,13 +304,25 @@ elif [ "$name" = "post-checkout" ]; then
|
|
|
294
304
|
fi
|
|
295
305
|
|
|
296
306
|
if [ -n "\$prev" ] && [ -n "\$new" ] && [ "\$prev" != "\$new" ]; then
|
|
297
|
-
|
|
307
|
+
changed="\$(git diff --name-only "\$prev" "\$new" 2>/dev/null)"
|
|
308
|
+
if printf '%s\n' "\$changed" | \\
|
|
298
309
|
grep -qE '^(dist/agent-src/|\\.agent-src\\.uncondensed/|src/scripts/condense\\.ts|\\.agent-tools\\.yml|Taskfile\\.yml)'; then
|
|
299
310
|
if command -v task >/dev/null 2>&1; then
|
|
300
311
|
task sync >/dev/null 2>&1 || true
|
|
301
312
|
task generate-tools >/dev/null 2>&1 || true
|
|
302
313
|
fi
|
|
303
314
|
fi
|
|
315
|
+
# TS sources changed → the gitignored tsc output (dist/cli, dist/scripts,
|
|
316
|
+
# …) is stale and the ./agent-config hook chain breaks with
|
|
317
|
+
# ERR_MODULE_NOT_FOUND. Rebuild tsc output only (build:cli), never the
|
|
318
|
+
# full build (install-bundle rewrites committed dist/install/install.mjs).
|
|
319
|
+
# Skip silently without node_modules (fresh clone / bare worktree).
|
|
320
|
+
if printf '%s\n' "\$changed" | \\
|
|
321
|
+
grep -qE '^(src/(cli|server|shared|install|scripts)/|tsconfig\\.json|package\\.json)'; then
|
|
322
|
+
if command -v npm >/dev/null 2>&1 && [ -d node_modules ]; then
|
|
323
|
+
npm run build:cli >/dev/null 2>&1 || true
|
|
324
|
+
fi
|
|
325
|
+
fi
|
|
304
326
|
fi
|
|
305
327
|
EOF
|
|
306
328
|
}
|
package/src/scripts/install.ts
CHANGED
|
@@ -74,6 +74,7 @@ import type * as YamlModule from 'yaml';
|
|
|
74
74
|
import { build_merge_entries } from './_lib/json_pointers.js';
|
|
75
75
|
import { is_claude_builtin_name } from './_lib/claude_builtin_names.js';
|
|
76
76
|
import * as installed_lock from './_lib/installed_lock.js';
|
|
77
|
+
import * as scoped_projection from './_lib/scoped_projection.js';
|
|
77
78
|
import * as surface_tiers from './_lib/surface_tiers.js';
|
|
78
79
|
import * as global_deploy_inventory from './_lib/global_deploy_inventory.js';
|
|
79
80
|
import * as installed_tools from './_lib/installed_tools.js';
|
|
@@ -3344,89 +3345,16 @@ function _prune_lab_modules(
|
|
|
3344
3345
|
// `requires` graph). Mirrors the core-only lab prune above — same
|
|
3345
3346
|
// `_prune_modules_by` mechanics, a different predicate.
|
|
3346
3347
|
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
requires: string[];
|
|
3354
|
-
}
|
|
3355
|
-
|
|
3356
|
-
/**
|
|
3357
|
-
* Parse `src/config/discovery/packs.yml` into `{id, workspaces, requires}`
|
|
3358
|
-
* records. `requires` falls back to the legacy `requires_hint` field name
|
|
3359
|
-
* when `requires` is absent.
|
|
3360
|
-
*
|
|
3361
|
-
* Deliberately THROWS on a missing file or a parse result that is not a
|
|
3362
|
-
* list — an empty active-pack set would be the LEAST safe fallback here
|
|
3363
|
-
* (it would prune every tagged artefact, the opposite of the fail-safe
|
|
3364
|
-
* contract). The caller wraps this in a try/catch that restores the full
|
|
3365
|
-
* tree on any error instead.
|
|
3366
|
-
*/
|
|
3367
|
-
function _load_packs_registry(package_root: string): PackRecord[] {
|
|
3368
|
-
const vocab_path = path.join(package_root, 'src', 'config', 'discovery', 'packs.yml');
|
|
3369
|
-
const data = yamlSafeLoad(readText(vocab_path));
|
|
3370
|
-
if (!Array.isArray(data)) {
|
|
3371
|
-
throw new Error(`packs.yml did not parse to a list: ${vocab_path}`);
|
|
3372
|
-
}
|
|
3373
|
-
const out: PackRecord[] = [];
|
|
3374
|
-
for (const entry of data) {
|
|
3375
|
-
if (!_isPlainObject(entry)) continue;
|
|
3376
|
-
const id = entry['id'];
|
|
3377
|
-
if (typeof id !== 'string' || id === '') continue;
|
|
3378
|
-
const workspaces_raw = entry['workspaces'];
|
|
3379
|
-
const workspaces = Array.isArray(workspaces_raw)
|
|
3380
|
-
? workspaces_raw.filter((w): w is string => typeof w === 'string')
|
|
3381
|
-
: [];
|
|
3382
|
-
const requires_raw = entry['requires'] ?? entry['requires_hint'];
|
|
3383
|
-
const requires = Array.isArray(requires_raw)
|
|
3384
|
-
? requires_raw.filter((r): r is string => typeof r === 'string')
|
|
3385
|
-
: [];
|
|
3386
|
-
out.push({ id, workspaces, requires });
|
|
3387
|
-
}
|
|
3388
|
-
return out;
|
|
3389
|
-
}
|
|
3390
|
-
|
|
3391
|
-
/**
|
|
3392
|
-
* Active pack id set for `scoped` mode: every pack whose `workspaces`
|
|
3393
|
-
* intersects `SCOPED_ACTIVE_WORKSPACES`, unioned with `runtime_active_packs`,
|
|
3394
|
-
* expanded over the `requires` graph (transitive closure).
|
|
3348
|
+
/*
|
|
3349
|
+
* The scoped-projection predicate lives in `_lib/scoped_projection.ts` so the
|
|
3350
|
+
* installer and `count_scoped_projection.ts` — which produces the number the
|
|
3351
|
+
* claims ledger publishes — run the SAME code rather than the same rule
|
|
3352
|
+
* written twice. Re-exported under the existing private names to keep the
|
|
3353
|
+
* prune tests' import surface unchanged.
|
|
3395
3354
|
*/
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
): Set<string> {
|
|
3400
|
-
const by_id = new Map<string, PackRecord>();
|
|
3401
|
-
for (const p of packs) by_id.set(p.id, p);
|
|
3402
|
-
|
|
3403
|
-
const active = new Set<string>();
|
|
3404
|
-
for (const p of packs) {
|
|
3405
|
-
if (p.workspaces.some((w) => SCOPED_ACTIVE_WORKSPACES.has(w))) {
|
|
3406
|
-
active.add(p.id);
|
|
3407
|
-
}
|
|
3408
|
-
}
|
|
3409
|
-
for (const id of runtime_active_packs) {
|
|
3410
|
-
active.add(id);
|
|
3411
|
-
}
|
|
3412
|
-
|
|
3413
|
-
let frontier = [...active];
|
|
3414
|
-
while (frontier.length > 0) {
|
|
3415
|
-
const next: string[] = [];
|
|
3416
|
-
for (const id of frontier) {
|
|
3417
|
-
const rec = by_id.get(id);
|
|
3418
|
-
if (rec === undefined) continue;
|
|
3419
|
-
for (const dep of rec.requires) {
|
|
3420
|
-
if (!active.has(dep)) {
|
|
3421
|
-
active.add(dep);
|
|
3422
|
-
next.push(dep);
|
|
3423
|
-
}
|
|
3424
|
-
}
|
|
3425
|
-
}
|
|
3426
|
-
frontier = next;
|
|
3427
|
-
}
|
|
3428
|
-
return active;
|
|
3429
|
-
}
|
|
3355
|
+
type PackRecord = scoped_projection.PackRecord;
|
|
3356
|
+
const _load_packs_registry = scoped_projection.load_packs_registry;
|
|
3357
|
+
const _compute_active_pack_ids = scoped_projection.compute_active_pack_ids;
|
|
3430
3358
|
|
|
3431
3359
|
/**
|
|
3432
3360
|
* Read the GLOBAL settings document that governs a global deploy, if one
|
|
@@ -3506,7 +3434,7 @@ function _resolve_global_rule_scope(package_root: string): RuleScope {
|
|
|
3506
3434
|
// contradict, and this is the documented upgrade-compat path.
|
|
3507
3435
|
if (settings_path === null) {
|
|
3508
3436
|
try {
|
|
3509
|
-
return ruleScopeFromSettings(_load_default_settings(package_root));
|
|
3437
|
+
return ruleScopeFromSettings(_load_default_settings(package_root), package_root);
|
|
3510
3438
|
} catch {
|
|
3511
3439
|
return LEGACY_ALL;
|
|
3512
3440
|
}
|
|
@@ -3539,7 +3467,7 @@ function _resolve_global_rule_scope(package_root: string): RuleScope {
|
|
|
3539
3467
|
return LEGACY_ALL;
|
|
3540
3468
|
}
|
|
3541
3469
|
try {
|
|
3542
|
-
return ruleScopeFromSettings(parsed as Record<string, unknown
|
|
3470
|
+
return ruleScopeFromSettings(parsed as Record<string, unknown>, package_root);
|
|
3543
3471
|
} catch (e) {
|
|
3544
3472
|
warn(
|
|
3545
3473
|
`could not derive rule scope from ${settings_path} (${String(e)}) — ` +
|
|
@@ -3574,14 +3502,9 @@ function _prune_scoped_modules(
|
|
|
3574
3502
|
deploy_results: Record<string, DeployResult>,
|
|
3575
3503
|
active_ids: ReadonlySet<string>,
|
|
3576
3504
|
): [number, Record<string, DeployResult>] {
|
|
3577
|
-
return _prune_modules_by(deploy_results, (p) =>
|
|
3578
|
-
|
|
3579
|
-
|
|
3580
|
-
for (const id of packs) {
|
|
3581
|
-
if (active_ids.has(id)) return false;
|
|
3582
|
-
}
|
|
3583
|
-
return true;
|
|
3584
|
-
});
|
|
3505
|
+
return _prune_modules_by(deploy_results, (p) =>
|
|
3506
|
+
scoped_projection.is_pruned_under_scoped(p, active_ids),
|
|
3507
|
+
);
|
|
3585
3508
|
}
|
|
3586
3509
|
|
|
3587
3510
|
function install_global(
|