@hasna/hooks 0.3.11 → 0.4.1
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/README.md +22 -0
- package/bin/index.js +324 -102
- package/hooks/codewith-native-common.test.ts +364 -12
- package/hooks/codewith-native-common.ts +558 -18
- package/hooks/worktree-guard/README.md +44 -0
- package/hooks/worktree-guard/src/hook.ts +50 -8
- package/package.json +1 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
canonicalRepoIdentity,
|
|
5
|
+
canonicalWorktreeTemplate,
|
|
4
6
|
classifyDangerousOperation,
|
|
5
7
|
claimCommand,
|
|
6
8
|
gitCommandInfo,
|
|
@@ -10,7 +12,6 @@ import {
|
|
|
10
12
|
managedWorktreeInfo,
|
|
11
13
|
readInput,
|
|
12
14
|
respond,
|
|
13
|
-
runIdFrom,
|
|
14
15
|
taskIdFrom,
|
|
15
16
|
warn,
|
|
16
17
|
type CodewithHookInput,
|
|
@@ -40,27 +41,63 @@ export async function evaluate(input: CodewithHookInput): Promise<{ output: Reco
|
|
|
40
41
|
const managed = managedWorktreeInfo(targetCwd);
|
|
41
42
|
if (managed.managed) return { output: { continue: true }, warnings };
|
|
42
43
|
|
|
44
|
+
// Migration shim, temporary and deliberately narrow: worktrees created under the
|
|
45
|
+
// pre-rule-8 station-id lease layout are non-compliant, but they are real, active
|
|
46
|
+
// worktrees. Hard-blocking their git work on day one would strand in-flight tasks
|
|
47
|
+
// with no way to land, so this one recognised layout warns instead of blocking.
|
|
48
|
+
// Everything else non-canonical is still blocked. Remove once they are re-homed.
|
|
49
|
+
if (managed.layout === "legacy-station-lease") {
|
|
50
|
+
const message = `${managed.reason}. This layout will stop being tolerated; re-home this worktree.`;
|
|
51
|
+
warnings.push(message);
|
|
52
|
+
return {
|
|
53
|
+
output: {
|
|
54
|
+
continue: true,
|
|
55
|
+
hookSpecificOutput: {
|
|
56
|
+
hookEventName: "PreToolUse",
|
|
57
|
+
additionalContext: `[worktree-guard] ${message}`,
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
warnings,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
43
64
|
const repoRoot = await gitRepoRoot(targetCwd);
|
|
44
|
-
|
|
65
|
+
// Rule 8 resolution order: the repos CLI is the source of truth for <repo-name>.
|
|
66
|
+
// The local checkout directory is the next best guess, and the remote slug is the
|
|
67
|
+
// last resort — for most repos the remote basename is NOT the canonical repo name.
|
|
68
|
+
const identity = await canonicalRepoIdentity(targetCwd);
|
|
69
|
+
const repo = identity.name
|
|
70
|
+
|| (repoRoot ? repoRoot.split("/").filter(Boolean).pop() || null : null)
|
|
71
|
+
|| (await gitRemoteSlug(targetCwd))?.split("/").filter(Boolean).pop()
|
|
72
|
+
|| null;
|
|
45
73
|
const taskId = taskIdFrom(input);
|
|
46
|
-
const
|
|
47
|
-
const recommended = claimCommand(repo, taskId, runId);
|
|
74
|
+
const recommended = claimCommand(repo, taskId, identity.defaultBranch);
|
|
48
75
|
const action = gitInfo?.action || null;
|
|
76
|
+
const canonical = canonicalWorktreeTemplate(managed.root);
|
|
77
|
+
// Rule 8 requires an exact repos-CLI lookup for <repo-name>. When that lookup did
|
|
78
|
+
// not resolve, the name below is a local guess, so say so rather than imply it is
|
|
79
|
+
// authoritative — the repo directory name and the remote basename often differ.
|
|
80
|
+
const unverifiedRepoName = identity.name
|
|
81
|
+
? null
|
|
82
|
+
: `Confirm <repo-name> with an exact repos CLI lookup first: repos repo ${repo || "<name>"} --json`;
|
|
49
83
|
|
|
50
84
|
if (action) {
|
|
51
85
|
const reason = [
|
|
52
|
-
`Blocked git ${action} outside a
|
|
86
|
+
`Blocked git ${action} outside a canonical task worktree (${managed.reason || "not under managed root"}).`,
|
|
53
87
|
`Command target cwd: ${targetCwd}`,
|
|
54
|
-
`
|
|
55
|
-
`
|
|
88
|
+
`Canonical worktree path (Agent Operating Rules rule 8): ${canonical}`,
|
|
89
|
+
`Create one first: ${recommended}`,
|
|
90
|
+
...(unverifiedRepoName ? [unverifiedRepoName] : []),
|
|
56
91
|
].join(" ");
|
|
57
92
|
return { output: { decision: "block", reason }, warnings };
|
|
58
93
|
}
|
|
59
94
|
|
|
60
95
|
if (isFeatureWork(input, command)) {
|
|
61
96
|
warnings.push([
|
|
62
|
-
`Feature work appears to be outside a
|
|
97
|
+
`Feature work appears to be outside a canonical task worktree (${managed.reason || "not under managed root"}).`,
|
|
98
|
+
`Canonical worktree path (Agent Operating Rules rule 8): ${canonical}.`,
|
|
63
99
|
`Use: ${recommended}`,
|
|
100
|
+
...(unverifiedRepoName ? [unverifiedRepoName] : []),
|
|
64
101
|
].join(" "));
|
|
65
102
|
return {
|
|
66
103
|
output: {
|
|
@@ -91,4 +128,9 @@ export async function run(): Promise<void> {
|
|
|
91
128
|
|
|
92
129
|
if (import.meta.main) {
|
|
93
130
|
await run();
|
|
131
|
+
// The verdict is written, so the hook is done. Exit rather than waiting for the
|
|
132
|
+
// event loop to drain: an optional CLI consulted during evaluation may have left a
|
|
133
|
+
// grandchild holding a pipe open, and a hook that has already answered must never
|
|
134
|
+
// keep the caller's PreToolUse path waiting on it.
|
|
135
|
+
process.exit(0);
|
|
94
136
|
}
|
package/package.json
CHANGED