@event4u/agent-config 16.2.0 → 16.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +89 -237
- package/README.md +14 -12
- package/dist/agent-src/commands/analyze/inbox.md +1 -1
- package/dist/agent-src/commands/analyze/roadmap-repos.md +1 -1
- package/dist/agent-src/commands/bug/fix.md +17 -2
- package/dist/agent-src/commands/feature/plan.md +1 -1
- package/dist/agent-src/commands/feature/roadmap.md +1 -1
- package/dist/agent-src/commands/fix/ci.md +1 -1
- package/dist/agent-src/commands/implement-ticket.md +1 -1
- package/dist/agent-src/commands/jira-ticket.md +1 -1
- package/dist/agent-src/commands/roadmap/create.md +1 -1
- package/dist/agent-src/commands/roadmap/materialize.md +1 -1
- package/dist/agent-src/commands/roadmap/process-full.md +4 -4
- package/dist/agent-src/commands/roadmap/resolve-blockers.md +361 -0
- package/dist/agent-src/commands/roadmap/triage-parked.md +325 -0
- package/dist/agent-src/commands/roadmap.md +18 -4
- package/dist/agent-src/commands/tests/create.md +17 -3
- package/dist/agent-src/commands/tests/execute.md +22 -5
- package/dist/agent-src/contexts/authority/decision-revisit-gate-enforcement.md +43 -0
- package/dist/agent-src/contexts/communication/rules-auto/design-fidelity-enforcement.md +24 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +6 -6
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +32 -2
- package/dist/agent-src/contexts/execution/session-canary-enforcement-history.md +67 -0
- package/dist/agent-src/contexts/execution/toolchain-resolver.md +101 -7
- package/dist/agent-src/rules/active-remediation.md +1 -1
- package/dist/agent-src/rules/autonomous-execution.md +1 -0
- package/dist/agent-src/rules/broken-access-control.md +1 -0
- package/dist/agent-src/rules/command-suggestion-policy.md +1 -0
- package/dist/agent-src/rules/context-hygiene.md +1 -1
- package/dist/agent-src/rules/decision-revisit-gate.md +16 -33
- package/dist/agent-src/rules/design-fidelity.md +13 -16
- package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -0
- package/dist/agent-src/rules/domain-safety-disclaimer.md +1 -0
- package/dist/agent-src/rules/domain-safety-pii.md +1 -0
- package/dist/agent-src/rules/domain-safety-retention.md +1 -0
- package/dist/agent-src/rules/engineering-safety-floor.md +1 -0
- package/dist/agent-src/rules/evaluator-independence.md +3 -2
- package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
- package/dist/agent-src/rules/finance-safety-floor.md +1 -0
- package/dist/agent-src/rules/git-history-discipline.md +1 -0
- package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
- package/dist/agent-src/rules/legal-safety-floor.md +3 -2
- package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
- package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
- package/dist/agent-src/rules/media-governance-routing.md +14 -17
- package/dist/agent-src/rules/persona-governance.md +2 -2
- package/dist/agent-src/rules/provider-lifecycle-discipline.md +1 -1
- package/dist/agent-src/rules/question-not-instruction.md +1 -0
- package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
- package/dist/agent-src/rules/runtime-safety.md +1 -0
- package/dist/agent-src/rules/secret-vcs-guard.md +1 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +3 -2
- package/dist/agent-src/rules/session-canary.md +8 -62
- package/dist/agent-src/rules/settings-ask-protocol.md +1 -0
- package/dist/agent-src/rules/source-confidentiality.md +1 -0
- package/dist/agent-src/rules/strategy-safety-floor.md +1 -0
- package/dist/agent-src/rules/tool-safety.md +1 -0
- package/dist/agent-src/rules/untrusted-input-defense.md +1 -0
- package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
- package/dist/agent-src/scripts/deferral_carry.ts +339 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +4 -1
- package/dist/agent-src/skills/api-design/data/manifest.json +2 -2
- package/dist/agent-src/skills/api-testing/SKILL.md +17 -0
- package/dist/agent-src/skills/code-intelligence/SKILL.md +14 -2
- package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +2 -2
- package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
- package/dist/agent-src/skills/iconography/SKILL.md +1 -1
- package/dist/agent-src/skills/js-library-packaging/SKILL.md +2 -2
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
- package/dist/agent-src/skills/quality-tools/SKILL.md +11 -1
- package/dist/agent-src/skills/quality-tools/references/go-tools.md +76 -0
- package/dist/agent-src/skills/quality-tools/references/python-tools.md +104 -0
- package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
- package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +3 -3
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +7 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +17 -0
- package/dist/agent-src/skills/test-performance/SKILL.md +19 -1
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +2 -2
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
- package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
- package/dist/cli/registry.js +1 -1
- package/dist/cli-delegate/archive_completed_roadmaps.js +92 -12
- package/dist/cli-delegate/check_update_banner.js +12 -1
- package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
- package/dist/cli-delegate/{chunk-M2DPGFZ4.js → chunk-6V6TA25H.js} +22 -152
- package/dist/cli-delegate/chunk-7IQE3D5S.js +191 -0
- package/dist/cli-delegate/{chunk-6KFEC3G7.js → chunk-CQWX336L.js} +1 -1
- package/dist/cli-delegate/chunk-DEIV7XP7.js +108 -0
- package/dist/cli-delegate/chunk-EEHDXSYD.js +2040 -0
- package/dist/cli-delegate/{chunk-P6ZHP5W2.js → chunk-GKE7ZVFA.js} +4 -4
- package/dist/cli-delegate/chunk-JQOM7TTM.js +1201 -0
- package/dist/cli-delegate/{chunk-IGD6ERX6.js → chunk-JQVYKIZA.js} +10 -0
- package/dist/cli-delegate/{chunk-FC75PGJE.js → chunk-JY2I2DXV.js} +12 -1
- package/dist/cli-delegate/chunk-KB5FZ655.js +208 -0
- package/dist/cli-delegate/{chunk-7RQP4UDH.js → chunk-KR6EM23A.js} +235 -1455
- package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-LIYTLH6O.js} +1 -1
- package/dist/cli-delegate/{chunk-YJ72UIAK.js → chunk-NQ7WT43P.js} +1186 -244
- package/dist/cli-delegate/chunk-NXEILF2K.js +2160 -0
- package/dist/cli-delegate/{chunk-5R7HOLAF.js → chunk-NYX3L2PZ.js} +1 -1
- package/dist/cli-delegate/{chunk-4E2W63DA.js → chunk-OKJUIAPS.js} +62 -1
- package/dist/cli-delegate/{chunk-QAJS5YWY.js → chunk-PHD7OOLE.js} +2 -1
- package/dist/cli-delegate/{chunk-RGNIWTEA.js → chunk-QLDHIPWR.js} +1 -1
- package/dist/cli-delegate/{chunk-7XGBX6ZH.js → chunk-RQNNW7IF.js} +1314 -560
- package/dist/cli-delegate/{chunk-4XFP2Y3E.js → chunk-S4UMZNES.js} +1 -1
- package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-S4YWNRKO.js} +24 -8
- package/dist/cli-delegate/{chunk-FP36SWJD.js → chunk-TRI6DQZ6.js} +2781 -340
- package/dist/cli-delegate/chunk-ULQRUR23.js +208 -0
- package/dist/cli-delegate/{chunk-NUHLI2RX.js → chunk-YCU3TIXH.js} +2 -0
- package/dist/cli-delegate/chunk-ZBZV2G5R.js +210 -0
- package/dist/cli-delegate/{chunk-WGXTVKCQ.js → chunk-ZUZKZYCN.js} +5 -3
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +1167 -234
- package/dist/cli-delegate/cmd_doctor.js +13 -6
- package/dist/cli-delegate/cmd_doctor_neighbours.js +23 -0
- package/dist/cli-delegate/cmd_explain.js +5 -5
- package/dist/cli-delegate/cmd_export.js +11 -8
- package/dist/cli-delegate/cmd_fleet.js +3 -3
- package/dist/cli-delegate/cmd_handoff.js +10 -7
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_memory_get.js +3 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +3 -3
- package/dist/cli-delegate/cmd_preflight.js +6 -6
- package/dist/cli-delegate/cmd_prune.js +11 -8
- package/dist/cli-delegate/cmd_refresh.js +2 -1
- package/dist/cli-delegate/cmd_route_audit.js +8 -6
- package/dist/cli-delegate/cmd_route_explain.js +4 -4
- package/dist/cli-delegate/cmd_session_recycle.js +4 -3
- package/dist/cli-delegate/cmd_settings_get.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +11 -8
- package/dist/cli-delegate/cmd_settings_set.js +4 -4
- package/dist/cli-delegate/cmd_sync.js +11 -8
- package/dist/cli-delegate/cmd_uninstall.js +11 -8
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +3 -2
- package/dist/cli-delegate/cmd_validate.js +11 -8
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/cli-delegate/deferral_carry.js +16 -0
- package/dist/cli-delegate/gate_execute.js +2 -2
- package/dist/cli-delegate/roadmap_gates.js +5 -5
- package/dist/cli-delegate/update_roadmap_progress.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +127 -75
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +7 -5
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +6 -4
- package/dist/hooks/dispatch.js +5357 -6656
- package/dist/hooks/dispatch.sha256 +1 -1
- package/dist/install/claudePathsPlan.js +47 -6
- package/dist/install/claudeRuleRewrite.js +7 -0
- package/dist/install/emit_host_rules_cli.js +39 -36
- package/dist/install/globalRuleScope.js +87 -0
- package/dist/install/host_rule_ownership.js +160 -0
- package/dist/install/install.mjs +2080 -1024
- package/dist/install/installThinLayer.js +261 -0
- package/dist/install/installedRuleLinks.js +176 -0
- package/dist/install/wizard-plan.js +24 -0
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +31965 -27154
- package/dist/scripts/_lib/agent_settings.js +11 -0
- package/dist/scripts/_lib/claude_settings_hooks.js +10 -8
- package/dist/scripts/_lib/installed_layer.js +378 -0
- package/dist/scripts/_lib/lean_projection_mode.js +218 -15
- package/dist/scripts/_lib/rule_consequence_class.js +45 -0
- package/dist/scripts/_lib/rule_injection.js +67 -10
- package/dist/scripts/_lib/rule_law_section.js +122 -0
- package/dist/scripts/_lib/thin_rules.js +458 -0
- package/dist/scripts/_lib/windsurf_render.js +60 -0
- package/dist/scripts/condense.js +14 -26
- package/dist/scripts/project_thin_rules.js +14 -294
- package/dist/server/schemas/settings.js +6 -0
- package/dist/shared/skillRanking.js +74 -13
- package/docs/contracts/settings-classes.md +7 -5
- package/package.json +1 -1
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
- package/src/agent-src/scripts/deferral_carry.ts +339 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +4 -1
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
- package/src/config/agent-settings.template.yml +62 -22
- package/src/config/continuity-surface.json +7 -0
- package/src/config/evaluator-budgets.json +4 -4
- package/src/config/gate-coverage.yml +96 -3
- package/src/config/gate-violation-baselines.json +17 -9
- package/src/config/hook-bundle-budget.json +25 -0
- package/src/config/hook-token-budget.json +6 -1
- package/src/config/host-instruction-limits.json +33 -0
- package/src/config/routing-coverage-seed.json +24 -3
- package/src/config/rule-activation-census.json +7 -3
- package/src/config/rule-consequence-class.json +161 -0
- package/src/config/rule-law-ceilings.json +99 -0
- package/src/install/claudePathsPlan.ts +46 -4
- package/src/install/claudeRuleRewrite.ts +10 -1
- package/src/install/emit_host_rules_cli.ts +43 -34
- package/src/install/globalRuleScope.ts +109 -0
- package/src/install/host_rule_ownership.ts +170 -0
- package/src/install/installThinLayer.ts +312 -0
- package/src/install/installedRuleLinks.ts +220 -0
- package/src/install/wizard-plan.ts +24 -0
- package/src/scripts/_cli/cmd_doctor.ts +23 -71
- package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
- package/src/scripts/_dispatch.bash +16 -2
- package/src/scripts/_lib/agent_settings.ts +11 -0
- package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
- package/src/scripts/_lib/estate_offsets.ts +241 -0
- package/src/scripts/_lib/exemption_shape.ts +143 -0
- package/src/scripts/_lib/fnv.ts +23 -0
- package/src/scripts/_lib/foreign_scan.ts +106 -0
- package/src/scripts/_lib/gitignore_block.ts +314 -0
- package/src/scripts/_lib/global_deploy_inventory.ts +32 -0
- package/src/scripts/_lib/graph_feeder_record.ts +214 -0
- package/src/scripts/_lib/hook_settings.ts +73 -0
- package/src/scripts/_lib/host_capability.ts +40 -19
- package/src/scripts/_lib/host_hook_merge.ts +165 -0
- package/src/scripts/_lib/installed_layer.ts +538 -0
- package/src/scripts/_lib/json_merge.ts +82 -0
- package/src/scripts/_lib/json_pointers.ts +93 -11
- package/src/scripts/_lib/lean_projection_mode.ts +275 -15
- package/src/scripts/_lib/machine_wake.ts +16 -3
- package/src/scripts/_lib/neighbour_census.ts +760 -0
- package/src/scripts/_lib/neighbour_scan.ts +122 -0
- package/src/scripts/_lib/neighbour_tool_use.ts +189 -0
- package/src/scripts/_lib/prompt_shape.ts +46 -0
- package/src/scripts/_lib/release_migration_gate.ts +19 -3
- package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
- package/src/scripts/_lib/review_coverage.ts +134 -0
- package/src/scripts/_lib/rule_consequence_class.ts +71 -0
- package/src/scripts/_lib/rule_injection.ts +130 -10
- package/src/scripts/_lib/rule_law_section.ts +147 -0
- package/src/scripts/_lib/rule_layer_overlap.ts +58 -0
- package/src/scripts/_lib/skill_origin.ts +254 -0
- package/src/scripts/_lib/structured_ask.ts +64 -21
- package/src/scripts/_lib/thin_rules.ts +540 -0
- package/src/scripts/_lib/touched_file_quality.ts +434 -0
- package/src/scripts/_lib/turn_end_refusals.ts +77 -2
- package/src/scripts/_lib/windsurf_render.ts +63 -0
- package/src/scripts/ai_council/cli_call_budget.ts +13 -5
- package/src/scripts/ai_council/clients.ts +26 -57
- package/src/scripts/ai_council/config.ts +1 -1
- package/src/scripts/ai_council/vendor_defaults.ts +45 -0
- package/src/scripts/audit_skill_overlap.ts +261 -0
- package/src/scripts/before_complete_hook.ts +243 -12
- package/src/scripts/bench_touched_file_quality.ts +238 -0
- package/src/scripts/check_enforcement_matrix.ts +377 -20
- package/src/scripts/check_estate_count.ts +130 -153
- package/src/scripts/check_finding_dispositions.ts +116 -4
- package/src/scripts/check_hook_bundle_composition.ts +370 -0
- package/src/scripts/check_kernel_edit_ratified.ts +49 -11
- package/src/scripts/check_memory.ts +7 -1
- package/src/scripts/check_release_highlights.ts +10 -0
- package/src/scripts/check_release_pr_shape.ts +38 -15
- package/src/scripts/check_standing_rule_delivery.ts +33 -13
- package/src/scripts/check_verify_expectation_delta.ts +521 -0
- package/src/scripts/code_graph/detect.ts +127 -7
- package/src/scripts/code_graph/foreign.ts +194 -0
- package/src/scripts/code_graph/query.ts +217 -4
- package/src/scripts/code_graph/types.ts +26 -2
- package/src/scripts/code_graph/validate.ts +3 -1
- package/src/scripts/code_graph/verbs.ts +103 -0
- package/src/scripts/compile_hook_manifest.ts +33 -1
- package/src/scripts/condense.ts +14 -27
- package/src/scripts/estate_exemption_shape.ts +202 -0
- package/src/scripts/generate_host_cost_table.ts +1 -1
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +150 -9
- package/src/scripts/hooks/block_config_weakening.ts +67 -2
- package/src/scripts/hooks/block_plumbing_writes.ts +29 -1
- package/src/scripts/hooks/code_graph_context_hook.ts +252 -23
- package/src/scripts/hooks/concern_failure_policy.ts +280 -0
- package/src/scripts/hooks/dispatch_hook.ts +52 -52
- package/src/scripts/hooks/exit_codes.ts +2 -1
- package/src/scripts/hooks/host_lowering.json +1 -1
- package/src/scripts/hooks/host_lowering.ts +19 -1
- package/src/scripts/hooks/one_question_per_ask_hook.ts +41 -25
- package/src/scripts/hooks/payload_stub.ts +10 -1
- package/src/scripts/hooks/rule_inject_hook.ts +757 -63
- package/src/scripts/hooks/table_fingerprint.ts +21 -0
- package/src/scripts/hooks/telemetry_usage_hook.ts +64 -8
- package/src/scripts/hooks/turn_end_gate_hook.ts +84 -0
- package/src/scripts/hooks_status.ts +112 -0
- package/src/scripts/injection_scan_hook.ts +7 -0
- package/src/scripts/install.ts +72 -185
- package/src/scripts/installed_layer_report.ts +103 -0
- package/src/scripts/language_mirror_hook.ts +15 -6
- package/src/scripts/learning_sidecar.ts +119 -8
- package/src/scripts/lint_hook_manifest.ts +53 -0
- package/src/scripts/lint_major_migration_sections.ts +156 -26
- package/src/scripts/lint_pack_boundaries.ts +63 -5
- package/src/scripts/lint_rule_law_section.ts +517 -0
- package/src/scripts/lint_workflow_security.ts +161 -5
- package/src/scripts/mcp_server/consumer_tool_catalog.json +53 -0
- package/src/scripts/mcp_server/graph_tools.ts +72 -3
- package/src/scripts/measure_routing_signal.ts +54 -16
- package/src/scripts/measure_skill_ranker_baseline.ts +216 -8
- package/src/scripts/memory_learn_hook.ts +91 -6
- package/src/scripts/model_rule_injection.ts +52 -5
- package/src/scripts/project_thin_rules.ts +34 -327
- package/src/scripts/report_evidence_temperature.ts +657 -0
- package/src/scripts/report_installed_rule_links.ts +157 -0
- package/src/scripts/report_label_agreement.ts +233 -0
- package/src/scripts/report_obligation_mechanism.ts +230 -0
- package/src/scripts/report_single_token_triggers.ts +277 -0
- package/src/scripts/report_skill_ranker_confusion.ts +442 -0
- package/src/scripts/report_standing_payload_by_host.ts +188 -15
- package/src/scripts/report_verification_record_coverage.ts +306 -0
- package/src/scripts/rule_activation_census.ts +5 -2
- package/src/scripts/rule_trigger_eval.ts +9 -9
- package/src/scripts/schemas/review-findings.schema.json +3 -2
- package/src/scripts/schemas/rule.schema.json +5 -0
- package/src/scripts/self_review_gate.ts +62 -7
- package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
- package/src/scripts/sweep_skill_ranker_signals.ts +129 -0
- package/src/scripts/sync_gitignore.ts +29 -235
- package/src/scripts/trigger_eval_grandfather.json +0 -5
- package/src/server/schemas/settings.ts +10 -0
- package/src/shared/skillRanking.ts +117 -11
- package/dist/cli-delegate/chunk-JNMVLWLR.js +0 -4457
- package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
|
@@ -13,209 +13,9 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
read_lockfile
|
|
15
15
|
} from "./chunk-T7L2QK3K.js";
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
var BASE64_ENTROPY_THRESHOLD = 4.5;
|
|
20
|
-
var HEX_ENTROPY_THRESHOLD = 3;
|
|
21
|
-
var RULES = [
|
|
22
|
-
{ rule: "anthropic-api-key", kind: "anthropic-api-key", regex: /sk-ant-[A-Za-z0-9_-]{20,}/g },
|
|
23
|
-
{ rule: "anthropic-project-key", kind: "anthropic-api-key", regex: /sk-proj-[A-Za-z0-9_-]{20,}/g },
|
|
24
|
-
{ rule: "aws-access-key", kind: "aws-access-key", regex: /AKIA[0-9A-Z]{16}/g },
|
|
25
|
-
{ rule: "google-api-key", kind: "google-api-key", regex: /AIza[0-9A-Za-z_-]{35}/g },
|
|
26
|
-
{ rule: "github-pat", kind: "github-pat", regex: /ghp_[0-9A-Za-z]{36}/g },
|
|
27
|
-
// Fine-grained PATs (github_pat_…) were a measured detector gap: the
|
|
28
|
-
// 2026-07-28 adversarial corpus pass flagged them as missed positives.
|
|
29
|
-
{ rule: "github-fine-grained-pat", kind: "github-pat", regex: /github_pat_[0-9A-Za-z_]{22,}/g },
|
|
30
|
-
{
|
|
31
|
-
rule: "jwt",
|
|
32
|
-
kind: "jwt",
|
|
33
|
-
regex: /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g
|
|
34
|
-
},
|
|
35
|
-
// Extensions.
|
|
36
|
-
{ rule: "stripe-secret-key", kind: "stripe-secret-key", regex: /sk_(?:live|test)_[0-9A-Za-z]{16,}/g },
|
|
37
|
-
{ rule: "slack-token", kind: "slack-token", regex: /xox[baprs]-[0-9A-Za-z-]{10,}/g },
|
|
38
|
-
{ rule: "pem-private-key", kind: "pem-private-key", regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----/g },
|
|
39
|
-
{
|
|
40
|
-
rule: "db-connection-uri",
|
|
41
|
-
kind: "db-connection-uri",
|
|
42
|
-
regex: /\b[a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:[^\s:/@]+@/g
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
rule: "generic-assignment",
|
|
46
|
-
kind: "generic-assignment",
|
|
47
|
-
regex: /(?:password|passwd|pwd|secret|api[_-]?key|access[_-]?token|auth[_-]?token)\s*[:=]\s*['"]?([^'"\s]{8,})['"]?/gi,
|
|
48
|
-
valueGroup: 1
|
|
49
|
-
}
|
|
50
|
-
];
|
|
51
|
-
var SECRET_ALLOW = /secret-allow/;
|
|
52
|
-
var PLACEHOLDER_PATTERNS = [
|
|
53
|
-
/x{4,}/i,
|
|
54
|
-
/example/i,
|
|
55
|
-
/changeme/i,
|
|
56
|
-
/your[_-].*here/i,
|
|
57
|
-
/<[^>]+>/,
|
|
58
|
-
/\.\.\./,
|
|
59
|
-
/^your[_-]/i,
|
|
60
|
-
/placeholder/i
|
|
61
|
-
];
|
|
62
|
-
var DATA_URI = /data:[\w.+-]+\/[\w.+-]+;base64,/i;
|
|
63
|
-
var UUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
64
|
-
var KEYWORD_CONTEXT = /password|secret|token|api[_-]?key|credential/i;
|
|
65
|
-
var ENTROPY_TOKEN = /[A-Za-z0-9+=_-]{20,}/g;
|
|
66
|
-
var IS_HEX = /^[0-9a-fA-F]+$/;
|
|
67
|
-
var HASH_HEX_LENGTHS = /* @__PURE__ */ new Set([32, 40, 64]);
|
|
68
|
-
function isAllSameChar(value) {
|
|
69
|
-
return value.length > 0 && /^(.)\1*$/.test(value);
|
|
70
|
-
}
|
|
71
|
-
var GENERIC_MIN_LENGTH = 16;
|
|
72
|
-
var GENERIC_MIN_ENTROPY = 3.3;
|
|
73
|
-
var CODE_EXPR = /[()$`{}<>[\];,]|->|=>|\.\w/;
|
|
74
|
-
function looksLikeSecretValue(value) {
|
|
75
|
-
if (value.length < GENERIC_MIN_LENGTH) {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
if (CODE_EXPR.test(value)) {
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
const hasDigit = /[0-9]/.test(value);
|
|
82
|
-
const mixedCase = /[a-z]/.test(value) && /[A-Z]/.test(value);
|
|
83
|
-
if (!hasDigit && !mixedCase) {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
return shannonEntropy(value) >= GENERIC_MIN_ENTROPY;
|
|
87
|
-
}
|
|
88
|
-
function isPlaceholderValue(value, pathHint) {
|
|
89
|
-
if (pathHint !== void 0) {
|
|
90
|
-
if (pathHint.endsWith(".example") || pathHint.endsWith(".sample") || pathHint.includes(".env.example")) {
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
if (isAllSameChar(value)) {
|
|
95
|
-
return true;
|
|
96
|
-
}
|
|
97
|
-
return PLACEHOLDER_PATTERNS.some((p) => p.test(value));
|
|
98
|
-
}
|
|
99
|
-
function maskSecret(value) {
|
|
100
|
-
const front = value.slice(0, 4);
|
|
101
|
-
const back = value.length > 6 ? value.slice(-2) : "";
|
|
102
|
-
if (front.length + back.length >= value.length) {
|
|
103
|
-
return `${value.slice(0, 1)}\u2026`;
|
|
104
|
-
}
|
|
105
|
-
return `${front}\u2026${back}`;
|
|
106
|
-
}
|
|
107
|
-
function shannonEntropy(value) {
|
|
108
|
-
const freq = /* @__PURE__ */ new Map();
|
|
109
|
-
for (const ch of value) {
|
|
110
|
-
freq.set(ch, (freq.get(ch) ?? 0) + 1);
|
|
111
|
-
}
|
|
112
|
-
const n = value.length;
|
|
113
|
-
let entropy = 0;
|
|
114
|
-
for (const count of freq.values()) {
|
|
115
|
-
const p = count / n;
|
|
116
|
-
entropy -= p * Math.log2(p);
|
|
117
|
-
}
|
|
118
|
-
return entropy;
|
|
119
|
-
}
|
|
120
|
-
function overlaps(spans, start, end) {
|
|
121
|
-
return spans.some(([s, e]) => start < e && s < end);
|
|
122
|
-
}
|
|
123
|
-
function scanText(text, opts = {}) {
|
|
124
|
-
const pathHint = opts.path;
|
|
125
|
-
const findings = [];
|
|
126
|
-
const lines = text.split(/\r\n|\r|\n/);
|
|
127
|
-
for (let i = 0; i < lines.length; i++) {
|
|
128
|
-
const line = lines[i];
|
|
129
|
-
const lineNo = i + 1;
|
|
130
|
-
if (SECRET_ALLOW.test(line)) {
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
const covered = [];
|
|
134
|
-
for (const rule of RULES) {
|
|
135
|
-
rule.regex.lastIndex = 0;
|
|
136
|
-
let m;
|
|
137
|
-
while ((m = rule.regex.exec(line)) !== null) {
|
|
138
|
-
const value = rule.valueGroup !== void 0 ? m[rule.valueGroup] : m[0];
|
|
139
|
-
if (value === void 0) {
|
|
140
|
-
if (m.index === rule.regex.lastIndex) {
|
|
141
|
-
rule.regex.lastIndex += 1;
|
|
142
|
-
}
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
if (rule.rule === "generic-assignment" && !looksLikeSecretValue(value)) {
|
|
146
|
-
if (m.index === rule.regex.lastIndex) {
|
|
147
|
-
rule.regex.lastIndex += 1;
|
|
148
|
-
}
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
const valueStart = m.index + m[0].indexOf(value);
|
|
152
|
-
const confidence = isPlaceholderValue(value, pathHint) ? "low" : "high";
|
|
153
|
-
findings.push({
|
|
154
|
-
rule: rule.rule,
|
|
155
|
-
kind: rule.kind,
|
|
156
|
-
line: lineNo,
|
|
157
|
-
column: valueStart + 1,
|
|
158
|
-
masked: maskSecret(value),
|
|
159
|
-
confidence
|
|
160
|
-
});
|
|
161
|
-
covered.push([valueStart, valueStart + value.length]);
|
|
162
|
-
if (m.index === rule.regex.lastIndex) {
|
|
163
|
-
rule.regex.lastIndex += 1;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
if (!DATA_URI.test(line)) {
|
|
168
|
-
const hasKeyword = KEYWORD_CONTEXT.test(line);
|
|
169
|
-
ENTROPY_TOKEN.lastIndex = 0;
|
|
170
|
-
let tm;
|
|
171
|
-
while ((tm = ENTROPY_TOKEN.exec(line)) !== null) {
|
|
172
|
-
const token = tm[0];
|
|
173
|
-
const start = tm.index;
|
|
174
|
-
const end = start + token.length;
|
|
175
|
-
if (token.length < ENTROPY_MIN_TOKEN_LENGTH) {
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
if (overlaps(covered, start, end)) {
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
if (UUID.test(token)) {
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
if (/^sha(?:256|384|512)-/.test(token)) {
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
const entropy = shannonEntropy(token);
|
|
188
|
-
const isHex = IS_HEX.test(token);
|
|
189
|
-
if (isHex && HASH_HEX_LENGTHS.has(token.length)) {
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
let confidence = null;
|
|
193
|
-
if (isHex) {
|
|
194
|
-
if (entropy >= HEX_ENTROPY_THRESHOLD && hasKeyword) {
|
|
195
|
-
confidence = "high";
|
|
196
|
-
}
|
|
197
|
-
} else if (entropy >= BASE64_ENTROPY_THRESHOLD) {
|
|
198
|
-
confidence = hasKeyword ? "high" : "medium";
|
|
199
|
-
}
|
|
200
|
-
if (confidence === null) {
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
if (isPlaceholderValue(token, pathHint)) {
|
|
204
|
-
confidence = "low";
|
|
205
|
-
}
|
|
206
|
-
findings.push({
|
|
207
|
-
rule: "entropy",
|
|
208
|
-
kind: "high-entropy-string",
|
|
209
|
-
line: lineNo,
|
|
210
|
-
column: start + 1,
|
|
211
|
-
masked: maskSecret(token),
|
|
212
|
-
confidence
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
return findings;
|
|
218
|
-
}
|
|
16
|
+
import {
|
|
17
|
+
scanText
|
|
18
|
+
} from "./chunk-KB5FZ655.js";
|
|
219
19
|
|
|
220
20
|
// src/scripts/_lib/subagent_capsule.ts
|
|
221
21
|
var EPISTEMIC_STATES = ["verified", "assumed", "gap"];
|
|
@@ -561,10 +361,57 @@ function validateAssumption(input, label = "assumption") {
|
|
|
561
361
|
}
|
|
562
362
|
|
|
563
363
|
// src/scripts/before_complete_hook.ts
|
|
564
|
-
import
|
|
565
|
-
import
|
|
364
|
+
import fs4 from "node:fs";
|
|
365
|
+
import path4 from "node:path";
|
|
566
366
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
567
367
|
|
|
368
|
+
// src/scripts/_lib/hook_settings.ts
|
|
369
|
+
import * as fs from "node:fs";
|
|
370
|
+
import * as path from "node:path";
|
|
371
|
+
var SETTINGS_FILE = ".agent-settings.yml";
|
|
372
|
+
function hookSectionValue(root, section, key) {
|
|
373
|
+
const file = path.join(root, SETTINGS_FILE);
|
|
374
|
+
let text;
|
|
375
|
+
try {
|
|
376
|
+
if (!fs.statSync(file).isFile()) return "";
|
|
377
|
+
text = fs.readFileSync(file, "utf-8");
|
|
378
|
+
} catch {
|
|
379
|
+
return "";
|
|
380
|
+
}
|
|
381
|
+
const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
382
|
+
const sectionPattern = new RegExp(`^(\\s+)${esc(section)}\\s*:\\s*$`);
|
|
383
|
+
const keyPattern = new RegExp(`^\\s+${esc(key)}\\s*:\\s*(.+)$`);
|
|
384
|
+
let inHooks = false;
|
|
385
|
+
let sectionIndent = null;
|
|
386
|
+
const indentOf = (line) => line.length - line.replace(/^\s+/, "").length;
|
|
387
|
+
for (const raw of text.split(/\r\n|\r|\n/)) {
|
|
388
|
+
const line = raw.replace(/\s+$/, "");
|
|
389
|
+
if (!line || line.replace(/^\s+/, "").startsWith("#")) continue;
|
|
390
|
+
if (!(line.startsWith(" ") || line.startsWith(" "))) {
|
|
391
|
+
inHooks = /^hooks\s*:\s*$/.test(line);
|
|
392
|
+
sectionIndent = null;
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
if (inHooks) {
|
|
396
|
+
const opened = sectionPattern.exec(line);
|
|
397
|
+
if (opened) {
|
|
398
|
+
sectionIndent = opened[1].length;
|
|
399
|
+
continue;
|
|
400
|
+
}
|
|
401
|
+
if (sectionIndent !== null && indentOf(line) <= sectionIndent) {
|
|
402
|
+
sectionIndent = null;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (sectionIndent !== null) {
|
|
406
|
+
const m = keyPattern.exec(line);
|
|
407
|
+
if (m) {
|
|
408
|
+
return (m[1] ?? "").replace(/\s+#.*$/, "").trim().replace(/^["']|["']$/g, "");
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return "";
|
|
413
|
+
}
|
|
414
|
+
|
|
568
415
|
// src/scripts/_lib/verification_command.ts
|
|
569
416
|
var VERIFY_BINARIES = /* @__PURE__ */ new Set([
|
|
570
417
|
"vitest",
|
|
@@ -733,6 +580,187 @@ function mightBeVerification(command) {
|
|
|
733
580
|
return _WIDE_CANDIDATE_RE.test(command);
|
|
734
581
|
}
|
|
735
582
|
|
|
583
|
+
// src/scripts/_lib/touched_file_quality.ts
|
|
584
|
+
import { spawnSync } from "node:child_process";
|
|
585
|
+
import * as fs2 from "node:fs";
|
|
586
|
+
import * as path2 from "node:path";
|
|
587
|
+
var OUTPUT_HEAD_LINES = 20;
|
|
588
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
589
|
+
var JS_EXTENSIONS = [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts"];
|
|
590
|
+
var SCOPED_FORMS = /* @__PURE__ */ new Map([
|
|
591
|
+
["npx eslint .", { argv: ["npx", "eslint"], extensions: JS_EXTENSIONS, check_form: false }],
|
|
592
|
+
[
|
|
593
|
+
"vendor/bin/phpstan analyse",
|
|
594
|
+
{ argv: ["vendor/bin/phpstan", "analyse"], extensions: [".php"], check_form: false }
|
|
595
|
+
],
|
|
596
|
+
// `vendor/bin/pint` REWRITES source. `--test` is its check form: same tool,
|
|
597
|
+
// same rules, reports instead of writing. Risk 3 of the roadmap is that this
|
|
598
|
+
// module reformats the consumer's tree; this row plus the fixture that proves
|
|
599
|
+
// no file changed is the mitigation.
|
|
600
|
+
[
|
|
601
|
+
"vendor/bin/pint",
|
|
602
|
+
{ argv: ["vendor/bin/pint", "--test"], extensions: [".php"], check_form: true }
|
|
603
|
+
],
|
|
604
|
+
["ruff check", { argv: ["ruff", "check"], extensions: [".py"], check_form: false }],
|
|
605
|
+
["mypy .", { argv: ["mypy"], extensions: [".py"], check_form: false }]
|
|
606
|
+
]);
|
|
607
|
+
var MUTATING_WITHOUT_CHECK_FORM = /* @__PURE__ */ new Set([]);
|
|
608
|
+
function extensionOf(file) {
|
|
609
|
+
const base = path2.basename(file);
|
|
610
|
+
const dot = base.lastIndexOf(".");
|
|
611
|
+
return dot <= 0 ? "" : base.slice(dot).toLowerCase();
|
|
612
|
+
}
|
|
613
|
+
function planQualityRuns(commands, files) {
|
|
614
|
+
return commands.map((source_command) => {
|
|
615
|
+
if (MUTATING_WITHOUT_CHECK_FORM.has(source_command)) {
|
|
616
|
+
return { source_command, argv: null, files: [], skipped: "mutating" };
|
|
617
|
+
}
|
|
618
|
+
const spec = SCOPED_FORMS.get(source_command);
|
|
619
|
+
if (spec === void 0) {
|
|
620
|
+
return { source_command, argv: null, files: [], skipped: "unscoped" };
|
|
621
|
+
}
|
|
622
|
+
const matched = spec.extensions.length === 0 ? [...files] : files.filter((f) => spec.extensions.includes(extensionOf(f)));
|
|
623
|
+
if (matched.length === 0) {
|
|
624
|
+
return { source_command, argv: null, files: [], skipped: "no_files" };
|
|
625
|
+
}
|
|
626
|
+
return { source_command, argv: [...spec.argv, ...matched], files: matched, skipped: null };
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
function renderCommand(argv) {
|
|
630
|
+
return argv.join(" ");
|
|
631
|
+
}
|
|
632
|
+
function headLines(text, limit) {
|
|
633
|
+
if (text === "") return [];
|
|
634
|
+
return text.split(/\r\n|\r|\n/).filter((line, i, all) => !(line === "" && i === all.length - 1)).slice(0, limit);
|
|
635
|
+
}
|
|
636
|
+
function now() {
|
|
637
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "+00:00");
|
|
638
|
+
}
|
|
639
|
+
function defaultSpawn(argv, cwd, timeout_ms) {
|
|
640
|
+
const [head, ...rest] = argv;
|
|
641
|
+
if (head === void 0) return { status: null, output: "", enoent: true };
|
|
642
|
+
const res = spawnSync(head, rest, {
|
|
643
|
+
cwd,
|
|
644
|
+
timeout: timeout_ms,
|
|
645
|
+
encoding: "utf-8",
|
|
646
|
+
// No shell. The argv comes from a committed table, and a shell would
|
|
647
|
+
// make a file path with a space into an injection surface.
|
|
648
|
+
shell: false,
|
|
649
|
+
maxBuffer: 4 * 1024 * 1024
|
|
650
|
+
});
|
|
651
|
+
const enoent = res.error !== void 0 && res.error.code === "ENOENT";
|
|
652
|
+
const output2 = `${res.stdout ?? ""}${res.stderr ?? ""}`;
|
|
653
|
+
return { status: res.status, output: output2, enoent };
|
|
654
|
+
}
|
|
655
|
+
function runTouchedFileQuality(opts) {
|
|
656
|
+
const timeout_ms = opts.timeout_ms ?? DEFAULT_TIMEOUT_MS;
|
|
657
|
+
const spawn = opts.spawn ?? defaultSpawn;
|
|
658
|
+
const out = [];
|
|
659
|
+
for (const plan of planQualityRuns(opts.commands, opts.files)) {
|
|
660
|
+
if (plan.argv === null) {
|
|
661
|
+
out.push({
|
|
662
|
+
command: plan.source_command,
|
|
663
|
+
source_command: plan.source_command,
|
|
664
|
+
exit_code: null,
|
|
665
|
+
output_head: [],
|
|
666
|
+
skipped: plan.skipped,
|
|
667
|
+
files: [],
|
|
668
|
+
at: now()
|
|
669
|
+
});
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
let status = null;
|
|
673
|
+
let output2 = "";
|
|
674
|
+
let enoent = false;
|
|
675
|
+
try {
|
|
676
|
+
const res = spawn(plan.argv, opts.root, timeout_ms);
|
|
677
|
+
status = res.status;
|
|
678
|
+
output2 = res.output;
|
|
679
|
+
enoent = res.enoent;
|
|
680
|
+
} catch {
|
|
681
|
+
enoent = true;
|
|
682
|
+
}
|
|
683
|
+
const absent = enoent || status === 127;
|
|
684
|
+
out.push({
|
|
685
|
+
command: renderCommand(plan.argv),
|
|
686
|
+
source_command: plan.source_command,
|
|
687
|
+
exit_code: absent ? null : status,
|
|
688
|
+
output_head: absent ? [] : headLines(output2, OUTPUT_HEAD_LINES),
|
|
689
|
+
skipped: absent ? "absent" : null,
|
|
690
|
+
files: plan.files,
|
|
691
|
+
at: now()
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
return out;
|
|
695
|
+
}
|
|
696
|
+
var WARN_LINE_MAX_BYTES = 200;
|
|
697
|
+
function advisoryLine(runs) {
|
|
698
|
+
const failed = runs.find((r) => r.skipped === null && r.exit_code !== null && r.exit_code !== 0);
|
|
699
|
+
if (failed === void 0) return null;
|
|
700
|
+
const file = failed.files[0] ?? "(no file)";
|
|
701
|
+
const more = failed.files.length > 1 ? ` (+${String(failed.files.length - 1)} more)` : "";
|
|
702
|
+
const line = `touched-file quality: ${failed.source_command} exited ${String(failed.exit_code)} on ${file}${more}`;
|
|
703
|
+
return truncateToBytes(line, WARN_LINE_MAX_BYTES);
|
|
704
|
+
}
|
|
705
|
+
function truncateToBytes(text, max) {
|
|
706
|
+
const buf = Buffer.from(text, "utf-8");
|
|
707
|
+
if (buf.length <= max) return text;
|
|
708
|
+
let end = max;
|
|
709
|
+
while (end > 0 && (buf[end] ?? 0) >= 128 && (buf[end] ?? 0) < 192) end -= 1;
|
|
710
|
+
return buf.subarray(0, end).toString("utf-8");
|
|
711
|
+
}
|
|
712
|
+
function touchedFilesAtStop(root, session_id, gitStatus = defaultGitStatus) {
|
|
713
|
+
let recorded;
|
|
714
|
+
try {
|
|
715
|
+
const raw = fs2.readFileSync(
|
|
716
|
+
path2.join(root, "agents", "state", "minimal-safe-diff.json"),
|
|
717
|
+
"utf-8"
|
|
718
|
+
);
|
|
719
|
+
const decoded = JSON.parse(raw);
|
|
720
|
+
if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) {
|
|
721
|
+
return { files: [], source: "none" };
|
|
722
|
+
}
|
|
723
|
+
const state = decoded;
|
|
724
|
+
if (state["session_id"] !== session_id || session_id === "") {
|
|
725
|
+
return { files: [], source: "none" };
|
|
726
|
+
}
|
|
727
|
+
const touched = state["files_touched_this_turn"];
|
|
728
|
+
if (!Array.isArray(touched)) return { files: [], source: "none" };
|
|
729
|
+
recorded = touched.filter((x) => typeof x === "string");
|
|
730
|
+
} catch {
|
|
731
|
+
return { files: [], source: "none" };
|
|
732
|
+
}
|
|
733
|
+
const changed = new Set(gitStatus(root));
|
|
734
|
+
const files = recorded.filter((f) => changed.has(normalizePath(f)));
|
|
735
|
+
return { files, source: "minimal-safe-diff\u2229worktree" };
|
|
736
|
+
}
|
|
737
|
+
function normalizePath(p) {
|
|
738
|
+
return p.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
739
|
+
}
|
|
740
|
+
function defaultGitStatus(root) {
|
|
741
|
+
const res = spawnSync("git", ["status", "--porcelain", "-z"], {
|
|
742
|
+
cwd: root,
|
|
743
|
+
encoding: "utf-8",
|
|
744
|
+
timeout: 1e4,
|
|
745
|
+
shell: false,
|
|
746
|
+
maxBuffer: 4 * 1024 * 1024
|
|
747
|
+
});
|
|
748
|
+
if (res.status !== 0 || typeof res.stdout !== "string") return [];
|
|
749
|
+
return parsePorcelainZ(res.stdout);
|
|
750
|
+
}
|
|
751
|
+
function parsePorcelainZ(stdout) {
|
|
752
|
+
const out = [];
|
|
753
|
+
const parts = stdout.split("\0");
|
|
754
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
755
|
+
const entry = parts[i];
|
|
756
|
+
if (entry === void 0 || entry.length < 4) continue;
|
|
757
|
+
const status = entry.slice(0, 2);
|
|
758
|
+
out.push(normalizePath(entry.slice(3)));
|
|
759
|
+
if (status.startsWith("R") || status.startsWith("C")) i += 1;
|
|
760
|
+
}
|
|
761
|
+
return out;
|
|
762
|
+
}
|
|
763
|
+
|
|
736
764
|
// src/scripts/_lib/verification_evidence.ts
|
|
737
765
|
var INSTRUMENT_GAP_REASONS = /* @__PURE__ */ new Set([
|
|
738
766
|
"exit_code_unavailable",
|
|
@@ -1029,9 +1057,814 @@ function describeRecordFinding(failed, reasons) {
|
|
|
1029
1057
|
}
|
|
1030
1058
|
var NO_RED_EVIDENCE_REASON = "a completion claim over production code this turn changed, with a test file touched but `no_red_evidence` \u2014 no recorded run shows that test target failing and then passing after the last edit. A test never seen red has unknown sensitivity: run it against the unfixed code, watch it fail for the intended reason, then fix and re-run";
|
|
1031
1059
|
|
|
1060
|
+
// src/agent-src/templates/scripts/work_engine/stack/runner.ts
|
|
1061
|
+
import * as fs3 from "node:fs";
|
|
1062
|
+
import * as path3 from "node:path";
|
|
1063
|
+
var BEHAVIOR_UNKNOWN = "unknown";
|
|
1064
|
+
var SPEED_FAST = "fast";
|
|
1065
|
+
var SPEED_SLOW = "slow";
|
|
1066
|
+
var SPEED_E2E = "e2e";
|
|
1067
|
+
var HIGH = "HIGH";
|
|
1068
|
+
var MEDIUM = "MEDIUM";
|
|
1069
|
+
var LOW = "LOW";
|
|
1070
|
+
var _MANIFESTS = [
|
|
1071
|
+
"composer.json",
|
|
1072
|
+
"package.json",
|
|
1073
|
+
"pyproject.toml",
|
|
1074
|
+
"go.mod",
|
|
1075
|
+
"Cargo.toml",
|
|
1076
|
+
"Makefile",
|
|
1077
|
+
"Taskfile.yml",
|
|
1078
|
+
"Taskfile.yaml",
|
|
1079
|
+
"Gemfile",
|
|
1080
|
+
"pom.xml",
|
|
1081
|
+
"build.gradle",
|
|
1082
|
+
"build.gradle.kts",
|
|
1083
|
+
// `settings.gradle[.kts]` is a Gradle signal `_jvm_build` acts on, so it
|
|
1084
|
+
// must be in the key: a settings-only multi-project root emits a junit row
|
|
1085
|
+
// and would otherwise report the greenfield `mtime: 0` forever.
|
|
1086
|
+
"settings.gradle",
|
|
1087
|
+
"settings.gradle.kts",
|
|
1088
|
+
"global.json",
|
|
1089
|
+
"Directory.Build.props"
|
|
1090
|
+
];
|
|
1091
|
+
var _DOTNET_PROJECT_EXTS = [".sln", ".slnx", ".csproj", ".fsproj", ".vbproj"];
|
|
1092
|
+
var _DOTNET_TEST_STACK = /Microsoft\.NET\.Test\.Sdk|xunit|nunit|mstest/i;
|
|
1093
|
+
var _DOTNET_SCAN_DEPTH = 2;
|
|
1094
|
+
var _DOTNET_MAX_PROJECTS = 50;
|
|
1095
|
+
var _DOTNET_SOLUTION_EXTS = [".sln", ".slnx"];
|
|
1096
|
+
function _has_dotnet_solution(dir) {
|
|
1097
|
+
try {
|
|
1098
|
+
return fs3.readdirSync(dir, { withFileTypes: true }).some((e) => e.isFile() && _DOTNET_SOLUTION_EXTS.includes(path3.extname(e.name).toLowerCase()));
|
|
1099
|
+
} catch {
|
|
1100
|
+
return false;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
var _BEHAVIOR_MARKERS = [
|
|
1104
|
+
"pnpm-workspace.yaml",
|
|
1105
|
+
"requirements.txt",
|
|
1106
|
+
"gems.rb",
|
|
1107
|
+
".rspec",
|
|
1108
|
+
"behat.yml",
|
|
1109
|
+
"behat.yaml",
|
|
1110
|
+
"behat.yml.dist",
|
|
1111
|
+
"behat.dist.yml",
|
|
1112
|
+
"cucumber.js",
|
|
1113
|
+
"cucumber.cjs",
|
|
1114
|
+
"cucumber.mjs",
|
|
1115
|
+
"cucumber.json"
|
|
1116
|
+
];
|
|
1117
|
+
var RunnerResult = class {
|
|
1118
|
+
ecosystem;
|
|
1119
|
+
runner;
|
|
1120
|
+
command;
|
|
1121
|
+
speed;
|
|
1122
|
+
confidence;
|
|
1123
|
+
basis;
|
|
1124
|
+
constructor(ecosystem, runner, command, speed = SPEED_FAST, confidence = HIGH, basis = "") {
|
|
1125
|
+
this.ecosystem = ecosystem;
|
|
1126
|
+
this.runner = runner;
|
|
1127
|
+
this.command = command;
|
|
1128
|
+
this.speed = speed;
|
|
1129
|
+
this.confidence = confidence;
|
|
1130
|
+
this.basis = basis;
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
1133
|
+
var BehaviorRunnerResult = class {
|
|
1134
|
+
constructor(ecosystem, runner, command, scope_root, confidence = HIGH, basis = "", conflict = []) {
|
|
1135
|
+
this.ecosystem = ecosystem;
|
|
1136
|
+
this.runner = runner;
|
|
1137
|
+
this.command = command;
|
|
1138
|
+
this.scope_root = scope_root;
|
|
1139
|
+
this.confidence = confidence;
|
|
1140
|
+
this.basis = basis;
|
|
1141
|
+
this.conflict = conflict;
|
|
1142
|
+
}
|
|
1143
|
+
ecosystem;
|
|
1144
|
+
runner;
|
|
1145
|
+
command;
|
|
1146
|
+
scope_root;
|
|
1147
|
+
confidence;
|
|
1148
|
+
basis;
|
|
1149
|
+
conflict;
|
|
1150
|
+
};
|
|
1151
|
+
var ToolchainResult = class {
|
|
1152
|
+
ecosystems;
|
|
1153
|
+
runners;
|
|
1154
|
+
selected;
|
|
1155
|
+
quality;
|
|
1156
|
+
confidence;
|
|
1157
|
+
mtime;
|
|
1158
|
+
/**
|
|
1159
|
+
* Per-scope behavior inventory — detection only, deliberately NOT merged
|
|
1160
|
+
* into `runners` nor reachable from `selected`, so nothing is ever run
|
|
1161
|
+
* BECAUSE this axis detected it.
|
|
1162
|
+
*
|
|
1163
|
+
* That is a claim about reachability, not about execution, and the two
|
|
1164
|
+
* came apart in review: for four of the eight labels the behavior command
|
|
1165
|
+
* is byte-identical to a native command already in `selected` —
|
|
1166
|
+
* `pytest-bdd`/`pytest`, `cucumber-jvm`/`jvm.command`, and both
|
|
1167
|
+
* `reqnroll` and `specflow`/`dotnet test`. Those native rows are
|
|
1168
|
+
* SPEED_FAST, so the suite does run by default; it runs as the native
|
|
1169
|
+
* runner's work, which this axis neither caused nor can prevent. Stating
|
|
1170
|
+
* it as "reported, never run" overreached in exactly the half a reader
|
|
1171
|
+
* would act on.
|
|
1172
|
+
*/
|
|
1173
|
+
behavior_runners;
|
|
1174
|
+
constructor(args) {
|
|
1175
|
+
this.ecosystems = args.ecosystems;
|
|
1176
|
+
this.runners = args.runners;
|
|
1177
|
+
this.selected = args.selected;
|
|
1178
|
+
this.quality = args.quality;
|
|
1179
|
+
this.confidence = args.confidence;
|
|
1180
|
+
this.mtime = args.mtime;
|
|
1181
|
+
this.behavior_runners = args.behavior_runners ?? [];
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Serialise to the auto-generated project-config shape.
|
|
1185
|
+
*
|
|
1186
|
+
* Written to `agents/runtime/state/toolchain.json` by
|
|
1187
|
+
* {@link write_config} so the per-stack commands are captured once and
|
|
1188
|
+
* re-read cheaply (keyed on `mtime`) instead of re-detected every turn.
|
|
1189
|
+
*/
|
|
1190
|
+
to_config() {
|
|
1191
|
+
return {
|
|
1192
|
+
ecosystems: [...this.ecosystems],
|
|
1193
|
+
confidence: this.confidence,
|
|
1194
|
+
// `mtime` is a Python float; wrap so `write_config`'s serialiser
|
|
1195
|
+
// renders an integral value as `N.0` (Python float repr).
|
|
1196
|
+
mtime: pyFloat(this.mtime),
|
|
1197
|
+
runners: this.runners.map((r) => ({
|
|
1198
|
+
ecosystem: r.ecosystem,
|
|
1199
|
+
runner: r.runner,
|
|
1200
|
+
command: r.command,
|
|
1201
|
+
speed: r.speed,
|
|
1202
|
+
confidence: r.confidence,
|
|
1203
|
+
basis: r.basis
|
|
1204
|
+
})),
|
|
1205
|
+
selected: this.selected.map((r) => r.command),
|
|
1206
|
+
quality: [...this.quality],
|
|
1207
|
+
behavior_runners: this.behavior_runners.map((r) => ({
|
|
1208
|
+
ecosystem: r.ecosystem,
|
|
1209
|
+
runner: r.runner,
|
|
1210
|
+
command: r.command,
|
|
1211
|
+
scope_root: r.scope_root,
|
|
1212
|
+
confidence: r.confidence,
|
|
1213
|
+
basis: r.basis,
|
|
1214
|
+
conflict: [...r.conflict]
|
|
1215
|
+
}))
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
};
|
|
1219
|
+
function resolve_toolchain(project_root, opts = {}) {
|
|
1220
|
+
const include_slow = opts.include_slow ?? false;
|
|
1221
|
+
const include_e2e = opts.include_e2e ?? false;
|
|
1222
|
+
const php_only = opts.php_only ?? false;
|
|
1223
|
+
const runners = [];
|
|
1224
|
+
const quality = [];
|
|
1225
|
+
const composer = _read_json(path3.join(project_root, "composer.json"));
|
|
1226
|
+
const pkg = _read_json(path3.join(project_root, "package.json"));
|
|
1227
|
+
const has_composer = _is_file(path3.join(project_root, "composer.json"));
|
|
1228
|
+
const has_package = _is_file(path3.join(project_root, "package.json"));
|
|
1229
|
+
const pyproject_text = _read_text(path3.join(project_root, "pyproject.toml"));
|
|
1230
|
+
const has_python = Boolean(pyproject_text) || _is_file(path3.join(project_root, "requirements.txt")) || _is_file(path3.join(project_root, "setup.cfg")) || _is_file(path3.join(project_root, "pytest.ini"));
|
|
1231
|
+
const has_go = _is_file(path3.join(project_root, "go.mod"));
|
|
1232
|
+
const has_cargo = _is_file(path3.join(project_root, "Cargo.toml"));
|
|
1233
|
+
const gemfile_text = _ruby_gemfile_text(project_root);
|
|
1234
|
+
const jvm = _jvm_build(project_root);
|
|
1235
|
+
const dotnet_basis = _dotnet_basis(project_root);
|
|
1236
|
+
const wrappers = _task_runner_wrappers(project_root, pkg);
|
|
1237
|
+
if (has_composer) {
|
|
1238
|
+
runners.push(..._php_runners(project_root, composer, wrappers));
|
|
1239
|
+
quality.push(..._php_quality(project_root, composer, wrappers));
|
|
1240
|
+
}
|
|
1241
|
+
if (has_package) {
|
|
1242
|
+
runners.push(..._js_runners(pkg, wrappers, _package_manager(project_root, pkg)));
|
|
1243
|
+
quality.push(..._js_quality(pkg, wrappers));
|
|
1244
|
+
}
|
|
1245
|
+
if (has_python) {
|
|
1246
|
+
runners.push(..._python_runners(pyproject_text));
|
|
1247
|
+
quality.push(..._python_quality(pyproject_text));
|
|
1248
|
+
}
|
|
1249
|
+
if (has_go) {
|
|
1250
|
+
runners.push(
|
|
1251
|
+
new RunnerResult("go", "go-test", "go test ./...", SPEED_FAST, HIGH, "go.mod present")
|
|
1252
|
+
);
|
|
1253
|
+
quality.push("go vet ./...");
|
|
1254
|
+
}
|
|
1255
|
+
if (has_cargo) {
|
|
1256
|
+
runners.push(
|
|
1257
|
+
new RunnerResult("rust", "cargo-test", "cargo test", SPEED_FAST, HIGH, "Cargo.toml present")
|
|
1258
|
+
);
|
|
1259
|
+
quality.push("cargo clippy");
|
|
1260
|
+
}
|
|
1261
|
+
runners.push(..._ruby_runners(project_root, gemfile_text));
|
|
1262
|
+
if (jvm !== null) {
|
|
1263
|
+
runners.push(..._jvm_runners(jvm));
|
|
1264
|
+
}
|
|
1265
|
+
if (dotnet_basis !== null) {
|
|
1266
|
+
runners.push(..._dotnet_runners(dotnet_basis));
|
|
1267
|
+
}
|
|
1268
|
+
const ecosystems = _dictFromKeys(runners.map((r) => r.ecosystem));
|
|
1269
|
+
const selected = _apply_guard(runners, { include_slow, include_e2e, php_only });
|
|
1270
|
+
const confidence = _overall_confidence(runners);
|
|
1271
|
+
const scopes = _behavior_scopes(project_root);
|
|
1272
|
+
return new ToolchainResult({
|
|
1273
|
+
ecosystems,
|
|
1274
|
+
runners: [...runners],
|
|
1275
|
+
selected: [...selected],
|
|
1276
|
+
quality: _dictFromKeys(quality),
|
|
1277
|
+
confidence,
|
|
1278
|
+
// Computed ONCE and threaded into both consumers: workspace discovery
|
|
1279
|
+
// reads a manifest and lists per glob parent, and both the cache probe
|
|
1280
|
+
// and the behavior axis need the same list.
|
|
1281
|
+
mtime: latest_manifest_mtime(project_root, scopes),
|
|
1282
|
+
behavior_runners: resolve_behavior_runners(project_root, scopes)
|
|
1283
|
+
});
|
|
1284
|
+
}
|
|
1285
|
+
function latest_manifest_mtime(project_root, scopes) {
|
|
1286
|
+
const mtimes = [];
|
|
1287
|
+
const names = [..._MANIFESTS, ..._BEHAVIOR_MARKERS];
|
|
1288
|
+
for (const scope of scopes ?? _behavior_scopes(project_root)) {
|
|
1289
|
+
const dir = scope === "." ? project_root : path3.join(project_root, scope);
|
|
1290
|
+
for (const name of names) {
|
|
1291
|
+
const p = path3.join(dir, name);
|
|
1292
|
+
if (_is_file(p)) {
|
|
1293
|
+
mtimes.push(_stat_mtime(p));
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
return mtimes.length > 0 ? Math.max(...mtimes) : 0;
|
|
1298
|
+
}
|
|
1299
|
+
function _php_runners(root, composer, wrappers) {
|
|
1300
|
+
const deps = _all_dependencies(composer, "require", "require-dev");
|
|
1301
|
+
if ("pestphp/pest" in deps || _is_file(path3.join(root, "vendor", "bin", "pest"))) {
|
|
1302
|
+
const cmd2 = wrappers["php-test"] || "vendor/bin/pest";
|
|
1303
|
+
const basis = "pestphp/pest" in deps ? "pestphp/pest in composer require" : "vendor/bin/pest present";
|
|
1304
|
+
return [new RunnerResult("php", "pest", cmd2, SPEED_FAST, HIGH, basis)];
|
|
1305
|
+
}
|
|
1306
|
+
if (_is_file(path3.join(root, "artisan"))) {
|
|
1307
|
+
const cmd2 = wrappers["php-test"] || "php artisan test";
|
|
1308
|
+
return [new RunnerResult("php", "phpunit", cmd2, SPEED_FAST, HIGH, "artisan present (Laravel)")];
|
|
1309
|
+
}
|
|
1310
|
+
if ("phpunit/phpunit" in deps || _is_file(path3.join(root, "vendor", "bin", "phpunit"))) {
|
|
1311
|
+
const cmd2 = wrappers["php-test"] || "vendor/bin/phpunit";
|
|
1312
|
+
return [new RunnerResult("php", "phpunit", cmd2, SPEED_FAST, HIGH, "phpunit/phpunit detected")];
|
|
1313
|
+
}
|
|
1314
|
+
const cmd = wrappers["php-test"] || "vendor/bin/phpunit";
|
|
1315
|
+
return [
|
|
1316
|
+
new RunnerResult(
|
|
1317
|
+
"php",
|
|
1318
|
+
"phpunit",
|
|
1319
|
+
cmd,
|
|
1320
|
+
SPEED_FAST,
|
|
1321
|
+
MEDIUM,
|
|
1322
|
+
"composer.json present, no explicit runner"
|
|
1323
|
+
)
|
|
1324
|
+
];
|
|
1325
|
+
}
|
|
1326
|
+
function _js_runners(pkg, wrappers, manager) {
|
|
1327
|
+
const deps = _all_dependencies(
|
|
1328
|
+
pkg,
|
|
1329
|
+
"dependencies",
|
|
1330
|
+
"devDependencies",
|
|
1331
|
+
"peerDependencies",
|
|
1332
|
+
"optionalDependencies"
|
|
1333
|
+
);
|
|
1334
|
+
const scripts = _isDict(pkg.scripts) ? pkg.scripts : {};
|
|
1335
|
+
const pm_test = wrappers["js-test"];
|
|
1336
|
+
const out = [];
|
|
1337
|
+
if ("vitest" in deps) {
|
|
1338
|
+
const cmd = pm_test && _script_uses(scripts, "test", "vitest") ? pm_test : "npx vitest run";
|
|
1339
|
+
out.push(new RunnerResult("js", "vitest", cmd, SPEED_FAST, HIGH, "vitest in package deps"));
|
|
1340
|
+
} else if ("jest" in deps) {
|
|
1341
|
+
const cmd = pm_test && _script_uses(scripts, "test", "jest") ? pm_test : "npx jest";
|
|
1342
|
+
out.push(new RunnerResult("js", "jest", cmd, SPEED_FAST, HIGH, "jest in package deps"));
|
|
1343
|
+
} else if (pm_test) {
|
|
1344
|
+
out.push(
|
|
1345
|
+
new RunnerResult(
|
|
1346
|
+
"js",
|
|
1347
|
+
"jest",
|
|
1348
|
+
pm_test,
|
|
1349
|
+
SPEED_FAST,
|
|
1350
|
+
MEDIUM,
|
|
1351
|
+
"package.json test script, runner unclear"
|
|
1352
|
+
)
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1355
|
+
if ("@playwright/test" in deps || "playwright" in deps) {
|
|
1356
|
+
const cmd = _script_command(scripts, ["test:e2e", "e2e", "playwright"], manager) || "npx playwright test";
|
|
1357
|
+
out.push(
|
|
1358
|
+
new RunnerResult("js", "playwright", cmd, SPEED_E2E, HIGH, "@playwright/test in package deps")
|
|
1359
|
+
);
|
|
1360
|
+
} else if ("cypress" in deps) {
|
|
1361
|
+
const cmd = _script_command(scripts, ["test:e2e", "e2e", "cypress"], manager) || "npx cypress run";
|
|
1362
|
+
out.push(new RunnerResult("js", "cypress", cmd, SPEED_E2E, HIGH, "cypress in package deps"));
|
|
1363
|
+
}
|
|
1364
|
+
const slow_cmd = _script_command(scripts, ["test:slow", "test:integration"], manager);
|
|
1365
|
+
if (slow_cmd) {
|
|
1366
|
+
out.push(
|
|
1367
|
+
new RunnerResult(
|
|
1368
|
+
"js",
|
|
1369
|
+
"vitest" in deps ? "vitest" : "jest",
|
|
1370
|
+
slow_cmd,
|
|
1371
|
+
SPEED_SLOW,
|
|
1372
|
+
MEDIUM,
|
|
1373
|
+
"test:slow/integration script"
|
|
1374
|
+
)
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
return out;
|
|
1378
|
+
}
|
|
1379
|
+
function _python_runners(pyproject_text) {
|
|
1380
|
+
if (pyproject_text.includes("pytest") || pyproject_text === "") {
|
|
1381
|
+
const conf = pyproject_text.includes("pytest") ? HIGH : MEDIUM;
|
|
1382
|
+
const basis = pyproject_text.includes("pytest") ? "pytest in pyproject" : "python project, no explicit runner";
|
|
1383
|
+
return [new RunnerResult("python", "pytest", "pytest", SPEED_FAST, conf, basis)];
|
|
1384
|
+
}
|
|
1385
|
+
return [
|
|
1386
|
+
new RunnerResult("python", "pytest", "pytest", SPEED_FAST, MEDIUM, "python project, no explicit runner")
|
|
1387
|
+
];
|
|
1388
|
+
}
|
|
1389
|
+
function _ruby_gemfile_text(dir) {
|
|
1390
|
+
let present = false;
|
|
1391
|
+
for (const name of ["Gemfile", "gems.rb"]) {
|
|
1392
|
+
const full = path3.join(dir, name);
|
|
1393
|
+
present = present || _is_file(full);
|
|
1394
|
+
const text = _read_text(full);
|
|
1395
|
+
if (text !== "") {
|
|
1396
|
+
return { text, file: name, present: true };
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
return { text: "", file: "", present };
|
|
1400
|
+
}
|
|
1401
|
+
function _gem_declared(text, gem) {
|
|
1402
|
+
return new RegExp(`^[ \\t]*gem[ \\t]+["']${gem}(-[A-Za-z0-9_-]+)?["']`, "m").test(text);
|
|
1403
|
+
}
|
|
1404
|
+
function _ruby_runners(root, gemfile) {
|
|
1405
|
+
const dot_rspec = _is_file(path3.join(root, ".rspec"));
|
|
1406
|
+
const spec_helper = _is_file(path3.join(root, "spec", "spec_helper.rb"));
|
|
1407
|
+
const in_gemfile = _gem_declared(gemfile.text, "rspec");
|
|
1408
|
+
if (!in_gemfile && !dot_rspec && !spec_helper) {
|
|
1409
|
+
return [];
|
|
1410
|
+
}
|
|
1411
|
+
const basis = in_gemfile ? `rspec in ${gemfile.file}` : dot_rspec ? ".rspec present" : "spec/spec_helper.rb present";
|
|
1412
|
+
const cmd = gemfile.present ? "bundle exec rspec" : "rspec";
|
|
1413
|
+
return [new RunnerResult("ruby", "rspec", cmd, SPEED_FAST, HIGH, basis)];
|
|
1414
|
+
}
|
|
1415
|
+
function _jvm_build(root) {
|
|
1416
|
+
const pom = _read_text(path3.join(root, "pom.xml"));
|
|
1417
|
+
if (pom !== "") {
|
|
1418
|
+
const wrapper = _is_file(path3.join(root, "mvnw"));
|
|
1419
|
+
return { tool: "maven", text: pom, command: wrapper ? "./mvnw test" : "mvn test" };
|
|
1420
|
+
}
|
|
1421
|
+
const gradleFile = ["build.gradle", "build.gradle.kts", "settings.gradle", "settings.gradle.kts"].find(
|
|
1422
|
+
(n) => _is_file(path3.join(root, n))
|
|
1423
|
+
);
|
|
1424
|
+
if (gradleFile !== void 0) {
|
|
1425
|
+
const wrapper = _is_file(path3.join(root, "gradlew"));
|
|
1426
|
+
return {
|
|
1427
|
+
tool: "gradle",
|
|
1428
|
+
text: _read_text(path3.join(root, "build.gradle")) + "\n" + _read_text(path3.join(root, "build.gradle.kts")),
|
|
1429
|
+
command: wrapper ? "./gradlew test" : "gradle test"
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
return null;
|
|
1433
|
+
}
|
|
1434
|
+
function _jvm_runners(build) {
|
|
1435
|
+
const named = /\bjunit\b/i.test(build.text);
|
|
1436
|
+
const conf = named ? HIGH : MEDIUM;
|
|
1437
|
+
const basis = named ? `junit in the ${build.tool} build file` : `${build.tool} build file present, no explicit runner`;
|
|
1438
|
+
return [new RunnerResult("jvm", "junit", build.command, SPEED_FAST, conf, basis)];
|
|
1439
|
+
}
|
|
1440
|
+
function _dotnet_basis(root) {
|
|
1441
|
+
let names;
|
|
1442
|
+
try {
|
|
1443
|
+
names = fs3.readdirSync(root, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name).sort();
|
|
1444
|
+
} catch {
|
|
1445
|
+
names = [];
|
|
1446
|
+
}
|
|
1447
|
+
const target = names.find((n) => _DOTNET_PROJECT_EXTS.includes(path3.extname(n).toLowerCase()));
|
|
1448
|
+
if (target === void 0) {
|
|
1449
|
+
return null;
|
|
1450
|
+
}
|
|
1451
|
+
return _DOTNET_TEST_STACK.test(_dotnet_project_text(root)) ? { basis: "test stack named in a .NET project file", confidence: HIGH } : { basis: `${target} present, no test stack named`, confidence: MEDIUM };
|
|
1452
|
+
}
|
|
1453
|
+
function _dotnet_runners(found) {
|
|
1454
|
+
return [
|
|
1455
|
+
new RunnerResult(
|
|
1456
|
+
"dotnet",
|
|
1457
|
+
"dotnet-test",
|
|
1458
|
+
"dotnet test",
|
|
1459
|
+
SPEED_FAST,
|
|
1460
|
+
found.confidence,
|
|
1461
|
+
found.basis
|
|
1462
|
+
)
|
|
1463
|
+
];
|
|
1464
|
+
}
|
|
1465
|
+
function resolve_behavior_runners(project_root, scopes) {
|
|
1466
|
+
const out = [];
|
|
1467
|
+
for (const scope of scopes ?? _behavior_scopes(project_root)) {
|
|
1468
|
+
const dir = scope === "." ? project_root : path3.join(project_root, scope);
|
|
1469
|
+
const rows = _behavior_runners_in_scope(dir, scope);
|
|
1470
|
+
for (const ecosystem of _dictFromKeys(rows.map((r) => r.ecosystem))) {
|
|
1471
|
+
const found = rows.filter((r) => r.ecosystem === ecosystem);
|
|
1472
|
+
const names = [...new Set(found.map((r) => r.runner))].sort();
|
|
1473
|
+
if (names.length <= 1) {
|
|
1474
|
+
out.push(found[0]);
|
|
1475
|
+
continue;
|
|
1476
|
+
}
|
|
1477
|
+
out.push(
|
|
1478
|
+
new BehaviorRunnerResult(
|
|
1479
|
+
ecosystem,
|
|
1480
|
+
BEHAVIOR_UNKNOWN,
|
|
1481
|
+
"",
|
|
1482
|
+
scope,
|
|
1483
|
+
LOW,
|
|
1484
|
+
`two ${ecosystem} behavior runners in one scope (${names.join(", ")}) \u2014 refusing to choose, as the frontend detector refuses between mutually exclusive workspaces`,
|
|
1485
|
+
names
|
|
1486
|
+
)
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
return out;
|
|
1491
|
+
}
|
|
1492
|
+
function _behavior_runners_in_scope(dir, scope) {
|
|
1493
|
+
const out = [];
|
|
1494
|
+
const at = (n) => path3.join(dir, n);
|
|
1495
|
+
const row = (ecosystem, runner, command, basis) => {
|
|
1496
|
+
out.push(new BehaviorRunnerResult(ecosystem, runner, command, scope, HIGH, basis));
|
|
1497
|
+
};
|
|
1498
|
+
const php_deps = _all_dependencies(_read_json(at("composer.json")), "require", "require-dev");
|
|
1499
|
+
const behat_config = ["behat.yml", "behat.yaml", "behat.yml.dist", "behat.dist.yml"].find(
|
|
1500
|
+
(n) => _is_file(at(n))
|
|
1501
|
+
);
|
|
1502
|
+
if ("behat/behat" in php_deps || behat_config !== void 0) {
|
|
1503
|
+
const declared = "behat/behat" in php_deps;
|
|
1504
|
+
const basis = declared ? "behat/behat in the composer manifest" : `${behat_config} present`;
|
|
1505
|
+
row("php", "behat", declared ? "vendor/bin/behat" : "behat", basis);
|
|
1506
|
+
}
|
|
1507
|
+
const js_deps = _all_dependencies(
|
|
1508
|
+
_read_json(at("package.json")),
|
|
1509
|
+
"dependencies",
|
|
1510
|
+
"devDependencies"
|
|
1511
|
+
);
|
|
1512
|
+
const cucumber_config = ["cucumber.js", "cucumber.cjs", "cucumber.mjs", "cucumber.json"].find(
|
|
1513
|
+
(n) => _is_file(at(n))
|
|
1514
|
+
);
|
|
1515
|
+
if ("@cucumber/cucumber" in js_deps || "cucumber" in js_deps || cucumber_config !== void 0) {
|
|
1516
|
+
const dep = "@cucumber/cucumber" in js_deps ? "@cucumber/cucumber" : "cucumber";
|
|
1517
|
+
const declared = dep in js_deps;
|
|
1518
|
+
const basis = declared ? `${dep} in package deps` : `${cucumber_config} present`;
|
|
1519
|
+
row("js", "cucumber-js", declared ? "npx cucumber-js" : "cucumber-js", basis);
|
|
1520
|
+
}
|
|
1521
|
+
const py_text = [_read_text(at("pyproject.toml")), _read_text(at("requirements.txt"))].join("\n");
|
|
1522
|
+
if (_PY_BEHAVE.test(py_text)) {
|
|
1523
|
+
row("python", "behave", "behave", "behave declared as a python dependency");
|
|
1524
|
+
}
|
|
1525
|
+
if (_PY_BDD.test(py_text)) {
|
|
1526
|
+
row("python", "pytest-bdd", "pytest", "pytest-bdd declared as a python dependency");
|
|
1527
|
+
}
|
|
1528
|
+
const gemfile = _ruby_gemfile_text(dir);
|
|
1529
|
+
const cucumber_gem = _gem_declared(gemfile.text, "cucumber");
|
|
1530
|
+
if (cucumber_gem || _is_file(path3.join(dir, "features", "support", "env.rb"))) {
|
|
1531
|
+
const basis = cucumber_gem ? `cucumber in ${gemfile.file}` : "features/support/env.rb present";
|
|
1532
|
+
const cmd = gemfile.present ? "bundle exec cucumber" : "cucumber";
|
|
1533
|
+
row("ruby", "cucumber-ruby", cmd, basis);
|
|
1534
|
+
}
|
|
1535
|
+
const jvm = _jvm_build(dir);
|
|
1536
|
+
if (jvm !== null && /io\.cucumber|cucumber-java|cucumber-junit/i.test(jvm.text)) {
|
|
1537
|
+
row("jvm", "cucumber-jvm", jvm.command, `cucumber in the ${jvm.tool} build file`);
|
|
1538
|
+
}
|
|
1539
|
+
const dotnet_text = _dotnet_project_text(dir);
|
|
1540
|
+
if (/\bReqnroll\b/i.test(dotnet_text)) {
|
|
1541
|
+
row("dotnet", "reqnroll", "dotnet test", "Reqnroll in a project file");
|
|
1542
|
+
}
|
|
1543
|
+
if (/\bSpecFlow\b/i.test(dotnet_text)) {
|
|
1544
|
+
row("dotnet", "specflow", "dotnet test", "SpecFlow in a project file");
|
|
1545
|
+
}
|
|
1546
|
+
return out;
|
|
1547
|
+
}
|
|
1548
|
+
function _dotnet_project_text(dir) {
|
|
1549
|
+
const texts = [];
|
|
1550
|
+
const solution = _has_dotnet_solution(dir);
|
|
1551
|
+
const walk = (at, depth) => {
|
|
1552
|
+
if (texts.length >= _DOTNET_MAX_PROJECTS) {
|
|
1553
|
+
return;
|
|
1554
|
+
}
|
|
1555
|
+
let entries;
|
|
1556
|
+
try {
|
|
1557
|
+
entries = fs3.readdirSync(at, { withFileTypes: true });
|
|
1558
|
+
} catch {
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
1562
|
+
let hit = false;
|
|
1563
|
+
for (const e of entries) {
|
|
1564
|
+
if (!e.isFile() || !_DOTNET_PROJECT_EXTS.includes(path3.extname(e.name).toLowerCase())) {
|
|
1565
|
+
continue;
|
|
1566
|
+
}
|
|
1567
|
+
if (_DOTNET_SOLUTION_EXTS.includes(path3.extname(e.name).toLowerCase())) {
|
|
1568
|
+
continue;
|
|
1569
|
+
}
|
|
1570
|
+
if (texts.length >= _DOTNET_MAX_PROJECTS) {
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
texts.push(_read_text(path3.join(at, e.name)));
|
|
1574
|
+
hit = true;
|
|
1575
|
+
}
|
|
1576
|
+
if (hit || !solution || depth >= _DOTNET_SCAN_DEPTH) {
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
for (const e of entries) {
|
|
1580
|
+
if (e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules") {
|
|
1581
|
+
walk(path3.join(at, e.name), depth + 1);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
};
|
|
1585
|
+
walk(dir, 0);
|
|
1586
|
+
return texts.join("\n");
|
|
1587
|
+
}
|
|
1588
|
+
var _MAX_BEHAVIOR_SCOPES = 200;
|
|
1589
|
+
var _PY_BEHAVE = /^[ \t]*["']?behave["']?[ \t]*(?:$|[=<>~!,;[#])/m;
|
|
1590
|
+
var _PY_BDD = /^[ \t]*["']?pytest-bdd["']?[ \t]*(?:$|[=<>~!,;[#])/m;
|
|
1591
|
+
function _pnpm_packages(text) {
|
|
1592
|
+
const out = [];
|
|
1593
|
+
let inPackages = false;
|
|
1594
|
+
const push = (raw) => {
|
|
1595
|
+
const v = raw.replace(/\s+#.*$/, "").trim().replace(/^['"]|['"]$/g, "").trim();
|
|
1596
|
+
if (v !== "") out.push(v);
|
|
1597
|
+
};
|
|
1598
|
+
for (const line of text.split(/\r?\n/)) {
|
|
1599
|
+
if (line.trim() === "" || /^\s*#/.test(line)) {
|
|
1600
|
+
continue;
|
|
1601
|
+
}
|
|
1602
|
+
const key = /^([A-Za-z0-9_-]+)\s*:(.*)$/.exec(line);
|
|
1603
|
+
if (key) {
|
|
1604
|
+
inPackages = key[1] === "packages";
|
|
1605
|
+
const rest = (key[2] ?? "").trim();
|
|
1606
|
+
if (inPackages && rest.startsWith("[")) {
|
|
1607
|
+
for (const part of rest.replace(/^\[|\]$/g, "").split(",")) {
|
|
1608
|
+
push(part);
|
|
1609
|
+
}
|
|
1610
|
+
inPackages = false;
|
|
1611
|
+
}
|
|
1612
|
+
continue;
|
|
1613
|
+
}
|
|
1614
|
+
if (!inPackages) {
|
|
1615
|
+
continue;
|
|
1616
|
+
}
|
|
1617
|
+
const item = /^\s*-\s*(.+?)\s*$/.exec(line);
|
|
1618
|
+
if (item && item[1] !== void 0) {
|
|
1619
|
+
push(item[1]);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
return out;
|
|
1623
|
+
}
|
|
1624
|
+
function _excluded_scope(name) {
|
|
1625
|
+
return name.split("/").some((seg) => seg.startsWith(".") || seg === "node_modules");
|
|
1626
|
+
}
|
|
1627
|
+
function _behavior_scopes(project_root) {
|
|
1628
|
+
const globs = [];
|
|
1629
|
+
const pkg = _read_json(path3.join(project_root, "package.json"));
|
|
1630
|
+
const ws = pkg["workspaces"];
|
|
1631
|
+
if (Array.isArray(ws)) {
|
|
1632
|
+
globs.push(...ws.filter((g) => typeof g === "string"));
|
|
1633
|
+
} else if (_isDict(ws) && Array.isArray(ws["packages"])) {
|
|
1634
|
+
globs.push(...ws["packages"].filter((g) => typeof g === "string"));
|
|
1635
|
+
}
|
|
1636
|
+
globs.push(..._pnpm_packages(_read_text(path3.join(project_root, "pnpm-workspace.yaml"))));
|
|
1637
|
+
const scopes = ["."];
|
|
1638
|
+
for (const raw of globs) {
|
|
1639
|
+
if (scopes.length >= _MAX_BEHAVIOR_SCOPES) {
|
|
1640
|
+
break;
|
|
1641
|
+
}
|
|
1642
|
+
const g = raw.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
1643
|
+
if (g === "" || g.startsWith("!") || g.startsWith("/") || g.includes("..")) {
|
|
1644
|
+
continue;
|
|
1645
|
+
}
|
|
1646
|
+
if (_excluded_scope(g)) {
|
|
1647
|
+
continue;
|
|
1648
|
+
}
|
|
1649
|
+
if (g.endsWith("/*")) {
|
|
1650
|
+
const parent = g.slice(0, -2);
|
|
1651
|
+
let children;
|
|
1652
|
+
try {
|
|
1653
|
+
children = fs3.readdirSync(path3.join(project_root, parent), { withFileTypes: true });
|
|
1654
|
+
} catch {
|
|
1655
|
+
continue;
|
|
1656
|
+
}
|
|
1657
|
+
children.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
1658
|
+
for (const c of children) {
|
|
1659
|
+
if (scopes.length >= _MAX_BEHAVIOR_SCOPES) {
|
|
1660
|
+
break;
|
|
1661
|
+
}
|
|
1662
|
+
if (c.isDirectory() && !_excluded_scope(c.name)) {
|
|
1663
|
+
scopes.push(`${parent}/${c.name}`);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
} else if (!g.includes("*")) {
|
|
1667
|
+
try {
|
|
1668
|
+
if (fs3.lstatSync(path3.join(project_root, g)).isDirectory()) {
|
|
1669
|
+
scopes.push(g);
|
|
1670
|
+
}
|
|
1671
|
+
} catch {
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
return _dictFromKeys(scopes);
|
|
1676
|
+
}
|
|
1677
|
+
function _php_quality(root, composer, _wrappers) {
|
|
1678
|
+
const deps = _all_dependencies(composer, "require", "require-dev");
|
|
1679
|
+
const out = [];
|
|
1680
|
+
if ("phpstan/phpstan" in deps || _is_file(path3.join(root, "vendor", "bin", "phpstan"))) {
|
|
1681
|
+
out.push("vendor/bin/phpstan analyse");
|
|
1682
|
+
}
|
|
1683
|
+
if ("laravel/pint" in deps || _is_file(path3.join(root, "vendor", "bin", "pint"))) {
|
|
1684
|
+
out.push("vendor/bin/pint");
|
|
1685
|
+
}
|
|
1686
|
+
return out;
|
|
1687
|
+
}
|
|
1688
|
+
function _js_quality(pkg, _wrappers) {
|
|
1689
|
+
const deps = _all_dependencies(
|
|
1690
|
+
pkg,
|
|
1691
|
+
"dependencies",
|
|
1692
|
+
"devDependencies",
|
|
1693
|
+
"peerDependencies",
|
|
1694
|
+
"optionalDependencies"
|
|
1695
|
+
);
|
|
1696
|
+
const out = [];
|
|
1697
|
+
if ("typescript" in deps) {
|
|
1698
|
+
out.push("npx tsc --noEmit");
|
|
1699
|
+
}
|
|
1700
|
+
if ("eslint" in deps) {
|
|
1701
|
+
out.push("npx eslint .");
|
|
1702
|
+
}
|
|
1703
|
+
return out;
|
|
1704
|
+
}
|
|
1705
|
+
function _python_quality(pyproject_text) {
|
|
1706
|
+
const out = [];
|
|
1707
|
+
if (pyproject_text.includes("ruff")) {
|
|
1708
|
+
out.push("ruff check");
|
|
1709
|
+
}
|
|
1710
|
+
if (pyproject_text.includes("mypy")) {
|
|
1711
|
+
out.push("mypy .");
|
|
1712
|
+
}
|
|
1713
|
+
return out;
|
|
1714
|
+
}
|
|
1715
|
+
function _task_runner_wrappers(root, pkg) {
|
|
1716
|
+
const out = {};
|
|
1717
|
+
const makefile = _read_text(path3.join(root, "Makefile"));
|
|
1718
|
+
const taskfile = _read_text(path3.join(root, "Taskfile.yml")) || _read_text(path3.join(root, "Taskfile.yaml"));
|
|
1719
|
+
if (makefile && _reSearch(/^test\s*:/m, makefile)) {
|
|
1720
|
+
out["php-test"] = "make test";
|
|
1721
|
+
} else if (taskfile && _reSearch(/^\s*test\s*:/m, taskfile)) {
|
|
1722
|
+
out["php-test"] = "task test";
|
|
1723
|
+
}
|
|
1724
|
+
const scripts = _isDict(pkg.scripts) ? pkg.scripts : {};
|
|
1725
|
+
if (_isDict(scripts) && "test" in scripts) {
|
|
1726
|
+
out["js-test"] = `${_package_manager(root, pkg)} test`;
|
|
1727
|
+
}
|
|
1728
|
+
return out;
|
|
1729
|
+
}
|
|
1730
|
+
var LOCKFILE_MANAGERS = [
|
|
1731
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
1732
|
+
["yarn.lock", "yarn"],
|
|
1733
|
+
["bun.lock", "bun"],
|
|
1734
|
+
["bun.lockb", "bun"],
|
|
1735
|
+
["package-lock.json", "npm"]
|
|
1736
|
+
];
|
|
1737
|
+
function _resolve_package_manager(root, pkg) {
|
|
1738
|
+
const declared = pkg === void 0 ? void 0 : pkg["packageManager"];
|
|
1739
|
+
if (typeof declared === "string" && declared.trim() !== "") {
|
|
1740
|
+
const name = declared.trim().split("@")[0];
|
|
1741
|
+
if (name !== "") {
|
|
1742
|
+
return { manager: name, via: "packageManager", finding: "" };
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
const present = LOCKFILE_MANAGERS.filter(([file]) => _is_file(path3.join(root, file)));
|
|
1746
|
+
const distinct = [...new Set(present.map(([, mgr]) => mgr))];
|
|
1747
|
+
if (distinct.length > 1) {
|
|
1748
|
+
return {
|
|
1749
|
+
manager: null,
|
|
1750
|
+
via: "ambiguous",
|
|
1751
|
+
finding: `two package-manager lockfiles present (${present.map(([f]) => f).join(", ")}) and no \`packageManager\` declaration \u2014 report both and stop, per monorepo-workspace SKILL.md \xA7 1`
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
if (distinct.length === 1) {
|
|
1755
|
+
const mgr = distinct[0];
|
|
1756
|
+
return { manager: mgr, via: mgr, finding: "" };
|
|
1757
|
+
}
|
|
1758
|
+
return { manager: "npm", via: "npm", finding: "" };
|
|
1759
|
+
}
|
|
1760
|
+
function _package_manager(root, pkg) {
|
|
1761
|
+
return _resolve_package_manager(root, pkg).manager ?? "npm";
|
|
1762
|
+
}
|
|
1763
|
+
function _apply_guard(runners, opts) {
|
|
1764
|
+
const out = [];
|
|
1765
|
+
for (const r of runners) {
|
|
1766
|
+
if (opts.php_only && r.ecosystem !== "php") {
|
|
1767
|
+
continue;
|
|
1768
|
+
}
|
|
1769
|
+
if (r.speed === SPEED_E2E && !opts.include_e2e) {
|
|
1770
|
+
continue;
|
|
1771
|
+
}
|
|
1772
|
+
if (r.speed === SPEED_SLOW && !opts.include_slow) {
|
|
1773
|
+
continue;
|
|
1774
|
+
}
|
|
1775
|
+
out.push(r);
|
|
1776
|
+
}
|
|
1777
|
+
return out;
|
|
1778
|
+
}
|
|
1779
|
+
function _overall_confidence(runners) {
|
|
1780
|
+
if (runners.length === 0) {
|
|
1781
|
+
return LOW;
|
|
1782
|
+
}
|
|
1783
|
+
if (runners.some((r) => r.confidence === HIGH)) {
|
|
1784
|
+
return HIGH;
|
|
1785
|
+
}
|
|
1786
|
+
return MEDIUM;
|
|
1787
|
+
}
|
|
1788
|
+
function _read_json(p) {
|
|
1789
|
+
if (!_is_file(p)) {
|
|
1790
|
+
return {};
|
|
1791
|
+
}
|
|
1792
|
+
let payload;
|
|
1793
|
+
try {
|
|
1794
|
+
payload = JSON.parse(fs3.readFileSync(p, { encoding: "utf-8" }));
|
|
1795
|
+
} catch {
|
|
1796
|
+
return {};
|
|
1797
|
+
}
|
|
1798
|
+
return _isDict(payload) ? payload : {};
|
|
1799
|
+
}
|
|
1800
|
+
function _read_text(p) {
|
|
1801
|
+
if (!_is_file(p)) {
|
|
1802
|
+
return "";
|
|
1803
|
+
}
|
|
1804
|
+
try {
|
|
1805
|
+
return fs3.readFileSync(p, { encoding: "utf-8" });
|
|
1806
|
+
} catch {
|
|
1807
|
+
return "";
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
function _all_dependencies(manifest, ...keys) {
|
|
1811
|
+
const merged = {};
|
|
1812
|
+
for (const key of keys) {
|
|
1813
|
+
const section = manifest[key];
|
|
1814
|
+
if (_isDict(section)) {
|
|
1815
|
+
Object.assign(merged, section);
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
return merged;
|
|
1819
|
+
}
|
|
1820
|
+
function _script_uses(scripts, name, tool) {
|
|
1821
|
+
const value = scripts[name];
|
|
1822
|
+
return typeof value === "string" && value.includes(tool);
|
|
1823
|
+
}
|
|
1824
|
+
function _script_command(scripts, names, manager) {
|
|
1825
|
+
for (const name of names) {
|
|
1826
|
+
if (typeof scripts[name] === "string") {
|
|
1827
|
+
return `${manager} run ${name}`;
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
return "";
|
|
1831
|
+
}
|
|
1832
|
+
function _is_file(p) {
|
|
1833
|
+
try {
|
|
1834
|
+
return fs3.statSync(p).isFile();
|
|
1835
|
+
} catch {
|
|
1836
|
+
return false;
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
function _stat_mtime(p) {
|
|
1840
|
+
try {
|
|
1841
|
+
return Number(fs3.statSync(p, { bigint: true }).mtimeNs) / 1e9;
|
|
1842
|
+
} catch {
|
|
1843
|
+
return 0;
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
function _isDict(v) {
|
|
1847
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1848
|
+
}
|
|
1849
|
+
function _reSearch(pattern, text) {
|
|
1850
|
+
return pattern.test(text);
|
|
1851
|
+
}
|
|
1852
|
+
function _dictFromKeys(seq) {
|
|
1853
|
+
return [...new Set(seq)];
|
|
1854
|
+
}
|
|
1855
|
+
var _PyFloat = class {
|
|
1856
|
+
value;
|
|
1857
|
+
constructor(value) {
|
|
1858
|
+
this.value = value;
|
|
1859
|
+
}
|
|
1860
|
+
};
|
|
1861
|
+
function pyFloat(value) {
|
|
1862
|
+
return new _PyFloat(value);
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1032
1865
|
// src/scripts/before_complete_hook.ts
|
|
1033
|
-
var STATE_FILE =
|
|
1034
|
-
var STATE_DIR =
|
|
1866
|
+
var STATE_FILE = path4.join("agents", "state", "verify-before-complete.json");
|
|
1867
|
+
var STATE_DIR = path4.join("agents", "state", "verify-before-complete");
|
|
1035
1868
|
var STATE_RETENTION_DAYS = 7;
|
|
1036
1869
|
function statePathFor(session_id) {
|
|
1037
1870
|
return session_state_file(STATE_DIR, session_id);
|
|
@@ -1080,9 +1913,55 @@ function _empty_state() {
|
|
|
1080
1913
|
// a reader must never have to distinguish "no runs yet" from "key absent".
|
|
1081
1914
|
verification_runs: [],
|
|
1082
1915
|
edits_this_turn: 0,
|
|
1916
|
+
// NOTE — `quality_runs` and `quality_files_source` are deliberately ABSENT
|
|
1917
|
+
// here, against the convention `verification_runs` above follows.
|
|
1918
|
+
//
|
|
1919
|
+
// That convention ("present in the empty state so a reader never has to tell
|
|
1920
|
+
// 'no runs yet' from 'key absent'") is the right default and it loses to a
|
|
1921
|
+
// stronger one: the roadmap's first acceptance criterion is that with
|
|
1922
|
+
// `touched_file_quality: off` the record is BYTE-IDENTICAL to the base ref.
|
|
1923
|
+
// Seeding two keys would break that for every install that never opted in,
|
|
1924
|
+
// and "the default-off feature changed my state file" is exactly the kind of
|
|
1925
|
+
// silent shape drift a consumer cannot audit. The convention's cost here is
|
|
1926
|
+
// zero, because the only reader of these keys is 2.1's analysis, which counts
|
|
1927
|
+
// stops — and a stop with no key is a stop where the pass did not run, which
|
|
1928
|
+
// is a distinction it needs rather than one it must not make.
|
|
1083
1929
|
checked_at: _now()
|
|
1084
1930
|
};
|
|
1085
1931
|
}
|
|
1932
|
+
function readTouchedFileQualityMode(consumer_root) {
|
|
1933
|
+
const raw = hookSectionValue(consumer_root, "verify_before_complete", "touched_file_quality");
|
|
1934
|
+
return raw === "shadow" || raw === "warn" ? raw : "off";
|
|
1935
|
+
}
|
|
1936
|
+
function collectTouchedFileQuality(consumer_root, session_id) {
|
|
1937
|
+
let touched;
|
|
1938
|
+
try {
|
|
1939
|
+
touched = touchedFilesAtStop(consumer_root, session_id);
|
|
1940
|
+
} catch {
|
|
1941
|
+
return null;
|
|
1942
|
+
}
|
|
1943
|
+
if (touched.files.length === 0) {
|
|
1944
|
+
return { runs: [], source: touched.source };
|
|
1945
|
+
}
|
|
1946
|
+
let commands = [];
|
|
1947
|
+
try {
|
|
1948
|
+
commands = resolve_toolchain(consumer_root).quality;
|
|
1949
|
+
} catch {
|
|
1950
|
+
commands = [];
|
|
1951
|
+
}
|
|
1952
|
+
try {
|
|
1953
|
+
return {
|
|
1954
|
+
runs: runTouchedFileQuality({
|
|
1955
|
+
root: consumer_root,
|
|
1956
|
+
commands,
|
|
1957
|
+
files: touched.files
|
|
1958
|
+
}),
|
|
1959
|
+
source: touched.source
|
|
1960
|
+
};
|
|
1961
|
+
} catch {
|
|
1962
|
+
return null;
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1086
1965
|
function _extract_command(payload) {
|
|
1087
1966
|
const toolRaw = payload["tool_name"] || payload["toolName"] || payload["tool"];
|
|
1088
1967
|
if (!(typeof toolRaw === "string" && COMMAND_TOOLS.has(toolRaw))) {
|
|
@@ -1195,6 +2074,7 @@ function _cap_runs(runs) {
|
|
|
1195
2074
|
return [head, ...tail];
|
|
1196
2075
|
}
|
|
1197
2076
|
var _ERROR_EXIT_PREFIX = /^Error:\s*Exit code\s*(\d+)/i;
|
|
2077
|
+
var _BARE_EXIT_PREFIX = /^\s*Exit code\s*(\d+)/i;
|
|
1198
2078
|
var _NO_EXIT = { code: null, source: null, interrupted: false };
|
|
1199
2079
|
function _numeric_exit_field(obj) {
|
|
1200
2080
|
for (const key of ["exit_code", "exitCode", "returncode", "returnCode", "status_code"]) {
|
|
@@ -1206,26 +2086,46 @@ function _numeric_exit_field(obj) {
|
|
|
1206
2086
|
}
|
|
1207
2087
|
return null;
|
|
1208
2088
|
}
|
|
1209
|
-
|
|
2089
|
+
var FAILURE_EVENT_NAMES = /* @__PURE__ */ new Set(["PostToolUseFailure"]);
|
|
2090
|
+
function _is_failure_event(payload, envelope) {
|
|
2091
|
+
for (const v of [payload["hook_event_name"], envelope["native_event"]]) {
|
|
2092
|
+
if (typeof v !== "string" || v === "") continue;
|
|
2093
|
+
if (FAILURE_EVENT_NAMES.has(v)) return true;
|
|
2094
|
+
if (/failure$/i.test(v)) return true;
|
|
2095
|
+
}
|
|
2096
|
+
return false;
|
|
2097
|
+
}
|
|
2098
|
+
function _extract_exit_reading(payload, failure_event = false) {
|
|
2099
|
+
const seal = (r) => failure_event && r.code === 0 ? { code: null, source: null, interrupted: r.interrupted } : r;
|
|
1210
2100
|
const direct = _numeric_exit_field(payload);
|
|
1211
|
-
if (direct !== null) return { code: direct, source: "field", interrupted: false };
|
|
2101
|
+
if (direct !== null) return seal({ code: direct, source: "field", interrupted: false });
|
|
2102
|
+
const interrupted_top = payload["is_interrupt"] === true;
|
|
2103
|
+
const err = payload["error"];
|
|
2104
|
+
if (typeof err === "string") {
|
|
2105
|
+
if (interrupted_top) return { code: null, source: null, interrupted: true };
|
|
2106
|
+
const bare = _BARE_EXIT_PREFIX.exec(err) ?? _ERROR_EXIT_PREFIX.exec(err);
|
|
2107
|
+
if (bare?.[1] !== void 0) {
|
|
2108
|
+
return seal({ code: Number(bare[1]), source: "error_prefix", interrupted: false });
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
if (interrupted_top) return { code: null, source: null, interrupted: true };
|
|
1212
2112
|
for (const key of ["tool_response", "toolResponse", "result", "output"]) {
|
|
1213
2113
|
const v = payload[key];
|
|
1214
2114
|
if (typeof v === "string") {
|
|
1215
2115
|
const m = _ERROR_EXIT_PREFIX.exec(v);
|
|
1216
2116
|
if (m?.[1] !== void 0) {
|
|
1217
|
-
return { code: Number(m[1]), source: "error_prefix", interrupted: false };
|
|
2117
|
+
return seal({ code: Number(m[1]), source: "error_prefix", interrupted: false });
|
|
1218
2118
|
}
|
|
1219
2119
|
continue;
|
|
1220
2120
|
}
|
|
1221
2121
|
if (v === null || typeof v !== "object" || Array.isArray(v)) continue;
|
|
1222
2122
|
const obj = v;
|
|
1223
2123
|
const nested = _numeric_exit_field(obj);
|
|
1224
|
-
if (nested !== null) return { code: nested, source: "field", interrupted: false };
|
|
2124
|
+
if (nested !== null) return seal({ code: nested, source: "field", interrupted: false });
|
|
1225
2125
|
if (obj["interrupted"] === true) return { code: null, source: null, interrupted: true };
|
|
1226
2126
|
const hasStream = typeof obj["stdout"] === "string" || typeof obj["stderr"] === "string";
|
|
1227
2127
|
if (obj["interrupted"] === false && hasStream) {
|
|
1228
|
-
return { code: 0, source: "response_shape", interrupted: false };
|
|
2128
|
+
return seal({ code: 0, source: "response_shape", interrupted: false });
|
|
1229
2129
|
}
|
|
1230
2130
|
}
|
|
1231
2131
|
return _NO_EXIT;
|
|
@@ -1247,10 +2147,15 @@ function _extract_run_streams(payload) {
|
|
|
1247
2147
|
}
|
|
1248
2148
|
const topOut = payload["stdout"] ?? payload["output"];
|
|
1249
2149
|
const topErr = payload["stderr"];
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
2150
|
+
if (typeof topOut === "string" || typeof topErr === "string") {
|
|
2151
|
+
return {
|
|
2152
|
+
stdout: typeof topOut === "string" ? topOut : "",
|
|
2153
|
+
stderr: typeof topErr === "string" ? topErr : ""
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
const errText = payload["error"];
|
|
2157
|
+
if (typeof errText === "string") return { stdout: errText, stderr: "" };
|
|
2158
|
+
return { stdout: "", stderr: "" };
|
|
1254
2159
|
}
|
|
1255
2160
|
function _reset_turn(state, session_id) {
|
|
1256
2161
|
state["session_id"] = session_id || state["session_id"] || "";
|
|
@@ -1259,6 +2164,8 @@ function _reset_turn(state, session_id) {
|
|
|
1259
2164
|
state["verified_this_turn"] = false;
|
|
1260
2165
|
state["verification_runs"] = [];
|
|
1261
2166
|
state["edits_this_turn"] = 0;
|
|
2167
|
+
delete state["quality_runs"];
|
|
2168
|
+
delete state["quality_files_source"];
|
|
1262
2169
|
state["ci_saw_pending"] = false;
|
|
1263
2170
|
state["nonevidence_this_turn"] = 0;
|
|
1264
2171
|
return state;
|
|
@@ -1273,7 +2180,7 @@ function _asInt(v) {
|
|
|
1273
2180
|
const n = Number(v);
|
|
1274
2181
|
return Number.isFinite(n) ? Math.trunc(n) : 0;
|
|
1275
2182
|
}
|
|
1276
|
-
function _update(state, event, envelope) {
|
|
2183
|
+
function _update(state, event, envelope, quality = null) {
|
|
1277
2184
|
const session_id = envelope["session_id"] || state["session_id"] || "";
|
|
1278
2185
|
if (session_id && session_id !== state["session_id"]) {
|
|
1279
2186
|
state["session_id"] = session_id;
|
|
@@ -1281,6 +2188,8 @@ function _update(state, event, envelope) {
|
|
|
1281
2188
|
state["ci_last"] = null;
|
|
1282
2189
|
state = _reset_turn(state, session_id);
|
|
1283
2190
|
}
|
|
2191
|
+
const platform = envelope["platform"];
|
|
2192
|
+
if (typeof platform === "string" && platform) state["platform"] = platform;
|
|
1284
2193
|
let payload = envelope["payload"];
|
|
1285
2194
|
if (!(typeof payload === "object" && payload !== null && !Array.isArray(payload))) {
|
|
1286
2195
|
payload = {};
|
|
@@ -1332,7 +2241,7 @@ function _update(state, event, envelope) {
|
|
|
1332
2241
|
}
|
|
1333
2242
|
if (cmd && mightBeVerification(cmd)) {
|
|
1334
2243
|
const streams = _extract_run_streams(pl);
|
|
1335
|
-
const exit = _extract_exit_reading(pl);
|
|
2244
|
+
const exit = _extract_exit_reading(pl, _is_failure_event(pl, envelope));
|
|
1336
2245
|
const runs = Array.isArray(state["verification_runs"]) ? [...state["verification_runs"]] : [];
|
|
1337
2246
|
runs.push({
|
|
1338
2247
|
command: cmd.slice(0, 512),
|
|
@@ -1356,6 +2265,10 @@ function _update(state, event, envelope) {
|
|
|
1356
2265
|
}
|
|
1357
2266
|
} else if (event === "stop") {
|
|
1358
2267
|
state["last_stop_at"] = _now();
|
|
2268
|
+
if (quality !== null) {
|
|
2269
|
+
state["quality_runs"] = quality.runs;
|
|
2270
|
+
state["quality_files_source"] = quality.source;
|
|
2271
|
+
}
|
|
1359
2272
|
}
|
|
1360
2273
|
state["checked_at"] = _now();
|
|
1361
2274
|
return state;
|
|
@@ -1384,10 +2297,12 @@ function run(stdin_text, options) {
|
|
|
1384
2297
|
}
|
|
1385
2298
|
return 0;
|
|
1386
2299
|
}
|
|
1387
|
-
const target =
|
|
2300
|
+
const target = path4.join(consumer_root, statePathFor(session_id));
|
|
2301
|
+
const quality_mode = event === "stop" ? readTouchedFileQualityMode(consumer_root) : "off";
|
|
2302
|
+
const quality = quality_mode === "off" ? null : collectTouchedFileQuality(consumer_root, session_id);
|
|
1388
2303
|
let state = {};
|
|
1389
2304
|
const outcome = update_json_under_lock(target, (loaded) => {
|
|
1390
|
-
state = _update({ ..._empty_state(), ...loaded }, event, envelope);
|
|
2305
|
+
state = _update({ ..._empty_state(), ...loaded }, event, envelope, quality);
|
|
1391
2306
|
return state;
|
|
1392
2307
|
});
|
|
1393
2308
|
if (outcome === "failed") {
|
|
@@ -1397,13 +2312,20 @@ function run(stdin_text, options) {
|
|
|
1397
2312
|
return 0;
|
|
1398
2313
|
}
|
|
1399
2314
|
if (event === "session_start" || event === "user_prompt_submit") {
|
|
1400
|
-
prune_legacy_state_file(
|
|
2315
|
+
prune_legacy_state_file(path4.join(consumer_root, STATE_FILE));
|
|
1401
2316
|
prune_stale_session_states(
|
|
1402
|
-
|
|
2317
|
+
path4.join(consumer_root, STATE_DIR),
|
|
1403
2318
|
Date.now(),
|
|
1404
2319
|
STATE_RETENTION_DAYS
|
|
1405
2320
|
);
|
|
1406
2321
|
}
|
|
2322
|
+
if (quality_mode === "warn" && quality !== null) {
|
|
2323
|
+
const line = advisoryLine(quality.runs);
|
|
2324
|
+
if (line !== null) {
|
|
2325
|
+
process.stderr.write(`${line}
|
|
2326
|
+
`);
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
1407
2329
|
if (verbose) {
|
|
1408
2330
|
process.stderr.write(
|
|
1409
2331
|
`verify-before-complete-hook: event=${event} verified_this_turn=${pyRepr(state["verified_this_turn"])} verifications_this_turn=${pyRepr(state["verifications_this_turn"])}
|
|
@@ -1447,7 +2369,7 @@ function main(argv) {
|
|
|
1447
2369
|
function readTurnRunState(workspaceRoot, session_id) {
|
|
1448
2370
|
if (!has_stable_session_id(session_id)) return null;
|
|
1449
2371
|
try {
|
|
1450
|
-
const raw =
|
|
2372
|
+
const raw = fs4.readFileSync(path4.join(workspaceRoot, statePathFor(session_id)), "utf-8");
|
|
1451
2373
|
const decoded = JSON.parse(raw);
|
|
1452
2374
|
if (!owns_session_state(decoded, session_id)) return null;
|
|
1453
2375
|
if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
|
|
@@ -1468,13 +2390,13 @@ function _isCliEntry() {
|
|
|
1468
2390
|
if (process.argv[1] === void 0) {
|
|
1469
2391
|
return false;
|
|
1470
2392
|
}
|
|
1471
|
-
const argvUrl = pathToFileURL(
|
|
2393
|
+
const argvUrl = pathToFileURL(path4.resolve(process.argv[1])).href;
|
|
1472
2394
|
if (import.meta.url === argvUrl) {
|
|
1473
2395
|
return true;
|
|
1474
2396
|
}
|
|
1475
2397
|
try {
|
|
1476
|
-
const here =
|
|
1477
|
-
const argv =
|
|
2398
|
+
const here = fs4.realpathSync(fileURLToPath(import.meta.url));
|
|
2399
|
+
const argv = fs4.realpathSync(path4.resolve(process.argv[1]));
|
|
1478
2400
|
return here === argv;
|
|
1479
2401
|
} catch {
|
|
1480
2402
|
return false;
|
|
@@ -1487,8 +2409,8 @@ if (isCliEntry) {
|
|
|
1487
2409
|
|
|
1488
2410
|
// src/scripts/_lib/turn_end_refusals.ts
|
|
1489
2411
|
import * as crypto from "node:crypto";
|
|
1490
|
-
import * as
|
|
1491
|
-
import * as
|
|
2412
|
+
import * as fs5 from "node:fs";
|
|
2413
|
+
import * as path5 from "node:path";
|
|
1492
2414
|
var DETECTOR_IDS = [
|
|
1493
2415
|
"promissory",
|
|
1494
2416
|
"language",
|
|
@@ -1497,19 +2419,19 @@ var DETECTOR_IDS = [
|
|
|
1497
2419
|
"pending-decision",
|
|
1498
2420
|
"untested"
|
|
1499
2421
|
];
|
|
1500
|
-
var REFUSAL_STATE_REL =
|
|
2422
|
+
var REFUSAL_STATE_REL = path5.join("agents", "runtime", "state", "turn-end-gate");
|
|
1501
2423
|
function refusalStateDir(workspaceRoot) {
|
|
1502
|
-
return
|
|
2424
|
+
return path5.join(workspaceRoot, REFUSAL_STATE_REL);
|
|
1503
2425
|
}
|
|
1504
2426
|
function deriveSessionKey(sessionId) {
|
|
1505
2427
|
return crypto.createHash("sha256").update(sessionId).digest("hex").slice(0, 32);
|
|
1506
2428
|
}
|
|
1507
2429
|
function sessionRefusalFile(workspaceRoot, sessionKey) {
|
|
1508
|
-
return
|
|
2430
|
+
return path5.join(refusalStateDir(workspaceRoot), `${sessionKey}.json`);
|
|
1509
2431
|
}
|
|
1510
2432
|
function readSessionCounts(workspaceRoot, sessionId) {
|
|
1511
2433
|
try {
|
|
1512
|
-
const raw =
|
|
2434
|
+
const raw = fs5.readFileSync(
|
|
1513
2435
|
sessionRefusalFile(workspaceRoot, deriveSessionKey(sessionId)),
|
|
1514
2436
|
"utf-8"
|
|
1515
2437
|
);
|
|
@@ -1620,7 +2542,7 @@ function emptyRetryCounts() {
|
|
|
1620
2542
|
var SHADOW_MAX_ROWS = 200;
|
|
1621
2543
|
var SHADOW_SUFFIX = ".shadow.json";
|
|
1622
2544
|
function sessionShadowFile(workspaceRoot, sessionKey) {
|
|
1623
|
-
return
|
|
2545
|
+
return path5.join(refusalStateDir(workspaceRoot), `${sessionKey}${SHADOW_SUFFIX}`);
|
|
1624
2546
|
}
|
|
1625
2547
|
function parseShadowRecord(raw) {
|
|
1626
2548
|
let decoded;
|
|
@@ -1645,7 +2567,9 @@ function parseShadowRecord(raw) {
|
|
|
1645
2567
|
if (typeof turn !== "number" || !Number.isFinite(turn)) continue;
|
|
1646
2568
|
if (typeof at !== "string") continue;
|
|
1647
2569
|
if (layer !== "stop_hook_active" && layer !== "refused_turn") continue;
|
|
1648
|
-
|
|
2570
|
+
const setByRaw = ro["set_by"];
|
|
2571
|
+
const set_by = setByRaw === "ours" || setByRaw === "foreign" || setByRaw === "unknown" ? setByRaw : "unknown";
|
|
2572
|
+
rows.push({ detector: det, turn, at, layer, set_by });
|
|
1649
2573
|
}
|
|
1650
2574
|
const num = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
1651
2575
|
const observed = o["retries_observed"];
|
|
@@ -1665,8 +2589,15 @@ function parseShadowRecord(raw) {
|
|
|
1665
2589
|
}
|
|
1666
2590
|
function foldShadow(prev, input) {
|
|
1667
2591
|
const rows = [...prev?.would_refuse_again ?? []];
|
|
2592
|
+
const set_by = input.setBy ?? "unknown";
|
|
1668
2593
|
for (const d of input.detectors) {
|
|
1669
|
-
rows.push({
|
|
2594
|
+
rows.push({
|
|
2595
|
+
detector: d,
|
|
2596
|
+
turn: input.turnOrdinal,
|
|
2597
|
+
at: input.at,
|
|
2598
|
+
layer: input.layer,
|
|
2599
|
+
set_by
|
|
2600
|
+
});
|
|
1670
2601
|
}
|
|
1671
2602
|
let dropped = prev?.dropped ?? 0;
|
|
1672
2603
|
if (rows.length > SHADOW_MAX_ROWS) {
|
|
@@ -1684,27 +2615,37 @@ function foldShadow(prev, input) {
|
|
|
1684
2615
|
last_at: input.at
|
|
1685
2616
|
};
|
|
1686
2617
|
}
|
|
2618
|
+
function shadowSetBy(workspaceRoot, sessionKey) {
|
|
2619
|
+
let raw;
|
|
2620
|
+
try {
|
|
2621
|
+
raw = fs5.readFileSync(sessionRefusalFile(workspaceRoot, sessionKey), "utf-8");
|
|
2622
|
+
} catch (err) {
|
|
2623
|
+
const code = err?.code;
|
|
2624
|
+
return code === "ENOENT" ? "foreign" : "unknown";
|
|
2625
|
+
}
|
|
2626
|
+
return parseRecord(raw) === null ? "unknown" : "ours";
|
|
2627
|
+
}
|
|
1687
2628
|
var REFUSAL_STATE_MAX_AGE_DAYS = 90;
|
|
1688
2629
|
function pruneAgedRefusalState(workspaceRoot, opts = {}) {
|
|
1689
2630
|
const maxAgeDays = opts.maxAgeDays ?? REFUSAL_STATE_MAX_AGE_DAYS;
|
|
1690
|
-
const
|
|
2631
|
+
const now2 = opts.now ?? /* @__PURE__ */ new Date();
|
|
1691
2632
|
const dir = refusalStateDir(workspaceRoot);
|
|
1692
2633
|
const result = { scanned: 0, pruned: 0, kept: 0 };
|
|
1693
2634
|
let entries;
|
|
1694
2635
|
try {
|
|
1695
|
-
entries =
|
|
2636
|
+
entries = fs5.readdirSync(dir);
|
|
1696
2637
|
} catch {
|
|
1697
2638
|
return result;
|
|
1698
2639
|
}
|
|
1699
|
-
const cutoffMs =
|
|
2640
|
+
const cutoffMs = now2.getTime() - maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
1700
2641
|
for (const name of entries) {
|
|
1701
2642
|
if (!name.endsWith(".json")) continue;
|
|
1702
2643
|
result.scanned += 1;
|
|
1703
|
-
const file =
|
|
2644
|
+
const file = path5.join(dir, name);
|
|
1704
2645
|
if (name.endsWith(SHADOW_SUFFIX)) {
|
|
1705
2646
|
let shadow = null;
|
|
1706
2647
|
try {
|
|
1707
|
-
shadow = parseShadowRecord(
|
|
2648
|
+
shadow = parseShadowRecord(fs5.readFileSync(file, "utf-8"));
|
|
1708
2649
|
} catch {
|
|
1709
2650
|
shadow = null;
|
|
1710
2651
|
}
|
|
@@ -1712,7 +2653,7 @@ function pruneAgedRefusalState(workspaceRoot, opts = {}) {
|
|
|
1712
2653
|
if (!Number.isFinite(shadowAt)) {
|
|
1713
2654
|
let mtime = NaN;
|
|
1714
2655
|
try {
|
|
1715
|
-
mtime =
|
|
2656
|
+
mtime = fs5.statSync(file).mtimeMs;
|
|
1716
2657
|
} catch {
|
|
1717
2658
|
mtime = NaN;
|
|
1718
2659
|
}
|
|
@@ -1725,7 +2666,7 @@ function pruneAgedRefusalState(workspaceRoot, opts = {}) {
|
|
|
1725
2666
|
continue;
|
|
1726
2667
|
}
|
|
1727
2668
|
try {
|
|
1728
|
-
|
|
2669
|
+
fs5.unlinkSync(file);
|
|
1729
2670
|
result.pruned += 1;
|
|
1730
2671
|
} catch {
|
|
1731
2672
|
result.kept += 1;
|
|
@@ -1734,7 +2675,7 @@ function pruneAgedRefusalState(workspaceRoot, opts = {}) {
|
|
|
1734
2675
|
}
|
|
1735
2676
|
let rec = null;
|
|
1736
2677
|
try {
|
|
1737
|
-
rec = parseRecord(
|
|
2678
|
+
rec = parseRecord(fs5.readFileSync(file, "utf-8"));
|
|
1738
2679
|
} catch {
|
|
1739
2680
|
rec = null;
|
|
1740
2681
|
}
|
|
@@ -1748,7 +2689,7 @@ function pruneAgedRefusalState(workspaceRoot, opts = {}) {
|
|
|
1748
2689
|
continue;
|
|
1749
2690
|
}
|
|
1750
2691
|
try {
|
|
1751
|
-
|
|
2692
|
+
fs5.unlinkSync(file);
|
|
1752
2693
|
result.pruned += 1;
|
|
1753
2694
|
} catch {
|
|
1754
2695
|
result.kept += 1;
|
|
@@ -1783,5 +2724,6 @@ export {
|
|
|
1783
2724
|
sessionShadowFile,
|
|
1784
2725
|
parseShadowRecord,
|
|
1785
2726
|
foldShadow,
|
|
2727
|
+
shadowSetBy,
|
|
1786
2728
|
pruneAgedRefusalState
|
|
1787
2729
|
};
|