@eir-labs/coltrane 0.4.0

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 (279) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +85 -0
  3. package/agents/anticipation-mapper.json +34 -0
  4. package/agents/claim-amender.json +34 -0
  5. package/agents/claim-architect.json +34 -0
  6. package/agents/claim-rewriter.json +32 -0
  7. package/agents/delivery-finalizer.json +40 -0
  8. package/agents/diamond-cutter.json +33 -0
  9. package/agents/disclosure-analyst.json +26 -0
  10. package/agents/domain-explorer.json +37 -0
  11. package/agents/e2e-runner.json +34 -0
  12. package/agents/e2e-suite-judge.json +26 -0
  13. package/agents/event-clusterer.json +29 -0
  14. package/agents/novelty-searcher.json +40 -0
  15. package/agents/patent-browser-scout.json +49 -0
  16. package/agents/patent-examiner.json +36 -0
  17. package/agents/phase_agents/claim-rewriter.md +44 -0
  18. package/agents/phase_agents/delivery-finalizer.md +30 -0
  19. package/agents/phase_agents/diamond-cutter.md +47 -0
  20. package/agents/phase_agents/domain-explorer.md +27 -0
  21. package/agents/phase_agents/novelty-searcher.md +50 -0
  22. package/agents/phase_agents/problem-definer.md +32 -0
  23. package/agents/phase_agents/solution-developer.md +26 -0
  24. package/agents/phase_agents/verdict-judger.md +70 -0
  25. package/agents/players/audience-modeler.md +40 -0
  26. package/agents/players/chain-audit-keeper.md +41 -0
  27. package/agents/players/illumination-reviewer.md +42 -0
  28. package/agents/players/methodology-cadence-keeper.md +42 -0
  29. package/agents/players/substrate-edge-keeper.md +45 -0
  30. package/agents/prior-art-scout.json +45 -0
  31. package/agents/problem-definer.json +35 -0
  32. package/agents/seeds/steve_seed_a.json +12 -0
  33. package/agents/seeds/steve_seed_b.json +12 -0
  34. package/agents/seeds/steve_seed_c.json +12 -0
  35. package/agents/seeds/steve_seed_d.json +12 -0
  36. package/agents/sensor.json +22 -0
  37. package/agents/solution-developer.json +35 -0
  38. package/agents/source-walker.json +38 -0
  39. package/agents/spec-drafter.json +31 -0
  40. package/agents/sub_thread_soft_judge.json +26 -0
  41. package/agents/sub_thread_trace_sensor.json +22 -0
  42. package/agents/summarizer.json +25 -0
  43. package/agents/synthesis-writer.json +29 -0
  44. package/agents/triage-judge.json +39 -0
  45. package/agents/user_flow_judge.json +26 -0
  46. package/agents/user_flow_sensor.json +22 -0
  47. package/agents/verdict-judger.json +39 -0
  48. package/core_types/artifact.json +17 -0
  49. package/core_types/interpretation.json +16 -0
  50. package/core_types/judgment.json +16 -0
  51. package/core_types/plan.json +16 -0
  52. package/core_types/signal.json +16 -0
  53. package/core_types/verdict.json +27 -0
  54. package/dist/src/access_grant.d.ts +46 -0
  55. package/dist/src/access_grant.js +76 -0
  56. package/dist/src/access_grant.js.map +1 -0
  57. package/dist/src/acoustics.d.ts +9 -0
  58. package/dist/src/acoustics.js +65 -0
  59. package/dist/src/acoustics.js.map +1 -0
  60. package/dist/src/agent_profile.d.ts +36 -0
  61. package/dist/src/agent_profile.js +57 -0
  62. package/dist/src/agent_profile.js.map +1 -0
  63. package/dist/src/bifrost_invoker.d.ts +33 -0
  64. package/dist/src/bifrost_invoker.js +85 -0
  65. package/dist/src/bifrost_invoker.js.map +1 -0
  66. package/dist/src/canonical_core_types.d.ts +2 -0
  67. package/dist/src/canonical_core_types.js +111 -0
  68. package/dist/src/canonical_core_types.js.map +1 -0
  69. package/dist/src/canonical_form.d.ts +55 -0
  70. package/dist/src/canonical_form.js +93 -0
  71. package/dist/src/canonical_form.js.map +1 -0
  72. package/dist/src/charter.d.ts +37 -0
  73. package/dist/src/charter.js +86 -0
  74. package/dist/src/charter.js.map +1 -0
  75. package/dist/src/circle_of_fifths.d.ts +27 -0
  76. package/dist/src/circle_of_fifths.js +78 -0
  77. package/dist/src/circle_of_fifths.js.map +1 -0
  78. package/dist/src/claude_invoker.d.ts +102 -0
  79. package/dist/src/claude_invoker.js +760 -0
  80. package/dist/src/claude_invoker.js.map +1 -0
  81. package/dist/src/coltrane_profile.d.ts +87 -0
  82. package/dist/src/coltrane_profile.js +132 -0
  83. package/dist/src/coltrane_profile.js.map +1 -0
  84. package/dist/src/composition.d.ts +57 -0
  85. package/dist/src/composition.js +447 -0
  86. package/dist/src/composition.js.map +1 -0
  87. package/dist/src/core_types.d.ts +6 -0
  88. package/dist/src/core_types.js +25 -0
  89. package/dist/src/core_types.js.map +1 -0
  90. package/dist/src/document_factory.d.ts +89 -0
  91. package/dist/src/document_factory.js +176 -0
  92. package/dist/src/document_factory.js.map +1 -0
  93. package/dist/src/genome_schema.d.ts +512 -0
  94. package/dist/src/genome_schema.js +168 -0
  95. package/dist/src/genome_schema.js.map +1 -0
  96. package/dist/src/genome_writer.d.ts +67 -0
  97. package/dist/src/genome_writer.js +179 -0
  98. package/dist/src/genome_writer.js.map +1 -0
  99. package/dist/src/gig_song.d.ts +29 -0
  100. package/dist/src/gig_song.js +56 -0
  101. package/dist/src/gig_song.js.map +1 -0
  102. package/dist/src/gig_tracker.d.ts +54 -0
  103. package/dist/src/gig_tracker.js +109 -0
  104. package/dist/src/gig_tracker.js.map +1 -0
  105. package/dist/src/harmonic_validation.d.ts +11 -0
  106. package/dist/src/harmonic_validation.js +35 -0
  107. package/dist/src/harmonic_validation.js.map +1 -0
  108. package/dist/src/hooks.d.ts +27 -0
  109. package/dist/src/hooks.js +2 -0
  110. package/dist/src/hooks.js.map +1 -0
  111. package/dist/src/index.d.ts +36 -0
  112. package/dist/src/index.js +37 -0
  113. package/dist/src/index.js.map +1 -0
  114. package/dist/src/jsong.d.ts +47 -0
  115. package/dist/src/jsong.js +130 -0
  116. package/dist/src/jsong.js.map +1 -0
  117. package/dist/src/judges/user_flow_judge.d.ts +89 -0
  118. package/dist/src/judges/user_flow_judge.js +200 -0
  119. package/dist/src/judges/user_flow_judge.js.map +1 -0
  120. package/dist/src/learner.d.ts +37 -0
  121. package/dist/src/learner.js +30 -0
  122. package/dist/src/learner.js.map +1 -0
  123. package/dist/src/ledger.d.ts +225 -0
  124. package/dist/src/ledger.js +306 -0
  125. package/dist/src/ledger.js.map +1 -0
  126. package/dist/src/loader.d.ts +87 -0
  127. package/dist/src/loader.js +504 -0
  128. package/dist/src/loader.js.map +1 -0
  129. package/dist/src/mcp.d.ts +23 -0
  130. package/dist/src/mcp.js +125 -0
  131. package/dist/src/mcp.js.map +1 -0
  132. package/dist/src/modulation_path.d.ts +42 -0
  133. package/dist/src/modulation_path.js +60 -0
  134. package/dist/src/modulation_path.js.map +1 -0
  135. package/dist/src/output_validation.d.ts +19 -0
  136. package/dist/src/output_validation.js +51 -0
  137. package/dist/src/output_validation.js.map +1 -0
  138. package/dist/src/outputs.d.ts +125 -0
  139. package/dist/src/outputs.js +452 -0
  140. package/dist/src/outputs.js.map +1 -0
  141. package/dist/src/overtones.d.ts +42 -0
  142. package/dist/src/overtones.js +98 -0
  143. package/dist/src/overtones.js.map +1 -0
  144. package/dist/src/player_to_claude_code.d.ts +17 -0
  145. package/dist/src/player_to_claude_code.js +141 -0
  146. package/dist/src/player_to_claude_code.js.map +1 -0
  147. package/dist/src/playwright_cage.d.ts +28 -0
  148. package/dist/src/playwright_cage.js +47 -0
  149. package/dist/src/playwright_cage.js.map +1 -0
  150. package/dist/src/polyphony.d.ts +46 -0
  151. package/dist/src/polyphony.js +91 -0
  152. package/dist/src/polyphony.js.map +1 -0
  153. package/dist/src/portfolio.d.ts +52 -0
  154. package/dist/src/portfolio.js +186 -0
  155. package/dist/src/portfolio.js.map +1 -0
  156. package/dist/src/pricing.d.ts +23 -0
  157. package/dist/src/pricing.js +16 -0
  158. package/dist/src/pricing.js.map +1 -0
  159. package/dist/src/registry.d.ts +46 -0
  160. package/dist/src/registry.js +199 -0
  161. package/dist/src/registry.js.map +1 -0
  162. package/dist/src/runtime.d.ts +304 -0
  163. package/dist/src/runtime.js +1099 -0
  164. package/dist/src/runtime.js.map +1 -0
  165. package/dist/src/server.d.ts +88 -0
  166. package/dist/src/server.js +1692 -0
  167. package/dist/src/server.js.map +1 -0
  168. package/dist/src/server_entry.d.ts +2 -0
  169. package/dist/src/server_entry.js +40 -0
  170. package/dist/src/server_entry.js.map +1 -0
  171. package/dist/src/server_relay.d.ts +82 -0
  172. package/dist/src/server_relay.js +341 -0
  173. package/dist/src/server_relay.js.map +1 -0
  174. package/dist/src/simulate.d.ts +60 -0
  175. package/dist/src/simulate.js +72 -0
  176. package/dist/src/simulate.js.map +1 -0
  177. package/dist/src/skill_runner.mjs +34 -0
  178. package/dist/src/skill_subprocess.d.ts +65 -0
  179. package/dist/src/skill_subprocess.js +132 -0
  180. package/dist/src/skill_subprocess.js.map +1 -0
  181. package/dist/src/skills.d.ts +85 -0
  182. package/dist/src/skills.js +258 -0
  183. package/dist/src/skills.js.map +1 -0
  184. package/dist/src/subthread_recorder.d.ts +68 -0
  185. package/dist/src/subthread_recorder.js +166 -0
  186. package/dist/src/subthread_recorder.js.map +1 -0
  187. package/dist/src/tensor_read.d.ts +38 -0
  188. package/dist/src/tensor_read.js +60 -0
  189. package/dist/src/tensor_read.js.map +1 -0
  190. package/dist/src/test_honest_broker.d.ts +34 -0
  191. package/dist/src/test_honest_broker.js +155 -0
  192. package/dist/src/test_honest_broker.js.map +1 -0
  193. package/dist/src/test_topology.d.ts +22 -0
  194. package/dist/src/test_topology.js +137 -0
  195. package/dist/src/test_topology.js.map +1 -0
  196. package/dist/src/tones.d.ts +20 -0
  197. package/dist/src/tones.js +81 -0
  198. package/dist/src/tones.js.map +1 -0
  199. package/dist/src/tool_providers.d.ts +45 -0
  200. package/dist/src/tool_providers.js +104 -0
  201. package/dist/src/tool_providers.js.map +1 -0
  202. package/dist/src/type_versioning.d.ts +23 -0
  203. package/dist/src/type_versioning.js +31 -0
  204. package/dist/src/type_versioning.js.map +1 -0
  205. package/dist/src/version.d.ts +28 -0
  206. package/dist/src/version.js +86 -0
  207. package/dist/src/version.js.map +1 -0
  208. package/domain_types/claim-draft.json +34 -0
  209. package/domain_types/coverage-report.json +31 -0
  210. package/domain_types/draft-agent-profile.json +31 -0
  211. package/domain_types/draft-domain-type.json +28 -0
  212. package/domain_types/draft-standard.json +46 -0
  213. package/domain_types/e2e-observation.json +32 -0
  214. package/domain_types/e2e-report.json +30 -0
  215. package/domain_types/e2e-target.json +26 -0
  216. package/domain_types/e2e-verdict.json +26 -0
  217. package/domain_types/examine-round-record.json +40 -0
  218. package/domain_types/examiner-rejection.json +28 -0
  219. package/domain_types/failure-modes.json +31 -0
  220. package/domain_types/invention-analysis.json +34 -0
  221. package/domain_types/invention-spec.json +34 -0
  222. package/domain_types/novelty-analysis.json +28 -0
  223. package/domain_types/novelty-verdict.json +35 -0
  224. package/domain_types/parsed-conversation-trace.json +38 -0
  225. package/domain_types/pattern-extraction.json +62 -0
  226. package/domain_types/prior-art-hit.json +50 -0
  227. package/domain_types/project-charter.json +71 -0
  228. package/domain_types/provisional-draft.json +34 -0
  229. package/domain_types/raw-note.json +19 -0
  230. package/domain_types/repo-survey.json +28 -0
  231. package/domain_types/seeding-verdict.json +70 -0
  232. package/domain_types/soft-verdict.json +55 -0
  233. package/domain_types/summary.json +19 -0
  234. package/domain_types/triage-verdict.json +59 -0
  235. package/domain_types/user-flow-transcript.json +43 -0
  236. package/domain_types/user-flow-verdict.json +60 -0
  237. package/domain_types/verdict-record.json +39 -0
  238. package/package.json +82 -0
  239. package/skills/citation-verify/fixtures/basic.json +14 -0
  240. package/skills/citation-verify/meta.json +10 -0
  241. package/skills/citation-verify/skill.mjs +26 -0
  242. package/skills/claim-element-decompose/fixtures/basic.json +11 -0
  243. package/skills/claim-element-decompose/meta.json +10 -0
  244. package/skills/claim-element-decompose/skill.mjs +14 -0
  245. package/skills/diamond-cutting-discipline/fixtures/basic.json +13 -0
  246. package/skills/diamond-cutting-discipline/meta.json +19 -0
  247. package/skills/diamond-cutting-discipline/skill.md +46 -0
  248. package/skills/element-mapping-matrix/fixtures/basic.json +13 -0
  249. package/skills/element-mapping-matrix/meta.json +10 -0
  250. package/skills/element-mapping-matrix/skill.mjs +20 -0
  251. package/skills/number-adder/fixtures/basic.json +9 -0
  252. package/skills/number-adder/meta.json +10 -0
  253. package/skills/number-adder/skill.mjs +12 -0
  254. package/skills/patent-fetch/fixtures/parse.json +18 -0
  255. package/skills/patent-fetch/meta.json +11 -0
  256. package/skills/patent-fetch/skill.mjs +34 -0
  257. package/skills/query-expand/fixtures/basic.json +14 -0
  258. package/skills/query-expand/meta.json +10 -0
  259. package/skills/query-expand/skill.mjs +29 -0
  260. package/skills/run-vitest-band/fixtures/basic.json +9 -0
  261. package/skills/run-vitest-band/meta.json +18 -0
  262. package/skills/run-vitest-band/skill.mjs +22 -0
  263. package/skills/statutory-checklist/fixtures/basic.json +16 -0
  264. package/skills/statutory-checklist/meta.json +10 -0
  265. package/skills/statutory-checklist/skill.mjs +25 -0
  266. package/skills/summarize-tight/fixtures/basic.json +13 -0
  267. package/skills/summarize-tight/meta.json +20 -0
  268. package/skills/summarize-tight/skill.md +1 -0
  269. package/skills/verdict-gate/fixtures/basic.json +9 -0
  270. package/skills/verdict-gate/meta.json +10 -0
  271. package/skills/verdict-gate/skill.mjs +82 -0
  272. package/standards/e2e-suite-v0.json +44 -0
  273. package/standards/patent-triage-v0.json +98 -0
  274. package/standards/patent-triage-v1.json +84 -0
  275. package/standards/seed-from-local-repos-v0.json +115 -0
  276. package/standards/sub_thread_invocation.json +50 -0
  277. package/standards/summarize.json +40 -0
  278. package/standards/synthesis-walk-v0.json +108 -0
  279. package/standards/user_flow_correctness.json +57 -0
@@ -0,0 +1,452 @@
1
+ // §6 — the universal output store + output_refs provenance graph + findings view.
2
+ // Pure-TS (in-memory by default; optional disk-backed jsonl persistence), ledger.ts
3
+ // style. Validation is NOT reimplemented here: write() wires registry.validate() and
4
+ // rejects bad-schema outputs AT WRITE (T3).
5
+ //
6
+ // Persistence (PR #78 follow-up): when a `persistDir` is supplied, every
7
+ // write() appends a json line to `<persistDir>/outputs/<gig_id>.jsonl` and every
8
+ // addRef() appends to `<persistDir>/refs/<from_gig_id>.jsonl`. Reads (get / all /
9
+ // findings / trace / refs) lazy-hydrate from disk on first access, so a fresh
10
+ // process can serve outputs written by an earlier session. Matches the
11
+ // append-only jsonl-chain shape used elsewhere in the audit substrate.
12
+ import { randomUUID } from "node:crypto";
13
+ import * as fs from "node:fs";
14
+ import * as path from "node:path";
15
+ import { CORE_TYPES } from "./core_types.js";
16
+ import { validateOutput } from "./output_validation.js";
17
+ import { outputContentHash } from "./canonical_form.js";
18
+ // §6 output_refs.relation CHECK constraint, as a closed set.
19
+ export const REF_RELATIONS = [
20
+ "derived_from",
21
+ "validates",
22
+ "challenges",
23
+ "refines",
24
+ "triggers",
25
+ "contains",
26
+ ];
27
+ export class OutputStoreError extends Error {
28
+ }
29
+ // Default disk-persistence root, matching chain_keeper.py's ~/.eir/<chain>/ shape.
30
+ // Resolved against COLTRANE_OUTPUTS_DIR (test override) or $HOME.
31
+ export function defaultOutputsPersistDir() {
32
+ const override = process.env["COLTRANE_OUTPUTS_DIR"];
33
+ if (override && override.length > 0)
34
+ return override;
35
+ const home = process.env["HOME"] ?? process.env["USERPROFILE"] ?? ".";
36
+ return path.join(home, ".eir", "coltrane_outputs");
37
+ }
38
+ function ensureDir(dir) {
39
+ fs.mkdirSync(dir, { recursive: true });
40
+ }
41
+ function appendJsonl(file, row) {
42
+ ensureDir(path.dirname(file));
43
+ fs.appendFileSync(file, JSON.stringify(row) + "\n", "utf8");
44
+ }
45
+ function readJsonl(file, corrupt) {
46
+ if (!fs.existsSync(file))
47
+ return [];
48
+ const text = fs.readFileSync(file, "utf8");
49
+ const rows = [];
50
+ const lines = text.split("\n");
51
+ for (let i = 0; i < lines.length; i++) {
52
+ // `.trim()`, not `.length > 0` — a lone `\r` from a CRLF file is length 1 and would
53
+ // otherwise reach JSON.parse (same reasoning as FileLedger.read).
54
+ const line = lines[i].trim();
55
+ if (!line)
56
+ continue;
57
+ try {
58
+ rows.push(JSON.parse(line));
59
+ }
60
+ catch (e) {
61
+ // Still forgiving (chain_keeper.py's shape), but no longer silent (#248). Compatibility
62
+ // with a forgiving reader is an argument for skipping the line, never for hiding it.
63
+ corrupt.push({
64
+ path: file,
65
+ line_no: i + 1,
66
+ reason: `unparseable JSON: ${e instanceof Error ? e.message : String(e)}`,
67
+ preview: line.slice(0, 120),
68
+ });
69
+ }
70
+ }
71
+ return rows;
72
+ }
73
+ function listJsonl(dir) {
74
+ if (!fs.existsSync(dir))
75
+ return [];
76
+ const out = [];
77
+ for (const name of fs.readdirSync(dir)) {
78
+ if (name.endsWith(".jsonl"))
79
+ out.push(path.join(dir, name));
80
+ }
81
+ return out;
82
+ }
83
+ export function createOutputStore(registry, options) {
84
+ const outputs = new Map();
85
+ const edges = [];
86
+ const persistDir = options?.persistDir;
87
+ const outputsDir = persistDir ? path.join(persistDir, "outputs") : undefined;
88
+ const refsDir = persistDir ? path.join(persistDir, "refs") : undefined;
89
+ // Track which gig_ids we've hydrated so a single gig file is read at most once.
90
+ const hydratedGigs = new Set();
91
+ let fullyHydrated = false;
92
+ const fileScans = new Map();
93
+ const unmoved = (file, st) => {
94
+ const prev = fileScans.get(file);
95
+ return prev && prev.size === st.size && prev.mtimeMs === st.mtimeMs ? prev : undefined;
96
+ };
97
+ /**
98
+ * Bring the damage record for one file up to date, WITHOUT parsing out its rows.
99
+ *
100
+ * This is the whole reason `integrity()` can afford to be honest. A file whose bytes have
101
+ * not moved keeps its recorded verdict for the price of a `stat`; only a file that changed
102
+ * — or one we have never actually read, which is precisely the file this process wrote —
103
+ * costs a read.
104
+ */
105
+ function ensureScan(file) {
106
+ if (!fs.existsSync(file))
107
+ return undefined;
108
+ const st = fs.statSync(file);
109
+ const cached = unmoved(file, st);
110
+ if (cached)
111
+ return cached;
112
+ const corrupt = [];
113
+ readJsonl(file, corrupt);
114
+ const scan = { size: st.size, mtimeMs: st.mtimeMs, corrupt };
115
+ fileScans.set(file, scan);
116
+ return scan;
117
+ }
118
+ function readRows(file) {
119
+ if (!fs.existsSync(file))
120
+ return [];
121
+ const st = fs.statSync(file);
122
+ if (unmoved(file, st))
123
+ return readJsonl(file, []); // damage already recorded for these bytes
124
+ const corrupt = [];
125
+ const rows = readJsonl(file, corrupt);
126
+ fileScans.set(file, { size: st.size, mtimeMs: st.mtimeMs, corrupt });
127
+ return rows;
128
+ }
129
+ function asStr(v) {
130
+ return typeof v === "string" ? v : undefined;
131
+ }
132
+ // The registry's answer to "what core is this type, really". A bare core is itself;
133
+ // a registered domain type is its `extends`; anything unregistered is unresolvable.
134
+ // Exposed as `coreTypeOf` below AND consulted by `write` — deliberately one function,
135
+ // because #263 was precisely two layers disagreeing about the same question.
136
+ function resolveCoreType(typeSlug) {
137
+ if (CORE_TYPES.includes(typeSlug))
138
+ return typeSlug;
139
+ const dt = registry.listTypes().find((t) => t.slug === typeSlug);
140
+ return dt ? dt.extends : null;
141
+ }
142
+ function hydrateGig(gig_id) {
143
+ if (!outputsDir || hydratedGigs.has(gig_id))
144
+ return;
145
+ hydratedGigs.add(gig_id);
146
+ const file = path.join(outputsDir, `${gig_id}.jsonl`);
147
+ for (const rec of readRows(file)) {
148
+ if (!outputs.has(rec.id))
149
+ outputs.set(rec.id, rec);
150
+ }
151
+ if (refsDir) {
152
+ const refsFile = path.join(refsDir, `${gig_id}.jsonl`);
153
+ for (const ref of readRows(refsFile)) {
154
+ if (!edges.some((e) => e.id === ref.id))
155
+ edges.push(ref);
156
+ }
157
+ }
158
+ }
159
+ function hydrateAll() {
160
+ if (!outputsDir || fullyHydrated)
161
+ return;
162
+ fullyHydrated = true;
163
+ for (const file of listJsonl(outputsDir)) {
164
+ const base = path.basename(file, ".jsonl");
165
+ hydrateGig(base);
166
+ }
167
+ // Also pull in any orphan refs files (defensive — addRef writes by from_gig_id).
168
+ if (refsDir) {
169
+ for (const file of listJsonl(refsDir)) {
170
+ for (const ref of readRows(file)) {
171
+ if (!edges.some((e) => e.id === ref.id))
172
+ edges.push(ref);
173
+ }
174
+ }
175
+ }
176
+ }
177
+ return {
178
+ write(o) {
179
+ // #263 — the asserted core must agree with the registry's answer.
180
+ //
181
+ // #227/#228 made `core_type` load-bearing: it selects which substance floor is
182
+ // enforced. So a caller asserting the wrong core does not merely mislabel the record —
183
+ // it gets the WRONG core's floor applied, and can satisfy `Verdict.checks[]` while
184
+ // sealing something the registry says is an Interpretation that owed `claims[]`.
185
+ //
186
+ // ORDER: this runs FIRST, ahead of the schema validation below, and that placement is
187
+ // load-bearing in a way the first draft of this fix got wrong. The agreement check is
188
+ // pure METADATA — it compares two declarations and never looks at the payload — so it
189
+ // does not need the schema to have passed. Running it second meant that for a CLOSED
190
+ // schema (the default, and what every shipped domain type uses) a payload carrying the
191
+ // wrong core's substance field died on Ajv first, telling the operator to delete
192
+ // `checks` when the actual repair is to fix the declared core. The diagnosis for a
193
+ // contradicted core has to come from the check that understands cores.
194
+ //
195
+ // Unresolvable slugs still fall through untouched: an unregistered domain_type is the
196
+ // registry's `unknown domain_type` rejection below, and this must not become a second,
197
+ // competing owner of that error.
198
+ if (o.domain_type) {
199
+ const registered = resolveCoreType(o.domain_type);
200
+ if (registered !== null && registered !== o.core_type) {
201
+ throw new OutputStoreError(`output rejected: ${o.domain_type} was sealed as core_type "${o.core_type}" but the ` +
202
+ `registry defines it as "${registered}" — one of the two is wrong, and the core ` +
203
+ `decides which substance invariant applies`);
204
+ }
205
+ }
206
+ // #263 follow-on — a `core_type` that is not a core type at all.
207
+ //
208
+ // The check above only fires when a domain_type RESOLVES. With no domain_type (the
209
+ // freeform path, Rob #133) any string sailed through: `core_type: "Nonsense"` sealed,
210
+ // and so did `core_type: ""`. `validateOutput` returns valid for an unrecognised core,
211
+ // so those records carried NO substance floor whatsoever — the purest form of the very
212
+ // defect #263 describes, reachable without a registry entry at all.
213
+ if (!CORE_TYPES.includes(o.core_type)) {
214
+ throw new OutputStoreError(`output rejected: "${o.core_type}" is not a core type — expected one of ` +
215
+ `[${CORE_TYPES.join(", ")}]. The core selects which substance invariant applies, ` +
216
+ `so an unrecognised one silently means "no floor at all".`);
217
+ }
218
+ // T2/T3: reject bad-schema output AT WRITE by wiring the registry validator.
219
+ const result = registry.validate({
220
+ core_type: o.core_type,
221
+ domain_type: o.domain_type,
222
+ data: o.data,
223
+ });
224
+ if (!result.valid) {
225
+ throw new OutputStoreError(`output rejected: ${o.domain_type} failed schema validation — ${result.errors.join("; ")}`);
226
+ }
227
+ // #227/#228 — the CORE-type invariant, checked on every write regardless of whether
228
+ // a domain schema applied. registry.validate above returns {valid:true} without
229
+ // looking at the data for a bare core type and for an absent domain_type, and a
230
+ // subtype can overload away an inherited floor (#230) — so the substance floor an
231
+ // Artifact/Verdict carries by definition has to be enforced here, at the one seal
232
+ // boundary, not delegated to the domain schema that may not exist.
233
+ //
234
+ // validateOutput was already written and already tested; it was simply never called
235
+ // (#228). Path (b) per the #228 ruling: an ABSENT substance key is rejected, not just
236
+ // an empty one — an Artifact nobody can check is not an artifact, and a Verdict with
237
+ // no evidence is not a verification.
238
+ //
239
+ // Per the #227 ruling ("there's no subtype thing — it's all the way top to bottom")
240
+ // ALL SIX cores now carry a floor, not just Artifact and Verdict, and it applies to
241
+ // bare cores and domain subtypes alike. src/output_validation.ts holds the table.
242
+ const core = validateOutput({
243
+ core_type: o.core_type,
244
+ domain_type: o.domain_type,
245
+ data: o.data,
246
+ });
247
+ if (!core.valid) {
248
+ throw new OutputStoreError(`output rejected: ${o.domain_type || o.core_type} failed core-type invariant — ${core.reason}`);
249
+ }
250
+ const domain_type_version = o.domain_type_version ?? 1;
251
+ const rec = {
252
+ id: randomUUID(),
253
+ core_type: o.core_type,
254
+ domain_type: o.domain_type,
255
+ domain_type_version,
256
+ domain: o.domain,
257
+ gig_id: o.gig_id,
258
+ agent_slug: o.agent_slug,
259
+ from_role: o.from_role,
260
+ phase: o.phase,
261
+ primitive: o.primitive,
262
+ data: o.data,
263
+ content_sha: outputContentHash({
264
+ core_type: o.core_type,
265
+ domain_type: o.domain_type,
266
+ domain_type_version,
267
+ domain: o.domain,
268
+ primitive: o.primitive,
269
+ phase: o.phase,
270
+ agent_slug: o.agent_slug,
271
+ data: o.data,
272
+ }),
273
+ input_refs: o.input_refs ?? [],
274
+ // Real predecessor hashes (#196): prefer caller-supplied, else resolve each input_ref's
275
+ // content_sha from the store. The chain is then walkable input_refs[i] ↔ input_shas[i].
276
+ input_shas: o.input_shas ?? (o.input_refs ?? []).map((id) => outputs.get(id)?.content_sha ?? ""),
277
+ created_at: new Date().toISOString(),
278
+ cost_usd: o.cost_usd,
279
+ tokens_used: o.tokens_used,
280
+ duration_ms: o.duration_ms,
281
+ skill_provenance: o.skill_provenance,
282
+ };
283
+ outputs.set(rec.id, rec);
284
+ if (outputsDir) {
285
+ // Mark this gig hydrated so we don't re-read what we just wrote.
286
+ hydratedGigs.add(rec.gig_id);
287
+ appendJsonl(path.join(outputsDir, `${rec.gig_id}.jsonl`), rec);
288
+ }
289
+ return rec;
290
+ },
291
+ get(id) {
292
+ const hit = outputs.get(id);
293
+ if (hit)
294
+ return hit;
295
+ // Lazy-hydrate on miss: a fresh session reading an id from an earlier run.
296
+ hydrateAll();
297
+ return outputs.get(id);
298
+ },
299
+ all() {
300
+ hydrateAll();
301
+ return [...outputs.values()];
302
+ },
303
+ addRef(from_output_id, to_output_id, relation, primitive) {
304
+ if (!REF_RELATIONS.includes(relation)) {
305
+ throw new OutputStoreError(`invalid relation "${relation}"`);
306
+ }
307
+ // Ensure endpoints exist (possibly hydrating to find them).
308
+ const fromRec = outputs.get(from_output_id) ?? (hydrateAll(), outputs.get(from_output_id));
309
+ if (!fromRec) {
310
+ throw new OutputStoreError(`from_output_id "${from_output_id}" does not exist`);
311
+ }
312
+ if (!outputs.has(to_output_id)) {
313
+ throw new OutputStoreError(`to_output_id "${to_output_id}" does not exist`);
314
+ }
315
+ const ref = {
316
+ id: randomUUID(),
317
+ from_output_id,
318
+ to_output_id,
319
+ relation,
320
+ primitive,
321
+ created_at: new Date().toISOString(),
322
+ };
323
+ edges.push(ref);
324
+ if (refsDir) {
325
+ appendJsonl(path.join(refsDir, `${fromRec.gig_id}.jsonl`), ref);
326
+ }
327
+ return ref;
328
+ },
329
+ refs() {
330
+ hydrateAll();
331
+ return [...edges];
332
+ },
333
+ trace(id, opts) {
334
+ // Walk backward: a node's parents are its input_refs plus the targets of
335
+ // its derived_from/refines edges. Returns every reachable ancestor (the
336
+ // provenance closure), cycle-safe.
337
+ hydrateAll();
338
+ //
339
+ // max_depth: hard cap on hop count from the seed. depth=0 → no walk
340
+ // (returns []); depth=1 → only direct parents; etc.
341
+ //
342
+ // gig_id scope: the walk only follows into nodes that share the seed's
343
+ // gig_id. Cross-gig ancestors are not surfaced.
344
+ const maxDepth = opts?.max_depth;
345
+ const seed = outputs.get(id);
346
+ const seedGigId = seed?.gig_id;
347
+ const seen = new Set();
348
+ const order = [];
349
+ const stack = [{ id, depth: 0 }];
350
+ while (stack.length) {
351
+ const { id: cur, depth } = stack.pop();
352
+ if (seen.has(cur))
353
+ continue;
354
+ seen.add(cur);
355
+ const rec = outputs.get(cur);
356
+ if (!rec)
357
+ continue;
358
+ if (cur !== id) {
359
+ if (seedGigId !== undefined && rec.gig_id !== seedGigId)
360
+ continue;
361
+ order.push(rec);
362
+ }
363
+ if (maxDepth !== undefined && depth >= maxDepth)
364
+ continue;
365
+ for (const p of rec.input_refs)
366
+ stack.push({ id: p, depth: depth + 1 });
367
+ for (const e of edges) {
368
+ if (e.from_output_id === cur && (e.relation === "derived_from" || e.relation === "refines")) {
369
+ stack.push({ id: e.to_output_id, depth: depth + 1 });
370
+ }
371
+ }
372
+ }
373
+ return order;
374
+ },
375
+ findings() {
376
+ hydrateAll();
377
+ const rows = [];
378
+ for (const o of outputs.values()) {
379
+ if (o.domain_type !== "finding" || o.domain !== "eirtests")
380
+ continue;
381
+ const d = o.data;
382
+ rows.push({
383
+ id: o.id,
384
+ gig_id: o.gig_id,
385
+ pattern_key: asStr(d["pattern_key"]),
386
+ severity: asStr(d["severity"]),
387
+ title: asStr(d["title"]),
388
+ evidence: asStr(d["evidence"]),
389
+ location: asStr(d["location"]),
390
+ recommendation: asStr(d["recommendation"]),
391
+ is_novel: typeof d["is_novel"] === "boolean" ? d["is_novel"] : undefined,
392
+ kpi_impacts: d["kpi_impacts"],
393
+ status: asStr(d["status"]),
394
+ agent_role: o.agent_slug,
395
+ dimension: asStr(d["dimension"]),
396
+ created_at: o.created_at,
397
+ });
398
+ }
399
+ return rows;
400
+ },
401
+ coreTypeOf(typeSlug) {
402
+ return resolveCoreType(typeSlug);
403
+ },
404
+ integrity() {
405
+ // Force the full hydrate first, so reads stay consistent with what we are about to
406
+ // report — an integrity report over a partially-hydrated store would itself be the
407
+ // "shorter chain reported as the whole chain" failure (#248).
408
+ hydrateAll();
409
+ // #255 round 2 — then RE-SCAN FROM DISK, every call, ignoring every hydration memo.
410
+ //
411
+ // Reporting `corruption` / `scannedFiles` was answering a different question than the
412
+ // one asked. Those accumulate as a side effect of READS, and two memos suppress them
413
+ // in exactly the cases that matter:
414
+ //
415
+ // • `write()` adds the gig to `hydratedGigs`, so a file this process WROTE is never
416
+ // read back. In production the MCP server is long-lived with a persistDir, so every
417
+ // gig a process ran was exempt from the corruption scan for that process's life —
418
+ // precisely the torn-append-from-a-crash case #248 exists to catch. The old code
419
+ // answered `{ok:true, scanned:0}` for a directory full of its own output.
420
+ // • `hydrateAll` short-circuits on `fullyHydrated`, so the SECOND system_health
421
+ // re-asserted the first one's answer no matter what had happened on disk since.
422
+ // CLAUDE.md tells operators to run system_health first thing in a session, which
423
+ // pins that snapshot at the earliest possible moment.
424
+ //
425
+ // A damage report has to describe the bytes now, not the bytes we happened to have
426
+ // cached. `scanFile` re-reads a file whose (size, mtime) has moved since we last looked
427
+ // and stats the rest, so this is a fresh answer without being a blind full re-read.
428
+ //
429
+ // That distinction is load-bearing, not an optimisation. `system_health` is the first
430
+ // thing CLAUDE.md tells an operator to run, and it goes through the MCP relay — which
431
+ // has a known handoff race after `server_restart` (#170). Measured against the real
432
+ // outputs dir (4,595 files / 5.6 MB), an unconditional re-read added ~110 ms to every
433
+ // call and made that race fire in 1 run of 4 where clean main passed 6 of 6. A guard
434
+ // that destabilises the transport it reports through is not a guard worth having.
435
+ const corrupt = [];
436
+ let scanned = 0;
437
+ for (const dir of [outputsDir, refsDir]) {
438
+ if (!dir)
439
+ continue;
440
+ for (const file of listJsonl(dir)) {
441
+ const scan = ensureScan(file);
442
+ if (!scan)
443
+ continue;
444
+ scanned++;
445
+ corrupt.push(...scan.corrupt);
446
+ }
447
+ }
448
+ return { ok: corrupt.length === 0, scanned, corrupt };
449
+ },
450
+ };
451
+ }
452
+ //# sourceMappingURL=outputs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outputs.js","sourceRoot":"","sources":["../../src/outputs.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,oFAAoF;AACpF,qFAAqF;AACrF,4CAA4C;AAC5C,EAAE;AACF,yEAAyE;AACzE,iFAAiF;AACjF,kFAAkF;AAClF,8EAA8E;AAC9E,uEAAuE;AACvE,uEAAuE;AACvE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,UAAU,EAAiB,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,6DAA6D;AAC7D,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,cAAc;IACd,WAAW;IACX,YAAY;IACZ,SAAS;IACT,UAAU;IACV,UAAU;CACF,CAAC;AAgGX,MAAM,OAAO,gBAAiB,SAAQ,KAAK;CAAG;AAgE9C,mFAAmF;AACnF,kEAAkE;AAClE,MAAM,UAAU,wBAAwB;IACtC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;IACrD,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IACrD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC;IACtE,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,kBAAkB,CAAC,CAAC;AACrD,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,WAAW,CAAC,IAAY,EAAE,GAAY;IAC7C,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9B,EAAE,CAAC,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,SAAS,CAAI,IAAY,EAAE,OAAgC;IAClE,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACpC,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC3C,MAAM,IAAI,GAAQ,EAAE,CAAC;IACrB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,oFAAoF;QACpF,kEAAkE;QAClE,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,IAAI,CAAC;YACH,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAM,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,wFAAwF;YACxF,qFAAqF;YACrF,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,IAAI;gBACV,OAAO,EAAE,CAAC,GAAG,CAAC;gBACd,MAAM,EAAE,qBAAqB,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;gBACzE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;aAC5B,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,SAAS,CAAC,GAAW;IAC5B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IACnC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,IAAI,IAAI,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;QACvC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAkB,EAAE,OAA4B;IAChF,MAAM,OAAO,GAAG,IAAI,GAAG,EAAwB,CAAC;IAChD,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,MAAM,UAAU,GAAG,OAAO,EAAE,UAAU,CAAC;IACvC,MAAM,UAAU,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7E,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAEvE,gFAAgF;IAChF,MAAM,YAAY,GAAG,IAAI,GAAG,EAAU,CAAC;IACvC,IAAI,aAAa,GAAG,KAAK,CAAC;IAiB1B,MAAM,SAAS,GAAG,IAAI,GAAG,EAAoB,CAAC;IAE9C,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,EAAY,EAAwB,EAAE;QACnE,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACjC,OAAO,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,KAAK,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IACzF,CAAC,CAAC;IAEF;;;;;;;OAOG;IACH,SAAS,UAAU,CAAC,IAAY;QAC9B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,SAAS,CAAC;QAC3C,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC7B,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACjC,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAC1B,MAAM,OAAO,GAA4B,EAAE,CAAC;QAC5C,SAAS,CAAU,IAAI,EAAE,OAAO,CAAC,CAAC;QAClC,MAAM,IAAI,GAAa,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC;QACvE,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,SAAS,QAAQ,CAAI,IAAY;QAC/B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QACpC,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;QAC7B,IAAI,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;YAAE,OAAO,SAAS,CAAI,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,0CAA0C;QAChG,MAAM,OAAO,GAA4B,EAAE,CAAC;QAC5C,MAAM,IAAI,GAAG,SAAS,CAAI,IAAI,EAAE,OAAO,CAAC,CAAC;QACzC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QACrE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,SAAS,KAAK,CAAC,CAAU;QACvB,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/C,CAAC;IAED,oFAAoF;IACpF,oFAAoF;IACpF,sFAAsF;IACtF,6EAA6E;IAC7E,SAAS,eAAe,CAAC,QAAgB;QACvC,IAAK,UAAgC,CAAC,QAAQ,CAAC,QAAQ,CAAC;YAAE,OAAO,QAAQ,CAAC;QAC1E,MAAM,EAAE,GAAG,QAAQ,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;QACjE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IAChC,CAAC;IAED,SAAS,UAAU,CAAC,MAAc;QAChC,IAAI,CAAC,UAAU,IAAI,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,OAAO;QACpD,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,MAAM,QAAQ,CAAC,CAAC;QACtD,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAe,IAAI,CAAC,EAAE,CAAC;YAC/C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QACrD,CAAC;QACD,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,MAAM,QAAQ,CAAC,CAAC;YACvD,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAY,QAAQ,CAAC,EAAE,CAAC;gBAChD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC;oBAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC3D,CAAC;QACH,CAAC;IACH,CAAC;IAED,SAAS,UAAU;QACjB,IAAI,CAAC,UAAU,IAAI,aAAa;YAAE,OAAO;QACzC,aAAa,GAAG,IAAI,CAAC;QACrB,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,UAAU,CAAC,EAAE,CAAC;YACzC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YAC3C,UAAU,CAAC,IAAI,CAAC,CAAC;QACnB,CAAC;QACD,iFAAiF;QACjF,IAAI,OAAO,EAAE,CAAC;YACZ,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;gBACtC,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAY,IAAI,CAAC,EAAE,CAAC;oBAC5C,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC;wBAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAC3D,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO;QACL,KAAK,CAAC,CAAC;YACL,kEAAkE;YAClE,EAAE;YACF,+EAA+E;YAC/E,uFAAuF;YACvF,mFAAmF;YACnF,iFAAiF;YACjF,EAAE;YACF,sFAAsF;YACtF,sFAAsF;YACtF,sFAAsF;YACtF,qFAAqF;YACrF,uFAAuF;YACvF,iFAAiF;YACjF,mFAAmF;YACnF,uEAAuE;YACvE,EAAE;YACF,sFAAsF;YACtF,uFAAuF;YACvF,iCAAiC;YACjC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;gBAClB,MAAM,UAAU,GAAG,eAAe,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;gBAClD,IAAI,UAAU,KAAK,IAAI,IAAI,UAAU,KAAK,CAAC,CAAC,SAAS,EAAE,CAAC;oBACtD,MAAM,IAAI,gBAAgB,CACxB,oBAAoB,CAAC,CAAC,WAAW,6BAA6B,CAAC,CAAC,SAAS,YAAY;wBACnF,2BAA2B,UAAU,4CAA4C;wBACjF,2CAA2C,CAC9C,CAAC;gBACJ,CAAC;YACH,CAAC;YACD,iEAAiE;YACjE,EAAE;YACF,mFAAmF;YACnF,sFAAsF;YACtF,uFAAuF;YACvF,uFAAuF;YACvF,oEAAoE;YACpE,IAAI,CAAE,UAAgC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC7D,MAAM,IAAI,gBAAgB,CACxB,qBAAqB,CAAC,CAAC,SAAS,yCAAyC;oBACvE,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,yDAAyD;oBAClF,0DAA0D,CAC7D,CAAC;YACJ,CAAC;YACD,6EAA6E;YAC7E,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC;gBAC/B,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,IAAI,EAAE,CAAC,CAAC,IAAI;aACb,CAAC,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;gBAClB,MAAM,IAAI,gBAAgB,CACxB,oBAAoB,CAAC,CAAC,WAAW,+BAA+B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC3F,CAAC;YACJ,CAAC;YACD,oFAAoF;YACpF,gFAAgF;YAChF,gFAAgF;YAChF,kFAAkF;YAClF,kFAAkF;YAClF,mEAAmE;YACnE,EAAE;YACF,oFAAoF;YACpF,sFAAsF;YACtF,qFAAqF;YACrF,qCAAqC;YACrC,EAAE;YACF,oFAAoF;YACpF,oFAAoF;YACpF,kFAAkF;YAClF,MAAM,IAAI,GAAG,cAAc,CAAC;gBAC1B,SAAS,EAAE,CAAC,CAAC,SAAqB;gBAClC,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,IAAI,EAAE,CAAC,CAAC,IAAI;aACb,CAAC,CAAC;YACH,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;gBAChB,MAAM,IAAI,gBAAgB,CACxB,oBAAoB,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,SAAS,iCAAiC,IAAI,CAAC,MAAM,EAAE,CAC/F,CAAC;YACJ,CAAC;YACD,MAAM,mBAAmB,GAAG,CAAC,CAAC,mBAAmB,IAAI,CAAC,CAAC;YACvD,MAAM,GAAG,GAAiB;gBACxB,EAAE,EAAE,UAAU,EAAE;gBAChB,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,mBAAmB;gBACnB,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,MAAM,EAAE,CAAC,CAAC,MAAM;gBAChB,UAAU,EAAE,CAAC,CAAC,UAAU;gBACxB,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,SAAS,EAAE,CAAC,CAAC,SAAS;gBACtB,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,WAAW,EAAE,iBAAiB,CAAC;oBAC7B,SAAS,EAAE,CAAC,CAAC,SAAS;oBACtB,WAAW,EAAE,CAAC,CAAC,WAAW;oBAC1B,mBAAmB;oBACnB,MAAM,EAAE,CAAC,CAAC,MAAM;oBAChB,SAAS,EAAE,CAAC,CAAC,SAAS;oBACtB,KAAK,EAAE,CAAC,CAAC,KAAK;oBACd,UAAU,EAAE,CAAC,CAAC,UAAU;oBACxB,IAAI,EAAE,CAAC,CAAC,IAAI;iBACb,CAAC;gBACF,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,EAAE;gBAC9B,wFAAwF;gBACxF,wFAAwF;gBACxF,UAAU,EAAE,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,WAAW,IAAI,EAAE,CAAC;gBAChG,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACpC,QAAQ,EAAE,CAAC,CAAC,QAAQ;gBACpB,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,WAAW,EAAE,CAAC,CAAC,WAAW;gBAC1B,gBAAgB,EAAE,CAAC,CAAC,gBAAgB;aACrC,CAAC;YACF,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;YACzB,IAAI,UAAU,EAAE,CAAC;gBACf,iEAAiE;gBACjE,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC7B,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,GAAG,CAAC,MAAM,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC;YACjE,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC;QAED,GAAG,CAAC,EAAE;YACJ,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC5B,IAAI,GAAG;gBAAE,OAAO,GAAG,CAAC;YACpB,2EAA2E;YAC3E,UAAU,EAAE,CAAC;YACb,OAAO,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACzB,CAAC;QAED,GAAG;YACD,UAAU,EAAE,CAAC;YACb,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAC/B,CAAC;QAED,MAAM,CAAC,cAAc,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS;YACtD,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACtC,MAAM,IAAI,gBAAgB,CAAC,qBAAqB,QAAQ,GAAG,CAAC,CAAC;YAC/D,CAAC;YACD,4DAA4D;YAC5D,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC;YAC3F,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,IAAI,gBAAgB,CAAC,mBAAmB,cAAc,kBAAkB,CAAC,CAAC;YAClF,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC/B,MAAM,IAAI,gBAAgB,CAAC,iBAAiB,YAAY,kBAAkB,CAAC,CAAC;YAC9E,CAAC;YACD,MAAM,GAAG,GAAc;gBACrB,EAAE,EAAE,UAAU,EAAE;gBAChB,cAAc;gBACd,YAAY;gBACZ,QAAQ;gBACR,SAAS;gBACT,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACrC,CAAC;YACF,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAChB,IAAI,OAAO,EAAE,CAAC;gBACZ,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC;YAClE,CAAC;YACD,OAAO,GAAG,CAAC;QACb,CAAC;QAED,IAAI;YACF,UAAU,EAAE,CAAC;YACb,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;QACpB,CAAC;QAED,KAAK,CAAC,EAAE,EAAE,IAAI;YACZ,yEAAyE;YACzE,wEAAwE;YACxE,mCAAmC;YACnC,UAAU,EAAE,CAAC;YACb,EAAE;YACF,oEAAoE;YACpE,oDAAoD;YACpD,EAAE;YACF,uEAAuE;YACvE,gDAAgD;YAChD,MAAM,QAAQ,GAAG,IAAI,EAAE,SAAS,CAAC;YACjC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC7B,MAAM,SAAS,GAAG,IAAI,EAAE,MAAM,CAAC;YAC/B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;YAC/B,MAAM,KAAK,GAAmB,EAAE,CAAC;YACjC,MAAM,KAAK,GAAyC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;YACvE,OAAO,KAAK,CAAC,MAAM,EAAE,CAAC;gBACpB,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC,GAAG,EAAG,CAAC;gBACxC,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;oBAAE,SAAS;gBAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBACd,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBAC7B,IAAI,CAAC,GAAG;oBAAE,SAAS;gBACnB,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;oBACf,IAAI,SAAS,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;wBAAE,SAAS;oBAClE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAClB,CAAC;gBACD,IAAI,QAAQ,KAAK,SAAS,IAAI,KAAK,IAAI,QAAQ;oBAAE,SAAS;gBAC1D,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,UAAU;oBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC;gBACxE,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;oBACtB,IAAI,CAAC,CAAC,cAAc,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,QAAQ,KAAK,cAAc,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,EAAE,CAAC;wBAC5F,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,YAAY,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC;oBACvD,CAAC;gBACH,CAAC;YACH,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC;QAED,QAAQ;YACN,UAAU,EAAE,CAAC;YACb,MAAM,IAAI,GAAc,EAAE,CAAC;YAC3B,KAAK,MAAM,CAAC,IAAI,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;gBACjC,IAAI,CAAC,CAAC,WAAW,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,KAAK,UAAU;oBAAE,SAAS;gBACrE,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;gBACjB,IAAI,CAAC,IAAI,CAAC;oBACR,EAAE,EAAE,CAAC,CAAC,EAAE;oBACR,MAAM,EAAE,CAAC,CAAC,MAAM;oBAChB,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;oBACpC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;oBAC9B,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;oBACxB,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;oBAC9B,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;oBAC9B,cAAc,EAAE,KAAK,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC;oBAC1C,QAAQ,EAAE,OAAO,CAAC,CAAC,UAAU,CAAC,KAAK,SAAS,CAAC,CAAC,CAAE,CAAC,CAAC,UAAU,CAAa,CAAC,CAAC,CAAC,SAAS;oBACrF,WAAW,EAAE,CAAC,CAAC,aAAa,CAAC;oBAC7B,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;oBAC1B,UAAU,EAAE,CAAC,CAAC,UAAU;oBACxB,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;oBAChC,UAAU,EAAE,CAAC,CAAC,UAAU;iBACzB,CAAC,CAAC;YACL,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,UAAU,CAAC,QAAQ;YACjB,OAAO,eAAe,CAAC,QAAQ,CAAC,CAAC;QACnC,CAAC;QAED,SAAS;YACP,mFAAmF;YACnF,mFAAmF;YACnF,8DAA8D;YAC9D,UAAU,EAAE,CAAC;YAEb,oFAAoF;YACpF,EAAE;YACF,sFAAsF;YACtF,qFAAqF;YACrF,oCAAoC;YACpC,EAAE;YACF,sFAAsF;YACtF,wFAAwF;YACxF,sFAAsF;YACtF,qFAAqF;YACrF,8EAA8E;YAC9E,kFAAkF;YAClF,oFAAoF;YACpF,qFAAqF;YACrF,0DAA0D;YAC1D,EAAE;YACF,mFAAmF;YACnF,wFAAwF;YACxF,oFAAoF;YACpF,EAAE;YACF,sFAAsF;YACtF,sFAAsF;YACtF,oFAAoF;YACpF,sFAAsF;YACtF,qFAAqF;YACrF,kFAAkF;YAClF,MAAM,OAAO,GAA4B,EAAE,CAAC;YAC5C,IAAI,OAAO,GAAG,CAAC,CAAC;YAChB,KAAK,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE,CAAC;gBACxC,IAAI,CAAC,GAAG;oBAAE,SAAS;gBACnB,KAAK,MAAM,IAAI,IAAI,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;oBAClC,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;oBAC9B,IAAI,CAAC,IAAI;wBAAE,SAAS;oBACpB,OAAO,EAAE,CAAC;oBACV,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,CAAC;gBAChC,CAAC;YACH,CAAC;YACD,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;QACxD,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { type Key } from "./circle_of_fifths.js";
2
+ /**
3
+ * Frequency of A4 = 440 Hz. The reference pitch for the tuning system.
4
+ * 12-TET (twelve-tone equal temperament) places every adjacent semitone at a
5
+ * frequency ratio of 2^(1/12) ≈ 1.05946.
6
+ */
7
+ export declare const A4_HZ = 440;
8
+ /** Fundamental frequency for a given key, tied to A4 = 440 Hz at octave 4. */
9
+ export declare function fundamentalHz(k: Key): number;
10
+ /**
11
+ * Overtone series for a fundamental. Returns the first N harmonics
12
+ * (multiples 1, 2, 3, ..., N of the fundamental frequency).
13
+ */
14
+ export declare function overtoneSeries(k: Key, n?: number): readonly number[];
15
+ /**
16
+ * Cents-distance between two frequencies. 100 cents = one semitone.
17
+ * Used to judge how close two overtones are — perceptually, within ~25 cents
18
+ * is heard as "the same note."
19
+ */
20
+ export declare function centsDistance(hz_a: number, hz_b: number): number;
21
+ /**
22
+ * How well two overtone series ALIGN. For each pair (a_i, b_j) with i,j in the
23
+ * first N harmonics, count the pairs that fall within tolerance_cents of each
24
+ * other. Returns a NUMBER (overlap count), not a verdict. Higher = more
25
+ * physically-resonant; lower = more dissonant.
26
+ *
27
+ * Octaves count as alignment (an octave is the same note class).
28
+ */
29
+ export declare function overtoneOverlap(a: Key, b: Key, n?: number, tolerance_cents?: number): number;
30
+ /**
31
+ * Consonance score between two keys, by overtone overlap.
32
+ * NOT a verdict — a feature for the tensor. The CALLER decides what threshold
33
+ * means "consonant" vs "dissonant" — different musical traditions use different
34
+ * thresholds (Western tonal vs jazz vs microtonal).
35
+ */
36
+ export declare function overtoneConsonance(a: Key, b: Key): number;
37
+ /**
38
+ * Map every key against every other key, returning a 12×12 overlap matrix.
39
+ * Pure data — preserved as a structure, never collapsed to "one chord is
40
+ * consonant" or similar.
41
+ */
42
+ export declare function consonanceMatrix(): Readonly<Record<Key, Readonly<Record<Key, number>>>>;
@@ -0,0 +1,98 @@
1
+ // Harmonic-series overtones. Each fundamental tone carries its overtone series
2
+ // — the integer multiples of its frequency that sound when it's played. This is
3
+ // what makes two notes physically consonant (their overtone series align) or
4
+ // dissonant (they don't). A SEPARATE read-channel from circle-of-fifths key-
5
+ // topology: where the circle measures KEY relationships, overtones measure
6
+ // FREQUENCY-resonance. Both belong in the tensor; neither collapses to scalar.
7
+ import { KEYS } from "./circle_of_fifths.js";
8
+ /**
9
+ * Frequency of A4 = 440 Hz. The reference pitch for the tuning system.
10
+ * 12-TET (twelve-tone equal temperament) places every adjacent semitone at a
11
+ * frequency ratio of 2^(1/12) ≈ 1.05946.
12
+ */
13
+ export const A4_HZ = 440;
14
+ const SEMITONE_RATIO = Math.pow(2, 1 / 12);
15
+ // Semitone count above C for each circle-of-fifths key (octave = 0).
16
+ // We use this to compute fundamentals: C=0, G=7, D=2, A=9, E=4, B=11, etc.
17
+ const SEMITONES_ABOVE_C = {
18
+ C: 0, G: 7, D: 2, A: 9, E: 4, B: 11,
19
+ Gb: 6, Db: 1, Ab: 8, Eb: 3, Bb: 10, F: 5,
20
+ };
21
+ /** Fundamental frequency for a given key, tied to A4 = 440 Hz at octave 4. */
22
+ export function fundamentalHz(k) {
23
+ const semitonesFromA4 = SEMITONES_ABOVE_C[k] - SEMITONES_ABOVE_C["A"];
24
+ return A4_HZ * Math.pow(SEMITONE_RATIO, semitonesFromA4);
25
+ }
26
+ /**
27
+ * Overtone series for a fundamental. Returns the first N harmonics
28
+ * (multiples 1, 2, 3, ..., N of the fundamental frequency).
29
+ */
30
+ export function overtoneSeries(k, n = 8) {
31
+ if (n < 1)
32
+ return [];
33
+ const f0 = fundamentalHz(k);
34
+ const out = [];
35
+ for (let i = 1; i <= n; i++)
36
+ out.push(f0 * i);
37
+ return out;
38
+ }
39
+ /**
40
+ * Cents-distance between two frequencies. 100 cents = one semitone.
41
+ * Used to judge how close two overtones are — perceptually, within ~25 cents
42
+ * is heard as "the same note."
43
+ */
44
+ export function centsDistance(hz_a, hz_b) {
45
+ return 1200 * Math.log2(hz_a / hz_b);
46
+ }
47
+ /**
48
+ * How well two overtone series ALIGN. For each pair (a_i, b_j) with i,j in the
49
+ * first N harmonics, count the pairs that fall within tolerance_cents of each
50
+ * other. Returns a NUMBER (overlap count), not a verdict. Higher = more
51
+ * physically-resonant; lower = more dissonant.
52
+ *
53
+ * Octaves count as alignment (an octave is the same note class).
54
+ */
55
+ export function overtoneOverlap(a, b, n = 8, tolerance_cents = 25) {
56
+ const series_a = overtoneSeries(a, n);
57
+ const series_b = overtoneSeries(b, n);
58
+ let count = 0;
59
+ for (const fa of series_a) {
60
+ for (const fb of series_b) {
61
+ // Fold into a single octave for the comparison (overtones repeat across octaves).
62
+ let cents = centsDistance(fa, fb);
63
+ while (cents > 600)
64
+ cents -= 1200;
65
+ while (cents < -600)
66
+ cents += 1200;
67
+ if (Math.abs(cents) <= tolerance_cents)
68
+ count++;
69
+ }
70
+ }
71
+ return count;
72
+ }
73
+ /**
74
+ * Consonance score between two keys, by overtone overlap.
75
+ * NOT a verdict — a feature for the tensor. The CALLER decides what threshold
76
+ * means "consonant" vs "dissonant" — different musical traditions use different
77
+ * thresholds (Western tonal vs jazz vs microtonal).
78
+ */
79
+ export function overtoneConsonance(a, b) {
80
+ return overtoneOverlap(a, b);
81
+ }
82
+ /**
83
+ * Map every key against every other key, returning a 12×12 overlap matrix.
84
+ * Pure data — preserved as a structure, never collapsed to "one chord is
85
+ * consonant" or similar.
86
+ */
87
+ export function consonanceMatrix() {
88
+ const out = {};
89
+ for (const a of KEYS) {
90
+ const row = {};
91
+ for (const b of KEYS) {
92
+ row[b] = overtoneOverlap(a, b);
93
+ }
94
+ out[a] = row;
95
+ }
96
+ return out;
97
+ }
98
+ //# sourceMappingURL=overtones.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"overtones.js","sourceRoot":"","sources":["../../src/overtones.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ export interface Player {
2
+ slug: string;
3
+ display_name: string;
4
+ description: string;
5
+ agent_profile_ref: string;
6
+ lane: string;
7
+ tools_allowlist: readonly string[];
8
+ charter: string;
9
+ }
10
+ /** Parse a player .md file body (frontmatter + body) into a Player record.
11
+ * Throws if required fields are missing or the frontmatter delimiters are
12
+ * malformed. Minimal YAML — we only support the exact shape we ship. */
13
+ export declare function parsePlayer(fileContents: string): Player;
14
+ /** Compile a Player into a Claude Code agent .md string.
15
+ * Output shape: YAML frontmatter (name, description, tools, model) + a body
16
+ * with the orientation line and the player's charter. */
17
+ export declare function compilePlayerToClaudeAgent(player: Player): string;