cool-workflow 0.1.98 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (306) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +11 -2
  4. package/apps/architecture-review/app.json +1 -1
  5. package/apps/architecture-review-fast/app.json +1 -1
  6. package/apps/end-to-end-golden-path/app.json +1 -1
  7. package/apps/pr-review-fix-ci/app.json +1 -1
  8. package/apps/release-cut/app.json +1 -1
  9. package/apps/research-synthesis/app.json +1 -1
  10. package/dist/cli/dispatch.js +236 -0
  11. package/dist/cli/entry.js +120 -0
  12. package/dist/cli/io.js +21 -4
  13. package/dist/cli/parseargv.js +157 -0
  14. package/dist/cli.js +6 -33
  15. package/dist/core/capability-table.js +3534 -0
  16. package/dist/core/format/help.js +314 -0
  17. package/dist/{state-explosion/format.js → core/format/state-explosion-text.js} +10 -1
  18. package/dist/core/hash.js +137 -0
  19. package/dist/core/multi-agent/candidate-scoring.js +219 -0
  20. package/dist/core/multi-agent/collaboration.js +481 -0
  21. package/dist/core/multi-agent/coordinator.js +515 -0
  22. package/dist/core/multi-agent/eval-replay.js +306 -0
  23. package/dist/core/multi-agent/runtime.js +929 -0
  24. package/dist/core/multi-agent/topology.js +298 -0
  25. package/dist/core/multi-agent/trust-policy.js +197 -0
  26. package/dist/core/pipeline/commit-gate.js +320 -0
  27. package/dist/{pipeline-contract.js → core/pipeline/contract.js} +24 -37
  28. package/dist/core/pipeline/dispatch.js +103 -0
  29. package/dist/core/pipeline/drive-decide.js +227 -0
  30. package/dist/core/pipeline/error-feedback.js +161 -0
  31. package/dist/core/pipeline/loop-expansion.js +124 -0
  32. package/dist/{result-normalize.js → core/pipeline/result-normalize.js} +14 -37
  33. package/dist/core/pipeline/runner.js +230 -0
  34. package/dist/{contract-migration.js → core/state/contract-migration.js} +68 -92
  35. package/dist/{state-migrations.js → core/state/migrations.js} +103 -96
  36. package/dist/core/state/node-projection.js +95 -0
  37. package/dist/core/state/node-snapshot.js +230 -0
  38. package/dist/core/state/run-paths.js +93 -0
  39. package/dist/{schema-validate.js → core/state/schema-validate.js} +35 -28
  40. package/dist/core/state/schema.js +50 -0
  41. package/dist/core/state/state-explosion/digest.js +243 -0
  42. package/dist/core/state/state-explosion/graph.js +527 -0
  43. package/dist/{state-explosion → core/state/state-explosion}/helpers.js +34 -3
  44. package/dist/core/state/state-explosion/report.js +187 -0
  45. package/dist/{state-explosion → core/state/state-explosion}/size.js +25 -7
  46. package/dist/{state-node.js → core/state/state-node.js} +90 -113
  47. package/dist/core/state/types.js +19 -0
  48. package/dist/{validation.js → core/state/validation.js} +64 -131
  49. package/dist/core/trust/evidence-grounding.js +134 -0
  50. package/dist/core/trust/ledger.js +199 -0
  51. package/dist/{telemetry-attestation.js → core/trust/telemetry-attestation.js} +94 -68
  52. package/dist/core/trust/telemetry-ledger.js +127 -0
  53. package/dist/core/types.js +20 -0
  54. package/dist/core/version.js +16 -0
  55. package/dist/{workflow-app-framework.js → core/workflow-apps/app-schema.js} +337 -426
  56. package/dist/mcp/dispatch.js +104 -0
  57. package/dist/mcp/server.js +144 -0
  58. package/dist/mcp-server.js +6 -84
  59. package/dist/{agent-config.js → shell/agent-config.js} +118 -71
  60. package/dist/shell/app-run-cli.js +88 -0
  61. package/dist/shell/audit-cli.js +259 -0
  62. package/dist/shell/audit-provenance.js +83 -0
  63. package/dist/shell/candidate-scoring-io.js +459 -0
  64. package/dist/shell/collaboration-io.js +264 -0
  65. package/dist/shell/commit-summary.js +104 -0
  66. package/dist/shell/commit.js +290 -0
  67. package/dist/shell/coordinator-io.js +476 -0
  68. package/dist/shell/demo-cli.js +19 -0
  69. package/dist/shell/dispatch.js +162 -0
  70. package/dist/{doctor.js → shell/doctor.js} +123 -56
  71. package/dist/shell/drive.js +873 -0
  72. package/dist/shell/error-feedback-io.js +305 -0
  73. package/dist/shell/eval-io.js +473 -0
  74. package/dist/{multi-agent-eval/format.js → shell/eval-text.js} +78 -49
  75. package/dist/{evidence-reasoning.js → shell/evidence-reasoning.js} +124 -255
  76. package/dist/shell/exec-backend-cli.js +88 -0
  77. package/dist/shell/execution-backend/agent.js +475 -0
  78. package/dist/shell/execution-backend/ci.js +15 -0
  79. package/dist/shell/execution-backend/container.js +69 -0
  80. package/dist/shell/execution-backend/envelopes.js +55 -0
  81. package/dist/shell/execution-backend/local.js +113 -0
  82. package/dist/shell/execution-backend/probes.js +175 -0
  83. package/dist/shell/execution-backend/registry.js +402 -0
  84. package/dist/shell/execution-backend/remote.js +128 -0
  85. package/dist/shell/execution-backend/types.js +11 -0
  86. package/dist/shell/feedback-cli.js +81 -0
  87. package/dist/shell/feedback-operations.js +48 -0
  88. package/dist/shell/fs-atomic.js +276 -0
  89. package/dist/shell/harness.js +98 -0
  90. package/dist/shell/ledger-cli.js +212 -0
  91. package/dist/shell/ledger-io.js +169 -0
  92. package/dist/shell/man-cli.js +89 -0
  93. package/dist/shell/metrics-cli.js +98 -0
  94. package/dist/shell/multi-agent-cli.js +1002 -0
  95. package/dist/shell/multi-agent-host.js +563 -0
  96. package/dist/shell/multi-agent-io.js +387 -0
  97. package/dist/{multi-agent-operator-ux.js → shell/multi-agent-operator-ux.js} +234 -191
  98. package/dist/shell/node-store.js +124 -0
  99. package/dist/{observability/format.js → shell/observability-format.js} +7 -1
  100. package/dist/{observability/intake.js → shell/observability-intake.js} +79 -56
  101. package/dist/{observability.js → shell/observability.js} +159 -332
  102. package/dist/{onramp.js → shell/onramp.js} +11 -0
  103. package/dist/{operator-ux/format.js → shell/operator-ux-text.js} +250 -239
  104. package/dist/shell/operator-ux.js +431 -0
  105. package/dist/shell/orchestrator.js +231 -0
  106. package/dist/shell/pipeline-cli.js +667 -0
  107. package/dist/shell/pipeline.js +217 -0
  108. package/dist/shell/reclamation-io.js +1366 -0
  109. package/dist/shell/registry-cli.js +329 -0
  110. package/dist/{remote-source.js → shell/remote-source.js} +2 -2
  111. package/dist/shell/report-cli.js +101 -0
  112. package/dist/shell/report-view-cli.js +117 -0
  113. package/dist/{orchestrator → shell}/report.js +289 -282
  114. package/dist/shell/reporter.js +62 -0
  115. package/dist/shell/run-export-cli.js +106 -0
  116. package/dist/shell/run-export.js +680 -0
  117. package/dist/shell/run-registry-io.js +1014 -0
  118. package/dist/shell/run-store.js +164 -0
  119. package/dist/{sandbox-profile.js → shell/sandbox-profile.js} +134 -95
  120. package/dist/{scheduler.js → shell/scheduler-io.js} +248 -48
  121. package/dist/shell/scheduling-io.js +311 -0
  122. package/dist/shell/state-cli.js +181 -0
  123. package/dist/shell/state-explosion-cli.js +197 -0
  124. package/dist/shell/telemetry-cli.js +85 -0
  125. package/dist/{telemetry-demo.js → shell/telemetry-demo.js} +149 -119
  126. package/dist/shell/telemetry-ledger-io.js +132 -0
  127. package/dist/{term.js → shell/term.js} +36 -46
  128. package/dist/shell/topology-io.js +361 -0
  129. package/dist/shell/trust-audit.js +471 -0
  130. package/dist/{multi-agent-trust.js → shell/trust-policy-io.js} +67 -205
  131. package/dist/shell/verifier.js +48 -0
  132. package/dist/shell/workbench-host.js +250 -0
  133. package/dist/shell/workbench-text.js +18 -0
  134. package/dist/shell/workbench.js +175 -0
  135. package/dist/shell/worker-cli.js +124 -0
  136. package/dist/shell/worker-isolation.js +852 -0
  137. package/dist/shell/workflow-app-loader.js +650 -0
  138. package/docs/agent-delegation-drive.7.md +2 -0
  139. package/docs/cli-mcp-parity.7.md +280 -219
  140. package/docs/contract-migration-tooling.7.md +2 -0
  141. package/docs/control-plane-scheduling.7.md +2 -0
  142. package/docs/durable-state-and-locking.7.md +2 -0
  143. package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
  144. package/docs/execution-backends.7.md +2 -0
  145. package/docs/multi-agent-cli-mcp-surface.7.md +2 -0
  146. package/docs/multi-agent-eval-replay-harness.7.md +2 -0
  147. package/docs/multi-agent-operator-ux.7.md +2 -0
  148. package/docs/node-snapshot-diff-replay.7.md +2 -0
  149. package/docs/observability-cost-accounting.7.md +2 -0
  150. package/docs/project-index.md +132 -71
  151. package/docs/real-execution-backends.7.md +2 -0
  152. package/docs/release-and-migration.7.md +2 -0
  153. package/docs/release-tooling.7.md +2 -0
  154. package/docs/run-registry-control-plane.7.md +2 -0
  155. package/docs/run-retention-reclamation.7.md +23 -0
  156. package/docs/state-explosion-management.7.md +2 -0
  157. package/docs/team-collaboration.7.md +2 -0
  158. package/docs/web-desktop-workbench.7.md +2 -0
  159. package/manifest/plugin.manifest.json +1 -1
  160. package/manifest/source-context-profiles.json +9 -13
  161. package/package.json +1 -1
  162. package/scripts/agents/cw-attest-wrap.js +2 -2
  163. package/scripts/bump-version.js +4 -3
  164. package/scripts/canonical-apps.js +4 -4
  165. package/scripts/dogfood-architecture-review.js +2 -3
  166. package/scripts/dogfood-release.js +3 -3
  167. package/scripts/gen-parity-doc.js +15 -2
  168. package/scripts/golden-path.js +4 -4
  169. package/scripts/onramp-check.js +1 -1
  170. package/scripts/parity-check.js +38 -21
  171. package/scripts/release-flow.js +2 -2
  172. package/scripts/sync-project-index.js +51 -27
  173. package/scripts/validate-run-state-schema.js +2 -2
  174. package/scripts/version-sync-check.js +30 -30
  175. package/dist/candidate-scoring.js +0 -729
  176. package/dist/capability-core.js +0 -1189
  177. package/dist/capability-registry.js +0 -885
  178. package/dist/cli/command-surface.js +0 -494
  179. package/dist/cli/format.js +0 -56
  180. package/dist/cli/handlers/audit.js +0 -82
  181. package/dist/cli/handlers/blackboard.js +0 -81
  182. package/dist/cli/handlers/candidate.js +0 -40
  183. package/dist/cli/handlers/clones.js +0 -34
  184. package/dist/cli/handlers/collaboration.js +0 -61
  185. package/dist/cli/handlers/eval.js +0 -40
  186. package/dist/cli/handlers/ledger.js +0 -169
  187. package/dist/cli/handlers/maintenance.js +0 -107
  188. package/dist/cli/handlers/multi-agent.js +0 -165
  189. package/dist/cli/handlers/node.js +0 -41
  190. package/dist/cli/handlers/operational.js +0 -155
  191. package/dist/cli/handlers/operator.js +0 -146
  192. package/dist/cli/handlers/registry.js +0 -68
  193. package/dist/cli/handlers/run.js +0 -153
  194. package/dist/cli/handlers/scheduling.js +0 -132
  195. package/dist/cli/handlers/workbench.js +0 -41
  196. package/dist/cli/handlers/worker.js +0 -45
  197. package/dist/cli/run-summary.js +0 -45
  198. package/dist/clones.js +0 -162
  199. package/dist/collaboration.js +0 -726
  200. package/dist/commit.js +0 -592
  201. package/dist/compare.js +0 -18
  202. package/dist/coordinator/classify.js +0 -45
  203. package/dist/coordinator/paths.js +0 -42
  204. package/dist/coordinator/util.js +0 -126
  205. package/dist/coordinator.js +0 -990
  206. package/dist/daemon.js +0 -44
  207. package/dist/dispatch.js +0 -250
  208. package/dist/drive.js +0 -864
  209. package/dist/error-feedback.js +0 -419
  210. package/dist/evidence-grounding.js +0 -184
  211. package/dist/execution-backend/agent.js +0 -354
  212. package/dist/execution-backend/probes.js +0 -112
  213. package/dist/execution-backend/util.js +0 -47
  214. package/dist/execution-backend.js +0 -961
  215. package/dist/gates.js +0 -48
  216. package/dist/harness.js +0 -61
  217. package/dist/ledger.js +0 -313
  218. package/dist/loop-expansion.js +0 -60
  219. package/dist/mcp/tool-call.js +0 -470
  220. package/dist/mcp/tool-definitions.js +0 -1066
  221. package/dist/mcp-surface.js +0 -30
  222. package/dist/multi-agent/graph.js +0 -84
  223. package/dist/multi-agent/helpers.js +0 -141
  224. package/dist/multi-agent/ids.js +0 -20
  225. package/dist/multi-agent/paths.js +0 -22
  226. package/dist/multi-agent-eval/normalize.js +0 -51
  227. package/dist/multi-agent-eval.js +0 -678
  228. package/dist/multi-agent-host.js +0 -777
  229. package/dist/multi-agent.js +0 -984
  230. package/dist/node-projection.js +0 -59
  231. package/dist/node-snapshot.js +0 -260
  232. package/dist/operator-ux.js +0 -631
  233. package/dist/orchestrator/app-operations.js +0 -211
  234. package/dist/orchestrator/audit-operations.js +0 -182
  235. package/dist/orchestrator/candidate-operations.js +0 -117
  236. package/dist/orchestrator/cli-options.js +0 -294
  237. package/dist/orchestrator/collaboration-operations.js +0 -86
  238. package/dist/orchestrator/feedback-operations.js +0 -81
  239. package/dist/orchestrator/host-operations.js +0 -78
  240. package/dist/orchestrator/lifecycle-operations.js +0 -650
  241. package/dist/orchestrator/migration-operations.js +0 -44
  242. package/dist/orchestrator/multi-agent-operations.js +0 -362
  243. package/dist/orchestrator/topology-operations.js +0 -84
  244. package/dist/orchestrator.js +0 -925
  245. package/dist/pipeline-runner.js +0 -285
  246. package/dist/reclamation/hash.js +0 -72
  247. package/dist/reclamation.js +0 -812
  248. package/dist/reporter.js +0 -67
  249. package/dist/run-export.js +0 -815
  250. package/dist/run-registry/derive.js +0 -175
  251. package/dist/run-registry/format.js +0 -124
  252. package/dist/run-registry/gc.js +0 -251
  253. package/dist/run-registry/policy.js +0 -16
  254. package/dist/run-registry/queue.js +0 -115
  255. package/dist/run-registry.js +0 -850
  256. package/dist/run-state-schema.js +0 -68
  257. package/dist/scheduling.js +0 -184
  258. package/dist/state-explosion.js +0 -1014
  259. package/dist/state.js +0 -367
  260. package/dist/telemetry-ledger.js +0 -196
  261. package/dist/topology.js +0 -565
  262. package/dist/triggers.js +0 -184
  263. package/dist/trust-audit.js +0 -644
  264. package/dist/types/blackboard.js +0 -2
  265. package/dist/types/candidate.js +0 -2
  266. package/dist/types/collaboration.js +0 -2
  267. package/dist/types/core.js +0 -2
  268. package/dist/types/drive.js +0 -10
  269. package/dist/types/error-feedback.js +0 -2
  270. package/dist/types/evidence-reasoning.js +0 -2
  271. package/dist/types/execution-backend.js +0 -2
  272. package/dist/types/multi-agent.js +0 -2
  273. package/dist/types/observability.js +0 -2
  274. package/dist/types/pipeline.js +0 -2
  275. package/dist/types/reclamation.js +0 -8
  276. package/dist/types/report-bundle.js +0 -6
  277. package/dist/types/result.js +0 -2
  278. package/dist/types/run-registry.js +0 -2
  279. package/dist/types/run.js +0 -2
  280. package/dist/types/sandbox.js +0 -2
  281. package/dist/types/schedule.js +0 -2
  282. package/dist/types/state-node.js +0 -2
  283. package/dist/types/topology.js +0 -2
  284. package/dist/types/trust.js +0 -2
  285. package/dist/types/workbench.js +0 -2
  286. package/dist/types/worker.js +0 -2
  287. package/dist/types/workflow-app.js +0 -2
  288. package/dist/types.js +0 -44
  289. package/dist/util/fingerprint.js +0 -19
  290. package/dist/util/fingerprint.test.js +0 -27
  291. package/dist/verifier.js +0 -78
  292. package/dist/version.js +0 -8
  293. package/dist/workbench-host.js +0 -192
  294. package/dist/workbench.js +0 -192
  295. package/dist/worker-accept/acceptance.js +0 -114
  296. package/dist/worker-accept/blackboard-fanout.js +0 -80
  297. package/dist/worker-accept/blackboard-linkage.js +0 -19
  298. package/dist/worker-accept/context.js +0 -2
  299. package/dist/worker-accept/telemetry-ledger.js +0 -126
  300. package/dist/worker-accept/validation.js +0 -77
  301. package/dist/worker-accept/verifier-completion.js +0 -73
  302. package/dist/worker-isolation/helpers.js +0 -51
  303. package/dist/worker-isolation/paths.js +0 -46
  304. package/dist/worker-isolation.js +0 -656
  305. package/dist/workflow-api.js +0 -131
  306. /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 v2/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
  }