@piagent/platform 1.6.0 → 1.6.1

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.
Files changed (213) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +17 -14
  3. package/README.vi.md +26 -1
  4. package/benchmarks/production-v1/spend-control.v1.json +27 -3
  5. package/benchmarks/production-v1/suite.json +5 -1
  6. package/catalog/capabilities.json +16 -16
  7. package/docs/authority-manifest.md +1 -1
  8. package/docs/auto-delegation-policy.md +38 -73
  9. package/docs/command-reference-vietnamese.md +23 -31
  10. package/docs/context-engine.md +5 -3
  11. package/docs/context-window-policy.md +37 -1
  12. package/docs/distribution-standard.md +7 -7
  13. package/docs/en/architecture.md +26 -13
  14. package/docs/languages.json +4 -4
  15. package/docs/operator-manual-vietnamese.md +21 -41
  16. package/docs/publishing-for-teams.md +5 -0
  17. package/docs/quality-benchmark.md +179 -23
  18. package/docs/release-install-policy.md +6 -6
  19. package/docs/runtime-harness-standard.md +3 -3
  20. package/docs/security-threat-model.md +1 -1
  21. package/docs/subagent-orchestration-capabilities.md +19 -51
  22. package/docs/subagents-and-multiagent.md +21 -47
  23. package/docs/task-implementation-contract.md +3 -3
  24. package/docs/task-lifecycle-vietnamese.md +1 -1
  25. package/docs/team-onboarding.md +2 -2
  26. package/docs/vi/architecture.md +26 -13
  27. package/docs-site/architecture.html +1 -1
  28. package/docs-site/benchmark.html +89 -95
  29. package/docs-site/commands.html +1 -1
  30. package/docs-site/content/benchmark.html +88 -94
  31. package/docs-site/content/en/benchmark.html +134 -90
  32. package/docs-site/content/en/index.html +19 -19
  33. package/docs-site/content/en/runtime.html +4 -4
  34. package/docs-site/content/en/whats-new.html +12 -1
  35. package/docs-site/content/en/workflows.html +2 -2
  36. package/docs-site/content/index.html +19 -19
  37. package/docs-site/content/runtime.html +4 -4
  38. package/docs-site/content/whats-new.html +12 -1
  39. package/docs-site/content/workflows.html +2 -2
  40. package/docs-site/context-engine.html +1 -1
  41. package/docs-site/en/architecture.html +1 -1
  42. package/docs-site/en/benchmark.html +135 -91
  43. package/docs-site/en/commands.html +1 -1
  44. package/docs-site/en/context-engine.html +1 -1
  45. package/docs-site/en/index.html +20 -20
  46. package/docs-site/en/mcp-approval.html +1 -1
  47. package/docs-site/en/mcp-auth.html +1 -1
  48. package/docs-site/en/mcp-servers.html +1 -1
  49. package/docs-site/en/mcp.html +1 -1
  50. package/docs-site/en/permissions.html +1 -1
  51. package/docs-site/en/profiles.html +1 -1
  52. package/docs-site/en/quickstart.html +1 -1
  53. package/docs-site/en/runtime.html +5 -5
  54. package/docs-site/en/scope.html +1 -1
  55. package/docs-site/en/security.html +1 -1
  56. package/docs-site/en/team.html +1 -1
  57. package/docs-site/en/whats-new.html +13 -2
  58. package/docs-site/en/workflows.html +3 -3
  59. package/docs-site/index.html +20 -20
  60. package/docs-site/mcp-approval.html +1 -1
  61. package/docs-site/mcp-auth.html +1 -1
  62. package/docs-site/mcp-servers.html +1 -1
  63. package/docs-site/mcp.html +1 -1
  64. package/docs-site/permissions.html +1 -1
  65. package/docs-site/profiles.html +1 -1
  66. package/docs-site/quickstart.html +1 -1
  67. package/docs-site/runtime.html +5 -5
  68. package/docs-site/scope.html +1 -1
  69. package/docs-site/security.html +1 -1
  70. package/docs-site/team.html +1 -1
  71. package/docs-site/whats-new.html +13 -2
  72. package/docs-site/workflows.html +3 -3
  73. package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
  74. package/evals/fixtures/benchmark-assurance-evidence.valid.json +1 -1
  75. package/evals/fixtures/codex-relative-efficiency-v1.invalid.json +4 -0
  76. package/evals/fixtures/codex-relative-efficiency-v1.valid.json +94 -0
  77. package/evals/fixtures/helper-request.invalid.json +1 -1
  78. package/evals/fixtures/helper-request.valid.json +1 -1
  79. package/evals/fixtures/piagent-webui/subagent-tree-v1.valid.json +7 -0
  80. package/evals/fixtures/task-authority-snapshot.valid.json +4 -4
  81. package/evals/fs4-readiness-matrix.v1.json +2 -2
  82. package/evals/fs5-causal-arm.v1.json +2 -2
  83. package/evals/fs5-pilot-protocol.v1.json +3 -3
  84. package/evals/fs5-pilot-protocol.v2.json +8 -8
  85. package/evals/fs5-pilot-protocol.v3.json +8 -8
  86. package/evals/fs5-pilot-protocol.v4.json +6 -6
  87. package/evals/fs5-pilot-protocol.v5.json +7 -6
  88. package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
  89. package/evals/runtime-conformance-v1/README.md +43 -0
  90. package/evals/runtime-conformance-v1/evaluate-paid-canary.mjs +198 -0
  91. package/evals/runtime-conformance-v1/lane.json +57 -0
  92. package/evals/runtime-conformance-v1/paid-canary.json +31 -0
  93. package/evals/runtime-conformance-v1/runner.mjs +456 -0
  94. package/package.json +1 -1
  95. package/packages/piagent-core/README.md +13 -4
  96. package/packages/piagent-core/benchmark/benchmark-codex-relative-efficiency.js +698 -0
  97. package/packages/piagent-core/benchmark/benchmark-core.js +4 -3
  98. package/packages/piagent-core/benchmark/benchmark-forensics.js +4 -2
  99. package/packages/piagent-core/benchmark/benchmark-preflight.js +3 -1
  100. package/packages/piagent-core/benchmark/benchmark-provider-free-evidence.js +345 -0
  101. package/packages/piagent-core/benchmark/benchmark-record-validation.js +74 -5
  102. package/packages/piagent-core/benchmark/benchmark-report.js +15 -1
  103. package/packages/piagent-core/benchmark/benchmark-runtime-causal.js +244 -0
  104. package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +201 -4
  105. package/packages/piagent-core/benchmark/benchmark-suite.js +22 -1
  106. package/packages/piagent-core/benchmark/benchmark-summary-support.js +4 -0
  107. package/packages/piagent-core/benchmark/benchmark-usage.js +15 -4
  108. package/packages/piagent-core/capabilities/runtime-integrity.js +3 -0
  109. package/packages/piagent-core/extensions/acceptance-behavior-proof.js +806 -0
  110. package/packages/piagent-core/extensions/acceptance-receipt.js +89 -48
  111. package/packages/piagent-core/extensions/context-engine.js +1 -11
  112. package/packages/piagent-core/extensions/context-index-policy.js +4 -0
  113. package/packages/piagent-core/extensions/context-query-paths.js +57 -0
  114. package/packages/piagent-core/extensions/criterion-context-pack.js +16 -3
  115. package/packages/piagent-core/extensions/criterion-graph.js +4 -1
  116. package/packages/piagent-core/extensions/fallback-base-policy.ts +101 -0
  117. package/packages/piagent-core/extensions/guard-types.ts +4 -0
  118. package/packages/piagent-core/extensions/piagent-guard.ts +194 -226
  119. package/packages/piagent-core/extensions/task-lifecycle.js +32 -0
  120. package/packages/piagent-core/extensions/task-state.js +69 -8
  121. package/packages/piagent-core/extensions/verification-intelligence.js +49 -12
  122. package/packages/piagent-core/extensions/workspace-evidence-roots.js +52 -16
  123. package/packages/piagent-core/package.json +1 -1
  124. package/packages/piagent-core/policies/base-policy.json +42 -2
  125. package/packages/piagent-core/policy/authority-manifest.v1.json +3 -3
  126. package/packages/piagent-core/prompts/be-to-fe.md +2 -8
  127. package/packages/piagent-core/prompts/plan.md +1 -1
  128. package/packages/piagent-core/prompts/platform-improve.md +2 -6
  129. package/packages/piagent-core/prompts/review.md +2 -7
  130. package/packages/piagent-core/prompts/scout.md +1 -1
  131. package/packages/piagent-core/prompts/task.md +4 -3
  132. package/packages/piagent-core/runtime/hooks/completion-hook.ts +7 -6
  133. package/packages/piagent-core/runtime/hooks/input-hook.ts +6 -0
  134. package/packages/piagent-core/runtime/hooks/session-start-hook.ts +100 -1
  135. package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +58 -72
  136. package/packages/piagent-core/runtime/hooks/tool-result-value-helpers.ts +66 -0
  137. package/packages/piagent-core/runtime/inspection/bounded-emission-guard.ts +286 -0
  138. package/packages/piagent-core/runtime/inspection/runtime-event-store.ts +37 -7
  139. package/packages/piagent-core/runtime/inspection/task-subagent-tree.ts +27 -4
  140. package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +20 -3
  141. package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +47 -11
  142. package/packages/piagent-core/runtime/orchestration/helper-lifecycle.ts +16 -12
  143. package/packages/piagent-core/runtime/orchestration/minimal-delegation-policy.ts +71 -0
  144. package/packages/piagent-core/runtime/orchestration/owned-work-budget.ts +28 -20
  145. package/packages/piagent-core/runtime/orchestration/role-policy.ts +27 -9
  146. package/packages/piagent-core/runtime/orchestration/subagent-tool-policy.ts +329 -0
  147. package/packages/piagent-core/runtime/policy/block-remedy.ts +0 -4
  148. package/packages/piagent-core/runtime/product/efficiency-metrics.ts +3 -0
  149. package/packages/piagent-core/runtime/quality/edit-freshness-guard.ts +102 -0
  150. package/packages/piagent-core/runtime/quality/exact-output-contract.ts +20 -10
  151. package/packages/piagent-core/runtime/quality/performance-assurance.ts +3 -3
  152. package/packages/piagent-core/runtime/recovery/continuation-budget.ts +1 -1
  153. package/packages/piagent-core/runtime/recovery/handoff-projection.ts +4 -1
  154. package/packages/piagent-core/runtime/recovery/pristine-task-policy-refresh.ts +91 -0
  155. package/packages/piagent-core/runtime/recovery/recovery-policy.ts +7 -4
  156. package/packages/piagent-core/runtime/recovery/resume-state.ts +112 -14
  157. package/packages/piagent-core/runtime/recovery/semantic-repair-handshake.ts +7 -13
  158. package/packages/piagent-core/runtime/recovery/task-evidence-coverage-refresh.ts +217 -0
  159. package/packages/piagent-core/runtime/registration/context-commands.ts +16 -2
  160. package/packages/piagent-core/runtime/registration/knowledge-tools.ts +9 -5
  161. package/packages/piagent-core/runtime/registration/operator-request-intake.ts +49 -0
  162. package/packages/piagent-core/runtime/registration/policy-tools.ts +4 -4
  163. package/packages/piagent-core/runtime/registration/session-commands.ts +1 -1
  164. package/packages/piagent-core/runtime/registration/task-completion-tools.ts +12 -9
  165. package/packages/piagent-core/runtime/registration/task-evidence-tools.ts +21 -12
  166. package/packages/piagent-core/runtime/registration/task-start-guidance.ts +19 -13
  167. package/packages/piagent-core/runtime/registration/task-start-manifest.ts +54 -0
  168. package/packages/piagent-core/runtime/registration/task-start-tool.ts +57 -57
  169. package/packages/piagent-core/runtime/runtime-limits.ts +16 -2
  170. package/packages/piagent-core/runtime/session/adaptive-context-analysis.ts +461 -0
  171. package/packages/piagent-core/runtime/session/adaptive-context-governor.ts +464 -0
  172. package/packages/piagent-core/runtime/session/adaptive-context-ledger.ts +265 -0
  173. package/packages/piagent-core/runtime/session/message-signals.ts +7 -3
  174. package/packages/piagent-core/runtime/session/operator-request-carry.ts +44 -0
  175. package/packages/piagent-core/runtime/session/runtime-state.ts +11 -7
  176. package/packages/piagent-core/runtime/session/source-checkout-read-grants.ts +35 -0
  177. package/packages/piagent-core/runtime/session/system-prompt.ts +76 -14
  178. package/packages/piagent-core/runtime/session/task-contract-presentation.ts +39 -0
  179. package/packages/piagent-core/runtime/tools/phase-tools.ts +8 -8
  180. package/packages/piagent-core/runtime/tools/tool-groups.ts +12 -2
  181. package/packages/piagent-core/runtime/verification/exact-verifier-reuse.ts +101 -0
  182. package/packages/piagent-core/runtime/workflows/runtime-intake-compaction.ts +164 -0
  183. package/packages/piagent-core/runtime/workflows/task-intake.ts +167 -39
  184. package/packages/piagent-core/subagents/piagent-oracle.md +5 -3
  185. package/packages/piagent-core/subagents/piagent-planner.md +6 -4
  186. package/packages/piagent-core/subagents/piagent-reviewer.md +5 -3
  187. package/packages/piagent-core/subagents/piagent-scout.md +4 -2
  188. package/packages/piagent-core/subagents/piagent-worker.md +6 -4
  189. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-CxeZqTte.js → MarkdownMessage-B4XehTCU.js} +1 -1
  190. package/packages/piagent-webui/dist/client/assets/{index-DynIMe8I.js → index-CgOEyRPG.js} +4 -4
  191. package/packages/piagent-webui/dist/client/index.html +1 -1
  192. package/packages/piagent-webui/extension/piagent-webui.ts +3 -1
  193. package/packages/piagent-webui/extension/same-session-bridge.ts +1 -1
  194. package/packages/piagent-webui/extension/session-stream-adapter.ts +33 -10
  195. package/packages/piagent-webui/gateway/gateway-session-stream.ts +37 -21
  196. package/packages/piagent-webui/gateway/session-operation-lifecycle.ts +237 -0
  197. package/packages/piagent-webui/gateway/session-operation-retry-control.ts +38 -0
  198. package/packages/piagent-webui/gateway/session-operation-watchdog.ts +9 -2
  199. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +8 -7
  200. package/packages/piagent-webui/ownership/session-lease-store.ts +5 -2
  201. package/packages/piagent-webui/server/core-inspection-provider.ts +2 -1
  202. package/packages/piagent-webui/server/transcript-projection.ts +91 -5
  203. package/schemas/codex-relative-efficiency-v1.schema.json +155 -0
  204. package/schemas/piagent-webui/runtime-event-v2.schema.json +15 -5
  205. package/schemas/piagent-webui/subagent-tree-v1.schema.json +11 -1
  206. package/schemas/project-profile.schema.json +1 -1
  207. package/schemas/task-contract.schema.json +15 -0
  208. package/scripts/benchmark-runner-core.mjs +14 -6
  209. package/scripts/benchmark-runner-finalization.mjs +4 -0
  210. package/scripts/configure-subagents.sh +42 -34
  211. package/scripts/helper-evaluation.mjs +1 -1
  212. package/scripts/team-doctor.sh +1 -1
  213. package/scripts/verify-local.sh +5 -0
@@ -0,0 +1,806 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+
5
+ import { criterionGraphValidationErrors } from "./criterion-graph.js";
6
+ import { regexCanStartAfterLexicalChunks } from "./javascript-regex-evidence.js";
7
+ import { normalizePathCandidate } from "./policy-core.js";
8
+
9
+ const BOUND_STRING = "__pi_bound_string_";
10
+ const BEHAVIOR_STOPWORDS = new Set([
11
+ "after", "also", "before", "behavior", "change", "configured", "every", "first", "remain", "requested",
12
+ "return", "shape", "standalone", "support", "value", "verification", "without"
13
+ ]);
14
+
15
+ function sha256(value) {
16
+ return crypto.createHash("sha256").update(String(value ?? "")).digest("hex");
17
+ }
18
+
19
+ function escapeRegex(value) {
20
+ return String(value ?? "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
21
+ }
22
+
23
+ function normalizedText(value) {
24
+ return String(value ?? "").normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
25
+ }
26
+
27
+ export function durableBehaviorProofRequired(text, changeMode = "source-change") {
28
+ const value = String(text ?? "");
29
+ if (changeMode === "read-only") return false;
30
+ return /\b(?:concurren|do not mutate|must not mutate|without mutating|return shape|returned? (?:element|object|value|representation)|preserve (?:the )?(?:api|input|output|return)|public api|exact error|typeerror|syntaxerror|rangeerror)\b/i.test(value);
31
+ }
32
+
33
+ function boundCriterionGraphNode(task, criterion) {
34
+ if (!task?.criterionGraph || criterionGraphValidationErrors(task.criterionGraph, task).length > 0) return undefined;
35
+ const criterionIndex = (Array.isArray(task.acceptanceCriteria) ? task.acceptanceCriteria : [])
36
+ .findIndex((text) => typeof text === "string" && sha256(text) === criterion?.hash);
37
+ return criterionIndex < 0 ? undefined : task.criterionGraph.nodes.find((node) => node.criterionIndex === criterionIndex);
38
+ }
39
+
40
+ function boundCriterionText(task, criterion) {
41
+ return (Array.isArray(task?.acceptanceCriteria) ? task.acceptanceCriteria : [])
42
+ .find((text) => typeof text === "string" && sha256(text) === criterion?.hash);
43
+ }
44
+
45
+ export function criterionRequiresBehavioralProof(task, criterion, taskText) {
46
+ return durableBehaviorProofRequired(taskText, task?.changeMode)
47
+ && boundCriterionGraphNode(task, criterion)?.proofKinds?.includes("behavioral-check") === true;
48
+ }
49
+
50
+ export function isVerificationOnlyCriterion(text) {
51
+ const value = normalizedText(text).replace(/^\s*\[[^\]]+\]\s*/, "").trim();
52
+ return /^(?:verify(?: the)? project|(?:run|execute|rerun|re-run)\s+(?:the\s+)?(?:(?:configured|exact|project|workspace|focused)\s+)?(?:verification(?: commands?)?|verifiers?|tests?|typecheck|lint|build)\b[\s\S]*|(?:the\s+)?(?:(?:configured|exact|project|workspace)\s+)?(?:verification(?: command)?|verifier|tests?|typecheck|lint|build)\b[\s\S]*\b(?:pass(?:es)?|run|succeed(?:s)?|verify)\b[\s\S]*)[.!?]*$/.test(value);
53
+ }
54
+
55
+ /* A small lexer used only to bind executable module strings and call inputs. */
56
+ function bindJavaScriptStrings(value) {
57
+ const source = String(value ?? "");
58
+ const output = [];
59
+ const strings = [];
60
+ let index = 0;
61
+ while (index < source.length) {
62
+ const current = source[index];
63
+ const next = source[index + 1];
64
+ if (current === "/" && next === "/") {
65
+ while (index < source.length && !/[\r\n]/.test(source[index])) { output.push(" "); index += 1; }
66
+ continue;
67
+ }
68
+ if (current === "/" && next === "*") {
69
+ const end = source.indexOf("*/", index + 2);
70
+ const limit = end < 0 ? source.length : end + 2;
71
+ while (index < limit) { output.push(/[\r\n]/.test(source[index]) ? source[index] : " "); index += 1; }
72
+ continue;
73
+ }
74
+ if (current === "'" || current === '"') {
75
+ const quote = current;
76
+ let cursor = index + 1;
77
+ let payload = "";
78
+ let closed = false;
79
+ while (cursor < source.length) {
80
+ if (source[cursor] === "\\") { payload += source.slice(cursor, cursor + 2); cursor += 2; continue; }
81
+ if (source[cursor] === quote) { cursor += 1; closed = true; break; }
82
+ payload += source[cursor]; cursor += 1;
83
+ }
84
+ if (closed) {
85
+ output.push(`${BOUND_STRING}${strings.length}__`);
86
+ strings.push(payload);
87
+ } else {
88
+ output.push(" ".repeat(Math.max(1, cursor - index)));
89
+ }
90
+ index = cursor;
91
+ continue;
92
+ }
93
+ if (current === "`") {
94
+ let cursor = index + 1;
95
+ let payload = "";
96
+ let closed = false;
97
+ let interpolated = false;
98
+ while (cursor < source.length) {
99
+ if (source[cursor] === "\\") { payload += source.slice(cursor, cursor + 2); cursor += 2; continue; }
100
+ if (source[cursor] === "$" && source[cursor + 1] === "{") interpolated = true;
101
+ if (source[cursor] === "`") { cursor += 1; closed = true; break; }
102
+ payload += source[cursor]; cursor += 1;
103
+ }
104
+ if (closed && !interpolated) {
105
+ output.push(`${BOUND_STRING}${strings.length}__`);
106
+ strings.push(payload);
107
+ } else {
108
+ output.push(" ".repeat(Math.max(1, cursor - index)));
109
+ }
110
+ index = cursor;
111
+ continue;
112
+ }
113
+ if (current === "/" && next !== "=" && regexCanStartAfterLexicalChunks(output)) {
114
+ let cursor = index + 1;
115
+ let inClass = false;
116
+ let closed = false;
117
+ while (cursor < source.length) {
118
+ if (source[cursor] === "\\") { cursor += 2; continue; }
119
+ if (source[cursor] === "[") inClass = true;
120
+ else if (source[cursor] === "]") inClass = false;
121
+ else if (source[cursor] === "/" && !inClass) { cursor += 1; while (/[a-z]/i.test(source[cursor] ?? "")) cursor += 1; closed = true; break; }
122
+ else if (/[\r\n]/.test(source[cursor])) break;
123
+ cursor += 1;
124
+ }
125
+ if (closed) {
126
+ output.push(" ".repeat(Math.max(1, cursor - index)));
127
+ index = cursor;
128
+ continue;
129
+ }
130
+ }
131
+ output.push(current);
132
+ index += 1;
133
+ }
134
+ return { code: output.join(""), strings };
135
+ }
136
+
137
+ function boundStringValue(token, strings) {
138
+ const index = Number(String(token ?? "").match(/^__pi_bound_string_(\d+)__$/)?.[1]);
139
+ return Number.isInteger(index) ? strings[index] : undefined;
140
+ }
141
+
142
+ function moduleRecords(entry) {
143
+ const evidence = String(entry.evidenceText ?? "");
144
+ if (!/\b(?:import|export|require)\b[\s\S]{0,260}\b__pi_[a-z0-9_]+__/i.test(evidence)) return [];
145
+ const { code, strings } = bindJavaScriptStrings(entry.text);
146
+ const records = [];
147
+ const token = "(__pi_bound_string_[0-9]+__)";
148
+ const patterns = [
149
+ { kind: "module", pattern: new RegExp(`\\b(?:import|export)\\s+([\\s\\S]{0,220}?)\\s+from\\s+${token}`, "g") },
150
+ { kind: "side-effect", pattern: new RegExp(`\\bimport\\s*(?:\\(\\s*)?${token}`, "g") },
151
+ { kind: "require", pattern: new RegExp(`\\b(?:const|let|var)\\s+([^=;\\n]{1,160})=\\s*require\\s*\\(\\s*${token}`, "g") }
152
+ ];
153
+ for (const { kind, pattern } of patterns) {
154
+ for (const match of code.matchAll(pattern)) {
155
+ const specifier = boundStringValue(match.at(-1), strings);
156
+ if (typeof specifier === "string") records.push({ kind, clause: match[1] ?? "", specifier });
157
+ }
158
+ }
159
+ return records;
160
+ }
161
+
162
+ function importedBindings(record) {
163
+ const bindings = [];
164
+ const clause = record.clause.trim();
165
+ for (const match of clause.matchAll(/(?:^|[{,])\s*([a-z_$][a-z0-9_$]*)\s*(?:as\s+([a-z_$][a-z0-9_$]*))?/gi)) {
166
+ bindings.push({ exported: match[1], local: match[2] ?? match[1] });
167
+ }
168
+ const namespace = clause.match(/\*\s+as\s+([a-z_$][a-z0-9_$]*)/i)?.[1];
169
+ if (namespace) bindings.push({ exported: "*", local: namespace });
170
+ const defaultBinding = clause.match(/^([a-z_$][a-z0-9_$]*)\s*(?:,|$)/i)?.[1];
171
+ if (defaultBinding && !bindings.some((item) => item.local === defaultBinding)) {
172
+ bindings.push({ exported: "default", local: defaultBinding });
173
+ }
174
+ if (record.kind === "require") {
175
+ for (const match of clause.matchAll(/(?:^|[{,])\s*([a-z_$][a-z0-9_$]*)\s*(?::\s*([a-z_$][a-z0-9_$]*))?/gi)) {
176
+ bindings.push({ exported: match[1], local: match[2] ?? match[1] });
177
+ }
178
+ }
179
+ return bindings;
180
+ }
181
+
182
+ function exportedNames(sourceEntries) {
183
+ const names = new Set();
184
+ for (const entry of sourceEntries) {
185
+ const text = String(entry.evidenceText ?? "");
186
+ for (const match of text.matchAll(/\bexport\s+(?:async\s+)?(?:function|class|const|let|var)\s+([a-z_$][a-z0-9_$]*)/gi)) names.add(match[1]);
187
+ for (const match of text.matchAll(/\bexport\s*\{([^}]+)\}/gi)) {
188
+ for (const item of match[1].split(",")) {
189
+ const name = item.trim().match(/(?:^|\s+as\s+)([a-z_$][a-z0-9_$]*)$/i)?.[1];
190
+ if (name) names.add(name);
191
+ }
192
+ }
193
+ for (const match of text.matchAll(/\b(?:module\.exports|exports)\.([a-z_$][a-z0-9_$]*)\s*=/gi)) names.add(match[1]);
194
+ }
195
+ return names;
196
+ }
197
+
198
+ function resolvedSpecifierMatchesSource(testPath, specifier, sourceFiles) {
199
+ if (!specifier.startsWith(".")) return false;
200
+ const resolved = normalizePathCandidate(path.posix.normalize(path.posix.join(path.posix.dirname(testPath), specifier)));
201
+ const candidates = new Set([resolved, resolved.replace(/\.[a-z0-9]+$/i, "")]);
202
+ for (const source of sourceFiles.map((file) => normalizePathCandidate(file))) {
203
+ const stem = source.replace(/\.[a-z0-9]+$/i, "");
204
+ if (candidates.has(source) || candidates.has(stem) || stem === `${resolved.replace(/\/$/, "")}/index`) return true;
205
+ }
206
+ return false;
207
+ }
208
+
209
+ /* linked = proven; unknown = alias/barrel/indirect may be valid; unlinked = only unrelated imports. */
210
+ function testSourceLinkage(corpus) {
211
+ const sourceExports = exportedNames(corpus.sourceEntries);
212
+ const linkedEntries = [];
213
+ let unresolvedProjectImport = false;
214
+ for (const entry of corpus.testEntries) {
215
+ const records = moduleRecords(entry);
216
+ const callables = new Set();
217
+ let linked = false;
218
+ for (const record of records) {
219
+ const bindings = importedBindings(record);
220
+ if (resolvedSpecifierMatchesSource(entry.path, record.specifier, corpus.sourceFiles)) {
221
+ linked = true;
222
+ for (const binding of bindings) {
223
+ if (binding.exported === "*") sourceExports.forEach((name) => callables.add(`${binding.local}.${name}`));
224
+ else callables.add(binding.local);
225
+ }
226
+ continue;
227
+ }
228
+ if (record.specifier.startsWith(".") || /^[#@~]/.test(record.specifier)) unresolvedProjectImport = true;
229
+ }
230
+ if (linked) linkedEntries.push({ entry, callables: [...callables] });
231
+ }
232
+ if (linkedEntries.length > 0) return { status: "linked", entries: linkedEntries };
233
+ return { status: unresolvedProjectImport ? "unknown" : "unlinked", entries: [] };
234
+ }
235
+
236
+ function balancedCallEnd(code, openIndex) {
237
+ let depth = 0;
238
+ for (let index = openIndex; index < code.length; index += 1) {
239
+ if (code[index] === "(") depth += 1;
240
+ else if (code[index] === ")" && --depth === 0) return index + 1;
241
+ }
242
+ return -1;
243
+ }
244
+
245
+ function assertionSlices(code) {
246
+ const assertions = [];
247
+ const pattern = /\b(?:assert(?:\.[a-z][a-z0-9_]*)?|expect)\s*\(/gi;
248
+ for (const match of code.matchAll(pattern)) {
249
+ const open = code.indexOf("(", match.index);
250
+ let end = balancedCallEnd(code, open);
251
+ if (end < 0) continue;
252
+ if (/\bexpect\s*\(/i.test(match[0])) {
253
+ const statementEnd = code.slice(end).search(/[;\r\n]/);
254
+ if (statementEnd >= 0) end += statementEnd;
255
+ }
256
+ assertions.push({ start: match.index, end, code: code.slice(match.index, end) });
257
+ }
258
+ return assertions;
259
+ }
260
+
261
+ function assignedCallNames(code, callStart) {
262
+ const prefix = code.slice(Math.max(0, callStart - 160), callStart);
263
+ return [
264
+ prefix.match(/\b(?:const|let|var)\s+([a-z_$][a-z0-9_$]*)\s*=\s*(?:await\s*)?$/i)?.[1],
265
+ prefix.match(/\b([a-z_$][a-z0-9_$]*)\s*=\s*(?:await\s*)?$/i)?.[1]
266
+ ].filter(Boolean);
267
+ }
268
+
269
+ function nextBindingWrite(code, name, after) {
270
+ const escaped = escapeRegex(name);
271
+ const pattern = new RegExp(`\\b(?:const|let|var)\\s+${escaped}\\s*=|\\b${escaped}\\s*=(?!=|>)`, "g");
272
+ pattern.lastIndex = after;
273
+ return pattern.exec(code)?.index ?? Number.POSITIVE_INFINITY;
274
+ }
275
+
276
+ function callArgumentNames(slice) {
277
+ const body = slice.slice(slice.indexOf("(") + 1, -1);
278
+ return [...body.matchAll(/(?:^|,)\s*([a-z_$][a-z0-9_$]*)\s*(?=,|$)/gi)].map((match) => match[1]);
279
+ }
280
+
281
+ function callableSlices(code, strings, callables) {
282
+ const calls = [];
283
+ const assertions = assertionSlices(code);
284
+ for (const callable of callables) {
285
+ const pattern = new RegExp(`\\b${escapeRegex(callable)}\\s*\\(`, "g");
286
+ for (const match of code.matchAll(pattern)) {
287
+ const open = code.indexOf("(", match.index);
288
+ const end = balancedCallEnd(code, open);
289
+ if (end < 0) continue;
290
+ const slice = code.slice(match.index, end);
291
+ const values = [...slice.matchAll(/__pi_bound_string_[0-9]+__/g)]
292
+ .map((item) => boundStringValue(item[0], strings)).filter((item) => typeof item === "string");
293
+ const argumentNames = callArgumentNames(slice);
294
+ const resultNames = [...new Set(assignedCallNames(code, match.index))];
295
+ const bindingNames = [...resultNames, ...argumentNames];
296
+ const resultWriteLimits = new Map(resultNames.map((name) => [name, nextBindingWrite(code, name, end)]));
297
+ const boundAssertions = assertions.filter((assertion) => (
298
+ (assertion.start <= match.index && assertion.end >= end)
299
+ || assertion.start >= end && bindingNames.some((name) => (
300
+ new RegExp(`\\b${escapeRegex(name)}\\b`).test(assertion.code)
301
+ && (!resultWriteLimits.has(name) || assertion.start < resultWriteLimits.get(name))
302
+ ))
303
+ ));
304
+ const assertionValues = boundAssertions.flatMap((assertion) => (
305
+ [...assertion.code.matchAll(/__pi_bound_string_[0-9]+__/g)]
306
+ .map((item) => boundStringValue(item[0], strings)).filter((item) => typeof item === "string")
307
+ ));
308
+ if (boundAssertions.length > 0) calls.push({
309
+ slice, values, strings, resultNames, argumentNames, assertionValues, prelude: code.slice(0, match.index),
310
+ assertions: boundAssertions.map((item) => item.code)
311
+ });
312
+ }
313
+ }
314
+ return calls;
315
+ }
316
+
317
+ function splitTopLevel(value) {
318
+ const parts = [];
319
+ let start = 0, round = 0, square = 0, curly = 0;
320
+ for (let index = 0; index < value.length; index += 1) {
321
+ const token = value[index];
322
+ if (token === "(") round += 1;
323
+ else if (token === ")") round -= 1;
324
+ else if (token === "[") square += 1;
325
+ else if (token === "]") square -= 1;
326
+ else if (token === "{") curly += 1;
327
+ else if (token === "}") curly -= 1;
328
+ else if (token === "," && round === 0 && square === 0 && curly === 0) {
329
+ parts.push(value.slice(start, index).trim()); start = index + 1;
330
+ }
331
+ }
332
+ parts.push(value.slice(start).trim());
333
+ return parts;
334
+ }
335
+
336
+ function assertionComparisons(call) {
337
+ return call.assertions.flatMap((assertion) => {
338
+ const assertCall = assertion.match(/^\s*assert\.(?:equal|strictEqual|deepEqual|deepStrictEqual)\s*\(/i);
339
+ if (assertCall) {
340
+ const open = assertion.indexOf("(", assertCall.index);
341
+ const end = balancedCallEnd(assertion, open);
342
+ const [actual, expected] = end > open ? splitTopLevel(assertion.slice(open + 1, end - 1)) : [];
343
+ return actual && expected ? [{ actual, expected }] : [];
344
+ }
345
+ const expectCall = assertion.match(/^\s*expect\s*\(/i);
346
+ if (!expectCall) return [];
347
+ const open = assertion.indexOf("(", expectCall.index);
348
+ const end = balancedCallEnd(assertion, open);
349
+ const matcher = end > open ? assertion.slice(end).match(/^\s*\.to(?:Be|Equal|StrictEqual)\s*\(/i) : undefined;
350
+ if (!matcher) return [];
351
+ const expectedOpen = assertion.indexOf("(", end + matcher.index);
352
+ const expectedEnd = balancedCallEnd(assertion, expectedOpen);
353
+ return expectedEnd > expectedOpen ? [{ actual: assertion.slice(open + 1, end - 1).trim(), expected: assertion.slice(expectedOpen + 1, expectedEnd - 1).trim() }] : [];
354
+ });
355
+ }
356
+
357
+ function exactBoundValue(expression, strings) {
358
+ const value = String(expression ?? "").trim();
359
+ if (value === "true") return true;
360
+ if (value === "false") return false;
361
+ return boundStringValue(value, strings);
362
+ }
363
+
364
+ function propertyExpression(objectExpression, property, strings) {
365
+ const value = String(objectExpression ?? "").trim();
366
+ if (!(value.startsWith("{") && value.endsWith("}"))) return undefined;
367
+ for (const member of splitTopLevel(value.slice(1, -1))) {
368
+ const colon = member.indexOf(":");
369
+ if (colon < 0) continue;
370
+ const key = member.slice(0, colon).trim();
371
+ if (key === property || boundStringValue(key, strings) === property) return member.slice(colon + 1).trim();
372
+ }
373
+ return undefined;
374
+ }
375
+
376
+ function callResultExpression(call, expression) {
377
+ const value = String(expression ?? "");
378
+ return call.resultNames.some((name) => new RegExp(`^\\s*${escapeRegex(name)}\\b`).test(value)) || value.includes(call.slice);
379
+ }
380
+
381
+ function actualField(call, actual, container, field) {
382
+ if (!callResultExpression(call, actual)) return false;
383
+ const dot = new RegExp(`\\.${escapeRegex(container)}\\s*\\.\\s*${escapeRegex(field)}\\b`);
384
+ if (dot.test(actual)) return true;
385
+ const bracket = new RegExp(`\\.${escapeRegex(container)}\\s*\\[\\s*(__pi_bound_string_[0-9]+__)\\s*\\]`).exec(actual);
386
+ return bracket ? boundStringValue(bracket[1], call.strings) === field : false;
387
+ }
388
+
389
+ function actualContainer(call, actual, container) {
390
+ return callResultExpression(call, actual) && new RegExp(`\\.${escapeRegex(container)}\\s*$`).test(actual);
391
+ }
392
+
393
+ function actualWholeResult(call, actual) {
394
+ const value = String(actual ?? "").trim();
395
+ return call.resultNames.includes(value) || value === call.slice;
396
+ }
397
+
398
+ function expectedValueMatches(expression, expected, strings) {
399
+ return exactBoundValue(expression, strings) === expected;
400
+ }
401
+
402
+ function assertedFlagValue(call, flag, expected) {
403
+ return assertionComparisons(call).some(({ actual, expected: asserted }) => {
404
+ if (actualField(call, actual, "flags", flag)) return expectedValueMatches(asserted, expected, call.strings);
405
+ const flags = actualContainer(call, actual, "flags") ? asserted
406
+ : actualWholeResult(call, actual) ? propertyExpression(asserted, "flags", call.strings) : undefined;
407
+ const field = propertyExpression(flags, flag, call.strings);
408
+ return field !== undefined && expectedValueMatches(field, expected, call.strings);
409
+ });
410
+ }
411
+
412
+ function exactStringArray(expression, strings) {
413
+ const value = String(expression ?? "").trim();
414
+ if (!(value.startsWith("[") && value.endsWith("]"))) return undefined;
415
+ const items = splitTopLevel(value.slice(1, -1));
416
+ if (items.length === 1 && items[0] === "") return [];
417
+ const resolved = items.map((item) => boundStringValue(item, strings));
418
+ return resolved.every((item) => typeof item === "string") ? resolved : undefined;
419
+ }
420
+
421
+ function assertedPositionalSuffix(call, suffix) {
422
+ return assertionComparisons(call).some(({ actual, expected }) => {
423
+ const positional = actualContainer(call, actual, "positional") ? expected
424
+ : actualWholeResult(call, actual) ? propertyExpression(expected, "positional", call.strings) : undefined;
425
+ const observed = exactStringArray(positional, call.strings);
426
+ return observed !== undefined && JSON.stringify(observed) === JSON.stringify(suffix);
427
+ });
428
+ }
429
+
430
+ function repeatedFlagExpectedValue(values) {
431
+ const flags = [];
432
+ for (let index = 0; index < values.length; index += 1) {
433
+ const token = values[index];
434
+ if (token === "--") break;
435
+ if (!/^--[^=\s]+/.test(token)) continue;
436
+ const equals = token.indexOf("=");
437
+ if (equals >= 0) flags.push({ name: token.slice(0, equals), value: token.slice(equals + 1) });
438
+ else if (values[index + 1] !== undefined && !/^--/.test(values[index + 1])) {
439
+ flags.push({ name: token, value: values[index + 1] });
440
+ index += 1;
441
+ } else flags.push({ name: token, value: true });
442
+ }
443
+ const repeated = flags.filter((flag, index) => flags.some((other, otherIndex) => otherIndex < index && other.name === flag.name));
444
+ return repeated.at(-1);
445
+ }
446
+
447
+ function preCallSnapshotName(call, argument) {
448
+ const escaped = escapeRegex(argument);
449
+ const deepPatterns = [
450
+ new RegExp(`\\b(?:const|let)\\s+([a-z_$][a-z0-9_$]*)\\s*=\\s*structuredClone\\s*\\(\\s*${escaped}\\s*\\)`, "gi"),
451
+ new RegExp(`\\b(?:const|let)\\s+([a-z_$][a-z0-9_$]*)\\s*=\\s*JSON\\.parse\\s*\\(\\s*JSON\\.stringify\\s*\\(\\s*${escaped}\\s*\\)\\s*\\)`, "gi")
452
+ ];
453
+ const deep = deepPatterns.flatMap((pattern) => [...call.prelude.matchAll(pattern)].map((match) => match[1])).at(-1);
454
+ if (deep) return deep;
455
+ const declaration = [...call.prelude.matchAll(new RegExp(`\\b(?:const|let)\\s+${escaped}\\s*=\\s*\\[([^\\]]*)\\]`, "gi"))].at(-1);
456
+ const primitiveIndexFactory = new RegExp("\\b(?:const|let)\\s+" + escaped + "\\s*=\\s*Array\\.from\\s*\\(\\s*\\{\\s*length\\s*:\\s*\\d+\\s*\\}\\s*,\\s*\\(\\s*[a-z_$][a-z0-9_$]*\\s*,\\s*([a-z_$][a-z0-9_$]*)\\s*\\)\\s*=>\\s*\\1\\s*\\)", "i").test(call.prelude);
457
+ const primitiveElements = primitiveIndexFactory || declaration && splitTopLevel(declaration[1]).every((item) => {
458
+ const value = item.trim();
459
+ return value === "" || exactBoundValue(value, call.strings) !== undefined
460
+ || /^(?:true|false|null|undefined|[-+]?\\d+(?:\\.\\d+)?n?)$/.test(value);
461
+ });
462
+ if (!primitiveElements) return undefined;
463
+ const shallowPatterns = [
464
+ new RegExp(`\\b(?:const|let)\\s+([a-z_$][a-z0-9_$]*)\\s*=\\s*\\[\\s*\\.\\.\\.\\s*${escaped}\\s*\\]`, "gi"),
465
+ new RegExp(`\\b(?:const|let)\\s+([a-z_$][a-z0-9_$]*)\\s*=\\s*${escaped}\\.(?:slice|concat)\\s*\\(\\s*\\)`, "gi"),
466
+ new RegExp(`\\b(?:const|let)\\s+([a-z_$][a-z0-9_$]*)\\s*=\\s*Array\\.from\\s*\\(\\s*${escaped}\\s*\\)`, "gi")
467
+ ];
468
+ return shallowPatterns.flatMap((pattern) => [...call.prelude.matchAll(pattern)].map((match) => match[1])).at(-1);
469
+ }
470
+
471
+ function nonMutationAssertion(call) {
472
+ return call.argumentNames.some((argument) => {
473
+ const snapshot = preCallSnapshotName(call, argument);
474
+ if (!snapshot) return false;
475
+ return call.assertions.some((assertion) => (
476
+ new RegExp(`\\bassert\\.(?:deepEqual|deepStrictEqual|strictEqual)\\s*\\(\\s*${escapeRegex(argument)}\\s*,\\s*${escapeRegex(snapshot)}\\b`, "i").test(assertion)
477
+ || new RegExp(`\\bexpect\\s*\\(\\s*${escapeRegex(argument)}\\s*\\)\\s*\\.to(?:Strict)?Equal\\s*\\(\\s*${escapeRegex(snapshot)}\\b`, "i").test(assertion)
478
+ ));
479
+ });
480
+ }
481
+
482
+ function definedExpectedExpression(expression, strings, prelude = "") {
483
+ const value = String(expression ?? "").trim();
484
+ if (!value || /^(?:undefined|null|void\b)/i.test(value)) return false;
485
+ const identifier = value.match(/^[a-z_$][a-z0-9_$]*$/i)?.[0];
486
+ if (identifier && new RegExp(`\\b(?:const|let)\\s+${escapeRegex(identifier)}\\s*=\\s*(?:\\{|\\[|true\\b|false\\b|[-+]?\\d|${BOUND_STRING})`, "i").test(prelude)) return true;
487
+ return exactBoundValue(value, strings) !== undefined
488
+ || /^(?:true|false|[-+]?\d+(?:\.\d+)?n?|\{|\[)/.test(value);
489
+ }
490
+
491
+ function positiveDefinedPredicate(call, expression, field) {
492
+ const value = String(expression ?? "").trim();
493
+ if (actualContainer(call, value, field)) return true;
494
+ const arrayCheck = value.match(/^Array\.isArray\s*\(([\s\S]+)\)$/i);
495
+ if (arrayCheck && actualContainer(call, arrayCheck[1], field)) return true;
496
+ const comparison = value.match(/^([\s\S]+?)\s*(===|==|!==|!=)\s*([\s\S]+)$/);
497
+ if (!comparison || !actualContainer(call, comparison[1], field)) return false;
498
+ const expected = comparison[3].trim();
499
+ if (comparison[2] === "!==" && expected === "undefined") return true;
500
+ if (comparison[2] === "!=" && /^(?:undefined|null)$/.test(expected)) return true;
501
+ return ["===", "=="].includes(comparison[2]) && definedExpectedExpression(expected, call.strings, call.prelude);
502
+ }
503
+
504
+ function assertionProvesDefinedField(call, field) {
505
+ if (assertionComparisons(call).some(({ actual, expected }) => {
506
+ if (actualContainer(call, actual, field)) return definedExpectedExpression(expected, call.strings, call.prelude);
507
+ if (!actualWholeResult(call, actual)) return false;
508
+ const fieldValue = propertyExpression(expected, field, call.strings);
509
+ return fieldValue !== undefined && definedExpectedExpression(fieldValue, call.strings, call.prelude);
510
+ })) return true;
511
+ return call.assertions.some((assertion) => {
512
+ const ok = assertion.match(/^\s*assert\.ok\s*\(/i);
513
+ if (ok) {
514
+ const open = assertion.indexOf("(", ok.index);
515
+ const end = balancedCallEnd(assertion, open);
516
+ return end > open && positiveDefinedPredicate(call, assertion.slice(open + 1, end - 1), field);
517
+ }
518
+ const expected = assertion.match(/^\s*expect\s*\(/i);
519
+ if (!expected) return false;
520
+ const open = assertion.indexOf("(", expected.index);
521
+ const end = balancedCallEnd(assertion, open);
522
+ return end > open && actualContainer(call, assertion.slice(open + 1, end - 1), field)
523
+ && /^\s*\.toBeDefined\s*\(/i.test(assertion.slice(end));
524
+ });
525
+ }
526
+
527
+ function linkedTestEvidence(linkage) {
528
+ const profiles = [];
529
+ for (const { entry, callables } of linkage.entries) {
530
+ if (!/\b(?:assert(?:\.[a-z][a-z0-9_]*)?\s*\(|expect\s*\()/i.test(entry.evidenceText)) continue;
531
+ const bound = bindJavaScriptStrings(entry.text);
532
+ const calls = callableSlices(bound.code, bound.strings, callables);
533
+ if (calls.length > 0) profiles.push({ path: entry.path, code: bound.code, calls, callables });
534
+ }
535
+ return profiles;
536
+ }
537
+
538
+ function shellTokens(command) {
539
+ return (String(command ?? "").match(/"[^"]*"|'[^']*'|[^\s]+/g) ?? [])
540
+ .map((token) => token.replace(/^(['"])([\s\S]*)\1$/, "$2"));
541
+ }
542
+
543
+ function shellStructure(command) {
544
+ const value = String(command ?? ""), segments = [], operators = [];
545
+ let quote = "", escaped = false, start = 0;
546
+ for (let index = 0; index < value.length; index += 1) {
547
+ const token = value[index];
548
+ if (escaped) { escaped = false; continue; }
549
+ if (token === "\\") { escaped = true; continue; }
550
+ if (quote) { if (token === quote) quote = ""; continue; }
551
+ if (token === "'" || token === '"') { quote = token; continue; }
552
+ const pair = value.slice(index, index + 2);
553
+ const width = ["&&", "||", "|&"].includes(pair) ? 2
554
+ : token === ";" || token === "\n" || token === "|" || (token === "&" && !/[<>]/.test(value[index - 1] ?? "")) ? 1 : 0;
555
+ if (!width) continue;
556
+ if (value.slice(start, index).trim()) segments.push(value.slice(start, index).trim());
557
+ operators.push(value.slice(index, index + width));
558
+ start = index + width; index += width - 1;
559
+ }
560
+ if (value.slice(start).trim()) segments.push(value.slice(start).trim());
561
+ return { segments, operators };
562
+ }
563
+
564
+ function shellSegments(command) { return shellStructure(command).segments; }
565
+
566
+ function workspacePredicatePath(cwd, candidate, executable) {
567
+ if (typeof cwd !== "string" || !cwd) return false;
568
+ const relative = normalizePathCandidate(String(candidate ?? "").replace(/^\.\//, ""));
569
+ if (!relative || path.isAbsolute(relative) || relative.split("/").includes("..")) return false;
570
+ try {
571
+ const target = path.join(cwd, relative);
572
+ if (executable) { fs.accessSync(target, fs.constants.X_OK); return fs.statSync(target).isFile(); }
573
+ return fs.statSync(target).isFile();
574
+ } catch { return false; }
575
+ }
576
+
577
+ function commandAvailable(command) {
578
+ const name = String(command ?? "");
579
+ return /^[a-z0-9_.-]+$/i.test(name) && String(process.env.PATH ?? "").split(path.delimiter).some((directory) => {
580
+ try { fs.accessSync(path.join(directory, name), fs.constants.X_OK); return true; } catch { return false; }
581
+ });
582
+ }
583
+
584
+ function safeConditionPart(part, cwd) {
585
+ const value = String(part ?? "").trim();
586
+ const file = value.match(/^test\s+-(f|x)\s+([^\s]+)$/i);
587
+ if (file) return workspacePredicatePath(cwd, file[2], file[1].toLowerCase() === "x");
588
+ const available = value.match(/^command\s+-v\s+([a-z0-9_.-]+)(?:\s+>\/dev\/null\s+2>&1)?$/i);
589
+ if (available) return commandAvailable(available[1]);
590
+ if (/^node\s+-e\b/.test(value) && /scripts?\|\|\{\}/.test(value) && /s\.test\?0:1/.test(value)) {
591
+ try { return typeof JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8"))?.scripts?.test === "string"; } catch { return false; }
592
+ }
593
+ return value === "true";
594
+ }
595
+
596
+ function verifierWorkingDirectory(cwd, body) {
597
+ const target = body.map((segment) => segment.match(/^cd\s+([^\s]+)$/)?.[1]).find(Boolean);
598
+ if (!target) return cwd;
599
+ const relative = normalizePathCandidate(String(target).replace(/^\.\//, ""));
600
+ const resolved = relative && cwd ? path.resolve(cwd, relative) : "";
601
+ try {
602
+ return resolved && !path.relative(cwd, resolved).split(path.sep).includes("..") && fs.statSync(resolved).isDirectory() ? resolved : undefined;
603
+ } catch { return undefined; }
604
+ }
605
+
606
+ function executableVerifierPlan(command, cwd) {
607
+ const { segments, operators } = shellStructure(command);
608
+ if (operators.some((operator) => ["||", "|", "|&", "&"].includes(operator))) return { segments: [], cwd };
609
+ let cursor = segments.findIndex((segment) => /^if\b/.test(segment));
610
+ if (cursor < 0) return { segments: operators.some((operator) => operator === ";" || operator === "\n") ? [] : segments, cwd };
611
+ let selected;
612
+ while (cursor >= 0 && cursor < segments.length) {
613
+ const marker = segments[cursor].match(/^(?:if|elif)\s+([\s\S]+)$/);
614
+ if (!marker) return { segments: [], cwd };
615
+ const condition = [marker[1]];
616
+ cursor += 1;
617
+ while (cursor < segments.length && !/^then\b/.test(segments[cursor])) condition.push(segments[cursor++]);
618
+ if (cursor >= segments.length) return { segments: [], cwd };
619
+ const body = [segments[cursor].replace(/^then\s*/, "")].filter(Boolean);
620
+ cursor += 1;
621
+ while (cursor < segments.length && !/^(?:elif|else|fi)\b/.test(segments[cursor])) body.push(segments[cursor++]);
622
+ if (condition.every((part) => safeConditionPart(part, cwd))) { selected = body; break; }
623
+ if (!/^elif\b/.test(segments[cursor] ?? "")) return { segments: [], cwd };
624
+ }
625
+ const end = segments.findIndex((segment, index) => index >= cursor && /^fi\b/.test(segment));
626
+ const selectedCwd = selected && verifierWorkingDirectory(cwd, selected);
627
+ return !selected || end < 0 || !selectedCwd ? { segments: [], cwd }
628
+ : { segments: [...selected, ...segments.slice(end + 1)], cwd: selectedCwd };
629
+ }
630
+
631
+ function testPathMatchesToken(testPath, token) {
632
+ const candidate = normalizePathCandidate(String(token ?? "").replace(/^\.\//, ""));
633
+ const target = normalizePathCandidate(testPath);
634
+ if (!/[?*]/.test(candidate)) return candidate === target;
635
+ const pattern = escapeRegex(candidate).replace(/\\\*\\\*\//g, "(?:.*/)?").replace(/\\\*\\\*/g, ".*").replace(/\\\*/g, "[^/]*").replace(/\\\?/g, "[^/]");
636
+ return new RegExp(`^${pattern}$`).test(target);
637
+ }
638
+
639
+ function testRunnerArguments(segment) {
640
+ const tokens = shellTokens(segment).filter((token) => !["then", "do"].includes(token));
641
+ while (/^[a-z_][a-z0-9_]*=/i.test(tokens[0] ?? "")) tokens.shift();
642
+ if (/^(?:npm|pnpm|yarn|bun)$/.test(tokens[0] ?? "")) {
643
+ const scriptIndex = tokens[1] === "run" ? 2 : 1, script = tokens[scriptIndex];
644
+ if (script === "test") return { args: tokens.slice(scriptIndex + 1).filter((token) => token !== "--" && token !== "--if-present") };
645
+ if (/^test:[a-z0-9_.-]+$/i.test(script ?? "")) return { args: [], packageScript: script };
646
+ return undefined;
647
+ }
648
+ const joined = tokens.join(" ");
649
+ const runners = [
650
+ /^node --test\b/, /^(?:uv run |python -m )?pytest\b/,
651
+ /^(?:npx )?(?:vitest(?: run)?|jest|playwright test|cypress run)\b/
652
+ ];
653
+ for (const runner of runners) {
654
+ const match = joined.match(runner);
655
+ if (match) return { args: shellTokens(joined.slice(match[0].length)).filter((token) => token !== "--") };
656
+ }
657
+ if (/^(?:flutter test|\.\/(?:mvnw|gradlew) test)$/.test(joined)) return { args: [] };
658
+ return undefined;
659
+ }
660
+
661
+ function packageTestScriptCovers(cwd, scriptName, testPath) {
662
+ if (typeof cwd !== "string" || !cwd) return false;
663
+ try {
664
+ const script = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8"))?.scripts?.[scriptName];
665
+ const structure = shellStructure(script);
666
+ return typeof script === "string" && structure.segments.length === 1 && structure.operators.length === 0
667
+ && verifierSegmentCoversTest(structure.segments[0], testPath, cwd, false);
668
+ } catch { return false; }
669
+ }
670
+
671
+ function verifierSegmentCoversTest(segment, testPath, cwd, allowPackageScript = true) {
672
+ const runner = testRunnerArguments(segment);
673
+ if (!runner || runner.packageScript && !allowPackageScript) return false;
674
+ if (runner.packageScript) return packageTestScriptCovers(cwd, runner.packageScript, testPath);
675
+ if (/(?:^|\s)--if-present(?:\s|$)/.test(segment) && !packageTestScriptCovers(cwd, "test", testPath)) return false;
676
+ const pathArguments = runner.args.filter((token) => !token.startsWith("-"));
677
+ return pathArguments.length === 0 || pathArguments.some((token) => testPathMatchesToken(testPath, token));
678
+ }
679
+
680
+ export function verifierCommandsCoverTests(task, testPaths, cwd) {
681
+ const commands = (Array.isArray(task?.verifyCommands) ? task.verifyCommands : []).filter((command) => typeof command === "string");
682
+ return testPaths.length > 0 && testPaths.every((testPath) => commands.some((command) => {
683
+ const plan = executableVerifierPlan(command, cwd);
684
+ const prefix = normalizePathCandidate(cwd && plan.cwd ? path.relative(cwd, plan.cwd).replaceAll("\\", "/") : "");
685
+ const normalizedTest = normalizePathCandidate(testPath);
686
+ const relativeTest = prefix ? normalizedTest.startsWith(prefix + "/") ? normalizedTest.slice(prefix.length + 1) : "" : normalizedTest;
687
+ return Boolean(relativeTest) && plan.segments.some((segment) => verifierSegmentCoversTest(segment, relativeTest, plan.cwd));
688
+ }));
689
+ }
690
+
691
+ function criterionBehaviorMatches(task, criterion, profiles, sourceFiles) {
692
+ const rawCriterion = boundCriterionText(task, criterion);
693
+ if (!rawCriterion || profiles.length === 0) return false;
694
+ const text = normalizedText(rawCriterion);
695
+ const calls = profiles.flatMap((profile) => profile.calls);
696
+ const code = calls.flatMap((call) => call.assertions).join("\n");
697
+ const requirements = [];
698
+ if (rawCriterion.split(";").filter((clause) => clause.trim()).length > 1) return false;
699
+ if (/\bfocused\s+tests?\b/.test(text)) requirements.push(calls.length > 0);
700
+ if (/--[a-z0-9_-]+=[a-z0-9_<[{]/i.test(text) || /\bname\s*=\s*value\b/.test(text)) {
701
+ requirements.push(calls.some((call) => call.values.some((value) => {
702
+ const match = value.match(/^--([^=\s]+)=(.+)$/);
703
+ return match ? assertedFlagValue(call, match[1], match[2]) : false;
704
+ })));
705
+ }
706
+ if (/--[a-z0-9_-]+\s+(?:value|<[^>]+>)/i.test(text)) {
707
+ requirements.push(calls.some((call) => call.values.some((value, index) => {
708
+ const name = value.match(/^--([^=\s]+)$/)?.[1], expected = call.values[index + 1];
709
+ return name && expected !== undefined && !/^--/.test(expected) && assertedFlagValue(call, name, expected);
710
+ })));
711
+ }
712
+ if (/\bboolean\b[\s\S]{0,30}--[a-z0-9_-]+/i.test(text)) {
713
+ requirements.push(calls.some((call) => call.values.some((value, index) => {
714
+ const name = value.match(/^--([^=\s]+)$/)?.[1];
715
+ return name && (call.values[index + 1] === undefined || /^--/.test(call.values[index + 1]))
716
+ && assertedFlagValue(call, name, true);
717
+ })));
718
+ }
719
+ if (/\bstandalone\s+`?--`?(?=\s|$)|`?--`?\s+(?:ends?|stops?)\s+flag/i.test(text)) {
720
+ requirements.push(calls.some((call) => {
721
+ const delimiter = call.values.indexOf("--"), suffix = call.values.slice(delimiter + 1);
722
+ return delimiter >= 0 && suffix.length > 0 && assertedPositionalSuffix(call, suffix);
723
+ }));
724
+ }
725
+ if (/\bflag\s+followed\s+by\s+another\s+flag\b/.test(text)) {
726
+ requirements.push(calls.some((call) => call.values.some((value, index) => {
727
+ const name = value.match(/^--([^=\s]+)$/)?.[1];
728
+ return name && /^--[^=\s]+$/.test(call.values[index + 1] ?? "") && assertedFlagValue(call, name, true);
729
+ })));
730
+ }
731
+ if (/\brepeated?\s+flags?\b|\blast\s+value\b/.test(text)) {
732
+ requirements.push(calls.some((call) => {
733
+ const expected = repeatedFlagExpectedValue(call.values);
734
+ return expected !== undefined && assertedFlagValue(call, expected.name.slice(2), expected.value);
735
+ }));
736
+ }
737
+ if (/\b(?:do not|must not|without)\s+mutat|\bpreserve\s+(?:the\s+)?input/.test(text)) {
738
+ requirements.push(calls.some(nonMutationAssertion));
739
+ }
740
+ if (/\breturn shape\b|\breturned? (?:object|representation)\b/.test(text)) {
741
+ requirements.push(calls.some((call) => assertionProvesDefinedField(call, "flags")
742
+ && assertionProvesDefinedField(call, "positional")));
743
+ }
744
+ if (/\bconcurren|\bparallel\b/.test(text)) requirements.push(/\bpromise\.all\s*\(|\ballsettled\s*\(/i.test(code));
745
+ const errorClass = text.match(/\b(typeerror|syntaxerror|rangeerror)\b/)?.[1];
746
+ if (errorClass) requirements.push(new RegExp(`\\b(?:assert\\.throws|rejects|tothrow)\\b[\\s\\S]{0,240}\\b${errorClass}\\b`, "i").test(code));
747
+ if (requirements.length > 0) return requirements.every(Boolean);
748
+ const names = new Set(profiles.flatMap((profile) => profile.callables).flatMap((name) => name.split(".")));
749
+ const paths = [...sourceFiles, ...profiles.map((profile) => profile.path)].join("\n");
750
+ const anchors = [...rawCriterion.matchAll(/\b[a-z_$][a-z0-9_$]{3,}\b/gi)]
751
+ .map((match) => match[0]).filter((word) => !BEHAVIOR_STOPWORDS.has(word.toLowerCase()));
752
+ return anchors.some((anchor) => names.has(anchor)
753
+ || new RegExp(`\\b${escapeRegex(anchor)}\\b`, "i").test(code)
754
+ || new RegExp(`(?:^|[/_.-])${escapeRegex(anchor)}(?:[/_.-]|$)`, "i").test(paths));
755
+ }
756
+
757
+ function behavioralFocusRequired(input) {
758
+ if (input.obligation === "requested-behavior") {
759
+ return criterionRequiresBehavioralProof(input.task, input.criterion, input.taskText)
760
+ || input.criterion.priority === "critical";
761
+ }
762
+ return input.obligation === "backward-compatibility"
763
+ && (criterionRequiresBehavioralProof(input.task, input.criterion, input.taskText) || input.criterion.priority === "critical");
764
+ }
765
+
766
+ export function criterionBehaviorProofDisposition(input) {
767
+ if (!behavioralFocusRequired(input)) return "not-applicable";
768
+ if (!input.passingVerifier) return "unlinked";
769
+ if (!input.corpus.adapter.proofCapable) {
770
+ return ["unsupported", "unresolved"].includes(input.corpus.adapter.status) ? "unknown" : "unlinked";
771
+ }
772
+ if (input.corpus.sourceFiles.length === 0 || input.corpus.testFiles.length === 0) return "unlinked";
773
+ return testSourceLinkage(input.corpus).status;
774
+ }
775
+
776
+ function focusedBehaviorEvidence(input) {
777
+ if (criterionBehaviorProofDisposition(input) !== "linked") return undefined;
778
+ const linkage = testSourceLinkage(input.corpus);
779
+ const profiles = linkedTestEvidence(linkage).filter((profile) => verifierCommandsCoverTests(input.task, [profile.path], input.cwd));
780
+ if (!criterionBehaviorMatches(input.task, input.criterion, profiles, input.corpus.sourceFiles)) return undefined;
781
+ return {
782
+ ...input.verifierEvidence,
783
+ kind: "verifier-backed-focused-test",
784
+ summary: "Configured verifier passed with changed executable assertions tied to this criterion and changed source.",
785
+ paths: [...new Set([...input.corpus.sourceFiles, ...profiles.map((profile) => profile.path)])]
786
+ };
787
+ }
788
+
789
+ export function genericCriterionEvidence(input) {
790
+ const behavioral = criterionRequiresBehavioralProof(input.task, input.criterion, input.taskText);
791
+ if (input.obligation === "requested-behavior") {
792
+ return { handled: true, evidence: behavioralFocusRequired(input)
793
+ ? focusedBehaviorEvidence(input)
794
+ : input.passingVerifier ? input.verifierEvidence : undefined };
795
+ }
796
+ if (input.obligation === "verification-evidence" || input.obligation === "backward-compatibility") {
797
+ return { handled: true, evidence: behavioralFocusRequired(input) ? focusedBehaviorEvidence(input)
798
+ : input.passingVerifier ? input.verifierEvidence : undefined };
799
+ }
800
+ return { handled: false, behavioral };
801
+ }
802
+
803
+ export function genericFallbackEvidence(input) {
804
+ /* No criterion is available at this legacy fallback boundary: abstain for behavioral proof. */
805
+ return input.behavioral ? undefined : input.passingVerifier ? input.verifierEvidence : undefined;
806
+ }