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,157 @@
1
+ // transaction-log.js
2
+ // Append-only journal of state mutations with durability guarantees.
3
+ // Each entry is one line of canonical JSON, fsynced before returning.
4
+
5
+ import { appendFile, open, mkdir, rename } from "node:fs/promises";
6
+ import { createHash } from "node:crypto";
7
+ import { dirname } from "node:path";
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Canonical JSON (shared semantic with state-schema.js but kept local so this
11
+ // file is usable independently — no cross-file coupling for the journal).
12
+ // ---------------------------------------------------------------------------
13
+
14
+ function canonical(v) {
15
+ if (v === null || typeof v !== "object") return JSON.stringify(v);
16
+ if (Array.isArray(v)) return "[" + v.map(canonical).join(",") + "]";
17
+ const keys = Object.keys(v).sort();
18
+ return "{" + keys.map((k) => JSON.stringify(k) + ":" + canonical(v[k])).join(",") + "}";
19
+ }
20
+
21
+ function entryHash(entry) {
22
+ const { hash, _corrupt, _rawLine, ...rest } = entry;
23
+ return createHash("sha256").update(canonical(rest)).digest("hex");
24
+ }
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // AppendLog
28
+ // ---------------------------------------------------------------------------
29
+
30
+ /**
31
+ * Append-only journal. Each append() is atomic (single write + fsync of the
32
+ * file descriptor). Reads return all entries from disk in append order.
33
+ *
34
+ * Recovery: caller replays entries from disk after a crash. The journal never
35
+ * overwrites prior entries; partial writes are detectable via per-entry hash.
36
+ */
37
+ export class TransactionLog {
38
+ /**
39
+ * @param {string} path absolute path to the journal file
40
+ * @param {object} [opts]
41
+ * @param {string} [opts.dir] directory to ensure exists
42
+ */
43
+ constructor(path, opts = {}) {
44
+ this.path = path;
45
+ this.dir = opts.dir ?? dirname(path);
46
+ this._seq = 0;
47
+ this._ready = this._init();
48
+ }
49
+
50
+ async _init() {
51
+ await mkdir(this.dir, { recursive: true });
52
+ }
53
+
54
+ async append(op) {
55
+ await this._ready;
56
+ const entry = {
57
+ seq: ++this._seq,
58
+ ts: new Date().toISOString(),
59
+ ...op,
60
+ };
61
+ entry.hash = entryHash(entry);
62
+ const line = canonical(entry) + "\n";
63
+ // appendFile is atomic for small writes < PIPE_BUF; we also explicitly
64
+ // fsync via a handle so the kernel commits to disk before we return.
65
+ const fh = await open(this.path, "a");
66
+ try {
67
+ await fh.appendFile(line);
68
+ await fh.sync();
69
+ } finally {
70
+ await fh.close();
71
+ }
72
+ return entry;
73
+ }
74
+
75
+ /** Read all entries. Skips lines that fail to parse (corruption isolation). */
76
+ async readAll() {
77
+ await this._ready;
78
+ let raw = "";
79
+ try {
80
+ const fh = await open(this.path, "r");
81
+ try {
82
+ raw = await fh.readFile("utf8");
83
+ } finally {
84
+ await fh.close();
85
+ }
86
+ } catch (e) {
87
+ if (e.code === "ENOENT") return [];
88
+ throw e;
89
+ }
90
+ const out = [];
91
+ const lines = raw.split("\n");
92
+ for (const line of lines) {
93
+ if (!line) continue;
94
+ try {
95
+ const entry = JSON.parse(line);
96
+ // Per-entry hash check; treat mismatch as corruption but include line
97
+ // so caller can quarantine it explicitly if needed.
98
+ if (entry && entry.hash && entry.hash !== entryHash(entry)) {
99
+ out.push({ ...entry, _corrupt: true });
100
+ } else {
101
+ out.push(entry);
102
+ }
103
+ } catch {
104
+ out.push({ _corrupt: true, _rawLine: line });
105
+ }
106
+ }
107
+ return out;
108
+ }
109
+
110
+ /** Number of valid entries currently on disk. */
111
+ async size() {
112
+ const all = await this.readAll();
113
+ return all.filter((e) => !e._corrupt).length;
114
+ }
115
+
116
+ /** Truncate the journal. Used by GC after compaction. */
117
+ async truncate() {
118
+ await this._ready;
119
+ await rename(this.path, this.path + ".truncated-" + Date.now()).catch(() => {});
120
+ this._seq = 0;
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Replay a list of journal entries, applying each to a state map.
126
+ * - Skips entries with _corrupt: true
127
+ * - Returns { applied, skipped, finalState }
128
+ *
129
+ * Each entry's payload is expected to be { type, taskId, data, op? } where
130
+ * op is "set" (default) or "del". Replay is idempotent by (seq).
131
+ */
132
+ export function replay(entries, initialState = {}) {
133
+ const applied = [];
134
+ const skipped = [];
135
+ const state = { ...initialState };
136
+
137
+ for (const entry of entries) {
138
+ if (!entry || entry._corrupt) {
139
+ skipped.push(entry);
140
+ continue;
141
+ }
142
+ const { seq, type, taskId, data, op = "set" } = entry;
143
+ if (!type || !taskId) {
144
+ skipped.push(entry);
145
+ continue;
146
+ }
147
+ const key = `${type}:${taskId}`;
148
+ if (op === "del") {
149
+ delete state[key];
150
+ } else {
151
+ state[key] = data;
152
+ }
153
+ applied.push({ seq, key, op });
154
+ }
155
+
156
+ return { applied, skipped, finalState: state };
157
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Action Capabilities — Structured classification of agent tool capabilities.
3
+ *
4
+ * Define cuatro niveles de capacidad, cada uno con un significado explícito
5
+ * respecto a mutación, autorización y fail-mode:
6
+ *
7
+ * - SAFE : read-only / efectos reversibles mínimos. Autónoma.
8
+ * - GUARDED : mutaciones bounded y reversibles. Autónoma si está dentro del scope.
9
+ * - BLOCKED : destructivo, irreversible o sensible. Requiere autorización explícita.
10
+ * - UNKNOWN : la herramienta no aparece en el catálogo. Fail-closed (treated as BLOCKED).
11
+ *
12
+ * Implementa: action-risk-envelope / C01 fail-closed.
13
+ *
14
+ * El uso de UNKNOWN permite que el evaluador aplique una política fail-closed
15
+ * ante herramientas no registradas: cualquier herramienta que no aparezca
16
+ * explícitamente en SAFE/GUARDED/BLOCKED se clasifica como UNKNOWN y, por
17
+ * política, se trata como BLOCKED para evitar que una herramienta nueva o
18
+ * mal nombrada ejecute mutaciones sin control.
19
+ */
20
+
21
+ export const CAPABILITY_LEVELS = Object.freeze({
22
+ SAFE: "SAFE",
23
+ GUARDED: "GUARDED",
24
+ BLOCKED: "BLOCKED",
25
+ UNKNOWN: "UNKNOWN",
26
+ });
27
+
28
+ /**
29
+ * Mapa explícito de capacidades por nombre de herramienta.
30
+ * Cualquier herramienta que NO esté aquí se clasifica como UNKNOWN.
31
+ */
32
+ export const ACTION_CAPABILITIES = Object.freeze({
33
+ // --- SAFE: read-only / sin efectos ---
34
+ read: CAPABILITY_LEVELS.SAFE,
35
+ glob: CAPABILITY_LEVELS.SAFE,
36
+ grep: CAPABILITY_LEVELS.SAFE,
37
+ ls: CAPABILITY_LEVELS.SAFE,
38
+ tool_search: CAPABILITY_LEVELS.SAFE,
39
+ webfetch: CAPABILITY_LEVELS.SAFE,
40
+ fetch: CAPABILITY_LEVELS.SAFE,
41
+
42
+ // --- GUARDED: mutaciones bounded ---
43
+ write: CAPABILITY_LEVELS.GUARDED,
44
+ edit: CAPABILITY_LEVELS.GUARDED,
45
+ apply_patch: CAPABILITY_LEVELS.GUARDED,
46
+ patch: CAPABILITY_LEVELS.GUARDED,
47
+ todo_write: CAPABILITY_LEVELS.GUARDED,
48
+ todowrite: CAPABILITY_LEVELS.GUARDED,
49
+ bash: CAPABILITY_LEVELS.GUARDED,
50
+ sh: CAPABILITY_LEVELS.GUARDED,
51
+ pty_spawn: CAPABILITY_LEVELS.GUARDED,
52
+ pty_write: CAPABILITY_LEVELS.GUARDED,
53
+ task: CAPABILITY_LEVELS.GUARDED, // delegation: GUARDED, not SAFE
54
+
55
+ // --- BLOCKED: destructivo / sensible ---
56
+ rm: CAPABILITY_LEVELS.BLOCKED,
57
+ rmdir: CAPABILITY_LEVELS.BLOCKED,
58
+ mv: CAPABILITY_LEVELS.BLOCKED,
59
+ sudo: CAPABILITY_LEVELS.BLOCKED,
60
+ chmod: CAPABILITY_LEVELS.BLOCKED,
61
+ chown: CAPABILITY_LEVELS.BLOCKED,
62
+ docker: CAPABILITY_LEVELS.BLOCKED,
63
+ ssh: CAPABILITY_LEVELS.BLOCKED,
64
+ scp: CAPABILITY_LEVELS.BLOCKED,
65
+ rsync: CAPABILITY_LEVELS.BLOCKED,
66
+ push: CAPABILITY_LEVELS.BLOCKED,
67
+ force_push: CAPABILITY_LEVELS.BLOCKED,
68
+ });
69
+
70
+ /**
71
+ * Resuelve la capacidad explícita de una herramienta por nombre exacto.
72
+ * No hace coincidencia por sub-string ni por prefijo — debe ser match exacto.
73
+ *
74
+ * Esto evita la colisión por sub-string: "bash-safe" no debe resolverse a "bash".
75
+ *
76
+ * @param {string} tool — nombre de la herramienta (case-insensitive).
77
+ * @returns {"SAFE"|"GUARDED"|"BLOCKED"|"UNKNOWN"}
78
+ */
79
+ export function getCapability(tool) {
80
+ if (typeof tool !== "string" || tool.length === 0) {
81
+ return CAPABILITY_LEVELS.UNKNOWN;
82
+ }
83
+ const normalized = tool.toLowerCase();
84
+ const cap = ACTION_CAPABILITIES[normalized];
85
+ return cap || CAPABILITY_LEVELS.UNKNOWN;
86
+ }
87
+
88
+ /**
89
+ * Resuelve la capacidad effective aplicando fail-closed:
90
+ * UNKNOWN → BLOCKED.
91
+ *
92
+ * Esta es la función que el risk-engine debe usar cuando evalúa una acción.
93
+ * Si la herramienta no está en el catálogo, la acción queda BLOCKED.
94
+ *
95
+ * @param {string} tool
96
+ * @returns {"SAFE"|"GUARDED"|"BLOCKED"}
97
+ */
98
+ export function getEffectiveCapability(tool) {
99
+ const cap = getCapability(tool);
100
+ if (cap === CAPABILITY_LEVELS.UNKNOWN) {
101
+ return CAPABILITY_LEVELS.BLOCKED;
102
+ }
103
+ return cap;
104
+ }
105
+
106
+ /**
107
+ * ¿Es una herramienta mutante? (GUARDED o BLOCKED).
108
+ * Incluye UNKNOWN como BLOCKED (fail-closed).
109
+ *
110
+ * @param {string} tool
111
+ * @returns {boolean}
112
+ */
113
+ export function isMutatingCapability(tool) {
114
+ const eff = getEffectiveCapability(tool);
115
+ return eff === CAPABILITY_LEVELS.GUARDED || eff === CAPABILITY_LEVELS.BLOCKED;
116
+ }
@@ -0,0 +1,4 @@
1
+ export function evaluateActionPolicy(action, strategy) {
2
+ if (!strategy || strategy.status !== "ACTIVE") return { allowed: true };
3
+ return { allowed: true, covered: true, reason: "Strategy active" };
4
+ }
@@ -0,0 +1,104 @@
1
+ import { AssessmentResult } from "../context/assessment-rules.js";
2
+ import { compareValues } from "../context/compare-values.js";
3
+ import { compareObject } from "../context/compare-object.js";
4
+ import { compareArray } from "../context/compare-array.js";
5
+
6
+ export { AssessmentResult };
7
+
8
+ /**
9
+ * Produce a structured assessment: { result, summary, findings }
10
+ * - result: aggregate verdict (worst-case across findings)
11
+ * - summary: counts of supported / contradicted / inconclusive / total
12
+ * - findings: per-field comparison details with `path`, `expected`, `actual`, `reason`
13
+ */
14
+ export function createAssessment(expected, actual) {
15
+ const findings = compareTopLevel(expected, actual);
16
+
17
+ const summary = summarize(findings);
18
+ const result = aggregate(summary);
19
+
20
+ return { result, summary, findings, expected, actual };
21
+ }
22
+
23
+ function compareTopLevel(expected, actual) {
24
+ if (expected === undefined || expected === null) {
25
+ return [
26
+ {
27
+ result: AssessmentResult.INCONCLUSIVE,
28
+ path: "",
29
+ expected,
30
+ actual,
31
+ reason: "no expected value provided",
32
+ },
33
+ ];
34
+ }
35
+
36
+ if (actual === undefined || actual === null) {
37
+ return [
38
+ {
39
+ result: AssessmentResult.CONTRADICTED,
40
+ path: "",
41
+ expected,
42
+ actual,
43
+ reason: "no actual value provided",
44
+ },
45
+ ];
46
+ }
47
+
48
+ if (Array.isArray(expected) && Array.isArray(actual)) {
49
+ return compareArray(expected, actual, "");
50
+ }
51
+
52
+ if (
53
+ typeof expected === "object" &&
54
+ typeof actual === "object" &&
55
+ !Array.isArray(actual)
56
+ ) {
57
+ return compareObject(expected, actual, "");
58
+ }
59
+
60
+ return compareValues(expected, actual, "");
61
+ }
62
+
63
+ function summarize(findings) {
64
+ const summary = { supported: 0, contradicted: 0, inconclusive: 0, total: 0 };
65
+ for (const f of findings) {
66
+ summary.total++;
67
+ if (f.result === AssessmentResult.SUPPORTED) summary.supported++;
68
+ else if (f.result === AssessmentResult.CONTRADICTED) summary.contradicted++;
69
+ else summary.inconclusive++;
70
+ }
71
+ return summary;
72
+ }
73
+
74
+ /**
75
+ * Aggregate verdict: a single CONTRADICTED makes the whole assessment CONTRADICTED;
76
+ * otherwise any INCONCLUSIVE downgrades SUPPORTED to INCONCLUSIVE.
77
+ */
78
+ function aggregate(summary) {
79
+ if (summary.contradicted > 0) return AssessmentResult.CONTRADICTED;
80
+ if (summary.inconclusive > 0) return AssessmentResult.INCONCLUSIVE;
81
+ if (summary.supported > 0) return AssessmentResult.SUPPORTED;
82
+ return AssessmentResult.INCONCLUSIVE;
83
+ }
84
+
85
+ export function assessObservation(experiment, observation) {
86
+ const expectedObs = experiment.expectedObservation;
87
+ if (!expectedObs || typeof expectedObs !== "object") {
88
+ return {
89
+ result: AssessmentResult.INCONCLUSIVE,
90
+ summary: { supported: 0, contradicted: 0, inconclusive: 0, total: 0 },
91
+ findings: [],
92
+ reasoning: "No expectedObservation defined for this experiment.",
93
+ expected: expectedObs,
94
+ actual: observation,
95
+ };
96
+ }
97
+
98
+ const actual = observation?.actual || observation;
99
+ const target = expectedObs.expected && typeof expectedObs.expected === "object"
100
+ ? expectedObs.expected
101
+ : expectedObs;
102
+
103
+ return createAssessment(target, actual);
104
+ }
@@ -0,0 +1,66 @@
1
+ let _assumptionCounter = 0;
2
+ export function createAssumption(statement, source, impact = "unknown") {
3
+ _assumptionCounter++;
4
+ return {
5
+ id: `assumption-${Date.now().toString(36)}-${_assumptionCounter}`,
6
+ statement,
7
+ source,
8
+ impact, // "critical" | "material" | "minor" | "unknown"
9
+ validated: false,
10
+ invalidated: false,
11
+ createdAt: Date.now()
12
+ };
13
+ }
14
+
15
+ export function validateAssumption(assumption, evidence) {
16
+ const supporting = evidence.filter(e =>
17
+ e.requirementId === assumption.relatedTo && e.supports !== false
18
+ );
19
+
20
+ return {
21
+ assumptionId: assumption.id,
22
+ validated: supporting.length > 0,
23
+ supportingEvidence: supporting.length,
24
+ invalidated: assumption.invalidated
25
+ };
26
+ }
27
+
28
+ export function trackAssumptionBudget(assumptions, maxUnvalidated = 5) {
29
+ const unvalidated = assumptions.filter(a => !a.validated && !a.invalidated);
30
+ return {
31
+ withinBudget: unvalidated.length <= maxUnvalidated,
32
+ current: unvalidated.length,
33
+ max: maxUnvalidated,
34
+ overBudget: Math.max(0, unvalidated.length - maxUnvalidated)
35
+ };
36
+ }
37
+
38
+ export const DEFAULT_ASSUMPTION_BUDGET = {
39
+ maxUnvalidated: 5,
40
+ maxCritical: 2,
41
+ maxMaterial: 5
42
+ };
43
+
44
+ export function checkAssumptionBudget(assumptions, budget = DEFAULT_ASSUMPTION_BUDGET) {
45
+ const unvalidated = assumptions.filter(a => !a.validated && !a.invalidated);
46
+ const critical = unvalidated.filter(a => a.impact === "critical");
47
+ const material = unvalidated.filter(a => a.impact === "material");
48
+
49
+ const violations = [];
50
+
51
+ if (unvalidated.length > budget.maxUnvalidated) {
52
+ violations.push(`Too many unvalidated assumptions: ${unvalidated.length} > ${budget.maxUnvalidated}`);
53
+ }
54
+ if (critical.length > budget.maxCritical) {
55
+ violations.push(`Too many critical assumptions: ${critical.length} > ${budget.maxCritical}`);
56
+ }
57
+ if (material.length > budget.maxMaterial) {
58
+ violations.push(`Too many material assumptions: ${material.length} > ${budget.maxMaterial}`);
59
+ }
60
+
61
+ return {
62
+ valid: violations.length === 0,
63
+ violations,
64
+ counts: { unvalidated: unvalidated.length, critical: critical.length, material: material.length }
65
+ };
66
+ }
@@ -0,0 +1,7 @@
1
+ export function enforceBudget(items, budget) {
2
+ let prunedItems = [...items];
3
+ if (budget.maxItems !== undefined) {
4
+ prunedItems = prunedItems.slice(0, budget.maxItems);
5
+ }
6
+ return prunedItems;
7
+ }
@@ -0,0 +1,102 @@
1
+ export function validateConfig(config, schema) {
2
+ const errors = [];
3
+
4
+ function validatePath(value, sub, path) {
5
+ if (sub.type === 'object' && sub.properties) {
6
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
7
+ if (sub.required) errors.push(`${path}: expected object`);
8
+ return;
9
+ }
10
+ for (const key of Object.keys(sub.properties)) {
11
+ const childSchema = sub.properties[key];
12
+ validatePath(value[key], childSchema, path ? `${path}.${key}` : key);
13
+ }
14
+ // Reject extra keys if additionalProperties is explicitly false.
15
+ if (sub.additionalProperties === false) {
16
+ for (const key of Object.keys(value)) {
17
+ if (!(key in sub.properties)) {
18
+ errors.push(`${path ? path + '.' : ''}${key}: unexpected property`);
19
+ }
20
+ }
21
+ }
22
+ return;
23
+ }
24
+
25
+ const isPresent = value !== undefined;
26
+ if (sub.required && !isPresent) {
27
+ errors.push(`${path}: required`);
28
+ return;
29
+ }
30
+ if (!isPresent) return;
31
+
32
+ switch (sub.type) {
33
+ case 'string':
34
+ if (typeof value !== 'string') {
35
+ errors.push(`${path}: expected string`);
36
+ } else {
37
+ if (sub.minLength !== undefined && value.length < sub.minLength) {
38
+ errors.push(`${path}: shorter than minLength ${sub.minLength}`);
39
+ }
40
+ if (sub.maxLength !== undefined && value.length > sub.maxLength) {
41
+ errors.push(`${path}: longer than maxLength ${sub.maxLength}`);
42
+ }
43
+ if (sub.pattern !== undefined && !sub.pattern.test(value)) {
44
+ errors.push(`${path}: does not match pattern`);
45
+ }
46
+ if (sub.enum && !sub.enum.includes(value)) {
47
+ errors.push(`${path}: must be one of ${sub.enum.join(', ')}`);
48
+ }
49
+ }
50
+ break;
51
+ case 'number':
52
+ if (typeof value !== 'number' || Number.isNaN(value)) {
53
+ errors.push(`${path}: expected number`);
54
+ } else {
55
+ if (sub.minimum !== undefined && value < sub.minimum) {
56
+ errors.push(`${path}: below minimum ${sub.minimum}`);
57
+ }
58
+ if (sub.maximum !== undefined && value > sub.maximum) {
59
+ errors.push(`${path}: above maximum ${sub.maximum}`);
60
+ }
61
+ }
62
+ break;
63
+ case 'integer': {
64
+ if (typeof value !== 'number' || !Number.isInteger(value)) {
65
+ errors.push(`${path}: expected integer`);
66
+ } else {
67
+ if (sub.minimum !== undefined && value < sub.minimum) {
68
+ errors.push(`${path}: below minimum ${sub.minimum}`);
69
+ }
70
+ if (sub.maximum !== undefined && value > sub.maximum) {
71
+ errors.push(`${path}: above maximum ${sub.maximum}`);
72
+ }
73
+ }
74
+ break;
75
+ }
76
+ case 'boolean':
77
+ if (typeof value !== 'boolean') errors.push(`${path}: expected boolean`);
78
+ break;
79
+ case 'array':
80
+ if (!Array.isArray(value)) {
81
+ errors.push(`${path}: expected array`);
82
+ } else {
83
+ if (sub.minItems !== undefined && value.length < sub.minItems) {
84
+ errors.push(`${path}: fewer than minItems ${sub.minItems}`);
85
+ }
86
+ if (sub.maxItems !== undefined && value.length > sub.maxItems) {
87
+ errors.push(`${path}: more than maxItems ${sub.maxItems}`);
88
+ }
89
+ if (sub.items) {
90
+ value.forEach((item, i) => validatePath(item, sub.items, `${path}[${i}]`));
91
+ }
92
+ }
93
+ break;
94
+ default:
95
+ // Unknown type — pass through (lenient).
96
+ break;
97
+ }
98
+ }
99
+
100
+ validatePath(config, schema, '');
101
+ return { valid: errors.length === 0, errors };
102
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Observation assessment — compares expected vs actual observations.
3
+ * Returns: SUPPORTED | INCONCLUSIVE | CONTRADICTED
4
+ */
5
+
6
+ export const AssessmentResult = {
7
+ SUPPORTED: "SUPPORTED",
8
+ INCONCLUSIVE: "INCONCLUSIVE",
9
+ CONTRADICTED: "CONTRADICTED",
10
+ };
11
+
12
+ function classifyComparison(expected, actual) {
13
+ if (expected === actual) {
14
+ return "SUPPORTED";
15
+ }
16
+
17
+ if (expected === undefined || expected === null || actual === undefined || actual === null) {
18
+ return "INCONCLUSIVE";
19
+ }
20
+
21
+ if (typeof expected === "boolean" && typeof actual === "boolean") {
22
+ return expected === actual ? "SUPPORTED" : "CONTRADICTED";
23
+ }
24
+
25
+ if (typeof expected === "number" && typeof actual === "number") {
26
+ if (expected === actual) return "SUPPORTED";
27
+ if (Math.abs(expected - actual) <= (Math.abs(expected) * 0.1 + 0.01)) return "SUPPORTED";
28
+ return "CONTRADICTED";
29
+ }
30
+
31
+ if (typeof expected === "string" && typeof actual === "string") {
32
+ if (expected === actual) return "SUPPORTED";
33
+ if (actual.includes(expected) || expected.includes(actual)) return "SUPPORTED";
34
+ if (actual === "pending" || actual === "unknown" || actual === "not applicable") {
35
+ return "INCONCLUSIVE";
36
+ }
37
+ return "CONTRADICTED";
38
+ }
39
+
40
+ if (Array.isArray(expected) && Array.isArray(actual)) {
41
+ if (expected.length === 0 && actual.length === 0) return "SUPPORTED";
42
+ if (expected.length === 0) return "INCONCLUSIVE";
43
+ const allPresent = expected.every((e) => actual.includes(e));
44
+ const anyPresent = expected.some((e) => actual.includes(e));
45
+ if (allPresent) return "SUPPORTED";
46
+ if (anyPresent) return "INCONCLUSIVE";
47
+ return "CONTRADICTED";
48
+ }
49
+
50
+ if (typeof expected === "object" && typeof actual === "object" && expected !== null && actual !== null) {
51
+ return "INCONCLUSIVE";
52
+ }
53
+
54
+ if (actual === "pending" || actual === "unknown" || actual === "not applicable") {
55
+ return "INCONCLUSIVE";
56
+ }
57
+
58
+ return "CONTRADICTED";
59
+ }
60
+
61
+ export function createAssessment(expected, actual) {
62
+ const comparisons = [];
63
+ let supported = 0;
64
+ let contradicted = 0;
65
+ let inconclusive = 0;
66
+
67
+ const expectedKeys = Object.keys(expected);
68
+ const actualKeys = Object.keys(actual || {});
69
+
70
+ for (const key of expectedKeys) {
71
+ const expectedValue = expected[key];
72
+ const actualValue = actualKeys.includes(key) ? actual[key] : undefined;
73
+ const status = classifyComparison(expectedValue, actualValue);
74
+
75
+ comparisons.push({
76
+ field: key,
77
+ expected: expectedValue,
78
+ actual: actualValue,
79
+ status,
80
+ });
81
+
82
+ if (status === "SUPPORTED") supported++;
83
+ else if (status === "CONTRADICTED") contradicted++;
84
+ else inconclusive++;
85
+ }
86
+
87
+ const missingFields = expectedKeys.filter((k) => !actualKeys.includes(k));
88
+
89
+ // Missing fields count as contradicted
90
+ for (const field of missingFields) {
91
+ comparisons.push({
92
+ field,
93
+ expected: expected[field],
94
+ actual: undefined,
95
+ status: "CONTRADICTED",
96
+ });
97
+ contradicted++;
98
+ }
99
+
100
+ let result;
101
+ if (contradicted > 0) {
102
+ result = "CONTRADICTED";
103
+ } else if (inconclusive > 0 || (supported === 0 && expectedKeys.length > 0)) {
104
+ result = "INCONCLUSIVE";
105
+ } else {
106
+ result = "SUPPORTED";
107
+ }
108
+
109
+ const reasoning = generateReasoning(result, comparisons, missingFields);
110
+
111
+ return {
112
+ result,
113
+ reasoning,
114
+ comparisons,
115
+ summary: { supported, contradicted, inconclusive, total: expectedKeys.length },
116
+ expected,
117
+ actual,
118
+ };
119
+ }
120
+
121
+ function generateReasoning(result, comparisons, missingFields) {
122
+ const parts = [];
123
+
124
+ if (result === "SUPPORTED") {
125
+ parts.push(`All ${comparisons.length} expected fields match observations.`);
126
+ } else if (result === "CONTRADICTED") {
127
+ const conflicts = comparisons.filter((c) => c.status === "CONTRADICTED");
128
+ parts.push(
129
+ `${conflicts.length} of ${comparisons.length} expected fields conflict with observations: ${conflicts.map((c) => c.field).join(", ")}.`
130
+ );
131
+ } else {
132
+ if (missingFields.length > 0) {
133
+ parts.push(`Missing data for: ${missingFields.join(", ")}. `);
134
+ }
135
+ const unclear = comparisons.filter((c) => c.status === "INCONCLUSIVE");
136
+ parts.push(
137
+ `${unclear.length} of ${comparisons.length} fields are inconclusive: ${unclear.map((c) => c.field).join(", ")}.`
138
+ );
139
+ }
140
+
141
+ return parts.join(" ");
142
+ }