@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 +9 -0
- package/package.json +1 -1
- package/schema/manifest.schema.json +6 -0
- package/src/v1/index.js +184 -2
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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
+
}
|