@neurodock/core 0.0.1 → 0.2.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 (57) hide show
  1. package/LICENSE +657 -7
  2. package/README.md +50 -3
  3. package/data/neurotype-addenda/v1.json +389 -0
  4. package/dist/index.d.ts +5 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +7 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/index.test.d.ts +2 -0
  9. package/dist/index.test.d.ts.map +1 -0
  10. package/dist/index.test.js +29 -0
  11. package/dist/index.test.js.map +1 -0
  12. package/dist/neurotype-addenda.d.ts +91 -0
  13. package/dist/neurotype-addenda.d.ts.map +1 -0
  14. package/dist/neurotype-addenda.js +175 -0
  15. package/dist/neurotype-addenda.js.map +1 -0
  16. package/dist/neurotype-addenda.parity.test.d.ts +2 -0
  17. package/dist/neurotype-addenda.parity.test.d.ts.map +1 -0
  18. package/dist/neurotype-addenda.parity.test.js +69 -0
  19. package/dist/neurotype-addenda.parity.test.js.map +1 -0
  20. package/dist/neurotype-addenda.schema.test.d.ts +2 -0
  21. package/dist/neurotype-addenda.schema.test.d.ts.map +1 -0
  22. package/dist/neurotype-addenda.schema.test.js +83 -0
  23. package/dist/neurotype-addenda.schema.test.js.map +1 -0
  24. package/dist/neurotype-addenda.test.d.ts +2 -0
  25. package/dist/neurotype-addenda.test.d.ts.map +1 -0
  26. package/dist/neurotype-addenda.test.js +165 -0
  27. package/dist/neurotype-addenda.test.js.map +1 -0
  28. package/dist/profile.d.ts +128 -0
  29. package/dist/profile.d.ts.map +1 -0
  30. package/dist/profile.js +27 -0
  31. package/dist/profile.js.map +1 -0
  32. package/dist/profile.presets.test.d.ts +2 -0
  33. package/dist/profile.presets.test.d.ts.map +1 -0
  34. package/dist/profile.presets.test.js +43 -0
  35. package/dist/profile.presets.test.js.map +1 -0
  36. package/dist/profile.schema.test.d.ts +2 -0
  37. package/dist/profile.schema.test.d.ts.map +1 -0
  38. package/dist/profile.schema.test.js +282 -0
  39. package/dist/profile.schema.test.js.map +1 -0
  40. package/dist/profile.test.d.ts +2 -0
  41. package/dist/profile.test.d.ts.map +1 -0
  42. package/dist/profile.test.js +60 -0
  43. package/dist/profile.test.js.map +1 -0
  44. package/dist/test-helpers/ajv.d.ts +22 -0
  45. package/dist/test-helpers/ajv.d.ts.map +1 -0
  46. package/dist/test-helpers/ajv.js +38 -0
  47. package/dist/test-helpers/ajv.js.map +1 -0
  48. package/package.json +16 -9
  49. package/schemas/neurotype-addenda.schema.json +167 -0
  50. package/schemas/plugin.example.yaml +116 -71
  51. package/schemas/plugin.minimal.yaml +18 -12
  52. package/schemas/plugin.schema.json +13 -2
  53. package/schemas/profile.example.yaml +112 -64
  54. package/schemas/profile.minimal.yaml +15 -7
  55. package/schemas/profile.schema.json +116 -0
  56. package/src/index.test.ts +0 -8
  57. package/src/index.ts +0 -1
@@ -0,0 +1,175 @@
1
+ /*
2
+ * SPDX-License-Identifier: AGPL-3.0-or-later
3
+ * Copyright (c) 2026 NeuroDock contributors.
4
+ */
5
+ /**
6
+ * neurotype-addenda.ts — the shared, language-neutral assembler for
7
+ * per-neurotype prompt shaping.
8
+ *
9
+ * This is the single source of truth (ADR 0012) for the per-(tool x
10
+ * neurotype) response-tailoring blocks that NeuroDock surfaces append to a
11
+ * model prompt. The content lives in `data/neurotype-addenda/v1.json`
12
+ * (validated against `schemas/neurotype-addenda.schema.json`); this module
13
+ * is the pure function that assembles those blocks into the final addendum
14
+ * string.
15
+ *
16
+ * Design constraints:
17
+ * - Zero runtime dependencies. The artifact is plain JSON imported at
18
+ * build time; the assembler is pure TypeScript with no I/O.
19
+ * - ADR 0011 compliant: this is enum-keyed CONTENT, not schema shape. The
20
+ * artifact adds no field constraints and forks no schema.
21
+ * - The browser extension and (a later PR) the mcp-translation server both
22
+ * read this so per-neurotype shaping stays identical across surfaces.
23
+ *
24
+ * The ONLY interpolation tokens are `{max_chunk_size}` (replaced with the
25
+ * caller's per-list item cap) and `{notes}` (replaced with the reader's
26
+ * free-form notes inside the self-described block).
27
+ */
28
+ import artifactV1 from "../data/neurotype-addenda/v1.json" with { type: "json" };
29
+ /**
30
+ * The shipped v1 artifact, typed. Re-exported so consumers (the browser
31
+ * extension, the mcp-translation server) bundle the canonical content
32
+ * through `@neurodock/core` without re-reading the JSON file themselves.
33
+ */
34
+ export const neurotypeAddendaV1 = artifactV1;
35
+ /**
36
+ * Apply the artifact's fusion (AuDHD substitution) rule and de-duplicate.
37
+ * Returns the neurotype list the addendum stack should iterate over.
38
+ *
39
+ * Reproduces the extension's `effectiveNeurotypes()`: when the input set
40
+ * lists the fusion `result` directly, OR lists every neurotype in `all_of`,
41
+ * the `result` is added and every neurotype in `remove` is dropped.
42
+ */
43
+ function effectiveNeurotypes(artifact, input) {
44
+ const { fusion } = artifact;
45
+ const set = new Set(input);
46
+ const hasResult = set.has(fusion.result);
47
+ const hasAll = fusion.all_of !== undefined &&
48
+ fusion.all_of.length > 0 &&
49
+ fusion.all_of.every((n) => set.has(n));
50
+ if (hasResult || hasAll) {
51
+ set.add(fusion.result);
52
+ for (const n of fusion.remove ?? []) {
53
+ set.delete(n);
54
+ }
55
+ }
56
+ return Array.from(set);
57
+ }
58
+ /**
59
+ * Sort by the artifact's priority ordering. Higher-priority addenda are
60
+ * placed LATER in the prompt (recency bias). Mirrors `orderByPriority()`.
61
+ */
62
+ function orderByPriority(artifact, neurotypes) {
63
+ const { priority } = artifact;
64
+ return [...neurotypes].sort((a, b) => priority.indexOf(a) - priority.indexOf(b));
65
+ }
66
+ /** Replace `{max_chunk_size}` in a single line. */
67
+ function interpolateMaxChunkSize(line, maxChunkSize) {
68
+ return line.split("{max_chunk_size}").join(String(maxChunkSize));
69
+ }
70
+ /** Render the output-format line. Mirrors `renderOutputFormatBlock()`. */
71
+ function renderOutputFormatBlock(artifact, format) {
72
+ const { output_format } = artifact;
73
+ const description = output_format.descriptions[format] ?? "";
74
+ return output_format.prefix + format + output_format.separator + description;
75
+ }
76
+ /** Render the self-described block with `{notes}` interpolated. */
77
+ function renderOtherBlock(artifact, notes) {
78
+ return artifact.other.block
79
+ .map((line) => line.split("{notes}").join(notes))
80
+ .join(artifact.framing.block_line_separator);
81
+ }
82
+ /**
83
+ * Render the block for a single neurotype. Mirrors `renderNeurotypeBlock()`:
84
+ * - `tourette` -> the tourette special block;
85
+ * - `other` -> the self-described block, or "" when there are no notes;
86
+ * - otherwise -> the per-tool concrete block when one exists, else the
87
+ * generic fallback block (both with `{max_chunk_size}`
88
+ * interpolated).
89
+ */
90
+ function renderNeurotypeBlock(artifact, neurotype, maxChunkSize, additionalNotes, tool) {
91
+ const sep = artifact.framing.block_line_separator;
92
+ if (neurotype === "tourette") {
93
+ return artifact.tourette.block.join(sep);
94
+ }
95
+ if (neurotype === "other") {
96
+ if (additionalNotes === null || additionalNotes.length === 0) {
97
+ return "";
98
+ }
99
+ return renderOtherBlock(artifact, additionalNotes);
100
+ }
101
+ let block;
102
+ if (tool !== undefined) {
103
+ const matrix = artifact.tools[tool];
104
+ block = matrix ? matrix[neurotype] : undefined;
105
+ }
106
+ if (block === undefined) {
107
+ block = artifact.generic[neurotype];
108
+ }
109
+ if (block === undefined) {
110
+ return "";
111
+ }
112
+ return block
113
+ .map((line) => interpolateMaxChunkSize(line, maxChunkSize))
114
+ .join(sep);
115
+ }
116
+ /**
117
+ * Assemble the per-neurotype prompt addendum from the artifact.
118
+ *
119
+ * Pure function: deterministic, no I/O, no mutation of inputs. Reproduces
120
+ * the assembly the NeuroDock browser extension shipped (fusion -> priority
121
+ * order -> per-tool blocks with generic fallback -> tourette/other specials
122
+ * -> voice-input cross-cutting block -> conflict footer -> token
123
+ * interpolation) so per-neurotype shaping is byte-identical across surfaces.
124
+ *
125
+ * Returns "" when there are no effective neurotypes, no notes, a default
126
+ * output format, AND no voice-input hint — i.e. the all-default case stays
127
+ * byte-identical to an un-shaped prompt.
128
+ */
129
+ export function assembleNeurotypeAddendum(artifact, options) {
130
+ const { tool, neurotypes, maxChunkSize, voiceInputPreferred, additionalNotes = null, } = options;
131
+ const outputFormat = options.outputFormat ?? artifact.output_format.default;
132
+ const effective = effectiveNeurotypes(artifact, neurotypes);
133
+ const hasNotes = additionalNotes !== null && additionalNotes.length > 0;
134
+ const hasNonDefaultFormat = outputFormat !== artifact.output_format.default;
135
+ const wantsVoiceInput = voiceInputPreferred === true;
136
+ if (effective.length === 0 &&
137
+ !hasNotes &&
138
+ !hasNonDefaultFormat &&
139
+ !wantsVoiceInput) {
140
+ return "";
141
+ }
142
+ const { framing } = artifact;
143
+ const sections = [];
144
+ for (const line of framing.header) {
145
+ sections.push(line);
146
+ }
147
+ sections.push(renderOutputFormatBlock(artifact, outputFormat));
148
+ if (wantsVoiceInput) {
149
+ sections.push("");
150
+ sections.push(artifact.voice_input.block.join(framing.block_line_separator));
151
+ }
152
+ const ordered = orderByPriority(artifact, effective);
153
+ for (const neurotype of ordered) {
154
+ const block = renderNeurotypeBlock(artifact, neurotype, maxChunkSize, additionalNotes, tool);
155
+ if (block.length > 0) {
156
+ sections.push("");
157
+ sections.push(block);
158
+ }
159
+ }
160
+ if (hasNotes && !ordered.includes("other")) {
161
+ sections.push("");
162
+ sections.push(renderOtherBlock(artifact, additionalNotes ?? ""));
163
+ }
164
+ if (ordered.length >= framing.conflict_footer_min_neurotypes) {
165
+ sections.push("");
166
+ sections.push(framing.conflict_footer);
167
+ }
168
+ for (const line of framing.footer) {
169
+ sections.push(line);
170
+ }
171
+ return (framing.wrapper_prefix +
172
+ sections.join(framing.section_separator) +
173
+ framing.wrapper_suffix);
174
+ }
175
+ //# sourceMappingURL=neurotype-addenda.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"neurotype-addenda.js","sourceRoot":"","sources":["../src/neurotype-addenda.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=neurotype-addenda.parity.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"neurotype-addenda.parity.test.d.ts","sourceRoot":"","sources":["../src/neurotype-addenda.parity.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,69 @@
1
+ /*
2
+ * SPDX-License-Identifier: AGPL-3.0-or-later
3
+ * Copyright (c) 2026 NeuroDock contributors.
4
+ */
5
+ /**
6
+ * neurotype-addenda.parity.test.ts — the TypeScript half of the cross-language
7
+ * parity gate (ADR 0012 binding rule 2).
8
+ *
9
+ * The TypeScript assembler is the SOURCE OF TRUTH. This test asserts that the
10
+ * TS assembler still produces the exact strings committed in
11
+ * `data/neurotype-addenda/parity-fixtures.json`. The Python half
12
+ * (`packages/mcp-translation/tests/test_addenda_parity.py`) asserts the Python
13
+ * assembler produces the SAME strings. If TS and Python ever diverge, exactly
14
+ * one of the two halves goes red — drift becomes a failed build, not a user
15
+ * report.
16
+ *
17
+ * The fixture inputs (`cases`) live in the committed JSON so both runtimes read
18
+ * byte-identical inputs. The `expected` strings are generated FROM this
19
+ * assembler: run `UPDATE_NEUROTYPE_PARITY_FIXTURES=1 pnpm --filter @neurodock/core test`
20
+ * to intentionally regenerate them after a deliberate artifact edit, then
21
+ * re-run the Python half to confirm it still matches.
22
+ */
23
+ import { readFileSync, writeFileSync } from "node:fs";
24
+ import { fileURLToPath } from "node:url";
25
+ import { describe, expect, test } from "vitest";
26
+ import { assembleNeurotypeAddendum, neurotypeAddendaV1, } from "./neurotype-addenda.js";
27
+ const FIXTURES_URL = new URL("../data/neurotype-addenda/parity-fixtures.json", import.meta.url);
28
+ const FIXTURES_PATH = fileURLToPath(FIXTURES_URL);
29
+ function optionsForCase(c) {
30
+ const options = {
31
+ neurotypes: c.neurotypes,
32
+ maxChunkSize: c.max_chunk_size,
33
+ };
34
+ if (c.tool !== undefined)
35
+ options.tool = c.tool;
36
+ if (c.output_format !== undefined)
37
+ options.outputFormat = c.output_format;
38
+ if (c.voice_input_preferred !== undefined)
39
+ options.voiceInputPreferred = c.voice_input_preferred;
40
+ if (c.additional_notes !== undefined)
41
+ options.additionalNotes = c.additional_notes;
42
+ return options;
43
+ }
44
+ const fixtures = JSON.parse(readFileSync(FIXTURES_PATH, "utf-8"));
45
+ describe("neurotype-addenda cross-language parity (TS source of truth)", () => {
46
+ if (process.env.UPDATE_NEUROTYPE_PARITY_FIXTURES === "1") {
47
+ test("regenerate parity fixtures from the TS assembler", () => {
48
+ for (const c of fixtures.cases) {
49
+ c.expected = assembleNeurotypeAddendum(neurotypeAddendaV1, optionsForCase(c));
50
+ }
51
+ const regenerated = {
52
+ ...fixtures,
53
+ artifact_version: neurotypeAddendaV1.artifact_version,
54
+ };
55
+ writeFileSync(FIXTURES_PATH, JSON.stringify(regenerated, null, 2) + "\n", "utf-8");
56
+ expect(fixtures.cases.length).toBeGreaterThan(0);
57
+ });
58
+ return;
59
+ }
60
+ test("the fixture corpus is non-trivial and pinned to the shipped artifact", () => {
61
+ // 59 cases exist; a floor of 55 catches accidental truncation of the corpus.
62
+ expect(fixtures.cases.length).toBeGreaterThanOrEqual(55);
63
+ expect(fixtures.artifact_version).toBe(neurotypeAddendaV1.artifact_version);
64
+ });
65
+ test.each(fixtures.cases.map((c) => [c.name, c]))("TS assembler reproduces fixture %s", (_name, c) => {
66
+ expect(assembleNeurotypeAddendum(neurotypeAddendaV1, optionsForCase(c))).toBe(c.expected);
67
+ });
68
+ });
69
+ //# sourceMappingURL=neurotype-addenda.parity.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"neurotype-addenda.parity.test.js","sourceRoot":"","sources":["../src/neurotype-addenda.parity.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=neurotype-addenda.schema.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"neurotype-addenda.schema.test.d.ts","sourceRoot":"","sources":["../src/neurotype-addenda.schema.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,83 @@
1
+ /*
2
+ * SPDX-License-Identifier: AGPL-3.0-or-later
3
+ * Copyright (c) 2026 NeuroDock contributors.
4
+ */
5
+ /**
6
+ * neurotype-addenda.schema.test.ts — schema-conformance gate for the shared
7
+ * neurotype-shaping artifact (data/neurotype-addenda/v1.json).
8
+ *
9
+ * Compiles the canonical schema (the exact file shipped in the package and
10
+ * consumed at runtime by the assembler) with Ajv 2020 and asserts:
11
+ * - the shipped v1.json artifact validates clean against its schema;
12
+ * - the declared interpolation tokens are exactly {max_chunk_size} + {notes};
13
+ * - every neurotype named in `priority` has a `generic` fallback block;
14
+ * - a missing required top-level key is rejected (so the schema actually
15
+ * gates the shape, not just rubber-stamps anything).
16
+ *
17
+ * Ajv + ajv-formats are dev-only test dependencies. The package keeps its
18
+ * zero-runtime-dependencies invariant.
19
+ */
20
+ import { readFileSync } from "node:fs";
21
+ import { fileURLToPath } from "node:url";
22
+ import { dirname, resolve } from "node:path";
23
+ import { beforeAll, describe, expect, test } from "vitest";
24
+ import { buildAjv } from "./test-helpers/ajv.js";
25
+ const here = dirname(fileURLToPath(import.meta.url));
26
+ const schemasDir = resolve(here, "..", "schemas");
27
+ const dataDir = resolve(here, "..", "data", "neurotype-addenda");
28
+ function readSchema() {
29
+ const raw = readFileSync(resolve(schemasDir, "neurotype-addenda.schema.json"), "utf8");
30
+ return JSON.parse(raw);
31
+ }
32
+ function readArtifact() {
33
+ const raw = readFileSync(resolve(dataDir, "v1.json"), "utf8");
34
+ return JSON.parse(raw);
35
+ }
36
+ let validate;
37
+ beforeAll(() => {
38
+ validate = buildAjv().compile(readSchema());
39
+ });
40
+ describe("neurotype-addenda schema — shipped artifact", () => {
41
+ test("v1.json validates clean against the schema", () => {
42
+ const ok = validate(readArtifact());
43
+ if (!ok) {
44
+ // Surface the first error to make a failure debuggable.
45
+ throw new Error(JSON.stringify(validate.errors, null, 2));
46
+ }
47
+ expect(ok).toBe(true);
48
+ });
49
+ test("artifact_version is 1.0.0", () => {
50
+ expect(readArtifact().artifact_version).toBe("1.0.0");
51
+ });
52
+ test("declares exactly the two interpolation tokens", () => {
53
+ expect(readArtifact().tokens).toEqual(["{max_chunk_size}", "{notes}"]);
54
+ });
55
+ test("every neurotype in priority has a generic fallback or a special block", () => {
56
+ const artifact = readArtifact();
57
+ const priority = artifact.priority;
58
+ const generic = artifact.generic;
59
+ // tourette + other are special blocks, not generic entries.
60
+ const specials = new Set(["tourette", "other"]);
61
+ for (const neurotype of priority) {
62
+ if (specials.has(neurotype)) {
63
+ expect(artifact[neurotype]).toBeDefined();
64
+ }
65
+ else {
66
+ expect(generic[neurotype]).toBeDefined();
67
+ }
68
+ }
69
+ });
70
+ });
71
+ describe("neurotype-addenda schema — gating", () => {
72
+ test("rejects an artifact missing a required top-level key", () => {
73
+ const artifact = readArtifact();
74
+ delete artifact.tools;
75
+ expect(validate(artifact)).toBe(false);
76
+ });
77
+ test("rejects a non-array block", () => {
78
+ const artifact = readArtifact();
79
+ artifact.voice_input.block = "not an array";
80
+ expect(validate(artifact)).toBe(false);
81
+ });
82
+ });
83
+ //# sourceMappingURL=neurotype-addenda.schema.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"neurotype-addenda.schema.test.js","sourceRoot":"","sources":["../src/neurotype-addenda.schema.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=neurotype-addenda.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"neurotype-addenda.test.d.ts","sourceRoot":"","sources":["../src/neurotype-addenda.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,165 @@
1
+ /*
2
+ * SPDX-License-Identifier: AGPL-3.0-or-later
3
+ * Copyright (c) 2026 NeuroDock contributors.
4
+ */
5
+ /**
6
+ * neurotype-addenda.test.ts — unit tests for the shared assembler.
7
+ *
8
+ * These exercise the assembly rules in isolation against the shipped v1
9
+ * artifact: fusion, priority ordering, per-tool vs generic fallback, token
10
+ * interpolation, voice-input gating, and the 3+ conflict footer. The
11
+ * byte-identical cross-surface guarantee is proven separately by the
12
+ * extension's golden-snapshot test.
13
+ */
14
+ import { describe, expect, test } from "vitest";
15
+ import { assembleNeurotypeAddendum, neurotypeAddendaV1, } from "./neurotype-addenda.js";
16
+ function assemble(overrides = {}) {
17
+ return assembleNeurotypeAddendum(neurotypeAddendaV1, {
18
+ neurotypes: [],
19
+ outputFormat: "answer_first",
20
+ maxChunkSize: 5,
21
+ additionalNotes: null,
22
+ ...overrides,
23
+ });
24
+ }
25
+ describe("assembleNeurotypeAddendum — empty gate", () => {
26
+ test("returns empty string for the all-default case", () => {
27
+ expect(assemble()).toBe("");
28
+ });
29
+ test("a non-default output_format alone triggers the addendum", () => {
30
+ expect(assemble({ outputFormat: "bullet_first" })).not.toBe("");
31
+ });
32
+ test("the voice-input flag alone triggers the addendum", () => {
33
+ expect(assemble({ voiceInputPreferred: true })).not.toBe("");
34
+ });
35
+ test("notes alone trigger the addendum", () => {
36
+ expect(assemble({ additionalNotes: "always quote the source" })).not.toBe("");
37
+ });
38
+ });
39
+ describe("assembleNeurotypeAddendum — fusion (AuDHD)", () => {
40
+ test("adhd + asd fuse to AuDHD (not concatenated)", () => {
41
+ const out = assemble({ neurotypes: ["adhd", "asd"] });
42
+ expect(out).toContain("Reader preferences (AuDHD)");
43
+ expect(out).not.toContain("Reader preferences (ADHD)");
44
+ expect(out).not.toContain("Reader preferences (autism)");
45
+ });
46
+ test("audhd declared directly uses the AuDHD block", () => {
47
+ const out = assemble({ neurotypes: ["audhd"] });
48
+ expect(out).toContain("Reader preferences (AuDHD)");
49
+ });
50
+ test("tourette + adhd + asd: AuDHD fuses, tourette stays, no raw ADHD", () => {
51
+ const out = assemble({ neurotypes: ["tourette", "adhd", "asd"] });
52
+ expect(out).toContain("Reader preferences (Tourette)");
53
+ expect(out).toContain("Reader preferences (AuDHD)");
54
+ expect(out).not.toContain("Reader preferences (ADHD):");
55
+ });
56
+ });
57
+ describe("assembleNeurotypeAddendum — priority ordering", () => {
58
+ test("dyslexia is placed before ADHD (priority/recency)", () => {
59
+ const out = assemble({ neurotypes: ["adhd", "dyslexia"] });
60
+ const dyslexiaIdx = out.indexOf("Reader preferences (dyslexia)");
61
+ const adhdIdx = out.indexOf("Reader preferences (ADHD)");
62
+ expect(dyslexiaIdx).toBeGreaterThan(-1);
63
+ expect(adhdIdx).toBeGreaterThan(-1);
64
+ expect(dyslexiaIdx).toBeLessThan(adhdIdx);
65
+ });
66
+ test("other is always last", () => {
67
+ const out = assemble({
68
+ neurotypes: ["adhd", "other"],
69
+ additionalNotes: "Always start with 'Heads up:'",
70
+ });
71
+ const adhdIdx = out.indexOf("Reader preferences (ADHD)");
72
+ const otherIdx = out.indexOf("Reader preferences (self-described)");
73
+ expect(adhdIdx).toBeLessThan(otherIdx);
74
+ });
75
+ });
76
+ describe("assembleNeurotypeAddendum — per-tool vs generic fallback", () => {
77
+ test("with a tool, the concrete per-tool block is used", () => {
78
+ const out = assemble({
79
+ neurotypes: ["adhd"],
80
+ tool: "translate_incoming",
81
+ });
82
+ expect(out).toContain("Reader preferences (ADHD) — translate_incoming:");
83
+ });
84
+ test("without a tool, the generic fallback block is used", () => {
85
+ const out = assemble({ neurotypes: ["adhd"] });
86
+ expect(out).toContain("Reader preferences (ADHD):");
87
+ expect(out).toContain("first phrase");
88
+ });
89
+ test("an unknown tool falls back to the generic block", () => {
90
+ const out = assemble({ neurotypes: ["adhd"], tool: "no_such_tool" });
91
+ expect(out).toContain("Reader preferences (ADHD):");
92
+ });
93
+ });
94
+ describe("assembleNeurotypeAddendum — token interpolation", () => {
95
+ test("max_chunk_size is interpolated into the generic block", () => {
96
+ const out = assemble({ neurotypes: ["adhd"], maxChunkSize: 3 });
97
+ expect(out).toContain("Cap any list you return at 3 items");
98
+ expect(out).not.toContain("{max_chunk_size}");
99
+ });
100
+ test("max_chunk_size is interpolated into a per-tool block", () => {
101
+ const out = assemble({
102
+ neurotypes: ["adhd"],
103
+ tool: "translate_incoming",
104
+ maxChunkSize: 7,
105
+ });
106
+ expect(out).toContain("'likely_subtext': cap at 7 items");
107
+ expect(out).not.toContain("{max_chunk_size}");
108
+ });
109
+ test("notes are interpolated into the self-described block", () => {
110
+ const out = assemble({
111
+ neurotypes: ["other"],
112
+ additionalNotes: "Please always quote the source verbatim.",
113
+ });
114
+ expect(out).toContain("Reader preferences (self-described)");
115
+ expect(out).toContain("Please always quote the source verbatim.");
116
+ expect(out).not.toContain("{notes}");
117
+ });
118
+ test("notes are appended as a footer when other is NOT selected", () => {
119
+ const out = assemble({
120
+ neurotypes: ["adhd"],
121
+ additionalNotes: "Use kelvin not celsius for temperatures.",
122
+ });
123
+ expect(out).toContain("Reader preferences (ADHD)");
124
+ expect(out).toContain("Use kelvin not celsius for temperatures.");
125
+ });
126
+ });
127
+ describe("assembleNeurotypeAddendum — voice-input gating", () => {
128
+ test("emits the single-block instruction when voice input is preferred", () => {
129
+ const out = assemble({
130
+ outputFormat: "bullet_first",
131
+ voiceInputPreferred: true,
132
+ });
133
+ expect(out).toContain("single, copy-pasteable block");
134
+ });
135
+ test("does NOT emit the voice-input line when the flag is false", () => {
136
+ const out = assemble({ neurotypes: ["adhd"], voiceInputPreferred: false });
137
+ expect(out).toContain("Reader preferences (ADHD)");
138
+ expect(out).not.toContain("copy-pasteable block");
139
+ });
140
+ test("does NOT emit the voice-input line when the flag is unset", () => {
141
+ const out = assemble({ neurotypes: ["adhd"] });
142
+ expect(out).not.toContain("copy-pasteable block");
143
+ });
144
+ });
145
+ describe("assembleNeurotypeAddendum — conflict footer", () => {
146
+ test("appends the conflict footer when 3+ neurotypes are active", () => {
147
+ const out = assemble({ neurotypes: ["adhd", "dyslexia", "ocd"] });
148
+ expect(out).toContain("prefer the more conservative reading: shorter, more concrete");
149
+ });
150
+ test("does NOT append the conflict footer for fewer than 3", () => {
151
+ const out = assemble({ neurotypes: ["adhd", "dyslexia"] });
152
+ expect(out).not.toContain("prefer the more conservative reading");
153
+ });
154
+ });
155
+ describe("assembleNeurotypeAddendum — purity", () => {
156
+ test("does not mutate the input neurotypes array", () => {
157
+ const neurotypes = ["adhd", "asd"];
158
+ assembleNeurotypeAddendum(neurotypeAddendaV1, {
159
+ neurotypes,
160
+ maxChunkSize: 5,
161
+ });
162
+ expect(neurotypes).toEqual(["adhd", "asd"]);
163
+ });
164
+ });
165
+ //# sourceMappingURL=neurotype-addenda.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"neurotype-addenda.test.js","sourceRoot":"","sources":["../src/neurotype-addenda.test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH;;;;;;;;GAQG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,EACL,yBAAyB,EACzB,kBAAkB,GAEnB,MAAM,wBAAwB,CAAC;AAEhC,SAAS,QAAQ,CACf,YAAuD,EAAE;IAEzD,OAAO,yBAAyB,CAAC,kBAAkB,EAAE;QACnD,UAAU,EAAE,EAAE;QACd,YAAY,EAAE,cAAc;QAC5B,YAAY,EAAE,CAAC;QACf,eAAe,EAAE,IAAI;QACrB,GAAG,SAAS;KACb,CAAC,CAAC;AACL,CAAC;AAED,QAAQ,CAAC,wCAAwC,EAAE,GAAG,EAAE;IACtD,IAAI,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACnE,MAAM,CAAC,QAAQ,CAAC,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC5D,MAAM,CAAC,QAAQ,CAAC,EAAE,mBAAmB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,QAAQ,CAAC,EAAE,eAAe,EAAE,yBAAyB,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CACvE,EAAE,CACH,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,4CAA4C,EAAE,GAAG,EAAE;IAC1D,IAAI,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACvD,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;QACtD,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,4BAA4B,CAAC,CAAC;QACpD,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;QACvD,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,6BAA6B,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACxD,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAChD,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,4BAA4B,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,iEAAiE,EAAE,GAAG,EAAE;QAC3E,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;QAClE,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,+BAA+B,CAAC,CAAC;QACvD,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,4BAA4B,CAAC,CAAC;QACpD,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,4BAA4B,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,+CAA+C,EAAE,GAAG,EAAE;IAC7D,IAAI,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC7D,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC;QAC3D,MAAM,WAAW,GAAG,GAAG,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC;QACjE,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;QACzD,MAAM,CAAC,WAAW,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;QACxC,MAAM,CAAC,OAAO,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;QACpC,MAAM,CAAC,WAAW,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAChC,MAAM,GAAG,GAAG,QAAQ,CAAC;YACnB,UAAU,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC;YAC7B,eAAe,EAAE,+BAA+B;SACjD,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;QACzD,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,qCAAqC,CAAC,CAAC;QACpE,MAAM,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,0DAA0D,EAAE,GAAG,EAAE;IACxE,IAAI,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC5D,MAAM,GAAG,GAAG,QAAQ,CAAC;YACnB,UAAU,EAAE,CAAC,MAAM,CAAC;YACpB,IAAI,EAAE,oBAAoB;SAC3B,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,iDAAiD,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,oDAAoD,EAAE,GAAG,EAAE;QAC9D,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC/C,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,4BAA4B,CAAC,CAAC;QACpD,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,iDAAiD,EAAE,GAAG,EAAE;QAC3D,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC;QACrE,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,4BAA4B,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,iDAAiD,EAAE,GAAG,EAAE;IAC/D,IAAI,CAAC,uDAAuD,EAAE,GAAG,EAAE;QACjE,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC;QAChE,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,oCAAoC,CAAC,CAAC;QAC5D,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAChE,MAAM,GAAG,GAAG,QAAQ,CAAC;YACnB,UAAU,EAAE,CAAC,MAAM,CAAC;YACpB,IAAI,EAAE,oBAAoB;YAC1B,YAAY,EAAE,CAAC;SAChB,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,kCAAkC,CAAC,CAAC;QAC1D,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAChE,MAAM,GAAG,GAAG,QAAQ,CAAC;YACnB,UAAU,EAAE,CAAC,OAAO,CAAC;YACrB,eAAe,EAAE,0CAA0C;SAC5D,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,qCAAqC,CAAC,CAAC;QAC7D,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,0CAA0C,CAAC,CAAC;QAClE,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACrE,MAAM,GAAG,GAAG,QAAQ,CAAC;YACnB,UAAU,EAAE,CAAC,MAAM,CAAC;YACpB,eAAe,EAAE,0CAA0C;SAC5D,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;QACnD,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,0CAA0C,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,gDAAgD,EAAE,GAAG,EAAE;IAC9D,IAAI,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC5E,MAAM,GAAG,GAAG,QAAQ,CAAC;YACnB,YAAY,EAAE,cAAc;YAC5B,mBAAmB,EAAE,IAAI;SAC1B,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,8BAA8B,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACrE,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,mBAAmB,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3E,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,2BAA2B,CAAC,CAAC;QACnD,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACrE,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC/C,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,6CAA6C,EAAE,GAAG,EAAE;IAC3D,IAAI,CAAC,2DAA2D,EAAE,GAAG,EAAE;QACrE,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;QAClE,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CACnB,8DAA8D,CAC/D,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,sDAAsD,EAAE,GAAG,EAAE;QAChE,MAAM,GAAG,GAAG,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC;QAC3D,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,sCAAsC,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oCAAoC,EAAE,GAAG,EAAE;IAClD,IAAI,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACtD,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QACnC,yBAAyB,CAAC,kBAAkB,EAAE;YAC5C,UAAU;YACV,YAAY,EAAE,CAAC;SAChB,CAAC,CAAC;QACH,MAAM,CAAC,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}