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,199 @@
1
+ /**
2
+ * Context Classification — categorize context items into semantic types.
3
+ *
4
+ * Categories: FACT, OBSERVATION, CLAIM, ASSUMPTION, DECISION,
5
+ * REQUIREMENT, EVIDENCE, ERROR, ACTION, RESULT.
6
+ *
7
+ * Each raw context record is classified by examining its type, content
8
+ * signals, and metadata flags. The classifier is rule-based and deterministic.
9
+ */
10
+
11
+ /** @typedef {"FACT"|"OBSERVATION"|"CLAIM"|"ASSUMPTION"|"DECISION"|"REQUIREMENT"|"EVIDENCE"|"ERROR"|"ACTION"|"RESULT"} ContextCategory */
12
+
13
+ /**
14
+ * @typedef {Object} ClassificationResult
15
+ * @property {ContextCategory} category
16
+ * @property {number} confidence 0-1
17
+ * @property {string[]} signals
18
+ */
19
+
20
+ // Signal keywords mapped to categories (lowercase matching)
21
+ const CATEGORY_SIGNALS = {
22
+ FACT: ["is", "are", "was", "were", "has", "have", "contains", "version", "file", "path", "directory", "exists", "size", "hash"],
23
+ OBSERVATION: ["observed", "noticed", "detected", "saw", "watched", "monitor", "check", "scan", "logged", "found"],
24
+ CLAIM: ["claim", "assert", "state", "alleged", "purported", "reported"],
25
+ ASSUMPTION: ["assume", "presume", "hypothesis", "suppose", "likely", "probably", "might", "may", "could"],
26
+ DECISION: ["decide", "decided", "decision", "chose", "choose", "selected", "approved", "rejected", "resolve", "concluded"],
27
+ REQUIREMENT: ["must", "should", "required", "requirement", "need to", "necessary", "mandatory", "shall", "must not"],
28
+ EVIDENCE: ["evidence", "proof", "data", "result", "test", "benchmark", "measure", "metric", "sample"],
29
+ ERROR: ["error", "fail", "exception", "throw", "crash", "fault", "exception", "bug", "broken", "stack"],
30
+ ACTION: ["action", "run", "execute", "perform", "task", "step", "do", "implement", "create", "delete", "update"],
31
+ RESULT: ["result", "outcome", "concluded", "finished", "complete", "success", "passed", "failed", "done"],
32
+ };
33
+
34
+ // Type-to-category hints (when content signals are ambiguous)
35
+ const TYPE_HINTS = {
36
+ fact: "FACT",
37
+ observation: "OBSERVATION",
38
+ claim: "CLAIM",
39
+ assumption: "ASSUMPTION",
40
+ decision: "DECISION",
41
+ requirement: "REQUIREMENT",
42
+ evidence: "EVIDENCE",
43
+ error: "ERROR",
44
+ action: "ACTION",
45
+ result: "RESULT",
46
+ tool_call: "ACTION",
47
+ tool_result: "RESULT",
48
+ agent_message: "OBSERVATION",
49
+ system_message: "FACT",
50
+ file_modification: "ACTION",
51
+ git_change: "ACTION",
52
+ requirement_change: "REQUIREMENT",
53
+ verification_result: "RESULT",
54
+ clarification_request: "ACTION",
55
+ reasoning: "ASSUMPTION",
56
+ assumption: "ASSUMPTION",
57
+ plan: "ACTION",
58
+ rationale: "CLAIM",
59
+ };
60
+
61
+ /**
62
+ * Extract text to analyze from a context item.
63
+ * @param {Object} item
64
+ * @returns {string}
65
+ */
66
+ function extractText(item) {
67
+ if (!item || typeof item !== "object") return "";
68
+ if (typeof item.content === "string") return item.content;
69
+ if (typeof item.summary === "string") return item.summary;
70
+ if (typeof item.description === "string") return item.description;
71
+ if (item.metadata && typeof item.metadata === "object") {
72
+ const parts = [
73
+ item.metadata.summary,
74
+ item.metadata.description,
75
+ item.metadata.content,
76
+ ];
77
+ return parts.filter(Boolean).join(" ");
78
+ }
79
+ return JSON.stringify(item.content || "");
80
+ }
81
+
82
+ /**
83
+ * Score a category by counting matching signals in text.
84
+ * Uses word boundary matching to avoid false positives from substrings.
85
+ * @param {string} text
86
+ * @param {string} category
87
+ * @returns {number}
88
+ */
89
+ function scoreCategory(text, category) {
90
+ const signals = CATEGORY_SIGNALS[category] || [];
91
+ let score = 0;
92
+ for (const signal of signals) {
93
+ const s = typeof signal === "string" ? signal : String(signal);
94
+ const sl = s.toLowerCase();
95
+ // Word boundary match: either start of string/non-word char before, or end of string/non-word char after
96
+ const regex = new RegExp(`(?:^|[^a-z0-9_])(${sl.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})(?:[^a-z0-9_]|$)`, "i");
97
+ if (regex.test(text)) {
98
+ score += 1;
99
+ }
100
+ }
101
+ return score;
102
+ }
103
+
104
+ /**
105
+ * Classify a context item into a category.
106
+ * @param {Object} item - Context item with type, content, metadata
107
+ * @returns {ClassificationResult}
108
+ */
109
+ export function classifyContextItem(item) {
110
+ if (!item || typeof item !== "object") {
111
+ return { category: "FACT", confidence: 0.3, signals: ["default"] };
112
+ }
113
+
114
+ const text = extractText(item);
115
+ const type = (item.type || "").toLowerCase();
116
+ const metadata = item.metadata || {};
117
+
118
+ // Check explicit category in metadata first
119
+ if (metadata.category && typeof metadata.category === "string") {
120
+ const cat = metadata.category.toUpperCase();
121
+ if (Object.keys(CATEGORY_SIGNALS).includes(cat)) {
122
+ return { category: cat, confidence: 0.95, signals: ["metadata_explicit"] };
123
+ }
124
+ }
125
+
126
+ // Check explicit classification field
127
+ if (item.classification && typeof item.classification === "string") {
128
+ const cat = item.classification.toUpperCase();
129
+ if (Object.keys(CATEGORY_SIGNALS).includes(cat)) {
130
+ return { category: cat, confidence: 0.9, signals: ["explicit_classification"] };
131
+ }
132
+ }
133
+
134
+ // Score by signal keywords (word-boundary matched)
135
+ const scores = {};
136
+ for (const [category, signals] of Object.entries(CATEGORY_SIGNALS)) {
137
+ scores[category] = scoreCategory(text, category);
138
+ }
139
+
140
+ // ERROR gets forced priority only when no other category has a strictly stronger signal count.
141
+ // A single weak error keyword (e.g. "fail" inside "should fail gracefully") must not preempt
142
+ // a multi-signal requirement like "must ... should ...".
143
+ const maxOtherScore = Math.max(
144
+ ...Object.entries(scores)
145
+ .filter(([c]) => c !== "ERROR")
146
+ .map(([, s]) => s),
147
+ );
148
+ if (scores.ERROR >= 1 && scores.ERROR >= maxOtherScore) {
149
+ return { category: "ERROR", confidence: 0.9, signals: ["error_signal"] };
150
+ }
151
+
152
+ // Type-based classification (primary)
153
+ const typeHint = TYPE_HINTS[type] || "FACT";
154
+
155
+ let bestCategory = typeHint;
156
+ let bestScore = scores[typeHint] || 0;
157
+ let bestIsTypeHint = typeHint !== "FACT";
158
+
159
+ // Override with stronger signal matches
160
+ for (const [category, score] of Object.entries(scores)) {
161
+ if (category !== "ERROR" && score > bestScore) {
162
+ bestScore = score;
163
+ bestCategory = category;
164
+ bestIsTypeHint = false;
165
+ }
166
+ }
167
+
168
+ // Determine confidence
169
+ let confidence;
170
+ if (bestIsTypeHint && bestScore === 0) {
171
+ confidence = 0.6;
172
+ } else if (bestScore >= 3) {
173
+ confidence = 0.9;
174
+ } else if (bestScore >= 1) {
175
+ confidence = 0.75;
176
+ } else if (typeHint) {
177
+ confidence = 0.65;
178
+ } else {
179
+ confidence = 0.5;
180
+ }
181
+
182
+ const signals = [];
183
+ if (metadata.category) signals.push("metadata");
184
+ if (item.classification) signals.push("explicit");
185
+ if (typeHint) signals.push(`type:${type}`);
186
+ if (bestScore > 0) signals.push(`${bestScore}_signals`);
187
+
188
+ return { category: bestCategory, confidence, signals };
189
+ }
190
+
191
+ /**
192
+ * Batch-classify multiple items.
193
+ * @param {Object[]} items
194
+ * @returns {ClassificationResult[]}
195
+ */
196
+ export function classifyContextItems(items) {
197
+ if (!Array.isArray(items)) return [];
198
+ return items.map(classifyContextItem);
199
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Context Compression — Functions for compacting, deltas, pruning,
3
+ * and reconstructing context state.
4
+ *
5
+ * OpenSpec changes: context-compaction, context-delta, context-pruning,
6
+ * context-reconstruction (Changes 44-47).
7
+ */
8
+
9
+ /**
10
+ * Change 44 — compactContext
11
+ * Compresses a full taskState into a minimal context object.
12
+ *
13
+ * Change 73 update: Preserves mandatory fields and provenance
14
+ * metadata explicitly so compacted context retains traceability.
15
+ */
16
+ export function compactContext(taskState) {
17
+ const mandatory = (taskState.requirements || []).filter((r) => !r.optional);
18
+
19
+ return {
20
+ // Core mandatory fields (always preserved)
21
+ currentGoal: taskState.currentGoal || null,
22
+ mandatoryRequirements: mandatory.map((r) => ({
23
+ id: r.id,
24
+ status: r.status,
25
+ provenance: r.provenance || null,
26
+ mandatory: true,
27
+ })),
28
+ currentState: taskState.phase,
29
+ // Evidence
30
+ verifiedFacts: (taskState.requirements || []).filter((r) => r.status === "VERIFIED").map((r) => ({
31
+ id: r.id,
32
+ provenance: r.provenance || null,
33
+ })),
34
+ openQuestions: (taskState.questions || []).filter((q) => q.status === "OPEN").map((q) => q.id),
35
+ evidenceGaps: taskState.evidenceGaps || [],
36
+ contextGaps: taskState.contextGaps || [],
37
+ recentActions: (taskState.actions || []).slice(-5),
38
+ nextConstraint: taskState.nextConstraint || null,
39
+ // Provenance tracking (Change 73)
40
+ _provenance: {
41
+ source: taskState.source || "task_state",
42
+ mandatoryCount: mandatory.length,
43
+ compressedAt: taskState.compressedAt || Date.now(),
44
+ retainedFields: [
45
+ "currentGoal",
46
+ "mandatoryRequirements",
47
+ "currentState",
48
+ "verifiedFacts",
49
+ "openQuestions",
50
+ "evidenceGaps",
51
+ "contextGaps",
52
+ "recentActions",
53
+ "nextConstraint",
54
+ ],
55
+ },
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Change 73 — preserveMandatoryProvenance
61
+ * Explicitly marks which fields in a compacted context are
62
+ * mandatory and must never be stripped by downstream processors.
63
+ */
64
+ export function preserveMandatoryProvenance(compacted) {
65
+ return {
66
+ ...compacted,
67
+ _mandatoryFields: [
68
+ "currentGoal",
69
+ "mandatoryRequirements",
70
+ "currentState",
71
+ "verifiedFacts",
72
+ ],
73
+ _provenance: {
74
+ ...(compacted._provenance || {}),
75
+ preservationGuaranteed: true,
76
+ },
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Change 45 — computeContextDelta
82
+ * Computes the delta between two context snapshots.
83
+ */
84
+ export function computeContextDelta(previousContext, currentContext) {
85
+ const delta = {
86
+ unchanged: [],
87
+ new: [],
88
+ invalidated: [],
89
+ modified: [],
90
+ };
91
+
92
+ const prevKeys = new Set(Object.keys(previousContext));
93
+ const currKeys = new Set(Object.keys(currentContext));
94
+
95
+ // New keys
96
+ for (const key of currKeys) {
97
+ if (!prevKeys.has(key)) {
98
+ delta.new.push(key);
99
+ }
100
+ }
101
+
102
+ // Removed/invalidated
103
+ for (const key of prevKeys) {
104
+ if (!currKeys.has(key)) {
105
+ delta.invalidated.push(key);
106
+ }
107
+ }
108
+
109
+ // Modified
110
+ for (const key of currKeys) {
111
+ if (prevKeys.has(key) && JSON.stringify(previousContext[key]) !== JSON.stringify(currentContext[key])) {
112
+ delta.modified.push(key);
113
+ }
114
+ }
115
+
116
+ // Unchanged
117
+ for (const key of currKeys) {
118
+ if (prevKeys.has(key) && !delta.modified.includes(key) && !delta.new.includes(key)) {
119
+ delta.unchanged.push(key);
120
+ }
121
+ }
122
+
123
+ return delta;
124
+ }
125
+
126
+ /**
127
+ * Change 46 — pruneContext
128
+ * Prunes context based on configurable rules.
129
+ */
130
+ export function pruneContext(context, rules = {}) {
131
+ const {
132
+ removeIrrelevant = true,
133
+ removeDuplicate = true,
134
+ removeStale = true,
135
+ removeInvalidated = true,
136
+ keepHistory = true,
137
+ } = rules;
138
+
139
+ let pruned = { ...context };
140
+
141
+ if (removeStale && pruned.staleItems) {
142
+ pruned.activeItems = (pruned.activeItems || []).filter(i => !pruned.staleItems.includes(i.id));
143
+ }
144
+
145
+ if (removeInvalidated && pruned.invalidatedItems) {
146
+ pruned.activeItems = (pruned.activeItems || []).filter(i => !pruned.invalidatedItems.includes(i.id));
147
+ }
148
+
149
+ if (removeDuplicate && pruned.activeItems) {
150
+ const seen = new Set();
151
+ pruned.activeItems = (pruned.activeItems || []).filter(i => {
152
+ if (seen.has(i.id)) return false;
153
+ seen.add(i.id);
154
+ return true;
155
+ });
156
+ }
157
+
158
+ return pruned;
159
+ }
160
+
161
+ /**
162
+ * Change 47 — reconstructContext
163
+ * Reconstructs a condensed context from WAM state.
164
+ */
165
+ export function reconstructContext(wamState) {
166
+ const { requirements = [], evidence = [], gaps = [], tasks = [] } = wamState;
167
+
168
+ return {
169
+ requirements: requirements.map(r => ({
170
+ id: r.id,
171
+ status: r.status,
172
+ claim: r.claim,
173
+ })),
174
+ verifiedFacts: requirements.filter(r => r.status === "VERIFIED").map(r => r.id),
175
+ invalidFacts: requirements.filter(r => r.status === "INVALIDATED").map(r => r.id),
176
+ openGaps: gaps.filter(g => g.status === "OPEN"),
177
+ recentTasks: tasks.slice(-3),
178
+ };
179
+ }
@@ -0,0 +1,267 @@
1
+
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { logger } from "../integration/logger.js";
5
+
6
+ /**
7
+ * ContextDecisionTracer
8
+ *
9
+ * Handles audit logging for context decision processes.
10
+ * Ensures persistence to .wam/traces/<trace_id>.json with fail-safe behavior.
11
+ */
12
+ export class ContextDecisionTracer {
13
+ constructor(traceId, projectRoot, mode = "standard") {
14
+ this.traceId = traceId;
15
+ this.projectRoot = projectRoot || process.cwd();
16
+ this.mode = mode; // minimal, standard, debug
17
+ this.ledger = {
18
+ traceId,
19
+ timestamp: new Date().toISOString(),
20
+ entries: [],
21
+ };
22
+ }
23
+
24
+ _getTraceFile() {
25
+ let current = this.projectRoot;
26
+
27
+ while (current !== path.parse(current).root) {
28
+ if (fs.existsSync(path.join(current, ".wam"))) {
29
+ // Verify this is a project root to avoid accidental jumps
30
+ const isProjectRoot = fs.existsSync(path.join(current, "package.json")) ||
31
+ fs.existsSync(path.join(current, ".opencode.jsonc"));
32
+
33
+ if (isProjectRoot) {
34
+ const traceDir = path.join(current, ".wam", "traces");
35
+ if (!fs.existsSync(traceDir)) {
36
+ fs.mkdirSync(traceDir, { recursive: true });
37
+ }
38
+ return path.join(traceDir, `${this.traceId}.json`);
39
+ }
40
+ }
41
+ current = path.dirname(current);
42
+ }
43
+
44
+ // Fallback to original behavior if no qualified root found
45
+ const traceDir = path.join(this.projectRoot, ".wam", "traces");
46
+ if (!fs.existsSync(traceDir)) {
47
+ fs.mkdirSync(traceDir, { recursive: true });
48
+ }
49
+ return path.join(traceDir, `${this.traceId}.json`);
50
+ }
51
+
52
+ _persist() {
53
+ let tracePath;
54
+ try {
55
+ tracePath = this._getTraceFile();
56
+ logger.info("AuditPersist", `traceId: ${this.traceId}, entries: ${this.ledger.entries.length}, path: ${tracePath}`);
57
+ if (this.ledger.entries.length === 0) {
58
+ logger.warn("AuditEmpty", `Attempting to persist empty ledger for trace ${this.traceId}`);
59
+ }
60
+ fs.writeFileSync(tracePath, JSON.stringify(this.ledger, null, 2));
61
+ logger.info("AuditSuccess", `Persisted trace to: ${tracePath}`);
62
+ } catch (err) {
63
+ logger.error("AuditFail", `Path: ${tracePath ?? "<unresolved>"} | Error: ${err?.message ?? err}`);
64
+ }
65
+ }
66
+
67
+ _addEntry(type, data) {
68
+ // _persist() already swallows persistence failures; ledger update is in-memory only.
69
+ this.ledger.entries.push({
70
+ type,
71
+ timestamp: new Date().toISOString(),
72
+ ...data,
73
+ });
74
+ this._persist();
75
+ }
76
+
77
+ logConceptExtraction(concept, metadata = {}) {
78
+ this._addEntry("CONCEPT_EXTRACTION", { concept, metadata });
79
+ }
80
+
81
+ logDictionaryResolution(term, resolution, metadata = {}) {
82
+ this._addEntry("DICTIONARY_RESOLUTION", { term, resolution, metadata });
83
+ }
84
+
85
+ logCandidateRetrieval(query, candidatesCount, metadata = {}) {
86
+ this._addEntry("CANDIDATE_RETRIEVAL", { query, candidatesCount, metadata });
87
+ }
88
+
89
+ logSelectionDecision(candidateId, evidenceClassification, reason, metadata = {}) {
90
+ this._addEntry("SELECTION_DECISION", {
91
+ candidateId,
92
+ evidenceClassification, // e.g., 'direct', 'inferred', 'probabilistic'
93
+ reason,
94
+ metadata,
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Registra una decisión técnica con punto de bifurcación, racional, alternativas
100
+ * evaluadas, evidencia y métricas técnicas opcionales. Usado por el Decision Gate
101
+ * para forzar documentación de elecciones arquitectónicas o de modelo de datos.
102
+ *
103
+ * @param {string} decisionPoint - Qué se decidió (ej: "nuevo servicio X", "cambio de schema Y").
104
+ * @param {string} rationale - Por qué se eligió esta ruta.
105
+ * @param {Array} alternatives - Alternativas descartadas [{name, pros, cons, whyRejected}].
106
+ * @param {Array} evidence - Evidencia técnica (benchmarks, docs, RFCs, links, file:line).
107
+ * @param {Object} [technicalMetrics] - Métricas técnicas (latencia, costo, complejidad, etc.).
108
+ */
109
+ logTechnicalDecision(decisionPoint, rationale, alternatives, evidence, technicalMetrics = {}) {
110
+ this._addEntry("TECHNICAL_DECISION", {
111
+ decisionPoint,
112
+ rationale,
113
+ alternatives: Array.isArray(alternatives) ? alternatives : [],
114
+ evidence: Array.isArray(evidence) ? evidence : [],
115
+ technicalMetrics: technicalMetrics || {},
116
+ });
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Valid sources per spec: N0_POLICY, N1_PROJECT, N2_TASK, OBSERVED
122
+ */
123
+ const VALID_SOURCES = new Set(["N0_POLICY", "N1_PROJECT", "N2_TASK", "OBSERVED"]);
124
+ const VALID_CONFIDENCE = new Set(["high", "medium", "low"]);
125
+ const VALID_STATUS = new Set(["accepted", "rejected", "provisional"]);
126
+
127
+ /**
128
+ * Audit a single context-influenced decision and append to contract.contextDecisions.
129
+ * Per spec: { id, date, source, statement, reason, confidence, status }
130
+ *
131
+ * @param {Object} contract - The task contract (must have contextDecisions array, or one is created)
132
+ * @param {Object} decision - { statement, reason, source, confidence, status }
133
+ * @returns {Object} the recorded decision entry
134
+ */
135
+ export function auditDecision(contract, decision) {
136
+ if (!contract || typeof contract !== "object") {
137
+ throw new Error("auditDecision: contract must be an object");
138
+ }
139
+ if (!decision || typeof decision !== "object") {
140
+ throw new Error("auditDecision: decision must be an object");
141
+ }
142
+
143
+ const { statement, reason, source, confidence, status } = decision;
144
+
145
+ if (typeof statement !== "string" || statement.length === 0) {
146
+ throw new Error("auditDecision: statement is required");
147
+ }
148
+ if (typeof reason !== "string" || reason.length === 0) {
149
+ throw new Error("auditDecision: reason is required");
150
+ }
151
+ if (!VALID_SOURCES.has(source)) {
152
+ throw new Error(
153
+ `auditDecision: source must be one of N0_POLICY|N1_PROJECT|N2_TASK|OBSERVED (got: ${source})`,
154
+ );
155
+ }
156
+ if (!VALID_CONFIDENCE.has(confidence)) {
157
+ throw new Error(
158
+ `auditDecision: confidence must be high|medium|low (got: ${confidence})`,
159
+ );
160
+ }
161
+ if (!VALID_STATUS.has(status)) {
162
+ throw new Error(
163
+ `auditDecision: status must be accepted|rejected|provisional (got: ${status})`,
164
+ );
165
+ }
166
+
167
+ if (!Array.isArray(contract.contextDecisions)) {
168
+ contract.contextDecisions = [];
169
+ }
170
+
171
+ const id = `CD${contract.contextDecisions.length + 1}`;
172
+ const entry = {
173
+ id,
174
+ date: new Date().toISOString(),
175
+ source,
176
+ statement,
177
+ reason,
178
+ confidence,
179
+ status,
180
+ };
181
+ contract.contextDecisions.push(entry);
182
+ return entry;
183
+ }
184
+
185
+ /**
186
+ * Inspect the audit trail: returns the contract.contextDecisions array
187
+ * (or an empty list if none).
188
+ */
189
+ export function inspectAudit(contract) {
190
+ if (!contract || typeof contract !== "object") return [];
191
+ return Array.isArray(contract.contextDecisions) ? contract.contextDecisions : [];
192
+ }
193
+
194
+ /**
195
+ * Audit gate. Returns { allowed, reason } indicating whether completion /
196
+ * approval is permitted.
197
+ *
198
+ * Rule: any decision that is (status === "provisional") OR (status ===
199
+ * "accepted" AND confidence === "low") is treated as a CRITICAL unverified
200
+ * context decision and blocks completion until resolved (accepted with
201
+ * medium/high, or rejected).
202
+ */
203
+ export function auditGate(contract) {
204
+ const decisions = inspectAudit(contract);
205
+ const blocking = decisions.filter(
206
+ (d) =>
207
+ d.status === "provisional" ||
208
+ (d.status === "accepted" && d.confidence === "low"),
209
+ );
210
+ if (blocking.length === 0) {
211
+ return { allowed: true };
212
+ }
213
+ return {
214
+ allowed: false,
215
+ reason: `Blocked by ${blocking.length} unverified context decision(s): ${blocking
216
+ .map((d) => d.id)
217
+ .join(", ")}`,
218
+ blocking,
219
+ };
220
+ }
221
+
222
+ /**
223
+ * Resolve a decision by id (used by /wam resolve-decision CLI).
224
+ * Updates status/confidence on the matching entry.
225
+ */
226
+ export function resolveDecision(contract, id, updates) {
227
+ if (!contract || typeof contract !== "object") {
228
+ throw new Error("resolveDecision: contract must be an object");
229
+ }
230
+ const decisions = inspectAudit(contract);
231
+ const idx = decisions.findIndex((d) => d.id === id);
232
+ if (idx === -1) {
233
+ throw new Error(`resolveDecision: decision ${id} not found`);
234
+ }
235
+ if (!updates || typeof updates !== "object") {
236
+ throw new Error("resolveDecision: updates must be an object");
237
+ }
238
+ const current = decisions[idx];
239
+ if (updates.status !== undefined) {
240
+ if (!VALID_STATUS.has(updates.status)) {
241
+ throw new Error(
242
+ `resolveDecision: status must be accepted|rejected|provisional`,
243
+ );
244
+ }
245
+ current.status = updates.status;
246
+ }
247
+ if (updates.confidence !== undefined) {
248
+ if (!VALID_CONFIDENCE.has(updates.confidence)) {
249
+ throw new Error(`resolveDecision: confidence must be high|medium|low`);
250
+ }
251
+ current.confidence = updates.confidence;
252
+ }
253
+ if (updates.statement !== undefined) {
254
+ if (typeof updates.statement !== "string" || updates.statement.length === 0) {
255
+ throw new Error("resolveDecision: statement must be a non-empty string");
256
+ }
257
+ current.statement = updates.statement;
258
+ }
259
+ if (updates.reason !== undefined) {
260
+ if (typeof updates.reason !== "string" || updates.reason.length === 0) {
261
+ throw new Error("resolveDecision: reason must be a non-empty string");
262
+ }
263
+ current.reason = updates.reason;
264
+ }
265
+ decisions[idx] = current;
266
+ return current;
267
+ }