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,525 @@
1
+ /**
2
+ * Evidence Lineage Invalidation
3
+ *
4
+ * Formaliza la relación causal:
5
+ * Requirement → Hypothesis → Experiment → Observation → Evidence
6
+ *
7
+ * Cada evidencia indica:
8
+ * - qué requisito soporta
9
+ * - qué hipótesis la produjo
10
+ * - qué experimento la generó
11
+ * - qué observación la originó
12
+ *
13
+ * Estados de evidencia:
14
+ * - valid: vigente y satisface el requisito
15
+ * - superseded: hay evidencia más reciente
16
+ * - invalidated: condición de entorno cambió
17
+ * - stale: hipótesis rechazada
18
+ * - insufficient: no basta por sí sola
19
+ * - unverified: sin verificación
20
+ * - stale: evidencia dependiente de hipótesis rechazada
21
+ */
22
+
23
+ import { getRun } from "../state/task-runs.js";
24
+ import { getTaskState } from "../skills/engine.js";
25
+
26
+ function getLineageDir(taskId, root) {
27
+ return path.join(root || process.cwd(), ".wam", "tasks", taskId, "lineage");
28
+ }
29
+
30
+ function ensureLineageDir(taskId, root) {
31
+ const dir = getLineageDir(taskId, root);
32
+ if (!fs.existsSync(dir)) {
33
+ fs.mkdirSync(dir, { recursive: true });
34
+ }
35
+ return dir;
36
+ }
37
+
38
+ import fs from "node:fs";
39
+ import path from "node:path";
40
+ import crypto from "node:crypto";
41
+
42
+ /**
43
+ * Generate a unique evidence ID.
44
+ * Uses timestamp + random suffix to avoid collisions.
45
+ * Never depends on file count (safe after deletions and concurrent access).
46
+ *
47
+ * @param {string} taskId
48
+ * @param {string} root - Project root (required, not process.cwd())
49
+ * @returns {string} Evidence ID like "ev-1789395644381-a1b2c3d4"
50
+ */
51
+ function generateEvidenceId(taskId, root) {
52
+ const timestamp = Date.now();
53
+ const random = crypto.randomUUID().slice(0, 8);
54
+ return `ev-${timestamp}-${random}`;
55
+ }
56
+
57
+ function getEvidenceFile(taskId, evidenceId, root) {
58
+ return path.join(getLineageDir(taskId, root), `${evidenceId}.json`);
59
+ }
60
+
61
+ function readEvidenceFile(taskId, evidenceId, root) {
62
+ const file = getEvidenceFile(taskId, evidenceId, root);
63
+ if (!fs.existsSync(file)) return null;
64
+ try {
65
+ return JSON.parse(fs.readFileSync(file, "utf-8"));
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
70
+
71
+ function writeEvidenceFile(taskId, evidenceId, data, root) {
72
+ const file = getEvidenceFile(taskId, evidenceId, root);
73
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
74
+ }
75
+
76
+ /**
77
+ * @typedef {Object} Evidence
78
+ * @property {string} id
79
+ * @property {string} requirementId
80
+ * @property {string} hypothesisId
81
+ * @property {string} experimentId
82
+ * @property {string} observationId
83
+ * @property {string} content
84
+ * @property {string} type - "test", "output", "verification", "inspection", "manual"
85
+ * @property {string} source - "tool", "user", "system"
86
+ * @property {number} createdAt
87
+ * @property {"valid"|"superseded"|"invalidated"|"insufficient"|"unverified"|"stale"} status
88
+ * @property {Verification[]} [verifications]
89
+ * @property {EnvironmentFingerprint} [environment]
90
+ * @property {string} [supersededBy]
91
+ * @property {string} [invalidationReason]
92
+ */
93
+
94
+ /**
95
+ * @typedef {Object} Verification
96
+ * @property {string} criterion
97
+ * @property {string} result - "PASS" | "FAIL" | "PARTIAL"
98
+ * @property {number} verifiedAt
99
+ * @property {string} [verifiedBy]
100
+ */
101
+
102
+ /**
103
+ * @typedef {Object} EnvironmentFingerprint
104
+ * @property {string} os
105
+ * @property {string} nodeVersion
106
+ * @property {string} [executable]
107
+ * @property {string} [version]
108
+ * @property {string} [repositoryRevision]
109
+ */
110
+
111
+ export function createEvidence(taskId, evidence, root) {
112
+ ensureLineageDir(taskId, root);
113
+ const dir = getLineageDir(taskId, root);
114
+ const id = evidence.id || `ev-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
115
+ const fullEvidence = {
116
+ ...evidence,
117
+ id,
118
+ status: evidence.status || "unverified",
119
+ createdAt: evidence.createdAt || new Date().toISOString(),
120
+ };
121
+ writeEvidenceFile(taskId, id, fullEvidence, root);
122
+ return fullEvidence;
123
+ }
124
+
125
+ export function linkEvidenceToRequirement(evidenceId, requirementId, hypothesisId, experimentId, observationId, taskId, root) {
126
+ // Link evidence to the causal chain: Requirement → Hypothesis → Experiment → Observation → Evidence
127
+ if (!taskId) {
128
+ return {
129
+ evidenceId,
130
+ requirementId,
131
+ hypothesisId,
132
+ experimentId,
133
+ observationId,
134
+ chain: ["requirement", "hypothesis", "experiment", "observation", "evidence"],
135
+ };
136
+ }
137
+
138
+ const evidence = readEvidenceFile(taskId, evidenceId, root);
139
+ if (!evidence) throw new Error(`Evidence ${evidenceId} not found`);
140
+
141
+ evidence.requirementId = requirementId;
142
+ evidence.hypothesisId = hypothesisId;
143
+ evidence.experimentId = experimentId;
144
+ evidence.observationId = observationId;
145
+
146
+ writeEvidenceFile(taskId, evidenceId, evidence, root);
147
+ return evidence;
148
+ }
149
+
150
+ export function invalidateDependentEvidence(taskId, hypothesisIdOrRequirementId, reason, root) {
151
+ const allEvidence = getAllEvidence(taskId, root);
152
+ const dependent = allEvidence.filter(ev =>
153
+ (ev.hypothesisId === hypothesisIdOrRequirementId || ev.requirementId === hypothesisIdOrRequirementId) &&
154
+ ev.status !== "stale"
155
+ );
156
+ for (const ev of dependent) {
157
+ ev.status = "stale";
158
+ ev.invalidationReason = reason;
159
+ writeEvidenceFile(taskId, ev.id, ev, root);
160
+ }
161
+ return dependent;
162
+ }
163
+
164
+ export function getEvidence(taskId, evidenceId, root) {
165
+ return readEvidenceFile(taskId, evidenceId, root);
166
+ }
167
+
168
+ export function getAllEvidence(taskId, root) {
169
+ const dir = getLineageDir(taskId, root);
170
+ if (!fs.existsSync(dir)) return [];
171
+
172
+ const files = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
173
+ const evidenceList = [];
174
+
175
+ for (const file of files) {
176
+ try {
177
+ const ev = JSON.parse(fs.readFileSync(path.join(dir, file), "utf-8"));
178
+ evidenceList.push(ev);
179
+ } catch {
180
+ // Skip invalid files
181
+ }
182
+ }
183
+
184
+ return evidenceList.sort((a, b) => {
185
+ const ta = Date.parse(a.createdAt) || 0;
186
+ const tb = Date.parse(b.createdAt) || 0;
187
+ if (ta !== tb) return tb - ta;
188
+ if (a.id && b.id && a.id !== b.id) return a.id < b.id ? 1 : -1;
189
+ return 0;
190
+ });
191
+ }
192
+
193
+ export function getEvidenceForRequirement(taskId, requirementId, root) {
194
+ const all = getAllEvidence(taskId, root);
195
+ return all.filter((ev) => ev.requirementId === requirementId);
196
+ }
197
+
198
+ export function getValidEvidence(taskId, requirementId, root) {
199
+ const forReq = getEvidenceForRequirement(taskId, requirementId, root);
200
+ return forReq.filter((ev) => ev.status === "valid");
201
+ }
202
+
203
+ export function verifyEvidence(taskId, evidenceId, criterion, result, root) {
204
+ const evidence = readEvidenceFile(taskId, evidenceId, root);
205
+ if (!evidence) throw new Error(`Evidence ${evidenceId} not found`);
206
+ if (!evidence.verifications) evidence.verifications = [];
207
+ evidence.verifications.push({
208
+ criterion,
209
+ result,
210
+ verifiedAt: Date.now(),
211
+ });
212
+ if (result === "PASS") {
213
+ const hasFullLineage =
214
+ evidence.requirementId && evidence.hypothesisId &&
215
+ evidence.experimentId && evidence.observationId;
216
+ if (hasFullLineage) {
217
+ evidence.status = "valid";
218
+ } else {
219
+ evidence.status = "insufficient";
220
+ }
221
+ } else if (result === "PARTIAL") {
222
+ evidence.status = "insufficient";
223
+ }
224
+ writeEvidenceFile(taskId, evidenceId, evidence, root);
225
+ return evidence;
226
+ }
227
+
228
+ export function supersedeEvidence(taskId, evidenceId, newEvidenceId, root) {
229
+ const evidence = readEvidenceFile(taskId, evidenceId, root);
230
+ if (!evidence) throw new Error(`Evidence ${evidenceId} not found`);
231
+ evidence.status = "superseded";
232
+ evidence.supersededBy = newEvidenceId;
233
+ writeEvidenceFile(taskId, evidenceId, evidence, root);
234
+ return evidence;
235
+ }
236
+
237
+ export function invalidateEvidence(taskId, evidenceId, reason, root) {
238
+ const evidence = readEvidenceFile(taskId, evidenceId, root);
239
+ if (!evidence) throw new Error(`Evidence ${evidenceId} not found`);
240
+ evidence.status = "invalidated";
241
+ evidence.invalidationReason = reason;
242
+ writeEvidenceFile(taskId, evidenceId, evidence, root);
243
+ return evidence;
244
+ }
245
+
246
+ export function detectOrphanedEvidence(taskId, root) {
247
+ const state = getTaskState(taskId, root);
248
+ if (!state) return [];
249
+ const allEvidence = getAllEvidence(taskId, root);
250
+ const requirementIds = new Set((state.requirements || []).map((r) => r.id));
251
+ return allEvidence.filter((ev) => !requirementIds.has(ev.requirementId));
252
+ }
253
+
254
+ export function detectInvalidatedEvidence(taskId, currentEnvironment, root) {
255
+ const allEvidence = getAllEvidence(taskId, root);
256
+ const newEnv = currentEnvironment || {};
257
+ const invalidated = allEvidence.filter((ev) => {
258
+ if (ev.status !== "valid" && ev.status !== "superseded") return false;
259
+ const oldEnv = ev.environment || {};
260
+ return Object.keys(oldEnv).some((key) => {
261
+ const oldVal = oldEnv[key];
262
+ const newVal = newEnv[key];
263
+ if (oldVal === undefined && newVal === undefined) return false;
264
+ return oldVal !== newVal;
265
+ });
266
+ });
267
+ return invalidated.map((ev) => ({ evidence: ev, previousEnvironment: ev.environment, currentEnvironment: newEnv }));
268
+ }
269
+
270
+ export function getLineageForRequirement(taskId, requirementId, root) {
271
+ const evidence = getEvidenceForRequirement(taskId, requirementId, root);
272
+
273
+ const lineage = {
274
+ requirement: requirementId,
275
+ evidence: [],
276
+ };
277
+
278
+ for (const ev of evidence) {
279
+ const entry = {
280
+ evidence: ev,
281
+ actions: [],
282
+ observations: [],
283
+ };
284
+
285
+ if (ev.runId) {
286
+ const run = getRun(taskId, ev.runId, root);
287
+ if (run) {
288
+ const action = (run.actions || []).find((a) => a.id === ev.actionId);
289
+ if (action) entry.actions.push(action);
290
+
291
+ const obs = (run.observations || []).find((o) => o.id === ev.observationId);
292
+ if (obs) entry.observations.push(obs);
293
+ }
294
+ }
295
+
296
+ lineage.evidence.push(entry);
297
+ }
298
+
299
+ return lineage;
300
+ }
301
+
302
+ export function isRequirementSatisfied(taskId, requirementId, root) {
303
+ const validEvidence = getValidEvidence(taskId, requirementId, root);
304
+ return validEvidence.length > 0;
305
+ }
306
+
307
+ export function getSatisfiedRequirements(taskId, root) {
308
+ const state = getTaskState(taskId, root);
309
+ if (!state) return [];
310
+
311
+ const satisfied = [];
312
+ for (const req of state.requirements || []) {
313
+ if (isRequirementSatisfied(taskId, req.id, root)) {
314
+ satisfied.push(req.id);
315
+ }
316
+ }
317
+ return satisfied;
318
+ }
319
+
320
+ export function getUnsatisfiedRequirements(taskId, root) {
321
+ const state = getTaskState(taskId, root);
322
+ if (!state) return [];
323
+
324
+ return (state.requirements || []).filter((req) => !isRequirementSatisfied(taskId, req.id, root));
325
+ }
326
+
327
+ export function getCompletionStatus(taskId, root) {
328
+ const state = getTaskState(taskId, root);
329
+ if (!state) return null;
330
+ const allRequirements = state.requirements || [];
331
+ const satisfied = getSatisfiedRequirements(taskId, root);
332
+ const orphaned = detectOrphanedEvidence(taskId, root);
333
+ const unsatisfied = allRequirements.filter((r) => !satisfied.includes(r.id));
334
+ return {
335
+ taskId,
336
+ phase: state.phase,
337
+ totalRequirements: allRequirements.length,
338
+ satisfiedCount: satisfied.length,
339
+ satisfied,
340
+ unsatisfied,
341
+ orphanedEvidence: orphaned,
342
+ canComplete: unsatisfied.length === 0 && orphaned.length === 0,
343
+ timestamp: Date.now(),
344
+ };
345
+ }
346
+
347
+ export function getEvidenceSummary(taskId, root) {
348
+ const evidenceList = getAllEvidence(taskId, root);
349
+ const byStatus = {};
350
+ for (const ev of evidenceList) {
351
+ byStatus[ev.status] = (byStatus[ev.status] || 0) + 1;
352
+ }
353
+ const valid = evidenceList.filter((ev) => ev.status === "valid");
354
+ return {
355
+ total: evidenceList.length,
356
+ byStatus: byStatus,
357
+ validCount: valid.length,
358
+ lineageIntegrity: !detectOrphanedEvidence(taskId, root).length,
359
+ };
360
+ }
361
+
362
+
363
+
364
+ /**
365
+ * Get complete lineage chain for a requirement.
366
+ * Requirement → Hypothesis → Experiment → Observation → Evidence
367
+ *
368
+ * @param {string} requirementIdOrTaskId
369
+ * @param {string} [requirementId]
370
+ * @param {string} [root]
371
+ * @returns {Object} Complete lineage chain
372
+ */
373
+ export function getLineage(requirementIdOrTaskId, requirementId, root) {
374
+ // Overload: getLineage(requirementId) for in-memory chain description,
375
+ // or getLineage(taskId, requirementId, root) for persisted lineage.
376
+ if (requirementId === undefined) {
377
+ const reqId = requirementIdOrTaskId;
378
+ return {
379
+ requirementId: reqId,
380
+ chain: ["requirement", "hypothesis", "experiment", "observation", "evidence"],
381
+ hypotheses: [],
382
+ experiments: [],
383
+ observations: [],
384
+ evidence: [],
385
+ };
386
+ }
387
+ const taskId = requirementIdOrTaskId;
388
+ const lineage = getLineageForRequirement(taskId, requirementId, root);
389
+ const evidence = getEvidenceForRequirement(taskId, requirementId, root);
390
+ return {
391
+ requirementId,
392
+ chain: ["requirement", "hypothesis", "experiment", "observation", "evidence"],
393
+ hypotheses: [...new Set(evidence.map((e) => e.hypothesisId).filter(Boolean))],
394
+ experiments: [...new Set(evidence.map((e) => e.experimentId).filter(Boolean))],
395
+ observations: [...new Set(evidence.map((e) => e.observationId).filter(Boolean))],
396
+ evidence,
397
+ requirement: lineage.requirement,
398
+ evidenceEntries: lineage.evidence,
399
+ };
400
+ }
401
+
402
+
403
+
404
+ /**
405
+ * True when a requirement has any STALE evidence that would block verification.
406
+ */
407
+ export function hasStaleEvidence(taskId, requirementId, root) {
408
+ const evidence = getEvidenceForRequirement(taskId, requirementId, root);
409
+ return evidence.some((ev) => ev.status === "stale" || ev.status === "STALE");
410
+ }
411
+
412
+ /**
413
+ * Structural ID enforcement (Completion Integrity / Change 03).
414
+ *
415
+ * Evidence generated from execution (tests, linters, experiments, observations)
416
+ * must carry the full causal chain of identifiers:
417
+ *
418
+ * requirementId -> hypothesisId -> experimentId -> observationId
419
+ *
420
+ * This module enforces:
421
+ * - each ID is a non-empty string
422
+ * - IDs follow the structural prefixes req- / hyp- / exp- / obs-
423
+ * (warning only, not rejection)
424
+ * - execution-produced evidence cannot be marked `valid` until all four
425
+ * identifiers are present
426
+ *
427
+ * The functions below are pure and side-effect-free.
428
+ */
429
+
430
+ export const STRUCTURAL_ID_PATTERNS = Object.freeze({
431
+ requirementId: /^req-[a-zA-Z0-9_-]+$/,
432
+ hypothesisId: /^hyp-[a-zA-Z0-9_-]+$/,
433
+ experimentId: /^exp-[a-zA-Z0-9_-]+$/,
434
+ observationId: /^obs-[a-zA-Z0-9_-]+$/,
435
+ });
436
+
437
+ export const REQUIRED_LINEAGE_FIELDS = Object.freeze([
438
+ "requirementId",
439
+ "hypothesisId",
440
+ "experimentId",
441
+ "observationId",
442
+ ]);
443
+
444
+ /**
445
+ * Returns the list of lineage fields that are missing or empty on the evidence.
446
+ */
447
+ export function findMissingLineageFields(evidence) {
448
+ if (!evidence || typeof evidence !== "object") {
449
+ return [...REQUIRED_LINEAGE_FIELDS];
450
+ }
451
+ return REQUIRED_LINEAGE_FIELDS.filter(
452
+ (field) => typeof evidence[field] !== "string" || evidence[field].trim() === ""
453
+ );
454
+ }
455
+
456
+ /**
457
+ * Validates an evidence object for structural integrity.
458
+ * Returns { valid, missing, warnings }.
459
+ */
460
+ export function validateEvidenceStructure(evidence) {
461
+ const missing = findMissingLineageFields(evidence);
462
+ const warnings = [];
463
+ if (evidence && typeof evidence === "object") {
464
+ for (const field of REQUIRED_LINEAGE_FIELDS) {
465
+ const value = evidence[field];
466
+ if (typeof value === "string" && value.trim() !== "") {
467
+ const pattern = STRUCTURAL_ID_PATTERNS[field];
468
+ if (pattern && !pattern.test(value)) {
469
+ warnings.push(`${field} "${value}" does not match expected pattern`);
470
+ }
471
+ }
472
+ }
473
+ }
474
+ return {
475
+ valid: missing.length === 0,
476
+ missing,
477
+ warnings,
478
+ };
479
+ }
480
+
481
+ /**
482
+ * True iff evidence is structurally complete (all four lineage IDs present).
483
+ */
484
+ export function hasCompleteLineage(evidence) {
485
+ return validateEvidenceStructure(evidence).valid;
486
+ }
487
+
488
+ /**
489
+ * Validate a batch of evidence entries (e.g., those attached to a requirement).
490
+ * Returns { complete, incomplete } lists.
491
+ */
492
+ export function partitionEvidenceByLineage(evidenceList) {
493
+ const complete = [];
494
+ const incomplete = [];
495
+ if (!Array.isArray(evidenceList)) {
496
+ return { complete, incomplete };
497
+ }
498
+ for (const ev of evidenceList) {
499
+ if (hasCompleteLineage(ev)) complete.push(ev);
500
+ else incomplete.push(ev);
501
+ }
502
+ return { complete, incomplete };
503
+ }
504
+
505
+ /**
506
+ * Decorates an existing evidence object with execution-lineage metadata.
507
+ * Returns a new object (does not mutate). Throws if any required field is
508
+ * missing — callers that want a non-throwing variant should call
509
+ * validateEvidenceStructure first.
510
+ */
511
+ export function stampExecutionLineage(evidence, lineage) {
512
+ if (!lineage || typeof lineage !== "object") {
513
+ throw new Error("lineage must be an object with requirementId, hypothesisId, experimentId, observationId");
514
+ }
515
+ const stamped = { ...(evidence || {}) };
516
+ for (const field of REQUIRED_LINEAGE_FIELDS) {
517
+ const value = lineage[field];
518
+ if (typeof value !== "string" || value.trim() === "") {
519
+ throw new Error(`missing or empty lineage field: ${field}`);
520
+ }
521
+ stamped[field] = value;
522
+ }
523
+ stamped.lineageStampedAt = new Date().toISOString();
524
+ return stamped;
525
+ }
@@ -0,0 +1,136 @@
1
+ // Evidence types (per spec)
2
+ export const EVIDENCE_TYPES = {
3
+ DIRECT: "DIRECT", // Direct observation
4
+ DERIVED: "DERIVED", // Derived from direct evidence
5
+ INFERRED: "INFERRED", // Inferred (weaker)
6
+ NEGATIVE: "NEGATIVE", // Contradictory evidence
7
+ ENVIRONMENT: "ENVIRONMENT", // Environment state
8
+ TOOL_OUTPUT: "TOOL_OUTPUT", // Tool execution result
9
+ TEST_RESULT: "TEST_RESULT", // Test execution result
10
+ USER_CONFIRMATION: "USER_CONFIRMATION" // User validation
11
+ };
12
+
13
+ // Evidence strength levels (per spec - L0 to L4)
14
+ export const EVIDENCE_STRENGTH = {
15
+ L0_CLAIM: "L0_CLAIM", // Just a claim
16
+ L1_INFERENCE: "L1_INFERENCE", // Inference
17
+ L2_OBSERVATION: "L2_OBSERVATION", // Observation
18
+ L3_DIRECT: "L3_DIRECT", // Direct evidence
19
+ L4_VERIFICATION: "L4_VERIFICATION" // Verification result
20
+ };
21
+
22
+ export function createEvidence({
23
+ id,
24
+ requirementId,
25
+ source,
26
+ type,
27
+ observation,
28
+ strength = EVIDENCE_STRENGTH.L2_OBSERVATION,
29
+ actionId = null,
30
+ supports = true
31
+ }) {
32
+ return {
33
+ id: id || `ev-${Date.now().toString(36)}`,
34
+ requirementId,
35
+ source,
36
+ type,
37
+ observation,
38
+ strength,
39
+ timestamp: Date.now(),
40
+ actionId,
41
+ supports // true = supports, false = contradicts
42
+ };
43
+ }
44
+
45
+ // Conflict resolution: conflicting evidence reverts to UNKNOWN
46
+ export function resolveConflict(evidenceA, evidenceB) {
47
+ if (!evidenceA || !evidenceB) return null;
48
+ if (evidenceA.requirementId && evidenceB.requirementId &&
49
+ evidenceA.requirementId !== evidenceB.requirementId) {
50
+ return null;
51
+ }
52
+ if (evidenceA.supports === evidenceB.supports) return null;
53
+ return {
54
+ requirementId: evidenceA.requirementId || evidenceB.requirementId,
55
+ status: "UNKNOWN",
56
+ reason: "conflicting-evidence",
57
+ conflictingIds: [evidenceA.id, evidenceB.id].filter(Boolean)
58
+ };
59
+ }
60
+
61
+ // Rule: INFERRED never closes critical requirement alone
62
+ export function canCloseRequirement(evidence, isCritical = false) {
63
+ if (!evidence || evidence.length === 0) return false;
64
+
65
+ const hasDirect = evidence.some(e => e.strength === EVIDENCE_STRENGTH.L3_DIRECT || e.strength === EVIDENCE_STRENGTH.L4_VERIFICATION);
66
+ const hasInferredOnly = evidence.every(e => e.strength === EVIDENCE_STRENGTH.L1_INFERENCE);
67
+
68
+ if (isCritical && hasInferredOnly) return false;
69
+ if (!hasDirect) return false;
70
+
71
+ // All evidence must support (no contradictions)
72
+ return evidence.every(e => e.supports !== false);
73
+ }
74
+
75
+ export const EVIDENCE_VOLATILITY = {
76
+ STABLE: "stable",
77
+ SEMI_STABLE: "semi-stable",
78
+ VOLATILE: "volatile",
79
+ HIGHLY_VOLATILE: "highly_volatile"
80
+ };
81
+
82
+ export function getEvidenceVolatility(type) {
83
+ const map = {
84
+ "file_exists": EVIDENCE_VOLATILITY.STABLE,
85
+ "package_version": EVIDENCE_VOLATILITY.SEMI_STABLE,
86
+ "process_running": EVIDENCE_VOLATILITY.VOLATILE,
87
+ "network_response": EVIDENCE_VOLATILITY.HIGHLY_VOLATILE,
88
+ "test_result": EVIDENCE_VOLATILITY.SEMI_STABLE
89
+ };
90
+ return map[type] || EVIDENCE_VOLATILITY.STABLE;
91
+ }
92
+
93
+ export function detectEvidenceConflict(evidenceA, evidenceB) {
94
+ if (evidenceA.requirementId !== evidenceB.requirementId) return null;
95
+ if (evidenceA.supports === evidenceB.supports) return null;
96
+
97
+ return {
98
+ type: "CONFLICT",
99
+ evidenceA: { id: evidenceA.id, supports: evidenceA.supports },
100
+ evidenceB: { id: evidenceB.id, supports: evidenceB.supports },
101
+ resolved: false
102
+ };
103
+ }
104
+
105
+ export function resolveEvidenceConflict(conflict, resolution) {
106
+ return {
107
+ ...conflict,
108
+ resolved: true,
109
+ resolution,
110
+ resolvedAt: Date.now()
111
+ };
112
+ }
113
+
114
+ // Detect conflicts in evidence
115
+ export function findConflicts(evidence) {
116
+ if (!evidence || evidence.length === 0) return [];
117
+
118
+ const conflicts = [];
119
+ const byRequirement = {};
120
+
121
+ for (const ev of evidence) {
122
+ const key = ev.requirementId;
123
+ if (!byRequirement[key]) byRequirement[key] = [];
124
+ byRequirement[key].push(ev);
125
+ }
126
+
127
+ for (const [reqId, evs] of Object.entries(byRequirement)) {
128
+ const supports = evs.filter(e => e.supports !== false);
129
+ const contradicts = evs.filter(e => e.supports === false);
130
+ if (supports.length > 0 && contradicts.length > 0) {
131
+ conflicts.push({ requirementId: reqId, supports, contradicts });
132
+ }
133
+ }
134
+
135
+ return conflicts;
136
+ }