opencode-wait-a-minute 0.0.0-stage → 1.1.0-rc.8

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 (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +318 -2
  3. package/index.js +1836 -0
  4. package/package.json +70 -4
  5. package/skills/codebase-design/SKILL.md +114 -0
  6. package/skills/design-contract/SKILL.md +27 -0
  7. package/skills/design-critique/SKILL.md +19 -0
  8. package/skills/design-system/SKILL.md +17 -0
  9. package/skills/design-system/references/image-analysis.md +14 -0
  10. package/skills/design-system/references/tokens.md +32 -0
  11. package/skills/design-system/references/ui-rules.md +14 -0
  12. package/skills/diagnosing-bugs/SKILL.md +138 -0
  13. package/skills/frontend-design/SKILL.md +36 -0
  14. package/skills/handoff/SKILL.md +16 -0
  15. package/skills/improve-codebase-architecture/SKILL.md +71 -0
  16. package/skills/linter-negative/SKILL.md +74 -0
  17. package/skills/no-slop-ui/SKILL.md +26 -0
  18. package/skills/prototype/SKILL.md +26 -0
  19. package/skills/registry.json +44087 -0
  20. package/skills/tastemaker/SKILL.md +8 -0
  21. package/skills/tastemaker/references/tastemaker.md +14 -0
  22. package/skills/tdd/SKILL.md +38 -0
  23. package/skills/visual-review/SKILL.md +103 -0
  24. package/skills/visual-review/references/review-gates.md +42 -0
  25. package/skills/visual-review/references/surface-archetypes.md +37 -0
  26. package/skills/wizard/SKILL.md +44 -0
  27. package/skills/writing-for-agents/SKILL.md +81 -0
  28. package/src/cognition/cognition-store.js +251 -0
  29. package/src/cognition/cognitive-state.js +126 -0
  30. package/src/cognition/hypothesis-manager.js +113 -0
  31. package/src/cognition/observation-engine.js +112 -0
  32. package/src/cognition/observation-provenance.js +268 -0
  33. package/src/context/active-context-boundary.js +68 -0
  34. package/src/context/assembly.js +494 -0
  35. package/src/context/assessment-rules.js +55 -0
  36. package/src/context/capsule-staleness.js +267 -0
  37. package/src/context/capsules.js +2 -0
  38. package/src/context/compare-array.js +98 -0
  39. package/src/context/compare-object.js +89 -0
  40. package/src/context/compare-values.js +42 -0
  41. package/src/context/context-assembly-contract.js +71 -0
  42. package/src/context/context-budget-manager.js +232 -0
  43. package/src/context/context-builder.js +49 -0
  44. package/src/context/context-cache.js +55 -0
  45. package/src/context/context-capture.js +243 -0
  46. package/src/context/context-classification.js +199 -0
  47. package/src/context/context-compaction.js +179 -0
  48. package/src/context/context-decision-audit.js +267 -0
  49. package/src/context/context-demotion.js +173 -0
  50. package/src/context/context-dictionary.js +106 -0
  51. package/src/context/context-drift-detection.js +225 -0
  52. package/src/context/context-evaluation.js +374 -0
  53. package/src/context/context-event-ingress.js +100 -0
  54. package/src/context/context-event-normalization.js +224 -0
  55. package/src/context/context-feedback-loop.js +216 -0
  56. package/src/context/context-freshness.js +21 -0
  57. package/src/context/context-graph-builder.js +71 -0
  58. package/src/context/context-graph.js +451 -0
  59. package/src/context/context-history-relevance.js +321 -0
  60. package/src/context/context-interception.js +145 -0
  61. package/src/context/context-invalidation-events.js +154 -0
  62. package/src/context/context-lifecycle.js +170 -0
  63. package/src/context/context-loop-prevention.js +347 -0
  64. package/src/context/context-manager.js +340 -0
  65. package/src/context/context-memory-layers.js +265 -0
  66. package/src/context/context-minimality.js +166 -0
  67. package/src/context/context-normalization.js +130 -0
  68. package/src/context/context-optimization-metrics.js +434 -0
  69. package/src/context/context-output-policy.js +135 -0
  70. package/src/context/context-pack-authority.js +157 -0
  71. package/src/context/context-promotion.js +172 -0
  72. package/src/context/context-query-contract.js +101 -0
  73. package/src/context/context-ranking.js +164 -0
  74. package/src/context/context-rate-distortion.js +261 -0
  75. package/src/context/context-recovery.js +209 -0
  76. package/src/context/context-retrieval-integration.js +60 -0
  77. package/src/context/context-retrieval.js +93 -0
  78. package/src/context/context-router.js +457 -0
  79. package/src/context/context-routing-evaluation.js +331 -0
  80. package/src/context/context-routing-observability.js +296 -0
  81. package/src/context/context-routing-shadow.js +237 -0
  82. package/src/context/context-scope.js +181 -0
  83. package/src/context/context-snapshot.js +226 -0
  84. package/src/context/context-source-registry.js +95 -0
  85. package/src/context/context-sufficiency-gate.js +120 -0
  86. package/src/context/context-sufficiency-oracle.js +167 -0
  87. package/src/context/context-telemetry.js +176 -0
  88. package/src/context/context.js +633 -0
  89. package/src/context/dependency-closure.js +246 -0
  90. package/src/context/page-fault-tracker.js +275 -0
  91. package/src/context/prompt-engine.js +74 -0
  92. package/src/context/runtime-context-graph.js +260 -0
  93. package/src/context/selection.js +2 -0
  94. package/src/context/session.js +2 -0
  95. package/src/context/sufficiency-contract.js +300 -0
  96. package/src/context/tokenizer.js +17 -0
  97. package/src/context-benchmark-router.mjs +125 -0
  98. package/src/evidence/evidence-engine.js +161 -0
  99. package/src/evidence/evidence-factory.js +3 -0
  100. package/src/evidence/evidence-freshness.js +463 -0
  101. package/src/evidence/evidence-gap.js +76 -0
  102. package/src/evidence/evidence-lineage.js +525 -0
  103. package/src/evidence/evidence.js +136 -0
  104. package/src/evidence/freshness-validation.js +187 -0
  105. package/src/evidence/lineage.js +4 -0
  106. package/src/execution/action-evaluation.js +179 -0
  107. package/src/execution/circuit-breaker.js +70 -0
  108. package/src/execution/claim-interception.js +63 -0
  109. package/src/execution/execution-engine.js +258 -0
  110. package/src/execution/execution-intent.js +69 -0
  111. package/src/execution/execution-state.js +89 -0
  112. package/src/execution/formatting.js +60 -0
  113. package/src/execution/l1-sanitize.js +16 -0
  114. package/src/execution/l2-resource-guards.js +19 -0
  115. package/src/execution/l3-invariant-checker.js +5 -0
  116. package/src/execution/l4-containment.js +6 -0
  117. package/src/execution/runtime-guards.js +106 -0
  118. package/src/execution/strategy-identity.js +162 -0
  119. package/src/execution/tool-interceptor.js +7 -0
  120. package/src/execution/tool-result-classifier.js +63 -0
  121. package/src/integration/canary-deploy.js +143 -0
  122. package/src/integration/collector.js +59 -0
  123. package/src/integration/error-recovery.js +6 -0
  124. package/src/integration/event-bus.js +66 -0
  125. package/src/integration/health-checker.js +57 -0
  126. package/src/integration/logger.js +39 -0
  127. package/src/integration/message-handler.js +611 -0
  128. package/src/integration/opencode-db.js +153 -0
  129. package/src/integration/orchestration.js +419 -0
  130. package/src/integration/part-provenance.js +65 -0
  131. package/src/integration/rate-limiter.js +55 -0
  132. package/src/integration/release-gate.js +336 -0
  133. package/src/integration/rollback-manager.js +164 -0
  134. package/src/integration/router-adapter.js +269 -0
  135. package/src/integration/telemetry.js +45 -0
  136. package/src/integration/update-checker.js +10 -0
  137. package/src/persistence/event-sourcing.js +75 -0
  138. package/src/persistence/memory.js +501 -0
  139. package/src/persistence/migration-runner.js +16 -0
  140. package/src/persistence/persistence-manager.js +39 -0
  141. package/src/persistence/transaction-log.js +157 -0
  142. package/src/policy/action-capabilities.js +116 -0
  143. package/src/policy/action-policy.js +4 -0
  144. package/src/policy/assessment-engine.js +104 -0
  145. package/src/policy/assumption-tracking.js +66 -0
  146. package/src/policy/budget-enforcer.js +7 -0
  147. package/src/policy/config-validator.js +102 -0
  148. package/src/policy/execution-assessment.js +142 -0
  149. package/src/policy/failure-classification.js +5 -0
  150. package/src/policy/feature-flags.js +65 -0
  151. package/src/policy/governance-enforcement.js +118 -0
  152. package/src/policy/locale.js +15 -0
  153. package/src/policy/multi-signer.js +151 -0
  154. package/src/policy/policy-state-machine.js +97 -0
  155. package/src/policy/ponytail-constraint.js +38 -0
  156. package/src/policy/project-inspector.js +93 -0
  157. package/src/policy/query-builder.js +14 -0
  158. package/src/policy/request-classifier.js +79 -0
  159. package/src/policy/risk-engine.js +266 -0
  160. package/src/policy/scope-enforcement.js +140 -0
  161. package/src/policy/secret-scanner.js +68 -0
  162. package/src/policy/skill-routing.js +104 -0
  163. package/src/policy/strategy-capabilities.js +211 -0
  164. package/src/policy/strategy.js +6 -0
  165. package/src/policy/uncertainty.js +117 -0
  166. package/src/shared/container.js +44 -0
  167. package/src/shared/decision-types.js +24 -0
  168. package/src/shared/simple-test.js +160 -0
  169. package/src/shared/wam-events.js +52 -0
  170. package/src/skills/engine.js +1705 -0
  171. package/src/skills/skill-registry.js +19 -0
  172. package/src/skills/skill-routing.js +340 -0
  173. package/src/state/lifecycle-manager.js +113 -0
  174. package/src/state/operational-state.js +198 -0
  175. package/src/state/projections.js +47 -0
  176. package/src/state/replay-engine.js +5 -0
  177. package/src/state/requirement-ownership.js +20 -0
  178. package/src/state/requirement-state.js +152 -0
  179. package/src/state/run-event-identity.js +172 -0
  180. package/src/state/state-engine.js +19 -0
  181. package/src/state/state-machine.js +119 -0
  182. package/src/state/state-schema.js +318 -0
  183. package/src/state/state-store.js +335 -0
  184. package/src/state/state-transition.js +84 -0
  185. package/src/state/task-dependencies.js +287 -0
  186. package/src/state/task-execution.js +217 -0
  187. package/src/state/task-lifecycle.js +59 -0
  188. package/src/state/task-runs.js +361 -0
  189. package/src/state/task-state-file.js +86 -0
  190. package/src/state/task-state.js +41 -0
  191. package/src/state/task-status.js +54 -0
  192. package/src/state/task-store.js +48 -0
  193. package/src/state/wam-state.js +148 -0
  194. package/src/verification/completion-gate-simple.js +8 -0
  195. package/src/verification/completion-gate.js +183 -0
  196. package/src/verification/contract.js +9 -0
  197. package/src/verification/contradiction-resolution.js +255 -0
  198. package/src/verification/diagnostic-engine.js +659 -0
  199. package/src/verification/false-completion-prevention.js +100 -0
  200. package/src/verification/hook-validator.js +6 -0
  201. package/src/verification/requirements.js +6 -0
  202. package/src/verification/verification-context.js +162 -0
  203. package/src/verification/verification-lifecycle.js +66 -0
  204. package/src/verification/verification-model.js +89 -0
  205. package/src/verification/verification-persistence.js +129 -0
  206. package/src/verification/verification-policy.js +127 -0
  207. package/src/verification/verification.js +415 -0
@@ -0,0 +1,361 @@
1
+ /**
2
+ * WAM Persistent Task Runs
3
+ *
4
+ * Task = identidad persistente.
5
+ * Run = ejecución concreta, almacenada como archivo separado.
6
+ *
7
+ * Estructura:
8
+ * .wam/tasks/<taskId>/
9
+ * ├── state.yaml (estado actual canónico)
10
+ * └── runs/
11
+ * ├── <runId>.json
12
+ * └── <runId>.json
13
+ *
14
+ * Invariant: Task permanece en .wam/tasks/ aunque ejecución termine.
15
+ * Runs terminados no se mueven a .wam/history/ - permanecen bajo runs/.
16
+ */
17
+
18
+ import fs from "node:fs";
19
+ import path from "node:path";
20
+ import { getTaskState, persistTaskState } from "../skills/engine.js";
21
+
22
+ function getRunsDir(taskId, root) {
23
+ return path.join(root || process.cwd(), ".wam", "tasks", taskId, "runs");
24
+ }
25
+
26
+ function ensureRunsDir(taskId, root) {
27
+ const dir = getRunsDir(taskId, root);
28
+ if (!fs.existsSync(dir)) {
29
+ fs.mkdirSync(dir, { recursive: true });
30
+ }
31
+ return dir;
32
+ }
33
+
34
+ function generateRunId(taskId, index) {
35
+ return `${taskId}-run-${String(index).padStart(3, "0")}`;
36
+ }
37
+
38
+ function now() {
39
+ return Date.now();
40
+ }
41
+
42
+ function getRunFile(taskId, runId, root) {
43
+ return path.join(getRunsDir(taskId, root), `${runId}.json`);
44
+ }
45
+
46
+ function readRunFile(taskId, runId, root) {
47
+ const file = getRunFile(taskId, runId, root);
48
+ if (!fs.existsSync(file)) return null;
49
+ try {
50
+ return JSON.parse(fs.readFileSync(file, "utf-8"));
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+
56
+ function writeRunFile(taskId, runId, data, root) {
57
+ const file = getRunFile(taskId, runId, root);
58
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
59
+ }
60
+
61
+ function getNextRunIndex(taskId, root) {
62
+ const dir = getRunsDir(taskId, root);
63
+ if (!fs.existsSync(dir)) return 1;
64
+ const files = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
65
+ return files.length + 1;
66
+ }
67
+
68
+ export function startRun(taskId, root) {
69
+ const state = getTaskState(taskId, root);
70
+ if (!state) throw new Error(`Task ${taskId} not found`);
71
+
72
+ ensureRunsDir(taskId, root);
73
+ const index = getNextRunIndex(taskId, root);
74
+ const runId = generateRunId(taskId, index);
75
+
76
+ const run = {
77
+ id: runId,
78
+ taskId,
79
+ startedAt: now(),
80
+ status: "active",
81
+ phase: state.phase || "IMPLEMENTING",
82
+ observations: [],
83
+ decisions: [],
84
+ evidence: [],
85
+ actions: [],
86
+ outcome: null,
87
+ };
88
+
89
+ writeRunFile(taskId, runId, run, root);
90
+
91
+ state.currentRunId = runId;
92
+ persistTaskState(taskId, state, root);
93
+
94
+ return run;
95
+ }
96
+
97
+ export function closeRun(taskId, runId, status, outcome, root) {
98
+ const run = readRunFile(taskId, runId, root);
99
+ if (!run) throw new Error(`Run ${runId} not found`);
100
+
101
+ run.completedAt = now();
102
+ run.status = status;
103
+ if (outcome) run.outcome = outcome;
104
+
105
+ writeRunFile(taskId, runId, run, root);
106
+
107
+ const state = getTaskState(taskId, root);
108
+ if (state?.currentRunId === runId) {
109
+ state.currentRunId = null;
110
+ persistTaskState(taskId, state, root);
111
+ }
112
+
113
+ return run;
114
+ }
115
+
116
+ export function updateRun(taskId, runId, updates, root) {
117
+ const run = readRunFile(taskId, runId, root);
118
+ if (!run) throw new Error(`Run ${runId} not found`);
119
+
120
+ Object.assign(run, updates);
121
+ writeRunFile(taskId, runId, run, root);
122
+ return run;
123
+ }
124
+
125
+ function syncRunToState(taskId, run, root) {
126
+ const state = getTaskState(taskId, root);
127
+ if (!state) return;
128
+ if (!Array.isArray(state.executions)) state.executions = [];
129
+ const normalized = normalizeRun(run);
130
+ const idx = state.executions.findIndex((e) => e.id === run.id);
131
+ if (idx >= 0) {
132
+ state.executions[idx] = normalized;
133
+ } else {
134
+ state.executions.push(normalized);
135
+ }
136
+ persistTaskState(taskId, state, root);
137
+ }
138
+
139
+ export function addObservation(taskId, runId, text, root) {
140
+ const run = readRunFile(taskId, runId, root);
141
+ if (!run) throw new Error(`Run ${runId} not found`);
142
+
143
+ if (!run.observations) run.observations = [];
144
+ run.observations.push({
145
+ id: `obs-${run.observations.length + 1}`,
146
+ text,
147
+ timestamp: now(),
148
+ });
149
+
150
+ writeRunFile(taskId, runId, run, root);
151
+ return run;
152
+ }
153
+
154
+ export function addDecision(taskId, runId, text, root) {
155
+ const run = readRunFile(taskId, runId, root);
156
+ if (!run) throw new Error(`Run ${runId} not found`);
157
+
158
+ if (!run.decisions) run.decisions = [];
159
+ run.decisions.push({
160
+ id: `dec-${run.decisions.length + 1}`,
161
+ text,
162
+ timestamp: now(),
163
+ });
164
+
165
+ writeRunFile(taskId, runId, run, root);
166
+ return run;
167
+ }
168
+
169
+ export function addEvidence(taskId, runId, text, root) {
170
+ const run = readRunFile(taskId, runId, root);
171
+ if (!run) throw new Error(`Run ${runId} not found`);
172
+
173
+ if (!run.evidence) run.evidence = [];
174
+ run.evidence.push({
175
+ id: `ev-${run.evidence.length + 1}`,
176
+ text,
177
+ timestamp: now(),
178
+ });
179
+
180
+ writeRunFile(taskId, runId, run, root);
181
+ return run;
182
+ }
183
+
184
+ export function addAction(taskId, runId, action, root) {
185
+ const run = readRunFile(taskId, runId, root);
186
+ if (!run) throw new Error(`Run ${runId} not found`);
187
+
188
+ if (!run.actions) run.actions = [];
189
+ run.actions.push({
190
+ id: `act-${run.actions.length + 1}`,
191
+ ...action,
192
+ timestamp: now(),
193
+ });
194
+
195
+ writeRunFile(taskId, runId, run, root);
196
+ return run;
197
+ }
198
+
199
+ export function getRun(taskId, runId, root) {
200
+ return readRunFile(taskId, runId, root);
201
+ }
202
+
203
+ export function getRuns(taskId, root) {
204
+ const dir = getRunsDir(taskId, root);
205
+ if (!fs.existsSync(dir)) return [];
206
+
207
+ const files = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
208
+ const runs = [];
209
+
210
+ for (const file of files) {
211
+ try {
212
+ const run = JSON.parse(fs.readFileSync(path.join(dir, file), "utf-8"));
213
+ runs.push(run);
214
+ } catch {
215
+ // Skip invalid files
216
+ }
217
+ }
218
+
219
+ return runs.sort((a, b) => (a.startedAt || 0) - (b.startedAt || 0));
220
+ }
221
+
222
+ // Legacy-compatible accessor: returns runs with observations/decisions/evidence
223
+ // normalized to string[] form for the task-execution.js compatibility layer.
224
+ export function getRunsNormalized(taskId, root) {
225
+ return getRuns(taskId, root).map(normalizeRun);
226
+ }
227
+
228
+ export function getLastRun(taskId, root) {
229
+ const runs = getRuns(taskId, root);
230
+ return runs.length > 0 ? runs[runs.length - 1] : null;
231
+ }
232
+
233
+ export function getCurrentRun(taskId, root) {
234
+ const state = getTaskState(taskId, root);
235
+ if (!state?.currentRunId) return null;
236
+ return getRun(taskId, state.currentRunId, root);
237
+ }
238
+
239
+ export function getActiveRun(taskId, root) {
240
+ const runs = getRuns(taskId, root);
241
+ return runs.find((r) => r.status === "active") || null;
242
+ }
243
+
244
+ export function getLastRunSummary(taskId, root) {
245
+ const last = getLastRun(taskId, root);
246
+ if (!last) return null;
247
+
248
+ return {
249
+ executionId: last.id,
250
+ runId: last.id,
251
+ completedAt: last.completedAt,
252
+ status: last.status,
253
+ outcome: last.outcome,
254
+ observationsCount: last.observations?.length || 0,
255
+ decisionsCount: last.decisions?.length || 0,
256
+ evidenceCount: last.evidence?.length || 0,
257
+ };
258
+ }
259
+
260
+ export function getUnresolvedRequirements(taskId, root) {
261
+ const state = getTaskState(taskId, root);
262
+ if (!state) return [];
263
+ return (state.requirements || []).filter((r) => r.status !== "done");
264
+ }
265
+
266
+ export function getPreviousFailures(taskId, root) {
267
+ const runs = getRuns(taskId, root);
268
+ return runs.filter((r) => r.status === "failed" || r.status === "abandoned");
269
+ }
270
+
271
+ export function getPreviousDecisions(taskId, root, limit = 5) {
272
+ const runs = getRuns(taskId, root);
273
+ const allDecisions = [];
274
+
275
+ for (const run of runs) {
276
+ for (const d of run.decisions || []) {
277
+ allDecisions.push({
278
+ executionId: run.id,
279
+ runId: run.id,
280
+ decision: typeof d === "string" ? d : d.text,
281
+ timestamp: typeof d === "string" ? run.completedAt : d.timestamp,
282
+ completedAt: run.completedAt,
283
+ });
284
+ }
285
+ }
286
+
287
+ return allDecisions.slice(-limit);
288
+ }
289
+
290
+ // Normalize run fields to legacy string[] form (for task-execution.js compat)
291
+ function normalizeRun(run) {
292
+ if (!run) return run;
293
+ const normalize = (arr) =>
294
+ Array.isArray(arr) ? arr.map((item) => (typeof item === "string" ? item : item.text)) : arr;
295
+ return {
296
+ ...run,
297
+ observations: normalize(run.observations),
298
+ decisions: normalize(run.decisions),
299
+ evidence: normalize(run.evidence),
300
+ };
301
+ }
302
+ export function getResumeContext(taskId, root) {
303
+ const state = getTaskState(taskId, root);
304
+ if (!state) return null;
305
+
306
+ const lastRun = getLastRun(taskId, root);
307
+ const unresolved = getUnresolvedRequirements(taskId, root);
308
+ const failures = getPreviousFailures(taskId, root);
309
+ const recentDecisions = getPreviousDecisions(taskId, root, 3);
310
+
311
+ return {
312
+ taskSummary: state.lastAction || state.contract?.objective || "",
313
+ phase: state.phase,
314
+ currentRunId: state.currentRunId,
315
+ lastExecution: lastRun
316
+ ? {
317
+ id: lastRun.id,
318
+ status: lastRun.status,
319
+ outcome: lastRun.outcome,
320
+ completedAt: lastRun.completedAt,
321
+ }
322
+ : null,
323
+ lastRun: lastRun
324
+ ? {
325
+ id: lastRun.id,
326
+ status: lastRun.status,
327
+ outcome: lastRun.outcome,
328
+ completedAt: lastRun.completedAt,
329
+ }
330
+ : null,
331
+ unresolvedRequirements: unresolved.map((r) => ({ id: r.id, title: r.title })),
332
+ recentFailures: failures.map((f) => ({
333
+ executionId: f.id,
334
+ runId: f.id,
335
+ outcome: f.outcome,
336
+ completedAt: f.completedAt,
337
+ })),
338
+ recentDecisions,
339
+ totalExecutions: getRuns(taskId, root).length,
340
+ totalRuns: getRuns(taskId, root).length,
341
+ };
342
+ }
343
+
344
+ export function needsNewRun(taskId, root) {
345
+ const active = getActiveRun(taskId, root);
346
+ if (active) return false;
347
+
348
+ const current = getCurrentRun(taskId, root);
349
+ if (current) return false;
350
+
351
+ return true;
352
+ }
353
+
354
+ export function getRunCount(taskId, root) {
355
+ return getRuns(taskId, root).length;
356
+ }
357
+
358
+ export function getRunsByStatus(taskId, root, status) {
359
+ const runs = getRuns(taskId, root);
360
+ return runs.filter((r) => r.status === status);
361
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Task State File — Normalize and validate the persisted `.wam/task-state.json`.
3
+ *
4
+ * This is the state-file model (one file holding many tasks keyed by id), and is
5
+ * deliberately kept separate from the single live-task model in task-state.js.
6
+ * Conflating the two caused writer/reader drift and false-completion reports.
7
+ *
8
+ * Guarantee: every task read from disk has a canonical, lowercased status from
9
+ * TASK_STATUS. Unknown statuses are rejected loudly instead of being silently
10
+ * treated as "not pending" (the root cause of the "all tasks completed" lie).
11
+ */
12
+
13
+ import {
14
+ TASK_STATUS_VALUES,
15
+ normalizeStatus,
16
+ isValidStatus,
17
+ } from "./task-status.js";
18
+
19
+ export function normalizeTaskFile(taskId, task) {
20
+ if (!task || typeof task !== "object") {
21
+ return null;
22
+ }
23
+
24
+ return {
25
+ ...task,
26
+ id: task.id || taskId,
27
+ status: normalizeStatus(task.status),
28
+ validations: Array.isArray(task.validations) ? task.validations : [],
29
+ startedAt: task.startedAt || null,
30
+ completedAt: task.completedAt || null,
31
+ createdAt: task.createdAt || new Date().toISOString(),
32
+ };
33
+ }
34
+
35
+ export function normalizeStateFile(fileState) {
36
+ const state = fileState || {};
37
+ const rawTasks = state.tasks || {};
38
+ const tasks = {};
39
+
40
+ for (const [taskId, task] of Object.entries(rawTasks)) {
41
+ const normalized = normalizeTaskFile(taskId, task);
42
+ if (!normalized) {
43
+ throw new Error(`Task ${taskId} is not a valid object.`);
44
+ }
45
+ if (!isValidStatus(normalized.status)) {
46
+ throw new Error(
47
+ `Task ${taskId} has invalid status "${task.status}". ` +
48
+ `Valid values: ${TASK_STATUS_VALUES.join(", ")}.`
49
+ );
50
+ }
51
+ tasks[taskId] = normalized;
52
+ }
53
+
54
+ return {
55
+ version: state.version || "1",
56
+ tasks,
57
+ lastCheckpoint: state.lastCheckpoint || null,
58
+ };
59
+ }
60
+
61
+ export function validateStateFile(fileState) {
62
+ if (!fileState || typeof fileState !== "object") {
63
+ return { valid: false, error: "State is not an object" };
64
+ }
65
+ const tasks = fileState.tasks;
66
+ if (!tasks || typeof tasks !== "object") {
67
+ return { valid: false, error: "Missing tasks" };
68
+ }
69
+ const taskIds = Object.keys(tasks);
70
+ if (taskIds.length === 0) {
71
+ return { valid: false, error: "No tasks defined" };
72
+ }
73
+ for (const taskId of taskIds) {
74
+ const status = normalizeStatus(tasks[taskId]?.status);
75
+ if (!status) {
76
+ return {
77
+ valid: false,
78
+ error: `Task ${taskId} has invalid status: "${tasks[taskId]?.status}"`,
79
+ };
80
+ }
81
+ if (!tasks[taskId].createdAt) {
82
+ return { valid: false, error: `Task ${taskId} missing createdAt` };
83
+ }
84
+ }
85
+ return { valid: true };
86
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Task State — Create, normalize, and validate task state.
3
+ */
4
+
5
+ import { PHASE_PROPOSED } from "../integration/orchestration.js";
6
+
7
+ export function createState(taskId) {
8
+ return {
9
+ taskId,
10
+ phase: PHASE_PROPOSED,
11
+ contract: { status: "DRAFT", requirements: [] },
12
+ requirements: [],
13
+ nextAction: null,
14
+ createdAt: Date.now(),
15
+ updatedAt: Date.now(),
16
+ };
17
+ }
18
+
19
+ export function normalizeState(state) {
20
+ if (!state) return createState("unknown");
21
+
22
+ return {
23
+ ...state,
24
+ phase: state.phase || PHASE_PROPOSED,
25
+ contract: state.contract || { status: "DRAFT", requirements: [] },
26
+ requirements: state.requirements || [],
27
+ nextAction: state.nextAction || null,
28
+ createdAt: state.createdAt || Date.now(),
29
+ updatedAt: Date.now(),
30
+ };
31
+ }
32
+
33
+ export function validateState(state) {
34
+ if (!state || !state.taskId) {
35
+ return { valid: false, error: "Missing taskId" };
36
+ }
37
+ if (!state.phase) {
38
+ return { valid: false, error: "Missing phase" };
39
+ }
40
+ return { valid: true };
41
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Task Status — Single source of truth for task status values.
3
+ * Prevents case-sensitivity bugs and drift between writers/readers.
4
+ */
5
+
6
+ export const TASK_STATUS = Object.freeze({
7
+ PENDING: "pending",
8
+ RUNNING: "running",
9
+ COMPLETED: "completed",
10
+ FAILED: "failed",
11
+ });
12
+
13
+ export const TASK_STATUS_VALUES = Object.values(TASK_STATUS);
14
+
15
+ export function normalizeStatus(value) {
16
+ if (typeof value !== "string") return null;
17
+ const v = value.trim().toLowerCase();
18
+ return TASK_STATUS_VALUES.includes(v) ? v : null;
19
+ }
20
+
21
+ export function isValidStatus(value) {
22
+ return normalizeStatus(value) !== null;
23
+ }
24
+
25
+ export function countByStatus(tasks) {
26
+ const counts = {
27
+ pending: 0,
28
+ running: 0,
29
+ completed: 0,
30
+ failed: 0,
31
+ total: 0,
32
+ };
33
+
34
+ for (const task of Object.values(tasks || {})) {
35
+ const normalized = normalizeStatus(task?.status);
36
+ if (normalized && counts.hasOwnProperty(normalized)) {
37
+ counts[normalized]++;
38
+ }
39
+ counts.total++;
40
+ }
41
+
42
+ return counts;
43
+ }
44
+
45
+ export function getNextPendingTask(tasks) {
46
+ if (!tasks) return null;
47
+ const taskIds = Object.keys(tasks).sort();
48
+ for (const taskId of taskIds) {
49
+ if (normalizeStatus(tasks[taskId]?.status) === TASK_STATUS.PENDING) {
50
+ return taskId;
51
+ }
52
+ }
53
+ return null;
54
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Task Store — IO operations for task persistence.
3
+ */
4
+
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+
8
+ const TASK_FILE = "task.json";
9
+
10
+ export function taskFilePath(taskId, root) {
11
+ return path.join(root || process.cwd(), ".wam", "tasks", taskId, TASK_FILE);
12
+ }
13
+
14
+ export function loadTask(taskId, root) {
15
+ const file = taskFilePath(taskId, root);
16
+ if (!fs.existsSync(file)) return null;
17
+ try {
18
+ return JSON.parse(fs.readFileSync(file, "utf-8"));
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ export function saveTask(taskId, state, root) {
25
+ const dir = path.join(root || process.cwd(), ".wam", "tasks", taskId);
26
+ fs.mkdirSync(dir, { recursive: true });
27
+ const file = path.join(dir, TASK_FILE);
28
+ fs.writeFileSync(file, JSON.stringify(state, null, 2));
29
+ }
30
+
31
+ export function taskExists(taskId, root) {
32
+ return fs.existsSync(taskFilePath(taskId, root));
33
+ }
34
+
35
+ export function listTasks(root) {
36
+ const dir = path.join(root || process.cwd(), ".wam", "tasks");
37
+ if (!fs.existsSync(dir)) return [];
38
+ return fs.readdirSync(dir, { withFileTypes: true })
39
+ .filter(d => d.isDirectory())
40
+ .map(d => d.name);
41
+ }
42
+
43
+ export function deleteTask(taskId, root) {
44
+ const dir = path.join(root || process.cwd(), ".wam", "tasks", taskId);
45
+ if (fs.existsSync(dir)) {
46
+ fs.rmSync(dir, { recursive: true, force: true });
47
+ }
48
+ }