@aikdna/kdna-core 0.1.0 → 0.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikdna/kdna-core",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
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",
package/src/compose.js ADDED
@@ -0,0 +1,123 @@
1
+ /**
2
+ * KDNA Compose — Multi-domain composition logic.
3
+ *
4
+ * Merges judgment constraints from multiple domains into a single
5
+ * agent context. Domains contribute independently; conflicts are
6
+ * surfaced, not silently resolved.
7
+ */
8
+
9
+ const { formatContext } = require('./loader');
10
+
11
+ /**
12
+ * Compose multiple loaded domains into a single agent context string.
13
+ *
14
+ * Each domain contributes its own section. If two domains define
15
+ * conflicting axioms or banned terms, both are included — the agent
16
+ * must report the conflict, not pick one.
17
+ *
18
+ * @param {Array<{core:object, patterns:object}>} domains
19
+ * @param {object} [options]
20
+ * @param {string} [options.separator] — section separator
21
+ * @returns {string}
22
+ */
23
+ function composeContext(domains, options = {}) {
24
+ if (!domains || !domains.length) return '';
25
+
26
+ const separator = options.separator || '\n\n---\n\n';
27
+
28
+ return domains
29
+ .filter((d) => d && d.core && d.patterns)
30
+ .map((d) => formatContext(d))
31
+ .filter((ctx) => ctx)
32
+ .join(separator);
33
+ }
34
+
35
+ /**
36
+ * Match user input against domain trigger_signals to determine
37
+ * which domains should be activated.
38
+ *
39
+ * Each domain can define trigger_signals in its core (array of
40
+ * keywords or phrases). This function checks if any signal matches
41
+ * the input and returns the list of matching domain indices.
42
+ *
43
+ * @param {string} input — user task description
44
+ * @param {Array<{id:string, core:{trigger_signals?:string[]}}>} domains
45
+ * @returns {number[]} — indices of matching domains
46
+ */
47
+ function classifySignals(input, domains) {
48
+ if (!input || !domains || !domains.length) return [];
49
+
50
+ const lower = input.toLowerCase();
51
+ const active = [];
52
+
53
+ for (let i = 0; i < domains.length; i++) {
54
+ const domain = domains[i];
55
+ const signals = domain.core?.trigger_signals || [];
56
+
57
+ if (!signals.length) {
58
+ // No signals defined → domain is primary (always active)
59
+ active.push(i);
60
+ continue;
61
+ }
62
+
63
+ const matched = signals.some((signal) => lower.includes(signal.toLowerCase()));
64
+ if (matched) active.push(i);
65
+ }
66
+
67
+ return active;
68
+ }
69
+
70
+ /**
71
+ * Compose self-check items from multiple domains into a single
72
+ * checklist. Each domain's checks are prefixed with its domain name
73
+ * so conflicts are visible.
74
+ *
75
+ * @param {Array<{id:string, core:{meta:{domain:string}}, patterns:{self_check:string[]}}>} domains
76
+ * @returns {string[]}
77
+ */
78
+ function composeChecks(domains) {
79
+ if (!domains || !domains.length) return [];
80
+
81
+ const checks = [];
82
+
83
+ for (const domain of domains) {
84
+ const name = domain.core?.meta?.domain || domain.id || 'unknown';
85
+ const items = domain.patterns?.self_check || [];
86
+
87
+ if (!items.length) continue;
88
+
89
+ if (domains.length === 1) {
90
+ checks.push(...items);
91
+ } else {
92
+ for (const item of items) {
93
+ checks.push(`[${name}] ${item}`);
94
+ }
95
+ }
96
+ }
97
+
98
+ return checks;
99
+ }
100
+
101
+ /**
102
+ * Load multiple domains from data maps and compose their context.
103
+ * Convenience function: loads each domain, then composes.
104
+ *
105
+ * @param {Array<object>} dataMaps — array of file data maps
106
+ * @param {object} [options] — passed to loadDomainFromFiles + composeContext
107
+ * @returns {{ domains: Array, context: string, activeIndices: number[] }}
108
+ */
109
+ function loadAndCompose(dataMaps, options = {}) {
110
+ const { loadDomainFromFiles } = require('./loader');
111
+
112
+ const domains = dataMaps.map((dm) => loadDomainFromFiles(dm, options)).filter(Boolean);
113
+
114
+ const { input = '' } = options;
115
+ const activeIndices = classifySignals(input, domains);
116
+
117
+ const activeDomains = activeIndices.map((i) => domains[i]);
118
+ const context = composeContext(activeDomains, options);
119
+
120
+ return { domains, context, activeIndices };
121
+ }
122
+
123
+ module.exports = { composeContext, classifySignals, composeChecks, loadAndCompose };
package/src/index.js CHANGED
@@ -5,10 +5,12 @@ const loader = require('./loader');
5
5
  const lint = require('./lint-pure');
6
6
  const validate = require('./validate-pure');
7
7
  const render = require('./render');
8
+ const compose = require('./compose');
8
9
 
9
10
  module.exports = {
10
11
  ...loader,
11
12
  ...lint,
12
13
  ...validate,
13
14
  ...render,
15
+ ...compose,
14
16
  };
package/src/index.mjs CHANGED
@@ -15,3 +15,5 @@ export { lintDomain } from './lint-pure.js';
15
15
  export { validateDomainSchema, validateCrossFile } from './validate-pure.js';
16
16
 
17
17
  export { renderPreviewHTML, escHtml, renderCard } from './render.js';
18
+
19
+ export { composeContext, classifySignals, composeChecks, loadAndCompose } from './compose.js';
package/src/lint-pure.js CHANGED
@@ -168,6 +168,25 @@ function lintDomain(dataMap) {
168
168
  (pat.self_check || []).forEach((s, i) => yesno(s, `KDNA_Patterns.json.self_check[${i}]`));
169
169
  }
170
170
 
171
+ // Anti-vagueness: axioms must be domain-specific, not generic platitudes
172
+ if (core && Array.isArray(core.axioms)) {
173
+ const vaguePhrases = [
174
+ 'be helpful', 'be professional', 'be accurate', 'best practices',
175
+ 'user-centric', 'customer-focused', 'excellence', 'innovation',
176
+ '以人为本', '用户至上', '最佳实践', '卓越',
177
+ ];
178
+ core.axioms.forEach((a, i) => {
179
+ const text = ((a.one_sentence || '') + ' ' + (a.full_statement || '')).toLowerCase();
180
+ vaguePhrases.forEach((phrase) => {
181
+ if (text.includes(phrase)) {
182
+ warnings.push(
183
+ `KDNA_Core.json.axioms[${i}]: axiom contains vague phrase "${phrase}" — axioms must be testable and domain-specific, not generic platitudes`,
184
+ );
185
+ }
186
+ });
187
+ });
188
+ }
189
+
171
190
  // Validate KDNA_Reasoning.json
172
191
  const rea = dataMap['KDNA_Reasoning.json'];
173
192
  if (rea) {
package/src/types.d.ts CHANGED
@@ -252,3 +252,9 @@ export function validateCrossFile(dataMap: KDNAFileDataMap): ValidationResult;
252
252
  export function renderPreviewHTML(domain: LoadedDomain, manifest?: KDNAManifest): string;
253
253
  export function escHtml(s: string): string;
254
254
  export function renderCard(title: string, count: number | undefined, items: string): string;
255
+
256
+ // Compose
257
+ export function composeContext(domains: LoadedDomain[], options?: { separator?: string }): string;
258
+ export function classifySignals(input: string, domains: Array<{ id: string; core: { trigger_signals?: string[] } }>): number[];
259
+ export function composeChecks(domains: Array<{ id: string; core: { meta: { domain: string } }; patterns: { self_check: string[] } }>): string[];
260
+ export function loadAndCompose(dataMaps: KDNAFileDataMap[], options?: LoadOptions & { separator?: string }): { domains: LoadedDomain[]; context: string; activeIndices: number[] };