@wipcomputer/wip-ldm-os 0.4.73-alpha.17 → 0.4.73-alpha.19
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/bin/ldm.js +16 -16
- package/lib/deploy.mjs +40 -8
- package/lib/detect.mjs +20 -6
- package/package.json +1 -1
package/bin/ldm.js
CHANGED
|
@@ -544,20 +544,14 @@ function deployDocs() {
|
|
|
544
544
|
return count;
|
|
545
545
|
}
|
|
546
546
|
|
|
547
|
-
// Deploy to
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
if (docsCount > 0) {
|
|
551
|
-
console.log(` + ${docsCount} personalized doc(s) deployed to ${docsDest.replace(HOME, '~')}/`);
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
// Deploy to library/documentation/ (human-readable library copy)
|
|
547
|
+
// Deploy to library/documentation/ (the canonical doc path since Mar 28 rename).
|
|
548
|
+
// Previously also deployed to settings/docs/ which Parker renamed to library/documentation/.
|
|
549
|
+
// That created a ghost folder on every install. Removed 2026-04-05 per INST-1.
|
|
555
550
|
const libraryDest = join(workspacePath, 'library', 'documentation');
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
}
|
|
551
|
+
mkdirSync(libraryDest, { recursive: true });
|
|
552
|
+
const docsCount = renderTemplates(libraryDest);
|
|
553
|
+
if (docsCount > 0) {
|
|
554
|
+
console.log(` + ${docsCount} personalized doc(s) deployed to ${libraryDest.replace(HOME, '~')}/`);
|
|
561
555
|
}
|
|
562
556
|
|
|
563
557
|
return docsCount;
|
|
@@ -820,10 +814,16 @@ async function cmdInit() {
|
|
|
820
814
|
// Scaffold workspace output dirs if workspace is configured
|
|
821
815
|
const workspace = config.workspace;
|
|
822
816
|
if (workspace && existsSync(workspace)) {
|
|
823
|
-
// Per-agent workspace dirs
|
|
824
|
-
|
|
817
|
+
// Per-agent workspace dirs.
|
|
818
|
+
// Resolve the team folder name from config.json agents[id].teamFolder
|
|
819
|
+
// so agents with unicode names or custom folder names don't get ghost
|
|
820
|
+
// folders created from their agent ID. Falls back to agent ID if no
|
|
821
|
+
// override is configured. Fixed 2026-04-05 per INST-1: previously
|
|
822
|
+
// hardcoded a map that only knew three agents and created ghost folders
|
|
823
|
+
// for any others.
|
|
825
824
|
for (const agentId of agentList) {
|
|
826
|
-
const
|
|
825
|
+
const agentObj = typeof agentsObj[agentId] === 'object' ? agentsObj[agentId] : {};
|
|
826
|
+
const teamName = agentObj.teamFolder || agentObj.name || agentId;
|
|
827
827
|
for (const sub of ['journals', 'automated/memory/summaries/daily', 'automated/memory/summaries/weekly', 'automated/memory/summaries/monthly', 'automated/memory/summaries/quarterly']) {
|
|
828
828
|
dirs.push(join(workspace, 'team', teamName, sub));
|
|
829
829
|
}
|
package/lib/deploy.mjs
CHANGED
|
@@ -812,7 +812,28 @@ function registerMCP(repoPath, door, toolName) {
|
|
|
812
812
|
}
|
|
813
813
|
}
|
|
814
814
|
|
|
815
|
-
|
|
815
|
+
/**
|
|
816
|
+
* Install Claude Code hook(s) for an extension.
|
|
817
|
+
*
|
|
818
|
+
* Accepts either a single door object (legacy) or an array of door objects
|
|
819
|
+
* (new in 2026-04-05 for wip-branch-guard 1.9.73 which registers on both
|
|
820
|
+
* PreToolUse and SessionStart). Normalizes to an array and installs each
|
|
821
|
+
* door independently.
|
|
822
|
+
*
|
|
823
|
+
* Returns true if at least one door installed successfully.
|
|
824
|
+
*/
|
|
825
|
+
function installClaudeCodeHook(repoPath, doorOrDoors) {
|
|
826
|
+
const doors = Array.isArray(doorOrDoors) ? doorOrDoors : [doorOrDoors];
|
|
827
|
+
let anyOk = false;
|
|
828
|
+
for (const door of doors) {
|
|
829
|
+
if (installClaudeCodeHookEvent(repoPath, door)) {
|
|
830
|
+
anyOk = true;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return anyOk;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function installClaudeCodeHookEvent(repoPath, door) {
|
|
816
837
|
const settingsPath = join(HOME, '.claude', 'settings.json');
|
|
817
838
|
let settings = readJSON(settingsPath);
|
|
818
839
|
|
|
@@ -826,6 +847,8 @@ function installClaudeCodeHook(repoPath, door) {
|
|
|
826
847
|
const installedGuard = join(extDir, 'guard.mjs');
|
|
827
848
|
|
|
828
849
|
// Deploy guard.mjs to ~/.ldm/extensions/{toolName}/ (#85: always update, not just when missing)
|
|
850
|
+
// Idempotent across multi-door invocations: two doors on the same repo
|
|
851
|
+
// will both trigger this copy, which is a filesystem no-op after the first.
|
|
829
852
|
const srcGuard = join(repoPath, 'guard.mjs');
|
|
830
853
|
if (existsSync(srcGuard)) {
|
|
831
854
|
try {
|
|
@@ -843,21 +866,30 @@ function installClaudeCodeHook(repoPath, door) {
|
|
|
843
866
|
? `node ${installedGuard}`
|
|
844
867
|
: (door.command || `node "${srcGuard}"`);
|
|
845
868
|
|
|
869
|
+
const event = door.event || 'PreToolUse';
|
|
870
|
+
|
|
846
871
|
if (DRY_RUN) {
|
|
847
|
-
ok(`Claude Code: would add ${
|
|
872
|
+
ok(`Claude Code: would add ${event} hook (dry run)`);
|
|
848
873
|
return true;
|
|
849
874
|
}
|
|
850
875
|
|
|
851
876
|
if (!settings.hooks) settings.hooks = {};
|
|
852
|
-
const event = door.event || 'PreToolUse';
|
|
853
877
|
if (!settings.hooks[event]) settings.hooks[event] = [];
|
|
854
878
|
|
|
855
|
-
|
|
856
|
-
|
|
879
|
+
// Match existing entries by the guard command path + the matcher, so that
|
|
880
|
+
// a single extension registering on multiple events (each with its own
|
|
881
|
+
// matcher) creates one entry per event rather than all entries colliding
|
|
882
|
+
// on the same hook slot. Before this change the existing-entry check was
|
|
883
|
+
// per-extension, not per-extension-per-event.
|
|
884
|
+
const doorMatcher = door.matcher || undefined;
|
|
885
|
+
const existingIdx = settings.hooks[event].findIndex(entry => {
|
|
886
|
+
const sameMatcher = (entry.matcher || undefined) === doorMatcher;
|
|
887
|
+
if (!sameMatcher) return false;
|
|
888
|
+
return entry.hooks?.some(h => {
|
|
857
889
|
const cmd = h.command || '';
|
|
858
890
|
return cmd.includes(`/${toolName}/`) || cmd === hookCommand;
|
|
859
|
-
})
|
|
860
|
-
);
|
|
891
|
+
});
|
|
892
|
+
});
|
|
861
893
|
|
|
862
894
|
if (existingIdx !== -1) {
|
|
863
895
|
const existingCmd = settings.hooks[event][existingIdx].hooks?.[0]?.command || '';
|
|
@@ -878,7 +910,7 @@ function installClaudeCodeHook(repoPath, door) {
|
|
|
878
910
|
}
|
|
879
911
|
|
|
880
912
|
settings.hooks[event].push({
|
|
881
|
-
matcher:
|
|
913
|
+
matcher: doorMatcher,
|
|
882
914
|
hooks: [{
|
|
883
915
|
type: 'command',
|
|
884
916
|
command: hookCommand,
|
package/lib/detect.mjs
CHANGED
|
@@ -53,16 +53,27 @@ export function detectInterfaces(repoPath) {
|
|
|
53
53
|
interfaces.skill = { path: join(repoPath, 'SKILL.md') };
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
// 6. Claude Code Hook: guard.mjs or claudeCode.hook in package.json
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
// 6. Claude Code Hook: guard.mjs or claudeCode.hook(s) in package.json
|
|
57
|
+
//
|
|
58
|
+
// Supports three shapes:
|
|
59
|
+
// - Legacy singular: pkg.claudeCode.hook = { event, matcher, ... }
|
|
60
|
+
// - New plural array: pkg.claudeCode.hooks = [{ event, matcher, ... }, ...]
|
|
61
|
+
// (one extension can register on multiple events, e.g. both PreToolUse
|
|
62
|
+
// and SessionStart. Added 2026-04-05 for wip-branch-guard 1.9.73.)
|
|
63
|
+
// - Implicit: a bare guard.mjs file with no package.json declaration.
|
|
64
|
+
//
|
|
65
|
+
// Normalized to an array internally so deploy.mjs has one code path.
|
|
66
|
+
if (Array.isArray(pkg?.claudeCode?.hooks)) {
|
|
67
|
+
interfaces.claudeCodeHook = pkg.claudeCode.hooks;
|
|
68
|
+
} else if (pkg?.claudeCode?.hook) {
|
|
69
|
+
interfaces.claudeCodeHook = [pkg.claudeCode.hook];
|
|
59
70
|
} else if (existsSync(join(repoPath, 'guard.mjs'))) {
|
|
60
|
-
interfaces.claudeCodeHook = {
|
|
71
|
+
interfaces.claudeCodeHook = [{
|
|
61
72
|
event: 'PreToolUse',
|
|
62
73
|
matcher: 'Edit|Write',
|
|
63
74
|
command: `node "${join(repoPath, 'guard.mjs')}"`,
|
|
64
75
|
timeout: 5,
|
|
65
|
-
};
|
|
76
|
+
}];
|
|
66
77
|
}
|
|
67
78
|
|
|
68
79
|
// 7. Claude Code Plugin: .claude-plugin/plugin.json
|
|
@@ -93,7 +104,10 @@ export function describeInterfaces(interfaces) {
|
|
|
93
104
|
if (interfaces.mcp) lines.push(`MCP Server: ${interfaces.mcp.file}`);
|
|
94
105
|
if (interfaces.openclaw) lines.push(`OpenClaw Plugin: ${interfaces.openclaw.config?.name || 'detected'}`);
|
|
95
106
|
if (interfaces.skill) lines.push(`Skill: SKILL.md`);
|
|
96
|
-
if (interfaces.claudeCodeHook)
|
|
107
|
+
if (interfaces.claudeCodeHook) {
|
|
108
|
+
const events = interfaces.claudeCodeHook.map(h => h.event || 'PreToolUse');
|
|
109
|
+
lines.push(`Claude Code Hook: ${events.join(', ')}`);
|
|
110
|
+
}
|
|
97
111
|
if (interfaces.claudeCodePlugin) lines.push(`Claude Code Plugin: ${interfaces.claudeCodePlugin.manifest?.name || 'detected'}`);
|
|
98
112
|
|
|
99
113
|
return `${names.length} interface(s): ${names.join(', ')}\n${lines.map(l => ` ${l}`).join('\n')}`;
|