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,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
|
+
}
|