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.
- package/LICENSE +21 -0
- package/README.md +318 -2
- package/index.js +1836 -0
- package/package.json +70 -4
- package/skills/codebase-design/SKILL.md +114 -0
- package/skills/design-contract/SKILL.md +27 -0
- package/skills/design-critique/SKILL.md +19 -0
- package/skills/design-system/SKILL.md +17 -0
- package/skills/design-system/references/image-analysis.md +14 -0
- package/skills/design-system/references/tokens.md +32 -0
- package/skills/design-system/references/ui-rules.md +14 -0
- package/skills/diagnosing-bugs/SKILL.md +138 -0
- package/skills/frontend-design/SKILL.md +36 -0
- package/skills/handoff/SKILL.md +16 -0
- package/skills/improve-codebase-architecture/SKILL.md +71 -0
- package/skills/linter-negative/SKILL.md +74 -0
- package/skills/no-slop-ui/SKILL.md +26 -0
- package/skills/prototype/SKILL.md +26 -0
- package/skills/registry.json +44087 -0
- package/skills/tastemaker/SKILL.md +8 -0
- package/skills/tastemaker/references/tastemaker.md +14 -0
- package/skills/tdd/SKILL.md +38 -0
- package/skills/visual-review/SKILL.md +103 -0
- package/skills/visual-review/references/review-gates.md +42 -0
- package/skills/visual-review/references/surface-archetypes.md +37 -0
- package/skills/wizard/SKILL.md +44 -0
- package/skills/writing-for-agents/SKILL.md +81 -0
- package/src/cognition/cognition-store.js +251 -0
- package/src/cognition/cognitive-state.js +126 -0
- package/src/cognition/hypothesis-manager.js +113 -0
- package/src/cognition/observation-engine.js +112 -0
- package/src/cognition/observation-provenance.js +268 -0
- package/src/context/active-context-boundary.js +68 -0
- package/src/context/assembly.js +494 -0
- package/src/context/assessment-rules.js +55 -0
- package/src/context/capsule-staleness.js +267 -0
- package/src/context/capsules.js +2 -0
- package/src/context/compare-array.js +98 -0
- package/src/context/compare-object.js +89 -0
- package/src/context/compare-values.js +42 -0
- package/src/context/context-assembly-contract.js +71 -0
- package/src/context/context-budget-manager.js +232 -0
- package/src/context/context-builder.js +49 -0
- package/src/context/context-cache.js +55 -0
- package/src/context/context-capture.js +243 -0
- package/src/context/context-classification.js +199 -0
- package/src/context/context-compaction.js +179 -0
- package/src/context/context-decision-audit.js +267 -0
- package/src/context/context-demotion.js +173 -0
- package/src/context/context-dictionary.js +106 -0
- package/src/context/context-drift-detection.js +225 -0
- package/src/context/context-evaluation.js +374 -0
- package/src/context/context-event-ingress.js +100 -0
- package/src/context/context-event-normalization.js +224 -0
- package/src/context/context-feedback-loop.js +216 -0
- package/src/context/context-freshness.js +21 -0
- package/src/context/context-graph-builder.js +71 -0
- package/src/context/context-graph.js +451 -0
- package/src/context/context-history-relevance.js +321 -0
- package/src/context/context-interception.js +145 -0
- package/src/context/context-invalidation-events.js +154 -0
- package/src/context/context-lifecycle.js +170 -0
- package/src/context/context-loop-prevention.js +347 -0
- package/src/context/context-manager.js +340 -0
- package/src/context/context-memory-layers.js +265 -0
- package/src/context/context-minimality.js +166 -0
- package/src/context/context-normalization.js +130 -0
- package/src/context/context-optimization-metrics.js +434 -0
- package/src/context/context-output-policy.js +135 -0
- package/src/context/context-pack-authority.js +157 -0
- package/src/context/context-promotion.js +172 -0
- package/src/context/context-query-contract.js +101 -0
- package/src/context/context-ranking.js +164 -0
- package/src/context/context-rate-distortion.js +261 -0
- package/src/context/context-recovery.js +209 -0
- package/src/context/context-retrieval-integration.js +60 -0
- package/src/context/context-retrieval.js +93 -0
- package/src/context/context-router.js +457 -0
- package/src/context/context-routing-evaluation.js +331 -0
- package/src/context/context-routing-observability.js +296 -0
- package/src/context/context-routing-shadow.js +237 -0
- package/src/context/context-scope.js +181 -0
- package/src/context/context-snapshot.js +226 -0
- package/src/context/context-source-registry.js +95 -0
- package/src/context/context-sufficiency-gate.js +120 -0
- package/src/context/context-sufficiency-oracle.js +167 -0
- package/src/context/context-telemetry.js +176 -0
- package/src/context/context.js +633 -0
- package/src/context/dependency-closure.js +246 -0
- package/src/context/page-fault-tracker.js +275 -0
- package/src/context/prompt-engine.js +74 -0
- package/src/context/runtime-context-graph.js +260 -0
- package/src/context/selection.js +2 -0
- package/src/context/session.js +2 -0
- package/src/context/sufficiency-contract.js +300 -0
- package/src/context/tokenizer.js +17 -0
- package/src/context-benchmark-router.mjs +125 -0
- package/src/evidence/evidence-engine.js +161 -0
- package/src/evidence/evidence-factory.js +3 -0
- package/src/evidence/evidence-freshness.js +463 -0
- package/src/evidence/evidence-gap.js +76 -0
- package/src/evidence/evidence-lineage.js +525 -0
- package/src/evidence/evidence.js +136 -0
- package/src/evidence/freshness-validation.js +187 -0
- package/src/evidence/lineage.js +4 -0
- package/src/execution/action-evaluation.js +179 -0
- package/src/execution/circuit-breaker.js +70 -0
- package/src/execution/claim-interception.js +63 -0
- package/src/execution/execution-engine.js +258 -0
- package/src/execution/execution-intent.js +69 -0
- package/src/execution/execution-state.js +89 -0
- package/src/execution/formatting.js +60 -0
- package/src/execution/l1-sanitize.js +16 -0
- package/src/execution/l2-resource-guards.js +19 -0
- package/src/execution/l3-invariant-checker.js +5 -0
- package/src/execution/l4-containment.js +6 -0
- package/src/execution/runtime-guards.js +106 -0
- package/src/execution/strategy-identity.js +162 -0
- package/src/execution/tool-interceptor.js +7 -0
- package/src/execution/tool-result-classifier.js +63 -0
- package/src/integration/canary-deploy.js +143 -0
- package/src/integration/collector.js +59 -0
- package/src/integration/error-recovery.js +6 -0
- package/src/integration/event-bus.js +66 -0
- package/src/integration/health-checker.js +57 -0
- package/src/integration/logger.js +39 -0
- package/src/integration/message-handler.js +611 -0
- package/src/integration/opencode-db.js +153 -0
- package/src/integration/orchestration.js +419 -0
- package/src/integration/part-provenance.js +65 -0
- package/src/integration/rate-limiter.js +55 -0
- package/src/integration/release-gate.js +336 -0
- package/src/integration/rollback-manager.js +164 -0
- package/src/integration/router-adapter.js +269 -0
- package/src/integration/telemetry.js +45 -0
- package/src/integration/update-checker.js +10 -0
- package/src/persistence/event-sourcing.js +75 -0
- package/src/persistence/memory.js +501 -0
- package/src/persistence/migration-runner.js +16 -0
- package/src/persistence/persistence-manager.js +39 -0
- package/src/persistence/transaction-log.js +157 -0
- package/src/policy/action-capabilities.js +116 -0
- package/src/policy/action-policy.js +4 -0
- package/src/policy/assessment-engine.js +104 -0
- package/src/policy/assumption-tracking.js +66 -0
- package/src/policy/budget-enforcer.js +7 -0
- package/src/policy/config-validator.js +102 -0
- package/src/policy/execution-assessment.js +142 -0
- package/src/policy/failure-classification.js +5 -0
- package/src/policy/feature-flags.js +65 -0
- package/src/policy/governance-enforcement.js +118 -0
- package/src/policy/locale.js +15 -0
- package/src/policy/multi-signer.js +151 -0
- package/src/policy/policy-state-machine.js +97 -0
- package/src/policy/ponytail-constraint.js +38 -0
- package/src/policy/project-inspector.js +93 -0
- package/src/policy/query-builder.js +14 -0
- package/src/policy/request-classifier.js +79 -0
- package/src/policy/risk-engine.js +266 -0
- package/src/policy/scope-enforcement.js +140 -0
- package/src/policy/secret-scanner.js +68 -0
- package/src/policy/skill-routing.js +104 -0
- package/src/policy/strategy-capabilities.js +211 -0
- package/src/policy/strategy.js +6 -0
- package/src/policy/uncertainty.js +117 -0
- package/src/shared/container.js +44 -0
- package/src/shared/decision-types.js +24 -0
- package/src/shared/simple-test.js +160 -0
- package/src/shared/wam-events.js +52 -0
- package/src/skills/engine.js +1705 -0
- package/src/skills/skill-registry.js +19 -0
- package/src/skills/skill-routing.js +340 -0
- package/src/state/lifecycle-manager.js +113 -0
- package/src/state/operational-state.js +198 -0
- package/src/state/projections.js +47 -0
- package/src/state/replay-engine.js +5 -0
- package/src/state/requirement-ownership.js +20 -0
- package/src/state/requirement-state.js +152 -0
- package/src/state/run-event-identity.js +172 -0
- package/src/state/state-engine.js +19 -0
- package/src/state/state-machine.js +119 -0
- package/src/state/state-schema.js +318 -0
- package/src/state/state-store.js +335 -0
- package/src/state/state-transition.js +84 -0
- package/src/state/task-dependencies.js +287 -0
- package/src/state/task-execution.js +217 -0
- package/src/state/task-lifecycle.js +59 -0
- package/src/state/task-runs.js +361 -0
- package/src/state/task-state-file.js +86 -0
- package/src/state/task-state.js +41 -0
- package/src/state/task-status.js +54 -0
- package/src/state/task-store.js +48 -0
- package/src/state/wam-state.js +148 -0
- package/src/verification/completion-gate-simple.js +8 -0
- package/src/verification/completion-gate.js +183 -0
- package/src/verification/contract.js +9 -0
- package/src/verification/contradiction-resolution.js +255 -0
- package/src/verification/diagnostic-engine.js +659 -0
- package/src/verification/false-completion-prevention.js +100 -0
- package/src/verification/hook-validator.js +6 -0
- package/src/verification/requirements.js +6 -0
- package/src/verification/verification-context.js +162 -0
- package/src/verification/verification-lifecycle.js +66 -0
- package/src/verification/verification-model.js +89 -0
- package/src/verification/verification-persistence.js +129 -0
- package/src/verification/verification-policy.js +127 -0
- 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
|
+
}
|