@event4u/agent-config 16.1.0 → 16.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +237 -248
- package/dist/agent-src/commands/roadmap/process-full.md +1 -1
- package/dist/agent-src/commands/work.md +4 -0
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
- package/dist/agent-src/rules/ui-audit-gate.md +1 -2
- package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
- package/dist/agent-src/skills/design-review/SKILL.md +30 -4
- package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +16 -1
- package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
- package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
- package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +30 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/dist/cli/python/knowledge_ingest.js +1 -1
- package/dist/cli/python/workspace_drive.js +1 -1
- package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
- package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-7XGBX6ZH.js} +471 -387
- package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-FP36SWJD.js} +432 -144
- package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-IGD6ERX6.js} +31 -0
- package/dist/cli-delegate/{chunk-FXYUYU4N.js → chunk-JNMVLWLR.js} +42 -1
- package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
- package/dist/cli-delegate/cmd_conformance.js +902 -782
- package/dist/cli-delegate/cmd_doctor.js +2 -2
- package/dist/cli-delegate/cmd_export.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +4 -4
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_prune.js +2 -2
- package/dist/cli-delegate/cmd_route_audit.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +2 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +2 -2
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +2 -2
- package/dist/cli-delegate/cmd_uninstall.js +2 -2
- package/dist/cli-delegate/cmd_validate.js +2 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +10 -10
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +6666 -2581
- package/dist/hooks/dispatch.sha256 +1 -0
- package/dist/install/install.mjs +848 -733
- package/dist/mcp/registry-manifest.json +2 -2
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +2747 -1510
- package/dist/server/schemas/settings.js +23 -0
- package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
- package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
- package/dist/ui/index.html +2 -2
- package/docs/contracts/settings-classes.md +6 -4
- package/package.json +4 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/src/config/agent-settings.template.yml +29 -0
- package/src/config/ci-local-parity.yml +18 -0
- package/src/config/continuity-surface.json +2 -2
- package/src/config/gate-coverage.yml +234 -3
- package/src/config/gate-violation-baselines.json +7 -3
- package/src/config/hook-latency-budget.json +21 -0
- package/src/config/hook-token-budget.json +2 -2
- package/src/config/metric-registry.yml +114 -0
- package/src/config/placeholder-drift-budget.json +10 -0
- package/src/config/routing-coverage-seed.json +18 -4
- package/src/scripts/_cli/cmd_doctor.ts +5 -19
- package/src/scripts/_cli/doctor_execution.ts +100 -17
- package/src/scripts/_cli/doctor_network_posture.ts +226 -0
- package/src/scripts/_lib/body_portable.ts +102 -0
- package/src/scripts/_lib/cc_transcript.ts +132 -1
- package/src/scripts/_lib/concern_sla_window.ts +200 -0
- package/src/scripts/_lib/forge_protection.ts +114 -0
- package/src/scripts/_lib/forge_reader.ts +558 -0
- package/src/scripts/_lib/host_env_write.ts +111 -0
- package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
- package/src/scripts/_lib/obligations.ts +119 -8
- package/src/scripts/_lib/probe_inputs.ts +271 -0
- package/src/scripts/_lib/read_surface_scan.ts +322 -0
- package/src/scripts/_lib/release_highlights.ts +2 -1
- package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
- package/src/scripts/_lib/shell_write_shapes.ts +108 -0
- package/src/scripts/_lib/structural_hiding.ts +622 -0
- package/src/scripts/_lib/turn_end_refusals.ts +525 -4
- package/src/scripts/_lib/turn_end_transcript.ts +216 -0
- package/src/scripts/_lib/verification_command.ts +324 -0
- package/src/scripts/_lib/verification_evidence.ts +647 -0
- package/src/scripts/_lib/verify_clause.ts +280 -0
- package/src/scripts/ai_team/team_dispatch.ts +4 -0
- package/src/scripts/before_complete_hook.ts +340 -23
- package/src/scripts/bench_hook_latency.ts +412 -5
- package/src/scripts/cache_realization_report.ts +61 -1
- package/src/scripts/check_argument_hint.ts +287 -0
- package/src/scripts/check_claims.ts +125 -1
- package/src/scripts/check_enforcement_coverage.ts +38 -9
- package/src/scripts/check_enforcement_matrix.ts +245 -3
- package/src/scripts/check_finding_dispositions.ts +22 -3
- package/src/scripts/check_generator_sync.ts +71 -0
- package/src/scripts/check_host_docs_digest.ts +786 -0
- package/src/scripts/check_host_format_column.ts +559 -0
- package/src/scripts/check_input_references.ts +228 -0
- package/src/scripts/check_inputs_parity.ts +235 -0
- package/src/scripts/check_kernel_edit_ratified.ts +47 -5
- package/src/scripts/check_kill_switch_table.ts +294 -0
- package/src/scripts/check_placeholder_drift.ts +221 -0
- package/src/scripts/check_read_surface_coverage.ts +515 -0
- package/src/scripts/check_release_holds.ts +2 -1
- package/src/scripts/check_routing_coverage.ts +378 -11
- package/src/scripts/check_static_parity.ts +17 -0
- package/src/scripts/ci_settle.ts +145 -19
- package/src/scripts/closure_scan.ts +115 -1
- package/src/scripts/compile_hook_manifest.ts +60 -16
- package/src/scripts/external_sources_denylist.json +29 -4
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +42 -5
- package/src/scripts/hooks/block_config_weakening.ts +229 -4
- package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
- package/src/scripts/hooks/block_plumbing_writes.ts +314 -0
- package/src/scripts/hooks/bundle_integrity.ts +291 -0
- package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
- package/src/scripts/hooks/code_graph_context_hook.ts +1 -2
- package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/concern_timings.ts +94 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
- package/src/scripts/hooks/design_pass_hook.ts +100 -9
- package/src/scripts/hooks/design_slop_hook.ts +1 -2
- package/src/scripts/hooks/dispatch_hook.ts +25 -19
- package/src/scripts/hooks/edit_shape_hook.ts +1 -2
- package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
- package/src/scripts/hooks/evidence_independence.ts +1 -2
- package/src/scripts/hooks/exit_codes.ts +128 -0
- package/src/scripts/hooks/host_lowering.json +1 -0
- package/src/scripts/hooks/host_lowering.ts +128 -3
- package/src/scripts/hooks/host_lowering.yaml +262 -46
- package/src/scripts/hooks/host_semantics.ts +30 -2
- package/src/scripts/hooks/injection_budget.ts +9 -2
- package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
- package/src/scripts/hooks/journal_record_hook.ts +1 -2
- package/src/scripts/hooks/memory_recall_hook.ts +1 -2
- package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
- package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
- package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
- package/src/scripts/hooks/permission_gate.ts +1 -2
- package/src/scripts/hooks/push_settle_hook.ts +1 -3
- package/src/scripts/hooks/replay_hook.ts +2 -1
- package/src/scripts/hooks/reread_guard_hook.ts +1 -2
- package/src/scripts/hooks/review_baseline_hook.ts +1 -2
- package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
- package/src/scripts/hooks/rule_inject_hook.ts +8 -5
- package/src/scripts/hooks/run_continuation_hook.ts +13 -15
- package/src/scripts/hooks/skill_route_hook.ts +1 -2
- package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
- package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
- package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
- package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
- package/src/scripts/hooks/table_fingerprint.ts +49 -0
- package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
- package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
- package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
- package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
- package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
- package/src/scripts/install.ts +3 -4
- package/src/scripts/lint_deny_text.ts +419 -0
- package/src/scripts/lint_exit_codes.ts +262 -0
- package/src/scripts/lint_hook_manifest.ts +55 -0
- package/src/scripts/lint_roadmap_blockers.ts +78 -12
- package/src/scripts/lint_skill_descriptions.ts +1 -1
- package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
- package/src/scripts/mcp_server/prompts.ts +142 -1
- package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
- package/src/scripts/measure_turn_end_gate.ts +193 -0
- package/src/scripts/memory_lookup.ts +1 -1
- package/src/scripts/prepack-check.mjs +53 -0
- package/src/scripts/release_publication.ts +13 -1
- package/src/scripts/report_invocation_surface.ts +383 -0
- package/src/scripts/report_obligation_carriers.ts +699 -0
- package/src/scripts/report_obligation_writer_split.ts +236 -0
- package/src/scripts/report_skill_menu_census.ts +208 -0
- package/src/scripts/report_standing_payload_by_host.ts +1 -1
- package/src/scripts/roadmap_verify_share.ts +213 -0
- package/src/scripts/schemas/command.schema.json +44 -0
- package/src/scripts/schemas/review-findings.schema.json +4 -0
- package/src/scripts/schemas/skill.schema.json +44 -0
- package/src/scripts/self_review_gate.ts +359 -10
- package/src/scripts/semantic_parity_census.ts +314 -0
- package/src/scripts/session_eol_report.ts +54 -1
- package/src/scripts/sweep_dead_scan_roots.ts +1 -1
- package/src/scripts/trigger_eval_grandfather.json +0 -2
- package/src/scripts/trigger_eval_rotation.ts +122 -27
- package/src/scripts/ui_conformance_probe.ts +156 -4
- package/src/scripts/update_prices.ts +85 -3
- package/src/scripts/write_bundle_digest.mjs +74 -0
- package/src/server/schemas/settings.ts +31 -0
- package/src/templates/consumer-settings/ONBOARDING.md +1 -0
- package/dist/ui/assets/index-DH1xc62v.js +0 -29
- package/dist/ui/assets/index-jSfMW8_t.css +0 -1
- /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-F7DQWYR5.js";
|
|
5
5
|
import {
|
|
6
6
|
has_stable_session_id,
|
|
7
|
+
owns_session_state,
|
|
7
8
|
prune_legacy_state_file,
|
|
8
9
|
prune_stale_session_states,
|
|
9
10
|
session_state_file,
|
|
@@ -563,6 +564,472 @@ function validateAssumption(input, label = "assumption") {
|
|
|
563
564
|
import fs from "node:fs";
|
|
564
565
|
import path from "node:path";
|
|
565
566
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
567
|
+
|
|
568
|
+
// src/scripts/_lib/verification_command.ts
|
|
569
|
+
var VERIFY_BINARIES = /* @__PURE__ */ new Set([
|
|
570
|
+
"vitest",
|
|
571
|
+
"jest",
|
|
572
|
+
"pytest",
|
|
573
|
+
"phpunit",
|
|
574
|
+
"pest",
|
|
575
|
+
"tsc",
|
|
576
|
+
"eslint",
|
|
577
|
+
"ruff",
|
|
578
|
+
"mypy",
|
|
579
|
+
"pyright",
|
|
580
|
+
"clippy",
|
|
581
|
+
"phpstan",
|
|
582
|
+
"psalm",
|
|
583
|
+
"ecs"
|
|
584
|
+
]);
|
|
585
|
+
var DELEGATING_HEADS = /* @__PURE__ */ new Set([
|
|
586
|
+
"npx",
|
|
587
|
+
"bunx",
|
|
588
|
+
"sudo",
|
|
589
|
+
"time",
|
|
590
|
+
"command"
|
|
591
|
+
]);
|
|
592
|
+
var PATH_PREFIXES = [
|
|
593
|
+
"./",
|
|
594
|
+
"vendor/bin/",
|
|
595
|
+
".venv/bin/",
|
|
596
|
+
"node_modules/.bin/",
|
|
597
|
+
"bin/"
|
|
598
|
+
];
|
|
599
|
+
var VERIFY_WORD = /(^|[-_:])(test|tests|check|checks|lint|typecheck|tsc|qa|preflight|smoke|validate)([-_:]|$)/i;
|
|
600
|
+
var NPM_SCRIPT_WORD = /(^|[-_:])(test|tests|check|checks|lint|typecheck|tsc|build)([-_:]|$)/i;
|
|
601
|
+
function stripComment(command) {
|
|
602
|
+
let inSingle = false;
|
|
603
|
+
let inDouble = false;
|
|
604
|
+
for (let i = 0; i < command.length; i += 1) {
|
|
605
|
+
const ch = command[i];
|
|
606
|
+
if (ch === "'" && !inDouble) inSingle = !inSingle;
|
|
607
|
+
else if (ch === '"' && !inSingle) inDouble = !inDouble;
|
|
608
|
+
else if (ch === "#" && !inSingle && !inDouble && (i === 0 || /\s/.test(command[i - 1] ?? ""))) {
|
|
609
|
+
return command.slice(0, i);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return command;
|
|
613
|
+
}
|
|
614
|
+
function splitSegments(command) {
|
|
615
|
+
const raw = [];
|
|
616
|
+
let buf = "";
|
|
617
|
+
let inSingle = false;
|
|
618
|
+
let inDouble = false;
|
|
619
|
+
for (let i = 0; i < command.length; i += 1) {
|
|
620
|
+
const ch = command[i];
|
|
621
|
+
const two = command.slice(i, i + 2);
|
|
622
|
+
if (ch === "'" && !inDouble) {
|
|
623
|
+
inSingle = !inSingle;
|
|
624
|
+
buf += ch;
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (ch === '"' && !inSingle) {
|
|
628
|
+
inDouble = !inDouble;
|
|
629
|
+
buf += ch;
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
if (!inSingle && !inDouble) {
|
|
633
|
+
if (two === "&&" || two === "||") {
|
|
634
|
+
raw.push({ text: buf, sep: two });
|
|
635
|
+
buf = "";
|
|
636
|
+
i += 1;
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
if (ch === ";" || ch === "|") {
|
|
640
|
+
raw.push({ text: buf, sep: ch });
|
|
641
|
+
buf = "";
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
buf += ch;
|
|
646
|
+
}
|
|
647
|
+
raw.push({ text: buf, sep: null });
|
|
648
|
+
return raw.map((entry, idx) => {
|
|
649
|
+
const next = raw[idx + 1];
|
|
650
|
+
const nextWords = next ? words(next.text) : [];
|
|
651
|
+
return {
|
|
652
|
+
words: words(entry.text),
|
|
653
|
+
nextSep: entry.sep,
|
|
654
|
+
nextHead: (nextWords[0] ?? null)?.toLowerCase() ?? null
|
|
655
|
+
};
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
function words(text) {
|
|
659
|
+
const tokens = text.trim().split(/\s+/).filter((w) => w.length > 0);
|
|
660
|
+
let i = 0;
|
|
661
|
+
while (i < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens[i])) i += 1;
|
|
662
|
+
return tokens.slice(i);
|
|
663
|
+
}
|
|
664
|
+
function bareName(word) {
|
|
665
|
+
let w = word;
|
|
666
|
+
for (const prefix of PATH_PREFIXES) {
|
|
667
|
+
if (w.startsWith(prefix)) {
|
|
668
|
+
w = w.slice(prefix.length);
|
|
669
|
+
break;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
const slash = w.lastIndexOf("/");
|
|
673
|
+
return (slash >= 0 ? w.slice(slash + 1) : w).toLowerCase();
|
|
674
|
+
}
|
|
675
|
+
function failureIsDiscarded(seg) {
|
|
676
|
+
if (seg.nextSep === "||") return seg.nextHead === "true" || seg.nextHead === ":";
|
|
677
|
+
if (seg.nextSep === ";") return seg.nextHead === "true" || seg.nextHead === ":";
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
function segmentVerifies(rawWords) {
|
|
681
|
+
const w = rawWords.filter((x) => x.length > 0);
|
|
682
|
+
let idx = 0;
|
|
683
|
+
while (idx < w.length && DELEGATING_HEADS.has(bareName(w[idx]))) idx += 1;
|
|
684
|
+
if (idx >= w.length) return false;
|
|
685
|
+
const head = bareName(w[idx]);
|
|
686
|
+
const rest = w.slice(idx + 1).map((x) => x.toLowerCase());
|
|
687
|
+
const arg1 = rest[0] ?? "";
|
|
688
|
+
if (VERIFY_BINARIES.has(head)) return true;
|
|
689
|
+
switch (head) {
|
|
690
|
+
case "npm":
|
|
691
|
+
case "pnpm":
|
|
692
|
+
case "yarn":
|
|
693
|
+
case "bun": {
|
|
694
|
+
if (arg1 === "dlx" || arg1 === "exec") return segmentVerifies(rest.slice(1));
|
|
695
|
+
const script = arg1 === "run" ? rest[1] ?? "" : arg1;
|
|
696
|
+
return NPM_SCRIPT_WORD.test(script);
|
|
697
|
+
}
|
|
698
|
+
case "task":
|
|
699
|
+
return VERIFY_WORD.test(arg1) || /^ci([-_:]|$)/i.test(arg1);
|
|
700
|
+
case "cargo":
|
|
701
|
+
return arg1 === "test" || arg1 === "check" || arg1 === "clippy";
|
|
702
|
+
case "go":
|
|
703
|
+
return arg1 === "test" || arg1 === "vet";
|
|
704
|
+
case "make":
|
|
705
|
+
return VERIFY_WORD.test(arg1) || /^ci([-_:]|$)/i.test(arg1);
|
|
706
|
+
case "composer":
|
|
707
|
+
return VERIFY_WORD.test(arg1) || arg1 === "phpstan";
|
|
708
|
+
case "php":
|
|
709
|
+
return arg1 === "artisan" && (rest[1] ?? "") === "test";
|
|
710
|
+
case "artisan":
|
|
711
|
+
return arg1 === "test";
|
|
712
|
+
case "python":
|
|
713
|
+
case "python3":
|
|
714
|
+
return arg1 === "-m" && VERIFY_BINARIES.has(rest[1] ?? "");
|
|
715
|
+
case "node":
|
|
716
|
+
case "deno":
|
|
717
|
+
return rest.some((a) => a === "test" || a.startsWith("--test"));
|
|
718
|
+
case "scripts-run": {
|
|
719
|
+
const script = bareName(arg1);
|
|
720
|
+
return /^(check|lint|verify|validate)[-_]/.test(script);
|
|
721
|
+
}
|
|
722
|
+
default:
|
|
723
|
+
return false;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
function isVerificationCommand(command) {
|
|
727
|
+
if (!command) return false;
|
|
728
|
+
const segments = splitSegments(stripComment(command));
|
|
729
|
+
return segments.some((seg) => !failureIsDiscarded(seg) && segmentVerifies(seg.words));
|
|
730
|
+
}
|
|
731
|
+
var _WIDE_CANDIDATE_RE = /\b(test|tests|vitest|jest|pytest|phpunit|pest|tsc|eslint|ruff|mypy|pyright|clippy|phpstan|typecheck|lint[-_:a-z]*|build|ci|preflight|smoke[-_:a-z]*|check[-_:a-z]*|validate[-_:a-z]*)\b|\b(task|npm|pnpm|yarn|composer|cargo|go|make|php artisan|bun)\s+(run\s+)?\S*(test|check|lint|build|ci|typecheck)/i;
|
|
732
|
+
function mightBeVerification(command) {
|
|
733
|
+
return _WIDE_CANDIDATE_RE.test(command);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// src/scripts/_lib/verification_evidence.ts
|
|
737
|
+
var INSTRUMENT_GAP_REASONS = /* @__PURE__ */ new Set([
|
|
738
|
+
"exit_code_unavailable",
|
|
739
|
+
"unreadable_record"
|
|
740
|
+
]);
|
|
741
|
+
function isInstrumentGap(verdict) {
|
|
742
|
+
return verdict.kind === "INVALID_RUN" && INSTRUMENT_GAP_REASONS.has(verdict.reason);
|
|
743
|
+
}
|
|
744
|
+
var NON_VERIFYING_EXECUTABLES = /* @__PURE__ */ new Set([
|
|
745
|
+
":",
|
|
746
|
+
"awk",
|
|
747
|
+
"basename",
|
|
748
|
+
"cat",
|
|
749
|
+
"cd",
|
|
750
|
+
"chmod",
|
|
751
|
+
"cp",
|
|
752
|
+
"curl",
|
|
753
|
+
"cut",
|
|
754
|
+
"date",
|
|
755
|
+
"dirname",
|
|
756
|
+
"echo",
|
|
757
|
+
"exit",
|
|
758
|
+
"export",
|
|
759
|
+
"false",
|
|
760
|
+
"find",
|
|
761
|
+
"git",
|
|
762
|
+
"grep",
|
|
763
|
+
"head",
|
|
764
|
+
"less",
|
|
765
|
+
"ln",
|
|
766
|
+
"ls",
|
|
767
|
+
"mkdir",
|
|
768
|
+
"more",
|
|
769
|
+
"mv",
|
|
770
|
+
"open",
|
|
771
|
+
"printf",
|
|
772
|
+
"pwd",
|
|
773
|
+
"realpath",
|
|
774
|
+
"rg",
|
|
775
|
+
"rm",
|
|
776
|
+
"sed",
|
|
777
|
+
"sleep",
|
|
778
|
+
"sort",
|
|
779
|
+
"source",
|
|
780
|
+
"tail",
|
|
781
|
+
"tee",
|
|
782
|
+
"touch",
|
|
783
|
+
"tr",
|
|
784
|
+
"true",
|
|
785
|
+
"type",
|
|
786
|
+
"uniq",
|
|
787
|
+
"wc",
|
|
788
|
+
"wget",
|
|
789
|
+
"which",
|
|
790
|
+
"yes"
|
|
791
|
+
]);
|
|
792
|
+
var COMMAND_PREFIXES = /* @__PURE__ */ new Set([
|
|
793
|
+
"bunx",
|
|
794
|
+
"command",
|
|
795
|
+
"env",
|
|
796
|
+
"exec",
|
|
797
|
+
"nice",
|
|
798
|
+
"nohup",
|
|
799
|
+
"npx",
|
|
800
|
+
"sudo",
|
|
801
|
+
"time"
|
|
802
|
+
]);
|
|
803
|
+
function leadingExecutable(segment) {
|
|
804
|
+
for (const raw of segment.trim().split(/\s+/)) {
|
|
805
|
+
if (raw === "") continue;
|
|
806
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(raw)) continue;
|
|
807
|
+
const base = (raw.split("/").pop() ?? raw).replace(/\.exe$/i, "").toLowerCase();
|
|
808
|
+
if (base === "") continue;
|
|
809
|
+
if (COMMAND_PREFIXES.has(base)) continue;
|
|
810
|
+
return base;
|
|
811
|
+
}
|
|
812
|
+
return null;
|
|
813
|
+
}
|
|
814
|
+
function canVerify(command) {
|
|
815
|
+
for (const segment of command.split(/&&|\|\||[;|\n]/)) {
|
|
816
|
+
if (segment.trim() === "") continue;
|
|
817
|
+
const exe = leadingExecutable(segment);
|
|
818
|
+
if (exe !== null && NON_VERIFYING_EXECUTABLES.has(exe)) continue;
|
|
819
|
+
if (isVerificationCommand(segment)) return true;
|
|
820
|
+
}
|
|
821
|
+
return false;
|
|
822
|
+
}
|
|
823
|
+
var KILLED_EXIT_CODES = /* @__PURE__ */ new Set([137, 143]);
|
|
824
|
+
var LOAD_FAILURE_PATTERNS = [
|
|
825
|
+
/\bfailed to load\b/i,
|
|
826
|
+
/\berror(?:s)? collecting\b/i,
|
|
827
|
+
/\bcollection error\b/i,
|
|
828
|
+
/\bcannot find module\b/i,
|
|
829
|
+
/\bmodule not found\b/i,
|
|
830
|
+
/\bunable to load\b/i,
|
|
831
|
+
/\btransform failed\b/i,
|
|
832
|
+
/\bconftest\.py\b.*\berror\b/i,
|
|
833
|
+
/\bcould not open input file\b/i,
|
|
834
|
+
/\bfatal error\b/i
|
|
835
|
+
];
|
|
836
|
+
function _num(m) {
|
|
837
|
+
return m?.[1] === void 0 ? 0 : Number(m[1]);
|
|
838
|
+
}
|
|
839
|
+
function parseVitestOrJest(text) {
|
|
840
|
+
const line = /^[ \t]*Tests:?[ \t]+(.+)$/m.exec(text);
|
|
841
|
+
if (line?.[1] === void 0) return null;
|
|
842
|
+
const body = line[1];
|
|
843
|
+
const failed = _num(/(\d+)\s+failed/i.exec(body));
|
|
844
|
+
const passed = _num(/(\d+)\s+passed/i.exec(body));
|
|
845
|
+
const totalMatch = /\((\d+)\)\s*$/.exec(body.trim()) ?? /(\d+)\s+total/i.exec(body);
|
|
846
|
+
const total = totalMatch?.[1] === void 0 ? null : Number(totalMatch[1]);
|
|
847
|
+
return { passed, failed, total };
|
|
848
|
+
}
|
|
849
|
+
function parsePytest(text) {
|
|
850
|
+
if (/\bno tests ran\b/i.test(text)) return { passed: 0, failed: 0, total: 0 };
|
|
851
|
+
const line = /^[= ]*((?:\d+\s+(?:passed|failed|error|errors|skipped|xfailed|xpassed|deselected)[,\s]*)+)(?:in\s+[\d.]+s)?[= ]*$/im.exec(
|
|
852
|
+
text
|
|
853
|
+
);
|
|
854
|
+
if (line?.[1] === void 0) return null;
|
|
855
|
+
const body = line[1];
|
|
856
|
+
const passed = _num(/(\d+)\s+passed/i.exec(body));
|
|
857
|
+
const failed = _num(/(\d+)\s+failed/i.exec(body)) + _num(/(\d+)\s+errors?\b/i.exec(body));
|
|
858
|
+
const skipped = _num(/(\d+)\s+skipped/i.exec(body));
|
|
859
|
+
return { passed, failed, total: passed + failed + skipped };
|
|
860
|
+
}
|
|
861
|
+
function parsePhpunit(text) {
|
|
862
|
+
if (/\bno tests executed\b/i.test(text)) return { passed: 0, failed: 0, total: 0 };
|
|
863
|
+
const ok = /\bOK\s*\((\d+)\s+tests?/i.exec(text);
|
|
864
|
+
if (ok?.[1] !== void 0) {
|
|
865
|
+
const n = Number(ok[1]);
|
|
866
|
+
return { passed: n, failed: 0, total: n };
|
|
867
|
+
}
|
|
868
|
+
const counted = /^[ \t]*Tests:\s*(\d+),\s*Assertions:\s*\d+(.*)$/m.exec(text);
|
|
869
|
+
if (counted?.[1] === void 0) return null;
|
|
870
|
+
const total = Number(counted[1]);
|
|
871
|
+
const rest = counted[2] ?? "";
|
|
872
|
+
const failed = _num(/Failures:\s*(\d+)/i.exec(rest)) + _num(/Errors:\s*(\d+)/i.exec(rest)) + _num(/Risky:\s*(\d+)/i.exec(rest));
|
|
873
|
+
return { passed: Math.max(total - failed, 0), failed, total };
|
|
874
|
+
}
|
|
875
|
+
function parseTap(text) {
|
|
876
|
+
const plan = /^1\.\.(\d+)\s*$/m.exec(text);
|
|
877
|
+
const okLines = text.match(/^ok\b/gim)?.length ?? 0;
|
|
878
|
+
const notOkLines = text.match(/^not ok\b/gim)?.length ?? 0;
|
|
879
|
+
const trailerFail = /^#\s*fail\s+(\d+)\s*$/im.exec(text);
|
|
880
|
+
if (plan?.[1] === void 0 && okLines === 0 && notOkLines === 0) return null;
|
|
881
|
+
const total = plan?.[1] === void 0 ? okLines + notOkLines : Number(plan[1]);
|
|
882
|
+
const failed = trailerFail?.[1] === void 0 ? notOkLines : Number(trailerFail[1]);
|
|
883
|
+
return { passed: okLines, failed, total };
|
|
884
|
+
}
|
|
885
|
+
function parseSummary(text) {
|
|
886
|
+
return parsePhpunit(text) ?? parseVitestOrJest(text) ?? parsePytest(text) ?? parseTap(text);
|
|
887
|
+
}
|
|
888
|
+
function output(record) {
|
|
889
|
+
return `${record.stdout_tail ?? ""}
|
|
890
|
+
${record.stderr_tail ?? ""}`;
|
|
891
|
+
}
|
|
892
|
+
function classifyRun(record) {
|
|
893
|
+
if (typeof record !== "object" || record === null || Array.isArray(record)) {
|
|
894
|
+
return { kind: "INVALID_RUN", reason: "unreadable_record" };
|
|
895
|
+
}
|
|
896
|
+
const r = record;
|
|
897
|
+
if (typeof r.command !== "string" || r.command.trim() === "") {
|
|
898
|
+
return { kind: "INVALID_RUN", reason: "unreadable_record" };
|
|
899
|
+
}
|
|
900
|
+
if (!canVerify(r.command)) {
|
|
901
|
+
return { kind: "INVALID_RUN", reason: "not_a_verification_command" };
|
|
902
|
+
}
|
|
903
|
+
if (r.exit_code !== null && r.exit_code !== void 0 && (typeof r.exit_code !== "number" || !Number.isFinite(r.exit_code))) {
|
|
904
|
+
return { kind: "INVALID_RUN", reason: "unreadable_record" };
|
|
905
|
+
}
|
|
906
|
+
const text = output(r);
|
|
907
|
+
const summary = parseSummary(text);
|
|
908
|
+
if (r.interrupted === true || typeof r.exit_code === "number" && KILLED_EXIT_CODES.has(r.exit_code)) {
|
|
909
|
+
if (summary !== null && summary.failed > 0) return { kind: "FAIL_EVIDENCE" };
|
|
910
|
+
return { kind: "INVALID_RUN", reason: "timeout_or_killed" };
|
|
911
|
+
}
|
|
912
|
+
if (summary !== null && summary.failed > 0) return { kind: "FAIL_EVIDENCE" };
|
|
913
|
+
if (r.exit_code === null || r.exit_code === void 0) {
|
|
914
|
+
return { kind: "INVALID_RUN", reason: "exit_code_unavailable" };
|
|
915
|
+
}
|
|
916
|
+
if ((summary === null || summary.passed === 0) && LOAD_FAILURE_PATTERNS.some((re) => re.test(text))) {
|
|
917
|
+
return { kind: "INVALID_RUN", reason: "fixture_or_load_failure" };
|
|
918
|
+
}
|
|
919
|
+
if (summary !== null) {
|
|
920
|
+
if (summary.failed > 0) return { kind: "FAIL_EVIDENCE" };
|
|
921
|
+
if (summary.total === 0 || summary.total === null && summary.passed === 0) {
|
|
922
|
+
return { kind: "INVALID_RUN", reason: "zero_tests_discovered" };
|
|
923
|
+
}
|
|
924
|
+
if (summary.total !== null && summary.total > 0 && summary.passed === 0) {
|
|
925
|
+
return { kind: "INVALID_RUN", reason: "zero_tests_discovered" };
|
|
926
|
+
}
|
|
927
|
+
if (r.exit_code !== 0) {
|
|
928
|
+
return { kind: "INVALID_RUN", reason: "nonzero_exit_without_test_failure" };
|
|
929
|
+
}
|
|
930
|
+
return { kind: "PASS_EVIDENCE_OK" };
|
|
931
|
+
}
|
|
932
|
+
if (r.exit_code !== 0) {
|
|
933
|
+
return { kind: "INVALID_RUN", reason: "nonzero_exit_without_test_failure" };
|
|
934
|
+
}
|
|
935
|
+
return { kind: "PASS_EVIDENCE_OK" };
|
|
936
|
+
}
|
|
937
|
+
function runnerOf(command) {
|
|
938
|
+
for (const [name, re] of [
|
|
939
|
+
["vitest", /\bvitest\b/i],
|
|
940
|
+
["jest", /\bjest\b/i],
|
|
941
|
+
["pytest", /\bpytest\b/i],
|
|
942
|
+
["phpunit", /\bphpunit\b/i],
|
|
943
|
+
["pest", /\bpest\b/i],
|
|
944
|
+
["go", /\bgo\s+test\b/i],
|
|
945
|
+
["cargo", /\bcargo\s+test\b/i]
|
|
946
|
+
]) {
|
|
947
|
+
if (re.test(command)) return name;
|
|
948
|
+
}
|
|
949
|
+
return "other";
|
|
950
|
+
}
|
|
951
|
+
function placeRecord(raw) {
|
|
952
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
|
|
953
|
+
const v = raw["after_edits"];
|
|
954
|
+
if (typeof v !== "number" || !Number.isFinite(v)) return null;
|
|
955
|
+
return v;
|
|
956
|
+
}
|
|
957
|
+
function readRunEvidence(state) {
|
|
958
|
+
const reasons = [];
|
|
959
|
+
let passed = false;
|
|
960
|
+
let failed = false;
|
|
961
|
+
let gap = false;
|
|
962
|
+
for (const raw of state.runs) {
|
|
963
|
+
const placed = placeRecord(raw);
|
|
964
|
+
if (placed === null) {
|
|
965
|
+
gap = true;
|
|
966
|
+
reasons.push("unreadable_record");
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
if (placed < state.edits_this_turn) continue;
|
|
970
|
+
const verdict = classifyRun(raw);
|
|
971
|
+
if (verdict.kind === "PASS_EVIDENCE_OK") {
|
|
972
|
+
passed = true;
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
if (verdict.kind === "FAIL_EVIDENCE") {
|
|
976
|
+
failed = true;
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
reasons.push(verdict.reason);
|
|
980
|
+
if (isInstrumentGap(verdict)) gap = true;
|
|
981
|
+
}
|
|
982
|
+
return { passed, failed, instrumentGap: gap && !passed && !failed, reasons };
|
|
983
|
+
}
|
|
984
|
+
function testTargetKey(command) {
|
|
985
|
+
const targets = /* @__PURE__ */ new Set();
|
|
986
|
+
for (const token of command.split(/\s+/)) {
|
|
987
|
+
const bare = token.replace(/^['"]|['"]$/g, "");
|
|
988
|
+
if (/[./]\w*(?:test|spec)\w*\.[a-z]+$/i.test(bare) || /(?:Test|Spec)\.php$/.test(bare)) {
|
|
989
|
+
targets.add(bare);
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
return [...targets].sort().join("|");
|
|
993
|
+
}
|
|
994
|
+
function hasRedThenGreen(state) {
|
|
995
|
+
let reddenedAnywhere = false;
|
|
996
|
+
const reddened = /* @__PURE__ */ new Set();
|
|
997
|
+
for (let i = 0; i < state.runs.length; i += 1) {
|
|
998
|
+
const raw = state.runs[i];
|
|
999
|
+
const verdict = classifyRun(raw);
|
|
1000
|
+
const key = testTargetKey(
|
|
1001
|
+
typeof raw === "object" && raw !== null && !Array.isArray(raw) ? String(raw["command"] ?? "") : ""
|
|
1002
|
+
);
|
|
1003
|
+
if (RED_VERDICTS(verdict)) {
|
|
1004
|
+
reddened.add(key);
|
|
1005
|
+
reddenedAnywhere = true;
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
if (verdict.kind !== "PASS_EVIDENCE_OK") continue;
|
|
1009
|
+
const placed = placeRecord(raw);
|
|
1010
|
+
if (placed === null || placed < state.edits_this_turn) continue;
|
|
1011
|
+
if (key === "" ? reddenedAnywhere : reddened.has(key)) return true;
|
|
1012
|
+
}
|
|
1013
|
+
return false;
|
|
1014
|
+
}
|
|
1015
|
+
function RED_VERDICTS(verdict) {
|
|
1016
|
+
if (verdict.kind === "FAIL_EVIDENCE") return true;
|
|
1017
|
+
if (verdict.kind !== "INVALID_RUN") return false;
|
|
1018
|
+
return verdict.reason === "fixture_or_load_failure" || verdict.reason === "nonzero_exit_without_test_failure" || verdict.reason === "zero_tests_discovered";
|
|
1019
|
+
}
|
|
1020
|
+
function describeRecordFinding(failed, reasons) {
|
|
1021
|
+
const tail = " (verify-before-complete: no verification command run in this message \u2192 you cannot claim it passes)";
|
|
1022
|
+
if (failed) {
|
|
1023
|
+
return "this turn changed a file and its verification run FAILED \u2014 a recorded run after the last edit reports failing tests" + tail;
|
|
1024
|
+
}
|
|
1025
|
+
if (reasons.length === 0) {
|
|
1026
|
+
return "this turn changed a file and no verification run was recorded after the last edit" + tail;
|
|
1027
|
+
}
|
|
1028
|
+
return `this turn changed a file and no recorded run after the last edit proves anything \u2014 the runs seen classify as ${[...new Set(reasons)].join(", ")}` + tail;
|
|
1029
|
+
}
|
|
1030
|
+
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
|
+
|
|
1032
|
+
// src/scripts/before_complete_hook.ts
|
|
566
1033
|
var STATE_FILE = path.join("agents", "state", "verify-before-complete.json");
|
|
567
1034
|
var STATE_DIR = path.join("agents", "state", "verify-before-complete");
|
|
568
1035
|
var STATE_RETENTION_DAYS = 7;
|
|
@@ -585,10 +1052,6 @@ var COMMAND_TOOLS = /* @__PURE__ */ new Set([
|
|
|
585
1052
|
"RunShellCommand"
|
|
586
1053
|
// Cursor
|
|
587
1054
|
]);
|
|
588
|
-
var _VERIFICATION_RE = new RegExp(
|
|
589
|
-
"(?:^|[\\s;&|`(])(task\\s+(?:ci|test|tests|lint|check|qa|phpstan|rector|ecs|pest|pytest)|(?:\\./|\\.venv/bin/|vendor/bin/)?(?:pest|phpunit|phpstan|psalm|rector|ecs)\\b|(?:python3?|\\.venv/bin/python3?)\\s+-m\\s+pytest|pytest\\b|(?:npm|pnpm|yarn|bun)\\s+(?:run\\s+)?(?:test|check|lint|typecheck|tsc)|cargo\\s+(?:test|check|clippy)|go\\s+test|make\\s+(?:test|check|lint)|composer\\s+(?:test|check|lint|phpstan)|(?:php\\s+)?artisan\\s+test)",
|
|
590
|
-
"i"
|
|
591
|
-
);
|
|
592
1055
|
function _now() {
|
|
593
1056
|
return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "+00:00");
|
|
594
1057
|
}
|
|
@@ -612,6 +1075,11 @@ function _empty_state() {
|
|
|
612
1075
|
// has to survive it: the measured failure is a completion claim in a LATER
|
|
613
1076
|
// turn than the poll it rests on.
|
|
614
1077
|
ci_last: null,
|
|
1078
|
+
// Round 8 — see `_reset_turn`. Present in the empty state so a freshly
|
|
1079
|
+
// created file has the same shape as one that has seen a turn boundary;
|
|
1080
|
+
// a reader must never have to distinguish "no runs yet" from "key absent".
|
|
1081
|
+
verification_runs: [],
|
|
1082
|
+
edits_this_turn: 0,
|
|
615
1083
|
checked_at: _now()
|
|
616
1084
|
};
|
|
617
1085
|
}
|
|
@@ -639,7 +1107,7 @@ function _extract_command(payload) {
|
|
|
639
1107
|
return [tool, null];
|
|
640
1108
|
}
|
|
641
1109
|
function _is_verification(command) {
|
|
642
|
-
return
|
|
1110
|
+
return isVerificationCommand(command) || isCiPoll(command);
|
|
643
1111
|
}
|
|
644
1112
|
var _VACUOUS_PATTERNS = [
|
|
645
1113
|
// gh pr checks / gh run — no checks registered at all.
|
|
@@ -655,8 +1123,8 @@ var _VACUOUS_PATTERNS = [
|
|
|
655
1123
|
/\b0\s+files?\s+(?:checked|scanned|linted)\b/i,
|
|
656
1124
|
/\bnothing to check\b/i
|
|
657
1125
|
];
|
|
658
|
-
function isVacuousOutput(
|
|
659
|
-
const text =
|
|
1126
|
+
function isVacuousOutput(output2) {
|
|
1127
|
+
const text = output2.trim();
|
|
660
1128
|
if (!text) {
|
|
661
1129
|
return false;
|
|
662
1130
|
}
|
|
@@ -667,19 +1135,19 @@ var _CI_POLL_RE = /\bgh\s+(?:pr\s+checks|run\s+(?:watch|list|view))\b/i;
|
|
|
667
1135
|
function isCiPoll(command) {
|
|
668
1136
|
return _CI_POLL_RE.test(command);
|
|
669
1137
|
}
|
|
670
|
-
function pendingCount(
|
|
671
|
-
const m = /(\d+)\s+(?:checks? )?pending\b/i.exec(
|
|
1138
|
+
function pendingCount(output2) {
|
|
1139
|
+
const m = /(\d+)\s+(?:checks? )?pending\b/i.exec(output2) ?? /\bpending[:=]\s*(\d+)/i.exec(output2);
|
|
672
1140
|
if (m?.[1] !== void 0) {
|
|
673
1141
|
return Number(m[1]);
|
|
674
1142
|
}
|
|
675
|
-
const rows =
|
|
1143
|
+
const rows = output2.match(/^\S.*\b(pending|in_progress|queued)\b/gim);
|
|
676
1144
|
if (rows) {
|
|
677
1145
|
return rows.length;
|
|
678
1146
|
}
|
|
679
|
-
if (/\bno checks reported\b/i.test(
|
|
1147
|
+
if (/\bno checks reported\b/i.test(output2)) {
|
|
680
1148
|
return null;
|
|
681
1149
|
}
|
|
682
|
-
if (/^\S+\s+(pass|fail|skipping|successful|failing)\b/im.test(
|
|
1150
|
+
if (/^\S+\s+(pass|fail|skipping|successful|failing)\b/im.test(output2)) {
|
|
683
1151
|
return 0;
|
|
684
1152
|
}
|
|
685
1153
|
return null;
|
|
@@ -696,11 +1164,101 @@ function _extract_output(payload) {
|
|
|
696
1164
|
}
|
|
697
1165
|
return null;
|
|
698
1166
|
}
|
|
1167
|
+
var EDIT_TOOLS = /* @__PURE__ */ new Set([
|
|
1168
|
+
"Edit",
|
|
1169
|
+
"Write",
|
|
1170
|
+
"MultiEdit",
|
|
1171
|
+
"NotebookEdit",
|
|
1172
|
+
"str-replace-editor",
|
|
1173
|
+
"save-file",
|
|
1174
|
+
"str_replace_editor",
|
|
1175
|
+
"create_file",
|
|
1176
|
+
"write_to_file",
|
|
1177
|
+
"replace_in_file",
|
|
1178
|
+
"apply_diff",
|
|
1179
|
+
"edit_file"
|
|
1180
|
+
]);
|
|
1181
|
+
var MAX_VERIFICATION_RUNS_PER_TURN = 24;
|
|
1182
|
+
var RUN_OUTPUT_TAIL_BYTES = 4096;
|
|
1183
|
+
function _cap_runs(runs) {
|
|
1184
|
+
if (runs.length <= MAX_VERIFICATION_RUNS_PER_TURN) return runs;
|
|
1185
|
+
const tail = runs.slice(-(MAX_VERIFICATION_RUNS_PER_TURN - 1));
|
|
1186
|
+
const head = runs.find((r) => {
|
|
1187
|
+
if (r === null || typeof r !== "object" || Array.isArray(r)) return false;
|
|
1188
|
+
const rec = r;
|
|
1189
|
+
const code = rec["exit_code"];
|
|
1190
|
+
return typeof code === "number" && code !== 0;
|
|
1191
|
+
});
|
|
1192
|
+
if (head === void 0 || tail.includes(head)) {
|
|
1193
|
+
return runs.slice(-MAX_VERIFICATION_RUNS_PER_TURN);
|
|
1194
|
+
}
|
|
1195
|
+
return [head, ...tail];
|
|
1196
|
+
}
|
|
1197
|
+
var _ERROR_EXIT_PREFIX = /^Error:\s*Exit code\s*(\d+)/i;
|
|
1198
|
+
var _NO_EXIT = { code: null, source: null, interrupted: false };
|
|
1199
|
+
function _numeric_exit_field(obj) {
|
|
1200
|
+
for (const key of ["exit_code", "exitCode", "returncode", "returnCode", "status_code"]) {
|
|
1201
|
+
const v = obj[key];
|
|
1202
|
+
if (typeof v === "number" && Number.isFinite(v)) return Math.trunc(v);
|
|
1203
|
+
if (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v))) {
|
|
1204
|
+
return Math.trunc(Number(v));
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
return null;
|
|
1208
|
+
}
|
|
1209
|
+
function _extract_exit_reading(payload) {
|
|
1210
|
+
const direct = _numeric_exit_field(payload);
|
|
1211
|
+
if (direct !== null) return { code: direct, source: "field", interrupted: false };
|
|
1212
|
+
for (const key of ["tool_response", "toolResponse", "result", "output"]) {
|
|
1213
|
+
const v = payload[key];
|
|
1214
|
+
if (typeof v === "string") {
|
|
1215
|
+
const m = _ERROR_EXIT_PREFIX.exec(v);
|
|
1216
|
+
if (m?.[1] !== void 0) {
|
|
1217
|
+
return { code: Number(m[1]), source: "error_prefix", interrupted: false };
|
|
1218
|
+
}
|
|
1219
|
+
continue;
|
|
1220
|
+
}
|
|
1221
|
+
if (v === null || typeof v !== "object" || Array.isArray(v)) continue;
|
|
1222
|
+
const obj = v;
|
|
1223
|
+
const nested = _numeric_exit_field(obj);
|
|
1224
|
+
if (nested !== null) return { code: nested, source: "field", interrupted: false };
|
|
1225
|
+
if (obj["interrupted"] === true) return { code: null, source: null, interrupted: true };
|
|
1226
|
+
const hasStream = typeof obj["stdout"] === "string" || typeof obj["stderr"] === "string";
|
|
1227
|
+
if (obj["interrupted"] === false && hasStream) {
|
|
1228
|
+
return { code: 0, source: "response_shape", interrupted: false };
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
return _NO_EXIT;
|
|
1232
|
+
}
|
|
1233
|
+
function _extract_run_streams(payload) {
|
|
1234
|
+
for (const key of ["tool_response", "toolResponse", "result", "output"]) {
|
|
1235
|
+
const v = payload[key];
|
|
1236
|
+
if (typeof v === "string") return { stdout: v, stderr: "" };
|
|
1237
|
+
if (v === null || typeof v !== "object" || Array.isArray(v)) continue;
|
|
1238
|
+
const obj = v;
|
|
1239
|
+
const out = obj["stdout"] ?? obj["output"];
|
|
1240
|
+
const err = obj["stderr"];
|
|
1241
|
+
if (typeof out === "string" || typeof err === "string") {
|
|
1242
|
+
return {
|
|
1243
|
+
stdout: typeof out === "string" ? out : "",
|
|
1244
|
+
stderr: typeof err === "string" ? err : ""
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
const topOut = payload["stdout"] ?? payload["output"];
|
|
1249
|
+
const topErr = payload["stderr"];
|
|
1250
|
+
return {
|
|
1251
|
+
stdout: typeof topOut === "string" ? topOut : "",
|
|
1252
|
+
stderr: typeof topErr === "string" ? topErr : ""
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
699
1255
|
function _reset_turn(state, session_id) {
|
|
700
1256
|
state["session_id"] = session_id || state["session_id"] || "";
|
|
701
1257
|
state["turn_started_at"] = _now();
|
|
702
1258
|
state["verifications_this_turn"] = 0;
|
|
703
1259
|
state["verified_this_turn"] = false;
|
|
1260
|
+
state["verification_runs"] = [];
|
|
1261
|
+
state["edits_this_turn"] = 0;
|
|
704
1262
|
state["ci_saw_pending"] = false;
|
|
705
1263
|
state["nonevidence_this_turn"] = 0;
|
|
706
1264
|
return state;
|
|
@@ -732,12 +1290,15 @@ function _update(state, event, envelope) {
|
|
|
732
1290
|
state = _reset_turn(state, session_id);
|
|
733
1291
|
} else if (event === "post_tool_use") {
|
|
734
1292
|
const [tool, cmd] = _extract_command(pl);
|
|
1293
|
+
if (tool !== null && EDIT_TOOLS.has(tool)) {
|
|
1294
|
+
state["edits_this_turn"] = _asInt(state["edits_this_turn"]) + 1;
|
|
1295
|
+
}
|
|
735
1296
|
if (cmd && _is_verification(cmd)) {
|
|
736
|
-
const
|
|
737
|
-
const vacuous =
|
|
1297
|
+
const output2 = _extract_output(pl);
|
|
1298
|
+
const vacuous = output2 !== null && isVacuousOutput(output2);
|
|
738
1299
|
let counts = !vacuous;
|
|
739
1300
|
if (isCiPoll(cmd)) {
|
|
740
|
-
const pending =
|
|
1301
|
+
const pending = output2 === null ? null : pendingCount(output2);
|
|
741
1302
|
if (pending !== null && pending > 0) {
|
|
742
1303
|
state["ci_saw_pending"] = true;
|
|
743
1304
|
counts = false;
|
|
@@ -769,6 +1330,30 @@ function _update(state, event, envelope) {
|
|
|
769
1330
|
state["nonevidence_this_turn"] = _asInt(state["nonevidence_this_turn"]) + 1;
|
|
770
1331
|
}
|
|
771
1332
|
}
|
|
1333
|
+
if (cmd && mightBeVerification(cmd)) {
|
|
1334
|
+
const streams = _extract_run_streams(pl);
|
|
1335
|
+
const exit = _extract_exit_reading(pl);
|
|
1336
|
+
const runs = Array.isArray(state["verification_runs"]) ? [...state["verification_runs"]] : [];
|
|
1337
|
+
runs.push({
|
|
1338
|
+
command: cmd.slice(0, 512),
|
|
1339
|
+
tool,
|
|
1340
|
+
exit_code: exit.code,
|
|
1341
|
+
// Provenance, not decoration: `response_shape` is a code this recorder
|
|
1342
|
+
// DERIVED from the host's success form, and a reader disputing a verdict
|
|
1343
|
+
// needs to know which of the three readings produced it.
|
|
1344
|
+
exit_source: exit.source,
|
|
1345
|
+
interrupted: exit.interrupted,
|
|
1346
|
+
stdout_tail: streams.stdout.slice(-RUN_OUTPUT_TAIL_BYTES),
|
|
1347
|
+
// Written, and previously not: `RunRecord.stderr_tail` was declared and
|
|
1348
|
+
// read by the classifier's `output()` while nothing populated it, so
|
|
1349
|
+
// stderr reached a parser only by accident inside a stringified blob.
|
|
1350
|
+
stderr_tail: streams.stderr.slice(-RUN_OUTPUT_TAIL_BYTES),
|
|
1351
|
+
runner: runnerOf(cmd),
|
|
1352
|
+
after_edits: _asInt(state["edits_this_turn"]),
|
|
1353
|
+
at: _now()
|
|
1354
|
+
});
|
|
1355
|
+
state["verification_runs"] = _cap_runs(runs);
|
|
1356
|
+
}
|
|
772
1357
|
} else if (event === "stop") {
|
|
773
1358
|
state["last_stop_at"] = _now();
|
|
774
1359
|
}
|
|
@@ -859,6 +1444,23 @@ function main(argv) {
|
|
|
859
1444
|
const args = parse_args(argv ?? process.argv.slice(2));
|
|
860
1445
|
return run(_readStdin(), { consumer_root: process.cwd(), verbose: args.verbose });
|
|
861
1446
|
}
|
|
1447
|
+
function readTurnRunState(workspaceRoot, session_id) {
|
|
1448
|
+
if (!has_stable_session_id(session_id)) return null;
|
|
1449
|
+
try {
|
|
1450
|
+
const raw = fs.readFileSync(path.join(workspaceRoot, statePathFor(session_id)), "utf-8");
|
|
1451
|
+
const decoded = JSON.parse(raw);
|
|
1452
|
+
if (!owns_session_state(decoded, session_id)) return null;
|
|
1453
|
+
if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
|
|
1454
|
+
const state = decoded;
|
|
1455
|
+
const runs = state["verification_runs"];
|
|
1456
|
+
const edits = state["edits_this_turn"];
|
|
1457
|
+
if (!Array.isArray(runs)) return null;
|
|
1458
|
+
if (typeof edits !== "number" || !Number.isFinite(edits) || edits < 1) return null;
|
|
1459
|
+
return { runs, edits_this_turn: edits };
|
|
1460
|
+
} catch {
|
|
1461
|
+
return null;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
862
1464
|
function _isCliEntry() {
|
|
863
1465
|
if (true) {
|
|
864
1466
|
return false;
|
|
@@ -1009,6 +1611,79 @@ function readInstallBoundary() {
|
|
|
1009
1611
|
return { version: null, installed_at: null };
|
|
1010
1612
|
}
|
|
1011
1613
|
}
|
|
1614
|
+
var SHADOW_LAYERS = ["stop_hook_active", "refused_turn"];
|
|
1615
|
+
function emptyRetryCounts() {
|
|
1616
|
+
const out = {};
|
|
1617
|
+
for (const l of SHADOW_LAYERS) out[l] = 0;
|
|
1618
|
+
return out;
|
|
1619
|
+
}
|
|
1620
|
+
var SHADOW_MAX_ROWS = 200;
|
|
1621
|
+
var SHADOW_SUFFIX = ".shadow.json";
|
|
1622
|
+
function sessionShadowFile(workspaceRoot, sessionKey) {
|
|
1623
|
+
return path2.join(refusalStateDir(workspaceRoot), `${sessionKey}${SHADOW_SUFFIX}`);
|
|
1624
|
+
}
|
|
1625
|
+
function parseShadowRecord(raw) {
|
|
1626
|
+
let decoded;
|
|
1627
|
+
try {
|
|
1628
|
+
decoded = JSON.parse(raw);
|
|
1629
|
+
} catch {
|
|
1630
|
+
return null;
|
|
1631
|
+
}
|
|
1632
|
+
if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
|
|
1633
|
+
const o = decoded;
|
|
1634
|
+
const rowsRaw = o["would_refuse_again"];
|
|
1635
|
+
if (!Array.isArray(rowsRaw)) return null;
|
|
1636
|
+
const rows = [];
|
|
1637
|
+
for (const r of rowsRaw) {
|
|
1638
|
+
if (typeof r !== "object" || r === null || Array.isArray(r)) continue;
|
|
1639
|
+
const ro = r;
|
|
1640
|
+
const det = ro["detector"];
|
|
1641
|
+
const turn = ro["turn"];
|
|
1642
|
+
const at = ro["at"];
|
|
1643
|
+
const layer = ro["layer"];
|
|
1644
|
+
if (!isDetector(det)) continue;
|
|
1645
|
+
if (typeof turn !== "number" || !Number.isFinite(turn)) continue;
|
|
1646
|
+
if (typeof at !== "string") continue;
|
|
1647
|
+
if (layer !== "stop_hook_active" && layer !== "refused_turn") continue;
|
|
1648
|
+
rows.push({ detector: det, turn, at, layer });
|
|
1649
|
+
}
|
|
1650
|
+
const num = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
|
|
1651
|
+
const observed = o["retries_observed"];
|
|
1652
|
+
const byLayer = emptyRetryCounts();
|
|
1653
|
+
if (typeof observed === "object" && observed !== null && !Array.isArray(observed)) {
|
|
1654
|
+
for (const l of SHADOW_LAYERS) {
|
|
1655
|
+
byLayer[l] = num(observed[l]);
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
return {
|
|
1659
|
+
would_refuse_again: rows,
|
|
1660
|
+
retries_observed: byLayer,
|
|
1661
|
+
dropped: num(o["dropped"]),
|
|
1662
|
+
first_at: typeof o["first_at"] === "string" ? o["first_at"] : "",
|
|
1663
|
+
last_at: typeof o["last_at"] === "string" ? o["last_at"] : ""
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
function foldShadow(prev, input) {
|
|
1667
|
+
const rows = [...prev?.would_refuse_again ?? []];
|
|
1668
|
+
for (const d of input.detectors) {
|
|
1669
|
+
rows.push({ detector: d, turn: input.turnOrdinal, at: input.at, layer: input.layer });
|
|
1670
|
+
}
|
|
1671
|
+
let dropped = prev?.dropped ?? 0;
|
|
1672
|
+
if (rows.length > SHADOW_MAX_ROWS) {
|
|
1673
|
+
dropped += rows.length - SHADOW_MAX_ROWS;
|
|
1674
|
+
rows.splice(0, rows.length - SHADOW_MAX_ROWS);
|
|
1675
|
+
}
|
|
1676
|
+
const retries = emptyRetryCounts();
|
|
1677
|
+
for (const l of SHADOW_LAYERS) retries[l] = prev?.retries_observed[l] ?? 0;
|
|
1678
|
+
retries[input.layer] += 1;
|
|
1679
|
+
return {
|
|
1680
|
+
would_refuse_again: rows,
|
|
1681
|
+
retries_observed: retries,
|
|
1682
|
+
dropped,
|
|
1683
|
+
first_at: prev?.first_at !== void 0 && prev.first_at !== "" ? prev.first_at : input.at,
|
|
1684
|
+
last_at: input.at
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1012
1687
|
var REFUSAL_STATE_MAX_AGE_DAYS = 90;
|
|
1013
1688
|
function pruneAgedRefusalState(workspaceRoot, opts = {}) {
|
|
1014
1689
|
const maxAgeDays = opts.maxAgeDays ?? REFUSAL_STATE_MAX_AGE_DAYS;
|
|
@@ -1026,6 +1701,37 @@ function pruneAgedRefusalState(workspaceRoot, opts = {}) {
|
|
|
1026
1701
|
if (!name.endsWith(".json")) continue;
|
|
1027
1702
|
result.scanned += 1;
|
|
1028
1703
|
const file = path2.join(dir, name);
|
|
1704
|
+
if (name.endsWith(SHADOW_SUFFIX)) {
|
|
1705
|
+
let shadow = null;
|
|
1706
|
+
try {
|
|
1707
|
+
shadow = parseShadowRecord(fs2.readFileSync(file, "utf-8"));
|
|
1708
|
+
} catch {
|
|
1709
|
+
shadow = null;
|
|
1710
|
+
}
|
|
1711
|
+
const shadowAt = shadow === null ? NaN : Date.parse(shadow.last_at);
|
|
1712
|
+
if (!Number.isFinite(shadowAt)) {
|
|
1713
|
+
let mtime = NaN;
|
|
1714
|
+
try {
|
|
1715
|
+
mtime = fs2.statSync(file).mtimeMs;
|
|
1716
|
+
} catch {
|
|
1717
|
+
mtime = NaN;
|
|
1718
|
+
}
|
|
1719
|
+
if (!Number.isFinite(mtime) || mtime >= cutoffMs) {
|
|
1720
|
+
result.kept += 1;
|
|
1721
|
+
continue;
|
|
1722
|
+
}
|
|
1723
|
+
} else if (shadowAt >= cutoffMs) {
|
|
1724
|
+
result.kept += 1;
|
|
1725
|
+
continue;
|
|
1726
|
+
}
|
|
1727
|
+
try {
|
|
1728
|
+
fs2.unlinkSync(file);
|
|
1729
|
+
result.pruned += 1;
|
|
1730
|
+
} catch {
|
|
1731
|
+
result.kept += 1;
|
|
1732
|
+
}
|
|
1733
|
+
continue;
|
|
1734
|
+
}
|
|
1029
1735
|
let rec = null;
|
|
1030
1736
|
try {
|
|
1031
1737
|
rec = parseRecord(fs2.readFileSync(file, "utf-8"));
|
|
@@ -1052,10 +1758,16 @@ function pruneAgedRefusalState(workspaceRoot, opts = {}) {
|
|
|
1052
1758
|
}
|
|
1053
1759
|
|
|
1054
1760
|
export {
|
|
1761
|
+
isVerificationCommand,
|
|
1762
|
+
readRunEvidence,
|
|
1763
|
+
hasRedThenGreen,
|
|
1764
|
+
describeRecordFinding,
|
|
1765
|
+
NO_RED_EVIDENCE_REASON,
|
|
1055
1766
|
statePathFor,
|
|
1056
1767
|
isVacuousOutput,
|
|
1057
1768
|
isCiPoll,
|
|
1058
1769
|
pendingCount,
|
|
1770
|
+
readTurnRunState,
|
|
1059
1771
|
CAPSULE_SCHEMA_VERSION,
|
|
1060
1772
|
scanEnvelopeDirectives,
|
|
1061
1773
|
wrapAsPriorSessionData,
|
|
@@ -1068,5 +1780,8 @@ export {
|
|
|
1068
1780
|
parseRecord,
|
|
1069
1781
|
foldRefusal,
|
|
1070
1782
|
readInstallBoundary,
|
|
1783
|
+
sessionShadowFile,
|
|
1784
|
+
parseShadowRecord,
|
|
1785
|
+
foldShadow,
|
|
1071
1786
|
pruneAgedRefusalState
|
|
1072
1787
|
};
|