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,157 @@
1
+ /**
2
+ * Context Pack Source of Truth — clarifies authority between components.
3
+ *
4
+ * Defines clear responsibility boundaries:
5
+ *
6
+ * Context Graph = relationships (what connects to what)
7
+ * Context Router = WHAT context is needed (graph-based resolution)
8
+ * Context Assembly = HOW to pack it (N0-N3 levels, budget, admission)
9
+ * Context Selection = capsule retrieval (legacy, fallback only)
10
+ *
11
+ * Authority:
12
+ * Router decides WHAT
13
+ * Assembly decides HOW
14
+ * Selection retrieves (no decisions)
15
+ */
16
+
17
+ /**
18
+ * @typedef {"graph" | "router" | "assembly" | "selection"} Component
19
+ */
20
+
21
+ /**
22
+ * @typedef {Object} AuthorityDecision
23
+ * @property {Component} component
24
+ * @property {string} decision
25
+ * @property {string} reason
26
+ * @property {number} timestamp
27
+ */
28
+
29
+ /**
30
+ * Component responsibilities (canonical).
31
+ */
32
+ export const RESPONSIBILITIES = {
33
+ graph: {
34
+ authority: "relationships",
35
+ description: "Stores and queries relationships between context nodes",
36
+ decisions: ["edge types", "node storage", "graph traversal primitives"],
37
+ },
38
+ router: {
39
+ authority: "what",
40
+ description: "Determines minimum sufficient context for a task",
41
+ decisions: [
42
+ "which nodes are required",
43
+ "dependency resolution",
44
+ "contradiction detection",
45
+ "sufficiency assessment",
46
+ ],
47
+ },
48
+ assembly: {
49
+ authority: "how",
50
+ description: "Packs context into N0-N3 levels within budget",
51
+ decisions: [
52
+ "level allocation (N0-N3)",
53
+ "budget partitioning",
54
+ "admission classes",
55
+ "final pack contents",
56
+ ],
57
+ },
58
+ selection: {
59
+ authority: "retrieval",
60
+ description: "Retrieves capsules from storage (legacy, no decisions)",
61
+ decisions: ["capsule lookup", "lifecycle filtering"],
62
+ },
63
+ };
64
+
65
+ /**
66
+ * Validate that a decision is within component's authority.
67
+ *
68
+ * @param {Component} component
69
+ * @param {string} decision
70
+ * @returns {{ valid: boolean, reason: string }}
71
+ */
72
+ export function validateAuthority(component, decision) {
73
+ const resp = RESPONSIBILITIES[component];
74
+ if (!resp) {
75
+ return { valid: false, reason: `Unknown component: ${component}` };
76
+ }
77
+
78
+ const isAuthorized = resp.decisions.some(
79
+ (d) => decision.toLowerCase().includes(d.toLowerCase())
80
+ );
81
+
82
+ if (isAuthorized) {
83
+ return { valid: true, reason: `${component} has authority over ${decision}` };
84
+ }
85
+
86
+ return {
87
+ valid: false,
88
+ reason: `${component} does NOT have authority over ${decision}. Authorized: ${resp.decisions.join(", ")}`,
89
+ };
90
+ }
91
+
92
+ /**
93
+ * Create an authority decision record.
94
+ *
95
+ * @param {Component} component
96
+ * @param {string} decision
97
+ * @param {string} reason
98
+ * @returns {AuthorityDecision}
99
+ */
100
+ export function recordDecision(component, decision, reason) {
101
+ return {
102
+ component,
103
+ decision,
104
+ reason,
105
+ timestamp: Date.now(),
106
+ };
107
+ }
108
+
109
+ /**
110
+ * Resolve authority conflict between two components.
111
+ *
112
+ * @param {Component} a
113
+ * @param {Component} b
114
+ * @param {string} decisionType
115
+ * @returns {{ winner: Component, reason: string }}
116
+ */
117
+ export function resolveAuthorityConflict(a, b, decisionType) {
118
+ // Authority hierarchy: assembly > router > selection > graph
119
+ const hierarchy = ["assembly", "router", "selection", "graph"];
120
+
121
+ const idxA = hierarchy.indexOf(a);
122
+ const idxB = hierarchy.indexOf(b);
123
+
124
+ if (idxA === -1) return { winner: b, reason: `${a} not in hierarchy` };
125
+ if (idxB === -1) return { winner: a, reason: `${b} not in hierarchy` };
126
+
127
+ if (idxA < idxB) {
128
+ return { winner: a, reason: `${a} has higher authority than ${b} for ${decisionType}` };
129
+ }
130
+ return { winner: b, reason: `${b} has higher authority than ${a} for ${decisionType}` };
131
+ }
132
+
133
+ /**
134
+ * Generate authority report for a context pack assembly.
135
+ *
136
+ * @param {Object} context - Assembly context
137
+ * @returns {string} Formatted report
138
+ */
139
+ export function generateAuthorityReport(context) {
140
+ const lines = ["[wam authority]"];
141
+
142
+ lines.push(" Graph: stores relationships");
143
+ lines.push(" Router: determines WHAT context is needed");
144
+ lines.push(" Assembly: determines HOW to pack it");
145
+ lines.push(" Selection: retrieves capsules (no decisions)");
146
+
147
+ if (context?.routerDecision) {
148
+ lines.push(` Router decision: ${context.routerDecision}`);
149
+ }
150
+ if (context?.assemblyDecision) {
151
+ lines.push(` Assembly decision: ${context.assemblyDecision}`);
152
+ }
153
+
154
+ return lines.join("\n");
155
+ }
156
+
157
+ export default RESPONSIBILITIES;
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Context Promotion — move items to higher-priority memory layers.
3
+ *
4
+ * Promotion examples: N3→N2, N2→N1, N1→N0, N3→N0, ARCHIVE→N3, etc.
5
+ *
6
+ * Side effects handled by this module:
7
+ * - Lifecycle transitions when necessary (COMPRESSED → ACTIVE)
8
+ * - Metadata updates (memoryLayer, updatedAt, promotion history)
9
+ * - Event logging via lifecycleHistory entry
10
+ *
11
+ * All operations are immutable: the input item is never mutated.
12
+ */
13
+
14
+ import {
15
+ WORKING_LAYERS,
16
+ MEMORY_LAYERS,
17
+ LAYER_CONFIG,
18
+ isMemoryLayer,
19
+ } from "./context-memory-layers.js";
20
+ import {
21
+ LIFECYCLE_STATES,
22
+ transition as lifecycleTransition,
23
+ canTransition as isValidTransition,
24
+ } from "./context-lifecycle.js";
25
+
26
+ /**
27
+ * @typedef {Object} PromotionEvent
28
+ * @property {"promote"} type
29
+ * @property {string} fromLayer
30
+ * @property {string} toLayer
31
+ * @property {boolean} lifecycleChanged
32
+ * @property {string} [fromLifecycle]
33
+ * @property {string} [toLifecycle]
34
+ * @property {number} timestamp
35
+ */
36
+
37
+ /**
38
+ * @typedef {Object} PromotionResult
39
+ * @property {Object} item — the promoted item (immutable copy with updates)
40
+ * @property {PromotionEvent} event
41
+ */
42
+
43
+ /**
44
+ * Promote a context item to a higher-priority memory layer.
45
+ *
46
+ * Handles necessary lifecycle transitions (e.g. COMPRESSED → ACTIVE)
47
+ * before the layer change, updates metadata, and logs the event.
48
+ *
49
+ * @param {Object} item - The item to promote (must have memoryLayer or inferable category)
50
+ * @param {string} targetLayer - Target memory layer (N0–N3)
51
+ * @returns {PromotionResult}
52
+ */
53
+ export function promoteItem(item, targetLayer) {
54
+ if (!item || typeof item !== "object") {
55
+ throw new TypeError("item must be an object");
56
+ }
57
+ if (!isMemoryLayer(targetLayer)) {
58
+ throw new TypeError(`invalid target layer: ${targetLayer}`);
59
+ }
60
+ if (!WORKING_LAYERS.includes(targetLayer)) {
61
+ throw new TypeError(
62
+ `cannot promote to ${targetLayer}: use demoteItem for archive moves`,
63
+ );
64
+ }
65
+
66
+ const currentLayer = resolveCurrentLayer(item);
67
+ const currentPriority = LAYER_CONFIG[currentLayer].priority;
68
+ const targetPriority = LAYER_CONFIG[targetLayer].priority;
69
+
70
+ if (targetPriority >= currentPriority) {
71
+ throw new TypeError(
72
+ `not a promotion: ${currentLayer} (priority ${currentPriority}) → ${targetLayer} (priority ${targetPriority})`,
73
+ );
74
+ }
75
+
76
+ const now = Date.now();
77
+
78
+ // --- Lifecycle transition if necessary ---
79
+ let lifecycle = item.lifecycle || LIFECYCLE_STATES.CREATED;
80
+ let lifecycleChanged = false;
81
+ let fromLifecycle = lifecycle;
82
+
83
+ if (lifecycle === LIFECYCLE_STATES.COMPRESSED) {
84
+ if (isValidTransition(lifecycle, LIFECYCLE_STATES.ACTIVE)) {
85
+ const transitioned = lifecycleTransition(item, LIFECYCLE_STATES.ACTIVE, {
86
+ reason: "promotion-from-compressed",
87
+ timestamp: now,
88
+ });
89
+ lifecycle = transitioned.lifecycle;
90
+ lifecycleChanged = true;
91
+ }
92
+ }
93
+
94
+ let toLifecycle = lifecycle;
95
+
96
+ // --- Build new item (immutable) ---
97
+ const promotedItem = {
98
+ ...item,
99
+ memoryLayer: targetLayer,
100
+ lifecycle,
101
+ updatedAt: now,
102
+ metadata: {
103
+ ...(item.metadata || {}),
104
+ promotedFrom: currentLayer,
105
+ promotedAt: now,
106
+ },
107
+ lifecycleHistory: [
108
+ ...(Array.isArray(item.lifecycleHistory) ? item.lifecycleHistory : []),
109
+ {
110
+ type: "promote",
111
+ from: currentLayer,
112
+ to: targetLayer,
113
+ ...(lifecycleChanged
114
+ ? { lifecycle: { from: fromLifecycle, to: toLifecycle } }
115
+ : {}),
116
+ timestamp: now,
117
+ },
118
+ ],
119
+ };
120
+
121
+ const event = {
122
+ type: "promote",
123
+ fromLayer: currentLayer,
124
+ toLayer: targetLayer,
125
+ lifecycleChanged,
126
+ ...(lifecycleChanged ? { fromLifecycle, toLifecycle } : {}),
127
+ timestamp: now,
128
+ };
129
+
130
+ return { item: Object.freeze(promotedItem), event };
131
+ }
132
+
133
+ /**
134
+ * Resolve the current memory layer of an item.
135
+ * Falls back from item.memoryLayer → category-based default → N3.
136
+ *
137
+ * @param {Object} item
138
+ * @returns {string}
139
+ */
140
+ function resolveCurrentLayer(item) {
141
+ if (isMemoryLayer(item.memoryLayer)) {
142
+ return item.memoryLayer;
143
+ }
144
+ const category = item.type || item.category;
145
+ if (category && typeof category === "string") {
146
+ return inferLayerFromCategory(category);
147
+ }
148
+ return "N3";
149
+ }
150
+
151
+ /**
152
+ * Infer a memory layer from a category string using the same
153
+ * rules as context-memory-layers.js CATEGORY_DEFAULT_LAYER.
154
+ *
155
+ * @param {string} category
156
+ * @returns {string}
157
+ */
158
+ function inferLayerFromCategory(category) {
159
+ const defaults = {
160
+ REQUIREMENT: "N0",
161
+ DECISION: "N0",
162
+ FACT: "N1",
163
+ OBSERVATION: "N2",
164
+ EVIDENCE: "N2",
165
+ RESULT: "N2",
166
+ ERROR: "N2",
167
+ CLAIM: "N3",
168
+ ASSUMPTION: "N3",
169
+ ACTION: "N3",
170
+ };
171
+ return defaults[category] || "N3";
172
+ }
@@ -0,0 +1,101 @@
1
+ import { LIFECYCLE_STATES } from "./context-lifecycle.js";
2
+ import { CONTEXT_EVENT_TYPES } from "./context-event-ingress.js";
3
+
4
+ export const CONTEXT_PURPOSE = Object.freeze({
5
+ PLANNING: "PLANNING",
6
+ IMPLEMENTATION: "IMPLEMENTATION",
7
+ INVESTIGATION: "INVESTIGATION",
8
+ DEBUGGING: "DEBUGGING",
9
+ VALIDATION: "VALIDATION",
10
+ REVIEW: "REVIEW",
11
+ RESUME: "RESUME",
12
+ });
13
+
14
+ const PURPOSE_LAYER_REQUIREMENT = Object.freeze({
15
+ [CONTEXT_PURPOSE.PLANNING]: ["R0", "R1", "R2"],
16
+ [CONTEXT_PURPOSE.IMPLEMENTATION]: ["R0", "R1"],
17
+ [CONTEXT_PURPOSE.INVESTIGATION]: ["H0", "H1"],
18
+ [CONTEXT_PURPOSE.DEBUGGING]: ["OBS", "EVID"],
19
+ [CONTEXT_PURPOSE.VALIDATION]: ["VER"],
20
+ [CONTEXT_PURPOSE.REVIEW]: ["ALL"],
21
+ [CONTEXT_PURPOSE.RESUME]: ["ALL"],
22
+ });
23
+
24
+ export function buildContextQuery({
25
+ taskId,
26
+ subtaskId = null,
27
+ requirementId = null,
28
+ hypothesisId = null,
29
+ experimentId = null,
30
+ executionId = null,
31
+ taskType = null,
32
+ phase = null,
33
+ activeFiles = [],
34
+ activeTools = [],
35
+ knownFailures = [],
36
+ unresolvedQuestions = [],
37
+ budget = null,
38
+ purpose = CONTEXT_PURPOSE.PLANNING,
39
+ } = {}) {
40
+ if (!taskId) throw new TypeError("contextQuery requires taskId");
41
+
42
+ return {
43
+ taskId,
44
+ subtaskId,
45
+ requirementId,
46
+ hypothesisId,
47
+ experimentId,
48
+ executionId,
49
+ taskType,
50
+ phase,
51
+ activeFiles,
52
+ activeTools,
53
+ knownFailures,
54
+ unresolvedQuestions,
55
+ budget,
56
+ purpose,
57
+ _layerRequirement: PURPOSE_LAYER_REQUIREMENT[purpose] || PURPOSE_LAYER_REQUIREMENT[CONTEXT_PURPOSE.PLANNING],
58
+ _timestamp: Date.now(),
59
+ };
60
+ }
61
+
62
+ export function matchesQuery(item, query) {
63
+ if (query.requirementId && item.requirementId !== query.requirementId) return false;
64
+ if (query.hypothesisId && item.hypothesisId !== query.hypothesisId) return false;
65
+ if (query.experimentId && item.experimentId !== query.experimentId) return false;
66
+ if (query.executionId && item.executionId !== query.executionId) return false;
67
+ if (query.taskType && item.taskType && item.taskType !== query.taskType) return false;
68
+ if (query.phase && item.phase && item.phase !== query.phase) return false;
69
+ if (query.activeFiles && query.activeFiles.length > 0 && item.scope === "FILE") {
70
+ if (!query.activeFiles.some((f) => (item.content?.path || "").includes(f))) return false;
71
+ }
72
+ if (query.activeTools && query.activeTools.length > 0 && item.scope === "TOOL") {
73
+ if (!query.activeTools.some((t) => (item.content?.tool || "").includes(t))) return false;
74
+ }
75
+ return true;
76
+ }
77
+
78
+ export function scoreRelevance(item, query) {
79
+ let score = 0;
80
+ if (item.mandatoryIncluded === true) score += 10;
81
+ if (item.lifecycle === LIFECYCLE_STATES.ACTIVE) score += 5;
82
+ if (item.lifecycle === LIFECYCLE_STATES.CREATED) score += 3;
83
+ if (item.importance === "HIGH") score += 5;
84
+ if (item.importance === "MEDIUM") score += 3;
85
+ if (item.unresolvedCriticalUnknowns?.length > 0) score += 8;
86
+ if (query.requirementId && item.requirementId === query.requirementId) score += 10;
87
+ if (query.hypothesisId && item.hypothesisId === query.hypothesisId) score += 10;
88
+ if (query.experimentId && item.experimentId === query.experimentId) score += 10;
89
+ if (query.executionId && item.executionId === query.executionId) score += 10;
90
+ const recency = (query._timestamp || Date.now()) - item.timestamp;
91
+ if (recency < 60000) score += 5;
92
+ else if (recency < 300000) score += 3;
93
+ return score;
94
+ }
95
+
96
+ export function retrieveRelevantContext(items, query) {
97
+ const filtered = items.filter((item) => matchesQuery(item, query));
98
+ const scored = filtered.map((item) => ({ item, score: scoreRelevance(item, query) }));
99
+ scored.sort((a, b) => b.score - a.score);
100
+ return scored.map((s) => s.item);
101
+ }
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Context Ranking — Relevance Scoring & Ranking.
3
+ *
4
+ * Change 70: Implements multi-signal ranking for context retrieval.
5
+ * Scores combine term matching, recency, importance, lifecycle state,
6
+ * and provenance quality.
7
+ *
8
+ * Ranking formula (weighted sum):
9
+ * score = w1 * termMatch + w2 * recency + w3 * importance + w4 * lifecycleBoost
10
+ *
11
+ * Invariant: All scores normalized to [0, 1] range. Higher = more relevant.
12
+ */
13
+
14
+ const WEIGHTS = Object.freeze({
15
+ termMatch: 0.40,
16
+ recency: 0.15,
17
+ importance: 0.25,
18
+ lifecycle: 0.10,
19
+ provenance: 0.10,
20
+ });
21
+
22
+ const LIFECYCLE_BOOST = Object.freeze({
23
+ ACTIVE: 1.0,
24
+ CREATED: 0.8,
25
+ COMPRESSED: 0.4,
26
+ STALE: 0.2,
27
+ INVALIDATED: 0.0,
28
+ ARCHIVED: 0.1,
29
+ });
30
+
31
+ const PROVENANCE_BOOST = Object.freeze({
32
+ user_decided: 1.0,
33
+ observed: 0.7,
34
+ inferred: 0.3,
35
+ });
36
+
37
+ /**
38
+ * Tokenizes text into a set of lowercase terms.
39
+ * @param {string} text
40
+ * @returns {Set<string>}
41
+ */
42
+ function tokenize(text = "") {
43
+ return new Set(
44
+ (text || "")
45
+ .toLowerCase()
46
+ .replace(/[^a-z0-9áéíóúñü\s]/g, " ")
47
+ .split(/\s+/)
48
+ .filter((w) => w.length > 1)
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Computes term overlap ratio between query and item text.
54
+ * @param {string} query
55
+ * @param {string} itemText
56
+ * @returns {number} 0..1
57
+ */
58
+ function computeTermMatch(query, itemText) {
59
+ const queryTokens = tokenize(query);
60
+ const itemTokens = tokenize(itemText);
61
+ if (!queryTokens.size || !itemTokens.size) return 0;
62
+
63
+ let hits = 0;
64
+ for (const t of queryTokens) {
65
+ if (itemTokens.has(t)) hits++;
66
+ }
67
+ return hits / queryTokens.size;
68
+ }
69
+
70
+ /**
71
+ * Computes recency score based on item timestamp.
72
+ * More recent = higher score. Decays over 30 days.
73
+ * @param {number} [timestamp]
74
+ * @param {number} [now]
75
+ * @returns {number} 0..1
76
+ */
77
+ function computeRecency(timestamp, now = Date.now()) {
78
+ if (!timestamp) return 0.5;
79
+ const ageMs = now - timestamp;
80
+ const ageDays = ageMs / (1000 * 60 * 60 * 24);
81
+ // Exponential decay: 1.0 at 0 days, ~0.1 at 30 days
82
+ return Math.exp(-ageDays / 30);
83
+ }
84
+
85
+ /**
86
+ * Computes importance score from item metadata.
87
+ * @param {number} [importance]
88
+ * @returns {number} 0..1
89
+ */
90
+ function computeImportance(importance) {
91
+ if (typeof importance !== "number") return 0.5;
92
+ return Math.min(1, Math.max(0, importance / 10));
93
+ }
94
+
95
+ /**
96
+ * Computes lifecycle boost factor.
97
+ * @param {string} lifecycle
98
+ * @returns {number}
99
+ */
100
+ function computeLifecycleBoost(lifecycle) {
101
+ return LIFECYCLE_BOOST[lifecycle] ?? 0.3;
102
+ }
103
+
104
+ /**
105
+ * Computes provenance quality factor.
106
+ * @param {string} provenance
107
+ * @returns {number}
108
+ */
109
+ function computeProvenanceBoost(provenance) {
110
+ return PROVENANCE_BOOST[provenance] ?? 0.3;
111
+ }
112
+
113
+ /**
114
+ * Ranks context items by multi-signal relevance scoring.
115
+ *
116
+ * @param {Array} items - Context items to rank.
117
+ * @param {string} query - Search query for term matching.
118
+ * @param {Object} [options]
119
+ * @param {string[]} [options.requiredTerms] - Terms that must appear (binary filter).
120
+ * @param {Object} [options.weights] - Override default weights.
121
+ * @returns {Array} Items sorted by relevanceScore descending, with score attached.
122
+ */
123
+ export function rankContextItems(items, query, options = {}) {
124
+ const { requiredTerms = [], weights = {} } = options;
125
+ const w = { ...WEIGHTS, ...weights };
126
+
127
+ // Validate weights sum ~1.0 (informational only)
128
+ const weightSum = Object.values(w).reduce((a, b) => a + b, 0);
129
+
130
+ return items
131
+ .map((item) => {
132
+ const text = item.content || item.label || item.text || item.summary || `${item.id}`;
133
+ const termScore = computeTermMatch(query, text);
134
+ const recencyScore = computeRecency(item.timestamp || item.updatedAt);
135
+ const importanceScore = computeImportance(item.importance);
136
+ const lifecycleScore = computeLifecycleBoost(item.lifecycle);
137
+ const provenanceScore = computeProvenanceBoost(item.provenance);
138
+
139
+ // Required terms binary filter (not a score component)
140
+ if (requiredTerms.length > 0) {
141
+ const itemTokens = tokenize(text);
142
+ const allRequiredPresent = requiredTerms.every((t) => itemTokens.has(t.toLowerCase()));
143
+ if (!allRequiredPresent) {
144
+ return { ...item, relevanceScore: 0, relevanceReason: "required_terms_missing" };
145
+ }
146
+ }
147
+
148
+ const compositeScore =
149
+ w.termMatch * termScore +
150
+ w.recency * recencyScore +
151
+ w.importance * importanceScore +
152
+ w.lifecycle * lifecycleScore +
153
+ w.provenance * provenanceScore;
154
+
155
+ return {
156
+ ...item,
157
+ relevanceScore: Math.round(compositeScore * 1000) / 1000,
158
+ relevanceReason: `term:${termScore.toFixed(2)} recency:${recencyScore.toFixed(2)} importance:${importanceScore.toFixed(2)}`,
159
+ };
160
+ })
161
+ .sort((a, b) => (b.relevanceScore ?? 0) - (a.relevanceScore ?? 0));
162
+ }
163
+
164
+ export { WEIGHTS, LIFECYCLE_BOOST, PROVENANCE_BOOST };