@event4u/agent-config 16.2.0 → 16.3.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 +89 -237
- package/README.md +14 -12
- package/dist/agent-src/commands/analyze/inbox.md +1 -1
- 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/feature/plan.md +1 -1
- package/dist/agent-src/commands/feature/roadmap.md +1 -1
- package/dist/agent-src/commands/fix/ci.md +1 -1
- package/dist/agent-src/commands/implement-ticket.md +1 -1
- package/dist/agent-src/commands/jira-ticket.md +1 -1
- 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/process-full.md +4 -4
- package/dist/agent-src/commands/roadmap/resolve-blockers.md +361 -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/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/execution/auto-dispatch-classification.md +6 -6
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +32 -2
- package/dist/agent-src/contexts/execution/session-canary-enforcement-history.md +67 -0
- package/dist/agent-src/contexts/execution/toolchain-resolver.md +101 -7
- package/dist/agent-src/rules/active-remediation.md +1 -1
- package/dist/agent-src/rules/autonomous-execution.md +1 -0
- package/dist/agent-src/rules/broken-access-control.md +1 -0
- package/dist/agent-src/rules/command-suggestion-policy.md +1 -0
- package/dist/agent-src/rules/context-hygiene.md +1 -1
- package/dist/agent-src/rules/decision-revisit-gate.md +16 -33
- package/dist/agent-src/rules/design-fidelity.md +13 -16
- package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -0
- package/dist/agent-src/rules/domain-safety-disclaimer.md +1 -0
- package/dist/agent-src/rules/domain-safety-pii.md +1 -0
- package/dist/agent-src/rules/domain-safety-retention.md +1 -0
- package/dist/agent-src/rules/engineering-safety-floor.md +1 -0
- package/dist/agent-src/rules/evaluator-independence.md +3 -2
- package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
- package/dist/agent-src/rules/finance-safety-floor.md +1 -0
- package/dist/agent-src/rules/git-history-discipline.md +1 -0
- package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
- package/dist/agent-src/rules/legal-safety-floor.md +3 -2
- package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
- package/dist/agent-src/rules/media-governance-routing.md +14 -17
- package/dist/agent-src/rules/persona-governance.md +2 -2
- package/dist/agent-src/rules/provider-lifecycle-discipline.md +1 -1
- package/dist/agent-src/rules/question-not-instruction.md +1 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
- package/dist/agent-src/rules/runtime-safety.md +1 -0
- package/dist/agent-src/rules/secret-vcs-guard.md +1 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +3 -2
- package/dist/agent-src/rules/session-canary.md +8 -62
- package/dist/agent-src/rules/settings-ask-protocol.md +1 -0
- package/dist/agent-src/rules/source-confidentiality.md +1 -0
- package/dist/agent-src/rules/strategy-safety-floor.md +1 -0
- package/dist/agent-src/rules/tool-safety.md +1 -0
- package/dist/agent-src/rules/untrusted-input-defense.md +1 -0
- package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
- 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/update_roadmap_progress.ts +4 -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/code-intelligence/SKILL.md +14 -2
- package/dist/agent-src/skills/design-system-capture/references/import-procedure.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/iconography/SKILL.md +1 -1
- package/dist/agent-src/skills/js-library-packaging/SKILL.md +2 -2
- package/dist/agent-src/skills/monorepo-workspace/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/review-routing/SKILL.md +2 -2
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +3 -3
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
- 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/ui-component-architect/SKILL.md +2 -2
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
- package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
- package/dist/cli/registry.js +1 -1
- package/dist/cli-delegate/archive_completed_roadmaps.js +92 -12
- package/dist/cli-delegate/check_update_banner.js +12 -1
- package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
- package/dist/cli-delegate/{chunk-M2DPGFZ4.js → chunk-6V6TA25H.js} +22 -152
- package/dist/cli-delegate/chunk-7IQE3D5S.js +191 -0
- package/dist/cli-delegate/{chunk-6KFEC3G7.js → chunk-CQWX336L.js} +1 -1
- package/dist/cli-delegate/chunk-DEIV7XP7.js +108 -0
- package/dist/cli-delegate/chunk-EEHDXSYD.js +2040 -0
- package/dist/cli-delegate/{chunk-P6ZHP5W2.js → chunk-GKE7ZVFA.js} +4 -4
- package/dist/cli-delegate/chunk-JQOM7TTM.js +1201 -0
- package/dist/cli-delegate/{chunk-IGD6ERX6.js → chunk-JQVYKIZA.js} +10 -0
- package/dist/cli-delegate/{chunk-FC75PGJE.js → chunk-JY2I2DXV.js} +12 -1
- 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-LIYTLH6O.js} +1 -1
- package/dist/cli-delegate/{chunk-YJ72UIAK.js → chunk-NQ7WT43P.js} +1186 -244
- package/dist/cli-delegate/chunk-NXEILF2K.js +2160 -0
- package/dist/cli-delegate/{chunk-5R7HOLAF.js → chunk-NYX3L2PZ.js} +1 -1
- 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-RGNIWTEA.js → chunk-QLDHIPWR.js} +1 -1
- package/dist/cli-delegate/{chunk-7XGBX6ZH.js → chunk-RQNNW7IF.js} +1314 -560
- package/dist/cli-delegate/{chunk-4XFP2Y3E.js → chunk-S4UMZNES.js} +1 -1
- package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-S4YWNRKO.js} +24 -8
- package/dist/cli-delegate/{chunk-FP36SWJD.js → chunk-TRI6DQZ6.js} +2781 -340
- package/dist/cli-delegate/chunk-ULQRUR23.js +208 -0
- 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 +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +1167 -234
- package/dist/cli-delegate/cmd_doctor.js +13 -6
- package/dist/cli-delegate/cmd_doctor_neighbours.js +23 -0
- package/dist/cli-delegate/cmd_explain.js +5 -5
- package/dist/cli-delegate/cmd_export.js +11 -8
- package/dist/cli-delegate/cmd_fleet.js +3 -3
- package/dist/cli-delegate/cmd_handoff.js +10 -7
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_memory_get.js +3 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +3 -3
- package/dist/cli-delegate/cmd_preflight.js +6 -6
- package/dist/cli-delegate/cmd_prune.js +11 -8
- package/dist/cli-delegate/cmd_refresh.js +2 -1
- package/dist/cli-delegate/cmd_route_audit.js +8 -6
- package/dist/cli-delegate/cmd_route_explain.js +4 -4
- package/dist/cli-delegate/cmd_session_recycle.js +4 -3
- package/dist/cli-delegate/cmd_settings_get.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +11 -8
- package/dist/cli-delegate/cmd_settings_set.js +4 -4
- package/dist/cli-delegate/cmd_sync.js +11 -8
- package/dist/cli-delegate/cmd_uninstall.js +11 -8
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +3 -2
- package/dist/cli-delegate/cmd_validate.js +11 -8
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/cli-delegate/deferral_carry.js +16 -0
- package/dist/cli-delegate/gate_execute.js +2 -2
- package/dist/cli-delegate/roadmap_gates.js +5 -5
- package/dist/cli-delegate/update_roadmap_progress.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +127 -75
- 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 +7 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +6 -4
- package/dist/hooks/dispatch.js +5357 -6656
- 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 +2080 -1024
- package/dist/install/installThinLayer.js +261 -0
- package/dist/install/installedRuleLinks.js +176 -0
- package/dist/install/wizard-plan.js +24 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +31965 -27154
- package/dist/scripts/_lib/agent_settings.js +11 -0
- package/dist/scripts/_lib/claude_settings_hooks.js +10 -8
- package/dist/scripts/_lib/installed_layer.js +378 -0
- package/dist/scripts/_lib/lean_projection_mode.js +218 -15
- 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/thin_rules.js +458 -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/schemas/settings.js +6 -0
- package/dist/shared/skillRanking.js +74 -13
- package/docs/contracts/settings-classes.md +7 -5
- 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/update_roadmap_progress.ts +4 -1
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
- package/src/config/agent-settings.template.yml +62 -22
- package/src/config/continuity-surface.json +7 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +96 -3
- package/src/config/gate-violation-baselines.json +17 -9
- 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/routing-coverage-seed.json +24 -3
- 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 +99 -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 +312 -0
- package/src/install/installedRuleLinks.ts +220 -0
- package/src/install/wizard-plan.ts +24 -0
- package/src/scripts/_cli/cmd_doctor.ts +23 -71
- package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
- package/src/scripts/_dispatch.bash +16 -2
- package/src/scripts/_lib/agent_settings.ts +11 -0
- package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
- 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/gitignore_block.ts +314 -0
- package/src/scripts/_lib/global_deploy_inventory.ts +32 -0
- package/src/scripts/_lib/graph_feeder_record.ts +214 -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 +538 -0
- package/src/scripts/_lib/json_merge.ts +82 -0
- package/src/scripts/_lib/json_pointers.ts +93 -11
- package/src/scripts/_lib/lean_projection_mode.ts +275 -15
- package/src/scripts/_lib/machine_wake.ts +16 -3
- package/src/scripts/_lib/neighbour_census.ts +760 -0
- package/src/scripts/_lib/neighbour_scan.ts +122 -0
- package/src/scripts/_lib/neighbour_tool_use.ts +189 -0
- package/src/scripts/_lib/prompt_shape.ts +46 -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/skill_origin.ts +254 -0
- package/src/scripts/_lib/structured_ask.ts +64 -21
- package/src/scripts/_lib/thin_rules.ts +540 -0
- package/src/scripts/_lib/touched_file_quality.ts +434 -0
- package/src/scripts/_lib/turn_end_refusals.ts +77 -2
- package/src/scripts/_lib/windsurf_render.ts +63 -0
- 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 +1 -1
- package/src/scripts/ai_council/vendor_defaults.ts +45 -0
- package/src/scripts/audit_skill_overlap.ts +261 -0
- package/src/scripts/before_complete_hook.ts +243 -12
- package/src/scripts/bench_touched_file_quality.ts +238 -0
- package/src/scripts/check_enforcement_matrix.ts +377 -20
- package/src/scripts/check_estate_count.ts +130 -153
- package/src/scripts/check_finding_dispositions.ts +116 -4
- package/src/scripts/check_hook_bundle_composition.ts +370 -0
- package/src/scripts/check_kernel_edit_ratified.ts +49 -11
- package/src/scripts/check_memory.ts +7 -1
- package/src/scripts/check_release_highlights.ts +10 -0
- package/src/scripts/check_release_pr_shape.ts +38 -15
- package/src/scripts/check_standing_rule_delivery.ts +33 -13
- package/src/scripts/check_verify_expectation_delta.ts +521 -0
- 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/compile_hook_manifest.ts +33 -1
- package/src/scripts/condense.ts +14 -27
- package/src/scripts/estate_exemption_shape.ts +202 -0
- package/src/scripts/generate_host_cost_table.ts +1 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +150 -9
- package/src/scripts/hooks/block_config_weakening.ts +67 -2
- package/src/scripts/hooks/block_plumbing_writes.ts +29 -1
- 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/dispatch_hook.ts +52 -52
- 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/one_question_per_ask_hook.ts +41 -25
- package/src/scripts/hooks/payload_stub.ts +10 -1
- package/src/scripts/hooks/rule_inject_hook.ts +757 -63
- package/src/scripts/hooks/table_fingerprint.ts +21 -0
- package/src/scripts/hooks/telemetry_usage_hook.ts +64 -8
- package/src/scripts/hooks/turn_end_gate_hook.ts +84 -0
- package/src/scripts/hooks_status.ts +112 -0
- package/src/scripts/injection_scan_hook.ts +7 -0
- package/src/scripts/install.ts +72 -185
- 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_hook_manifest.ts +53 -0
- package/src/scripts/lint_major_migration_sections.ts +156 -26
- package/src/scripts/lint_pack_boundaries.ts +63 -5
- package/src/scripts/lint_rule_law_section.ts +517 -0
- package/src/scripts/lint_workflow_security.ts +161 -5
- 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 +216 -8
- package/src/scripts/memory_learn_hook.ts +91 -6
- package/src/scripts/model_rule_injection.ts +52 -5
- package/src/scripts/project_thin_rules.ts +34 -327
- package/src/scripts/report_evidence_temperature.ts +657 -0
- package/src/scripts/report_installed_rule_links.ts +157 -0
- package/src/scripts/report_label_agreement.ts +233 -0
- package/src/scripts/report_obligation_mechanism.ts +230 -0
- package/src/scripts/report_single_token_triggers.ts +277 -0
- package/src/scripts/report_skill_ranker_confusion.ts +442 -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/rule_activation_census.ts +5 -2
- package/src/scripts/rule_trigger_eval.ts +9 -9
- package/src/scripts/schemas/review-findings.schema.json +3 -2
- package/src/scripts/schemas/rule.schema.json +5 -0
- package/src/scripts/self_review_gate.ts +62 -7
- package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
- package/src/scripts/sweep_skill_ranker_signals.ts +129 -0
- package/src/scripts/sync_gitignore.ts +29 -235
- package/src/scripts/trigger_eval_grandfather.json +0 -5
- package/src/server/schemas/settings.ts +10 -0
- package/src/shared/skillRanking.ts +117 -11
- package/dist/cli-delegate/chunk-JNMVLWLR.js +0 -4457
- package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
|
@@ -238,7 +238,11 @@ Tier 2 — maintenance / internal (hooks, MCP, memory, telemetry):
|
|
|
238
238
|
roadmap:set-step Flip ONE checkbox glyph safely (lock + line anchor +
|
|
239
239
|
live invariant + survival check)
|
|
240
240
|
roadmap:archive Archive completed roadmaps (branch-touched by default;
|
|
241
|
-
--all for every complete one; --dry-run to preview
|
|
241
|
+
--all for every complete one; --dry-run to preview;
|
|
242
|
+
bare [~] steps are carried to a follow-up first,
|
|
243
|
+
--no-carry to leave them blocking; owner-blocked
|
|
244
|
+
steps ask first; --owner-decision later --only <x>
|
|
245
|
+
parks that one roadmap)
|
|
242
246
|
stubs:due Read-only: roadmap stubs past their `review_by:`
|
|
243
247
|
date, plus the decisions routed to the owner.
|
|
244
248
|
Writes nothing. Flags: --json, --counts,
|
|
@@ -342,7 +346,9 @@ Tier 2 — maintenance / internal (hooks, MCP, memory, telemetry):
|
|
|
342
346
|
memory:check-proposal Run the admission gate on a memory proposal
|
|
343
347
|
memory:learn Aggregate memory intake signals into the local learning
|
|
344
348
|
sidecar (read-only; --write to emit). Usage: memory:learn [--intake-dir DIR]
|
|
345
|
-
[--out-dir DIR] [--now ISO] [--write] [--format text|json]
|
|
349
|
+
[--out-dir DIR] [--now ISO] [--write] [--format text|json|status]
|
|
350
|
+
--format status prints the one-screen human view (verdict counts,
|
|
351
|
+
top preferred lessons, the hand-off); it refuses --write.
|
|
346
352
|
analytics Local-only workspace analytics (emit|show|prune|migrate).
|
|
347
353
|
Usage: analytics show [--window 30d|7d|24h] · analytics prune
|
|
348
354
|
knowledge Global knowledge-card store (list|show|trace|forget|promote|
|
|
@@ -1456,7 +1462,15 @@ cmd_prune() {
|
|
|
1456
1462
|
# `agent-config doctor` — read-only drift report against the manifest.
|
|
1457
1463
|
# Surfaces missing / modified / foreign files. Exit 0 clean, 1 drift,
|
|
1458
1464
|
# 2 manifest-absent. See scripts/_cli/cmd_doctor.ts.
|
|
1465
|
+
# `doctor neighbours` routes to its own entry point rather than a branch inside
|
|
1466
|
+
# cmd_doctor.ts, which is 2,000 lines over the source-size ceiling and cannot
|
|
1467
|
+
# grow. Routed here because the dispatcher is where verbs are already resolved.
|
|
1459
1468
|
cmd_doctor() {
|
|
1469
|
+
if [[ "${1:-}" == "neighbours" ]]; then
|
|
1470
|
+
shift
|
|
1471
|
+
exec_ts "$PACKAGE_ROOT/src/scripts/_cli/cmd_doctor_neighbours.ts" "$@"
|
|
1472
|
+
return
|
|
1473
|
+
fi
|
|
1460
1474
|
exec_ts "$PACKAGE_ROOT/src/scripts/_cli/cmd_doctor.ts" "$@"
|
|
1461
1475
|
}
|
|
1462
1476
|
|
|
@@ -305,6 +305,17 @@ export const MERGEABLE_KEYS: readonly string[] = [
|
|
|
305
305
|
// GUI; whitelisting decides only whether the value SURVIVES the merge
|
|
306
306
|
// (ADR-271, road-to-design-intent-conformance 2.3).
|
|
307
307
|
'design.fidelity_mode',
|
|
308
|
+
// `lean_projection.*` is a per-MACHINE setting and the install it governs is
|
|
309
|
+
// a user-scope one: the layer being thinned is `~/.claude/rules`, which
|
|
310
|
+
// belongs to the machine and not to any checkout. Without these two rows a
|
|
311
|
+
// consumer who wrote the opt-in into the user-global file got no error, no
|
|
312
|
+
// warning and no effect — the same silent-filter defect the two entries
|
|
313
|
+
// above repair, reaching a third key, and unsatisfiable-by-construction on
|
|
314
|
+
// an ADR-020 global-only install, where no project layer exists to carry it.
|
|
315
|
+
// Whitelisting decides only whether a value SURVIVES the merge: it sets
|
|
316
|
+
// nothing, and `installerThinsHost` still refuses a template-only value.
|
|
317
|
+
'lean_projection.mode',
|
|
318
|
+
'lean_projection.hosts',
|
|
308
319
|
];
|
|
309
320
|
|
|
310
321
|
/** The shipped template, relative to the package root. */
|
|
@@ -98,16 +98,18 @@ export function build_claude_hook_matrix(manifest_path: string): ClaudeHookMatri
|
|
|
98
98
|
const aliasesAll = (manifest['native_event_aliases'] ?? {}) as Record<string, unknown>;
|
|
99
99
|
const aliases = ((aliasesAll['claude'] ?? {}) as Record<string, unknown>) || {};
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
101
|
+
// Iterating the ALIAS map, not the event map, because the relation is
|
|
102
|
+
// many-natives-to-one-event and the inversion this replaced silently lost
|
|
103
|
+
// every native but the last. `PostToolUse` and `PostToolUseFailure` both
|
|
104
|
+
// lower to `post_tool_use`; building `ac_to_native` would have kept one of
|
|
105
|
+
// them and UNBOUND the other, turning a routing fix into a routing
|
|
106
|
+
// regression that no existing test could see. Pinned by
|
|
107
|
+
// `before_complete_failure_envelope.test.ts` ("both natives stay bound").
|
|
106
108
|
const matrix: ClaudeHookMatrix = {};
|
|
107
|
-
for (const [
|
|
109
|
+
for (const [native, ac_raw] of Object.entries(aliases)) {
|
|
110
|
+
const ac_event = String(ac_raw);
|
|
111
|
+
const concerns = claude_events[ac_event];
|
|
108
112
|
if (!concerns || (Array.isArray(concerns) && concerns.length === 0)) continue;
|
|
109
|
-
const native = ac_to_native[ac_event];
|
|
110
|
-
if (native === undefined) continue;
|
|
111
113
|
// Bundle fast path (road-to-hook-latency-repair Phase 2): invoke the
|
|
112
114
|
// precompiled dispatcher bundle directly — no bash shim, no CLI boot
|
|
113
115
|
// (measured ~450-500 ms CLI vs ~110 ms bundle per event on a 1-vCPU
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a CHANGE touches the roadmap estate — the diff half, read once.
|
|
3
|
+
*
|
|
4
|
+
* `check_estate_count` answers two different questions with two different
|
|
5
|
+
* inputs. The count half reads the TREE and ratchets it against the base ref's
|
|
6
|
+
* tree. This module is the other half: it reads the DIFF and says what the
|
|
7
|
+
* change did — which roadmaps entered the active top level, which left it, which
|
|
8
|
+
* of the arrivals declared an exemption, and what that exemption says.
|
|
9
|
+
*
|
|
10
|
+
* WHY IT LIVES HERE AND NOT IN THE GATE. `check_source_size_budget` scores
|
|
11
|
+
* `Σ max(0, lines(f) − 1500)` over `src/**` and the gate crossed the ceiling
|
|
12
|
+
* when the exemption-shape work landed. That gate does not object to new code;
|
|
13
|
+
* it objects to new code in the file that is already too big to read, and the
|
|
14
|
+
* ratchet was right about which file that was. The seam it forced is the one a
|
|
15
|
+
* reader would draw anyway — tree-reading on one side of it, diff-reading on the
|
|
16
|
+
* other — so the extraction is a correction rather than a concession.
|
|
17
|
+
*
|
|
18
|
+
* `exemptionReason` sits here rather than beside the vocabulary it feeds
|
|
19
|
+
* (`exemption_shape.ts`) because this is the module that READS a change; that
|
|
20
|
+
* one is the module that JUDGES a reason. The split is read-then-judge, and
|
|
21
|
+
* keeping it means the vocabulary module imports no filesystem and no git.
|
|
22
|
+
*/
|
|
23
|
+
import * as path from 'node:path';
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
is_roadmap_candidate as isRoadmapCandidate,
|
|
27
|
+
parse_frontmatter as parseFrontmatter,
|
|
28
|
+
} from '../../agent-src/scripts/update_roadmap_progress.js';
|
|
29
|
+
|
|
30
|
+
/** How a change touched the active roadmap tree, as git reports it. */
|
|
31
|
+
export interface OffsetLedger {
|
|
32
|
+
/** Files that entered the active top level: new files, and un-parked ones. */
|
|
33
|
+
added: string[];
|
|
34
|
+
/** Files that left it: deleted, archived, parked or merged away. */
|
|
35
|
+
offsets: string[];
|
|
36
|
+
/** Added files carrying an `estate_offset_exempt:` reason, with the reason. */
|
|
37
|
+
exempt: Array<{ file: string; reason: string }>;
|
|
38
|
+
/**
|
|
39
|
+
* The subset of `offsets` that went to `later/`.
|
|
40
|
+
*
|
|
41
|
+
* Tracked separately because parking is the one offset that RAISES another
|
|
42
|
+
* gated count: active falls by one and later rises by one, which under an
|
|
43
|
+
* exact floor is growth in `later_roadmaps` unless this allowance exists.
|
|
44
|
+
* Kept out of a general "any offset raises any allowance" rule on purpose —
|
|
45
|
+
* an archived roadmap must not buy a new `later/` file.
|
|
46
|
+
*/
|
|
47
|
+
parked: string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** An `estate_growth_exempt:` reason ADDED to a roadmap in this change. */
|
|
51
|
+
export interface GrowthClaim {
|
|
52
|
+
/** The roadmap the claim was added to, as git reports the path. */
|
|
53
|
+
file: string;
|
|
54
|
+
reason: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** `agents/roadmaps/<name>.md` — the active top level. code-comment-allow provenance-comment -- operand, not provenance */
|
|
58
|
+
function isActiveTopLevel(rel: string): boolean {
|
|
59
|
+
const norm = rel.split(path.sep).join('/');
|
|
60
|
+
if (!norm.startsWith('agents/roadmaps/') || !norm.endsWith('.md')) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const tail = norm.slice('agents/roadmaps/'.length);
|
|
64
|
+
return !tail.includes('/') && isRoadmapCandidate(norm);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* A disposition directory — where an offset sends a roadmap.
|
|
69
|
+
*
|
|
70
|
+
* `stubs/` is in the set, and it was missing from the first version. Un-stubbing
|
|
71
|
+
* is the documented promotion path, so a stub moved to the top level is an
|
|
72
|
+
* ADDITION that T3 must charge, and a roadmap demoted to a stub is an offset.
|
|
73
|
+
* With `stubs/` unrecognised, a promotion was classified as neither and the lint
|
|
74
|
+
* could never charge it — the one hole that let an active roadmap arrive for free.
|
|
75
|
+
*/
|
|
76
|
+
function isDisposed(rel: string): boolean {
|
|
77
|
+
const norm = rel.split(path.sep).join('/');
|
|
78
|
+
return /^agents\/roadmaps\/(archive|later|skipped|stubs)\//.test(norm);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** A roadmap parked for later — the one disposition that grows another count. */
|
|
82
|
+
function isParked(rel: string): boolean {
|
|
83
|
+
return rel.split(path.sep).join('/').startsWith('agents/roadmaps/later/');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Read the exemption reason a newly added roadmap declares, if any.
|
|
88
|
+
*
|
|
89
|
+
* The key lives in the file's own frontmatter rather than in a config or a
|
|
90
|
+
* commit trailer, for the reason `RATCHET_RESET_KEY` gives for living inside the
|
|
91
|
+
* baseline JSON: the claim then shows up in the diff of the change that makes
|
|
92
|
+
* it, and a reviewer sees it without being told to look.
|
|
93
|
+
*
|
|
94
|
+
* THE BLOCK SCALAR IS READ HERE AND NOT BY `parseFrontmatter`, which is a flat
|
|
95
|
+
* line parser with no YAML block support: it returns the value as everything
|
|
96
|
+
* after the first colon. Measured 2026-10-01 across the three roadmap trees,
|
|
97
|
+
* **38 of 221 exemptions are written `estate_offset_exempt: >-` with the reason
|
|
98
|
+
* indented beneath**, and for every one of them this function used to return
|
|
99
|
+
* the literal two-character string `>-`. That is non-empty, so the gate
|
|
100
|
+
* accepted it — a key whose whole purpose is that a reviewer sees the claim was
|
|
101
|
+
* being checked against a YAML punctuation mark on one file in six. The fix
|
|
102
|
+
* belongs here rather than in the shared parser because every other caller of
|
|
103
|
+
* that parser reads single-token values (`status`, `complexity`) where block
|
|
104
|
+
* support buys nothing and a rewrite risks the dashboard.
|
|
105
|
+
*/
|
|
106
|
+
export function exemptionReason(text: string): string | null {
|
|
107
|
+
const block = blockScalar(text, 'estate_offset_exempt');
|
|
108
|
+
if (block !== null) return block === '' ? null : block;
|
|
109
|
+
const fm = parseFrontmatter(text);
|
|
110
|
+
const raw = (fm as Record<string, unknown>)['estate_offset_exempt'];
|
|
111
|
+
if (typeof raw !== 'string') {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
const reason = raw.trim().replace(/^["']|["']$/g, '').trim();
|
|
115
|
+
return reason === '' ? null : reason;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The folded body of `<key>: >-` / `<key>: |`, or `null` when not that form.
|
|
120
|
+
*
|
|
121
|
+
* Returns `''` — not `null` — for a block header with no body, so the caller can
|
|
122
|
+
* tell "declared and empty" from "not a block scalar". An empty block is the
|
|
123
|
+
* blank-reason case the key already refuses, and collapsing it into `null` here
|
|
124
|
+
* would send it back to the flat parser, which would hand back `>-` and accept it.
|
|
125
|
+
*/
|
|
126
|
+
function blockScalar(text: string, key: string): string | null {
|
|
127
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
|
|
128
|
+
if (m === null) return null;
|
|
129
|
+
const lines = (m[1] as string).split(/\r?\n/);
|
|
130
|
+
const head = new RegExp(`^${key}:[ \\t]*(.*)$`);
|
|
131
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
132
|
+
const hm = head.exec(lines[i] as string);
|
|
133
|
+
if (hm === null) continue;
|
|
134
|
+
// Only the block indicators. A quoted or bare value is the flat form and
|
|
135
|
+
// stays with the flat parser, which already handles it.
|
|
136
|
+
//
|
|
137
|
+
// THE TRAILING COMMENT IS PART OF THE HEADER, and omitting it was a
|
|
138
|
+
// BYPASS rather than a parse gap. `estate_offset_exempt: >- # offset`
|
|
139
|
+
// failed this test, fell through to the flat parser, and came back as
|
|
140
|
+
// the string `">- # offset"` — which the shape rule then searched for a
|
|
141
|
+
// disposition word and found one, IN THE COMMENT, over a body it never
|
|
142
|
+
// read. The indicator may also be written `>2-` as well as `>-2`, so
|
|
143
|
+
// both orders are accepted; an indentation indicator is never `0`.
|
|
144
|
+
if (!/^[>|](?:[1-9][-+]?|[-+]?[1-9]?)(?:[ \t]+#.*)?$/.test((hm[1] as string).trim())) return null;
|
|
145
|
+
const body: string[] = [];
|
|
146
|
+
for (let j = i + 1; j < lines.length; j += 1) {
|
|
147
|
+
const line = lines[j] as string;
|
|
148
|
+
if (line.trim() === '') continue;
|
|
149
|
+
// The first unindented line ends the block — the next key.
|
|
150
|
+
if (!/^[ \t]/.test(line)) break;
|
|
151
|
+
body.push(line.trim());
|
|
152
|
+
}
|
|
153
|
+
return body.join(' ').replace(/\s+/g, ' ').trim();
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Classify the change's effect on the active roadmap tree.
|
|
160
|
+
*
|
|
161
|
+
* Renames carry information a name-only diff loses: `road-to-x.md` →
|
|
162
|
+
* `archive/road-to-x.md` is the wanted direction and counts as an offset, while
|
|
163
|
+
* `later/road-to-x.md` → `road-to-x.md` is an un-parking and counts as an
|
|
164
|
+
* addition. A top-level-to-top-level rename is neither.
|
|
165
|
+
*/
|
|
166
|
+
export function classifyDiff(
|
|
167
|
+
nameStatus: string,
|
|
168
|
+
readFile: (rel: string) => string | null,
|
|
169
|
+
): OffsetLedger {
|
|
170
|
+
const added: string[] = [];
|
|
171
|
+
const offsets: string[] = [];
|
|
172
|
+
const exempt: Array<{ file: string; reason: string }> = [];
|
|
173
|
+
const parked: string[] = [];
|
|
174
|
+
for (const line of nameStatus.split('\n')) {
|
|
175
|
+
if (line.trim() === '') continue;
|
|
176
|
+
const cols = line.split('\t');
|
|
177
|
+
const status = (cols[0] ?? '').trim();
|
|
178
|
+
if (status.startsWith('R') || status.startsWith('C')) {
|
|
179
|
+
const from = cols[1] ?? '';
|
|
180
|
+
const to = cols[2] ?? '';
|
|
181
|
+
if (isActiveTopLevel(from) && isDisposed(to)) {
|
|
182
|
+
offsets.push(from);
|
|
183
|
+
if (isParked(to)) parked.push(to);
|
|
184
|
+
} else if (isDisposed(from) && isActiveTopLevel(to)) {
|
|
185
|
+
added.push(to);
|
|
186
|
+
}
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const file = cols[1] ?? '';
|
|
190
|
+
if (!isActiveTopLevel(file)) continue;
|
|
191
|
+
if (status === 'A') {
|
|
192
|
+
added.push(file);
|
|
193
|
+
} else if (status === 'D') {
|
|
194
|
+
offsets.push(file);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
for (const file of added) {
|
|
198
|
+
const text = readFile(file);
|
|
199
|
+
if (text === null) continue;
|
|
200
|
+
const reason = exemptionReason(text);
|
|
201
|
+
if (reason !== null) {
|
|
202
|
+
exempt.push({ file, reason });
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { added, offsets, exempt, parked };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* The `estate_growth_exempt:` reasons this change ADDS, read from the patch.
|
|
210
|
+
*
|
|
211
|
+
* Read from the diff rather than from the file, and that is the point: a claim
|
|
212
|
+
* sitting in a roadmap authorises nothing on a later change, so an exemption
|
|
213
|
+
* cannot be banked the way surplus in a stored baseline could. It also means a
|
|
214
|
+
* newly added roadmap and an edited one need no separate handling — in a
|
|
215
|
+
* `base...HEAD` patch both arrive as `+` lines.
|
|
216
|
+
*
|
|
217
|
+
* `--unified=0` keeps context lines out, so a claim that merely sits NEAR an
|
|
218
|
+
* edited line is not read as added. Deliberately tolerant of leading whitespace
|
|
219
|
+
* and of quoted values, matching `exemptionReason`; deliberately NOT tolerant of
|
|
220
|
+
* an empty reason, because an exemption whose reason is blank is the silent
|
|
221
|
+
* exception the key exists to replace.
|
|
222
|
+
*/
|
|
223
|
+
export function growthClaims(patch: string): GrowthClaim[] {
|
|
224
|
+
const out: GrowthClaim[] = [];
|
|
225
|
+
let file = '';
|
|
226
|
+
for (const line of patch.split('\n')) {
|
|
227
|
+
// `+++ b/<path>` names the file the following `+` lines belong to. The
|
|
228
|
+
// `/dev/null` form is a deletion, which cannot carry a claim.
|
|
229
|
+
const head = /^\+\+\+ b\/(.+)$/.exec(line);
|
|
230
|
+
if (head !== null) {
|
|
231
|
+
file = (head[1] ?? '').trim();
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
if (!line.startsWith('+') || line.startsWith('+++')) continue;
|
|
235
|
+
const m = /^\+\s*estate_growth_exempt:\s*(.+?)\s*$/.exec(line);
|
|
236
|
+
if (m === null) continue;
|
|
237
|
+
const reason = (m[1] ?? '').trim().replace(/^["']|["']$/g, '').trim();
|
|
238
|
+
if (reason !== '') out.push({ file, reason });
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an `estate_offset_exempt` reason has to SAY — one vocabulary, two readers.
|
|
3
|
+
*
|
|
4
|
+
* THE DEFECT THIS EXISTS TO NAME. The one-in-one-out rule charges every added
|
|
5
|
+
* active roadmap against a disposed one, and `estate_offset_exempt: <reason>`
|
|
6
|
+
* is the escape hatch that costs "one reviewable line instead of a silent
|
|
7
|
+
* exception". `exemptionReason` accepted any non-empty string, so the line was
|
|
8
|
+
* reviewable in the sense that it existed. Measured 2026-10-01 over the active,
|
|
9
|
+
* `later/` and `archive/` trees: 221 files carry the key, and 43 of them name
|
|
10
|
+
* no alternative disposition at all under the three-lemma set the roadmap named
|
|
11
|
+
* from memory, and 6 under the measured one below — including three that say only
|
|
12
|
+
* `lane N of <parent>`. The escape hatch was free.
|
|
13
|
+
*
|
|
14
|
+
* WHAT A SHAPED REASON SAYS. It names at least one disposition the author
|
|
15
|
+
* considered instead of adding a file: archiving something, parking it in
|
|
16
|
+
* `later/`, merging into an existing roadmap, or any of the words this tree
|
|
17
|
+
* actually uses for those three. The requirement is deliberately NOT "explain
|
|
18
|
+
* yourself well" — that is unjudgeable — but "name what you rejected", which is
|
|
19
|
+
* a word that is either present or absent.
|
|
20
|
+
*
|
|
21
|
+
* WHY THE VOCABULARY IS MEASURED AND NOT CHOSEN. The commissioning roadmap
|
|
22
|
+
* named three lemmas, archive / park / merge, from memory. Over the real
|
|
23
|
+
* population those three cover 178 of 221; the measured ten below cover 215.
|
|
24
|
+
* The 37-file gap is not boilerplate — "Offsetting it individually is not
|
|
25
|
+
* possible without splitting the set" and "no completed roadmap to retire
|
|
26
|
+
* against this addition" both name a rejected alternative in words the
|
|
27
|
+
* three-lemma set does not carry. A refusal vocabulary that reds one added
|
|
28
|
+
* roadmap in five on the house style gets weakened until it finds nothing, so
|
|
29
|
+
* the set here is the one the tree writes. Every number above carries its
|
|
30
|
+
* producing command: `./scripts-run src/scripts/estate_exemption_shape`.
|
|
31
|
+
*
|
|
32
|
+
* `close` is excluded on purpose, and it is the most common verb in these
|
|
33
|
+
* reasons: it almost always refers to closing WORK, not to disposing of a file,
|
|
34
|
+
* so accepting it would make the check pass on a sentence that named nothing.
|
|
35
|
+
*
|
|
36
|
+
* WHAT THIS DOES NOT DO, stated because a reviewer called it HIGH and the
|
|
37
|
+
* disagreement is worth recording rather than resolving silently. The cheapest
|
|
38
|
+
* string that passes is the single word `park`, and `park 1` / `park 2` are two
|
|
39
|
+
* distinct reasons to the duplicate rule. So this verifies that a disposition
|
|
40
|
+
* word APPEARS; it does not verify that the author thought about one, and no
|
|
41
|
+
* check can. The claim the gate's message makes is therefore "name what you
|
|
42
|
+
* rejected", which is what a word either does or does not do — not "explain
|
|
43
|
+
* yourself well", which is unjudgeable. A reason gamed down to one word is
|
|
44
|
+
* visible to a human reviewer in the diff, which is where the key's own stated
|
|
45
|
+
* purpose puts the real control; what this removes is the case where there was
|
|
46
|
+
* nothing to look at because the reason was free text nobody had read.
|
|
47
|
+
*
|
|
48
|
+
* WHY ONE MODULE. Two readers consume this — the estate gate, which refuses,
|
|
49
|
+
* and the measurement script, which reports. A second copy of the lemma list is
|
|
50
|
+
* how a gate and its own evidence drift apart, and a drifted vocabulary fails
|
|
51
|
+
* silently: the report says the tree conforms while the gate refuses it.
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The dispositions a reason may name, as source strings.
|
|
56
|
+
*
|
|
57
|
+
* Exported individually rather than as one baked regex so the measurement
|
|
58
|
+
* script can report per-lemma counts against the same list the gate refuses on.
|
|
59
|
+
*/
|
|
60
|
+
export const DISPOSITION_LEMMAS: readonly { readonly name: string; readonly source: string }[] = [
|
|
61
|
+
{ name: 'archive', source: String.raw`archiv\w*` },
|
|
62
|
+
// `park` is the one stem with a common false friend, and a reviewer named
|
|
63
|
+
// it: `park\w*` matches `parkour` at a word boundary, so a reason saying
|
|
64
|
+
// nothing about dispositions passes on a word that is not one. Enumerated
|
|
65
|
+
// instead of stemmed — the inflections this tree writes, and no more. Every
|
|
66
|
+
// other lemma keeps its stem; none of them has a false friend in English
|
|
67
|
+
// that could plausibly appear in a sentence about roadmap dispositions.
|
|
68
|
+
{ name: 'park', source: String.raw`unpark(?:ed|ing|s)?\b|park(?:ed|ing|s)?\b` },
|
|
69
|
+
{ name: 'merge', source: String.raw`merg\w*` },
|
|
70
|
+
{ name: 'later', source: String.raw`later\/` },
|
|
71
|
+
{ name: 'offset', source: String.raw`offset\w*` },
|
|
72
|
+
{ name: 'defer', source: String.raw`defer\w*` },
|
|
73
|
+
{ name: 'fold', source: String.raw`fold\w*` },
|
|
74
|
+
{ name: 'consolidate', source: String.raw`consolidat\w*` },
|
|
75
|
+
{ name: 'absorb', source: String.raw`absorb\w*` },
|
|
76
|
+
{ name: 'retire', source: String.raw`retir\w*` },
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
/** The whole vocabulary as one alternation — the gate's own test. */
|
|
80
|
+
export const DISPOSITION_SOURCE = `\\b(?:${DISPOSITION_LEMMAS.map((l) => l.source).join('|')})`;
|
|
81
|
+
|
|
82
|
+
const DISPOSITION_RE = new RegExp(DISPOSITION_SOURCE, 'i');
|
|
83
|
+
|
|
84
|
+
/** Does this reason name at least one rejected alternative? */
|
|
85
|
+
export function namesDisposition(reason: string): boolean {
|
|
86
|
+
return DISPOSITION_RE.test(reason);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Which lemmas a reason names, for the reader's per-file column. */
|
|
90
|
+
export function lemmasNamed(reason: string): string[] {
|
|
91
|
+
return DISPOSITION_LEMMAS.filter((l) => new RegExp(`\\b(?:${l.source})`, 'i').test(reason)).map((l) => l.name);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The comparison key for "two added files say the same thing".
|
|
96
|
+
*
|
|
97
|
+
* Case and whitespace are folded because a reason pasted into a second file and
|
|
98
|
+
* re-wrapped by an editor is the same reason. Nothing else is folded: a reason
|
|
99
|
+
* that differs by one clause is a different reason, and a normaliser clever
|
|
100
|
+
* enough to call those equal would start refusing reasons that genuinely differ.
|
|
101
|
+
*/
|
|
102
|
+
export function reasonKey(reason: string): string {
|
|
103
|
+
return reason.toLowerCase().replace(/\s+/g, ' ').trim();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** One added file whose exemption reason does not hold up. */
|
|
107
|
+
export interface ExemptionFinding {
|
|
108
|
+
readonly file: string;
|
|
109
|
+
readonly kind: 'shapeless' | 'duplicate';
|
|
110
|
+
/** For `duplicate`, the other added file carrying the same reason. */
|
|
111
|
+
readonly twin: string | null;
|
|
112
|
+
readonly reason: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Judge the exemptions a single change ADDS — never the ones already in the tree.
|
|
117
|
+
*
|
|
118
|
+
* Grandfathering is load-bearing rather than polite. 43 of the 221 existing
|
|
119
|
+
* reasons name no disposition; re-reading them would land this check red on a
|
|
120
|
+
* fifth of the corpus, which is a backlog wearing a control's clothes. The
|
|
121
|
+
* input is `classifyDiff`'s exempt ledger, which by construction lists only the
|
|
122
|
+
* files this change added.
|
|
123
|
+
*/
|
|
124
|
+
export function exemptionFindings(
|
|
125
|
+
exempt: readonly { readonly file: string; readonly reason: string }[],
|
|
126
|
+
): ExemptionFinding[] {
|
|
127
|
+
const out: ExemptionFinding[] = [];
|
|
128
|
+
const seen = new Map<string, string>();
|
|
129
|
+
for (const e of exempt) {
|
|
130
|
+
if (!namesDisposition(e.reason)) {
|
|
131
|
+
out.push({ file: e.file, kind: 'shapeless', twin: null, reason: e.reason });
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const key = reasonKey(e.reason);
|
|
135
|
+
const twin = seen.get(key);
|
|
136
|
+
if (twin === undefined) {
|
|
137
|
+
seen.set(key, e.file);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
out.push({ file: e.file, kind: 'duplicate', twin, reason: e.reason });
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FNV-1a, 32-bit — the one definition in this tree.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `rule_trigger_eval.ts`, which still re-exports it under its
|
|
5
|
+
* own name so its suite keeps importing the symbol it always did. The reason
|
|
6
|
+
* for the extraction is the reason this file carries a doc block at all: a
|
|
7
|
+
* second consumer arrived (`measure_skill_ranker_baseline`'s held-out
|
|
8
|
+
* partition), and two copies of a hash that decides which rows a measurement
|
|
9
|
+
* reads is the shape in which a partition quietly stops being the same
|
|
10
|
+
* partition. One definition, two callers.
|
|
11
|
+
*
|
|
12
|
+
* Pure — no imports, no I/O, no clock.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** FNV-1a over UTF-16 code units, returned as an unsigned 32-bit integer. */
|
|
16
|
+
export function fnv1a(s: string): number {
|
|
17
|
+
let h = 0x811c9dc5;
|
|
18
|
+
for (let i = 0; i < s.length; i += 1) {
|
|
19
|
+
h ^= s.charCodeAt(i);
|
|
20
|
+
h = Math.imul(h, 0x01000193) >>> 0;
|
|
21
|
+
}
|
|
22
|
+
return h >>> 0;
|
|
23
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The unclaimed-file scan under this package's own deploy roots.
|
|
3
|
+
*
|
|
4
|
+
* Moved verbatim out of `src/scripts/_cli/cmd_doctor.ts`
|
|
5
|
+
* (road-to-a-tree-that-keeps-its-neighbours Phase 2.6). The behaviour is
|
|
6
|
+
* unchanged — this is a module boundary, not a rewrite.
|
|
7
|
+
*
|
|
8
|
+
* Two reasons it earns its own file. `cmd_doctor.ts` is 2,100 lines above the
|
|
9
|
+
* `check_source_size_budget` ceiling, so the census line that now links this
|
|
10
|
+
* listing is paid for by a MOVE rather than by raising the tree-wide excess.
|
|
11
|
+
* And the neighbour census answers a strictly larger question than this scan
|
|
12
|
+
* does — this one lists FILES under paths this package deploys to, the census
|
|
13
|
+
* lists ARTEFACTS wherever a host keeps them — so the two belong beside each
|
|
14
|
+
* other rather than nested inside a doctor renderer.
|
|
15
|
+
*
|
|
16
|
+
* What this scan does NOT do, stated because the two listings look alike: it
|
|
17
|
+
* never leaves a deploy root, so a neighbour's skill in `~/.claude/skills` or
|
|
18
|
+
* hook entry in `.cursor/hooks.json` is invisible to it. That gap is the
|
|
19
|
+
* census's subject, and the census links back here rather than repeating the
|
|
20
|
+
* paths this one already prints.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import * as fs from 'node:fs';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* `Path.rglob("*")` — every descendant path (dirs + files), recursively.
|
|
27
|
+
*
|
|
28
|
+
* Order is not relied upon by callers (they sort the matched set), but the
|
|
29
|
+
* walk is deterministic (sorted per directory) to keep behaviour stable.
|
|
30
|
+
*/
|
|
31
|
+
export function rglob(root: string, isDir: (p: string) => boolean, join: (a: string, b: string) => string): string[] {
|
|
32
|
+
const out: string[] = [];
|
|
33
|
+
const walk = (dir: string): void => {
|
|
34
|
+
let entries: fs.Dirent[];
|
|
35
|
+
try {
|
|
36
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
37
|
+
} catch {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
41
|
+
for (const ent of entries) {
|
|
42
|
+
const full = join(dir, ent.name);
|
|
43
|
+
out.push(full);
|
|
44
|
+
let dirLike = ent.isDirectory();
|
|
45
|
+
if (ent.isSymbolicLink()) {
|
|
46
|
+
// pathlib rglob follows into symlinked dirs.
|
|
47
|
+
dirLike = isDir(full);
|
|
48
|
+
}
|
|
49
|
+
if (dirLike) {
|
|
50
|
+
walk(full);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
walk(root);
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ForeignScanDeps {
|
|
59
|
+
pathExists: (p: string) => boolean;
|
|
60
|
+
isDir: (p: string) => boolean;
|
|
61
|
+
isFile: (p: string) => boolean;
|
|
62
|
+
resolvePath: (p: string) => string;
|
|
63
|
+
join: (a: string, b: string) => string;
|
|
64
|
+
/** Resolve one declared deploy root against the consumer root. */
|
|
65
|
+
resolveRoot: (projectRoot: string, rel: string) => string;
|
|
66
|
+
defaultRoots: readonly string[];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Walk every declared deploy root and surface unclaimed files. Only regular
|
|
71
|
+
* files under `deploy_roots` count; bookkeeping is on the resolved final path.
|
|
72
|
+
*/
|
|
73
|
+
export function scanForeign(
|
|
74
|
+
projectRoot: string,
|
|
75
|
+
manifest: Record<string, unknown>,
|
|
76
|
+
known: Set<string>,
|
|
77
|
+
deps: ForeignScanDeps,
|
|
78
|
+
): string[] {
|
|
79
|
+
const roots = (manifest['deploy_roots'] as unknown[] | undefined) || Array.from(deps.defaultRoots);
|
|
80
|
+
const foreign: string[] = [];
|
|
81
|
+
const seen = new Set<string>();
|
|
82
|
+
for (const root_rel of roots) {
|
|
83
|
+
const root = deps.resolveRoot(projectRoot, String(root_rel));
|
|
84
|
+
if (!deps.pathExists(root) || !deps.isDir(root)) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
for (const child of rglob(root, deps.isDir, deps.join)) {
|
|
88
|
+
if (!deps.isFile(child)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
let resolved: string;
|
|
92
|
+
try {
|
|
93
|
+
resolved = deps.resolvePath(child);
|
|
94
|
+
} catch {
|
|
95
|
+
resolved = child;
|
|
96
|
+
}
|
|
97
|
+
if (known.has(resolved) || seen.has(resolved)) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
seen.add(resolved);
|
|
101
|
+
foreign.push(child);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
foreign.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
105
|
+
return foreign;
|
|
106
|
+
}
|