@crouton-kit/crouter 0.3.23 → 0.3.24
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.
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Regression test for the namespaced plugin-doc READ path in resolveMemoryDoc.
|
|
2
|
+
//
|
|
3
|
+
// Bug (substrate-unification cut f1b071b, fixed by 945d121): plugin memory docs
|
|
4
|
+
// mount at a virtual `<pluginName>/` namespace and ARE enumerated by
|
|
5
|
+
// listAllMemoryDocs, so `crtr memory list` advertises canonical names like
|
|
6
|
+
// `claude-capture/capture`. But findMemoryMatches (the direct-path lookup inside
|
|
7
|
+
// resolveMemoryDoc) only searched the NATIVE scopeMemoryDir, never each plugin's
|
|
8
|
+
// pluginMemoryDir — so `crtr memory read <plugin>/<leaf>` returned not_found for
|
|
9
|
+
// a name that `list` had just shown. Only the bare-leaf fallback (`read capture`)
|
|
10
|
+
// resolved. The fix taught findMemoryMatches to resolve `<plugin>/<rest>` against
|
|
11
|
+
// each enabled plugin's pluginMemoryDir, native-before-plugin precedence kept.
|
|
12
|
+
//
|
|
13
|
+
// This test FAILS on the pre-fix code (the two fully-qualified reads throw
|
|
14
|
+
// notFound) and PASSES on the current code.
|
|
15
|
+
//
|
|
16
|
+
// Run: node --import tsx/esm --test src/core/__tests__/memory-resolver.test.ts
|
|
17
|
+
import { test, before, after } from 'node:test';
|
|
18
|
+
import assert from 'node:assert/strict';
|
|
19
|
+
import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
20
|
+
import { tmpdir } from 'node:os';
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
import { resolveMemoryDoc } from '../memory-resolver.js';
|
|
23
|
+
import { resetScopeCache } from '../scope.js';
|
|
24
|
+
// Fixture: a temp PROJECT scope (a `.crouter/` dir discovered by walking up from
|
|
25
|
+
// cwd) holding both a native memory/ tree and one enabled fixture plugin whose
|
|
26
|
+
// memory/ tree mounts under the `fixplug/` namespace.
|
|
27
|
+
let projectDir;
|
|
28
|
+
let prevCwd;
|
|
29
|
+
const PLUGIN = 'fixplug';
|
|
30
|
+
function doc(name) {
|
|
31
|
+
return `---\nkind: skill\nwhen-and-why-to-read: fixture doc ${name}\n---\n\n# ${name}\n`;
|
|
32
|
+
}
|
|
33
|
+
before(() => {
|
|
34
|
+
prevCwd = process.cwd();
|
|
35
|
+
projectDir = mkdtempSync(join(tmpdir(), 'crtr-memres-'));
|
|
36
|
+
const scopeRoot = join(projectDir, '.crouter');
|
|
37
|
+
// Native scope docs. `fixplug/shared` is a native SHADOW of a plugin doc of
|
|
38
|
+
// the same canonical name — it must win (native-before-plugin precedence).
|
|
39
|
+
const memDir = join(scopeRoot, 'memory');
|
|
40
|
+
mkdirSync(join(memDir, PLUGIN), { recursive: true });
|
|
41
|
+
writeFileSync(join(memDir, PLUGIN, 'shared.md'), doc('native-shared'));
|
|
42
|
+
// Fixture plugin: enabled manifest + a flat substrate doc and a nested one.
|
|
43
|
+
const pluginRoot = join(scopeRoot, 'plugins', PLUGIN);
|
|
44
|
+
mkdirSync(join(pluginRoot, '.crouter-plugin'), { recursive: true });
|
|
45
|
+
writeFileSync(join(pluginRoot, '.crouter-plugin', 'plugin.json'), JSON.stringify({ name: PLUGIN, version: '0.0.0' }));
|
|
46
|
+
const pluginMem = join(pluginRoot, 'memory');
|
|
47
|
+
mkdirSync(join(pluginMem, 'area'), { recursive: true });
|
|
48
|
+
writeFileSync(join(pluginMem, 'widget.md'), doc('plugin-widget'));
|
|
49
|
+
writeFileSync(join(pluginMem, 'area', 'zone.md'), doc('plugin-zone'));
|
|
50
|
+
writeFileSync(join(pluginMem, 'shared.md'), doc('plugin-shared'));
|
|
51
|
+
// Explicitly enable the plugin in config (default is enabled, but be robust).
|
|
52
|
+
writeFileSync(join(scopeRoot, 'config.json'), JSON.stringify({ plugins: { [PLUGIN]: { enabled: true } } }));
|
|
53
|
+
// Point the scope resolver at the fixture: project scope is found by walking
|
|
54
|
+
// up from cwd, so chdir into the temp dir and clear the cached project root.
|
|
55
|
+
process.chdir(projectDir);
|
|
56
|
+
resetScopeCache();
|
|
57
|
+
});
|
|
58
|
+
after(() => {
|
|
59
|
+
process.chdir(prevCwd);
|
|
60
|
+
resetScopeCache();
|
|
61
|
+
rmSync(projectDir, { recursive: true, force: true });
|
|
62
|
+
});
|
|
63
|
+
test('fully-qualified <plugin>/<leaf> resolves to the plugin substrate doc', () => {
|
|
64
|
+
// Pre-fix this threw notFound (findMemoryMatches never searched pluginMemoryDir).
|
|
65
|
+
const d = resolveMemoryDoc(`${PLUGIN}/widget`);
|
|
66
|
+
assert.equal(d.name, `${PLUGIN}/widget`);
|
|
67
|
+
assert.ok(d.path.endsWith(join('plugins', PLUGIN, 'memory', 'widget.md')));
|
|
68
|
+
});
|
|
69
|
+
test('fully-qualified multi-segment <plugin>/<a>/<b> resolves the nested doc', () => {
|
|
70
|
+
// Pre-fix this also threw notFound.
|
|
71
|
+
const d = resolveMemoryDoc(`${PLUGIN}/area/zone`);
|
|
72
|
+
assert.equal(d.name, `${PLUGIN}/area/zone`);
|
|
73
|
+
assert.ok(d.path.endsWith(join('plugins', PLUGIN, 'memory', 'area', 'zone.md')));
|
|
74
|
+
});
|
|
75
|
+
test('the bare leaf still resolves via last-segment fallback', () => {
|
|
76
|
+
// This worked even pre-fix (leaf fallback scans listAllMemoryDocs, which DID
|
|
77
|
+
// enumerate plugin docs) — the gap was the direct fully-qualified path only.
|
|
78
|
+
const d = resolveMemoryDoc('widget');
|
|
79
|
+
assert.equal(d.name, `${PLUGIN}/widget`);
|
|
80
|
+
});
|
|
81
|
+
test('native doc wins over a plugin doc of the same canonical name', () => {
|
|
82
|
+
// native-before-plugin precedence: findMemoryMatches checks scopeMemoryDir
|
|
83
|
+
// before pluginMemoryDir, so the native shadow at memory/fixplug/shared.md
|
|
84
|
+
// wins over the plugin's memory/shared.md.
|
|
85
|
+
const d = resolveMemoryDoc(`${PLUGIN}/shared`);
|
|
86
|
+
assert.equal(d.name, `${PLUGIN}/shared`);
|
|
87
|
+
assert.ok(d.path.endsWith(join('memory', PLUGIN, 'shared.md')));
|
|
88
|
+
assert.ok(!d.path.includes(join('plugins', PLUGIN)));
|
|
89
|
+
});
|
|
@@ -97,17 +97,43 @@ export function listAllMemoryDocs(scope) {
|
|
|
97
97
|
function findMemoryMatches(name, scope) {
|
|
98
98
|
const matches = [];
|
|
99
99
|
for (const s of scopesInPrecedence(scope)) {
|
|
100
|
+
// Native scope memory dir first (native-before-plugin precedence).
|
|
100
101
|
const dir = scopeMemoryDir(s);
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
if (dir) {
|
|
103
|
+
const path = join(dir, ...name.split('/')) + '.md';
|
|
104
|
+
if (pathExists(path)) {
|
|
105
|
+
matches.push(loadMemoryDoc(name, s, path));
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const indexPath = join(dir, ...name.split('/'), 'INDEX.md');
|
|
109
|
+
if (pathExists(indexPath)) {
|
|
110
|
+
matches.push(loadMemoryDoc(name, s, indexPath));
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// Plugin memory dir: a fully-qualified `<plugin>/<rest>` name resolves
|
|
115
|
+
// against that enabled plugin's memory/ tree (the `<pluginName>/` mount
|
|
116
|
+
// that listAllMemoryDocs enumerates — `read` must resolve what `list` shows).
|
|
117
|
+
const slash = name.indexOf('/');
|
|
118
|
+
if (slash <= 0)
|
|
106
119
|
continue;
|
|
120
|
+
const pluginName = name.slice(0, slash);
|
|
121
|
+
const rest = name.slice(slash + 1);
|
|
122
|
+
for (const p of listInstalledPlugins(s)) {
|
|
123
|
+
if (!p.enabled || p.name !== pluginName)
|
|
124
|
+
continue;
|
|
125
|
+
const pdir = pluginMemoryDir(p);
|
|
126
|
+
const ppath = join(pdir, ...rest.split('/')) + '.md';
|
|
127
|
+
if (pathExists(ppath)) {
|
|
128
|
+
matches.push(loadMemoryDoc(name, s, ppath));
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
const pindex = join(pdir, ...rest.split('/'), 'INDEX.md');
|
|
132
|
+
if (pathExists(pindex)) {
|
|
133
|
+
matches.push(loadMemoryDoc(name, s, pindex));
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
107
136
|
}
|
|
108
|
-
const indexPath = join(dir, ...name.split('/'), 'INDEX.md');
|
|
109
|
-
if (pathExists(indexPath))
|
|
110
|
-
matches.push(loadMemoryDoc(name, s, indexPath));
|
|
111
137
|
}
|
|
112
138
|
return matches;
|
|
113
139
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crouton-kit/crouter",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.24",
|
|
4
4
|
"description": "crtr — fast access to skills, plugins, and marketplaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@crouton-kit/humanloop": "^0.3.
|
|
40
|
+
"@crouton-kit/humanloop": "^0.3.17",
|
|
41
41
|
"@earendil-works/pi-ai": "^0.79.0",
|
|
42
42
|
"@earendil-works/pi-coding-agent": "^0.79.0",
|
|
43
43
|
"@earendil-works/pi-tui": "^0.79.0",
|