@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
|
@@ -1,24 +1,43 @@
|
|
|
1
1
|
import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
|
+
readRecentEntries
|
|
4
|
+
} from "./chunk-NYX3L2PZ.js";
|
|
5
|
+
import {
|
|
6
|
+
NO_RED_EVIDENCE_REASON,
|
|
3
7
|
deriveSessionKey,
|
|
8
|
+
describeRecordFinding,
|
|
4
9
|
foldRefusal,
|
|
10
|
+
foldShadow,
|
|
11
|
+
hasRedThenGreen,
|
|
5
12
|
isCiPoll,
|
|
6
13
|
isVacuousOutput,
|
|
14
|
+
isVerificationCommand,
|
|
7
15
|
parseRecord,
|
|
16
|
+
parseShadowRecord,
|
|
8
17
|
pendingCount,
|
|
9
18
|
readInstallBoundary,
|
|
19
|
+
readRunEvidence,
|
|
20
|
+
readTurnRunState,
|
|
10
21
|
sessionRefusalFile,
|
|
22
|
+
sessionShadowFile,
|
|
23
|
+
shadowSetBy,
|
|
11
24
|
statePathFor,
|
|
12
25
|
validateAssumption
|
|
13
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-NQ7WT43P.js";
|
|
14
27
|
import {
|
|
15
28
|
readHookStdin
|
|
16
29
|
} from "./chunk-F7DQWYR5.js";
|
|
17
30
|
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
31
|
+
LexicalIndex,
|
|
32
|
+
isSqliteAvailableSync,
|
|
33
|
+
loadSqliteSync,
|
|
34
|
+
readUserVersion,
|
|
35
|
+
stampUserVersion,
|
|
36
|
+
tokenize
|
|
37
|
+
} from "./chunk-ZBZV2G5R.js";
|
|
38
|
+
import {
|
|
39
|
+
detectToolPresence
|
|
40
|
+
} from "./chunk-A4QJ6ENT.js";
|
|
22
41
|
import {
|
|
23
42
|
ArgparseExit,
|
|
24
43
|
STATUS_SYMBOLS,
|
|
@@ -35,16 +54,22 @@ import {
|
|
|
35
54
|
comparePair,
|
|
36
55
|
frontmatterPaths,
|
|
37
56
|
proseEqual,
|
|
57
|
+
resolveSessionRole,
|
|
38
58
|
splitFrontmatter
|
|
39
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-TRI6DQZ6.js";
|
|
40
60
|
import "./chunk-FTAISGDQ.js";
|
|
41
|
-
import "./chunk-
|
|
61
|
+
import "./chunk-6V6TA25H.js";
|
|
42
62
|
import "./chunk-ROYYTEZU.js";
|
|
43
63
|
import "./chunk-BTWUYDGN.js";
|
|
44
64
|
import {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
} from "./chunk-
|
|
65
|
+
atomicAppendLine,
|
|
66
|
+
probeHostCapabilities
|
|
67
|
+
} from "./chunk-S4YWNRKO.js";
|
|
68
|
+
import "./chunk-ULQRUR23.js";
|
|
69
|
+
import {
|
|
70
|
+
hardenedSpawnEnv,
|
|
71
|
+
projectStoreSlug
|
|
72
|
+
} from "./chunk-EEHDXSYD.js";
|
|
48
73
|
import {
|
|
49
74
|
atomic_write_json,
|
|
50
75
|
atomic_write_text,
|
|
@@ -55,26 +80,22 @@ import {
|
|
|
55
80
|
prune_stale_session_states,
|
|
56
81
|
session_state_file
|
|
57
82
|
} from "./chunk-E4XIZDIB.js";
|
|
58
|
-
import {
|
|
59
|
-
artefact_roots
|
|
60
|
-
} from "./chunk-NUHLI2RX.js";
|
|
61
|
-
import {
|
|
62
|
-
readRecentEntries
|
|
63
|
-
} from "./chunk-5R7HOLAF.js";
|
|
64
|
-
import {
|
|
65
|
-
detectToolPresence
|
|
66
|
-
} from "./chunk-A4QJ6ENT.js";
|
|
67
|
-
import {
|
|
68
|
-
atomicAppendLine,
|
|
69
|
-
probeHostCapabilities
|
|
70
|
-
} from "./chunk-6KM6ORRY.js";
|
|
71
83
|
import "./chunk-T7L2QK3K.js";
|
|
72
84
|
import "./chunk-DMSZHXZS.js";
|
|
85
|
+
import "./chunk-NXEILF2K.js";
|
|
86
|
+
import "./chunk-DEIV7XP7.js";
|
|
87
|
+
import "./chunk-KB5FZ655.js";
|
|
88
|
+
import "./chunk-JQOM7TTM.js";
|
|
73
89
|
import {
|
|
74
90
|
manifest_path,
|
|
75
91
|
read_manifest
|
|
76
|
-
} from "./chunk-
|
|
92
|
+
} from "./chunk-OKJUIAPS.js";
|
|
77
93
|
import "./chunk-MWRD5YOH.js";
|
|
94
|
+
import {
|
|
95
|
+
DeadScopeError,
|
|
96
|
+
assertScanned,
|
|
97
|
+
reportScanned
|
|
98
|
+
} from "./chunk-I3EIN2YG.js";
|
|
78
99
|
import {
|
|
79
100
|
require_dist
|
|
80
101
|
} from "./chunk-OY53J6II.js";
|
|
@@ -83,17 +104,20 @@ import {
|
|
|
83
104
|
} from "./chunk-MCJYCQPF.js";
|
|
84
105
|
import {
|
|
85
106
|
load_agent_settings
|
|
86
|
-
} from "./chunk-
|
|
107
|
+
} from "./chunk-JY2I2DXV.js";
|
|
87
108
|
import "./chunk-NDJLNC6Q.js";
|
|
109
|
+
import {
|
|
110
|
+
artefact_roots
|
|
111
|
+
} from "./chunk-YCU3TIXH.js";
|
|
88
112
|
import {
|
|
89
113
|
__toESM
|
|
90
114
|
} from "./chunk-J55WI3RR.js";
|
|
91
115
|
|
|
92
116
|
// src/scripts/_cli/cmd_conformance.ts
|
|
93
117
|
var YAML = __toESM(require_dist(), 1);
|
|
94
|
-
import * as
|
|
118
|
+
import * as fs20 from "node:fs";
|
|
95
119
|
import * as os7 from "node:os";
|
|
96
|
-
import * as
|
|
120
|
+
import * as path18 from "node:path";
|
|
97
121
|
import process5 from "node:process";
|
|
98
122
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
99
123
|
import { fileURLToPath as fileURLToPath14, pathToFileURL as pathToFileURL14 } from "node:url";
|
|
@@ -109,10 +133,10 @@ var ROUTE_TARGET_TEMPLATES = Object.freeze({
|
|
|
109
133
|
])
|
|
110
134
|
});
|
|
111
135
|
function parseRouteTarget(target) {
|
|
112
|
-
const
|
|
113
|
-
if (
|
|
114
|
-
const kind = target.slice(0,
|
|
115
|
-
const id = target.slice(
|
|
136
|
+
const sep3 = target.indexOf(":");
|
|
137
|
+
if (sep3 <= 0) return null;
|
|
138
|
+
const kind = target.slice(0, sep3);
|
|
139
|
+
const id = target.slice(sep3 + 1);
|
|
116
140
|
if (!id) return null;
|
|
117
141
|
if (!Object.prototype.hasOwnProperty.call(ROUTE_TARGET_TEMPLATES, kind)) return null;
|
|
118
142
|
return { kind, id };
|
|
@@ -124,17 +148,17 @@ function routeTargetPathsPosix(target) {
|
|
|
124
148
|
}
|
|
125
149
|
|
|
126
150
|
// src/scripts/report_conformance_funnel.ts
|
|
127
|
-
import * as
|
|
151
|
+
import * as fs19 from "node:fs";
|
|
128
152
|
import * as os6 from "node:os";
|
|
129
|
-
import * as
|
|
153
|
+
import * as path17 from "node:path";
|
|
130
154
|
import process4 from "node:process";
|
|
131
155
|
import { fileURLToPath as fileURLToPath13, pathToFileURL as pathToFileURL13 } from "node:url";
|
|
132
156
|
|
|
133
157
|
// src/scripts/conformance_scan.ts
|
|
134
|
-
import { createHash as
|
|
135
|
-
import
|
|
158
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
159
|
+
import fs15 from "node:fs";
|
|
136
160
|
import os2 from "node:os";
|
|
137
|
-
import
|
|
161
|
+
import path13 from "node:path";
|
|
138
162
|
import { fileURLToPath as fileURLToPath9, pathToFileURL as pathToFileURL9 } from "node:url";
|
|
139
163
|
|
|
140
164
|
// src/scripts/_lib/transcript_entry.ts
|
|
@@ -182,7 +206,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
182
206
|
// src/scripts/_lib/prompt_shape.ts
|
|
183
207
|
function isSyntheticPrompt(prompt) {
|
|
184
208
|
const head = prompt.trimStart().slice(0, 400);
|
|
185
|
-
return head.startsWith("[SYSTEM NOTIFICATION - NOT USER INPUT]") || head.startsWith("<task-notification>") || head.startsWith("<system-reminder>") || head.startsWith("<local-command-caveat>") || /^\[SYSTEM NOTIFICATION\b/.test(head);
|
|
209
|
+
return head.startsWith("[SYSTEM NOTIFICATION - NOT USER INPUT]") || head.startsWith("<task-notification>") || head.startsWith("<system-reminder>") || head.startsWith("<local-command-caveat>") || head.startsWith("<agent-message") || head.startsWith("Your claude.ai usage limit has reset") || /^\[SYSTEM NOTIFICATION\b/.test(head);
|
|
186
210
|
}
|
|
187
211
|
var DOCUMENT_HEAD = /^\s*(#{1,6}\s|-{3,}\s*$|={3,}\s*$|<[A-Za-z][\w-]*>\s*$)/;
|
|
188
212
|
function humanAuthoredLead(text) {
|
|
@@ -339,7 +363,7 @@ This is the WEAKER of the two provenances \u2014 it reflects the machine, not th
|
|
|
339
363
|
return `<language-pin>
|
|
340
364
|
The user submitted this turn's prompt in ${name}. That is the mirror target for EVERY user-visible token of your replies this turn \u2014 including the short inter-tool lines between tool calls, headings, table cells, bullets, status lines, and the recommendation label under any numbered-options block.
|
|
341
365
|
|
|
342
|
-
|
|
366
|
+
Tool output, file contents, and an injected skill body are NOT the trigger \u2014 this pin is.
|
|
343
367
|
</language-pin>`;
|
|
344
368
|
}
|
|
345
369
|
function _loadState(target) {
|
|
@@ -707,7 +731,8 @@ import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from
|
|
|
707
731
|
// src/scripts/_lib/machine_wake.ts
|
|
708
732
|
var MACHINE_WAKE_PREFIXES = [
|
|
709
733
|
"<task-notification>",
|
|
710
|
-
"<system-reminder>"
|
|
734
|
+
"<system-reminder>",
|
|
735
|
+
"<agent-message"
|
|
711
736
|
];
|
|
712
737
|
function humanTypedThisTurn(prompt) {
|
|
713
738
|
const head = prompt.trimStart();
|
|
@@ -1918,8 +1943,8 @@ var COMMAND_OBJECTS = {
|
|
|
1918
1943
|
};
|
|
1919
1944
|
|
|
1920
1945
|
// src/scripts/hooks/turn_end_gate_hook.ts
|
|
1921
|
-
import * as
|
|
1922
|
-
import * as
|
|
1946
|
+
import * as fs12 from "node:fs";
|
|
1947
|
+
import * as path10 from "node:path";
|
|
1923
1948
|
import { fileURLToPath as fileURLToPath6, pathToFileURL as pathToFileURL6 } from "node:url";
|
|
1924
1949
|
|
|
1925
1950
|
// src/scripts/check_reply_consistency.ts
|
|
@@ -2298,526 +2323,950 @@ function detectDroppedDecision(assistantTurnTexts, closingFromPayload = false) {
|
|
|
2298
2323
|
return null;
|
|
2299
2324
|
}
|
|
2300
2325
|
|
|
2301
|
-
// src/scripts/
|
|
2302
|
-
import
|
|
2303
|
-
import * as
|
|
2304
|
-
import * as fs5 from "node:fs";
|
|
2305
|
-
import * as os from "node:os";
|
|
2306
|
-
import * as path5 from "node:path";
|
|
2307
|
-
import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL4 } from "node:url";
|
|
2326
|
+
// src/scripts/_lib/graph_feeder_record.ts
|
|
2327
|
+
import * as fs7 from "node:fs";
|
|
2328
|
+
import * as path6 from "node:path";
|
|
2308
2329
|
|
|
2309
|
-
// src/scripts/
|
|
2310
|
-
import {
|
|
2330
|
+
// src/scripts/code_graph/detect.ts
|
|
2331
|
+
import { execFileSync } from "node:child_process";
|
|
2311
2332
|
import * as fs4 from "node:fs";
|
|
2312
2333
|
import * as path4 from "node:path";
|
|
2313
|
-
|
|
2334
|
+
|
|
2335
|
+
// src/scripts/code_graph/types.ts
|
|
2336
|
+
var GUESS_RESOLVED_VIA = /* @__PURE__ */ new Set([
|
|
2337
|
+
"name-lookup",
|
|
2338
|
+
"dynamic",
|
|
2339
|
+
// The accepted-edge filter is a DENYLIST, so a new value is accepted by
|
|
2340
|
+
// default — which is exactly backwards for this one. `foreign` must be
|
|
2341
|
+
// listed here or a consumer's graph would silently answer the gate verbs
|
|
2342
|
+
// as if this package had resolved its edges.
|
|
2343
|
+
"foreign"
|
|
2344
|
+
]);
|
|
2345
|
+
function isStatedResolution(resolved_via) {
|
|
2346
|
+
return !GUESS_RESOLVED_VIA.has(resolved_via);
|
|
2347
|
+
}
|
|
2348
|
+
var SCHEMA_VERSION = 4;
|
|
2349
|
+
var EXT_LANG = {
|
|
2350
|
+
".php": "php",
|
|
2351
|
+
".ts": "typescript",
|
|
2352
|
+
".tsx": "typescript",
|
|
2353
|
+
".mts": "typescript",
|
|
2354
|
+
".cts": "typescript",
|
|
2355
|
+
".js": "javascript",
|
|
2356
|
+
".jsx": "javascript",
|
|
2357
|
+
".mjs": "javascript",
|
|
2358
|
+
".cjs": "javascript"
|
|
2359
|
+
};
|
|
2360
|
+
|
|
2361
|
+
// src/scripts/code_graph/validate.ts
|
|
2362
|
+
var RELATIONS = /* @__PURE__ */ new Set(["calls", "imports", "uses", "inherits", "member", "tests"]);
|
|
2363
|
+
var CONFIDENCES = /* @__PURE__ */ new Set(["EXTRACTED", "INFERRED", "AMBIGUOUS"]);
|
|
2364
|
+
var RESOLVED_VIA = /* @__PURE__ */ new Set([
|
|
2365
|
+
"same-file",
|
|
2366
|
+
"import-specifier",
|
|
2367
|
+
"path-alias",
|
|
2368
|
+
"psr4",
|
|
2369
|
+
"route-table",
|
|
2370
|
+
"test-import",
|
|
2371
|
+
"name-lookup",
|
|
2372
|
+
"dynamic",
|
|
2373
|
+
// Adapted from a consumer's own graph file; see `foreign.ts`.
|
|
2374
|
+
"foreign"
|
|
2375
|
+
]);
|
|
2376
|
+
var PROVIDERS = /* @__PURE__ */ new Set(["native", "foreign"]);
|
|
2377
|
+
var KINDS = /* @__PURE__ */ new Set([
|
|
2378
|
+
"file",
|
|
2379
|
+
"class",
|
|
2380
|
+
"interface",
|
|
2381
|
+
"trait",
|
|
2382
|
+
"function",
|
|
2383
|
+
"method",
|
|
2384
|
+
"constant",
|
|
2385
|
+
"type",
|
|
2386
|
+
"enum",
|
|
2387
|
+
"skipped"
|
|
2388
|
+
]);
|
|
2389
|
+
function validateGraph(g) {
|
|
2390
|
+
const errors = [];
|
|
2391
|
+
const push = (m) => {
|
|
2392
|
+
if (errors.length < 50) errors.push(m);
|
|
2393
|
+
};
|
|
2394
|
+
if (typeof g !== "object" || g === null) return { ok: false, errors: ["graph is not an object"] };
|
|
2395
|
+
const obj = g;
|
|
2396
|
+
if (typeof obj["schema_version"] !== "number") push("schema_version must be a number");
|
|
2397
|
+
if (typeof obj["source_checksum"] !== "string") push("source_checksum must be a string");
|
|
2398
|
+
if (!Array.isArray(obj["nodes"])) push("nodes must be an array");
|
|
2399
|
+
if (!Array.isArray(obj["edges"])) push("edges must be an array");
|
|
2400
|
+
if (errors.length) return { ok: false, errors };
|
|
2401
|
+
const nodes = obj["nodes"];
|
|
2402
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2403
|
+
nodes.forEach((n, i) => {
|
|
2404
|
+
if (typeof n !== "object" || n === null) return push(`node[${i}] not an object`);
|
|
2405
|
+
const nn = n;
|
|
2406
|
+
if (typeof nn["id"] !== "string") push(`node[${i}].id must be a string`);
|
|
2407
|
+
else ids.add(nn["id"]);
|
|
2408
|
+
if (!KINDS.has(nn["kind"])) push(`node[${i}].kind invalid: ${String(nn["kind"])}`);
|
|
2409
|
+
if (typeof nn["source_file"] !== "string") push(`node[${i}].source_file must be a string`);
|
|
2410
|
+
if (!Array.isArray(nn["source_location"])) push(`node[${i}].source_location must be an array`);
|
|
2411
|
+
});
|
|
2412
|
+
obj["edges"].forEach((e, i) => {
|
|
2413
|
+
if (typeof e !== "object" || e === null) return push(`edge[${i}] not an object`);
|
|
2414
|
+
const ee = e;
|
|
2415
|
+
if (typeof ee["source"] !== "string") push(`edge[${i}].source must be a string`);
|
|
2416
|
+
if (typeof ee["target"] !== "string") push(`edge[${i}].target must be a string`);
|
|
2417
|
+
if (!RELATIONS.has(ee["relation"])) push(`edge[${i}].relation invalid: ${String(ee["relation"])}`);
|
|
2418
|
+
if (!CONFIDENCES.has(ee["confidence"]))
|
|
2419
|
+
push(`edge[${i}].confidence invalid: ${String(ee["confidence"])}`);
|
|
2420
|
+
if (ee["resolved_via"] === void 0) push(`edge[${i}].resolved_via is required`);
|
|
2421
|
+
else if (!RESOLVED_VIA.has(ee["resolved_via"]))
|
|
2422
|
+
push(`edge[${i}].resolved_via invalid: ${String(ee["resolved_via"])}`);
|
|
2423
|
+
if (ee["provider"] === void 0) push(`edge[${i}].provider is required`);
|
|
2424
|
+
else if (!PROVIDERS.has(ee["provider"]))
|
|
2425
|
+
push(`edge[${i}].provider invalid: ${String(ee["provider"])}`);
|
|
2426
|
+
if (typeof ee["source"] === "string" && !ids.has(ee["source"]))
|
|
2427
|
+
push(`edge[${i}].source is not a known node: ${String(ee["source"])}`);
|
|
2428
|
+
if (ee["candidates"] !== void 0 && !Array.isArray(ee["candidates"]))
|
|
2429
|
+
push(`edge[${i}].candidates must be an array when present`);
|
|
2430
|
+
});
|
|
2431
|
+
return { ok: errors.length === 0, errors };
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
// src/scripts/code_graph/detect.ts
|
|
2435
|
+
var CONSUMER_CANDIDATES = ["graph.json", "code-graph.json", ".code-graph/graph.json"];
|
|
2436
|
+
function consumerCandidates(root) {
|
|
2437
|
+
let declared;
|
|
2314
2438
|
try {
|
|
2315
|
-
const
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
return out === "" ? null : out;
|
|
2439
|
+
const settings = load_agent_settings({ cwd: root }) ?? {};
|
|
2440
|
+
const section = settings["code_graph"];
|
|
2441
|
+
declared = section !== null && typeof section === "object" && !Array.isArray(section) ? section["consumer_index_paths"] : void 0;
|
|
2319
2442
|
} catch {
|
|
2320
|
-
|
|
2443
|
+
declared = void 0;
|
|
2321
2444
|
}
|
|
2445
|
+
const extra = Array.isArray(declared) ? declared.filter(
|
|
2446
|
+
(p) => typeof p === "string" && p !== "" && !path4.isAbsolute(p) && !path4.normalize(p).startsWith("..")
|
|
2447
|
+
) : [];
|
|
2448
|
+
return [...CONSUMER_CANDIDATES, ...extra.filter((p) => !CONSUMER_CANDIDATES.includes(p))];
|
|
2322
2449
|
}
|
|
2323
|
-
|
|
2324
|
-
function baselineStateDir(workspaceRoot) {
|
|
2325
|
-
return path4.join(workspaceRoot, BASELINE_STATE_REL);
|
|
2326
|
-
}
|
|
2327
|
-
function baselineStateFile(workspaceRoot, sessionKey) {
|
|
2328
|
-
return path4.join(baselineStateDir(workspaceRoot), `${sessionKey}.json`);
|
|
2329
|
-
}
|
|
2330
|
-
function parseBaseline(raw) {
|
|
2331
|
-
let decoded;
|
|
2450
|
+
function gitLastCommitEpoch(root) {
|
|
2332
2451
|
try {
|
|
2333
|
-
|
|
2452
|
+
const out = execFileSync("git", ["-C", root, "log", "-1", "--format=%ct"], {
|
|
2453
|
+
env: hardenedSpawnEnv(),
|
|
2454
|
+
encoding: "utf-8",
|
|
2455
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2456
|
+
}).trim();
|
|
2457
|
+
const n = Number(out);
|
|
2458
|
+
return Number.isFinite(n) ? n : null;
|
|
2334
2459
|
} catch {
|
|
2335
2460
|
return null;
|
|
2336
2461
|
}
|
|
2337
|
-
if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
|
|
2338
|
-
const o = decoded;
|
|
2339
|
-
const lines = o["baseline_lines"];
|
|
2340
|
-
if (typeof lines !== "number" || !Number.isFinite(lines) || lines < 0) return null;
|
|
2341
|
-
const written = o["written_at"];
|
|
2342
|
-
if (typeof written !== "string") return null;
|
|
2343
|
-
const head = o["head_sha"];
|
|
2344
|
-
const measure = o["measure"];
|
|
2345
|
-
return {
|
|
2346
|
-
head_sha: typeof head === "string" && head !== "" ? head : null,
|
|
2347
|
-
baseline_lines: lines,
|
|
2348
|
-
...measure === "exact" || measure === "capped_approximation" ? { measure } : {},
|
|
2349
|
-
written_at: written
|
|
2350
|
-
};
|
|
2351
2462
|
}
|
|
2352
|
-
function
|
|
2353
|
-
const file = baselineStateFile(workspaceRoot, sessionKey);
|
|
2354
|
-
let raw;
|
|
2463
|
+
function commitsBehind(root, sha) {
|
|
2355
2464
|
try {
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2465
|
+
const out = execFileSync("git", ["-C", root, "rev-list", "--count", `${sha}..HEAD`], {
|
|
2466
|
+
env: hardenedSpawnEnv(),
|
|
2467
|
+
encoding: "utf-8",
|
|
2468
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2469
|
+
}).trim();
|
|
2470
|
+
const n = Number(out);
|
|
2471
|
+
return Number.isFinite(n) ? n : null;
|
|
2472
|
+
} catch {
|
|
2473
|
+
return null;
|
|
2360
2474
|
}
|
|
2361
|
-
return parseBaseline(raw) ?? "unreadable";
|
|
2362
2475
|
}
|
|
2363
|
-
function
|
|
2364
|
-
if (
|
|
2365
|
-
|
|
2476
|
+
function freshness(root, file, embeddedSha) {
|
|
2477
|
+
if (embeddedSha) {
|
|
2478
|
+
const behind = commitsBehind(root, embeddedSha);
|
|
2479
|
+
if (behind !== null) return { stale: behind > 0, commits_behind: behind };
|
|
2366
2480
|
}
|
|
2367
|
-
|
|
2368
|
-
|
|
2481
|
+
const last = gitLastCommitEpoch(root);
|
|
2482
|
+
if (last === null) return {};
|
|
2483
|
+
try {
|
|
2484
|
+
const mtime = Math.floor(fs4.statSync(file).mtimeMs / 1e3);
|
|
2485
|
+
return { stale: mtime < last };
|
|
2486
|
+
} catch {
|
|
2487
|
+
return {};
|
|
2369
2488
|
}
|
|
2370
|
-
|
|
2371
|
-
|
|
2489
|
+
}
|
|
2490
|
+
function looksLikeGraph(p) {
|
|
2491
|
+
try {
|
|
2492
|
+
const raw = JSON.parse(fs4.readFileSync(p, "utf-8"));
|
|
2493
|
+
if (validateGraph(raw).ok) return { ok: true, sha: raw["head_at_build"] ?? null };
|
|
2494
|
+
const nodes = raw["nodes"];
|
|
2495
|
+
const edges = raw["edges"] ?? raw["links"];
|
|
2496
|
+
const okShape = Array.isArray(nodes) && Array.isArray(edges) && (edges.length === 0 || typeof edges[0] === "object" && edges[0] !== null && "source" in edges[0] && "target" in edges[0]);
|
|
2497
|
+
return { ok: okShape, sha: raw["head_at_build"] ?? null };
|
|
2498
|
+
} catch {
|
|
2499
|
+
return { ok: false, sha: null };
|
|
2372
2500
|
}
|
|
2373
|
-
return {
|
|
2374
|
-
applied: true,
|
|
2375
|
-
baseline,
|
|
2376
|
-
lines: Math.max(0, measuredLines - baseline.baseline_lines)
|
|
2377
|
-
};
|
|
2378
2501
|
}
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
var MAX_APPLIED_IDS = 32;
|
|
2382
|
-
function appliedIds(values) {
|
|
2502
|
+
function detectSources(root, nativeCache) {
|
|
2503
|
+
const abs = path4.resolve(root);
|
|
2383
2504
|
const out = [];
|
|
2384
|
-
for (const
|
|
2385
|
-
const
|
|
2386
|
-
if (
|
|
2387
|
-
|
|
2388
|
-
|
|
2505
|
+
for (const rel of consumerCandidates(abs)) {
|
|
2506
|
+
const p = path4.join(abs, rel);
|
|
2507
|
+
if (fs4.existsSync(p)) {
|
|
2508
|
+
const { ok, sha } = looksLikeGraph(p);
|
|
2509
|
+
if (ok) out.push({ kind: "consumer", path: p, present: true, ...freshness(abs, p, sha) });
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
const scipCandidates = [path4.join(abs, "index.scip")];
|
|
2513
|
+
try {
|
|
2514
|
+
for (const e of fs4.readdirSync(abs)) if (e.endsWith(".scip")) scipCandidates.push(path4.join(abs, e));
|
|
2515
|
+
} catch {
|
|
2389
2516
|
}
|
|
2517
|
+
const scip = scipCandidates.find((p) => fs4.existsSync(p));
|
|
2518
|
+
if (scip) out.push({ kind: "scip", path: scip, present: true, note: "SCIP detected \u2014 peer tooling required (no owned reader)" });
|
|
2519
|
+
if (fs4.existsSync(nativeCache)) out.push({ kind: "native", path: nativeCache, present: true, ...freshness(abs, nativeCache, null) });
|
|
2390
2520
|
return out;
|
|
2391
2521
|
}
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
"
|
|
2396
|
-
|
|
2397
|
-
"
|
|
2398
|
-
"head_moved",
|
|
2399
|
-
"mixed_measure"
|
|
2400
|
-
];
|
|
2401
|
-
var PRODUCER_PRIVACY_CLASS = "ids-only";
|
|
2402
|
-
function isNonNegInt(n) {
|
|
2403
|
-
return typeof n === "number" && Number.isFinite(n) && Number.isInteger(n) && n >= 0;
|
|
2522
|
+
function pickSource(verdicts) {
|
|
2523
|
+
const consumer = verdicts.find((v) => v.kind === "consumer" && v.stale !== true);
|
|
2524
|
+
if (consumer) return consumer;
|
|
2525
|
+
const native = verdicts.find((v) => v.kind === "native");
|
|
2526
|
+
if (native) return native;
|
|
2527
|
+
return verdicts.find((v) => v.kind === "consumer") ?? null;
|
|
2404
2528
|
}
|
|
2405
|
-
var
|
|
2406
|
-
function
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2529
|
+
var NATIVE_CACHE_REL = path4.join("agents", "runtime", "state", "code-graph-v1.json");
|
|
2530
|
+
function hasUncommittedIndexedEdit(root) {
|
|
2531
|
+
let out;
|
|
2532
|
+
try {
|
|
2533
|
+
out = execFileSync("git", ["-C", root, "status", "--porcelain", "--untracked-files=all"], {
|
|
2534
|
+
env: hardenedSpawnEnv(),
|
|
2535
|
+
encoding: "utf-8",
|
|
2536
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
2537
|
+
// Both bounds are the review's: Node's 1 MB default would throw
|
|
2538
|
+
// ENOBUFS on a large working tree, the catch below would return
|
|
2539
|
+
// false, and the state would read `fresh` — a second silent
|
|
2540
|
+
// under-report on a probe that runs at PreToolUse and at every stop.
|
|
2541
|
+
// The timeout bounds the other failure: this probe sits in front of
|
|
2542
|
+
// a tool call, so a hung git must not hold one up.
|
|
2543
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
2544
|
+
timeout: 5e3
|
|
2545
|
+
});
|
|
2546
|
+
} catch {
|
|
2547
|
+
return false;
|
|
2410
2548
|
}
|
|
2411
|
-
|
|
2412
|
-
|
|
2549
|
+
for (const raw of out.split("\n")) {
|
|
2550
|
+
if (raw.length < 4) continue;
|
|
2551
|
+
let p = raw.slice(3);
|
|
2552
|
+
const arrow = p.indexOf(" -> ");
|
|
2553
|
+
if (arrow !== -1) p = p.slice(arrow + 4);
|
|
2554
|
+
p = p.trim().replace(/^"|"$/g, "");
|
|
2555
|
+
const dot = p.lastIndexOf(".");
|
|
2556
|
+
if (dot === -1) continue;
|
|
2557
|
+
if (p.slice(dot).toLowerCase() in EXT_LANG) return true;
|
|
2413
2558
|
}
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2559
|
+
return false;
|
|
2560
|
+
}
|
|
2561
|
+
function graphState(root, nativeCache) {
|
|
2562
|
+
const picked = pickSource(detectSources(root, nativeCache ?? path4.join(root, NATIVE_CACHE_REL)));
|
|
2563
|
+
if (!picked) return "absent";
|
|
2564
|
+
if (picked.stale === true) {
|
|
2565
|
+
const behind = picked.commits_behind;
|
|
2566
|
+
return `behind:${typeof behind === "number" ? behind : 0}`;
|
|
2418
2567
|
}
|
|
2419
|
-
|
|
2420
|
-
const line = {
|
|
2421
|
-
schema_version: 1,
|
|
2422
|
-
id: input.id,
|
|
2423
|
-
ts: input.ts,
|
|
2424
|
-
work_id: input.work_id ?? `review-skipped-${input.ts}`,
|
|
2425
|
-
phase: "report",
|
|
2426
|
-
outcome: "skipped",
|
|
2427
|
-
confidence_band: "medium",
|
|
2428
|
-
risk_class: input.diff_lines >= HIGH_RISK_DIFF_LINES ? "high" : "medium",
|
|
2429
|
-
memory: { asks: 0, hits: 0 },
|
|
2430
|
-
verify: { claims: 0, first_try_passes: 0 },
|
|
2431
|
-
// COMPUTED, never a producer constant.
|
|
2432
|
-
rules_applied: appliedIds(input.rules_applied),
|
|
2433
|
-
// MANDATORY on every audit line, same as the orchestration producer.
|
|
2434
|
-
privacy_class: PRODUCER_PRIVACY_CLASS,
|
|
2435
|
-
// `skills_applied` is OMITTED here, deliberately and not by oversight.
|
|
2436
|
-
// audit-log-v1 distinguishes an absent key ("not recorded") from `[]`
|
|
2437
|
-
// ("recorded, none applied"), and this writer observes a review that
|
|
2438
|
-
// did not happen -- it has no skill observation to offer in either
|
|
2439
|
-
// direction. Emitting `[]` would assert "no skills applied" on evidence
|
|
2440
|
-
// this producer never gathered, which is the exact fabrication the
|
|
2441
|
-
// absent/empty split exists to make impossible.
|
|
2442
|
-
persona: null,
|
|
2443
|
-
input_kind: "prompt",
|
|
2444
|
-
type: "note",
|
|
2445
|
-
review_skipped: {
|
|
2446
|
-
diff_lines: input.diff_lines,
|
|
2447
|
-
mutation_measure: input.mutation_measure,
|
|
2448
|
-
// Omitted, not defaulted, when the producer recorded nothing —
|
|
2449
|
-
// audit-log-v1's absent-versus-present split is the same one
|
|
2450
|
-
// `skills_applied` uses above, for the same reason.
|
|
2451
|
-
...input.baseline === void 0 ? {} : { baseline: input.baseline }
|
|
2452
|
-
}
|
|
2453
|
-
};
|
|
2454
|
-
return { line, errors: [] };
|
|
2568
|
+
return hasUncommittedIndexedEdit(root) ? "edited" : "fresh";
|
|
2455
2569
|
}
|
|
2456
2570
|
|
|
2457
|
-
// src/scripts/
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
function
|
|
2465
|
-
return
|
|
2466
|
-
}
|
|
2467
|
-
function
|
|
2468
|
-
if (!
|
|
2571
|
+
// src/scripts/code_graph/query.ts
|
|
2572
|
+
import * as fs6 from "node:fs";
|
|
2573
|
+
|
|
2574
|
+
// src/scripts/code_graph/sqlite_store.ts
|
|
2575
|
+
import * as fs5 from "node:fs";
|
|
2576
|
+
var GRAPH_STORE_VERSION = 4;
|
|
2577
|
+
var INDEXED_READ_MIN_EDGES = 5e4;
|
|
2578
|
+
function sqliteTwinPath(jsonPath) {
|
|
2579
|
+
return jsonPath.endsWith(".json") ? `${jsonPath.slice(0, -5)}.sqlite3` : `${jsonPath}.sqlite3`;
|
|
2580
|
+
}
|
|
2581
|
+
function emitSqliteTwin(graph, serialized, jsonPath) {
|
|
2582
|
+
if (!isSqliteAvailableSync()) return false;
|
|
2583
|
+
const twin = sqliteTwinPath(jsonPath);
|
|
2584
|
+
const tmp = `${twin}.tmp-${process.pid}`;
|
|
2585
|
+
try {
|
|
2586
|
+
const { DatabaseSync } = loadSqliteSync("code_graph.sqlite_store");
|
|
2587
|
+
let stat;
|
|
2588
|
+
try {
|
|
2589
|
+
stat = fs5.statSync(jsonPath);
|
|
2590
|
+
} catch {
|
|
2591
|
+
return false;
|
|
2592
|
+
}
|
|
2593
|
+
fs5.rmSync(tmp, { force: true });
|
|
2594
|
+
const db = new DatabaseSync(tmp);
|
|
2595
|
+
try {
|
|
2596
|
+
db.exec("PRAGMA journal_mode = MEMORY");
|
|
2597
|
+
db.exec(
|
|
2598
|
+
"CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);CREATE TABLE nodes (id TEXT PRIMARY KEY, label TEXT NOT NULL, kind TEXT, source_file TEXT, l0 INTEGER, l1 INTEGER, l2 INTEGER, l3 INTEGER);CREATE TABLE edges (source TEXT NOT NULL, target TEXT NOT NULL, relation TEXT NOT NULL, confidence TEXT NOT NULL, candidates TEXT, ambiguity_reason TEXT, resolved_via TEXT, provider TEXT);CREATE INDEX edges_source ON edges(source);CREATE INDEX edges_target ON edges(target);CREATE INDEX nodes_label ON nodes(label);CREATE INDEX nodes_source_file ON nodes(source_file);"
|
|
2599
|
+
);
|
|
2600
|
+
const meta = db.prepare("INSERT INTO meta (key, value) VALUES (?, ?)");
|
|
2601
|
+
meta.run("source_checksum", graph.source_checksum);
|
|
2602
|
+
meta.run("json_size", String(stat.size));
|
|
2603
|
+
meta.run("json_mtime_ms", String(stat.mtimeMs));
|
|
2604
|
+
meta.run("graph_json", serialized);
|
|
2605
|
+
meta.run("node_count", String(graph.nodes.length));
|
|
2606
|
+
meta.run("edge_count", String(graph.edges.length));
|
|
2607
|
+
const insNode = db.prepare(
|
|
2608
|
+
"INSERT INTO nodes (id, label, kind, source_file, l0, l1, l2, l3) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
|
|
2609
|
+
);
|
|
2610
|
+
for (const n of graph.nodes) {
|
|
2611
|
+
const loc = n.source_location ?? [];
|
|
2612
|
+
insNode.run(
|
|
2613
|
+
n.id,
|
|
2614
|
+
n.label,
|
|
2615
|
+
n.kind ?? null,
|
|
2616
|
+
n.source_file ?? null,
|
|
2617
|
+
loc[0] ?? null,
|
|
2618
|
+
loc[1] ?? null,
|
|
2619
|
+
loc[2] ?? null,
|
|
2620
|
+
loc[3] ?? null
|
|
2621
|
+
);
|
|
2622
|
+
}
|
|
2623
|
+
const insEdge = db.prepare(
|
|
2624
|
+
"INSERT INTO edges (source, target, relation, confidence, candidates, ambiguity_reason, resolved_via, provider) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
|
|
2625
|
+
);
|
|
2626
|
+
for (const e of graph.edges) {
|
|
2627
|
+
insEdge.run(
|
|
2628
|
+
e.source,
|
|
2629
|
+
e.target,
|
|
2630
|
+
e.relation,
|
|
2631
|
+
e.confidence,
|
|
2632
|
+
e.candidates ? JSON.stringify(e.candidates) : null,
|
|
2633
|
+
e.ambiguity_reason ?? null,
|
|
2634
|
+
e.resolved_via,
|
|
2635
|
+
e.provider
|
|
2636
|
+
);
|
|
2637
|
+
}
|
|
2638
|
+
stampUserVersion(db, GRAPH_STORE_VERSION);
|
|
2639
|
+
} finally {
|
|
2640
|
+
db.close();
|
|
2641
|
+
}
|
|
2642
|
+
fs5.renameSync(tmp, twin);
|
|
2643
|
+
return true;
|
|
2644
|
+
} catch {
|
|
2645
|
+
fs5.rmSync(tmp, { force: true });
|
|
2469
2646
|
return false;
|
|
2470
2647
|
}
|
|
2471
|
-
return ENVELOPE_KEYS.every((key) => key in obj);
|
|
2472
2648
|
}
|
|
2473
|
-
function
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
let decoded;
|
|
2649
|
+
function loadSerializedFromTwin(jsonPath) {
|
|
2650
|
+
if (!isSqliteAvailableSync()) return null;
|
|
2651
|
+
const twin = sqliteTwinPath(jsonPath);
|
|
2652
|
+
if (!fs5.existsSync(twin)) return null;
|
|
2653
|
+
let stat;
|
|
2479
2654
|
try {
|
|
2480
|
-
|
|
2655
|
+
stat = fs5.statSync(jsonPath);
|
|
2481
2656
|
} catch {
|
|
2482
|
-
return
|
|
2657
|
+
return null;
|
|
2483
2658
|
}
|
|
2484
|
-
|
|
2485
|
-
const
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2659
|
+
try {
|
|
2660
|
+
const { DatabaseSync } = loadSqliteSync("code_graph.sqlite_store");
|
|
2661
|
+
const db = new DatabaseSync(twin, { readOnly: true });
|
|
2662
|
+
try {
|
|
2663
|
+
if (readUserVersion(db) !== GRAPH_STORE_VERSION) return null;
|
|
2664
|
+
const rows = db.prepare("SELECT key, value FROM meta").all();
|
|
2665
|
+
const meta = new Map(rows.map((r) => [r.key, r.value]));
|
|
2666
|
+
if (meta.get("json_size") !== String(stat.size)) return null;
|
|
2667
|
+
if (meta.get("json_mtime_ms") !== String(stat.mtimeMs)) return null;
|
|
2668
|
+
const serialized = meta.get("graph_json");
|
|
2669
|
+
return serialized && serialized.length > 0 ? serialized : null;
|
|
2670
|
+
} finally {
|
|
2671
|
+
db.close();
|
|
2489
2672
|
}
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
return
|
|
2673
|
+
} catch {
|
|
2674
|
+
fs5.rmSync(twin, { force: true });
|
|
2675
|
+
return null;
|
|
2493
2676
|
}
|
|
2494
|
-
const payload = _isObject4(decoded) ? decoded : {};
|
|
2495
|
-
return [
|
|
2496
|
-
{
|
|
2497
|
-
schema_version: 1,
|
|
2498
|
-
platform: default_platform,
|
|
2499
|
-
event: "",
|
|
2500
|
-
native_event: "",
|
|
2501
|
-
session_id: "",
|
|
2502
|
-
workspace_root: "",
|
|
2503
|
-
payload,
|
|
2504
|
-
settings: {}
|
|
2505
|
-
},
|
|
2506
|
-
payload,
|
|
2507
|
-
default_platform
|
|
2508
|
-
];
|
|
2509
2677
|
}
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
try {
|
|
2520
|
-
const realHome = fs5.realpathSync(homeDir);
|
|
2521
|
-
const realPath = fs5.realpathSync(rawPath);
|
|
2522
|
-
const relative3 = path5.relative(realHome, realPath);
|
|
2523
|
-
if (relative3 === "" || relative3.startsWith("..") || path5.isAbsolute(relative3)) {
|
|
2524
|
-
return false;
|
|
2525
|
-
}
|
|
2526
|
-
const { size } = fs5.statSync(realPath);
|
|
2527
|
-
return size <= maxBytes;
|
|
2528
|
-
} catch {
|
|
2529
|
-
return false;
|
|
2530
|
-
}
|
|
2678
|
+
function toNode(r) {
|
|
2679
|
+
const loc = [r.l0, r.l1, r.l2, r.l3].filter((v) => typeof v === "number");
|
|
2680
|
+
return {
|
|
2681
|
+
id: r.id,
|
|
2682
|
+
label: r.label,
|
|
2683
|
+
kind: r.kind ?? "file",
|
|
2684
|
+
source_file: r.source_file ?? "",
|
|
2685
|
+
source_location: loc
|
|
2686
|
+
};
|
|
2531
2687
|
}
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2688
|
+
function toEdge(r) {
|
|
2689
|
+
const e = {
|
|
2690
|
+
source: r.source,
|
|
2691
|
+
target: r.target,
|
|
2692
|
+
relation: r.relation,
|
|
2693
|
+
confidence: r.confidence,
|
|
2694
|
+
// The SQL columns are nullable, the TypeScript fields are not. A null
|
|
2695
|
+
// can only come from a twin emitted before these fields existed, which
|
|
2696
|
+
// GRAPH_STORE_VERSION already refuses — so this coalesce is unreachable
|
|
2697
|
+
// in a healthy tree and is written to fail READABLE rather than to
|
|
2698
|
+
// fabricate: `name-lookup` is the weakest mechanism, so a mislabelled
|
|
2699
|
+
// edge is under-trusted rather than over-trusted by Phase 3's filter.
|
|
2700
|
+
resolved_via: r.resolved_via ?? "name-lookup",
|
|
2701
|
+
provider: r.provider ?? "native"
|
|
2702
|
+
};
|
|
2703
|
+
if (r.candidates) e.candidates = JSON.parse(r.candidates);
|
|
2704
|
+
if (r.ambiguity_reason === "receiver-unknown" || r.ambiguity_reason === "hierarchy-unresolved") {
|
|
2705
|
+
e.ambiguity_reason = r.ambiguity_reason;
|
|
2545
2706
|
}
|
|
2546
|
-
return
|
|
2707
|
+
return e;
|
|
2547
2708
|
}
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
return rows.filter((r) => !isDocPath(r.path)).reduce((sum, r) => sum + r.added + r.deleted, 0);
|
|
2554
|
-
}
|
|
2555
|
-
function gitNumstatRows(cwd) {
|
|
2709
|
+
function openGraphIndex(jsonPath) {
|
|
2710
|
+
if (!isSqliteAvailableSync()) return null;
|
|
2711
|
+
const twin = sqliteTwinPath(jsonPath);
|
|
2712
|
+
if (!fs5.existsSync(twin)) return null;
|
|
2713
|
+
let stat;
|
|
2556
2714
|
try {
|
|
2557
|
-
|
|
2558
|
-
cwd,
|
|
2559
|
-
encoding: "utf8",
|
|
2560
|
-
maxBuffer: 16 * 1024 * 1024
|
|
2561
|
-
});
|
|
2562
|
-
if (r.status !== 0) return [];
|
|
2563
|
-
return parseNumstat(r.stdout ?? "");
|
|
2715
|
+
stat = fs5.statSync(jsonPath);
|
|
2564
2716
|
} catch {
|
|
2565
|
-
return
|
|
2717
|
+
return null;
|
|
2566
2718
|
}
|
|
2567
|
-
}
|
|
2568
|
-
function untrackedNonDocFiles(cwd) {
|
|
2569
2719
|
try {
|
|
2570
|
-
const
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2720
|
+
const { DatabaseSync } = loadSqliteSync("code_graph.sqlite_store");
|
|
2721
|
+
const db = new DatabaseSync(twin, { readOnly: true });
|
|
2722
|
+
try {
|
|
2723
|
+
if (readUserVersion(db) !== GRAPH_STORE_VERSION) {
|
|
2724
|
+
db.close();
|
|
2725
|
+
return null;
|
|
2726
|
+
}
|
|
2727
|
+
const rows = db.prepare("SELECT key, value FROM meta").all();
|
|
2728
|
+
const meta = new Map(rows.map((r) => [r.key, r.value]));
|
|
2729
|
+
if (meta.get("json_size") !== String(stat.size) || meta.get("json_mtime_ms") !== String(stat.mtimeMs)) {
|
|
2730
|
+
db.close();
|
|
2731
|
+
return null;
|
|
2732
|
+
}
|
|
2733
|
+
const nodeCount = Number(meta.get("node_count") ?? NaN);
|
|
2734
|
+
const edgeCount = Number(meta.get("edge_count") ?? NaN);
|
|
2735
|
+
if (!Number.isFinite(nodeCount) || !Number.isFinite(edgeCount)) {
|
|
2736
|
+
db.close();
|
|
2737
|
+
return null;
|
|
2738
|
+
}
|
|
2739
|
+
const qNode = db.prepare("SELECT * FROM nodes WHERE id = ?");
|
|
2740
|
+
const qFrom = db.prepare("SELECT * FROM edges WHERE source = ?");
|
|
2741
|
+
const qTo = db.prepare("SELECT * FROM edges WHERE target = ?");
|
|
2742
|
+
const qLabel = db.prepare("SELECT id FROM nodes WHERE label = ? LIMIT ?");
|
|
2743
|
+
return {
|
|
2744
|
+
nodeCount,
|
|
2745
|
+
edgeCount,
|
|
2746
|
+
sourceChecksum: meta.get("source_checksum") ?? "",
|
|
2747
|
+
getNode(id) {
|
|
2748
|
+
const r = qNode.get(id);
|
|
2749
|
+
return r ? toNode(r) : void 0;
|
|
2750
|
+
},
|
|
2751
|
+
hasNode(id) {
|
|
2752
|
+
return qNode.get(id) !== void 0;
|
|
2753
|
+
},
|
|
2754
|
+
edgesFrom(id) {
|
|
2755
|
+
return qFrom.all(id).map(toEdge);
|
|
2756
|
+
},
|
|
2757
|
+
edgesTo(id) {
|
|
2758
|
+
return qTo.all(id).map(toEdge);
|
|
2759
|
+
},
|
|
2760
|
+
idsByLabel(labelText, limit) {
|
|
2761
|
+
return qLabel.all(labelText, limit).map((r) => r.id);
|
|
2762
|
+
},
|
|
2763
|
+
idsInFiles(files) {
|
|
2764
|
+
if (files.length === 0) return [];
|
|
2765
|
+
const out = [];
|
|
2766
|
+
const CHUNK = 400;
|
|
2767
|
+
for (let i = 0; i < files.length; i += CHUNK) {
|
|
2768
|
+
const slice = files.slice(i, i + CHUNK);
|
|
2769
|
+
const marks = slice.map(() => "?").join(",");
|
|
2770
|
+
const stmt = db.prepare(
|
|
2771
|
+
`SELECT id FROM nodes WHERE kind != 'file' AND source_file IN (${marks})`
|
|
2772
|
+
);
|
|
2773
|
+
for (const r of stmt.all(...slice)) out.push(r.id);
|
|
2774
|
+
}
|
|
2775
|
+
return out;
|
|
2776
|
+
},
|
|
2777
|
+
allNodeIds() {
|
|
2778
|
+
return db.prepare("SELECT id FROM nodes ORDER BY id").all().map(
|
|
2779
|
+
(r) => r.id
|
|
2780
|
+
);
|
|
2781
|
+
},
|
|
2782
|
+
lexicalCorpus() {
|
|
2783
|
+
return db.prepare("SELECT id, label FROM nodes").all().map(
|
|
2784
|
+
(r) => ({ id: r.id, text: `${r.label} ${r.id}` })
|
|
2785
|
+
);
|
|
2786
|
+
},
|
|
2787
|
+
close() {
|
|
2788
|
+
db.close();
|
|
2789
|
+
}
|
|
2790
|
+
};
|
|
2791
|
+
} catch (err) {
|
|
2792
|
+
db.close();
|
|
2793
|
+
throw err;
|
|
2794
|
+
}
|
|
2577
2795
|
} catch {
|
|
2578
|
-
return
|
|
2796
|
+
return null;
|
|
2579
2797
|
}
|
|
2580
2798
|
}
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2799
|
+
|
|
2800
|
+
// src/scripts/code_graph/foreign.ts
|
|
2801
|
+
import { createHash } from "node:crypto";
|
|
2802
|
+
var RELATION_MAP = {
|
|
2803
|
+
calls: "calls",
|
|
2804
|
+
call: "calls",
|
|
2805
|
+
invokes: "calls",
|
|
2806
|
+
imports: "imports",
|
|
2807
|
+
import: "imports",
|
|
2808
|
+
requires: "imports",
|
|
2809
|
+
uses: "uses",
|
|
2810
|
+
use: "uses",
|
|
2811
|
+
references: "uses",
|
|
2812
|
+
inherits: "inherits",
|
|
2813
|
+
extends: "inherits",
|
|
2814
|
+
implements: "inherits",
|
|
2815
|
+
member: "member",
|
|
2816
|
+
contains: "member",
|
|
2817
|
+
tests: "tests",
|
|
2818
|
+
test: "tests"
|
|
2819
|
+
};
|
|
2820
|
+
var KINDS2 = /* @__PURE__ */ new Set([
|
|
2821
|
+
"file",
|
|
2822
|
+
"class",
|
|
2823
|
+
"interface",
|
|
2824
|
+
"trait",
|
|
2825
|
+
"function",
|
|
2826
|
+
"method",
|
|
2827
|
+
"constant",
|
|
2828
|
+
"type",
|
|
2829
|
+
"enum",
|
|
2830
|
+
"skipped"
|
|
2831
|
+
]);
|
|
2832
|
+
function isObject(v) {
|
|
2833
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2834
|
+
}
|
|
2835
|
+
function looksForeign(doc) {
|
|
2836
|
+
if (!isObject(doc)) return false;
|
|
2837
|
+
if (!Array.isArray(doc["nodes"])) return false;
|
|
2838
|
+
if (Array.isArray(doc["links"])) return true;
|
|
2839
|
+
return Array.isArray(doc["edges"]) && typeof doc["schema_version"] !== "number";
|
|
2840
|
+
}
|
|
2841
|
+
function adaptForeignGraph(doc) {
|
|
2842
|
+
if (!isObject(doc)) return null;
|
|
2843
|
+
const rawNodes = doc["nodes"];
|
|
2844
|
+
const rawEdges = doc["links"] ?? doc["edges"];
|
|
2845
|
+
if (!Array.isArray(rawNodes) || !Array.isArray(rawEdges)) return null;
|
|
2846
|
+
const nodes = [];
|
|
2847
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2848
|
+
for (const n of rawNodes) {
|
|
2849
|
+
if (!isObject(n)) continue;
|
|
2850
|
+
const id = n["id"];
|
|
2851
|
+
if (typeof id !== "string" || id === "") continue;
|
|
2852
|
+
const kind = typeof n["kind"] === "string" && KINDS2.has(n["kind"]) ? n["kind"] : "skipped";
|
|
2853
|
+
const sourceFile = typeof n["source_file"] === "string" ? n["source_file"] : typeof n["file"] === "string" ? n["file"] : "";
|
|
2854
|
+
nodes.push({
|
|
2855
|
+
id,
|
|
2856
|
+
label: typeof n["label"] === "string" ? n["label"] : id,
|
|
2857
|
+
kind,
|
|
2858
|
+
source_file: sourceFile,
|
|
2859
|
+
// A foreign node rarely carries a span. An empty one is honest —
|
|
2860
|
+
// it says "no location recorded", where a fabricated [1,1] would
|
|
2861
|
+
// point a reader at the top of a file for no reason.
|
|
2862
|
+
source_location: Array.isArray(n["source_location"]) ? n["source_location"] : []
|
|
2863
|
+
});
|
|
2864
|
+
ids.add(id);
|
|
2865
|
+
}
|
|
2866
|
+
const edges = [];
|
|
2867
|
+
const unmapped = {};
|
|
2868
|
+
let malformed = 0;
|
|
2869
|
+
for (const e of rawEdges) {
|
|
2870
|
+
if (!isObject(e)) {
|
|
2871
|
+
malformed += 1;
|
|
2872
|
+
continue;
|
|
2590
2873
|
}
|
|
2591
|
-
const
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
for (const byte of buf) {
|
|
2597
|
-
if (byte === 10) lines += 1;
|
|
2874
|
+
const source = e["source"];
|
|
2875
|
+
const target = e["target"];
|
|
2876
|
+
if (typeof source !== "string" || typeof target !== "string" || !ids.has(source)) {
|
|
2877
|
+
malformed += 1;
|
|
2878
|
+
continue;
|
|
2598
2879
|
}
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2880
|
+
const rawRelation = typeof e["relation"] === "string" ? e["relation"] : "";
|
|
2881
|
+
const relation = RELATION_MAP[rawRelation.toLowerCase()];
|
|
2882
|
+
if (relation === void 0) {
|
|
2883
|
+
const key = rawRelation === "" ? "(none)" : rawRelation;
|
|
2884
|
+
unmapped[key] = (unmapped[key] ?? 0) + 1;
|
|
2885
|
+
continue;
|
|
2886
|
+
}
|
|
2887
|
+
edges.push({
|
|
2888
|
+
source,
|
|
2889
|
+
target,
|
|
2890
|
+
relation,
|
|
2891
|
+
// Not EXTRACTED: this package did not extract it and cannot say
|
|
2892
|
+
// how the tool that did arrived at it.
|
|
2893
|
+
confidence: "AMBIGUOUS",
|
|
2894
|
+
resolved_via: "foreign",
|
|
2895
|
+
provider: "foreign"
|
|
2896
|
+
});
|
|
2603
2897
|
}
|
|
2898
|
+
const counts = { EXTRACTED: 0, INFERRED: 0, AMBIGUOUS: edges.length };
|
|
2899
|
+
const graph = {
|
|
2900
|
+
schema_version: SCHEMA_VERSION,
|
|
2901
|
+
source_checksum: createHash("sha256").update(JSON.stringify(doc), "utf-8").digest("hex"),
|
|
2902
|
+
languages: [],
|
|
2903
|
+
grammar_abi: 0,
|
|
2904
|
+
edge_confidence_counts: counts,
|
|
2905
|
+
suppressed_edge_counts: { dynamic_no_candidate: 0 },
|
|
2906
|
+
nodes,
|
|
2907
|
+
edges
|
|
2908
|
+
};
|
|
2909
|
+
return { graph, unmapped_relations: unmapped, malformed_edges: malformed };
|
|
2604
2910
|
}
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2911
|
+
|
|
2912
|
+
// src/scripts/code_graph/query.ts
|
|
2913
|
+
function indexBackedGraph(index, source, answeredBy) {
|
|
2914
|
+
const nodeCache = /* @__PURE__ */ new Map();
|
|
2915
|
+
const readNode = (id) => {
|
|
2916
|
+
if (nodeCache.has(id)) return nodeCache.get(id);
|
|
2917
|
+
const n = index.getNode(id);
|
|
2918
|
+
nodeCache.set(id, n);
|
|
2919
|
+
return n;
|
|
2920
|
+
};
|
|
2921
|
+
let lexical = null;
|
|
2922
|
+
return {
|
|
2923
|
+
graph: null,
|
|
2924
|
+
source,
|
|
2925
|
+
answeredBy,
|
|
2926
|
+
byId: { get: readNode, has: (id) => readNode(id) !== void 0 },
|
|
2927
|
+
out: { get: (id) => index.edgesFrom(id) },
|
|
2928
|
+
in: { get: (id) => index.edgesTo(id) },
|
|
2929
|
+
idsByLabel: (labelText, limit) => index.idsByLabel(labelText, limit),
|
|
2930
|
+
idsInFiles: (files) => index.idsInFiles(files),
|
|
2931
|
+
allNodeIds: () => index.allNodeIds(),
|
|
2932
|
+
lex: () => {
|
|
2933
|
+
lexical ??= new LexicalIndex(index.lexicalCorpus());
|
|
2934
|
+
return lexical;
|
|
2935
|
+
},
|
|
2936
|
+
close: () => index.close()
|
|
2937
|
+
};
|
|
2938
|
+
}
|
|
2939
|
+
function loadGraph(graphPath, source = graphPath) {
|
|
2940
|
+
const index = openGraphIndex(graphPath);
|
|
2941
|
+
if (index) {
|
|
2942
|
+
if (index.edgeCount >= INDEXED_READ_MIN_EDGES) return indexBackedGraph(index, source, graphPath);
|
|
2943
|
+
index.close();
|
|
2944
|
+
}
|
|
2945
|
+
const fromTwin = loadSerializedFromTwin(graphPath);
|
|
2946
|
+
const raw = fromTwin ?? fs6.readFileSync(graphPath, "utf-8");
|
|
2947
|
+
const parsed = JSON.parse(raw);
|
|
2948
|
+
let foreignAdapted = false;
|
|
2949
|
+
let candidate = parsed;
|
|
2950
|
+
if (looksForeign(parsed)) {
|
|
2951
|
+
const adapted = adaptForeignGraph(parsed);
|
|
2952
|
+
if (adapted !== null) {
|
|
2953
|
+
candidate = adapted.graph;
|
|
2954
|
+
foreignAdapted = true;
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
const v = validateGraph(candidate);
|
|
2958
|
+
if (!v.ok) throw new Error(`invalid graph at ${graphPath}: ${v.errors.slice(0, 3).join("; ")}`);
|
|
2959
|
+
const graph = candidate;
|
|
2960
|
+
if (fromTwin === null && !foreignAdapted) {
|
|
2961
|
+
emitSqliteTwin(graph, raw, graphPath);
|
|
2962
|
+
}
|
|
2963
|
+
const byId = new Map(graph.nodes.map((n) => [n.id, n]));
|
|
2964
|
+
const outM = /* @__PURE__ */ new Map();
|
|
2965
|
+
const inM = /* @__PURE__ */ new Map();
|
|
2966
|
+
const add = (m, k, e) => {
|
|
2967
|
+
const arr = m.get(k);
|
|
2968
|
+
if (arr) arr.push(e);
|
|
2969
|
+
else m.set(k, [e]);
|
|
2970
|
+
};
|
|
2971
|
+
for (const e of graph.edges) {
|
|
2972
|
+
add(outM, e.source, e);
|
|
2973
|
+
add(inM, e.target, e);
|
|
2610
2974
|
}
|
|
2611
|
-
const
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2975
|
+
const lex = new LexicalIndex(graph.nodes.map((n) => ({ id: n.id, text: `${n.label} ${n.id}` })));
|
|
2976
|
+
return {
|
|
2977
|
+
graph,
|
|
2978
|
+
source,
|
|
2979
|
+
answeredBy: graphPath,
|
|
2980
|
+
byId,
|
|
2981
|
+
out: outM,
|
|
2982
|
+
in: inM,
|
|
2983
|
+
idsByLabel: (labelText, limit) => graph.nodes.filter((n) => n.label === labelText).slice(0, limit).map((n) => n.id),
|
|
2984
|
+
idsInFiles: (files) => {
|
|
2985
|
+
const set = new Set(files);
|
|
2986
|
+
return graph.nodes.filter((n) => set.has(n.source_file) && n.kind !== "file").map((n) => n.id);
|
|
2987
|
+
},
|
|
2988
|
+
allNodeIds: () => graph.nodes.map((n) => n.id).sort((a, b) => a < b ? -1 : a > b ? 1 : 0),
|
|
2989
|
+
lex: () => lex,
|
|
2990
|
+
close: () => {
|
|
2991
|
+
}
|
|
2992
|
+
};
|
|
2616
2993
|
}
|
|
2617
|
-
function
|
|
2618
|
-
|
|
2994
|
+
function resolveSeedsTiered(g, seed, limit = 5) {
|
|
2995
|
+
if (g.byId.has(seed)) return { ids: [seed], weak: false };
|
|
2996
|
+
const exactLabel = g.idsByLabel(seed, limit);
|
|
2997
|
+
if (exactLabel.length) return { ids: exactLabel, weak: false };
|
|
2998
|
+
const ranked = g.lex().rank(tokenize(seed)).filter((r) => r.score > 0);
|
|
2999
|
+
return { ids: ranked.slice(0, limit).map((r) => r.id), weak: true };
|
|
2619
3000
|
}
|
|
2620
|
-
function
|
|
2621
|
-
return
|
|
3001
|
+
function resolveSeeds(g, seed, limit = 5) {
|
|
3002
|
+
return resolveSeedsTiered(g, seed, limit).ids;
|
|
2622
3003
|
}
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
3004
|
+
|
|
3005
|
+
// src/scripts/code_graph/verbs.ts
|
|
3006
|
+
import * as path5 from "node:path";
|
|
3007
|
+
function isAcceptedEdge(e) {
|
|
3008
|
+
return isStatedResolution(e.resolved_via);
|
|
3009
|
+
}
|
|
3010
|
+
function foreignRefusal(g) {
|
|
3011
|
+
const edges = g.graph?.edges ?? [];
|
|
3012
|
+
if (edges.length === 0) return null;
|
|
3013
|
+
if (!edges.every((e) => e.resolved_via === "foreign")) return null;
|
|
3014
|
+
return 'every edge in this graph was read from a file this package did not build, so all of them are guesses and none is gate-grade. A gate verb would accept no edge and report an empty answer, which reads as "nothing found" rather than "nothing trusted". Build a native graph (`agent-config graph build`) to use this verb, or use `query` and `explain`, which answer from foreign edges and say so.';
|
|
3015
|
+
}
|
|
3016
|
+
function viaHistogram(edges) {
|
|
3017
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3018
|
+
for (const e of edges) counts.set(e.resolved_via, (counts.get(e.resolved_via) ?? 0) + 1);
|
|
3019
|
+
if (counts.size === 0) return "(none)";
|
|
3020
|
+
return [...counts.entries()].sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1)).map(([via, n]) => `${via} ${n}`).join(" \xB7 ");
|
|
3021
|
+
}
|
|
3022
|
+
function readFor(g, id) {
|
|
3023
|
+
const n = g.byId.get(id);
|
|
3024
|
+
if (!n) return null;
|
|
3025
|
+
const loc = n.source_location;
|
|
3026
|
+
return { path: n.source_file, lines: loc.length >= 3 ? [loc[0], loc[2]] : null };
|
|
3027
|
+
}
|
|
3028
|
+
function dedupeReads(reads) {
|
|
3029
|
+
const m = /* @__PURE__ */ new Map();
|
|
3030
|
+
for (const r of reads) {
|
|
3031
|
+
if (!r) continue;
|
|
3032
|
+
m.set(r.lines ? `${r.path}:${r.lines[0]}-${r.lines[1]}` : r.path, r);
|
|
3033
|
+
}
|
|
3034
|
+
return [...m.values()];
|
|
3035
|
+
}
|
|
3036
|
+
function seedsForFiles(g, files) {
|
|
3037
|
+
const symbols = g.idsInFiles(files);
|
|
3038
|
+
const seeds = new Set(symbols);
|
|
3039
|
+
const unresolved = [];
|
|
3040
|
+
const notIndexed = [];
|
|
3041
|
+
for (const f of files) {
|
|
3042
|
+
if (g.byId.has(f)) {
|
|
3043
|
+
seeds.add(f);
|
|
2635
3044
|
continue;
|
|
2636
3045
|
}
|
|
2637
|
-
if (
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
3046
|
+
if (symbols.some((s) => s.startsWith(`${f}#`))) continue;
|
|
3047
|
+
if (EXT_LANG[path5.extname(f).toLowerCase()] === void 0) notIndexed.push(f);
|
|
3048
|
+
else unresolved.push(f);
|
|
3049
|
+
}
|
|
3050
|
+
return {
|
|
3051
|
+
seeds: [...seeds].sort(),
|
|
3052
|
+
unresolved: unresolved.sort(),
|
|
3053
|
+
not_indexed: notIndexed.sort()
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
function testsFor(g, symbol, state) {
|
|
3057
|
+
const seeds = resolveSeeds(g, symbol);
|
|
3058
|
+
const targets = new Set(seeds);
|
|
3059
|
+
for (const s of seeds) {
|
|
3060
|
+
const f = g.byId.get(s)?.source_file;
|
|
3061
|
+
if (f && g.byId.has(f)) targets.add(f);
|
|
3062
|
+
}
|
|
3063
|
+
const accepted = [];
|
|
3064
|
+
const rejected = [];
|
|
3065
|
+
const tests = /* @__PURE__ */ new Set();
|
|
3066
|
+
for (const t of targets) {
|
|
3067
|
+
for (const e of g.in.get(t) ?? []) {
|
|
3068
|
+
if (e.relation !== "tests") continue;
|
|
3069
|
+
if (!isAcceptedEdge(e)) {
|
|
3070
|
+
rejected.push(e);
|
|
2660
3071
|
continue;
|
|
2661
3072
|
}
|
|
2662
|
-
|
|
3073
|
+
accepted.push(e);
|
|
3074
|
+
tests.add(e.source);
|
|
2663
3075
|
}
|
|
2664
3076
|
}
|
|
2665
|
-
|
|
3077
|
+
const list = [...tests].sort();
|
|
3078
|
+
return {
|
|
3079
|
+
source: g.source,
|
|
3080
|
+
state,
|
|
3081
|
+
accepted_via: viaHistogram(accepted),
|
|
3082
|
+
rejected_via: viaHistogram(rejected),
|
|
3083
|
+
seeds,
|
|
3084
|
+
tests: list,
|
|
3085
|
+
accepted_edges: accepted,
|
|
3086
|
+
rejected_edges: rejected,
|
|
3087
|
+
lines: list,
|
|
3088
|
+
recommended_reads: dedupeReads(list.map((t) => readFor(g, t)))
|
|
3089
|
+
};
|
|
2666
3090
|
}
|
|
2667
|
-
function
|
|
2668
|
-
|
|
3091
|
+
function untested(g, files, state) {
|
|
3092
|
+
const foreign = foreignRefusal(g);
|
|
3093
|
+
if (foreign !== null) {
|
|
3094
|
+
return {
|
|
3095
|
+
source: g.source,
|
|
3096
|
+
state,
|
|
3097
|
+
accepted_via: "(none)",
|
|
3098
|
+
rejected_via: "(none)",
|
|
3099
|
+
refusal: foreign,
|
|
3100
|
+
refusal_reason: "foreign-edges-not-accepted",
|
|
3101
|
+
seeds: [],
|
|
3102
|
+
unresolved_files: [],
|
|
3103
|
+
not_indexed_files: [],
|
|
3104
|
+
untested: [],
|
|
3105
|
+
tested: [],
|
|
3106
|
+
lines: [],
|
|
3107
|
+
recommended_reads: []
|
|
3108
|
+
};
|
|
3109
|
+
}
|
|
3110
|
+
const { seeds, unresolved, not_indexed } = seedsForFiles(g, files);
|
|
3111
|
+
const accepted = [];
|
|
3112
|
+
const rejected = [];
|
|
3113
|
+
const tested = [];
|
|
3114
|
+
const bare = [];
|
|
3115
|
+
for (const s of seeds) {
|
|
3116
|
+
if (g.byId.get(s)?.kind === "file") continue;
|
|
3117
|
+
const r = testsFor(g, s, state);
|
|
3118
|
+
accepted.push(...r.accepted_edges);
|
|
3119
|
+
rejected.push(...r.rejected_edges);
|
|
3120
|
+
if (r.tests.length) tested.push(s);
|
|
3121
|
+
else bare.push(s);
|
|
3122
|
+
}
|
|
3123
|
+
return {
|
|
3124
|
+
source: g.source,
|
|
3125
|
+
state,
|
|
3126
|
+
accepted_via: viaHistogram(accepted),
|
|
3127
|
+
rejected_via: viaHistogram(rejected),
|
|
3128
|
+
refusal: null,
|
|
3129
|
+
refusal_reason: null,
|
|
3130
|
+
seeds,
|
|
3131
|
+
unresolved_files: unresolved,
|
|
3132
|
+
not_indexed_files: not_indexed,
|
|
3133
|
+
untested: bare.sort(),
|
|
3134
|
+
tested: tested.sort(),
|
|
3135
|
+
lines: bare.sort(),
|
|
3136
|
+
recommended_reads: dedupeReads(bare.map((b) => readFor(g, b)))
|
|
3137
|
+
};
|
|
2669
3138
|
}
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
if (
|
|
3139
|
+
|
|
3140
|
+
// src/scripts/_lib/graph_feeder_record.ts
|
|
3141
|
+
var MAX_PATHS = 5;
|
|
3142
|
+
var MAX_ROWS_PER_SESSION = 200;
|
|
3143
|
+
function feederFile(workspaceRoot, sessionKey) {
|
|
3144
|
+
return path6.join(workspaceRoot, "agents", "state", "graph-feeder", `${sessionKey}.jsonl`);
|
|
3145
|
+
}
|
|
3146
|
+
function graphUntestedVerdict(root, state, paths) {
|
|
3147
|
+
const none = { verdict: null, untested: 0, tested: 0 };
|
|
3148
|
+
if (state !== "fresh" && state !== "edited") return none;
|
|
3149
|
+
if (paths.length === 0) return none;
|
|
3150
|
+
let picked;
|
|
2681
3151
|
try {
|
|
2682
|
-
|
|
2683
|
-
fs5.mkdirSync(dir, { recursive: true });
|
|
2684
|
-
const file = path5.join(dir, `${ts.slice(0, 7)}.jsonl`);
|
|
2685
|
-
fs5.appendFileSync(file, `${JSON.stringify(line)}
|
|
2686
|
-
`, "utf8");
|
|
3152
|
+
picked = pickSource(detectSources(root, path6.join(root, NATIVE_CACHE_REL)));
|
|
2687
3153
|
} catch {
|
|
3154
|
+
return none;
|
|
2688
3155
|
}
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
return path5.join(workspaceRoot, "agents", "runtime", "state", "end-review-nudge");
|
|
2692
|
-
}
|
|
2693
|
-
function sessionStateFile(workspaceRoot, sessionKey) {
|
|
2694
|
-
return path5.join(sessionStateDir(workspaceRoot), `${sessionKey}.json`);
|
|
2695
|
-
}
|
|
2696
|
-
function deriveSessionKey2(envelope, payload) {
|
|
2697
|
-
const raw = str(envelope["session_id"]) || str(payload["transcript_path"] ?? payload["transcriptPath"]);
|
|
2698
|
-
const material = raw || "unknown-session";
|
|
2699
|
-
return crypto.createHash("sha256").update(material).digest("hex");
|
|
2700
|
-
}
|
|
2701
|
-
function hasFiredThisSession(workspaceRoot, sessionKey) {
|
|
3156
|
+
if (!picked || picked.kind === "scip") return none;
|
|
3157
|
+
let g;
|
|
2702
3158
|
try {
|
|
2703
|
-
|
|
3159
|
+
g = loadGraph(picked.path, `${picked.kind}:${path6.relative(root, picked.path)}`);
|
|
2704
3160
|
} catch {
|
|
2705
|
-
return
|
|
3161
|
+
return none;
|
|
2706
3162
|
}
|
|
2707
|
-
}
|
|
2708
|
-
function markFiredThisSession(workspaceRoot, sessionKey, ts) {
|
|
2709
3163
|
try {
|
|
2710
|
-
|
|
3164
|
+
const r = untested(g, paths, state);
|
|
3165
|
+
if (r.seeds.length === 0) return { verdict: "no-seeds", untested: 0, tested: 0 };
|
|
3166
|
+
return {
|
|
3167
|
+
verdict: r.untested.length > 0 ? "untested" : "tested",
|
|
3168
|
+
untested: r.untested.length,
|
|
3169
|
+
tested: r.tested.length
|
|
3170
|
+
};
|
|
2711
3171
|
} catch {
|
|
3172
|
+
return none;
|
|
3173
|
+
} finally {
|
|
3174
|
+
try {
|
|
3175
|
+
g.close();
|
|
3176
|
+
} catch {
|
|
3177
|
+
}
|
|
2712
3178
|
}
|
|
2713
3179
|
}
|
|
2714
|
-
function
|
|
2715
|
-
return readHookStdin();
|
|
2716
|
-
}
|
|
2717
|
-
var FALLBACK_TO_TELEMETRY = {
|
|
2718
|
-
absent: "absent",
|
|
2719
|
-
unreadable: "unreadable",
|
|
2720
|
-
"head-moved": "head_moved",
|
|
2721
|
-
"mixed-measure": "mixed_measure"
|
|
2722
|
-
};
|
|
2723
|
-
function main4() {
|
|
2724
|
-
const [envelope, payload] = unwrap(_read_stdin(), "claude");
|
|
2725
|
-
const event = String(envelope["event"] ?? "");
|
|
2726
|
-
if (event !== "" && event !== "stop") {
|
|
2727
|
-
return 0;
|
|
2728
|
-
}
|
|
2729
|
-
const workspace_root = String(envelope["workspace_root"] ?? "").trim() || process.cwd();
|
|
2730
|
-
let measuredLines;
|
|
2731
|
-
let mutationMeasure;
|
|
3180
|
+
function appendFeederRow(workspaceRoot, sessionKey, row) {
|
|
2732
3181
|
try {
|
|
2733
|
-
const
|
|
2734
|
-
|
|
2735
|
-
|
|
3182
|
+
const file = feederFile(workspaceRoot, sessionKey);
|
|
3183
|
+
let existing = 0;
|
|
3184
|
+
try {
|
|
3185
|
+
existing = fs7.readFileSync(file, "utf-8").split("\n").filter((l) => l.trim() !== "").length;
|
|
3186
|
+
} catch {
|
|
3187
|
+
}
|
|
3188
|
+
if (existing >= MAX_ROWS_PER_SESSION) return;
|
|
3189
|
+
fs7.mkdirSync(path6.dirname(file), { recursive: true });
|
|
3190
|
+
fs7.appendFileSync(file, `${JSON.stringify(row)}
|
|
3191
|
+
`);
|
|
2736
3192
|
} catch {
|
|
2737
|
-
return 0;
|
|
2738
|
-
}
|
|
2739
|
-
const sessionKey = deriveSessionKey2(envelope, payload);
|
|
2740
|
-
const baselineOutcome = applyBaseline(
|
|
2741
|
-
measuredLines,
|
|
2742
|
-
readBaseline(workspace_root, sessionKey),
|
|
2743
|
-
// A THUNK, not a value: on the `absent` and `unreadable` branches the
|
|
2744
|
-
// result is discarded, and those two are the steady state on every host
|
|
2745
|
-
// without this nudge and on every session before its first baseline
|
|
2746
|
-
// write. Passing the value eagerly spent an 11-14 ms `git rev-parse` on
|
|
2747
|
-
// each of those stops for nothing.
|
|
2748
|
-
() => currentHeadSha(workspace_root),
|
|
2749
|
-
mutationMeasure
|
|
2750
|
-
);
|
|
2751
|
-
const diffLines = baselineOutcome.lines;
|
|
2752
|
-
const baselineApplication = baselineOutcome.applied ? "applied" : FALLBACK_TO_TELEMETRY[baselineOutcome.fallback];
|
|
2753
|
-
if (diffLines <= MUTATION_LINE_THRESHOLD) {
|
|
2754
|
-
return 0;
|
|
2755
3193
|
}
|
|
2756
|
-
if (hasFiredThisSession(workspace_root, sessionKey)) {
|
|
2757
|
-
return 0;
|
|
2758
|
-
}
|
|
2759
|
-
const transcriptPath = str(
|
|
2760
|
-
payload["transcript_path"] ?? payload["transcriptPath"]
|
|
2761
|
-
).trim();
|
|
2762
|
-
let reviewerRan = false;
|
|
2763
|
-
if (transcriptPath && isSafeTranscriptPath(transcriptPath)) {
|
|
2764
|
-
try {
|
|
2765
|
-
reviewerRan = scanTranscriptForReviewer(fs5.readFileSync(transcriptPath, "utf-8"));
|
|
2766
|
-
} catch {
|
|
2767
|
-
reviewerRan = false;
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2770
|
-
if (reviewerRan) {
|
|
2771
|
-
return 0;
|
|
2772
|
-
}
|
|
2773
|
-
markFiredThisSession(workspace_root, sessionKey, (/* @__PURE__ */ new Date()).toISOString());
|
|
2774
|
-
try {
|
|
2775
|
-
appendReviewSkippedTelemetry(
|
|
2776
|
-
workspace_root,
|
|
2777
|
-
diffLines,
|
|
2778
|
-
mutationMeasure,
|
|
2779
|
-
baselineApplication
|
|
2780
|
-
);
|
|
2781
|
-
} catch {
|
|
2782
|
-
}
|
|
2783
|
-
process.stdout.write(
|
|
2784
|
-
`${JSON.stringify({
|
|
2785
|
-
decision: "warn",
|
|
2786
|
-
reason: `end-review-nudge: session mutated ${diffLines} non-doc lines without a neutral review`,
|
|
2787
|
-
additional_context: buildAdvisoryLine(diffLines)
|
|
2788
|
-
})}
|
|
2789
|
-
`
|
|
2790
|
-
);
|
|
2791
|
-
return EXIT_WARN2;
|
|
2792
3194
|
}
|
|
2793
|
-
function
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
3195
|
+
function buildFeederRow(input) {
|
|
3196
|
+
return {
|
|
3197
|
+
at: input.at ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
3198
|
+
turn: input.turn,
|
|
3199
|
+
layer: input.layer,
|
|
3200
|
+
graph_state: input.state,
|
|
3201
|
+
f: input.fFired,
|
|
3202
|
+
f_mode: input.fMode,
|
|
3203
|
+
graph: input.graph.verdict,
|
|
3204
|
+
graph_untested: input.graph.untested,
|
|
3205
|
+
graph_tested: input.graph.tested,
|
|
3206
|
+
paths: input.paths.slice(0, MAX_PATHS).map((p) => p),
|
|
3207
|
+
path_count: input.paths.length
|
|
3208
|
+
};
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3211
|
+
// src/scripts/hooks/envelope.ts
|
|
3212
|
+
var ENVELOPE_KEYS = [
|
|
3213
|
+
"schema_version",
|
|
3214
|
+
"platform",
|
|
3215
|
+
"event",
|
|
3216
|
+
"payload"
|
|
3217
|
+
];
|
|
3218
|
+
function _isObject4(obj) {
|
|
3219
|
+
return typeof obj === "object" && obj !== null && !Array.isArray(obj);
|
|
3220
|
+
}
|
|
3221
|
+
function looks_like_envelope(obj) {
|
|
3222
|
+
if (!_isObject4(obj)) {
|
|
2798
3223
|
return false;
|
|
2799
3224
|
}
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
3225
|
+
return ENVELOPE_KEYS.every((key) => key in obj);
|
|
3226
|
+
}
|
|
3227
|
+
function unwrap(stdin_text, default_platform = "generic") {
|
|
3228
|
+
const text = (stdin_text ?? "").trim();
|
|
3229
|
+
if (!text) {
|
|
3230
|
+
return [{}, {}, default_platform];
|
|
2803
3231
|
}
|
|
3232
|
+
let decoded;
|
|
2804
3233
|
try {
|
|
2805
|
-
|
|
2806
|
-
const argv = fs5.realpathSync(path5.resolve(process.argv[1]));
|
|
2807
|
-
return here === argv;
|
|
3234
|
+
decoded = JSON.parse(text);
|
|
2808
3235
|
} catch {
|
|
2809
|
-
return
|
|
3236
|
+
return [{}, {}, default_platform];
|
|
2810
3237
|
}
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
3238
|
+
if (looks_like_envelope(decoded)) {
|
|
3239
|
+
const env = decoded;
|
|
3240
|
+
let payload2 = env["payload"];
|
|
3241
|
+
if (!payload2 || !_isObject4(payload2)) {
|
|
3242
|
+
payload2 = {};
|
|
3243
|
+
}
|
|
3244
|
+
const platRaw = env["platform"];
|
|
3245
|
+
const platform = String(platRaw || default_platform);
|
|
3246
|
+
return [env, payload2, platform];
|
|
3247
|
+
}
|
|
3248
|
+
const payload = _isObject4(decoded) ? decoded : {};
|
|
3249
|
+
return [
|
|
3250
|
+
{
|
|
3251
|
+
schema_version: 1,
|
|
3252
|
+
platform: default_platform,
|
|
3253
|
+
event: "",
|
|
3254
|
+
native_event: "",
|
|
3255
|
+
session_id: "",
|
|
3256
|
+
workspace_root: "",
|
|
3257
|
+
payload,
|
|
3258
|
+
settings: {}
|
|
3259
|
+
},
|
|
3260
|
+
payload,
|
|
3261
|
+
default_platform
|
|
3262
|
+
];
|
|
2814
3263
|
}
|
|
2815
3264
|
|
|
2816
3265
|
// src/scripts/hooks/subagent_ledger_hook.ts
|
|
2817
|
-
import * as
|
|
2818
|
-
import * as
|
|
2819
|
-
import * as
|
|
2820
|
-
import { fileURLToPath as
|
|
3266
|
+
import * as crypto from "node:crypto";
|
|
3267
|
+
import * as fs8 from "node:fs";
|
|
3268
|
+
import * as path7 from "node:path";
|
|
3269
|
+
import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL4 } from "node:url";
|
|
2821
3270
|
|
|
2822
3271
|
// src/scripts/_lib/subagent_response.ts
|
|
2823
3272
|
var MAX_SUMMARY_CHARS = 2e3;
|
|
@@ -2918,22 +3367,57 @@ function validateResponse(input) {
|
|
|
2918
3367
|
return { valid: errors.length === 0, errors };
|
|
2919
3368
|
}
|
|
2920
3369
|
|
|
2921
|
-
// src/scripts/hooks/
|
|
3370
|
+
// src/scripts/hooks/exit_codes.ts
|
|
2922
3371
|
var EXIT_ALLOW3 = 0;
|
|
3372
|
+
var EXIT_BLOCK = 1;
|
|
3373
|
+
var EXIT_WARN2 = 2;
|
|
3374
|
+
var EXIT_ERROR = 3;
|
|
3375
|
+
var EXIT_CODES = Object.freeze([
|
|
3376
|
+
Object.freeze({
|
|
3377
|
+
code: EXIT_ALLOW3,
|
|
3378
|
+
name: "allow",
|
|
3379
|
+
meaning: "no objection; the dispatcher passes the call through",
|
|
3380
|
+
owner: "every concern \u2014 the default verdict",
|
|
3381
|
+
authorizedBy: "nothing; allowing needs no authority"
|
|
3382
|
+
}),
|
|
3383
|
+
Object.freeze({
|
|
3384
|
+
code: EXIT_BLOCK,
|
|
3385
|
+
name: "block",
|
|
3386
|
+
meaning: "refuse the call; the dispatcher lowers this onto the host deny channel",
|
|
3387
|
+
owner: "a concern declared `severity: blocking` in hook_manifest.yaml",
|
|
3388
|
+
authorizedBy: "the manifest entry plus its row in the BLOCKING_ALLOWLIST of tests/hooks/concern_severity.test.ts \u2014 an advisory concern emitting this is downgraded to warn by dispatch_hook._is_advisory"
|
|
3389
|
+
}),
|
|
3390
|
+
Object.freeze({
|
|
3391
|
+
code: EXIT_WARN2,
|
|
3392
|
+
name: "warn",
|
|
3393
|
+
meaning: "advisory; the dispatcher exits 0 and surfaces the reason",
|
|
3394
|
+
owner: "any concern",
|
|
3395
|
+
authorizedBy: "nothing; warning is the ceiling for an advisory concern and is always available to a blocking one"
|
|
3396
|
+
}),
|
|
3397
|
+
Object.freeze({
|
|
3398
|
+
code: EXIT_ERROR,
|
|
3399
|
+
name: "error",
|
|
3400
|
+
meaning: "the concern crashed, timed out, or could not decide \u2014 any rc >= 3. The dispatcher resolves it by the concern`s declared severity, never by the number itself",
|
|
3401
|
+
owner: "no concern emits this on purpose; the runtime does",
|
|
3402
|
+
authorizedBy: "hook_manifest.yaml `severity` alone \u2014 blocking refuses, anything else fails open; `fail_closed` is no longer read on this branch. See docs/contracts/hook-architecture-v1.md \xA7 Exit-code semantics"
|
|
3403
|
+
})
|
|
3404
|
+
]);
|
|
3405
|
+
|
|
3406
|
+
// src/scripts/hooks/subagent_ledger_hook.ts
|
|
2923
3407
|
function isObject2(v) {
|
|
2924
3408
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2925
3409
|
}
|
|
2926
|
-
var LEDGER_DIR =
|
|
3410
|
+
var LEDGER_DIR = path7.join("agents", "runtime", "state", "subagent-ledger");
|
|
2927
3411
|
var OPEN_SUBDIR = "open";
|
|
2928
3412
|
var OPEN_RECORD_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2929
3413
|
function refFor(agentId) {
|
|
2930
|
-
return
|
|
3414
|
+
return crypto.createHash("sha256").update(agentId, "utf8").digest("hex").slice(0, 12);
|
|
2931
3415
|
}
|
|
2932
3416
|
function unwrapPayload(envelope) {
|
|
2933
3417
|
const inner = envelope["payload"];
|
|
2934
3418
|
return isObject2(inner) ? inner : envelope;
|
|
2935
3419
|
}
|
|
2936
|
-
function
|
|
3420
|
+
function str(payload, ...keys) {
|
|
2937
3421
|
for (const k of keys) {
|
|
2938
3422
|
const v = payload[k];
|
|
2939
3423
|
if (typeof v === "string" && v) return v;
|
|
@@ -2941,11 +3425,11 @@ function str2(payload, ...keys) {
|
|
|
2941
3425
|
return null;
|
|
2942
3426
|
}
|
|
2943
3427
|
function extractParentId(payload, ownId) {
|
|
2944
|
-
const explicit =
|
|
3428
|
+
const explicit = str(payload, "parent_agent_id", "parentAgentId");
|
|
2945
3429
|
if (explicit) return explicit;
|
|
2946
3430
|
const session = payload["session"];
|
|
2947
3431
|
if (isObject2(session)) {
|
|
2948
|
-
const sessionAgent =
|
|
3432
|
+
const sessionAgent = str(session, "agent_id", "agentId");
|
|
2949
3433
|
if (sessionAgent && sessionAgent !== ownId) return sessionAgent;
|
|
2950
3434
|
}
|
|
2951
3435
|
return null;
|
|
@@ -3013,10 +3497,10 @@ function _jsonObjectCandidates(message) {
|
|
|
3013
3497
|
return out;
|
|
3014
3498
|
}
|
|
3015
3499
|
function openDir(root) {
|
|
3016
|
-
return
|
|
3500
|
+
return path7.join(root, LEDGER_DIR, OPEN_SUBDIR);
|
|
3017
3501
|
}
|
|
3018
3502
|
function openFile(root, ref) {
|
|
3019
|
-
return
|
|
3503
|
+
return path7.join(openDir(root), `${ref}.json`);
|
|
3020
3504
|
}
|
|
3021
3505
|
var DEPTH_BASES = /* @__PURE__ */ new Set(["observed", "asserted-parent", "assumed-root"]);
|
|
3022
3506
|
function _asOpenRecord(value, expectedRef) {
|
|
@@ -3043,7 +3527,7 @@ function readOpenRecords(root) {
|
|
|
3043
3527
|
const out = /* @__PURE__ */ new Map();
|
|
3044
3528
|
let names;
|
|
3045
3529
|
try {
|
|
3046
|
-
names =
|
|
3530
|
+
names = fs8.readdirSync(openDir(root));
|
|
3047
3531
|
} catch {
|
|
3048
3532
|
return out;
|
|
3049
3533
|
}
|
|
@@ -3051,7 +3535,7 @@ function readOpenRecords(root) {
|
|
|
3051
3535
|
if (!name.endsWith(".json")) continue;
|
|
3052
3536
|
const expectedRef = name.slice(0, -".json".length);
|
|
3053
3537
|
try {
|
|
3054
|
-
const raw =
|
|
3538
|
+
const raw = fs8.readFileSync(path7.join(openDir(root), name), "utf8");
|
|
3055
3539
|
const rec = _asOpenRecord(JSON.parse(raw), expectedRef);
|
|
3056
3540
|
if (rec) out.set(rec.ref, rec);
|
|
3057
3541
|
} catch {
|
|
@@ -3066,13 +3550,13 @@ function resolveDepth(parentRef, open) {
|
|
|
3066
3550
|
return { depth: parent.depth + 1, depth_basis: "observed" };
|
|
3067
3551
|
}
|
|
3068
3552
|
function ledgerFileFor2(root, ts) {
|
|
3069
|
-
return
|
|
3553
|
+
return path7.join(root, LEDGER_DIR, `${ts.slice(0, 7)}.jsonl`);
|
|
3070
3554
|
}
|
|
3071
3555
|
function appendLedgerLine(root, ts, line) {
|
|
3072
3556
|
if (is_replay_mode()) return;
|
|
3073
3557
|
const file = ledgerFileFor2(root, ts);
|
|
3074
|
-
|
|
3075
|
-
|
|
3558
|
+
fs8.mkdirSync(path7.dirname(file), { recursive: true });
|
|
3559
|
+
fs8.appendFileSync(file, `${JSON.stringify(line)}
|
|
3076
3560
|
`, "utf8");
|
|
3077
3561
|
}
|
|
3078
3562
|
var WALL_CLOCK_ARMS_MS = [
|
|
@@ -3086,7 +3570,7 @@ function armsExceeded(elapsedMs) {
|
|
|
3086
3570
|
return WALL_CLOCK_ARMS_MS.filter((a) => elapsedMs >= a.ms).map((a) => a.label);
|
|
3087
3571
|
}
|
|
3088
3572
|
function openRecordStats(root, nowMs = Date.now()) {
|
|
3089
|
-
const ledgerPresent =
|
|
3573
|
+
const ledgerPresent = fs8.existsSync(path7.join(root, LEDGER_DIR));
|
|
3090
3574
|
const open = readOpenRecords(root);
|
|
3091
3575
|
let maxDepth = 0;
|
|
3092
3576
|
let counted = 0;
|
|
@@ -3114,13 +3598,13 @@ function writeOpenRecord(root, rec) {
|
|
|
3114
3598
|
function removeOpenRecord(root, ref) {
|
|
3115
3599
|
if (is_replay_mode()) return;
|
|
3116
3600
|
try {
|
|
3117
|
-
|
|
3601
|
+
fs8.unlinkSync(openFile(root, ref));
|
|
3118
3602
|
} catch {
|
|
3119
3603
|
}
|
|
3120
3604
|
}
|
|
3121
3605
|
function countOpen(root) {
|
|
3122
3606
|
try {
|
|
3123
|
-
return
|
|
3607
|
+
return fs8.readdirSync(openDir(root)).filter((n) => n.endsWith(".json")).length;
|
|
3124
3608
|
} catch {
|
|
3125
3609
|
return 0;
|
|
3126
3610
|
}
|
|
@@ -3161,12 +3645,12 @@ function appendUnidentified(root, event, nowIso, payload) {
|
|
|
3161
3645
|
unidentified: true,
|
|
3162
3646
|
// The agent TYPE is an id-shaped enum and may still be present; it is
|
|
3163
3647
|
// the only thing salvageable from a payload with no id.
|
|
3164
|
-
agent_type:
|
|
3648
|
+
agent_type: str(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
|
|
3165
3649
|
reason: "payload carried no agent id \u2014 nothing to correlate start with stop"
|
|
3166
3650
|
});
|
|
3167
3651
|
}
|
|
3168
3652
|
function handleStart(root, payload, nowIso, sessionId) {
|
|
3169
|
-
const agentId =
|
|
3653
|
+
const agentId = str(payload, "agent_id", "agentId");
|
|
3170
3654
|
if (agentId === null) {
|
|
3171
3655
|
appendUnidentified(root, "subagent_start", nowIso, payload);
|
|
3172
3656
|
return;
|
|
@@ -3177,7 +3661,7 @@ function handleStart(root, payload, nowIso, sessionId) {
|
|
|
3177
3661
|
const { depth, depth_basis } = resolveDepth(parentRef, readOpenRecords(root));
|
|
3178
3662
|
const rec = {
|
|
3179
3663
|
ref,
|
|
3180
|
-
agent_type:
|
|
3664
|
+
agent_type: str(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
|
|
3181
3665
|
started_at: nowIso,
|
|
3182
3666
|
parent_ref: parentRef,
|
|
3183
3667
|
depth,
|
|
@@ -3204,7 +3688,7 @@ function handleStart(root, payload, nowIso, sessionId) {
|
|
|
3204
3688
|
});
|
|
3205
3689
|
}
|
|
3206
3690
|
function handleStop(root, payload, nowIso, sessionId) {
|
|
3207
|
-
const agentId =
|
|
3691
|
+
const agentId = str(payload, "agent_id", "agentId");
|
|
3208
3692
|
if (agentId === null) {
|
|
3209
3693
|
appendUnidentified(root, "subagent_stop", nowIso, payload);
|
|
3210
3694
|
return;
|
|
@@ -3216,7 +3700,7 @@ function handleStop(root, payload, nowIso, sessionId) {
|
|
|
3216
3700
|
const started = Date.parse(rec.started_at);
|
|
3217
3701
|
if (Number.isFinite(started)) durationMs = Math.max(0, Date.parse(nowIso) - started);
|
|
3218
3702
|
}
|
|
3219
|
-
const parse2 = classifyEnvelope(
|
|
3703
|
+
const parse2 = classifyEnvelope(str(payload, "last_assistant_message", "lastAssistantMessage"));
|
|
3220
3704
|
removeOpenRecord(root, ref);
|
|
3221
3705
|
appendLedgerLine(root, nowIso, {
|
|
3222
3706
|
event: "subagent_stop",
|
|
@@ -3227,7 +3711,7 @@ function handleStop(root, payload, nowIso, sessionId) {
|
|
|
3227
3711
|
// relabel a cross-session stop as belonging to the dispatching session
|
|
3228
3712
|
// and hide exactly the mismatch this field exists to expose.
|
|
3229
3713
|
session_id: sessionId,
|
|
3230
|
-
agent_type: rec?.agent_type ??
|
|
3714
|
+
agent_type: rec?.agent_type ?? str(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
|
|
3231
3715
|
depth: rec?.depth ?? null,
|
|
3232
3716
|
depth_basis: rec?.depth_basis ?? null,
|
|
3233
3717
|
// `null` = the matching start was never seen. That is a finding about
|
|
@@ -3252,7 +3736,7 @@ function processEnvelope(envelope, consumerRoot) {
|
|
|
3252
3736
|
if (event !== "subagent_start" && event !== "subagent_stop") return EXIT_ALLOW3;
|
|
3253
3737
|
const payload = unwrapPayload(envelope);
|
|
3254
3738
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
3255
|
-
const sessionId =
|
|
3739
|
+
const sessionId = str(payload, "session_id", "sessionId") ?? str(envelope, "session_id", "sessionId");
|
|
3256
3740
|
reapStaleOpenRecords(consumerRoot, nowIso);
|
|
3257
3741
|
if (event === "subagent_start") handleStart(consumerRoot, payload, nowIso, sessionId);
|
|
3258
3742
|
else handleStop(consumerRoot, payload, nowIso, sessionId);
|
|
@@ -3263,42 +3747,599 @@ function processEnvelope(envelope, consumerRoot) {
|
|
|
3263
3747
|
}
|
|
3264
3748
|
function resolveConsumerRoot(envelope) {
|
|
3265
3749
|
if (isObject2(envelope)) {
|
|
3266
|
-
const pr =
|
|
3750
|
+
const pr = str(envelope, "workspace_root", "project_root");
|
|
3267
3751
|
if (pr) return pr;
|
|
3268
3752
|
const payload = unwrapPayload(envelope);
|
|
3269
|
-
const cwd =
|
|
3753
|
+
const cwd = str(payload, "cwd");
|
|
3270
3754
|
if (cwd) return cwd;
|
|
3271
3755
|
}
|
|
3272
|
-
return process.cwd();
|
|
3756
|
+
return process.cwd();
|
|
3757
|
+
}
|
|
3758
|
+
function main4() {
|
|
3759
|
+
const raw = readHookStdin();
|
|
3760
|
+
let envelope = {};
|
|
3761
|
+
try {
|
|
3762
|
+
envelope = raw.trim() ? JSON.parse(raw) : {};
|
|
3763
|
+
} catch {
|
|
3764
|
+
return EXIT_ALLOW3;
|
|
3765
|
+
}
|
|
3766
|
+
return processEnvelope(envelope, resolveConsumerRoot(envelope));
|
|
3767
|
+
}
|
|
3768
|
+
function _isCliEntry4() {
|
|
3769
|
+
if (true) {
|
|
3770
|
+
return false;
|
|
3771
|
+
}
|
|
3772
|
+
if (process.argv[1] === void 0) return false;
|
|
3773
|
+
const argvUrl = pathToFileURL4(path7.resolve(process.argv[1])).href;
|
|
3774
|
+
if (import.meta.url === argvUrl) return true;
|
|
3775
|
+
try {
|
|
3776
|
+
return fs8.realpathSync(fileURLToPath4(import.meta.url)) === fs8.realpathSync(path7.resolve(process.argv[1]));
|
|
3777
|
+
} catch {
|
|
3778
|
+
return false;
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
if (_isCliEntry4()) process.exit(main4());
|
|
3782
|
+
|
|
3783
|
+
// src/scripts/_lib/turn_end_transcript.ts
|
|
3784
|
+
import * as fs11 from "node:fs";
|
|
3785
|
+
|
|
3786
|
+
// src/scripts/hooks/end_review_nudge_hook.ts
|
|
3787
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
3788
|
+
import * as crypto2 from "node:crypto";
|
|
3789
|
+
import * as fs10 from "node:fs";
|
|
3790
|
+
import * as os from "node:os";
|
|
3791
|
+
import * as path9 from "node:path";
|
|
3792
|
+
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
|
|
3793
|
+
|
|
3794
|
+
// src/scripts/_lib/review_baseline.ts
|
|
3795
|
+
import { spawnSync } from "node:child_process";
|
|
3796
|
+
import * as fs9 from "node:fs";
|
|
3797
|
+
import * as path8 from "node:path";
|
|
3798
|
+
function currentHeadSha(cwd) {
|
|
3799
|
+
try {
|
|
3800
|
+
const r = spawnSync("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" });
|
|
3801
|
+
if (r.status !== 0) return null;
|
|
3802
|
+
const out = (r.stdout ?? "").trim();
|
|
3803
|
+
return out === "" ? null : out;
|
|
3804
|
+
} catch {
|
|
3805
|
+
return null;
|
|
3806
|
+
}
|
|
3807
|
+
}
|
|
3808
|
+
var BASELINE_STATE_REL = path8.join("agents", "runtime", "state", "review-baseline");
|
|
3809
|
+
function baselineStateDir(workspaceRoot) {
|
|
3810
|
+
return path8.join(workspaceRoot, BASELINE_STATE_REL);
|
|
3811
|
+
}
|
|
3812
|
+
function baselineStateFile(workspaceRoot, sessionKey) {
|
|
3813
|
+
return path8.join(baselineStateDir(workspaceRoot), `${sessionKey}.json`);
|
|
3814
|
+
}
|
|
3815
|
+
function parseBaseline(raw) {
|
|
3816
|
+
let decoded;
|
|
3817
|
+
try {
|
|
3818
|
+
decoded = JSON.parse(raw);
|
|
3819
|
+
} catch {
|
|
3820
|
+
return null;
|
|
3821
|
+
}
|
|
3822
|
+
if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
|
|
3823
|
+
const o = decoded;
|
|
3824
|
+
const lines = o["baseline_lines"];
|
|
3825
|
+
if (typeof lines !== "number" || !Number.isFinite(lines) || lines < 0) return null;
|
|
3826
|
+
const written = o["written_at"];
|
|
3827
|
+
if (typeof written !== "string") return null;
|
|
3828
|
+
const head = o["head_sha"];
|
|
3829
|
+
const measure = o["measure"];
|
|
3830
|
+
return {
|
|
3831
|
+
head_sha: typeof head === "string" && head !== "" ? head : null,
|
|
3832
|
+
baseline_lines: lines,
|
|
3833
|
+
...measure === "exact" || measure === "capped_approximation" ? { measure } : {},
|
|
3834
|
+
written_at: written
|
|
3835
|
+
};
|
|
3836
|
+
}
|
|
3837
|
+
function readBaseline(workspaceRoot, sessionKey) {
|
|
3838
|
+
const file = baselineStateFile(workspaceRoot, sessionKey);
|
|
3839
|
+
let raw;
|
|
3840
|
+
try {
|
|
3841
|
+
raw = fs9.readFileSync(file, "utf-8");
|
|
3842
|
+
} catch (exc) {
|
|
3843
|
+
const code = exc?.code;
|
|
3844
|
+
return code === "ENOENT" ? "absent" : "unreadable";
|
|
3845
|
+
}
|
|
3846
|
+
return parseBaseline(raw) ?? "unreadable";
|
|
3847
|
+
}
|
|
3848
|
+
function applyBaseline(measuredLines, baseline, headSha, measuredMeasure = "exact") {
|
|
3849
|
+
if (baseline === "absent" || baseline === "unreadable") {
|
|
3850
|
+
return { applied: false, fallback: baseline, lines: measuredLines };
|
|
3851
|
+
}
|
|
3852
|
+
if (baseline.measure !== "exact" || measuredMeasure !== "exact") {
|
|
3853
|
+
return { applied: false, fallback: "mixed-measure", lines: measuredLines };
|
|
3854
|
+
}
|
|
3855
|
+
if (baseline.head_sha !== headSha()) {
|
|
3856
|
+
return { applied: false, fallback: "head-moved", lines: measuredLines };
|
|
3857
|
+
}
|
|
3858
|
+
return {
|
|
3859
|
+
applied: true,
|
|
3860
|
+
baseline,
|
|
3861
|
+
lines: Math.max(0, measuredLines - baseline.baseline_lines)
|
|
3862
|
+
};
|
|
3863
|
+
}
|
|
3864
|
+
|
|
3865
|
+
// src/scripts/_lib/audit_field_provenance.ts
|
|
3866
|
+
var MAX_APPLIED_IDS = 32;
|
|
3867
|
+
function appliedIds(values) {
|
|
3868
|
+
const out = [];
|
|
3869
|
+
for (const raw of values ?? []) {
|
|
3870
|
+
const v = String(raw).trim();
|
|
3871
|
+
if (v === "" || out.includes(v)) continue;
|
|
3872
|
+
out.push(v);
|
|
3873
|
+
if (out.length >= MAX_APPLIED_IDS) break;
|
|
3874
|
+
}
|
|
3875
|
+
return out;
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3878
|
+
// src/scripts/_lib/review_skipped_record.ts
|
|
3879
|
+
var BASELINE_APPLICATIONS = [
|
|
3880
|
+
"applied",
|
|
3881
|
+
"absent",
|
|
3882
|
+
"unreadable",
|
|
3883
|
+
"head_moved",
|
|
3884
|
+
"mixed_measure"
|
|
3885
|
+
];
|
|
3886
|
+
var PRODUCER_PRIVACY_CLASS = "ids-only";
|
|
3887
|
+
function isNonNegInt(n) {
|
|
3888
|
+
return typeof n === "number" && Number.isFinite(n) && Number.isInteger(n) && n >= 0;
|
|
3889
|
+
}
|
|
3890
|
+
var HIGH_RISK_DIFF_LINES = 200;
|
|
3891
|
+
function buildReviewSkippedLine(input) {
|
|
3892
|
+
const errors = [];
|
|
3893
|
+
if (!isNonNegInt(input.diff_lines)) {
|
|
3894
|
+
errors.push("diff_lines must be a non-negative integer");
|
|
3895
|
+
}
|
|
3896
|
+
if (input.mutation_measure !== "exact" && input.mutation_measure !== "capped_approximation") {
|
|
3897
|
+
errors.push('mutation_measure must be "exact" or "capped_approximation"');
|
|
3898
|
+
}
|
|
3899
|
+
if (!input.ts) errors.push("ts (ISO-8601 UTC) is required");
|
|
3900
|
+
if (!input.id) errors.push("id (ULID, UUID, or content hash) is required");
|
|
3901
|
+
if (input.baseline !== void 0 && !BASELINE_APPLICATIONS.includes(input.baseline)) {
|
|
3902
|
+
errors.push(`baseline must be one of ${BASELINE_APPLICATIONS.join(", ")}`);
|
|
3903
|
+
}
|
|
3904
|
+
if (errors.length) return { line: null, errors };
|
|
3905
|
+
const line = {
|
|
3906
|
+
schema_version: 1,
|
|
3907
|
+
id: input.id,
|
|
3908
|
+
ts: input.ts,
|
|
3909
|
+
work_id: input.work_id ?? `review-skipped-${input.ts}`,
|
|
3910
|
+
phase: "report",
|
|
3911
|
+
outcome: "skipped",
|
|
3912
|
+
confidence_band: "medium",
|
|
3913
|
+
risk_class: input.diff_lines >= HIGH_RISK_DIFF_LINES ? "high" : "medium",
|
|
3914
|
+
memory: { asks: 0, hits: 0 },
|
|
3915
|
+
verify: { claims: 0, first_try_passes: 0 },
|
|
3916
|
+
// COMPUTED, never a producer constant.
|
|
3917
|
+
rules_applied: appliedIds(input.rules_applied),
|
|
3918
|
+
// MANDATORY on every audit line, same as the orchestration producer.
|
|
3919
|
+
privacy_class: PRODUCER_PRIVACY_CLASS,
|
|
3920
|
+
// `skills_applied` is OMITTED here, deliberately and not by oversight.
|
|
3921
|
+
// audit-log-v1 distinguishes an absent key ("not recorded") from `[]`
|
|
3922
|
+
// ("recorded, none applied"), and this writer observes a review that
|
|
3923
|
+
// did not happen -- it has no skill observation to offer in either
|
|
3924
|
+
// direction. Emitting `[]` would assert "no skills applied" on evidence
|
|
3925
|
+
// this producer never gathered, which is the exact fabrication the
|
|
3926
|
+
// absent/empty split exists to make impossible.
|
|
3927
|
+
persona: null,
|
|
3928
|
+
input_kind: "prompt",
|
|
3929
|
+
type: "note",
|
|
3930
|
+
review_skipped: {
|
|
3931
|
+
diff_lines: input.diff_lines,
|
|
3932
|
+
mutation_measure: input.mutation_measure,
|
|
3933
|
+
// Omitted, not defaulted, when the producer recorded nothing —
|
|
3934
|
+
// audit-log-v1's absent-versus-present split is the same one
|
|
3935
|
+
// `skills_applied` uses above, for the same reason.
|
|
3936
|
+
...input.baseline === void 0 ? {} : { baseline: input.baseline }
|
|
3937
|
+
}
|
|
3938
|
+
};
|
|
3939
|
+
return { line, errors: [] };
|
|
3940
|
+
}
|
|
3941
|
+
|
|
3942
|
+
// src/scripts/hooks/end_review_nudge_hook.ts
|
|
3943
|
+
var MUTATION_LINE_THRESHOLD = 50;
|
|
3944
|
+
var UNTRACKED_FILE_CAP = 20;
|
|
3945
|
+
var TRANSCRIPT_SCAN_MAX_BYTES = 50 * 1024 * 1024;
|
|
3946
|
+
function isSafeTranscriptPath(rawPath, opts = {}) {
|
|
3947
|
+
if (!rawPath || !/\.jsonl$/i.test(rawPath)) return false;
|
|
3948
|
+
const homeDir = opts.homeDir ?? os.homedir();
|
|
3949
|
+
const maxBytes = opts.maxBytes ?? TRANSCRIPT_SCAN_MAX_BYTES;
|
|
3950
|
+
try {
|
|
3951
|
+
const realHome = fs10.realpathSync(homeDir);
|
|
3952
|
+
const realPath = fs10.realpathSync(rawPath);
|
|
3953
|
+
const relative5 = path9.relative(realHome, realPath);
|
|
3954
|
+
if (relative5 === "" || relative5.startsWith("..") || path9.isAbsolute(relative5)) {
|
|
3955
|
+
return false;
|
|
3956
|
+
}
|
|
3957
|
+
const { size } = fs10.statSync(realPath);
|
|
3958
|
+
return size <= maxBytes;
|
|
3959
|
+
} catch {
|
|
3960
|
+
return false;
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
function parseNumstat(raw) {
|
|
3964
|
+
const rows = [];
|
|
3965
|
+
for (const line of raw.split("\n")) {
|
|
3966
|
+
const m = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line);
|
|
3967
|
+
if (!m) continue;
|
|
3968
|
+
const addedStr = m[1];
|
|
3969
|
+
const deletedStr = m[2];
|
|
3970
|
+
rows.push({
|
|
3971
|
+
added: addedStr === "-" ? 0 : Number(addedStr),
|
|
3972
|
+
deleted: deletedStr === "-" ? 0 : Number(deletedStr),
|
|
3973
|
+
path: m[3]
|
|
3974
|
+
});
|
|
3975
|
+
}
|
|
3976
|
+
return rows;
|
|
3977
|
+
}
|
|
3978
|
+
var DOC_PATH_RE = /\.md$/i;
|
|
3979
|
+
function isDocPath(p) {
|
|
3980
|
+
return DOC_PATH_RE.test(p);
|
|
3981
|
+
}
|
|
3982
|
+
function nonDocMutatedLines(rows) {
|
|
3983
|
+
return rows.filter((r) => !isDocPath(r.path)).reduce((sum, r) => sum + r.added + r.deleted, 0);
|
|
3984
|
+
}
|
|
3985
|
+
function gitNumstatRows(cwd) {
|
|
3986
|
+
try {
|
|
3987
|
+
const r = spawnSync2("git", ["diff", "--numstat", "HEAD"], {
|
|
3988
|
+
cwd,
|
|
3989
|
+
encoding: "utf8",
|
|
3990
|
+
maxBuffer: 16 * 1024 * 1024
|
|
3991
|
+
});
|
|
3992
|
+
if (r.status !== 0) return [];
|
|
3993
|
+
return parseNumstat(r.stdout ?? "");
|
|
3994
|
+
} catch {
|
|
3995
|
+
return [];
|
|
3996
|
+
}
|
|
3997
|
+
}
|
|
3998
|
+
function untrackedNonDocFiles(cwd) {
|
|
3999
|
+
try {
|
|
4000
|
+
const r = spawnSync2("git", ["ls-files", "--others", "--exclude-standard"], {
|
|
4001
|
+
cwd,
|
|
4002
|
+
encoding: "utf8",
|
|
4003
|
+
maxBuffer: 16 * 1024 * 1024
|
|
4004
|
+
});
|
|
4005
|
+
if (r.status !== 0) return [];
|
|
4006
|
+
return (r.stdout ?? "").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !isDocPath(l));
|
|
4007
|
+
} catch {
|
|
4008
|
+
return [];
|
|
4009
|
+
}
|
|
4010
|
+
}
|
|
4011
|
+
var BINARY_PROBE_BYTES = 8e3;
|
|
4012
|
+
var UNTRACKED_FILE_READ_CAP_BYTES = 8 * 1024 * 1024;
|
|
4013
|
+
function untrackedFileLineCount(cwd, relPath) {
|
|
4014
|
+
try {
|
|
4015
|
+
const abs = path9.resolve(cwd, relPath);
|
|
4016
|
+
const size = fs10.statSync(abs).size;
|
|
4017
|
+
if (size === 0) return 0;
|
|
4018
|
+
if (size > UNTRACKED_FILE_READ_CAP_BYTES) {
|
|
4019
|
+
return MUTATION_LINE_THRESHOLD + 1;
|
|
4020
|
+
}
|
|
4021
|
+
const buf = fs10.readFileSync(abs);
|
|
4022
|
+
if (buf.length === 0) return 0;
|
|
4023
|
+
const probe = buf.subarray(0, Math.min(buf.length, BINARY_PROBE_BYTES));
|
|
4024
|
+
if (probe.includes(0)) return 0;
|
|
4025
|
+
let lines = 0;
|
|
4026
|
+
for (const byte of buf) {
|
|
4027
|
+
if (byte === 10) lines += 1;
|
|
4028
|
+
}
|
|
4029
|
+
if (buf[buf.length - 1] !== 10) lines += 1;
|
|
4030
|
+
return lines;
|
|
4031
|
+
} catch {
|
|
4032
|
+
return 0;
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
function totalNonDocMutatedLinesWithMeasure(cwd) {
|
|
4036
|
+
const trackedLines = nonDocMutatedLines(gitNumstatRows(cwd));
|
|
4037
|
+
const untracked = untrackedNonDocFiles(cwd);
|
|
4038
|
+
if (untracked.length > UNTRACKED_FILE_CAP) {
|
|
4039
|
+
return { lines: MUTATION_LINE_THRESHOLD + 1 + trackedLines, measure: "capped_approximation" };
|
|
4040
|
+
}
|
|
4041
|
+
const untrackedLines = untracked.reduce(
|
|
4042
|
+
(sum, relPath) => sum + untrackedFileLineCount(cwd, relPath),
|
|
4043
|
+
0
|
|
4044
|
+
);
|
|
4045
|
+
return { lines: trackedLines + untrackedLines, measure: "exact" };
|
|
4046
|
+
}
|
|
4047
|
+
function isObject3(v) {
|
|
4048
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4049
|
+
}
|
|
4050
|
+
function str2(v) {
|
|
4051
|
+
return typeof v === "string" ? v : "";
|
|
4052
|
+
}
|
|
4053
|
+
var DISPATCH_TOOL_RE = /^(agent|task)$/i;
|
|
4054
|
+
var REVIEW_SHAPED_LABEL_RE = /\b(review|judge|adversarial|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
|
|
4055
|
+
var REVIEW_SHAPED_PROMPT_RE = /\b(code\s+review|neutral\s+review|adversarial|judge|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
|
|
4056
|
+
var REVIEW_SKILL_NAME_RE = /^(review[-:]changes|judge[-:][a-z0-9-]+|adversarial-review|code-review)$/i;
|
|
4057
|
+
function scanTranscriptForReviewer(transcriptText) {
|
|
4058
|
+
for (const raw of transcriptText.split("\n")) {
|
|
4059
|
+
const line = raw.trim();
|
|
4060
|
+
if (!line) continue;
|
|
4061
|
+
let obj;
|
|
4062
|
+
try {
|
|
4063
|
+
obj = JSON.parse(line);
|
|
4064
|
+
} catch {
|
|
4065
|
+
continue;
|
|
4066
|
+
}
|
|
4067
|
+
if (!isObject3(obj) || obj["type"] !== "assistant") continue;
|
|
4068
|
+
const msg = obj["message"];
|
|
4069
|
+
if (!isObject3(msg)) continue;
|
|
4070
|
+
const content = msg["content"];
|
|
4071
|
+
if (!Array.isArray(content)) continue;
|
|
4072
|
+
for (const blk of content) {
|
|
4073
|
+
if (!isObject3(blk) || blk["type"] !== "tool_use") continue;
|
|
4074
|
+
const name = str2(blk["name"]);
|
|
4075
|
+
const input = blk["input"];
|
|
4076
|
+
const inputObj = isObject3(input) ? input : {};
|
|
4077
|
+
if (DISPATCH_TOOL_RE.test(name)) {
|
|
4078
|
+
const label = [
|
|
4079
|
+
str2(inputObj["description"]),
|
|
4080
|
+
str2(inputObj["subagent_type"])
|
|
4081
|
+
].filter(Boolean).join(" ");
|
|
4082
|
+
if (REVIEW_SHAPED_LABEL_RE.test(label)) return true;
|
|
4083
|
+
const prompt = str2(inputObj["prompt"]);
|
|
4084
|
+
if (REVIEW_SHAPED_PROMPT_RE.test(prompt)) return true;
|
|
4085
|
+
continue;
|
|
4086
|
+
}
|
|
4087
|
+
if (/^skill$/i.test(name)) {
|
|
4088
|
+
const skillName = str2(inputObj["skill"]) || str2(inputObj["name"]) || str2(inputObj["command"]);
|
|
4089
|
+
if (REVIEW_SKILL_NAME_RE.test(skillName)) return true;
|
|
4090
|
+
continue;
|
|
4091
|
+
}
|
|
4092
|
+
if (REVIEW_SKILL_NAME_RE.test(name)) return true;
|
|
4093
|
+
}
|
|
4094
|
+
}
|
|
4095
|
+
return false;
|
|
4096
|
+
}
|
|
4097
|
+
function buildAdvisoryLine(diffLines) {
|
|
4098
|
+
return `this session mutated ${diffLines} lines without a neutral review; spawn a cross-model reviewer before claiming done (delegation-policy / verify-budget)`;
|
|
4099
|
+
}
|
|
4100
|
+
function appendReviewSkippedTelemetry(workspaceRoot, diffLines, mutationMeasure, baseline) {
|
|
4101
|
+
if (is_replay_mode()) return;
|
|
4102
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
4103
|
+
const { line, errors } = buildReviewSkippedLine({
|
|
4104
|
+
diff_lines: diffLines,
|
|
4105
|
+
mutation_measure: mutationMeasure,
|
|
4106
|
+
ts,
|
|
4107
|
+
id: crypto2.randomUUID(),
|
|
4108
|
+
baseline
|
|
4109
|
+
});
|
|
4110
|
+
if (errors.length || line === null) return;
|
|
4111
|
+
try {
|
|
4112
|
+
const dir = path9.join(workspaceRoot, "agents", "runtime", "state", "audit");
|
|
4113
|
+
fs10.mkdirSync(dir, { recursive: true });
|
|
4114
|
+
const file = path9.join(dir, `${ts.slice(0, 7)}.jsonl`);
|
|
4115
|
+
fs10.appendFileSync(file, `${JSON.stringify(line)}
|
|
4116
|
+
`, "utf8");
|
|
4117
|
+
} catch {
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4120
|
+
function sessionStateDir(workspaceRoot) {
|
|
4121
|
+
return path9.join(workspaceRoot, "agents", "runtime", "state", "end-review-nudge");
|
|
4122
|
+
}
|
|
4123
|
+
function sessionStateFile(workspaceRoot, sessionKey) {
|
|
4124
|
+
return path9.join(sessionStateDir(workspaceRoot), `${sessionKey}.json`);
|
|
4125
|
+
}
|
|
4126
|
+
function deriveSessionKey2(envelope, payload) {
|
|
4127
|
+
const raw = str2(envelope["session_id"]) || str2(payload["transcript_path"] ?? payload["transcriptPath"]);
|
|
4128
|
+
const material = raw || "unknown-session";
|
|
4129
|
+
return crypto2.createHash("sha256").update(material).digest("hex");
|
|
4130
|
+
}
|
|
4131
|
+
function hasFiredThisSession(workspaceRoot, sessionKey) {
|
|
4132
|
+
try {
|
|
4133
|
+
return fs10.existsSync(sessionStateFile(workspaceRoot, sessionKey));
|
|
4134
|
+
} catch {
|
|
4135
|
+
return false;
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
4138
|
+
function markFiredThisSession(workspaceRoot, sessionKey, ts) {
|
|
4139
|
+
try {
|
|
4140
|
+
atomic_write_json(sessionStateFile(workspaceRoot, sessionKey), { fired_at: ts });
|
|
4141
|
+
} catch {
|
|
4142
|
+
}
|
|
4143
|
+
}
|
|
4144
|
+
function _read_stdin() {
|
|
4145
|
+
return readHookStdin();
|
|
4146
|
+
}
|
|
4147
|
+
var FALLBACK_TO_TELEMETRY = {
|
|
4148
|
+
absent: "absent",
|
|
4149
|
+
unreadable: "unreadable",
|
|
4150
|
+
"head-moved": "head_moved",
|
|
4151
|
+
"mixed-measure": "mixed_measure"
|
|
4152
|
+
};
|
|
4153
|
+
function main5() {
|
|
4154
|
+
const [envelope, payload] = unwrap(_read_stdin(), "claude");
|
|
4155
|
+
const event = String(envelope["event"] ?? "");
|
|
4156
|
+
if (event !== "" && event !== "stop") {
|
|
4157
|
+
return 0;
|
|
4158
|
+
}
|
|
4159
|
+
const workspace_root = String(envelope["workspace_root"] ?? "").trim() || process.cwd();
|
|
4160
|
+
let measuredLines;
|
|
4161
|
+
let mutationMeasure;
|
|
4162
|
+
try {
|
|
4163
|
+
const measured = totalNonDocMutatedLinesWithMeasure(workspace_root);
|
|
4164
|
+
measuredLines = measured.lines;
|
|
4165
|
+
mutationMeasure = measured.measure;
|
|
4166
|
+
} catch {
|
|
4167
|
+
return 0;
|
|
4168
|
+
}
|
|
4169
|
+
const sessionKey = deriveSessionKey2(envelope, payload);
|
|
4170
|
+
const baselineOutcome = applyBaseline(
|
|
4171
|
+
measuredLines,
|
|
4172
|
+
readBaseline(workspace_root, sessionKey),
|
|
4173
|
+
// A THUNK, not a value: on the `absent` and `unreadable` branches the
|
|
4174
|
+
// result is discarded, and those two are the steady state on every host
|
|
4175
|
+
// without this nudge and on every session before its first baseline
|
|
4176
|
+
// write. Passing the value eagerly spent an 11-14 ms `git rev-parse` on
|
|
4177
|
+
// each of those stops for nothing.
|
|
4178
|
+
() => currentHeadSha(workspace_root),
|
|
4179
|
+
mutationMeasure
|
|
4180
|
+
);
|
|
4181
|
+
const diffLines = baselineOutcome.lines;
|
|
4182
|
+
const baselineApplication = baselineOutcome.applied ? "applied" : FALLBACK_TO_TELEMETRY[baselineOutcome.fallback];
|
|
4183
|
+
if (diffLines <= MUTATION_LINE_THRESHOLD) {
|
|
4184
|
+
return 0;
|
|
4185
|
+
}
|
|
4186
|
+
if (hasFiredThisSession(workspace_root, sessionKey)) {
|
|
4187
|
+
return 0;
|
|
4188
|
+
}
|
|
4189
|
+
const transcriptPath = str2(
|
|
4190
|
+
payload["transcript_path"] ?? payload["transcriptPath"]
|
|
4191
|
+
).trim();
|
|
4192
|
+
let reviewerRan = false;
|
|
4193
|
+
if (transcriptPath && isSafeTranscriptPath(transcriptPath)) {
|
|
4194
|
+
try {
|
|
4195
|
+
reviewerRan = scanTranscriptForReviewer(fs10.readFileSync(transcriptPath, "utf-8"));
|
|
4196
|
+
} catch {
|
|
4197
|
+
reviewerRan = false;
|
|
4198
|
+
}
|
|
4199
|
+
}
|
|
4200
|
+
if (reviewerRan) {
|
|
4201
|
+
return 0;
|
|
4202
|
+
}
|
|
4203
|
+
markFiredThisSession(workspace_root, sessionKey, (/* @__PURE__ */ new Date()).toISOString());
|
|
4204
|
+
try {
|
|
4205
|
+
appendReviewSkippedTelemetry(
|
|
4206
|
+
workspace_root,
|
|
4207
|
+
diffLines,
|
|
4208
|
+
mutationMeasure,
|
|
4209
|
+
baselineApplication
|
|
4210
|
+
);
|
|
4211
|
+
} catch {
|
|
4212
|
+
}
|
|
4213
|
+
process.stdout.write(
|
|
4214
|
+
`${JSON.stringify({
|
|
4215
|
+
decision: "warn",
|
|
4216
|
+
reason: `end-review-nudge: session mutated ${diffLines} non-doc lines without a neutral review`,
|
|
4217
|
+
additional_context: buildAdvisoryLine(diffLines)
|
|
4218
|
+
})}
|
|
4219
|
+
`
|
|
4220
|
+
);
|
|
4221
|
+
return EXIT_WARN2;
|
|
4222
|
+
}
|
|
4223
|
+
function _isCliEntry5() {
|
|
4224
|
+
if (true) {
|
|
4225
|
+
return false;
|
|
4226
|
+
}
|
|
4227
|
+
if (process.argv[1] === void 0) {
|
|
4228
|
+
return false;
|
|
4229
|
+
}
|
|
4230
|
+
const argvUrl = pathToFileURL5(path9.resolve(process.argv[1])).href;
|
|
4231
|
+
if (import.meta.url === argvUrl) {
|
|
4232
|
+
return true;
|
|
4233
|
+
}
|
|
4234
|
+
try {
|
|
4235
|
+
const here = fs10.realpathSync(fileURLToPath5(import.meta.url));
|
|
4236
|
+
const argv = fs10.realpathSync(path9.resolve(process.argv[1]));
|
|
4237
|
+
return here === argv;
|
|
4238
|
+
} catch {
|
|
4239
|
+
return false;
|
|
4240
|
+
}
|
|
4241
|
+
}
|
|
4242
|
+
if (_isCliEntry5()) {
|
|
4243
|
+
process.exit(main5());
|
|
4244
|
+
}
|
|
4245
|
+
|
|
4246
|
+
// src/scripts/_lib/turn_end_transcript.ts
|
|
4247
|
+
function readTranscriptTail(transcriptPath, opts = {}) {
|
|
4248
|
+
const empty = {
|
|
4249
|
+
lastAssistant: "",
|
|
4250
|
+
turnOrdinal: 0,
|
|
4251
|
+
toolCalls: [],
|
|
4252
|
+
assistantTurnTexts: []
|
|
4253
|
+
};
|
|
4254
|
+
if (!transcriptPath || !isSafeTranscriptPath(transcriptPath, opts)) return empty;
|
|
4255
|
+
let lines;
|
|
4256
|
+
try {
|
|
4257
|
+
if (opts.maxBytes !== void 0 && fs11.statSync(transcriptPath).size > opts.maxBytes) {
|
|
4258
|
+
return empty;
|
|
4259
|
+
}
|
|
4260
|
+
lines = fs11.readFileSync(transcriptPath, "utf-8").split("\n");
|
|
4261
|
+
} catch {
|
|
4262
|
+
return empty;
|
|
4263
|
+
}
|
|
4264
|
+
let lastAssistant = "";
|
|
4265
|
+
let turnOrdinal = 0;
|
|
4266
|
+
let toolCalls = [];
|
|
4267
|
+
let assistantTurnTexts = [];
|
|
4268
|
+
for (const rawLine of lines) {
|
|
4269
|
+
const line = rawLine.trim();
|
|
4270
|
+
if (!line) continue;
|
|
4271
|
+
let obj;
|
|
4272
|
+
try {
|
|
4273
|
+
obj = JSON.parse(line);
|
|
4274
|
+
} catch {
|
|
4275
|
+
continue;
|
|
4276
|
+
}
|
|
4277
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) continue;
|
|
4278
|
+
const entry = obj;
|
|
4279
|
+
if (entry["isSidechain"] === true) continue;
|
|
4280
|
+
const role = entry["type"];
|
|
4281
|
+
if (role !== "assistant" && role !== "user") continue;
|
|
4282
|
+
const msg = entry["message"];
|
|
4283
|
+
if (typeof msg !== "object" || msg === null || Array.isArray(msg)) continue;
|
|
4284
|
+
const content = msg["content"];
|
|
4285
|
+
if (role === "assistant") {
|
|
4286
|
+
toolCalls.push(...extractToolCalls(content));
|
|
4287
|
+
}
|
|
4288
|
+
const text = _messageText(content);
|
|
4289
|
+
if (text === null) continue;
|
|
4290
|
+
if (role === "assistant") {
|
|
4291
|
+
lastAssistant = text;
|
|
4292
|
+
assistantTurnTexts.push(text);
|
|
4293
|
+
} else if (!isSyntheticPrompt(text)) {
|
|
4294
|
+
turnOrdinal += 1;
|
|
4295
|
+
toolCalls = [];
|
|
4296
|
+
assistantTurnTexts = [];
|
|
4297
|
+
}
|
|
4298
|
+
}
|
|
4299
|
+
return {
|
|
4300
|
+
lastAssistant: lastAssistant.trim(),
|
|
4301
|
+
turnOrdinal,
|
|
4302
|
+
toolCalls,
|
|
4303
|
+
assistantTurnTexts
|
|
4304
|
+
};
|
|
3273
4305
|
}
|
|
3274
|
-
function
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
4306
|
+
function extractToolCalls(content) {
|
|
4307
|
+
if (!Array.isArray(content)) return [];
|
|
4308
|
+
const out = [];
|
|
4309
|
+
for (const blk of content) {
|
|
4310
|
+
if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
|
|
4311
|
+
const b = blk;
|
|
4312
|
+
if (b["type"] !== "tool_use") continue;
|
|
4313
|
+
const name = b["name"];
|
|
4314
|
+
if (typeof name !== "string") continue;
|
|
4315
|
+
const input = b["input"];
|
|
4316
|
+
const call = { name };
|
|
4317
|
+
if (typeof input === "object" && input !== null && !Array.isArray(input)) {
|
|
4318
|
+
const inp = input;
|
|
4319
|
+
const cmd = inp["command"];
|
|
4320
|
+
if (typeof cmd === "string") call.command = cmd;
|
|
4321
|
+
const p = inp["file_path"] ?? inp["path"] ?? inp["notebook_path"];
|
|
4322
|
+
if (typeof p === "string") call.path = p;
|
|
4323
|
+
}
|
|
4324
|
+
out.push(call);
|
|
3281
4325
|
}
|
|
3282
|
-
return
|
|
4326
|
+
return out;
|
|
3283
4327
|
}
|
|
3284
|
-
function
|
|
3285
|
-
if (
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
return false;
|
|
4328
|
+
function _messageText(content) {
|
|
4329
|
+
if (typeof content === "string") return content;
|
|
4330
|
+
if (!Array.isArray(content)) return null;
|
|
4331
|
+
const parts = [];
|
|
4332
|
+
for (const blk of content) {
|
|
4333
|
+
if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
|
|
4334
|
+
const b = blk;
|
|
4335
|
+
if (b["type"] !== "text") continue;
|
|
4336
|
+
const t = b["text"];
|
|
4337
|
+
if (typeof t === "string") parts.push(t);
|
|
3295
4338
|
}
|
|
4339
|
+
return parts.length > 0 ? parts.join("\n") : null;
|
|
3296
4340
|
}
|
|
3297
|
-
if (_isCliEntry5()) process.exit(main5());
|
|
3298
4341
|
|
|
3299
4342
|
// src/scripts/hooks/turn_end_gate_hook.ts
|
|
3300
|
-
var EXIT_BLOCK = 1;
|
|
3301
|
-
var EXIT_ALLOW4 = 0;
|
|
3302
4343
|
var TRANSCRIPT_READ_MAX_BYTES = 8 * 1024 * 1024;
|
|
3303
4344
|
function stripFencedBlocks(text) {
|
|
3304
4345
|
const out = [];
|
|
@@ -3403,7 +4444,7 @@ function detectPromissory(reply) {
|
|
|
3403
4444
|
function readCiSettled(workspaceRoot, session_id) {
|
|
3404
4445
|
if (!hasStableSessionId(session_id)) return { seen: false, settled: false };
|
|
3405
4446
|
try {
|
|
3406
|
-
const raw =
|
|
4447
|
+
const raw = fs12.readFileSync(path10.join(workspaceRoot, statePathFor(session_id)), "utf-8");
|
|
3407
4448
|
const decoded = JSON.parse(raw);
|
|
3408
4449
|
if (!owns_session_state(decoded, session_id)) return { seen: false, settled: false };
|
|
3409
4450
|
if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
|
|
@@ -3439,8 +4480,8 @@ function detectCompletionClaim(reply, ci) {
|
|
|
3439
4480
|
function readLanguagePin(workspaceRoot, session_id) {
|
|
3440
4481
|
if (!hasStableSessionId(session_id)) return "und";
|
|
3441
4482
|
try {
|
|
3442
|
-
const raw =
|
|
3443
|
-
|
|
4483
|
+
const raw = fs12.readFileSync(
|
|
4484
|
+
path10.join(workspaceRoot, statePathFor2(session_id)),
|
|
3444
4485
|
"utf-8"
|
|
3445
4486
|
);
|
|
3446
4487
|
const decoded = JSON.parse(raw);
|
|
@@ -3466,29 +4507,38 @@ function detectLanguage(reply, pinned) {
|
|
|
3466
4507
|
};
|
|
3467
4508
|
}
|
|
3468
4509
|
var _EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
3469
|
-
|
|
3470
|
-
function isVerificationCommand(command) {
|
|
3471
|
-
return _VERIFY_RE.test(command);
|
|
3472
|
-
}
|
|
3473
|
-
function detectUnverifiedEdit(toolCalls) {
|
|
4510
|
+
function detectUnverifiedEdit(toolCalls, runState = null) {
|
|
3474
4511
|
let lastEdit = -1;
|
|
3475
4512
|
for (let i = 0; i < toolCalls.length; i += 1) {
|
|
3476
4513
|
if (_EDIT_TOOLS.has(toolCalls[i].name)) lastEdit = i;
|
|
3477
4514
|
}
|
|
3478
4515
|
if (lastEdit === -1) return null;
|
|
4516
|
+
const edited = toolCalls[lastEdit].path ?? toolCalls[lastEdit].name;
|
|
4517
|
+
if (runState !== null) {
|
|
4518
|
+
const reading = readRunEvidence(runState);
|
|
4519
|
+
if (reading.passed) return null;
|
|
4520
|
+
if (!reading.instrumentGap) {
|
|
4521
|
+
return {
|
|
4522
|
+
detector: "verification",
|
|
4523
|
+
evidence: edited,
|
|
4524
|
+
reason: describeRecordFinding(reading.failed, reading.reasons),
|
|
4525
|
+
mode: "record"
|
|
4526
|
+
};
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
3479
4529
|
for (let i = lastEdit + 1; i < toolCalls.length; i += 1) {
|
|
3480
4530
|
const c = toolCalls[i];
|
|
3481
4531
|
if (c.name === "Bash" && c.command !== void 0 && isVerificationCommand(c.command)) {
|
|
3482
4532
|
return null;
|
|
3483
4533
|
}
|
|
3484
4534
|
}
|
|
3485
|
-
const edited = toolCalls[lastEdit].path;
|
|
3486
4535
|
return {
|
|
3487
4536
|
detector: "verification",
|
|
3488
4537
|
// The path, never the diff: the evidence span is quoted into a refusal,
|
|
3489
4538
|
// and `ToolCall` is shaped so a file body cannot reach it.
|
|
3490
|
-
evidence: edited
|
|
3491
|
-
reason: "this turn changed a file and then ran no verification command \u2014 no test, type-check, lint or build call follows the last edit (verify-before-complete: a claim without a fresh run is unverified)"
|
|
4539
|
+
evidence: edited,
|
|
4540
|
+
reason: "this turn changed a file and then ran no verification command \u2014 no test, type-check, lint or build call follows the last edit (verify-before-complete: a claim without a fresh run is unverified)",
|
|
4541
|
+
mode: "transcript"
|
|
3492
4542
|
};
|
|
3493
4543
|
}
|
|
3494
4544
|
var _SOURCE_RE = /\.(?:ts|tsx|js|jsx|mjs|cjs|vue|svelte|php|py|rb|go|rs|java|kt|swift|cs|scala|ex|exs)$/i;
|
|
@@ -3500,18 +4550,37 @@ function _isTestPath(p) {
|
|
|
3500
4550
|
function _isProductionSource(p) {
|
|
3501
4551
|
return _SOURCE_RE.test(p) && !_isTestPath(p);
|
|
3502
4552
|
}
|
|
3503
|
-
function detectUntestedChange(reply, toolCalls) {
|
|
4553
|
+
function detectUntestedChange(reply, toolCalls, runState = null) {
|
|
3504
4554
|
const edited = toolCalls.filter((c) => _EDIT_TOOLS.has(c.name) && c.path !== void 0).map((c) => c.path);
|
|
3505
4555
|
const source = edited.filter(_isProductionSource);
|
|
3506
4556
|
if (source.length === 0) return null;
|
|
3507
|
-
|
|
4557
|
+
const wroteNewTest = toolCalls.some(
|
|
4558
|
+
(c) => c.name === "Write" && c.path !== void 0 && _isTestPath(c.path)
|
|
4559
|
+
);
|
|
4560
|
+
let noRedEvidence = false;
|
|
4561
|
+
if (edited.some(_isTestPath)) {
|
|
4562
|
+
if (!wroteNewTest) return null;
|
|
4563
|
+
if (runState === null) return null;
|
|
4564
|
+
if (hasRedThenGreen(runState)) return null;
|
|
4565
|
+
if (readRunEvidence(runState).instrumentGap) return null;
|
|
4566
|
+
noRedEvidence = true;
|
|
4567
|
+
}
|
|
3508
4568
|
const prose = visibleProse(reply);
|
|
3509
4569
|
const m = _COMPLETION_RE.exec(prose);
|
|
3510
4570
|
if (!m) return null;
|
|
3511
4571
|
if (_NEGATED_CLAIM_RE.test(_lineAround(prose, m.index))) return null;
|
|
3512
4572
|
const shown = source.slice(0, 3).join(", ");
|
|
4573
|
+
if (noRedEvidence) {
|
|
4574
|
+
return {
|
|
4575
|
+
detector: "untested",
|
|
4576
|
+
evidence: shown + (source.length > 3 ? ` (+${String(source.length - 3)} more)` : ""),
|
|
4577
|
+
mode: "record",
|
|
4578
|
+
reason: NO_RED_EVIDENCE_REASON
|
|
4579
|
+
};
|
|
4580
|
+
}
|
|
3513
4581
|
return {
|
|
3514
4582
|
detector: "untested",
|
|
4583
|
+
mode: "transcript",
|
|
3515
4584
|
evidence: shown + (source.length > 3 ? ` (+${String(source.length - 3)} more)` : ""),
|
|
3516
4585
|
reason: "a completion claim over production code this turn changed, with NO test file touched anywhere in the turn \u2014 running a linter is not evidence the change works. Write the test that would have caught the failure, and enumerate the states the change actually has (every view, every CRUD path, the empty and error states) rather than the happy one. If a test genuinely does not belong here, say which file carries the coverage instead"
|
|
3517
4586
|
};
|
|
@@ -3521,7 +4590,7 @@ function sessionStateFile2(workspaceRoot, sessionKey) {
|
|
|
3521
4590
|
}
|
|
3522
4591
|
function alreadyRefusedTurn(workspaceRoot, sessionKey, turnOrdinal) {
|
|
3523
4592
|
try {
|
|
3524
|
-
const raw =
|
|
4593
|
+
const raw = fs12.readFileSync(sessionStateFile2(workspaceRoot, sessionKey), "utf-8");
|
|
3525
4594
|
const decoded = JSON.parse(raw);
|
|
3526
4595
|
if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
|
|
3527
4596
|
return decoded["refused_turn"] === turnOrdinal;
|
|
@@ -3535,7 +4604,7 @@ function markRefusedTurn(workspaceRoot, sessionKey, turnOrdinal, detectors, prom
|
|
|
3535
4604
|
const file = sessionStateFile2(workspaceRoot, sessionKey);
|
|
3536
4605
|
let prev = null;
|
|
3537
4606
|
try {
|
|
3538
|
-
prev = parseRecord(
|
|
4607
|
+
prev = parseRecord(fs12.readFileSync(file, "utf-8"));
|
|
3539
4608
|
} catch {
|
|
3540
4609
|
}
|
|
3541
4610
|
let version;
|
|
@@ -3558,112 +4627,10 @@ function markRefusedTurn(workspaceRoot, sessionKey, turnOrdinal, detectors, prom
|
|
|
3558
4627
|
} catch {
|
|
3559
4628
|
}
|
|
3560
4629
|
}
|
|
3561
|
-
function readTranscriptTail(transcriptPath, opts = {}) {
|
|
3562
|
-
const empty = {
|
|
3563
|
-
lastAssistant: "",
|
|
3564
|
-
turnOrdinal: 0,
|
|
3565
|
-
toolCalls: [],
|
|
3566
|
-
assistantTurnTexts: []
|
|
3567
|
-
};
|
|
3568
|
-
if (!transcriptPath || !isSafeTranscriptPath(transcriptPath, opts)) return empty;
|
|
3569
|
-
let lines;
|
|
3570
|
-
try {
|
|
3571
|
-
if (opts.maxBytes !== void 0 && fs7.statSync(transcriptPath).size > opts.maxBytes) {
|
|
3572
|
-
return empty;
|
|
3573
|
-
}
|
|
3574
|
-
lines = fs7.readFileSync(transcriptPath, "utf-8").split("\n");
|
|
3575
|
-
} catch {
|
|
3576
|
-
return empty;
|
|
3577
|
-
}
|
|
3578
|
-
let lastAssistant = "";
|
|
3579
|
-
let turnOrdinal = 0;
|
|
3580
|
-
let toolCalls = [];
|
|
3581
|
-
let assistantTurnTexts = [];
|
|
3582
|
-
for (const rawLine of lines) {
|
|
3583
|
-
const line = rawLine.trim();
|
|
3584
|
-
if (!line) continue;
|
|
3585
|
-
let obj;
|
|
3586
|
-
try {
|
|
3587
|
-
obj = JSON.parse(line);
|
|
3588
|
-
} catch {
|
|
3589
|
-
continue;
|
|
3590
|
-
}
|
|
3591
|
-
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) continue;
|
|
3592
|
-
const entry = obj;
|
|
3593
|
-
if (entry["isSidechain"] === true) continue;
|
|
3594
|
-
const role = entry["type"];
|
|
3595
|
-
if (role !== "assistant" && role !== "user") continue;
|
|
3596
|
-
const msg = entry["message"];
|
|
3597
|
-
if (typeof msg !== "object" || msg === null || Array.isArray(msg)) continue;
|
|
3598
|
-
const content = msg["content"];
|
|
3599
|
-
if (role === "assistant") {
|
|
3600
|
-
toolCalls.push(...extractToolCalls(content));
|
|
3601
|
-
}
|
|
3602
|
-
const text = _messageText(content);
|
|
3603
|
-
if (text === null) continue;
|
|
3604
|
-
if (role === "assistant") {
|
|
3605
|
-
lastAssistant = text;
|
|
3606
|
-
assistantTurnTexts.push(text);
|
|
3607
|
-
} else if (!isSyntheticPrompt(text)) {
|
|
3608
|
-
turnOrdinal += 1;
|
|
3609
|
-
toolCalls = [];
|
|
3610
|
-
assistantTurnTexts = [];
|
|
3611
|
-
}
|
|
3612
|
-
}
|
|
3613
|
-
return {
|
|
3614
|
-
lastAssistant: lastAssistant.trim(),
|
|
3615
|
-
turnOrdinal,
|
|
3616
|
-
toolCalls,
|
|
3617
|
-
assistantTurnTexts
|
|
3618
|
-
};
|
|
3619
|
-
}
|
|
3620
|
-
function extractToolCalls(content) {
|
|
3621
|
-
if (!Array.isArray(content)) return [];
|
|
3622
|
-
const out = [];
|
|
3623
|
-
for (const blk of content) {
|
|
3624
|
-
if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
|
|
3625
|
-
const b = blk;
|
|
3626
|
-
if (b["type"] !== "tool_use") continue;
|
|
3627
|
-
const name = b["name"];
|
|
3628
|
-
if (typeof name !== "string") continue;
|
|
3629
|
-
const input = b["input"];
|
|
3630
|
-
const call = { name };
|
|
3631
|
-
if (typeof input === "object" && input !== null && !Array.isArray(input)) {
|
|
3632
|
-
const inp = input;
|
|
3633
|
-
const cmd = inp["command"];
|
|
3634
|
-
if (typeof cmd === "string") call.command = cmd;
|
|
3635
|
-
const p = inp["file_path"] ?? inp["path"] ?? inp["notebook_path"];
|
|
3636
|
-
if (typeof p === "string") call.path = p;
|
|
3637
|
-
}
|
|
3638
|
-
out.push(call);
|
|
3639
|
-
}
|
|
3640
|
-
return out;
|
|
3641
|
-
}
|
|
3642
|
-
function _messageText(content) {
|
|
3643
|
-
if (typeof content === "string") return content;
|
|
3644
|
-
if (!Array.isArray(content)) return null;
|
|
3645
|
-
const parts = [];
|
|
3646
|
-
for (const blk of content) {
|
|
3647
|
-
if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
|
|
3648
|
-
const b = blk;
|
|
3649
|
-
if (b["type"] !== "text") continue;
|
|
3650
|
-
const t = b["text"];
|
|
3651
|
-
if (typeof t === "string") parts.push(t);
|
|
3652
|
-
}
|
|
3653
|
-
return parts.length > 0 ? parts.join("\n") : null;
|
|
3654
|
-
}
|
|
3655
4630
|
function str3(v) {
|
|
3656
4631
|
return typeof v === "string" ? v : "";
|
|
3657
4632
|
}
|
|
3658
|
-
function
|
|
3659
|
-
let envelope;
|
|
3660
|
-
let payload;
|
|
3661
|
-
try {
|
|
3662
|
-
[envelope, payload] = unwrap(readHookStdin(), "claude");
|
|
3663
|
-
} catch {
|
|
3664
|
-
return EXIT_ALLOW4;
|
|
3665
|
-
}
|
|
3666
|
-
if (payload["stop_hook_active"] === true) return EXIT_ALLOW4;
|
|
4633
|
+
function assembleDetectorInputs(envelope, payload) {
|
|
3667
4634
|
const workspaceRoot = str3(envelope["workspace_root"]) || process.cwd();
|
|
3668
4635
|
let dispatchOpen = false;
|
|
3669
4636
|
try {
|
|
@@ -3677,18 +4644,32 @@ function main6() {
|
|
|
3677
4644
|
transcriptPath,
|
|
3678
4645
|
{ maxBytes: TRANSCRIPT_READ_MAX_BYTES }
|
|
3679
4646
|
);
|
|
3680
|
-
if (!lastAssistant) return
|
|
4647
|
+
if (!lastAssistant) return null;
|
|
3681
4648
|
const payloadClosing = str3(
|
|
3682
4649
|
payload["last_assistant_message"] ?? payload["lastAssistantMessage"]
|
|
3683
4650
|
);
|
|
3684
4651
|
const closingFromPayload = payloadClosing !== "" && !assistantTurnTexts.includes(payloadClosing);
|
|
3685
4652
|
const turnTexts = closingFromPayload ? [...assistantTurnTexts, payloadClosing] : assistantTurnTexts;
|
|
3686
|
-
const promptId = str3(
|
|
3687
|
-
payload["prompt_id"] ?? payload["promptId"]
|
|
3688
|
-
);
|
|
4653
|
+
const promptId = str3(payload["prompt_id"] ?? payload["promptId"]);
|
|
3689
4654
|
const rawSessionId = str3(envelope["session_id"]) || "";
|
|
3690
4655
|
const sessionKey = deriveSessionKey(rawSessionId || "unknown-session");
|
|
3691
|
-
|
|
4656
|
+
const runState = readTurnRunState(workspaceRoot, rawSessionId);
|
|
4657
|
+
return {
|
|
4658
|
+
workspaceRoot,
|
|
4659
|
+
rawSessionId,
|
|
4660
|
+
sessionKey,
|
|
4661
|
+
turnOrdinal,
|
|
4662
|
+
promptId,
|
|
4663
|
+
lastAssistant,
|
|
4664
|
+
toolCalls,
|
|
4665
|
+
turnTexts,
|
|
4666
|
+
closingFromPayload,
|
|
4667
|
+
dispatchOpen,
|
|
4668
|
+
runState
|
|
4669
|
+
};
|
|
4670
|
+
}
|
|
4671
|
+
function runDetectors(inputs) {
|
|
4672
|
+
const { dispatchOpen, lastAssistant, workspaceRoot, rawSessionId, toolCalls, runState } = inputs;
|
|
3692
4673
|
const findings = [];
|
|
3693
4674
|
for (const f of [
|
|
3694
4675
|
// A and D are the completion-adjacent pair a pending dispatch excuses
|
|
@@ -3696,17 +4677,14 @@ function main6() {
|
|
|
3696
4677
|
// excused by anything about a dispatch and run unchanged.
|
|
3697
4678
|
dispatchOpen ? null : detectPromissory(lastAssistant),
|
|
3698
4679
|
detectLanguage(lastAssistant, readLanguagePin(workspaceRoot, rawSessionId)),
|
|
3699
|
-
detectUnverifiedEdit(toolCalls),
|
|
3700
|
-
// Round 7 § Phase 1 — detector D. It is NOT unconditional
|
|
3701
|
-
//
|
|
3702
|
-
//
|
|
3703
|
-
//
|
|
3704
|
-
//
|
|
3705
|
-
//
|
|
3706
|
-
//
|
|
3707
|
-
// gating is where the comment above says gating belongs — inside the
|
|
3708
|
-
// detector: no CI observed, or a settled read, or no completion claim
|
|
3709
|
-
// ⇒ no finding.
|
|
4680
|
+
detectUnverifiedEdit(toolCalls, runState),
|
|
4681
|
+
// Round 7 § Phase 1 — detector D. It is NOT unconditional: A and D are
|
|
4682
|
+
// both excused by an open dispatch, per the note above. It shipped with
|
|
4683
|
+
// its own settings flag one commit earlier and that flag is gone:
|
|
4684
|
+
// `hooks.turn_end_gate.*` was deleted on 2026-08-12 because a
|
|
4685
|
+
// default-off safety gate is an absent one. Its gating is where the
|
|
4686
|
+
// note above says gating belongs — inside the detector: no CI observed,
|
|
4687
|
+
// or a settled read, or no completion claim ⇒ no finding.
|
|
3710
4688
|
dispatchOpen ? null : detectCompletionClaim(lastAssistant, readCiSettled(workspaceRoot, rawSessionId)),
|
|
3711
4689
|
// Detector E is UNCONDITIONAL, and that is the opposite choice from A
|
|
3712
4690
|
// and D one line above rather than an oversight. An open dispatch
|
|
@@ -3716,24 +4694,103 @@ function main6() {
|
|
|
3716
4694
|
// that produced the measured failure — IS the continuation that drops
|
|
3717
4695
|
// the decision, so narrowing E the same way would silence it in exactly
|
|
3718
4696
|
// the case it exists for.
|
|
3719
|
-
detectDroppedDecision(turnTexts, closingFromPayload),
|
|
4697
|
+
detectDroppedDecision(inputs.turnTexts, inputs.closingFromPayload),
|
|
3720
4698
|
// Detector F takes the OTHER side of that same question, and the two
|
|
3721
4699
|
// sitting adjacent is the clearest place to say why. E fires on a
|
|
3722
4700
|
// question already asked, which an open dispatch cannot excuse. F fires
|
|
3723
4701
|
// on a completion CLAIM, and a turn waiting on a subagent has not
|
|
3724
4702
|
// finished — so its closing is not the claim F is about. Same slot,
|
|
3725
4703
|
// opposite trigger, opposite treatment of `dispatchOpen`.
|
|
3726
|
-
dispatchOpen ? null : detectUntestedChange(lastAssistant, toolCalls)
|
|
4704
|
+
dispatchOpen ? null : detectUntestedChange(lastAssistant, toolCalls, runState)
|
|
3727
4705
|
]) {
|
|
3728
4706
|
if (f) findings.push(f);
|
|
3729
4707
|
}
|
|
3730
|
-
|
|
4708
|
+
return findings;
|
|
4709
|
+
}
|
|
4710
|
+
function recordGraphFeeder(inputs, findings, layer) {
|
|
4711
|
+
if (is_replay_mode()) return;
|
|
4712
|
+
try {
|
|
4713
|
+
let state;
|
|
4714
|
+
try {
|
|
4715
|
+
state = graphState(inputs.workspaceRoot);
|
|
4716
|
+
} catch {
|
|
4717
|
+
return;
|
|
4718
|
+
}
|
|
4719
|
+
if (state === "absent") return;
|
|
4720
|
+
const paths = inputs.toolCalls.filter((c) => _EDIT_TOOLS.has(c.name) && c.path !== void 0).map((c) => c.path).filter(_isProductionSource);
|
|
4721
|
+
const f = findings.find((x) => x.detector === "untested");
|
|
4722
|
+
appendFeederRow(
|
|
4723
|
+
inputs.workspaceRoot,
|
|
4724
|
+
inputs.sessionKey,
|
|
4725
|
+
buildFeederRow({
|
|
4726
|
+
turn: inputs.turnOrdinal,
|
|
4727
|
+
layer,
|
|
4728
|
+
state,
|
|
4729
|
+
fFired: f !== void 0,
|
|
4730
|
+
fMode: f?.mode ?? null,
|
|
4731
|
+
paths,
|
|
4732
|
+
graph: graphUntestedVerdict(inputs.workspaceRoot, state, paths)
|
|
4733
|
+
})
|
|
4734
|
+
);
|
|
4735
|
+
} catch {
|
|
4736
|
+
}
|
|
4737
|
+
}
|
|
4738
|
+
function recordShadow(inputs, layer) {
|
|
4739
|
+
if (is_replay_mode()) return;
|
|
4740
|
+
try {
|
|
4741
|
+
const findings = runDetectors(inputs);
|
|
4742
|
+
const setBy = layer === "refused_turn" ? "ours" : shadowSetBy(inputs.workspaceRoot, inputs.sessionKey);
|
|
4743
|
+
recordGraphFeeder(inputs, findings, layer);
|
|
4744
|
+
const file = sessionShadowFile(inputs.workspaceRoot, inputs.sessionKey);
|
|
4745
|
+
let prev = null;
|
|
4746
|
+
try {
|
|
4747
|
+
prev = parseShadowRecord(fs12.readFileSync(file, "utf-8"));
|
|
4748
|
+
} catch {
|
|
4749
|
+
}
|
|
4750
|
+
atomic_write_json(
|
|
4751
|
+
file,
|
|
4752
|
+
foldShadow(prev, {
|
|
4753
|
+
detectors: findings.map((f) => f.detector),
|
|
4754
|
+
turnOrdinal: inputs.turnOrdinal,
|
|
4755
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4756
|
+
layer,
|
|
4757
|
+
setBy
|
|
4758
|
+
})
|
|
4759
|
+
);
|
|
4760
|
+
} catch {
|
|
4761
|
+
}
|
|
4762
|
+
}
|
|
4763
|
+
function main6() {
|
|
4764
|
+
let envelope;
|
|
4765
|
+
let payload;
|
|
4766
|
+
try {
|
|
4767
|
+
[envelope, payload] = unwrap(readHookStdin(), "claude");
|
|
4768
|
+
} catch {
|
|
4769
|
+
return EXIT_ALLOW3;
|
|
4770
|
+
}
|
|
4771
|
+
if (payload["stop_hook_active"] === true) {
|
|
4772
|
+
try {
|
|
4773
|
+
const retryInputs = assembleDetectorInputs(envelope, payload);
|
|
4774
|
+
if (retryInputs !== null) recordShadow(retryInputs, "stop_hook_active");
|
|
4775
|
+
} catch {
|
|
4776
|
+
}
|
|
4777
|
+
return EXIT_ALLOW3;
|
|
4778
|
+
}
|
|
4779
|
+
const inputs = assembleDetectorInputs(envelope, payload);
|
|
4780
|
+
if (inputs === null) return EXIT_ALLOW3;
|
|
4781
|
+
if (alreadyRefusedTurn(inputs.workspaceRoot, inputs.sessionKey, inputs.turnOrdinal)) {
|
|
4782
|
+
recordShadow(inputs, "refused_turn");
|
|
4783
|
+
return EXIT_ALLOW3;
|
|
4784
|
+
}
|
|
4785
|
+
const findings = runDetectors(inputs);
|
|
4786
|
+
recordGraphFeeder(inputs, findings, "live");
|
|
4787
|
+
if (findings.length === 0) return EXIT_ALLOW3;
|
|
3731
4788
|
markRefusedTurn(
|
|
3732
|
-
workspaceRoot,
|
|
3733
|
-
sessionKey,
|
|
3734
|
-
turnOrdinal,
|
|
4789
|
+
inputs.workspaceRoot,
|
|
4790
|
+
inputs.sessionKey,
|
|
4791
|
+
inputs.turnOrdinal,
|
|
3735
4792
|
findings.map((f) => f.detector),
|
|
3736
|
-
promptId
|
|
4793
|
+
inputs.promptId
|
|
3737
4794
|
);
|
|
3738
4795
|
const lines = findings.map(
|
|
3739
4796
|
(f) => ` \xB7 ${f.detector}: ${f.reason}
|
|
@@ -3755,13 +4812,13 @@ function _isCliEntry6() {
|
|
|
3755
4812
|
if (process.argv[1] === void 0) {
|
|
3756
4813
|
return false;
|
|
3757
4814
|
}
|
|
3758
|
-
const argvUrl = pathToFileURL6(
|
|
4815
|
+
const argvUrl = pathToFileURL6(path10.resolve(process.argv[1])).href;
|
|
3759
4816
|
if (import.meta.url === argvUrl) {
|
|
3760
4817
|
return true;
|
|
3761
4818
|
}
|
|
3762
4819
|
try {
|
|
3763
|
-
const here =
|
|
3764
|
-
const argv =
|
|
4820
|
+
const here = fs12.realpathSync(fileURLToPath6(import.meta.url));
|
|
4821
|
+
const argv = fs12.realpathSync(path10.resolve(process.argv[1]));
|
|
3765
4822
|
return here === argv;
|
|
3766
4823
|
} catch {
|
|
3767
4824
|
return false;
|
|
@@ -3774,8 +4831,8 @@ if (_isCliEntry6()) {
|
|
|
3774
4831
|
// src/scripts/hooks/evidence_independence.ts
|
|
3775
4832
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
3776
4833
|
import crypto3 from "node:crypto";
|
|
3777
|
-
import
|
|
3778
|
-
import
|
|
4834
|
+
import fs13 from "node:fs";
|
|
4835
|
+
import path11 from "node:path";
|
|
3779
4836
|
import { fileURLToPath as fileURLToPath7, pathToFileURL as pathToFileURL7 } from "node:url";
|
|
3780
4837
|
|
|
3781
4838
|
// src/scripts/_lib/test_delta.ts
|
|
@@ -3794,9 +4851,7 @@ function isTestPath(p) {
|
|
|
3794
4851
|
}
|
|
3795
4852
|
|
|
3796
4853
|
// src/scripts/hooks/evidence_independence.ts
|
|
3797
|
-
var
|
|
3798
|
-
var EXIT_BLOCK2 = 1;
|
|
3799
|
-
var STATE_FILE3 = path8.join("agents", "state", "evidence-dispatch.json");
|
|
4854
|
+
var STATE_FILE3 = path11.join("agents", "state", "evidence-dispatch.json");
|
|
3800
4855
|
function _isObject5(v) {
|
|
3801
4856
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3802
4857
|
}
|
|
@@ -3864,8 +4919,8 @@ function extractDispatch(envelope) {
|
|
|
3864
4919
|
}
|
|
3865
4920
|
function _ledgerStamp(consumer_root) {
|
|
3866
4921
|
try {
|
|
3867
|
-
const raw =
|
|
3868
|
-
|
|
4922
|
+
const raw = fs13.readFileSync(
|
|
4923
|
+
path11.join(consumer_root, "agents", "state", "git-authorization.json"),
|
|
3869
4924
|
"utf8"
|
|
3870
4925
|
);
|
|
3871
4926
|
const d = JSON.parse(raw);
|
|
@@ -3876,7 +4931,7 @@ function _ledgerStamp(consumer_root) {
|
|
|
3876
4931
|
}
|
|
3877
4932
|
function _load(target) {
|
|
3878
4933
|
try {
|
|
3879
|
-
const decoded = JSON.parse(
|
|
4934
|
+
const decoded = JSON.parse(fs13.readFileSync(target, "utf8"));
|
|
3880
4935
|
if (_isObject5(decoded) && Array.isArray(decoded["evaluations"])) {
|
|
3881
4936
|
return decoded;
|
|
3882
4937
|
}
|
|
@@ -3887,12 +4942,12 @@ function _load(target) {
|
|
|
3887
4942
|
var WARN_TEXT = "First evaluation dispatch this turn. Two shapes fabricated a binding honest-null in the audited sessions \u2014 check your prompt for both before it goes out: (1) a verdict pre-loaded into the prompt, and (2) a scope narrowed to files you chose. A review you commissioned on your own work is admissible as gate evidence only when the prompt is recorded alongside the verdict.";
|
|
3888
4943
|
function decide(tool, prompt, priorEvaluations) {
|
|
3889
4944
|
if (!isDispatchTool(tool) || !isEvaluationPrompt(prompt)) {
|
|
3890
|
-
return { exit:
|
|
4945
|
+
return { exit: EXIT_ALLOW3, stdout: "", stderr: "", evaluations: priorEvaluations };
|
|
3891
4946
|
}
|
|
3892
4947
|
const preloaded = preloadedVerdict(prompt);
|
|
3893
4948
|
if (preloaded !== null) {
|
|
3894
4949
|
return {
|
|
3895
|
-
exit:
|
|
4950
|
+
exit: EXIT_BLOCK,
|
|
3896
4951
|
stdout: "",
|
|
3897
4952
|
stderr: `Blocked: this evaluation prompt pre-loads its verdict ("${preloaded}"). That is the construct that produced a fabricated NO-FINDINGS committed as binding gate evidence, over a delta an unsteered pass then found a live critical in. Remove the expectation from the prompt and let the evaluator reach its own verdict.
|
|
3898
4953
|
`,
|
|
@@ -3900,11 +4955,11 @@ function decide(tool, prompt, priorEvaluations) {
|
|
|
3900
4955
|
};
|
|
3901
4956
|
}
|
|
3902
4957
|
if (!isSelfScoped(prompt)) {
|
|
3903
|
-
return { exit:
|
|
4958
|
+
return { exit: EXIT_ALLOW3, stdout: "", stderr: "", evaluations: priorEvaluations };
|
|
3904
4959
|
}
|
|
3905
4960
|
if (priorEvaluations >= 1) {
|
|
3906
4961
|
return {
|
|
3907
|
-
exit:
|
|
4962
|
+
exit: EXIT_ALLOW3,
|
|
3908
4963
|
stdout: `${JSON.stringify({
|
|
3909
4964
|
decision: "warn",
|
|
3910
4965
|
reason: `Second evaluation dispatch of your own work in this turn. If both passes judge the SAME subject, that is verdict shopping \u2014 commissioning another with a different prompt or scope selects the answer instead of measuring it. Report what the first pass returned, then re-plan. If this is an implementation fan-out rather than a second review, carry on: this check reads prose and cannot tell the two apart with certainty.`
|
|
@@ -3915,7 +4970,7 @@ function decide(tool, prompt, priorEvaluations) {
|
|
|
3915
4970
|
};
|
|
3916
4971
|
}
|
|
3917
4972
|
return {
|
|
3918
|
-
exit:
|
|
4973
|
+
exit: EXIT_ALLOW3,
|
|
3919
4974
|
stdout: `${JSON.stringify({ decision: "warn", reason: WARN_TEXT })}
|
|
3920
4975
|
`,
|
|
3921
4976
|
stderr: "",
|
|
@@ -3957,10 +5012,10 @@ function run3(stdin_text, options) {
|
|
|
3957
5012
|
envelope = decoded;
|
|
3958
5013
|
}
|
|
3959
5014
|
} catch {
|
|
3960
|
-
return
|
|
5015
|
+
return EXIT_ALLOW3;
|
|
3961
5016
|
}
|
|
3962
5017
|
}
|
|
3963
|
-
const target =
|
|
5018
|
+
const target = path11.join(options.consumer_root, STATE_FILE3);
|
|
3964
5019
|
const state = _load(target);
|
|
3965
5020
|
const session_id = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
|
|
3966
5021
|
const turnMarker = `${session_id}:${_ledgerStamp(options.consumer_root)}`;
|
|
@@ -3976,7 +5031,7 @@ function run3(stdin_text, options) {
|
|
|
3976
5031
|
if (staged !== null && touchesTestAndCode(staged)) {
|
|
3977
5032
|
process.stdout.write(`${JSON.stringify({ decision: "warn", reason: flag })}
|
|
3978
5033
|
`);
|
|
3979
|
-
return
|
|
5034
|
+
return EXIT_ALLOW3;
|
|
3980
5035
|
}
|
|
3981
5036
|
}
|
|
3982
5037
|
const decision = decide(tool, prompt, state.evaluations.length);
|
|
@@ -4028,13 +5083,13 @@ function _isCliEntry7() {
|
|
|
4028
5083
|
if (process.argv[1] === void 0) {
|
|
4029
5084
|
return false;
|
|
4030
5085
|
}
|
|
4031
|
-
const argvUrl = pathToFileURL7(
|
|
5086
|
+
const argvUrl = pathToFileURL7(path11.resolve(process.argv[1])).href;
|
|
4032
5087
|
if (import.meta.url === argvUrl) {
|
|
4033
5088
|
return true;
|
|
4034
5089
|
}
|
|
4035
5090
|
try {
|
|
4036
|
-
const here =
|
|
4037
|
-
const argv =
|
|
5091
|
+
const here = fs13.realpathSync(fileURLToPath7(import.meta.url));
|
|
5092
|
+
const argv = fs13.realpathSync(path11.resolve(process.argv[1]));
|
|
4038
5093
|
return here === argv;
|
|
4039
5094
|
} catch {
|
|
4040
5095
|
return false;
|
|
@@ -4045,8 +5100,8 @@ if (_isCliEntry7()) {
|
|
|
4045
5100
|
}
|
|
4046
5101
|
|
|
4047
5102
|
// src/scripts/hooks/delegation_nudge_hook.ts
|
|
4048
|
-
import
|
|
4049
|
-
import
|
|
5103
|
+
import fs14 from "node:fs";
|
|
5104
|
+
import path12 from "node:path";
|
|
4050
5105
|
import { fileURLToPath as fileURLToPath8, pathToFileURL as pathToFileURL8 } from "node:url";
|
|
4051
5106
|
|
|
4052
5107
|
// src/scripts/_lib/auto_dispatch.ts
|
|
@@ -4327,8 +5382,6 @@ function resolveSubagentRouting(inp) {
|
|
|
4327
5382
|
}
|
|
4328
5383
|
|
|
4329
5384
|
// src/scripts/hooks/delegation_nudge_hook.ts
|
|
4330
|
-
var EXIT_ALLOW6 = 0;
|
|
4331
|
-
var EXIT_WARN3 = 2;
|
|
4332
5385
|
function _isObject6(v) {
|
|
4333
5386
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4334
5387
|
}
|
|
@@ -4508,28 +5561,28 @@ function main8() {
|
|
|
4508
5561
|
const raw = readHookStdin();
|
|
4509
5562
|
envelope = raw.trim() ? JSON.parse(raw) : {};
|
|
4510
5563
|
} catch {
|
|
4511
|
-
return
|
|
5564
|
+
return EXIT_ALLOW3;
|
|
4512
5565
|
}
|
|
4513
5566
|
const env = _isObject6(envelope) ? envelope : {};
|
|
4514
5567
|
const event = env["event"];
|
|
4515
5568
|
const slot = typeof event === "string" && event !== "" ? event : "user_prompt_submit";
|
|
4516
5569
|
if (slot !== "user_prompt_submit") {
|
|
4517
|
-
return
|
|
5570
|
+
return EXIT_ALLOW3;
|
|
4518
5571
|
}
|
|
4519
5572
|
const payload = _isObject6(env["payload"]) ? env["payload"] : env;
|
|
4520
5573
|
const prompt = _extractPrompt2(payload);
|
|
4521
5574
|
if (!prompt) {
|
|
4522
|
-
return
|
|
5575
|
+
return EXIT_ALLOW3;
|
|
4523
5576
|
}
|
|
4524
5577
|
if (isSyntheticPrompt(prompt)) {
|
|
4525
|
-
return
|
|
5578
|
+
return EXIT_ALLOW3;
|
|
4526
5579
|
}
|
|
4527
5580
|
const root = _workspaceRoot(env);
|
|
4528
5581
|
const platform = env["platform"];
|
|
4529
5582
|
const hostId = typeof platform === "string" && platform ? platform : null;
|
|
4530
5583
|
const result = classifyPrompt(prompt, root, hostId);
|
|
4531
5584
|
if (!result) {
|
|
4532
|
-
return
|
|
5585
|
+
return EXIT_ALLOW3;
|
|
4533
5586
|
}
|
|
4534
5587
|
process.stdout.write(
|
|
4535
5588
|
`${JSON.stringify({
|
|
@@ -4539,7 +5592,7 @@ function main8() {
|
|
|
4539
5592
|
})}
|
|
4540
5593
|
`
|
|
4541
5594
|
);
|
|
4542
|
-
return
|
|
5595
|
+
return EXIT_WARN2;
|
|
4543
5596
|
}
|
|
4544
5597
|
function _isCliEntry8() {
|
|
4545
5598
|
if (true) {
|
|
@@ -4548,13 +5601,13 @@ function _isCliEntry8() {
|
|
|
4548
5601
|
if (process.argv[1] === void 0) {
|
|
4549
5602
|
return false;
|
|
4550
5603
|
}
|
|
4551
|
-
const argvUrl = pathToFileURL8(
|
|
5604
|
+
const argvUrl = pathToFileURL8(path12.resolve(process.argv[1])).href;
|
|
4552
5605
|
if (import.meta.url === argvUrl) {
|
|
4553
5606
|
return true;
|
|
4554
5607
|
}
|
|
4555
5608
|
try {
|
|
4556
|
-
const here =
|
|
4557
|
-
const argv =
|
|
5609
|
+
const here = fs14.realpathSync(fileURLToPath8(import.meta.url));
|
|
5610
|
+
const argv = fs14.realpathSync(path12.resolve(process.argv[1]));
|
|
4558
5611
|
return here === argv;
|
|
4559
5612
|
} catch {
|
|
4560
5613
|
return false;
|
|
@@ -4565,7 +5618,7 @@ if (_isCliEntry8()) {
|
|
|
4565
5618
|
}
|
|
4566
5619
|
|
|
4567
5620
|
// src/scripts/conformance_scan.ts
|
|
4568
|
-
var REPO_ROOT =
|
|
5621
|
+
var REPO_ROOT = path13.resolve(path13.dirname(fileURLToPath9(import.meta.url)), "..", "..");
|
|
4569
5622
|
var OBSERVED_BAND = { low: 9.1, high: 39.2 };
|
|
4570
5623
|
var BAND_MIN_TURNS = 1978;
|
|
4571
5624
|
var CARRIER_CHANGES = [
|
|
@@ -4931,7 +5984,7 @@ function scanSession(sessionId, lines) {
|
|
|
4931
5984
|
closeWindow();
|
|
4932
5985
|
return report;
|
|
4933
5986
|
}
|
|
4934
|
-
var DEFAULT_RATE_SERIES =
|
|
5987
|
+
var DEFAULT_RATE_SERIES = path13.join(
|
|
4935
5988
|
REPO_ROOT,
|
|
4936
5989
|
"agents",
|
|
4937
5990
|
"runtime",
|
|
@@ -4940,7 +5993,7 @@ var DEFAULT_RATE_SERIES = path10.join(
|
|
|
4940
5993
|
);
|
|
4941
5994
|
function defaultStore(projectDir) {
|
|
4942
5995
|
const slug = projectStoreSlug(projectDir);
|
|
4943
|
-
return
|
|
5996
|
+
return path13.join(process.env["HOME"] ?? "", ".claude", "projects", slug);
|
|
4944
5997
|
}
|
|
4945
5998
|
function _tokens(chars) {
|
|
4946
5999
|
return Math.round(chars / 4);
|
|
@@ -4951,13 +6004,13 @@ function measureDelivered(projectRulesDir, globalRulesDir) {
|
|
|
4951
6004
|
return {
|
|
4952
6005
|
project: {
|
|
4953
6006
|
dir: projectRulesDir,
|
|
4954
|
-
present:
|
|
6007
|
+
present: fs15.existsSync(projectRulesDir),
|
|
4955
6008
|
files: p.files,
|
|
4956
6009
|
tokens: _tokens(p.chars)
|
|
4957
6010
|
},
|
|
4958
6011
|
global: {
|
|
4959
6012
|
dir: globalRulesDir,
|
|
4960
|
-
present:
|
|
6013
|
+
present: fs15.existsSync(globalRulesDir),
|
|
4961
6014
|
files: g.files,
|
|
4962
6015
|
tokens: _tokens(g.chars)
|
|
4963
6016
|
},
|
|
@@ -4965,13 +6018,13 @@ function measureDelivered(projectRulesDir, globalRulesDir) {
|
|
|
4965
6018
|
};
|
|
4966
6019
|
}
|
|
4967
6020
|
function storeKey(store) {
|
|
4968
|
-
return
|
|
6021
|
+
return createHash4("sha256").update(path13.resolve(store)).digest("hex").slice(0, 12);
|
|
4969
6022
|
}
|
|
4970
6023
|
function scanStore(store, limit) {
|
|
4971
|
-
const files =
|
|
6024
|
+
const files = fs15.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, m: fs15.statSync(path13.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.f);
|
|
4972
6025
|
const per_session = [];
|
|
4973
6026
|
for (const f of files) {
|
|
4974
|
-
const lines =
|
|
6027
|
+
const lines = fs15.readFileSync(path13.join(store, f), "utf8").split("\n");
|
|
4975
6028
|
const r = scanSession(f.replace(/\.jsonl$/, "").slice(0, 8), lines);
|
|
4976
6029
|
if (r.assistant_turns > 0) {
|
|
4977
6030
|
per_session.push(r);
|
|
@@ -4999,10 +6052,10 @@ function scanStore(store, limit) {
|
|
|
4999
6052
|
const task_completeness_rate_pct = completeness_windows === 0 ? 0 : Number((100 * task_completeness / completeness_windows).toFixed(1));
|
|
5000
6053
|
const era = spansCarrierChange(per_session.map((s) => s.first_at));
|
|
5001
6054
|
const delivered = measureDelivered(
|
|
5002
|
-
|
|
5003
|
-
|
|
6055
|
+
path13.join(REPO_ROOT, ".claude", "rules"),
|
|
6056
|
+
path13.join(process.env["HOME"] ?? os2.homedir(), ".claude", "rules")
|
|
5004
6057
|
);
|
|
5005
|
-
const ownStore =
|
|
6058
|
+
const ownStore = path13.resolve(store) === path13.resolve(defaultStore(REPO_ROOT));
|
|
5006
6059
|
return {
|
|
5007
6060
|
scanned_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5008
6061
|
store,
|
|
@@ -5032,8 +6085,8 @@ function scanStore(store, limit) {
|
|
|
5032
6085
|
};
|
|
5033
6086
|
}
|
|
5034
6087
|
function recordRate(file, scanned_at, rate) {
|
|
5035
|
-
|
|
5036
|
-
|
|
6088
|
+
fs15.mkdirSync(path13.dirname(file), { recursive: true });
|
|
6089
|
+
fs15.appendFileSync(file, `${JSON.stringify({ measured_at: scanned_at, ...rate })}
|
|
5037
6090
|
`, "utf8");
|
|
5038
6091
|
}
|
|
5039
6092
|
function render(report) {
|
|
@@ -5164,14 +6217,14 @@ function main9(argv) {
|
|
|
5164
6217
|
return 2;
|
|
5165
6218
|
}
|
|
5166
6219
|
const resolved = store ?? defaultStore(process.cwd());
|
|
5167
|
-
if (!
|
|
6220
|
+
if (!fs15.existsSync(resolved)) {
|
|
5168
6221
|
process.stderr.write(`conformance:behavior: no transcript store at ${resolved}
|
|
5169
6222
|
`);
|
|
5170
6223
|
return 1;
|
|
5171
6224
|
}
|
|
5172
6225
|
const report = scanStore(resolved, Number.isFinite(limit) && limit > 0 ? limit : 30);
|
|
5173
6226
|
if (out) {
|
|
5174
|
-
|
|
6227
|
+
fs15.writeFileSync(out, `${JSON.stringify(report, null, 2)}
|
|
5175
6228
|
`, "utf8");
|
|
5176
6229
|
}
|
|
5177
6230
|
if (record) {
|
|
@@ -5179,7 +6232,7 @@ function main9(argv) {
|
|
|
5179
6232
|
}
|
|
5180
6233
|
if (why !== null) {
|
|
5181
6234
|
const id = why;
|
|
5182
|
-
|
|
6235
|
+
fs15.writeSync(
|
|
5183
6236
|
1,
|
|
5184
6237
|
json ? `${JSON.stringify(
|
|
5185
6238
|
{
|
|
@@ -5199,7 +6252,7 @@ function main9(argv) {
|
|
|
5199
6252
|
);
|
|
5200
6253
|
return 0;
|
|
5201
6254
|
}
|
|
5202
|
-
|
|
6255
|
+
fs15.writeSync(1, json ? `${JSON.stringify(report, null, 2)}
|
|
5203
6256
|
` : `${render(report)}
|
|
5204
6257
|
`);
|
|
5205
6258
|
return 0;
|
|
@@ -5211,13 +6264,13 @@ function _isCliEntry9() {
|
|
|
5211
6264
|
if (process.argv[1] === void 0) {
|
|
5212
6265
|
return false;
|
|
5213
6266
|
}
|
|
5214
|
-
const argvUrl = pathToFileURL9(
|
|
6267
|
+
const argvUrl = pathToFileURL9(path13.resolve(process.argv[1])).href;
|
|
5215
6268
|
if (import.meta.url === argvUrl) {
|
|
5216
6269
|
return true;
|
|
5217
6270
|
}
|
|
5218
6271
|
try {
|
|
5219
|
-
const here =
|
|
5220
|
-
const argv =
|
|
6272
|
+
const here = fs15.realpathSync(fileURLToPath9(import.meta.url));
|
|
6273
|
+
const argv = fs15.realpathSync(path13.resolve(process.argv[1]));
|
|
5221
6274
|
return here === argv;
|
|
5222
6275
|
} catch {
|
|
5223
6276
|
return false;
|
|
@@ -5228,19 +6281,19 @@ if (_isCliEntry9()) {
|
|
|
5228
6281
|
}
|
|
5229
6282
|
|
|
5230
6283
|
// src/scripts/report_carrier_divergence.ts
|
|
5231
|
-
import * as
|
|
6284
|
+
import * as fs16 from "node:fs";
|
|
5232
6285
|
import * as os3 from "node:os";
|
|
5233
|
-
import * as
|
|
6286
|
+
import * as path14 from "node:path";
|
|
5234
6287
|
import process2 from "node:process";
|
|
5235
6288
|
import { fileURLToPath as fileURLToPath10, pathToFileURL as pathToFileURL10 } from "node:url";
|
|
5236
6289
|
var _HERE2 = fileURLToPath10(import.meta.url);
|
|
5237
|
-
var REPO_ROOT2 =
|
|
5238
|
-
var PROJECT_RULES =
|
|
5239
|
-
var PROJECTION_SOURCE =
|
|
5240
|
-
var GLOBAL_RULES =
|
|
6290
|
+
var REPO_ROOT2 = path14.resolve(path14.dirname(_HERE2), "..", "..");
|
|
6291
|
+
var PROJECT_RULES = path14.join(".claude", "rules");
|
|
6292
|
+
var PROJECTION_SOURCE = path14.join("dist", "agent-src", "rules");
|
|
6293
|
+
var GLOBAL_RULES = path14.join(".claude", "rules");
|
|
5241
6294
|
function _listRules(dir) {
|
|
5242
6295
|
try {
|
|
5243
|
-
return
|
|
6296
|
+
return fs16.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
5244
6297
|
} catch {
|
|
5245
6298
|
return [];
|
|
5246
6299
|
}
|
|
@@ -5248,7 +6301,7 @@ function _listRules(dir) {
|
|
|
5248
6301
|
function _ruleType(file) {
|
|
5249
6302
|
let text;
|
|
5250
6303
|
try {
|
|
5251
|
-
text =
|
|
6304
|
+
text = fs16.readFileSync(file, "utf-8");
|
|
5252
6305
|
} catch {
|
|
5253
6306
|
return "";
|
|
5254
6307
|
}
|
|
@@ -5259,8 +6312,8 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
|
|
|
5259
6312
|
const out = {
|
|
5260
6313
|
projectDir,
|
|
5261
6314
|
globalDir,
|
|
5262
|
-
globalPresent:
|
|
5263
|
-
projectPresent:
|
|
6315
|
+
globalPresent: fs16.existsSync(globalDir),
|
|
6316
|
+
projectPresent: fs16.existsSync(projectDir),
|
|
5264
6317
|
shared: 0,
|
|
5265
6318
|
identical: [],
|
|
5266
6319
|
provenanceOnly: [],
|
|
@@ -5283,7 +6336,7 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
|
|
|
5283
6336
|
}
|
|
5284
6337
|
if (!inP && inG) {
|
|
5285
6338
|
out.globalOnly.push(rule);
|
|
5286
|
-
if (sourceDir !== void 0 && _ruleType(
|
|
6339
|
+
if (sourceDir !== void 0 && _ruleType(path14.join(sourceDir, rule)) === "manual") {
|
|
5287
6340
|
out.manualOnlyGlobal.push(rule);
|
|
5288
6341
|
}
|
|
5289
6342
|
continue;
|
|
@@ -5293,8 +6346,8 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
|
|
|
5293
6346
|
let projectCopy;
|
|
5294
6347
|
let globalCopy;
|
|
5295
6348
|
try {
|
|
5296
|
-
projectCopy =
|
|
5297
|
-
globalCopy =
|
|
6349
|
+
projectCopy = fs16.readFileSync(path14.join(projectDir, rule));
|
|
6350
|
+
globalCopy = fs16.readFileSync(path14.join(globalDir, rule));
|
|
5298
6351
|
verdict = comparePair(projectCopy, globalCopy);
|
|
5299
6352
|
} catch {
|
|
5300
6353
|
out.shared -= 1;
|
|
@@ -5441,9 +6494,9 @@ function render2(d) {
|
|
|
5441
6494
|
}
|
|
5442
6495
|
function main10(argv) {
|
|
5443
6496
|
const args = argv ?? process2.argv.slice(2);
|
|
5444
|
-
let projectDir =
|
|
5445
|
-
let globalDir =
|
|
5446
|
-
let sourceDir =
|
|
6497
|
+
let projectDir = path14.join(REPO_ROOT2, PROJECT_RULES);
|
|
6498
|
+
let globalDir = path14.join(os3.homedir(), GLOBAL_RULES);
|
|
6499
|
+
let sourceDir = path14.join(REPO_ROOT2, PROJECTION_SOURCE);
|
|
5447
6500
|
const value = (i) => {
|
|
5448
6501
|
const v = args[i + 1];
|
|
5449
6502
|
return v === void 0 || v.startsWith("-") ? null : v;
|
|
@@ -5457,7 +6510,7 @@ function main10(argv) {
|
|
|
5457
6510
|
`);
|
|
5458
6511
|
return 1;
|
|
5459
6512
|
}
|
|
5460
|
-
const resolved =
|
|
6513
|
+
const resolved = path14.resolve(v);
|
|
5461
6514
|
if (a === "--project") projectDir = resolved;
|
|
5462
6515
|
else if (a === "--global") globalDir = resolved;
|
|
5463
6516
|
else sourceDir = resolved;
|
|
@@ -5479,9 +6532,9 @@ function main10(argv) {
|
|
|
5479
6532
|
}
|
|
5480
6533
|
function _isCliEntry10() {
|
|
5481
6534
|
if (process2.argv[1] === void 0) return false;
|
|
5482
|
-
if (import.meta.url === pathToFileURL10(
|
|
6535
|
+
if (import.meta.url === pathToFileURL10(path14.resolve(process2.argv[1])).href) return true;
|
|
5483
6536
|
try {
|
|
5484
|
-
return
|
|
6537
|
+
return fs16.realpathSync(_HERE2) === fs16.realpathSync(path14.resolve(process2.argv[1]));
|
|
5485
6538
|
} catch {
|
|
5486
6539
|
return false;
|
|
5487
6540
|
}
|
|
@@ -5491,12 +6544,12 @@ if (_isCliEntry10()) {
|
|
|
5491
6544
|
}
|
|
5492
6545
|
|
|
5493
6546
|
// src/scripts/report_skill_activation.ts
|
|
5494
|
-
import * as
|
|
6547
|
+
import * as fs17 from "node:fs";
|
|
5495
6548
|
import * as os4 from "node:os";
|
|
5496
|
-
import * as
|
|
6549
|
+
import * as path15 from "node:path";
|
|
5497
6550
|
import { fileURLToPath as fileURLToPath11, pathToFileURL as pathToFileURL11 } from "node:url";
|
|
5498
6551
|
var _HERE3 = fileURLToPath11(import.meta.url);
|
|
5499
|
-
var REPO_ROOT3 =
|
|
6552
|
+
var REPO_ROOT3 = path15.resolve(path15.dirname(_HERE3), "..", "..");
|
|
5500
6553
|
var SKILLS_ROOT = "src/skills";
|
|
5501
6554
|
var TRIGGER_KEYS = ["triggers:", "trigger_description:", "file_pattern:", "path_prefix:"];
|
|
5502
6555
|
var DETERMINISTIC_RE = /^\s*(?:[-*]\s*)?(?:\*\*)?(MUST|NEVER|ALWAYS)\b/m;
|
|
@@ -5507,25 +6560,25 @@ function censusSkills(root) {
|
|
|
5507
6560
|
withTriggerCorpus: [],
|
|
5508
6561
|
withDeterministicObligation: []
|
|
5509
6562
|
};
|
|
5510
|
-
if (!
|
|
6563
|
+
if (!fs17.existsSync(root)) {
|
|
5511
6564
|
return out;
|
|
5512
6565
|
}
|
|
5513
|
-
for (const entry of
|
|
6566
|
+
for (const entry of fs17.readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
5514
6567
|
if (!entry.isDirectory()) {
|
|
5515
6568
|
continue;
|
|
5516
6569
|
}
|
|
5517
|
-
const file =
|
|
5518
|
-
if (!
|
|
6570
|
+
const file = path15.join(root, entry.name, "SKILL.md");
|
|
6571
|
+
if (!fs17.existsSync(file)) {
|
|
5519
6572
|
continue;
|
|
5520
6573
|
}
|
|
5521
6574
|
out.total += 1;
|
|
5522
|
-
const text =
|
|
6575
|
+
const text = fs17.readFileSync(file, "utf8");
|
|
5523
6576
|
const fmEnd = text.startsWith("---") ? text.indexOf("\n---", 3) : -1;
|
|
5524
6577
|
const fm = fmEnd === -1 ? "" : text.slice(0, fmEnd);
|
|
5525
6578
|
if (TRIGGER_KEYS.some((k) => new RegExp(`^${k}`, "m").test(fm))) {
|
|
5526
6579
|
out.withTriggerKey.push(entry.name);
|
|
5527
6580
|
}
|
|
5528
|
-
if (
|
|
6581
|
+
if (fs17.existsSync(path15.join(root, entry.name, "evals", "triggers.json"))) {
|
|
5529
6582
|
out.withTriggerCorpus.push(entry.name);
|
|
5530
6583
|
}
|
|
5531
6584
|
const body = fmEnd === -1 ? text : text.slice(fmEnd + 4);
|
|
@@ -5536,17 +6589,17 @@ function censusSkills(root) {
|
|
|
5536
6589
|
return out;
|
|
5537
6590
|
}
|
|
5538
6591
|
function defaultStore2(cwd) {
|
|
5539
|
-
return
|
|
6592
|
+
return path15.join(os4.homedir(), ".claude", "projects", projectStoreSlug(cwd));
|
|
5540
6593
|
}
|
|
5541
6594
|
function measureUsage(store, limit) {
|
|
5542
6595
|
const rep = { store, sessions: 0, assistantTurns: 0, invocations: 0, bySkill: {} };
|
|
5543
|
-
if (!
|
|
6596
|
+
if (!fs17.existsSync(store)) {
|
|
5544
6597
|
return rep;
|
|
5545
6598
|
}
|
|
5546
|
-
const files =
|
|
6599
|
+
const files = fs17.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => path15.join(store, f)).sort((a, b) => fs17.statSync(b).mtimeMs - fs17.statSync(a).mtimeMs).slice(0, limit);
|
|
5547
6600
|
rep.sessions = files.length;
|
|
5548
6601
|
for (const file of files) {
|
|
5549
|
-
for (const line of
|
|
6602
|
+
for (const line of fs17.readFileSync(file, "utf8").split("\n")) {
|
|
5550
6603
|
if (!line.trim()) {
|
|
5551
6604
|
continue;
|
|
5552
6605
|
}
|
|
@@ -5619,15 +6672,15 @@ function buildRecord(census, usage, today) {
|
|
|
5619
6672
|
assistant_turns: usage.reduce((a, u) => a + u.assistantTurns, 0),
|
|
5620
6673
|
invocations: usage.reduce((a, u) => a + u.invocations, 0),
|
|
5621
6674
|
distinct_skills_invoked: distinct.size,
|
|
5622
|
-
stores: usage.map((u) =>
|
|
6675
|
+
stores: usage.map((u) => path15.basename(u.store))
|
|
5623
6676
|
};
|
|
5624
6677
|
}
|
|
5625
6678
|
function readRecord(repoRoot) {
|
|
5626
|
-
const file =
|
|
5627
|
-
if (!
|
|
6679
|
+
const file = path15.join(repoRoot, RECORD_PATH);
|
|
6680
|
+
if (!fs17.existsSync(file)) {
|
|
5628
6681
|
return null;
|
|
5629
6682
|
}
|
|
5630
|
-
return JSON.parse(
|
|
6683
|
+
return JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
5631
6684
|
}
|
|
5632
6685
|
function render3(census, usage, published = null) {
|
|
5633
6686
|
const lines = [];
|
|
@@ -5662,7 +6715,7 @@ function render3(census, usage, published = null) {
|
|
|
5662
6715
|
}
|
|
5663
6716
|
totalInv += u.invocations;
|
|
5664
6717
|
lines.push(
|
|
5665
|
-
` ${
|
|
6718
|
+
` ${path15.basename(u.store).slice(-38).padEnd(38)} sessions=${String(u.sessions).padStart(3)} asst=${String(u.assistantTurns).padStart(6)} Skill calls=${String(u.invocations).padStart(3)}`
|
|
5666
6719
|
);
|
|
5667
6720
|
}
|
|
5668
6721
|
lines.push("");
|
|
@@ -5766,7 +6819,7 @@ function main11(argv) {
|
|
|
5766
6819
|
if (stores.length === 0) {
|
|
5767
6820
|
stores.push(defaultStore2(REPO_ROOT3));
|
|
5768
6821
|
}
|
|
5769
|
-
const census = censusSkills(
|
|
6822
|
+
const census = censusSkills(path15.join(root, SKILLS_ROOT));
|
|
5770
6823
|
const usage = stores.map((s) => measureUsage(s, limit));
|
|
5771
6824
|
process.stdout.write(`${render3(census, usage, readRecord(root))}
|
|
5772
6825
|
`);
|
|
@@ -5787,27 +6840,27 @@ function main11(argv) {
|
|
|
5787
6840
|
return 1;
|
|
5788
6841
|
}
|
|
5789
6842
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5790
|
-
const file =
|
|
5791
|
-
|
|
5792
|
-
|
|
6843
|
+
const file = path15.join(root, RECORD_PATH);
|
|
6844
|
+
fs17.mkdirSync(path15.dirname(file), { recursive: true });
|
|
6845
|
+
fs17.writeFileSync(file, `${JSON.stringify(buildRecord(census, usage, today), null, 2)}
|
|
5793
6846
|
`);
|
|
5794
6847
|
process.stdout.write(`
|
|
5795
6848
|
wrote ${RECORD_PATH} (measured ${today})
|
|
5796
6849
|
`);
|
|
5797
6850
|
return 0;
|
|
5798
6851
|
}
|
|
5799
|
-
if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL11(
|
|
6852
|
+
if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL11(path15.resolve(process.argv[1])).href) {
|
|
5800
6853
|
process.exit(main11(process.argv.slice(2)));
|
|
5801
6854
|
}
|
|
5802
6855
|
|
|
5803
6856
|
// src/scripts/report_skill_obligation_violations.ts
|
|
5804
|
-
import * as
|
|
6857
|
+
import * as fs18 from "node:fs";
|
|
5805
6858
|
import * as os5 from "node:os";
|
|
5806
|
-
import * as
|
|
6859
|
+
import * as path16 from "node:path";
|
|
5807
6860
|
import process3 from "node:process";
|
|
5808
6861
|
import { fileURLToPath as fileURLToPath12, pathToFileURL as pathToFileURL12 } from "node:url";
|
|
5809
6862
|
var _HERE4 = fileURLToPath12(import.meta.url);
|
|
5810
|
-
var REPO_ROOT4 =
|
|
6863
|
+
var REPO_ROOT4 = path16.resolve(path16.dirname(_HERE4), "..", "..");
|
|
5811
6864
|
var OBLIGATION_LINE_RE = new RegExp(`${DETERMINISTIC_RE.source}.*`, "gm");
|
|
5812
6865
|
var PATH_RE = /`([A-Za-z0-9_./-]*\/[A-Za-z0-9_.-]+\.[a-z]{2,6})`/g;
|
|
5813
6866
|
var CMD_RE = /`([a-z][a-z0-9-]*(?: [a-z][a-z0-9:._-]*){1,3})`/g;
|
|
@@ -5845,7 +6898,7 @@ function _artefacts(line) {
|
|
|
5845
6898
|
return out;
|
|
5846
6899
|
}
|
|
5847
6900
|
function extractObligations(repoRoot) {
|
|
5848
|
-
const skillsRoot =
|
|
6901
|
+
const skillsRoot = path16.join(repoRoot, SKILLS_ROOT);
|
|
5849
6902
|
const census = censusSkills(skillsRoot);
|
|
5850
6903
|
const out = {
|
|
5851
6904
|
skills: census.withDeterministicObligation,
|
|
@@ -5859,7 +6912,7 @@ function extractObligations(repoRoot) {
|
|
|
5859
6912
|
for (const skill of census.withDeterministicObligation) {
|
|
5860
6913
|
let text;
|
|
5861
6914
|
try {
|
|
5862
|
-
text =
|
|
6915
|
+
text = fs18.readFileSync(path16.join(skillsRoot, skill, "SKILL.md"), "utf-8");
|
|
5863
6916
|
} catch {
|
|
5864
6917
|
continue;
|
|
5865
6918
|
}
|
|
@@ -5975,7 +7028,7 @@ function scanSessionForViolations(session, lines, forbidden, parsed) {
|
|
|
5975
7028
|
return flags;
|
|
5976
7029
|
}
|
|
5977
7030
|
function defaultStore3(cwd) {
|
|
5978
|
-
return
|
|
7031
|
+
return path16.join(os5.homedir(), ".claude", "projects", projectStoreSlug(cwd));
|
|
5979
7032
|
}
|
|
5980
7033
|
function scanStore2(repoRoot, store, limit) {
|
|
5981
7034
|
const census = extractObligations(repoRoot);
|
|
@@ -5988,14 +7041,14 @@ function scanStore2(repoRoot, store, limit) {
|
|
|
5988
7041
|
};
|
|
5989
7042
|
let files;
|
|
5990
7043
|
try {
|
|
5991
|
-
files =
|
|
7044
|
+
files = fs18.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ p: path16.join(store, f), m: fs18.statSync(path16.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.p);
|
|
5992
7045
|
} catch {
|
|
5993
7046
|
return report;
|
|
5994
7047
|
}
|
|
5995
7048
|
report.sessions = files.length;
|
|
5996
7049
|
for (const file of files) {
|
|
5997
|
-
const lines =
|
|
5998
|
-
const id =
|
|
7050
|
+
const lines = fs18.readFileSync(file, "utf-8").split("\n");
|
|
7051
|
+
const id = path16.basename(file).replace(/\.jsonl$/, "").slice(0, 8);
|
|
5999
7052
|
const entries = _parseSession(lines);
|
|
6000
7053
|
if (loadedSkillsFrom(entries).size > 0) report.sessionsWithASkill += 1;
|
|
6001
7054
|
report.flags.push(...scanSessionForViolations(id, lines, census.forbidden, entries));
|
|
@@ -6120,9 +7173,9 @@ function main12(argv) {
|
|
|
6120
7173
|
}
|
|
6121
7174
|
function _isCliEntry11() {
|
|
6122
7175
|
if (process3.argv[1] === void 0) return false;
|
|
6123
|
-
if (import.meta.url === pathToFileURL12(
|
|
7176
|
+
if (import.meta.url === pathToFileURL12(path16.resolve(process3.argv[1])).href) return true;
|
|
6124
7177
|
try {
|
|
6125
|
-
return
|
|
7178
|
+
return fs18.realpathSync(_HERE4) === fs18.realpathSync(path16.resolve(process3.argv[1]));
|
|
6126
7179
|
} catch {
|
|
6127
7180
|
return false;
|
|
6128
7181
|
}
|
|
@@ -6133,20 +7186,20 @@ if (_isCliEntry11()) {
|
|
|
6133
7186
|
|
|
6134
7187
|
// src/scripts/report_conformance_funnel.ts
|
|
6135
7188
|
var _HERE5 = fileURLToPath13(import.meta.url);
|
|
6136
|
-
var REPO_ROOT5 =
|
|
7189
|
+
var REPO_ROOT5 = path17.resolve(path17.dirname(_HERE5), "..", "..");
|
|
6137
7190
|
function buildFunnel(opts = {}) {
|
|
6138
7191
|
const repoRoot = opts.repoRoot ?? REPO_ROOT5;
|
|
6139
|
-
const projectRulesDir = opts.projectRulesDir ??
|
|
6140
|
-
const globalRulesDir = opts.globalRulesDir ??
|
|
6141
|
-
const sourceDir = opts.sourceDir ??
|
|
7192
|
+
const projectRulesDir = opts.projectRulesDir ?? path17.join(repoRoot, PROJECT_RULES);
|
|
7193
|
+
const globalRulesDir = opts.globalRulesDir ?? path17.join(os6.homedir(), GLOBAL_RULES);
|
|
7194
|
+
const sourceDir = opts.sourceDir ?? path17.join(repoRoot, PROJECTION_SOURCE);
|
|
6142
7195
|
const store = opts.store ?? defaultStore(repoRoot);
|
|
6143
7196
|
const limit = opts.limit ?? 30;
|
|
6144
7197
|
return {
|
|
6145
7198
|
delivered: measureDelivered(projectRulesDir, globalRulesDir),
|
|
6146
7199
|
divergence: compareCarriers(projectRulesDir, globalRulesDir, sourceDir),
|
|
6147
|
-
skills: censusSkills(
|
|
7200
|
+
skills: censusSkills(path17.join(repoRoot, SKILLS_ROOT)),
|
|
6148
7201
|
usage: measureUsage(store, limit),
|
|
6149
|
-
storePresent:
|
|
7202
|
+
storePresent: fs19.existsSync(store),
|
|
6150
7203
|
sk2: scanStore2(repoRoot, store, limit)
|
|
6151
7204
|
};
|
|
6152
7205
|
}
|
|
@@ -6287,7 +7340,7 @@ function main13(argv) {
|
|
|
6287
7340
|
`);
|
|
6288
7341
|
return 1;
|
|
6289
7342
|
}
|
|
6290
|
-
const resolved =
|
|
7343
|
+
const resolved = path17.resolve(v);
|
|
6291
7344
|
if (a === "--store") opts.store = resolved;
|
|
6292
7345
|
else if (a === "--project") opts.projectRulesDir = resolved;
|
|
6293
7346
|
else if (a === "--global") opts.globalRulesDir = resolved;
|
|
@@ -6319,9 +7372,9 @@ function main13(argv) {
|
|
|
6319
7372
|
}
|
|
6320
7373
|
function _isCliEntry12() {
|
|
6321
7374
|
if (process4.argv[1] === void 0) return false;
|
|
6322
|
-
if (import.meta.url === pathToFileURL13(
|
|
7375
|
+
if (import.meta.url === pathToFileURL13(path17.resolve(process4.argv[1])).href) return true;
|
|
6323
7376
|
try {
|
|
6324
|
-
return
|
|
7377
|
+
return fs19.realpathSync(_HERE5) === fs19.realpathSync(path17.resolve(process4.argv[1]));
|
|
6325
7378
|
} catch {
|
|
6326
7379
|
return false;
|
|
6327
7380
|
}
|
|
@@ -6358,20 +7411,20 @@ function conformanceLogPath() {
|
|
|
6358
7411
|
if (override && override.trim().length > 0) {
|
|
6359
7412
|
return override;
|
|
6360
7413
|
}
|
|
6361
|
-
return
|
|
7414
|
+
return path18.join(os7.homedir(), ".event4u", "agent-config", "conformance-log.jsonl");
|
|
6362
7415
|
}
|
|
6363
7416
|
function installLogPath() {
|
|
6364
7417
|
const override = process5.env["AGENT_CONFIG_INSTALL_LOG"];
|
|
6365
7418
|
if (override && override.trim().length > 0) {
|
|
6366
7419
|
return override;
|
|
6367
7420
|
}
|
|
6368
|
-
return
|
|
7421
|
+
return path18.join(os7.homedir(), ".event4u", "agent-config", "install-log.jsonl");
|
|
6369
7422
|
}
|
|
6370
7423
|
function _installLogExpected(projectRoot) {
|
|
6371
|
-
return
|
|
7424
|
+
return fs20.existsSync(manifest_path(projectRoot));
|
|
6372
7425
|
}
|
|
6373
7426
|
function _check_txlog_clean(logPath = installLogPath(), projectRoot = null) {
|
|
6374
|
-
const entries =
|
|
7427
|
+
const entries = fs20.existsSync(logPath) ? readRecentEntries(logPath) : [];
|
|
6375
7428
|
if (entries.length === 0) {
|
|
6376
7429
|
if (projectRoot !== null && _installLogExpected(projectRoot)) {
|
|
6377
7430
|
return {
|
|
@@ -6405,11 +7458,11 @@ function _check_txlog_clean(logPath = installLogPath(), projectRoot = null) {
|
|
|
6405
7458
|
};
|
|
6406
7459
|
}
|
|
6407
7460
|
function routeTargetPaths(target) {
|
|
6408
|
-
return routeTargetPathsPosix(target).map((rel) =>
|
|
7461
|
+
return routeTargetPathsPosix(target).map((rel) => path18.join(...rel.split("/")));
|
|
6409
7462
|
}
|
|
6410
7463
|
function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
6411
|
-
const routerPath =
|
|
6412
|
-
if (!
|
|
7464
|
+
const routerPath = path18.join(packageRoot, "dist", "router.json");
|
|
7465
|
+
if (!fs20.existsSync(routerPath)) {
|
|
6413
7466
|
return {
|
|
6414
7467
|
id: "router-pointers",
|
|
6415
7468
|
status: "fail",
|
|
@@ -6419,7 +7472,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
6419
7472
|
}
|
|
6420
7473
|
let router;
|
|
6421
7474
|
try {
|
|
6422
|
-
router = JSON.parse(
|
|
7475
|
+
router = JSON.parse(fs20.readFileSync(routerPath, "utf8"));
|
|
6423
7476
|
} catch (exc) {
|
|
6424
7477
|
return {
|
|
6425
7478
|
id: "router-pointers",
|
|
@@ -6428,13 +7481,13 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
6428
7481
|
remedy: "reinstall the package \u2014 dist/router.json is corrupt"
|
|
6429
7482
|
};
|
|
6430
7483
|
}
|
|
6431
|
-
const rulesDir =
|
|
7484
|
+
const rulesDir = path18.join(packageRoot, "dist", "agent-src", "rules");
|
|
6432
7485
|
const broken = [];
|
|
6433
7486
|
let ids = 0;
|
|
6434
7487
|
let targets = 0;
|
|
6435
7488
|
for (const kernelId of router["kernel"] ?? []) {
|
|
6436
7489
|
ids += 1;
|
|
6437
|
-
if (!
|
|
7490
|
+
if (!fs20.existsSync(path18.join(rulesDir, `${kernelId}.md`))) {
|
|
6438
7491
|
broken.push(`kernel:${kernelId} \u2192 rules/${kernelId}.md missing`);
|
|
6439
7492
|
}
|
|
6440
7493
|
}
|
|
@@ -6442,7 +7495,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
6442
7495
|
for (const entry of router[tier] ?? []) {
|
|
6443
7496
|
const ruleId = String(entry["id"] ?? "");
|
|
6444
7497
|
ids += 1;
|
|
6445
|
-
if (!
|
|
7498
|
+
if (!fs20.existsSync(path18.join(rulesDir, `${ruleId}.md`))) {
|
|
6446
7499
|
broken.push(`${tier}:${ruleId} \u2192 rules/${ruleId}.md missing`);
|
|
6447
7500
|
}
|
|
6448
7501
|
for (const target of entry["routes_to"] ?? []) {
|
|
@@ -6452,7 +7505,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
6452
7505
|
broken.push(`${ruleId} \u2192 unparseable routes_to ${JSON.stringify(target)}`);
|
|
6453
7506
|
continue;
|
|
6454
7507
|
}
|
|
6455
|
-
if (!candidates.some((rel) =>
|
|
7508
|
+
if (!candidates.some((rel) => fs20.existsSync(path18.join(packageRoot, rel)))) {
|
|
6456
7509
|
broken.push(`${ruleId} \u2192 ${target} (${candidates[0]} missing)`);
|
|
6457
7510
|
}
|
|
6458
7511
|
}
|
|
@@ -6484,9 +7537,9 @@ function detectSmokePlatform(projectRoot) {
|
|
|
6484
7537
|
return "claude";
|
|
6485
7538
|
}
|
|
6486
7539
|
function dispatcherRunner(packageRoot) {
|
|
6487
|
-
const localTsx =
|
|
6488
|
-
const script =
|
|
6489
|
-
if (
|
|
7540
|
+
const localTsx = path18.join(packageRoot, "node_modules", ".bin", "tsx");
|
|
7541
|
+
const script = path18.join(packageRoot, "src", "scripts", "hooks", "dispatch_hook.ts");
|
|
7542
|
+
if (fs20.existsSync(localTsx)) {
|
|
6490
7543
|
return [localTsx, script];
|
|
6491
7544
|
}
|
|
6492
7545
|
return ["npx", "tsx", script];
|
|
@@ -6536,12 +7589,12 @@ function _check_hook_dispatcher(projectRoot, packageRoot = PACKAGE_ROOT, opts =
|
|
|
6536
7589
|
};
|
|
6537
7590
|
}
|
|
6538
7591
|
function readSettingsMode(projectRoot) {
|
|
6539
|
-
const settingsPath =
|
|
6540
|
-
if (!
|
|
7592
|
+
const settingsPath = path18.join(projectRoot, ".agent-settings.yml");
|
|
7593
|
+
if (!fs20.existsSync(settingsPath)) {
|
|
6541
7594
|
return "eager-all";
|
|
6542
7595
|
}
|
|
6543
7596
|
try {
|
|
6544
|
-
const parsed = YAML.parse(
|
|
7597
|
+
const parsed = YAML.parse(fs20.readFileSync(settingsPath, "utf8"));
|
|
6545
7598
|
const lean = parsed?.["lean_projection"] ?? null;
|
|
6546
7599
|
const mode = lean?.["mode"];
|
|
6547
7600
|
return typeof mode === "string" && mode.length > 0 ? mode : "eager-all";
|
|
@@ -6552,7 +7605,7 @@ function readSettingsMode(projectRoot) {
|
|
|
6552
7605
|
function kernelIdsFromRouter(packageRoot) {
|
|
6553
7606
|
try {
|
|
6554
7607
|
const router = JSON.parse(
|
|
6555
|
-
|
|
7608
|
+
fs20.readFileSync(path18.join(packageRoot, "dist", "router.json"), "utf8")
|
|
6556
7609
|
);
|
|
6557
7610
|
return new Set((router["kernel"] ?? []).map(String));
|
|
6558
7611
|
} catch {
|
|
@@ -6569,8 +7622,8 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
|
|
|
6569
7622
|
remedy: "set lean_projection.mode to 'eager-all' or 'thin' in .agent-settings.yml"
|
|
6570
7623
|
};
|
|
6571
7624
|
}
|
|
6572
|
-
const rulesDir = projectedRulesDir ??
|
|
6573
|
-
if (!
|
|
7625
|
+
const rulesDir = projectedRulesDir ?? path18.join(projectRoot, ".augment", "rules");
|
|
7626
|
+
if (!fs20.existsSync(rulesDir)) {
|
|
6574
7627
|
return {
|
|
6575
7628
|
id: "lean-projection",
|
|
6576
7629
|
status: "skipped",
|
|
@@ -6579,7 +7632,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
|
|
|
6579
7632
|
};
|
|
6580
7633
|
}
|
|
6581
7634
|
const kernel = kernelIdsFromRouter(packageRoot);
|
|
6582
|
-
const files =
|
|
7635
|
+
const files = fs20.readdirSync(rulesDir).filter((f) => f.endsWith(".md") && f !== "INDEX.md").sort();
|
|
6583
7636
|
const nonKernel = files.filter((f) => !kernel.has(f.replace(/\.md$/, "")));
|
|
6584
7637
|
if (nonKernel.length === 0) {
|
|
6585
7638
|
return {
|
|
@@ -6593,7 +7646,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
|
|
|
6593
7646
|
for (const f of nonKernel) {
|
|
6594
7647
|
let text = "";
|
|
6595
7648
|
try {
|
|
6596
|
-
text =
|
|
7649
|
+
text = fs20.readFileSync(path18.join(rulesDir, f), "utf8");
|
|
6597
7650
|
} catch {
|
|
6598
7651
|
continue;
|
|
6599
7652
|
}
|
|
@@ -6624,8 +7677,8 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
|
|
|
6624
7677
|
function _check_host_manifest(projectRoot) {
|
|
6625
7678
|
const detected = Object.entries(detectToolPresence(projectRoot)).filter(([, v]) => v === true).map(([k]) => k);
|
|
6626
7679
|
const detectedNote = detected.length > 0 ? `detected surfaces: ${detected.join(", ")}` : "no surfaces detected";
|
|
6627
|
-
const settingsPath =
|
|
6628
|
-
if (!
|
|
7680
|
+
const settingsPath = path18.join(projectRoot, ".agent-settings.yml");
|
|
7681
|
+
if (!fs20.existsSync(settingsPath)) {
|
|
6629
7682
|
return {
|
|
6630
7683
|
id: "host-manifest",
|
|
6631
7684
|
status: "ok",
|
|
@@ -6635,7 +7688,7 @@ function _check_host_manifest(projectRoot) {
|
|
|
6635
7688
|
}
|
|
6636
7689
|
let parsed = null;
|
|
6637
7690
|
try {
|
|
6638
|
-
parsed = YAML.parse(
|
|
7691
|
+
parsed = YAML.parse(fs20.readFileSync(settingsPath, "utf8"));
|
|
6639
7692
|
} catch (exc) {
|
|
6640
7693
|
return {
|
|
6641
7694
|
id: "host-manifest",
|
|
@@ -6809,13 +7862,13 @@ function _isCliEntry13() {
|
|
|
6809
7862
|
if (process5.argv[1] === void 0) {
|
|
6810
7863
|
return false;
|
|
6811
7864
|
}
|
|
6812
|
-
const argvUrl = pathToFileURL14(
|
|
7865
|
+
const argvUrl = pathToFileURL14(path18.resolve(process5.argv[1])).href;
|
|
6813
7866
|
if (import.meta.url === argvUrl) {
|
|
6814
7867
|
return true;
|
|
6815
7868
|
}
|
|
6816
7869
|
try {
|
|
6817
|
-
const here =
|
|
6818
|
-
const argv =
|
|
7870
|
+
const here = fs20.realpathSync(fileURLToPath14(import.meta.url));
|
|
7871
|
+
const argv = fs20.realpathSync(path18.resolve(process5.argv[1]));
|
|
6819
7872
|
return here === argv;
|
|
6820
7873
|
} catch {
|
|
6821
7874
|
return false;
|