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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +318 -2
  3. package/index.js +1836 -0
  4. package/package.json +70 -4
  5. package/skills/codebase-design/SKILL.md +114 -0
  6. package/skills/design-contract/SKILL.md +27 -0
  7. package/skills/design-critique/SKILL.md +19 -0
  8. package/skills/design-system/SKILL.md +17 -0
  9. package/skills/design-system/references/image-analysis.md +14 -0
  10. package/skills/design-system/references/tokens.md +32 -0
  11. package/skills/design-system/references/ui-rules.md +14 -0
  12. package/skills/diagnosing-bugs/SKILL.md +138 -0
  13. package/skills/frontend-design/SKILL.md +36 -0
  14. package/skills/handoff/SKILL.md +16 -0
  15. package/skills/improve-codebase-architecture/SKILL.md +71 -0
  16. package/skills/linter-negative/SKILL.md +74 -0
  17. package/skills/no-slop-ui/SKILL.md +26 -0
  18. package/skills/prototype/SKILL.md +26 -0
  19. package/skills/registry.json +44087 -0
  20. package/skills/tastemaker/SKILL.md +8 -0
  21. package/skills/tastemaker/references/tastemaker.md +14 -0
  22. package/skills/tdd/SKILL.md +38 -0
  23. package/skills/visual-review/SKILL.md +103 -0
  24. package/skills/visual-review/references/review-gates.md +42 -0
  25. package/skills/visual-review/references/surface-archetypes.md +37 -0
  26. package/skills/wizard/SKILL.md +44 -0
  27. package/skills/writing-for-agents/SKILL.md +81 -0
  28. package/src/cognition/cognition-store.js +251 -0
  29. package/src/cognition/cognitive-state.js +126 -0
  30. package/src/cognition/hypothesis-manager.js +113 -0
  31. package/src/cognition/observation-engine.js +112 -0
  32. package/src/cognition/observation-provenance.js +268 -0
  33. package/src/context/active-context-boundary.js +68 -0
  34. package/src/context/assembly.js +494 -0
  35. package/src/context/assessment-rules.js +55 -0
  36. package/src/context/capsule-staleness.js +267 -0
  37. package/src/context/capsules.js +2 -0
  38. package/src/context/compare-array.js +98 -0
  39. package/src/context/compare-object.js +89 -0
  40. package/src/context/compare-values.js +42 -0
  41. package/src/context/context-assembly-contract.js +71 -0
  42. package/src/context/context-budget-manager.js +232 -0
  43. package/src/context/context-builder.js +49 -0
  44. package/src/context/context-cache.js +55 -0
  45. package/src/context/context-capture.js +243 -0
  46. package/src/context/context-classification.js +199 -0
  47. package/src/context/context-compaction.js +179 -0
  48. package/src/context/context-decision-audit.js +267 -0
  49. package/src/context/context-demotion.js +173 -0
  50. package/src/context/context-dictionary.js +106 -0
  51. package/src/context/context-drift-detection.js +225 -0
  52. package/src/context/context-evaluation.js +374 -0
  53. package/src/context/context-event-ingress.js +100 -0
  54. package/src/context/context-event-normalization.js +224 -0
  55. package/src/context/context-feedback-loop.js +216 -0
  56. package/src/context/context-freshness.js +21 -0
  57. package/src/context/context-graph-builder.js +71 -0
  58. package/src/context/context-graph.js +451 -0
  59. package/src/context/context-history-relevance.js +321 -0
  60. package/src/context/context-interception.js +145 -0
  61. package/src/context/context-invalidation-events.js +154 -0
  62. package/src/context/context-lifecycle.js +170 -0
  63. package/src/context/context-loop-prevention.js +347 -0
  64. package/src/context/context-manager.js +340 -0
  65. package/src/context/context-memory-layers.js +265 -0
  66. package/src/context/context-minimality.js +166 -0
  67. package/src/context/context-normalization.js +130 -0
  68. package/src/context/context-optimization-metrics.js +434 -0
  69. package/src/context/context-output-policy.js +135 -0
  70. package/src/context/context-pack-authority.js +157 -0
  71. package/src/context/context-promotion.js +172 -0
  72. package/src/context/context-query-contract.js +101 -0
  73. package/src/context/context-ranking.js +164 -0
  74. package/src/context/context-rate-distortion.js +261 -0
  75. package/src/context/context-recovery.js +209 -0
  76. package/src/context/context-retrieval-integration.js +60 -0
  77. package/src/context/context-retrieval.js +93 -0
  78. package/src/context/context-router.js +457 -0
  79. package/src/context/context-routing-evaluation.js +331 -0
  80. package/src/context/context-routing-observability.js +296 -0
  81. package/src/context/context-routing-shadow.js +237 -0
  82. package/src/context/context-scope.js +181 -0
  83. package/src/context/context-snapshot.js +226 -0
  84. package/src/context/context-source-registry.js +95 -0
  85. package/src/context/context-sufficiency-gate.js +120 -0
  86. package/src/context/context-sufficiency-oracle.js +167 -0
  87. package/src/context/context-telemetry.js +176 -0
  88. package/src/context/context.js +633 -0
  89. package/src/context/dependency-closure.js +246 -0
  90. package/src/context/page-fault-tracker.js +275 -0
  91. package/src/context/prompt-engine.js +74 -0
  92. package/src/context/runtime-context-graph.js +260 -0
  93. package/src/context/selection.js +2 -0
  94. package/src/context/session.js +2 -0
  95. package/src/context/sufficiency-contract.js +300 -0
  96. package/src/context/tokenizer.js +17 -0
  97. package/src/context-benchmark-router.mjs +125 -0
  98. package/src/evidence/evidence-engine.js +161 -0
  99. package/src/evidence/evidence-factory.js +3 -0
  100. package/src/evidence/evidence-freshness.js +463 -0
  101. package/src/evidence/evidence-gap.js +76 -0
  102. package/src/evidence/evidence-lineage.js +525 -0
  103. package/src/evidence/evidence.js +136 -0
  104. package/src/evidence/freshness-validation.js +187 -0
  105. package/src/evidence/lineage.js +4 -0
  106. package/src/execution/action-evaluation.js +179 -0
  107. package/src/execution/circuit-breaker.js +70 -0
  108. package/src/execution/claim-interception.js +63 -0
  109. package/src/execution/execution-engine.js +258 -0
  110. package/src/execution/execution-intent.js +69 -0
  111. package/src/execution/execution-state.js +89 -0
  112. package/src/execution/formatting.js +60 -0
  113. package/src/execution/l1-sanitize.js +16 -0
  114. package/src/execution/l2-resource-guards.js +19 -0
  115. package/src/execution/l3-invariant-checker.js +5 -0
  116. package/src/execution/l4-containment.js +6 -0
  117. package/src/execution/runtime-guards.js +106 -0
  118. package/src/execution/strategy-identity.js +162 -0
  119. package/src/execution/tool-interceptor.js +7 -0
  120. package/src/execution/tool-result-classifier.js +63 -0
  121. package/src/integration/canary-deploy.js +143 -0
  122. package/src/integration/collector.js +59 -0
  123. package/src/integration/error-recovery.js +6 -0
  124. package/src/integration/event-bus.js +66 -0
  125. package/src/integration/health-checker.js +57 -0
  126. package/src/integration/logger.js +39 -0
  127. package/src/integration/message-handler.js +611 -0
  128. package/src/integration/opencode-db.js +153 -0
  129. package/src/integration/orchestration.js +419 -0
  130. package/src/integration/part-provenance.js +65 -0
  131. package/src/integration/rate-limiter.js +55 -0
  132. package/src/integration/release-gate.js +336 -0
  133. package/src/integration/rollback-manager.js +164 -0
  134. package/src/integration/router-adapter.js +269 -0
  135. package/src/integration/telemetry.js +45 -0
  136. package/src/integration/update-checker.js +10 -0
  137. package/src/persistence/event-sourcing.js +75 -0
  138. package/src/persistence/memory.js +501 -0
  139. package/src/persistence/migration-runner.js +16 -0
  140. package/src/persistence/persistence-manager.js +39 -0
  141. package/src/persistence/transaction-log.js +157 -0
  142. package/src/policy/action-capabilities.js +116 -0
  143. package/src/policy/action-policy.js +4 -0
  144. package/src/policy/assessment-engine.js +104 -0
  145. package/src/policy/assumption-tracking.js +66 -0
  146. package/src/policy/budget-enforcer.js +7 -0
  147. package/src/policy/config-validator.js +102 -0
  148. package/src/policy/execution-assessment.js +142 -0
  149. package/src/policy/failure-classification.js +5 -0
  150. package/src/policy/feature-flags.js +65 -0
  151. package/src/policy/governance-enforcement.js +118 -0
  152. package/src/policy/locale.js +15 -0
  153. package/src/policy/multi-signer.js +151 -0
  154. package/src/policy/policy-state-machine.js +97 -0
  155. package/src/policy/ponytail-constraint.js +38 -0
  156. package/src/policy/project-inspector.js +93 -0
  157. package/src/policy/query-builder.js +14 -0
  158. package/src/policy/request-classifier.js +79 -0
  159. package/src/policy/risk-engine.js +266 -0
  160. package/src/policy/scope-enforcement.js +140 -0
  161. package/src/policy/secret-scanner.js +68 -0
  162. package/src/policy/skill-routing.js +104 -0
  163. package/src/policy/strategy-capabilities.js +211 -0
  164. package/src/policy/strategy.js +6 -0
  165. package/src/policy/uncertainty.js +117 -0
  166. package/src/shared/container.js +44 -0
  167. package/src/shared/decision-types.js +24 -0
  168. package/src/shared/simple-test.js +160 -0
  169. package/src/shared/wam-events.js +52 -0
  170. package/src/skills/engine.js +1705 -0
  171. package/src/skills/skill-registry.js +19 -0
  172. package/src/skills/skill-routing.js +340 -0
  173. package/src/state/lifecycle-manager.js +113 -0
  174. package/src/state/operational-state.js +198 -0
  175. package/src/state/projections.js +47 -0
  176. package/src/state/replay-engine.js +5 -0
  177. package/src/state/requirement-ownership.js +20 -0
  178. package/src/state/requirement-state.js +152 -0
  179. package/src/state/run-event-identity.js +172 -0
  180. package/src/state/state-engine.js +19 -0
  181. package/src/state/state-machine.js +119 -0
  182. package/src/state/state-schema.js +318 -0
  183. package/src/state/state-store.js +335 -0
  184. package/src/state/state-transition.js +84 -0
  185. package/src/state/task-dependencies.js +287 -0
  186. package/src/state/task-execution.js +217 -0
  187. package/src/state/task-lifecycle.js +59 -0
  188. package/src/state/task-runs.js +361 -0
  189. package/src/state/task-state-file.js +86 -0
  190. package/src/state/task-state.js +41 -0
  191. package/src/state/task-status.js +54 -0
  192. package/src/state/task-store.js +48 -0
  193. package/src/state/wam-state.js +148 -0
  194. package/src/verification/completion-gate-simple.js +8 -0
  195. package/src/verification/completion-gate.js +183 -0
  196. package/src/verification/contract.js +9 -0
  197. package/src/verification/contradiction-resolution.js +255 -0
  198. package/src/verification/diagnostic-engine.js +659 -0
  199. package/src/verification/false-completion-prevention.js +100 -0
  200. package/src/verification/hook-validator.js +6 -0
  201. package/src/verification/requirements.js +6 -0
  202. package/src/verification/verification-context.js +162 -0
  203. package/src/verification/verification-lifecycle.js +66 -0
  204. package/src/verification/verification-model.js +89 -0
  205. package/src/verification/verification-persistence.js +129 -0
  206. package/src/verification/verification-policy.js +127 -0
  207. package/src/verification/verification.js +415 -0
@@ -0,0 +1,148 @@
1
+ /**
2
+ * wam-state.js — Wait-a-Minute session state management.
3
+ *
4
+ * Provides CRUD for WAM (Wait-a-Minute) session state persisted to disk,
5
+ * session resumption, and schema migration utilities.
6
+ */
7
+
8
+ import fs from 'node:fs/promises';
9
+ import path from 'node:path';
10
+
11
+ // ── Constants ────────────────────────────────────────────────
12
+ export const SCHEMA_VERSION = 1;
13
+ export const STATE_FILENAME = 'wam-state.json';
14
+
15
+ /**
16
+ * Returns the current schema version number.
17
+ * @param {object} [state] — optional state object to inspect for its version
18
+ * @returns {number} schema version
19
+ */
20
+ export function getSchemaVersion(state) {
21
+ if (state && typeof state === 'object' && '_schemaVersion' in state) {
22
+ return state._schemaVersion;
23
+ }
24
+ return SCHEMA_VERSION;
25
+ }
26
+
27
+ /**
28
+ * Creates a new WAM state object.
29
+ * @param {string} taskId
30
+ * @param {object} [requirements={}]
31
+ * @param {object} [context={}]
32
+ * @param {object} [evidence={}]
33
+ * @returns {object} the created state
34
+ */
35
+ export function createWamState(taskId, requirements = {}, context = {}, evidence = {}) {
36
+ const now = new Date().toISOString();
37
+ return {
38
+ _schemaVersion: SCHEMA_VERSION,
39
+ taskId,
40
+ createdAt: now,
41
+ updatedAt: now,
42
+ requirements,
43
+ context,
44
+ evidence,
45
+ status: 'active',
46
+ };
47
+ }
48
+
49
+ /**
50
+ * Saves WAM state to disk.
51
+ * @param {string} taskId
52
+ * @param {object} state
53
+ * @param {string} rootDir
54
+ * @returns {Promise<string>} resolved file path
55
+ */
56
+ export async function saveWamState(taskId, state, rootDir) {
57
+ const dir = path.join(rootDir, taskId);
58
+ await fs.mkdir(dir, { recursive: true });
59
+ state.updatedAt = new Date().toISOString();
60
+ const filePath = path.join(dir, STATE_FILENAME);
61
+ await fs.writeFile(filePath, JSON.stringify(state, null, 2));
62
+ return filePath;
63
+ }
64
+
65
+ /**
66
+ * Loads WAM state from disk.
67
+ * @param {string} taskId
68
+ * @param {string} rootDir
69
+ * @returns {Promise<object|null>} the loaded state or null if not found
70
+ */
71
+ export async function loadWamState(taskId, rootDir) {
72
+ const filePath = path.join(rootDir, taskId, STATE_FILENAME);
73
+ try {
74
+ await fs.access(filePath);
75
+ } catch {
76
+ return null;
77
+ }
78
+ const raw = await fs.readFile(filePath, 'utf8');
79
+ return JSON.parse(raw);
80
+ }
81
+
82
+ /**
83
+ * Resumes a session from a loaded WAM state.
84
+ * Returns a resume descriptor with metadata about the session to continue.
85
+ * @param {object} wamState
86
+ * @returns {object} resume descriptor
87
+ */
88
+ export function resumeSession(wamState) {
89
+ if (!wamState || typeof wamState !== 'object') {
90
+ return {
91
+ resumable: false,
92
+ reason: 'No valid state provided',
93
+ taskId: null,
94
+ };
95
+ }
96
+
97
+ if (wamState.status === 'completed') {
98
+ return {
99
+ resumable: false,
100
+ reason: 'Session already completed',
101
+ taskId: wamState.taskId ?? null,
102
+ };
103
+ }
104
+
105
+ return {
106
+ resumable: true,
107
+ taskId: wamState.taskId ?? null,
108
+ status: wamState.status ?? 'unknown',
109
+ createdAt: wamState.createdAt,
110
+ updatedAt: wamState.updatedAt,
111
+ schemaVersion: getSchemaVersion(wamState),
112
+ hasRequirements: Boolean(wamState.requirements && Object.keys(wamState.requirements).length > 0),
113
+ hasContext: Boolean(wamState.context && Object.keys(wamState.context).length > 0),
114
+ hasEvidence: Boolean(wamState.evidence && Object.keys(wamState.evidence).length > 0),
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Migrates an old-format WAM state to the current schema version.
120
+ * Handles common migration scenarios: adding missing fields, renaming
121
+ * deprecated keys, and setting default values.
122
+ * @param {object} oldState
123
+ * @returns {object} migrated state
124
+ */
125
+ export function migrateWamState(oldState) {
126
+ if (!oldState || typeof oldState !== 'object') {
127
+ return createWamState('migrated-empty');
128
+ }
129
+
130
+ const migrated = { ...oldState };
131
+
132
+ if (!('_schemaVersion' in migrated)) {
133
+ migrated._schemaVersion = 0;
134
+ }
135
+
136
+ // Migration from v0 → v1
137
+ if (migrated._schemaVersion < 1) {
138
+ if (!migrated.createdAt) migrated.createdAt = new Date().toISOString();
139
+ if (!migrated.updatedAt) migrated.updatedAt = migrated.createdAt;
140
+ if (!migrated.requirements) migrated.requirements = {};
141
+ if (!migrated.context) migrated.context = {};
142
+ if (!migrated.evidence) migrated.evidence = {};
143
+ if (!migrated.status) migrated.status = 'active';
144
+ migrated._schemaVersion = SCHEMA_VERSION;
145
+ }
146
+
147
+ return migrated;
148
+ }
@@ -0,0 +1,8 @@
1
+ export function evaluateCompletion(state) {
2
+ const reqs = state.requirements || [];
3
+ const unverified = reqs.filter(r => r.status !== "verified");
4
+ return {
5
+ blocked: unverified.length > 0,
6
+ unresolvedRequirements: unverified.map(r => r.id)
7
+ };
8
+ }
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Completion Gate
3
+ *
4
+ * Decides whether a task can be marked complete based on:
5
+ * - every requirement being VERIFIED (via requirement-state)
6
+ * - existence of at least one piece of verified evidence per requirement
7
+ * - absence of contradicting evidence that would invalidate completion
8
+ * - absence of inconclusive evidence that leaves the requirement unresolved
9
+ *
10
+ * API:
11
+ * canComplete(taskRoot, taskId) -> {
12
+ * allowed: boolean,
13
+ * reason: string,
14
+ * blockers: Array<{ requirementId, reason }>,
15
+ * summary: { total, complete, incomplete, blocked },
16
+ * }
17
+ *
18
+ * The function is read-only; it does not mutate task state. Callers may use
19
+ * the result to either allow completion, emit a structured failure, or block
20
+ * a "done" claim emitted by an agent.
21
+ */
22
+
23
+ import fs from "node:fs";
24
+ import path from "node:path";
25
+ import { getTaskState } from "../skills/engine.js";
26
+ import {
27
+ isRequirementComplete,
28
+ summarizeRequirements,
29
+ REQUIREMENT_STATES,
30
+ } from "../state/requirement-state.js";
31
+ import {
32
+ getAllEvidence,
33
+ getEvidenceForRequirement,
34
+ } from "../evidence/evidence-lineage.js";
35
+
36
+ /**
37
+ * Read all evidence for a task (regardless of status). Tolerates errors.
38
+ */
39
+ function readAllEvidence(taskRoot, taskId) {
40
+ try {
41
+ const ev = getAllEvidence(taskId, taskRoot);
42
+ return Array.isArray(ev) ? ev : [];
43
+ } catch {
44
+ return [];
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Inspect a single requirement and classify why it would block completion.
50
+ * Returns null if the requirement does NOT block.
51
+ */
52
+ function findBlocker(requirement, allEvidence) {
53
+ const id = requirement.id;
54
+ const evidenceForReq = allEvidence.filter((e) => e && e.requirementId === id);
55
+
56
+ // 1. No evidence at all -> blocked (regardless of status).
57
+ if (evidenceForReq.length === 0) {
58
+ return {
59
+ requirementId: id,
60
+ reason: `no evidence linked (status=${requirement.status || REQUIREMENT_STATES.PENDING})`,
61
+ };
62
+ }
63
+
64
+ // 2. Evidence exists but is contradictory or invalidated -> blocked.
65
+ const statuses = evidenceForReq.map((e) => e.status || e.verdict);
66
+ if (statuses.some((s) => s === "contradictory" || s === "CONTRADICTORY" || s === "INVALIDATED" || s === "invalidated")) {
67
+ return {
68
+ requirementId: id,
69
+ reason: `contradictory or invalidated evidence present`,
70
+ };
71
+ }
72
+
73
+ // 3. All evidence is inconclusive -> blocked.
74
+ if (statuses.every((s) => s === "inconclusive" || s === "INCONCLUSIVE")) {
75
+ return {
76
+ requirementId: id,
77
+ reason: `only inconclusive evidence; requirement unresolved`,
78
+ };
79
+ }
80
+
81
+ // 4. Requirement is not VERIFIED but has evidence.
82
+ if (!isRequirementComplete(requirement)) {
83
+ // Has evidence but no valid piece yet -> blocked.
84
+ if (!statuses.some((s) => s === "valid" || s === "VERIFIED")) {
85
+ return {
86
+ requirementId: id,
87
+ reason: `no valid evidence for requirement`,
88
+ };
89
+ }
90
+ return {
91
+ requirementId: id,
92
+ reason: `requirement status is ${requirement.status || REQUIREMENT_STATES.PENDING} (not VERIFIED)`,
93
+ };
94
+ }
95
+
96
+ return null;
97
+ }
98
+
99
+ /**
100
+ * Decide if a task can complete.
101
+ *
102
+ * @param {string} taskRoot absolute path to the project root (where .wam/ lives)
103
+ * @param {string} taskId task identifier
104
+ * @returns {{allowed: boolean, reason: string, blockers: Array, summary: object}}
105
+ */
106
+ export function canComplete(taskRoot, taskId) {
107
+ if (!taskRoot || !taskId) {
108
+ return {
109
+ allowed: false,
110
+ reason: "taskRoot and taskId are required",
111
+ blockers: [],
112
+ summary: { total: 0, complete: 0, incomplete: 0, blocked: 0 },
113
+ };
114
+ }
115
+
116
+ const root = path.resolve(taskRoot);
117
+ if (!fs.existsSync(root)) {
118
+ return {
119
+ allowed: false,
120
+ reason: `task root does not exist: ${root}`,
121
+ blockers: [],
122
+ summary: { total: 0, complete: 0, incomplete: 0, blocked: 0 },
123
+ };
124
+ }
125
+
126
+ let taskState;
127
+ try {
128
+ taskState = getTaskState(taskId, root);
129
+ } catch (err) {
130
+ return {
131
+ allowed: false,
132
+ reason: `failed to load task state: ${err.message}`,
133
+ blockers: [],
134
+ summary: { total: 0, complete: 0, incomplete: 0, blocked: 0 },
135
+ };
136
+ }
137
+
138
+ const requirements = Array.isArray(taskState.requirements)
139
+ ? taskState.requirements
140
+ : [];
141
+
142
+ if (requirements.length === 0) {
143
+ return {
144
+ allowed: false,
145
+ reason: "task has no requirements",
146
+ blockers: [],
147
+ summary: { total: 0, complete: 0, incomplete: 0, blocked: 0 },
148
+ };
149
+ }
150
+
151
+ const allEvidence = readAllEvidence(root, taskId);
152
+
153
+ const blockers = [];
154
+ for (const req of requirements) {
155
+ const blocker = findBlocker(req, allEvidence);
156
+ if (blocker) blockers.push(blocker);
157
+ }
158
+
159
+ const summary = summarizeRequirements(requirements);
160
+
161
+ if (blockers.length === 0) {
162
+ return {
163
+ allowed: true,
164
+ reason: "all requirements verified",
165
+ blockers: [],
166
+ summary: summary.summary,
167
+ };
168
+ }
169
+
170
+ return {
171
+ allowed: false,
172
+ reason: `${blockers.length} requirement(s) blocking completion`,
173
+ blockers,
174
+ summary: summary.summary,
175
+ };
176
+ }
177
+
178
+ /**
179
+ * Convenience helper: returns true iff canComplete(...).allowed
180
+ */
181
+ export function isCompletionAllowed(taskRoot, taskId) {
182
+ return canComplete(taskRoot, taskId).allowed;
183
+ }
@@ -0,0 +1,9 @@
1
+ export function createContract(requirements, verification) {
2
+ return { status: "PROPOSED", requirements, verification, unknowns: [], assumptions: [] };
3
+ }
4
+ export function approveContract(contract) {
5
+ return { ...contract, status: "APPROVED" };
6
+ }
7
+ export function rejectContract(contract) {
8
+ return { ...contract, status: "REJECTED" };
9
+ }
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Context Contradiction Resolution — explicit conflict detection and resolution.
3
+ *
4
+ * Detects when two pieces of context contradict each other and determines
5
+ * which one governs based on authority precedence.
6
+ *
7
+ * Resolution precedence:
8
+ * 1. user_decided (highest authority)
9
+ * 2. explicit current constraint
10
+ * 3. verified current evidence
11
+ * 4. newer valid decision
12
+ * 5. inferred
13
+ * 6. unknown
14
+ *
15
+ * States: RESOLVED | UNRESOLVED | BLOCKED
16
+ */
17
+
18
+ /**
19
+ * @typedef {"RESOLVED" | "UNRESOLVED" | "BLOCKED"} ConflictStatus
20
+ */
21
+
22
+ /**
23
+ * @typedef {"user_decided" | "constraint" | "evidence" | "decision" | "inferred" | "unknown"} AuthorityType
24
+ */
25
+
26
+ /**
27
+ * @typedef {Object} Conflict
28
+ * @property {string} id
29
+ * @property {string} nodeA - First contradicting node
30
+ * @property {string} nodeB - Second contradicting node
31
+ * @property {string} type - "contradiction" | "invalidation"
32
+ * @property {AuthorityType} authorityA - Authority type of node A
33
+ * @property {AuthorityType} authorityB - Authority type of node B
34
+ * @property {ConflictStatus} status
35
+ * @property {string} winner - Node ID that wins (if resolved)
36
+ * @property {string} reason - Why this resolution was chosen
37
+ * @property {boolean} blocking - Whether this blocks execution
38
+ */
39
+
40
+ /** Authority precedence (lower number = higher authority) */
41
+ const AUTHORITY_PRECEDENCE = {
42
+ user_decided: 1,
43
+ constraint: 2,
44
+ evidence: 3,
45
+ decision: 4,
46
+ inferred: 5,
47
+ unknown: 6,
48
+ };
49
+
50
+ /** Conflict types that block execution */
51
+ const BLOCKING_TYPES = [
52
+ "architecture",
53
+ "security",
54
+ "compatibility",
55
+ "acceptance",
56
+ "destructive",
57
+ ];
58
+
59
+ /**
60
+ * Determine the authority type of a node based on its content and metadata.
61
+ *
62
+ * @param {Object} node
63
+ * @returns {AuthorityType}
64
+ */
65
+ export function getAuthorityType(node) {
66
+ if (!node) return "unknown";
67
+
68
+ const content = (node.content || "").toLowerCase();
69
+ const type = node.type || "";
70
+
71
+ // User decided
72
+ if (content.includes("user decided") || content.includes("user-decided")) {
73
+ return "user_decided";
74
+ }
75
+
76
+ // Constraint
77
+ if (
78
+ type === "constraint" ||
79
+ content.includes("must") ||
80
+ content.includes("shall") ||
81
+ content.includes("mandatory") ||
82
+ content.includes("required")
83
+ ) {
84
+ return "constraint";
85
+ }
86
+
87
+ // Evidence
88
+ if (
89
+ type === "evidence" ||
90
+ content.includes("evidence") ||
91
+ content.includes("verified") ||
92
+ content.includes("test passed")
93
+ ) {
94
+ return "evidence";
95
+ }
96
+
97
+ // Decision
98
+ if (
99
+ type === "decision" ||
100
+ content.includes("decision") ||
101
+ content.includes("decided") ||
102
+ content.includes("chose") ||
103
+ content.includes("selected")
104
+ ) {
105
+ return "decision";
106
+ }
107
+
108
+ // Inferred
109
+ if (
110
+ type === "inferred" ||
111
+ content.includes("inferred") ||
112
+ content.includes("assumed") ||
113
+ content.includes("likely")
114
+ ) {
115
+ return "inferred";
116
+ }
117
+
118
+ return "unknown";
119
+ }
120
+
121
+ /**
122
+ * Compare two authority types and determine which wins.
123
+ *
124
+ * @param {AuthorityType} a
125
+ * @param {AuthorityType} b
126
+ * @returns {{ winner: AuthorityType, reason: string }}
127
+ */
128
+ export function compareAuthority(a, b) {
129
+ const precA = AUTHORITY_PRECEDENCE[a] || 6;
130
+ const precB = AUTHORITY_PRECEDENCE[b] || 6;
131
+
132
+ if (precA < precB) {
133
+ return { winner: a, reason: `${a} has higher precedence than ${b}` };
134
+ }
135
+ if (precB < precA) {
136
+ return { winner: b, reason: `${b} has higher precedence than ${a}` };
137
+ }
138
+
139
+ // Same precedence - use timestamp or other tiebreaker
140
+ return { winner: a, reason: "same precedence, first wins" };
141
+ }
142
+
143
+ /**
144
+ * Create a conflict object from two contradicting nodes.
145
+ *
146
+ * @param {Object} nodeA
147
+ * @param {Object} nodeB
148
+ * @param {string} type - "contradiction" | "invalidation"
149
+ * @returns {Conflict}
150
+ */
151
+ export function createConflict(nodeA, nodeB, type = "contradiction") {
152
+ const authorityA = getAuthorityType(nodeA);
153
+ const authorityB = getAuthorityType(nodeB);
154
+
155
+ const { winner, reason } = compareAuthority(authorityA, authorityB);
156
+
157
+ return {
158
+ id: `conflict-${nodeA.id}-${nodeB.id}`,
159
+ nodeA: nodeA.id,
160
+ nodeB: nodeB.id,
161
+ type,
162
+ authorityA,
163
+ authorityB,
164
+ status: "UNRESOLVED",
165
+ winner: null,
166
+ reason: "",
167
+ blocking: false,
168
+ };
169
+ }
170
+
171
+ /**
172
+ * Resolve a conflict based on authority precedence.
173
+ *
174
+ * @param {Conflict} conflict
175
+ * @param {Object} graph - Context graph for node lookup
176
+ * @returns {Conflict} Resolved conflict
177
+ */
178
+ export function resolveConflict(conflict, graph) {
179
+ const nodeA = graph.getNode(conflict.nodeA);
180
+ const nodeB = graph.getNode(conflict.nodeB);
181
+
182
+ if (!nodeA || !nodeB) {
183
+ conflict.status = "UNRESOLVED";
184
+ conflict.reason = "One or both nodes not found in graph";
185
+ return conflict;
186
+ }
187
+
188
+ const authorityA = getAuthorityType(nodeA);
189
+ const authorityB = getAuthorityType(nodeB);
190
+
191
+ const { winner, reason } = compareAuthority(authorityA, authorityB);
192
+
193
+ conflict.authorityA = authorityA;
194
+ conflict.authorityB = authorityB;
195
+ conflict.winner = winner === authorityA ? conflict.nodeA : conflict.nodeB;
196
+ conflict.reason = reason;
197
+ conflict.status = "RESOLVED";
198
+
199
+ return conflict;
200
+ }
201
+
202
+ /**
203
+ * Check if a conflict should block execution.
204
+ *
205
+ * @param {Conflict} conflict
206
+ * @param {Object} taskContext - Task context for domain detection
207
+ * @returns {boolean}
208
+ */
209
+ export function shouldBlock(conflict, taskContext = {}) {
210
+ if (conflict.status === "UNRESOLVED") {
211
+ return true;
212
+ }
213
+
214
+ // Check if the conflict affects critical domains
215
+ const content = (taskContext.content || "").toLowerCase();
216
+ for (const domain of BLOCKING_TYPES) {
217
+ if (content.includes(domain)) {
218
+ return true;
219
+ }
220
+ }
221
+
222
+ return false;
223
+ }
224
+
225
+ /**
226
+ * Generate conflict report for agent consumption.
227
+ *
228
+ * @param {Conflict[]} conflicts
229
+ * @returns {string} Formatted conflict report
230
+ */
231
+ export function generateConflictReport(conflicts) {
232
+ if (conflicts.length === 0) {
233
+ return "";
234
+ }
235
+
236
+ const lines = ["[wam conflicts]"];
237
+
238
+ for (const conflict of conflicts) {
239
+ const status = conflict.status;
240
+ const winner = conflict.winner ? ` → ${conflict.winner}` : "";
241
+ lines.push(` ${conflict.id}: ${conflict.nodeA} vs ${conflict.nodeB} (${status}${winner})`);
242
+ if (conflict.reason) {
243
+ lines.push(` reason: ${conflict.reason}`);
244
+ }
245
+ }
246
+
247
+ const unresolved = conflicts.filter((c) => c.status === "UNRESOLVED");
248
+ if (unresolved.length > 0) {
249
+ lines.push(` ⚠ ${unresolved.length} unresolved conflict(s) — do not choose silently`);
250
+ }
251
+
252
+ return lines.join("\n");
253
+ }
254
+
255
+ export { AUTHORITY_PRECEDENCE, BLOCKING_TYPES };