@event4u/agent-config 16.0.0 → 16.2.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 +289 -140
- package/CONTRIBUTING.md +9 -0
- package/README.md +2 -2
- package/dist/agent-src/commands/analyze/inbox.md +26 -0
- package/dist/agent-src/commands/analyze/roadmap-repos.md +27 -0
- package/dist/agent-src/commands/challenge-me/closure.md +178 -0
- package/dist/agent-src/commands/challenge-me.md +9 -2
- package/dist/agent-src/commands/council/default.md +25 -5
- package/dist/agent-src/commands/feature/plan.md +28 -1
- package/dist/agent-src/commands/feature/roadmap.md +28 -1
- package/dist/agent-src/commands/implement-ticket.md +27 -0
- package/dist/agent-src/commands/jira-ticket.md +27 -0
- package/dist/agent-src/commands/pr/create.md +29 -0
- package/dist/agent-src/commands/pr/merge.md +60 -9
- package/dist/agent-src/commands/roadmap/ai-council.md +7 -1
- package/dist/agent-src/commands/roadmap/create.md +74 -27
- package/dist/agent-src/commands/roadmap/materialize.md +27 -0
- package/dist/agent-src/commands/roadmap/process-full.md +123 -18
- package/dist/agent-src/commands/team.md +22 -0
- package/dist/agent-src/commands/work.md +4 -0
- package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
- package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +47 -0
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +19 -23
- package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
- package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
- package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
- package/dist/agent-src/rules/autonomous-execution.md +15 -8
- package/dist/agent-src/rules/delegation-policy.md +23 -45
- package/dist/agent-src/rules/design-fidelity.md +32 -37
- package/dist/agent-src/rules/evaluator-independence.md +43 -49
- package/dist/agent-src/rules/test-first.md +41 -0
- package/dist/agent-src/rules/ui-audit-gate.md +1 -2
- package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
- package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
- package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
- package/dist/agent-src/skills/database/data/manifest.json +4 -4
- package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
- package/dist/agent-src/skills/design-review/SKILL.md +80 -4
- package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +30 -5
- package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
- package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
- package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
- package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
- package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
- package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
- package/dist/agent-src/templates/agent-settings.md +1 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +218 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -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 +260 -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/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-delegate/check_update_banner.js +40 -2
- package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
- package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
- package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
- package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
- package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
- package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-7XGBX6ZH.js} +973 -555
- package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
- package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-FP36SWJD.js} +587 -141
- package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-IGD6ERX6.js} +62 -7
- package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-JNMVLWLR.js} +99 -14
- package/dist/cli-delegate/{chunk-H32DGUJH.js → chunk-P6ZHP5W2.js} +1 -1
- package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
- package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
- package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
- package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
- package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
- 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 +985 -831
- package/dist/cli-delegate/cmd_doctor.js +7 -5
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +9 -7
- package/dist/cli-delegate/cmd_fleet.js +4 -2
- package/dist/cli-delegate/cmd_handoff.js +7 -7
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +2 -2
- package/dist/cli-delegate/cmd_preflight.js +4 -2
- package/dist/cli-delegate/cmd_prune.js +10 -8
- package/dist/cli-delegate/cmd_route_audit.js +3 -3
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +4 -4
- package/dist/cli-delegate/cmd_settings_get.js +28 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +10 -8
- package/dist/cli-delegate/cmd_uninstall.js +16 -13
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +12 -10
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +90 -43
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +10 -8
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +8 -6
- package/dist/hooks/dispatch.js +7682 -2772
- package/dist/hooks/dispatch.sha256 +1 -0
- package/dist/install/claudeRuleRewrite.js +14 -3
- package/dist/install/conflict.js +60 -10
- package/dist/install/conflictTracker.js +218 -0
- package/dist/install/fsPrimitives.js +54 -0
- package/dist/install/install.mjs +2200 -1559
- package/dist/install/manifestFiles.js +259 -0
- package/dist/install/preflight.js +14 -2
- package/dist/install/preserve.js +157 -0
- package/dist/install/recordedOwnership.js +132 -0
- package/dist/install/txlog.js +11 -2
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +3066 -1470
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +3 -2
- package/dist/scripts/_lib/fs_atomic.js +167 -0
- package/dist/scripts/_lib/installed_tools.js +460 -0
- package/dist/scripts/_lib/lean_projection_mode.js +3 -2
- package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
- package/dist/server/routes/install.js +8 -0
- package/dist/server/routes/wizard.js +9 -3
- package/dist/server/schemas/settings.js +40 -5
- package/dist/shared/missionExecution.js +97 -0
- 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/MIGRATION.md +103 -0
- package/docs/contracts/settings-classes.md +28 -7
- package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/docs/guidelines/design-fidelity-routing.md +29 -0
- package/package.json +4 -4
- package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- 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 +260 -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/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 +168 -27
- package/src/config/auto-rule-budget.json +16 -0
- package/src/config/ci-local-parity.yml +36 -5
- package/src/config/continuity-surface.json +23 -2
- package/src/config/gate-coverage.yml +377 -13
- package/src/config/gate-violation-baselines.json +10 -6
- package/src/config/hook-latency-budget.json +21 -0
- package/src/config/hook-token-budget.json +4 -2
- package/src/config/loop-surfaces.yaml +215 -0
- package/src/config/metric-registry.yml +114 -0
- package/src/config/placeholder-drift-budget.json +10 -0
- package/src/config/platform-anchor.json +3 -2
- package/src/config/release-gate-locality.yml +11 -0
- package/src/config/routing-coverage-seed.json +33 -5
- package/src/config/rule-enforcement-baseline.json +0 -1
- package/src/install/claudeRuleRewrite.ts +16 -2
- package/src/install/conflict.ts +65 -9
- package/src/install/conflictTracker.ts +299 -0
- package/src/install/fsPrimitives.ts +54 -0
- package/src/install/manifestFiles.ts +312 -0
- package/src/install/preflight.ts +16 -2
- package/src/install/preserve.ts +205 -0
- package/src/install/recordedOwnership.ts +149 -0
- package/src/install/txlog.ts +11 -2
- package/src/install/types.ts +19 -4
- package/src/scripts/_cli/cmd_conformance.ts +88 -15
- package/src/scripts/_cli/cmd_doctor.ts +7 -20
- package/src/scripts/_cli/cmd_route_explain.ts +9 -2
- package/src/scripts/_cli/cmd_settings_get.ts +44 -0
- package/src/scripts/_cli/doctor_execution.ts +173 -0
- package/src/scripts/_cli/doctor_network_posture.ts +226 -0
- package/src/scripts/_lib/activation_payload.ts +247 -0
- package/src/scripts/_lib/agent_settings.ts +4 -2
- package/src/scripts/_lib/authority_path.ts +102 -0
- package/src/scripts/_lib/body_portable.ts +102 -0
- package/src/scripts/_lib/cascade_base.ts +94 -0
- package/src/scripts/_lib/cc_transcript.ts +132 -1
- package/src/scripts/_lib/changelog_eras.ts +7 -2
- package/src/scripts/_lib/concern_sla_window.ts +200 -0
- package/src/scripts/_lib/continuation_ladder.ts +149 -2
- package/src/scripts/_lib/council_transport.ts +129 -0
- package/src/scripts/_lib/delivery_ready.ts +112 -0
- package/src/scripts/_lib/forge_protection.ts +381 -0
- package/src/scripts/_lib/forge_reader.ts +558 -0
- package/src/scripts/_lib/hook_platform_keys.ts +27 -0
- package/src/scripts/_lib/host_env_write.ts +111 -0
- package/src/scripts/_lib/json_python_parity.ts +127 -0
- package/src/scripts/_lib/lean_projection_mode.ts +14 -3
- package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
- package/src/scripts/_lib/loop_surfaces.ts +382 -0
- package/src/scripts/_lib/mission_record.ts +190 -0
- package/src/scripts/_lib/obligation_frequency.ts +217 -0
- package/src/scripts/_lib/obligations.ts +513 -0
- package/src/scripts/_lib/orchestration_record.ts +11 -20
- package/src/scripts/_lib/probe_inputs.ts +271 -0
- package/src/scripts/_lib/read_surface_scan.ts +322 -0
- package/src/scripts/_lib/release_highlights.ts +201 -11
- package/src/scripts/_lib/release_holds.ts +493 -0
- package/src/scripts/_lib/release_migration_gate.ts +184 -0
- package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
- package/src/scripts/_lib/rides_along.ts +102 -0
- package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
- package/src/scripts/_lib/shell_write_shapes.ts +108 -0
- package/src/scripts/_lib/structural_hiding.ts +622 -0
- package/src/scripts/_lib/test_delta.ts +166 -0
- package/src/scripts/_lib/test_provenance.ts +171 -0
- package/src/scripts/_lib/turn_end_refusals.ts +525 -4
- package/src/scripts/_lib/turn_end_transcript.ts +216 -0
- package/src/scripts/_lib/typed_op_grant.ts +246 -0
- package/src/scripts/_lib/typed_op_watch.ts +185 -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/ai_council/config.ts +8 -11
- package/src/scripts/ai_council/fallback_config.ts +5 -3
- package/src/scripts/ai_council/ownership_classes.ts +118 -0
- package/src/scripts/ai_team/team_dispatch.ts +4 -0
- package/src/scripts/ask_block_census.ts +285 -15
- package/src/scripts/before_complete_hook.ts +340 -23
- package/src/scripts/bench_hook_latency.ts +412 -5
- 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 +866 -0
- package/src/scripts/check_finding_dispositions.ts +22 -3
- package/src/scripts/check_gate_reachability.ts +173 -10
- package/src/scripts/check_generator_sync.ts +1025 -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 +47 -5
- package/src/scripts/check_kill_switch_table.ts +294 -0
- package/src/scripts/check_placeholder_drift.ts +221 -0
- package/src/scripts/check_preamble_payload_budget.ts +75 -2
- package/src/scripts/check_read_surface_coverage.ts +515 -0
- package/src/scripts/check_release_highlights.ts +33 -1
- package/src/scripts/check_release_holds.ts +568 -0
- package/src/scripts/check_routing_coverage.ts +378 -11
- package/src/scripts/check_rule_projection_integrity.ts +22 -12
- package/src/scripts/check_static_parity.ts +17 -0
- package/src/scripts/check_test_delta.ts +294 -0
- package/src/scripts/check_test_weakening.ts +299 -0
- package/src/scripts/ci_settle.ts +145 -19
- package/src/scripts/closure_scan.ts +447 -0
- package/src/scripts/compile_hook_manifest.ts +102 -18
- package/src/scripts/council_record_shape.ts +209 -0
- package/src/scripts/external_sources_denylist.json +29 -4
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +127 -11
- package/src/scripts/hooks/block_config_weakening.ts +229 -4
- package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
- package/src/scripts/hooks/block_plumbing_writes.ts +314 -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 +1 -2
- package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
- package/src/scripts/hooks/concern_registry.ts +6 -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 +221 -5
- package/src/scripts/hooks/design_slop_hook.ts +1 -2
- package/src/scripts/hooks/dispatch_hook.ts +25 -19
- 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 +97 -2
- package/src/scripts/hooks/exit_codes.ts +128 -0
- package/src/scripts/hooks/host_lowering.json +1 -0
- package/src/scripts/hooks/host_lowering.ts +128 -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 +539 -0
- package/src/scripts/hooks/obligation_settle_hook.ts +328 -0
- package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
- package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
- 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 +55 -4
- package/src/scripts/hooks/run_continuation_hook.ts +30 -35
- 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 +49 -0
- package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
- package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
- package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
- package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
- package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
- package/src/scripts/hooks_doctor.ts +85 -0
- package/src/scripts/hooks_status.ts +19 -0
- package/src/scripts/install.ts +109 -210
- package/src/scripts/janitor.ts +7 -0
- package/src/scripts/lint_decision_classes.ts +464 -0
- 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 +66 -0
- package/src/scripts/lint_major_migration_sections.ts +450 -0
- package/src/scripts/lint_plan_risk_register.ts +47 -3
- package/src/scripts/lint_roadmap_blockers.ts +139 -12
- package/src/scripts/lint_roadmap_producers.ts +202 -0
- package/src/scripts/lint_skill_descriptions.ts +1 -1
- package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
- package/src/scripts/mcp_server/prompts.ts +142 -1
- package/src/scripts/measure_rule_budget.ts +101 -5
- package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
- package/src/scripts/measure_turn_end_gate.ts +193 -0
- package/src/scripts/memory_lookup.ts +1 -1
- package/src/scripts/new_roadmap.ts +37 -0
- package/src/scripts/orchestration_record.ts +0 -5
- package/src/scripts/prepack-check.mjs +53 -0
- package/src/scripts/release.ts +14 -98
- package/src/scripts/release_drill.ts +267 -1
- package/src/scripts/release_publication.ts +64 -1
- package/src/scripts/report_enforcement_drift.ts +566 -0
- package/src/scripts/report_host_injection_effect.ts +3 -11
- package/src/scripts/report_invocation_surface.ts +383 -0
- package/src/scripts/report_obligation_carriers.ts +699 -0
- package/src/scripts/report_obligation_writer_split.ts +236 -0
- package/src/scripts/report_skill_menu_census.ts +208 -0
- package/src/scripts/report_standing_payload_by_host.ts +26 -152
- package/src/scripts/roadmap_verify_share.ts +213 -0
- package/src/scripts/schemas/agent-settings.schema.json +23 -0
- package/src/scripts/schemas/command.schema.json +48 -0
- package/src/scripts/schemas/review-findings.schema.json +4 -0
- package/src/scripts/schemas/skill.schema.json +44 -0
- package/src/scripts/scope_growth.ts +219 -0
- package/src/scripts/self_review_gate.ts +359 -10
- package/src/scripts/semantic_parity_census.ts +314 -0
- package/src/scripts/session_eol_report.ts +54 -1
- package/src/scripts/sweep_dead_scan_roots.ts +1 -1
- package/src/scripts/trigger_eval_grandfather.json +0 -3
- package/src/scripts/trigger_eval_rotation.ts +122 -27
- package/src/scripts/ui_conformance_probe.ts +774 -0
- package/src/scripts/update_prices.ts +85 -3
- package/src/scripts/write_bundle_digest.mjs +74 -0
- package/src/server/schemas/settings.ts +62 -7
- package/src/shared/missionExecution.ts +126 -0
- package/src/templates/consumer-settings/ONBOARDING.md +1 -0
- package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
- package/dist/ui/assets/index-D0jaCRPM.js +0 -29
- package/dist/ui/assets/index-jSfMW8_t.css +0 -1
- /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
|
+
NO_RED_EVIDENCE_REASON,
|
|
3
4
|
deriveSessionKey,
|
|
5
|
+
describeRecordFinding,
|
|
4
6
|
foldRefusal,
|
|
7
|
+
foldShadow,
|
|
8
|
+
hasRedThenGreen,
|
|
5
9
|
isCiPoll,
|
|
6
10
|
isVacuousOutput,
|
|
11
|
+
isVerificationCommand,
|
|
7
12
|
parseRecord,
|
|
13
|
+
parseShadowRecord,
|
|
8
14
|
pendingCount,
|
|
9
15
|
readInstallBoundary,
|
|
16
|
+
readRunEvidence,
|
|
17
|
+
readTurnRunState,
|
|
10
18
|
sessionRefusalFile,
|
|
19
|
+
sessionShadowFile,
|
|
11
20
|
statePathFor,
|
|
12
21
|
validateAssumption
|
|
13
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-YJ72UIAK.js";
|
|
14
23
|
import {
|
|
15
24
|
readHookStdin
|
|
16
25
|
} from "./chunk-F7DQWYR5.js";
|
|
@@ -36,14 +45,15 @@ import {
|
|
|
36
45
|
frontmatterPaths,
|
|
37
46
|
proseEqual,
|
|
38
47
|
splitFrontmatter
|
|
39
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-FP36SWJD.js";
|
|
40
49
|
import "./chunk-FTAISGDQ.js";
|
|
41
50
|
import "./chunk-M2DPGFZ4.js";
|
|
51
|
+
import "./chunk-ROYYTEZU.js";
|
|
42
52
|
import "./chunk-BTWUYDGN.js";
|
|
43
53
|
import {
|
|
44
54
|
projectStoreSlug,
|
|
45
55
|
resolveSessionRole
|
|
46
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-JNMVLWLR.js";
|
|
47
57
|
import {
|
|
48
58
|
atomic_write_json,
|
|
49
59
|
atomic_write_text,
|
|
@@ -57,18 +67,23 @@ import {
|
|
|
57
67
|
import {
|
|
58
68
|
artefact_roots
|
|
59
69
|
} from "./chunk-NUHLI2RX.js";
|
|
70
|
+
import {
|
|
71
|
+
readRecentEntries
|
|
72
|
+
} from "./chunk-5R7HOLAF.js";
|
|
60
73
|
import {
|
|
61
74
|
detectToolPresence
|
|
62
75
|
} from "./chunk-A4QJ6ENT.js";
|
|
63
76
|
import {
|
|
64
77
|
atomicAppendLine,
|
|
78
|
+
probeHostCapabilities
|
|
79
|
+
} from "./chunk-6KM6ORRY.js";
|
|
80
|
+
import "./chunk-T7L2QK3K.js";
|
|
81
|
+
import "./chunk-DMSZHXZS.js";
|
|
82
|
+
import {
|
|
65
83
|
manifest_path,
|
|
66
|
-
probeHostCapabilities,
|
|
67
84
|
read_manifest
|
|
68
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-4E2W63DA.js";
|
|
69
86
|
import "./chunk-MWRD5YOH.js";
|
|
70
|
-
import "./chunk-T7L2QK3K.js";
|
|
71
|
-
import "./chunk-DMSZHXZS.js";
|
|
72
87
|
import {
|
|
73
88
|
require_dist
|
|
74
89
|
} from "./chunk-OY53J6II.js";
|
|
@@ -77,7 +92,7 @@ import {
|
|
|
77
92
|
} from "./chunk-MCJYCQPF.js";
|
|
78
93
|
import {
|
|
79
94
|
load_agent_settings
|
|
80
|
-
} from "./chunk-
|
|
95
|
+
} from "./chunk-FC75PGJE.js";
|
|
81
96
|
import "./chunk-NDJLNC6Q.js";
|
|
82
97
|
import {
|
|
83
98
|
__toESM
|
|
@@ -85,46 +100,13 @@ import {
|
|
|
85
100
|
|
|
86
101
|
// src/scripts/_cli/cmd_conformance.ts
|
|
87
102
|
var YAML = __toESM(require_dist(), 1);
|
|
88
|
-
import * as
|
|
103
|
+
import * as fs16 from "node:fs";
|
|
89
104
|
import * as os7 from "node:os";
|
|
90
105
|
import * as path15 from "node:path";
|
|
91
106
|
import process5 from "node:process";
|
|
92
|
-
import { spawnSync as
|
|
107
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
93
108
|
import { fileURLToPath as fileURLToPath14, pathToFileURL as pathToFileURL14 } from "node:url";
|
|
94
109
|
|
|
95
|
-
// src/install/txlog.ts
|
|
96
|
-
import { createReadStream, createWriteStream, existsSync, readFileSync, renameSync, statSync, unlinkSync } from "node:fs";
|
|
97
|
-
var ROTATION_MAX_BYTES = 10 * 1024 * 1024;
|
|
98
|
-
var ROTATION_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
99
|
-
var RECOVERY_DEPTH_CAP = 500;
|
|
100
|
-
function readRecentEntries(logPath) {
|
|
101
|
-
if (!existsSync(logPath)) {
|
|
102
|
-
return [];
|
|
103
|
-
}
|
|
104
|
-
const raw = readFileSync(logPath, "utf8");
|
|
105
|
-
const lines = raw.split("\n").filter((l) => l.length > 0);
|
|
106
|
-
const tail = lines.slice(-RECOVERY_DEPTH_CAP);
|
|
107
|
-
const out = [];
|
|
108
|
-
for (const line of tail) {
|
|
109
|
-
const parsed = tryParseEntry(line);
|
|
110
|
-
if (parsed !== null) {
|
|
111
|
-
out.push(parsed);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return out;
|
|
115
|
-
}
|
|
116
|
-
function tryParseEntry(line) {
|
|
117
|
-
try {
|
|
118
|
-
const obj = JSON.parse(line);
|
|
119
|
-
if (typeof obj.ts !== "string" || typeof obj.kind !== "string" || typeof obj.path !== "string") {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
return obj;
|
|
123
|
-
} catch {
|
|
124
|
-
return null;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
110
|
// src/scripts/router_target_paths.mjs
|
|
129
111
|
var ROUTE_TARGET_TEMPLATES = Object.freeze({
|
|
130
112
|
skill: Object.freeze(["dist/agent-src/skills/{id}/SKILL.md"]),
|
|
@@ -151,7 +133,7 @@ function routeTargetPathsPosix(target) {
|
|
|
151
133
|
}
|
|
152
134
|
|
|
153
135
|
// src/scripts/report_conformance_funnel.ts
|
|
154
|
-
import * as
|
|
136
|
+
import * as fs15 from "node:fs";
|
|
155
137
|
import * as os6 from "node:os";
|
|
156
138
|
import * as path14 from "node:path";
|
|
157
139
|
import process4 from "node:process";
|
|
@@ -159,7 +141,7 @@ import { fileURLToPath as fileURLToPath13, pathToFileURL as pathToFileURL13 } fr
|
|
|
159
141
|
|
|
160
142
|
// src/scripts/conformance_scan.ts
|
|
161
143
|
import { createHash as createHash3 } from "node:crypto";
|
|
162
|
-
import
|
|
144
|
+
import fs11 from "node:fs";
|
|
163
145
|
import os2 from "node:os";
|
|
164
146
|
import path10 from "node:path";
|
|
165
147
|
import { fileURLToPath as fileURLToPath9, pathToFileURL as pathToFileURL9 } from "node:url";
|
|
@@ -1945,7 +1927,7 @@ var COMMAND_OBJECTS = {
|
|
|
1945
1927
|
};
|
|
1946
1928
|
|
|
1947
1929
|
// src/scripts/hooks/turn_end_gate_hook.ts
|
|
1948
|
-
import * as
|
|
1930
|
+
import * as fs8 from "node:fs";
|
|
1949
1931
|
import * as path7 from "node:path";
|
|
1950
1932
|
import { fileURLToPath as fileURLToPath6, pathToFileURL as pathToFileURL6 } from "node:url";
|
|
1951
1933
|
|
|
@@ -2325,162 +2307,6 @@ function detectDroppedDecision(assistantTurnTexts, closingFromPayload = false) {
|
|
|
2325
2307
|
return null;
|
|
2326
2308
|
}
|
|
2327
2309
|
|
|
2328
|
-
// src/scripts/hooks/end_review_nudge_hook.ts
|
|
2329
|
-
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2330
|
-
import * as crypto from "node:crypto";
|
|
2331
|
-
import * as fs5 from "node:fs";
|
|
2332
|
-
import * as os from "node:os";
|
|
2333
|
-
import * as path5 from "node:path";
|
|
2334
|
-
import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL4 } from "node:url";
|
|
2335
|
-
|
|
2336
|
-
// src/scripts/_lib/review_baseline.ts
|
|
2337
|
-
import { spawnSync } from "node:child_process";
|
|
2338
|
-
import * as fs4 from "node:fs";
|
|
2339
|
-
import * as path4 from "node:path";
|
|
2340
|
-
function currentHeadSha(cwd) {
|
|
2341
|
-
try {
|
|
2342
|
-
const r = spawnSync("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" });
|
|
2343
|
-
if (r.status !== 0) return null;
|
|
2344
|
-
const out = (r.stdout ?? "").trim();
|
|
2345
|
-
return out === "" ? null : out;
|
|
2346
|
-
} catch {
|
|
2347
|
-
return null;
|
|
2348
|
-
}
|
|
2349
|
-
}
|
|
2350
|
-
var BASELINE_STATE_REL = path4.join("agents", "runtime", "state", "review-baseline");
|
|
2351
|
-
function baselineStateDir(workspaceRoot) {
|
|
2352
|
-
return path4.join(workspaceRoot, BASELINE_STATE_REL);
|
|
2353
|
-
}
|
|
2354
|
-
function baselineStateFile(workspaceRoot, sessionKey) {
|
|
2355
|
-
return path4.join(baselineStateDir(workspaceRoot), `${sessionKey}.json`);
|
|
2356
|
-
}
|
|
2357
|
-
function parseBaseline(raw) {
|
|
2358
|
-
let decoded;
|
|
2359
|
-
try {
|
|
2360
|
-
decoded = JSON.parse(raw);
|
|
2361
|
-
} catch {
|
|
2362
|
-
return null;
|
|
2363
|
-
}
|
|
2364
|
-
if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
|
|
2365
|
-
const o = decoded;
|
|
2366
|
-
const lines = o["baseline_lines"];
|
|
2367
|
-
if (typeof lines !== "number" || !Number.isFinite(lines) || lines < 0) return null;
|
|
2368
|
-
const written = o["written_at"];
|
|
2369
|
-
if (typeof written !== "string") return null;
|
|
2370
|
-
const head = o["head_sha"];
|
|
2371
|
-
const measure = o["measure"];
|
|
2372
|
-
return {
|
|
2373
|
-
head_sha: typeof head === "string" && head !== "" ? head : null,
|
|
2374
|
-
baseline_lines: lines,
|
|
2375
|
-
...measure === "exact" || measure === "capped_approximation" ? { measure } : {},
|
|
2376
|
-
written_at: written
|
|
2377
|
-
};
|
|
2378
|
-
}
|
|
2379
|
-
function readBaseline(workspaceRoot, sessionKey) {
|
|
2380
|
-
const file = baselineStateFile(workspaceRoot, sessionKey);
|
|
2381
|
-
let raw;
|
|
2382
|
-
try {
|
|
2383
|
-
raw = fs4.readFileSync(file, "utf-8");
|
|
2384
|
-
} catch (exc) {
|
|
2385
|
-
const code = exc?.code;
|
|
2386
|
-
return code === "ENOENT" ? "absent" : "unreadable";
|
|
2387
|
-
}
|
|
2388
|
-
return parseBaseline(raw) ?? "unreadable";
|
|
2389
|
-
}
|
|
2390
|
-
function applyBaseline(measuredLines, baseline, headSha, measuredMeasure = "exact") {
|
|
2391
|
-
if (baseline === "absent" || baseline === "unreadable") {
|
|
2392
|
-
return { applied: false, fallback: baseline, lines: measuredLines };
|
|
2393
|
-
}
|
|
2394
|
-
if (baseline.measure !== "exact" || measuredMeasure !== "exact") {
|
|
2395
|
-
return { applied: false, fallback: "mixed-measure", lines: measuredLines };
|
|
2396
|
-
}
|
|
2397
|
-
if (baseline.head_sha !== headSha()) {
|
|
2398
|
-
return { applied: false, fallback: "head-moved", lines: measuredLines };
|
|
2399
|
-
}
|
|
2400
|
-
return {
|
|
2401
|
-
applied: true,
|
|
2402
|
-
baseline,
|
|
2403
|
-
lines: Math.max(0, measuredLines - baseline.baseline_lines)
|
|
2404
|
-
};
|
|
2405
|
-
}
|
|
2406
|
-
|
|
2407
|
-
// src/scripts/_lib/audit_field_provenance.ts
|
|
2408
|
-
var MAX_APPLIED_IDS = 32;
|
|
2409
|
-
function appliedIds(values) {
|
|
2410
|
-
const out = [];
|
|
2411
|
-
for (const raw of values ?? []) {
|
|
2412
|
-
const v = String(raw).trim();
|
|
2413
|
-
if (v === "" || out.includes(v)) continue;
|
|
2414
|
-
out.push(v);
|
|
2415
|
-
if (out.length >= MAX_APPLIED_IDS) break;
|
|
2416
|
-
}
|
|
2417
|
-
return out;
|
|
2418
|
-
}
|
|
2419
|
-
|
|
2420
|
-
// src/scripts/_lib/review_skipped_record.ts
|
|
2421
|
-
var BASELINE_APPLICATIONS = [
|
|
2422
|
-
"applied",
|
|
2423
|
-
"absent",
|
|
2424
|
-
"unreadable",
|
|
2425
|
-
"head_moved",
|
|
2426
|
-
"mixed_measure"
|
|
2427
|
-
];
|
|
2428
|
-
var PRODUCER_PRIVACY_CLASS = "ids-only";
|
|
2429
|
-
function isNonNegInt(n) {
|
|
2430
|
-
return typeof n === "number" && Number.isFinite(n) && Number.isInteger(n) && n >= 0;
|
|
2431
|
-
}
|
|
2432
|
-
var HIGH_RISK_DIFF_LINES = 200;
|
|
2433
|
-
function buildReviewSkippedLine(input) {
|
|
2434
|
-
const errors = [];
|
|
2435
|
-
if (!isNonNegInt(input.diff_lines)) {
|
|
2436
|
-
errors.push("diff_lines must be a non-negative integer");
|
|
2437
|
-
}
|
|
2438
|
-
if (input.mutation_measure !== "exact" && input.mutation_measure !== "capped_approximation") {
|
|
2439
|
-
errors.push('mutation_measure must be "exact" or "capped_approximation"');
|
|
2440
|
-
}
|
|
2441
|
-
if (!input.ts) errors.push("ts (ISO-8601 UTC) is required");
|
|
2442
|
-
if (!input.id) errors.push("id (ULID, UUID, or content hash) is required");
|
|
2443
|
-
if (input.baseline !== void 0 && !BASELINE_APPLICATIONS.includes(input.baseline)) {
|
|
2444
|
-
errors.push(`baseline must be one of ${BASELINE_APPLICATIONS.join(", ")}`);
|
|
2445
|
-
}
|
|
2446
|
-
if (errors.length) return { line: null, errors };
|
|
2447
|
-
const line = {
|
|
2448
|
-
schema_version: 1,
|
|
2449
|
-
id: input.id,
|
|
2450
|
-
ts: input.ts,
|
|
2451
|
-
work_id: input.work_id ?? `review-skipped-${input.ts}`,
|
|
2452
|
-
phase: "report",
|
|
2453
|
-
outcome: "skipped",
|
|
2454
|
-
confidence_band: "medium",
|
|
2455
|
-
risk_class: input.diff_lines >= HIGH_RISK_DIFF_LINES ? "high" : "medium",
|
|
2456
|
-
memory: { asks: 0, hits: 0 },
|
|
2457
|
-
verify: { claims: 0, first_try_passes: 0 },
|
|
2458
|
-
// COMPUTED, never a producer constant.
|
|
2459
|
-
rules_applied: appliedIds(input.rules_applied),
|
|
2460
|
-
// MANDATORY on every audit line, same as the orchestration producer.
|
|
2461
|
-
privacy_class: PRODUCER_PRIVACY_CLASS,
|
|
2462
|
-
// `skills_applied` is OMITTED here, deliberately and not by oversight.
|
|
2463
|
-
// audit-log-v1 distinguishes an absent key ("not recorded") from `[]`
|
|
2464
|
-
// ("recorded, none applied"), and this writer observes a review that
|
|
2465
|
-
// did not happen -- it has no skill observation to offer in either
|
|
2466
|
-
// direction. Emitting `[]` would assert "no skills applied" on evidence
|
|
2467
|
-
// this producer never gathered, which is the exact fabrication the
|
|
2468
|
-
// absent/empty split exists to make impossible.
|
|
2469
|
-
persona: null,
|
|
2470
|
-
input_kind: "prompt",
|
|
2471
|
-
type: "note",
|
|
2472
|
-
review_skipped: {
|
|
2473
|
-
diff_lines: input.diff_lines,
|
|
2474
|
-
mutation_measure: input.mutation_measure,
|
|
2475
|
-
// Omitted, not defaulted, when the producer recorded nothing —
|
|
2476
|
-
// audit-log-v1's absent-versus-present split is the same one
|
|
2477
|
-
// `skills_applied` uses above, for the same reason.
|
|
2478
|
-
...input.baseline === void 0 ? {} : { baseline: input.baseline }
|
|
2479
|
-
}
|
|
2480
|
-
};
|
|
2481
|
-
return { line, errors: [] };
|
|
2482
|
-
}
|
|
2483
|
-
|
|
2484
2310
|
// src/scripts/hooks/envelope.ts
|
|
2485
2311
|
var ENVELOPE_KEYS = [
|
|
2486
2312
|
"schema_version",
|
|
@@ -2535,340 +2361,35 @@ function unwrap(stdin_text, default_platform = "generic") {
|
|
|
2535
2361
|
];
|
|
2536
2362
|
}
|
|
2537
2363
|
|
|
2538
|
-
// src/scripts/hooks/
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
for (const line of raw.split("\n")) {
|
|
2563
|
-
const m = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line);
|
|
2564
|
-
if (!m) continue;
|
|
2565
|
-
const addedStr = m[1];
|
|
2566
|
-
const deletedStr = m[2];
|
|
2567
|
-
rows.push({
|
|
2568
|
-
added: addedStr === "-" ? 0 : Number(addedStr),
|
|
2569
|
-
deleted: deletedStr === "-" ? 0 : Number(deletedStr),
|
|
2570
|
-
path: m[3]
|
|
2571
|
-
});
|
|
2572
|
-
}
|
|
2573
|
-
return rows;
|
|
2574
|
-
}
|
|
2575
|
-
var DOC_PATH_RE = /\.md$/i;
|
|
2576
|
-
function isDocPath(p) {
|
|
2577
|
-
return DOC_PATH_RE.test(p);
|
|
2578
|
-
}
|
|
2579
|
-
function nonDocMutatedLines(rows) {
|
|
2580
|
-
return rows.filter((r) => !isDocPath(r.path)).reduce((sum, r) => sum + r.added + r.deleted, 0);
|
|
2364
|
+
// src/scripts/hooks/subagent_ledger_hook.ts
|
|
2365
|
+
import * as crypto from "node:crypto";
|
|
2366
|
+
import * as fs4 from "node:fs";
|
|
2367
|
+
import * as path4 from "node:path";
|
|
2368
|
+
import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL4 } from "node:url";
|
|
2369
|
+
|
|
2370
|
+
// src/scripts/_lib/subagent_response.ts
|
|
2371
|
+
var MAX_SUMMARY_CHARS = 2e3;
|
|
2372
|
+
var MAX_RESPONSE_LINE_CHARS = 240;
|
|
2373
|
+
var MAX_RESPONSE_ENTRIES = 40;
|
|
2374
|
+
var MAX_ARTIFACT_REF_CHARS = 200;
|
|
2375
|
+
var MAX_ENVELOPE_CHARS = 12e3;
|
|
2376
|
+
var CONFIDENCE = /* @__PURE__ */ new Set(["low", "medium", "high"]);
|
|
2377
|
+
var RESPONSE_REQUIRED_FIELDS = [
|
|
2378
|
+
"summary",
|
|
2379
|
+
"handoff",
|
|
2380
|
+
"confidence",
|
|
2381
|
+
"findings",
|
|
2382
|
+
"risks"
|
|
2383
|
+
];
|
|
2384
|
+
function countContractFields(input) {
|
|
2385
|
+
if (input === null || typeof input !== "object") return 0;
|
|
2386
|
+
const r = input;
|
|
2387
|
+
return RESPONSE_REQUIRED_FIELDS.filter((k) => Object.hasOwn(r, k)).length;
|
|
2581
2388
|
}
|
|
2582
|
-
function
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
encoding: "utf8",
|
|
2587
|
-
maxBuffer: 16 * 1024 * 1024
|
|
2588
|
-
});
|
|
2589
|
-
if (r.status !== 0) return [];
|
|
2590
|
-
return parseNumstat(r.stdout ?? "");
|
|
2591
|
-
} catch {
|
|
2592
|
-
return [];
|
|
2593
|
-
}
|
|
2594
|
-
}
|
|
2595
|
-
function untrackedNonDocFiles(cwd) {
|
|
2596
|
-
try {
|
|
2597
|
-
const r = spawnSync2("git", ["ls-files", "--others", "--exclude-standard"], {
|
|
2598
|
-
cwd,
|
|
2599
|
-
encoding: "utf8",
|
|
2600
|
-
maxBuffer: 16 * 1024 * 1024
|
|
2601
|
-
});
|
|
2602
|
-
if (r.status !== 0) return [];
|
|
2603
|
-
return (r.stdout ?? "").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !isDocPath(l));
|
|
2604
|
-
} catch {
|
|
2605
|
-
return [];
|
|
2606
|
-
}
|
|
2607
|
-
}
|
|
2608
|
-
var BINARY_PROBE_BYTES = 8e3;
|
|
2609
|
-
var UNTRACKED_FILE_READ_CAP_BYTES = 8 * 1024 * 1024;
|
|
2610
|
-
function untrackedFileLineCount(cwd, relPath) {
|
|
2611
|
-
try {
|
|
2612
|
-
const abs = path5.resolve(cwd, relPath);
|
|
2613
|
-
const size = fs5.statSync(abs).size;
|
|
2614
|
-
if (size === 0) return 0;
|
|
2615
|
-
if (size > UNTRACKED_FILE_READ_CAP_BYTES) {
|
|
2616
|
-
return MUTATION_LINE_THRESHOLD + 1;
|
|
2617
|
-
}
|
|
2618
|
-
const buf = fs5.readFileSync(abs);
|
|
2619
|
-
if (buf.length === 0) return 0;
|
|
2620
|
-
const probe = buf.subarray(0, Math.min(buf.length, BINARY_PROBE_BYTES));
|
|
2621
|
-
if (probe.includes(0)) return 0;
|
|
2622
|
-
let lines = 0;
|
|
2623
|
-
for (const byte of buf) {
|
|
2624
|
-
if (byte === 10) lines += 1;
|
|
2625
|
-
}
|
|
2626
|
-
if (buf[buf.length - 1] !== 10) lines += 1;
|
|
2627
|
-
return lines;
|
|
2628
|
-
} catch {
|
|
2629
|
-
return 0;
|
|
2630
|
-
}
|
|
2631
|
-
}
|
|
2632
|
-
function totalNonDocMutatedLinesWithMeasure(cwd) {
|
|
2633
|
-
const trackedLines = nonDocMutatedLines(gitNumstatRows(cwd));
|
|
2634
|
-
const untracked = untrackedNonDocFiles(cwd);
|
|
2635
|
-
if (untracked.length > UNTRACKED_FILE_CAP) {
|
|
2636
|
-
return { lines: MUTATION_LINE_THRESHOLD + 1 + trackedLines, measure: "capped_approximation" };
|
|
2637
|
-
}
|
|
2638
|
-
const untrackedLines = untracked.reduce(
|
|
2639
|
-
(sum, relPath) => sum + untrackedFileLineCount(cwd, relPath),
|
|
2640
|
-
0
|
|
2641
|
-
);
|
|
2642
|
-
return { lines: trackedLines + untrackedLines, measure: "exact" };
|
|
2643
|
-
}
|
|
2644
|
-
function isObject(v) {
|
|
2645
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2646
|
-
}
|
|
2647
|
-
function str(v) {
|
|
2648
|
-
return typeof v === "string" ? v : "";
|
|
2649
|
-
}
|
|
2650
|
-
var DISPATCH_TOOL_RE = /^(agent|task)$/i;
|
|
2651
|
-
var REVIEW_SHAPED_LABEL_RE = /\b(review|judge|adversarial|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
|
|
2652
|
-
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;
|
|
2653
|
-
var REVIEW_SKILL_NAME_RE = /^(review[-:]changes|judge[-:][a-z0-9-]+|adversarial-review|code-review)$/i;
|
|
2654
|
-
function scanTranscriptForReviewer(transcriptText) {
|
|
2655
|
-
for (const raw of transcriptText.split("\n")) {
|
|
2656
|
-
const line = raw.trim();
|
|
2657
|
-
if (!line) continue;
|
|
2658
|
-
let obj;
|
|
2659
|
-
try {
|
|
2660
|
-
obj = JSON.parse(line);
|
|
2661
|
-
} catch {
|
|
2662
|
-
continue;
|
|
2663
|
-
}
|
|
2664
|
-
if (!isObject(obj) || obj["type"] !== "assistant") continue;
|
|
2665
|
-
const msg = obj["message"];
|
|
2666
|
-
if (!isObject(msg)) continue;
|
|
2667
|
-
const content = msg["content"];
|
|
2668
|
-
if (!Array.isArray(content)) continue;
|
|
2669
|
-
for (const blk of content) {
|
|
2670
|
-
if (!isObject(blk) || blk["type"] !== "tool_use") continue;
|
|
2671
|
-
const name = str(blk["name"]);
|
|
2672
|
-
const input = blk["input"];
|
|
2673
|
-
const inputObj = isObject(input) ? input : {};
|
|
2674
|
-
if (DISPATCH_TOOL_RE.test(name)) {
|
|
2675
|
-
const label = [
|
|
2676
|
-
str(inputObj["description"]),
|
|
2677
|
-
str(inputObj["subagent_type"])
|
|
2678
|
-
].filter(Boolean).join(" ");
|
|
2679
|
-
if (REVIEW_SHAPED_LABEL_RE.test(label)) return true;
|
|
2680
|
-
const prompt = str(inputObj["prompt"]);
|
|
2681
|
-
if (REVIEW_SHAPED_PROMPT_RE.test(prompt)) return true;
|
|
2682
|
-
continue;
|
|
2683
|
-
}
|
|
2684
|
-
if (/^skill$/i.test(name)) {
|
|
2685
|
-
const skillName = str(inputObj["skill"]) || str(inputObj["name"]) || str(inputObj["command"]);
|
|
2686
|
-
if (REVIEW_SKILL_NAME_RE.test(skillName)) return true;
|
|
2687
|
-
continue;
|
|
2688
|
-
}
|
|
2689
|
-
if (REVIEW_SKILL_NAME_RE.test(name)) return true;
|
|
2690
|
-
}
|
|
2691
|
-
}
|
|
2692
|
-
return false;
|
|
2693
|
-
}
|
|
2694
|
-
function buildAdvisoryLine(diffLines) {
|
|
2695
|
-
return `this session mutated ${diffLines} lines without a neutral review; spawn a cross-model reviewer before claiming done (delegation-policy / verify-budget)`;
|
|
2696
|
-
}
|
|
2697
|
-
function appendReviewSkippedTelemetry(workspaceRoot, diffLines, mutationMeasure, baseline) {
|
|
2698
|
-
if (is_replay_mode()) return;
|
|
2699
|
-
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
2700
|
-
const { line, errors } = buildReviewSkippedLine({
|
|
2701
|
-
diff_lines: diffLines,
|
|
2702
|
-
mutation_measure: mutationMeasure,
|
|
2703
|
-
ts,
|
|
2704
|
-
id: crypto.randomUUID(),
|
|
2705
|
-
baseline
|
|
2706
|
-
});
|
|
2707
|
-
if (errors.length || line === null) return;
|
|
2708
|
-
try {
|
|
2709
|
-
const dir = path5.join(workspaceRoot, "agents", "runtime", "state", "audit");
|
|
2710
|
-
fs5.mkdirSync(dir, { recursive: true });
|
|
2711
|
-
const file = path5.join(dir, `${ts.slice(0, 7)}.jsonl`);
|
|
2712
|
-
fs5.appendFileSync(file, `${JSON.stringify(line)}
|
|
2713
|
-
`, "utf8");
|
|
2714
|
-
} catch {
|
|
2715
|
-
}
|
|
2716
|
-
}
|
|
2717
|
-
function sessionStateDir(workspaceRoot) {
|
|
2718
|
-
return path5.join(workspaceRoot, "agents", "runtime", "state", "end-review-nudge");
|
|
2719
|
-
}
|
|
2720
|
-
function sessionStateFile(workspaceRoot, sessionKey) {
|
|
2721
|
-
return path5.join(sessionStateDir(workspaceRoot), `${sessionKey}.json`);
|
|
2722
|
-
}
|
|
2723
|
-
function deriveSessionKey2(envelope, payload) {
|
|
2724
|
-
const raw = str(envelope["session_id"]) || str(payload["transcript_path"] ?? payload["transcriptPath"]);
|
|
2725
|
-
const material = raw || "unknown-session";
|
|
2726
|
-
return crypto.createHash("sha256").update(material).digest("hex");
|
|
2727
|
-
}
|
|
2728
|
-
function hasFiredThisSession(workspaceRoot, sessionKey) {
|
|
2729
|
-
try {
|
|
2730
|
-
return fs5.existsSync(sessionStateFile(workspaceRoot, sessionKey));
|
|
2731
|
-
} catch {
|
|
2732
|
-
return false;
|
|
2733
|
-
}
|
|
2734
|
-
}
|
|
2735
|
-
function markFiredThisSession(workspaceRoot, sessionKey, ts) {
|
|
2736
|
-
try {
|
|
2737
|
-
atomic_write_json(sessionStateFile(workspaceRoot, sessionKey), { fired_at: ts });
|
|
2738
|
-
} catch {
|
|
2739
|
-
}
|
|
2740
|
-
}
|
|
2741
|
-
function _read_stdin() {
|
|
2742
|
-
return readHookStdin();
|
|
2743
|
-
}
|
|
2744
|
-
var FALLBACK_TO_TELEMETRY = {
|
|
2745
|
-
absent: "absent",
|
|
2746
|
-
unreadable: "unreadable",
|
|
2747
|
-
"head-moved": "head_moved",
|
|
2748
|
-
"mixed-measure": "mixed_measure"
|
|
2749
|
-
};
|
|
2750
|
-
function main4() {
|
|
2751
|
-
const [envelope, payload] = unwrap(_read_stdin(), "claude");
|
|
2752
|
-
const event = String(envelope["event"] ?? "");
|
|
2753
|
-
if (event !== "" && event !== "stop") {
|
|
2754
|
-
return 0;
|
|
2755
|
-
}
|
|
2756
|
-
const workspace_root = String(envelope["workspace_root"] ?? "").trim() || process.cwd();
|
|
2757
|
-
let measuredLines;
|
|
2758
|
-
let mutationMeasure;
|
|
2759
|
-
try {
|
|
2760
|
-
const measured = totalNonDocMutatedLinesWithMeasure(workspace_root);
|
|
2761
|
-
measuredLines = measured.lines;
|
|
2762
|
-
mutationMeasure = measured.measure;
|
|
2763
|
-
} catch {
|
|
2764
|
-
return 0;
|
|
2765
|
-
}
|
|
2766
|
-
const sessionKey = deriveSessionKey2(envelope, payload);
|
|
2767
|
-
const baselineOutcome = applyBaseline(
|
|
2768
|
-
measuredLines,
|
|
2769
|
-
readBaseline(workspace_root, sessionKey),
|
|
2770
|
-
// A THUNK, not a value: on the `absent` and `unreadable` branches the
|
|
2771
|
-
// result is discarded, and those two are the steady state on every host
|
|
2772
|
-
// without this nudge and on every session before its first baseline
|
|
2773
|
-
// write. Passing the value eagerly spent an 11-14 ms `git rev-parse` on
|
|
2774
|
-
// each of those stops for nothing.
|
|
2775
|
-
() => currentHeadSha(workspace_root),
|
|
2776
|
-
mutationMeasure
|
|
2777
|
-
);
|
|
2778
|
-
const diffLines = baselineOutcome.lines;
|
|
2779
|
-
const baselineApplication = baselineOutcome.applied ? "applied" : FALLBACK_TO_TELEMETRY[baselineOutcome.fallback];
|
|
2780
|
-
if (diffLines <= MUTATION_LINE_THRESHOLD) {
|
|
2781
|
-
return 0;
|
|
2782
|
-
}
|
|
2783
|
-
if (hasFiredThisSession(workspace_root, sessionKey)) {
|
|
2784
|
-
return 0;
|
|
2785
|
-
}
|
|
2786
|
-
const transcriptPath = str(
|
|
2787
|
-
payload["transcript_path"] ?? payload["transcriptPath"]
|
|
2788
|
-
).trim();
|
|
2789
|
-
let reviewerRan = false;
|
|
2790
|
-
if (transcriptPath && isSafeTranscriptPath(transcriptPath)) {
|
|
2791
|
-
try {
|
|
2792
|
-
reviewerRan = scanTranscriptForReviewer(fs5.readFileSync(transcriptPath, "utf-8"));
|
|
2793
|
-
} catch {
|
|
2794
|
-
reviewerRan = false;
|
|
2795
|
-
}
|
|
2796
|
-
}
|
|
2797
|
-
if (reviewerRan) {
|
|
2798
|
-
return 0;
|
|
2799
|
-
}
|
|
2800
|
-
markFiredThisSession(workspace_root, sessionKey, (/* @__PURE__ */ new Date()).toISOString());
|
|
2801
|
-
try {
|
|
2802
|
-
appendReviewSkippedTelemetry(
|
|
2803
|
-
workspace_root,
|
|
2804
|
-
diffLines,
|
|
2805
|
-
mutationMeasure,
|
|
2806
|
-
baselineApplication
|
|
2807
|
-
);
|
|
2808
|
-
} catch {
|
|
2809
|
-
}
|
|
2810
|
-
process.stdout.write(
|
|
2811
|
-
`${JSON.stringify({
|
|
2812
|
-
decision: "warn",
|
|
2813
|
-
reason: `end-review-nudge: session mutated ${diffLines} non-doc lines without a neutral review`,
|
|
2814
|
-
additional_context: buildAdvisoryLine(diffLines)
|
|
2815
|
-
})}
|
|
2816
|
-
`
|
|
2817
|
-
);
|
|
2818
|
-
return EXIT_WARN2;
|
|
2819
|
-
}
|
|
2820
|
-
function _isCliEntry4() {
|
|
2821
|
-
if (true) {
|
|
2822
|
-
return false;
|
|
2823
|
-
}
|
|
2824
|
-
if (process.argv[1] === void 0) {
|
|
2825
|
-
return false;
|
|
2826
|
-
}
|
|
2827
|
-
const argvUrl = pathToFileURL4(path5.resolve(process.argv[1])).href;
|
|
2828
|
-
if (import.meta.url === argvUrl) {
|
|
2829
|
-
return true;
|
|
2830
|
-
}
|
|
2831
|
-
try {
|
|
2832
|
-
const here = fs5.realpathSync(fileURLToPath4(import.meta.url));
|
|
2833
|
-
const argv = fs5.realpathSync(path5.resolve(process.argv[1]));
|
|
2834
|
-
return here === argv;
|
|
2835
|
-
} catch {
|
|
2836
|
-
return false;
|
|
2837
|
-
}
|
|
2838
|
-
}
|
|
2839
|
-
if (_isCliEntry4()) {
|
|
2840
|
-
process.exit(main4());
|
|
2841
|
-
}
|
|
2842
|
-
|
|
2843
|
-
// src/scripts/hooks/subagent_ledger_hook.ts
|
|
2844
|
-
import * as crypto2 from "node:crypto";
|
|
2845
|
-
import * as fs6 from "node:fs";
|
|
2846
|
-
import * as path6 from "node:path";
|
|
2847
|
-
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
|
|
2848
|
-
|
|
2849
|
-
// src/scripts/_lib/subagent_response.ts
|
|
2850
|
-
var MAX_SUMMARY_CHARS = 2e3;
|
|
2851
|
-
var MAX_RESPONSE_LINE_CHARS = 240;
|
|
2852
|
-
var MAX_RESPONSE_ENTRIES = 40;
|
|
2853
|
-
var MAX_ARTIFACT_REF_CHARS = 200;
|
|
2854
|
-
var MAX_ENVELOPE_CHARS = 12e3;
|
|
2855
|
-
var CONFIDENCE = /* @__PURE__ */ new Set(["low", "medium", "high"]);
|
|
2856
|
-
var RESPONSE_REQUIRED_FIELDS = [
|
|
2857
|
-
"summary",
|
|
2858
|
-
"handoff",
|
|
2859
|
-
"confidence",
|
|
2860
|
-
"findings",
|
|
2861
|
-
"risks"
|
|
2862
|
-
];
|
|
2863
|
-
function countContractFields(input) {
|
|
2864
|
-
if (input === null || typeof input !== "object") return 0;
|
|
2865
|
-
const r = input;
|
|
2866
|
-
return RESPONSE_REQUIRED_FIELDS.filter((k) => Object.hasOwn(r, k)).length;
|
|
2867
|
-
}
|
|
2868
|
-
function validateResponse(input) {
|
|
2869
|
-
const errors = [];
|
|
2870
|
-
if (input === null || typeof input !== "object") {
|
|
2871
|
-
return { valid: false, errors: ["response is not an object"] };
|
|
2389
|
+
function validateResponse(input) {
|
|
2390
|
+
const errors = [];
|
|
2391
|
+
if (input === null || typeof input !== "object") {
|
|
2392
|
+
return { valid: false, errors: ["response is not an object"] };
|
|
2872
2393
|
}
|
|
2873
2394
|
const r = input;
|
|
2874
2395
|
if (typeof r.summary !== "string") errors.push("summary missing or not a string");
|
|
@@ -2945,22 +2466,57 @@ function validateResponse(input) {
|
|
|
2945
2466
|
return { valid: errors.length === 0, errors };
|
|
2946
2467
|
}
|
|
2947
2468
|
|
|
2948
|
-
// src/scripts/hooks/
|
|
2469
|
+
// src/scripts/hooks/exit_codes.ts
|
|
2949
2470
|
var EXIT_ALLOW3 = 0;
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
var
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2471
|
+
var EXIT_BLOCK = 1;
|
|
2472
|
+
var EXIT_WARN2 = 2;
|
|
2473
|
+
var EXIT_ERROR = 3;
|
|
2474
|
+
var EXIT_CODES = Object.freeze([
|
|
2475
|
+
Object.freeze({
|
|
2476
|
+
code: EXIT_ALLOW3,
|
|
2477
|
+
name: "allow",
|
|
2478
|
+
meaning: "no objection; the dispatcher passes the call through",
|
|
2479
|
+
owner: "every concern \u2014 the default verdict",
|
|
2480
|
+
authorizedBy: "nothing; allowing needs no authority"
|
|
2481
|
+
}),
|
|
2482
|
+
Object.freeze({
|
|
2483
|
+
code: EXIT_BLOCK,
|
|
2484
|
+
name: "block",
|
|
2485
|
+
meaning: "refuse the call; the dispatcher lowers this onto the host deny channel",
|
|
2486
|
+
owner: "a concern declared `severity: blocking` in hook_manifest.yaml",
|
|
2487
|
+
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"
|
|
2488
|
+
}),
|
|
2489
|
+
Object.freeze({
|
|
2490
|
+
code: EXIT_WARN2,
|
|
2491
|
+
name: "warn",
|
|
2492
|
+
meaning: "advisory; the dispatcher exits 0 and surfaces the reason",
|
|
2493
|
+
owner: "any concern",
|
|
2494
|
+
authorizedBy: "nothing; warning is the ceiling for an advisory concern and is always available to a blocking one"
|
|
2495
|
+
}),
|
|
2496
|
+
Object.freeze({
|
|
2497
|
+
code: EXIT_ERROR,
|
|
2498
|
+
name: "error",
|
|
2499
|
+
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",
|
|
2500
|
+
owner: "no concern emits this on purpose; the runtime does",
|
|
2501
|
+
authorizedBy: "hook_manifest.yaml `fail_closed` and `severity` together \u2014 see docs/contracts/hook-architecture-v1.md \xA7 Exit-code semantics"
|
|
2502
|
+
})
|
|
2503
|
+
]);
|
|
2504
|
+
|
|
2505
|
+
// src/scripts/hooks/subagent_ledger_hook.ts
|
|
2506
|
+
function isObject(v) {
|
|
2507
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
2508
|
+
}
|
|
2509
|
+
var LEDGER_DIR = path4.join("agents", "runtime", "state", "subagent-ledger");
|
|
2510
|
+
var OPEN_SUBDIR = "open";
|
|
2511
|
+
var OPEN_RECORD_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
2512
|
+
function refFor(agentId) {
|
|
2513
|
+
return crypto.createHash("sha256").update(agentId, "utf8").digest("hex").slice(0, 12);
|
|
2958
2514
|
}
|
|
2959
2515
|
function unwrapPayload(envelope) {
|
|
2960
2516
|
const inner = envelope["payload"];
|
|
2961
|
-
return
|
|
2517
|
+
return isObject(inner) ? inner : envelope;
|
|
2962
2518
|
}
|
|
2963
|
-
function
|
|
2519
|
+
function str(payload, ...keys) {
|
|
2964
2520
|
for (const k of keys) {
|
|
2965
2521
|
const v = payload[k];
|
|
2966
2522
|
if (typeof v === "string" && v) return v;
|
|
@@ -2968,11 +2524,11 @@ function str2(payload, ...keys) {
|
|
|
2968
2524
|
return null;
|
|
2969
2525
|
}
|
|
2970
2526
|
function extractParentId(payload, ownId) {
|
|
2971
|
-
const explicit =
|
|
2527
|
+
const explicit = str(payload, "parent_agent_id", "parentAgentId");
|
|
2972
2528
|
if (explicit) return explicit;
|
|
2973
2529
|
const session = payload["session"];
|
|
2974
|
-
if (
|
|
2975
|
-
const sessionAgent =
|
|
2530
|
+
if (isObject(session)) {
|
|
2531
|
+
const sessionAgent = str(session, "agent_id", "agentId");
|
|
2976
2532
|
if (sessionAgent && sessionAgent !== ownId) return sessionAgent;
|
|
2977
2533
|
}
|
|
2978
2534
|
return null;
|
|
@@ -3040,14 +2596,14 @@ function _jsonObjectCandidates(message) {
|
|
|
3040
2596
|
return out;
|
|
3041
2597
|
}
|
|
3042
2598
|
function openDir(root) {
|
|
3043
|
-
return
|
|
2599
|
+
return path4.join(root, LEDGER_DIR, OPEN_SUBDIR);
|
|
3044
2600
|
}
|
|
3045
2601
|
function openFile(root, ref) {
|
|
3046
|
-
return
|
|
2602
|
+
return path4.join(openDir(root), `${ref}.json`);
|
|
3047
2603
|
}
|
|
3048
2604
|
var DEPTH_BASES = /* @__PURE__ */ new Set(["observed", "asserted-parent", "assumed-root"]);
|
|
3049
2605
|
function _asOpenRecord(value, expectedRef) {
|
|
3050
|
-
if (!
|
|
2606
|
+
if (!isObject(value)) return null;
|
|
3051
2607
|
const ref = value["ref"];
|
|
3052
2608
|
if (typeof ref !== "string" || ref !== expectedRef) return null;
|
|
3053
2609
|
if (typeof value["started_at"] !== "string") return null;
|
|
@@ -3070,7 +2626,7 @@ function readOpenRecords(root) {
|
|
|
3070
2626
|
const out = /* @__PURE__ */ new Map();
|
|
3071
2627
|
let names;
|
|
3072
2628
|
try {
|
|
3073
|
-
names =
|
|
2629
|
+
names = fs4.readdirSync(openDir(root));
|
|
3074
2630
|
} catch {
|
|
3075
2631
|
return out;
|
|
3076
2632
|
}
|
|
@@ -3078,7 +2634,7 @@ function readOpenRecords(root) {
|
|
|
3078
2634
|
if (!name.endsWith(".json")) continue;
|
|
3079
2635
|
const expectedRef = name.slice(0, -".json".length);
|
|
3080
2636
|
try {
|
|
3081
|
-
const raw =
|
|
2637
|
+
const raw = fs4.readFileSync(path4.join(openDir(root), name), "utf8");
|
|
3082
2638
|
const rec = _asOpenRecord(JSON.parse(raw), expectedRef);
|
|
3083
2639
|
if (rec) out.set(rec.ref, rec);
|
|
3084
2640
|
} catch {
|
|
@@ -3093,13 +2649,13 @@ function resolveDepth(parentRef, open) {
|
|
|
3093
2649
|
return { depth: parent.depth + 1, depth_basis: "observed" };
|
|
3094
2650
|
}
|
|
3095
2651
|
function ledgerFileFor2(root, ts) {
|
|
3096
|
-
return
|
|
2652
|
+
return path4.join(root, LEDGER_DIR, `${ts.slice(0, 7)}.jsonl`);
|
|
3097
2653
|
}
|
|
3098
2654
|
function appendLedgerLine(root, ts, line) {
|
|
3099
2655
|
if (is_replay_mode()) return;
|
|
3100
2656
|
const file = ledgerFileFor2(root, ts);
|
|
3101
|
-
|
|
3102
|
-
|
|
2657
|
+
fs4.mkdirSync(path4.dirname(file), { recursive: true });
|
|
2658
|
+
fs4.appendFileSync(file, `${JSON.stringify(line)}
|
|
3103
2659
|
`, "utf8");
|
|
3104
2660
|
}
|
|
3105
2661
|
var WALL_CLOCK_ARMS_MS = [
|
|
@@ -3113,7 +2669,7 @@ function armsExceeded(elapsedMs) {
|
|
|
3113
2669
|
return WALL_CLOCK_ARMS_MS.filter((a) => elapsedMs >= a.ms).map((a) => a.label);
|
|
3114
2670
|
}
|
|
3115
2671
|
function openRecordStats(root, nowMs = Date.now()) {
|
|
3116
|
-
const ledgerPresent =
|
|
2672
|
+
const ledgerPresent = fs4.existsSync(path4.join(root, LEDGER_DIR));
|
|
3117
2673
|
const open = readOpenRecords(root);
|
|
3118
2674
|
let maxDepth = 0;
|
|
3119
2675
|
let counted = 0;
|
|
@@ -3141,13 +2697,13 @@ function writeOpenRecord(root, rec) {
|
|
|
3141
2697
|
function removeOpenRecord(root, ref) {
|
|
3142
2698
|
if (is_replay_mode()) return;
|
|
3143
2699
|
try {
|
|
3144
|
-
|
|
2700
|
+
fs4.unlinkSync(openFile(root, ref));
|
|
3145
2701
|
} catch {
|
|
3146
2702
|
}
|
|
3147
2703
|
}
|
|
3148
2704
|
function countOpen(root) {
|
|
3149
2705
|
try {
|
|
3150
|
-
return
|
|
2706
|
+
return fs4.readdirSync(openDir(root)).filter((n) => n.endsWith(".json")).length;
|
|
3151
2707
|
} catch {
|
|
3152
2708
|
return 0;
|
|
3153
2709
|
}
|
|
@@ -3188,12 +2744,12 @@ function appendUnidentified(root, event, nowIso, payload) {
|
|
|
3188
2744
|
unidentified: true,
|
|
3189
2745
|
// The agent TYPE is an id-shaped enum and may still be present; it is
|
|
3190
2746
|
// the only thing salvageable from a payload with no id.
|
|
3191
|
-
agent_type:
|
|
2747
|
+
agent_type: str(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
|
|
3192
2748
|
reason: "payload carried no agent id \u2014 nothing to correlate start with stop"
|
|
3193
2749
|
});
|
|
3194
2750
|
}
|
|
3195
2751
|
function handleStart(root, payload, nowIso, sessionId) {
|
|
3196
|
-
const agentId =
|
|
2752
|
+
const agentId = str(payload, "agent_id", "agentId");
|
|
3197
2753
|
if (agentId === null) {
|
|
3198
2754
|
appendUnidentified(root, "subagent_start", nowIso, payload);
|
|
3199
2755
|
return;
|
|
@@ -3204,7 +2760,7 @@ function handleStart(root, payload, nowIso, sessionId) {
|
|
|
3204
2760
|
const { depth, depth_basis } = resolveDepth(parentRef, readOpenRecords(root));
|
|
3205
2761
|
const rec = {
|
|
3206
2762
|
ref,
|
|
3207
|
-
agent_type:
|
|
2763
|
+
agent_type: str(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
|
|
3208
2764
|
started_at: nowIso,
|
|
3209
2765
|
parent_ref: parentRef,
|
|
3210
2766
|
depth,
|
|
@@ -3231,7 +2787,7 @@ function handleStart(root, payload, nowIso, sessionId) {
|
|
|
3231
2787
|
});
|
|
3232
2788
|
}
|
|
3233
2789
|
function handleStop(root, payload, nowIso, sessionId) {
|
|
3234
|
-
const agentId =
|
|
2790
|
+
const agentId = str(payload, "agent_id", "agentId");
|
|
3235
2791
|
if (agentId === null) {
|
|
3236
2792
|
appendUnidentified(root, "subagent_stop", nowIso, payload);
|
|
3237
2793
|
return;
|
|
@@ -3243,7 +2799,7 @@ function handleStop(root, payload, nowIso, sessionId) {
|
|
|
3243
2799
|
const started = Date.parse(rec.started_at);
|
|
3244
2800
|
if (Number.isFinite(started)) durationMs = Math.max(0, Date.parse(nowIso) - started);
|
|
3245
2801
|
}
|
|
3246
|
-
const parse2 = classifyEnvelope(
|
|
2802
|
+
const parse2 = classifyEnvelope(str(payload, "last_assistant_message", "lastAssistantMessage"));
|
|
3247
2803
|
removeOpenRecord(root, ref);
|
|
3248
2804
|
appendLedgerLine(root, nowIso, {
|
|
3249
2805
|
event: "subagent_stop",
|
|
@@ -3254,7 +2810,7 @@ function handleStop(root, payload, nowIso, sessionId) {
|
|
|
3254
2810
|
// relabel a cross-session stop as belonging to the dispatching session
|
|
3255
2811
|
// and hide exactly the mismatch this field exists to expose.
|
|
3256
2812
|
session_id: sessionId,
|
|
3257
|
-
agent_type: rec?.agent_type ??
|
|
2813
|
+
agent_type: rec?.agent_type ?? str(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
|
|
3258
2814
|
depth: rec?.depth ?? null,
|
|
3259
2815
|
depth_basis: rec?.depth_basis ?? null,
|
|
3260
2816
|
// `null` = the matching start was never seen. That is a finding about
|
|
@@ -3274,12 +2830,12 @@ function handleStop(root, payload, nowIso, sessionId) {
|
|
|
3274
2830
|
}
|
|
3275
2831
|
function processEnvelope(envelope, consumerRoot) {
|
|
3276
2832
|
try {
|
|
3277
|
-
if (!
|
|
2833
|
+
if (!isObject(envelope)) return EXIT_ALLOW3;
|
|
3278
2834
|
const event = envelope["event"];
|
|
3279
2835
|
if (event !== "subagent_start" && event !== "subagent_stop") return EXIT_ALLOW3;
|
|
3280
2836
|
const payload = unwrapPayload(envelope);
|
|
3281
2837
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
3282
|
-
const sessionId =
|
|
2838
|
+
const sessionId = str(payload, "session_id", "sessionId") ?? str(envelope, "session_id", "sessionId");
|
|
3283
2839
|
reapStaleOpenRecords(consumerRoot, nowIso);
|
|
3284
2840
|
if (event === "subagent_start") handleStart(consumerRoot, payload, nowIso, sessionId);
|
|
3285
2841
|
else handleStop(consumerRoot, payload, nowIso, sessionId);
|
|
@@ -3289,43 +2845,600 @@ function processEnvelope(envelope, consumerRoot) {
|
|
|
3289
2845
|
return EXIT_ALLOW3;
|
|
3290
2846
|
}
|
|
3291
2847
|
function resolveConsumerRoot(envelope) {
|
|
3292
|
-
if (
|
|
3293
|
-
const pr =
|
|
2848
|
+
if (isObject(envelope)) {
|
|
2849
|
+
const pr = str(envelope, "workspace_root", "project_root");
|
|
3294
2850
|
if (pr) return pr;
|
|
3295
2851
|
const payload = unwrapPayload(envelope);
|
|
3296
|
-
const cwd =
|
|
2852
|
+
const cwd = str(payload, "cwd");
|
|
3297
2853
|
if (cwd) return cwd;
|
|
3298
2854
|
}
|
|
3299
|
-
return process.cwd();
|
|
2855
|
+
return process.cwd();
|
|
2856
|
+
}
|
|
2857
|
+
function main4() {
|
|
2858
|
+
const raw = readHookStdin();
|
|
2859
|
+
let envelope = {};
|
|
2860
|
+
try {
|
|
2861
|
+
envelope = raw.trim() ? JSON.parse(raw) : {};
|
|
2862
|
+
} catch {
|
|
2863
|
+
return EXIT_ALLOW3;
|
|
2864
|
+
}
|
|
2865
|
+
return processEnvelope(envelope, resolveConsumerRoot(envelope));
|
|
2866
|
+
}
|
|
2867
|
+
function _isCliEntry4() {
|
|
2868
|
+
if (true) {
|
|
2869
|
+
return false;
|
|
2870
|
+
}
|
|
2871
|
+
if (process.argv[1] === void 0) return false;
|
|
2872
|
+
const argvUrl = pathToFileURL4(path4.resolve(process.argv[1])).href;
|
|
2873
|
+
if (import.meta.url === argvUrl) return true;
|
|
2874
|
+
try {
|
|
2875
|
+
return fs4.realpathSync(fileURLToPath4(import.meta.url)) === fs4.realpathSync(path4.resolve(process.argv[1]));
|
|
2876
|
+
} catch {
|
|
2877
|
+
return false;
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
if (_isCliEntry4()) process.exit(main4());
|
|
2881
|
+
|
|
2882
|
+
// src/scripts/_lib/turn_end_transcript.ts
|
|
2883
|
+
import * as fs7 from "node:fs";
|
|
2884
|
+
|
|
2885
|
+
// src/scripts/hooks/end_review_nudge_hook.ts
|
|
2886
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2887
|
+
import * as crypto2 from "node:crypto";
|
|
2888
|
+
import * as fs6 from "node:fs";
|
|
2889
|
+
import * as os from "node:os";
|
|
2890
|
+
import * as path6 from "node:path";
|
|
2891
|
+
import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
|
|
2892
|
+
|
|
2893
|
+
// src/scripts/_lib/review_baseline.ts
|
|
2894
|
+
import { spawnSync } from "node:child_process";
|
|
2895
|
+
import * as fs5 from "node:fs";
|
|
2896
|
+
import * as path5 from "node:path";
|
|
2897
|
+
function currentHeadSha(cwd) {
|
|
2898
|
+
try {
|
|
2899
|
+
const r = spawnSync("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" });
|
|
2900
|
+
if (r.status !== 0) return null;
|
|
2901
|
+
const out = (r.stdout ?? "").trim();
|
|
2902
|
+
return out === "" ? null : out;
|
|
2903
|
+
} catch {
|
|
2904
|
+
return null;
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
var BASELINE_STATE_REL = path5.join("agents", "runtime", "state", "review-baseline");
|
|
2908
|
+
function baselineStateDir(workspaceRoot) {
|
|
2909
|
+
return path5.join(workspaceRoot, BASELINE_STATE_REL);
|
|
2910
|
+
}
|
|
2911
|
+
function baselineStateFile(workspaceRoot, sessionKey) {
|
|
2912
|
+
return path5.join(baselineStateDir(workspaceRoot), `${sessionKey}.json`);
|
|
2913
|
+
}
|
|
2914
|
+
function parseBaseline(raw) {
|
|
2915
|
+
let decoded;
|
|
2916
|
+
try {
|
|
2917
|
+
decoded = JSON.parse(raw);
|
|
2918
|
+
} catch {
|
|
2919
|
+
return null;
|
|
2920
|
+
}
|
|
2921
|
+
if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
|
|
2922
|
+
const o = decoded;
|
|
2923
|
+
const lines = o["baseline_lines"];
|
|
2924
|
+
if (typeof lines !== "number" || !Number.isFinite(lines) || lines < 0) return null;
|
|
2925
|
+
const written = o["written_at"];
|
|
2926
|
+
if (typeof written !== "string") return null;
|
|
2927
|
+
const head = o["head_sha"];
|
|
2928
|
+
const measure = o["measure"];
|
|
2929
|
+
return {
|
|
2930
|
+
head_sha: typeof head === "string" && head !== "" ? head : null,
|
|
2931
|
+
baseline_lines: lines,
|
|
2932
|
+
...measure === "exact" || measure === "capped_approximation" ? { measure } : {},
|
|
2933
|
+
written_at: written
|
|
2934
|
+
};
|
|
2935
|
+
}
|
|
2936
|
+
function readBaseline(workspaceRoot, sessionKey) {
|
|
2937
|
+
const file = baselineStateFile(workspaceRoot, sessionKey);
|
|
2938
|
+
let raw;
|
|
2939
|
+
try {
|
|
2940
|
+
raw = fs5.readFileSync(file, "utf-8");
|
|
2941
|
+
} catch (exc) {
|
|
2942
|
+
const code = exc?.code;
|
|
2943
|
+
return code === "ENOENT" ? "absent" : "unreadable";
|
|
2944
|
+
}
|
|
2945
|
+
return parseBaseline(raw) ?? "unreadable";
|
|
2946
|
+
}
|
|
2947
|
+
function applyBaseline(measuredLines, baseline, headSha, measuredMeasure = "exact") {
|
|
2948
|
+
if (baseline === "absent" || baseline === "unreadable") {
|
|
2949
|
+
return { applied: false, fallback: baseline, lines: measuredLines };
|
|
2950
|
+
}
|
|
2951
|
+
if (baseline.measure !== "exact" || measuredMeasure !== "exact") {
|
|
2952
|
+
return { applied: false, fallback: "mixed-measure", lines: measuredLines };
|
|
2953
|
+
}
|
|
2954
|
+
if (baseline.head_sha !== headSha()) {
|
|
2955
|
+
return { applied: false, fallback: "head-moved", lines: measuredLines };
|
|
2956
|
+
}
|
|
2957
|
+
return {
|
|
2958
|
+
applied: true,
|
|
2959
|
+
baseline,
|
|
2960
|
+
lines: Math.max(0, measuredLines - baseline.baseline_lines)
|
|
2961
|
+
};
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
// src/scripts/_lib/audit_field_provenance.ts
|
|
2965
|
+
var MAX_APPLIED_IDS = 32;
|
|
2966
|
+
function appliedIds(values) {
|
|
2967
|
+
const out = [];
|
|
2968
|
+
for (const raw of values ?? []) {
|
|
2969
|
+
const v = String(raw).trim();
|
|
2970
|
+
if (v === "" || out.includes(v)) continue;
|
|
2971
|
+
out.push(v);
|
|
2972
|
+
if (out.length >= MAX_APPLIED_IDS) break;
|
|
2973
|
+
}
|
|
2974
|
+
return out;
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
// src/scripts/_lib/review_skipped_record.ts
|
|
2978
|
+
var BASELINE_APPLICATIONS = [
|
|
2979
|
+
"applied",
|
|
2980
|
+
"absent",
|
|
2981
|
+
"unreadable",
|
|
2982
|
+
"head_moved",
|
|
2983
|
+
"mixed_measure"
|
|
2984
|
+
];
|
|
2985
|
+
var PRODUCER_PRIVACY_CLASS = "ids-only";
|
|
2986
|
+
function isNonNegInt(n) {
|
|
2987
|
+
return typeof n === "number" && Number.isFinite(n) && Number.isInteger(n) && n >= 0;
|
|
2988
|
+
}
|
|
2989
|
+
var HIGH_RISK_DIFF_LINES = 200;
|
|
2990
|
+
function buildReviewSkippedLine(input) {
|
|
2991
|
+
const errors = [];
|
|
2992
|
+
if (!isNonNegInt(input.diff_lines)) {
|
|
2993
|
+
errors.push("diff_lines must be a non-negative integer");
|
|
2994
|
+
}
|
|
2995
|
+
if (input.mutation_measure !== "exact" && input.mutation_measure !== "capped_approximation") {
|
|
2996
|
+
errors.push('mutation_measure must be "exact" or "capped_approximation"');
|
|
2997
|
+
}
|
|
2998
|
+
if (!input.ts) errors.push("ts (ISO-8601 UTC) is required");
|
|
2999
|
+
if (!input.id) errors.push("id (ULID, UUID, or content hash) is required");
|
|
3000
|
+
if (input.baseline !== void 0 && !BASELINE_APPLICATIONS.includes(input.baseline)) {
|
|
3001
|
+
errors.push(`baseline must be one of ${BASELINE_APPLICATIONS.join(", ")}`);
|
|
3002
|
+
}
|
|
3003
|
+
if (errors.length) return { line: null, errors };
|
|
3004
|
+
const line = {
|
|
3005
|
+
schema_version: 1,
|
|
3006
|
+
id: input.id,
|
|
3007
|
+
ts: input.ts,
|
|
3008
|
+
work_id: input.work_id ?? `review-skipped-${input.ts}`,
|
|
3009
|
+
phase: "report",
|
|
3010
|
+
outcome: "skipped",
|
|
3011
|
+
confidence_band: "medium",
|
|
3012
|
+
risk_class: input.diff_lines >= HIGH_RISK_DIFF_LINES ? "high" : "medium",
|
|
3013
|
+
memory: { asks: 0, hits: 0 },
|
|
3014
|
+
verify: { claims: 0, first_try_passes: 0 },
|
|
3015
|
+
// COMPUTED, never a producer constant.
|
|
3016
|
+
rules_applied: appliedIds(input.rules_applied),
|
|
3017
|
+
// MANDATORY on every audit line, same as the orchestration producer.
|
|
3018
|
+
privacy_class: PRODUCER_PRIVACY_CLASS,
|
|
3019
|
+
// `skills_applied` is OMITTED here, deliberately and not by oversight.
|
|
3020
|
+
// audit-log-v1 distinguishes an absent key ("not recorded") from `[]`
|
|
3021
|
+
// ("recorded, none applied"), and this writer observes a review that
|
|
3022
|
+
// did not happen -- it has no skill observation to offer in either
|
|
3023
|
+
// direction. Emitting `[]` would assert "no skills applied" on evidence
|
|
3024
|
+
// this producer never gathered, which is the exact fabrication the
|
|
3025
|
+
// absent/empty split exists to make impossible.
|
|
3026
|
+
persona: null,
|
|
3027
|
+
input_kind: "prompt",
|
|
3028
|
+
type: "note",
|
|
3029
|
+
review_skipped: {
|
|
3030
|
+
diff_lines: input.diff_lines,
|
|
3031
|
+
mutation_measure: input.mutation_measure,
|
|
3032
|
+
// Omitted, not defaulted, when the producer recorded nothing —
|
|
3033
|
+
// audit-log-v1's absent-versus-present split is the same one
|
|
3034
|
+
// `skills_applied` uses above, for the same reason.
|
|
3035
|
+
...input.baseline === void 0 ? {} : { baseline: input.baseline }
|
|
3036
|
+
}
|
|
3037
|
+
};
|
|
3038
|
+
return { line, errors: [] };
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
// src/scripts/hooks/end_review_nudge_hook.ts
|
|
3042
|
+
var MUTATION_LINE_THRESHOLD = 50;
|
|
3043
|
+
var UNTRACKED_FILE_CAP = 20;
|
|
3044
|
+
var TRANSCRIPT_SCAN_MAX_BYTES = 50 * 1024 * 1024;
|
|
3045
|
+
function isSafeTranscriptPath(rawPath, opts = {}) {
|
|
3046
|
+
if (!rawPath || !/\.jsonl$/i.test(rawPath)) return false;
|
|
3047
|
+
const homeDir = opts.homeDir ?? os.homedir();
|
|
3048
|
+
const maxBytes = opts.maxBytes ?? TRANSCRIPT_SCAN_MAX_BYTES;
|
|
3049
|
+
try {
|
|
3050
|
+
const realHome = fs6.realpathSync(homeDir);
|
|
3051
|
+
const realPath = fs6.realpathSync(rawPath);
|
|
3052
|
+
const relative3 = path6.relative(realHome, realPath);
|
|
3053
|
+
if (relative3 === "" || relative3.startsWith("..") || path6.isAbsolute(relative3)) {
|
|
3054
|
+
return false;
|
|
3055
|
+
}
|
|
3056
|
+
const { size } = fs6.statSync(realPath);
|
|
3057
|
+
return size <= maxBytes;
|
|
3058
|
+
} catch {
|
|
3059
|
+
return false;
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
function parseNumstat(raw) {
|
|
3063
|
+
const rows = [];
|
|
3064
|
+
for (const line of raw.split("\n")) {
|
|
3065
|
+
const m = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line);
|
|
3066
|
+
if (!m) continue;
|
|
3067
|
+
const addedStr = m[1];
|
|
3068
|
+
const deletedStr = m[2];
|
|
3069
|
+
rows.push({
|
|
3070
|
+
added: addedStr === "-" ? 0 : Number(addedStr),
|
|
3071
|
+
deleted: deletedStr === "-" ? 0 : Number(deletedStr),
|
|
3072
|
+
path: m[3]
|
|
3073
|
+
});
|
|
3074
|
+
}
|
|
3075
|
+
return rows;
|
|
3076
|
+
}
|
|
3077
|
+
var DOC_PATH_RE = /\.md$/i;
|
|
3078
|
+
function isDocPath(p) {
|
|
3079
|
+
return DOC_PATH_RE.test(p);
|
|
3080
|
+
}
|
|
3081
|
+
function nonDocMutatedLines(rows) {
|
|
3082
|
+
return rows.filter((r) => !isDocPath(r.path)).reduce((sum, r) => sum + r.added + r.deleted, 0);
|
|
3083
|
+
}
|
|
3084
|
+
function gitNumstatRows(cwd) {
|
|
3085
|
+
try {
|
|
3086
|
+
const r = spawnSync2("git", ["diff", "--numstat", "HEAD"], {
|
|
3087
|
+
cwd,
|
|
3088
|
+
encoding: "utf8",
|
|
3089
|
+
maxBuffer: 16 * 1024 * 1024
|
|
3090
|
+
});
|
|
3091
|
+
if (r.status !== 0) return [];
|
|
3092
|
+
return parseNumstat(r.stdout ?? "");
|
|
3093
|
+
} catch {
|
|
3094
|
+
return [];
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
function untrackedNonDocFiles(cwd) {
|
|
3098
|
+
try {
|
|
3099
|
+
const r = spawnSync2("git", ["ls-files", "--others", "--exclude-standard"], {
|
|
3100
|
+
cwd,
|
|
3101
|
+
encoding: "utf8",
|
|
3102
|
+
maxBuffer: 16 * 1024 * 1024
|
|
3103
|
+
});
|
|
3104
|
+
if (r.status !== 0) return [];
|
|
3105
|
+
return (r.stdout ?? "").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !isDocPath(l));
|
|
3106
|
+
} catch {
|
|
3107
|
+
return [];
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
var BINARY_PROBE_BYTES = 8e3;
|
|
3111
|
+
var UNTRACKED_FILE_READ_CAP_BYTES = 8 * 1024 * 1024;
|
|
3112
|
+
function untrackedFileLineCount(cwd, relPath) {
|
|
3113
|
+
try {
|
|
3114
|
+
const abs = path6.resolve(cwd, relPath);
|
|
3115
|
+
const size = fs6.statSync(abs).size;
|
|
3116
|
+
if (size === 0) return 0;
|
|
3117
|
+
if (size > UNTRACKED_FILE_READ_CAP_BYTES) {
|
|
3118
|
+
return MUTATION_LINE_THRESHOLD + 1;
|
|
3119
|
+
}
|
|
3120
|
+
const buf = fs6.readFileSync(abs);
|
|
3121
|
+
if (buf.length === 0) return 0;
|
|
3122
|
+
const probe = buf.subarray(0, Math.min(buf.length, BINARY_PROBE_BYTES));
|
|
3123
|
+
if (probe.includes(0)) return 0;
|
|
3124
|
+
let lines = 0;
|
|
3125
|
+
for (const byte of buf) {
|
|
3126
|
+
if (byte === 10) lines += 1;
|
|
3127
|
+
}
|
|
3128
|
+
if (buf[buf.length - 1] !== 10) lines += 1;
|
|
3129
|
+
return lines;
|
|
3130
|
+
} catch {
|
|
3131
|
+
return 0;
|
|
3132
|
+
}
|
|
3133
|
+
}
|
|
3134
|
+
function totalNonDocMutatedLinesWithMeasure(cwd) {
|
|
3135
|
+
const trackedLines = nonDocMutatedLines(gitNumstatRows(cwd));
|
|
3136
|
+
const untracked = untrackedNonDocFiles(cwd);
|
|
3137
|
+
if (untracked.length > UNTRACKED_FILE_CAP) {
|
|
3138
|
+
return { lines: MUTATION_LINE_THRESHOLD + 1 + trackedLines, measure: "capped_approximation" };
|
|
3139
|
+
}
|
|
3140
|
+
const untrackedLines = untracked.reduce(
|
|
3141
|
+
(sum, relPath) => sum + untrackedFileLineCount(cwd, relPath),
|
|
3142
|
+
0
|
|
3143
|
+
);
|
|
3144
|
+
return { lines: trackedLines + untrackedLines, measure: "exact" };
|
|
3145
|
+
}
|
|
3146
|
+
function isObject2(v) {
|
|
3147
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3148
|
+
}
|
|
3149
|
+
function str2(v) {
|
|
3150
|
+
return typeof v === "string" ? v : "";
|
|
3151
|
+
}
|
|
3152
|
+
var DISPATCH_TOOL_RE = /^(agent|task)$/i;
|
|
3153
|
+
var REVIEW_SHAPED_LABEL_RE = /\b(review|judge|adversarial|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
|
|
3154
|
+
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;
|
|
3155
|
+
var REVIEW_SKILL_NAME_RE = /^(review[-:]changes|judge[-:][a-z0-9-]+|adversarial-review|code-review)$/i;
|
|
3156
|
+
function scanTranscriptForReviewer(transcriptText) {
|
|
3157
|
+
for (const raw of transcriptText.split("\n")) {
|
|
3158
|
+
const line = raw.trim();
|
|
3159
|
+
if (!line) continue;
|
|
3160
|
+
let obj;
|
|
3161
|
+
try {
|
|
3162
|
+
obj = JSON.parse(line);
|
|
3163
|
+
} catch {
|
|
3164
|
+
continue;
|
|
3165
|
+
}
|
|
3166
|
+
if (!isObject2(obj) || obj["type"] !== "assistant") continue;
|
|
3167
|
+
const msg = obj["message"];
|
|
3168
|
+
if (!isObject2(msg)) continue;
|
|
3169
|
+
const content = msg["content"];
|
|
3170
|
+
if (!Array.isArray(content)) continue;
|
|
3171
|
+
for (const blk of content) {
|
|
3172
|
+
if (!isObject2(blk) || blk["type"] !== "tool_use") continue;
|
|
3173
|
+
const name = str2(blk["name"]);
|
|
3174
|
+
const input = blk["input"];
|
|
3175
|
+
const inputObj = isObject2(input) ? input : {};
|
|
3176
|
+
if (DISPATCH_TOOL_RE.test(name)) {
|
|
3177
|
+
const label = [
|
|
3178
|
+
str2(inputObj["description"]),
|
|
3179
|
+
str2(inputObj["subagent_type"])
|
|
3180
|
+
].filter(Boolean).join(" ");
|
|
3181
|
+
if (REVIEW_SHAPED_LABEL_RE.test(label)) return true;
|
|
3182
|
+
const prompt = str2(inputObj["prompt"]);
|
|
3183
|
+
if (REVIEW_SHAPED_PROMPT_RE.test(prompt)) return true;
|
|
3184
|
+
continue;
|
|
3185
|
+
}
|
|
3186
|
+
if (/^skill$/i.test(name)) {
|
|
3187
|
+
const skillName = str2(inputObj["skill"]) || str2(inputObj["name"]) || str2(inputObj["command"]);
|
|
3188
|
+
if (REVIEW_SKILL_NAME_RE.test(skillName)) return true;
|
|
3189
|
+
continue;
|
|
3190
|
+
}
|
|
3191
|
+
if (REVIEW_SKILL_NAME_RE.test(name)) return true;
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
return false;
|
|
3195
|
+
}
|
|
3196
|
+
function buildAdvisoryLine(diffLines) {
|
|
3197
|
+
return `this session mutated ${diffLines} lines without a neutral review; spawn a cross-model reviewer before claiming done (delegation-policy / verify-budget)`;
|
|
3198
|
+
}
|
|
3199
|
+
function appendReviewSkippedTelemetry(workspaceRoot, diffLines, mutationMeasure, baseline) {
|
|
3200
|
+
if (is_replay_mode()) return;
|
|
3201
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
3202
|
+
const { line, errors } = buildReviewSkippedLine({
|
|
3203
|
+
diff_lines: diffLines,
|
|
3204
|
+
mutation_measure: mutationMeasure,
|
|
3205
|
+
ts,
|
|
3206
|
+
id: crypto2.randomUUID(),
|
|
3207
|
+
baseline
|
|
3208
|
+
});
|
|
3209
|
+
if (errors.length || line === null) return;
|
|
3210
|
+
try {
|
|
3211
|
+
const dir = path6.join(workspaceRoot, "agents", "runtime", "state", "audit");
|
|
3212
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
3213
|
+
const file = path6.join(dir, `${ts.slice(0, 7)}.jsonl`);
|
|
3214
|
+
fs6.appendFileSync(file, `${JSON.stringify(line)}
|
|
3215
|
+
`, "utf8");
|
|
3216
|
+
} catch {
|
|
3217
|
+
}
|
|
3218
|
+
}
|
|
3219
|
+
function sessionStateDir(workspaceRoot) {
|
|
3220
|
+
return path6.join(workspaceRoot, "agents", "runtime", "state", "end-review-nudge");
|
|
3221
|
+
}
|
|
3222
|
+
function sessionStateFile(workspaceRoot, sessionKey) {
|
|
3223
|
+
return path6.join(sessionStateDir(workspaceRoot), `${sessionKey}.json`);
|
|
3224
|
+
}
|
|
3225
|
+
function deriveSessionKey2(envelope, payload) {
|
|
3226
|
+
const raw = str2(envelope["session_id"]) || str2(payload["transcript_path"] ?? payload["transcriptPath"]);
|
|
3227
|
+
const material = raw || "unknown-session";
|
|
3228
|
+
return crypto2.createHash("sha256").update(material).digest("hex");
|
|
3229
|
+
}
|
|
3230
|
+
function hasFiredThisSession(workspaceRoot, sessionKey) {
|
|
3231
|
+
try {
|
|
3232
|
+
return fs6.existsSync(sessionStateFile(workspaceRoot, sessionKey));
|
|
3233
|
+
} catch {
|
|
3234
|
+
return false;
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
function markFiredThisSession(workspaceRoot, sessionKey, ts) {
|
|
3238
|
+
try {
|
|
3239
|
+
atomic_write_json(sessionStateFile(workspaceRoot, sessionKey), { fired_at: ts });
|
|
3240
|
+
} catch {
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
function _read_stdin() {
|
|
3244
|
+
return readHookStdin();
|
|
3245
|
+
}
|
|
3246
|
+
var FALLBACK_TO_TELEMETRY = {
|
|
3247
|
+
absent: "absent",
|
|
3248
|
+
unreadable: "unreadable",
|
|
3249
|
+
"head-moved": "head_moved",
|
|
3250
|
+
"mixed-measure": "mixed_measure"
|
|
3251
|
+
};
|
|
3252
|
+
function main5() {
|
|
3253
|
+
const [envelope, payload] = unwrap(_read_stdin(), "claude");
|
|
3254
|
+
const event = String(envelope["event"] ?? "");
|
|
3255
|
+
if (event !== "" && event !== "stop") {
|
|
3256
|
+
return 0;
|
|
3257
|
+
}
|
|
3258
|
+
const workspace_root = String(envelope["workspace_root"] ?? "").trim() || process.cwd();
|
|
3259
|
+
let measuredLines;
|
|
3260
|
+
let mutationMeasure;
|
|
3261
|
+
try {
|
|
3262
|
+
const measured = totalNonDocMutatedLinesWithMeasure(workspace_root);
|
|
3263
|
+
measuredLines = measured.lines;
|
|
3264
|
+
mutationMeasure = measured.measure;
|
|
3265
|
+
} catch {
|
|
3266
|
+
return 0;
|
|
3267
|
+
}
|
|
3268
|
+
const sessionKey = deriveSessionKey2(envelope, payload);
|
|
3269
|
+
const baselineOutcome = applyBaseline(
|
|
3270
|
+
measuredLines,
|
|
3271
|
+
readBaseline(workspace_root, sessionKey),
|
|
3272
|
+
// A THUNK, not a value: on the `absent` and `unreadable` branches the
|
|
3273
|
+
// result is discarded, and those two are the steady state on every host
|
|
3274
|
+
// without this nudge and on every session before its first baseline
|
|
3275
|
+
// write. Passing the value eagerly spent an 11-14 ms `git rev-parse` on
|
|
3276
|
+
// each of those stops for nothing.
|
|
3277
|
+
() => currentHeadSha(workspace_root),
|
|
3278
|
+
mutationMeasure
|
|
3279
|
+
);
|
|
3280
|
+
const diffLines = baselineOutcome.lines;
|
|
3281
|
+
const baselineApplication = baselineOutcome.applied ? "applied" : FALLBACK_TO_TELEMETRY[baselineOutcome.fallback];
|
|
3282
|
+
if (diffLines <= MUTATION_LINE_THRESHOLD) {
|
|
3283
|
+
return 0;
|
|
3284
|
+
}
|
|
3285
|
+
if (hasFiredThisSession(workspace_root, sessionKey)) {
|
|
3286
|
+
return 0;
|
|
3287
|
+
}
|
|
3288
|
+
const transcriptPath = str2(
|
|
3289
|
+
payload["transcript_path"] ?? payload["transcriptPath"]
|
|
3290
|
+
).trim();
|
|
3291
|
+
let reviewerRan = false;
|
|
3292
|
+
if (transcriptPath && isSafeTranscriptPath(transcriptPath)) {
|
|
3293
|
+
try {
|
|
3294
|
+
reviewerRan = scanTranscriptForReviewer(fs6.readFileSync(transcriptPath, "utf-8"));
|
|
3295
|
+
} catch {
|
|
3296
|
+
reviewerRan = false;
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
if (reviewerRan) {
|
|
3300
|
+
return 0;
|
|
3301
|
+
}
|
|
3302
|
+
markFiredThisSession(workspace_root, sessionKey, (/* @__PURE__ */ new Date()).toISOString());
|
|
3303
|
+
try {
|
|
3304
|
+
appendReviewSkippedTelemetry(
|
|
3305
|
+
workspace_root,
|
|
3306
|
+
diffLines,
|
|
3307
|
+
mutationMeasure,
|
|
3308
|
+
baselineApplication
|
|
3309
|
+
);
|
|
3310
|
+
} catch {
|
|
3311
|
+
}
|
|
3312
|
+
process.stdout.write(
|
|
3313
|
+
`${JSON.stringify({
|
|
3314
|
+
decision: "warn",
|
|
3315
|
+
reason: `end-review-nudge: session mutated ${diffLines} non-doc lines without a neutral review`,
|
|
3316
|
+
additional_context: buildAdvisoryLine(diffLines)
|
|
3317
|
+
})}
|
|
3318
|
+
`
|
|
3319
|
+
);
|
|
3320
|
+
return EXIT_WARN2;
|
|
3321
|
+
}
|
|
3322
|
+
function _isCliEntry5() {
|
|
3323
|
+
if (true) {
|
|
3324
|
+
return false;
|
|
3325
|
+
}
|
|
3326
|
+
if (process.argv[1] === void 0) {
|
|
3327
|
+
return false;
|
|
3328
|
+
}
|
|
3329
|
+
const argvUrl = pathToFileURL5(path6.resolve(process.argv[1])).href;
|
|
3330
|
+
if (import.meta.url === argvUrl) {
|
|
3331
|
+
return true;
|
|
3332
|
+
}
|
|
3333
|
+
try {
|
|
3334
|
+
const here = fs6.realpathSync(fileURLToPath5(import.meta.url));
|
|
3335
|
+
const argv = fs6.realpathSync(path6.resolve(process.argv[1]));
|
|
3336
|
+
return here === argv;
|
|
3337
|
+
} catch {
|
|
3338
|
+
return false;
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
if (_isCliEntry5()) {
|
|
3342
|
+
process.exit(main5());
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
// src/scripts/_lib/turn_end_transcript.ts
|
|
3346
|
+
function readTranscriptTail(transcriptPath, opts = {}) {
|
|
3347
|
+
const empty = {
|
|
3348
|
+
lastAssistant: "",
|
|
3349
|
+
turnOrdinal: 0,
|
|
3350
|
+
toolCalls: [],
|
|
3351
|
+
assistantTurnTexts: []
|
|
3352
|
+
};
|
|
3353
|
+
if (!transcriptPath || !isSafeTranscriptPath(transcriptPath, opts)) return empty;
|
|
3354
|
+
let lines;
|
|
3355
|
+
try {
|
|
3356
|
+
if (opts.maxBytes !== void 0 && fs7.statSync(transcriptPath).size > opts.maxBytes) {
|
|
3357
|
+
return empty;
|
|
3358
|
+
}
|
|
3359
|
+
lines = fs7.readFileSync(transcriptPath, "utf-8").split("\n");
|
|
3360
|
+
} catch {
|
|
3361
|
+
return empty;
|
|
3362
|
+
}
|
|
3363
|
+
let lastAssistant = "";
|
|
3364
|
+
let turnOrdinal = 0;
|
|
3365
|
+
let toolCalls = [];
|
|
3366
|
+
let assistantTurnTexts = [];
|
|
3367
|
+
for (const rawLine of lines) {
|
|
3368
|
+
const line = rawLine.trim();
|
|
3369
|
+
if (!line) continue;
|
|
3370
|
+
let obj;
|
|
3371
|
+
try {
|
|
3372
|
+
obj = JSON.parse(line);
|
|
3373
|
+
} catch {
|
|
3374
|
+
continue;
|
|
3375
|
+
}
|
|
3376
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) continue;
|
|
3377
|
+
const entry = obj;
|
|
3378
|
+
if (entry["isSidechain"] === true) continue;
|
|
3379
|
+
const role = entry["type"];
|
|
3380
|
+
if (role !== "assistant" && role !== "user") continue;
|
|
3381
|
+
const msg = entry["message"];
|
|
3382
|
+
if (typeof msg !== "object" || msg === null || Array.isArray(msg)) continue;
|
|
3383
|
+
const content = msg["content"];
|
|
3384
|
+
if (role === "assistant") {
|
|
3385
|
+
toolCalls.push(...extractToolCalls(content));
|
|
3386
|
+
}
|
|
3387
|
+
const text = _messageText(content);
|
|
3388
|
+
if (text === null) continue;
|
|
3389
|
+
if (role === "assistant") {
|
|
3390
|
+
lastAssistant = text;
|
|
3391
|
+
assistantTurnTexts.push(text);
|
|
3392
|
+
} else if (!isSyntheticPrompt(text)) {
|
|
3393
|
+
turnOrdinal += 1;
|
|
3394
|
+
toolCalls = [];
|
|
3395
|
+
assistantTurnTexts = [];
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
return {
|
|
3399
|
+
lastAssistant: lastAssistant.trim(),
|
|
3400
|
+
turnOrdinal,
|
|
3401
|
+
toolCalls,
|
|
3402
|
+
assistantTurnTexts
|
|
3403
|
+
};
|
|
3300
3404
|
}
|
|
3301
|
-
function
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3405
|
+
function extractToolCalls(content) {
|
|
3406
|
+
if (!Array.isArray(content)) return [];
|
|
3407
|
+
const out = [];
|
|
3408
|
+
for (const blk of content) {
|
|
3409
|
+
if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
|
|
3410
|
+
const b = blk;
|
|
3411
|
+
if (b["type"] !== "tool_use") continue;
|
|
3412
|
+
const name = b["name"];
|
|
3413
|
+
if (typeof name !== "string") continue;
|
|
3414
|
+
const input = b["input"];
|
|
3415
|
+
const call = { name };
|
|
3416
|
+
if (typeof input === "object" && input !== null && !Array.isArray(input)) {
|
|
3417
|
+
const inp = input;
|
|
3418
|
+
const cmd = inp["command"];
|
|
3419
|
+
if (typeof cmd === "string") call.command = cmd;
|
|
3420
|
+
const p = inp["file_path"] ?? inp["path"] ?? inp["notebook_path"];
|
|
3421
|
+
if (typeof p === "string") call.path = p;
|
|
3422
|
+
}
|
|
3423
|
+
out.push(call);
|
|
3308
3424
|
}
|
|
3309
|
-
return
|
|
3425
|
+
return out;
|
|
3310
3426
|
}
|
|
3311
|
-
function
|
|
3312
|
-
if (
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
return false;
|
|
3427
|
+
function _messageText(content) {
|
|
3428
|
+
if (typeof content === "string") return content;
|
|
3429
|
+
if (!Array.isArray(content)) return null;
|
|
3430
|
+
const parts = [];
|
|
3431
|
+
for (const blk of content) {
|
|
3432
|
+
if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
|
|
3433
|
+
const b = blk;
|
|
3434
|
+
if (b["type"] !== "text") continue;
|
|
3435
|
+
const t = b["text"];
|
|
3436
|
+
if (typeof t === "string") parts.push(t);
|
|
3322
3437
|
}
|
|
3438
|
+
return parts.length > 0 ? parts.join("\n") : null;
|
|
3323
3439
|
}
|
|
3324
|
-
if (_isCliEntry5()) process.exit(main5());
|
|
3325
3440
|
|
|
3326
3441
|
// src/scripts/hooks/turn_end_gate_hook.ts
|
|
3327
|
-
var EXIT_BLOCK = 1;
|
|
3328
|
-
var EXIT_ALLOW4 = 0;
|
|
3329
3442
|
var TRANSCRIPT_READ_MAX_BYTES = 8 * 1024 * 1024;
|
|
3330
3443
|
function stripFencedBlocks(text) {
|
|
3331
3444
|
const out = [];
|
|
@@ -3430,7 +3543,7 @@ function detectPromissory(reply) {
|
|
|
3430
3543
|
function readCiSettled(workspaceRoot, session_id) {
|
|
3431
3544
|
if (!hasStableSessionId(session_id)) return { seen: false, settled: false };
|
|
3432
3545
|
try {
|
|
3433
|
-
const raw =
|
|
3546
|
+
const raw = fs8.readFileSync(path7.join(workspaceRoot, statePathFor(session_id)), "utf-8");
|
|
3434
3547
|
const decoded = JSON.parse(raw);
|
|
3435
3548
|
if (!owns_session_state(decoded, session_id)) return { seen: false, settled: false };
|
|
3436
3549
|
if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
|
|
@@ -3466,7 +3579,7 @@ function detectCompletionClaim(reply, ci) {
|
|
|
3466
3579
|
function readLanguagePin(workspaceRoot, session_id) {
|
|
3467
3580
|
if (!hasStableSessionId(session_id)) return "und";
|
|
3468
3581
|
try {
|
|
3469
|
-
const raw =
|
|
3582
|
+
const raw = fs8.readFileSync(
|
|
3470
3583
|
path7.join(workspaceRoot, statePathFor2(session_id)),
|
|
3471
3584
|
"utf-8"
|
|
3472
3585
|
);
|
|
@@ -3493,29 +3606,38 @@ function detectLanguage(reply, pinned) {
|
|
|
3493
3606
|
};
|
|
3494
3607
|
}
|
|
3495
3608
|
var _EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
3496
|
-
|
|
3497
|
-
function isVerificationCommand(command) {
|
|
3498
|
-
return _VERIFY_RE.test(command);
|
|
3499
|
-
}
|
|
3500
|
-
function detectUnverifiedEdit(toolCalls) {
|
|
3609
|
+
function detectUnverifiedEdit(toolCalls, runState = null) {
|
|
3501
3610
|
let lastEdit = -1;
|
|
3502
3611
|
for (let i = 0; i < toolCalls.length; i += 1) {
|
|
3503
3612
|
if (_EDIT_TOOLS.has(toolCalls[i].name)) lastEdit = i;
|
|
3504
3613
|
}
|
|
3505
3614
|
if (lastEdit === -1) return null;
|
|
3615
|
+
const edited = toolCalls[lastEdit].path ?? toolCalls[lastEdit].name;
|
|
3616
|
+
if (runState !== null) {
|
|
3617
|
+
const reading = readRunEvidence(runState);
|
|
3618
|
+
if (reading.passed) return null;
|
|
3619
|
+
if (!reading.instrumentGap) {
|
|
3620
|
+
return {
|
|
3621
|
+
detector: "verification",
|
|
3622
|
+
evidence: edited,
|
|
3623
|
+
reason: describeRecordFinding(reading.failed, reading.reasons),
|
|
3624
|
+
mode: "record"
|
|
3625
|
+
};
|
|
3626
|
+
}
|
|
3627
|
+
}
|
|
3506
3628
|
for (let i = lastEdit + 1; i < toolCalls.length; i += 1) {
|
|
3507
3629
|
const c = toolCalls[i];
|
|
3508
3630
|
if (c.name === "Bash" && c.command !== void 0 && isVerificationCommand(c.command)) {
|
|
3509
3631
|
return null;
|
|
3510
3632
|
}
|
|
3511
3633
|
}
|
|
3512
|
-
const edited = toolCalls[lastEdit].path;
|
|
3513
3634
|
return {
|
|
3514
3635
|
detector: "verification",
|
|
3515
3636
|
// The path, never the diff: the evidence span is quoted into a refusal,
|
|
3516
3637
|
// and `ToolCall` is shaped so a file body cannot reach it.
|
|
3517
|
-
evidence: edited
|
|
3518
|
-
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)"
|
|
3638
|
+
evidence: edited,
|
|
3639
|
+
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)",
|
|
3640
|
+
mode: "transcript"
|
|
3519
3641
|
};
|
|
3520
3642
|
}
|
|
3521
3643
|
var _SOURCE_RE = /\.(?:ts|tsx|js|jsx|mjs|cjs|vue|svelte|php|py|rb|go|rs|java|kt|swift|cs|scala|ex|exs)$/i;
|
|
@@ -3527,18 +3649,37 @@ function _isTestPath(p) {
|
|
|
3527
3649
|
function _isProductionSource(p) {
|
|
3528
3650
|
return _SOURCE_RE.test(p) && !_isTestPath(p);
|
|
3529
3651
|
}
|
|
3530
|
-
function detectUntestedChange(reply, toolCalls) {
|
|
3652
|
+
function detectUntestedChange(reply, toolCalls, runState = null) {
|
|
3531
3653
|
const edited = toolCalls.filter((c) => _EDIT_TOOLS.has(c.name) && c.path !== void 0).map((c) => c.path);
|
|
3532
3654
|
const source = edited.filter(_isProductionSource);
|
|
3533
3655
|
if (source.length === 0) return null;
|
|
3534
|
-
|
|
3656
|
+
const wroteNewTest = toolCalls.some(
|
|
3657
|
+
(c) => c.name === "Write" && c.path !== void 0 && _isTestPath(c.path)
|
|
3658
|
+
);
|
|
3659
|
+
let noRedEvidence = false;
|
|
3660
|
+
if (edited.some(_isTestPath)) {
|
|
3661
|
+
if (!wroteNewTest) return null;
|
|
3662
|
+
if (runState === null) return null;
|
|
3663
|
+
if (hasRedThenGreen(runState)) return null;
|
|
3664
|
+
if (readRunEvidence(runState).instrumentGap) return null;
|
|
3665
|
+
noRedEvidence = true;
|
|
3666
|
+
}
|
|
3535
3667
|
const prose = visibleProse(reply);
|
|
3536
3668
|
const m = _COMPLETION_RE.exec(prose);
|
|
3537
3669
|
if (!m) return null;
|
|
3538
3670
|
if (_NEGATED_CLAIM_RE.test(_lineAround(prose, m.index))) return null;
|
|
3539
3671
|
const shown = source.slice(0, 3).join(", ");
|
|
3672
|
+
if (noRedEvidence) {
|
|
3673
|
+
return {
|
|
3674
|
+
detector: "untested",
|
|
3675
|
+
evidence: shown + (source.length > 3 ? ` (+${String(source.length - 3)} more)` : ""),
|
|
3676
|
+
mode: "record",
|
|
3677
|
+
reason: NO_RED_EVIDENCE_REASON
|
|
3678
|
+
};
|
|
3679
|
+
}
|
|
3540
3680
|
return {
|
|
3541
3681
|
detector: "untested",
|
|
3682
|
+
mode: "transcript",
|
|
3542
3683
|
evidence: shown + (source.length > 3 ? ` (+${String(source.length - 3)} more)` : ""),
|
|
3543
3684
|
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"
|
|
3544
3685
|
};
|
|
@@ -3548,7 +3689,7 @@ function sessionStateFile2(workspaceRoot, sessionKey) {
|
|
|
3548
3689
|
}
|
|
3549
3690
|
function alreadyRefusedTurn(workspaceRoot, sessionKey, turnOrdinal) {
|
|
3550
3691
|
try {
|
|
3551
|
-
const raw =
|
|
3692
|
+
const raw = fs8.readFileSync(sessionStateFile2(workspaceRoot, sessionKey), "utf-8");
|
|
3552
3693
|
const decoded = JSON.parse(raw);
|
|
3553
3694
|
if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
|
|
3554
3695
|
return decoded["refused_turn"] === turnOrdinal;
|
|
@@ -3562,7 +3703,7 @@ function markRefusedTurn(workspaceRoot, sessionKey, turnOrdinal, detectors, prom
|
|
|
3562
3703
|
const file = sessionStateFile2(workspaceRoot, sessionKey);
|
|
3563
3704
|
let prev = null;
|
|
3564
3705
|
try {
|
|
3565
|
-
prev = parseRecord(
|
|
3706
|
+
prev = parseRecord(fs8.readFileSync(file, "utf-8"));
|
|
3566
3707
|
} catch {
|
|
3567
3708
|
}
|
|
3568
3709
|
let version;
|
|
@@ -3585,112 +3726,10 @@ function markRefusedTurn(workspaceRoot, sessionKey, turnOrdinal, detectors, prom
|
|
|
3585
3726
|
} catch {
|
|
3586
3727
|
}
|
|
3587
3728
|
}
|
|
3588
|
-
function readTranscriptTail(transcriptPath, opts = {}) {
|
|
3589
|
-
const empty = {
|
|
3590
|
-
lastAssistant: "",
|
|
3591
|
-
turnOrdinal: 0,
|
|
3592
|
-
toolCalls: [],
|
|
3593
|
-
assistantTurnTexts: []
|
|
3594
|
-
};
|
|
3595
|
-
if (!transcriptPath || !isSafeTranscriptPath(transcriptPath, opts)) return empty;
|
|
3596
|
-
let lines;
|
|
3597
|
-
try {
|
|
3598
|
-
if (opts.maxBytes !== void 0 && fs7.statSync(transcriptPath).size > opts.maxBytes) {
|
|
3599
|
-
return empty;
|
|
3600
|
-
}
|
|
3601
|
-
lines = fs7.readFileSync(transcriptPath, "utf-8").split("\n");
|
|
3602
|
-
} catch {
|
|
3603
|
-
return empty;
|
|
3604
|
-
}
|
|
3605
|
-
let lastAssistant = "";
|
|
3606
|
-
let turnOrdinal = 0;
|
|
3607
|
-
let toolCalls = [];
|
|
3608
|
-
let assistantTurnTexts = [];
|
|
3609
|
-
for (const rawLine of lines) {
|
|
3610
|
-
const line = rawLine.trim();
|
|
3611
|
-
if (!line) continue;
|
|
3612
|
-
let obj;
|
|
3613
|
-
try {
|
|
3614
|
-
obj = JSON.parse(line);
|
|
3615
|
-
} catch {
|
|
3616
|
-
continue;
|
|
3617
|
-
}
|
|
3618
|
-
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) continue;
|
|
3619
|
-
const entry = obj;
|
|
3620
|
-
if (entry["isSidechain"] === true) continue;
|
|
3621
|
-
const role = entry["type"];
|
|
3622
|
-
if (role !== "assistant" && role !== "user") continue;
|
|
3623
|
-
const msg = entry["message"];
|
|
3624
|
-
if (typeof msg !== "object" || msg === null || Array.isArray(msg)) continue;
|
|
3625
|
-
const content = msg["content"];
|
|
3626
|
-
if (role === "assistant") {
|
|
3627
|
-
toolCalls.push(...extractToolCalls(content));
|
|
3628
|
-
}
|
|
3629
|
-
const text = _messageText(content);
|
|
3630
|
-
if (text === null) continue;
|
|
3631
|
-
if (role === "assistant") {
|
|
3632
|
-
lastAssistant = text;
|
|
3633
|
-
assistantTurnTexts.push(text);
|
|
3634
|
-
} else if (!isSyntheticPrompt(text)) {
|
|
3635
|
-
turnOrdinal += 1;
|
|
3636
|
-
toolCalls = [];
|
|
3637
|
-
assistantTurnTexts = [];
|
|
3638
|
-
}
|
|
3639
|
-
}
|
|
3640
|
-
return {
|
|
3641
|
-
lastAssistant: lastAssistant.trim(),
|
|
3642
|
-
turnOrdinal,
|
|
3643
|
-
toolCalls,
|
|
3644
|
-
assistantTurnTexts
|
|
3645
|
-
};
|
|
3646
|
-
}
|
|
3647
|
-
function extractToolCalls(content) {
|
|
3648
|
-
if (!Array.isArray(content)) return [];
|
|
3649
|
-
const out = [];
|
|
3650
|
-
for (const blk of content) {
|
|
3651
|
-
if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
|
|
3652
|
-
const b = blk;
|
|
3653
|
-
if (b["type"] !== "tool_use") continue;
|
|
3654
|
-
const name = b["name"];
|
|
3655
|
-
if (typeof name !== "string") continue;
|
|
3656
|
-
const input = b["input"];
|
|
3657
|
-
const call = { name };
|
|
3658
|
-
if (typeof input === "object" && input !== null && !Array.isArray(input)) {
|
|
3659
|
-
const inp = input;
|
|
3660
|
-
const cmd = inp["command"];
|
|
3661
|
-
if (typeof cmd === "string") call.command = cmd;
|
|
3662
|
-
const p = inp["file_path"] ?? inp["path"] ?? inp["notebook_path"];
|
|
3663
|
-
if (typeof p === "string") call.path = p;
|
|
3664
|
-
}
|
|
3665
|
-
out.push(call);
|
|
3666
|
-
}
|
|
3667
|
-
return out;
|
|
3668
|
-
}
|
|
3669
|
-
function _messageText(content) {
|
|
3670
|
-
if (typeof content === "string") return content;
|
|
3671
|
-
if (!Array.isArray(content)) return null;
|
|
3672
|
-
const parts = [];
|
|
3673
|
-
for (const blk of content) {
|
|
3674
|
-
if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
|
|
3675
|
-
const b = blk;
|
|
3676
|
-
if (b["type"] !== "text") continue;
|
|
3677
|
-
const t = b["text"];
|
|
3678
|
-
if (typeof t === "string") parts.push(t);
|
|
3679
|
-
}
|
|
3680
|
-
return parts.length > 0 ? parts.join("\n") : null;
|
|
3681
|
-
}
|
|
3682
3729
|
function str3(v) {
|
|
3683
3730
|
return typeof v === "string" ? v : "";
|
|
3684
3731
|
}
|
|
3685
|
-
function
|
|
3686
|
-
let envelope;
|
|
3687
|
-
let payload;
|
|
3688
|
-
try {
|
|
3689
|
-
[envelope, payload] = unwrap(readHookStdin(), "claude");
|
|
3690
|
-
} catch {
|
|
3691
|
-
return EXIT_ALLOW4;
|
|
3692
|
-
}
|
|
3693
|
-
if (payload["stop_hook_active"] === true) return EXIT_ALLOW4;
|
|
3732
|
+
function assembleDetectorInputs(envelope, payload) {
|
|
3694
3733
|
const workspaceRoot = str3(envelope["workspace_root"]) || process.cwd();
|
|
3695
3734
|
let dispatchOpen = false;
|
|
3696
3735
|
try {
|
|
@@ -3704,18 +3743,32 @@ function main6() {
|
|
|
3704
3743
|
transcriptPath,
|
|
3705
3744
|
{ maxBytes: TRANSCRIPT_READ_MAX_BYTES }
|
|
3706
3745
|
);
|
|
3707
|
-
if (!lastAssistant) return
|
|
3746
|
+
if (!lastAssistant) return null;
|
|
3708
3747
|
const payloadClosing = str3(
|
|
3709
3748
|
payload["last_assistant_message"] ?? payload["lastAssistantMessage"]
|
|
3710
3749
|
);
|
|
3711
3750
|
const closingFromPayload = payloadClosing !== "" && !assistantTurnTexts.includes(payloadClosing);
|
|
3712
3751
|
const turnTexts = closingFromPayload ? [...assistantTurnTexts, payloadClosing] : assistantTurnTexts;
|
|
3713
|
-
const promptId = str3(
|
|
3714
|
-
payload["prompt_id"] ?? payload["promptId"]
|
|
3715
|
-
);
|
|
3752
|
+
const promptId = str3(payload["prompt_id"] ?? payload["promptId"]);
|
|
3716
3753
|
const rawSessionId = str3(envelope["session_id"]) || "";
|
|
3717
3754
|
const sessionKey = deriveSessionKey(rawSessionId || "unknown-session");
|
|
3718
|
-
|
|
3755
|
+
const runState = readTurnRunState(workspaceRoot, rawSessionId);
|
|
3756
|
+
return {
|
|
3757
|
+
workspaceRoot,
|
|
3758
|
+
rawSessionId,
|
|
3759
|
+
sessionKey,
|
|
3760
|
+
turnOrdinal,
|
|
3761
|
+
promptId,
|
|
3762
|
+
lastAssistant,
|
|
3763
|
+
toolCalls,
|
|
3764
|
+
turnTexts,
|
|
3765
|
+
closingFromPayload,
|
|
3766
|
+
dispatchOpen,
|
|
3767
|
+
runState
|
|
3768
|
+
};
|
|
3769
|
+
}
|
|
3770
|
+
function runDetectors(inputs) {
|
|
3771
|
+
const { dispatchOpen, lastAssistant, workspaceRoot, rawSessionId, toolCalls, runState } = inputs;
|
|
3719
3772
|
const findings = [];
|
|
3720
3773
|
for (const f of [
|
|
3721
3774
|
// A and D are the completion-adjacent pair a pending dispatch excuses
|
|
@@ -3723,17 +3776,14 @@ function main6() {
|
|
|
3723
3776
|
// excused by anything about a dispatch and run unchanged.
|
|
3724
3777
|
dispatchOpen ? null : detectPromissory(lastAssistant),
|
|
3725
3778
|
detectLanguage(lastAssistant, readLanguagePin(workspaceRoot, rawSessionId)),
|
|
3726
|
-
detectUnverifiedEdit(toolCalls),
|
|
3727
|
-
// Round 7 § Phase 1 — detector D. It is NOT unconditional
|
|
3728
|
-
//
|
|
3729
|
-
//
|
|
3730
|
-
//
|
|
3731
|
-
//
|
|
3732
|
-
//
|
|
3733
|
-
//
|
|
3734
|
-
// gating is where the comment above says gating belongs — inside the
|
|
3735
|
-
// detector: no CI observed, or a settled read, or no completion claim
|
|
3736
|
-
// ⇒ no finding.
|
|
3779
|
+
detectUnverifiedEdit(toolCalls, runState),
|
|
3780
|
+
// Round 7 § Phase 1 — detector D. It is NOT unconditional: A and D are
|
|
3781
|
+
// both excused by an open dispatch, per the note above. It shipped with
|
|
3782
|
+
// its own settings flag one commit earlier and that flag is gone:
|
|
3783
|
+
// `hooks.turn_end_gate.*` was deleted on 2026-08-12 because a
|
|
3784
|
+
// default-off safety gate is an absent one. Its gating is where the
|
|
3785
|
+
// note above says gating belongs — inside the detector: no CI observed,
|
|
3786
|
+
// or a settled read, or no completion claim ⇒ no finding.
|
|
3737
3787
|
dispatchOpen ? null : detectCompletionClaim(lastAssistant, readCiSettled(workspaceRoot, rawSessionId)),
|
|
3738
3788
|
// Detector E is UNCONDITIONAL, and that is the opposite choice from A
|
|
3739
3789
|
// and D one line above rather than an oversight. An open dispatch
|
|
@@ -3743,24 +3793,71 @@ function main6() {
|
|
|
3743
3793
|
// that produced the measured failure — IS the continuation that drops
|
|
3744
3794
|
// the decision, so narrowing E the same way would silence it in exactly
|
|
3745
3795
|
// the case it exists for.
|
|
3746
|
-
detectDroppedDecision(turnTexts, closingFromPayload),
|
|
3796
|
+
detectDroppedDecision(inputs.turnTexts, inputs.closingFromPayload),
|
|
3747
3797
|
// Detector F takes the OTHER side of that same question, and the two
|
|
3748
3798
|
// sitting adjacent is the clearest place to say why. E fires on a
|
|
3749
3799
|
// question already asked, which an open dispatch cannot excuse. F fires
|
|
3750
3800
|
// on a completion CLAIM, and a turn waiting on a subagent has not
|
|
3751
3801
|
// finished — so its closing is not the claim F is about. Same slot,
|
|
3752
3802
|
// opposite trigger, opposite treatment of `dispatchOpen`.
|
|
3753
|
-
dispatchOpen ? null : detectUntestedChange(lastAssistant, toolCalls)
|
|
3803
|
+
dispatchOpen ? null : detectUntestedChange(lastAssistant, toolCalls, runState)
|
|
3754
3804
|
]) {
|
|
3755
3805
|
if (f) findings.push(f);
|
|
3756
3806
|
}
|
|
3757
|
-
|
|
3807
|
+
return findings;
|
|
3808
|
+
}
|
|
3809
|
+
function recordShadow(inputs, layer) {
|
|
3810
|
+
if (is_replay_mode()) return;
|
|
3811
|
+
try {
|
|
3812
|
+
const findings = runDetectors(inputs);
|
|
3813
|
+
const file = sessionShadowFile(inputs.workspaceRoot, inputs.sessionKey);
|
|
3814
|
+
let prev = null;
|
|
3815
|
+
try {
|
|
3816
|
+
prev = parseShadowRecord(fs8.readFileSync(file, "utf-8"));
|
|
3817
|
+
} catch {
|
|
3818
|
+
}
|
|
3819
|
+
atomic_write_json(
|
|
3820
|
+
file,
|
|
3821
|
+
foldShadow(prev, {
|
|
3822
|
+
detectors: findings.map((f) => f.detector),
|
|
3823
|
+
turnOrdinal: inputs.turnOrdinal,
|
|
3824
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3825
|
+
layer
|
|
3826
|
+
})
|
|
3827
|
+
);
|
|
3828
|
+
} catch {
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
function main6() {
|
|
3832
|
+
let envelope;
|
|
3833
|
+
let payload;
|
|
3834
|
+
try {
|
|
3835
|
+
[envelope, payload] = unwrap(readHookStdin(), "claude");
|
|
3836
|
+
} catch {
|
|
3837
|
+
return EXIT_ALLOW3;
|
|
3838
|
+
}
|
|
3839
|
+
if (payload["stop_hook_active"] === true) {
|
|
3840
|
+
try {
|
|
3841
|
+
const retryInputs = assembleDetectorInputs(envelope, payload);
|
|
3842
|
+
if (retryInputs !== null) recordShadow(retryInputs, "stop_hook_active");
|
|
3843
|
+
} catch {
|
|
3844
|
+
}
|
|
3845
|
+
return EXIT_ALLOW3;
|
|
3846
|
+
}
|
|
3847
|
+
const inputs = assembleDetectorInputs(envelope, payload);
|
|
3848
|
+
if (inputs === null) return EXIT_ALLOW3;
|
|
3849
|
+
if (alreadyRefusedTurn(inputs.workspaceRoot, inputs.sessionKey, inputs.turnOrdinal)) {
|
|
3850
|
+
recordShadow(inputs, "refused_turn");
|
|
3851
|
+
return EXIT_ALLOW3;
|
|
3852
|
+
}
|
|
3853
|
+
const findings = runDetectors(inputs);
|
|
3854
|
+
if (findings.length === 0) return EXIT_ALLOW3;
|
|
3758
3855
|
markRefusedTurn(
|
|
3759
|
-
workspaceRoot,
|
|
3760
|
-
sessionKey,
|
|
3761
|
-
turnOrdinal,
|
|
3856
|
+
inputs.workspaceRoot,
|
|
3857
|
+
inputs.sessionKey,
|
|
3858
|
+
inputs.turnOrdinal,
|
|
3762
3859
|
findings.map((f) => f.detector),
|
|
3763
|
-
promptId
|
|
3860
|
+
inputs.promptId
|
|
3764
3861
|
);
|
|
3765
3862
|
const lines = findings.map(
|
|
3766
3863
|
(f) => ` \xB7 ${f.detector}: ${f.reason}
|
|
@@ -3787,8 +3884,8 @@ function _isCliEntry6() {
|
|
|
3787
3884
|
return true;
|
|
3788
3885
|
}
|
|
3789
3886
|
try {
|
|
3790
|
-
const here =
|
|
3791
|
-
const argv =
|
|
3887
|
+
const here = fs8.realpathSync(fileURLToPath6(import.meta.url));
|
|
3888
|
+
const argv = fs8.realpathSync(path7.resolve(process.argv[1]));
|
|
3792
3889
|
return here === argv;
|
|
3793
3890
|
} catch {
|
|
3794
3891
|
return false;
|
|
@@ -3799,12 +3896,28 @@ if (_isCliEntry6()) {
|
|
|
3799
3896
|
}
|
|
3800
3897
|
|
|
3801
3898
|
// src/scripts/hooks/evidence_independence.ts
|
|
3899
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
3802
3900
|
import crypto3 from "node:crypto";
|
|
3803
|
-
import
|
|
3901
|
+
import fs9 from "node:fs";
|
|
3804
3902
|
import path8 from "node:path";
|
|
3805
3903
|
import { fileURLToPath as fileURLToPath7, pathToFileURL as pathToFileURL7 } from "node:url";
|
|
3806
|
-
|
|
3807
|
-
|
|
3904
|
+
|
|
3905
|
+
// src/scripts/_lib/test_delta.ts
|
|
3906
|
+
function isCodePath(p) {
|
|
3907
|
+
const f = p.replace(/\\/g, "/");
|
|
3908
|
+
if (isTestPath(f)) return false;
|
|
3909
|
+
if (!/\.(ts|tsx|js|mjs|cjs)$/.test(f)) return false;
|
|
3910
|
+
if (f.endsWith(".d.ts")) return false;
|
|
3911
|
+
if (/^(dist|\.augment|\.claude|\.cursor|node_modules)\//.test(f)) return false;
|
|
3912
|
+
return /^(src|scripts)\//.test(f);
|
|
3913
|
+
}
|
|
3914
|
+
function isTestPath(p) {
|
|
3915
|
+
const f = p.replace(/\\/g, "/");
|
|
3916
|
+
if (/^tests\//.test(f)) return true;
|
|
3917
|
+
return /\.(test|spec)\.(ts|tsx|js|mjs|cjs)$/.test(f);
|
|
3918
|
+
}
|
|
3919
|
+
|
|
3920
|
+
// src/scripts/hooks/evidence_independence.ts
|
|
3808
3921
|
var STATE_FILE3 = path8.join("agents", "state", "evidence-dispatch.json");
|
|
3809
3922
|
function _isObject5(v) {
|
|
3810
3923
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
@@ -3873,7 +3986,7 @@ function extractDispatch(envelope) {
|
|
|
3873
3986
|
}
|
|
3874
3987
|
function _ledgerStamp(consumer_root) {
|
|
3875
3988
|
try {
|
|
3876
|
-
const raw =
|
|
3989
|
+
const raw = fs9.readFileSync(
|
|
3877
3990
|
path8.join(consumer_root, "agents", "state", "git-authorization.json"),
|
|
3878
3991
|
"utf8"
|
|
3879
3992
|
);
|
|
@@ -3885,7 +3998,7 @@ function _ledgerStamp(consumer_root) {
|
|
|
3885
3998
|
}
|
|
3886
3999
|
function _load(target) {
|
|
3887
4000
|
try {
|
|
3888
|
-
const decoded = JSON.parse(
|
|
4001
|
+
const decoded = JSON.parse(fs9.readFileSync(target, "utf8"));
|
|
3889
4002
|
if (_isObject5(decoded) && Array.isArray(decoded["evaluations"])) {
|
|
3890
4003
|
return decoded;
|
|
3891
4004
|
}
|
|
@@ -3896,12 +4009,12 @@ function _load(target) {
|
|
|
3896
4009
|
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.";
|
|
3897
4010
|
function decide(tool, prompt, priorEvaluations) {
|
|
3898
4011
|
if (!isDispatchTool(tool) || !isEvaluationPrompt(prompt)) {
|
|
3899
|
-
return { exit:
|
|
4012
|
+
return { exit: EXIT_ALLOW3, stdout: "", stderr: "", evaluations: priorEvaluations };
|
|
3900
4013
|
}
|
|
3901
4014
|
const preloaded = preloadedVerdict(prompt);
|
|
3902
4015
|
if (preloaded !== null) {
|
|
3903
4016
|
return {
|
|
3904
|
-
exit:
|
|
4017
|
+
exit: EXIT_BLOCK,
|
|
3905
4018
|
stdout: "",
|
|
3906
4019
|
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.
|
|
3907
4020
|
`,
|
|
@@ -3909,11 +4022,11 @@ function decide(tool, prompt, priorEvaluations) {
|
|
|
3909
4022
|
};
|
|
3910
4023
|
}
|
|
3911
4024
|
if (!isSelfScoped(prompt)) {
|
|
3912
|
-
return { exit:
|
|
4025
|
+
return { exit: EXIT_ALLOW3, stdout: "", stderr: "", evaluations: priorEvaluations };
|
|
3913
4026
|
}
|
|
3914
4027
|
if (priorEvaluations >= 1) {
|
|
3915
4028
|
return {
|
|
3916
|
-
exit:
|
|
4029
|
+
exit: EXIT_ALLOW3,
|
|
3917
4030
|
stdout: `${JSON.stringify({
|
|
3918
4031
|
decision: "warn",
|
|
3919
4032
|
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.`
|
|
@@ -3924,13 +4037,39 @@ function decide(tool, prompt, priorEvaluations) {
|
|
|
3924
4037
|
};
|
|
3925
4038
|
}
|
|
3926
4039
|
return {
|
|
3927
|
-
exit:
|
|
4040
|
+
exit: EXIT_ALLOW3,
|
|
3928
4041
|
stdout: `${JSON.stringify({ decision: "warn", reason: WARN_TEXT })}
|
|
3929
4042
|
`,
|
|
3930
4043
|
stderr: "",
|
|
3931
4044
|
evaluations: priorEvaluations + 1
|
|
3932
4045
|
};
|
|
3933
4046
|
}
|
|
4047
|
+
function sameSessionTestFlag(command) {
|
|
4048
|
+
if (command === null || !/\bgit\b[^|;&]*\bcommit\b/.test(command)) return null;
|
|
4049
|
+
return SAME_SESSION_TEXT;
|
|
4050
|
+
}
|
|
4051
|
+
var SAME_SESSION_TEXT = "This commit stages both a test and code it could cover \u2014 the shape L0 takes in a diff, where the implementer authored the evaluator. This check reads the staged set only: it cannot tell who wrote the test, so it is a prompt to STATE the level, not a finding that you are at L0. L0 is a permitted FALLBACK, not the default: where a second session, a second model or a second provider was available, the test should have come from one of them. Say which in the commit or the PR body \u2014 a diff looks identical either way. See evaluator-independence \xA7 Tests are evaluators.";
|
|
4052
|
+
function touchesTestAndCode(paths) {
|
|
4053
|
+
return paths.some(isTestPath) && paths.some(isCodePath);
|
|
4054
|
+
}
|
|
4055
|
+
function _commandOf(envelope) {
|
|
4056
|
+
const payload = _isObject5(envelope["payload"]) ? envelope["payload"] : envelope;
|
|
4057
|
+
const input = _isObject5(payload["tool_input"]) ? payload["tool_input"] : payload;
|
|
4058
|
+
for (const key of ["command", "cmd", "shell_command"]) {
|
|
4059
|
+
const v = input[key];
|
|
4060
|
+
if (typeof v === "string" && v !== "") return v;
|
|
4061
|
+
}
|
|
4062
|
+
return null;
|
|
4063
|
+
}
|
|
4064
|
+
function _stagedPaths(consumer_root) {
|
|
4065
|
+
const r = spawnSync3("git", ["diff", "--cached", "--name-only"], {
|
|
4066
|
+
cwd: consumer_root,
|
|
4067
|
+
encoding: "utf8"
|
|
4068
|
+
});
|
|
4069
|
+
if (r.status !== 0) return null;
|
|
4070
|
+
const out = r.stdout.split("\n").map((s) => s.trim()).filter((s) => s !== "");
|
|
4071
|
+
return out.length === 0 ? null : out;
|
|
4072
|
+
}
|
|
3934
4073
|
function run3(stdin_text, options) {
|
|
3935
4074
|
let envelope = {};
|
|
3936
4075
|
if (stdin_text.trim()) {
|
|
@@ -3940,7 +4079,7 @@ function run3(stdin_text, options) {
|
|
|
3940
4079
|
envelope = decoded;
|
|
3941
4080
|
}
|
|
3942
4081
|
} catch {
|
|
3943
|
-
return
|
|
4082
|
+
return EXIT_ALLOW3;
|
|
3944
4083
|
}
|
|
3945
4084
|
}
|
|
3946
4085
|
const target = path8.join(options.consumer_root, STATE_FILE3);
|
|
@@ -3953,6 +4092,15 @@ function run3(stdin_text, options) {
|
|
|
3953
4092
|
state.evaluations = [];
|
|
3954
4093
|
}
|
|
3955
4094
|
const [tool, prompt] = extractDispatch(envelope);
|
|
4095
|
+
const flag = sameSessionTestFlag(_commandOf(envelope));
|
|
4096
|
+
if (flag !== null) {
|
|
4097
|
+
const staged = _stagedPaths(options.consumer_root);
|
|
4098
|
+
if (staged !== null && touchesTestAndCode(staged)) {
|
|
4099
|
+
process.stdout.write(`${JSON.stringify({ decision: "warn", reason: flag })}
|
|
4100
|
+
`);
|
|
4101
|
+
return EXIT_ALLOW3;
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
3956
4104
|
const decision = decide(tool, prompt, state.evaluations.length);
|
|
3957
4105
|
if (decision.evaluations > state.evaluations.length) {
|
|
3958
4106
|
state.evaluations.push({
|
|
@@ -4007,8 +4155,8 @@ function _isCliEntry7() {
|
|
|
4007
4155
|
return true;
|
|
4008
4156
|
}
|
|
4009
4157
|
try {
|
|
4010
|
-
const here =
|
|
4011
|
-
const argv =
|
|
4158
|
+
const here = fs9.realpathSync(fileURLToPath7(import.meta.url));
|
|
4159
|
+
const argv = fs9.realpathSync(path8.resolve(process.argv[1]));
|
|
4012
4160
|
return here === argv;
|
|
4013
4161
|
} catch {
|
|
4014
4162
|
return false;
|
|
@@ -4019,7 +4167,7 @@ if (_isCliEntry7()) {
|
|
|
4019
4167
|
}
|
|
4020
4168
|
|
|
4021
4169
|
// src/scripts/hooks/delegation_nudge_hook.ts
|
|
4022
|
-
import
|
|
4170
|
+
import fs10 from "node:fs";
|
|
4023
4171
|
import path9 from "node:path";
|
|
4024
4172
|
import { fileURLToPath as fileURLToPath8, pathToFileURL as pathToFileURL8 } from "node:url";
|
|
4025
4173
|
|
|
@@ -4301,8 +4449,6 @@ function resolveSubagentRouting(inp) {
|
|
|
4301
4449
|
}
|
|
4302
4450
|
|
|
4303
4451
|
// src/scripts/hooks/delegation_nudge_hook.ts
|
|
4304
|
-
var EXIT_ALLOW6 = 0;
|
|
4305
|
-
var EXIT_WARN3 = 2;
|
|
4306
4452
|
function _isObject6(v) {
|
|
4307
4453
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
4308
4454
|
}
|
|
@@ -4482,28 +4628,28 @@ function main8() {
|
|
|
4482
4628
|
const raw = readHookStdin();
|
|
4483
4629
|
envelope = raw.trim() ? JSON.parse(raw) : {};
|
|
4484
4630
|
} catch {
|
|
4485
|
-
return
|
|
4631
|
+
return EXIT_ALLOW3;
|
|
4486
4632
|
}
|
|
4487
4633
|
const env = _isObject6(envelope) ? envelope : {};
|
|
4488
4634
|
const event = env["event"];
|
|
4489
4635
|
const slot = typeof event === "string" && event !== "" ? event : "user_prompt_submit";
|
|
4490
4636
|
if (slot !== "user_prompt_submit") {
|
|
4491
|
-
return
|
|
4637
|
+
return EXIT_ALLOW3;
|
|
4492
4638
|
}
|
|
4493
4639
|
const payload = _isObject6(env["payload"]) ? env["payload"] : env;
|
|
4494
4640
|
const prompt = _extractPrompt2(payload);
|
|
4495
4641
|
if (!prompt) {
|
|
4496
|
-
return
|
|
4642
|
+
return EXIT_ALLOW3;
|
|
4497
4643
|
}
|
|
4498
4644
|
if (isSyntheticPrompt(prompt)) {
|
|
4499
|
-
return
|
|
4645
|
+
return EXIT_ALLOW3;
|
|
4500
4646
|
}
|
|
4501
4647
|
const root = _workspaceRoot(env);
|
|
4502
4648
|
const platform = env["platform"];
|
|
4503
4649
|
const hostId = typeof platform === "string" && platform ? platform : null;
|
|
4504
4650
|
const result = classifyPrompt(prompt, root, hostId);
|
|
4505
4651
|
if (!result) {
|
|
4506
|
-
return
|
|
4652
|
+
return EXIT_ALLOW3;
|
|
4507
4653
|
}
|
|
4508
4654
|
process.stdout.write(
|
|
4509
4655
|
`${JSON.stringify({
|
|
@@ -4513,7 +4659,7 @@ function main8() {
|
|
|
4513
4659
|
})}
|
|
4514
4660
|
`
|
|
4515
4661
|
);
|
|
4516
|
-
return
|
|
4662
|
+
return EXIT_WARN2;
|
|
4517
4663
|
}
|
|
4518
4664
|
function _isCliEntry8() {
|
|
4519
4665
|
if (true) {
|
|
@@ -4527,8 +4673,8 @@ function _isCliEntry8() {
|
|
|
4527
4673
|
return true;
|
|
4528
4674
|
}
|
|
4529
4675
|
try {
|
|
4530
|
-
const here =
|
|
4531
|
-
const argv =
|
|
4676
|
+
const here = fs10.realpathSync(fileURLToPath8(import.meta.url));
|
|
4677
|
+
const argv = fs10.realpathSync(path9.resolve(process.argv[1]));
|
|
4532
4678
|
return here === argv;
|
|
4533
4679
|
} catch {
|
|
4534
4680
|
return false;
|
|
@@ -4925,13 +5071,13 @@ function measureDelivered(projectRulesDir, globalRulesDir) {
|
|
|
4925
5071
|
return {
|
|
4926
5072
|
project: {
|
|
4927
5073
|
dir: projectRulesDir,
|
|
4928
|
-
present:
|
|
5074
|
+
present: fs11.existsSync(projectRulesDir),
|
|
4929
5075
|
files: p.files,
|
|
4930
5076
|
tokens: _tokens(p.chars)
|
|
4931
5077
|
},
|
|
4932
5078
|
global: {
|
|
4933
5079
|
dir: globalRulesDir,
|
|
4934
|
-
present:
|
|
5080
|
+
present: fs11.existsSync(globalRulesDir),
|
|
4935
5081
|
files: g.files,
|
|
4936
5082
|
tokens: _tokens(g.chars)
|
|
4937
5083
|
},
|
|
@@ -4942,10 +5088,10 @@ function storeKey(store) {
|
|
|
4942
5088
|
return createHash3("sha256").update(path10.resolve(store)).digest("hex").slice(0, 12);
|
|
4943
5089
|
}
|
|
4944
5090
|
function scanStore(store, limit) {
|
|
4945
|
-
const files =
|
|
5091
|
+
const files = fs11.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, m: fs11.statSync(path10.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.f);
|
|
4946
5092
|
const per_session = [];
|
|
4947
5093
|
for (const f of files) {
|
|
4948
|
-
const lines =
|
|
5094
|
+
const lines = fs11.readFileSync(path10.join(store, f), "utf8").split("\n");
|
|
4949
5095
|
const r = scanSession(f.replace(/\.jsonl$/, "").slice(0, 8), lines);
|
|
4950
5096
|
if (r.assistant_turns > 0) {
|
|
4951
5097
|
per_session.push(r);
|
|
@@ -5006,8 +5152,8 @@ function scanStore(store, limit) {
|
|
|
5006
5152
|
};
|
|
5007
5153
|
}
|
|
5008
5154
|
function recordRate(file, scanned_at, rate) {
|
|
5009
|
-
|
|
5010
|
-
|
|
5155
|
+
fs11.mkdirSync(path10.dirname(file), { recursive: true });
|
|
5156
|
+
fs11.appendFileSync(file, `${JSON.stringify({ measured_at: scanned_at, ...rate })}
|
|
5011
5157
|
`, "utf8");
|
|
5012
5158
|
}
|
|
5013
5159
|
function render(report) {
|
|
@@ -5138,14 +5284,14 @@ function main9(argv) {
|
|
|
5138
5284
|
return 2;
|
|
5139
5285
|
}
|
|
5140
5286
|
const resolved = store ?? defaultStore(process.cwd());
|
|
5141
|
-
if (!
|
|
5287
|
+
if (!fs11.existsSync(resolved)) {
|
|
5142
5288
|
process.stderr.write(`conformance:behavior: no transcript store at ${resolved}
|
|
5143
5289
|
`);
|
|
5144
5290
|
return 1;
|
|
5145
5291
|
}
|
|
5146
5292
|
const report = scanStore(resolved, Number.isFinite(limit) && limit > 0 ? limit : 30);
|
|
5147
5293
|
if (out) {
|
|
5148
|
-
|
|
5294
|
+
fs11.writeFileSync(out, `${JSON.stringify(report, null, 2)}
|
|
5149
5295
|
`, "utf8");
|
|
5150
5296
|
}
|
|
5151
5297
|
if (record) {
|
|
@@ -5153,7 +5299,7 @@ function main9(argv) {
|
|
|
5153
5299
|
}
|
|
5154
5300
|
if (why !== null) {
|
|
5155
5301
|
const id = why;
|
|
5156
|
-
|
|
5302
|
+
fs11.writeSync(
|
|
5157
5303
|
1,
|
|
5158
5304
|
json ? `${JSON.stringify(
|
|
5159
5305
|
{
|
|
@@ -5173,7 +5319,7 @@ function main9(argv) {
|
|
|
5173
5319
|
);
|
|
5174
5320
|
return 0;
|
|
5175
5321
|
}
|
|
5176
|
-
|
|
5322
|
+
fs11.writeSync(1, json ? `${JSON.stringify(report, null, 2)}
|
|
5177
5323
|
` : `${render(report)}
|
|
5178
5324
|
`);
|
|
5179
5325
|
return 0;
|
|
@@ -5190,8 +5336,8 @@ function _isCliEntry9() {
|
|
|
5190
5336
|
return true;
|
|
5191
5337
|
}
|
|
5192
5338
|
try {
|
|
5193
|
-
const here =
|
|
5194
|
-
const argv =
|
|
5339
|
+
const here = fs11.realpathSync(fileURLToPath9(import.meta.url));
|
|
5340
|
+
const argv = fs11.realpathSync(path10.resolve(process.argv[1]));
|
|
5195
5341
|
return here === argv;
|
|
5196
5342
|
} catch {
|
|
5197
5343
|
return false;
|
|
@@ -5202,7 +5348,7 @@ if (_isCliEntry9()) {
|
|
|
5202
5348
|
}
|
|
5203
5349
|
|
|
5204
5350
|
// src/scripts/report_carrier_divergence.ts
|
|
5205
|
-
import * as
|
|
5351
|
+
import * as fs12 from "node:fs";
|
|
5206
5352
|
import * as os3 from "node:os";
|
|
5207
5353
|
import * as path11 from "node:path";
|
|
5208
5354
|
import process2 from "node:process";
|
|
@@ -5214,7 +5360,7 @@ var PROJECTION_SOURCE = path11.join("dist", "agent-src", "rules");
|
|
|
5214
5360
|
var GLOBAL_RULES = path11.join(".claude", "rules");
|
|
5215
5361
|
function _listRules(dir) {
|
|
5216
5362
|
try {
|
|
5217
|
-
return
|
|
5363
|
+
return fs12.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
|
|
5218
5364
|
} catch {
|
|
5219
5365
|
return [];
|
|
5220
5366
|
}
|
|
@@ -5222,7 +5368,7 @@ function _listRules(dir) {
|
|
|
5222
5368
|
function _ruleType(file) {
|
|
5223
5369
|
let text;
|
|
5224
5370
|
try {
|
|
5225
|
-
text =
|
|
5371
|
+
text = fs12.readFileSync(file, "utf-8");
|
|
5226
5372
|
} catch {
|
|
5227
5373
|
return "";
|
|
5228
5374
|
}
|
|
@@ -5233,8 +5379,8 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
|
|
|
5233
5379
|
const out = {
|
|
5234
5380
|
projectDir,
|
|
5235
5381
|
globalDir,
|
|
5236
|
-
globalPresent:
|
|
5237
|
-
projectPresent:
|
|
5382
|
+
globalPresent: fs12.existsSync(globalDir),
|
|
5383
|
+
projectPresent: fs12.existsSync(projectDir),
|
|
5238
5384
|
shared: 0,
|
|
5239
5385
|
identical: [],
|
|
5240
5386
|
provenanceOnly: [],
|
|
@@ -5267,8 +5413,8 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
|
|
|
5267
5413
|
let projectCopy;
|
|
5268
5414
|
let globalCopy;
|
|
5269
5415
|
try {
|
|
5270
|
-
projectCopy =
|
|
5271
|
-
globalCopy =
|
|
5416
|
+
projectCopy = fs12.readFileSync(path11.join(projectDir, rule));
|
|
5417
|
+
globalCopy = fs12.readFileSync(path11.join(globalDir, rule));
|
|
5272
5418
|
verdict = comparePair(projectCopy, globalCopy);
|
|
5273
5419
|
} catch {
|
|
5274
5420
|
out.shared -= 1;
|
|
@@ -5455,7 +5601,7 @@ function _isCliEntry10() {
|
|
|
5455
5601
|
if (process2.argv[1] === void 0) return false;
|
|
5456
5602
|
if (import.meta.url === pathToFileURL10(path11.resolve(process2.argv[1])).href) return true;
|
|
5457
5603
|
try {
|
|
5458
|
-
return
|
|
5604
|
+
return fs12.realpathSync(_HERE2) === fs12.realpathSync(path11.resolve(process2.argv[1]));
|
|
5459
5605
|
} catch {
|
|
5460
5606
|
return false;
|
|
5461
5607
|
}
|
|
@@ -5465,7 +5611,7 @@ if (_isCliEntry10()) {
|
|
|
5465
5611
|
}
|
|
5466
5612
|
|
|
5467
5613
|
// src/scripts/report_skill_activation.ts
|
|
5468
|
-
import * as
|
|
5614
|
+
import * as fs13 from "node:fs";
|
|
5469
5615
|
import * as os4 from "node:os";
|
|
5470
5616
|
import * as path12 from "node:path";
|
|
5471
5617
|
import { fileURLToPath as fileURLToPath11, pathToFileURL as pathToFileURL11 } from "node:url";
|
|
@@ -5481,25 +5627,25 @@ function censusSkills(root) {
|
|
|
5481
5627
|
withTriggerCorpus: [],
|
|
5482
5628
|
withDeterministicObligation: []
|
|
5483
5629
|
};
|
|
5484
|
-
if (!
|
|
5630
|
+
if (!fs13.existsSync(root)) {
|
|
5485
5631
|
return out;
|
|
5486
5632
|
}
|
|
5487
|
-
for (const entry of
|
|
5633
|
+
for (const entry of fs13.readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
5488
5634
|
if (!entry.isDirectory()) {
|
|
5489
5635
|
continue;
|
|
5490
5636
|
}
|
|
5491
5637
|
const file = path12.join(root, entry.name, "SKILL.md");
|
|
5492
|
-
if (!
|
|
5638
|
+
if (!fs13.existsSync(file)) {
|
|
5493
5639
|
continue;
|
|
5494
5640
|
}
|
|
5495
5641
|
out.total += 1;
|
|
5496
|
-
const text =
|
|
5642
|
+
const text = fs13.readFileSync(file, "utf8");
|
|
5497
5643
|
const fmEnd = text.startsWith("---") ? text.indexOf("\n---", 3) : -1;
|
|
5498
5644
|
const fm = fmEnd === -1 ? "" : text.slice(0, fmEnd);
|
|
5499
5645
|
if (TRIGGER_KEYS.some((k) => new RegExp(`^${k}`, "m").test(fm))) {
|
|
5500
5646
|
out.withTriggerKey.push(entry.name);
|
|
5501
5647
|
}
|
|
5502
|
-
if (
|
|
5648
|
+
if (fs13.existsSync(path12.join(root, entry.name, "evals", "triggers.json"))) {
|
|
5503
5649
|
out.withTriggerCorpus.push(entry.name);
|
|
5504
5650
|
}
|
|
5505
5651
|
const body = fmEnd === -1 ? text : text.slice(fmEnd + 4);
|
|
@@ -5514,13 +5660,13 @@ function defaultStore2(cwd) {
|
|
|
5514
5660
|
}
|
|
5515
5661
|
function measureUsage(store, limit) {
|
|
5516
5662
|
const rep = { store, sessions: 0, assistantTurns: 0, invocations: 0, bySkill: {} };
|
|
5517
|
-
if (!
|
|
5663
|
+
if (!fs13.existsSync(store)) {
|
|
5518
5664
|
return rep;
|
|
5519
5665
|
}
|
|
5520
|
-
const files =
|
|
5666
|
+
const files = fs13.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => path12.join(store, f)).sort((a, b) => fs13.statSync(b).mtimeMs - fs13.statSync(a).mtimeMs).slice(0, limit);
|
|
5521
5667
|
rep.sessions = files.length;
|
|
5522
5668
|
for (const file of files) {
|
|
5523
|
-
for (const line of
|
|
5669
|
+
for (const line of fs13.readFileSync(file, "utf8").split("\n")) {
|
|
5524
5670
|
if (!line.trim()) {
|
|
5525
5671
|
continue;
|
|
5526
5672
|
}
|
|
@@ -5598,10 +5744,10 @@ function buildRecord(census, usage, today) {
|
|
|
5598
5744
|
}
|
|
5599
5745
|
function readRecord(repoRoot) {
|
|
5600
5746
|
const file = path12.join(repoRoot, RECORD_PATH);
|
|
5601
|
-
if (!
|
|
5747
|
+
if (!fs13.existsSync(file)) {
|
|
5602
5748
|
return null;
|
|
5603
5749
|
}
|
|
5604
|
-
return JSON.parse(
|
|
5750
|
+
return JSON.parse(fs13.readFileSync(file, "utf8"));
|
|
5605
5751
|
}
|
|
5606
5752
|
function render3(census, usage, published = null) {
|
|
5607
5753
|
const lines = [];
|
|
@@ -5762,8 +5908,8 @@ function main11(argv) {
|
|
|
5762
5908
|
}
|
|
5763
5909
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5764
5910
|
const file = path12.join(root, RECORD_PATH);
|
|
5765
|
-
|
|
5766
|
-
|
|
5911
|
+
fs13.mkdirSync(path12.dirname(file), { recursive: true });
|
|
5912
|
+
fs13.writeFileSync(file, `${JSON.stringify(buildRecord(census, usage, today), null, 2)}
|
|
5767
5913
|
`);
|
|
5768
5914
|
process.stdout.write(`
|
|
5769
5915
|
wrote ${RECORD_PATH} (measured ${today})
|
|
@@ -5775,7 +5921,7 @@ if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL11(path12.res
|
|
|
5775
5921
|
}
|
|
5776
5922
|
|
|
5777
5923
|
// src/scripts/report_skill_obligation_violations.ts
|
|
5778
|
-
import * as
|
|
5924
|
+
import * as fs14 from "node:fs";
|
|
5779
5925
|
import * as os5 from "node:os";
|
|
5780
5926
|
import * as path13 from "node:path";
|
|
5781
5927
|
import process3 from "node:process";
|
|
@@ -5833,7 +5979,7 @@ function extractObligations(repoRoot) {
|
|
|
5833
5979
|
for (const skill of census.withDeterministicObligation) {
|
|
5834
5980
|
let text;
|
|
5835
5981
|
try {
|
|
5836
|
-
text =
|
|
5982
|
+
text = fs14.readFileSync(path13.join(skillsRoot, skill, "SKILL.md"), "utf-8");
|
|
5837
5983
|
} catch {
|
|
5838
5984
|
continue;
|
|
5839
5985
|
}
|
|
@@ -5962,13 +6108,13 @@ function scanStore2(repoRoot, store, limit) {
|
|
|
5962
6108
|
};
|
|
5963
6109
|
let files;
|
|
5964
6110
|
try {
|
|
5965
|
-
files =
|
|
6111
|
+
files = fs14.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ p: path13.join(store, f), m: fs14.statSync(path13.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.p);
|
|
5966
6112
|
} catch {
|
|
5967
6113
|
return report;
|
|
5968
6114
|
}
|
|
5969
6115
|
report.sessions = files.length;
|
|
5970
6116
|
for (const file of files) {
|
|
5971
|
-
const lines =
|
|
6117
|
+
const lines = fs14.readFileSync(file, "utf-8").split("\n");
|
|
5972
6118
|
const id = path13.basename(file).replace(/\.jsonl$/, "").slice(0, 8);
|
|
5973
6119
|
const entries = _parseSession(lines);
|
|
5974
6120
|
if (loadedSkillsFrom(entries).size > 0) report.sessionsWithASkill += 1;
|
|
@@ -6096,7 +6242,7 @@ function _isCliEntry11() {
|
|
|
6096
6242
|
if (process3.argv[1] === void 0) return false;
|
|
6097
6243
|
if (import.meta.url === pathToFileURL12(path13.resolve(process3.argv[1])).href) return true;
|
|
6098
6244
|
try {
|
|
6099
|
-
return
|
|
6245
|
+
return fs14.realpathSync(_HERE4) === fs14.realpathSync(path13.resolve(process3.argv[1]));
|
|
6100
6246
|
} catch {
|
|
6101
6247
|
return false;
|
|
6102
6248
|
}
|
|
@@ -6120,7 +6266,7 @@ function buildFunnel(opts = {}) {
|
|
|
6120
6266
|
divergence: compareCarriers(projectRulesDir, globalRulesDir, sourceDir),
|
|
6121
6267
|
skills: censusSkills(path14.join(repoRoot, SKILLS_ROOT)),
|
|
6122
6268
|
usage: measureUsage(store, limit),
|
|
6123
|
-
storePresent:
|
|
6269
|
+
storePresent: fs15.existsSync(store),
|
|
6124
6270
|
sk2: scanStore2(repoRoot, store, limit)
|
|
6125
6271
|
};
|
|
6126
6272
|
}
|
|
@@ -6295,7 +6441,7 @@ function _isCliEntry12() {
|
|
|
6295
6441
|
if (process4.argv[1] === void 0) return false;
|
|
6296
6442
|
if (import.meta.url === pathToFileURL13(path14.resolve(process4.argv[1])).href) return true;
|
|
6297
6443
|
try {
|
|
6298
|
-
return
|
|
6444
|
+
return fs15.realpathSync(_HERE5) === fs15.realpathSync(path14.resolve(process4.argv[1]));
|
|
6299
6445
|
} catch {
|
|
6300
6446
|
return false;
|
|
6301
6447
|
}
|
|
@@ -6315,6 +6461,10 @@ var CONFORMANCE_CHECK_IDS = [
|
|
|
6315
6461
|
"host-manifest"
|
|
6316
6462
|
];
|
|
6317
6463
|
var THIN_STUB_MARKER = "Routed rule \u2014 load the body on trigger-match";
|
|
6464
|
+
var CONFORMANCE_STATUS_SYMBOLS = {
|
|
6465
|
+
...STATUS_SYMBOLS,
|
|
6466
|
+
unknown: "\u2754"
|
|
6467
|
+
};
|
|
6318
6468
|
function print(s = "") {
|
|
6319
6469
|
process5.stdout.write(`${s}
|
|
6320
6470
|
`);
|
|
@@ -6337,21 +6487,24 @@ function installLogPath() {
|
|
|
6337
6487
|
}
|
|
6338
6488
|
return path15.join(os7.homedir(), ".event4u", "agent-config", "install-log.jsonl");
|
|
6339
6489
|
}
|
|
6340
|
-
function
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
message: "no install transaction log \u2014 nothing to recover",
|
|
6346
|
-
remedy: ""
|
|
6347
|
-
};
|
|
6348
|
-
}
|
|
6349
|
-
const entries = readRecentEntries(logPath);
|
|
6490
|
+
function _installLogExpected(projectRoot) {
|
|
6491
|
+
return fs16.existsSync(manifest_path(projectRoot));
|
|
6492
|
+
}
|
|
6493
|
+
function _check_txlog_clean(logPath = installLogPath(), projectRoot = null) {
|
|
6494
|
+
const entries = fs16.existsSync(logPath) ? readRecentEntries(logPath) : [];
|
|
6350
6495
|
if (entries.length === 0) {
|
|
6496
|
+
if (projectRoot !== null && _installLogExpected(projectRoot)) {
|
|
6497
|
+
return {
|
|
6498
|
+
id: "txlog-clean",
|
|
6499
|
+
status: "unknown",
|
|
6500
|
+
message: "install manifest present, no usable install transaction log \u2014 nothing recorded this install, so the last run cannot be confirmed complete",
|
|
6501
|
+
remedy: "no install path writes this log today; run `agent-config doctor` for the drift answer this check cannot give"
|
|
6502
|
+
};
|
|
6503
|
+
}
|
|
6351
6504
|
return {
|
|
6352
6505
|
id: "txlog-clean",
|
|
6353
6506
|
status: "ok",
|
|
6354
|
-
message: "install transaction log
|
|
6507
|
+
message: "no usable install transaction log and no install manifest \u2014 nothing was installed here",
|
|
6355
6508
|
remedy: ""
|
|
6356
6509
|
};
|
|
6357
6510
|
}
|
|
@@ -6361,7 +6514,7 @@ function _check_txlog_clean(logPath = installLogPath()) {
|
|
|
6361
6514
|
id: "txlog-clean",
|
|
6362
6515
|
status: "fail",
|
|
6363
6516
|
message: `install log tail is an abandoned run (abort at ${last.ts}${last.note ? `: ${last.note}` : ""})`,
|
|
6364
|
-
remedy: "re-run `agent-config init`
|
|
6517
|
+
remedy: "re-run `agent-config init` \u2014 it re-applies the plan over the partial tail; the aborted run's writes stay on disk and are replaced only where the plan covers them"
|
|
6365
6518
|
};
|
|
6366
6519
|
}
|
|
6367
6520
|
return {
|
|
@@ -6376,7 +6529,7 @@ function routeTargetPaths(target) {
|
|
|
6376
6529
|
}
|
|
6377
6530
|
function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
6378
6531
|
const routerPath = path15.join(packageRoot, "dist", "router.json");
|
|
6379
|
-
if (!
|
|
6532
|
+
if (!fs16.existsSync(routerPath)) {
|
|
6380
6533
|
return {
|
|
6381
6534
|
id: "router-pointers",
|
|
6382
6535
|
status: "fail",
|
|
@@ -6386,7 +6539,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
6386
6539
|
}
|
|
6387
6540
|
let router;
|
|
6388
6541
|
try {
|
|
6389
|
-
router = JSON.parse(
|
|
6542
|
+
router = JSON.parse(fs16.readFileSync(routerPath, "utf8"));
|
|
6390
6543
|
} catch (exc) {
|
|
6391
6544
|
return {
|
|
6392
6545
|
id: "router-pointers",
|
|
@@ -6401,7 +6554,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
6401
6554
|
let targets = 0;
|
|
6402
6555
|
for (const kernelId of router["kernel"] ?? []) {
|
|
6403
6556
|
ids += 1;
|
|
6404
|
-
if (!
|
|
6557
|
+
if (!fs16.existsSync(path15.join(rulesDir, `${kernelId}.md`))) {
|
|
6405
6558
|
broken.push(`kernel:${kernelId} \u2192 rules/${kernelId}.md missing`);
|
|
6406
6559
|
}
|
|
6407
6560
|
}
|
|
@@ -6409,7 +6562,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
6409
6562
|
for (const entry of router[tier] ?? []) {
|
|
6410
6563
|
const ruleId = String(entry["id"] ?? "");
|
|
6411
6564
|
ids += 1;
|
|
6412
|
-
if (!
|
|
6565
|
+
if (!fs16.existsSync(path15.join(rulesDir, `${ruleId}.md`))) {
|
|
6413
6566
|
broken.push(`${tier}:${ruleId} \u2192 rules/${ruleId}.md missing`);
|
|
6414
6567
|
}
|
|
6415
6568
|
for (const target of entry["routes_to"] ?? []) {
|
|
@@ -6419,7 +6572,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
|
|
|
6419
6572
|
broken.push(`${ruleId} \u2192 unparseable routes_to ${JSON.stringify(target)}`);
|
|
6420
6573
|
continue;
|
|
6421
6574
|
}
|
|
6422
|
-
if (!candidates.some((rel) =>
|
|
6575
|
+
if (!candidates.some((rel) => fs16.existsSync(path15.join(packageRoot, rel)))) {
|
|
6423
6576
|
broken.push(`${ruleId} \u2192 ${target} (${candidates[0]} missing)`);
|
|
6424
6577
|
}
|
|
6425
6578
|
}
|
|
@@ -6453,7 +6606,7 @@ function detectSmokePlatform(projectRoot) {
|
|
|
6453
6606
|
function dispatcherRunner(packageRoot) {
|
|
6454
6607
|
const localTsx = path15.join(packageRoot, "node_modules", ".bin", "tsx");
|
|
6455
6608
|
const script = path15.join(packageRoot, "src", "scripts", "hooks", "dispatch_hook.ts");
|
|
6456
|
-
if (
|
|
6609
|
+
if (fs16.existsSync(localTsx)) {
|
|
6457
6610
|
return [localTsx, script];
|
|
6458
6611
|
}
|
|
6459
6612
|
return ["npx", "tsx", script];
|
|
@@ -6470,7 +6623,7 @@ function _check_hook_dispatcher(projectRoot, packageRoot = PACKAGE_ROOT, opts =
|
|
|
6470
6623
|
...event === "stop" ? { stop_reason: "conformance-smoke" } : { source: "startup" }
|
|
6471
6624
|
});
|
|
6472
6625
|
const argv = [...runner, "--platform", platform, "--event", event];
|
|
6473
|
-
const proc =
|
|
6626
|
+
const proc = spawnSync4(argv[0], argv.slice(1), {
|
|
6474
6627
|
input: payload,
|
|
6475
6628
|
cwd: projectRoot,
|
|
6476
6629
|
encoding: "utf8",
|
|
@@ -6504,11 +6657,11 @@ function _check_hook_dispatcher(projectRoot, packageRoot = PACKAGE_ROOT, opts =
|
|
|
6504
6657
|
}
|
|
6505
6658
|
function readSettingsMode(projectRoot) {
|
|
6506
6659
|
const settingsPath = path15.join(projectRoot, ".agent-settings.yml");
|
|
6507
|
-
if (!
|
|
6660
|
+
if (!fs16.existsSync(settingsPath)) {
|
|
6508
6661
|
return "eager-all";
|
|
6509
6662
|
}
|
|
6510
6663
|
try {
|
|
6511
|
-
const parsed = YAML.parse(
|
|
6664
|
+
const parsed = YAML.parse(fs16.readFileSync(settingsPath, "utf8"));
|
|
6512
6665
|
const lean = parsed?.["lean_projection"] ?? null;
|
|
6513
6666
|
const mode = lean?.["mode"];
|
|
6514
6667
|
return typeof mode === "string" && mode.length > 0 ? mode : "eager-all";
|
|
@@ -6519,7 +6672,7 @@ function readSettingsMode(projectRoot) {
|
|
|
6519
6672
|
function kernelIdsFromRouter(packageRoot) {
|
|
6520
6673
|
try {
|
|
6521
6674
|
const router = JSON.parse(
|
|
6522
|
-
|
|
6675
|
+
fs16.readFileSync(path15.join(packageRoot, "dist", "router.json"), "utf8")
|
|
6523
6676
|
);
|
|
6524
6677
|
return new Set((router["kernel"] ?? []).map(String));
|
|
6525
6678
|
} catch {
|
|
@@ -6537,7 +6690,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
|
|
|
6537
6690
|
};
|
|
6538
6691
|
}
|
|
6539
6692
|
const rulesDir = projectedRulesDir ?? path15.join(projectRoot, ".augment", "rules");
|
|
6540
|
-
if (!
|
|
6693
|
+
if (!fs16.existsSync(rulesDir)) {
|
|
6541
6694
|
return {
|
|
6542
6695
|
id: "lean-projection",
|
|
6543
6696
|
status: "skipped",
|
|
@@ -6546,7 +6699,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
|
|
|
6546
6699
|
};
|
|
6547
6700
|
}
|
|
6548
6701
|
const kernel = kernelIdsFromRouter(packageRoot);
|
|
6549
|
-
const files =
|
|
6702
|
+
const files = fs16.readdirSync(rulesDir).filter((f) => f.endsWith(".md") && f !== "INDEX.md").sort();
|
|
6550
6703
|
const nonKernel = files.filter((f) => !kernel.has(f.replace(/\.md$/, "")));
|
|
6551
6704
|
if (nonKernel.length === 0) {
|
|
6552
6705
|
return {
|
|
@@ -6560,7 +6713,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
|
|
|
6560
6713
|
for (const f of nonKernel) {
|
|
6561
6714
|
let text = "";
|
|
6562
6715
|
try {
|
|
6563
|
-
text =
|
|
6716
|
+
text = fs16.readFileSync(path15.join(rulesDir, f), "utf8");
|
|
6564
6717
|
} catch {
|
|
6565
6718
|
continue;
|
|
6566
6719
|
}
|
|
@@ -6592,7 +6745,7 @@ function _check_host_manifest(projectRoot) {
|
|
|
6592
6745
|
const detected = Object.entries(detectToolPresence(projectRoot)).filter(([, v]) => v === true).map(([k]) => k);
|
|
6593
6746
|
const detectedNote = detected.length > 0 ? `detected surfaces: ${detected.join(", ")}` : "no surfaces detected";
|
|
6594
6747
|
const settingsPath = path15.join(projectRoot, ".agent-settings.yml");
|
|
6595
|
-
if (!
|
|
6748
|
+
if (!fs16.existsSync(settingsPath)) {
|
|
6596
6749
|
return {
|
|
6597
6750
|
id: "host-manifest",
|
|
6598
6751
|
status: "ok",
|
|
@@ -6602,7 +6755,7 @@ function _check_host_manifest(projectRoot) {
|
|
|
6602
6755
|
}
|
|
6603
6756
|
let parsed = null;
|
|
6604
6757
|
try {
|
|
6605
|
-
parsed = YAML.parse(
|
|
6758
|
+
parsed = YAML.parse(fs16.readFileSync(settingsPath, "utf8"));
|
|
6606
6759
|
} catch (exc) {
|
|
6607
6760
|
return {
|
|
6608
6761
|
id: "host-manifest",
|
|
@@ -6632,7 +6785,7 @@ function runConformanceChecks(opts = {}) {
|
|
|
6632
6785
|
const projectRoot = opts.projectRoot ?? process5.cwd();
|
|
6633
6786
|
const packageRoot = opts.packageRoot ?? PACKAGE_ROOT;
|
|
6634
6787
|
const checks = [];
|
|
6635
|
-
checks.push(_check_txlog_clean());
|
|
6788
|
+
checks.push(_check_txlog_clean(installLogPath(), projectRoot));
|
|
6636
6789
|
checks.push(_check_router_pointers(packageRoot));
|
|
6637
6790
|
if (opts.skipDispatcher === true) {
|
|
6638
6791
|
checks.push({
|
|
@@ -6756,7 +6909,7 @@ function main14(argv = null) {
|
|
|
6756
6909
|
print(` \u{1F4CD} project_root: ${project_root}`);
|
|
6757
6910
|
print("conformance:");
|
|
6758
6911
|
for (const c of allChecks) {
|
|
6759
|
-
const sym =
|
|
6912
|
+
const sym = CONFORMANCE_STATUS_SYMBOLS[c["status"]] ?? "?";
|
|
6760
6913
|
print(` ${sym} ${c["id"]}: ${c["message"]}`);
|
|
6761
6914
|
if (c["status"] !== "ok" && c["remedy"]) {
|
|
6762
6915
|
print(` fix: ${c["remedy"]}`);
|
|
@@ -6781,8 +6934,8 @@ function _isCliEntry13() {
|
|
|
6781
6934
|
return true;
|
|
6782
6935
|
}
|
|
6783
6936
|
try {
|
|
6784
|
-
const here =
|
|
6785
|
-
const argv =
|
|
6937
|
+
const here = fs16.realpathSync(fileURLToPath14(import.meta.url));
|
|
6938
|
+
const argv = fs16.realpathSync(path15.resolve(process5.argv[1]));
|
|
6786
6939
|
return here === argv;
|
|
6787
6940
|
} catch {
|
|
6788
6941
|
return false;
|
|
@@ -6808,6 +6961,7 @@ export {
|
|
|
6808
6961
|
_check_lean_projection,
|
|
6809
6962
|
_check_router_pointers,
|
|
6810
6963
|
_check_txlog_clean,
|
|
6964
|
+
_installLogExpected,
|
|
6811
6965
|
_parse,
|
|
6812
6966
|
appendConformanceReport,
|
|
6813
6967
|
conformanceLogPath,
|