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

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,415 @@
1
+ /**
2
+ * Verification Engine — WAM 1.1 (openspec/changes/add-verifiable-task-completion).
3
+ *
4
+ * Ejecuta checks de verificación definidos en el Completion Contract aprobado
5
+ * y genera evidencia machine-verifiable. Aislado del adapter OpenCode
6
+ * (index.js) y del razonamiento (engine.js): solo ejecuta, observa y registra.
7
+ *
8
+ * Reglas:
9
+ * - Solo ejecuta comandos del contract aprobado (el engine NUNCA ejecuta
10
+ * texto libre proveniente del campo `evidence` del agente).
11
+ * - Ejecución acotada: timeout + límite de stdout/stderr.
12
+ * - La evidencia persistente usa output_hash, no logs completos.
13
+ */
14
+
15
+ import { execFile, execFileSync } from "node:child_process";
16
+ import { createHash } from "node:crypto";
17
+ import fs from "node:fs";
18
+ import path from "node:path";
19
+ import { fileURLToPath } from "node:url";
20
+ import { createRequire } from "node:module";
21
+
22
+ const require = createRequire(import.meta.url);
23
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
+
25
+ export const DEFAULT_TIMEOUT_MS = 30000;
26
+ export const MAX_OUTPUT_BYTES = 64 * 1024;
27
+ const GIT_TIMEOUT_MS = 5000;
28
+
29
+ function nowIso() {
30
+ return new Date().toISOString();
31
+ }
32
+
33
+ function truncate(buf) {
34
+ if (buf.length <= MAX_OUTPUT_BYTES) return { text: buf.toString("utf-8"), truncated: false };
35
+ return { text: buf.subarray(0, MAX_OUTPUT_BYTES).toString("utf-8"), truncated: true };
36
+ }
37
+
38
+ function sha256(text) {
39
+ return createHash("sha256").update(text, "utf-8").digest("hex");
40
+ }
41
+
42
+ /**
43
+ * Captura el estado del repositorio en `cwd`. Fail-safe: si no es un repo
44
+ * git o git no está disponible, devuelve head/dirty en null.
45
+ *
46
+ * Cache: 5s TTL por directorio para evitar llamadas git redundantes
47
+ * durante verificaciones con múltiples checks.
48
+ */
49
+ const REPO_STATE_CACHE_TTL_MS = 5000;
50
+ const _repoStateCache = new Map();
51
+
52
+ export function captureRepositoryState(cwd) {
53
+ const root = cwd || process.cwd();
54
+ const cached = _repoStateCache.get(root);
55
+ if (cached && (Date.now() - cached.ts) < REPO_STATE_CACHE_TTL_MS) {
56
+ return cached.state;
57
+ }
58
+ let state;
59
+ try {
60
+ const head = execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, timeout: GIT_TIMEOUT_MS, stdio: ["ignore", "pipe", "ignore"] })
61
+ .toString()
62
+ .trim();
63
+ let dirty = null;
64
+ try {
65
+ const porcelain = execFileSync("git", ["status", "--porcelain"], { cwd: root, timeout: GIT_TIMEOUT_MS, stdio: ["ignore", "pipe", "ignore"] })
66
+ .toString()
67
+ .trim();
68
+ dirty = porcelain.length > 0;
69
+ } catch {}
70
+ state = { root, head: head || null, working_tree_dirty: dirty };
71
+ } catch {
72
+ state = { root, head: null, working_tree_dirty: null };
73
+ }
74
+ _repoStateCache.set(root, { state, ts: Date.now() });
75
+ if (_repoStateCache.size > 50) {
76
+ const oldest = _repoStateCache.keys().next().value;
77
+ _repoStateCache.delete(oldest);
78
+ }
79
+ return state;
80
+ }
81
+
82
+ export const VALID_CHECK_TYPES = new Set(["command", "service", "e2e"]);
83
+ export { validateCheck };
84
+
85
+ async function executeServiceCheck(check, timeout) {
86
+ const startedHr = Date.now();
87
+ try {
88
+ const controller = new AbortController();
89
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
90
+
91
+ const response = await fetch(check.url, {
92
+ method: "GET",
93
+ signal: controller.signal,
94
+ headers: { "User-Agent": "WAM-Verification/1.0" }
95
+ });
96
+ clearTimeout(timeoutId);
97
+
98
+ const text = await response.text();
99
+ return {
100
+ status: response.ok ? "PASS" : "FAIL",
101
+ exit_code: response.ok ? 0 : response.status,
102
+ diagnostic: `HTTP ${response.status} ${response.statusText} - ${text.slice(0, 500)}`,
103
+ output_hash: sha256(text),
104
+ output_truncated: text.length > MAX_OUTPUT_BYTES,
105
+ response_status: response.status,
106
+ response_status_text: response.statusText,
107
+ };
108
+ } catch (error) {
109
+ if (error.name === "AbortError") {
110
+ return { status: "TIMEOUT", exit_code: null, diagnostic: `timeout tras ${timeout}ms` };
111
+ }
112
+ return { status: "ERROR", exit_code: null, diagnostic: error.message };
113
+ }
114
+ }
115
+
116
+ async function executeE2eCheck(check, timeout, cwd) {
117
+ const startedHr = Date.now();
118
+ const playwrightScript = `
119
+ import { chromium } from 'playwright';
120
+ (async () => {
121
+ const browser = await chromium.launch();
122
+ const page = await browser.newPage();
123
+ const results = [];
124
+ try {
125
+ await page.goto('${check.url}', { timeout: ${timeout} });
126
+ for (const selector of ${JSON.stringify(check.selectors)}) {
127
+ try {
128
+ const element = await page.locator(selector).first();
129
+ const isVisible = await element.isVisible();
130
+ const text = isVisible ? await element.textContent() : null;
131
+ results.push({ selector, found: true, visible: isVisible, text: text?.slice(0, 200) });
132
+ } catch (e) {
133
+ results.push({ selector, found: false, error: e.message });
134
+ }
135
+ }
136
+ await page.waitForLoadState('networkidle');
137
+ } catch (e) {
138
+ results.push({ error: e.message });
139
+ }
140
+ await browser.close();
141
+ console.log(JSON.stringify(results));
142
+ })();
143
+ `;
144
+
145
+ const scriptPath = path.join(cwd || process.cwd(), ".wam", "temp-e2e-check.mjs");
146
+
147
+ try {
148
+ fs.mkdirSync(path.dirname(scriptPath), { recursive: true });
149
+ fs.writeFileSync(scriptPath, playwrightScript);
150
+ } catch (e) {
151
+ return { status: "ERROR", exit_code: null, diagnostic: `No se pudo crear script: ${e.message}` };
152
+ }
153
+
154
+ return new Promise((resolve) => {
155
+ execFile(
156
+ "node",
157
+ [scriptPath],
158
+ { cwd: cwd || process.cwd(), timeout: timeout + 10000 },
159
+ (error, stdout, stderr) => {
160
+ try { fs.unlinkSync(scriptPath); } catch {}
161
+
162
+ const completed_at = nowIso();
163
+ let results = [];
164
+ let output = stdout || stderr;
165
+
166
+ try {
167
+ results = JSON.parse(output);
168
+ } catch {
169
+ results = [{ error: "parse failed", raw: output.slice(0, 500) }];
170
+ }
171
+
172
+ const allFound = results.every(r => r.found !== false && r.error == null);
173
+ const base = {
174
+ check_id: check.id,
175
+ requirement_id: check.requirement_id,
176
+ started_at: new Date(Date.now() - (Date.now() - startedHr)).toISOString(),
177
+ completed_at,
178
+ duration_ms: Date.now() - startedHr,
179
+ output_hash: sha256(output),
180
+ output_truncated: output.length > MAX_OUTPUT_BYTES,
181
+ repository_state: captureRepositoryState(cwd),
182
+ };
183
+
184
+ if (error && (error.killed || error.code === "ETIMEDOUT")) {
185
+ return resolve({ ...base, status: "TIMEOUT", exit_code: null, diagnostic: "Playwright timeout" });
186
+ }
187
+ if (error) {
188
+ return resolve({ ...base, status: "FAIL", exit_code: error.code, diagnostic: output.slice(-500), e2e_results: results });
189
+ }
190
+
191
+ return resolve({
192
+ ...base,
193
+ status: allFound ? "PASS" : "FAIL",
194
+ exit_code: allFound ? 0 : 1,
195
+ diagnostic: JSON.stringify(results).slice(0, 500),
196
+ e2e_results: results
197
+ });
198
+ }
199
+ );
200
+ });
201
+ }
202
+
203
+ function validateCheck(check) {
204
+ if (!check || typeof check !== "object") return "check inválido";
205
+ if (!VALID_CHECK_TYPES.has(check.type)) return `tipo no soportado: ${check.type}`;
206
+
207
+ if (check.type === "command") {
208
+ if (!check.command || typeof check.command !== "string" || !check.command.trim()) {
209
+ return "command vacío";
210
+ }
211
+ const cwd = check.cwd || process.cwd();
212
+ try {
213
+ if (!fs.existsSync(cwd) || !fs.statSync(cwd).isDirectory()) return `cwd inexistente: ${cwd}`;
214
+ } catch {
215
+ return `cwd ilegible: ${cwd}`;
216
+ }
217
+ }
218
+
219
+ if (check.type === "service") {
220
+ if (!check.url || typeof check.url !== "string") return "url vacía para check service";
221
+ try {
222
+ new URL(check.url);
223
+ } catch {
224
+ return `url inválida: ${check.url}`;
225
+ }
226
+ }
227
+
228
+ if (check.type === "e2e") {
229
+ if (!check.url || typeof check.url !== "string") return "url vacía para check e2e";
230
+ if (!check.selectors || !Array.isArray(check.selectors) || check.selectors.length === 0) {
231
+ return "selectors vacíos para check e2e";
232
+ }
233
+ }
234
+
235
+ return null;
236
+ }
237
+
238
+ /**
239
+ * Ejecuta un VerificationCheck y devuelve un VerificationResult normalizado:
240
+ * { check_id, requirement_id, status: PASS|FAIL|TIMEOUT|ERROR, exit_code,
241
+ * started_at, completed_at, duration_ms, output_hash, output_truncated,
242
+ * diagnostic, repository_state }.
243
+ */
244
+ export async function executeCheck(check, { timeout_ms = DEFAULT_TIMEOUT_MS } = {}) {
245
+ const started_at = nowIso();
246
+ const startedHr = Date.now();
247
+ const invalid = validateCheck(check);
248
+ const repository_state = captureRepositoryState(check?.cwd);
249
+ if (invalid) {
250
+ return {
251
+ check_id: check?.id || null,
252
+ requirement_id: check?.requirement_id || null,
253
+ status: "ERROR",
254
+ exit_code: null,
255
+ started_at,
256
+ completed_at: nowIso(),
257
+ duration_ms: Date.now() - startedHr,
258
+ output_hash: sha256(""),
259
+ output_truncated: false,
260
+ diagnostic: invalid.slice(0, 500),
261
+ repository_state,
262
+ };
263
+ }
264
+
265
+ if (check.type === "service") {
266
+ const timeout = Number(check.timeout_ms ?? timeout_ms);
267
+ const result = await executeServiceCheck(check, timeout);
268
+ return {
269
+ check_id: check.id || null,
270
+ requirement_id: check.requirement_id || null,
271
+ started_at,
272
+ completed_at: nowIso(),
273
+ duration_ms: Date.now() - startedHr,
274
+ ...result,
275
+ repository_state,
276
+ };
277
+ }
278
+
279
+ if (check.type === "e2e") {
280
+ const timeout = Number(check.timeout_ms ?? timeout_ms);
281
+ const result = await executeE2eCheck(check, timeout, check.cwd);
282
+ return {
283
+ check_id: check.id || null,
284
+ requirement_id: check.requirement_id || null,
285
+ started_at,
286
+ completed_at: nowIso(),
287
+ duration_ms: Date.now() - startedHr,
288
+ ...result,
289
+ repository_state,
290
+ };
291
+ }
292
+
293
+ const timeout = Number(check.timeout_ms ?? timeout_ms);
294
+ return new Promise((resolve) => {
295
+ execFile(
296
+ "sh",
297
+ ["-c", check.command],
298
+ { cwd: check.cwd || process.cwd(), timeout: Number.isFinite(timeout) && timeout > 0 ? timeout : DEFAULT_TIMEOUT_MS, maxBuffer: MAX_OUTPUT_BYTES * 4 },
299
+ (error, stdout, stderr) => {
300
+ const completed_at = nowIso();
301
+ const out = truncate(Buffer.concat([Buffer.from(stdout || ""), Buffer.from(stderr || "")]));
302
+ const base = {
303
+ check_id: check.id || null,
304
+ requirement_id: check.requirement_id || null,
305
+ started_at,
306
+ completed_at,
307
+ duration_ms: Date.now() - startedHr,
308
+ output_hash: sha256(out.text),
309
+ output_truncated: out.truncated,
310
+ repository_state,
311
+ };
312
+ if (error && (error.killed || error.code === "ETIMEDOUT" || /timed out/i.test(error.message || ""))) {
313
+ return resolve({ ...base, status: "TIMEOUT", exit_code: null, diagnostic: `timeout tras ${timeout}ms`.slice(0, 500) });
314
+ }
315
+ if (error && error.code === "ENOENT") {
316
+ return resolve({ ...base, status: "ERROR", exit_code: null, diagnostic: String(error.message || error).slice(0, 500) });
317
+ }
318
+ const exit_code = typeof error?.code === "number" ? error.code : 0;
319
+ if (exit_code === 0) {
320
+ return resolve({ ...base, status: "PASS", exit_code, diagnostic: out.text.slice(-500) });
321
+ }
322
+ return resolve({ ...base, status: "FAIL", exit_code, diagnostic: out.text.slice(-500) });
323
+ }
324
+ );
325
+ });
326
+ }
327
+
328
+ /**
329
+ * Genera la Evidence machine-verifiable de un check ejecutado.
330
+ */
331
+ export function createEvidence(check, result) {
332
+ const base = {
333
+ id: `ev-${result.check_id || "unknown"}-${Date.now().toString(36)}`,
334
+ requirement_id: result.requirement_id || check?.requirement_id || null,
335
+ check_id: result.check_id,
336
+ check_type: check?.type || "command",
337
+ exit_code: result.exit_code,
338
+ started_at: result.started_at,
339
+ completed_at: result.completed_at,
340
+ output_hash: result.output_hash,
341
+ output_truncated: result.output_truncated,
342
+ repository_state: result.repository_state,
343
+ };
344
+
345
+ if (check?.type === "command") {
346
+ return { ...base, type: "command", command: check?.command || null, cwd: check?.cwd || process.cwd() };
347
+ }
348
+ if (check?.type === "service") {
349
+ return {
350
+ ...base,
351
+ type: "service",
352
+ url: check?.url || null,
353
+ response_status: result.response_status,
354
+ response_status_text: result.response_status_text,
355
+ diagnostic: result.diagnostic,
356
+ };
357
+ }
358
+ if (check?.type === "e2e") {
359
+ return {
360
+ ...base,
361
+ type: "e2e",
362
+ url: check?.url || null,
363
+ selectors: check?.selectors || [],
364
+ e2e_results: result.e2e_results,
365
+ diagnostic: result.diagnostic,
366
+ };
367
+ }
368
+
369
+ return { ...base, type: "command", command: check?.command || null, cwd: check?.cwd || process.cwd() };
370
+ }
371
+
372
+ /**
373
+ * Evalúa un requirement a partir de sus checks (todos obligatorios):
374
+ * VERIFIED solo si todos los resultados son PASS y check_id corresponde 1:1.
375
+ * Sin verificación parcial ni duplicados.
376
+ */
377
+ export function evaluateRequirement(checks, results) {
378
+ const list = Array.isArray(checks) ? checks : [];
379
+ const res = Array.isArray(results) ? results : [];
380
+ if (!list.length) return { status: "VERIFYING", reason: "sin checks definidos" };
381
+ if (res.length !== list.length) return { status: "VERIFYING", reason: "checks pendientes de ejecutar" };
382
+ // Validación uno-a-uno por check_id
383
+ const checkIds = new Set(list.map((c) => c.check_id || c.id || ""));
384
+ const resultIds = new Set(res.map((r) => r.check_id || r.id || ""));
385
+ if ([...checkIds].every((id) => resultIds.has(id)) === false) {
386
+ return { status: "VERIFYING", reason: "mapeo check_id incompleto o duplicado" };
387
+ }
388
+ // Detectar check_id duplicados en results
389
+ const seen = new Set();
390
+ for (const r of res) {
391
+ const id = r.check_id || r.id || "";
392
+ if (seen.has(id)) return { status: "VERIFYING", reason: `check_id duplicado: ${id}` };
393
+ seen.add(id);
394
+ }
395
+ const failed = res.find((r) => r.status !== "PASS");
396
+ if (failed) return { status: "VERIFYING", reason: `${failed.check_id || "?"}: ${failed.status}` };
397
+ return { status: "VERIFIED", reason: "todos los checks pasaron y mapeo es uno-a-uno" };
398
+ }
399
+
400
+ /**
401
+ * Verifica un requirement completo: ejecuta sus checks en orden,
402
+ * genera evidencia por cada uno y devuelve el estado agregado.
403
+ */
404
+ export async function verifyRequirement(checks, opts = {}) {
405
+ const list = Array.isArray(checks) ? checks : [];
406
+ const results = [];
407
+ const evidence = [];
408
+ for (const check of list) {
409
+ const result = await executeCheck(check, opts);
410
+ results.push(result);
411
+ evidence.push(createEvidence(check, result));
412
+ if (result.status !== "PASS") break;
413
+ }
414
+ return { ...evaluateRequirement(list, results), results, evidence };
415
+ }