@piagent/platform 1.5.5 → 1.6.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.
Files changed (227) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/README.md +9 -8
  3. package/README.vi.md +9 -5
  4. package/adapters/web-frontend/profile.json +2 -4
  5. package/architecture/layers.json +18 -3
  6. package/benchmarks/deep-logic-v1/SPECIALIST_PROTOCOL.vi.md +122 -0
  7. package/benchmarks/deep-logic-v1/grade.mjs +272 -10
  8. package/benchmarks/deep-logic-v1/project/apps/admin/src/billing-summary.js +3 -0
  9. package/benchmarks/deep-logic-v1/project/benchmark-contract/public-smoke.test.js +166 -0
  10. package/benchmarks/deep-logic-v1/project/package.json +1 -1
  11. package/benchmarks/deep-logic-v1/project/packages/billing/src/close-period.js +3 -0
  12. package/benchmarks/deep-logic-v1/project/packages/billing/src/plan-timeline.js +3 -0
  13. package/benchmarks/deep-logic-v1/project/test/smoke.test.js +7 -1
  14. package/benchmarks/deep-logic-v1/prompts/budgeted-context-graph.md +2 -0
  15. package/benchmarks/deep-logic-v1/prompts/fair-dependency-scheduler.md +2 -0
  16. package/benchmarks/deep-logic-v1/prompts/layered-policy-resolution.md +2 -0
  17. package/benchmarks/deep-logic-v1/prompts/resumable-stream-assembly.md +3 -1
  18. package/benchmarks/deep-logic-v1/prompts/revision-event-reconciliation.md +2 -0
  19. package/benchmarks/deep-logic-v1/prompts/temporal-usage-billing-close.md +123 -0
  20. package/benchmarks/deep-logic-v1/prompts/transactional-config-merge.md +2 -0
  21. package/benchmarks/deep-logic-v1/suite.json +51 -3
  22. package/benchmarks/deep-logic-v1/variant.mjs +22 -1
  23. package/benchmarks/production-v1/spend-control.v1.json +68 -0
  24. package/benchmarks/production-v1/suite.json +42 -3
  25. package/catalog/capabilities.json +6 -6
  26. package/docs/command-reference-vietnamese.md +6 -6
  27. package/docs/context-engine.md +7 -1
  28. package/docs/distribution-standard.md +7 -7
  29. package/docs/en/architecture.md +1 -0
  30. package/docs/languages.json +4 -4
  31. package/docs/model-options.md +8 -5
  32. package/docs/operator-manual-vietnamese.md +13 -11
  33. package/docs/project-adapters.md +2 -2
  34. package/docs/publishing-for-teams.md +5 -0
  35. package/docs/quality-benchmark.md +271 -62
  36. package/docs/release-install-policy.md +10 -9
  37. package/docs/runtime-policy-design.md +4 -4
  38. package/docs/runtime-quality-baseline.md +13 -6
  39. package/docs/security-threat-model.md +2 -2
  40. package/docs/task-lifecycle-vietnamese.md +2 -2
  41. package/docs/team-onboarding.md +2 -2
  42. package/docs/usage-observability.md +7 -2
  43. package/docs/vi/architecture.md +1 -0
  44. package/docs-site/architecture.html +1 -1
  45. package/docs-site/benchmark.html +1 -1
  46. package/docs-site/commands.html +1 -1
  47. package/docs-site/context-engine.html +1 -1
  48. package/docs-site/en/architecture.html +1 -1
  49. package/docs-site/en/benchmark.html +1 -1
  50. package/docs-site/en/commands.html +1 -1
  51. package/docs-site/en/context-engine.html +1 -1
  52. package/docs-site/en/index.html +1 -1
  53. package/docs-site/en/mcp-approval.html +1 -1
  54. package/docs-site/en/mcp-auth.html +1 -1
  55. package/docs-site/en/mcp-servers.html +1 -1
  56. package/docs-site/en/mcp.html +1 -1
  57. package/docs-site/en/permissions.html +1 -1
  58. package/docs-site/en/profiles.html +1 -1
  59. package/docs-site/en/quickstart.html +1 -1
  60. package/docs-site/en/runtime.html +1 -1
  61. package/docs-site/en/scope.html +1 -1
  62. package/docs-site/en/security.html +1 -1
  63. package/docs-site/en/team.html +1 -1
  64. package/docs-site/en/whats-new.html +1 -1
  65. package/docs-site/en/workflows.html +1 -1
  66. package/docs-site/index.html +1 -1
  67. package/docs-site/mcp-approval.html +1 -1
  68. package/docs-site/mcp-auth.html +1 -1
  69. package/docs-site/mcp-servers.html +1 -1
  70. package/docs-site/mcp.html +1 -1
  71. package/docs-site/permissions.html +1 -1
  72. package/docs-site/profiles.html +1 -1
  73. package/docs-site/quickstart.html +1 -1
  74. package/docs-site/runtime.html +1 -1
  75. package/docs-site/scope.html +1 -1
  76. package/docs-site/security.html +1 -1
  77. package/docs-site/team.html +1 -1
  78. package/docs-site/whats-new.html +1 -1
  79. package/docs-site/workflows.html +1 -1
  80. package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
  81. package/evals/fixtures/benchmark-assurance-evidence.valid.json +1 -1
  82. package/evals/fixtures/piagent-webui/session-live-state-v1.invalid.json +18 -0
  83. package/evals/fixtures/piagent-webui/session-live-state-v1.valid.json +27 -0
  84. package/evals/fs4-readiness-matrix.v1.json +2 -2
  85. package/evals/fs5-causal-arm.v1.json +2 -2
  86. package/evals/fs5-pilot-protocol.v1.json +2 -2
  87. package/evals/fs5-pilot-protocol.v4.json +6 -5
  88. package/evals/fs5-pilot-protocol.v5.json +7 -6
  89. package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
  90. package/package.json +3 -1
  91. package/packages/piagent-core/benchmark/benchmark-assurance.js +4 -1
  92. package/packages/piagent-core/benchmark/benchmark-bootstrap.js +91 -9
  93. package/packages/piagent-core/benchmark/benchmark-claim-restrictions.js +38 -7
  94. package/packages/piagent-core/benchmark/benchmark-cli.js +7 -7
  95. package/packages/piagent-core/benchmark/benchmark-codex.js +3 -1
  96. package/packages/piagent-core/benchmark/benchmark-comparison.js +271 -2
  97. package/packages/piagent-core/benchmark/benchmark-core.js +732 -661
  98. package/packages/piagent-core/benchmark/benchmark-execution-guard.js +7 -5
  99. package/packages/piagent-core/benchmark/benchmark-forensics.js +439 -4
  100. package/packages/piagent-core/benchmark/benchmark-host-readiness.js +639 -0
  101. package/packages/piagent-core/benchmark/benchmark-matrix.js +22 -4
  102. package/packages/piagent-core/benchmark/benchmark-normalized-cost.js +317 -0
  103. package/packages/piagent-core/benchmark/benchmark-preflight.js +3 -1
  104. package/packages/piagent-core/benchmark/benchmark-process.js +6 -5
  105. package/packages/piagent-core/benchmark/benchmark-provider-wire.js +126 -0
  106. package/packages/piagent-core/benchmark/benchmark-record-validation.js +183 -0
  107. package/packages/piagent-core/benchmark/benchmark-report.js +116 -12
  108. package/packages/piagent-core/benchmark/benchmark-resume-recovery.js +14 -0
  109. package/packages/piagent-core/benchmark/benchmark-runner-policy.js +26 -0
  110. package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +782 -0
  111. package/packages/piagent-core/benchmark/benchmark-statistics.js +64 -0
  112. package/packages/piagent-core/benchmark/benchmark-stop-policy.js +8 -5
  113. package/packages/piagent-core/benchmark/benchmark-suite-identity.js +31 -0
  114. package/packages/piagent-core/benchmark/benchmark-suite-runtime.js +13 -9
  115. package/packages/piagent-core/benchmark/benchmark-suite.js +128 -2
  116. package/packages/piagent-core/benchmark/benchmark-summary-support.js +376 -0
  117. package/packages/piagent-core/benchmark/benchmark-timing-diagnostics.js +971 -0
  118. package/packages/piagent-core/benchmark/benchmark-usage.js +311 -0
  119. package/packages/piagent-core/benchmark/benchmark-workflow.js +166 -0
  120. package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +1080 -0
  121. package/packages/piagent-core/extensions/acceptance-contract-semantics.js +83 -69
  122. package/packages/piagent-core/extensions/acceptance-error-classes.js +160 -0
  123. package/packages/piagent-core/extensions/acceptance-executable-evidence.js +12 -9
  124. package/packages/piagent-core/extensions/acceptance-receipt.js +34 -11
  125. package/packages/piagent-core/extensions/acceptance-state-machine-evidence.js +518 -0
  126. package/packages/piagent-core/extensions/acceptance-test-binding-evidence.js +60 -0
  127. package/packages/piagent-core/extensions/context-efficiency-metrics.js +302 -0
  128. package/packages/piagent-core/extensions/context-efficiency-report.js +167 -0
  129. package/packages/piagent-core/extensions/context-engine.js +148 -145
  130. package/packages/piagent-core/extensions/context-evidence.js +48 -0
  131. package/packages/piagent-core/extensions/context-import-links.js +296 -0
  132. package/packages/piagent-core/extensions/context-telemetry-integrity.js +65 -0
  133. package/packages/piagent-core/extensions/criterion-context-pack.js +422 -7
  134. package/packages/piagent-core/extensions/criterion-graph.js +78 -8
  135. package/packages/piagent-core/extensions/guard-types.ts +2 -0
  136. package/packages/piagent-core/extensions/javascript-regex-evidence.js +32 -0
  137. package/packages/piagent-core/extensions/piagent-guard.ts +233 -41
  138. package/packages/piagent-core/extensions/task-contract-view.js +17 -3
  139. package/packages/piagent-core/extensions/task-lifecycle.js +7 -2
  140. package/packages/piagent-core/extensions/task-state.js +40 -134
  141. package/packages/piagent-core/extensions/verification-intelligence.js +3 -1
  142. package/packages/piagent-core/extensions/workspace-evidence-roots.js +157 -0
  143. package/packages/piagent-core/package.json +1 -1
  144. package/packages/piagent-core/policies/base-policy.json +2 -0
  145. package/packages/piagent-core/runtime/context/context-delivery.ts +103 -0
  146. package/packages/piagent-core/runtime/context/context-delta-shadow.ts +53 -0
  147. package/packages/piagent-core/runtime/context/context-efficiency-format.ts +47 -0
  148. package/packages/piagent-core/runtime/context/context-evidence-qualification.ts +32 -0
  149. package/packages/piagent-core/runtime/context/prefix-telemetry.ts +47 -0
  150. package/packages/piagent-core/runtime/context/repository-memory-hints.ts +27 -0
  151. package/packages/piagent-core/runtime/hooks/agent-prompt-telemetry.ts +56 -0
  152. package/packages/piagent-core/runtime/hooks/agent-start-hook.ts +162 -102
  153. package/packages/piagent-core/runtime/hooks/completion-hook.ts +5 -6
  154. package/packages/piagent-core/runtime/hooks/input-hook.ts +11 -1
  155. package/packages/piagent-core/runtime/hooks/session-hooks.ts +22 -2
  156. package/packages/piagent-core/runtime/hooks/session-start-hook.ts +7 -2
  157. package/packages/piagent-core/runtime/hooks/tool-result-content-guards.ts +95 -0
  158. package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +84 -102
  159. package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +4 -2
  160. package/packages/piagent-core/runtime/inspection/activity-recovery.ts +104 -0
  161. package/packages/piagent-core/runtime/inspection/task-compaction-history.ts +1 -1
  162. package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +1 -1
  163. package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +53 -0
  164. package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +34 -6
  165. package/packages/piagent-core/runtime/model/authenticated-catalog.ts +17 -9
  166. package/packages/piagent-core/runtime/model/openai-codex-reasoning.ts +83 -0
  167. package/packages/piagent-core/runtime/model/provider-wire-fingerprint.ts +238 -0
  168. package/packages/piagent-core/runtime/orchestration/role-policy.ts +1 -1
  169. package/packages/piagent-core/runtime/product/activity-inspector.ts +7 -2
  170. package/packages/piagent-core/runtime/product/efficiency-metrics.ts +4 -2
  171. package/packages/piagent-core/runtime/quality/exact-output-contract.ts +2 -1
  172. package/packages/piagent-core/runtime/quality/performance-assurance-signals.ts +23 -0
  173. package/packages/piagent-core/runtime/quality/performance-assurance.ts +6 -2
  174. package/packages/piagent-core/runtime/recovery/edit-recovery-context.ts +113 -0
  175. package/packages/piagent-core/runtime/recovery/edit-recovery-delivery.ts +42 -0
  176. package/packages/piagent-core/runtime/recovery/semantic-repair-handshake.ts +55 -2
  177. package/packages/piagent-core/runtime/recovery/semantic-repair-runtime.ts +55 -2
  178. package/packages/piagent-core/runtime/registration/context-commands.ts +66 -5
  179. package/packages/piagent-core/runtime/registration/policy-tools.ts +46 -11
  180. package/packages/piagent-core/runtime/registration/task-completion-tools.ts +49 -16
  181. package/packages/piagent-core/runtime/registration/task-start-guidance.ts +163 -0
  182. package/packages/piagent-core/runtime/registration/task-start-retry-helpers.ts +37 -0
  183. package/packages/piagent-core/runtime/registration/task-start-tool.ts +67 -62
  184. package/packages/piagent-core/runtime/session/runtime-state.ts +154 -2
  185. package/packages/piagent-core/runtime/session/system-prompt.ts +4 -4
  186. package/packages/piagent-core/runtime/tools/apply-patch-tool.ts +500 -0
  187. package/packages/piagent-core/runtime/trajectory/trajectory-runtime.ts +2 -1
  188. package/packages/piagent-core/runtime/workflows/task-intake.ts +43 -2
  189. package/packages/piagent-core/security/sensitive-data.js +13 -218
  190. package/packages/piagent-core/security/sensitive-project-file.js +42 -0
  191. package/packages/piagent-core/security/sensitive-source-expression.js +330 -0
  192. package/packages/piagent-core/security/sensitive-source-formats.js +394 -0
  193. package/packages/piagent-core/security/sensitive-text.js +234 -0
  194. package/packages/piagent-core/subagents/piagent-worker.md +1 -1
  195. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-rD9WJKxs.js → MarkdownMessage-CxeZqTte.js} +1 -1
  196. package/packages/piagent-webui/dist/client/assets/index-DynIMe8I.js +176 -0
  197. package/packages/piagent-webui/dist/client/index.html +1 -1
  198. package/packages/piagent-webui/gateway/gateway-events.ts +109 -5
  199. package/packages/piagent-webui/gateway/gateway-service.ts +3 -0
  200. package/packages/piagent-webui/gateway/gateway-session-stream.ts +93 -7
  201. package/packages/piagent-webui/gateway/session-command-controller.ts +18 -3
  202. package/packages/piagent-webui/gateway/session-effective-options.ts +47 -0
  203. package/packages/piagent-webui/gateway/session-live-state.ts +21 -0
  204. package/packages/piagent-webui/gateway/session-operation-start.ts +5 -1
  205. package/packages/piagent-webui/gateway/session-operation-watchdog.ts +158 -0
  206. package/packages/piagent-webui/gateway/session-runtime-factory.ts +45 -0
  207. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +139 -124
  208. package/packages/piagent-webui/server/loopback-server.ts +8 -0
  209. package/packages/piagent-webui/server/transcript-projection.ts +22 -4
  210. package/packages/piagent-webui/shared/text-visibility.ts +10 -0
  211. package/schemas/piagent-webui/catalog-v1.json +9 -0
  212. package/schemas/piagent-webui/gateway-protocol-v1.schema.json +53 -3
  213. package/schemas/piagent-webui/session-live-state-v1.schema.json +76 -0
  214. package/schemas/task-contract.schema.json +12 -1
  215. package/scripts/benchmark-runner-core.mjs +355 -359
  216. package/scripts/benchmark-runner-finalization.mjs +309 -0
  217. package/scripts/benchmark-runner-host-readiness.mjs +94 -0
  218. package/scripts/benchmark-runner-support.mjs +200 -0
  219. package/scripts/benchmark-session.mjs +95 -41
  220. package/scripts/configure-model-scope.sh +4 -3
  221. package/scripts/helper-evaluation.mjs +1 -1
  222. package/scripts/install-global.sh +2 -2
  223. package/scripts/pi-usage-history.mjs +37 -10
  224. package/scripts/setup.sh +2 -2
  225. package/scripts/verify-local.sh +5 -1
  226. package/templates/global/settings.json +2 -1
  227. package/packages/piagent-webui/dist/client/assets/index-BZRFw1u6.js +0 -174
@@ -1,7 +1,11 @@
1
1
  import path from "node:path";
2
2
  import { acceptanceBoundaryProofGuidance } from "./acceptance-boundary-guidance.js";
3
3
  import { evidenceTopLevelArguments, executableRejectionAssertions } from "./acceptance-executable-evidence.js";
4
-
4
+ import { ERROR_CONSTRUCTORS, ERROR_CONSTRUCTOR_DISPLAY_NAMES, errorMappingsProveContract,
5
+ hasAmbiguousErrorClassIntent, rejectionStatementErrorClass, requestedErrorClasses, requestedErrorPartitionMapping } from "./acceptance-error-classes.js";
6
+ import { statefulTerminalRejectionEvidence } from "./acceptance-state-machine-evidence.js";
7
+ import { boundRejectionTestEvidence, callableAssertionMode } from "./acceptance-test-binding-evidence.js";
8
+ import { regexCanStartAfterLexicalChunks } from "./javascript-regex-evidence.js";
5
9
  const INTEGER_TARGET_STOPWORDS = new Set([
6
10
  "and", "basis", "cents", "input", "inputs", "items", "money", "number", "numbers", "or", "points", "typeerror", "value", "values"
7
11
  ]);
@@ -19,6 +23,7 @@ function erasedLexeme(value) {
19
23
 
20
24
  function stringLiteralSentinel(value) {
21
25
  if (value.length === 0) return "__pi_empty_string_literal__";
26
+ if (value === '"') return "__pi_double_quote_string_literal__";
22
27
  if (/^(?:\s|\\[nrtvf0])+$/u.test(value)) return "__pi_whitespace_string_literal__";
23
28
  if (["assert", "assert/strict", "node:assert", "node:assert/strict"].includes(value.trim().toLowerCase())) {
24
29
  return "__pi_node_assert_module_literal__";
@@ -30,14 +35,6 @@ function stringLiteralSentinel(value) {
30
35
  return "__pi_string_literal__";
31
36
  }
32
37
 
33
- function regexCanStart(source, offset) {
34
- const prefix = source.slice(0, offset).replace(/\s+$/u, "");
35
- if (!prefix) return true;
36
- const previous = prefix.at(-1);
37
- if (/[([{,:;=!?&|+*%^~<>-]/u.test(previous)) return true;
38
- return /\b(?:await|case|delete|do|else|in|instanceof|new|of|return|throw|typeof|void|yield)$/u.test(prefix);
39
- }
40
-
41
38
  /**
42
39
  * Return a bounded lexical evidence view of JavaScript/TypeScript source.
43
40
  *
@@ -118,7 +115,7 @@ function lexJavaScriptEvidence(value, bindStrings = false) {
118
115
  index = end;
119
116
  continue;
120
117
  }
121
- if (current === "/" && next !== "=" && regexCanStart(source, index)) {
118
+ if (current === "/" && next !== "=" && regexCanStartAfterLexicalChunks(output)) {
122
119
  let end = index + 1;
123
120
  let inClass = false;
124
121
  let closed = false;
@@ -160,6 +157,7 @@ export function sanitizeJavaScriptEvidence(value) {
160
157
  function uniqueStrings(values) {
161
158
  return [...new Set((Array.isArray(values) ? values : []).filter((item) => typeof item === "string" && item.trim()).map((item) => item.trim()))];
162
159
  }
160
+ function hasReservedEvidenceIdentifier(value) { return /\b__pi_[a-z0-9_]*\b/i.test(String(value ?? "")); }
163
161
 
164
162
  function escapeRegex(value) {
165
163
  return String(value ?? "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -207,8 +205,6 @@ function nullRejectingDefaultTargets(text, sourceText = "") {
207
205
  return uniqueTargets.filter((target) => new RegExp(`(?:\\.${escapeRegex(target)}\\b|\\b${escapeRegex(target)}\\b)\\s*\\?\\?`, "i").test(source));
208
206
  }
209
207
 
210
- const ERROR_CONSTRUCTORS = ["typeerror", "rangeerror", "syntaxerror", "referenceerror", "urierror", "evalerror", "aggregateerror", "error"];
211
-
212
208
  function evidenceBalancedEnd(text, openIndex, opening = "(", closing = ")", ceiling = 8_000) {
213
209
  if (text[openIndex] !== opening) return -1;
214
210
  let depth = 0;
@@ -219,13 +215,6 @@ function evidenceBalancedEnd(text, openIndex, opening = "(", closing = ")", ceil
219
215
  return -1;
220
216
  }
221
217
 
222
- function requestedErrorClasses(text) {
223
- const value = normalizedText(text);
224
- const specific = ERROR_CONSTRUCTORS.slice(0, -1).filter((name) => new RegExp(`\\b${name}\\b`).test(value));
225
- if (specific.length > 0) return specific;
226
- return /\b(?:throw(?:s|ing)?|reject(?:s|ed|ion)?)\b[^.\n]{0,80}\berror\b/.test(value) ? ["error"] : [];
227
- }
228
-
229
218
  function simpleConstantValue(raw) {
230
219
  let value = String(raw ?? "").trim();
231
220
  while (value.startsWith("(") && evidenceBalancedEnd(value, 0) === value.length) value = value.slice(1, -1).trim();
@@ -423,15 +412,24 @@ function namedTargetBindings(sourceEntries, testEntries, namedTargets) {
423
412
  return namedTargets.map((target) => {
424
413
  const matches = exports.filter((item) => item.contractName === target
425
414
  && sourceCallableBindingIsStable(sourceEntries.find((entry) => entryPath(entry?.path) === item.sourcePath), item.sourceName));
426
- if (matches.length !== 1) return { target, sourcePath: null, sourceName: null, testNames: [] };
415
+ if (matches.length !== 1) return { target, sourcePath: null, sourceName: null, testNames: [], testBindings: [] };
427
416
  const exported = matches[0];
428
- const testNames = [];
417
+ const testBindings = [];
429
418
  for (const binding of imports.filter((item) => importResolvesTo(item, exported.sourcePath))) {
430
419
  const testName = binding.importName === "*" ? `${binding.localName}.${exported.exportName}` : binding.localName;
431
420
  if ((binding.importName === exported.exportName || binding.importName === "*")
432
- && !importedBindingIsShadowed(entries.get(binding.testPath), testName)) testNames.push(testName);
421
+ && !importedBindingIsShadowed(entries.get(binding.testPath), testName)) {
422
+ testBindings.push({ testName, testPath: binding.testPath });
423
+ }
433
424
  }
434
- return { target, sourcePath: exported.sourcePath, sourceName: exported.sourceName, testNames: uniqueStrings(testNames) };
425
+ const uniqueBindings = [...new Map(testBindings.map((item) => [`${item.testPath}\u0000${item.testName}`, item])).values()];
426
+ return {
427
+ target,
428
+ sourcePath: exported.sourcePath,
429
+ sourceName: exported.sourceName,
430
+ testNames: uniqueStrings(uniqueBindings.map((item) => item.testName)),
431
+ testBindings: uniqueBindings
432
+ };
435
433
  });
436
434
  }
437
435
 
@@ -461,25 +459,23 @@ function formalParameterNames(parameters) {
461
459
 
462
460
  function callableBodies(code) {
463
461
  const bodies = new Map();
464
- const addBraced = (name, parameters, open) => {
462
+ const addBraced = (name, parameters, open, asynchronous = false) => {
465
463
  const end = evidenceBalancedEnd(code, open, "{", "}");
466
464
  if (end !== -1 && !bodies.has(name)) bodies.set(name, {
467
465
  body: code.slice(open + 1, end - 1),
468
- parameters: formalParameterNames(parameters),
469
- sourceCode: code
466
+ parameters: formalParameterNames(parameters), sourceCode: code, asynchronous
470
467
  });
471
468
  };
472
- for (const match of code.matchAll(/\bfunction\s+([a-z_$][a-z0-9_$]*)\s*\(([^)]*)\)\s*\{/gi)) addBraced(match[1].toLowerCase(), match[2], match.index + match[0].lastIndexOf("{"));
469
+ for (const match of code.matchAll(/\b(?:async\s+)?function\s+([a-z_$][a-z0-9_$]*)\s*\(([^)]*)\)\s*\{/gi)) addBraced(match[1].toLowerCase(), match[2], match.index + match[0].lastIndexOf("{"), /^async\b/i.test(match[0]));
473
470
  for (const match of code.matchAll(/^\s*(?:async\s+)?([a-z_$][a-z0-9_$]*)\s*\(([^)]*)\)\s*\{/gim)) {
474
- if (!["catch", "for", "if", "switch", "while", "with"].includes(match[1].toLowerCase())) addBraced(match[1].toLowerCase(), match[2], match.index + match[0].lastIndexOf("{"));
471
+ if (!["catch", "for", "if", "switch", "while", "with"].includes(match[1].toLowerCase())) addBraced(match[1].toLowerCase(), match[2], match.index + match[0].lastIndexOf("{"), /^\s*async\b/i.test(match[0]));
475
472
  }
476
473
  for (const match of code.matchAll(/\b(?:const|let|var)\s+([a-z_$][a-z0-9_$]*)\s*=\s*(?:async\s*)?(?:\(([^)]*)\)|([a-z_$][a-z0-9_$]*))\s*=>\s*/gi)) {
477
474
  const start = match.index + match[0].length;
478
- if (code[start] === "{") addBraced(match[1].toLowerCase(), match[2] ?? match[3], start);
475
+ if (code[start] === "{") addBraced(match[1].toLowerCase(), match[2] ?? match[3], start, /=\s*async\b/i.test(match[0]));
479
476
  else bodies.set(match[1].toLowerCase(), {
480
477
  body: code.slice(start, [code.indexOf(";", start), code.indexOf("\n", start)].filter((item) => item >= 0).sort((a, b) => a - b)[0] ?? code.length),
481
- parameters: formalParameterNames(match[2] ?? match[3]),
482
- sourceCode: code
478
+ parameters: formalParameterNames(match[2] ?? match[3]), sourceCode: code, asynchronous: /=\s*async\b/i.test(match[0])
483
479
  });
484
480
  }
485
481
  return bodies;
@@ -624,12 +620,6 @@ function validationConditionProof(condition, names, bodies) {
624
620
  return proof;
625
621
  }
626
622
 
627
- function rejectionStatementProves(text, requestedErrors) {
628
- return requestedErrors.length === 0
629
- ? /^\s*throw\s+(?:new\s+)?[a-z_$][a-z0-9_$]*(?:\s*\(|\b)/.test(text)
630
- : requestedErrors.some((name) => new RegExp(`^\\s*throw\\s+(?:new\\s+)?${escapeRegex(name)}\\s*\\(`).test(text));
631
- }
632
-
633
623
  function exactHelperCall(text) {
634
624
  let value = String(text ?? "").trim();
635
625
  if (value.startsWith("{") && evidenceBalancedEnd(value, 0, "{", "}") === value.length) {
@@ -721,8 +711,8 @@ function inputNameWrittenBefore(callable, name, offset) {
721
711
  return new RegExp(`(?:\\+\\+|--)\\s*\\b${escaped}\\b`, "i").test(prefix);
722
712
  }
723
713
 
724
- function conditionalRejection(callable, requestedErrors, bodies) {
725
- const proof = { generic: false, partitions: new Set() };
714
+ function conditionalRejection(callable, requestedErrors, bodies, contractText = "", contractTarget = "") {
715
+ const proof = { generic: false, partitions: new Set(), errorMappings: [] };
726
716
  const names = inputDerivedNames(callable);
727
717
  for (const match of callable.body.matchAll(/\bif\s*\(/g)) {
728
718
  if (sourceBraceDepthAt(callable.body, match.index) !== 0 || priorUnconditionalExit(callable.body, match.index)) continue;
@@ -737,8 +727,11 @@ function conditionalRejection(callable, requestedErrors, bodies) {
737
727
  const consequent = callable.body.slice(consequentStart, consequentEnd === -1 ? callable.body.length : consequentEnd);
738
728
  const condition = callable.body.slice(conditionOpen + 1, conditionEnd - 1);
739
729
  const liveNames = names.filter((name) => !inputNameWrittenBefore(callable, name, match.index));
740
- if (rejectionStatementProves(consequent.replace(/^\s*\{/, ""), requestedErrors)) {
741
- mergeValidationProof(proof, validationConditionProof(condition, liveNames, bodies));
730
+ const consequentError = rejectionStatementErrorClass(consequent.replace(/^\s*\{/, ""), requestedErrors);
731
+ if (consequentError) {
732
+ const validation = validationConditionProof(condition, liveNames, bodies);
733
+ mergeValidationProof(proof, validation);
734
+ for (const partition of validation.partitions) proof.errorMappings.push({ partition, errorClass: consequentError });
742
735
  continue;
743
736
  }
744
737
  const throwingHelper = exactHelperCall(consequent);
@@ -749,18 +742,25 @@ function conditionalRejection(callable, requestedErrors, bodies) {
749
742
  }
750
743
  if (!/^\s*\{?\s*return\b/.test(consequent)) continue;
751
744
  const following = callable.body.slice(consequentEnd === -1 ? callable.body.length : consequentEnd);
752
- if (rejectionStatementProves(following, requestedErrors)) {
753
- mergeValidationProof(proof, positiveValidationConditionProof(condition, liveNames, bodies));
745
+ const followingError = rejectionStatementErrorClass(following, requestedErrors);
746
+ if (followingError) {
747
+ const validation = positiveValidationConditionProof(condition, liveNames, bodies);
748
+ mergeValidationProof(proof, validation);
749
+ for (const partition of validation.partitions) proof.errorMappings.push({ partition, errorClass: followingError });
754
750
  }
755
751
  }
752
+ proof.generic ||= statefulTerminalRejectionEvidence(callable.body, callable.parameters, requestedErrors, contractText, contractTarget, callable.sourceCode);
756
753
  return { ...proof, names };
757
754
  }
758
-
759
- function sourceCallableProof(bodies, name, requestedErrors, visited = new Set()) {
755
+ function sourceCallableProvesErrorMapping(bodies, name, mapping) {
756
+ const observed = sourceCallableProof(bodies, name, []).errorMappings ?? [];
757
+ return errorMappingsProveContract(observed, mapping);
758
+ }
759
+ function sourceCallableProof(bodies, name, requestedErrors, visited = new Set(), contractText = "", contractTarget = name) {
760
760
  if (visited.has(name) || visited.size >= 12) return { generic: false, partitions: new Set() };
761
761
  const callable = bodies.get(name);
762
762
  if (!callable) return { generic: false, partitions: new Set() };
763
- const proof = conditionalRejection(callable, requestedErrors, bodies);
763
+ const proof = conditionalRejection(callable, requestedErrors, bodies, contractText, contractTarget);
764
764
  const nextVisited = new Set(visited).add(name);
765
765
  for (const match of callable.body.matchAll(/\b([a-z_$][a-z0-9_$]*)\s*\(/gi)) {
766
766
  const helper = match[1].toLowerCase();
@@ -770,17 +770,17 @@ function sourceCallableProof(bodies, name, requestedErrors, visited = new Set())
770
770
  const argumentsText = end === -1 ? "" : callable.body.slice(open + 1, end - 1);
771
771
  if (!proof.names.some((candidate) => !inputNameWrittenBefore(callable, candidate, match.index)
772
772
  && new RegExp(`\\b${escapeRegex(candidate)}\\b`).test(argumentsText))) continue;
773
- const helperProof = sourceCallableProof(bodies, helper, requestedErrors, nextVisited);
773
+ const helperProof = sourceCallableProof(bodies, helper, requestedErrors, nextVisited, contractText, contractTarget);
774
774
  proof.generic ||= helperProof.generic;
775
775
  for (const partition of helperProof.partitions) proof.partitions.add(partition);
776
776
  }
777
777
  return proof;
778
778
  }
779
-
780
- function sourceCallableProves(bodies, name, requestedErrors, requestedPartitions) {
781
- const proof = sourceCallableProof(bodies, name, requestedErrors);
779
+ function sourceCallableProves(bodies, name, requestedErrors, requestedPartitions, contractText = "") {
780
+ const proof = sourceCallableProof(bodies, name, requestedErrors, new Set(), contractText, name);
782
781
  return proof.generic && requestedPartitions.every((partition) => partitionCovered(partition, proof.partitions));
783
782
  }
783
+
784
784
  function evidenceCallableNames(sourceText) {
785
785
  const names = new Set();
786
786
  const ignored = new Set(["catch", "for", "if", "switch", "while", "with"]);
@@ -891,7 +891,12 @@ export function acceptanceInvalidInputEvidence(input = {}) {
891
891
  const sourceText = normalizedText(sanitizeJavaScriptEvidence(input.sourceText));
892
892
  const testText = normalizedText(sanitizeJavaScriptEvidence(input.testText));
893
893
  const requestedErrors = requestedErrorClasses(taskText);
894
+ const ambiguousErrorIntent = hasAmbiguousErrorClassIntent(taskText);
894
895
  const requestedPartitions = requestedInvalidPartitions(taskText);
896
+ // A constructor set alone cannot prove a mapping. Mixed-class evidence uses a narrow grammar;
897
+ // ambiguous prose or indirect source/test proof abstains fail-closed.
898
+ const requestedErrorMapping = requestedErrors.length > 1
899
+ ? requestedErrorPartitionMapping(taskText, requestedErrors, requestedPartitions) : null;
895
900
  const namedTargets = uniqueStrings(input.namedTargets).map((item) => item.toLowerCase());
896
901
  const provenanceTargets = new Set(uniqueStrings(input.provenanceTargets).map((item) => item.toLowerCase()));
897
902
  const sourceEntries = (Array.isArray(input.sourceEntries) ? input.sourceEntries : [])
@@ -900,8 +905,8 @@ export function acceptanceInvalidInputEvidence(input = {}) {
900
905
  .filter((entry) => entry && typeof entry.path === "string" && typeof entry.text === "string");
901
906
  const bodyMaps = new Map(sourceEntries.map((entry) => [entryPath(entry.path), callableBodies(normalizedText(sanitizeJavaScriptEvidence(entry.text)))]));
902
907
  if (sourceEntries.length === 0) bodyMaps.set("", callableBodies(sourceText));
903
- const sourceLexicalOk = sourceEntries.length > 0 ? sourceEntries.every((entry) => lexicalEvidenceIsNonReflective(normalizedText(sanitizeJavaScriptEvidence(entry.text)))) : lexicalEvidenceIsNonReflective(sourceText);
904
- const testLexicalOk = testEntries.length > 0 ? testEntries.every((entry) => lexicalEvidenceIsNonReflective(normalizedText(sanitizeJavaScriptEvidence(entry.text)))) : lexicalEvidenceIsNonReflective(testText);
908
+ const sourceLexicalOk = sourceEntries.length > 0 ? sourceEntries.every((entry) => !hasReservedEvidenceIdentifier(entry.text) && lexicalEvidenceIsNonReflective(normalizedText(sanitizeJavaScriptEvidence(entry.text)))) : !hasReservedEvidenceIdentifier(input.sourceText) && lexicalEvidenceIsNonReflective(sourceText);
909
+ const testLexicalOk = testEntries.length > 0 ? testEntries.every((entry) => !hasReservedEvidenceIdentifier(entry.text) && lexicalEvidenceIsNonReflective(normalizedText(sanitizeJavaScriptEvidence(entry.text)))) : !hasReservedEvidenceIdentifier(input.testText) && lexicalEvidenceIsNonReflective(testText);
905
910
  const sourceConstructorOk = requestedErrors.length === 0 || (sourceEntries.length > 0
906
911
  ? sourceEntries.every((entry) => intrinsicErrorBindingsUntampered(normalizedText(sanitizeJavaScriptEvidence(entry.text)), requestedErrors))
907
912
  : intrinsicErrorBindingsUntampered(sourceText, requestedErrors));
@@ -922,35 +927,44 @@ export function acceptanceInvalidInputEvidence(input = {}) {
922
927
  && (requestedErrors.length === 0 || requestedErrors.some((name) => assertion.errorClasses.includes(name))))
923
928
  .map((assertion) => ({ assertion, binding }));
924
929
  });
925
- const testCallables = new Set([...bindings.flatMap((binding) => binding.testNames), ...structuralTargets.map((target) => `*.${target}`)]);
926
- const namedAssertions = namedTargets.length > 0
927
- ? executableRejectionAssertions(testText, testCallables).filter((assertion) => (
928
- requestedErrors.length === 0 || requestedErrors.some((name) => assertion.errorClasses.includes(name))
929
- ))
930
- : [];
931
- const assertions = namedTargets.length > 0 ? namedAssertions : inferredAssertions.map((item) => item.assertion);
932
- const targetOk = assertions.length > 0 && (namedTargets.length === 0 || (
933
- bindings.every((binding) => binding.testNames.length > 0 && binding.testNames.some((name) => assertions.some((assertion) => assertion.targets.includes(name))))
934
- && structuralTargets.every((target) => assertions.some((assertion) => assertion.targets.includes(`*.${target}`)))
935
- ));
930
+ const boundTestEvidence = boundRejectionTestEvidence({
931
+ bindings, structuralTargets, requestedErrors, requestedErrorMapping,
932
+ testCodeEntries: testEntries.map((entry) => ({ path: entryPath(entry.path), code: normalizedText(sanitizeJavaScriptEvidence(entry.text)) })),
933
+ fallbackTestCode: testText, bodyMaps
934
+ });
935
+ const assertions = namedTargets.length > 0 ? boundTestEvidence.assertions : inferredAssertions.map((item) => item.assertion);
936
+ const mappingTestOk = namedTargets.length > 0 ? boundTestEvidence.mappingOk : requestedErrors.length <= 1;
937
+ const targetOk = namedTargets.length > 0 ? boundTestEvidence.targetOk : assertions.length > 0;
936
938
  const sourceOk = sourceLexicalOk && sourceConstructorOk && (namedTargets.length > 0
937
939
  ? bindings.length === strictTargets.length
938
- && bindings.every((binding) => Boolean(binding.sourcePath) && Boolean(binding.sourceName) && sourceCallableProves(bodyMaps.get(binding.sourcePath) ?? new Map(), binding.sourceName, requestedErrors, requestedPartitions))
940
+ && bindings.every((binding) => Boolean(binding.sourcePath) && Boolean(binding.sourceName) && sourceCallableProves(bodyMaps.get(binding.sourcePath) ?? new Map(), binding.sourceName, requestedErrors, requestedPartitions, taskText))
939
941
  && structuralTargets.every((target) => [...bodyMaps.values()].filter((bodies) => bodies.has(target)).length === 1
940
- && [...bodyMaps.values()].some((bodies) => sourceCallableProves(bodies, target, requestedErrors, requestedPartitions)))
942
+ && [...bodyMaps.values()].some((bodies) => sourceCallableProves(bodies, target, requestedErrors, requestedPartitions, taskText)))
941
943
  : inferredAssertions.length > 0 && inferredAssertions.every(({ binding }) => (
942
- sourceCallableProves(bodyMaps.get(binding.sourcePath) ?? new Map(), binding.sourceName, requestedErrors, requestedPartitions)
943
- )));
944
+ sourceCallableProves(bodyMaps.get(binding.sourcePath) ?? new Map(), binding.sourceName, requestedErrors, requestedPartitions, taskText)
945
+ ))) && (requestedErrors.length <= 1 || Boolean(requestedErrorMapping && namedTargets.length > 0
946
+ && bindings.every((binding) => sourceCallableProvesErrorMapping(
947
+ bodyMaps.get(binding.sourcePath) ?? new Map(), binding.sourceName, requestedErrorMapping
948
+ ))
949
+ && structuralTargets.every((target) => [...bodyMaps.values()].filter((bodies) => bodies.has(target)).length === 1
950
+ && [...bodyMaps.values()].some((bodies) => sourceCallableProvesErrorMapping(bodies, target, requestedErrorMapping)))));
951
+ const assertionModeOk = namedTargets.length > 0 ? boundTestEvidence.modeOk
952
+ : inferredAssertions.length > 0 && inferredAssertions.every(({ assertion, binding }) => (
953
+ assertion.mode === callableAssertionMode(bodyMaps.get(binding.sourcePath) ?? new Map(), binding.sourceName)
954
+ ));
944
955
  const partitions = new Set(assertions.flatMap((assertion) => [...assertion.partitions]));
945
956
  const partitionOk = requestedPartitions.every((partition) => partitionCovered(partition, partitions));
946
- return { sourceOk, testOk: testLexicalOk && testConstructorOk && targetOk && partitionOk };
957
+ return { sourceOk: sourceOk && !ambiguousErrorIntent,
958
+ testOk: testLexicalOk && testConstructorOk && targetOk && partitionOk && mappingTestOk && assertionModeOk && !ambiguousErrorIntent };
947
959
  }
948
960
 
949
961
  export function acceptanceContractProofGuidance(raw) {
950
962
  const value = normalizedText(raw);
951
963
  const guidance = [...acceptanceBoundaryProofGuidance(raw)];
952
964
  if (onlyUndefinedContract(raw)) guidance.push("Prove undefined falls through while null, false, 0, and empty string are each preserved at the highest-precedence position.");
953
- if (/\btypeerror\b/.test(value)) guidance.push("Assert TypeError for every rejected partition named by the request; a different error class is not equivalent.");
965
+ const requestedSpecificErrors = hasAmbiguousErrorClassIntent(raw) ? [] : requestedErrorClasses(raw).map((errorClass) => ERROR_CONSTRUCTOR_DISPLAY_NAMES[errorClass]).filter(Boolean);
966
+ if (requestedSpecificErrors.length === 1) guidance.push(`Assert ${requestedSpecificErrors[0]} for every rejected partition named by the request; a different error class is not equivalent.`);
967
+ if (requestedSpecificErrors.length > 1) guidance.push(`Preserve the request's partition-to-error mapping: assert each rejected partition with its explicitly named class (${requestedSpecificErrors.join(", ")}); do not apply one class to every partition.`);
954
968
  const integerTargets = integerConstraintTargets(raw);
955
969
  if (integerTargets.length > 0) guidance.push(`Reject fractional values for every integer-constrained argument (${integerTargets.join(", ")}).`);
956
970
  const nullRejectingTargets = nullRejectingDefaultTargets(raw);
@@ -0,0 +1,160 @@
1
+ export const ERROR_CONSTRUCTORS = [
2
+ "typeerror",
3
+ "rangeerror",
4
+ "syntaxerror",
5
+ "referenceerror",
6
+ "urierror",
7
+ "evalerror",
8
+ "aggregateerror",
9
+ "error"
10
+ ];
11
+
12
+ export const ERROR_CONSTRUCTOR_DISPLAY_NAMES = Object.freeze({
13
+ typeerror: "TypeError",
14
+ rangeerror: "RangeError",
15
+ syntaxerror: "SyntaxError",
16
+ referenceerror: "ReferenceError",
17
+ urierror: "URIError",
18
+ evalerror: "EvalError",
19
+ aggregateerror: "AggregateError",
20
+ error: "Error"
21
+ });
22
+
23
+ const ERROR_ACTION = String.raw`(?:throw(?:s|ing)?|rais(?:e|es|ed|ing)|reject(?:s|ed|ing)?|use(?:s|d|ing)?|return(?:s|ed|ing)?|emit(?:s|ted|ting)?|produc(?:e|es|ed|ing)|expect(?:s|ed|ing)?|requir(?:e|es|ed|ing))`;
24
+ const ERROR_ACTION_MENTION = new RegExp(String.raw`\b(?:(?:(?:must|should|shall|do|does|did|can|could|may|might|will|would)\s+)?(?:not|never)\s+(?:ever\s+)?(${ERROR_ACTION})|(${ERROR_ACTION}))\b`, "g");
25
+ const NEGATED_ERROR_PREFIX = /\b(?:never|rather\s+than|instead\s+of|as\s+opposed\s+to|except|other\s+than|(?:but\s+)?not|no)(?:\s+an?)?\s*$/;
26
+ const NEGATED_ERROR_ACTION_PREFIX = /\b(?:without|avoid(?:s|ed|ing)?|prevent(?:s|ed|ing)?|suppress(?:es|ed|ing)?)\s+(?:ever\s+)?(?:throw(?:s|n|ing)?|rais(?:e|es|ed|ing)|reject(?:s|ed|ing)?|us(?:e|es|ed|ing)|return(?:s|ed|ing)?|emit(?:s|ted|ting)?|produc(?:e|es|ed|ing))?(?:\s+an?)?\s*$/;
27
+ const NON_REQUEST_SOURCE_PREFIX = /\b(?:catch(?:es|ing)?|caught|handl(?:e|es|ed|ing)|observ(?:e|es|ed|ing)|receiv(?:e|es|ed|ing)|inspect(?:s|ed|ing)?|log(?:s|ged|ging)?|ignor(?:e|es|ed|ing)|swallow(?:s|ed|ing)?|convert(?:s|ed|ing)?|wrap(?:s|ped|ping)?|translat(?:e|es|ed|ing)|replac(?:e|es|ed|ing)|chang(?:e|es|ed|ing)|turn(?:s|ed|ing)?)\s+(?:an?)?\s*$/;
28
+ const NEGATED_ERROR_SUFFIX = /^\s+(?:(?:must|should|shall|can|could|may|might|will|would)\s+)?(?:not|never)\s+be\s+(?:thrown|raised|used|returned|emitted|produced)\b|^\s+is\s+(?:not\s+(?:allowed|required|expected|acceptable|equivalent)|forbidden|disallowed|prohibited)\b/;
29
+ const AFFIRMATIVE_ERROR_SUFFIX = /^\s+(?:(?:must|should|shall|can|could|may|might|will|would)\s+)?(?:be\s+)?(?:thrown|raised|used|returned|emitted|produced|required|expected)\b|^\s+is\s+(?:required|expected)\b/;
30
+ const TRANSFORM_TARGET_PREFIX = /\b(?:convert(?:s|ed|ing)?|wrap(?:s|ped|ping)?|translat(?:e|es|ed|ing)|map(?:s|ped|ping)?|replac(?:e|es|ed|ing)|chang(?:e|es|ed|ing)|turn(?:s|ed|ing)?)\b[^.;!?\n]{0,96}\b(?:into|to|as)(?:\s+an?)?\s*$/;
31
+ const RESULT_TARGET_PREFIX = /\b(?:result(?:s|ed|ing)?\s+in|resolve(?:s|d|ing)?\s+to)(?:\s+an?)?\s*$/;
32
+ const MAPPED_PARTITION_SUFFIX = /^\s*`?\s+(?:for|with)\s+(?:an?\s+)?(?:negative|fractional|zero|null|empty[- ]string|whitespace-only|non-finite|unsafe[- ]integer|missing|undefined|non-array|non-string|arrays?|plain[- ]objects?|primitives?)\b/;
33
+ const NEUTRAL_CONSTRUCTOR_SUFFIX = /^\s*`?\s*(?:from|reported\s+by|(?:is|was)\s+(?:caught|handled|logged|ignored|observed)|(?:is|was)\s+(?:not\s+allowed|forbidden|disallowed|prohibited))\b/;
34
+ const ERROR_MAPPING_PARTITIONS = Object.freeze([
35
+ ["negative", /\bnegative\b/], ["fractional", /\bfractional\b/], ["zero", /\bzero\b/], ["null", /\bnull\b/],
36
+ ["empty-string", /\bempty[- ]string\b/], ["whitespace-string", /\bwhitespace-only\b/],
37
+ ["non-finite", /\bnon-finite\b/], ["unsafe-integer", /\bunsafe[- ]integer\b/],
38
+ ["missing", /\b(?:missing|undefined)\b/], ["non-array", /\bnon-array\b/], ["non-string", /\bnon-string\b/],
39
+ ["array", /\barrays?\b/], ["plain-object", /\bplain[- ]objects?\b/], ["primitive", /\bprimitives?\b/]
40
+ ]);
41
+
42
+ function normalizedText(value) {
43
+ return String(value ?? "")
44
+ .normalize("NFD")
45
+ .replace(/[\u0300-\u036f]/g, "")
46
+ .replace(/\b(is|are|was|were|do|does|did|should|would|could|must|will|shall)n['’]t\b/gi, "$1 not")
47
+ .replace(/\bcan['’]t\b/gi, "can not")
48
+ .toLowerCase();
49
+ }
50
+
51
+ function affirmativeErrorConstructorMention(value, name) {
52
+ for (const match of value.matchAll(new RegExp(`\\b${name}\\b`, "g"))) {
53
+ const start = match.index ?? 0;
54
+ const end = start + match[0].length;
55
+ const clauseStart = Math.max(value.lastIndexOf(".", start - 1), value.lastIndexOf(";", start - 1), value.lastIndexOf("!", start - 1), value.lastIndexOf("?", start - 1), value.lastIndexOf("\n", start - 1));
56
+ const followingBoundary = value.slice(end).search(/[.;!?\n]/);
57
+ const prefix = value.slice(clauseStart + 1, start).trimEnd();
58
+ const suffix = value.slice(end, followingBoundary < 0 ? value.length : end + followingBoundary);
59
+ if (NEGATED_ERROR_PREFIX.test(prefix) || NEGATED_ERROR_ACTION_PREFIX.test(prefix)
60
+ || NON_REQUEST_SOURCE_PREFIX.test(prefix) || NEGATED_ERROR_SUFFIX.test(suffix)) continue;
61
+ const conditional = prefix.match(/\b(?:when|if|after|once|while)\b[^,;]*$/i);
62
+ if (conditional && !new RegExp(`\\b(?:must|should|shall|will)\\s+${ERROR_ACTION}\\b`, "i").test(conditional[0])) continue;
63
+ let lastActionNegated = false;
64
+ let actionObserved = false;
65
+ for (const action of prefix.matchAll(ERROR_ACTION_MENTION)) {
66
+ actionObserved = true;
67
+ lastActionNegated = Boolean(action[1]);
68
+ }
69
+ if (actionObserved) {
70
+ if (lastActionNegated) continue;
71
+ return true;
72
+ }
73
+ if (AFFIRMATIVE_ERROR_SUFFIX.test(suffix) || TRANSFORM_TARGET_PREFIX.test(prefix)
74
+ || RESULT_TARGET_PREFIX.test(prefix)) return true;
75
+ }
76
+ return false;
77
+ }
78
+
79
+ function bareConstructorSegment(value, start, end) {
80
+ const before = value.slice(0, start);
81
+ const boundaries = [...before.matchAll(/(?:[;,.!?\n]|\b(?:and|but|whereas|or)\b)/g)];
82
+ const segmentStart = boundaries.at(-1)?.index + boundaries.at(-1)?.[0].length || 0;
83
+ const following = value.slice(end), boundary = following.search(/[;,.!?\n]|\b(?:and|but|whereas|or)\b/);
84
+ const prefix = value.slice(segmentStart, start), suffix = value.slice(end, boundary < 0 ? value.length : end + boundary);
85
+ return /^\s*`?\s*$/.test(prefix) ? { suffix } : null;
86
+ }
87
+
88
+ function ellipticalOrAmbiguousMention(value, name) {
89
+ let ambiguous = false;
90
+ for (const match of value.matchAll(new RegExp(`\\b${name}\\b`, "g"))) {
91
+ const segment = bareConstructorSegment(value, match.index ?? 0, (match.index ?? 0) + match[0].length);
92
+ if (!segment || NEUTRAL_CONSTRUCTOR_SUFFIX.test(segment.suffix)) continue;
93
+ if (MAPPED_PARTITION_SUFFIX.test(segment.suffix)) return { affirmative: true, ambiguous: false };
94
+ ambiguous = true;
95
+ }
96
+ return { affirmative: false, ambiguous };
97
+ }
98
+
99
+ export function requestedErrorClasses(text) {
100
+ const value = normalizedText(text);
101
+ const raw = String(text ?? ""), eligible = ERROR_CONSTRUCTORS.filter((name) => name !== "error" || /\bError\b/.test(raw));
102
+ return eligible.filter((name) => affirmativeErrorConstructorMention(value, name)
103
+ || ellipticalOrAmbiguousMention(value, name).affirmative);
104
+ }
105
+
106
+ export function hasAmbiguousErrorClassIntent(text) {
107
+ const value = normalizedText(text), raw = String(text ?? "");
108
+ return ERROR_CONSTRUCTORS.some((name) => (name !== "error" || /\bError\b/.test(raw))
109
+ && !affirmativeErrorConstructorMention(value, name)
110
+ && ellipticalOrAmbiguousMention(value, name).ambiguous);
111
+ }
112
+
113
+ export function requestedErrorPartitionMapping(text, requestedErrors, requestedPartitions) {
114
+ const wanted = new Set(requestedPartitions), mappings = new Map();
115
+ const segments = normalizedText(text).split(/(?:[.;!?\n]+|,\s*|\s+\b(?:and|but|whereas)\b\s+)/).filter(Boolean);
116
+ for (const segment of segments) {
117
+ const partitions = ERROR_MAPPING_PARTITIONS.filter(([partition, pattern]) => wanted.has(partition) && pattern.test(segment)).map(([partition]) => partition);
118
+ const classes = requestedErrors.filter((name) => new RegExp(`\\b${name}\\b`).test(segment));
119
+ if (partitions.length === 0 && classes.length === 0) continue;
120
+ if (partitions.length !== 1 || classes.length !== 1
121
+ || mappings.has(partitions[0]) && mappings.get(partitions[0]) !== classes[0]) return null;
122
+ mappings.set(partitions[0], classes[0]);
123
+ }
124
+ if (mappings.size !== wanted.size || requestedErrors.some((name) => ![...mappings.values()].includes(name))) return null;
125
+ return [...mappings].map(([partition, errorClass]) => ({ partition, errorClass }));
126
+ }
127
+
128
+ export function assertionsProveErrorMapping(assertions, targets, mapping) {
129
+ return mapping.every((expected) => {
130
+ const relevant = assertions.filter((assertion) => assertion.partitions.has(expected.partition)
131
+ && assertion.targets.some((target) => targets.includes(target)));
132
+ return relevant.length > 0 && relevant.every((assertion) => (
133
+ assertion.errorClasses.length === 1 && assertion.errorClasses[0] === expected.errorClass
134
+ ));
135
+ });
136
+ }
137
+
138
+ export function rejectionStatementErrorClass(text, requestedErrors) {
139
+ const match = text.match(/^\s*throw\s+(?:new\s+)?([a-z_$][a-z0-9_$]*)\s*\(/);
140
+ const errorClass = match?.[1]?.toLowerCase();
141
+ if (!errorClass || !ERROR_CONSTRUCTORS.includes(errorClass)) return null;
142
+ const open = match[0].lastIndexOf("(");
143
+ let depth = 0, close = -1;
144
+ for (let index = open; index < Math.min(text.length, open + 2_000); index += 1) {
145
+ if (text[index] === "(") depth += 1;
146
+ else if (text[index] === ")" && --depth === 0) { close = index + 1; break; }
147
+ }
148
+ if (close === -1) return null;
149
+ const argument = text.slice(open + 1, close - 1).trim();
150
+ if (argument && !/^__pi_(?:(?:empty|double_quote|whitespace)_string|string|node_assert_module|code_generation_module|module_loader_module|error_name_[a-z]+)_literal__$/i.test(argument)) return null;
151
+ if (!/^(?:[ \t]*;|[ \t]*(?:\}|$))/u.test(text.slice(close))) return null;
152
+ return requestedErrors.length === 0 || requestedErrors.includes(errorClass) ? errorClass : null;
153
+ }
154
+
155
+ export function errorMappingsProveContract(observed, mapping) {
156
+ return mapping.every((expected) => {
157
+ const matches = observed.filter((actual) => actual.partition === expected.partition);
158
+ return matches.length > 0 && matches.every((actual) => actual.errorClass === expected.errorClass);
159
+ });
160
+ }
@@ -243,7 +243,7 @@ function rejectionAssertionHelpers(testText, assertionCarriers) {
243
243
  for (const declaration of assertionHelperDeclarations(testText)) {
244
244
  if (/\b(?:if|for|while|switch|try|catch|return|throw|class|function)\b|\.(?:skip|todo)\b/.test(declaration.body)) continue;
245
245
  const parameters = [...declaration.parameters.matchAll(/[a-z_$][a-z0-9_$]*/gi)].map((match) => match[0].toLowerCase()).slice(0, 8);
246
- for (const match of declaration.body.matchAll(/\b([a-z_$][a-z0-9_$]*)\.(?:throws|rejects)\s*\(/gi)) {
246
+ for (const match of declaration.body.matchAll(/\b([a-z_$][a-z0-9_$]*)\.(throws|rejects)\s*\(/gi)) {
247
247
  if (!assertionCarriers.has(match[1].toLowerCase())) continue;
248
248
  const open = declaration.body.indexOf("(", match.index);
249
249
  const end = balancedEnd(declaration.body, open);
@@ -254,7 +254,7 @@ function rejectionAssertionHelpers(testText, assertionCarriers) {
254
254
  const before = declaration.body.slice(0, match.index).replace(/^\s*\{?\s*(?:return\s+|await\s+)?/, "");
255
255
  const after = declaration.body.slice(end).replace(/;?\s*\}?\s*$/, "");
256
256
  if (errorClasses.length > 0 && directParameter && !before && !after) {
257
- helpers.push({ ...declaration, errorClasses });
257
+ helpers.push({ ...declaration, errorClasses, mode: match[2].toLowerCase() });
258
258
  break;
259
259
  }
260
260
  }
@@ -283,7 +283,7 @@ function callbackBodyRange(text, argument) {
283
283
  return end === -1 || end > argument.end + 1 ? undefined : { start: bodyStart + 1, end: end - 1, braced: true };
284
284
  }
285
285
 
286
- export function executableRejectionAssertions(testText, callableNames) {
286
+ export function executableRejectionAssertions(testText, callableNames, modeHintsOnly = false) {
287
287
  const assertions = [];
288
288
  const carrierBindings = assertionCarrierBindings(testText);
289
289
  const assertionCarriers = new Set((assertionEvidenceFileSupported(testText)
@@ -420,6 +420,7 @@ export function executableRejectionAssertions(testText, callableNames) {
420
420
  const forOf = header.match(/^\s*(?:const|let)\s+([a-z_$][a-z0-9_$]*)\s+of\s+([\s\S]+?)\s*$/i);
421
421
  const iterable = forOf && iterationLiteral(forOf[2], match.index);
422
422
  if (iterable) iterationRanges.push({ ...iterationBody, ...iterable, kind: "for-of", variable: forOf[1] });
423
+ else if (modeHintsOnly && forOf) iterationRanges.push({ ...iterationBody, kind: "for-of", variable: forOf[1] });
423
424
  else unsupportedControlRanges.push(body);
424
425
  }
425
426
  for (const match of testText.matchAll(/\.foreach\s*\(/g)) {
@@ -460,7 +461,7 @@ export function executableRejectionAssertions(testText, callableNames) {
460
461
  const prefix = testText.slice(range.start, start);
461
462
  return !new RegExp(`\\b(?:const|let|var|function|class)\\s+${escaped}\\b|\\b${escaped}\\b\\s*(?:\\+\\+|--|[+*/%&|^-]=|=(?!=|>))|(?:\\+\\+|--)\\s*\\b${escaped}\\b`, "i").test(prefix);
462
463
  };
463
- const record = (start, end, errorClasses, operation) => {
464
+ const record = (start, end, errorClasses, operation, mode) => {
464
465
  if (end === -1) return;
465
466
  if ([...declarationRanges, ...skippedRanges, ...conditionalRanges, ...unsupportedControlRanges]
466
467
  .some((range) => start >= range.start && start < range.end)) return;
@@ -495,16 +496,17 @@ export function executableRejectionAssertions(testText, callableNames) {
495
496
  if (targets.length === 0) return;
496
497
  const partitions = evidencePartitionSignals(assertion);
497
498
  for (const range of referencedIterations) {
498
- for (const signal of literalArrayPartitionSignals(range.literal)) partitions.add(signal);
499
+ if (range.literal) for (const signal of literalArrayPartitionSignals(range.literal)) partitions.add(signal);
499
500
  }
500
- assertions.push({ targets, errorClasses, partitions });
501
+ assertions.push({ targets, errorClasses, partitions, mode });
501
502
  };
502
503
  for (const match of testText.matchAll(/\b([a-z_$][a-z0-9_$]*)\.(?:throws|rejects)\s*\(/gi)) {
503
504
  if (!assertionCarriers.has(match[1].toLowerCase())) continue;
504
505
  const open = testText.indexOf("(", match.index);
505
506
  const end = balancedEnd(testText, open);
506
507
  const argumentsList = end === -1 ? [] : topLevelArgumentRanges(testText, open, end);
507
- record(match.index, end, evidenceErrorClasses(end === -1 ? "" : testText.slice(match.index, end)), argumentsList[0]?.text);
508
+ record(match.index, end, evidenceErrorClasses(end === -1 ? "" : testText.slice(match.index, end)), argumentsList[0]?.text,
509
+ /\.rejects\s*\(/i.test(match[0]) ? "rejects" : "throws");
508
510
  }
509
511
  for (const helper of rejectionAssertionHelpers(testText, assertionCarriers)) {
510
512
  for (const match of testText.matchAll(new RegExp(`\\b${escapeRegex(helper.name)}\\s*\\(`, "g"))) {
@@ -512,7 +514,7 @@ export function executableRejectionAssertions(testText, callableNames) {
512
514
  const open = testText.indexOf("(", match.index);
513
515
  const end = balancedEnd(testText, open);
514
516
  const argumentsList = end === -1 ? [] : topLevelArgumentRanges(testText, open, end);
515
- record(match.index, end, helper.errorClasses, argumentsList[0]?.text);
517
+ record(match.index, end, helper.errorClasses, argumentsList[0]?.text, helper.mode);
516
518
  }
517
519
  }
518
520
  for (const match of testText.matchAll(/\bexpect\s*\(/g)) {
@@ -525,7 +527,8 @@ export function executableRejectionAssertions(testText, callableNames) {
525
527
  const throwOpen = testText.indexOf("(", expectEnd + chain.index);
526
528
  const end = balancedEnd(testText, throwOpen);
527
529
  const argumentsList = topLevelArgumentRanges(testText, open, expectEnd);
528
- record(match.index, end, evidenceErrorClasses(end === -1 ? "" : testText.slice(match.index, end)), argumentsList[0]?.text);
530
+ record(match.index, end, evidenceErrorClasses(end === -1 ? "" : testText.slice(match.index, end)), argumentsList[0]?.text,
531
+ /\.rejects/i.test(chain[0]) ? "rejects" : "throws");
529
532
  }
530
533
  return assertions.slice(0, 64);
531
534
  }