@jterrats/open-orchestra 0.5.5 → 1.0.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 (310) hide show
  1. package/AGENTS.md +9 -8
  2. package/CLAUDE.md +13 -11
  3. package/README.md +78 -11
  4. package/dist/assets/web-console.js +203 -36
  5. package/dist/automation-evidence.d.ts +23 -0
  6. package/dist/automation-evidence.js +97 -0
  7. package/dist/automation-evidence.js.map +1 -0
  8. package/dist/autonomous-run-state.d.ts +4 -1
  9. package/dist/autonomous-run-state.js +8 -2
  10. package/dist/autonomous-run-state.js.map +1 -1
  11. package/dist/autonomous-run-store.d.ts +3 -1
  12. package/dist/autonomous-run-store.js +9 -3
  13. package/dist/autonomous-run-store.js.map +1 -1
  14. package/dist/autonomous-workflow-constants.js +5 -1
  15. package/dist/autonomous-workflow-constants.js.map +1 -1
  16. package/dist/benchmark.d.ts +4 -1
  17. package/dist/benchmark.js +140 -19
  18. package/dist/benchmark.js.map +1 -1
  19. package/dist/cli.js +88 -2
  20. package/dist/cli.js.map +1 -1
  21. package/dist/collaboration-flows.js +5 -19
  22. package/dist/collaboration-flows.js.map +1 -1
  23. package/dist/collection-utils.d.ts +3 -0
  24. package/dist/collection-utils.js +10 -0
  25. package/dist/collection-utils.js.map +1 -0
  26. package/dist/command-manifest.d.ts +12 -1
  27. package/dist/command-manifest.js +218 -10
  28. package/dist/command-manifest.js.map +1 -1
  29. package/dist/commands.d.ts +14 -6
  30. package/dist/commands.js +78 -28
  31. package/dist/commands.js.map +1 -1
  32. package/dist/config-migrations.d.ts +24 -0
  33. package/dist/config-migrations.js +102 -0
  34. package/dist/config-migrations.js.map +1 -0
  35. package/dist/constants.d.ts +3 -0
  36. package/dist/constants.js +26 -0
  37. package/dist/constants.js.map +1 -1
  38. package/dist/cursor-canvas.d.ts +20 -0
  39. package/dist/cursor-canvas.js +119 -0
  40. package/dist/cursor-canvas.js.map +1 -0
  41. package/dist/dashboard-commands.d.ts +2 -0
  42. package/dist/dashboard-commands.js +14 -0
  43. package/dist/dashboard-commands.js.map +1 -0
  44. package/dist/defaults.d.ts +13 -0
  45. package/dist/defaults.js +13 -0
  46. package/dist/defaults.js.map +1 -1
  47. package/dist/delegation-decision.js +23 -8
  48. package/dist/delegation-decision.js.map +1 -1
  49. package/dist/delivery-commands.js +5 -0
  50. package/dist/delivery-commands.js.map +1 -1
  51. package/dist/delivery-dashboard-charts.d.ts +4 -0
  52. package/dist/delivery-dashboard-charts.js +156 -0
  53. package/dist/delivery-dashboard-charts.js.map +1 -0
  54. package/dist/delivery-dashboard-html.d.ts +2 -0
  55. package/dist/delivery-dashboard-html.js +115 -0
  56. package/dist/delivery-dashboard-html.js.map +1 -0
  57. package/dist/delivery-dashboard-types.d.ts +78 -0
  58. package/dist/delivery-dashboard-types.js +2 -0
  59. package/dist/delivery-dashboard-types.js.map +1 -0
  60. package/dist/delivery-dashboard.d.ts +8 -0
  61. package/dist/delivery-dashboard.js +124 -0
  62. package/dist/delivery-dashboard.js.map +1 -0
  63. package/dist/doc-sync.d.ts +25 -0
  64. package/dist/doc-sync.js +79 -0
  65. package/dist/doc-sync.js.map +1 -0
  66. package/dist/effort-classification.d.ts +7 -0
  67. package/dist/effort-classification.js +72 -0
  68. package/dist/effort-classification.js.map +1 -0
  69. package/dist/extension-commands.d.ts +3 -0
  70. package/dist/extension-commands.js +40 -0
  71. package/dist/extension-commands.js.map +1 -0
  72. package/dist/extensions.d.ts +22 -0
  73. package/dist/extensions.js +126 -0
  74. package/dist/extensions.js.map +1 -0
  75. package/dist/gemini-provider.d.ts +3 -6
  76. package/dist/gemini-provider.js +8 -17
  77. package/dist/gemini-provider.js.map +1 -1
  78. package/dist/github.d.ts +2 -0
  79. package/dist/github.js +15 -3
  80. package/dist/github.js.map +1 -1
  81. package/dist/health-checks.js +51 -0
  82. package/dist/health-checks.js.map +1 -1
  83. package/dist/lucid-story-map.d.ts +73 -0
  84. package/dist/lucid-story-map.js +112 -0
  85. package/dist/lucid-story-map.js.map +1 -0
  86. package/dist/mcp-integrations.d.ts +19 -0
  87. package/dist/mcp-integrations.js +58 -0
  88. package/dist/mcp-integrations.js.map +1 -0
  89. package/dist/mcp-tool-adapter.d.ts +21 -0
  90. package/dist/mcp-tool-adapter.js +56 -0
  91. package/dist/mcp-tool-adapter.js.map +1 -0
  92. package/dist/metrics-commands.js +47 -13
  93. package/dist/metrics-commands.js.map +1 -1
  94. package/dist/model-commands.d.ts +5 -0
  95. package/dist/model-commands.js +95 -1
  96. package/dist/model-commands.js.map +1 -1
  97. package/dist/model-providers.d.ts +5 -12
  98. package/dist/model-providers.js +30 -43
  99. package/dist/model-providers.js.map +1 -1
  100. package/dist/network-policy.d.ts +2 -0
  101. package/dist/network-policy.js +6 -0
  102. package/dist/network-policy.js.map +1 -0
  103. package/dist/ollama-provider.d.ts +3 -6
  104. package/dist/ollama-provider.js +7 -16
  105. package/dist/ollama-provider.js.map +1 -1
  106. package/dist/package-update-check.d.ts +19 -0
  107. package/dist/package-update-check.js +24 -0
  108. package/dist/package-update-check.js.map +1 -1
  109. package/dist/phase-executor.d.ts +1 -0
  110. package/dist/phase-executor.js +401 -9
  111. package/dist/phase-executor.js.map +1 -1
  112. package/dist/phase-playbooks.d.ts +18 -1
  113. package/dist/phase-playbooks.js +146 -2
  114. package/dist/phase-playbooks.js.map +1 -1
  115. package/dist/planning-commands.d.ts +1 -0
  116. package/dist/planning-commands.js +36 -36
  117. package/dist/planning-commands.js.map +1 -1
  118. package/dist/policy-commands.d.ts +2 -0
  119. package/dist/policy-commands.js +29 -0
  120. package/dist/policy-commands.js.map +1 -0
  121. package/dist/policy-defaults.d.ts +2 -0
  122. package/dist/policy-defaults.js +42 -0
  123. package/dist/policy-defaults.js.map +1 -0
  124. package/dist/policy.d.ts +20 -0
  125. package/dist/policy.js +155 -0
  126. package/dist/policy.js.map +1 -0
  127. package/dist/project-detection.js +9 -7
  128. package/dist/project-detection.js.map +1 -1
  129. package/dist/prompt-registry-update.d.ts +2 -0
  130. package/dist/prompt-registry-update.js +5 -1
  131. package/dist/prompt-registry-update.js.map +1 -1
  132. package/dist/prompt-registry-validation.d.ts +3 -0
  133. package/dist/prompt-registry-validation.js +61 -21
  134. package/dist/prompt-registry-validation.js.map +1 -1
  135. package/dist/provider-utils.d.ts +11 -0
  136. package/dist/provider-utils.js +14 -0
  137. package/dist/provider-utils.js.map +1 -1
  138. package/dist/qa-commands.d.ts +2 -0
  139. package/dist/qa-commands.js +18 -0
  140. package/dist/qa-commands.js.map +1 -0
  141. package/dist/qa-coverage.d.ts +24 -0
  142. package/dist/qa-coverage.js +189 -0
  143. package/dist/qa-coverage.js.map +1 -0
  144. package/dist/qa-readiness.d.ts +5 -0
  145. package/dist/qa-readiness.js +26 -0
  146. package/dist/qa-readiness.js.map +1 -0
  147. package/dist/refresh-generated.d.ts +32 -0
  148. package/dist/refresh-generated.js +180 -0
  149. package/dist/refresh-generated.js.map +1 -0
  150. package/dist/release-candidate.d.ts +9 -1
  151. package/dist/release-candidate.js +52 -1
  152. package/dist/release-candidate.js.map +1 -1
  153. package/dist/release-commands.js +161 -8
  154. package/dist/release-commands.js.map +1 -1
  155. package/dist/release-readiness.d.ts +33 -0
  156. package/dist/release-readiness.js +187 -3
  157. package/dist/release-readiness.js.map +1 -1
  158. package/dist/runtime-adapters.d.ts +2 -1
  159. package/dist/runtime-adapters.js +16 -0
  160. package/dist/runtime-adapters.js.map +1 -1
  161. package/dist/runtime-bootstrap.js +1 -1
  162. package/dist/runtime-bootstrap.js.map +1 -1
  163. package/dist/runtime-commands.d.ts +2 -0
  164. package/dist/runtime-commands.js +85 -3
  165. package/dist/runtime-commands.js.map +1 -1
  166. package/dist/runtime-execution-adapters.js +40 -0
  167. package/dist/runtime-execution-adapters.js.map +1 -1
  168. package/dist/runtime-execution-renderer.d.ts +3 -2
  169. package/dist/runtime-execution-renderer.js +46 -8
  170. package/dist/runtime-execution-renderer.js.map +1 -1
  171. package/dist/runtime-execution.d.ts +8 -2
  172. package/dist/runtime-execution.js +109 -11
  173. package/dist/runtime-execution.js.map +1 -1
  174. package/dist/runtime-guardrails.d.ts +26 -0
  175. package/dist/runtime-guardrails.js +168 -0
  176. package/dist/runtime-guardrails.js.map +1 -0
  177. package/dist/setup-agents-import.js +5 -3
  178. package/dist/setup-agents-import.js.map +1 -1
  179. package/dist/skills-catalog.js +1 -0
  180. package/dist/skills-catalog.js.map +1 -1
  181. package/dist/skills-commands.d.ts +5 -0
  182. package/dist/skills-commands.js +79 -2
  183. package/dist/skills-commands.js.map +1 -1
  184. package/dist/skills-memory.d.ts +36 -2
  185. package/dist/skills-memory.js +165 -6
  186. package/dist/skills-memory.js.map +1 -1
  187. package/dist/skills-planning.js +9 -22
  188. package/dist/skills-planning.js.map +1 -1
  189. package/dist/skills-render.js +2 -4
  190. package/dist/skills-render.js.map +1 -1
  191. package/dist/skills.d.ts +1 -1
  192. package/dist/skills.js +1 -1
  193. package/dist/skills.js.map +1 -1
  194. package/dist/sprint-commands.js +2 -1
  195. package/dist/sprint-commands.js.map +1 -1
  196. package/dist/subagent-protocol.js +3 -5
  197. package/dist/subagent-protocol.js.map +1 -1
  198. package/dist/support-commands.d.ts +2 -0
  199. package/dist/support-commands.js +18 -0
  200. package/dist/support-commands.js.map +1 -0
  201. package/dist/support-diagnostics.d.ts +49 -0
  202. package/dist/support-diagnostics.js +86 -0
  203. package/dist/support-diagnostics.js.map +1 -0
  204. package/dist/task-graph-commands.js +6 -14
  205. package/dist/task-graph-commands.js.map +1 -1
  206. package/dist/task-text.d.ts +8 -0
  207. package/dist/task-text.js +18 -0
  208. package/dist/task-text.js.map +1 -0
  209. package/dist/telemetry-redaction.js +8 -1
  210. package/dist/telemetry-redaction.js.map +1 -1
  211. package/dist/tool-commands.d.ts +3 -0
  212. package/dist/tool-commands.js +62 -0
  213. package/dist/tool-commands.js.map +1 -1
  214. package/dist/tracker-adapters.d.ts +71 -0
  215. package/dist/tracker-adapters.js +186 -0
  216. package/dist/tracker-adapters.js.map +1 -0
  217. package/dist/tracker-commands.d.ts +2 -0
  218. package/dist/tracker-commands.js +119 -0
  219. package/dist/tracker-commands.js.map +1 -0
  220. package/dist/types/metrics.d.ts +25 -1
  221. package/dist/types/model-config.d.ts +51 -4
  222. package/dist/types/runtime.d.ts +83 -0
  223. package/dist/types/skills.d.ts +2 -0
  224. package/dist/types/tasks.d.ts +10 -0
  225. package/dist/types/workflow-run.d.ts +35 -0
  226. package/dist/types.d.ts +12 -4
  227. package/dist/types.js.map +1 -1
  228. package/dist/upgrade-commands.js +13 -4
  229. package/dist/upgrade-commands.js.map +1 -1
  230. package/dist/validation.js +2 -2
  231. package/dist/validation.js.map +1 -1
  232. package/dist/visual-validation.d.ts +81 -0
  233. package/dist/visual-validation.js +290 -0
  234. package/dist/visual-validation.js.map +1 -0
  235. package/dist/web-action-security.d.ts +11 -0
  236. package/dist/web-action-security.js +45 -0
  237. package/dist/web-action-security.js.map +1 -0
  238. package/dist/web-api-read-routes.js +115 -3
  239. package/dist/web-api-read-routes.js.map +1 -1
  240. package/dist/web-api.js +507 -5
  241. package/dist/web-api.js.map +1 -1
  242. package/dist/web-artifacts.d.ts +55 -0
  243. package/dist/web-artifacts.js +222 -0
  244. package/dist/web-artifacts.js.map +1 -0
  245. package/dist/web-console/assets/index-C9lx-V42.css +1 -0
  246. package/dist/web-console/assets/index-M3S0g1GK.js +11 -0
  247. package/dist/web-console/index.html +13 -0
  248. package/dist/web-console.js +9 -3
  249. package/dist/web-console.js.map +1 -1
  250. package/dist/web-recovery.d.ts +30 -0
  251. package/dist/web-recovery.js +163 -0
  252. package/dist/web-recovery.js.map +1 -0
  253. package/dist/web-workflow-progress.d.ts +41 -0
  254. package/dist/web-workflow-progress.js +114 -0
  255. package/dist/web-workflow-progress.js.map +1 -0
  256. package/dist/workflow-approval-service.d.ts +2 -1
  257. package/dist/workflow-approval-service.js +83 -4
  258. package/dist/workflow-approval-service.js.map +1 -1
  259. package/dist/workflow-approval-utils.js +13 -3
  260. package/dist/workflow-approval-utils.js.map +1 -1
  261. package/dist/workflow-event-query.d.ts +2 -0
  262. package/dist/workflow-event-query.js +6 -0
  263. package/dist/workflow-event-query.js.map +1 -0
  264. package/dist/workflow-evidence-service.js +18 -9
  265. package/dist/workflow-evidence-service.js.map +1 -1
  266. package/dist/workflow-gates.d.ts +2 -0
  267. package/dist/workflow-gates.js +103 -0
  268. package/dist/workflow-gates.js.map +1 -1
  269. package/dist/workflow-markdown.d.ts +6 -0
  270. package/dist/workflow-markdown.js +25 -0
  271. package/dist/workflow-markdown.js.map +1 -0
  272. package/dist/workflow-phase-planner.d.ts +19 -0
  273. package/dist/workflow-phase-planner.js +133 -0
  274. package/dist/workflow-phase-planner.js.map +1 -0
  275. package/dist/workflow-run-commands.d.ts +1 -0
  276. package/dist/workflow-run-commands.js +247 -20
  277. package/dist/workflow-run-commands.js.map +1 -1
  278. package/dist/workflow-services.d.ts +21 -12
  279. package/dist/workflow-services.js +376 -260
  280. package/dist/workflow-services.js.map +1 -1
  281. package/dist/workflow-task-service.d.ts +11 -0
  282. package/dist/workflow-task-service.js +242 -0
  283. package/dist/workflow-task-service.js.map +1 -0
  284. package/dist/workflow-templates.js +2 -14
  285. package/dist/workflow-templates.js.map +1 -1
  286. package/dist/workspace-validator.js +133 -5
  287. package/dist/workspace-validator.js.map +1 -1
  288. package/dist/workspace.js +10 -2
  289. package/dist/workspace.js.map +1 -1
  290. package/docs/adoption-guide.md +147 -0
  291. package/docs/autonomous-workflow.md +146 -28
  292. package/docs/benchmark.md +17 -9
  293. package/docs/command-contracts.md +18 -1
  294. package/docs/core-command-surface.md +62 -13
  295. package/docs/end-to-end-demo.md +1 -0
  296. package/docs/extension-contracts.md +83 -0
  297. package/docs/orchestra-mvp.md +86 -3
  298. package/docs/persona-workflows.md +32 -0
  299. package/docs/release-test-matrix.md +42 -0
  300. package/docs/runtime-adapters.md +113 -0
  301. package/docs/runtime-llm-flow.md +13 -0
  302. package/docs/setup-agents-applicability-review.md +173 -0
  303. package/docs/skill-loading-strategy.md +1 -0
  304. package/docs/source-of-truth-and-agent-learning.md +14 -0
  305. package/docs/traceability-flow.md +5 -1
  306. package/docs/tracker-adapter-contract.md +10 -1
  307. package/docs/web-console-qa.md +35 -0
  308. package/package.json +12 -6
  309. package/rules/development-engineering.mdc +66 -0
  310. package/skills/doc-sync/SKILL.md +2 -0
@@ -1,9 +1,10 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import path from "node:path";
3
+ import { uniqueStrings } from "./collection-utils.js";
3
4
  import { FILES } from "./constants.js";
4
5
  import { removeUndefined } from "./command-utils.js";
5
- import { ensureDir, readJson, resolveWorkflowPath, updateJsonFile, writeJson, } from "./fs-utils.js";
6
- import { assertProviderAllowed, assertVendorFallbackAllowed, createModelProvider, defaultApiKeyFileEnv, defaultBaseUrlEnv, FakeModelProvider, InMemoryModelProviderRegistry, providerEnvFromCredentialConfig, summarizeConfiguredProviders, } from "./model-providers.js";
6
+ import { ensureDir, exists, readJson, resolveWorkflowPath, updateJsonFile, writeJson, } from "./fs-utils.js";
7
+ import { assertProviderAllowed, assertVendorFallbackAllowed, createModelProvider, defaultApiKeyEnv, defaultApiKeyFileEnv, defaultBaseUrlEnv, FakeModelProvider, InMemoryModelProviderRegistry, providerEnvFromCredentialConfig, summarizeConfiguredProviders, } from "./model-providers.js";
7
8
  import { appendEvent, loadWorkspace, readEvents, writeArtifact, } from "./workspace.js";
8
9
  import { validateReadiness } from "./validation.js";
9
10
  import { getWorkflowGate } from "./workflow-gates.js";
@@ -13,143 +14,20 @@ import { getTelemetryConsent } from "./telemetry.js";
13
14
  import { latestDelegationDecision } from "./delegation-decision.js";
14
15
  import { listAutonomousRuns } from "./autonomous-workflow.js";
15
16
  import { readEstimate } from "./benchmark.js";
17
+ import { qaPlanReadinessBlocker } from "./qa-readiness.js";
16
18
  import { handoffFlowRequirements, recommendCollaborationFlow, } from "./collaboration-flows.js";
17
19
  import { queryMemory, recordMemoryEvent } from "./memory.js";
18
20
  import { applyContextBudget, DEFAULT_CONTEXT_TOKEN_BUDGET, } from "./context-budget.js";
19
21
  import { selectWorkflowTemplates } from "./workflow-templates.js";
20
22
  import { findStoredApprovalForProposal } from "./workflow-approval-service.js";
23
+ import { listWorkflowEventsByType } from "./workflow-event-query.js";
21
24
  import { aggregateUsage, aggregateUsageBy, budgetEstimateWarningsForBudgets, budgetViolations, emptyUsageBreakdown, projectUsageCost, } from "./workflow-budget-utils.js";
25
+ import { listTasks as listWorkflowTasks, updateTask as updateWorkflowTask, } from "./workflow-task-service.js";
22
26
  import { SIZING_LABELS } from "./types.js";
27
+ export { addTask, archiveTask, deleteTask, getWorkflowStatus, listTasks, updateTask, } from "./workflow-task-service.js";
23
28
  export { addEvidence, addPlaywrightEvidence, listEvidence, listReviews, recordReview, } from "./workflow-evidence-service.js";
24
29
  export { generatePlaywrightTestPlan, generatePullRequestSummary, getWorkflowSummary, } from "./workflow-summary-service.js";
25
- export { approveApproval, approveWorkflowGate, listApprovals, rejectApproval, showApproval, } from "./workflow-approval-service.js";
26
- export async function getWorkflowStatus(root = process.cwd()) {
27
- const workspace = await loadWorkspace(root);
28
- const config = await readJson(resolveWorkflowPath(root, FILES.config), {});
29
- const counts = Object.create(null);
30
- const blocked = [];
31
- for (const task of workspace.tasks) {
32
- counts[task.status] = (counts[task.status] ?? 0) + 1;
33
- if (task.status === "blocked") {
34
- blocked.push({
35
- id: task.id,
36
- title: task.title,
37
- reason: task.blockedReason ?? "not specified",
38
- });
39
- }
40
- }
41
- return {
42
- ...(config.mode ? { mode: config.mode } : {}),
43
- tasks: {
44
- total: workspace.tasks.length,
45
- byStatus: counts,
46
- blocked,
47
- },
48
- locks: {
49
- total: workspace.locks.length,
50
- },
51
- };
52
- }
53
- export async function addTask(input, root = process.cwd()) {
54
- const workspace = await loadWorkspace(root);
55
- if (!workspace.roleIds.has(input.ownerRole)) {
56
- throw new Error(`unknown owner role: ${input.ownerRole}`);
57
- }
58
- const now = new Date().toISOString();
59
- const task = removeUndefined({
60
- ...input,
61
- status: "pending",
62
- createdAt: now,
63
- updatedAt: now,
64
- });
65
- await mutateTasks(workspace.base, (tasks) => {
66
- if (tasks.some((candidate) => candidate.id === input.id)) {
67
- throw new Error(`task already exists: ${input.id}`);
68
- }
69
- return [...tasks, task];
70
- });
71
- await appendEvent(root, {
72
- type: "TASK_ASSIGNED",
73
- taskId: input.id,
74
- actor: "parent",
75
- summary: `Task assigned to ${input.ownerRole}`,
76
- metadata: { title: input.title, ownerRole: input.ownerRole },
77
- });
78
- return task;
79
- }
80
- export async function listTasks(root = process.cwd()) {
81
- const workspace = await loadWorkspace(root);
82
- return workspace.tasks;
83
- }
84
- export async function deleteTask(taskId, options = {}, root = process.cwd()) {
85
- const workspace = await loadWorkspace(root);
86
- let deleted;
87
- await mutateTasks(workspace.base, (tasks) => {
88
- const taskIndex = tasks.findIndex((task) => task.id === taskId);
89
- if (taskIndex < 0) {
90
- throw new Error(`unknown task: ${taskId}`);
91
- }
92
- const current = tasks[taskIndex];
93
- if (!current) {
94
- throw new Error(`unknown task: ${taskId}`);
95
- }
96
- assertTaskCanBeRemoved(current, tasks, options.force ?? false);
97
- deleted = current;
98
- return tasks.filter((task) => task.id !== taskId);
99
- });
100
- if (!deleted) {
101
- throw new Error(`unknown task: ${taskId}`);
102
- }
103
- await appendEvent(root, {
104
- type: "TASK_DELETED",
105
- taskId,
106
- actor: "parent",
107
- summary: `Task deleted: ${taskId}`,
108
- metadata: {
109
- title: deleted.title,
110
- status: deleted.status,
111
- forced: Boolean(options.force),
112
- },
113
- });
114
- return deleted;
115
- }
116
- export async function archiveTask(taskId, options = {}, root = process.cwd()) {
117
- const workspace = await loadWorkspace(root);
118
- let archived;
119
- await mutateTasks(workspace.base, (tasks) => {
120
- const taskIndex = tasks.findIndex((task) => task.id === taskId);
121
- if (taskIndex < 0) {
122
- throw new Error(`unknown task: ${taskId}`);
123
- }
124
- const current = tasks[taskIndex];
125
- if (!current) {
126
- throw new Error(`unknown task: ${taskId}`);
127
- }
128
- assertTaskCanBeRemoved(current, tasks, options.force ?? false);
129
- archived = {
130
- ...current,
131
- status: "archived",
132
- updatedAt: new Date().toISOString(),
133
- };
134
- const nextTasks = [...tasks];
135
- nextTasks[taskIndex] = archived;
136
- return nextTasks;
137
- });
138
- if (!archived) {
139
- throw new Error(`unknown task: ${taskId}`);
140
- }
141
- await appendEvent(root, {
142
- type: "TASK_ARCHIVED",
143
- taskId,
144
- actor: "parent",
145
- summary: `Task archived: ${taskId}`,
146
- metadata: {
147
- title: archived.title,
148
- forced: Boolean(options.force),
149
- },
150
- });
151
- return archived;
152
- }
30
+ export { approveApproval, approveWorkflowGate, listApprovals, recordWorkflowGateDecision, rejectApproval, showApproval, } from "./workflow-approval-service.js";
153
31
  export async function listRoles(root = process.cwd()) {
154
32
  const workspace = await loadWorkspace(root);
155
33
  return workspace.roles;
@@ -197,72 +75,6 @@ export async function validatePreRun(taskId, options = {}, root = process.cwd())
197
75
  bypassDecisionArtifact,
198
76
  });
199
77
  }
200
- export async function updateTask(input, root = process.cwd()) {
201
- const workspace = await loadWorkspace(root);
202
- if (input.ownerRole && !workspace.roleIds.has(input.ownerRole)) {
203
- throw new Error(`unknown owner role: ${input.ownerRole}`);
204
- }
205
- let updated;
206
- let changedFields = [];
207
- await mutateTasks(workspace.base, (tasks) => {
208
- const taskIndex = tasks.findIndex((task) => task.id === input.id);
209
- if (taskIndex < 0) {
210
- throw new Error(`unknown task: ${input.id}`);
211
- }
212
- const current = tasks[taskIndex];
213
- if (!current) {
214
- throw new Error(`unknown task: ${input.id}`);
215
- }
216
- const next = { ...current };
217
- const changes = new Set();
218
- applyTaskUpdate(next, current, input, changes);
219
- changedFields = [...changes].sort();
220
- updated = { ...next, updatedAt: new Date().toISOString() };
221
- const nextTasks = [...tasks];
222
- nextTasks[taskIndex] = updated;
223
- return nextTasks;
224
- });
225
- if (!updated) {
226
- throw new Error(`unknown task: ${input.id}`);
227
- }
228
- await appendEvent(root, {
229
- type: "TASK_UPDATED",
230
- taskId: input.id,
231
- actor: "parent",
232
- summary: `Task updated: ${input.id}`,
233
- metadata: { status: updated.status, changedFields },
234
- });
235
- return updated;
236
- }
237
- function applyTaskUpdate(next, current, input, changedFields) {
238
- setTaskField(next, current, "title", input.title, changedFields);
239
- setTaskField(next, current, "ownerRole", input.ownerRole, changedFields);
240
- setTaskField(next, current, "goal", input.goal, changedFields);
241
- setTaskField(next, current, "scope", input.scope, changedFields);
242
- setTaskField(next, current, "paths", input.paths, changedFields);
243
- setTaskField(next, current, "testStrategy", input.testStrategy, changedFields);
244
- setTaskField(next, current, "status", input.status, changedFields);
245
- setTaskField(next, current, "blockedReason", input.blockedReason, changedFields);
246
- appendTaskField(next, "acceptanceCriteria", input.acceptanceCriteria, changedFields);
247
- appendTaskField(next, "assumptions", input.assumptions, changedFields);
248
- appendTaskField(next, "risks", input.risks, changedFields);
249
- }
250
- function setTaskField(next, current, key, value, changedFields) {
251
- if (value === undefined) {
252
- return;
253
- }
254
- if (JSON.stringify(current[key]) !== JSON.stringify(value)) {
255
- changedFields.add(String(key));
256
- }
257
- next[key] = value;
258
- }
259
- function appendTaskField(next, key, values, changedFields) {
260
- if (!values || values.length === 0) {
261
- return;
262
- }
263
- next[key] = [...(next[key] ?? []), ...values];
264
- changedFields.add(key);
265
- }
266
78
  export async function checkTaskDependencies(taskId, root = process.cwd()) {
267
79
  const workspace = await loadWorkspace(root);
268
80
  const task = workspace.tasks.find((candidate) => candidate.id === taskId);
@@ -289,7 +101,7 @@ export async function checkTaskDependencies(taskId, root = process.cwd()) {
289
101
  };
290
102
  }
291
103
  export async function generateTaskGraphPlan(root = process.cwd()) {
292
- const tasks = await listTasks(root);
104
+ const tasks = await listWorkflowTasks(root);
293
105
  const locks = await listLocks(root);
294
106
  const ready = [];
295
107
  const blocked = [];
@@ -311,6 +123,14 @@ export async function generateTaskGraphPlan(root = process.cwd()) {
311
123
  }
312
124
  const dependencies = await checkTaskDependencies(task.id, root);
313
125
  if (dependencies.isSatisfied) {
126
+ const qaBlocker = qaPlanReadinessBlocker(task);
127
+ if (qaBlocker) {
128
+ blocked.push({
129
+ ...item,
130
+ incomplete: [qaBlocker],
131
+ });
132
+ continue;
133
+ }
314
134
  const taskLocks = locksForTask(task, locks);
315
135
  if (taskLocks.length > 0) {
316
136
  locked.push({
@@ -557,7 +377,7 @@ export async function createHandoff(input, root = process.cwd()) {
557
377
  metadata: { to: input.to, updateOwner: Boolean(input.updateOwner) },
558
378
  });
559
379
  if (input.updateOwner) {
560
- await updateTask({ id: input.task, ownerRole: input.to }, root);
380
+ await updateWorkflowTask({ id: input.task, ownerRole: input.to }, root);
561
381
  }
562
382
  return { artifact, content };
563
383
  }
@@ -587,8 +407,9 @@ export async function recordDecision(input, root = process.cwd()) {
587
407
  "",
588
408
  ].join("\n");
589
409
  const artifact = await writeArtifact(root, "decisions", fileName, content);
590
- // Extract sizing/points from decision text when owner is architect
591
- // Expected format: "<sizing> [N points]" e.g. "m [5 points]" or just "m"
410
+ // Extract sizing/points from decision text for estimation decisions.
411
+ // Architect format: "<sizing> [N points]" e.g. "m [5 points]" or just "m".
412
+ // Developer format: "N points" for implementation effort.
592
413
  const sizingMetadata = {};
593
414
  if (input.owner === "architect") {
594
415
  const sizingMatch = /^\s*(xs|s|m|l|xl)\b/i.exec(input.decision);
@@ -603,6 +424,12 @@ export async function recordDecision(input, root = process.cwd()) {
603
424
  }
604
425
  }
605
426
  }
427
+ else if (input.owner === "developer") {
428
+ const pointsMatch = /\b(\d+)\s*points?\b/i.exec(input.decision);
429
+ if (pointsMatch) {
430
+ sizingMetadata.points = parseInt(pointsMatch[1], 10);
431
+ }
432
+ }
606
433
  await appendEvent(root, {
607
434
  type: "DECISION_RECORDED",
608
435
  taskId: input.task,
@@ -612,13 +439,15 @@ export async function recordDecision(input, root = process.cwd()) {
612
439
  metadata: {
613
440
  status: input.status,
614
441
  title: input.title,
442
+ decision: input.decision,
443
+ consequences: input.consequences,
615
444
  ...sizingMetadata,
616
445
  },
617
446
  });
618
447
  return { ...input, artifact };
619
448
  }
620
449
  export async function listDecisions(taskId, root = process.cwd()) {
621
- return filterEvents("DECISION_RECORDED", taskId, root);
450
+ return listWorkflowEventsByType("DECISION_RECORDED", taskId, root);
622
451
  }
623
452
  export async function getTaskContext(taskId, root = process.cwd(), options = {}) {
624
453
  const workspace = await loadWorkspace(root);
@@ -1027,6 +856,146 @@ export async function getWorkflowConfig(root = process.cwd()) {
1027
856
  export async function listConfiguredModelProviders(root = process.cwd()) {
1028
857
  return summarizeConfiguredProviders(await getWorkflowConfig(root));
1029
858
  }
859
+ export async function listProviderRuntimeProfiles(root = process.cwd()) {
860
+ const config = await getWorkflowConfig(root);
861
+ const activeProfile = config.providers?.activeProfile;
862
+ return Object.entries(config.providers?.profiles ?? {})
863
+ .map(([name, profile]) => removeUndefined({
864
+ name,
865
+ active: name === activeProfile,
866
+ description: profile.description,
867
+ roles: Object.keys(profile.byRole ?? {}).sort(),
868
+ defaultProvider: profile.defaults?.provider,
869
+ defaultModel: profile.defaults?.model,
870
+ requiredEnv: profile.requiredEnv ?? [],
871
+ }))
872
+ .sort((a, b) => a.name.localeCompare(b.name));
873
+ }
874
+ export async function saveProviderRuntimeProfile(input, root = process.cwd()) {
875
+ const workspace = await loadWorkspace(root);
876
+ validateProfileName(input.name);
877
+ for (const role of input.roles) {
878
+ if (!workspace.roleIds.has(role)) {
879
+ throw new Error(`unknown role: ${role}`);
880
+ }
881
+ }
882
+ if (input.roles.length === 0) {
883
+ throw new Error("at least one role is required");
884
+ }
885
+ validateProfileRouting(input.routing);
886
+ const configPath = resolveWorkflowPath(root, FILES.config);
887
+ const config = await readJson(configPath, {});
888
+ const profile = removeUndefined({
889
+ description: input.description,
890
+ byRole: Object.fromEntries(input.roles.map((role) => [role, input.routing])),
891
+ requiredEnv: input.requiredEnv,
892
+ });
893
+ config.providers = {
894
+ defaults: config.providers?.defaults,
895
+ byRole: config.providers?.byRole ?? {},
896
+ profiles: {
897
+ ...(config.providers?.profiles ?? {}),
898
+ [input.name]: profile,
899
+ },
900
+ ...(input.activate ? { activeProfile: input.name } : {}),
901
+ };
902
+ await writeJson(configPath, config);
903
+ if (input.apply || input.activate) {
904
+ await applyProviderRuntimeProfile(input.name, root);
905
+ }
906
+ await appendEvent(root, {
907
+ type: "MODEL_PROFILE_SAVED",
908
+ actor: "parent",
909
+ summary: `Provider runtime profile saved: ${input.name}`,
910
+ metadata: {
911
+ profile: input.name,
912
+ roles: input.roles,
913
+ provider: input.routing.provider,
914
+ model: input.routing.model,
915
+ },
916
+ });
917
+ const summaries = await listProviderRuntimeProfiles(root);
918
+ return summaries.find((summary) => summary.name === input.name);
919
+ }
920
+ export async function applyProviderRuntimeProfile(name, root = process.cwd()) {
921
+ validateProfileName(name);
922
+ const workspace = await loadWorkspace(root);
923
+ const configPath = resolveWorkflowPath(root, FILES.config);
924
+ const config = await readJson(configPath, {});
925
+ const profile = config.providers?.profiles?.[name];
926
+ if (!profile) {
927
+ throw new Error(`unknown provider runtime profile: ${name}`);
928
+ }
929
+ for (const role of Object.keys(profile.byRole ?? {})) {
930
+ if (!workspace.roleIds.has(role)) {
931
+ throw new Error(`profile ${name} references unknown role: ${role}`);
932
+ }
933
+ }
934
+ for (const routing of Object.values(profile.byRole ?? {})) {
935
+ validateProfileRouting(routing);
936
+ }
937
+ if (profile.defaults) {
938
+ validateProfileRouting(profile.defaults);
939
+ }
940
+ config.providers = {
941
+ defaults: profile.defaults ?? config.providers.defaults,
942
+ byRole: {
943
+ ...(config.providers.byRole ?? {}),
944
+ ...(profile.byRole ?? {}),
945
+ },
946
+ profiles: config.providers.profiles ?? {},
947
+ activeProfile: name,
948
+ };
949
+ if (profile.budgets) {
950
+ config.budgets = profile.budgets;
951
+ }
952
+ if (profile.providerPolicy) {
953
+ config.providerPolicy = {
954
+ ...(config.providerPolicy ?? {}),
955
+ ...profile.providerPolicy,
956
+ };
957
+ }
958
+ await writeJson(configPath, config);
959
+ await appendEvent(root, {
960
+ type: "MODEL_PROFILE_APPLIED",
961
+ actor: "parent",
962
+ summary: `Provider runtime profile applied: ${name}`,
963
+ metadata: { profile: name, roles: Object.keys(profile.byRole ?? {}) },
964
+ });
965
+ const summaries = await listProviderRuntimeProfiles(root);
966
+ return summaries.find((summary) => summary.name === name);
967
+ }
968
+ export async function smokeProviderRuntimeProfile(name, root = process.cwd(), env = process.env) {
969
+ validateProfileName(name);
970
+ const config = await getWorkflowConfig(root);
971
+ const profile = config.providers?.profiles?.[name];
972
+ if (!profile) {
973
+ throw new Error(`unknown provider runtime profile: ${name}`);
974
+ }
975
+ const checks = [];
976
+ for (const envName of profile.requiredEnv ?? []) {
977
+ checks.push({
978
+ scope: `env:${envName}`,
979
+ provider: "environment",
980
+ model: "not-applicable",
981
+ status: env[envName]?.trim() ? "pass" : "fail",
982
+ detail: env[envName]?.trim()
983
+ ? "environment variable is configured"
984
+ : `missing required environment variable ${envName}`,
985
+ });
986
+ }
987
+ const routes = Object.entries(profile.byRole ?? {}).map(([role, routing]) => [`role:${role}`, routing]);
988
+ if (profile.defaults)
989
+ routes.unshift(["defaults", profile.defaults]);
990
+ for (const [scope, routing] of routes) {
991
+ checks.push(...(await smokeRouting(scope, routing, config, root, env)));
992
+ }
993
+ return {
994
+ profile: name,
995
+ passed: checks.every((check) => check.status === "pass"),
996
+ checks,
997
+ };
998
+ }
1030
999
  export async function completeWithProviderFallback(routing, prompt, { failingProviders = [], root = process.cwd(), taskId, role = "parent", jsonMode = false, providerMode = "fake", } = {}) {
1031
1000
  const registry = new InMemoryModelProviderRegistry();
1032
1001
  const config = await getWorkflowConfig(root);
@@ -1049,44 +1018,125 @@ export async function completeWithProviderFallback(routing, prompt, { failingPro
1049
1018
  if (providerMode === "real" && index > 0) {
1050
1019
  assertVendorFallbackAllowed(providerId, config.providerPolicy);
1051
1020
  }
1052
- try {
1053
- const provider = registry.get(providerId);
1054
- const response = await provider.complete({
1055
- model: routing.model,
1056
- jsonMode,
1057
- timeoutMs: routing.timeoutMs,
1058
- messages: [{ role: "user", content: prompt }],
1059
- });
1060
- if (index > 0) {
1061
- await appendEvent(root, removeUndefined({
1062
- type: "MODEL_FALLBACK_USED",
1063
- actor: role,
1064
- taskId,
1065
- summary: `Fallback provider used: ${providerId}`,
1066
- metadata: { provider: providerId, failedProviders },
1067
- }));
1021
+ let attempts = 0;
1022
+ const maxAttempts = Math.max(1, 1 + routing.retries);
1023
+ while (attempts < maxAttempts) {
1024
+ attempts += 1;
1025
+ try {
1026
+ const provider = registry.get(providerId);
1027
+ const response = await provider.complete({
1028
+ model: routing.model,
1029
+ jsonMode,
1030
+ timeoutMs: routing.timeoutMs,
1031
+ messages: [{ role: "user", content: prompt }],
1032
+ });
1033
+ if (index > 0) {
1034
+ await appendEvent(root, removeUndefined({
1035
+ type: "MODEL_FALLBACK_USED",
1036
+ actor: role,
1037
+ taskId,
1038
+ summary: `Fallback provider used: ${providerId}`,
1039
+ metadata: { provider: providerId, failedProviders },
1040
+ }));
1041
+ }
1042
+ return {
1043
+ provider: providerId,
1044
+ model: routing.model,
1045
+ response,
1046
+ fallbackUsed: index > 0,
1047
+ failedProviders,
1048
+ };
1049
+ }
1050
+ catch (error) {
1051
+ const failure = providerFailureFromError(providerId, error, attempts);
1052
+ if (failure.code === "timeout") {
1053
+ throw error;
1054
+ }
1055
+ if (failure.retryable && attempts < maxAttempts) {
1056
+ continue;
1057
+ }
1058
+ failedProviders.push(failure);
1059
+ break;
1068
1060
  }
1069
- return {
1070
- provider: providerId,
1071
- model: routing.model,
1072
- response,
1073
- fallbackUsed: index > 0,
1074
- failedProviders,
1075
- };
1076
1061
  }
1077
- catch (error) {
1078
- if (isProviderTimeoutError(error)) {
1079
- throw error;
1062
+ }
1063
+ throw new ProviderFallbackError(failedProviders);
1064
+ }
1065
+ export class ProviderFallbackError extends Error {
1066
+ failures;
1067
+ constructor(failures) {
1068
+ super(providerFailureSummary(failures));
1069
+ this.name = "ProviderFallbackError";
1070
+ this.failures = failures;
1071
+ }
1072
+ }
1073
+ export function providerFailuresFromError(error) {
1074
+ return error instanceof ProviderFallbackError ? error.failures : [];
1075
+ }
1076
+ function providerFailureSummary(failures) {
1077
+ const providers = failures.map((failure) => failure.provider).join(", ");
1078
+ const details = failures
1079
+ .map((failure) => `${failure.provider}: ${failure.reason} (${failure.code}, attempts=${failure.attempts})`)
1080
+ .join("; ");
1081
+ return `all providers failed: ${providers}${details ? ` (${details})` : ""}`;
1082
+ }
1083
+ function providerFailureFromError(providerId, error, attempts) {
1084
+ const code = providerFailureCode(error);
1085
+ return {
1086
+ provider: providerId,
1087
+ code,
1088
+ reason: sanitizeProviderError(error),
1089
+ retryable: code === "provider_error",
1090
+ attempts,
1091
+ };
1092
+ }
1093
+ function providerFailureCode(error) {
1094
+ const message = errorMessage(error).toLowerCase();
1095
+ if (isProviderTimeoutError(error))
1096
+ return "timeout";
1097
+ if (/permission|denied|forbidden|unauthorized/.test(message)) {
1098
+ return "permission_denied";
1099
+ }
1100
+ if (/policy|not allowed|blocked/.test(message))
1101
+ return "policy_blocked";
1102
+ return "provider_error";
1103
+ }
1104
+ function sanitizeProviderError(error) {
1105
+ const messages = [];
1106
+ if (error instanceof Error) {
1107
+ messages.push(error.message);
1108
+ const cause = error.cause;
1109
+ if (cause instanceof Error && cause.message !== error.message) {
1110
+ messages.push(cause.message);
1111
+ }
1112
+ else if (isErrorCauseRecord(cause)) {
1113
+ const code = typeof cause.code === "string" && cause.code.trim()
1114
+ ? cause.code
1115
+ : undefined;
1116
+ const hostname = typeof cause.hostname === "string" && cause.hostname.trim()
1117
+ ? cause.hostname
1118
+ : undefined;
1119
+ if (code || hostname) {
1120
+ messages.push([code, hostname].filter(Boolean).join(" "));
1080
1121
  }
1081
- failedProviders.push({
1082
- provider: providerId,
1083
- reason: error instanceof Error ? error.message : String(error),
1084
- });
1085
1122
  }
1086
1123
  }
1087
- throw new Error(`all providers failed: ${failedProviders
1088
- .map((failure) => failure.provider)
1089
- .join(", ")}`);
1124
+ else {
1125
+ messages.push(String(error));
1126
+ }
1127
+ return redactProviderError(messages.filter(Boolean).join(": "));
1128
+ }
1129
+ function errorMessage(error) {
1130
+ return error instanceof Error ? error.message : String(error);
1131
+ }
1132
+ function isErrorCauseRecord(value) {
1133
+ return typeof value === "object" && value !== null;
1134
+ }
1135
+ function redactProviderError(message) {
1136
+ return message
1137
+ .replace(/(x-goog-api-key|api[_-]?key|authorization)\s*[:=]\s*\S+/gi, "$1=[REDACTED]")
1138
+ .replace(/Bearer\s+[A-Za-z0-9._~+/=-]+/g, "Bearer [REDACTED]")
1139
+ .replace(/AIza[0-9A-Za-z_-]{20,}/g, "[REDACTED_API_KEY]");
1090
1140
  }
1091
1141
  function isProviderTimeoutError(error) {
1092
1142
  if (!(error instanceof Error)) {
@@ -1109,6 +1159,10 @@ export async function setRoleModelProvider(role, routing, root = process.cwd())
1109
1159
  ...(config.providers.byRole ?? {}),
1110
1160
  [role]: routing,
1111
1161
  },
1162
+ profiles: config.providers.profiles ?? {},
1163
+ ...(config.providers.activeProfile
1164
+ ? { activeProfile: config.providers.activeProfile }
1165
+ : {}),
1112
1166
  };
1113
1167
  await writeJson(configPath, config);
1114
1168
  await appendEvent(root, {
@@ -1145,6 +1199,10 @@ export async function connectModelProvider(input, root = process.cwd()) {
1145
1199
  config.providers = {
1146
1200
  defaults: config.providers?.defaults ?? routing,
1147
1201
  byRole,
1202
+ profiles: config.providers?.profiles ?? {},
1203
+ ...(config.providers?.activeProfile
1204
+ ? { activeProfile: config.providers.activeProfile }
1205
+ : {}),
1148
1206
  };
1149
1207
  const credential = removeUndefined({
1150
1208
  apiKeyFile: input.apiKeyFile,
@@ -1165,7 +1223,7 @@ export async function connectModelProvider(input, root = process.cwd()) {
1165
1223
  if (input.allowDirectProviderApi) {
1166
1224
  config.providerPolicy = {
1167
1225
  ...(config.providerPolicy ?? {}),
1168
- allowedProviders: unique([
1226
+ allowedProviders: uniqueStrings([
1169
1227
  ...(config.providerPolicy?.allowedProviders ?? []),
1170
1228
  input.provider,
1171
1229
  ]),
@@ -1177,6 +1235,9 @@ export async function connectModelProvider(input, root = process.cwd()) {
1177
1235
  delegation: {
1178
1236
  mode: config.runtimePolicy?.delegation?.mode ?? "runtime-native",
1179
1237
  allowDirectProviderApi: true,
1238
+ ...(config.runtimePolicy?.delegation?.guardrails
1239
+ ? { guardrails: config.runtimePolicy.delegation.guardrails }
1240
+ : {}),
1180
1241
  },
1181
1242
  };
1182
1243
  }
@@ -1201,8 +1262,85 @@ export async function connectModelProvider(input, root = process.cwd()) {
1201
1262
  allowDirectProviderApi: input.allowDirectProviderApi,
1202
1263
  });
1203
1264
  }
1204
- function unique(values) {
1205
- return [...new Set(values)];
1265
+ function validateProfileName(name) {
1266
+ if (!/^[a-z0-9][a-z0-9._-]{1,62}$/i.test(name)) {
1267
+ throw new Error("profile name must be 2-63 characters and contain only letters, numbers, dots, underscores, or dashes");
1268
+ }
1269
+ }
1270
+ function validateProfileRouting(routing) {
1271
+ if (!routing.provider?.trim()) {
1272
+ throw new Error("profile routing requires provider");
1273
+ }
1274
+ if (!routing.model?.trim()) {
1275
+ throw new Error("profile routing requires model");
1276
+ }
1277
+ if (new Set([routing.provider, ...(routing.fallbacks ?? [])]).size !==
1278
+ [routing.provider, ...(routing.fallbacks ?? [])].length) {
1279
+ throw new Error("profile routing fallback chain contains duplicates");
1280
+ }
1281
+ }
1282
+ async function smokeRouting(scope, routing, config, root, env) {
1283
+ const checks = [];
1284
+ const providerIds = [routing.provider, ...(routing.fallbacks ?? [])];
1285
+ for (const [index, providerId] of providerIds.entries()) {
1286
+ const providerScope = index === 0 ? scope : `${scope}:fallback:${index}`;
1287
+ const policyError = providerPolicySmokeError(providerId, config, index);
1288
+ if (policyError) {
1289
+ checks.push({
1290
+ scope: providerScope,
1291
+ provider: providerId,
1292
+ model: routing.model,
1293
+ status: "fail",
1294
+ detail: policyError,
1295
+ });
1296
+ continue;
1297
+ }
1298
+ const credentialError = await providerCredentialSmokeError(providerId, config.providerCredentials?.byProvider?.[providerId], root, env);
1299
+ checks.push({
1300
+ scope: providerScope,
1301
+ provider: providerId,
1302
+ model: routing.model,
1303
+ status: credentialError ? "fail" : "pass",
1304
+ detail: credentialError ?? "provider configuration is present",
1305
+ });
1306
+ }
1307
+ return checks;
1308
+ }
1309
+ function providerPolicySmokeError(providerId, config, fallbackIndex) {
1310
+ try {
1311
+ assertProviderAllowed(providerId, config.providerPolicy);
1312
+ if (fallbackIndex > 0) {
1313
+ assertVendorFallbackAllowed(providerId, config.providerPolicy);
1314
+ }
1315
+ return undefined;
1316
+ }
1317
+ catch (error) {
1318
+ return error instanceof Error ? error.message : String(error);
1319
+ }
1320
+ }
1321
+ async function providerCredentialSmokeError(providerId, credential, root, env) {
1322
+ if (providerId === "none" ||
1323
+ providerId === "fake" ||
1324
+ providerId === "ollama") {
1325
+ return undefined;
1326
+ }
1327
+ const apiKeyEnv = credential?.apiKeyEnv ?? defaultApiKeyEnv(providerId);
1328
+ const apiKeyFileEnv = credential?.apiKeyFileEnv ?? defaultApiKeyFileEnv(providerId);
1329
+ const effectiveEnv = providerEnvFromCredentialConfig(providerId, credential, env);
1330
+ if (apiKeyEnv && effectiveEnv[apiKeyEnv]?.trim()) {
1331
+ return undefined;
1332
+ }
1333
+ if (apiKeyFileEnv && effectiveEnv[apiKeyFileEnv]?.trim()) {
1334
+ const keyFile = effectiveEnv[apiKeyFileEnv].trim();
1335
+ if (!path.isAbsolute(keyFile)) {
1336
+ return `${apiKeyFileEnv} must reference an absolute path`;
1337
+ }
1338
+ if (!(await exists(keyFile))) {
1339
+ return `${apiKeyFileEnv} references an unreadable secret file`;
1340
+ }
1341
+ return undefined;
1342
+ }
1343
+ return `${apiKeyEnv ?? "provider API key"} or ${apiKeyFileEnv ?? "provider API key file"} is required`;
1206
1344
  }
1207
1345
  export async function recordModelProvenance(input, root = process.cwd()) {
1208
1346
  const workspace = await loadWorkspace(root);
@@ -1224,7 +1362,7 @@ export async function recordModelProvenance(input, root = process.cwd()) {
1224
1362
  return record;
1225
1363
  }
1226
1364
  export async function listModelProvenance(taskId, root = process.cwd()) {
1227
- const events = await filterEvents("MODEL_PROVENANCE_RECORDED", taskId, root);
1365
+ const events = await listWorkflowEventsByType("MODEL_PROVENANCE_RECORDED", taskId, root);
1228
1366
  return events.map((event) => event.metadata);
1229
1367
  }
1230
1368
  export async function getUsageReport(taskId, root = process.cwd()) {
@@ -1378,31 +1516,9 @@ async function writeBudgetEscalationProposal(root, taskId, proposal) {
1378
1516
  ].join("\n");
1379
1517
  return writeArtifact(root, "approvals", `${taskId}-budget-fallback.md`, content);
1380
1518
  }
1381
- async function filterEvents(type, taskId, root) {
1382
- const events = (await readEvents(root)).filter((event) => event.type === type);
1383
- return taskId ? events.filter((event) => event.taskId === taskId) : events;
1384
- }
1385
- async function mutateTasks(base, update) {
1386
- return updateJsonFile(path.join(base, FILES.tasks), [], update);
1387
- }
1388
1519
  async function mutateLocks(base, update) {
1389
1520
  return updateJsonFile(path.join(base, FILES.locks), [], update);
1390
1521
  }
1391
- function assertTaskCanBeRemoved(task, tasks, isForced) {
1392
- if (!isForced &&
1393
- (task.status === "in_progress" || task.status === "blocked")) {
1394
- throw new Error(`task ${task.id} is ${task.status}; use --force to remove it`);
1395
- }
1396
- const dependent = tasks.find((candidate) => candidate.id !== task.id &&
1397
- !isTerminalTaskStatus(candidate.status) &&
1398
- candidate.dependencies.includes(task.id));
1399
- if (dependent) {
1400
- throw new Error(`task ${task.id} is a dependency of active task ${dependent.id}`);
1401
- }
1402
- }
1403
- function isTerminalTaskStatus(status) {
1404
- return ["done", "canceled", "rejected", "archived"].includes(status);
1405
- }
1406
1522
  function flowRequirementLines(requirements) {
1407
1523
  return requirements.length > 0
1408
1524
  ? requirements.map((requirement) => `- ${requirement}`)