@aikdna/kdna-core 0.15.6 → 0.15.7

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.15.7 (2026-06-28)
4
+
5
+ Story 6 — dependencies runtime. Supports semver-matching local/registry dependency resolution and topological sort-based loading order.
6
+
7
+ - **Dependencies manifest schema support**: Explicitly declared `"dependencies"` as a first-class manifest property.
8
+ - **Topological Sorting Resolver**: Added a robust, pure post-order DFS topological sorter with circular cycle, missing dependency, and version range mismatch checks.
9
+ - **Topological Prompt Composition**: Enabled recursive, multi-domain prompt composition and flat topological JSON return inside `loadAuthorized`.
10
+
11
+
3
12
  ## v0.15.6 (2026-06-28)
4
13
 
5
14
  Story 5 — Bundle payload type. Bumps KDNA manifest format schema to v2, introduces bundle-profile-v1 payload profile, and starts KDNA v1 format deprecation window.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aikdna/kdna-core",
3
- "version": "0.15.6",
3
+ "version": "0.15.7",
4
4
  "description": "KDNA Core library for validating, planning, and loading local .kdna judgment assets.",
5
5
  "engines": {
6
6
  "node": ">=18.0.0"
@@ -195,6 +195,12 @@
195
195
  }
196
196
  }
197
197
  },
198
+ "dependencies": {
199
+ "type": "object",
200
+ "additionalProperties": {
201
+ "type": "string"
202
+ }
203
+ },
198
204
  "encryption": { "type": "object" },
199
205
  "load_contract": { "$ref": "load-contract.schema.json" },
200
206
  "scope": { "type": "object" },
package/src/v1/index.js CHANGED
@@ -1241,6 +1241,46 @@ function planLoad(inputPath, opts = {}) {
1241
1241
  }
1242
1242
 
1243
1243
  const manifest = v1.manifest;
1244
+
1245
+ // Resolve dependencies if declared (Story 6)
1246
+ const resolvedDeps = [];
1247
+ if (manifest.dependencies && typeof manifest.dependencies === 'object' && Object.keys(manifest.dependencies).length > 0) {
1248
+ if (typeof opts.resolveAsset === 'function') {
1249
+ try {
1250
+ const resolved = resolveDependencies(manifest, opts.resolveAsset);
1251
+ resolvedDeps.push(...resolved);
1252
+ } catch (err) {
1253
+ plan.state = 'invalid';
1254
+ plan.required_action = 'block';
1255
+ plan.can_load_now = false;
1256
+ plan.issues.push(buildLoadPlanIssue(
1257
+ 'KDNA_DEPENDENCY_RESOLUTION_FAILED',
1258
+ 'blocking',
1259
+ err.message
1260
+ ));
1261
+ return finalizeLoadPlan(plan);
1262
+ }
1263
+ } else {
1264
+ plan.state = 'invalid';
1265
+ plan.required_action = 'block';
1266
+ plan.can_load_now = false;
1267
+ plan.issues.push(buildLoadPlanIssue(
1268
+ 'KDNA_DEPENDENCY_RESOLVER_MISSING',
1269
+ 'blocking',
1270
+ 'Asset has dependencies but no dependency resolver callback was provided.'
1271
+ ));
1272
+ return finalizeLoadPlan(plan);
1273
+ }
1274
+ }
1275
+
1276
+ if (resolvedDeps.length > 0) {
1277
+ plan.resolved_dependencies = resolvedDeps.map(dep => ({
1278
+ name: dep.name,
1279
+ version: dep.version,
1280
+ path: dep.path
1281
+ }));
1282
+ }
1283
+
1244
1284
  const payloadDeclaredEncrypted =
1245
1285
  manifest.payload && manifest.payload.encrypted === true;
1246
1286
  const encryptedEntries = Array.isArray(manifest.encryption && manifest.encryption.encrypted_entries)
@@ -1622,7 +1662,8 @@ function loadAuthorized(inputPath, opts = {}) {
1622
1662
  err.plan = plan;
1623
1663
  throw err;
1624
1664
  }
1625
- return loadV1Unsafe(inputPath, opts);
1665
+ const mergedOpts = { ...opts, resolvedDependencies: plan.resolved_dependencies };
1666
+ return loadV1Unsafe(inputPath, mergedOpts);
1626
1667
  }
1627
1668
 
1628
1669
  function loadV1(inputPath, opts = {}) {
@@ -1803,6 +1844,20 @@ function loadV1Unsafe(inputPath, opts = {}) {
1803
1844
  throw new Error(`unknown load profile: ${profile}`);
1804
1845
  }
1805
1846
 
1847
+ if (opts.resolvedDependencies && opts.resolvedDependencies.length > 0) {
1848
+ result.resolved_dependencies = opts.resolvedDependencies.map(dep => {
1849
+ const depLoaded = loadV1Unsafe(dep.path, { ...opts, resolvedDependencies: [] });
1850
+ return {
1851
+ name: dep.name,
1852
+ version: dep.version,
1853
+ path: dep.path,
1854
+ status: depLoaded.status,
1855
+ profile: depLoaded.profile,
1856
+ content: depLoaded.content
1857
+ };
1858
+ });
1859
+ }
1860
+
1806
1861
  if (as === 'prompt') {
1807
1862
  const c = result.content;
1808
1863
  if (!c || (c.scenarios && c.scenarios.length === 0 && !c.highest_question && !(c.axioms && c.axioms.length) && !(c.boundaries && c.boundaries.length))) {
@@ -1826,8 +1881,135 @@ function loadV1Unsafe(inputPath, opts = {}) {
1826
1881
  if (c.failure_modes && c.failure_modes.length) text += 'Failure modes:\n' + c.failure_modes.map((f) => '- ' + renderPromptItem(f)).join('\n') + '\n';
1827
1882
  if (c.patterns && c.patterns.length) text += 'Patterns:\n' + c.patterns.map((p) => '- ' + renderPromptItem(p)).join('\n') + '\n';
1828
1883
  if (c.note) text += 'Note: ' + c.note + '\n';
1829
- return { status: result.status, profile: result.profile, text: text.trim() };
1884
+
1885
+ let textOut = text.trim();
1886
+ if (opts.resolvedDependencies && opts.resolvedDependencies.length > 0) {
1887
+ for (const dep of opts.resolvedDependencies) {
1888
+ const depLoaded = loadV1Unsafe(dep.path, { ...opts, resolvedDependencies: [] });
1889
+ if (depLoaded.text) {
1890
+ textOut += '\n\n---\n\n' + depLoaded.text;
1891
+ }
1892
+ }
1893
+ }
1894
+ return { status: result.status, profile: result.profile, text: textOut };
1830
1895
  }
1831
1896
 
1832
1897
  return result;
1833
1898
  }
1899
+
1900
+ // ─── Semver & Dependency Resolution (Story 6) ─────────────────────────
1901
+
1902
+ function parseSemver(v) {
1903
+ if (!v) return null;
1904
+ const match = v.trim().match(/^v?([0-9]+)\.([0-9]+)\.([0-9]+)(?:-[a-zA-Z0-9.]+)?/);
1905
+ if (!match) return null;
1906
+ return {
1907
+ major: parseInt(match[1], 10),
1908
+ minor: parseInt(match[2], 10),
1909
+ patch: parseInt(match[3], 10)
1910
+ };
1911
+ }
1912
+
1913
+ function compareSemver(v1, v2) {
1914
+ const p1 = parseSemver(v1);
1915
+ const p2 = parseSemver(v2);
1916
+ if (!p1 || !p2) return 0;
1917
+ if (p1.major !== p2.major) return p1.major - p2.major;
1918
+ if (p1.minor !== p2.minor) return p1.minor - p2.minor;
1919
+ return p1.patch - p2.patch;
1920
+ }
1921
+
1922
+ function satisfies(version, range) {
1923
+ if (!range || range === '*' || range.trim() === '') return true;
1924
+ const r = range.trim();
1925
+
1926
+ // Specific version comparison (e.g. "1.2.3")
1927
+ if (/^[0-9]/.test(r)) {
1928
+ return compareSemver(version, r) === 0;
1929
+ }
1930
+
1931
+ // ^ range comparison (e.g. "^1.2.3")
1932
+ if (r.startsWith('^')) {
1933
+ const min = r.slice(1);
1934
+ const parsedMin = parseSemver(min);
1935
+ if (!parsedMin) return false;
1936
+ const parsedVer = parseSemver(version);
1937
+ if (!parsedVer) return false;
1938
+ // same major, and version >= min
1939
+ if (parsedVer.major !== parsedMin.major) return false;
1940
+ return compareSemver(version, min) >= 0;
1941
+ }
1942
+
1943
+ // ~ range comparison (e.g. "~1.2.3")
1944
+ if (r.startsWith('~')) {
1945
+ const min = r.slice(1);
1946
+ const parsedMin = parseSemver(min);
1947
+ if (!parsedMin) return false;
1948
+ const parsedVer = parseSemver(version);
1949
+ if (!parsedVer) return false;
1950
+ // same major, same minor, and version >= min
1951
+ if (parsedVer.major !== parsedMin.major || parsedVer.minor !== parsedMin.minor) return false;
1952
+ return compareSemver(version, min) >= 0;
1953
+ }
1954
+
1955
+ // Custom ranges like ">=1.0.0 <2.0.0"
1956
+ if (r.includes(' ')) {
1957
+ const parts = r.split(/\s+/);
1958
+ return parts.every(part => satisfies(version, part));
1959
+ }
1960
+
1961
+ if (r.startsWith('>=')) {
1962
+ return compareSemver(version, r.slice(2)) >= 0;
1963
+ }
1964
+ if (r.startsWith('>')) {
1965
+ return compareSemver(version, r.slice(1)) > 0;
1966
+ }
1967
+ if (r.startsWith('<=')) {
1968
+ return compareSemver(version, r.slice(2)) <= 0;
1969
+ }
1970
+ if (r.startsWith('<')) {
1971
+ return compareSemver(version, r.slice(1)) < 0;
1972
+ }
1973
+
1974
+ return false;
1975
+ }
1976
+
1977
+ function resolveDependencies(manifest, resolveAssetCallback, seen = new Set(), stack = []) {
1978
+ const resolved = [];
1979
+ const deps = manifest.dependencies || {};
1980
+
1981
+ for (const [name, range] of Object.entries(deps)) {
1982
+ if (stack.includes(name)) {
1983
+ throw new Error(`Circular dependency detected: ${stack.join(' -> ')} -> ${name}`);
1984
+ }
1985
+
1986
+ if (seen.has(name)) continue;
1987
+
1988
+ // Resolve the dependency using the callback
1989
+ const depAsset = resolveAssetCallback(name);
1990
+ if (!depAsset) {
1991
+ throw new Error(`Dependency not satisfied: "${name}" matching "${range}" is not installed.`);
1992
+ }
1993
+
1994
+ // Check if the resolved version satisfies the range
1995
+ if (!satisfies(depAsset.version, range)) {
1996
+ throw new Error(`Dependency mismatch: installed "${name}" version "${depAsset.version}" does not satisfy range "${range}".`);
1997
+ }
1998
+
1999
+ // Recurse
2000
+ stack.push(name);
2001
+ const subResolved = resolveDependencies(depAsset.manifest, resolveAssetCallback, seen, stack);
2002
+ stack.pop();
2003
+
2004
+ resolved.push(...subResolved);
2005
+ resolved.push({
2006
+ name,
2007
+ version: depAsset.version,
2008
+ path: depAsset.path,
2009
+ manifest: depAsset.manifest
2010
+ });
2011
+ seen.add(name);
2012
+ }
2013
+
2014
+ return resolved;
2015
+ }