@event4u/agent-config 16.1.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 +98 -257
- 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 +5 -5
- 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/commands/work.md +4 -0
- 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/autonomy-mechanics.md +4 -8
- 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/ui-audit-gate.md +1 -2
- 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/accessibility-auditor/data/aria-patterns.csv +1 -1
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
- 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-review/SKILL.md +30 -4
- package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
- package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +18 -3
- package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
- 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/markitdown/SKILL.md +4 -0
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
- package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
- 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 +25 -2
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +30 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
- package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/dist/cli/python/knowledge_ingest.js +1 -1
- package/dist/cli/python/workspace_drive.js +1 -1
- 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-UNCRJBT3.js → chunk-JQVYKIZA.js} +41 -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-YUM5KURG.js → chunk-KR6EM23A.js} +235 -1455
- package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-LIYTLH6O.js} +1 -1
- package/dist/cli-delegate/chunk-NQ7WT43P.js +2729 -0
- 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-HDVVGPQW.js → chunk-RQNNW7IF.js} +1405 -567
- package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-S4UMZNES.js} +1 -1
- package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-S4YWNRKO.js} +24 -8
- package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-TRI6DQZ6.js} +3054 -325
- 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 +1862 -809
- 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 +132 -80
- 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 +7972 -5186
- package/dist/hooks/dispatch.sha256 +1 -0
- 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 +2226 -1055
- 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 +40913 -34865
- 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 +29 -0
- package/dist/shared/skillRanking.js +74 -13
- package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
- package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
- package/dist/ui/index.html +2 -2
- package/docs/contracts/settings-classes.md +10 -6
- package/package.json +4 -4
- 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/app_spec.ts +10 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
- package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/src/config/agent-settings.template.yml +91 -22
- package/src/config/ci-local-parity.yml +18 -0
- package/src/config/continuity-surface.json +9 -2
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +327 -3
- package/src/config/gate-violation-baselines.json +21 -9
- package/src/config/hook-bundle-budget.json +25 -0
- package/src/config/hook-latency-budget.json +21 -0
- package/src/config/hook-token-budget.json +8 -3
- package/src/config/host-instruction-limits.json +33 -0
- package/src/config/metric-registry.yml +114 -0
- package/src/config/placeholder-drift-budget.json +10 -0
- package/src/config/routing-coverage-seed.json +38 -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 +28 -90
- package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
- package/src/scripts/_cli/doctor_execution.ts +100 -17
- package/src/scripts/_cli/doctor_network_posture.ts +226 -0
- package/src/scripts/_dispatch.bash +16 -2
- package/src/scripts/_lib/agent_settings.ts +11 -0
- package/src/scripts/_lib/body_portable.ts +102 -0
- package/src/scripts/_lib/cc_transcript.ts +132 -1
- package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
- package/src/scripts/_lib/concern_sla_window.ts +200 -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/forge_protection.ts +114 -0
- package/src/scripts/_lib/forge_reader.ts +558 -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_env_write.ts +111 -0
- 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/llm_proposer_transport.ts +14 -4
- 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/obligations.ts +119 -8
- package/src/scripts/_lib/probe_inputs.ts +271 -0
- package/src/scripts/_lib/prompt_shape.ts +46 -0
- package/src/scripts/_lib/read_surface_scan.ts +322 -0
- package/src/scripts/_lib/release_highlights.ts +2 -1
- package/src/scripts/_lib/release_migration_gate.ts +19 -3
- package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
- package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
- 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/shell_write_shapes.ts +108 -0
- package/src/scripts/_lib/skill_origin.ts +254 -0
- package/src/scripts/_lib/structural_hiding.ts +622 -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 +600 -4
- package/src/scripts/_lib/turn_end_transcript.ts +216 -0
- package/src/scripts/_lib/verification_command.ts +324 -0
- package/src/scripts/_lib/verification_evidence.ts +647 -0
- package/src/scripts/_lib/verify_clause.ts +280 -0
- 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/ai_team/team_dispatch.ts +4 -0
- package/src/scripts/audit_skill_overlap.ts +261 -0
- package/src/scripts/before_complete_hook.ts +573 -25
- package/src/scripts/bench_hook_latency.ts +412 -5
- package/src/scripts/bench_touched_file_quality.ts +238 -0
- package/src/scripts/cache_realization_report.ts +61 -1
- package/src/scripts/check_argument_hint.ts +287 -0
- package/src/scripts/check_claims.ts +125 -1
- package/src/scripts/check_enforcement_coverage.ts +38 -9
- package/src/scripts/check_enforcement_matrix.ts +621 -22
- package/src/scripts/check_estate_count.ts +130 -153
- package/src/scripts/check_finding_dispositions.ts +138 -7
- package/src/scripts/check_generator_sync.ts +71 -0
- package/src/scripts/check_hook_bundle_composition.ts +370 -0
- package/src/scripts/check_host_docs_digest.ts +786 -0
- package/src/scripts/check_host_format_column.ts +559 -0
- package/src/scripts/check_input_references.ts +228 -0
- package/src/scripts/check_inputs_parity.ts +235 -0
- package/src/scripts/check_kernel_edit_ratified.ts +89 -9
- package/src/scripts/check_kill_switch_table.ts +294 -0
- package/src/scripts/check_memory.ts +7 -1
- package/src/scripts/check_placeholder_drift.ts +221 -0
- package/src/scripts/check_read_surface_coverage.ts +515 -0
- package/src/scripts/check_release_highlights.ts +10 -0
- package/src/scripts/check_release_holds.ts +2 -1
- package/src/scripts/check_release_pr_shape.ts +38 -15
- package/src/scripts/check_routing_coverage.ts +378 -11
- package/src/scripts/check_standing_rule_delivery.ts +33 -13
- package/src/scripts/check_static_parity.ts +17 -0
- package/src/scripts/check_verify_expectation_delta.ts +521 -0
- package/src/scripts/ci_settle.ts +145 -19
- package/src/scripts/closure_scan.ts +115 -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/compile_hook_manifest.ts +90 -14
- package/src/scripts/condense.ts +14 -27
- package/src/scripts/estate_exemption_shape.ts +202 -0
- package/src/scripts/external_sources_denylist.json +29 -4
- package/src/scripts/generate_host_cost_table.ts +1 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +192 -14
- package/src/scripts/hooks/block_config_weakening.ts +294 -4
- package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
- package/src/scripts/hooks/block_plumbing_writes.ts +342 -0
- package/src/scripts/hooks/bundle_integrity.ts +291 -0
- package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
- package/src/scripts/hooks/code_graph_context_hook.ts +253 -25
- package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
- package/src/scripts/hooks/concern_failure_policy.ts +280 -0
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/concern_timings.ts +94 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
- package/src/scripts/hooks/design_pass_hook.ts +100 -9
- package/src/scripts/hooks/design_slop_hook.ts +1 -2
- package/src/scripts/hooks/dispatch_hook.ts +77 -71
- package/src/scripts/hooks/edit_shape_hook.ts +1 -2
- package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
- package/src/scripts/hooks/evidence_independence.ts +1 -2
- package/src/scripts/hooks/exit_codes.ts +129 -0
- package/src/scripts/hooks/host_lowering.json +1 -0
- package/src/scripts/hooks/host_lowering.ts +146 -3
- package/src/scripts/hooks/host_lowering.yaml +262 -46
- package/src/scripts/hooks/host_semantics.ts +30 -2
- package/src/scripts/hooks/injection_budget.ts +9 -2
- package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
- package/src/scripts/hooks/journal_record_hook.ts +1 -2
- package/src/scripts/hooks/memory_recall_hook.ts +1 -2
- package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
- package/src/scripts/hooks/one_question_per_ask_hook.ts +43 -6
- package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
- package/src/scripts/hooks/payload_stub.ts +10 -1
- package/src/scripts/hooks/permission_gate.ts +1 -2
- package/src/scripts/hooks/push_settle_hook.ts +1 -3
- package/src/scripts/hooks/replay_hook.ts +2 -1
- package/src/scripts/hooks/reread_guard_hook.ts +1 -2
- package/src/scripts/hooks/review_baseline_hook.ts +1 -2
- package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
- package/src/scripts/hooks/rule_inject_hook.ts +765 -68
- package/src/scripts/hooks/run_continuation_hook.ts +13 -15
- package/src/scripts/hooks/skill_route_hook.ts +1 -2
- package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
- package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
- package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
- package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
- package/src/scripts/hooks/table_fingerprint.ts +70 -0
- package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
- package/src/scripts/hooks/telemetry_usage_hook.ts +65 -10
- package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
- package/src/scripts/hooks/turn_end_gate_hook.ts +421 -324
- package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
- package/src/scripts/hooks_status.ts +112 -0
- package/src/scripts/injection_scan_hook.ts +7 -0
- package/src/scripts/install.ts +75 -189
- 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_deny_text.ts +419 -0
- package/src/scripts/lint_exit_codes.ts +262 -0
- package/src/scripts/lint_hook_manifest.ts +108 -0
- package/src/scripts/lint_major_migration_sections.ts +156 -26
- package/src/scripts/lint_pack_boundaries.ts +63 -5
- package/src/scripts/lint_roadmap_blockers.ts +78 -12
- package/src/scripts/lint_rule_law_section.ts +517 -0
- package/src/scripts/lint_skill_descriptions.ts +1 -1
- package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
- 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/mcp_server/prompts.ts +142 -1
- package/src/scripts/measure_routing_signal.ts +54 -16
- package/src/scripts/measure_skill_ranker_baseline.ts +515 -53
- package/src/scripts/measure_turn_end_gate.ts +193 -0
- package/src/scripts/memory_learn_hook.ts +91 -6
- package/src/scripts/memory_lookup.ts +1 -1
- package/src/scripts/model_rule_injection.ts +52 -5
- package/src/scripts/prepack-check.mjs +53 -0
- package/src/scripts/project_thin_rules.ts +34 -327
- package/src/scripts/release_publication.ts +13 -1
- package/src/scripts/report_evidence_temperature.ts +657 -0
- package/src/scripts/report_installed_rule_links.ts +157 -0
- package/src/scripts/report_invocation_surface.ts +383 -0
- package/src/scripts/report_label_agreement.ts +233 -0
- package/src/scripts/report_obligation_carriers.ts +699 -0
- package/src/scripts/report_obligation_mechanism.ts +230 -0
- package/src/scripts/report_obligation_writer_split.ts +236 -0
- package/src/scripts/report_single_token_triggers.ts +277 -0
- package/src/scripts/report_skill_menu_census.ts +208 -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/roadmap_verify_share.ts +213 -0
- package/src/scripts/rule_activation_census.ts +5 -2
- package/src/scripts/rule_trigger_eval.ts +9 -9
- package/src/scripts/schemas/command.schema.json +44 -0
- package/src/scripts/schemas/review-findings.schema.json +7 -2
- package/src/scripts/schemas/rule.schema.json +5 -0
- package/src/scripts/schemas/skill.schema.json +44 -0
- package/src/scripts/self_review_gate.ts +421 -17
- package/src/scripts/semantic_parity_census.ts +314 -0
- package/src/scripts/session_eol_report.ts +54 -1
- package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
- package/src/scripts/sweep_dead_scan_roots.ts +1 -1
- 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 -7
- package/src/scripts/trigger_eval_rotation.ts +122 -27
- package/src/scripts/ui_conformance_probe.ts +156 -4
- package/src/scripts/update_prices.ts +85 -3
- package/src/scripts/write_bundle_digest.mjs +74 -0
- package/src/server/schemas/settings.ts +41 -0
- package/src/shared/skillRanking.ts +117 -11
- package/src/templates/consumer-settings/ONBOARDING.md +1 -0
- package/dist/cli-delegate/chunk-FXYUYU4N.js +0 -4416
- package/dist/cli-delegate/chunk-QJWAUMJW.js +0 -1072
- package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
- package/dist/ui/assets/index-DH1xc62v.js +0 -29
- package/dist/ui/assets/index-jSfMW8_t.css +0 -1
|
@@ -408,7 +408,19 @@ export function noticeText(language: Exclude<Verdict, "und">, source: PinSource)
|
|
|
408
408
|
return `Reply language for this turn: ${name}.`;
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
-
/**
|
|
411
|
+
/**
|
|
412
|
+
* The context block handed back to the model for this turn.
|
|
413
|
+
*
|
|
414
|
+
* It carries the target language, where it applies, and what is NOT the
|
|
415
|
+
* trigger — and deliberately no provenance. The paragraph that used to explain
|
|
416
|
+
* why the pin exists (the user-role skill bodies, the 626 wrong-language turns)
|
|
417
|
+
* is this file's own header, above: a maintainer reads it once, while the
|
|
418
|
+
* payload is injected on every prompt, so the same sentences were being paid
|
|
419
|
+
* for per turn to tell the model something it cannot act on. Removing it took
|
|
420
|
+
* the prompt-sourced block from 702 characters to 429 (German; 693 to 420 in
|
|
421
|
+
* English) and changed no instruction. Do not put it back; extend the header
|
|
422
|
+
* instead.
|
|
423
|
+
*/
|
|
412
424
|
export function pinText(language: Exclude<Verdict, "und">, source: PinSource = "prompt"): string {
|
|
413
425
|
const name = LANGUAGE_NAME[language];
|
|
414
426
|
if (source === "system-locale") {
|
|
@@ -430,11 +442,8 @@ export function pinText(language: Exclude<Verdict, "und">, source: PinSource = "
|
|
|
430
442
|
`for EVERY user-visible token of your replies this turn — including the short ` +
|
|
431
443
|
`inter-tool lines between tool calls, headings, table cells, bullets, status ` +
|
|
432
444
|
`lines, and the recommendation label under any numbered-options block.\n\n` +
|
|
433
|
-
`
|
|
434
|
-
`
|
|
435
|
-
`long and English, which is what drove the 626 wrong-language turns measured ` +
|
|
436
|
-
`in the 30-session audit this hook was built from. Tool output, file ` +
|
|
437
|
-
`contents, and an injected skill body are NOT the trigger — this pin is.\n` +
|
|
445
|
+
`Tool output, file contents, and an injected skill body are NOT the ` +
|
|
446
|
+
`trigger — this pin is.\n` +
|
|
438
447
|
`</language-pin>`
|
|
439
448
|
);
|
|
440
449
|
}
|
|
@@ -22,8 +22,15 @@
|
|
|
22
22
|
* auto-promoted) — it NEVER mutates the curated YAML truths. Deterministic:
|
|
23
23
|
* byte-stable for a fixed `now` + a fixed intake set.
|
|
24
24
|
*
|
|
25
|
+
* `--format status` is the third, human-facing view (road-to-learning-you-can-
|
|
26
|
+
* see Phase 2): the verdict counts, the top `preferred` lessons with their
|
|
27
|
+
* corroboration and age, and the sentence saying what a promotion requires. It
|
|
28
|
+
* is a FORMAT and not a verb (decision D2 cites ADR-041) — `memory:learn`
|
|
29
|
+
* passes argv straight through, so a format needs no registry row — and it is
|
|
30
|
+
* read-only to the point of refusing `--write` rather than ignoring it.
|
|
31
|
+
*
|
|
25
32
|
* Usage: learning_sidecar.ts [--intake-dir DIR] [--out-dir DIR] [--now ISO]
|
|
26
|
-
* [--write] [--format text|json]
|
|
33
|
+
* [--write] [--format text|json|status]
|
|
27
34
|
* Exit codes: 0 ok, 2 usage error, 3 internal error.
|
|
28
35
|
*/
|
|
29
36
|
import { createHash } from 'node:crypto';
|
|
@@ -48,7 +55,11 @@ export const SCHEMA_VERSION = 1;
|
|
|
48
55
|
export type Polarity = 'preferred' | 'dead_end';
|
|
49
56
|
export type Verdict = 'preferred' | 'contested' | 'dead_end';
|
|
50
57
|
|
|
51
|
-
|
|
58
|
+
/** The output formats. `status` is the one-screen human view (read-only). */
|
|
59
|
+
export const FORMATS = ['text', 'json', 'status'] as const;
|
|
60
|
+
export type Format = (typeof FORMATS)[number];
|
|
61
|
+
|
|
62
|
+
export interface Signal {
|
|
52
63
|
id: string;
|
|
53
64
|
ts: string;
|
|
54
65
|
tsMs: number;
|
|
@@ -226,9 +237,21 @@ export function aggregate(signals: readonly Signal[], nowMs: number): Lesson[] {
|
|
|
226
237
|
}
|
|
227
238
|
|
|
228
239
|
export function buildSidecar(intakeDir: string, nowIso: string): Sidecar {
|
|
240
|
+
return buildSidecarFromSignals(readSignals(intakeDir), nowIso);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* The half of `buildSidecar` that does not read the disk.
|
|
245
|
+
*
|
|
246
|
+
* Split out for a caller that needs BOTH the input count and the output
|
|
247
|
+
* lessons from one pass — the session-end dogfood ledger records `signals_in`
|
|
248
|
+
* beside `lessons_out`, and reading the intake twice to get them would make the
|
|
249
|
+
* two numbers describe two different reads under the 2 s budget they exist to
|
|
250
|
+
* measure.
|
|
251
|
+
*/
|
|
252
|
+
export function buildSidecarFromSignals(signals: readonly Signal[], nowIso: string): Sidecar {
|
|
229
253
|
const nowMs = _parseTsMs(nowIso) ?? 0;
|
|
230
|
-
|
|
231
|
-
return { schema_version: SCHEMA_VERSION, generated_at: nowIso, lessons };
|
|
254
|
+
return { schema_version: SCHEMA_VERSION, generated_at: nowIso, lessons: aggregate(signals, nowMs) };
|
|
232
255
|
}
|
|
233
256
|
|
|
234
257
|
/** Render the human dead-ends + contested ledger. */
|
|
@@ -261,22 +284,98 @@ export function renderLessonsMd(sidecar: Sidecar): string {
|
|
|
261
284
|
return L.join('\n');
|
|
262
285
|
}
|
|
263
286
|
|
|
287
|
+
/** How many `preferred` lessons the status screen lists before it stops. */
|
|
288
|
+
export const STATUS_TOP_N = 5;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* The human step. Printed as the LAST line of the status screen, because this
|
|
292
|
+
* tree writes no rule and no skill on its own: a lesson becomes a durable
|
|
293
|
+
* artefact only when a person carries it through `/memory:propose` or the
|
|
294
|
+
* `learning-to-rule-or-skill` skill. A list of preferred lessons is one flag
|
|
295
|
+
* away from `--apply`, and naming the hand-off on every screen is the cheapest
|
|
296
|
+
* standing answer to "why is there no button".
|
|
297
|
+
*/
|
|
298
|
+
export const PROMOTION_HANDOFF =
|
|
299
|
+
'Promote by hand: /memory:propose · src/skills/learning-to-rule-or-skill/SKILL.md';
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* One screen answering "what has this learned?" — verdict counts, the top
|
|
303
|
+
* `preferred` lessons with their corroboration and age, and what a promotion
|
|
304
|
+
* requires. READ ONLY by construction: it takes a built sidecar and returns a
|
|
305
|
+
* string, so there is no path from this function to the disk.
|
|
306
|
+
*/
|
|
307
|
+
export function renderStatus(sidecar: Sidecar): string {
|
|
308
|
+
const byVerdict: Record<Verdict, number> = { preferred: 0, contested: 0, dead_end: 0 };
|
|
309
|
+
for (const l of sidecar.lessons) byVerdict[l.verdict] += 1;
|
|
310
|
+
|
|
311
|
+
const nowMs = _parseTsMs(sidecar.generated_at) ?? 0;
|
|
312
|
+
const L: string[] = [];
|
|
313
|
+
L.push(`${PROG}: ${String(sidecar.lessons.length)} lesson(s) as of ${sidecar.generated_at}`);
|
|
314
|
+
L.push(
|
|
315
|
+
`preferred: ${String(byVerdict.preferred)} · ` +
|
|
316
|
+
`contested: ${String(byVerdict.contested)} · ` +
|
|
317
|
+
`dead-end: ${String(byVerdict.dead_end)}`,
|
|
318
|
+
);
|
|
319
|
+
L.push('');
|
|
320
|
+
|
|
321
|
+
const preferred = sidecar.lessons
|
|
322
|
+
.filter((l) => l.verdict === 'preferred')
|
|
323
|
+
.sort((a, b) => b.score - a.score || (a.path < b.path ? -1 : 1))
|
|
324
|
+
.slice(0, STATUS_TOP_N);
|
|
325
|
+
L.push(`Top ${String(STATUS_TOP_N)} preferred:`);
|
|
326
|
+
if (preferred.length === 0) {
|
|
327
|
+
L.push(' (none — a lesson needs corroboration before it appears here)');
|
|
328
|
+
} else {
|
|
329
|
+
preferred.forEach((l, i) => {
|
|
330
|
+
// Age is floored, so a lesson seen hours ago reads `0d old` rather
|
|
331
|
+
// than rounding up into a day it has not lived.
|
|
332
|
+
const ageDays = Math.max(0, Math.floor((nowMs - (_parseTsMs(l.last_ts) ?? nowMs)) / 86400000));
|
|
333
|
+
L.push(
|
|
334
|
+
` ${String(i + 1)}. ${l.entry_type} @ ${l.path} — ` +
|
|
335
|
+
`${String(l.corroborations)} origins, ${String(ageDays)}d old`,
|
|
336
|
+
);
|
|
337
|
+
L.push(` ${l.body}`);
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
L.push('');
|
|
341
|
+
L.push(
|
|
342
|
+
`A lesson is promoted at ${String(MIN_CORROBORATIONS)} distinct origins, ` +
|
|
343
|
+
`weighted by a ${String(HALF_LIFE_DAYS)}-day half-life; nothing here promotes itself.`,
|
|
344
|
+
);
|
|
345
|
+
L.push(PROMOTION_HANDOFF);
|
|
346
|
+
return `${L.join('\n')}\n`;
|
|
347
|
+
}
|
|
348
|
+
|
|
264
349
|
export function main(argv: string[]): number {
|
|
265
350
|
let intakeDir = path.join('agents', 'memory', 'intake');
|
|
266
351
|
let outDir = path.join('agents', 'memory');
|
|
267
352
|
let nowIso = '';
|
|
268
353
|
let write = false;
|
|
269
|
-
let format:
|
|
354
|
+
let format: Format = 'text';
|
|
270
355
|
for (let i = 0; i < argv.length; i++) {
|
|
271
356
|
const a = argv[i] as string;
|
|
272
357
|
if (a === '--intake-dir') intakeDir = argv[++i] ?? intakeDir;
|
|
273
358
|
else if (a === '--out-dir') outDir = argv[++i] ?? outDir;
|
|
274
359
|
else if (a === '--now') nowIso = argv[++i] ?? '';
|
|
275
360
|
else if (a === '--write') write = true;
|
|
276
|
-
else if (a === '--format')
|
|
277
|
-
|
|
361
|
+
else if (a === '--format') {
|
|
362
|
+
const raw = argv[++i] ?? '';
|
|
363
|
+
// Validated rather than cast. With two formats an unknown value
|
|
364
|
+
// fell through to `text` and looked like it had worked; with three,
|
|
365
|
+
// a typo in `status` would silently print the one-line text form
|
|
366
|
+
// and a reader would take that for the screen they asked for.
|
|
367
|
+
if (!(FORMATS as readonly string[]).includes(raw)) {
|
|
368
|
+
process.stderr.write(
|
|
369
|
+
`${PROG}: error: argument --format: invalid choice: '${raw}' ` +
|
|
370
|
+
`(choose from ${FORMATS.map((f) => `'${f}'`).join(', ')})\n`,
|
|
371
|
+
);
|
|
372
|
+
return 2;
|
|
373
|
+
}
|
|
374
|
+
format = raw as Format;
|
|
375
|
+
} else if (a === '-h' || a === '--help') {
|
|
278
376
|
process.stdout.write(
|
|
279
|
-
`usage: ${PROG} [--intake-dir DIR] [--out-dir DIR] [--now ISO] [--write]
|
|
377
|
+
`usage: ${PROG} [--intake-dir DIR] [--out-dir DIR] [--now ISO] [--write] ` +
|
|
378
|
+
`[--format ${FORMATS.join('|')}]\n`,
|
|
280
379
|
);
|
|
281
380
|
return 0;
|
|
282
381
|
} else {
|
|
@@ -284,6 +383,14 @@ export function main(argv: string[]): number {
|
|
|
284
383
|
return 2;
|
|
285
384
|
}
|
|
286
385
|
}
|
|
386
|
+
if (format === 'status' && write) {
|
|
387
|
+
// Refused, not ignored. The acceptance criterion is that the screen
|
|
388
|
+
// leaves `git status` identical; ignoring the flag would honour that
|
|
389
|
+
// too, but it would do so silently, and a caller who asked for a write
|
|
390
|
+
// deserves to be told it did not happen.
|
|
391
|
+
process.stderr.write(`${PROG}: error: --format status is read-only; drop --write\n`);
|
|
392
|
+
return 2;
|
|
393
|
+
}
|
|
287
394
|
if (!nowIso) {
|
|
288
395
|
// Deterministic by contract: refuse to guess `now` from the clock when
|
|
289
396
|
// writing, so output is reproducible. Callers pass --now explicitly.
|
|
@@ -310,6 +417,10 @@ export function main(argv: string[]): number {
|
|
|
310
417
|
process.stdout.write(JSON.stringify(sidecar, null, 2) + '\n');
|
|
311
418
|
return 0;
|
|
312
419
|
}
|
|
420
|
+
if (format === 'status') {
|
|
421
|
+
process.stdout.write(renderStatus(sidecar));
|
|
422
|
+
return 0;
|
|
423
|
+
}
|
|
313
424
|
const byVerdict = { preferred: 0, contested: 0, dead_end: 0 };
|
|
314
425
|
for (const l of sidecar.lessons) byVerdict[l.verdict] += 1;
|
|
315
426
|
process.stdout.write(
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* A deny never names its own kill switch.
|
|
4
|
+
*
|
|
5
|
+
* road-to-a-kernel-that-guards-its-plumbing 1.3.
|
|
6
|
+
*
|
|
7
|
+
* A blocking concern refuses a tool call and then prints a message to the one
|
|
8
|
+
* party that wanted the call to succeed. If that message names the environment
|
|
9
|
+
* variable, the `export` line or the settings key that turns the guard off, the
|
|
10
|
+
* refusal is a speed bump with instructions attached. The reader most motivated
|
|
11
|
+
* to follow them is the agent that was just refused.
|
|
12
|
+
*
|
|
13
|
+
* The rule is therefore about the OUTPUT, not about the mechanism: a kill
|
|
14
|
+
* switch may exist, may be documented in the concern's header, may be listed in
|
|
15
|
+
* the manifest and in the contract. It may not appear in the text the refusal
|
|
16
|
+
* prints. Two of the nine blocking concerns carry a kill switch today and both
|
|
17
|
+
* name it only in a comment — this gate is what keeps that true, rather than
|
|
18
|
+
* true-so-far.
|
|
19
|
+
*
|
|
20
|
+
* WHAT IS SCANNED, stated precisely because the boundary is the whole design.
|
|
21
|
+
*
|
|
22
|
+
* The corpus is the `severity: blocking` concerns in `hook_manifest.yaml`, read
|
|
23
|
+
* from the manifest rather than listed here, so a guard added there joins the
|
|
24
|
+
* corpus without an edit. Within each, the unit is a MESSAGE-SHAPED string
|
|
25
|
+
* literal — comments stripped first, then any literal containing whitespace.
|
|
26
|
+
*
|
|
27
|
+
* The prose test is the discriminator, and it is what makes the settings-key
|
|
28
|
+
* rule usable at all: `'hooks.enabled'` as a lookup key is how a concern reads
|
|
29
|
+
* its own configuration and is not a message, while
|
|
30
|
+
* `'set hooks.enabled: false to skip'` is. A rule that fired on the bare key
|
|
31
|
+
* would fire on every concern that reads a setting, which is the false-positive
|
|
32
|
+
* shape that gets a young gate disabled.
|
|
33
|
+
*
|
|
34
|
+
* WHAT IT DOES NOT SEE, so nobody reads more into a green than is there:
|
|
35
|
+
* a message assembled at runtime from non-literal parts, a message in a module
|
|
36
|
+
* the concern imports, and a kill switch named in a way none of the three
|
|
37
|
+
* patterns matches. This is a pattern gate over source text, not a proof about
|
|
38
|
+
* emitted bytes.
|
|
39
|
+
*
|
|
40
|
+
* Exit codes: 0 clean · 1 a deny names a bypass · 2 the corpus could not be read.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import fs from 'node:fs';
|
|
44
|
+
import os from 'node:os';
|
|
45
|
+
import path from 'node:path';
|
|
46
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
47
|
+
|
|
48
|
+
import * as yaml from 'js-yaml';
|
|
49
|
+
|
|
50
|
+
import { GateLedger } from './_lib/gate_ledger.js';
|
|
51
|
+
import { runGateCli, runSelfTest } from './_lib/gate_self_test.js';
|
|
52
|
+
import { assertScanned, reportScanned } from './_lib/scan_scope.js';
|
|
53
|
+
|
|
54
|
+
const _HERE = fileURLToPath(import.meta.url);
|
|
55
|
+
const ROOT = path.resolve(path.dirname(_HERE), '..', '..');
|
|
56
|
+
|
|
57
|
+
export const GATE = 'lint_deny_text';
|
|
58
|
+
|
|
59
|
+
const MANIFEST_REL = 'src/scripts/hook_manifest.yaml';
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Top-level settings keys, so `hooks.enabled` reads as a settings path and
|
|
63
|
+
* `payload.tool_input` does not.
|
|
64
|
+
*
|
|
65
|
+
* A fixed list rather than a parse of the template: the template is a large
|
|
66
|
+
* file with placeholder substitutions, and the gate needs the discriminator to
|
|
67
|
+
* be stable and reviewable in a diff. Adding a top-level settings section is
|
|
68
|
+
* the moment to add it here; a missing entry costs a miss, never a false
|
|
69
|
+
* refusal.
|
|
70
|
+
*/
|
|
71
|
+
export const SETTINGS_ROOTS: readonly string[] = [
|
|
72
|
+
'ai_team',
|
|
73
|
+
'augment',
|
|
74
|
+
'chat_history',
|
|
75
|
+
'code_style',
|
|
76
|
+
'commands',
|
|
77
|
+
'consistency',
|
|
78
|
+
'continuity',
|
|
79
|
+
'cost',
|
|
80
|
+
'decision_engine',
|
|
81
|
+
'delivery',
|
|
82
|
+
'design',
|
|
83
|
+
'emergency',
|
|
84
|
+
'execution',
|
|
85
|
+
'github',
|
|
86
|
+
'hooks',
|
|
87
|
+
'knowledge',
|
|
88
|
+
'memory',
|
|
89
|
+
'model',
|
|
90
|
+
'onboarding',
|
|
91
|
+
'personal',
|
|
92
|
+
'pipelines',
|
|
93
|
+
'planning',
|
|
94
|
+
'profile',
|
|
95
|
+
'project',
|
|
96
|
+
'projection',
|
|
97
|
+
'quality',
|
|
98
|
+
'reasoning',
|
|
99
|
+
'roadmap',
|
|
100
|
+
'screenshots',
|
|
101
|
+
'subagents',
|
|
102
|
+
'telegraph',
|
|
103
|
+
'tokens',
|
|
104
|
+
'update_check',
|
|
105
|
+
'verbosity',
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
export interface DenyFinding {
|
|
109
|
+
readonly concern: string;
|
|
110
|
+
readonly file: string;
|
|
111
|
+
readonly kind: 'env-assignment' | 'export-line' | 'settings-key';
|
|
112
|
+
readonly text: string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Characters after which a `/` begins a REGEX literal rather than a division.
|
|
117
|
+
*
|
|
118
|
+
* Regex handling is not a nicety here, it is the difference between a working
|
|
119
|
+
* gate and a nonsense one — measured on the first run against the real tree.
|
|
120
|
+
* `turn_end_gate_hook.ts` carries `/\bI (wi)ll\b/i`; without this the
|
|
121
|
+
* apostrophe inside a pattern like `/\bI'll\b/` opens a phantom string literal
|
|
122
|
+
* that swallows the next few hundred characters of real code and comments, and
|
|
123
|
+
* the gate then reports `export function …` as deny text. Three such
|
|
124
|
+
* false positives, all in one file, all from one unhandled token class.
|
|
125
|
+
*/
|
|
126
|
+
const REGEX_PRECEDERS = new Set('(,=:[!&|?{};+-*%~^<>'.split(''));
|
|
127
|
+
|
|
128
|
+
/** Strip `//` and block comments, preserving string literals and regexes. */
|
|
129
|
+
export function stripComments(src: string): string {
|
|
130
|
+
let out = '';
|
|
131
|
+
let i = 0;
|
|
132
|
+
let quote: string | null = null;
|
|
133
|
+
let lastSignificant = '';
|
|
134
|
+
while (i < src.length) {
|
|
135
|
+
const c = src[i] as string;
|
|
136
|
+
const next = src[i + 1];
|
|
137
|
+
if (quote !== null) {
|
|
138
|
+
out += c;
|
|
139
|
+
if (c === '\\') {
|
|
140
|
+
out += src[i + 1] ?? '';
|
|
141
|
+
i += 2;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (c === quote) {
|
|
145
|
+
quote = null;
|
|
146
|
+
}
|
|
147
|
+
i += 1;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (c === "'" || c === '"' || c === '`') {
|
|
151
|
+
quote = c;
|
|
152
|
+
out += c;
|
|
153
|
+
i += 1;
|
|
154
|
+
lastSignificant = c;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (c === '/' && next === '/') {
|
|
158
|
+
while (i < src.length && src[i] !== '\n') i += 1;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (c === '/' && next === '*') {
|
|
162
|
+
i += 2;
|
|
163
|
+
while (i < src.length && !(src[i] === '*' && src[i + 1] === '/')) i += 1;
|
|
164
|
+
i += 2;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (c === '/' && (lastSignificant === '' || REGEX_PRECEDERS.has(lastSignificant))) {
|
|
168
|
+
// Regex literal — consumed whole, emitted as a placeholder so the
|
|
169
|
+
// scanner below never sees its contents as a message.
|
|
170
|
+
i += 1;
|
|
171
|
+
let inClass = false;
|
|
172
|
+
while (i < src.length) {
|
|
173
|
+
const r = src[i] as string;
|
|
174
|
+
if (r === '\\') {
|
|
175
|
+
i += 2;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (r === '[') inClass = true;
|
|
179
|
+
else if (r === ']') inClass = false;
|
|
180
|
+
else if (r === '/' && !inClass) break;
|
|
181
|
+
else if (r === '\n') break;
|
|
182
|
+
i += 1;
|
|
183
|
+
}
|
|
184
|
+
i += 1;
|
|
185
|
+
out += '0';
|
|
186
|
+
lastSignificant = '0';
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
out += c;
|
|
190
|
+
i += 1;
|
|
191
|
+
if (!/\s/.test(c)) {
|
|
192
|
+
lastSignificant = c;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Message-shaped string literals — comments already stripped. */
|
|
199
|
+
export function messageLiterals(src: string): string[] {
|
|
200
|
+
const out: string[] = [];
|
|
201
|
+
const re = /'((?:[^'\\]|\\.)*)'|"((?:[^"\\]|\\.)*)"|`((?:[^`\\]|\\.)*)`/g;
|
|
202
|
+
for (const m of src.matchAll(re)) {
|
|
203
|
+
const body = m[1] ?? m[2] ?? m[3] ?? '';
|
|
204
|
+
if (/\s/.test(body)) {
|
|
205
|
+
out.push(body);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return out;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const ENV_ASSIGNMENT_RE = /AGENT_CONFIG_[A-Z0-9_]+\s*=/;
|
|
212
|
+
const EXPORT_RE = /(^|[\s`'"(])export\s/;
|
|
213
|
+
|
|
214
|
+
function settingsKeyIn(text: string): boolean {
|
|
215
|
+
for (const root of SETTINGS_ROOTS) {
|
|
216
|
+
const re = new RegExp(`(^|[^A-Za-z0-9_.])${root}\\.[a-z][a-z0-9_]*`);
|
|
217
|
+
if (re.test(text)) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Findings for one concern's source text. */
|
|
225
|
+
export function scanSource(concern: string, file: string, src: string): DenyFinding[] {
|
|
226
|
+
const out: DenyFinding[] = [];
|
|
227
|
+
for (const text of messageLiterals(stripComments(src))) {
|
|
228
|
+
if (ENV_ASSIGNMENT_RE.test(text)) {
|
|
229
|
+
out.push({ concern, file, kind: 'env-assignment', text });
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (EXPORT_RE.test(text)) {
|
|
233
|
+
out.push({ concern, file, kind: 'export-line', text });
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (settingsKeyIn(text)) {
|
|
237
|
+
out.push({ concern, file, kind: 'settings-key', text });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** `severity: blocking` concerns, as `[name, script]`, read from the manifest. */
|
|
244
|
+
export function blockingConcerns(manifestText: string): Array<[string, string]> {
|
|
245
|
+
const doc = yaml.load(manifestText) as { concerns?: Record<string, Record<string, unknown>> };
|
|
246
|
+
const concerns = doc?.concerns ?? {};
|
|
247
|
+
const out: Array<[string, string]> = [];
|
|
248
|
+
for (const [name, body] of Object.entries(concerns)) {
|
|
249
|
+
if (String(body['severity'] ?? '') !== 'blocking') continue;
|
|
250
|
+
const script = body['script'];
|
|
251
|
+
if (typeof script === 'string') out.push([name, script]);
|
|
252
|
+
}
|
|
253
|
+
return out.sort((a, b) => a[0].localeCompare(b[0]));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function scan(root: string): { findings: DenyFinding[]; scanned: number } {
|
|
257
|
+
const manifest = path.join(root, MANIFEST_REL);
|
|
258
|
+
if (!fs.existsSync(manifest)) {
|
|
259
|
+
return { findings: [], scanned: 0 };
|
|
260
|
+
}
|
|
261
|
+
const findings: DenyFinding[] = [];
|
|
262
|
+
let scanned = 0;
|
|
263
|
+
for (const [name, script] of blockingConcerns(fs.readFileSync(manifest, 'utf8'))) {
|
|
264
|
+
const abs = path.join(root, script);
|
|
265
|
+
if (!fs.existsSync(abs)) continue;
|
|
266
|
+
scanned += 1;
|
|
267
|
+
findings.push(...scanSource(name, script, fs.readFileSync(abs, 'utf8')));
|
|
268
|
+
}
|
|
269
|
+
return { findings, scanned };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const REASON: Readonly<Record<DenyFinding['kind'], string>> = {
|
|
273
|
+
'env-assignment': 'names an environment kill switch, with its value',
|
|
274
|
+
'export-line': 'carries an `export` line the reader can paste',
|
|
275
|
+
'settings-key': 'names a settings key path',
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
export function main(argv: readonly string[]): number {
|
|
279
|
+
if (argv.includes('--self-test')) return selfTest();
|
|
280
|
+
const rootIdx = argv.indexOf('--root');
|
|
281
|
+
const root = rootIdx === -1 ? ROOT : (argv[rootIdx + 1] ?? ROOT);
|
|
282
|
+
|
|
283
|
+
const { findings, scanned } = scan(root);
|
|
284
|
+
|
|
285
|
+
const ledger = new GateLedger(GATE);
|
|
286
|
+
ledger.plan(findings.map((f) => `${f.concern}:${f.kind}:${f.text.slice(0, 40)}`));
|
|
287
|
+
for (const f of findings) {
|
|
288
|
+
ledger.fail(`${f.concern}:${f.kind}:${f.text.slice(0, 40)}`, REASON[f.kind]);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// A gate that read nothing has not passed. BEFORE the verdict: a manifest
|
|
292
|
+
// that moved, or a `severity:` key that was renamed, would otherwise
|
|
293
|
+
// certify an empty corpus clean.
|
|
294
|
+
assertScanned({
|
|
295
|
+
gate: GATE,
|
|
296
|
+
scanned,
|
|
297
|
+
units: 'blocking concern(s)',
|
|
298
|
+
roots: [MANIFEST_REL],
|
|
299
|
+
});
|
|
300
|
+
reportScanned({
|
|
301
|
+
gate: GATE,
|
|
302
|
+
scanned,
|
|
303
|
+
units: 'blocking concern(s)',
|
|
304
|
+
roots: [MANIFEST_REL],
|
|
305
|
+
});
|
|
306
|
+
ledger.report();
|
|
307
|
+
|
|
308
|
+
if (findings.length > 0) {
|
|
309
|
+
for (const f of findings) {
|
|
310
|
+
process.stdout.write(
|
|
311
|
+
`❌ ${GATE}: ${f.file} (${f.concern}) ${REASON[f.kind]}:\n ${f.text.slice(0, 200)}\n`,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
process.stdout.write(
|
|
315
|
+
'\nA blocking concern may HAVE a kill switch. Its refusal message may not name it.\n' +
|
|
316
|
+
"Move the sentence to the concern's header comment, the manifest entry, or\n" +
|
|
317
|
+
'docs/contracts/hook-architecture-v1.md — all three are read by people deciding\n' +
|
|
318
|
+
'whether the guard should exist, and none is read by the agent it just refused.\n',
|
|
319
|
+
);
|
|
320
|
+
return 1;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
process.stdout.write(
|
|
324
|
+
`✅ ${GATE}: no blocking concern names its own bypass ` +
|
|
325
|
+
`(${String(scanned)} concern(s) scanned).\n`,
|
|
326
|
+
);
|
|
327
|
+
return 0;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function selfTest(): number {
|
|
331
|
+
const plant = (name: string, body: string): string => {
|
|
332
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), `deny-text-${name}-`));
|
|
333
|
+
fs.mkdirSync(path.join(dir, 'src', 'scripts', 'hooks'), { recursive: true });
|
|
334
|
+
fs.writeFileSync(
|
|
335
|
+
path.join(dir, 'src', 'scripts', 'hook_manifest.yaml'),
|
|
336
|
+
[
|
|
337
|
+
'schema_version: 1',
|
|
338
|
+
'concerns:',
|
|
339
|
+
' probe-guard:',
|
|
340
|
+
' script: src/scripts/hooks/probe_guard.ts',
|
|
341
|
+
' severity: blocking',
|
|
342
|
+
' probe-advisory:',
|
|
343
|
+
' script: src/scripts/hooks/probe_advisory.ts',
|
|
344
|
+
' severity: advisory',
|
|
345
|
+
'',
|
|
346
|
+
].join('\n'),
|
|
347
|
+
'utf-8',
|
|
348
|
+
);
|
|
349
|
+
fs.writeFileSync(path.join(dir, 'src', 'scripts', 'hooks', 'probe_guard.ts'), body, 'utf-8');
|
|
350
|
+
fs.writeFileSync(
|
|
351
|
+
path.join(dir, 'src', 'scripts', 'hooks', 'probe_advisory.ts'),
|
|
352
|
+
"process.stderr.write('set AGENT_CONFIG_X=1 to skip this advice');\n",
|
|
353
|
+
'utf-8',
|
|
354
|
+
);
|
|
355
|
+
return dir;
|
|
356
|
+
};
|
|
357
|
+
const emit = (msg: string): string => `process.stderr.write(${JSON.stringify(msg)});\n`;
|
|
358
|
+
const run = (root: string): number =>
|
|
359
|
+
runGateCli(ROOT, path.join('src', 'scripts', 'lint_deny_text.ts'), ['--root', root], ROOT);
|
|
360
|
+
|
|
361
|
+
return runSelfTest({
|
|
362
|
+
gate: GATE,
|
|
363
|
+
minCases: 7,
|
|
364
|
+
minRejectCases: 3,
|
|
365
|
+
cases: [
|
|
366
|
+
{
|
|
367
|
+
name: 'an empty corpus is refused, not certified green',
|
|
368
|
+
expect: 'reject',
|
|
369
|
+
run: () => run(fs.mkdtempSync(path.join(os.tmpdir(), 'deny-text-empty-'))),
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
name: 'a deny naming an env kill switch with its value fails',
|
|
373
|
+
expect: 'reject',
|
|
374
|
+
run: () => run(plant('env', emit('BLOCKED — set AGENT_CONFIG_X=1 to skip'))),
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: 'a deny carrying an export line fails',
|
|
378
|
+
expect: 'reject',
|
|
379
|
+
run: () => run(plant('export', emit('BLOCKED — run export AC_OFF=1 first'))),
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
name: 'a deny naming a settings key path fails',
|
|
383
|
+
expect: 'reject',
|
|
384
|
+
run: () => run(plant('settings', emit('BLOCKED — flip hooks.enabled to false'))),
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
name: 'a deny that names no bypass passes',
|
|
388
|
+
expect: 'accept',
|
|
389
|
+
run: () => run(plant('clean', emit('BLOCKED — rebuild it from its source'))),
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
name: 'the kill switch named in a COMMENT passes — the rule is about output',
|
|
393
|
+
expect: 'accept',
|
|
394
|
+
run: () =>
|
|
395
|
+
run(
|
|
396
|
+
plant(
|
|
397
|
+
'comment',
|
|
398
|
+
'// Kill switch: AGENT_CONFIG_X=1.\n/* or AGENT_CONFIG_Y=1 */\n' +
|
|
399
|
+
emit('BLOCKED — rebuild it from its source'),
|
|
400
|
+
),
|
|
401
|
+
),
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
name: 'a bare settings key used as a lookup passes — it is not a message',
|
|
405
|
+
expect: 'accept',
|
|
406
|
+
run: () => run(plant('lookup', "const k = 'hooks.enabled';\nvoid k;\n")),
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
name: 'an ADVISORY concern naming its switch passes — only denials are scanned',
|
|
410
|
+
expect: 'accept',
|
|
411
|
+
run: () => run(plant('advisory-only', emit('BLOCKED — rebuild it'))),
|
|
412
|
+
},
|
|
413
|
+
],
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) {
|
|
418
|
+
process.exit(main(process.argv.slice(2)));
|
|
419
|
+
}
|