cool-workflow 0.1.98 → 0.2.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 (315) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +18 -7
  4. package/apps/architecture-review/app.json +2 -2
  5. package/apps/architecture-review/workflow.js +12 -12
  6. package/apps/architecture-review-fast/app.json +1 -1
  7. package/apps/end-to-end-golden-path/app.json +1 -1
  8. package/apps/pr-review-fix-ci/app.json +1 -1
  9. package/apps/release-cut/app.json +1 -1
  10. package/apps/research-synthesis/app.json +1 -1
  11. package/dist/cli/dispatch.js +236 -0
  12. package/dist/cli/entry.js +120 -0
  13. package/dist/cli/io.js +21 -4
  14. package/dist/cli/parseargv.js +157 -0
  15. package/dist/cli.js +6 -33
  16. package/dist/core/capability-table.js +3518 -0
  17. package/dist/core/format/help.js +314 -0
  18. package/dist/{state-explosion/format.js → core/format/state-explosion-text.js} +10 -1
  19. package/dist/core/hash.js +137 -0
  20. package/dist/core/multi-agent/candidate-scoring.js +219 -0
  21. package/dist/core/multi-agent/collaboration.js +481 -0
  22. package/dist/core/multi-agent/coordinator.js +515 -0
  23. package/dist/core/multi-agent/eval-replay.js +306 -0
  24. package/dist/core/multi-agent/runtime.js +929 -0
  25. package/dist/core/multi-agent/topology.js +298 -0
  26. package/dist/core/multi-agent/trust-policy.js +197 -0
  27. package/dist/core/pipeline/commit-gate.js +320 -0
  28. package/dist/{pipeline-contract.js → core/pipeline/contract.js} +24 -37
  29. package/dist/core/pipeline/dispatch.js +103 -0
  30. package/dist/core/pipeline/drive-decide.js +227 -0
  31. package/dist/core/pipeline/error-feedback.js +161 -0
  32. package/dist/core/pipeline/loop-expansion.js +124 -0
  33. package/dist/{result-normalize.js → core/pipeline/result-normalize.js} +14 -37
  34. package/dist/core/pipeline/runner.js +230 -0
  35. package/dist/{contract-migration.js → core/state/contract-migration.js} +68 -92
  36. package/dist/{state-migrations.js → core/state/migrations.js} +103 -96
  37. package/dist/core/state/node-projection.js +95 -0
  38. package/dist/core/state/node-snapshot.js +230 -0
  39. package/dist/core/state/run-paths.js +93 -0
  40. package/dist/{schema-validate.js → core/state/schema-validate.js} +35 -28
  41. package/dist/core/state/schema.js +50 -0
  42. package/dist/core/state/state-explosion/digest.js +243 -0
  43. package/dist/core/state/state-explosion/graph.js +527 -0
  44. package/dist/{state-explosion → core/state/state-explosion}/helpers.js +34 -3
  45. package/dist/core/state/state-explosion/report.js +187 -0
  46. package/dist/{state-explosion → core/state/state-explosion}/size.js +25 -7
  47. package/dist/{state-node.js → core/state/state-node.js} +90 -113
  48. package/dist/core/state/types.js +19 -0
  49. package/dist/{validation.js → core/state/validation.js} +64 -131
  50. package/dist/core/trust/evidence-grounding.js +134 -0
  51. package/dist/core/trust/ledger.js +199 -0
  52. package/dist/{telemetry-attestation.js → core/trust/telemetry-attestation.js} +94 -68
  53. package/dist/core/trust/telemetry-ledger.js +127 -0
  54. package/dist/core/types.js +20 -0
  55. package/dist/core/version.js +16 -0
  56. package/dist/{workflow-app-framework.js → core/workflow-apps/app-schema.js} +337 -426
  57. package/dist/mcp/dispatch.js +104 -0
  58. package/dist/mcp/server.js +144 -0
  59. package/dist/mcp-server.js +6 -84
  60. package/dist/{agent-config.js → shell/agent-config.js} +118 -71
  61. package/dist/shell/app-run-cli.js +88 -0
  62. package/dist/shell/audit-cli.js +259 -0
  63. package/dist/shell/audit-provenance.js +83 -0
  64. package/dist/shell/candidate-scoring-io.js +459 -0
  65. package/dist/shell/collaboration-io.js +264 -0
  66. package/dist/shell/commit-summary.js +104 -0
  67. package/dist/shell/commit.js +290 -0
  68. package/dist/shell/coordinator-io.js +476 -0
  69. package/dist/shell/demo-cli.js +19 -0
  70. package/dist/shell/dispatch.js +162 -0
  71. package/dist/shell/doctor.js +292 -0
  72. package/dist/shell/drive.js +885 -0
  73. package/dist/shell/error-feedback-io.js +305 -0
  74. package/dist/shell/eval-io.js +473 -0
  75. package/dist/{multi-agent-eval/format.js → shell/eval-text.js} +78 -49
  76. package/dist/{evidence-reasoning.js → shell/evidence-reasoning.js} +124 -255
  77. package/dist/shell/exec-backend-cli.js +88 -0
  78. package/dist/shell/execution-backend/agent.js +507 -0
  79. package/dist/shell/execution-backend/ci.js +15 -0
  80. package/dist/shell/execution-backend/container.js +69 -0
  81. package/dist/shell/execution-backend/envelopes.js +55 -0
  82. package/dist/shell/execution-backend/local.js +113 -0
  83. package/dist/shell/execution-backend/probes.js +175 -0
  84. package/dist/shell/execution-backend/registry.js +402 -0
  85. package/dist/shell/execution-backend/remote.js +128 -0
  86. package/dist/shell/execution-backend/types.js +11 -0
  87. package/dist/shell/feedback-cli.js +81 -0
  88. package/dist/shell/feedback-operations.js +48 -0
  89. package/dist/shell/fs-atomic.js +276 -0
  90. package/dist/shell/harness.js +98 -0
  91. package/dist/shell/ledger-cli.js +212 -0
  92. package/dist/shell/ledger-io.js +169 -0
  93. package/dist/shell/man-cli.js +89 -0
  94. package/dist/shell/metrics-cli.js +100 -0
  95. package/dist/shell/multi-agent-cli.js +1002 -0
  96. package/dist/shell/multi-agent-host.js +563 -0
  97. package/dist/shell/multi-agent-io.js +387 -0
  98. package/dist/{multi-agent-operator-ux.js → shell/multi-agent-operator-ux.js} +234 -191
  99. package/dist/shell/node-store.js +124 -0
  100. package/dist/{observability/format.js → shell/observability-format.js} +7 -1
  101. package/dist/{observability/intake.js → shell/observability-intake.js} +79 -56
  102. package/dist/{observability.js → shell/observability.js} +159 -332
  103. package/dist/{onramp.js → shell/onramp.js} +11 -0
  104. package/dist/{operator-ux/format.js → shell/operator-ux-text.js} +250 -239
  105. package/dist/shell/operator-ux.js +431 -0
  106. package/dist/shell/orchestrator.js +231 -0
  107. package/dist/shell/pipeline-cli.js +667 -0
  108. package/dist/shell/pipeline.js +217 -0
  109. package/dist/shell/reclamation-io.js +1366 -0
  110. package/dist/shell/registry-cli.js +344 -0
  111. package/dist/{remote-source.js → shell/remote-source.js} +2 -2
  112. package/dist/shell/report-cli.js +101 -0
  113. package/dist/shell/report-view-cli.js +117 -0
  114. package/dist/{orchestrator → shell}/report.js +289 -282
  115. package/dist/shell/reporter.js +62 -0
  116. package/dist/shell/run-export-cli.js +106 -0
  117. package/dist/shell/run-export.js +680 -0
  118. package/dist/shell/run-registry-io.js +1014 -0
  119. package/dist/shell/run-store.js +164 -0
  120. package/dist/{sandbox-profile.js → shell/sandbox-profile.js} +134 -95
  121. package/dist/{scheduler.js → shell/scheduler-io.js} +257 -48
  122. package/dist/shell/scheduling-io.js +321 -0
  123. package/dist/shell/state-cli.js +181 -0
  124. package/dist/shell/state-explosion-cli.js +197 -0
  125. package/dist/shell/telemetry-cli.js +85 -0
  126. package/dist/{telemetry-demo.js → shell/telemetry-demo.js} +149 -119
  127. package/dist/shell/telemetry-ledger-io.js +132 -0
  128. package/dist/{term.js → shell/term.js} +36 -46
  129. package/dist/shell/topology-io.js +361 -0
  130. package/dist/shell/trust-audit.js +472 -0
  131. package/dist/{multi-agent-trust.js → shell/trust-policy-io.js} +67 -205
  132. package/dist/shell/verifier.js +48 -0
  133. package/dist/shell/workbench-host.js +258 -0
  134. package/dist/shell/workbench-text.js +18 -0
  135. package/dist/shell/workbench.js +170 -0
  136. package/dist/shell/worker-cli.js +124 -0
  137. package/dist/shell/worker-isolation.js +852 -0
  138. package/dist/shell/workflow-app-loader.js +650 -0
  139. package/docs/agent-delegation-drive.7.md +4 -0
  140. package/docs/cli-mcp-parity.7.md +282 -219
  141. package/docs/contract-migration-tooling.7.md +4 -0
  142. package/docs/control-plane-scheduling.7.md +4 -0
  143. package/docs/durable-state-and-locking.7.md +4 -0
  144. package/docs/evidence-adoption-reasoning-chain.7.md +4 -0
  145. package/docs/execution-backends.7.md +4 -0
  146. package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
  147. package/docs/multi-agent-eval-replay-harness.7.md +4 -0
  148. package/docs/multi-agent-operator-ux.7.md +4 -0
  149. package/docs/node-snapshot-diff-replay.7.md +4 -0
  150. package/docs/observability-cost-accounting.7.md +6 -1
  151. package/docs/project-index.md +137 -71
  152. package/docs/real-execution-backends.7.md +4 -0
  153. package/docs/release-and-migration.7.md +4 -0
  154. package/docs/release-tooling.7.md +4 -0
  155. package/docs/remote-source-review.7.md +4 -4
  156. package/docs/report-verifiable-bundle.7.md +1 -1
  157. package/docs/run-registry-control-plane.7.md +4 -0
  158. package/docs/run-retention-reclamation.7.md +25 -0
  159. package/docs/security-trust-hardening.7.md +3 -1
  160. package/docs/state-explosion-management.7.md +4 -0
  161. package/docs/team-collaboration.7.md +4 -0
  162. package/docs/web-desktop-workbench.7.md +15 -1
  163. package/manifest/plugin.manifest.json +1 -1
  164. package/manifest/source-context-profiles.json +9 -13
  165. package/package.json +1 -1
  166. package/scripts/agents/agent-adapter-core.js +22 -2
  167. package/scripts/agents/claude-p-agent.js +8 -3
  168. package/scripts/agents/cw-attest-wrap.js +2 -2
  169. package/scripts/agents/gemini-agent.js +5 -1
  170. package/scripts/agents/opencode-agent.js +5 -1
  171. package/scripts/bump-version.js +4 -3
  172. package/scripts/canonical-apps.js +4 -4
  173. package/scripts/dogfood-architecture-review.js +2 -3
  174. package/scripts/dogfood-release.js +3 -3
  175. package/scripts/gen-parity-doc.js +15 -2
  176. package/scripts/golden-path.js +4 -4
  177. package/scripts/onramp-check.js +1 -1
  178. package/scripts/parity-check.js +38 -21
  179. package/scripts/release-flow.js +2 -2
  180. package/scripts/sync-project-index.js +51 -27
  181. package/scripts/validate-run-state-schema.js +2 -2
  182. package/scripts/version-sync-check.js +30 -30
  183. package/dist/candidate-scoring.js +0 -729
  184. package/dist/capability-core.js +0 -1189
  185. package/dist/capability-registry.js +0 -885
  186. package/dist/cli/command-surface.js +0 -494
  187. package/dist/cli/format.js +0 -56
  188. package/dist/cli/handlers/audit.js +0 -82
  189. package/dist/cli/handlers/blackboard.js +0 -81
  190. package/dist/cli/handlers/candidate.js +0 -40
  191. package/dist/cli/handlers/clones.js +0 -34
  192. package/dist/cli/handlers/collaboration.js +0 -61
  193. package/dist/cli/handlers/eval.js +0 -40
  194. package/dist/cli/handlers/ledger.js +0 -169
  195. package/dist/cli/handlers/maintenance.js +0 -107
  196. package/dist/cli/handlers/multi-agent.js +0 -165
  197. package/dist/cli/handlers/node.js +0 -41
  198. package/dist/cli/handlers/operational.js +0 -155
  199. package/dist/cli/handlers/operator.js +0 -146
  200. package/dist/cli/handlers/registry.js +0 -68
  201. package/dist/cli/handlers/run.js +0 -153
  202. package/dist/cli/handlers/scheduling.js +0 -132
  203. package/dist/cli/handlers/workbench.js +0 -41
  204. package/dist/cli/handlers/worker.js +0 -45
  205. package/dist/cli/run-summary.js +0 -45
  206. package/dist/clones.js +0 -162
  207. package/dist/collaboration.js +0 -726
  208. package/dist/commit.js +0 -592
  209. package/dist/compare.js +0 -18
  210. package/dist/coordinator/classify.js +0 -45
  211. package/dist/coordinator/paths.js +0 -42
  212. package/dist/coordinator/util.js +0 -126
  213. package/dist/coordinator.js +0 -990
  214. package/dist/daemon.js +0 -44
  215. package/dist/dispatch.js +0 -250
  216. package/dist/doctor.js +0 -212
  217. package/dist/drive.js +0 -864
  218. package/dist/error-feedback.js +0 -419
  219. package/dist/evidence-grounding.js +0 -184
  220. package/dist/execution-backend/agent.js +0 -354
  221. package/dist/execution-backend/probes.js +0 -112
  222. package/dist/execution-backend/util.js +0 -47
  223. package/dist/execution-backend.js +0 -961
  224. package/dist/gates.js +0 -48
  225. package/dist/harness.js +0 -61
  226. package/dist/ledger.js +0 -313
  227. package/dist/loop-expansion.js +0 -60
  228. package/dist/mcp/tool-call.js +0 -470
  229. package/dist/mcp/tool-definitions.js +0 -1066
  230. package/dist/mcp-surface.js +0 -30
  231. package/dist/multi-agent/graph.js +0 -84
  232. package/dist/multi-agent/helpers.js +0 -141
  233. package/dist/multi-agent/ids.js +0 -20
  234. package/dist/multi-agent/paths.js +0 -22
  235. package/dist/multi-agent-eval/normalize.js +0 -51
  236. package/dist/multi-agent-eval.js +0 -678
  237. package/dist/multi-agent-host.js +0 -777
  238. package/dist/multi-agent.js +0 -984
  239. package/dist/node-projection.js +0 -59
  240. package/dist/node-snapshot.js +0 -260
  241. package/dist/operator-ux.js +0 -631
  242. package/dist/orchestrator/app-operations.js +0 -211
  243. package/dist/orchestrator/audit-operations.js +0 -182
  244. package/dist/orchestrator/candidate-operations.js +0 -117
  245. package/dist/orchestrator/cli-options.js +0 -294
  246. package/dist/orchestrator/collaboration-operations.js +0 -86
  247. package/dist/orchestrator/feedback-operations.js +0 -81
  248. package/dist/orchestrator/host-operations.js +0 -78
  249. package/dist/orchestrator/lifecycle-operations.js +0 -650
  250. package/dist/orchestrator/migration-operations.js +0 -44
  251. package/dist/orchestrator/multi-agent-operations.js +0 -362
  252. package/dist/orchestrator/topology-operations.js +0 -84
  253. package/dist/orchestrator.js +0 -925
  254. package/dist/pipeline-runner.js +0 -285
  255. package/dist/reclamation/hash.js +0 -72
  256. package/dist/reclamation.js +0 -812
  257. package/dist/reporter.js +0 -67
  258. package/dist/run-export.js +0 -815
  259. package/dist/run-registry/derive.js +0 -175
  260. package/dist/run-registry/format.js +0 -124
  261. package/dist/run-registry/gc.js +0 -251
  262. package/dist/run-registry/policy.js +0 -16
  263. package/dist/run-registry/queue.js +0 -115
  264. package/dist/run-registry.js +0 -850
  265. package/dist/run-state-schema.js +0 -68
  266. package/dist/scheduling.js +0 -184
  267. package/dist/state-explosion.js +0 -1014
  268. package/dist/state.js +0 -367
  269. package/dist/telemetry-ledger.js +0 -196
  270. package/dist/topology.js +0 -565
  271. package/dist/triggers.js +0 -184
  272. package/dist/trust-audit.js +0 -644
  273. package/dist/types/blackboard.js +0 -2
  274. package/dist/types/candidate.js +0 -2
  275. package/dist/types/collaboration.js +0 -2
  276. package/dist/types/core.js +0 -2
  277. package/dist/types/drive.js +0 -10
  278. package/dist/types/error-feedback.js +0 -2
  279. package/dist/types/evidence-reasoning.js +0 -2
  280. package/dist/types/execution-backend.js +0 -2
  281. package/dist/types/multi-agent.js +0 -2
  282. package/dist/types/observability.js +0 -2
  283. package/dist/types/pipeline.js +0 -2
  284. package/dist/types/reclamation.js +0 -8
  285. package/dist/types/report-bundle.js +0 -6
  286. package/dist/types/result.js +0 -2
  287. package/dist/types/run-registry.js +0 -2
  288. package/dist/types/run.js +0 -2
  289. package/dist/types/sandbox.js +0 -2
  290. package/dist/types/schedule.js +0 -2
  291. package/dist/types/state-node.js +0 -2
  292. package/dist/types/topology.js +0 -2
  293. package/dist/types/trust.js +0 -2
  294. package/dist/types/workbench.js +0 -2
  295. package/dist/types/worker.js +0 -2
  296. package/dist/types/workflow-app.js +0 -2
  297. package/dist/types.js +0 -44
  298. package/dist/util/fingerprint.js +0 -19
  299. package/dist/util/fingerprint.test.js +0 -27
  300. package/dist/verifier.js +0 -78
  301. package/dist/version.js +0 -8
  302. package/dist/workbench-host.js +0 -192
  303. package/dist/workbench.js +0 -192
  304. package/dist/worker-accept/acceptance.js +0 -114
  305. package/dist/worker-accept/blackboard-fanout.js +0 -80
  306. package/dist/worker-accept/blackboard-linkage.js +0 -19
  307. package/dist/worker-accept/context.js +0 -2
  308. package/dist/worker-accept/telemetry-ledger.js +0 -126
  309. package/dist/worker-accept/validation.js +0 -77
  310. package/dist/worker-accept/verifier-completion.js +0 -73
  311. package/dist/worker-isolation/helpers.js +0 -51
  312. package/dist/worker-isolation/paths.js +0 -46
  313. package/dist/worker-isolation.js +0 -656
  314. package/dist/workflow-api.js +0 -131
  315. /package/dist/{types → core/types}/boundary.js +0 -0
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+ // core/pipeline/drive-decide.ts — driveStep/driveConcurrentRound's PURE
3
+ // decision core: task selection, terminal/gate logic, token-budget check,
4
+ // retry/park math, cache-key formulas.
5
+ //
6
+ // MILESTONE 6+7 (combined; the big one — see docs/rebuild/PLAN.md Open risk 9).
7
+ // Every branch here is a pure function of already-loaded run state; it
8
+ // does not itself spawn a process or touch disk. shell/drive.ts is the
9
+ // thin imperative loop that calls these functions once per step and
10
+ // performs the actual spawn/commit/cache-write IO the decision names.
11
+ //
12
+ // Evidence: SPEC/pipeline-run.md "Drive loop — src/drive.ts", "Drive
13
+ // internals a rebuild must copy", "`--incremental` and the result
14
+ // cache".
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.DEFAULT_SCHEDULING_POLICY = exports.MAX_SUB_WORKFLOW_DEPTH = exports.DRIVE_SCHEMA_VERSION = void 0;
17
+ exports.makeStep = makeStep;
18
+ exports.selectDriveTask = selectDriveTask;
19
+ exports.countCompleted = countCompleted;
20
+ exports.countParked = countParked;
21
+ exports.verdictVerifierNodeId = verdictVerifierNodeId;
22
+ exports.exitCodeFromEvidence = exitCodeFromEvidence;
23
+ exports.hasTerminalCommit = hasTerminalCommit;
24
+ exports.terminalOrConfigStep = terminalOrConfigStep;
25
+ exports.retryOrPark = retryOrPark;
26
+ exports.priorAttempts = priorAttempts;
27
+ exports.maxIterations = maxIterations;
28
+ exports.autoWidth = autoWidth;
29
+ exports.roundWidth = roundWidth;
30
+ exports.finalDriveStatus = finalDriveStatus;
31
+ exports.cacheFileName = cacheFileName;
32
+ exports.defaultCacheKey = defaultCacheKey;
33
+ exports.incrementalCacheKey = incrementalCacheKey;
34
+ exports.incrementalDelegationDigest = incrementalDelegationDigest;
35
+ const dispatch_1 = require("./dispatch");
36
+ const hash_1 = require("../hash");
37
+ exports.DRIVE_SCHEMA_VERSION = 1;
38
+ exports.MAX_SUB_WORKFLOW_DEPTH = 4;
39
+ function makeStep(action, status, fields) {
40
+ return { schemaVersion: 1, action, status, ...fields };
41
+ }
42
+ /** The task the next drive step would advance: a running task first,
43
+ * else the next pending task of the first runnable phase. */
44
+ function selectDriveTask(run) {
45
+ const phase = (0, dispatch_1.firstRunnablePhase)(run);
46
+ if (!phase)
47
+ return undefined;
48
+ const phaseTasks = run.tasks.filter((task) => phase.taskIds.includes(task.id));
49
+ return phaseTasks.find((task) => task.status === "running") || phaseTasks.find((task) => task.status === "pending");
50
+ }
51
+ function countCompleted(run) {
52
+ return run.tasks.filter((task) => task.status === "completed").length;
53
+ }
54
+ function countParked(run) {
55
+ return run.tasks.filter((task) => task.status === "failed").length;
56
+ }
57
+ /** The completed verdict/synthesis task's verifierNodeId, if any. */
58
+ function verdictVerifierNodeId(run) {
59
+ const verdict = run.tasks.find((task) => /^verdict[:/]|^synthesis[:/]/i.test(task.id) && task.status === "completed");
60
+ return verdict?.verifierNodeId;
61
+ }
62
+ function exitCodeFromEvidence(evidence) {
63
+ const entry = evidence.find((line) => line.startsWith("exitCode:"));
64
+ if (!entry)
65
+ return null;
66
+ const raw = entry.slice("exitCode:".length);
67
+ return raw === "null" ? null : Number(raw);
68
+ }
69
+ /** Whether a commit already exists whose reason starts with
70
+ * "agent-delegation-drive" (the once-only terminal-commit check). */
71
+ function hasTerminalCommit(run) {
72
+ return (run.commits || []).some((commit) => commit.reason && commit.reason.startsWith("agent-delegation-drive"));
73
+ }
74
+ /** terminalOrConfigStep's pure decision half. Returns a DriveStep only
75
+ * for the non-advancing outcomes (terminal commit/complete, blocked
76
+ * phase, blocked token budget, blocked unconfigured agent); returns
77
+ * `{kind: undefined}` when there is a ready task to actually process. */
78
+ function terminalOrConfigStep(run, selected, agentConfigured, tokenBudget) {
79
+ if (!selected) {
80
+ const allComplete = run.tasks.every((task) => task.status === "completed");
81
+ if (allComplete) {
82
+ if (!hasTerminalCommit(run)) {
83
+ return { kind: "commit", verifierNodeId: verdictVerifierNodeId(run) };
84
+ }
85
+ return { kind: "complete", step: makeStep("complete", "complete", { runId: run.id }) };
86
+ }
87
+ return {
88
+ kind: "blocked",
89
+ step: makeStep("blocked", "blocked", { runId: run.id, reason: "no eligible worker (a parked/failed worker blocks the phase gate)" }),
90
+ };
91
+ }
92
+ if (tokenBudget && tokenBudget.budget > 0 && tokenBudget.spent >= tokenBudget.budget) {
93
+ return {
94
+ kind: "blocked",
95
+ step: makeStep("blocked", "blocked", {
96
+ runId: run.id,
97
+ taskId: selected.id,
98
+ phase: selected.phase,
99
+ reason: `token budget exhausted: ${tokenBudget.spent} recorded tokens >= budget ${tokenBudget.budget} — refusing to spawn further agents`,
100
+ }),
101
+ };
102
+ }
103
+ if (!agentConfigured) {
104
+ return {
105
+ kind: "blocked",
106
+ step: makeStep("blocked", "blocked", {
107
+ runId: run.id,
108
+ taskId: selected.id,
109
+ phase: selected.phase,
110
+ reason: "agent backend not configured (set CW_AGENT_COMMAND/CW_AGENT_ENDPOINT or pass --agent-command/--agent-endpoint) — refusing rather than fabricating a completion",
111
+ }),
112
+ };
113
+ }
114
+ return { kind: undefined };
115
+ }
116
+ exports.DEFAULT_SCHEDULING_POLICY = { maxAttempts: 3 };
117
+ /** `retryOrPark` — adds one attempt; at `attempts >= maxAttempts` parks
118
+ * with `parkedReason = "<reason> (attempt <n>/<max>)"`. */
119
+ function retryOrPark(priorAttempts, policy, reason) {
120
+ const attempts = priorAttempts + 1;
121
+ if (attempts >= policy.maxAttempts) {
122
+ return { status: "parked", attempts, parkedReason: `${reason} (attempt ${attempts}/${policy.maxAttempts})` };
123
+ }
124
+ return { status: "retryable", attempts };
125
+ }
126
+ /** `handleHop`'s attempt-accounting rule: prior attempts = max(in-memory
127
+ * count, the worker scope's persisted retryCount). */
128
+ function priorAttempts(inMemoryAttempts, persistedRetryCount) {
129
+ return Math.max(inMemoryAttempts, persistedRetryCount);
130
+ }
131
+ // ---------------------------------------------------------------------------
132
+ // Loop iteration bound.
133
+ // ---------------------------------------------------------------------------
134
+ /** `maxIterations = (plannedWorkers + maxLoopExpansion) * (maxAttempts +
135
+ * 1) + 5`. */
136
+ function maxIterations(plannedWorkers, loopExpansion, policy) {
137
+ return (plannedWorkers + loopExpansion) * (policy.maxAttempts + 1) + 5;
138
+ }
139
+ /** Round width per iteration: an explicit concurrency > 1 wins; else
140
+ * autoWidth for a first-runnable parallel phase. */
141
+ function autoWidth(run) {
142
+ const phase = (0, dispatch_1.firstRunnablePhase)(run);
143
+ if (!phase || phase.mode !== "parallel")
144
+ return 1;
145
+ const cap = Math.max(1, Math.floor(run.workflow.limits?.maxConcurrentAgents || 1));
146
+ return Math.max(1, Math.min(cap, phase.taskIds.length));
147
+ }
148
+ function roundWidth(run, concurrency) {
149
+ return concurrency && concurrency > 1 ? concurrency : autoWidth(run);
150
+ }
151
+ function finalDriveStatus(inputs) {
152
+ if (inputs.once) {
153
+ if (inputs.completedWorkers === inputs.plannedWorkers && inputs.committed)
154
+ return "complete";
155
+ if (inputs.lastStepStatus === "parked" || inputs.lastStepStatus === "blocked")
156
+ return inputs.lastStepStatus;
157
+ return "in-progress";
158
+ }
159
+ if (inputs.exhaustedMaxIterations)
160
+ return "blocked";
161
+ if (inputs.parkedWorkers > 0 || inputs.lastStepStatus === "parked")
162
+ return "parked";
163
+ if (inputs.lastStepStatus === "blocked")
164
+ return "blocked";
165
+ return "complete";
166
+ }
167
+ // ---------------------------------------------------------------------------
168
+ // Result cache key formulas.
169
+ // ---------------------------------------------------------------------------
170
+ /** Cache file path (relative to `<run.cwd>/.cw/cache/worker-results/`).
171
+ * Actual path join / atomic write is shell-side. */
172
+ function cacheFileName(taskId, digest) {
173
+ return `${safeFileNamePart(taskId)}-${digest.replace(/^sha256:/, "").slice(0, 32)}.md`;
174
+ }
175
+ /** Kept in sync with shell/fs-atomic.ts's `safeFileName` by the same
176
+ * regex (this pure module cannot import the shell-side impure copy);
177
+ * both are pinned by SPEC/pipeline-run.md's cache-file-path line, which
178
+ * names it `safeFileName` explicitly — the charset MUST include `:` so
179
+ * a task id like `golden:path` keeps its colon in the cache filename. */
180
+ function safeFileNamePart(value) {
181
+ return String(value).replace(/[^a-zA-Z0-9_.:-]+/g, "_");
182
+ }
183
+ /** Default (no `--incremental`) cache key. `undefined` disables caching
184
+ * for this call (no keyInput, empty keyValue, or an unavailable
185
+ * completedResultsDigest upstream). */
186
+ function defaultCacheKey(workflowId, taskId, keyInput, keyValue, promptDigest, completedResultsDigest) {
187
+ if (!keyInput || !keyValue || !keyValue.trim())
188
+ return undefined;
189
+ if (completedResultsDigest === undefined)
190
+ return undefined;
191
+ return (0, hash_1.sha256)(JSON.stringify({
192
+ schemaVersion: 1,
193
+ workflowId,
194
+ taskId,
195
+ keyInput,
196
+ keyValue: keyValue.trim(),
197
+ promptDigest,
198
+ completedResultsDigest,
199
+ }));
200
+ }
201
+ /** `--incremental` cache key (schemaVersion 2 — never collides with
202
+ * schemaVersion 1). `undefined` disables caching (an upstream result is
203
+ * unavailable). */
204
+ function incrementalCacheKey(workflowId, taskId, promptDigest, runInputsDigest, delegationDigest, upstreamResultsDigest) {
205
+ if (upstreamResultsDigest === undefined)
206
+ return undefined;
207
+ return (0, hash_1.sha256)((0, hash_1.stableStringify)({
208
+ schemaVersion: 2,
209
+ workflowId,
210
+ taskId,
211
+ promptDigest,
212
+ runInputsDigest,
213
+ delegationDigest,
214
+ upstreamResultsDigest,
215
+ }));
216
+ }
217
+ /** The delegation digest folded into the incremental cache key. */
218
+ function incrementalDelegationDigest(model, agentType, sandboxProfileId, command, strippedArgs, endpoint) {
219
+ return (0, hash_1.sha256)((0, hash_1.stableStringify)({
220
+ model,
221
+ agentType,
222
+ sandboxProfileId,
223
+ command,
224
+ args: strippedArgs,
225
+ endpoint,
226
+ }));
227
+ }
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ // core/pipeline/error-feedback.ts — classifyFeedback, recordFeedback's
3
+ // decision half, dedup key.
4
+ //
5
+ // MILESTONE 6+7 (combined). Byte-exact port of the DECISION half of the
6
+ // old build's src/error-feedback.ts (record shape, classification,
7
+ // severity, dedup key, id formatting). The disk write (feedback/*.json +
8
+ // index.json + saveCheckpoint) is the caller's job (shell/), since this
9
+ // file stays pure per the core/shell split.
10
+ //
11
+ // Evidence: SPEC/pipeline-run.md "Error feedback — src/error-feedback.ts".
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.ERROR_FEEDBACK_SCHEMA_VERSION = void 0;
14
+ exports.classifyFeedback = classifyFeedback;
15
+ exports.feedbackKey = feedbackKey;
16
+ exports.formatFeedbackId = formatFeedbackId;
17
+ exports.findExistingFeedback = findExistingFeedback;
18
+ exports.buildFeedbackRecord = buildFeedbackRecord;
19
+ exports.summarizeFeedback = summarizeFeedback;
20
+ exports.ERROR_FEEDBACK_SCHEMA_VERSION = 1;
21
+ function normalizeError(error, now) {
22
+ if (typeof error === "string") {
23
+ return { code: "runtime-error", message: error, at: now };
24
+ }
25
+ if (error instanceof Error) {
26
+ return { code: codeFromError(error), message: error.message, at: now };
27
+ }
28
+ return { ...error, code: error.code || "runtime-error", message: error.message || "Unknown error", at: error.at || now };
29
+ }
30
+ function codeFromError(error) {
31
+ if (/Invalid cw:result JSON/i.test(error.message))
32
+ return "result-parse-error";
33
+ if (/requires cw:result evidence/i.test(error.message))
34
+ return "missing-required-evidence";
35
+ if (/requires evidence/i.test(error.message))
36
+ return "missing-required-evidence";
37
+ if (/Phase gate blocked/i.test(error.message))
38
+ return "phase-gate-blocked";
39
+ return "runtime-error";
40
+ }
41
+ /** classifyFeedback — fixed order, byte-exact to the old build. */
42
+ function classifyFeedback(error, context = {}, now = new Date(0).toISOString()) {
43
+ const normalized = normalizeError(error, now);
44
+ const code = normalized.code.toLowerCase();
45
+ if (code.includes("missing-artifact") || code.includes("artifact-path"))
46
+ return "missing-artifact";
47
+ if (code.includes("missing-required-evidence") || code.includes("missing-evidence"))
48
+ return "missing-evidence";
49
+ if (code.includes("verifier") || context.stageId === "verify" || context.source === "verifier")
50
+ return "verifier-failure";
51
+ if (code.includes("illegal-transition") || code.includes("state-transition"))
52
+ return "state-transition";
53
+ if (code.includes("contract") || code.includes("unexpected-node") || context.contractId)
54
+ return "contract-violation";
55
+ if (code.startsWith("sandbox-"))
56
+ return "sandbox-policy";
57
+ if (code.includes("parse") || code.includes("json"))
58
+ return "parse-error";
59
+ if (code.includes("pipeline"))
60
+ return "pipeline-failure";
61
+ if (normalized.code === "runtime-error")
62
+ return "runtime-error";
63
+ return "unknown";
64
+ }
65
+ function severityFor(classification, error) {
66
+ if (classification === "verifier-failure" || classification === "contract-violation")
67
+ return "high";
68
+ if (classification === "sandbox-policy")
69
+ return "medium";
70
+ if (classification === "state-transition" || classification === "missing-evidence")
71
+ return "medium";
72
+ if (classification === "missing-artifact" || classification === "parse-error" || classification === "pipeline-failure") {
73
+ return error.retryable ? "medium" : "low";
74
+ }
75
+ return "low";
76
+ }
77
+ function sourceFor(classification) {
78
+ if (classification === "contract-violation")
79
+ return "contract";
80
+ if (classification === "verifier-failure" || classification === "missing-evidence")
81
+ return "verifier";
82
+ if (classification === "pipeline-failure")
83
+ return "pipeline-runner";
84
+ if (classification === "sandbox-policy")
85
+ return "contract";
86
+ return "manual";
87
+ }
88
+ /** Feedback dedup key: joined with `` (runId, code, message, nodeId,
89
+ * stageId, contractId, path). */
90
+ function feedbackKey(value) {
91
+ return [value.runId || "", value.code || "", value.message || "", value.nodeId || "", value.stageId || "", value.contractId || "", value.path || ""].join("");
92
+ }
93
+ function compactMetadata(metadata) {
94
+ const compacted = {};
95
+ for (const [key, value] of Object.entries(metadata))
96
+ if (value !== undefined)
97
+ compacted[key] = value;
98
+ return Object.keys(compacted).length ? compacted : undefined;
99
+ }
100
+ /** Deterministic feedback id: position in the run's append-only feedback
101
+ * log (1-based), qualified by classification. */
102
+ function formatFeedbackId(classification, seq) {
103
+ return `feedback-${classification}-${String(seq).padStart(4, "0")}`;
104
+ }
105
+ /** Find an existing UNRESOLVED record matching the same
106
+ * {code,message,nodeId,stageId,contractId,path} — recordFeedback's dedup
107
+ * rule. */
108
+ function findExistingFeedback(existing, error, nodeId, stageId, contractId, path) {
109
+ return existing.find((record) => record.status !== "resolved" &&
110
+ record.code === error.code &&
111
+ record.message === error.message &&
112
+ record.nodeId === nodeId &&
113
+ record.stageId === stageId &&
114
+ record.contractId === contractId &&
115
+ record.path === path);
116
+ }
117
+ /** Build a new (not-yet-persisted) feedback record. `existingCount` is
118
+ * `(run.feedback || []).length` BEFORE this record is appended. */
119
+ function buildFeedbackRecord(runId, input, existingCount, now) {
120
+ const error = normalizeError(input.error, now);
121
+ const nodeId = input.nodeId || error.nodeId;
122
+ const classification = classifyFeedback(error, { source: input.source, stageId: input.stageId, contractId: input.contractId, metadata: input.metadata }, now);
123
+ return {
124
+ schemaVersion: exports.ERROR_FEEDBACK_SCHEMA_VERSION,
125
+ id: formatFeedbackId(classification, existingCount + 1),
126
+ runId,
127
+ createdAt: now,
128
+ updatedAt: now,
129
+ status: "open",
130
+ severity: severityFor(classification, error),
131
+ classification,
132
+ source: input.source || sourceFor(classification),
133
+ code: error.code,
134
+ message: error.message,
135
+ nodeId,
136
+ stageId: input.stageId,
137
+ contractId: input.contractId,
138
+ taskId: input.taskId,
139
+ path: input.path || error.path,
140
+ retryable: input.retryable ?? error.retryable ?? false,
141
+ evidence: input.evidence || [],
142
+ artifacts: input.artifacts || [],
143
+ metadata: compactMetadata({ ...input.metadata, details: input.metadata?.details || error.details }),
144
+ };
145
+ }
146
+ function summarizeFeedback(records) {
147
+ return {
148
+ total: records.length,
149
+ byStatus: countBy(records, (r) => r.status),
150
+ bySeverity: countBy(records, (r) => r.severity),
151
+ byClassification: countBy(records, (r) => r.classification),
152
+ };
153
+ }
154
+ function countBy(values, key) {
155
+ const counts = {};
156
+ for (const value of values) {
157
+ const bucket = key(value);
158
+ counts[bucket] = (counts[bucket] || 0) + 1;
159
+ }
160
+ return counts;
161
+ }
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ // core/pipeline/loop-expansion.ts — predicate registry, maxLoopExpansion,
3
+ // maybeExpandLoop's decision half (round clone, stop reasons).
4
+ //
5
+ // MILESTONE 6+7 (combined). Byte-exact port of the old build's
6
+ // src/loop-expansion.ts (predicate registry + maxLoopExpansion are
7
+ // already pure there) plus the DECISION half of
8
+ // src/orchestrator/lifecycle-operations.ts's maybeExpandLoop (materializing
9
+ // the cloned phase/tasks + loop-control node is the caller's job in
10
+ // shell/, since it needs writeTaskFiles + the plan pipeline stage).
11
+ //
12
+ // Evidence: SPEC/pipeline-run.md "loop() expansion — src/loop-
13
+ // expansion.ts + maybeExpandLoop".
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.registerLoopPredicate = registerLoopPredicate;
16
+ exports.getLoopPredicate = getLoopPredicate;
17
+ exports.hasLoopPredicate = hasLoopPredicate;
18
+ exports.maxLoopExpansion = maxLoopExpansion;
19
+ exports.evaluateLoopStop = evaluateLoopStop;
20
+ exports.cloneLoopRoundTasks = cloneLoopRoundTasks;
21
+ exports.loopControlNodeId = loopControlNodeId;
22
+ const REGISTRY = new Map();
23
+ function registerLoopPredicate(name, fn) {
24
+ REGISTRY.set(name, fn);
25
+ }
26
+ function getLoopPredicate(name) {
27
+ return REGISTRY.get(name);
28
+ }
29
+ function hasLoopPredicate(name) {
30
+ return REGISTRY.has(name);
31
+ }
32
+ registerLoopPredicate("no-new-findings", (ctx) => {
33
+ const empty = ctx.roundResults.every((r) => !r || !Array.isArray(r.findings) || r.findings.length === 0);
34
+ return empty
35
+ ? { done: true, reason: "no-new-findings: the latest round produced no findings" }
36
+ : { done: false, reason: "no-new-findings: the latest round still has findings" };
37
+ });
38
+ registerLoopPredicate("single-round", () => ({ done: true, reason: "single-round: stop after one round" }));
39
+ /** Static worst-case number of EXTRA tasks a fully-expanded run could
40
+ * mint, derived purely from the workflow declaration. Zero with no loop
41
+ * phases. */
42
+ function maxLoopExpansion(run) {
43
+ let extra = 0;
44
+ for (const phase of run.phases) {
45
+ const loop = phase.loop;
46
+ if (loop && typeof loop.maxRounds === "number" && loop.maxRounds > 1) {
47
+ extra += (loop.maxRounds - 1) * phase.taskIds.length;
48
+ }
49
+ }
50
+ return extra;
51
+ }
52
+ /** Decide whether a just-completed loop round should stop, given the
53
+ * origin phase's loop spec and the round's recorded results. Pure —
54
+ * `until:{kind:"predicate"}` looks up the registry; an unregistered ref
55
+ * stops fail-closed (never throws). */
56
+ function evaluateLoopStop(origin, round, ctx) {
57
+ const loop = origin.loop;
58
+ if (!loop)
59
+ return { done: true, atCap: false, reason: "no loop spec" };
60
+ const maxRounds = loop.maxRounds;
61
+ let decision;
62
+ if (loop.until.kind === "budget-target") {
63
+ const target = loop.until.target || 0;
64
+ const spent = ctx.usageTotals.totalTokens;
65
+ decision = spent >= target
66
+ ? { done: true, reason: `budget-target: ${spent}/${target} recorded tokens` }
67
+ : { done: false, reason: `budget-target: ${spent}/${target} recorded tokens` };
68
+ }
69
+ else {
70
+ const ref = loop.until.ref || "";
71
+ const predicate = getLoopPredicate(ref);
72
+ decision = predicate ? predicate(ctx) : { done: true, reason: `loop predicate "${ref}" not registered — stopping fail-closed` };
73
+ }
74
+ const atCap = round >= maxRounds;
75
+ return { done: decision.done || atCap, atCap, reason: decision.reason };
76
+ }
77
+ /** Clone round-1 template tasks into `<base-id>@r<nextRound>` for a fresh
78
+ * phase `id: <origin-id>@r<nextRound>`, `name: <origin name> (round
79
+ * <n>)`. Pure data transform; the caller writes task files + runs the
80
+ * plan pipeline stage per new task. */
81
+ function cloneLoopRoundTasks(origin, templateTasks, nextRound) {
82
+ // Round-suffix strips any prior @rN so round 3 clones round 1's base id,
83
+ // never round 2's suffixed id — byte-exact to the old build's
84
+ // `t.id.replace(/@r\d+$/, "")` in maybeExpandLoop.
85
+ const suffix = `@r${nextRound}`;
86
+ const nextPhaseName = `${origin.name} (round ${nextRound})`;
87
+ const tasks = templateTasks.map((task) => ({
88
+ ...task,
89
+ id: `${task.id.replace(/@r\d+$/, "")}${suffix}`,
90
+ // The cloned tasks belong to the NEW round phase, not round 1 — the old
91
+ // build set `phase: nextPhaseName` on every cloned task so progress views
92
+ // and previousPhaseResultsDigest resolve to the right phase.
93
+ phase: nextPhaseName,
94
+ status: "pending",
95
+ loopStage: "interpret",
96
+ loopRound: nextRound,
97
+ dispatchId: undefined,
98
+ dispatchedAt: undefined,
99
+ startedAt: undefined,
100
+ completedAt: undefined,
101
+ result: undefined,
102
+ stateNodeId: undefined,
103
+ resultNodeId: undefined,
104
+ verifierNodeId: undefined,
105
+ workerId: undefined,
106
+ workerManifestPath: undefined,
107
+ taskPath: "",
108
+ resultPath: "",
109
+ }));
110
+ const phase = {
111
+ id: `${origin.id}${suffix}`,
112
+ name: nextPhaseName,
113
+ status: "pending",
114
+ taskIds: tasks.map((t) => t.id),
115
+ ...(origin.mode ? { mode: origin.mode } : {}),
116
+ loopOrigin: origin.id,
117
+ loopRound: nextRound,
118
+ };
119
+ return { phase, tasks };
120
+ }
121
+ /** Loop-control node id: `<run-id>:loop-control:<origin-phase-id>:r<round>`. */
122
+ function loopControlNodeId(runId, originPhaseId, round) {
123
+ return `${runId}:loop-control:${originPhaseId}:r${round}`;
124
+ }
@@ -1,10 +1,16 @@
1
1
  "use strict";
2
+ // core/pipeline/result-normalize.ts — normalizeResultEnvelope,
3
+ // isEmptyCapture.
4
+ //
5
+ // MILESTONE 6+7 (combined). Byte-exact port of the old build's
6
+ // src/result-normalize.ts. Pure (no fs/clock/random).
7
+ //
8
+ // Evidence: SPEC/pipeline-run.md "Result ingest — src/result-normalize.ts".
2
9
  Object.defineProperty(exports, "__esModule", { value: true });
3
10
  exports.normalizeResultEnvelope = normalizeResultEnvelope;
4
11
  exports.firstNonEmptyLine = firstNonEmptyLine;
5
12
  exports.isEmptyCapture = isEmptyCapture;
6
- const evidence_grounding_1 = require("./evidence-grounding");
7
- // Alternative keys a capable model naturally uses for the findings array.
13
+ const evidence_grounding_1 = require("../trust/evidence-grounding");
8
14
  const FINDING_ARRAY_KEYS = [
9
15
  "findings",
10
16
  "candidate_risks",
@@ -16,22 +22,15 @@ const FINDING_ARRAY_KEYS = [
16
22
  "topRisks",
17
23
  "issues",
18
24
  "problems",
19
- "concerns"
25
+ "concerns",
20
26
  ];
21
- // Per-finding keys that may carry evidence locators.
22
27
  const FINDING_EVIDENCE_KEYS = ["evidence", "evidence_paths", "evidencePaths", "locators", "refs", "files", "location", "locations", "path", "paths", "line", "lines", "where"];
23
28
  const CLASSIFICATIONS = new Set(["real", "conditional", "non-issue", "unknown"]);
24
- // Evidence caps (v0.1.42): the v0.1.41 experiment hit a single flat cap of 64 on
25
- // the top-level union, silently dropping locators on richly-cited workers. The
26
- // top-level array now holds the whole run's harvested locators (high cap), while
27
- // each finding keeps a focused, readable set.
28
29
  const TOP_LEVEL_EVIDENCE_CAP = 256;
29
30
  const PER_FINDING_EVIDENCE_CAP = 32;
30
31
  function normalizeResultEnvelope(markdown) {
31
32
  const match = markdown.match(/```cw:result\s*([\s\S]*?)```/);
32
33
  if (!match) {
33
- // No fence: still DERIVE grounded evidence from the prose so a fence-less but
34
- // well-cited report is not silently captured as empty.
35
34
  return { summary: firstNonEmptyLine(markdown), findings: [], evidence: harvestGrounded([markdown], TOP_LEVEL_EVIDENCE_CAP) };
36
35
  }
37
36
  let parsed;
@@ -43,27 +42,17 @@ function normalizeResultEnvelope(markdown) {
43
42
  throw new Error(`Invalid cw:result JSON: ${message}`);
44
43
  }
45
44
  const summary = pickString(parsed, ["summary", "short_answer", "shortAnswer", "verdict", "answer", "conclusion"]) || firstNonEmptyLine(markdown);
46
- // Findings: prefer the canonical `findings` array, else the first present
47
- // alternative array. EVERY finding is then normalized (id guaranteed, severity/
48
- // classification coerced, evidence derived) so a capable agent that omits an `id`
49
- // or uses `high`/`candidate_risks` is captured rather than fail-closed-rejected.
50
45
  const canonicalFindings = Array.isArray(parsed.findings) ? parsed.findings : undefined;
51
46
  const rawFindings = (canonicalFindings && canonicalFindings.length ? canonicalFindings : extractFindingsRaw(parsed)) ?? [];
52
47
  const findings = rawFindings.map((item, index) => normalizeFinding(item, index));
53
- // Evidence: canonical non-empty wins; else CW derives grounded locators itself
54
- // from the entire envelope JSON, the findings, and the prose.
55
48
  const canonicalEvidence = Array.isArray(parsed.evidence) ? parsed.evidence : undefined;
56
- const evidence = canonicalEvidence && canonicalEvidence.length
57
- ? canonicalEvidence
58
- : harvestGrounded([parsed, findings, stripFence(markdown)], TOP_LEVEL_EVIDENCE_CAP);
49
+ const evidence = canonicalEvidence && canonicalEvidence.length ? canonicalEvidence : harvestGrounded([parsed, findings, stripFence(markdown)], TOP_LEVEL_EVIDENCE_CAP);
59
50
  return { summary, findings, evidence };
60
51
  }
61
- /** The first recognized alternative findings array (raw items, normalized by the
62
- * caller). Returns undefined when no alternative findings source is present. */
63
52
  function extractFindingsRaw(parsed) {
64
53
  for (const key of FINDING_ARRAY_KEYS) {
65
54
  if (key === "findings")
66
- continue; // canonical handled by the caller
55
+ continue;
67
56
  const raw = parsed[key];
68
57
  if (Array.isArray(raw) && raw.length)
69
58
  return raw;
@@ -76,19 +65,16 @@ function normalizeFinding(item, index) {
76
65
  const id = pickString(obj, ["id", "key", "name", "title"]) || `finding-${index + 1}`;
77
66
  const classification = normalizeClassification(pickString(obj, ["classification", "class", "kind", "status"]));
78
67
  const severity = normalizeSeverity(pickString(obj, ["severity", "priority", "level", "rank", "rating"]));
79
- // Evidence: explicit per-finding evidence keys first, else derive grounded
80
- // locators from the whole finding object (so `{title, detail, file, line}` works).
81
68
  const explicit = harvestGrounded(FINDING_EVIDENCE_KEYS.map((k) => obj[k]).filter((v) => v !== undefined), PER_FINDING_EVIDENCE_CAP);
82
69
  const evidence = explicit.length ? explicit : harvestGrounded([obj], PER_FINDING_EVIDENCE_CAP);
83
- return { id, classification, severity, evidence };
70
+ return { id, classification: classification ?? "unknown", severity, evidence };
84
71
  }
85
- // A bare string finding ("P1 — revoke stays live (app.ts:1644)").
86
72
  const text = String(item ?? "");
87
73
  return {
88
74
  id: `finding-${index + 1}`,
89
75
  classification: "unknown",
90
76
  severity: normalizeSeverity(text),
91
- evidence: harvestGrounded([text], PER_FINDING_EVIDENCE_CAP)
77
+ evidence: harvestGrounded([text], PER_FINDING_EVIDENCE_CAP),
92
78
  };
93
79
  }
94
80
  function normalizeClassification(value) {
@@ -120,9 +106,6 @@ function normalizeSeverity(value) {
120
106
  return "P3";
121
107
  return "none";
122
108
  }
123
- /** Collect GROUNDED locators (path:line / URL / namespace:value) from any mix of
124
- * JSON values and prose strings. This is how CW derives evidence itself instead
125
- * of trusting the agent's `evidence` key. Deterministic: deduped + sorted + capped. */
126
109
  function harvestGrounded(values, cap = TOP_LEVEL_EVIDENCE_CAP) {
127
110
  const acc = [];
128
111
  for (const v of values)
@@ -136,9 +119,6 @@ function collect(value, acc) {
136
119
  return;
137
120
  if (typeof value === "string") {
138
121
  const v = value.trim();
139
- // A single whitespace-free token that is itself a grounded locator is kept
140
- // verbatim; anything with spaces is PROSE — extract the locator tokens from it
141
- // (a sentence containing a "/" must not be captured whole as one "locator").
142
122
  if (v && !/\s/.test(v) && (0, evidence_grounding_1.isGroundedEvidence)(v))
143
123
  acc.push(v);
144
124
  else
@@ -156,8 +136,6 @@ function collect(value, acc) {
156
136
  collect(item, acc);
157
137
  }
158
138
  }
159
- // Pull file:line / file.ext / URL / `backtick` tokens out of a prose string, then
160
- // keep only the grounded ones. Backtick spans are a strong signal for code refs.
161
139
  const LOCATOR_RE = /`([^`]+)`|([A-Za-z0-9_@./-]+\.[A-Za-z]{1,8}(?::\d+(?:-\d+)?)?)|(https?:\/\/[^\s)]+)/g;
162
140
  function locatorsFromText(text) {
163
141
  const out = [];
@@ -187,8 +165,7 @@ function firstNonEmptyLine(markdown) {
187
165
  .map((line) => line.trim())
188
166
  .find((line) => line && !line.startsWith("#") && !line.startsWith("```")) || "");
189
167
  }
190
- /** True when an accepted result captured no structured signal at all. Drives the
191
- * "warn, don't silently pass" surface in the record paths. */
168
+ /** True when an accepted result captured no structured signal at all. */
192
169
  function isEmptyCapture(envelope) {
193
170
  return (envelope.findings?.length || 0) === 0 && (envelope.evidence?.length || 0) === 0;
194
171
  }