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