@primitive.ai/prim 0.1.0-alpha.65 → 0.1.0-alpha.66
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/dist/{chunk-5CKL5EYQ.js → chunk-CPSE3Y3T.js} +62 -0
- package/dist/{chunk-LNU44OJV.js → chunk-CZ24W5QU.js} +2 -1
- package/dist/{chunk-CPHJUQNE.js → chunk-DR66XM5L.js} +228 -88
- package/dist/hooks/post-commit.js +1 -1
- package/dist/hooks/post-rewrite.js +194 -0
- package/dist/hooks/post-tool-use.js +5 -5
- package/dist/hooks/pre-tool-use.js +1 -1
- package/dist/hooks/prim-hook.js +15 -15
- package/dist/hooks/session-start.js +7 -2
- package/dist/index.js +97 -57
- package/package.json +5 -4
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
canonicalRewritePairs,
|
|
4
|
+
toRewriteMove
|
|
5
|
+
} from "../chunk-CPSE3Y3T.js";
|
|
6
|
+
import {
|
|
7
|
+
appendMove,
|
|
8
|
+
resolveOrg
|
|
9
|
+
} from "../chunk-HIMWM526.js";
|
|
10
|
+
import {
|
|
11
|
+
getOrCreateWorkspaceId
|
|
12
|
+
} from "../chunk-UVIWZWM6.js";
|
|
13
|
+
import {
|
|
14
|
+
githubRepositoryFullName,
|
|
15
|
+
isRepoActiveForCapture,
|
|
16
|
+
repoSyncId,
|
|
17
|
+
resolveRepositoryContext
|
|
18
|
+
} from "../chunk-TDRXHBEX.js";
|
|
19
|
+
import "../chunk-CPX3BUOC.js";
|
|
20
|
+
|
|
21
|
+
// src/hooks/post-rewrite.ts
|
|
22
|
+
import { execFileSync, spawn } from "child_process";
|
|
23
|
+
import {
|
|
24
|
+
constants,
|
|
25
|
+
closeSync,
|
|
26
|
+
fstatSync,
|
|
27
|
+
lstatSync,
|
|
28
|
+
openSync,
|
|
29
|
+
readFileSync,
|
|
30
|
+
unlinkSync
|
|
31
|
+
} from "fs";
|
|
32
|
+
import { basename, dirname, isAbsolute, join } from "path";
|
|
33
|
+
import { fileURLToPath } from "url";
|
|
34
|
+
var here = dirname(fileURLToPath(import.meta.url));
|
|
35
|
+
var GIT_TIMEOUT_MS = 1e3;
|
|
36
|
+
var MAX_PAIRS_FILE_BYTES = 1048576;
|
|
37
|
+
var MAX_PAIRS_FILE_AGE_MS = 10 * 60 * 1e3;
|
|
38
|
+
var MAX_PAIRS_FILE_FUTURE_SKEW_MS = 5e3;
|
|
39
|
+
var PAIRS_FILE_RE = /^prim-post-rewrite-pairs\.[A-Za-z0-9]+$/u;
|
|
40
|
+
var FULL_SHA_SOURCE = "(?:[0-9a-fA-F]{40}|[0-9a-fA-F]{64})";
|
|
41
|
+
var REWRITE_PAIR_RE = new RegExp(
|
|
42
|
+
`^(${FULL_SHA_SOURCE}) (${FULL_SHA_SOURCE})(?: ([^\\s]+))?$`,
|
|
43
|
+
"u"
|
|
44
|
+
);
|
|
45
|
+
function parseRewritePairs(value) {
|
|
46
|
+
if (value.length === 0) return [];
|
|
47
|
+
const body = value.endsWith("\n") ? value.slice(0, -1) : value;
|
|
48
|
+
if (body.length === 0) return [];
|
|
49
|
+
const parsed = [];
|
|
50
|
+
for (const line of body.split("\n")) {
|
|
51
|
+
const match = REWRITE_PAIR_RE.exec(line);
|
|
52
|
+
if (!match?.[1] || !match[2]) return;
|
|
53
|
+
parsed.push({ oldSha: match[1], newSha: match[2] });
|
|
54
|
+
}
|
|
55
|
+
return canonicalRewritePairs(parsed) ?? void 0;
|
|
56
|
+
}
|
|
57
|
+
function privatePairsFileStat(stat, now) {
|
|
58
|
+
const capturedAt = Math.trunc(stat.mtimeMs);
|
|
59
|
+
const uid = process.getuid?.();
|
|
60
|
+
return stat.isFile() && !stat.isSymbolicLink() && stat.size <= MAX_PAIRS_FILE_BYTES && stat.nlink === 1 && (stat.mode & 511) === 384 && (uid === void 0 || stat.uid === uid) && Number.isSafeInteger(capturedAt) && capturedAt > 0 && capturedAt >= now - MAX_PAIRS_FILE_AGE_MS && capturedAt <= now + MAX_PAIRS_FILE_FUTURE_SKEW_MS;
|
|
61
|
+
}
|
|
62
|
+
function rewritePairsFromLauncher(env = process.env, now = Date.now()) {
|
|
63
|
+
const path = env.PRIM_REWRITE_PAIRS_FILE;
|
|
64
|
+
if (!path || !isAbsolute(path) || !PAIRS_FILE_RE.test(basename(path))) return;
|
|
65
|
+
let fd;
|
|
66
|
+
try {
|
|
67
|
+
const pathStat = lstatSync(path);
|
|
68
|
+
if (!privatePairsFileStat(pathStat, now)) return;
|
|
69
|
+
fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
70
|
+
const openStat = fstatSync(fd);
|
|
71
|
+
if (!privatePairsFileStat(openStat, now) || openStat.dev !== pathStat.dev || openStat.ino !== pathStat.ino) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const bytes = readFileSync(fd);
|
|
75
|
+
if (bytes.length !== openStat.size) return;
|
|
76
|
+
let text;
|
|
77
|
+
try {
|
|
78
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
79
|
+
} catch {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const pairs = parseRewritePairs(text);
|
|
83
|
+
if (!pairs) return;
|
|
84
|
+
return {
|
|
85
|
+
path,
|
|
86
|
+
dev: openStat.dev,
|
|
87
|
+
ino: openStat.ino,
|
|
88
|
+
capturedAt: Math.trunc(openStat.mtimeMs),
|
|
89
|
+
pairs
|
|
90
|
+
};
|
|
91
|
+
} catch {
|
|
92
|
+
return;
|
|
93
|
+
} finally {
|
|
94
|
+
if (fd !== void 0) {
|
|
95
|
+
try {
|
|
96
|
+
closeSync(fd);
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function unlinkSnapshot(snapshot) {
|
|
103
|
+
try {
|
|
104
|
+
const current = lstatSync(snapshot.path);
|
|
105
|
+
if (current.isFile() && !current.isSymbolicLink() && current.dev === snapshot.dev && current.ino === snapshot.ino) {
|
|
106
|
+
unlinkSync(snapshot.path);
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function gitText(args) {
|
|
112
|
+
try {
|
|
113
|
+
return execFileSync("git", args, {
|
|
114
|
+
encoding: "utf-8",
|
|
115
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
116
|
+
timeout: GIT_TIMEOUT_MS
|
|
117
|
+
}).trim();
|
|
118
|
+
} catch {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function validSource(value) {
|
|
123
|
+
return value === "amend" || value === "rebase";
|
|
124
|
+
}
|
|
125
|
+
function validBranch(value) {
|
|
126
|
+
return value && value === value.trim() && ![...value].some((char) => char < " " || char === "\x7F") ? value : void 0;
|
|
127
|
+
}
|
|
128
|
+
function resolveCliVersion() {
|
|
129
|
+
try {
|
|
130
|
+
const pkg = JSON.parse(readFileSync(join(here, "..", "..", "package.json"), "utf-8"));
|
|
131
|
+
return pkg.version ?? "unknown";
|
|
132
|
+
} catch {
|
|
133
|
+
return "unknown";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function spawnBackgroundFlush() {
|
|
137
|
+
const entry = join(here, "..", "index.js");
|
|
138
|
+
spawn(process.execPath, [entry, "moves", "flush"], {
|
|
139
|
+
detached: true,
|
|
140
|
+
stdio: "ignore"
|
|
141
|
+
}).unref();
|
|
142
|
+
}
|
|
143
|
+
function runPostRewrite() {
|
|
144
|
+
const snapshot = rewritePairsFromLauncher();
|
|
145
|
+
if (!snapshot) return;
|
|
146
|
+
try {
|
|
147
|
+
const source = process.env.PRIM_REWRITE_SOURCE;
|
|
148
|
+
if (!validSource(source) || snapshot.pairs.length === 0) return;
|
|
149
|
+
const cwd = gitText(["rev-parse", "--show-toplevel"]) ?? process.cwd();
|
|
150
|
+
if (!isRepoActiveForCapture(cwd)) return;
|
|
151
|
+
const repository = resolveRepositoryContext(cwd);
|
|
152
|
+
const identity = getOrCreateWorkspaceId(cwd);
|
|
153
|
+
const workspaceId = identity.status === "ready" ? identity.workspaceId : void 0;
|
|
154
|
+
const moves = toRewriteMove(
|
|
155
|
+
{
|
|
156
|
+
source,
|
|
157
|
+
pairs: snapshot.pairs,
|
|
158
|
+
branch: validBranch(process.env.PRIM_REWRITE_BRANCH)
|
|
159
|
+
},
|
|
160
|
+
resolveCliVersion(),
|
|
161
|
+
cwd,
|
|
162
|
+
{
|
|
163
|
+
repository,
|
|
164
|
+
repoFullName: githubRepositoryFullName(cwd) ?? void 0,
|
|
165
|
+
repoSyncId: repoSyncId(cwd),
|
|
166
|
+
workspaceId,
|
|
167
|
+
capturedAt: snapshot.capturedAt
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
if (moves.length === 0) return;
|
|
171
|
+
const { orgId } = resolveOrg({ sessionId: "", cwd });
|
|
172
|
+
for (const move of moves) appendMove(move, orgId);
|
|
173
|
+
spawnBackgroundFlush();
|
|
174
|
+
} finally {
|
|
175
|
+
unlinkSnapshot(snapshot);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (!process.env.VITEST) {
|
|
179
|
+
try {
|
|
180
|
+
runPostRewrite();
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (process.env.PRIM_HOOK_DEBUG) {
|
|
183
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
184
|
+
process.stderr.write(`[prim-post-rewrite] capture failed: ${detail}
|
|
185
|
+
`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
process.exit(0);
|
|
189
|
+
}
|
|
190
|
+
export {
|
|
191
|
+
parseRewritePairs,
|
|
192
|
+
rewritePairsFromLauncher,
|
|
193
|
+
runPostRewrite
|
|
194
|
+
};
|
|
@@ -12,20 +12,20 @@ import {
|
|
|
12
12
|
postToolInvocationId,
|
|
13
13
|
toMove,
|
|
14
14
|
toolOutcomeFor
|
|
15
|
-
} from "../chunk-
|
|
15
|
+
} from "../chunk-CPSE3Y3T.js";
|
|
16
16
|
import {
|
|
17
17
|
appendMove,
|
|
18
18
|
resolveOrg
|
|
19
19
|
} from "../chunk-HIMWM526.js";
|
|
20
|
-
import {
|
|
21
|
-
scrubFromCwd
|
|
22
|
-
} from "../chunk-G5D3FF3D.js";
|
|
23
20
|
import {
|
|
24
21
|
getOrCreateWorkspaceId
|
|
25
22
|
} from "../chunk-UVIWZWM6.js";
|
|
23
|
+
import {
|
|
24
|
+
scrubFromCwd
|
|
25
|
+
} from "../chunk-G5D3FF3D.js";
|
|
26
26
|
import {
|
|
27
27
|
warmBinCache
|
|
28
|
-
} from "../chunk-
|
|
28
|
+
} from "../chunk-CZ24W5QU.js";
|
|
29
29
|
import {
|
|
30
30
|
isRepoActiveForCapture,
|
|
31
31
|
repoSyncId,
|
package/dist/hooks/prim-hook.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
buildHookOutput,
|
|
4
|
+
handoffHookOutput
|
|
5
|
+
} from "../chunk-4GLVJLEQ.js";
|
|
6
|
+
import {
|
|
7
|
+
FEEDBACK_DEADLINE_MS,
|
|
8
|
+
acknowledgeDecisionFeedback,
|
|
9
|
+
leaseDecisionFeedback,
|
|
10
|
+
renderFeedback
|
|
11
|
+
} from "../chunk-KHZNYEUV.js";
|
|
2
12
|
import {
|
|
3
13
|
enrichHookPayloadWithFileRefs,
|
|
4
14
|
preserveHookFileMetadata
|
|
@@ -7,30 +17,20 @@ import {
|
|
|
7
17
|
postToolInvocationId,
|
|
8
18
|
shouldFlushAfter,
|
|
9
19
|
toMove
|
|
10
|
-
} from "../chunk-
|
|
20
|
+
} from "../chunk-CPSE3Y3T.js";
|
|
11
21
|
import {
|
|
12
22
|
appendMove,
|
|
13
23
|
resolveOrg
|
|
14
24
|
} from "../chunk-HIMWM526.js";
|
|
15
|
-
import {
|
|
16
|
-
scrubFromCwd
|
|
17
|
-
} from "../chunk-G5D3FF3D.js";
|
|
18
|
-
import {
|
|
19
|
-
buildHookOutput,
|
|
20
|
-
handoffHookOutput
|
|
21
|
-
} from "../chunk-4GLVJLEQ.js";
|
|
22
|
-
import {
|
|
23
|
-
FEEDBACK_DEADLINE_MS,
|
|
24
|
-
acknowledgeDecisionFeedback,
|
|
25
|
-
leaseDecisionFeedback,
|
|
26
|
-
renderFeedback
|
|
27
|
-
} from "../chunk-KHZNYEUV.js";
|
|
28
25
|
import {
|
|
29
26
|
getOrCreateWorkspaceId
|
|
30
27
|
} from "../chunk-UVIWZWM6.js";
|
|
28
|
+
import {
|
|
29
|
+
scrubFromCwd
|
|
30
|
+
} from "../chunk-G5D3FF3D.js";
|
|
31
31
|
import {
|
|
32
32
|
warmBinCache
|
|
33
|
-
} from "../chunk-
|
|
33
|
+
} from "../chunk-CZ24W5QU.js";
|
|
34
34
|
import {
|
|
35
35
|
isRepoActiveForCapture,
|
|
36
36
|
repoSyncId,
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
bindRepository,
|
|
4
4
|
ensureEffectivePostCommitHook,
|
|
5
|
+
ensureEffectivePostRewriteHook,
|
|
5
6
|
hasUsableCodexGuidance,
|
|
6
7
|
refreshClaudePlugins
|
|
7
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-DR66XM5L.js";
|
|
8
9
|
import {
|
|
9
10
|
buildHookOutput,
|
|
10
11
|
handoffHookOutput
|
|
@@ -21,7 +22,7 @@ import {
|
|
|
21
22
|
import {
|
|
22
23
|
binFile,
|
|
23
24
|
warmBinCache
|
|
24
|
-
} from "../chunk-
|
|
25
|
+
} from "../chunk-CZ24W5QU.js";
|
|
25
26
|
import {
|
|
26
27
|
gitToplevel,
|
|
27
28
|
isRepoActiveForCapture,
|
|
@@ -100,6 +101,10 @@ async function activeProjectRoot(cwd) {
|
|
|
100
101
|
ensureEffectivePostCommitHook(root);
|
|
101
102
|
} catch {
|
|
102
103
|
}
|
|
104
|
+
try {
|
|
105
|
+
ensureEffectivePostRewriteHook(root);
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
103
108
|
}
|
|
104
109
|
try {
|
|
105
110
|
await bindRepository(root, {
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
bindRepository,
|
|
4
|
+
compareSemver,
|
|
5
|
+
ensureEffectivePostCommitHook,
|
|
6
|
+
ensureEffectivePostRewriteHook,
|
|
7
|
+
ensurePostCommitHookAtPath,
|
|
8
|
+
ensurePostRewriteHookAtPath,
|
|
9
|
+
inspectEffectivePostCommitHook,
|
|
10
|
+
inspectEffectivePostRewriteHook,
|
|
11
|
+
postCommitHookBlock,
|
|
12
|
+
postRewriteHookBlock,
|
|
13
|
+
printJson,
|
|
14
|
+
registerSkillCommands,
|
|
15
|
+
resolveRepositoryBinding,
|
|
16
|
+
uninstallPostCommitHookAtPath,
|
|
17
|
+
uninstallPostRewriteHookAtPath,
|
|
18
|
+
uninstallProjectPostCommitHook,
|
|
19
|
+
uninstallProjectPostRewriteHook
|
|
20
|
+
} from "./chunk-DR66XM5L.js";
|
|
2
21
|
import {
|
|
3
22
|
bold,
|
|
4
23
|
boundedHealthError,
|
|
@@ -14,6 +33,9 @@ import {
|
|
|
14
33
|
daemonOrDirectGet,
|
|
15
34
|
formatDecisionsWarning
|
|
16
35
|
} from "./chunk-WS3MCNFD.js";
|
|
36
|
+
import {
|
|
37
|
+
fetchFeedbackCapability
|
|
38
|
+
} from "./chunk-KHZNYEUV.js";
|
|
17
39
|
import {
|
|
18
40
|
JOURNAL_DIR,
|
|
19
41
|
SESSIONS_DIR,
|
|
@@ -23,22 +45,6 @@ import {
|
|
|
23
45
|
pendingJournalStats,
|
|
24
46
|
readMovesFromPath
|
|
25
47
|
} from "./chunk-HIMWM526.js";
|
|
26
|
-
import {
|
|
27
|
-
bindRepository,
|
|
28
|
-
compareSemver,
|
|
29
|
-
ensureEffectivePostCommitHook,
|
|
30
|
-
ensurePostCommitHookAtPath,
|
|
31
|
-
inspectEffectivePostCommitHook,
|
|
32
|
-
postCommitHookBlock,
|
|
33
|
-
printJson,
|
|
34
|
-
registerSkillCommands,
|
|
35
|
-
resolveRepositoryBinding,
|
|
36
|
-
uninstallPostCommitHookAtPath,
|
|
37
|
-
uninstallProjectPostCommitHook
|
|
38
|
-
} from "./chunk-CPHJUQNE.js";
|
|
39
|
-
import {
|
|
40
|
-
fetchFeedbackCapability
|
|
41
|
-
} from "./chunk-KHZNYEUV.js";
|
|
42
48
|
import {
|
|
43
49
|
inspectWorkspaceId
|
|
44
50
|
} from "./chunk-UVIWZWM6.js";
|
|
@@ -49,7 +55,7 @@ import {
|
|
|
49
55
|
packageVersion,
|
|
50
56
|
pinnedHookCommand,
|
|
51
57
|
warmBinCache
|
|
52
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-CZ24W5QU.js";
|
|
53
59
|
import {
|
|
54
60
|
canonicalGitRoot,
|
|
55
61
|
canonicalRepositoryPath,
|
|
@@ -101,8 +107,16 @@ async function applyActivation(active) {
|
|
|
101
107
|
try {
|
|
102
108
|
let binding;
|
|
103
109
|
let postCommitHook;
|
|
110
|
+
let postRewriteHook;
|
|
104
111
|
if (active) {
|
|
105
112
|
postCommitHook = ensureEffectivePostCommitHook(root).path;
|
|
113
|
+
try {
|
|
114
|
+
postRewriteHook = ensureEffectivePostRewriteHook(root).path;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
117
|
+
process.stderr.write(`[prim] post-rewrite hook coverage is degraded: ${detail}
|
|
118
|
+
`);
|
|
119
|
+
}
|
|
106
120
|
phase = "repository binding";
|
|
107
121
|
binding = await bindRepository(root);
|
|
108
122
|
phase = "local activation";
|
|
@@ -132,7 +146,8 @@ async function applyActivation(active) {
|
|
|
132
146
|
repositoryFullName: binding.repositoryFullName,
|
|
133
147
|
...binding.status === "connected" ? { repoSyncId: binding.repoSyncId } : {}
|
|
134
148
|
} : {},
|
|
135
|
-
...postCommitHook ? { postCommitHook } : {}
|
|
149
|
+
...postCommitHook ? { postCommitHook } : {},
|
|
150
|
+
...postRewriteHook ? { postRewriteHook } : {}
|
|
136
151
|
});
|
|
137
152
|
} catch (err) {
|
|
138
153
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -4004,27 +4019,27 @@ async function checkRepositoryBinding() {
|
|
|
4004
4019
|
};
|
|
4005
4020
|
}
|
|
4006
4021
|
}
|
|
4007
|
-
function
|
|
4022
|
+
function classifyManagedHook(hookName, inspection) {
|
|
4008
4023
|
if (inspection.covered) {
|
|
4009
4024
|
return {
|
|
4010
|
-
name:
|
|
4025
|
+
name: hookName,
|
|
4011
4026
|
status: "ok",
|
|
4012
4027
|
detail: `effective and executable \xB7 ${inspection.kind} \xB7 ${inspection.hookPath}`
|
|
4013
4028
|
};
|
|
4014
4029
|
}
|
|
4015
4030
|
return {
|
|
4016
|
-
name:
|
|
4031
|
+
name: hookName,
|
|
4017
4032
|
status: "fail",
|
|
4018
4033
|
detail: `${inspection.reason ?? "uncovered"} \xB7 ${inspection.hookPath}`
|
|
4019
4034
|
};
|
|
4020
4035
|
}
|
|
4021
|
-
function
|
|
4036
|
+
function checkManagedHook(hookName, inspect) {
|
|
4022
4037
|
try {
|
|
4023
|
-
return
|
|
4038
|
+
return classifyManagedHook(hookName, inspect());
|
|
4024
4039
|
} catch (error) {
|
|
4025
4040
|
const detail = error instanceof Error ? error.message : String(error);
|
|
4026
4041
|
return {
|
|
4027
|
-
name:
|
|
4042
|
+
name: hookName,
|
|
4028
4043
|
status: /not a git repository/iu.test(detail) ? "warn" : "fail",
|
|
4029
4044
|
detail: detail.slice(0, 120)
|
|
4030
4045
|
};
|
|
@@ -4150,7 +4165,8 @@ async function collectChecks() {
|
|
|
4150
4165
|
checkFeedbackHooks(),
|
|
4151
4166
|
checkWorkspaceIdentity(),
|
|
4152
4167
|
await checkRepositoryBinding(),
|
|
4153
|
-
|
|
4168
|
+
checkManagedHook("post-commit", inspectEffectivePostCommitHook),
|
|
4169
|
+
checkManagedHook("post-rewrite", inspectEffectivePostRewriteHook),
|
|
4154
4170
|
...backend,
|
|
4155
4171
|
await checkFeedbackCapability()
|
|
4156
4172
|
];
|
|
@@ -4534,7 +4550,8 @@ import { dirname as dirname4, join as join7, resolve as resolve2 } from "path";
|
|
|
4534
4550
|
import { Option as Option3 } from "commander";
|
|
4535
4551
|
var PRE_COMMIT = { hookName: "pre-commit", binName: "prim-pre-commit" };
|
|
4536
4552
|
var POST_COMMIT = { hookName: "post-commit", binName: "prim-post-commit" };
|
|
4537
|
-
var
|
|
4553
|
+
var POST_REWRITE = { hookName: "post-rewrite", binName: "prim-post-rewrite" };
|
|
4554
|
+
var HOOKS = [PRE_COMMIT, POST_COMMIT, POST_REWRITE];
|
|
4538
4555
|
var GIT_TIMEOUT_MS = 1e3;
|
|
4539
4556
|
function blockMarkers(spec) {
|
|
4540
4557
|
return {
|
|
@@ -4563,6 +4580,7 @@ ${hookShim(spec.binName)}
|
|
|
4563
4580
|
}
|
|
4564
4581
|
function huskyBlock(spec) {
|
|
4565
4582
|
if (spec.hookName === POST_COMMIT.hookName) return postCommitHookBlock();
|
|
4583
|
+
if (spec.hookName === POST_REWRITE.hookName) return postRewriteHookBlock();
|
|
4566
4584
|
const { start, end } = blockMarkers(spec);
|
|
4567
4585
|
return `${start}
|
|
4568
4586
|
${hookShim(spec.binName)}
|
|
@@ -4570,6 +4588,7 @@ ${end}`;
|
|
|
4570
4588
|
}
|
|
4571
4589
|
function gatedBlock(spec) {
|
|
4572
4590
|
if (spec.hookName === POST_COMMIT.hookName) return postCommitHookBlock();
|
|
4591
|
+
if (spec.hookName === POST_REWRITE.hookName) return postRewriteHookBlock();
|
|
4573
4592
|
const { start, end } = blockMarkers(spec);
|
|
4574
4593
|
return `${start}
|
|
4575
4594
|
${gatedShim(spec.binName)}
|
|
@@ -4707,7 +4726,6 @@ var PASSTHROUGH_HOOKS = [
|
|
|
4707
4726
|
"post-checkout",
|
|
4708
4727
|
"post-merge",
|
|
4709
4728
|
"pre-push",
|
|
4710
|
-
"post-rewrite",
|
|
4711
4729
|
"pre-auto-gc",
|
|
4712
4730
|
"push-to-checkout",
|
|
4713
4731
|
"sendemail-validate",
|
|
@@ -4717,11 +4735,13 @@ var PASSTHROUGH_HOOKS = [
|
|
|
4717
4735
|
// with a query protocol, not a lifecycle event, so a bare exec stub is wrong.)
|
|
4718
4736
|
];
|
|
4719
4737
|
function globalHookScript(spec) {
|
|
4720
|
-
const chainExit = spec.hookName ===
|
|
4721
|
-
const
|
|
4722
|
-
const
|
|
4738
|
+
const chainExit = spec.hookName === PRE_COMMIT.hookName ? "|| exit $?" : "|| true";
|
|
4739
|
+
const managedBlock = spec.hookName === POST_COMMIT.hookName ? postCommitHookBlock() : spec.hookName === POST_REWRITE.hookName ? postRewriteHookBlock() : void 0;
|
|
4740
|
+
const invocation = managedBlock ?? gatedShim(spec.binName);
|
|
4741
|
+
const managedRepoGuard = managedBlock ? ` && ! grep -Fq '${blockMarkers(spec).start}' "$repo_hook" 2>/dev/null` : "";
|
|
4742
|
+
const beforeComments = managedBlock ? `${invocation}
|
|
4723
4743
|
` : "";
|
|
4724
|
-
const afterComments =
|
|
4744
|
+
const afterComments = managedBlock ? "" : `${invocation}
|
|
4725
4745
|
`;
|
|
4726
4746
|
return `#!/bin/sh
|
|
4727
4747
|
${beforeComments}# prim global ${spec.hookName} hook (core.hooksPath) \u2014 managed by prim; do not edit.
|
|
@@ -4731,7 +4751,7 @@ ${beforeComments}# prim global ${spec.hookName} hook (core.hooksPath) \u2014 man
|
|
|
4731
4751
|
# hook regardless, so inactive repos are unaffected.
|
|
4732
4752
|
${afterComments}common_dir=$(git rev-parse --git-common-dir 2>/dev/null) || exit 0
|
|
4733
4753
|
repo_hook="$common_dir/hooks/${spec.hookName}"
|
|
4734
|
-
if [ -x "$repo_hook" ] && ! grep -q '${PRIM_MANAGED_MARK}' "$repo_hook" 2>/dev/null; then
|
|
4754
|
+
if [ -x "$repo_hook" ] && ! grep -q '${PRIM_MANAGED_MARK}' "$repo_hook" 2>/dev/null${managedRepoGuard}; then
|
|
4735
4755
|
"$repo_hook" "$@" ${chainExit}
|
|
4736
4756
|
fi
|
|
4737
4757
|
exit 0
|
|
@@ -4757,6 +4777,8 @@ function writeOwnHooks() {
|
|
|
4757
4777
|
const path = resolve2(PRIM_GIT_HOOKS_DIR, spec.hookName);
|
|
4758
4778
|
if (spec.hookName === POST_COMMIT.hookName) {
|
|
4759
4779
|
ensurePostCommitHookAtPath(path, globalHookScript(spec));
|
|
4780
|
+
} else if (spec.hookName === POST_REWRITE.hookName) {
|
|
4781
|
+
writeFileSync4(path, globalHookScript(spec), { mode: 493 });
|
|
4760
4782
|
} else {
|
|
4761
4783
|
writeFileSync4(path, globalHookScript(spec), { mode: 493 });
|
|
4762
4784
|
}
|
|
@@ -4770,6 +4792,10 @@ function appendPrimBlock(hookPath, spec) {
|
|
|
4770
4792
|
ensurePostCommitHookAtPath(hookPath);
|
|
4771
4793
|
return;
|
|
4772
4794
|
}
|
|
4795
|
+
if (spec.hookName === POST_REWRITE.hookName) {
|
|
4796
|
+
ensurePostRewriteHookAtPath(hookPath);
|
|
4797
|
+
return;
|
|
4798
|
+
}
|
|
4773
4799
|
mergePrimBlock(hookPath, gatedBlock(spec), spec.binName);
|
|
4774
4800
|
}
|
|
4775
4801
|
function stripPrimBlock(hookPath, spec) {
|
|
@@ -4777,6 +4803,10 @@ function stripPrimBlock(hookPath, spec) {
|
|
|
4777
4803
|
uninstallPostCommitHookAtPath(hookPath);
|
|
4778
4804
|
return;
|
|
4779
4805
|
}
|
|
4806
|
+
if (spec.hookName === POST_REWRITE.hookName) {
|
|
4807
|
+
uninstallPostRewriteHookAtPath(hookPath);
|
|
4808
|
+
return;
|
|
4809
|
+
}
|
|
4780
4810
|
if (!existsSync7(hookPath)) return;
|
|
4781
4811
|
const existing = readFileSync6(hookPath, "utf-8");
|
|
4782
4812
|
const primCreated = existing.includes(PRIM_CREATED_MARK);
|
|
@@ -4856,22 +4886,29 @@ function uninstallGlobalHooks() {
|
|
|
4856
4886
|
console.log("No prim global git hooks found.");
|
|
4857
4887
|
}
|
|
4858
4888
|
function installHooks(gitRoot, target) {
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
4862
|
-
|
|
4863
|
-
installToDotGit(gitRoot, spec);
|
|
4864
|
-
}
|
|
4889
|
+
if (target === "husky") {
|
|
4890
|
+
installToHusky(gitRoot, PRE_COMMIT);
|
|
4891
|
+
} else {
|
|
4892
|
+
installToDotGit(gitRoot, PRE_COMMIT);
|
|
4865
4893
|
}
|
|
4866
4894
|
const postCommit = ensureEffectivePostCommitHook(gitRoot);
|
|
4867
4895
|
console.log(
|
|
4868
4896
|
`${postCommit.changed ? "Installed" : "Refreshed"} effective post-commit hook at ${postCommit.path}.`
|
|
4869
4897
|
);
|
|
4898
|
+
try {
|
|
4899
|
+
const postRewrite = ensureEffectivePostRewriteHook(gitRoot);
|
|
4900
|
+
console.log(
|
|
4901
|
+
`${postRewrite.changed ? "Installed" : "Refreshed"} effective post-rewrite hook at ${postRewrite.path}.`
|
|
4902
|
+
);
|
|
4903
|
+
} catch (error) {
|
|
4904
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
4905
|
+
console.error(`[prim] post-rewrite hook coverage is degraded: ${detail}`);
|
|
4906
|
+
}
|
|
4870
4907
|
}
|
|
4871
4908
|
function registerHooksCommands(program2) {
|
|
4872
4909
|
const hooks = program2.command("hooks").description("Manage git hooks");
|
|
4873
4910
|
hooks.command("install").description(
|
|
4874
|
-
"Install the prim git hooks \u2014 pre-commit + post-commit (auto-detects Husky; use --target to override)"
|
|
4911
|
+
"Install the prim git hooks \u2014 pre-commit + post-commit + post-rewrite (auto-detects Husky; use --target to override)"
|
|
4875
4912
|
).addOption(
|
|
4876
4913
|
new Option3("--target <where>", "install destination; bypasses Husky detection").choices([
|
|
4877
4914
|
"husky",
|
|
@@ -4929,27 +4966,30 @@ function registerHooksCommands(program2) {
|
|
|
4929
4966
|
}
|
|
4930
4967
|
const gitRoot = getGitRoot();
|
|
4931
4968
|
const hooksDir = projectHooksDir(gitRoot);
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
}
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4969
|
+
const preCommitPath = resolve2(hooksDir, PRE_COMMIT.hookName);
|
|
4970
|
+
if (!existsSync7(preCommitPath)) {
|
|
4971
|
+
console.log(`No ${PRE_COMMIT.hookName} hook found.`);
|
|
4972
|
+
} else if (containsPrimHook(readFileSync6(preCommitPath, "utf-8"), PRE_COMMIT.binName)) {
|
|
4973
|
+
unlinkSync2(preCommitPath);
|
|
4974
|
+
console.log(`Removed ${PRE_COMMIT.hookName} hook at ${preCommitPath}`);
|
|
4975
|
+
} else {
|
|
4976
|
+
console.log(
|
|
4977
|
+
`Left ${PRE_COMMIT.hookName} hook at ${preCommitPath} untouched (not a prim hook).`
|
|
4978
|
+
);
|
|
4979
|
+
}
|
|
4980
|
+
for (const [hookName, uninstall] of [
|
|
4981
|
+
[POST_COMMIT.hookName, uninstallProjectPostCommitHook],
|
|
4982
|
+
[POST_REWRITE.hookName, uninstallProjectPostRewriteHook]
|
|
4983
|
+
]) {
|
|
4984
|
+
const result = uninstall(gitRoot);
|
|
4985
|
+
if (!result.changed) {
|
|
4986
|
+
console.log(`No Prim ${hookName} block found at ${result.path}.`);
|
|
4987
|
+
} else if (result.removedFile) {
|
|
4988
|
+
console.log(`Removed Prim-created ${hookName} hook at ${result.path}.`);
|
|
4941
4989
|
} else {
|
|
4942
|
-
console.log(`
|
|
4990
|
+
console.log(`Removed the Prim ${hookName} block from ${result.path}.`);
|
|
4943
4991
|
}
|
|
4944
4992
|
}
|
|
4945
|
-
const postCommit = uninstallProjectPostCommitHook(gitRoot);
|
|
4946
|
-
if (!postCommit.changed) {
|
|
4947
|
-
console.log(`No Prim post-commit block found at ${postCommit.path}.`);
|
|
4948
|
-
} else if (postCommit.removedFile) {
|
|
4949
|
-
console.log(`Removed Prim-created post-commit hook at ${postCommit.path}.`);
|
|
4950
|
-
} else {
|
|
4951
|
-
console.log(`Removed the Prim post-commit block from ${postCommit.path}.`);
|
|
4952
|
-
}
|
|
4953
4993
|
});
|
|
4954
4994
|
}
|
|
4955
4995
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primitive.ai/prim",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.66",
|
|
4
4
|
"description": "CLI for Primitive's decision graph — passive decision capture, conflict gate, and team presence",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"prim": "dist/index.js",
|
|
32
32
|
"prim-pre-commit": "dist/hooks/pre-commit.js",
|
|
33
33
|
"prim-post-commit": "dist/hooks/post-commit.js",
|
|
34
|
+
"prim-post-rewrite": "dist/hooks/post-rewrite.js",
|
|
34
35
|
"prim-hook": "dist/hooks/prim-hook.js",
|
|
35
36
|
"prim-pre-tool-use": "dist/hooks/pre-tool-use.js",
|
|
36
37
|
"prim-post-tool-use": "dist/hooks/post-tool-use.js",
|
|
@@ -47,9 +48,9 @@
|
|
|
47
48
|
"SKILL.md"
|
|
48
49
|
],
|
|
49
50
|
"scripts": {
|
|
50
|
-
"build": "tsup src/index.ts src/hooks/pre-commit.ts src/hooks/post-commit.ts src/hooks/prim-hook.ts src/hooks/pre-tool-use.ts src/hooks/post-tool-use.ts src/hooks/session-start.ts src/hooks/session-end.ts --format esm --clean && tsup src/statusline-main.ts src/daemon/server.ts --format esm --splitting false",
|
|
51
|
-
"postbuild": "chmod +x ./dist/index.js ./dist/statusline-main.js ./dist/hooks/pre-commit.js ./dist/hooks/post-commit.js ./dist/hooks/prim-hook.js ./dist/hooks/pre-tool-use.js ./dist/hooks/post-tool-use.js ./dist/hooks/session-start.js ./dist/hooks/session-end.js ./dist/daemon/server.js",
|
|
52
|
-
"dev": "tsup src/index.ts src/statusline-main.ts src/hooks/pre-commit.ts src/hooks/post-commit.ts src/hooks/prim-hook.ts src/hooks/pre-tool-use.ts src/hooks/post-tool-use.ts src/hooks/session-start.ts src/hooks/session-end.ts src/daemon/server.ts --format esm --watch --clean --splitting false",
|
|
51
|
+
"build": "tsup src/index.ts src/hooks/pre-commit.ts src/hooks/post-commit.ts src/hooks/post-rewrite.ts src/hooks/prim-hook.ts src/hooks/pre-tool-use.ts src/hooks/post-tool-use.ts src/hooks/session-start.ts src/hooks/session-end.ts --format esm --clean && tsup src/statusline-main.ts src/daemon/server.ts --format esm --splitting false",
|
|
52
|
+
"postbuild": "chmod +x ./dist/index.js ./dist/statusline-main.js ./dist/hooks/pre-commit.js ./dist/hooks/post-commit.js ./dist/hooks/post-rewrite.js ./dist/hooks/prim-hook.js ./dist/hooks/pre-tool-use.js ./dist/hooks/post-tool-use.js ./dist/hooks/session-start.js ./dist/hooks/session-end.js ./dist/daemon/server.js",
|
|
53
|
+
"dev": "tsup src/index.ts src/statusline-main.ts src/hooks/pre-commit.ts src/hooks/post-commit.ts src/hooks/post-rewrite.ts src/hooks/prim-hook.ts src/hooks/pre-tool-use.ts src/hooks/post-tool-use.ts src/hooks/session-start.ts src/hooks/session-end.ts src/daemon/server.ts --format esm --watch --clean --splitting false",
|
|
53
54
|
"clean": "rm -rf dist coverage",
|
|
54
55
|
"lint": "biome check src/",
|
|
55
56
|
"format": "biome check --fix src/",
|