Skip to content

Commit 9ab62e3

Browse files
authored
Merge pull request #5 from Soju06/feat/relocate-hook-to-skill-resource
fix(hook): relocate detect.js to skill resource for team compatibility
2 parents a11f2ed + e492a71 commit 9ab62e3

10 files changed

Lines changed: 111 additions & 50 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ jobs:
5656
set -euo pipefail
5757
TARBALL=$(ls knowpatch-*.tgz)
5858
CONTENTS=$(tar tzf "${TARBALL}")
59-
echo "${CONTENTS}" | grep -q "package/bin/cli.js" || { echo "FAIL: bin/cli.js missing"; exit 1; }
60-
echo "${CONTENTS}" | grep -q "package/bin/detect.js" || { echo "FAIL: bin/detect.js missing"; exit 1; }
61-
echo "${CONTENTS}" | grep -q "package/skills/" || { echo "FAIL: skills/ missing"; exit 1; }
59+
echo "${CONTENTS}" | grep -q "package/bin/cli.js" || { echo "FAIL: bin/cli.js missing"; exit 1; }
60+
echo "${CONTENTS}" | grep -q "package/skills/knowpatch/bin/detect.js" || { echo "FAIL: skills/knowpatch/bin/detect.js missing"; exit 1; }
61+
echo "${CONTENTS}" | grep -q "package/skills/" || { echo "FAIL: skills/ missing"; exit 1; }
6262
echo "Package contents verified"

‎.github/workflows/release.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ jobs:
7171
- run: bun run build
7272
- name: Verify build artifacts
7373
run: |
74-
[ -f bin/cli.js ] && [ -f bin/detect.js ] && echo "OK" || exit 1
74+
[ -f bin/cli.js ] && [ -f skills/knowpatch/bin/detect.js ] && echo "OK" || exit 1
7575
- name: Verify package contents
7676
run: npm pack --dry-run
7777
- name: Publish to npm

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ dist/
44
*.js.map
55
.DS_Store
66
.agents/hooks/state/
7+
skills/knowpatch/bin/
78

89
__pycache__

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"knowpatch": "bin/cli.js"
88
},
99
"scripts": {
10-
"build": "bun build src/cli.ts --outdir bin --target node --format esm && bun build src/hooks/detect.ts --outdir bin --target node --format esm",
10+
"build": "bun build src/cli.ts --outdir bin --target node --format esm && bun build src/hooks/detect.ts --outdir skills/knowpatch/bin --target node --format esm",
1111
"dev": "bun run src/cli.ts",
1212
"test": "bun test",
1313
"lint": "biome check src/ tests/",

‎src/commands/update.ts‎

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,34 +67,54 @@ async function syncInstallation(scope: Scope): Promise<void> {
6767
if (ps.implicitlyLinked) {
6868
const hookStatus = ps.platform.supportsHooks
6969
? ps.hookInstalled
70-
? "hook up-to-date"
70+
? ps.hookUpToDate
71+
? "hook up-to-date"
72+
: "hook outdated"
7173
: "hook missing"
7274
: "";
7375
console.log(
7476
` ${ICONS.ok} ${COLORS.success(`${ps.platform.displayName}:`)} shared via parent symlink${hookStatus ? `, ${hookStatus}` : ""}`,
7577
);
7678

77-
if (ps.platform.supportsHooks && !ps.hookInstalled) {
78-
await installPlatformHook(ps.platform, scope);
79-
console.log(
80-
` ${ICONS.ok} ${COLORS.success(`${ps.platform.displayName}:`)} hook restored`,
81-
);
79+
if (ps.platform.supportsHooks) {
80+
if (!ps.hookInstalled) {
81+
await installPlatformHook(ps.platform, scope);
82+
console.log(
83+
` ${ICONS.ok} ${COLORS.success(`${ps.platform.displayName}:`)} hook restored`,
84+
);
85+
} else if (!ps.hookUpToDate) {
86+
await uninstallPlatformHook(ps.platform, scope);
87+
await installPlatformHook(ps.platform, scope);
88+
console.log(
89+
` ${ICONS.ok} ${COLORS.success(`${ps.platform.displayName}:`)} hook updated`,
90+
);
91+
}
8292
}
8393
} else if (ps.symlinkValid) {
8494
const hookStatus = ps.platform.supportsHooks
8595
? ps.hookInstalled
86-
? "hook up-to-date"
96+
? ps.hookUpToDate
97+
? "hook up-to-date"
98+
: "hook outdated"
8799
: "hook missing"
88100
: "";
89101
console.log(
90102
` ${ICONS.ok} ${COLORS.success(`${ps.platform.displayName}:`)} symlink valid${hookStatus ? `, ${hookStatus}` : ""}`,
91103
);
92104

93-
if (ps.platform.supportsHooks && !ps.hookInstalled) {
94-
await installPlatformHook(ps.platform, scope);
95-
console.log(
96-
` ${ICONS.ok} ${COLORS.success(`${ps.platform.displayName}:`)} hook restored`,
97-
);
105+
if (ps.platform.supportsHooks) {
106+
if (!ps.hookInstalled) {
107+
await installPlatformHook(ps.platform, scope);
108+
console.log(
109+
` ${ICONS.ok} ${COLORS.success(`${ps.platform.displayName}:`)} hook restored`,
110+
);
111+
} else if (!ps.hookUpToDate) {
112+
await uninstallPlatformHook(ps.platform, scope);
113+
await installPlatformHook(ps.platform, scope);
114+
console.log(
115+
` ${ICONS.ok} ${COLORS.success(`${ps.platform.displayName}:`)} hook updated`,
116+
);
117+
}
98118
}
99119
} else {
100120
console.log(
@@ -107,6 +127,21 @@ async function syncInstallation(scope: Scope): Promise<void> {
107127
console.log(
108128
` ${ICONS.ok} ${COLORS.success(`${ps.platform.displayName}:`)} symlink restored`,
109129
);
130+
131+
if (ps.platform.supportsHooks) {
132+
if (!ps.hookInstalled) {
133+
await installPlatformHook(ps.platform, scope);
134+
console.log(
135+
` ${ICONS.ok} ${COLORS.success(`${ps.platform.displayName}:`)} hook restored`,
136+
);
137+
} else if (!ps.hookUpToDate) {
138+
await uninstallPlatformHook(ps.platform, scope);
139+
await installPlatformHook(ps.platform, scope);
140+
console.log(
141+
` ${ICONS.ok} ${COLORS.success(`${ps.platform.displayName}:`)} hook updated`,
142+
);
143+
}
144+
}
110145
}
111146
}
112147

‎src/core/hooks.ts‎

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,29 @@ export async function isPlatformHookInstalled(
6060
if (!settingsPath) return false;
6161

6262
const settings = await readSettings(settingsPath);
63-
const hookCmd = getHookCommand();
63+
const hookCmd = getHookCommand(scope);
6464
const entries = settings.hooks?.UserPromptSubmit ?? [];
6565
return entries.some((m) => isOurHook(m, hookCmd));
6666
}
6767

68+
/** Check if the installed hook command matches the expected command for this scope */
69+
export async function isPlatformHookUpToDate(
70+
platform: PlatformConfig,
71+
scope: Scope,
72+
): Promise<boolean> {
73+
if (platform.hookType === "none") return true;
74+
const settingsPath = getPlatformSettingsPath(platform, scope);
75+
if (!settingsPath) return true;
76+
77+
const hookCmd = getHookCommand(scope);
78+
const settings = await readSettings(settingsPath);
79+
const entries = settings.hooks?.UserPromptSubmit ?? [];
80+
81+
return entries.some((m) =>
82+
m.hooks.some((h) => h.type === "command" && h.command === hookCmd),
83+
);
84+
}
85+
6886
/** Add the knowpatch hook to a platform's settings.json */
6987
export async function installPlatformHook(
7088
platform: PlatformConfig,
@@ -74,7 +92,7 @@ export async function installPlatformHook(
7492
const settingsPath = getPlatformSettingsPath(platform, scope);
7593
if (!settingsPath) return false;
7694

77-
const hookCmd = getHookCommand();
95+
const hookCmd = getHookCommand(scope);
7896
const settings = await readSettings(settingsPath);
7997

8098
if (!settings.hooks) {
@@ -106,7 +124,7 @@ export async function uninstallPlatformHook(
106124
const settingsPath = getPlatformSettingsPath(platform, scope);
107125
if (!settingsPath) return false;
108126

109-
const hookCmd = getHookCommand();
127+
const hookCmd = getHookCommand(scope);
110128
const settings = await readSettings(settingsPath);
111129
const hooks = settings.hooks;
112130
const entries = hooks?.UserPromptSubmit;

‎src/core/paths.ts‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,13 @@ export function getPlatformSettingsPath(
6262
return resolve(getScopeBase(scope), platform.configDir, "settings.json");
6363
}
6464

65-
/** Absolute path to the built hook detect script */
66-
export function getHookCommand(): string {
67-
return `node ${resolve(getPackageRoot(), "bin/detect.js")}`;
65+
/** Hook command for settings.json — relative for project scope, absolute for user */
66+
export function getHookCommand(scope: Scope): string {
67+
const detectScript = ".agents/skills/knowpatch/bin/detect.js";
68+
if (scope === "project") {
69+
return `node ${detectScript}`;
70+
}
71+
return `node ${resolve(homedir(), detectScript)}`;
6872
}
6973

7074
/** Safe lstat that returns null instead of throwing */

‎src/core/status.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { lstat, readlink } from "node:fs/promises";
22
import { dirname, resolve } from "node:path";
3-
import { isPlatformHookInstalled } from "./hooks.js";
3+
import { isPlatformHookInstalled, isPlatformHookUpToDate } from "./hooks.js";
44
import {
55
getAgentsSkillPath,
66
getPlatformSkillPath,
@@ -22,6 +22,7 @@ export interface PlatformStatus {
2222
symlinkValid: boolean;
2323
implicitlyLinked: boolean;
2424
hookInstalled: boolean;
25+
hookUpToDate: boolean;
2526
}
2627

2728
export interface InstallationStatus {
@@ -85,13 +86,17 @@ export async function detectInstallation(
8586
}
8687

8788
const hookInstalled = await isPlatformHookInstalled(platform, scope);
89+
const hookUpToDate = hookInstalled
90+
? await isPlatformHookUpToDate(platform, scope)
91+
: false;
8892

8993
platforms.push({
9094
platform,
9195
installed,
9296
symlinkValid,
9397
implicitlyLinked,
9498
hookInstalled,
99+
hookUpToDate,
95100
});
96101
}
97102

‎src/hooks/detect.ts‎

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
* is derived from correction files.
99
*/
1010

11-
import { accessSync, readdirSync, readFileSync } from "node:fs";
11+
import { readdirSync, readFileSync } from "node:fs";
1212
import { dirname, resolve } from "node:path";
13+
import { fileURLToPath } from "node:url";
1314

1415
export interface CorrectionEntry {
1516
header: string;
@@ -73,21 +74,10 @@ export function buildMessage(
7374
].join("\n");
7475
}
7576

76-
/** Find the package root by walking up from this file */
77-
function findPackageRoot(): string {
78-
let dir = dirname(new URL(import.meta.url).pathname);
79-
while (true) {
80-
try {
81-
accessSync(resolve(dir, "package.json"));
82-
return dir;
83-
} catch {
84-
const parent = dirname(dir);
85-
if (parent === dir) {
86-
throw new Error("Could not find knowpatch package root");
87-
}
88-
dir = parent;
89-
}
90-
}
77+
/** Resolve corrections directory relative to this script's location */
78+
function getCorrectionsDir(): string {
79+
const scriptDir = dirname(fileURLToPath(import.meta.url));
80+
return resolve(scriptDir, "..", "corrections");
9181
}
9282

9383
/** Load all correction files, returning parsed tags and entries */
@@ -147,12 +137,7 @@ async function main() {
147137
return;
148138
}
149139

150-
let correctionsDir: string;
151-
try {
152-
correctionsDir = resolve(findPackageRoot(), "skills/knowpatch/corrections");
153-
} catch {
154-
return;
155-
}
140+
const correctionsDir = getCorrectionsDir();
156141

157142
// Load corrections and derive keywords from tags
158143
const corrections = loadCorrections(correctionsDir);

‎tests/paths.test.ts‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,23 @@ describe("getPlatformSettingsPath", () => {
9595
});
9696

9797
describe("getHookCommand", () => {
98-
test("includes bin/detect.js", () => {
99-
const cmd = getHookCommand();
100-
expect(cmd).toContain("bin/detect.js");
101-
expect(cmd).toStartWith("node ");
98+
test("project scope returns relative path", () => {
99+
const cmd = getHookCommand("project");
100+
expect(cmd).toBe("node .agents/skills/knowpatch/bin/detect.js");
101+
});
102+
103+
test("user scope returns absolute path with homedir", () => {
104+
const { homedir } = require("node:os");
105+
const { resolve } = require("node:path");
106+
const cmd = getHookCommand("user");
107+
expect(cmd).toBe(
108+
`node ${resolve(homedir(), ".agents/skills/knowpatch/bin/detect.js")}`,
109+
);
110+
});
111+
112+
test("both scopes start with node prefix", () => {
113+
expect(getHookCommand("project")).toStartWith("node ");
114+
expect(getHookCommand("user")).toStartWith("node ");
102115
});
103116
});
104117

0 commit comments

Comments
 (0)