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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +318 -2
  3. package/index.js +1836 -0
  4. package/package.json +70 -4
  5. package/skills/codebase-design/SKILL.md +114 -0
  6. package/skills/design-contract/SKILL.md +27 -0
  7. package/skills/design-critique/SKILL.md +19 -0
  8. package/skills/design-system/SKILL.md +17 -0
  9. package/skills/design-system/references/image-analysis.md +14 -0
  10. package/skills/design-system/references/tokens.md +32 -0
  11. package/skills/design-system/references/ui-rules.md +14 -0
  12. package/skills/diagnosing-bugs/SKILL.md +138 -0
  13. package/skills/frontend-design/SKILL.md +36 -0
  14. package/skills/handoff/SKILL.md +16 -0
  15. package/skills/improve-codebase-architecture/SKILL.md +71 -0
  16. package/skills/linter-negative/SKILL.md +74 -0
  17. package/skills/no-slop-ui/SKILL.md +26 -0
  18. package/skills/prototype/SKILL.md +26 -0
  19. package/skills/registry.json +44087 -0
  20. package/skills/tastemaker/SKILL.md +8 -0
  21. package/skills/tastemaker/references/tastemaker.md +14 -0
  22. package/skills/tdd/SKILL.md +38 -0
  23. package/skills/visual-review/SKILL.md +103 -0
  24. package/skills/visual-review/references/review-gates.md +42 -0
  25. package/skills/visual-review/references/surface-archetypes.md +37 -0
  26. package/skills/wizard/SKILL.md +44 -0
  27. package/skills/writing-for-agents/SKILL.md +81 -0
  28. package/src/cognition/cognition-store.js +251 -0
  29. package/src/cognition/cognitive-state.js +126 -0
  30. package/src/cognition/hypothesis-manager.js +113 -0
  31. package/src/cognition/observation-engine.js +112 -0
  32. package/src/cognition/observation-provenance.js +268 -0
  33. package/src/context/active-context-boundary.js +68 -0
  34. package/src/context/assembly.js +494 -0
  35. package/src/context/assessment-rules.js +55 -0
  36. package/src/context/capsule-staleness.js +267 -0
  37. package/src/context/capsules.js +2 -0
  38. package/src/context/compare-array.js +98 -0
  39. package/src/context/compare-object.js +89 -0
  40. package/src/context/compare-values.js +42 -0
  41. package/src/context/context-assembly-contract.js +71 -0
  42. package/src/context/context-budget-manager.js +232 -0
  43. package/src/context/context-builder.js +49 -0
  44. package/src/context/context-cache.js +55 -0
  45. package/src/context/context-capture.js +243 -0
  46. package/src/context/context-classification.js +199 -0
  47. package/src/context/context-compaction.js +179 -0
  48. package/src/context/context-decision-audit.js +267 -0
  49. package/src/context/context-demotion.js +173 -0
  50. package/src/context/context-dictionary.js +106 -0
  51. package/src/context/context-drift-detection.js +225 -0
  52. package/src/context/context-evaluation.js +374 -0
  53. package/src/context/context-event-ingress.js +100 -0
  54. package/src/context/context-event-normalization.js +224 -0
  55. package/src/context/context-feedback-loop.js +216 -0
  56. package/src/context/context-freshness.js +21 -0
  57. package/src/context/context-graph-builder.js +71 -0
  58. package/src/context/context-graph.js +451 -0
  59. package/src/context/context-history-relevance.js +321 -0
  60. package/src/context/context-interception.js +145 -0
  61. package/src/context/context-invalidation-events.js +154 -0
  62. package/src/context/context-lifecycle.js +170 -0
  63. package/src/context/context-loop-prevention.js +347 -0
  64. package/src/context/context-manager.js +340 -0
  65. package/src/context/context-memory-layers.js +265 -0
  66. package/src/context/context-minimality.js +166 -0
  67. package/src/context/context-normalization.js +130 -0
  68. package/src/context/context-optimization-metrics.js +434 -0
  69. package/src/context/context-output-policy.js +135 -0
  70. package/src/context/context-pack-authority.js +157 -0
  71. package/src/context/context-promotion.js +172 -0
  72. package/src/context/context-query-contract.js +101 -0
  73. package/src/context/context-ranking.js +164 -0
  74. package/src/context/context-rate-distortion.js +261 -0
  75. package/src/context/context-recovery.js +209 -0
  76. package/src/context/context-retrieval-integration.js +60 -0
  77. package/src/context/context-retrieval.js +93 -0
  78. package/src/context/context-router.js +457 -0
  79. package/src/context/context-routing-evaluation.js +331 -0
  80. package/src/context/context-routing-observability.js +296 -0
  81. package/src/context/context-routing-shadow.js +237 -0
  82. package/src/context/context-scope.js +181 -0
  83. package/src/context/context-snapshot.js +226 -0
  84. package/src/context/context-source-registry.js +95 -0
  85. package/src/context/context-sufficiency-gate.js +120 -0
  86. package/src/context/context-sufficiency-oracle.js +167 -0
  87. package/src/context/context-telemetry.js +176 -0
  88. package/src/context/context.js +633 -0
  89. package/src/context/dependency-closure.js +246 -0
  90. package/src/context/page-fault-tracker.js +275 -0
  91. package/src/context/prompt-engine.js +74 -0
  92. package/src/context/runtime-context-graph.js +260 -0
  93. package/src/context/selection.js +2 -0
  94. package/src/context/session.js +2 -0
  95. package/src/context/sufficiency-contract.js +300 -0
  96. package/src/context/tokenizer.js +17 -0
  97. package/src/context-benchmark-router.mjs +125 -0
  98. package/src/evidence/evidence-engine.js +161 -0
  99. package/src/evidence/evidence-factory.js +3 -0
  100. package/src/evidence/evidence-freshness.js +463 -0
  101. package/src/evidence/evidence-gap.js +76 -0
  102. package/src/evidence/evidence-lineage.js +525 -0
  103. package/src/evidence/evidence.js +136 -0
  104. package/src/evidence/freshness-validation.js +187 -0
  105. package/src/evidence/lineage.js +4 -0
  106. package/src/execution/action-evaluation.js +179 -0
  107. package/src/execution/circuit-breaker.js +70 -0
  108. package/src/execution/claim-interception.js +63 -0
  109. package/src/execution/execution-engine.js +258 -0
  110. package/src/execution/execution-intent.js +69 -0
  111. package/src/execution/execution-state.js +89 -0
  112. package/src/execution/formatting.js +60 -0
  113. package/src/execution/l1-sanitize.js +16 -0
  114. package/src/execution/l2-resource-guards.js +19 -0
  115. package/src/execution/l3-invariant-checker.js +5 -0
  116. package/src/execution/l4-containment.js +6 -0
  117. package/src/execution/runtime-guards.js +106 -0
  118. package/src/execution/strategy-identity.js +162 -0
  119. package/src/execution/tool-interceptor.js +7 -0
  120. package/src/execution/tool-result-classifier.js +63 -0
  121. package/src/integration/canary-deploy.js +143 -0
  122. package/src/integration/collector.js +59 -0
  123. package/src/integration/error-recovery.js +6 -0
  124. package/src/integration/event-bus.js +66 -0
  125. package/src/integration/health-checker.js +57 -0
  126. package/src/integration/logger.js +39 -0
  127. package/src/integration/message-handler.js +611 -0
  128. package/src/integration/opencode-db.js +153 -0
  129. package/src/integration/orchestration.js +419 -0
  130. package/src/integration/part-provenance.js +65 -0
  131. package/src/integration/rate-limiter.js +55 -0
  132. package/src/integration/release-gate.js +336 -0
  133. package/src/integration/rollback-manager.js +164 -0
  134. package/src/integration/router-adapter.js +269 -0
  135. package/src/integration/telemetry.js +45 -0
  136. package/src/integration/update-checker.js +10 -0
  137. package/src/persistence/event-sourcing.js +75 -0
  138. package/src/persistence/memory.js +501 -0
  139. package/src/persistence/migration-runner.js +16 -0
  140. package/src/persistence/persistence-manager.js +39 -0
  141. package/src/persistence/transaction-log.js +157 -0
  142. package/src/policy/action-capabilities.js +116 -0
  143. package/src/policy/action-policy.js +4 -0
  144. package/src/policy/assessment-engine.js +104 -0
  145. package/src/policy/assumption-tracking.js +66 -0
  146. package/src/policy/budget-enforcer.js +7 -0
  147. package/src/policy/config-validator.js +102 -0
  148. package/src/policy/execution-assessment.js +142 -0
  149. package/src/policy/failure-classification.js +5 -0
  150. package/src/policy/feature-flags.js +65 -0
  151. package/src/policy/governance-enforcement.js +118 -0
  152. package/src/policy/locale.js +15 -0
  153. package/src/policy/multi-signer.js +151 -0
  154. package/src/policy/policy-state-machine.js +97 -0
  155. package/src/policy/ponytail-constraint.js +38 -0
  156. package/src/policy/project-inspector.js +93 -0
  157. package/src/policy/query-builder.js +14 -0
  158. package/src/policy/request-classifier.js +79 -0
  159. package/src/policy/risk-engine.js +266 -0
  160. package/src/policy/scope-enforcement.js +140 -0
  161. package/src/policy/secret-scanner.js +68 -0
  162. package/src/policy/skill-routing.js +104 -0
  163. package/src/policy/strategy-capabilities.js +211 -0
  164. package/src/policy/strategy.js +6 -0
  165. package/src/policy/uncertainty.js +117 -0
  166. package/src/shared/container.js +44 -0
  167. package/src/shared/decision-types.js +24 -0
  168. package/src/shared/simple-test.js +160 -0
  169. package/src/shared/wam-events.js +52 -0
  170. package/src/skills/engine.js +1705 -0
  171. package/src/skills/skill-registry.js +19 -0
  172. package/src/skills/skill-routing.js +340 -0
  173. package/src/state/lifecycle-manager.js +113 -0
  174. package/src/state/operational-state.js +198 -0
  175. package/src/state/projections.js +47 -0
  176. package/src/state/replay-engine.js +5 -0
  177. package/src/state/requirement-ownership.js +20 -0
  178. package/src/state/requirement-state.js +152 -0
  179. package/src/state/run-event-identity.js +172 -0
  180. package/src/state/state-engine.js +19 -0
  181. package/src/state/state-machine.js +119 -0
  182. package/src/state/state-schema.js +318 -0
  183. package/src/state/state-store.js +335 -0
  184. package/src/state/state-transition.js +84 -0
  185. package/src/state/task-dependencies.js +287 -0
  186. package/src/state/task-execution.js +217 -0
  187. package/src/state/task-lifecycle.js +59 -0
  188. package/src/state/task-runs.js +361 -0
  189. package/src/state/task-state-file.js +86 -0
  190. package/src/state/task-state.js +41 -0
  191. package/src/state/task-status.js +54 -0
  192. package/src/state/task-store.js +48 -0
  193. package/src/state/wam-state.js +148 -0
  194. package/src/verification/completion-gate-simple.js +8 -0
  195. package/src/verification/completion-gate.js +183 -0
  196. package/src/verification/contract.js +9 -0
  197. package/src/verification/contradiction-resolution.js +255 -0
  198. package/src/verification/diagnostic-engine.js +659 -0
  199. package/src/verification/false-completion-prevention.js +100 -0
  200. package/src/verification/hook-validator.js +6 -0
  201. package/src/verification/requirements.js +6 -0
  202. package/src/verification/verification-context.js +162 -0
  203. package/src/verification/verification-lifecycle.js +66 -0
  204. package/src/verification/verification-model.js +89 -0
  205. package/src/verification/verification-persistence.js +129 -0
  206. package/src/verification/verification-policy.js +127 -0
  207. package/src/verification/verification.js +415 -0
@@ -0,0 +1,494 @@
1
+ /**
2
+ * Context Assembly Layer — Context Pack Builder.
3
+ *
4
+ * OpenSpec changes: context-assembly-layer, refactor-context-engine,
5
+ * wam-context-budget-admission.
6
+ *
7
+ * Rol: decide qué nivel (N0/N1/N2/N3) entra al pack y emite N0/N1/N2.
8
+ * Capsule selection dentro de N3 se delega a context.js (Context Selection Engine).
9
+ *
10
+ * Budget partitioning with admission policy:
11
+ * budget = reserved (MANDATORY) + flex (CONDITIONAL + OPTIONAL)
12
+ * MANDATORY items are preserved even if they exceed budget.
13
+ * OPTIONAL items are dropped first when budget is tight.
14
+ *
15
+ * Admission classes:
16
+ * MANDATORY — N0, N2, required dependencies (never dropped)
17
+ * CONDITIONAL — cognition, relevant sections (dropped after OPTIONAL)
18
+ * OPTIONAL — extra context, skills (dropped first)
19
+ *
20
+ * 4 niveles con fuente canónica, obligación y prohibición:
21
+ * N0 Global/Policy — obligatorio, tiny (MANDATORY)
22
+ * N1 Project — selectivo por dominio (CONDITIONAL)
23
+ * N2 Task — obligatorio (live task state) (MANDATORY)
24
+ * N3 Session — capsules por utility (OPTIONAL)
25
+ *
26
+ * Prohibido: L4 ephemeral, superseded, transcript, docs/dominios sin match.
27
+ */
28
+
29
+ import fs from "node:fs";
30
+ import path from "node:path";
31
+ import { getOperationalContext, summarizeOperationalContext, normalizeConfidence, confidenceLabel } from "../persistence/memory.js";
32
+ import { selectContext, estimateCapsuleTokens, getSessionId } from "./context.js";
33
+ import { loadCognitiveState, compactCognitiveState } from "../cognition/cognitive-state.js";
34
+ import { routeAndAdapt } from "../integration/router-adapter.js";
35
+ import { buildContextGraph } from "./context-graph-builder.js";
36
+ import { buildRuntimeContextGraph } from "./runtime-context-graph.js";
37
+ import { ADMISSION } from "./context-router.js";
38
+
39
+ /**
40
+ * Admission classes are sourced from Context Router.
41
+ * Assembly must not reinterpret or reclassify admission decisions.
42
+ */
43
+
44
+ /**
45
+ * @typedef {Object} AdmissionItem
46
+ * @property {string} level - Context level (N0, N1, N2, N3)
47
+ * @property {string} admission - Admission class
48
+ * @property {string} reason - Why this item has this admission class
49
+ * @property {number} tokenCost - Estimated token cost
50
+ * @property {string} text - The actual content
51
+ */
52
+
53
+
54
+ function tokenize(text = "") {
55
+ return new Set(
56
+ (text || "")
57
+ .toLowerCase()
58
+ .replace(/[^a-z0-9áéíóúñü\s]/g, " ")
59
+ .split(/\s+/)
60
+ .filter((w) => w.length > 2)
61
+ );
62
+ }
63
+
64
+ function overlap(aTokens, bTokens) {
65
+ if (!aTokens.size || !bTokens.size) return 0;
66
+ let hit = 0;
67
+ for (const t of bTokens) if (aTokens.has(t)) hit++;
68
+ return hit / Math.max(aTokens.size, 1);
69
+ }
70
+
71
+ function estTokens(text) {
72
+ return Math.ceil((text || "").length / 4);
73
+ }
74
+
75
+ function extractRelevantSections(docName, body, taskTokens, { base = false } = {}) {
76
+ const sections = (body || "").split(/^## /m).map((s) => s.trim()).filter(Boolean);
77
+ if (base || !sections.length) return body || "";
78
+ const hits = sections.filter((s) => overlap(taskTokens, tokenize(s.split("\n")[0] + " " + s.slice(0, 200))) > 0);
79
+ if (!hits.length) return "";
80
+ return "# " + hits.join("\n\n# ");
81
+ }
82
+
83
+ /**
84
+ * Cap on individually-rendered requirement capsules in N3.
85
+ *
86
+ * Every requirement node is MANDATORY in the router, so a task with dozens of
87
+ * requirements produces one capsule per requirement. The mandatory N2 line
88
+ * already reports requirement counts, so listing each requirement here is
89
+ * duplication that floods the pack. Above this cap the requirement set is
90
+ * consolidated into a single summary line.
91
+ */
92
+ const MAX_VISIBLE_REQS = 8;
93
+
94
+ function isRequirementCapsule(c) {
95
+ return c?.scope === "requirement" || String(c?.context_id || "").startsWith("req-");
96
+ }
97
+
98
+ /**
99
+ * Ensambla el Context Pack de la tarea.
100
+ *
101
+ * @returns { levels, lines, budget_used, budget, budget_violation, reserved, flex, rationale, continuation }
102
+ */
103
+ export function assembleContext({
104
+ prompt = "",
105
+ taskId = "",
106
+ classification = "normal",
107
+ mode = "NORMAL",
108
+ continuation = false,
109
+ projectPath = process.cwd(),
110
+ budget = 4000,
111
+ taskState = null,
112
+ runState = null,
113
+ evidenceLineage = [],
114
+ cognitionState = null,
115
+ decisions = [],
116
+ constraints = [],
117
+ artifacts = [],
118
+ observations = [],
119
+ hypotheses = [],
120
+ experiments = [],
121
+ skillRegistry = null,
122
+ selectedSkills = [],
123
+ useLegacySelector = false,
124
+ collector = null,
125
+ } = {}) {
126
+ const levels = { N0: [], N1: [], N2: [], N3: [] };
127
+ const rationale = [];
128
+ const taskTokens = tokenize(prompt);
129
+ let routerSufficiency = null;
130
+ // C02: Context Router is the canonical authority. Legacy selector is opt-in
131
+ // via WAM_CONTEXT_SELECTOR=legacy in env/options. Default is router-only.
132
+ const legacySelectorAllowed = process.env.WAM_CONTEXT_SELECTOR === "legacy" || useLegacySelector;
133
+ let selectionSource = legacySelectorAllowed ? "legacy" : "router";
134
+ const isTrivial = classification === "trivial" || mode === "FAST";
135
+ const isArch = classification === "architectural" || mode === "STRICT";
136
+
137
+ /** @type {AdmissionItem[]} */
138
+ const admissionItems = [];
139
+
140
+ const reserve = (level, text, admission = ADMISSION.MANDATORY, reason = "required") => {
141
+ const t = estTokens(text);
142
+ levels[level].push(text);
143
+ admissionItems.push({ level, admission, reason, tokenCost: t, text });
144
+ if (typeof globalThis.__wamReserveLines !== "undefined") {
145
+ globalThis.__wamReserveLines.push({ level, text });
146
+ }
147
+ return t;
148
+ };
149
+
150
+ const spend = (level, text, admission = ADMISSION.OPTIONAL, reason = "context") => {
151
+ const t = estTokens(text);
152
+ admissionItems.push({ level, admission, reason, tokenCost: t, text });
153
+ if (t > flex) {
154
+ rationale.push(`${level}: excede presupuesto (${t} tok, restante ${flex})`);
155
+ return 0;
156
+ }
157
+ levels[level].push(text);
158
+ flex -= t;
159
+ return t;
160
+ };
161
+
162
+ /** Drop OPTIONAL items first, then CONDITIONAL, to free budget for MANDATORY. */
163
+ const dropByAdmission = (minAdmission) => {
164
+ const order = [ADMISSION.OPTIONAL, ADMISSION.CONDITIONAL];
165
+ const minIdx = order.indexOf(minAdmission);
166
+ const droppable = order.slice(minIdx);
167
+
168
+ let freed = 0;
169
+ for (const admission of droppable) {
170
+ const items = admissionItems.filter((i) => i.admission === admission && levels[i.level].includes(i.text));
171
+ for (const item of items) {
172
+ const idx = levels[item.level].indexOf(item.text);
173
+ if (idx !== -1) {
174
+ levels[item.level].splice(idx, 1);
175
+ flex += item.tokenCost;
176
+ freed += item.tokenCost;
177
+ rationale.push(`admission: dropped ${item.level} (${admission}) — ${item.reason}`);
178
+ }
179
+ }
180
+ }
181
+ return freed;
182
+ };
183
+
184
+ // -- N0 Global/Policy (MANDATORY) -----------------------------------------
185
+ const n0Text = "[wam N0 policy] autonomy loop | soft-archive only (U1)";
186
+ const n0Spent = reserve("N0", n0Text, ADMISSION.MANDATORY, "global policy");
187
+
188
+ // -- N2 Task (reservado, obligatorio) -------------------------------------
189
+ const liveFile = path.join(projectPath, ".wam", "tasks", taskId, "context.md");
190
+ let liveBody = "";
191
+ try {
192
+ if (fs.existsSync(liveFile)) liveBody = fs.readFileSync(liveFile, "utf-8").trim();
193
+ } catch {}
194
+ if (taskState) {
195
+ const reqs = taskState.requirements || [];
196
+ const pend = reqs.filter((r) => r.status !== "done" && r.status !== "verified").length;
197
+ const nextActionTruncated = (taskState.nextAction || "—").slice(0, 80);
198
+ liveBody = [
199
+ `task: ${taskId} — ${taskState.phase} / ${taskState.contract?.status || "?"}`,
200
+ `req: ${pend}/${reqs.length} pend | next: ${nextActionTruncated}`,
201
+ ].join("\n") || liveBody;
202
+ }
203
+ const n2Head = liveBody ? `[wam N2 task]\n${liveBody}` : "";
204
+ const n2TaskSpent = n2Head ? reserve("N2", n2Head, ADMISSION.MANDATORY, "live task state") : 0;
205
+ let reserved = n0Spent + n2TaskSpent;
206
+
207
+ // Cognitive state injection (compact, only if cognition exists) - also conditional, but handled by router
208
+ const cognitionRaw = loadCognitiveState(projectPath);
209
+ const hasCognition =
210
+ cognitionRaw.activeHypotheses.length > 0 ||
211
+ cognitionRaw.rejectedHypotheses.length > 0 ||
212
+ cognitionRaw.recentExperiments.length > 0 ||
213
+ cognitionRaw.criticalObservations.length > 0;
214
+ if (hasCognition) {
215
+ const compact = compactCognitiveState(cognitionRaw);
216
+ const cogText = `[wam N2 cognition] ${JSON.stringify(compact)}`;
217
+ const cogTokens = estTokens(cogText);
218
+ // Cognition is CONDITIONAL — can be dropped if budget is tight, but router should have accounted for this
219
+ admissionItems.push({ level: "N2", admission: ADMISSION.CONDITIONAL, reason: "cognitive state", tokenCost: cogTokens, text: cogText });
220
+ reserved += cogTokens;
221
+ levels.N2.push(cogText);
222
+ }
223
+ const budget_violation = reserved > budget;
224
+ let flex = Math.max(0, budget - reserved);
225
+ if (budget_violation) {
226
+ rationale.push(`VIOLACIÓN: Reserva N0+N2 (${reserved}) excede budget (${budget})`);
227
+ // Admission policy: try to drop OPTIONAL and CONDITIONAL items to make room
228
+ const freed = dropByAdmission(ADMISSION.MANDATORY);
229
+ if (freed > 0) {
230
+ rationale.push(`admission: freed ${freed} tokens by dropping non-MANDATORY items`);
231
+ }
232
+ // Recalculate flex after dropping items
233
+ flex = Math.max(0, budget - reserved);
234
+ }
235
+
236
+ // -- Continuation: solo N2 (ya reservado y emitido) -----------------------
237
+ if (!continuation) {
238
+ // -- N1 Project (selectivo por dominio, consume flex) -------------------
239
+ const ctx = getOperationalContext(projectPath);
240
+ const recent = ctx.recentChanges?.body || "";
241
+ // Recent changes are only relevant when their scope/content overlaps the
242
+ // task. An unrelated task must not inherit unrelated operational history.
243
+ const recentSummary = recent
244
+ .split(/^## /m)
245
+ .map((s) => s.trim())
246
+ .filter(Boolean)
247
+ .filter((s) => taskTokens.length === 0 || overlap(taskTokens, tokenize(s)) > 0)
248
+ .slice(0, 3)
249
+ .join("\n# ");
250
+ if (!isTrivial) {
251
+ // N1 solo si hay memoria operacional real — cero líneas vacías (rigor = ahorro de tokens)
252
+ const n1summary = summarizeOperationalContext(projectPath);
253
+ if (n1summary) spend("N1", `[wam N1 project] ${n1summary}`, ADMISSION.CONDITIONAL, "project context");
254
+ if (recentSummary) spend("N1", `[wam N1 recent] ${recentSummary.slice(0, 500)}`, ADMISSION.CONDITIONAL, "recent changes");
255
+
256
+ // Provenance: inferido ≠ hecho
257
+ for (const [key, label] of [["project", "project.md"], ["architecture", "architecture.md"], ["decisions", "decisions.md"], ["constraints", "constraints.md"]]) {
258
+ const meta = ctx[key]?.meta || {};
259
+ const conf = normalizeConfidence(meta.confidence);
260
+ if (meta.source === "inferred" && conf < 0.4) {
261
+ spend("N1", `[wam N1 WARNING] ${label} es INFERIDO (conf ${meta.confidence} ${confidenceLabel(conf)}) — no es decisión confirmada; validar antes de asumir`, ADMISSION.CONDITIONAL, "provenance warning");
262
+ }
263
+ }
264
+ const archDoc = ctx.architecture?.body || "";
265
+ if (isArch && archDoc.trim()) {
266
+ const arch = extractRelevantSections("architecture", archDoc, taskTokens, { base: true });
267
+ if (arch.trim()) spend("N1", `[wam N1 architecture] ${arch.slice(0, 600)}`, ADMISSION.CONDITIONAL, "architecture context");
268
+ }
269
+ const projectDecisionsText = extractRelevantSections("decisions", ctx.decisions?.body || "", taskTokens, { base: false });
270
+ if (projectDecisionsText.trim()) spend("N1", `[wam N1 decisions] ${projectDecisionsText.slice(0, 600)}`, ADMISSION.CONDITIONAL, "decisions context");
271
+ const projectConstraintsText = extractRelevantSections("constraints", ctx.constraints?.body || "", taskTokens, { base: isArch });
272
+ if (projectConstraintsText.trim()) spend("N1", `[wam N1 constraints] ${projectConstraintsText.slice(0, 400)}`, ADMISSION.CONDITIONAL, "constraints context");
273
+ }
274
+
275
+ // -- N3 Session (Context Router is canonical authority) -----------------
276
+ // C02: Router is the sole canonical selector. Legacy selector is opt-in
277
+ // via WAM_CONTEXT_SELECTOR=legacy in env/options. If router cannot resolve
278
+ // or returns insufficient without the legacy flag, surface insufficiency —
279
+ // do NOT silently swap to it.
280
+ if (!isTrivial) {
281
+ // Canonical primary source: context-graph-builder.buildContextGraph
282
+ // provides the base task/requirement/evidence nodes. The runtime
283
+ // builder then LAYERS on the additional state slices (decisions,
284
+ // constraints, artifacts, observations, cognition, hypotheses,
285
+ // experiments) by delegating to buildContextGraph for the base.
286
+ // This keeps context-graph-builder.js as the single source of truth
287
+ // for the canonical node/edge schema while preserving full coverage.
288
+ // The active task id may live on the assembly options rather than on the
289
+ // taskState payload. Normalize it so the router graph is anchored on the
290
+ // real task node instead of degrading to a fallback.
291
+ const effectiveTaskState = taskState
292
+ ? { ...taskState, taskId: taskState.taskId || taskId }
293
+ : (taskId ? { taskId } : taskState);
294
+ const graph = buildRuntimeContextGraph({
295
+ taskState: effectiveTaskState,
296
+ runState,
297
+ evidenceLineage,
298
+ cognitionState,
299
+ decisions,
300
+ constraints,
301
+ artifacts,
302
+ observations,
303
+ hypotheses,
304
+ experiments,
305
+ });
306
+ const routerPkg = routeAndAdapt(graph, {
307
+ taskId: effectiveTaskState?.taskId,
308
+ budget: flex,
309
+ root: projectPath,
310
+ });
311
+
312
+ // C02: Router is canonical authority. Legacy only if explicitly requested.
313
+ let pkg;
314
+ if (routerPkg.source === "router" && routerPkg.capsules.length > 0) {
315
+ pkg = routerPkg;
316
+ selectionSource = "router";
317
+ routerSufficiency = pkg.sufficiency || null;
318
+ rationale.push("N3: using Context Router for selection");
319
+ } else if (legacySelectorAllowed) {
320
+ pkg = selectContext(prompt, { budget: flex, root: projectPath, sessionId: getSessionId(projectPath) });
321
+ selectionSource = "legacy";
322
+ routerSufficiency = "insufficient";
323
+ rationale.push("N3: using legacy selector (explicit WAM_CONTEXT_SELECTOR=legacy)");
324
+ } else {
325
+ pkg = {
326
+ ...routerPkg,
327
+ capsules: routerPkg.capsules || [],
328
+ sufficiency: routerPkg.sufficiency || "insufficient",
329
+ omitted: routerPkg.omitted || [],
330
+ missing: routerPkg.missing || [],
331
+ contract: routerPkg.contract || null,
332
+ };
333
+ selectionSource = "router";
334
+ routerSufficiency = "insufficient";
335
+ rationale.push("N3: router returned insufficient — no legacy fallback without WAM_CONTEXT_SELECTOR=legacy");
336
+ }
337
+
338
+ // Capsule store enrichment: the Context Router selects graph nodes while
339
+ // the Context Selection Engine (context.js) selects persisted capsules.
340
+ // Both feed N3; dedupe by context_id. The router stays the sufficiency
341
+ // authority (C02) — this is not a selector fallback.
342
+ if (selectionSource !== "legacy") {
343
+ const capsulePkg = selectContext(prompt, {
344
+ budget: flex,
345
+ root: projectPath,
346
+ sessionId: getSessionId(projectPath),
347
+ });
348
+ const merged = [];
349
+ for (const c of pkg.capsules || []) {
350
+ // The active task is already emitted as the mandatory N2 line;
351
+ // re-listing it under N3 is duplication, not supporting context.
352
+ if (c.context_id === effectiveTaskState?.taskId) continue;
353
+ if (!merged.some((m) => m.context_id === c.context_id)) merged.push(c);
354
+ }
355
+ for (const c of capsulePkg.capsules || []) {
356
+ if (!merged.some((m) => m.context_id === c.context_id)) merged.push(c);
357
+ }
358
+ pkg = { ...pkg, capsules: merged };
359
+ }
360
+
361
+ const emitCapsule = (c) => {
362
+ const head = `[wam N3 ${c.level || "N3"} ${c.provenance}] ${c.context_id} — ${(c.purpose || "").slice(0, 100)}`;
363
+ const contentMax = 800;
364
+ const truncated = (c.content || "").length > contentMax
365
+ ? c.content.slice(0, contentMax) + `...[truncado: ver /wam ctx get ${c.context_id}]`
366
+ : (c.content || "");
367
+ const line = truncated ? `${head}\n content: ${truncated.replace(/\n+/g, " ").slice(0, contentMax)}` : head;
368
+ spend("N3", line, ADMISSION.OPTIONAL, `capsule ${c.context_id} (${selectionSource})`);
369
+ };
370
+
371
+ // Split requirement-derived capsules from everything else. Requirement
372
+ // nodes are MANDATORY in the router, so a task with many requirements
373
+ // yields one capsule each; emitting them all is duplication of the N2
374
+ // summary. Above MAX_VISIBLE_REQS, consolidate into a single count line.
375
+ const requirementCapsules = (pkg.capsules || []).filter(isRequirementCapsule);
376
+ const otherCapsules = (pkg.capsules || []).filter((c) => !isRequirementCapsule(c));
377
+
378
+ for (const c of otherCapsules) emitCapsule(c);
379
+
380
+ if (requirementCapsules.length > MAX_VISIBLE_REQS) {
381
+ spend(
382
+ "N3",
383
+ `[wam N3 requirements] ${requirementCapsules.length} reqs consolidados — ver /wam req list`,
384
+ ADMISSION.OPTIONAL,
385
+ "requirement capsule consolidation"
386
+ );
387
+ } else {
388
+ for (const c of requirementCapsules) emitCapsule(c);
389
+ }
390
+
391
+ // Router mandatory omissions: explicitly report as admission failures, never silently drop
392
+ for (const omitted of pkg.omitted || []) {
393
+ if (omitted.admission === ADMISSION.MANDATORY) {
394
+ rationale.push(`admission: MANDATORY omitted by router — ${omitted.id}: ${omitted.reason}`);
395
+ }
396
+ }
397
+
398
+ if (pkg.sufficiency === "insufficient") {
399
+ rationale.push(`N3: sufficiency insufficient — faltan ${pkg.missing.join(", ")}`);
400
+ const conditions = pkg.contract?.conditions || [];
401
+ const contractReport = conditions
402
+ .filter((c) => c.status !== "SATISFIED" && c.severity === "MANDATORY")
403
+ .map((c) => ` - [${c.id}] ${c.type}: ${c.description} (${c.status})`)
404
+ .join("\n");
405
+ spend("N3", `[wam N3 warning] contexto insuficiente:\n${contractReport}\n/wam ctx get <q>`, ADMISSION.MANDATORY, "sufficiency warning");
406
+ }
407
+ }
408
+
409
+ // -- N3 Skills (contenido de skills seleccionadas, consume flex) ---------
410
+ // Inyecta el contenido real de las skills seleccionadas para que el agente
411
+ // respete las restricciones y patrones de cada skill (layer responsibility).
412
+ // Lee directamente del registry sin importar engine.js (evita circular import).
413
+ if (!isTrivial && skillRegistry && selectedSkills.length > 0) {
414
+ const skillBudget = Math.floor(flex * 0.4); // 40% del flex restante para skills
415
+ let skillSpent = 0;
416
+ const skillContentMax = 1200; // max chars por skill
417
+
418
+ for (const skill of selectedSkills) {
419
+ if (skillSpent >= skillBudget) {
420
+ rationale.push(`N3: budget agotado para skills (${skillSpent}/${skillBudget})`);
421
+ break;
422
+ }
423
+
424
+ const skillData = skillRegistry[skill.id];
425
+ if (!skillData) {
426
+ rationale.push(`N3: ${skill.id} no encontrada en registry`);
427
+ continue;
428
+ }
429
+
430
+ const content = skillData.content || "";
431
+ if (!content.trim()) {
432
+ rationale.push(`N3: ${skill.id} sin contenido embebido`);
433
+ continue;
434
+ }
435
+
436
+ const truncated = content.length > skillContentMax
437
+ ? content.slice(0, skillContentMax) + `...[truncado]`
438
+ : content;
439
+
440
+ const head = `[wam N3 skill] ${skill.id} — ${(skill.reason || "").slice(0, 100)}`;
441
+ const line = `${head}\n content: ${truncated.replace(/\n+/g, " ").slice(0, skillContentMax)}`;
442
+ const cost = spend("N3", line, ADMISSION.OPTIONAL, `skill ${skill.id}`);
443
+ skillSpent += cost;
444
+ rationale.push(`N3: ${skill.id} inyectada (${cost} tok, reason: ${skill.reason})`);
445
+ }
446
+ }
447
+ }
448
+
449
+ const lines = [...levels.N0, ...levels.N1, ...levels.N2, ...levels.N3];
450
+
451
+ // Admission report — Router is canonical authority for sufficiency when available.
452
+ const mandatoryItems = admissionItems.filter((i) => i.admission === ADMISSION.MANDATORY);
453
+ const mandatoryTokens = mandatoryItems.reduce((sum, i) => sum + i.tokenCost, 0);
454
+
455
+ let sufficiency;
456
+ if (routerSufficiency) {
457
+ sufficiency = routerSufficiency === "ok" ? "sufficient" : "insufficient";
458
+ } else if (budget_violation && mandatoryTokens > budget) {
459
+ sufficiency = "insufficient";
460
+ } else {
461
+ sufficiency = "sufficient";
462
+ }
463
+
464
+ // Instrumentation: record assembly metrics
465
+ if (collector) {
466
+ collector.record("Context_assembled");
467
+ collector.set("Mandatory_items", admissionItems.filter((i) => i.admission === ADMISSION.MANDATORY).length);
468
+ collector.set("Conditional_items", admissionItems.filter((i) => i.admission === ADMISSION.CONDITIONAL).length);
469
+ collector.set("Optional_items", admissionItems.filter((i) => i.admission === ADMISSION.OPTIONAL).length);
470
+ collector.set("Tokens_before", admissionItems.reduce((sum, i) => sum + i.tokenCost, 0));
471
+ collector.set("Tokens_after", lines.reduce((sum, line) => sum + estTokens(line), 0));
472
+ }
473
+
474
+ return {
475
+ levels: Object.fromEntries(Object.entries(levels).map(([k, v]) => [k, v.length])),
476
+ lines,
477
+ budget_used: budget - flex,
478
+ budget,
479
+ budget_violation,
480
+ reserved,
481
+ flex,
482
+ rationale,
483
+ continuation,
484
+ source: selectionSource || "legacy",
485
+ admission: {
486
+ sufficiency,
487
+ mandatoryCount: mandatoryItems.length,
488
+ mandatoryTokens,
489
+ droppedItems: rationale.filter((r) => r.startsWith("admission: dropped")),
490
+ },
491
+ };
492
+ }
493
+
494
+ export { estimateCapsuleTokens, ADMISSION };
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Assessment rules — shared classification primitives.
3
+ * Single source of truth for SUPPORTED / INCONCLUSIVE / CONTRADICTED verdicts.
4
+ */
5
+
6
+ export const AssessmentResult = {
7
+ SUPPORTED: "SUPPORTED",
8
+ INCONCLUSIVE: "INCONCLUSIVE",
9
+ CONTRADICTED: "CONTRADICTED",
10
+ };
11
+
12
+ export function isMissing(value) {
13
+ return value === undefined || value === null;
14
+ }
15
+
16
+ export function classifyScalar(expected, actual) {
17
+ if (isMissing(expected) || isMissing(actual)) {
18
+ return AssessmentResult.INCONCLUSIVE;
19
+ }
20
+
21
+ if (expected === actual) {
22
+ return AssessmentResult.SUPPORTED;
23
+ }
24
+
25
+ const tExp = typeof expected;
26
+ const tAct = typeof actual;
27
+
28
+ if (tExp !== tAct) {
29
+ return AssessmentResult.CONTRADICTED;
30
+ }
31
+
32
+ if (tExp === "boolean") {
33
+ return AssessmentResult.CONTRADICTED;
34
+ }
35
+
36
+ if (tExp === "number") {
37
+ const tolerance = Math.abs(expected) * 0.1 + 0.01;
38
+ return Math.abs(expected - actual) <= tolerance
39
+ ? AssessmentResult.SUPPORTED
40
+ : AssessmentResult.CONTRADICTED;
41
+ }
42
+
43
+ if (tExp === "string") {
44
+ const placeholderValues = new Set(["pending", "unknown", "not applicable"]);
45
+ if (placeholderValues.has(actual)) {
46
+ return AssessmentResult.INCONCLUSIVE;
47
+ }
48
+ if (actual.includes(expected) || expected.includes(actual)) {
49
+ return AssessmentResult.SUPPORTED;
50
+ }
51
+ return AssessmentResult.CONTRADICTED;
52
+ }
53
+
54
+ return AssessmentResult.CONTRADICTED;
55
+ }