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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +318 -2
  3. package/index.js +1836 -0
  4. package/package.json +70 -4
  5. package/skills/codebase-design/SKILL.md +114 -0
  6. package/skills/design-contract/SKILL.md +27 -0
  7. package/skills/design-critique/SKILL.md +19 -0
  8. package/skills/design-system/SKILL.md +17 -0
  9. package/skills/design-system/references/image-analysis.md +14 -0
  10. package/skills/design-system/references/tokens.md +32 -0
  11. package/skills/design-system/references/ui-rules.md +14 -0
  12. package/skills/diagnosing-bugs/SKILL.md +138 -0
  13. package/skills/frontend-design/SKILL.md +36 -0
  14. package/skills/handoff/SKILL.md +16 -0
  15. package/skills/improve-codebase-architecture/SKILL.md +71 -0
  16. package/skills/linter-negative/SKILL.md +74 -0
  17. package/skills/no-slop-ui/SKILL.md +26 -0
  18. package/skills/prototype/SKILL.md +26 -0
  19. package/skills/registry.json +44087 -0
  20. package/skills/tastemaker/SKILL.md +8 -0
  21. package/skills/tastemaker/references/tastemaker.md +14 -0
  22. package/skills/tdd/SKILL.md +38 -0
  23. package/skills/visual-review/SKILL.md +103 -0
  24. package/skills/visual-review/references/review-gates.md +42 -0
  25. package/skills/visual-review/references/surface-archetypes.md +37 -0
  26. package/skills/wizard/SKILL.md +44 -0
  27. package/skills/writing-for-agents/SKILL.md +81 -0
  28. package/src/cognition/cognition-store.js +251 -0
  29. package/src/cognition/cognitive-state.js +126 -0
  30. package/src/cognition/hypothesis-manager.js +113 -0
  31. package/src/cognition/observation-engine.js +112 -0
  32. package/src/cognition/observation-provenance.js +268 -0
  33. package/src/context/active-context-boundary.js +68 -0
  34. package/src/context/assembly.js +494 -0
  35. package/src/context/assessment-rules.js +55 -0
  36. package/src/context/capsule-staleness.js +267 -0
  37. package/src/context/capsules.js +2 -0
  38. package/src/context/compare-array.js +98 -0
  39. package/src/context/compare-object.js +89 -0
  40. package/src/context/compare-values.js +42 -0
  41. package/src/context/context-assembly-contract.js +71 -0
  42. package/src/context/context-budget-manager.js +232 -0
  43. package/src/context/context-builder.js +49 -0
  44. package/src/context/context-cache.js +55 -0
  45. package/src/context/context-capture.js +243 -0
  46. package/src/context/context-classification.js +199 -0
  47. package/src/context/context-compaction.js +179 -0
  48. package/src/context/context-decision-audit.js +267 -0
  49. package/src/context/context-demotion.js +173 -0
  50. package/src/context/context-dictionary.js +106 -0
  51. package/src/context/context-drift-detection.js +225 -0
  52. package/src/context/context-evaluation.js +374 -0
  53. package/src/context/context-event-ingress.js +100 -0
  54. package/src/context/context-event-normalization.js +224 -0
  55. package/src/context/context-feedback-loop.js +216 -0
  56. package/src/context/context-freshness.js +21 -0
  57. package/src/context/context-graph-builder.js +71 -0
  58. package/src/context/context-graph.js +451 -0
  59. package/src/context/context-history-relevance.js +321 -0
  60. package/src/context/context-interception.js +145 -0
  61. package/src/context/context-invalidation-events.js +154 -0
  62. package/src/context/context-lifecycle.js +170 -0
  63. package/src/context/context-loop-prevention.js +347 -0
  64. package/src/context/context-manager.js +340 -0
  65. package/src/context/context-memory-layers.js +265 -0
  66. package/src/context/context-minimality.js +166 -0
  67. package/src/context/context-normalization.js +130 -0
  68. package/src/context/context-optimization-metrics.js +434 -0
  69. package/src/context/context-output-policy.js +135 -0
  70. package/src/context/context-pack-authority.js +157 -0
  71. package/src/context/context-promotion.js +172 -0
  72. package/src/context/context-query-contract.js +101 -0
  73. package/src/context/context-ranking.js +164 -0
  74. package/src/context/context-rate-distortion.js +261 -0
  75. package/src/context/context-recovery.js +209 -0
  76. package/src/context/context-retrieval-integration.js +60 -0
  77. package/src/context/context-retrieval.js +93 -0
  78. package/src/context/context-router.js +457 -0
  79. package/src/context/context-routing-evaluation.js +331 -0
  80. package/src/context/context-routing-observability.js +296 -0
  81. package/src/context/context-routing-shadow.js +237 -0
  82. package/src/context/context-scope.js +181 -0
  83. package/src/context/context-snapshot.js +226 -0
  84. package/src/context/context-source-registry.js +95 -0
  85. package/src/context/context-sufficiency-gate.js +120 -0
  86. package/src/context/context-sufficiency-oracle.js +167 -0
  87. package/src/context/context-telemetry.js +176 -0
  88. package/src/context/context.js +633 -0
  89. package/src/context/dependency-closure.js +246 -0
  90. package/src/context/page-fault-tracker.js +275 -0
  91. package/src/context/prompt-engine.js +74 -0
  92. package/src/context/runtime-context-graph.js +260 -0
  93. package/src/context/selection.js +2 -0
  94. package/src/context/session.js +2 -0
  95. package/src/context/sufficiency-contract.js +300 -0
  96. package/src/context/tokenizer.js +17 -0
  97. package/src/context-benchmark-router.mjs +125 -0
  98. package/src/evidence/evidence-engine.js +161 -0
  99. package/src/evidence/evidence-factory.js +3 -0
  100. package/src/evidence/evidence-freshness.js +463 -0
  101. package/src/evidence/evidence-gap.js +76 -0
  102. package/src/evidence/evidence-lineage.js +525 -0
  103. package/src/evidence/evidence.js +136 -0
  104. package/src/evidence/freshness-validation.js +187 -0
  105. package/src/evidence/lineage.js +4 -0
  106. package/src/execution/action-evaluation.js +179 -0
  107. package/src/execution/circuit-breaker.js +70 -0
  108. package/src/execution/claim-interception.js +63 -0
  109. package/src/execution/execution-engine.js +258 -0
  110. package/src/execution/execution-intent.js +69 -0
  111. package/src/execution/execution-state.js +89 -0
  112. package/src/execution/formatting.js +60 -0
  113. package/src/execution/l1-sanitize.js +16 -0
  114. package/src/execution/l2-resource-guards.js +19 -0
  115. package/src/execution/l3-invariant-checker.js +5 -0
  116. package/src/execution/l4-containment.js +6 -0
  117. package/src/execution/runtime-guards.js +106 -0
  118. package/src/execution/strategy-identity.js +162 -0
  119. package/src/execution/tool-interceptor.js +7 -0
  120. package/src/execution/tool-result-classifier.js +63 -0
  121. package/src/integration/canary-deploy.js +143 -0
  122. package/src/integration/collector.js +59 -0
  123. package/src/integration/error-recovery.js +6 -0
  124. package/src/integration/event-bus.js +66 -0
  125. package/src/integration/health-checker.js +57 -0
  126. package/src/integration/logger.js +39 -0
  127. package/src/integration/message-handler.js +611 -0
  128. package/src/integration/opencode-db.js +153 -0
  129. package/src/integration/orchestration.js +419 -0
  130. package/src/integration/part-provenance.js +65 -0
  131. package/src/integration/rate-limiter.js +55 -0
  132. package/src/integration/release-gate.js +336 -0
  133. package/src/integration/rollback-manager.js +164 -0
  134. package/src/integration/router-adapter.js +269 -0
  135. package/src/integration/telemetry.js +45 -0
  136. package/src/integration/update-checker.js +10 -0
  137. package/src/persistence/event-sourcing.js +75 -0
  138. package/src/persistence/memory.js +501 -0
  139. package/src/persistence/migration-runner.js +16 -0
  140. package/src/persistence/persistence-manager.js +39 -0
  141. package/src/persistence/transaction-log.js +157 -0
  142. package/src/policy/action-capabilities.js +116 -0
  143. package/src/policy/action-policy.js +4 -0
  144. package/src/policy/assessment-engine.js +104 -0
  145. package/src/policy/assumption-tracking.js +66 -0
  146. package/src/policy/budget-enforcer.js +7 -0
  147. package/src/policy/config-validator.js +102 -0
  148. package/src/policy/execution-assessment.js +142 -0
  149. package/src/policy/failure-classification.js +5 -0
  150. package/src/policy/feature-flags.js +65 -0
  151. package/src/policy/governance-enforcement.js +118 -0
  152. package/src/policy/locale.js +15 -0
  153. package/src/policy/multi-signer.js +151 -0
  154. package/src/policy/policy-state-machine.js +97 -0
  155. package/src/policy/ponytail-constraint.js +38 -0
  156. package/src/policy/project-inspector.js +93 -0
  157. package/src/policy/query-builder.js +14 -0
  158. package/src/policy/request-classifier.js +79 -0
  159. package/src/policy/risk-engine.js +266 -0
  160. package/src/policy/scope-enforcement.js +140 -0
  161. package/src/policy/secret-scanner.js +68 -0
  162. package/src/policy/skill-routing.js +104 -0
  163. package/src/policy/strategy-capabilities.js +211 -0
  164. package/src/policy/strategy.js +6 -0
  165. package/src/policy/uncertainty.js +117 -0
  166. package/src/shared/container.js +44 -0
  167. package/src/shared/decision-types.js +24 -0
  168. package/src/shared/simple-test.js +160 -0
  169. package/src/shared/wam-events.js +52 -0
  170. package/src/skills/engine.js +1705 -0
  171. package/src/skills/skill-registry.js +19 -0
  172. package/src/skills/skill-routing.js +340 -0
  173. package/src/state/lifecycle-manager.js +113 -0
  174. package/src/state/operational-state.js +198 -0
  175. package/src/state/projections.js +47 -0
  176. package/src/state/replay-engine.js +5 -0
  177. package/src/state/requirement-ownership.js +20 -0
  178. package/src/state/requirement-state.js +152 -0
  179. package/src/state/run-event-identity.js +172 -0
  180. package/src/state/state-engine.js +19 -0
  181. package/src/state/state-machine.js +119 -0
  182. package/src/state/state-schema.js +318 -0
  183. package/src/state/state-store.js +335 -0
  184. package/src/state/state-transition.js +84 -0
  185. package/src/state/task-dependencies.js +287 -0
  186. package/src/state/task-execution.js +217 -0
  187. package/src/state/task-lifecycle.js +59 -0
  188. package/src/state/task-runs.js +361 -0
  189. package/src/state/task-state-file.js +86 -0
  190. package/src/state/task-state.js +41 -0
  191. package/src/state/task-status.js +54 -0
  192. package/src/state/task-store.js +48 -0
  193. package/src/state/wam-state.js +148 -0
  194. package/src/verification/completion-gate-simple.js +8 -0
  195. package/src/verification/completion-gate.js +183 -0
  196. package/src/verification/contract.js +9 -0
  197. package/src/verification/contradiction-resolution.js +255 -0
  198. package/src/verification/diagnostic-engine.js +659 -0
  199. package/src/verification/false-completion-prevention.js +100 -0
  200. package/src/verification/hook-validator.js +6 -0
  201. package/src/verification/requirements.js +6 -0
  202. package/src/verification/verification-context.js +162 -0
  203. package/src/verification/verification-lifecycle.js +66 -0
  204. package/src/verification/verification-model.js +89 -0
  205. package/src/verification/verification-persistence.js +129 -0
  206. package/src/verification/verification-policy.js +127 -0
  207. package/src/verification/verification.js +415 -0
@@ -0,0 +1,100 @@
1
+ import { detectEvidenceGaps } from "../evidence/evidence-gap.js";
2
+
3
+ export const COMPLETION_KEYWORDS = [
4
+ "done", "fixed", "working", "solved", "completed",
5
+ "implemented", "resolved", "ready", "complete"
6
+ ];
7
+
8
+ export function interceptCompletionClaim(agentMessage) {
9
+ const claims = [];
10
+ const lower = agentMessage.toLowerCase();
11
+
12
+ for (const keyword of COMPLETION_KEYWORDS) {
13
+ const regex = new RegExp(`\\b${keyword}\\b`, "gi");
14
+ let match;
15
+ while ((match = regex.exec(agentMessage)) !== null) {
16
+ claims.push({
17
+ keyword,
18
+ position: match.index,
19
+ context: agentMessage.slice(Math.max(0, match.index - 30), match.index + 30)
20
+ });
21
+ }
22
+ }
23
+
24
+ return claims;
25
+ }
26
+
27
+ export function classifyClaim(claim, context) {
28
+ const patterns = {
29
+ FACT: /\b(exists|present|available|installed|configured)\b/i,
30
+ HYPOTHESIS: /\b(think|believe|probably|might|could be)\b/i,
31
+ OBSERVATION: /\b(see|observe|notice|found|detected)\b/i,
32
+ INTENT: /\b(will|going to|plan|should|need to)\b/i,
33
+ CONCLUSION: /\b(therefore|so|thus|conclude)\b/i,
34
+ COMPLETION_CLAIM: /\b(done|fixed|working|solved|completed)\b/i
35
+ };
36
+
37
+ for (const [type, pattern] of Object.entries(patterns)) {
38
+ if (pattern.test(claim)) return type;
39
+ }
40
+
41
+ return "UNKNOWN";
42
+ }
43
+
44
+ export function validateCompletionClaim(claim, taskState) {
45
+ if (!taskState.requirements || taskState.requirements.length === 0) {
46
+ return { valid: false, reason: "No requirements defined" };
47
+ }
48
+
49
+ const gaps = detectEvidenceGaps(taskState.requirements, taskState.evidence || []);
50
+ const mandatoryIncomplete = taskState.requirements.filter(
51
+ r => !r.optional && r.status !== "VERIFIED"
52
+ );
53
+
54
+ return {
55
+ valid: mandatoryIncomplete.length === 0 && gaps.length === 0,
56
+ reason: gaps.length > 0 ? "Evidence gaps remain" :
57
+ mandatoryIncomplete.length > 0 ? "Requirements not verified" : null,
58
+ incompleteRequirements: mandatoryIncomplete.map(r => r.id),
59
+ evidenceGaps: gaps.map(g => g.requirementId)
60
+ };
61
+ }
62
+
63
+ export function validateContractNotReduced(originalRequirements, currentRequirements) {
64
+ const violations = [];
65
+
66
+ for (const orig of originalRequirements) {
67
+ const current = currentRequirements.find(r => r.id === orig.id);
68
+ if (!current) {
69
+ violations.push({ type: "REMOVED", requirementId: orig.id });
70
+ continue;
71
+ }
72
+
73
+ if (orig.acceptanceCriteria && current.acceptanceCriteria) {
74
+ const origLen = orig.acceptanceCriteria.length;
75
+ const currLen = current.acceptanceCriteria.length;
76
+ if (currLen < origLen) {
77
+ violations.push({
78
+ type: "WEAKENED",
79
+ requirementId: orig.id,
80
+ originalCount: origLen,
81
+ currentCount: currLen
82
+ });
83
+ }
84
+ }
85
+
86
+ if (orig.claim && current.claim) {
87
+ if (current.claim.length < orig.claim.length * 0.8) {
88
+ violations.push({
89
+ type: "WEAKENED_CLAIM",
90
+ requirementId: orig.id
91
+ });
92
+ }
93
+ }
94
+ }
95
+
96
+ return {
97
+ valid: violations.length === 0,
98
+ violations
99
+ };
100
+ }
@@ -0,0 +1,6 @@
1
+ export function validateHook(hook) {
2
+ const errors = [];
3
+ if (!hook || !hook.name) errors.push('Missing hook name');
4
+ if (!hook || !hook.callback) errors.push('Missing callback');
5
+ return { valid: errors.length === 0, errors };
6
+ }
@@ -0,0 +1,6 @@
1
+ export function createRequirement(id, title) {
2
+ return { id, title, status: "pending", evidence: [] };
3
+ }
4
+ export function markRequirement(requirement, status, evidence) {
5
+ return { ...requirement, status, evidence: [...requirement.evidence, evidence] };
6
+ }
@@ -0,0 +1,162 @@
1
+ // Change 12 — context-verification
2
+ // Change 13 — verification-context-budget
3
+ // Change 15 — verification-invalidation
4
+
5
+ export const CONTEXT_LEVELS = {
6
+ N0: "N0", // Task / requirements
7
+ N1: "N1", // Domain-specific verification knowledge
8
+ N2: "N2", // Current evidence / observations
9
+ N3: "N3" // Opportunistic context
10
+ };
11
+
12
+ export function loadVerificationContext(level, taskData) {
13
+ switch (level) {
14
+ case CONTEXT_LEVELS.N0:
15
+ return { requirements: taskData.requirements };
16
+ case CONTEXT_LEVELS.N1:
17
+ return {
18
+ requirements: taskData.requirements,
19
+ verificationMethods: taskData.verificationMethods || []
20
+ };
21
+ case CONTEXT_LEVELS.N2:
22
+ return {
23
+ requirements: taskData.requirements,
24
+ evidence: taskData.evidence || [],
25
+ observations: taskData.observations || []
26
+ };
27
+ case CONTEXT_LEVELS.N3:
28
+ return {
29
+ opportunistic: true,
30
+ context: taskData.opportunisticContext
31
+ };
32
+ default:
33
+ return {};
34
+ }
35
+ }
36
+
37
+ // Rule: Never load N3 to substitute missing N2 evidence
38
+ export function validateContextBudget(loadedContext, requiredLevels) {
39
+ const missing = requiredLevels.filter(l => !loadedContext[l]);
40
+ return {
41
+ valid: missing.length === 0,
42
+ missingLevels: missing,
43
+ warning: loadedContext[CONTEXT_LEVELS.N3] && !loadedContext[CONTEXT_LEVELS.N2]
44
+ ? "N3 context used to substitute missing N2"
45
+ : null
46
+ };
47
+ }
48
+
49
+ export const DEFAULT_VERIFICATION_BUDGET = {
50
+ maxActions: 10,
51
+ maxTokens: 50000,
52
+ maxLatencyMs: 120000,
53
+ maxRetries: 2
54
+ };
55
+
56
+ export function createVerificationBudget(overrides = {}) {
57
+ return { ...DEFAULT_VERIFICATION_BUDGET, ...overrides };
58
+ }
59
+
60
+ // Change 32 — verification-invalidation
61
+ export function createVerificationInvalidation(requirement, reason, relatedEvidence = []) {
62
+ return {
63
+ requirementId: requirement.id,
64
+ previousStatus: requirement.status,
65
+ newStatus: "UNKNOWN",
66
+ reason,
67
+ relatedEvidence: relatedEvidence.map(e => e.id),
68
+ invalidatedAt: Date.now(),
69
+ invalidatedBy: "verificationEngine"
70
+ };
71
+ }
72
+
73
+ export function checkVerificationDependencies(requirement, currentContext) {
74
+ const dependencies = requirement.dependencies || [];
75
+ const changes = [];
76
+
77
+ for (const dep of dependencies) {
78
+ const currentValue = currentContext[dep.key];
79
+ const previousValue = dep.value;
80
+
81
+ if (currentValue !== previousValue) {
82
+ changes.push({
83
+ dependency: dep.key,
84
+ previous: previousValue,
85
+ current: currentValue
86
+ });
87
+ }
88
+ }
89
+
90
+ return {
91
+ stable: changes.length === 0,
92
+ changes
93
+ };
94
+ }
95
+
96
+ export function shouldInvalidate(requirement, currentContext, stalenessThreshold = 3600000) {
97
+ if (!requirement.verifiedAt) return false;
98
+
99
+ const age = Date.now() - requirement.verifiedAt;
100
+ if (age > stalenessThreshold) return true;
101
+
102
+ const deps = checkVerificationDependencies(requirement, currentContext);
103
+ return !deps.stable;
104
+ }
105
+
106
+ export function isBudgetExhausted(budget, usage) {
107
+ return (
108
+ usage.actions >= budget.maxActions ||
109
+ usage.tokens >= budget.maxTokens ||
110
+ usage.latencyMs >= budget.maxLatencyMs ||
111
+ usage.retries >= budget.maxRetries
112
+ );
113
+ }
114
+
115
+ export function invalidateVerification(requirement, invalidatingObservation) {
116
+ return {
117
+ ...requirement,
118
+ status: "UNKNOWN",
119
+ invalidatedAt: Date.now(),
120
+ invalidationReason: invalidatingObservation,
121
+ previousStatus: requirement.status
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Before verifying a requirement, reject STALE evidence.
127
+ * If any linked evidence is STALE the requirement returns to PENDING
128
+ * and verification is skipped (caller must replan).
129
+ *
130
+ * @param {object} requirement
131
+ * @param {Array<{id?: string, status?: string}>} evidence
132
+ * @returns {{ canVerify: boolean, requirement: object, staleEvidence: Array, reason: string|null }}
133
+ */
134
+ export function checkEvidenceFreshness(requirement, evidence = []) {
135
+ const staleEvidence = (evidence || []).filter(
136
+ (e) => e && (e.status === "stale" || e.status === "STALE")
137
+ );
138
+ if (staleEvidence.length === 0) {
139
+ return { canVerify: true, requirement, staleEvidence: [], reason: null };
140
+ }
141
+ const reverted = {
142
+ ...requirement,
143
+ status: "pending",
144
+ previousStatus: requirement.status,
145
+ replan: true,
146
+ staleEvidenceIds: staleEvidence.map((e) => e.id).filter(Boolean),
147
+ };
148
+ return {
149
+ canVerify: false,
150
+ requirement: reverted,
151
+ staleEvidence,
152
+ reason: "STALE evidence — requirement returned to PENDING, replan required",
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Gate used immediately before verifying a requirement.
158
+ * Returns the requirement (possibly reset to PENDING) plus a verify flag.
159
+ */
160
+ export function prepareRequirementVerification(requirement, evidence = []) {
161
+ return checkEvidenceFreshness(requirement, evidence);
162
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Verification Lifecycle — requirement verification state machine.
3
+ * Implements spec: verification-lifecycle.
4
+ *
5
+ * States: UNVERIFIED → VERIFYING → VERIFIED (or → FAILED → new hypothesis)
6
+ * Evidence: method (test/lint/build/other), result (pass/fail), details
7
+ */
8
+
9
+ const VERIFICATION_STATES = new Set(["UNVERIFIED", "VERIFYING", "VERIFIED", "FAILED", "INVALIDATED"]);
10
+
11
+ const VALID_TRANSITIONS = {
12
+ UNVERIFIED: new Set(["VERIFYING"]),
13
+ VERIFYING: new Set(["VERIFIED", "FAILED"]),
14
+ FAILED: new Set(["VERIFYING"]),
15
+ VERIFIED: new Set(["INVALIDATED"]),
16
+ INVALIDATED: new Set(["VERIFYING"]),
17
+ };
18
+
19
+ export function isValidVerificationState(state) {
20
+ return VERIFICATION_STATES.has(state);
21
+ }
22
+
23
+ export function transitionVerification(from, to) {
24
+ if (!isValidVerificationState(from)) throw new Error(`Invalid verification state: ${from}`);
25
+ if (!VALID_TRANSITIONS[from].has(to)) throw new Error(`Invalid transition: ${from} → ${to}. Allowed: ${[...VALID_TRANSITIONS[from]].join(", ") || "(none)"}`);
26
+ return to;
27
+ }
28
+
29
+ export function createEvidence(method, result, details) {
30
+ if (!method || !result) throw new Error("Evidence requires method and result");
31
+ return { method, result, details: details || "", at: Date.now() };
32
+ }
33
+
34
+ export function isVerified(requirement) {
35
+ return requirement?.verificationStatus === "VERIFIED" && Array.isArray(requirement.evidence) && requirement.evidence.length > 0;
36
+ }
37
+
38
+ export function canComplete(taskState) {
39
+ const reqs = taskState?.requirements || [];
40
+ const unverified = reqs.filter((r) => !isVerified(r));
41
+ return { canComplete: unverified.length === 0, unverifiedCount: unverified.length, unverifiedIds: unverified.map((r) => r.id) };
42
+ }
43
+
44
+ export function hasOutstandingWork(taskState) {
45
+ const reqs = taskState?.requirements || [];
46
+ const unverified = reqs.filter((r) => !isVerified(r));
47
+ const backlogPending = (taskState?.backlog || []).filter((b) => b.status === "pending");
48
+ return unverified.length > 0 || backlogPending.length > 0;
49
+ }
50
+
51
+ export function verifyRequirement(requirement, evidenceItems = []) {
52
+ if (evidenceItems.length === 0) throw new Error(`Cannot verify ${requirement.id}: no evidence provided`);
53
+ return {
54
+ ...requirement,
55
+ verificationStatus: "VERIFIED",
56
+ evidence: evidenceItems,
57
+ };
58
+ }
59
+
60
+ export function failRequirement(requirement, reason = "") {
61
+ return {
62
+ ...requirement,
63
+ verificationStatus: "FAILED",
64
+ failureReason: reason,
65
+ };
66
+ }
@@ -0,0 +1,89 @@
1
+ // Requirement states
2
+ export const REQUIREMENT_STATES = {
3
+ OPEN: "OPEN",
4
+ IN_PROGRESS: "IN_PROGRESS",
5
+ OBSERVED: "OBSERVED",
6
+ SUPPORTED: "SUPPORTED",
7
+ VERIFIED: "VERIFIED",
8
+ FAILED: "FAILED",
9
+ UNKNOWN: "UNKNOWN"
10
+ };
11
+
12
+ // Validation rules:
13
+ // - OBSERVED never implies VERIFIED
14
+ // - SUPPORTED requires relevant evidence
15
+ // - VERIFIED requires satisfying acceptanceCriteria
16
+ // - FAILED requires contradictory evidence
17
+ // - UNKNOWN means insufficient evidence
18
+ // - No VERIFIED allowed without verificationMethod
19
+ // - No task completion while any mandatory requirement is in OPEN, UNKNOWN, OBSERVED or SUPPORTED
20
+
21
+ export function createRequirement({ id, claim, intent, expectedOutcome, acceptanceCriteria = [], verificationMethod = null, optional = false }) {
22
+ if (!id || !claim) throw new Error("id and claim are required");
23
+ return {
24
+ id,
25
+ claim,
26
+ intent: intent || "",
27
+ expectedOutcome: expectedOutcome || "",
28
+ acceptanceCriteria,
29
+ verificationMethod,
30
+ status: REQUIREMENT_STATES.OPEN,
31
+ evidence: [],
32
+ optional,
33
+ createdAt: Date.now(),
34
+ updatedAt: Date.now()
35
+ };
36
+ }
37
+
38
+ export function isRequirementCompletable(requirement) {
39
+ const terminalStates = [REQUIREMENT_STATES.VERIFIED, REQUIREMENT_STATES.FAILED];
40
+ return terminalStates.includes(requirement.status);
41
+ }
42
+
43
+ export function canCompleteTask(requirements) {
44
+ const mandatory = requirements.filter(r => !r.optional);
45
+ const incomplete = mandatory.filter(r => !isRequirementCompletable(r));
46
+ return {
47
+ canComplete: incomplete.length === 0,
48
+ incompleteRequirements: incomplete.map(r => ({ id: r.id, status: r.status }))
49
+ };
50
+ }
51
+
52
+ // State transitions (per spec)
53
+ export const VALID_TRANSITIONS = {
54
+ [REQUIREMENT_STATES.OPEN]: [REQUIREMENT_STATES.IN_PROGRESS],
55
+ [REQUIREMENT_STATES.IN_PROGRESS]: [REQUIREMENT_STATES.OBSERVED, REQUIREMENT_STATES.FAILED, REQUIREMENT_STATES.UNKNOWN],
56
+ [REQUIREMENT_STATES.OBSERVED]: [REQUIREMENT_STATES.SUPPORTED, REQUIREMENT_STATES.FAILED, REQUIREMENT_STATES.UNKNOWN],
57
+ [REQUIREMENT_STATES.SUPPORTED]: [REQUIREMENT_STATES.VERIFIED, REQUIREMENT_STATES.FAILED, REQUIREMENT_STATES.UNKNOWN],
58
+ [REQUIREMENT_STATES.UNKNOWN]: [REQUIREMENT_STATES.IN_PROGRESS],
59
+ [REQUIREMENT_STATES.VERIFIED]: [REQUIREMENT_STATES.OPEN], // only if new evidence invalidates
60
+ [REQUIREMENT_STATES.FAILED]: [], // terminal
61
+ };
62
+
63
+ export function canTransition(fromState, toState) {
64
+ const allowed = VALID_TRANSITIONS[fromState];
65
+ return allowed && allowed.includes(toState);
66
+ }
67
+
68
+ export function transitionRequirement(requirement, toState, evidence = null) {
69
+ if (!canTransition(requirement.status, toState)) {
70
+ throw new Error(`Invalid transition: ${requirement.status} → ${toState}. Allowed: ${VALID_TRANSITIONS[requirement.status]?.join(", ") || "none"}`);
71
+ }
72
+
73
+ const updated = {
74
+ ...requirement,
75
+ status: toState,
76
+ updatedAt: Date.now()
77
+ };
78
+
79
+ if (evidence) {
80
+ updated.evidence = [...requirement.evidence, evidence];
81
+ }
82
+
83
+ // If moving back to OPEN from VERIFIED, mark as stale
84
+ if (requirement.status === REQUIREMENT_STATES.VERIFIED && toState === REQUIREMENT_STATES.OPEN) {
85
+ updated.invalidatedAt = Date.now();
86
+ }
87
+
88
+ return updated;
89
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Verification Persistence + Stale Evidence — WAM 1.1
3
+ *
4
+ * Change 16 — session-persistence:
5
+ * persistVerificationState, loadVerificationState, createVerificationSnapshot
6
+ *
7
+ * Change 17 — stale-evidence:
8
+ * EVIDENCE_TTL, getEvidenceTTL, isEvidenceStale
9
+ *
10
+ * Persistence root: .wam/verifications/ (mirrors PersistenceManager convention).
11
+ */
12
+
13
+ import fs from "node:fs";
14
+ import path from "node:path";
15
+
16
+ let snapshotCounter = 0;
17
+
18
+ // ── Stale Evidence Constants ────────────────────────────────────────────────
19
+
20
+ /** Default TTL for evidence freshness: 24 hours in milliseconds. */
21
+ export const EVIDENCE_TTL = 24 * 60 * 60 * 1000; // 86_400_000 ms
22
+
23
+ /**
24
+ * Returns the configured TTL for evidence staleness checks (ms).
25
+ * Override via WAM_EVIDENCE_TTL env var for testing flexibility.
26
+ */
27
+ export function getEvidenceTTL() {
28
+ const env = process.env.WAM_EVIDENCE_TTL;
29
+ if (env) {
30
+ const parsed = parseInt(env, 10);
31
+ if (!isNaN(parsed) && parsed > 0) return parsed;
32
+ }
33
+ return EVIDENCE_TTL;
34
+ }
35
+
36
+ /**
37
+ * Determines whether evidence is stale relative to the configured TTL.
38
+ * Evidence is stale when (now - evidence.timestamp) > getEvidenceTTL().
39
+ * Accepts evidence objects with a `timestamp` (number, ms) or `at` (ISO string) field.
40
+ */
41
+ export function isEvidenceStale(evidence) {
42
+ if (!evidence) return true;
43
+ const ttl = getEvidenceTTL();
44
+ const now = Date.now();
45
+
46
+ let evidenceTime;
47
+ if (typeof evidence.timestamp === "number") {
48
+ evidenceTime = evidence.timestamp;
49
+ } else if (typeof evidence.at === "string") {
50
+ evidenceTime = new Date(evidence.at).getTime();
51
+ } else {
52
+ return true; // no recognizable timestamp → stale
53
+ }
54
+
55
+ if (isNaN(evidenceTime)) return true;
56
+ return now - evidenceTime > ttl;
57
+ }
58
+
59
+ // ── Session Persistence ───────────────────────────────────────────────────────
60
+
61
+ const VERIFICATIONS_DIR = path.join(process.cwd(), ".wam", "verifications");
62
+
63
+ function ensureDir(dir) {
64
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
65
+ }
66
+
67
+ function getPath(taskId) {
68
+ return path.join(VERIFICATIONS_DIR, `${taskId}.json`);
69
+ }
70
+
71
+ /**
72
+ * Persists the verification state for a task to disk as JSON.
73
+ * @param {string} taskId
74
+ * @param {object} state — verification state object to serialize
75
+ * @returns {{ taskId, persistedAt, path }}
76
+ */
77
+ export function persistVerificationState(taskId, state) {
78
+ if (!taskId) throw new Error("taskId is required");
79
+ ensureDir(VERIFICATIONS_DIR);
80
+ const filePath = getPath(taskId);
81
+ const record = {
82
+ taskId,
83
+ state,
84
+ persistedAt: new Date().toISOString(),
85
+ };
86
+ fs.writeFileSync(filePath, JSON.stringify(record, null, 2), "utf-8");
87
+ return { taskId, persistedAt: record.persistedAt, path: filePath };
88
+ }
89
+
90
+ /**
91
+ * Loads the verification state for a task from disk.
92
+ * Returns null if no file exists for the task.
93
+ * @param {string} taskId
94
+ * @returns {object|null} — { taskId, state, persistedAt } or null
95
+ */
96
+ export function loadVerificationState(taskId) {
97
+ if (!taskId) throw new Error("taskId is required");
98
+ const filePath = getPath(taskId);
99
+ if (!fs.existsSync(filePath)) return null;
100
+ const raw = fs.readFileSync(filePath, "utf-8");
101
+ return JSON.parse(raw);
102
+ }
103
+
104
+ /**
105
+ * Creates a timestamped snapshot of the current verification state.
106
+ * Appends a snapshot entry to the task's existing record (or creates a new one).
107
+ * Snapshots are stored in `.wam/verifications/{taskId}.snapshots/` as individual files.
108
+ * @param {string} taskId
109
+ * @param {object} state — current verification state to snapshot
110
+ * @returns {{ taskId, snapshotId, snapshotAt, path }}
111
+ */
112
+ export function createVerificationSnapshot(taskId, state) {
113
+ if (!taskId) throw new Error("taskId is required");
114
+ const snapshotsDir = path.join(VERIFICATIONS_DIR, taskId, "snapshots");
115
+ ensureDir(snapshotsDir);
116
+
117
+ const snapshotId = Date.now().toString(36) + "-" + (snapshotCounter++);
118
+ const snapshotAt = new Date().toISOString();
119
+ const snapshot = {
120
+ snapshotId,
121
+ taskId,
122
+ state,
123
+ snapshotAt,
124
+ };
125
+ const filePath = path.join(snapshotsDir, `${snapshotId}.json`);
126
+ fs.writeFileSync(filePath, JSON.stringify(snapshot, null, 2), "utf-8");
127
+
128
+ return { taskId, snapshotId, snapshotAt, path: filePath };
129
+ }
@@ -0,0 +1,127 @@
1
+ import { detectEvidenceGaps } from "../evidence/evidence-gap.js";
2
+ import { EVIDENCE_STRENGTH } from "../evidence/evidence.js";
3
+
4
+ export const VERIFICATION_STRATEGY = {
5
+ EXISTING_TEST: "existing_test",
6
+ TARGETED_COMMAND: "targeted_command",
7
+ TARGETED_INSPECTION: "targeted_inspection",
8
+ MINIMAL_REPRODUCTION: "minimal_reproduction",
9
+ BROADER_TEST: "broader_test",
10
+ MANUAL_VALIDATION: "manual_validation"
11
+ };
12
+
13
+ export function selectMinimalVerification(gap, availableChecks = []) {
14
+ // Prefer existing tests
15
+ const existingTest = availableChecks.find(c => c.type === "test");
16
+ if (existingTest) return { strategy: VERIFICATION_STRATEGY.EXISTING_TEST, check: existingTest };
17
+
18
+ // Then targeted command
19
+ const targetedCmd = availableChecks.find(c => c.type === "command");
20
+ if (targetedCmd) return { strategy: VERIFICATION_STRATEGY.TARGETED_COMMAND, check: targetedCmd };
21
+
22
+ // Then targeted inspection
23
+ const inspection = availableChecks.find(c => c.type === "inspection");
24
+ if (inspection) return { strategy: VERIFICATION_STRATEGY.TARGETED_INSPECTION, check: inspection };
25
+
26
+ // Fallback to broader test
27
+ const broader = availableChecks.find(c => c.type === "broader_test");
28
+ if (broader) return { strategy: VERIFICATION_STRATEGY.BROADER_TEST, check: broader };
29
+
30
+ return { strategy: VERIFICATION_STRATEGY.MANUAL_VALIDATION, check: null };
31
+ }
32
+
33
+ // Change 28 — Verification-strategy: ordered strategy with cost estimation
34
+ export const VERIFICATION_STRATEGY_ORDER = [
35
+ VERIFICATION_STRATEGY.EXISTING_TEST,
36
+ VERIFICATION_STRATEGY.TARGETED_COMMAND,
37
+ VERIFICATION_STRATEGY.TARGETED_INSPECTION,
38
+ VERIFICATION_STRATEGY.MINIMAL_REPRODUCTION,
39
+ VERIFICATION_STRATEGY.BROADER_TEST,
40
+ VERIFICATION_STRATEGY.MANUAL_VALIDATION
41
+ ];
42
+
43
+ export function rankVerificationStrategies(strategies, context) {
44
+ // Always prefer cheaper strategies first
45
+ return strategies
46
+ .filter(s => VERIFICATION_STRATEGY_ORDER.includes(s))
47
+ .sort((a, b) => VERIFICATION_STRATEGY_ORDER.indexOf(a) - VERIFICATION_STRATEGY_ORDER.indexOf(b));
48
+ }
49
+
50
+ export function estimateStrategyCost(strategy) {
51
+ const costs = {
52
+ [VERIFICATION_STRATEGY.EXISTING_TEST]: 1,
53
+ [VERIFICATION_STRATEGY.TARGETED_COMMAND]: 2,
54
+ [VERIFICATION_STRATEGY.TARGETED_INSPECTION]: 3,
55
+ [VERIFICATION_STRATEGY.MINIMAL_REPRODUCTION]: 4,
56
+ [VERIFICATION_STRATEGY.BROADER_TEST]: 5,
57
+ [VERIFICATION_STRATEGY.MANUAL_VALIDATION]: 10
58
+ };
59
+ return costs[strategy] || 10;
60
+ }
61
+
62
+ // Verification-strategy coordination: validate that strategy transitions are legal.
63
+ // NOTE: operates on VERIFICATION_STRATEGY, not policy states. Distinct from
64
+ // policy-state-machine.js#validatePolicyFlow despite the shared name.
65
+ export function validatePolicyFlow(currentPolicy, nextPolicy) {
66
+ const validTransitions = {
67
+ [VERIFICATION_STRATEGY.EXISTING_TEST]: [VERIFICATION_STRATEGY.TARGETED_COMMAND],
68
+ [VERIFICATION_STRATEGY.TARGETED_COMMAND]: [VERIFICATION_STRATEGY.TARGETED_INSPECTION],
69
+ [VERIFICATION_STRATEGY.TARGETED_INSPECTION]: [VERIFICATION_STRATEGY.MINIMAL_REPRODUCTION],
70
+ [VERIFICATION_STRATEGY.MINIMAL_REPRODUCTION]: [VERIFICATION_STRATEGY.BROADER_TEST],
71
+ [VERIFICATION_STRATEGY.BROADER_TEST]: [VERIFICATION_STRATEGY.MANUAL_VALIDATION],
72
+ };
73
+ if (currentPolicy === nextPolicy) return { valid: false, reason: "same-policy" };
74
+ const allowed = validTransitions[currentPolicy] || [];
75
+ if (!allowed.includes(nextPolicy)) return { valid: false, reason: "invalid-transition" };
76
+ return { valid: true };
77
+ }
78
+
79
+ // Verification-strategy coordination: get next strategy in escalation chain.
80
+ // Distinct from policy-state-machine.js#getNextPolicy.
81
+ export function getNextPolicy(currentPolicy) {
82
+ const chain = [
83
+ VERIFICATION_STRATEGY.EXISTING_TEST,
84
+ VERIFICATION_STRATEGY.TARGETED_COMMAND,
85
+ VERIFICATION_STRATEGY.TARGETED_INSPECTION,
86
+ VERIFICATION_STRATEGY.MINIMAL_REPRODUCTION,
87
+ VERIFICATION_STRATEGY.BROADER_TEST,
88
+ VERIFICATION_STRATEGY.MANUAL_VALIDATION,
89
+ ];
90
+ const idx = chain.indexOf(currentPolicy);
91
+ if (idx === -1 || idx === chain.length - 1) return null;
92
+ return chain[idx + 1];
93
+ }
94
+
95
+ /**
96
+ * Fail-closed completion gate over a task's requirements and evidence.
97
+ *
98
+ * Invariants:
99
+ * - A task is `blocked` unless EVERY mandatory (non-optional) requirement is
100
+ * `VERIFIED` AND there are no evidence gaps.
101
+ * - Missing or malformed evidence never passes: absent requirements/evidence
102
+ * default to empty arrays, which blocks completion.
103
+ * - Optional requirements do not affect `blocked`.
104
+ *
105
+ * @param {{ requirements?: Array<{id:string, optional?:boolean, status?:string}>,
106
+ * evidence?: Array<object> }} task
107
+ * @returns {{ blocked: boolean, completedRequirements: number,
108
+ * totalRequirements: number, evidenceGaps: Array<object>,
109
+ * unresolvedRequirements: string[] }}
110
+ */
111
+ export function evaluateCompletionGate(task) {
112
+ const { requirements = [], evidence = [] } = task;
113
+
114
+ const gaps = detectEvidenceGaps(requirements, evidence);
115
+ const mandatory = requirements.filter(r => !r.optional);
116
+ const verified = mandatory.filter(r => r.status === "VERIFIED");
117
+
118
+ const blocked = verified.length !== mandatory.length || gaps.length > 0;
119
+
120
+ return {
121
+ blocked,
122
+ completedRequirements: verified.length,
123
+ totalRequirements: mandatory.length,
124
+ evidenceGaps: gaps,
125
+ unresolvedRequirements: gaps.map(g => g.requirementId)
126
+ };
127
+ }