@aikdna/kdna-core 0.3.0 → 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.
package/README.md CHANGED
@@ -44,6 +44,36 @@ Returns `{ errors: string[], warnings: string[] }`.
44
44
  ### `renderDomain(dataMap, options?)`
45
45
  Renders domain files into a structured context block using a standard template. The rendered context preserves the domain's structure as distinct, named sections suitable for agent system prompts.
46
46
 
47
+ ## Compose API (9 functions)
48
+
49
+ Multi-domain composition — load multiple KDNA domains, classify which should activate for a given input, detect conflicts, and merge their judgment into a single agent context.
50
+
51
+ ### Context Composition
52
+
53
+ - **`composeContext(domains, options?)`** — Merge multiple loaded domains into a single context string. Conflicting axioms or banned terms from different domains are both included; the agent must report the conflict rather than silently resolve it.
54
+
55
+ - **`composeContextWithAttribution(domains, options?)`** — Same as `composeContext`, but every axiom, misunderstanding, banned term, and self-check is prefixed with its origin domain (e.g., `[writing:axiom.axiom_problem_not_prose]`). Returns `{ context, attributionMap }`.
56
+
57
+ - **`loadAndCompose(dataMaps, options?)`** — Convenience function: loads each domain from file data maps, classifies signals against input, then composes the active domains. Returns `{ domains, context, activeIndices }`.
58
+
59
+ ### Signal Classification
60
+
61
+ - **`classifySignals(input, domains)`** — Match user input against each domain's `trigger_signals`. Returns indices of matching domains. Domains with no signals defined are treated as primary (always active).
62
+
63
+ - **`classifySignalsAcrossDomains(input, domainEntries)`** — Full diagnostic version of signal classification. Returns `{ selected, excluded }` with reasons (`signal_match`, `required`, `blocked by does_not_apply_when`, `no signal match`).
64
+
65
+ ### Cluster Operations
66
+
67
+ - **`loadCluster(clusterManifestPath, domainLoader)`** — Load a cluster manifest (`kdna.cluster.json`) and resolve each domain via the provided loader function. Returns `{ manifest, domains, errors }`.
68
+
69
+ - **`detectDomainConflicts(domains)`** — Detect conflicts between loaded domains in a cluster. Currently checks for: (1) banned term collisions across domains, (2) contradictory stances (simple negation heuristic). Returns array of conflict objects with `type`, `domains`, and `description`.
70
+
71
+ - **`generateClusterTrace({ input, loadedDomains, activeDomains, conflicts })`** — Generate a judgment trace record for a cluster operation. Returns `{ input, timestamp, loaded_domains, active_domains, active_count, domains_excluded, conflicts }`.
72
+
73
+ ### Utilities
74
+
75
+ - **`composeChecks(domains)`** — Merge self-check items from multiple domains into a single checklist. Each item is prefixed with its domain name so overlaps are visible.
76
+
47
77
  ## License
48
78
 
49
79
  Apache-2.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikdna/kdna-core",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "KDNA core library — pure logic for loading, validating, linting, and rendering KDNA domain cognition packages. Zero Node.js dependencies.",
5
5
  "type": "commonjs",
6
6
  "main": "src/index.js",
@@ -15,6 +15,9 @@
15
15
  "./schema/*": "./schema/*"
16
16
  },
17
17
  "types": "src/types.d.ts",
18
+ "scripts": {
19
+ "test": "node -e \"const m = require('./src/index.js'); console.log('kdna-core exports:', Object.keys(m).join(', '));\""
20
+ },
18
21
  "files": [
19
22
  "src/",
20
23
  "schema/",
@@ -24,13 +27,12 @@
24
27
  "kdna",
25
28
  "kdna-core",
26
29
  "ai-agent",
27
- "domain-cognition",
28
- "knowledge-dna"
30
+ "domain-cognition"
29
31
  ],
30
32
  "license": "Apache-2.0",
31
33
  "repository": {
32
34
  "type": "git",
33
- "url": "git+https://github.com/knowledge-dna/KDNA.git",
35
+ "url": "git+https://github.com/aikdna/KDNA.git",
34
36
  "directory": "packages/kdna-core"
35
37
  },
36
38
  "homepage": "https://aikdna.com",
@@ -58,7 +58,10 @@
58
58
  "type": "string"
59
59
  },
60
60
  "does_not_act_as": {
61
- "type": "string"
61
+ "oneOf": [
62
+ { "type": "string" },
63
+ { "type": "array", "items": { "type": "string" } }
64
+ ]
62
65
  },
63
66
  "responsibility": {
64
67
  "type": "string"
@@ -0,0 +1,290 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "KDNA_Core",
4
+ "type": "object",
5
+ "required": [
6
+ "meta",
7
+ "axioms",
8
+ "ontology",
9
+ "frameworks",
10
+ "core_structure",
11
+ "stances"
12
+ ],
13
+ "properties": {
14
+ "meta": {
15
+ "type": "object",
16
+ "required": [
17
+ "version",
18
+ "domain",
19
+ "created",
20
+ "purpose",
21
+ "load_condition"
22
+ ],
23
+ "properties": {
24
+ "version": {
25
+ "type": "string"
26
+ },
27
+ "domain": {
28
+ "type": "string",
29
+ "pattern": "^[a-z][a-z0-9_]*$"
30
+ },
31
+ "created": {
32
+ "type": "string"
33
+ },
34
+ "purpose": {
35
+ "type": "string"
36
+ },
37
+ "load_condition": {
38
+ "type": "string"
39
+ }
40
+ },
41
+ "additionalProperties": true
42
+ },
43
+ "highest_question": {
44
+ "type": "string",
45
+ "description": "The highest-order question this domain answers."
46
+ },
47
+ "worldview": {
48
+ "type": "array",
49
+ "items": {
50
+ "type": "string"
51
+ },
52
+ "description": "Default assumptions about how the world works in this domain."
53
+ },
54
+ "judgment_role": {
55
+ "type": "object",
56
+ "properties": {
57
+ "acts_as": {
58
+ "type": "string"
59
+ },
60
+ "does_not_act_as": {
61
+ "oneOf": [
62
+ { "type": "string" },
63
+ { "type": "array", "items": { "type": "string" } }
64
+ ]
65
+ },
66
+ "responsibility": {
67
+ "type": "string"
68
+ }
69
+ },
70
+ "additionalProperties": true
71
+ },
72
+ "value_order": {
73
+ "type": "array",
74
+ "items": {
75
+ "type": "string"
76
+ },
77
+ "description": "Priority ordering of values in this domain."
78
+ },
79
+ "axioms": {
80
+ "type": "array",
81
+ "items": {
82
+ "type": "object",
83
+ "required": [
84
+ "id",
85
+ "one_sentence",
86
+ "full_statement",
87
+ "why",
88
+ "applies_when",
89
+ "does_not_apply_when",
90
+ "failure_risk",
91
+ "confidence"
92
+ ],
93
+ "properties": {
94
+ "id": {
95
+ "type": "string"
96
+ },
97
+ "one_sentence": {
98
+ "type": "string"
99
+ },
100
+ "full_statement": {
101
+ "type": "string"
102
+ },
103
+ "why": {
104
+ "type": "string"
105
+ },
106
+ "confidence": {
107
+ "type": "string"
108
+ },
109
+ "evidence_type": {
110
+ "type": "array",
111
+ "items": {
112
+ "type": "string"
113
+ }
114
+ },
115
+ "applies_when": {
116
+ "type": "array",
117
+ "items": {
118
+ "type": "string"
119
+ }
120
+ },
121
+ "does_not_apply_when": {
122
+ "type": "array",
123
+ "items": {
124
+ "type": "string"
125
+ }
126
+ },
127
+ "failure_risk": {
128
+ "type": "string"
129
+ }
130
+ },
131
+ "additionalProperties": true
132
+ }
133
+ },
134
+ "ontology": {
135
+ "type": "array",
136
+ "items": {
137
+ "type": "object",
138
+ "required": [
139
+ "id",
140
+ "one_sentence",
141
+ "essence",
142
+ "boundary",
143
+ "trigger_signal"
144
+ ],
145
+ "properties": {
146
+ "id": {
147
+ "type": "string"
148
+ },
149
+ "one_sentence": {
150
+ "type": "string"
151
+ },
152
+ "essence": {
153
+ "type": "string"
154
+ },
155
+ "boundary": {
156
+ "type": "string"
157
+ },
158
+ "trigger_signal": {
159
+ "type": "string"
160
+ },
161
+ "applies_when": {
162
+ "type": "array",
163
+ "items": {
164
+ "type": "string"
165
+ }
166
+ },
167
+ "does_not_apply_when": {
168
+ "type": "array",
169
+ "items": {
170
+ "type": "string"
171
+ }
172
+ }
173
+ },
174
+ "additionalProperties": true
175
+ }
176
+ },
177
+ "frameworks": {
178
+ "type": "array",
179
+ "items": {
180
+ "type": "object",
181
+ "required": [
182
+ "id",
183
+ "name",
184
+ "when_to_use",
185
+ "steps"
186
+ ],
187
+ "properties": {
188
+ "id": {
189
+ "type": "string"
190
+ },
191
+ "name": {
192
+ "type": "string"
193
+ },
194
+ "when_to_use": {
195
+ "type": "string"
196
+ },
197
+ "steps": {
198
+ "type": "array",
199
+ "items": {
200
+ "type": "string"
201
+ }
202
+ },
203
+ "applies_when": {
204
+ "type": "array",
205
+ "items": {
206
+ "type": "string"
207
+ }
208
+ },
209
+ "does_not_apply_when": {
210
+ "type": "array",
211
+ "items": {
212
+ "type": "string"
213
+ }
214
+ }
215
+ },
216
+ "additionalProperties": true
217
+ }
218
+ },
219
+ "core_structure": {
220
+ "type": "array",
221
+ "items": {
222
+ "type": "object",
223
+ "required": [
224
+ "from",
225
+ "to",
226
+ "via"
227
+ ],
228
+ "properties": {
229
+ "from": {
230
+ "type": "string"
231
+ },
232
+ "to": {
233
+ "type": "string"
234
+ },
235
+ "via": {
236
+ "type": "string"
237
+ },
238
+ "applies_when": {
239
+ "type": "array",
240
+ "items": {
241
+ "type": "string"
242
+ }
243
+ },
244
+ "does_not_apply_when": {
245
+ "type": "array",
246
+ "items": {
247
+ "type": "string"
248
+ }
249
+ }
250
+ },
251
+ "additionalProperties": true
252
+ }
253
+ },
254
+ "stances": {
255
+ "type": "array",
256
+ "items": {
257
+ "anyOf": [
258
+ {
259
+ "type": "string"
260
+ },
261
+ {
262
+ "type": "object",
263
+ "required": [
264
+ "stance"
265
+ ],
266
+ "properties": {
267
+ "stance": {
268
+ "type": "string"
269
+ },
270
+ "applies_when": {
271
+ "type": "array",
272
+ "items": {
273
+ "type": "string"
274
+ }
275
+ },
276
+ "does_not_apply_when": {
277
+ "type": "array",
278
+ "items": {
279
+ "type": "string"
280
+ }
281
+ }
282
+ },
283
+ "additionalProperties": true
284
+ }
285
+ ]
286
+ }
287
+ }
288
+ },
289
+ "additionalProperties": true
290
+ }
@@ -158,10 +158,12 @@
158
158
  "type": "string"
159
159
  },
160
160
  "signals_good": {
161
- "type": "string"
161
+ "type": "array",
162
+ "items": { "type": "string" }
162
163
  },
163
164
  "signals_bad": {
164
- "type": "string"
165
+ "type": "array",
166
+ "items": { "type": "string" }
165
167
  }
166
168
  },
167
169
  "additionalProperties": true
@@ -183,10 +185,12 @@
183
185
  "type": "string"
184
186
  },
185
187
  "must_not_do": {
186
- "type": "string"
188
+ "type": "array",
189
+ "items": { "type": "string" }
187
190
  },
188
191
  "acceptable_exception": {
189
- "type": "string"
192
+ "type": "array",
193
+ "items": { "type": "string" }
190
194
  }
191
195
  },
192
196
  "additionalProperties": true
@@ -208,10 +212,12 @@
208
212
  }
209
213
  },
210
214
  "must_block_when": {
211
- "type": "string"
215
+ "type": "array",
216
+ "items": { "type": "string" }
212
217
  },
213
218
  "warn_when": {
214
- "type": "string"
219
+ "type": "array",
220
+ "items": { "type": "string" }
215
221
  }
216
222
  },
217
223
  "additionalProperties": true
@@ -0,0 +1,342 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "KDNA_Patterns",
4
+ "type": "object",
5
+ "required": [
6
+ "meta",
7
+ "terminology",
8
+ "misunderstandings",
9
+ "self_check"
10
+ ],
11
+ "properties": {
12
+ "meta": {
13
+ "type": "object",
14
+ "required": [
15
+ "version",
16
+ "domain",
17
+ "created",
18
+ "purpose",
19
+ "load_condition"
20
+ ],
21
+ "properties": {
22
+ "version": {
23
+ "type": "string"
24
+ },
25
+ "domain": {
26
+ "type": "string",
27
+ "pattern": "^[a-z][a-z0-9_]*$"
28
+ },
29
+ "created": {
30
+ "type": "string"
31
+ },
32
+ "purpose": {
33
+ "type": "string"
34
+ },
35
+ "load_condition": {
36
+ "type": "string"
37
+ }
38
+ },
39
+ "additionalProperties": true
40
+ },
41
+ "terminology": {
42
+ "type": "object",
43
+ "minProperties": 1,
44
+ "properties": {
45
+ "standard_terms": {
46
+ "type": "array",
47
+ "items": {
48
+ "type": "object",
49
+ "required": [
50
+ "term",
51
+ "definition"
52
+ ],
53
+ "properties": {
54
+ "term": {
55
+ "type": "string"
56
+ },
57
+ "definition": {
58
+ "type": "string"
59
+ },
60
+ "applies_when": {
61
+ "type": "array",
62
+ "items": {
63
+ "type": "string"
64
+ }
65
+ },
66
+ "does_not_apply_when": {
67
+ "type": "array",
68
+ "items": {
69
+ "type": "string"
70
+ }
71
+ }
72
+ },
73
+ "additionalProperties": true
74
+ }
75
+ },
76
+ "preferred_terms": {
77
+ "type": "array",
78
+ "items": {
79
+ "type": "object",
80
+ "required": [
81
+ "term",
82
+ "definition"
83
+ ],
84
+ "properties": {
85
+ "term": {
86
+ "type": "string"
87
+ },
88
+ "definition": {
89
+ "type": "string"
90
+ },
91
+ "applies_when": {
92
+ "type": "array",
93
+ "items": {
94
+ "type": "string"
95
+ }
96
+ },
97
+ "does_not_apply_when": {
98
+ "type": "array",
99
+ "items": {
100
+ "type": "string"
101
+ }
102
+ }
103
+ },
104
+ "additionalProperties": true
105
+ }
106
+ },
107
+ "banned_terms": {
108
+ "type": "array",
109
+ "items": {
110
+ "type": "object",
111
+ "required": [
112
+ "term",
113
+ "why",
114
+ "replace_with"
115
+ ],
116
+ "properties": {
117
+ "term": {
118
+ "type": "string"
119
+ },
120
+ "why": {
121
+ "type": "string"
122
+ },
123
+ "replace_with": {
124
+ "type": "string"
125
+ },
126
+ "applies_when": {
127
+ "type": "array",
128
+ "items": {
129
+ "type": "string"
130
+ }
131
+ },
132
+ "does_not_apply_when": {
133
+ "type": "array",
134
+ "items": {
135
+ "type": "string"
136
+ }
137
+ }
138
+ },
139
+ "additionalProperties": true
140
+ }
141
+ }
142
+ },
143
+ "additionalProperties": true
144
+ },
145
+ "aesthetic_preferences": {
146
+ "type": "array",
147
+ "items": {
148
+ "type": "object",
149
+ "required": [
150
+ "prefer",
151
+ "avoid"
152
+ ],
153
+ "properties": {
154
+ "prefer": {
155
+ "type": "string"
156
+ },
157
+ "avoid": {
158
+ "type": "string"
159
+ },
160
+ "signals_good": {
161
+ "type": "array",
162
+ "items": { "type": "string" }
163
+ },
164
+ "signals_bad": {
165
+ "type": "array",
166
+ "items": { "type": "string" }
167
+ }
168
+ },
169
+ "additionalProperties": true
170
+ }
171
+ },
172
+ "boundaries": {
173
+ "type": "array",
174
+ "items": {
175
+ "type": "object",
176
+ "required": [
177
+ "rule",
178
+ "why"
179
+ ],
180
+ "properties": {
181
+ "rule": {
182
+ "type": "string"
183
+ },
184
+ "why": {
185
+ "type": "string"
186
+ },
187
+ "must_not_do": {
188
+ "type": "array",
189
+ "items": { "type": "string" }
190
+ },
191
+ "acceptable_exception": {
192
+ "type": "array",
193
+ "items": { "type": "string" }
194
+ }
195
+ },
196
+ "additionalProperties": true
197
+ }
198
+ },
199
+ "risk_model": {
200
+ "type": "object",
201
+ "properties": {
202
+ "highest_risk_errors": {
203
+ "type": "array",
204
+ "items": {
205
+ "type": "string"
206
+ }
207
+ },
208
+ "acceptable_errors": {
209
+ "type": "array",
210
+ "items": {
211
+ "type": "string"
212
+ }
213
+ },
214
+ "must_block_when": {
215
+ "type": "array",
216
+ "items": { "type": "string" }
217
+ },
218
+ "warn_when": {
219
+ "type": "array",
220
+ "items": { "type": "string" }
221
+ }
222
+ },
223
+ "additionalProperties": true
224
+ },
225
+ "misunderstandings": {
226
+ "type": "array",
227
+ "items": {
228
+ "type": "object",
229
+ "required": [
230
+ "id",
231
+ "wrong",
232
+ "correct",
233
+ "key_distinction",
234
+ "why",
235
+ "applies_when",
236
+ "does_not_apply_when",
237
+ "failure_risk",
238
+ "confidence"
239
+ ],
240
+ "properties": {
241
+ "id": {
242
+ "type": "string"
243
+ },
244
+ "wrong": {
245
+ "type": "string"
246
+ },
247
+ "correct": {
248
+ "type": "string"
249
+ },
250
+ "key_distinction": {
251
+ "type": "string"
252
+ },
253
+ "why": {
254
+ "type": "string"
255
+ },
256
+ "confidence": {
257
+ "type": "string"
258
+ },
259
+ "evidence_type": {
260
+ "type": "array",
261
+ "items": {
262
+ "type": "string"
263
+ }
264
+ },
265
+ "applies_when": {
266
+ "type": "array",
267
+ "items": {
268
+ "type": "string"
269
+ }
270
+ },
271
+ "does_not_apply_when": {
272
+ "type": "array",
273
+ "items": {
274
+ "type": "string"
275
+ }
276
+ },
277
+ "failure_risk": {
278
+ "type": "string"
279
+ }
280
+ },
281
+ "additionalProperties": true
282
+ }
283
+ },
284
+ "self_check": {
285
+ "type": "array",
286
+ "items": {
287
+ "anyOf": [
288
+ {
289
+ "type": "string"
290
+ },
291
+ {
292
+ "type": "object",
293
+ "required": [
294
+ "question"
295
+ ],
296
+ "properties": {
297
+ "question": {
298
+ "type": "string"
299
+ },
300
+ "applies_when": {
301
+ "type": "array",
302
+ "items": {
303
+ "type": "string"
304
+ }
305
+ }
306
+ },
307
+ "additionalProperties": true
308
+ }
309
+ ]
310
+ }
311
+ },
312
+ "counterexamples": {
313
+ "type": "array",
314
+ "items": {
315
+ "type": "object",
316
+ "required": [
317
+ "bad_example",
318
+ "why_bad"
319
+ ],
320
+ "properties": {
321
+ "bad_example": {
322
+ "type": "string"
323
+ },
324
+ "why_bad": {
325
+ "type": "string"
326
+ },
327
+ "violated_axioms": {
328
+ "type": "array",
329
+ "items": {
330
+ "type": "string"
331
+ }
332
+ },
333
+ "better_direction": {
334
+ "type": "string"
335
+ }
336
+ },
337
+ "additionalProperties": true
338
+ }
339
+ }
340
+ },
341
+ "additionalProperties": true
342
+ }
@@ -32,6 +32,7 @@
32
32
  }
33
33
  },
34
34
  "scenes": {
35
+ "description": "Deprecated since v1.0-rc. Use scenarios (canonical) instead. Kept for backward compatibility.",
35
36
  "type": "array",
36
37
  "items": {
37
38
  "type": "object",
@@ -41,7 +42,7 @@
41
42
  "name": { "type": "string" },
42
43
  "trigger_signal": {
43
44
  "type": "string",
44
- "description": "Deprecated: use trigger_signals (array) instead. Kept for backward compatibility."
45
+ "description": "Deprecated since v1.0-rc. Use trigger_signals (array) instead. Kept for backward compatibility."
45
46
  },
46
47
  "trigger_signals": {
47
48
  "type": "array",
@@ -0,0 +1,101 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "title": "KDNA_Scenarios (Strict)",
4
+ "type": "object",
5
+ "required": ["meta", "scenarios"],
6
+ "properties": {
7
+ "meta": {
8
+ "type": "object",
9
+ "required": [
10
+ "version",
11
+ "domain",
12
+ "created",
13
+ "purpose",
14
+ "load_condition"
15
+ ],
16
+ "properties": {
17
+ "version": { "type": "string" },
18
+ "domain": {
19
+ "type": "string",
20
+ "pattern": "^[a-z][a-z0-9_]*$"
21
+ },
22
+ "created": { "type": "string" },
23
+ "purpose": { "type": "string" },
24
+ "load_condition": { "type": "string" }
25
+ },
26
+ "additionalProperties": true
27
+ },
28
+ "scenarios": {
29
+ "type": "array",
30
+ "items": {
31
+ "type": "object",
32
+ "required": ["id", "name", "trigger_signals"],
33
+ "properties": {
34
+ "id": { "type": "string" },
35
+ "name": { "type": "string" },
36
+ "trigger_signals": {
37
+ "type": "array",
38
+ "items": { "type": "string" },
39
+ "description": "Signals that trigger this scenario."
40
+ },
41
+ "negative_signals": {
42
+ "type": "array",
43
+ "items": { "type": "string" },
44
+ "description": "Signals that explicitly exclude this scenario."
45
+ },
46
+ "classification_rule": {
47
+ "type": "string",
48
+ "description": "Rule for classifying input into this scenario."
49
+ },
50
+ "risk_level": {
51
+ "type": "string",
52
+ "description": "Risk level associated with this scenario."
53
+ },
54
+ "expected_judgment_shift": {
55
+ "type": "string",
56
+ "description": "How judgment should shift when this scenario is detected."
57
+ },
58
+ "sub_scenarios": {
59
+ "type": "array",
60
+ "items": {
61
+ "type": "object",
62
+ "required": ["id", "trap_belief", "action_template", "expected_result"],
63
+ "properties": {
64
+ "id": { "type": "string" },
65
+ "trap_belief": { "type": "string" },
66
+ "three_questions": {
67
+ "type": "object",
68
+ "required": ["belief", "state", "need"],
69
+ "properties": {
70
+ "belief": { "type": "string" },
71
+ "state": { "type": "string" },
72
+ "need": { "type": "string" }
73
+ }
74
+ },
75
+ "action_template": {
76
+ "type": "array",
77
+ "items": { "type": "string" }
78
+ },
79
+ "replace": {
80
+ "type": "array",
81
+ "items": {
82
+ "type": "object",
83
+ "required": ["avoid", "use"],
84
+ "properties": {
85
+ "avoid": { "type": "string" },
86
+ "use": { "type": "string" }
87
+ }
88
+ }
89
+ },
90
+ "expected_result": { "type": "string" }
91
+ },
92
+ "additionalProperties": true
93
+ }
94
+ }
95
+ },
96
+ "additionalProperties": true
97
+ }
98
+ }
99
+ },
100
+ "additionalProperties": false
101
+ }
package/src/compose.js CHANGED
@@ -322,8 +322,8 @@ function detectDomainConflicts(domains) {
322
322
  for (const sa of sA) {
323
323
  for (const sb of sB) {
324
324
  // Simple negation check — can be extended
325
- if (sa && sb && (sa.includes('not') && sb.toLowerCase().includes(sa.toLowerCase().replace('not ', ''))) ||
326
- (sb.includes('not') && sa.toLowerCase().includes(sb.toLowerCase().replace('not ', '')))) {
325
+ if (sa && sb && (/\bnot\b/.test(sa) && sb.toLowerCase().includes(sa.toLowerCase().replace('not ', ''))) ||
326
+ (/\bnot\b/.test(sb) && sa.toLowerCase().includes(sb.toLowerCase().replace('not ', '')))) {
327
327
  conflicts.push({
328
328
  type: 'stance_conflict',
329
329
  domains: [domains[i].id, domains[j].id],
package/src/lint-pure.js CHANGED
@@ -29,6 +29,15 @@ const OLD_FIELD_HINTS = {
29
29
  judgment: 'via',
30
30
  };
31
31
 
32
+ const KDNA_DOMAIN_FILES = new Set([
33
+ 'KDNA_Core.json',
34
+ 'KDNA_Patterns.json',
35
+ 'KDNA_Scenarios.json',
36
+ 'KDNA_Cases.json',
37
+ 'KDNA_Reasoning.json',
38
+ 'KDNA_Evolution.json',
39
+ ]);
40
+
32
41
  /**
33
42
  * Lint a KDNA domain from a map of parsed JSON objects.
34
43
  *
@@ -104,9 +113,7 @@ function lintDomain(dataMap) {
104
113
  }
105
114
 
106
115
  // Check file count
107
- const kdnaFiles = Object.keys(dataMap).filter(
108
- (f) => f.endsWith('.json') && f !== 'kdna.json',
109
- );
116
+ const kdnaFiles = Object.keys(dataMap).filter((f) => KDNA_DOMAIN_FILES.has(f));
110
117
  if (kdnaFiles.length > 6) errors.push(`Domain has ${kdnaFiles.length} JSON files; KDNA allows at most 6.`);
111
118
 
112
119
  // Validate meta on all files
@@ -245,4 +252,152 @@ function lintDomain(dataMap) {
245
252
  return { errors, warnings };
246
253
  }
247
254
 
248
- module.exports = { lintDomain };
255
+ /**
256
+ * Canonical enum tables for manifest validation.
257
+ * Single source of truth — keep in sync with schema/kdna-manifest-v1rc.json and specs/enum-tables.md.
258
+ */
259
+ const VALID_STATUS = new Set(['draft', 'experimental', 'stable', 'deprecated', 'staging']);
260
+ const VALID_BADGE = new Set(['untested', 'tested', 'validated', 'expert_reviewed', 'production_ready']);
261
+ const VALID_ACCESS = new Set(['open', 'licensed', 'runtime']);
262
+ const VALID_RISK = new Set(['R0', 'R1', 'R2', 'R3']);
263
+ const VALID_I18N = new Set(['L0', 'L1', 'L2', 'L3']);
264
+
265
+ const MANIFEST_REQUIRED = [
266
+ 'kdna_spec', 'name', 'version', 'judgment_version',
267
+ 'description', 'author', 'license', 'status',
268
+ 'quality_badge', 'access', 'language',
269
+ ];
270
+
271
+ /**
272
+ * Validate a kdna.json manifest against the canonical v1.0-rc schema.
273
+ *
274
+ * @param {Object} manifest — parsed kdna.json
275
+ * @returns {{ errors: string[], warnings: string[] }}
276
+ */
277
+ function validateManifest(manifest) {
278
+ const errors = [];
279
+ const warnings = [];
280
+
281
+ if (!manifest || typeof manifest !== 'object') {
282
+ errors.push('kdna.json: missing or empty manifest');
283
+ return { errors, warnings };
284
+ }
285
+
286
+ // 1. Check spec_version is NOT in domain manifest (use kdna_spec only)
287
+ if ('spec_version' in manifest) {
288
+ errors.push(
289
+ 'kdna.json: spec_version is deprecated in domain manifests. Use kdna_spec. ' +
290
+ '(spec_version is reserved for .kdna container manifests only.)',
291
+ );
292
+ }
293
+
294
+ // 2. Check required fields
295
+ for (const field of MANIFEST_REQUIRED) {
296
+ if (!(field in manifest) || manifest[field] === undefined || manifest[field] === '') {
297
+ errors.push(`kdna.json: missing required field "${field}"`);
298
+ }
299
+ }
300
+
301
+ // 3. Validate name format
302
+ if (manifest.name && !/^@[a-z][a-z0-9-]*\/[a-z][a-z0-9_]*$/.test(manifest.name)) {
303
+ errors.push(`kdna.json.name: invalid format "${manifest.name}". Expected @scope/name.`);
304
+ }
305
+
306
+ // 4. Validate enum fields
307
+ if (manifest.status && !VALID_STATUS.has(manifest.status)) {
308
+ errors.push(
309
+ `kdna.json.status: invalid value "${manifest.status}". ` +
310
+ `Valid: ${[...VALID_STATUS].join(', ')}`,
311
+ );
312
+ }
313
+ if (manifest.quality_badge && !VALID_BADGE.has(manifest.quality_badge)) {
314
+ errors.push(
315
+ `kdna.json.quality_badge: invalid value "${manifest.quality_badge}". ` +
316
+ `Valid: ${[...VALID_BADGE].join(', ')}`,
317
+ );
318
+ }
319
+ if (manifest.access && !VALID_ACCESS.has(manifest.access)) {
320
+ errors.push(
321
+ `kdna.json.access: invalid value "${manifest.access}". ` +
322
+ `Valid: ${[...VALID_ACCESS].join(', ')}`,
323
+ );
324
+ }
325
+ if (manifest.risk_level && !VALID_RISK.has(manifest.risk_level)) {
326
+ errors.push(
327
+ `kdna.json.risk_level: invalid value "${manifest.risk_level}". ` +
328
+ `Valid: ${[...VALID_RISK].join(', ')}`,
329
+ );
330
+ }
331
+ if (manifest.i18n_level && !VALID_I18N.has(manifest.i18n_level)) {
332
+ warnings.push(
333
+ `kdna.json.i18n_level: non-standard value "${manifest.i18n_level}". ` +
334
+ `Valid: ${[...VALID_I18N].join(', ')}`,
335
+ );
336
+ }
337
+
338
+ // 5. Deprecated status must have replaced_by
339
+ if (manifest.status === 'deprecated' && !manifest.replaced_by) {
340
+ errors.push('kdna.json: status is "deprecated" but replaced_by is missing');
341
+ }
342
+
343
+ // 6. Tested+ badge must have signature
344
+ const needsSig = ['tested', 'validated', 'expert_reviewed', 'production_ready'];
345
+ if (needsSig.includes(manifest.quality_badge) && !manifest.signature) {
346
+ warnings.push(
347
+ `kdna.json: quality_badge "${manifest.quality_badge}" should have a signature`,
348
+ );
349
+ }
350
+
351
+ // 7. Validate author
352
+ if (manifest.author) {
353
+ if (!manifest.author.name) errors.push('kdna.json.author: missing "name"');
354
+ if (!manifest.author.id) errors.push('kdna.json.author: missing "id"');
355
+ if (manifest.author.pubkey && !/^ed25519:[0-9a-f]{64}$/.test(manifest.author.pubkey)) {
356
+ warnings.push('kdna.json.author.pubkey: non-standard format. Expected ed25519:<64 hex chars>.');
357
+ }
358
+ }
359
+
360
+ // 8. Validate license
361
+ if (manifest.license && !manifest.license.type) {
362
+ errors.push('kdna.json.license: missing "type"');
363
+ }
364
+
365
+ // 9. Validate kdna_spec value
366
+ if (manifest.kdna_spec && manifest.kdna_spec !== '1.0-rc') {
367
+ warnings.push(
368
+ `kdna.json.kdna_spec: non-standard value "${manifest.kdna_spec}". Expected "1.0-rc".`,
369
+ );
370
+ }
371
+
372
+ // 10. Validate version format
373
+ if (manifest.version && !/^\d+\.\d+\.\d+/.test(manifest.version)) {
374
+ warnings.push(
375
+ `kdna.json.version: non-semver format "${manifest.version}". Expected MAJOR.MINOR.PATCH.`,
376
+ );
377
+ }
378
+
379
+ // 11. Check for removed fields
380
+ const removedFields = ['release_status', 'domain_field', 'judgment_patterns', 'files', 'registry'];
381
+ for (const field of removedFields) {
382
+ if (field in manifest) {
383
+ warnings.push(
384
+ `kdna.json: field "${field}" is not in the canonical domain manifest and should be removed`,
385
+ );
386
+ }
387
+ }
388
+
389
+ // 12. Check removed license sub-fields
390
+ if (manifest.license && typeof manifest.license === 'object') {
391
+ for (const field of ['commercial', 'allow_agent_use', 'allow_redistribution', 'allow_training']) {
392
+ if (field in manifest.license) {
393
+ warnings.push(
394
+ `kdna.json.license.${field}: license-type-specific field, not universal. Consider removing.`,
395
+ );
396
+ }
397
+ }
398
+ }
399
+
400
+ return { errors, warnings };
401
+ }
402
+
403
+ module.exports = { lintDomain, validateManifest };
package/src/loader.js CHANGED
@@ -174,6 +174,16 @@ function detectOldFieldNames(obj, path = '', warnings = []) {
174
174
  * @param {object} domain — result from loadDomainFromData() or loadDomainFromFiles()
175
175
  * @returns {string}
176
176
  */
177
+ function sanitize(str) {
178
+ if (typeof str !== 'string') return str;
179
+ return str
180
+ .replace(/^#{1,6}\s/gm, '\\# ') // Escape leading # to prevent fake headers
181
+ .replace(/```/g, '\\`\\`\\`') // Escape code blocks
182
+ .replace(/<\|/g, '&lt;|') // Escape special tokens
183
+ .replace(/\b(ignore|forget|disregard)\s+(all\s+)?(previous|prior|above)\s+(instructions?|directives?|rules?|constraints?)\b/gi,
184
+ '[filtered: $&]'); // Filter prompt injection patterns
185
+ }
186
+
177
187
  function formatContext(domain) {
178
188
  if (!domain || !domain.core || !domain.patterns) return '';
179
189
 
@@ -193,13 +203,13 @@ function formatContext(domain) {
193
203
  }
194
204
 
195
205
  parts.push('## Domain Cognition (KDNA)');
196
- parts.push(`Domain: ${core.meta.domain}`);
206
+ parts.push(`Domain: ${sanitize(core.meta.domain)}`);
197
207
  parts.push('');
198
208
 
199
209
  if (core.stances && core.stances.length) {
200
210
  parts.push('### Stances');
201
211
  for (const s of core.stances) {
202
- parts.push(`- ${s}`);
212
+ parts.push(`- ${sanitize(s)}`);
203
213
  }
204
214
  parts.push('');
205
215
  }
@@ -207,8 +217,8 @@ function formatContext(domain) {
207
217
  if (core.axioms && core.axioms.length) {
208
218
  parts.push('### Axioms');
209
219
  for (const a of core.axioms) {
210
- parts.push(`- **${a.one_sentence}** ${a.full_statement}`);
211
- parts.push(` *Why:* ${a.why}`);
220
+ parts.push(`- **${sanitize(a.one_sentence)}** ${sanitize(a.full_statement)}`);
221
+ parts.push(` *Why:* ${sanitize(a.why)}`);
212
222
  }
213
223
  parts.push('');
214
224
  }
@@ -216,8 +226,8 @@ function formatContext(domain) {
216
226
  if (core.ontology && core.ontology.length) {
217
227
  parts.push('### Key Concepts');
218
228
  for (const c of core.ontology) {
219
- parts.push(`- **${c.id.replace(/_/g, ' ')}** — ${c.one_sentence}`);
220
- parts.push(` Boundary: ${c.boundary}`);
229
+ parts.push(`- **${sanitize(c.id.replace(/_/g, ' '))}** — ${sanitize(c.one_sentence)}`);
230
+ parts.push(` Boundary: ${sanitize(c.boundary)}`);
221
231
  }
222
232
  parts.push('');
223
233
  }
@@ -225,7 +235,7 @@ function formatContext(domain) {
225
235
  if (core.frameworks && core.frameworks.length) {
226
236
  parts.push('### Frameworks');
227
237
  for (const fw of core.frameworks) {
228
- parts.push(`- **${fw.name}**: ${fw.when_to_use}`);
238
+ parts.push(`- **${sanitize(fw.name)}**: ${sanitize(fw.when_to_use)}`);
229
239
  }
230
240
  parts.push('');
231
241
  }
@@ -233,7 +243,7 @@ function formatContext(domain) {
233
243
  if (pat.terminology && pat.terminology.banned_terms && pat.terminology.banned_terms.length) {
234
244
  parts.push('### Avoid These Terms');
235
245
  for (const b of pat.terminology.banned_terms) {
236
- parts.push(`- Avoid "${b.term}". ${b.why} Use "${b.replace_with}" instead.`);
246
+ parts.push(`- Avoid "${sanitize(b.term)}". ${sanitize(b.why)} Use "${sanitize(b.replace_with)}" instead.`);
237
247
  }
238
248
  parts.push('');
239
249
  }
@@ -241,8 +251,8 @@ function formatContext(domain) {
241
251
  if (pat.misunderstandings && pat.misunderstandings.length) {
242
252
  parts.push('### Watch For These Misunderstandings');
243
253
  for (const m of pat.misunderstandings) {
244
- parts.push(`- **Wrong:** ${m.wrong}`);
245
- parts.push(` **Correct:** ${m.correct}`);
254
+ parts.push(`- **Wrong:** ${sanitize(m.wrong)}`);
255
+ parts.push(` **Correct:** ${sanitize(m.correct)}`);
246
256
  }
247
257
  parts.push('');
248
258
  }
@@ -250,7 +260,7 @@ function formatContext(domain) {
250
260
  if (pat.self_check && pat.self_check.length) {
251
261
  parts.push('### Before Responding, Check');
252
262
  for (const s of pat.self_check) {
253
- parts.push(`- [ ] ${s}`);
263
+ parts.push(`- [ ] ${sanitize(s)}`);
254
264
  }
255
265
  parts.push('');
256
266
  }
@@ -258,7 +268,7 @@ function formatContext(domain) {
258
268
  if (domain.scenarios && domain.scenarios.scenes) {
259
269
  parts.push('### Relevant Scenarios');
260
270
  for (const scene of domain.scenarios.scenes) {
261
- parts.push(`- **${scene.name}**: ${scene.trigger_signal}`);
271
+ parts.push(`- **${sanitize(scene.name)}**: ${sanitize(scene.trigger_signal)}`);
262
272
  }
263
273
  parts.push('');
264
274
  }
@@ -266,7 +276,7 @@ function formatContext(domain) {
266
276
  if (domain.reasoning && domain.reasoning.reasoning_chains) {
267
277
  parts.push('### Reasoning Chains');
268
278
  for (const r of domain.reasoning.reasoning_chains) {
269
- parts.push(`- **${r.one_sentence}** → ${r.so_what}`);
279
+ parts.push(`- **${sanitize(r.one_sentence)}** → ${sanitize(r.so_what)}`);
270
280
  }
271
281
  parts.push('');
272
282
  }
@@ -274,11 +284,11 @@ function formatContext(domain) {
274
284
  if (domain.cases && domain.cases.cases && domain.cases.cases.length) {
275
285
  parts.push('### Cases');
276
286
  for (const c of domain.cases.cases) {
277
- parts.push(`- **${c.title}**`);
278
- parts.push(` Context: ${c.context}`);
279
- parts.push(` What happened: ${c.what_happened}`);
280
- parts.push(` Learned: ${c.what_was_learned}`);
281
- parts.push(` Pattern: ${c.structural_pattern}`);
287
+ parts.push(`- **${sanitize(c.title)}**`);
288
+ parts.push(` Context: ${sanitize(c.context)}`);
289
+ parts.push(` What happened: ${sanitize(c.what_happened)}`);
290
+ parts.push(` Learned: ${sanitize(c.what_was_learned)}`);
291
+ parts.push(` Pattern: ${sanitize(c.structural_pattern)}`);
282
292
  }
283
293
  parts.push('');
284
294
  }
@@ -288,7 +298,7 @@ function formatContext(domain) {
288
298
  if (evo.stages && evo.stages.length) {
289
299
  parts.push('### Growth Stages');
290
300
  for (const stage of evo.stages) {
291
- parts.push(`- **${stage.name}**: ${stage.description}`);
301
+ parts.push(`- **${sanitize(stage.name)}**: ${sanitize(stage.description)}`);
292
302
  }
293
303
  parts.push('');
294
304
  }
@@ -296,7 +306,7 @@ function formatContext(domain) {
296
306
  parts.push('### Capability Layers');
297
307
  for (const layer of evo.evolution_layers) {
298
308
  parts.push(
299
- `- **${layer.name}**: ${layer.capability} (${layer.from_stage} → ${layer.to_stage})`,
309
+ `- **${sanitize(layer.name)}**: ${sanitize(layer.capability)} (${sanitize(layer.from_stage)} → ${sanitize(layer.to_stage)})`,
300
310
  );
301
311
  }
302
312
  parts.push('');