@event4u/agent-config 16.2.0 → 16.4.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 +337 -234
- package/README.md +15 -13
- package/agents/templates/.ai-council.yml.example +26 -8
- package/dist/agent-src/commands/analyze/inbox.md +11 -8
- package/dist/agent-src/commands/analyze/roadmap-repos.md +1 -1
- package/dist/agent-src/commands/bug/fix.md +17 -2
- package/dist/agent-src/commands/bug/investigate.md +1 -1
- package/dist/agent-src/commands/commit/in-chunks.md +40 -31
- package/dist/agent-src/commands/commit.md +72 -33
- package/dist/agent-src/commands/condense.md +2 -8
- package/dist/agent-src/commands/council/debate.md +11 -7
- package/dist/agent-src/commands/council/default.md +10 -9
- package/dist/agent-src/commands/council.md +1 -1
- package/dist/agent-src/commands/estimate-ticket.md +1 -1
- package/dist/agent-src/commands/feature/explore.md +3 -3
- package/dist/agent-src/commands/feature/plan.md +2 -2
- package/dist/agent-src/commands/feature/roadmap.md +1 -1
- package/dist/agent-src/commands/fix/ci.md +18 -4
- package/dist/agent-src/commands/fix/commit-messages.md +48 -23
- package/dist/agent-src/commands/fix/pr-comments.md +3 -6
- package/dist/agent-src/commands/implement-ticket.md +4 -4
- package/dist/agent-src/commands/jira-ticket.md +1 -1
- package/dist/agent-src/commands/memory/add.md +4 -16
- package/dist/agent-src/commands/memory/load.md +5 -6
- package/dist/agent-src/commands/memory/promote.md +3 -16
- package/dist/agent-src/commands/mission/upgrade.md +3 -2
- package/dist/agent-src/commands/optimize/agents-dir.md +2 -2
- package/dist/agent-src/commands/optimize/deep.md +4 -2
- package/dist/agent-src/commands/optimize/rtk.md +4 -26
- package/dist/agent-src/commands/pr/create/description-only.md +18 -15
- package/dist/agent-src/commands/pr/create.md +42 -34
- package/dist/agent-src/commands/pr/merge.md +136 -9
- package/dist/agent-src/commands/prepare-for-review.md +13 -11
- package/dist/agent-src/commands/project/analyze.md +7 -35
- package/dist/agent-src/commands/refine-ticket.md +1 -1
- package/dist/agent-src/commands/review/changes.md +14 -19
- package/dist/agent-src/commands/roadmap/create.md +1 -1
- package/dist/agent-src/commands/roadmap/materialize.md +1 -1
- package/dist/agent-src/commands/roadmap/next.md +10 -1
- package/dist/agent-src/commands/roadmap/process-full.md +56 -42
- package/dist/agent-src/commands/roadmap/resolve-blockers.md +411 -0
- package/dist/agent-src/commands/roadmap/triage-parked.md +325 -0
- package/dist/agent-src/commands/roadmap.md +18 -4
- package/dist/agent-src/commands/tests/create.md +17 -3
- package/dist/agent-src/commands/tests/execute.md +22 -5
- package/dist/agent-src/commands/worktree/create.md +7 -3
- package/dist/agent-src/commands/worktree/verify.md +2 -1
- package/dist/agent-src/contexts/authority/decision-revisit-gate-enforcement.md +43 -0
- package/dist/agent-src/contexts/communication/rules-auto/design-fidelity-enforcement.md +24 -0
- package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +5 -3
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +7 -9
- package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +8 -6
- package/dist/agent-src/contexts/contracts/frugality-charter.md +8 -5
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +7 -7
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +0 -1
- package/dist/agent-src/contexts/execution/evidence-discipline.md +2 -2
- package/dist/agent-src/contexts/execution/non-interactive-contract.md +5 -8
- package/dist/agent-src/contexts/execution/rdp-gate.md +4 -4
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +9 -8
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +99 -54
- package/dist/agent-src/contexts/execution/session-canary-enforcement-history.md +67 -0
- package/dist/agent-src/contexts/execution/subagent-routing.md +8 -8
- package/dist/agent-src/contexts/execution/subagent-steering.md +6 -4
- package/dist/agent-src/contexts/execution/terminal-states.md +2 -2
- package/dist/agent-src/contexts/execution/toolchain-resolver.md +95 -7
- package/dist/agent-src/contexts/subagent-configuration.md +1 -1
- package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +39 -0
- package/dist/agent-src/guidelines/agent-infra/context-hygiene-mechanics.md +1 -1
- package/dist/agent-src/guidelines/agent-infra/cross-source-consistency-mechanics.md +8 -10
- package/dist/agent-src/guidelines/agent-infra/frontier-reasoning-operating-profile.md +3 -2
- package/dist/agent-src/guidelines/agent-infra/layered-settings.md +13 -1
- package/dist/agent-src/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
- package/dist/agent-src/guidelines/agent-infra/self-improvement-pipeline.md +10 -3
- package/dist/agent-src/guidelines/agent-infra/size-and-scope.md +36 -0
- package/dist/agent-src/guidelines/php/git.md +15 -2
- package/dist/agent-src/rules/active-remediation.md +1 -2
- package/dist/agent-src/rules/analysis-skill-routing.md +0 -1
- package/dist/agent-src/rules/architecture.md +0 -1
- package/dist/agent-src/rules/artifact-drafting-protocol.md +0 -1
- package/dist/agent-src/rules/artifact-engagement-recording.md +0 -1
- package/dist/agent-src/rules/augment-edit-discipline.md +0 -1
- package/dist/agent-src/rules/autonomous-execution.md +1 -1
- package/dist/agent-src/rules/brand-consistency.md +0 -1
- package/dist/agent-src/rules/brand-source-of-truth.md +0 -1
- package/dist/agent-src/rules/broken-access-control.md +1 -1
- package/dist/agent-src/rules/cli-output-handling.md +0 -1
- package/dist/agent-src/rules/code-comment-discipline.md +0 -1
- package/dist/agent-src/rules/code-provenance.md +0 -1
- package/dist/agent-src/rules/command-suggestion-policy.md +1 -1
- package/dist/agent-src/rules/commit-conventions.md +1 -2
- package/dist/agent-src/rules/communication-through-line.md +1 -2
- package/dist/agent-src/rules/content-quoting-floor.md +1 -2
- package/dist/agent-src/rules/context-hygiene.md +6 -6
- package/dist/agent-src/rules/copilot-routing.md +1 -2
- package/dist/agent-src/rules/council-availability.md +4 -4
- package/dist/agent-src/rules/cross-source-consistency.md +4 -6
- package/dist/agent-src/rules/decision-revisit-gate.md +17 -35
- package/dist/agent-src/rules/delegation-policy.md +2 -3
- package/dist/agent-src/rules/design-fidelity.md +12 -16
- package/dist/agent-src/rules/design-review-after-ui-write.md +3 -3
- package/dist/agent-src/rules/devcontainer-routing.md +1 -2
- package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -1
- package/dist/agent-src/rules/docker-commands.md +0 -1
- package/dist/agent-src/rules/domain-adoption-policy.md +0 -1
- package/dist/agent-src/rules/domain-safety-disclaimer.md +1 -1
- package/dist/agent-src/rules/domain-safety-pii.md +1 -1
- package/dist/agent-src/rules/domain-safety-retention.md +1 -1
- package/dist/agent-src/rules/downstream-changes.md +0 -1
- package/dist/agent-src/rules/engineering-safety-floor.md +1 -1
- package/dist/agent-src/rules/evaluator-independence.md +3 -2
- package/dist/agent-src/rules/external-code-graph-interop.md +0 -1
- package/dist/agent-src/rules/external-reference-deep-dive.md +0 -1
- package/dist/agent-src/rules/fast-path-marker-visibility.md +2 -2
- package/dist/agent-src/rules/finance-safety-floor.md +1 -1
- package/dist/agent-src/rules/fix-what-you-see.md +6 -32
- package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +0 -1
- package/dist/agent-src/rules/git-history-discipline.md +2 -2
- package/dist/agent-src/rules/guidelines.md +0 -1
- package/dist/agent-src/rules/history-discipline.md +0 -1
- package/dist/agent-src/rules/icon-consistency.md +0 -1
- package/dist/agent-src/rules/image-likeness-and-rights.md +1 -1
- package/dist/agent-src/rules/improve-before-implement.md +0 -1
- package/dist/agent-src/rules/invite-challenge.md +0 -1
- package/dist/agent-src/rules/laravel-routing.md +1 -2
- package/dist/agent-src/rules/laravel-translations.md +0 -1
- package/dist/agent-src/rules/legal-safety-floor.md +3 -3
- package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -1
- package/dist/agent-src/rules/linked-projects-onboarding-gate.md +0 -1
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -1
- package/dist/agent-src/rules/markdown-safe-codeblocks.md +0 -1
- package/dist/agent-src/rules/media-governance-routing.md +13 -17
- package/dist/agent-src/rules/media-sync-ground-truth.md +0 -1
- package/dist/agent-src/rules/minimal-safe-diff.md +0 -1
- package/dist/agent-src/rules/missing-skill-recovery.md +0 -1
- package/dist/agent-src/rules/missing-tool-handling.md +0 -1
- package/dist/agent-src/rules/model-recommendation.md +0 -1
- package/dist/agent-src/rules/neighbour-precedence.md +64 -0
- package/dist/agent-src/rules/no-attribution-footers.md +0 -1
- package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +0 -1
- package/dist/agent-src/rules/no-pr-progress-comments.md +0 -1
- package/dist/agent-src/rules/no-roadmap-references.md +0 -1
- package/dist/agent-src/rules/notes-first-reasoning.md +0 -1
- package/dist/agent-src/rules/onboarding-gate.md +0 -1
- package/dist/agent-src/rules/output-discipline.md +0 -1
- package/dist/agent-src/rules/package-ci-checks.md +0 -1
- package/dist/agent-src/rules/persona-governance.md +4 -5
- package/dist/agent-src/rules/php-coding.md +0 -1
- package/dist/agent-src/rules/prefer-enums-over-literals.md +0 -1
- package/dist/agent-src/rules/preservation-guard.md +0 -1
- package/dist/agent-src/rules/provider-lifecycle-discipline.md +3 -4
- package/dist/agent-src/rules/question-not-instruction.md +1 -1
- package/dist/agent-src/rules/reviewer-awareness.md +0 -1
- package/dist/agent-src/rules/roadmap-ci-steps-policy.md +0 -1
- package/dist/agent-src/rules/roadmap-progress-sync.md +4 -7
- package/dist/agent-src/rules/role-mode-adherence.md +0 -1
- package/dist/agent-src/rules/rule-type-governance.md +0 -1
- package/dist/agent-src/rules/runtime-safety.md +1 -1
- package/dist/agent-src/rules/scale-discipline.md +0 -1
- package/dist/agent-src/rules/secret-vcs-guard.md +1 -1
- package/dist/agent-src/rules/security-sensitive-stop.md +3 -3
- package/dist/agent-src/rules/self-repair-loop.md +0 -1
- package/dist/agent-src/rules/senior-engineering-discipline.md +0 -1
- package/dist/agent-src/rules/session-canary.md +7 -62
- package/dist/agent-src/rules/settings-ask-protocol.md +6 -6
- package/dist/agent-src/rules/size-enforcement.md +0 -1
- package/dist/agent-src/rules/skill-improvement-trigger.md +2 -3
- package/dist/agent-src/rules/skill-quality.md +0 -1
- package/dist/agent-src/rules/slash-command-routing-policy.md +0 -1
- package/dist/agent-src/rules/source-confidentiality.md +1 -1
- package/dist/agent-src/rules/source-discovery-gate.md +0 -1
- package/dist/agent-src/rules/source-of-truth.md +0 -1
- package/dist/agent-src/rules/spreadsheet-source-quality.md +1 -2
- package/dist/agent-src/rules/strategy-safety-floor.md +1 -1
- package/dist/agent-src/rules/symfony-routing.md +1 -2
- package/dist/agent-src/rules/think-before-action.md +0 -1
- package/dist/agent-src/rules/token-budget-discipline.md +12 -46
- package/dist/agent-src/rules/token-efficiency.md +0 -1
- package/dist/agent-src/rules/token-optimizer-maintenance.md +0 -1
- package/dist/agent-src/rules/tool-safety.md +1 -1
- package/dist/agent-src/rules/ui-audit-gate.md +1 -2
- package/dist/agent-src/rules/untrusted-input-defense.md +1 -1
- package/dist/agent-src/rules/upstream-proposal.md +0 -1
- package/dist/agent-src/rules/user-interaction.md +0 -1
- package/dist/agent-src/rules/user-interrupt-priority.md +1 -1
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
- package/dist/agent-src/scripts/deferral_carry.ts +339 -0
- package/dist/agent-src/scripts/gate_budget.ts +47 -23
- package/dist/agent-src/scripts/gate_execute.ts +12 -4
- package/dist/agent-src/scripts/update_roadmap_progress.ts +4 -1
- package/dist/agent-src/skills/ai-council/SKILL.md +7 -4
- package/dist/agent-src/skills/ai-council/references/cost-and-redaction.md +21 -11
- package/dist/agent-src/skills/ai-council/references/procedure.md +12 -6
- package/dist/agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
- package/dist/agent-src/skills/api-design/data/manifest.json +2 -2
- package/dist/agent-src/skills/api-testing/SKILL.md +17 -0
- package/dist/agent-src/skills/bug-analyzer/SKILL.md +1 -1
- package/dist/agent-src/skills/code-intelligence/SKILL.md +14 -2
- package/dist/agent-src/skills/command-routing/SKILL.md +3 -4
- package/dist/agent-src/skills/conventional-commits-writing/SKILL.md +127 -139
- package/dist/agent-src/skills/corpus-grounding/ATTRIBUTION.md +39 -0
- package/dist/agent-src/skills/corpus-grounding/SKILL.md +8 -2
- package/dist/agent-src/skills/database/data/manifest.json +2 -2
- package/dist/agent-src/skills/database/data/query-tuning.csv +13 -13
- package/dist/agent-src/skills/design-system-capture/SKILL.md +0 -3
- package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
- package/dist/agent-src/skills/estimate-ticket/SKILL.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +2 -2
- package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
- package/dist/agent-src/skills/feature-planning/SKILL.md +1 -1
- package/dist/agent-src/skills/git-workflow/SKILL.md +44 -44
- package/dist/agent-src/skills/git-workflow/references/branch-update.md +312 -0
- package/dist/agent-src/skills/git-workflow/references/commit-subject.md +205 -0
- package/dist/agent-src/skills/iconography/SKILL.md +1 -1
- package/dist/agent-src/skills/jira-integration/SKILL.md +3 -3
- package/dist/agent-src/skills/js-library-packaging/SKILL.md +2 -2
- package/dist/agent-src/skills/merge-conflicts/SKILL.md +10 -4
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
- package/dist/agent-src/skills/po-discovery/SKILL.md +1 -1
- package/dist/agent-src/skills/quality-tools/SKILL.md +11 -1
- package/dist/agent-src/skills/quality-tools/references/go-tools.md +76 -0
- package/dist/agent-src/skills/quality-tools/references/python-tools.md +104 -0
- package/dist/agent-src/skills/refine-ticket/SKILL.md +4 -4
- package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +2 -2
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +5 -3
- package/dist/agent-src/skills/source-discovery/SKILL.md +1 -1
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +3 -3
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +4 -2
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +7 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +17 -0
- package/dist/agent-src/skills/test-performance/SKILL.md +19 -1
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +2 -2
- package/dist/agent-src/skills/using-git-worktrees/SKILL.md +1 -1
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +2 -2
- package/dist/agent-src/templates/AGENTS.md +1 -1
- package/dist/agent-src/templates/agent-settings.md +46 -129
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +3 -3
- package/dist/agent-src/templates/command.md +2 -2
- package/dist/agent-src/templates/roadmaps.md +6 -1
- package/dist/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +0 -6
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +11 -7
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
- package/dist/agent-src/templates/scripts/work_engine/hooks/builtin/decision_trace.ts +3 -2
- package/dist/agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.ts +2 -1
- package/dist/agent-src/templates/scripts/work_engine/hooks/settings.ts +0 -6
- package/dist/agent-src/templates/scripts/work_engine/scoring/decision_engine.ts +9 -12
- package/dist/agent-src/templates/scripts/work_engine/scoring/memory_visibility.ts +2 -1
- package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
- package/dist/agent-src/templates/skill.md +1 -1
- package/dist/cli/registry.js +2 -1
- package/dist/cli-delegate/archive_completed_roadmaps.js +92 -12
- package/dist/cli-delegate/check_update_banner.js +207 -120
- package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
- package/dist/cli-delegate/{chunk-FP36SWJD.js → chunk-5GPULQBE.js} +2797 -338
- package/dist/cli-delegate/{chunk-IGD6ERX6.js → chunk-6SRJTYJS.js} +42 -176
- package/dist/cli-delegate/chunk-6V5FMA3P.js +186 -0
- package/dist/cli-delegate/{chunk-M2DPGFZ4.js → chunk-6V6TA25H.js} +22 -152
- package/dist/cli-delegate/chunk-6YQC2HLF.js +415 -0
- package/dist/cli-delegate/{chunk-E4XIZDIB.js → chunk-7HKRE54K.js} +1 -64
- package/dist/cli-delegate/chunk-7IQE3D5S.js +191 -0
- package/dist/cli-delegate/{chunk-YJ72UIAK.js → chunk-AED2VQGM.js} +1245 -249
- package/dist/cli-delegate/{chunk-7HQ52DHU.js → chunk-AL3RZ22D.js} +1 -1
- package/dist/cli-delegate/chunk-DEIV7XP7.js +108 -0
- package/dist/cli-delegate/{chunk-DBH4AHDK.js → chunk-ED7KCMLB.js} +9 -11
- package/dist/cli-delegate/chunk-F3QDG2GT.js +2569 -0
- package/dist/cli-delegate/{chunk-6KFEC3G7.js → chunk-G5VVIZRC.js} +3 -3
- package/dist/cli-delegate/{chunk-FC75PGJE.js → chunk-H76PHZBI.js} +149 -61
- package/dist/cli-delegate/chunk-IAYTIIQW.js +67 -0
- package/dist/cli-delegate/chunk-JQOM7TTM.js +1201 -0
- package/dist/cli-delegate/chunk-KB5FZ655.js +208 -0
- package/dist/cli-delegate/{chunk-7RQP4UDH.js → chunk-KR6EM23A.js} +235 -1455
- package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-KXXWJTDO.js} +6 -11
- package/dist/cli-delegate/{chunk-T7L2QK3K.js → chunk-LQOCRFWT.js} +1 -1
- package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-MDPPKQCQ.js} +58 -9
- package/dist/cli-delegate/{chunk-4XFP2Y3E.js → chunk-MSUL5NYP.js} +5 -5
- package/dist/cli-delegate/chunk-OAFSJBHH.js +272 -0
- package/dist/cli-delegate/{chunk-4E2W63DA.js → chunk-OKJUIAPS.js} +62 -1
- package/dist/cli-delegate/{chunk-QAJS5YWY.js → chunk-PHD7OOLE.js} +2 -1
- package/dist/cli-delegate/{chunk-P6ZHP5W2.js → chunk-PWX7CKMD.js} +4 -4
- package/dist/cli-delegate/{chunk-NDJLNC6Q.js → chunk-T6BP34WI.js} +1 -0
- package/dist/cli-delegate/{chunk-7XGBX6ZH.js → chunk-T7XSJSUQ.js} +1144 -589
- package/dist/cli-delegate/chunk-ULQRUR23.js +208 -0
- package/dist/cli-delegate/chunk-WQWXGIWB.js +1779 -0
- package/dist/cli-delegate/{chunk-RGNIWTEA.js → chunk-XQTPXKFA.js} +1 -1
- package/dist/cli-delegate/{chunk-NUHLI2RX.js → chunk-YCU3TIXH.js} +2 -0
- package/dist/cli-delegate/chunk-ZBZV2G5R.js +210 -0
- package/dist/cli-delegate/{chunk-WGXTVKCQ.js → chunk-ZUZKZYCN.js} +5 -3
- package/dist/cli-delegate/cmd_analyze_session.js +223 -43
- package/dist/cli-delegate/cmd_brand_status.js +3 -2
- package/dist/cli-delegate/cmd_conformance.js +1252 -249
- package/dist/cli-delegate/cmd_converge.js +3 -3
- package/dist/cli-delegate/cmd_doctor.js +19 -9
- package/dist/cli-delegate/cmd_doctor_neighbours.js +30 -0
- package/dist/cli-delegate/cmd_explain.js +45 -21
- package/dist/cli-delegate/cmd_export.js +13 -10
- package/dist/cli-delegate/cmd_fleet.js +3 -3
- package/dist/cli-delegate/cmd_git_convention.js +1851 -0
- package/dist/cli-delegate/cmd_handoff.js +44 -22
- package/dist/cli-delegate/cmd_mcp_available.js +3 -2
- package/dist/cli-delegate/cmd_memory_get.js +3 -1
- package/dist/cli-delegate/cmd_migrate.js +3 -3
- package/dist/cli-delegate/cmd_packs_active.js +5 -4
- package/dist/cli-delegate/cmd_preflight.js +6 -6
- package/dist/cli-delegate/cmd_prune.js +14 -11
- package/dist/cli-delegate/cmd_refresh.js +2 -1
- package/dist/cli-delegate/cmd_route_audit.js +10 -8
- package/dist/cli-delegate/cmd_route_explain.js +4 -4
- package/dist/cli-delegate/cmd_session_recycle.js +10 -7
- package/dist/cli-delegate/cmd_settings_check.js +54 -7
- package/dist/cli-delegate/cmd_settings_get.js +18 -8
- package/dist/cli-delegate/cmd_settings_migrate.js +13 -10
- package/dist/cli-delegate/cmd_settings_set.js +4 -4
- package/dist/cli-delegate/cmd_sync.js +14 -11
- package/dist/cli-delegate/cmd_uninstall.js +15 -12
- package/dist/cli-delegate/cmd_update.js +7 -7
- package/dist/cli-delegate/cmd_upgrade.js +9 -8
- package/dist/cli-delegate/cmd_validate.js +15 -12
- package/dist/cli-delegate/cmd_versions.js +5 -5
- package/dist/cli-delegate/deferral_carry.js +16 -0
- package/dist/cli-delegate/gate_budget.js +1 -1
- package/dist/cli-delegate/gate_execute.js +3 -3
- package/dist/cli-delegate/roadmap_gates.js +6 -6
- package/dist/cli-delegate/update_roadmap_progress.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +232 -141
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +26 -9
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +7 -4
- package/dist/hooks/dispatch.js +5655 -6623
- package/dist/hooks/dispatch.sha256 +1 -1
- package/dist/install/claudePathsPlan.js +47 -6
- package/dist/install/claudeRuleRewrite.js +7 -0
- package/dist/install/emit_host_rules_cli.js +39 -36
- package/dist/install/globalRuleScope.js +87 -0
- package/dist/install/host_rule_ownership.js +160 -0
- package/dist/install/install.mjs +2336 -1249
- package/dist/install/installThinLayer.js +289 -0
- package/dist/install/installedRuleLinks.js +262 -0
- package/dist/install/wizard-plan.js +24 -0
- package/dist/mcp/awesome-mcp-servers.row.md +1 -1
- package/dist/mcp/mcp-cloudflare-catalogue.json +2 -2
- package/dist/mcp/registry-manifest.json +5 -5
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +36504 -30986
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +26 -120
- package/dist/scripts/_lib/claude_settings_hooks.js +10 -8
- package/dist/scripts/_lib/compile_time_toggles.js +12 -21
- package/dist/scripts/_lib/git_convention.js +232 -0
- package/dist/scripts/_lib/global_deploy_inventory.js +730 -0
- package/dist/scripts/_lib/installed_layer.js +704 -0
- package/dist/scripts/_lib/lean_projection_mode.js +218 -15
- package/dist/scripts/_lib/package_root.js +68 -0
- package/dist/scripts/_lib/rule_consequence_class.js +45 -0
- package/dist/scripts/_lib/rule_injection.js +67 -10
- package/dist/scripts/_lib/rule_law_section.js +122 -0
- package/dist/scripts/_lib/settings_removed_keys.js +168 -0
- package/dist/scripts/_lib/thin_rules.js +535 -0
- package/dist/scripts/_lib/windsurf_render.js +60 -0
- package/dist/scripts/condense.js +14 -26
- package/dist/scripts/project_thin_rules.js +14 -294
- package/dist/server/app.js +2 -1
- package/dist/server/gitKeysGate.js +105 -0
- package/dist/server/routes/settings.js +67 -8
- package/dist/server/routes/wizard.js +4 -0
- package/dist/server/schemas/settings.js +29 -81
- package/dist/shared/skillRanking.js +103 -16
- package/dist/ui/assets/{index-Cwq4d0hX.js → index-C3djEJah.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/docs/MIGRATION.md +72 -1
- package/docs/contracts/settings-classes.md +39 -67
- package/docs/guidelines/agent-infra/active-remediation-mechanics.md +39 -0
- package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +1 -1
- package/docs/guidelines/agent-infra/cross-source-consistency-mechanics.md +8 -10
- package/docs/guidelines/agent-infra/frontier-reasoning-operating-profile.md +3 -2
- package/docs/guidelines/agent-infra/layered-settings.md +13 -1
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
- package/docs/guidelines/agent-infra/self-improvement-pipeline.md +10 -3
- package/docs/guidelines/agent-infra/size-and-scope.md +36 -0
- package/docs/guidelines/php/git.md +15 -2
- package/package.json +1 -1
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
- package/src/agent-src/scripts/deferral_carry.ts +339 -0
- package/src/agent-src/scripts/gate_budget.ts +47 -23
- package/src/agent-src/scripts/gate_execute.ts +12 -4
- package/src/agent-src/scripts/update_roadmap_progress.ts +4 -1
- package/src/agent-src/templates/AGENTS.md +1 -1
- package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +0 -6
- package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +11 -7
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_trace.ts +3 -2
- package/src/agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.ts +2 -1
- package/src/agent-src/templates/scripts/work_engine/hooks/settings.ts +0 -6
- package/src/agent-src/templates/scripts/work_engine/scoring/decision_engine.ts +9 -12
- package/src/agent-src/templates/scripts/work_engine/scoring/memory_visibility.ts +2 -1
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
- package/src/config/agent-settings.template.yml +141 -334
- package/src/config/agents-paths.yml +16 -0
- package/src/config/assurance-capability-registry.json +7 -3
- package/src/config/ci-local-parity.yml +3 -0
- package/src/config/continuity-surface.json +10 -3
- package/src/config/discovery/packs.yml +17 -1
- package/src/config/evaluator-budgets.json +23 -10
- package/src/config/gate-coverage.yml +138 -3
- package/src/config/gate-violation-baselines.json +64 -16
- package/src/config/gitignore-block.txt +19 -0
- package/src/config/hook-bundle-budget.json +25 -0
- package/src/config/hook-token-budget.json +6 -1
- package/src/config/host-instruction-limits.json +33 -0
- package/src/config/release-gate-locality.yml +2 -1
- package/src/config/routing-coverage-seed.json +67 -4
- package/src/config/rule-activation-census.json +7 -3
- package/src/config/rule-consequence-class.json +161 -0
- package/src/config/rule-law-ceilings.json +105 -0
- package/src/config/rule-obligations.json +833 -0
- package/src/install/claudePathsPlan.ts +46 -4
- package/src/install/claudeRuleRewrite.ts +10 -1
- package/src/install/emit_host_rules_cli.ts +43 -34
- package/src/install/globalRuleScope.ts +109 -0
- package/src/install/host_rule_ownership.ts +170 -0
- package/src/install/installThinLayer.ts +378 -0
- package/src/install/installedRuleLinks.ts +308 -0
- package/src/install/wizard-plan.ts +24 -0
- package/src/scripts/_cli/cmd_analyze_session.ts +9 -15
- package/src/scripts/_cli/cmd_conformance.ts +13 -2
- package/src/scripts/_cli/cmd_doctor.ts +33 -81
- package/src/scripts/_cli/cmd_doctor_neighbours.ts +426 -0
- package/src/scripts/_cli/cmd_explain.ts +72 -14
- package/src/scripts/_cli/cmd_git_convention.ts +637 -0
- package/src/scripts/_cli/cmd_settings_check.ts +66 -8
- package/src/scripts/_cli/cmd_settings_get.ts +18 -3
- package/src/scripts/_cli/doctor_execution.ts +72 -1
- package/src/scripts/_dispatch.bash +53 -4
- package/src/scripts/_lib/agent_settings.ts +26 -124
- package/src/scripts/_lib/blocked_by_marker.ts +1 -1
- package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
- package/src/scripts/_lib/compile_time_toggles.ts +12 -21
- package/src/scripts/_lib/dispatch_import_closure.ts +163 -0
- package/src/scripts/_lib/estate_offsets.ts +241 -0
- package/src/scripts/_lib/exemption_shape.ts +143 -0
- package/src/scripts/_lib/fnv.ts +23 -0
- package/src/scripts/_lib/foreign_scan.ts +106 -0
- package/src/scripts/_lib/git_base_ref.ts +92 -0
- package/src/scripts/_lib/git_convention.ts +291 -0
- package/src/scripts/_lib/git_convention_carrier.ts +389 -0
- package/src/scripts/_lib/git_convention_grammar.ts +246 -0
- package/src/scripts/_lib/git_convention_measure.ts +344 -0
- package/src/scripts/_lib/gitignore_block.ts +314 -0
- package/src/scripts/_lib/global_deploy_inventory.ts +106 -0
- package/src/scripts/_lib/graph_feeder_record.ts +437 -0
- package/src/scripts/_lib/hook_settings.ts +73 -0
- package/src/scripts/_lib/host_capability.ts +40 -19
- package/src/scripts/_lib/host_hook_merge.ts +165 -0
- package/src/scripts/_lib/installed_layer.ts +963 -0
- package/src/scripts/_lib/json_merge.ts +82 -0
- package/src/scripts/_lib/json_pointers.ts +93 -11
- package/src/scripts/_lib/knowledge_global.ts +14 -1
- package/src/scripts/_lib/lean_projection_mode.ts +275 -15
- package/src/scripts/_lib/machine_wake.ts +16 -3
- package/src/scripts/_lib/module_reach.ts +456 -0
- package/src/scripts/_lib/neighbour_census.ts +760 -0
- package/src/scripts/_lib/neighbour_contradictions.ts +236 -0
- package/src/scripts/_lib/neighbour_scan.ts +122 -0
- package/src/scripts/_lib/neighbour_tool_use.ts +187 -0
- package/src/scripts/_lib/prompt_shape.ts +46 -0
- package/src/scripts/_lib/ratification_artifact.ts +220 -1
- package/src/scripts/_lib/ratification_subject.ts +92 -0
- package/src/scripts/_lib/release_findings_ingest.ts +15 -7
- package/src/scripts/_lib/release_highlights.ts +5 -0
- package/src/scripts/_lib/release_migration_gate.ts +19 -3
- package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
- package/src/scripts/_lib/review_coverage.ts +134 -0
- package/src/scripts/_lib/rule_consequence_class.ts +71 -0
- package/src/scripts/_lib/rule_injection.ts +130 -10
- package/src/scripts/_lib/rule_law_section.ts +147 -0
- package/src/scripts/_lib/rule_layer_overlap.ts +58 -0
- package/src/scripts/_lib/rule_obligations.ts +205 -0
- package/src/scripts/_lib/self_repair.ts +1 -1
- package/src/scripts/_lib/settings_removed_keys.ts +173 -0
- package/src/scripts/_lib/skill_origin.ts +254 -0
- package/src/scripts/_lib/structured_ask.ts +64 -21
- package/src/scripts/_lib/test_red_state.ts +5 -0
- package/src/scripts/_lib/thin_rules.ts +620 -0
- package/src/scripts/_lib/touched_file_quality.ts +570 -0
- package/src/scripts/_lib/turn_end_refusals.ts +77 -2
- package/src/scripts/_lib/windsurf_render.ts +63 -0
- package/src/scripts/_lib/worker_budget.ts +1 -1
- package/src/scripts/adr/regenerate_index.ts +16 -1
- package/src/scripts/ai_council/cli_call_budget.ts +13 -5
- package/src/scripts/ai_council/clients.ts +26 -57
- package/src/scripts/ai_council/config.ts +8 -8
- package/src/scripts/ai_council/modification_review.ts +100 -0
- package/src/scripts/ai_council/modification_review_questions.ts +90 -0
- package/src/scripts/ai_council/orchestrator.ts +5 -5
- package/src/scripts/ai_council/spend_gate.ts +18 -7
- package/src/scripts/ai_council/vendor_defaults.ts +45 -0
- package/src/scripts/ai_team/config.ts +11 -16
- package/src/scripts/audit_skill_overlap.ts +261 -0
- package/src/scripts/audit_user_type_axis.ts +20 -8
- package/src/scripts/before_complete_hook.ts +243 -12
- package/src/scripts/bench_ab_clone.ts +1 -2
- package/src/scripts/bench_concern_spawn_path.ts +129 -0
- package/src/scripts/bench_graph_feeder_latency.ts +584 -0
- package/src/scripts/bench_touched_file_quality.ts +238 -0
- package/src/scripts/check_agent_artifact_location.ts +36 -0
- package/src/scripts/check_branch_freshness.ts +147 -24
- package/src/scripts/check_enforcement_coverage.ts +130 -0
- package/src/scripts/check_enforcement_matrix.ts +377 -20
- package/src/scripts/check_estate_count.ts +130 -153
- package/src/scripts/check_evaluator_budgets.ts +4 -0
- package/src/scripts/check_finding_dispositions.ts +196 -21
- package/src/scripts/check_gate_coverage.ts +4 -3
- package/src/scripts/check_gate_reachability.ts +48 -1
- package/src/scripts/check_generator_sync.ts +48 -0
- package/src/scripts/check_hook_bundle_composition.ts +477 -0
- package/src/scripts/check_kernel_edit_ratified.ts +132 -14
- package/src/scripts/check_memory.ts +7 -1
- package/src/scripts/check_pack_size.ts +1 -1
- package/src/scripts/check_release_highlights.ts +10 -0
- package/src/scripts/check_release_manifest_content.ts +189 -0
- package/src/scripts/check_release_pr_shape.ts +38 -15
- package/src/scripts/check_standing_rule_delivery.ts +129 -13
- package/src/scripts/check_suppression_hygiene.ts +2 -5
- package/src/scripts/check_verify_expectation_delta.ts +521 -0
- package/src/scripts/ci_settle.ts +42 -5
- package/src/scripts/classify_merge_risk.ts +320 -0
- package/src/scripts/closure_scan.ts +91 -1
- package/src/scripts/code_graph/detect.ts +127 -7
- package/src/scripts/code_graph/foreign.ts +194 -0
- package/src/scripts/code_graph/query.ts +217 -4
- package/src/scripts/code_graph/types.ts +26 -2
- package/src/scripts/code_graph/validate.ts +3 -1
- package/src/scripts/code_graph/verbs.ts +103 -0
- package/src/scripts/command_suggester/settings.ts +8 -108
- package/src/scripts/command_suggester/types.ts +4 -5
- package/src/scripts/compile_hook_manifest.ts +33 -1
- package/src/scripts/condense.ts +14 -27
- package/src/scripts/consumer_matrix.ts +102 -0
- package/src/scripts/context_hygiene_hook.ts +13 -23
- package/src/scripts/cost/preflight.mjs +12 -1
- package/src/scripts/council_cli.ts +7 -7
- package/src/scripts/dispatch_r2_reviewer.ts +24 -11
- package/src/scripts/estate_exemption_shape.ts +202 -0
- package/src/scripts/explain_run.ts +8 -13
- package/src/scripts/generate_git_convention_grammar.ts +127 -0
- package/src/scripts/generate_host_cost_table.ts +21 -4
- package/src/scripts/handoff_context_hook.ts +86 -37
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +175 -15
- package/src/scripts/hooks/block_config_weakening.ts +115 -7
- package/src/scripts/hooks/block_plumbing_writes.ts +37 -5
- package/src/scripts/hooks/code_graph_context_hook.ts +252 -23
- package/src/scripts/hooks/concern_failure_policy.ts +280 -0
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +2 -3
- package/src/scripts/hooks/dispatch_hook.ts +52 -52
- package/src/scripts/hooks/evidence_independence.ts +12 -4
- package/src/scripts/hooks/exit_codes.ts +2 -1
- package/src/scripts/hooks/host_lowering.json +1 -1
- package/src/scripts/hooks/host_lowering.ts +19 -1
- package/src/scripts/hooks/mcp_usage_observation_hook.ts +121 -0
- package/src/scripts/hooks/one_question_per_ask_hook.ts +41 -25
- package/src/scripts/hooks/payload_stub.ts +10 -1
- package/src/scripts/hooks/push_settle_hook.ts +4 -3
- package/src/scripts/hooks/rule_inject_hook.ts +795 -60
- package/src/scripts/hooks/state_io.ts +8 -4
- package/src/scripts/hooks/table_fingerprint.ts +21 -0
- package/src/scripts/hooks/telemetry_usage_hook.ts +24 -7
- package/src/scripts/hooks/turn_end_gate_hook.ts +85 -0
- package/src/scripts/hooks_status.ts +112 -0
- package/src/scripts/injection_scan_hook.ts +7 -0
- package/src/scripts/install-hooks.sh +42 -14
- package/src/scripts/install.ts +78 -215
- package/src/scripts/installed_layer_report.ts +103 -0
- package/src/scripts/language_mirror_hook.ts +15 -6
- package/src/scripts/learning_sidecar.ts +119 -8
- package/src/scripts/lint_blocker_citations.ts +198 -0
- package/src/scripts/lint_evidence_artifacts.ts +9 -0
- package/src/scripts/lint_hook_concern_budget.ts +10 -21
- package/src/scripts/lint_hook_manifest.ts +53 -0
- package/src/scripts/lint_major_migration_sections.ts +156 -26
- package/src/scripts/lint_pack_boundaries.ts +465 -29
- package/src/scripts/lint_roadmap_blockers.ts +282 -29
- package/src/scripts/lint_roadmap_complexity.ts +4 -2
- package/src/scripts/lint_rule_law_section.ts +647 -0
- package/src/scripts/lint_rule_skill_pack_reach.ts +17 -0
- package/src/scripts/lint_settings_classes.ts +1 -1
- package/src/scripts/lint_ticked_unmet_criteria.ts +165 -0
- package/src/scripts/lint_workflow_security.ts +173 -6
- package/src/scripts/mcp_server/consumer_tool_catalog.json +53 -0
- package/src/scripts/mcp_server/graph_tools.ts +72 -3
- package/src/scripts/measure_routing_signal.ts +54 -16
- package/src/scripts/measure_skill_ranker_baseline.ts +221 -8
- package/src/scripts/memory_learn_hook.ts +91 -6
- package/src/scripts/model_rule_injection.ts +55 -8
- package/src/scripts/onboarding_gate_hook.ts +7 -6
- package/src/scripts/print_required_checks.ts +15 -3
- package/src/scripts/probe_host_compliance.ts +33 -5
- package/src/scripts/probe_turnaround.ts +183 -3
- package/src/scripts/project_thin_rules.ts +34 -327
- package/src/scripts/ratification_header.ts +77 -0
- package/src/scripts/refusal_envelope.ts +138 -0
- package/src/scripts/release_publication.ts +29 -3
- package/src/scripts/report_dispatch_import_closure.ts +76 -0
- package/src/scripts/report_evidence_temperature.ts +657 -0
- package/src/scripts/report_installed_rule_links.ts +274 -0
- package/src/scripts/report_label_agreement.ts +233 -0
- package/src/scripts/report_module_reach.ts +212 -0
- package/src/scripts/report_obligation_mechanism.ts +228 -0
- package/src/scripts/report_parked_blockers.ts +222 -0
- package/src/scripts/report_required_checks_drift.ts +196 -0
- package/src/scripts/report_single_token_triggers.ts +277 -0
- package/src/scripts/report_skill_ranker_confusion.ts +543 -0
- package/src/scripts/report_standing_payload_by_host.ts +188 -15
- package/src/scripts/report_verification_record_coverage.ts +306 -0
- package/src/scripts/routing_doctor.ts +2 -2
- package/src/scripts/rule_activation_census.ts +5 -2
- package/src/scripts/rule_trigger_eval.ts +9 -9
- package/src/scripts/schemas/agent-settings.schema.json +17 -31
- package/src/scripts/schemas/command.schema.json +1 -1
- package/src/scripts/schemas/review-findings.schema.json +3 -2
- package/src/scripts/schemas/rule.schema.json +5 -0
- package/src/scripts/schemas/skill.schema.json +1 -1
- package/src/scripts/self_review_gate.ts +101 -9
- package/src/scripts/skill_tools/score_skill_relevance.ts +314 -40
- package/src/scripts/sweep_skill_ranker_signals.ts +129 -0
- package/src/scripts/sync_gitignore.ts +29 -235
- package/src/scripts/sync_pr_branch.ts +403 -132
- package/src/scripts/trigger_eval_grandfather.json +0 -21
- package/src/server/schemas/settings.ts +42 -176
- package/src/shared/skillRanking.ts +169 -14
- package/dist/cli-delegate/chunk-5R7HOLAF.js +0 -99
- package/dist/cli-delegate/chunk-JNMVLWLR.js +0 -4457
- package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
- package/src/scripts/_lib/config_chain.ts +0 -477
- package/src/scripts/_lib/conformance_report.ts +0 -403
- package/src/scripts/_lib/eval_discrimination.ts +0 -95
- package/src/scripts/_lib/evaluator_promotion.ts +0 -225
- package/src/scripts/_lib/file_slicer.ts +0 -117
- package/src/scripts/_lib/legacy_boundary_map.ts +0 -228
- package/src/scripts/_lib/minimality_tiebreak.ts +0 -168
- package/src/scripts/_lib/overbuild_lens_contract.ts +0 -196
- package/src/scripts/_lib/role_split.ts +0 -338
|
@@ -36,41 +36,90 @@
|
|
|
36
36
|
* does. On any other host, and on a `lean_projection.mode: eager-all` rollback,
|
|
37
37
|
* it emits nothing.
|
|
38
38
|
*
|
|
39
|
+
* THE INSTALL DECLARES THE SCOPE, THE PACKAGE SUPPLIES THE TEXT (step 1.3).
|
|
40
|
+
* A rule is delivered only when the host's own rule directories carry a file
|
|
41
|
+
* for it — `~/.claude/rules/` and `<project>/.claude/rules/`, as a union,
|
|
42
|
+
* because the host loads both. The router is the PACKAGE's list of what could
|
|
43
|
+
* route and is a larger set: 13 router tier rules are maintainer-only by
|
|
44
|
+
* `workspaces`, and before this filter a consumer could receive a body for a
|
|
45
|
+
* rule their install never carried. The two halves cannot be one read — under
|
|
46
|
+
* `delivery` the installed files are thin stubs by construction, so they can say
|
|
47
|
+
* WHICH rules are in scope and never WHAT they say. Where no host rule directory
|
|
48
|
+
* exists at all, nothing declares a scope and no filter applies; see
|
|
49
|
+
* `rule_layer_overlap.hostRuleLayerIds` for why that is not the empty set.
|
|
50
|
+
*
|
|
51
|
+
* THE HOST FORM, BY THE PROJECTOR'S OWN PARSER (step 1.4). Delivered text is
|
|
52
|
+
* `rule_law_section.ruleBody` of the file: frontmatter and HTML comments
|
|
53
|
+
* removed. Both are bytes the model cannot act on — the frontmatter is the
|
|
54
|
+
* routing surface the router has already read, and the comments are authoring
|
|
55
|
+
* scaffolding no host renders. `project_thin_rules.ts` calls the same function
|
|
56
|
+
* for the same reason, so "what a stub carries" and "what a delivery carries"
|
|
57
|
+
* cannot drift into two spellings. A file with nothing left after the strip is
|
|
58
|
+
* not delivered: an empty `<rule>` element is framing with no content in it.
|
|
59
|
+
*
|
|
60
|
+
* ONE STRING THE HOST DOES NOT REPLACE, AND A MANIFEST (step 1.5). Claude Code
|
|
61
|
+
* substitutes an `additionalContext` over 10,000 CHARACTERS with a path and a
|
|
62
|
+
* 2,000-character preview, so an oversized delivery does not arrive truncated —
|
|
63
|
+
* it does not arrive. {@link COMPOSED_CHARS} bounds the whole rule-produced
|
|
64
|
+
* string at 8,000, and `compose` fills it in the order D1 fixes: the law of
|
|
65
|
+
* every matched high-consequence rule, then the highest-priority full body that
|
|
66
|
+
* fits, then further bodies, then the law of everything still unsent, then the
|
|
67
|
+
* manifest. Nothing is ever cut mid-obligation — a rule whose law alone does not
|
|
68
|
+
* fit is REPORTED, because half a law reads like a whole one.
|
|
69
|
+
*
|
|
70
|
+
* AND EVERY MATCH IS IN THE MANIFEST, labelled `full`, `law`, `omitted_budget`
|
|
71
|
+
* or `source_unavailable`. A pointer is never labelled delivered. This is where
|
|
72
|
+
* `selectForInjection`'s `dropped` list is finally read: before it, a body the
|
|
73
|
+
* byte cap discarded left no trace, so a consumer could not tell a rule that did
|
|
74
|
+
* not match from one that matched and was thrown away.
|
|
75
|
+
*
|
|
39
76
|
* ONE MATCHER, SHARED WITH THE OFFLINE MODEL. Everything about selection,
|
|
40
77
|
* ordering, capping and body loading comes from `_lib/rule_injection.ts`, which
|
|
41
78
|
* `model_rule_injection.ts` also imports. Step 0.5 states the reason in as many
|
|
42
79
|
* words: an experiment whose offline pricing and runtime delivery use different
|
|
43
80
|
* matchers measures nothing.
|
|
44
81
|
*
|
|
45
|
-
* ONCE PER SESSION PER RULE, RE-ARMED ON COMPACTION. A rule's body
|
|
46
|
-
* the first time one of its triggers fires and not again, because
|
|
47
|
-
* already has it. Compaction is exactly the event that makes that
|
|
48
|
-
* `pre_compact`
|
|
49
|
-
* uses.
|
|
50
|
-
*
|
|
82
|
+
* ONCE PER SESSION PER RULE, RE-ARMED AND RESTORED ON COMPACTION. A rule's body
|
|
83
|
+
* is injected the first time one of its triggers fires and not again, because
|
|
84
|
+
* the model already has it. Compaction is exactly the event that makes that
|
|
85
|
+
* false, so `pre_compact` empties the seen-set — the same pin-lost shape
|
|
86
|
+
* `language-mirror` uses.
|
|
87
|
+
*
|
|
88
|
+
* STATE LIVES UNDER THE USER-GLOBAL ROOT, KEYED BY PROJECT (step 1.7). It used
|
|
89
|
+
* to be `<workspace>/agents/runtime/state/rule-inject/`, so every consumer
|
|
90
|
+
* session left dispatcher bookkeeping as untracked files inside someone else's
|
|
91
|
+
* repository. The seen-set is state about a SESSION, not about the project.
|
|
92
|
+
* The delivered-row ledger beside it is deliberately NOT moved — see
|
|
93
|
+
* {@link statePath} for the join that depends on where it is.
|
|
51
94
|
*
|
|
52
95
|
* WHAT IS RE-DELIVERED AFTER A COMPACTION, EXACTLY
|
|
53
|
-
* (road-to-delivery-for-every-host 2.4
|
|
54
|
-
* compaction boundary
|
|
55
|
-
* does not say what a reader may rely on):
|
|
96
|
+
* (road-to-delivery-for-every-host 2.4, amended by step 1.6 — stated because a
|
|
97
|
+
* rule lost at a compaction boundary used to be lost for the rest of the
|
|
98
|
+
* session, and "re-armed" alone does not say what a reader may rely on):
|
|
56
99
|
*
|
|
57
|
-
* · `pre_compact`
|
|
100
|
+
* · `pre_compact` empties the WHOLE seen-set for that session, not the rules
|
|
58
101
|
* matched on the compacted turn. There is no per-rule bookkeeping to be
|
|
59
|
-
* partially wrong about.
|
|
102
|
+
* partially wrong about. Since 1.6 it also hands those ids to `pending`
|
|
103
|
+
* rather than deleting them — see {@link armRestore}.
|
|
60
104
|
* · Nothing is delivered BY the compaction itself. The slot emits zero bytes
|
|
61
|
-
* and exits allow; re-arming is silent.
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
105
|
+
* and exits allow; re-arming is silent. It cannot deliver: the host is about
|
|
106
|
+
* to discard the context it would emit into.
|
|
107
|
+
* · `session_start` with `source: compact` sends the LAW section of every
|
|
108
|
+
* rule in that pending set, under the same 8,000-character budget, once.
|
|
109
|
+
* That is the half this paragraph used to end at: a rule whose trigger does
|
|
110
|
+
* not recur is no longer unrestored. A rule with no law section is reported
|
|
111
|
+
* in the manifest rather than having its whole body sent in its place.
|
|
112
|
+
* · A rule's BODY still returns on the next turn whose trigger matches it.
|
|
113
|
+
* The restore does not re-arm the de-duplication and does not replace
|
|
114
|
+
* trigger-driven delivery; it covers the gap between the two.
|
|
115
|
+
* · The seen-set is per session, so a compaction in one session re-arms and
|
|
116
|
+
* restores only that session.
|
|
117
|
+
*
|
|
118
|
+
* Held by fixtures in `tests/scripts/rule_inject_hook.test.ts` under "once per
|
|
119
|
+
* session per rule" and "re-deliver after a compact", and — for the BINDING
|
|
120
|
+
* rather than the function — by the dispatcher column in
|
|
121
|
+
* `rule_inject_foreign_matrix.test.ts`, because a suite that only calls `main()`
|
|
122
|
+
* stays green over a slot the dispatcher never routes to this concern.
|
|
74
123
|
*
|
|
75
124
|
* NEVER BLOCKS. Every failure path returns 0: unreadable stdin, malformed JSON,
|
|
76
125
|
* missing router, unreadable body, unwritable state. The one non-zero exit is
|
|
@@ -90,16 +139,18 @@
|
|
|
90
139
|
* claude 2.1.241, three verdicts). Delivery is therefore orchestrator-only, and
|
|
91
140
|
* no third binding is added.
|
|
92
141
|
*/
|
|
142
|
+
import { createHash } from 'node:crypto';
|
|
93
143
|
import * as fs from 'node:fs';
|
|
94
144
|
import * as path from 'node:path';
|
|
95
145
|
import process from 'node:process';
|
|
96
146
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
97
147
|
|
|
98
|
-
import { hookSectionEnabled
|
|
148
|
+
import { hookSectionEnabled } from '../_lib/hook_settings.js';
|
|
149
|
+
import * as user_global_paths from '../_lib/user_global_paths.js';
|
|
99
150
|
import {
|
|
100
151
|
deliversBodies,
|
|
101
|
-
|
|
102
|
-
|
|
152
|
+
installerThinsHost,
|
|
153
|
+
resolveLeanProjection,
|
|
103
154
|
} from '../_lib/lean_projection_mode.js';
|
|
104
155
|
import { enforcement_class_from_frontmatter } from '../_lib/obligation_frequency.js';
|
|
105
156
|
import {
|
|
@@ -109,11 +160,20 @@ import {
|
|
|
109
160
|
type WriterInput,
|
|
110
161
|
} from '../_lib/obligations.js';
|
|
111
162
|
import {
|
|
163
|
+
allTierRules,
|
|
164
|
+
bytesOf,
|
|
112
165
|
loadRuleBody,
|
|
113
166
|
loadRouter,
|
|
114
167
|
matchTierRules,
|
|
168
|
+
ruleSources,
|
|
115
169
|
selectForInjection,
|
|
170
|
+
type SelectionResult,
|
|
171
|
+
type TierRuleMatch,
|
|
116
172
|
} from '../_lib/rule_injection.js';
|
|
173
|
+
import { readConsequenceClass, stubLawIds } from '../_lib/rule_consequence_class.js';
|
|
174
|
+
import { lawText, ruleBody } from '../_lib/rule_law_section.js';
|
|
175
|
+
import { hostRuleLayerDirs, hostRuleLayerIds } from '../_lib/rule_layer_overlap.js';
|
|
176
|
+
import { is_thin_entry } from '../_lib/thin_rules.js';
|
|
117
177
|
import { readHookStdin } from './hook_stdin.js';
|
|
118
178
|
import { EXIT_ALLOW, EXIT_WARN } from './exit_codes.js';
|
|
119
179
|
|
|
@@ -189,45 +249,142 @@ export function workspaceRoot(env: JsonObject): string {
|
|
|
189
249
|
// ── seen-set state ───────────────────────────────────────────────────────
|
|
190
250
|
|
|
191
251
|
export interface SeenState {
|
|
252
|
+
/** Ids already delivered in this session. */
|
|
192
253
|
rules: string[];
|
|
254
|
+
/**
|
|
255
|
+
* Ids the compaction boundary took away, waiting for the restore slot.
|
|
256
|
+
*
|
|
257
|
+
* IDS, NEVER TEXT. The restore re-reads each rule's law from the corpus; a
|
|
258
|
+
* seen-set that cached bodies would be a second copy of the rule layer
|
|
259
|
+
* living in a consumer's state directory, going stale on every upgrade.
|
|
260
|
+
*/
|
|
261
|
+
pending?: string[];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* A stable, readable, collision-free directory name for one project root.
|
|
266
|
+
*
|
|
267
|
+
* The basename alone collides — a developer with `~/work/api` and
|
|
268
|
+
* `~/clients/api` would share a seen-set and silently lose deliveries in both.
|
|
269
|
+
* The digest alone is unreadable, and this directory is one a human opens when
|
|
270
|
+
* a delivery looks wrong. Both, so neither problem is the one you get.
|
|
271
|
+
*/
|
|
272
|
+
function projectKey(root: string): string {
|
|
273
|
+
const abs = path.resolve(root);
|
|
274
|
+
const name = (path.basename(abs) || 'root').replace(/[^A-Za-z0-9_-]/g, '_').slice(0, 40);
|
|
275
|
+
return `${name}-${createHash('sha256').update(abs, 'utf-8').digest('hex').slice(0, 12)}`;
|
|
193
276
|
}
|
|
194
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Where this session's seen-set lives (step 1.7).
|
|
280
|
+
*
|
|
281
|
+
* UNDER THE USER-GLOBAL ROOT, KEYED BY PROJECT. It used to be written to
|
|
282
|
+
* `<workspace>/agents/runtime/state/rule-inject/`, so every consumer session
|
|
283
|
+
* left dispatcher bookkeeping inside someone else's repository — untracked
|
|
284
|
+
* files in their `git status`, in a directory their `.gitignore` says nothing
|
|
285
|
+
* about unless this package's managed block was installed. The seen-set is
|
|
286
|
+
* state ABOUT a session, not about the project, and the project is the one
|
|
287
|
+
* place it has no business being.
|
|
288
|
+
*
|
|
289
|
+
* THE DELIVERED-ROW LEDGER IS DELIBERATELY NOT MOVED WITH IT.
|
|
290
|
+
* `recordDelivered` writes through `_lib/obligations.ts`, which resolves under
|
|
291
|
+
* the project root, and `road-to-a-stop-that-holds` Phase 3 reads those rows
|
|
292
|
+
* and joins them on the session id. Moving the ledger would be a second,
|
|
293
|
+
* larger change with a live consumer; moving only the seen-set leaves that join
|
|
294
|
+
* untouched by construction, because the two were never keyed on each other.
|
|
295
|
+
*
|
|
296
|
+
* `EVENT4U_CONFIG_HOME` and `$HOME` resolve the root, so a test points it
|
|
297
|
+
* somewhere disposable instead of writing into the developer's own state.
|
|
298
|
+
*
|
|
299
|
+
* ONE COST, STATED: an upgrade mid-session orphans the old file and the seen-set
|
|
300
|
+
* reads empty, which re-injects each matched rule once more. Session state is
|
|
301
|
+
* per session and cheap to rebuild; a migration step for it would be more code
|
|
302
|
+
* than the duplicate it prevents.
|
|
303
|
+
*/
|
|
195
304
|
export function statePath(root: string, session: string): string {
|
|
196
305
|
const safe = session.replace(/[^A-Za-z0-9_-]/g, '_').slice(0, 80) || 'unknown';
|
|
197
|
-
return path.join(
|
|
306
|
+
return path.join(
|
|
307
|
+
user_global_paths.write_target(path.join('state', 'rule-inject', projectKey(root))),
|
|
308
|
+
`${safe}.json`,
|
|
309
|
+
);
|
|
198
310
|
}
|
|
199
311
|
|
|
200
|
-
|
|
312
|
+
function readState(root: string, session: string): SeenState {
|
|
201
313
|
try {
|
|
202
314
|
const raw = fs.readFileSync(statePath(root, session), 'utf-8');
|
|
203
315
|
const parsed = JSON.parse(raw) as SeenState;
|
|
204
|
-
return
|
|
316
|
+
return {
|
|
317
|
+
rules: Array.isArray(parsed.rules) ? parsed.rules.map(String) : [],
|
|
318
|
+
pending: Array.isArray(parsed.pending) ? parsed.pending.map(String) : [],
|
|
319
|
+
};
|
|
205
320
|
} catch {
|
|
206
|
-
return
|
|
321
|
+
return { rules: [], pending: [] }; // fresh session, or a file nothing can parse
|
|
207
322
|
}
|
|
208
323
|
}
|
|
209
324
|
|
|
210
|
-
export function
|
|
325
|
+
export function readSeen(root: string, session: string): Set<string> {
|
|
326
|
+
return new Set(readState(root, session).rules);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function writeState(root: string, session: string, state: SeenState): void {
|
|
211
330
|
const p = statePath(root, session);
|
|
212
331
|
try {
|
|
213
332
|
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
214
|
-
|
|
215
|
-
fs.writeFileSync(`${p}.tmp`, `${JSON.stringify(payload)}\n`, 'utf-8');
|
|
333
|
+
fs.writeFileSync(`${p}.tmp`, `${JSON.stringify(state)}\n`, 'utf-8');
|
|
216
334
|
fs.renameSync(`${p}.tmp`, p);
|
|
217
335
|
} catch {
|
|
218
336
|
/* unwritable state must never fail a turn — worst case a re-injection */
|
|
219
337
|
}
|
|
220
338
|
}
|
|
221
339
|
|
|
222
|
-
export function
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
340
|
+
export function writeSeen(root: string, session: string, seen: Set<string>): void {
|
|
341
|
+
const prev = readState(root, session);
|
|
342
|
+
writeState(root, session, { rules: [...seen].sort(), pending: prev.pending ?? [] });
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Hand the seen-set to the restore slot instead of deleting it (step 1.6).
|
|
347
|
+
*
|
|
348
|
+
* `pre_compact` used to `rm` this file, which re-armed delivery correctly and
|
|
349
|
+
* threw away the one thing the restore needs. The de-duplication is still
|
|
350
|
+
* cleared — `rules` goes empty, so a rule whose trigger fires again gets its
|
|
351
|
+
* BODY back exactly as before — and the ids move to `pending`, where
|
|
352
|
+
* `session_start` with `source: compact` reads them and sends each rule's LAW.
|
|
353
|
+
*
|
|
354
|
+
* That is the half the header used to end at: a rule whose trigger does not
|
|
355
|
+
* recur was not restored at all, and a rule lost at a compaction boundary is
|
|
356
|
+
* lost for the rest of the session.
|
|
357
|
+
*/
|
|
358
|
+
export function armRestore(root: string, session: string): void {
|
|
359
|
+
const prev = readState(root, session);
|
|
360
|
+
const pending = [...new Set([...prev.rules, ...(prev.pending ?? [])])].sort();
|
|
361
|
+
if (pending.length === 0) {
|
|
362
|
+
try {
|
|
363
|
+
fs.rmSync(statePath(root, session), { force: true });
|
|
364
|
+
} catch {
|
|
365
|
+
/* ignore */
|
|
366
|
+
}
|
|
367
|
+
return;
|
|
227
368
|
}
|
|
369
|
+
writeState(root, session, { rules: [], pending });
|
|
228
370
|
}
|
|
229
371
|
|
|
230
|
-
|
|
372
|
+
/** The ids waiting for the restore slot. */
|
|
373
|
+
export function readPending(root: string, session: string): Set<string> {
|
|
374
|
+
return new Set(readState(root, session).pending ?? []);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Drop the pending set, once the restore it fed has actually been built.
|
|
379
|
+
*
|
|
380
|
+
* SEPARATE FROM THE READ on purpose. A single take-and-clear loses the set when
|
|
381
|
+
* the build fails, on a slot that does not fire again for that boundary — so
|
|
382
|
+
* the clear is the caller's last step rather than its first.
|
|
383
|
+
*/
|
|
384
|
+
export function clearPending(root: string, session: string): void {
|
|
385
|
+
const prev = readState(root, session);
|
|
386
|
+
if ((prev.pending ?? []).length > 0) writeState(root, session, { rules: prev.rules, pending: [] });
|
|
387
|
+
}
|
|
231
388
|
|
|
232
389
|
/** The user's prompt, across the shapes the hosts use. */
|
|
233
390
|
export function extractPrompt(payload: JsonObject): string {
|
|
@@ -241,12 +398,162 @@ export function extractFilePath(payload: JsonObject): string | null {
|
|
|
241
398
|
return str(ti, 'file_path', 'path', 'filePath', 'notebook_path');
|
|
242
399
|
}
|
|
243
400
|
|
|
244
|
-
|
|
401
|
+
/**
|
|
402
|
+
* Per-fire ceiling on the whole rule-produced string, in CHARACTERS (D2).
|
|
403
|
+
*
|
|
404
|
+
* CHARACTERS, because that is the unit the host's own threshold is in. Claude
|
|
405
|
+
* Code replaces an `additionalContext` longer than 10,000 CHARACTERS with a
|
|
406
|
+
* path and a 2,000-character preview, so a delivery over that line is not
|
|
407
|
+
* truncated — it is substituted, and the obligation text never arrives at all.
|
|
408
|
+
*
|
|
409
|
+
* WHICH STRING IT BOUNDS, stated because the two readings give different
|
|
410
|
+
* margins and D2's own wording did not separate them. This number bounds the
|
|
411
|
+
* RULE-PRODUCED string: the `<rule>` elements and the manifest, which is what
|
|
412
|
+
* this concern emits. The host's 10,000 applies to the `additionalContext` it
|
|
413
|
+
* assembles, and the other concerns on this slot measured 1,272 characters of
|
|
414
|
+
* non-rule text. So the margin is 2,000 if the host measures this concern's
|
|
415
|
+
* payload alone, and **728** if it measures the assembled string — the
|
|
416
|
+
* conservative reading, and still a margin. 8,000 is safe under both; which
|
|
417
|
+
* reading is right has not been established here and the smaller number is the
|
|
418
|
+
* one to plan against.
|
|
419
|
+
*
|
|
420
|
+
* IT BOUNDS THE WHOLE STRING, framing and manifest included, because that is
|
|
421
|
+
* what the host measures. {@link CAP_BYTES} above bounds something else — the
|
|
422
|
+
* SELECTION, shared byte-for-byte with the offline model — and both still
|
|
423
|
+
* apply: selection drops whole bodies to stay under the byte cap, then
|
|
424
|
+
* composition drops or demotes to stay under this one.
|
|
425
|
+
*
|
|
426
|
+
* THE TWO CANNOT BREACH EACH OTHER AT ANY RATIO THIS CORPUS PRODUCES, and the
|
|
427
|
+
* ratio is measured rather than assumed: over the 121 projected rule files the
|
|
428
|
+
* mean is 1.0088 bytes per character and the per-file maximum is 1.0346
|
|
429
|
+
* (`no-decorative-emojis-in-git-surfaces.md`), so 8,000 characters is at most
|
|
430
|
+
* 8,277 bytes against a registered 16,384. A composed string would have to
|
|
431
|
+
* average 2.05 bytes per character to reach the byte row, which no Markdown
|
|
432
|
+
* rule corpus produces. `per_concern_caps_chars` in
|
|
433
|
+
* `src/config/hook-token-budget.json` registers this number in its own unit
|
|
434
|
+
* rather than converting it into the bytes map, and a fixture pins both.
|
|
435
|
+
*/
|
|
436
|
+
export const COMPOSED_CHARS = 8000;
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* What a matched rule actually got, as the manifest labels it.
|
|
440
|
+
*
|
|
441
|
+
* `full` and `law` mean text arrived. `omitted_budget` and `source_unavailable`
|
|
442
|
+
* mean it did not, and both are a POINTER — an id the model can ask for. A
|
|
443
|
+
* pointer is never labelled delivered; that distinction is the whole reason the
|
|
444
|
+
* manifest has four values instead of a present/absent flag.
|
|
445
|
+
*/
|
|
446
|
+
export type DeliveryForm = 'full' | 'law' | 'omitted_budget' | 'source_unavailable';
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* The host's own threshold, in the host's own unit (step 0.3).
|
|
450
|
+
*
|
|
451
|
+
* Claude Code REPLACES an `additionalContext` longer than this with a path and
|
|
452
|
+
* a 2,000-character preview. A composed string over this line is therefore not
|
|
453
|
+
* truncated — it is substituted, and the obligation text never arrives. That is
|
|
454
|
+
* why this number is the one the arrival record checks against rather than
|
|
455
|
+
* {@link COMPOSED_CHARS}: 8,000 is the budget this concern holds itself to, and
|
|
456
|
+
* 10,000 is the line past which the host stops delivering what it was handed.
|
|
457
|
+
*
|
|
458
|
+
* WHICH STRING IS MEASURED, stated because risk 5 of this roadmap's register
|
|
459
|
+
* says the answer is not established. This records the RULE-PRODUCED string,
|
|
460
|
+
* which is the only one this concern can see. If the host measures the
|
|
461
|
+
* ASSEMBLED `additionalContext` instead, the real margin is 728 rather than
|
|
462
|
+
* 2,000 and a reading of 0 here is necessary but not sufficient. The record is
|
|
463
|
+
* honest about which of the two it is; it does not settle the question.
|
|
464
|
+
*/
|
|
465
|
+
export const HOST_REPLACE_CHARS = 10000;
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* The three forms step 0.3 names, which is not {@link DeliveryForm}'s four.
|
|
469
|
+
*
|
|
470
|
+
* `omitted_budget` and `source_unavailable` are different REASONS for the same
|
|
471
|
+
* outcome — an id the model can ask for and no text. The arrival record asks
|
|
472
|
+
* what the model got, so the two collapse to `pointer`; the manifest keeps the
|
|
473
|
+
* distinction, because a consumer diagnosing a broken install needs the reason
|
|
474
|
+
* and a reader counting arrivals does not.
|
|
475
|
+
*/
|
|
476
|
+
export type ArrivalForm = 'body' | 'law' | 'pointer';
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* How far a matched rule got, in the levels D4 fixed (step 0.3).
|
|
480
|
+
*
|
|
481
|
+
* - `A0` discoverable — a file or pointer exists.
|
|
482
|
+
* - `A1` routed — a trigger matched.
|
|
483
|
+
* - `A2` arrived — the obligation text is in context.
|
|
484
|
+
* - `A3` enforced — a gate blocks the forbidden outcome.
|
|
485
|
+
*
|
|
486
|
+
* THIS CONCERN NEVER WRITES A0 OR A3, and both omissions are deliberate rather
|
|
487
|
+
* than unimplemented. A0 is the level of a rule that never routed, and this
|
|
488
|
+
* concern only ever sees rules that did — writing an A0 row per fire for every
|
|
489
|
+
* rule in the corpus would be a per-turn sweep of the router to record the
|
|
490
|
+
* absence of an event. A3 belongs to whatever gate refuses the outcome; a
|
|
491
|
+
* carrier that recorded its own delivery as `enforced` would be the
|
|
492
|
+
* emitter-record-read-as-compliance mistake the delivered ledger's own header
|
|
493
|
+
* carries a council lock against.
|
|
494
|
+
*/
|
|
495
|
+
export type ArrivalLevel = 'A0' | 'A1' | 'A2' | 'A3';
|
|
496
|
+
|
|
497
|
+
/** One matched rule, with what it got and how far that got it. */
|
|
498
|
+
export interface ArrivalRow {
|
|
499
|
+
readonly rule: string;
|
|
500
|
+
readonly form: ArrivalForm;
|
|
501
|
+
readonly arrival: ArrivalLevel;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/** One fire, measured on the composed string in the host's unit. */
|
|
505
|
+
export interface ArrivalRecord {
|
|
506
|
+
/** The composed string's length in CHARACTERS — what the host counts. */
|
|
507
|
+
readonly chars: number;
|
|
508
|
+
/** 1 when this fire was over {@link HOST_REPLACE_CHARS}, else 0. Summing the column gives the over-budget count. */
|
|
509
|
+
readonly over_budget: number;
|
|
510
|
+
/** Which slot the fire reached the host on. */
|
|
511
|
+
readonly slot: string;
|
|
512
|
+
readonly at: string;
|
|
513
|
+
readonly rows: readonly ArrivalRow[];
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** The manifest's four reasons, as the three forms a reader of arrivals wants. */
|
|
517
|
+
export function arrivalForm(f: DeliveryForm): ArrivalForm {
|
|
518
|
+
if (f === 'full') return 'body';
|
|
519
|
+
if (f === 'law') return 'law';
|
|
520
|
+
return 'pointer';
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* What this fire actually delivered, in the host's unit.
|
|
525
|
+
*
|
|
526
|
+
* TWO WAYS TO STAY AT A1, and they are different failures with the same
|
|
527
|
+
* reading. A pointer never carried text in the first place — step 0.3's "a
|
|
528
|
+
* pointer is never recorded above A1", taken literally. And a composed string
|
|
529
|
+
* the host REPLACES carried text that the host then did not deliver, so every
|
|
530
|
+
* row in that fire is A1 however good its form looked: the manifest says `body`
|
|
531
|
+
* and the model received a path. Recording those as A2 would be the instrument
|
|
532
|
+
* reporting its own intent instead of the outcome, which is the one thing a
|
|
533
|
+
* measurement of arrival must not do.
|
|
534
|
+
*/
|
|
535
|
+
export function arrivalRecord(injection: Injection, slot: string, now = stamp()): ArrivalRecord {
|
|
536
|
+
const chars = injection.body.length;
|
|
537
|
+
const replaced = chars > HOST_REPLACE_CHARS;
|
|
538
|
+
return {
|
|
539
|
+
chars,
|
|
540
|
+
over_budget: replaced ? 1 : 0,
|
|
541
|
+
slot,
|
|
542
|
+
at: now,
|
|
543
|
+
rows: injection.manifest.map(([rule, f]) => {
|
|
544
|
+
const form = arrivalForm(f);
|
|
545
|
+
return { rule, form, arrival: form === 'pointer' || replaced ? 'A1' : 'A2' };
|
|
546
|
+
}),
|
|
547
|
+
};
|
|
548
|
+
}
|
|
245
549
|
|
|
246
550
|
export interface Injection {
|
|
551
|
+
/** Ids whose TEXT was emitted — what the ledger records. */
|
|
247
552
|
rules: string[];
|
|
248
553
|
bytes: number;
|
|
249
554
|
body: string;
|
|
555
|
+
/** Every matched rule with the form it got, in router order. */
|
|
556
|
+
manifest: Array<[string, DeliveryForm]>;
|
|
250
557
|
}
|
|
251
558
|
|
|
252
559
|
/**
|
|
@@ -287,6 +594,256 @@ export function recordDelivered(root: string, session: string, ruleIds: string[]
|
|
|
287
594
|
}
|
|
288
595
|
}
|
|
289
596
|
|
|
597
|
+
/**
|
|
598
|
+
* Ids whose LAW may stand on its own, empty when the config is unreachable.
|
|
599
|
+
*
|
|
600
|
+
* `stubLawIds` and not `Object.keys(members)`: the `no_stub` subset is exactly
|
|
601
|
+
* the members whose law is missing or too long to carry the obligation alone,
|
|
602
|
+
* which is why the projector ships those full-bodied. Serving one of them its
|
|
603
|
+
* law here would be the thin-stub defect `rule_consequence_class.ts` exists to
|
|
604
|
+
* prevent, reproduced at the delivery end — and `stubLawIds` is that module's
|
|
605
|
+
* own answer, so re-deriving the distinction here would be a second reader of a
|
|
606
|
+
* class that is supposed to have one.
|
|
607
|
+
*
|
|
608
|
+
* MEMOISED PER ROOT. The config is 12 kB of JSON and this runs on the
|
|
609
|
+
* `user_prompt_submit` hot path; a dispatch is one process and an install does
|
|
610
|
+
* not change under it.
|
|
611
|
+
*/
|
|
612
|
+
const HI_CACHE = new Map<string, Set<string>>();
|
|
613
|
+
|
|
614
|
+
function highConsequenceIds(root: string): Set<string> {
|
|
615
|
+
const hit = HI_CACHE.get(root);
|
|
616
|
+
if (hit !== undefined) return hit;
|
|
617
|
+
let out = new Set<string>();
|
|
618
|
+
for (const r of [root, ruleSources(root).pkg]) {
|
|
619
|
+
if (r === null) continue;
|
|
620
|
+
try {
|
|
621
|
+
out = stubLawIds(readConsequenceClass(r));
|
|
622
|
+
break;
|
|
623
|
+
} catch {
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
HI_CACHE.set(root, out);
|
|
628
|
+
return out;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/** The manifest block, which is part of the budgeted string rather than beside it. */
|
|
632
|
+
function manifestText(rows: Array<[string, DeliveryForm]>): string {
|
|
633
|
+
return `<rule-manifest>\n${rows.map(([id, f]) => `${id}=${f}`).join('\n')}\n</rule-manifest>`;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Compose one fire under {@link COMPOSED_CHARS}, in the order D1 fixes.
|
|
638
|
+
*
|
|
639
|
+
* THE ORDER IS A DECISION, not an implementation detail. D1:
|
|
640
|
+
*
|
|
641
|
+
* 1. the law of every matched HIGH-CONSEQUENCE rule,
|
|
642
|
+
* 2. then the highest-priority full body that fits,
|
|
643
|
+
* 3. then further full bodies,
|
|
644
|
+
* 4. then the law of everything still unsent,
|
|
645
|
+
* 5. then the manifest.
|
|
646
|
+
*
|
|
647
|
+
* Phase 1 first because a high-consequence rule's law arriving is worth more
|
|
648
|
+
* than a lower-consequence rule's whole body, and a budget that filled in
|
|
649
|
+
* priority order alone would spend itself before reaching it.
|
|
650
|
+
*
|
|
651
|
+
* PHASE 1 IS A FLOOR AND NEVER A CEILING, which is the half the first
|
|
652
|
+
* implementation got backwards. A rule served its law in phase 1 can be
|
|
653
|
+
* UPGRADED to its whole body in phase 2 when the budget still has room for the
|
|
654
|
+
* difference — `put` replaces the part in place and charges only the delta.
|
|
655
|
+
* Without that, the 23 class members with a law section could never receive
|
|
656
|
+
* their body however empty the budget was: `security-sensitive-stop` would have
|
|
657
|
+
* dropped 5,687 characters to 429 on every fire, and its id still enters the
|
|
658
|
+
* seen-set, so the body would not have come back later either. D1's "then the
|
|
659
|
+
* highest-priority full body that fits" reads as a floor and has to behave like
|
|
660
|
+
* one.
|
|
661
|
+
*
|
|
662
|
+
* EVERY MATCHED CLASS MEMBER REACHES PHASE 1, including one the byte cap
|
|
663
|
+
* dropped. `selectForInjection` ranks on score alone and knows nothing about
|
|
664
|
+
* the class, so a high-consequence rule can lose that race; D1 says "every
|
|
665
|
+
* matched", so the text for a dropped class member is loaded here rather than
|
|
666
|
+
* left unreachable.
|
|
667
|
+
*
|
|
668
|
+
* NOTHING IS EVER TRUNCATED. A rule whose law alone does not fit is REPORTED —
|
|
669
|
+
* `omitted_budget` in the manifest — because half an obligation reads like a
|
|
670
|
+
* whole one and is the more dangerous output. The manifest's space is reserved
|
|
671
|
+
* BEFORE any text is admitted, using the longest label for every matched id, so
|
|
672
|
+
* the report can never be the thing that pushes the string over.
|
|
673
|
+
*
|
|
674
|
+
* `sel.dropped` is read here, and that is the point of the step as much as the
|
|
675
|
+
* budget is: before this, a body dropped by the byte cap left no trace at all,
|
|
676
|
+
* so a consumer could not tell a rule that did not match from one that matched
|
|
677
|
+
* and was silently discarded.
|
|
678
|
+
*/
|
|
679
|
+
function compose(
|
|
680
|
+
root: string,
|
|
681
|
+
sel: SelectionResult,
|
|
682
|
+
hi: Set<string>,
|
|
683
|
+
lawOnly = false,
|
|
684
|
+
): Injection | null {
|
|
685
|
+
const form = new Map<string, DeliveryForm>();
|
|
686
|
+
const full = new Map<string, string>();
|
|
687
|
+
const read = (id: string): void => {
|
|
688
|
+
const raw = loadRuleBody(root, id);
|
|
689
|
+
// STEP 1.4 — the host form, by the parser the thin projector uses.
|
|
690
|
+
// `ruleBody` strips frontmatter and HTML comments: the first is the
|
|
691
|
+
// routing surface the router already read, the second is authoring
|
|
692
|
+
// scaffolding no host renders to a model. A file with nothing left after
|
|
693
|
+
// the strip carries no obligation — an empty `<rule>` element is framing
|
|
694
|
+
// with no content inside it.
|
|
695
|
+
const body = raw === null ? '' : ruleBody(raw);
|
|
696
|
+
if (body === '') {
|
|
697
|
+
form.set(id, 'source_unavailable');
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
form.set(id, 'omitted_budget');
|
|
701
|
+
full.set(id, body);
|
|
702
|
+
};
|
|
703
|
+
for (const m of sel.dropped) {
|
|
704
|
+
if (hi.has(m.id)) {
|
|
705
|
+
read(m.id);
|
|
706
|
+
continue;
|
|
707
|
+
}
|
|
708
|
+
form.set(m.id, (sel.bodyBytes.get(m.id) ?? 0) === 0 ? 'source_unavailable' : 'omitted_budget');
|
|
709
|
+
}
|
|
710
|
+
for (const m of sel.selected) read(m.id);
|
|
711
|
+
|
|
712
|
+
const all = [...sel.selected, ...sel.dropped].sort((a, b) => a.order - b.order);
|
|
713
|
+
const tiers = new Map(all.map((m) => [m.id, m.tier]));
|
|
714
|
+
const reserve = manifestText(all.map((m) => [m.id, 'source_unavailable']));
|
|
715
|
+
let left = COMPOSED_CHARS - reserve.length - 2;
|
|
716
|
+
// BOTH UNITS RESERVE THE MANIFEST. The byte row is the registered one, so a
|
|
717
|
+
// manifest charged against the character budget and not against this one
|
|
718
|
+
// would let the emission exceed the row it is registered at.
|
|
719
|
+
let bytesLeft = CAP_BYTES - bytesOf(reserve) - 2;
|
|
720
|
+
const parts: string[] = [];
|
|
721
|
+
const partOf = new Map<string, number>();
|
|
722
|
+
const put = (id: string, kind: 'full' | 'law', text: string): boolean => {
|
|
723
|
+
const part = `<rule id="${id}" tier="${tiers.get(id) ?? ''}" form="${kind}">\n${text}\n</rule>`;
|
|
724
|
+
const at = partOf.get(id);
|
|
725
|
+
const prev = at === undefined ? '' : (parts[at] as string);
|
|
726
|
+
const sep = at === undefined && parts.length > 0 ? 2 : 0;
|
|
727
|
+
// BOTH UNITS, INDEPENDENTLY. The measured 1.0346 bytes-per-character
|
|
728
|
+
// maximum over today's corpus says the character budget binds first, and
|
|
729
|
+
// a measurement over one corpus is evidence rather than an invariant: a
|
|
730
|
+
// 6,000-character payload of three-byte code points is 18,000 bytes.
|
|
731
|
+
// Safety that rests on a ratio stops being safety the day the corpus
|
|
732
|
+
// changes, so the registered byte row is enforced here too and the ratio
|
|
733
|
+
// is what makes the second check almost never bind rather than what
|
|
734
|
+
// makes it unnecessary.
|
|
735
|
+
const cost = part.length - prev.length + sep;
|
|
736
|
+
const costBytes = bytesOf(part) - bytesOf(prev) + sep;
|
|
737
|
+
if (cost > left || costBytes > bytesLeft) return false;
|
|
738
|
+
left -= cost;
|
|
739
|
+
bytesLeft -= costBytes;
|
|
740
|
+
if (at === undefined) {
|
|
741
|
+
partOf.set(id, parts.length);
|
|
742
|
+
parts.push(part);
|
|
743
|
+
} else {
|
|
744
|
+
parts[at] = part;
|
|
745
|
+
}
|
|
746
|
+
form.set(id, kind);
|
|
747
|
+
return true;
|
|
748
|
+
};
|
|
749
|
+
|
|
750
|
+
const ranked = [...all].sort((a, b) => b.score - a.score || a.order - b.order);
|
|
751
|
+
for (const m of ranked) {
|
|
752
|
+
const body = full.get(m.id);
|
|
753
|
+
if (body === undefined || !hi.has(m.id)) continue;
|
|
754
|
+
const law = lawText(body);
|
|
755
|
+
// A class member with no law section cannot be served by phase 1. It is
|
|
756
|
+
// left to compete for a full body below rather than reported, which is
|
|
757
|
+
// `no_stub`'s own answer to the same state.
|
|
758
|
+
if (law !== null) put(m.id, 'law', law);
|
|
759
|
+
}
|
|
760
|
+
for (const m of ranked) {
|
|
761
|
+
const body = full.get(m.id);
|
|
762
|
+
if (body === undefined || form.get(m.id) === 'full') continue;
|
|
763
|
+
// A RESTORE SENDS LAWS, with one exception: a rule with NO law section
|
|
764
|
+
// has no law to send, and reporting it `omitted_budget` would blame a
|
|
765
|
+
// budget decision nobody made. Those compete for their body instead, so
|
|
766
|
+
// every label on the restore is true.
|
|
767
|
+
if (lawOnly && lawText(body) !== null) continue;
|
|
768
|
+
put(m.id, 'full', body);
|
|
769
|
+
}
|
|
770
|
+
for (const m of ranked) {
|
|
771
|
+
const body = full.get(m.id);
|
|
772
|
+
if (body === undefined || form.get(m.id) !== 'omitted_budget') continue;
|
|
773
|
+
const law = lawText(body);
|
|
774
|
+
if (law !== null) put(m.id, 'law', law);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const rows: Array<[string, DeliveryForm]> = all.map((m) => [
|
|
778
|
+
m.id,
|
|
779
|
+
form.get(m.id) ?? 'source_unavailable',
|
|
780
|
+
]);
|
|
781
|
+
const ids = rows.filter(([, f]) => f === 'full' || f === 'law').map(([id]) => id);
|
|
782
|
+
// THE MANIFEST IS BOUNDED TOO, and by construction rather than by how many
|
|
783
|
+
// rules this router happens to carry. Its space is reserved above at the
|
|
784
|
+
// longest label, so it always fits what was reserved — but if the RESERVE
|
|
785
|
+
// itself exceeded the budget (enough matches that the report alone is over
|
|
786
|
+
// 8,000 characters) the fill loop would admit nothing and the manifest
|
|
787
|
+
// would be the whole emission, over the cap. No router today comes close;
|
|
788
|
+
// "no corpus today comes close" is the shape of claim the byte check above
|
|
789
|
+
// exists because of, so this is a property instead.
|
|
790
|
+
//
|
|
791
|
+
// UNDELIVERED ROWS GO FIRST. A row for a rule whose text IS in this string
|
|
792
|
+
// must never be the one dropped — that would report a delivered rule as
|
|
793
|
+
// omitted, which is the one thing AC-2 forbids outright.
|
|
794
|
+
const order = [...rows.keys()].sort(
|
|
795
|
+
(a, b) =>
|
|
796
|
+
Number(['full', 'law'].includes((rows[a] as [string, DeliveryForm])[1])) -
|
|
797
|
+
Number(['full', 'law'].includes((rows[b] as [string, DeliveryForm])[1])),
|
|
798
|
+
);
|
|
799
|
+
const head = parts.join('\n\n') + (parts.length > 0 ? '\n\n' : '');
|
|
800
|
+
let drop = 0;
|
|
801
|
+
let body = head + manifestText(rows);
|
|
802
|
+
while (body.length > COMPOSED_CHARS && drop < rows.length) {
|
|
803
|
+
drop += 1;
|
|
804
|
+
const cut = new Set(order.slice(0, drop));
|
|
805
|
+
const kept = rows.filter((_, i) => !cut.has(i));
|
|
806
|
+
body = head + manifestText([...kept, [`+${drop}`, 'omitted_budget']]);
|
|
807
|
+
}
|
|
808
|
+
return { rules: ids, bytes: bytesOf(body), body, manifest: rows };
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* Build the post-compaction restore, or `null` for silence (step 1.6).
|
|
813
|
+
*
|
|
814
|
+
* LAWS, NOT BODIES, and the asymmetry with a normal fire is the point. A
|
|
815
|
+
* compaction removed text the model was already under; re-sending every body
|
|
816
|
+
* would re-spend the whole budget on rules whose obligations the session may
|
|
817
|
+
* never touch again. The law is the part that cannot be inferred from the rest,
|
|
818
|
+
* so it is what the budget buys back. Everything that does not fit is reported
|
|
819
|
+
* in the same manifest as any other fire.
|
|
820
|
+
*
|
|
821
|
+
* The router supplies each id's tier and declaration order, because the
|
|
822
|
+
* seen-set deliberately carries neither — see {@link SeenState.pending}.
|
|
823
|
+
*/
|
|
824
|
+
function restore(root: string, ids: Set<string>): Injection | null {
|
|
825
|
+
if (ids.size === 0) return null;
|
|
826
|
+
let router;
|
|
827
|
+
try {
|
|
828
|
+
router = loadRouter(root);
|
|
829
|
+
} catch {
|
|
830
|
+
return null;
|
|
831
|
+
}
|
|
832
|
+
const selected: TierRuleMatch[] = [];
|
|
833
|
+
let order = 0;
|
|
834
|
+
for (const r of allTierRules(router)) {
|
|
835
|
+
if (ids.has(r.id)) selected.push({ id: r.id, tier: r.tier, score: 0, order });
|
|
836
|
+
order += 1;
|
|
837
|
+
}
|
|
838
|
+
if (selected.length === 0) return null;
|
|
839
|
+
return compose(
|
|
840
|
+
root,
|
|
841
|
+
{ selected, dropped: [], bytes: 0, bodyBytes: new Map() },
|
|
842
|
+
new Set(selected.map((m) => m.id)),
|
|
843
|
+
true,
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
|
|
290
847
|
/**
|
|
291
848
|
* Build the injection for one event, or `null` for silence.
|
|
292
849
|
*
|
|
@@ -301,6 +858,9 @@ export function buildInjection(
|
|
|
301
858
|
openFiles: string[] | null,
|
|
302
859
|
command: string | null,
|
|
303
860
|
seen: Set<string>,
|
|
861
|
+
// `false` only for an offline measurement of WHAT the concern delivers,
|
|
862
|
+
// which must not read the measuring machine's installed rule files.
|
|
863
|
+
servedOnce = true,
|
|
304
864
|
): Injection | null {
|
|
305
865
|
let router;
|
|
306
866
|
try {
|
|
@@ -308,21 +868,63 @@ export function buildInjection(
|
|
|
308
868
|
} catch {
|
|
309
869
|
return null; // no router — nothing to deliver, and never a failure
|
|
310
870
|
}
|
|
311
|
-
|
|
312
|
-
|
|
871
|
+
// STEP 1.3 — the install declares the scope, the package supplies the text.
|
|
872
|
+
//
|
|
873
|
+
// The router is the PACKAGE's list of every rule that could route. What a
|
|
874
|
+
// given consumer is actually under is what their install wrote into the
|
|
875
|
+
// host's rule directories, and the two are not the same set: 13 router tier
|
|
876
|
+
// rules are maintainer-only by `workspaces`, and before this filter a
|
|
877
|
+
// consumer could receive a body for a rule their install never carried.
|
|
878
|
+
//
|
|
879
|
+
// The two halves cannot be one read, and that is the reason this is a
|
|
880
|
+
// filter rather than a different body source. In `delivery` mode the
|
|
881
|
+
// installed files are thin stubs by construction, so the installed layer can
|
|
882
|
+
// say WHICH rules are in scope and never WHAT they say; the body still comes
|
|
883
|
+
// from the package corpus `ruleSources` resolves.
|
|
884
|
+
//
|
|
885
|
+
// `null` means no host rule layer exists to declare a scope — see
|
|
886
|
+
// `hostRuleLayerIds`. No filter then, because scoping to an empty set would
|
|
887
|
+
// re-create the silence step 1.1 repaired.
|
|
888
|
+
const scope = hostRuleLayerIds(root);
|
|
889
|
+
let matches = matchTierRules(router, prompt, openFiles, command).filter(
|
|
890
|
+
(m) => !seen.has(m.id) && (scope === null || scope.has(m.id)),
|
|
313
891
|
);
|
|
314
892
|
if (matches.length === 0) return null;
|
|
893
|
+
// ONE COPY (`road-to-a-default-install-served-once` 1.1). The gate opens on
|
|
894
|
+
// the template's `delivery`, but the installer thins only on an explicit
|
|
895
|
+
// user-global choice, so a default install carries every routed body in
|
|
896
|
+
// its rule files already. A rule whose installed copy is a full body gets
|
|
897
|
+
// nothing; a stub still gets its body. Asked only off the consent path, so
|
|
898
|
+
// an opted-in install's delivery is unchanged byte for byte.
|
|
899
|
+
if (servedOnce && scope !== null && !installerThinsHost(DELIVERY_HOST, { packageRoot: ruleSources(root).pkg })) {
|
|
900
|
+
const full = installedFullIds(root, matches.map((m) => m.id));
|
|
901
|
+
matches = matches.filter((m) => !full.has(m.id));
|
|
902
|
+
if (matches.length === 0) return null;
|
|
903
|
+
}
|
|
315
904
|
const sel = selectForInjection(root, matches, CAP_BYTES);
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
905
|
+
return compose(root, sel, highConsequenceIds(root));
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/**
|
|
909
|
+
* Ids among `ids` whose copy in EITHER host rule layer is a full body rather
|
|
910
|
+
* than a stub. Either, because the host loads both: one full copy is enough
|
|
911
|
+
* for the body to be standing in context already. Reads only matched ids,
|
|
912
|
+
* never the whole layer, because this runs on the per-prompt path.
|
|
913
|
+
*/
|
|
914
|
+
export function installedFullIds(root: string, ids: readonly string[]): Set<string> {
|
|
915
|
+
const out = new Set<string>();
|
|
916
|
+
for (const dir of hostRuleLayerDirs(root)) {
|
|
917
|
+
for (const id of ids) {
|
|
918
|
+
let text: string;
|
|
919
|
+
try {
|
|
920
|
+
text = fs.readFileSync(path.join(dir, `${id}.md`), 'utf-8');
|
|
921
|
+
} catch {
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
if (!is_thin_entry(text)) out.add(id);
|
|
925
|
+
}
|
|
323
926
|
}
|
|
324
|
-
|
|
325
|
-
return { rules: ids, bytes: sel.bytes, body: parts.join('\n\n') };
|
|
927
|
+
return out;
|
|
326
928
|
}
|
|
327
929
|
|
|
328
930
|
// ── main ─────────────────────────────────────────────────────────────────
|
|
@@ -361,10 +963,16 @@ export const DELIVERY_HOST = 'claude-code';
|
|
|
361
963
|
* to open. `AGENT_CONFIG_REPLAY` re-imposes it, which is what keeps
|
|
362
964
|
* `bench_hook_injection` measuring the configured tree rather than the probe.
|
|
363
965
|
*/
|
|
364
|
-
export function gateOpen(root: string, cliEntry: boolean): boolean {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
966
|
+
export function gateOpen(root: string, cliEntry: boolean, packageRoot?: string | null): boolean {
|
|
967
|
+
// ONE resolver since step 1.2 — the full cascade (template base, canonical
|
|
968
|
+
// `agents/settings/`, project root, user-global), not the legacy root file
|
|
969
|
+
// this concern used to read alone. `packageRoot` is where the template is
|
|
970
|
+
// found; inside the bundle nothing else can locate it.
|
|
971
|
+
const { mode, hosts } = resolveLeanProjection({
|
|
972
|
+
projectRoot: root,
|
|
973
|
+
packageRoot: packageRoot ?? null,
|
|
974
|
+
});
|
|
975
|
+
if (deliversBodies(mode) && hosts.hosts.includes(DELIVERY_HOST)) return true;
|
|
368
976
|
if (hookSectionEnabled(root, 'rule_inject')) return true;
|
|
369
977
|
return cliEntry && process.env['AGENT_CONFIG_REPLAY'] !== '1';
|
|
370
978
|
}
|
|
@@ -393,11 +1001,38 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
393
1001
|
const session = str(env, 'session_id', 'sessionId') ?? str(payload, 'session_id', 'sessionId') ?? 'unknown';
|
|
394
1002
|
|
|
395
1003
|
if (slot === 'pre_compact') {
|
|
396
|
-
|
|
397
|
-
return EXIT_ALLOW; // re-arm is silent; the next
|
|
1004
|
+
armRestore(root, session);
|
|
1005
|
+
return EXIT_ALLOW; // re-arm is silent; the restore happens on the next slot
|
|
1006
|
+
}
|
|
1007
|
+
if (slot === 'session_start') {
|
|
1008
|
+
// ONLY on `source: compact`. `startup`, `resume`, `clear` and `fork`
|
|
1009
|
+
// either begin a session that never had a seen-set or hand back a
|
|
1010
|
+
// transcript the host already restored; emitting there would be a
|
|
1011
|
+
// duplicate, and on `fork` a duplicate belonging to a session that is
|
|
1012
|
+
// still alive. An unknown source emits nothing rather than guessing —
|
|
1013
|
+
// the same stance `handoff-context`'s `sourceGate` takes.
|
|
1014
|
+
if ((str(payload, 'source') ?? str(env, 'source')) !== 'compact') return EXIT_ALLOW;
|
|
1015
|
+
if (!gateOpen(root, _isCliEntry(), ruleSources(root).pkg)) return EXIT_ALLOW;
|
|
1016
|
+
// READ, then BUILD, then clear — never clear first. A restore that
|
|
1017
|
+
// fires twice on one boundary is a duplicate delivery, so the set has to
|
|
1018
|
+
// be cleared; clearing it BEFORE the build means a build that fails
|
|
1019
|
+
// (the package moved mid-session, or an upgrade renamed every pending
|
|
1020
|
+
// rule out of the router) loses the set permanently and silently, on the
|
|
1021
|
+
// one slot that does not come round again.
|
|
1022
|
+
const restored = restore(root, readPending(root, session));
|
|
1023
|
+
if (restored === null) return EXIT_ALLOW;
|
|
1024
|
+
clearPending(root, session);
|
|
1025
|
+
recordDelivered(root, session, restored.rules);
|
|
1026
|
+
return emit(restored, slot, root, session);
|
|
398
1027
|
}
|
|
399
1028
|
if (slot !== 'user_prompt_submit' && slot !== 'pre_tool_use') return EXIT_ALLOW;
|
|
400
|
-
|
|
1029
|
+
|
|
1030
|
+
// Resolved ONCE, before the gate. The gate needs the package root to find
|
|
1031
|
+
// the settings template and the delivery needs it to find the corpus;
|
|
1032
|
+
// reading the environment and probing the filesystem twice for the same
|
|
1033
|
+
// answer is a cost every hook dispatch on every slot would pay.
|
|
1034
|
+
const src = ruleSources(root);
|
|
1035
|
+
if (!gateOpen(root, _isCliEntry(), src.pkg)) return EXIT_ALLOW;
|
|
401
1036
|
|
|
402
1037
|
let prompt = '';
|
|
403
1038
|
let openFiles: string[] | null = null;
|
|
@@ -415,6 +1050,39 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
415
1050
|
openFiles = [fp];
|
|
416
1051
|
}
|
|
417
1052
|
|
|
1053
|
+
// Fail CLOSED and SAY SO. An empty delivery is indistinguishable from "no
|
|
1054
|
+
// rule matched", which is the exact silence this concern shipped with; one
|
|
1055
|
+
// line on stderr — which the dispatcher captures — is what makes a broken
|
|
1056
|
+
// install diagnosable from outside this file. Still allow: a carrier that
|
|
1057
|
+
// cannot find its corpus must not fail a turn.
|
|
1058
|
+
if (src.gap !== null) {
|
|
1059
|
+
// Fail CLOSED and SAY SO, through the channel the dispatcher actually
|
|
1060
|
+
// surfaces. Writing to stderr looks right and is not: `_run_concern_inproc`
|
|
1061
|
+
// captures a concern's stderr and `dispatch_hook` re-emits it only at
|
|
1062
|
+
// rc >= 3 (a crash), so a concern exiting allow is silent by
|
|
1063
|
+
// construction — which is the exact failure this diagnostic exists to
|
|
1064
|
+
// end, reproduced one layer up. Found by the 1.8 matrix against the
|
|
1065
|
+
// built bundle; no in-process fixture could have seen it.
|
|
1066
|
+
//
|
|
1067
|
+
// WHERE IT ACTUALLY LANDS, measured against the built bundle rather
|
|
1068
|
+
// than assumed: on Claude Code `emitFor` translates an advisory warn on
|
|
1069
|
+
// this slot into `hookSpecificOutput.additionalContext` at exit 0, so
|
|
1070
|
+
// this line reaches the MODEL, not the operator's terminal. That is the
|
|
1071
|
+
// host's translation and not a choice available here — `reason` alone
|
|
1072
|
+
// has no terminal-facing path on this slot.
|
|
1073
|
+
//
|
|
1074
|
+
// It is the right place anyway: the agent is what the user is talking
|
|
1075
|
+
// to, so an agent told its rule corpus is missing can say so. The cost
|
|
1076
|
+
// is one short line per turn for as long as the install stays broken,
|
|
1077
|
+
// which is bounded and deliberate. Bounding it further — once per
|
|
1078
|
+
// session, via the seen-set — needs a state write this concern cannot
|
|
1079
|
+
// currently afford (`src/config/hook-bundle-budget.json` left 30 bytes
|
|
1080
|
+
// of headroom at this commit), and is recorded in the roadmap rather
|
|
1081
|
+
// than silently skipped.
|
|
1082
|
+
process.stdout.write(`${JSON.stringify({ decision: 'warn', reason: src.gap })}\n`);
|
|
1083
|
+
return EXIT_WARN;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
418
1086
|
const seen = readSeen(root, session);
|
|
419
1087
|
const injection = buildInjection(root, prompt, openFiles, command, seen);
|
|
420
1088
|
if (injection === null) return EXIT_ALLOW; // silence is the default
|
|
@@ -423,10 +1091,77 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
|
|
|
423
1091
|
writeSeen(root, session, seen);
|
|
424
1092
|
recordDelivered(root, session, injection.rules);
|
|
425
1093
|
|
|
1094
|
+
return emit(injection, slot, root, session);
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* Where this session's arrival record lives (step 0.3).
|
|
1099
|
+
*
|
|
1100
|
+
* BESIDE THE SEEN-SET, under the user-global root and keyed by project, for the
|
|
1101
|
+
* reason step 1.7 moved that one: a measurement about a session is not state
|
|
1102
|
+
* about the project, and a consumer's `git status` is the wrong place to learn
|
|
1103
|
+
* what this dispatcher counted. The delivered-row ledger stays where it is —
|
|
1104
|
+
* `road-to-a-stop-that-holds` Phase 3 joins on it — and this is a different
|
|
1105
|
+
* record with a different reader, so the two do not have to live together.
|
|
1106
|
+
*
|
|
1107
|
+
* JSONL rather than one rewritten object: a fire appends, and an append cannot
|
|
1108
|
+
* lose the rows already written by partially serialising the whole file. The
|
|
1109
|
+
* over-budget COUNT the step asks for is the sum of the `over_budget` column,
|
|
1110
|
+
* which a reader computes; storing a running total here would be a second
|
|
1111
|
+
* number that can disagree with the rows it summarises.
|
|
1112
|
+
*/
|
|
1113
|
+
export function arrivalPath(root: string, session: string): string {
|
|
1114
|
+
const safe = session.replace(/[^A-Za-z0-9_-]/g, '_').slice(0, 80) || 'unknown';
|
|
1115
|
+
return path.join(
|
|
1116
|
+
user_global_paths.write_target(path.join('state', 'rule-inject', projectKey(root))),
|
|
1117
|
+
`${safe}.arrival.jsonl`,
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* Append one fire's arrival record, best-effort.
|
|
1123
|
+
*
|
|
1124
|
+
* NEVER THROWS. This is an instrument, and a turn refused because an instrument
|
|
1125
|
+
* missed a reading is worse than the missing reading — the same stance
|
|
1126
|
+
* `appendDelivered` takes, for the same reason.
|
|
1127
|
+
*/
|
|
1128
|
+
export function recordArrival(root: string, session: string, record: ArrivalRecord): void {
|
|
1129
|
+
try {
|
|
1130
|
+
const p = arrivalPath(root, session);
|
|
1131
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
1132
|
+
fs.appendFileSync(p, `${JSON.stringify(record)}\n`, 'utf-8');
|
|
1133
|
+
} catch {
|
|
1134
|
+
/* an unwritable instrument costs a reading, never a turn */
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
/** Read back what this session recorded, dropping any line nothing can parse. */
|
|
1139
|
+
export function readArrival(root: string, session: string): ArrivalRecord[] {
|
|
1140
|
+
let raw: string;
|
|
1141
|
+
try {
|
|
1142
|
+
raw = fs.readFileSync(arrivalPath(root, session), 'utf-8');
|
|
1143
|
+
} catch {
|
|
1144
|
+
return [];
|
|
1145
|
+
}
|
|
1146
|
+
const out: ArrivalRecord[] = [];
|
|
1147
|
+
for (const line of raw.split('\n')) {
|
|
1148
|
+
if (line.trim() === '') continue;
|
|
1149
|
+
try {
|
|
1150
|
+
out.push(JSON.parse(line) as ArrivalRecord);
|
|
1151
|
+
} catch {
|
|
1152
|
+
continue; // a torn last line must not cost the rows before it
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
return out;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
/** The one place a delivery reaches the host, shared by the fire and the restore. */
|
|
1159
|
+
function emit(injection: Injection, slot: string, root: string, session: string): number {
|
|
1160
|
+
recordArrival(root, session, arrivalRecord(injection, slot));
|
|
426
1161
|
process.stdout.write(
|
|
427
1162
|
`${JSON.stringify({
|
|
428
1163
|
decision: 'warn',
|
|
429
|
-
reason: `rule-inject: ${injection.rules.length} rule
|
|
1164
|
+
reason: `rule-inject: ${injection.rules.length}/${injection.manifest.length} matched rule(s) sent on ${slot} (${injection.body.length} chars)`,
|
|
430
1165
|
additional_context: injection.body,
|
|
431
1166
|
})}\n`,
|
|
432
1167
|
);
|