@primitive.ai/prim 0.1.0-alpha.18 → 0.1.0-alpha.20
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 +80 -37
- package/SKILL.md +40 -183
- package/dist/{chunk-6SIEWWUL.js → chunk-26VA3ADF.js} +1 -3
- package/dist/{chunk-TPQ3X244.js → chunk-E5UZXMZL.js} +2 -31
- package/dist/daemon/server.js +1 -1
- package/dist/hooks/post-tool-use.js +1 -1
- package/dist/hooks/pre-commit.js +4 -157
- package/dist/hooks/pre-tool-use.js +1 -1
- package/dist/index.js +252 -567
- package/package.json +4 -4
package/dist/hooks/pre-commit.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
checkAffectedDecisions,
|
|
4
|
-
formatDecisionsWarning
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
getClient
|
|
9
|
-
} from "../chunk-6SIEWWUL.js";
|
|
4
|
+
formatDecisionsWarning
|
|
5
|
+
} from "../chunk-E5UZXMZL.js";
|
|
6
|
+
import "../chunk-26VA3ADF.js";
|
|
10
7
|
import "../chunk-UTKQTZHL.js";
|
|
11
8
|
|
|
12
9
|
// src/hooks/pre-commit.ts
|
|
@@ -17,150 +14,6 @@ function getStagedFiles() {
|
|
|
17
14
|
});
|
|
18
15
|
return output.trim().split("\n").filter((f) => f.length > 0);
|
|
19
16
|
}
|
|
20
|
-
function getStagedDiff(files) {
|
|
21
|
-
return execSync(`git diff --cached -- ${files.map((f) => `"${f}"`).join(" ")}`, {
|
|
22
|
-
encoding: "utf-8"
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
function matchPattern(filePath, pattern) {
|
|
26
|
-
const regexStr = pattern.replaceAll("**", "\xA7GLOBSTAR\xA7").replaceAll("*", "[^/]*").replaceAll("\xA7GLOBSTAR\xA7", ".*");
|
|
27
|
-
const regex = new RegExp(`^${regexStr}$`);
|
|
28
|
-
return regex.test(filePath);
|
|
29
|
-
}
|
|
30
|
-
function findAffectedContexts(stagedFiles, specs) {
|
|
31
|
-
const affected = /* @__PURE__ */ new Map();
|
|
32
|
-
for (const file of stagedFiles) {
|
|
33
|
-
for (const spec of specs) {
|
|
34
|
-
for (const pattern of spec.filePatterns) {
|
|
35
|
-
if (matchPattern(file, pattern)) {
|
|
36
|
-
const existing = affected.get(spec._id);
|
|
37
|
-
if (existing) {
|
|
38
|
-
existing.matchedFiles.push(file);
|
|
39
|
-
} else {
|
|
40
|
-
affected.set(spec._id, {
|
|
41
|
-
contextId: spec._id,
|
|
42
|
-
matchedFiles: [file]
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
break;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return affected;
|
|
51
|
-
}
|
|
52
|
-
var HOOK_TIMEOUT_MS = 1e4;
|
|
53
|
-
var defaultDeps = {
|
|
54
|
-
getClient,
|
|
55
|
-
getStagedFiles,
|
|
56
|
-
getStagedDiff,
|
|
57
|
-
getGitContext
|
|
58
|
-
};
|
|
59
|
-
async function syncAffectedSpecs(deps = defaultDeps) {
|
|
60
|
-
const stagedFiles = deps.getStagedFiles();
|
|
61
|
-
if (stagedFiles.length === 0) {
|
|
62
|
-
return [];
|
|
63
|
-
}
|
|
64
|
-
const client = deps.getClient();
|
|
65
|
-
const gitCtx = deps.getGitContext();
|
|
66
|
-
let mappingsUrl = "/api/cli/specs/mappings";
|
|
67
|
-
if (gitCtx.repoFullName && gitCtx.branch) {
|
|
68
|
-
const params = new URLSearchParams({
|
|
69
|
-
repoFullName: gitCtx.repoFullName,
|
|
70
|
-
branch: gitCtx.branch
|
|
71
|
-
});
|
|
72
|
-
mappingsUrl = `${mappingsUrl}?${params.toString()}`;
|
|
73
|
-
}
|
|
74
|
-
let mappings = [];
|
|
75
|
-
try {
|
|
76
|
-
mappings = await client.get(mappingsUrl, {
|
|
77
|
-
signal: AbortSignal.timeout(HOOK_TIMEOUT_MS)
|
|
78
|
-
});
|
|
79
|
-
} catch {
|
|
80
|
-
return [];
|
|
81
|
-
}
|
|
82
|
-
if (mappings.length === 0) {
|
|
83
|
-
return [];
|
|
84
|
-
}
|
|
85
|
-
const specsById = new Map(mappings.map((s) => [s._id, s]));
|
|
86
|
-
const affectedContexts = findAffectedContexts(stagedFiles, mappings);
|
|
87
|
-
if (affectedContexts.size === 0) {
|
|
88
|
-
return [];
|
|
89
|
-
}
|
|
90
|
-
console.log(`[prim] ${String(affectedContexts.size)} spec(s) affected by staged changes:`);
|
|
91
|
-
const synced = [];
|
|
92
|
-
for (const [contextId, affected] of affectedContexts) {
|
|
93
|
-
try {
|
|
94
|
-
const ctx = await client.get(`/api/cli/contexts/${contextId}`, {
|
|
95
|
-
signal: AbortSignal.timeout(HOOK_TIMEOUT_MS)
|
|
96
|
-
});
|
|
97
|
-
if (!ctx._id) {
|
|
98
|
-
console.log(` [skip] ${contextId} \u2014 not found`);
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
if (!ctx.isSpecDocument) {
|
|
102
|
-
console.log(` [skip] ${contextId} \u2014 not a spec document`);
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
const diffContent = deps.getStagedDiff(affected.matchedFiles);
|
|
106
|
-
if (!diffContent) {
|
|
107
|
-
console.log(` [skip] ${contextId} \u2014 no diff content`);
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
const body = {
|
|
111
|
-
diffContent,
|
|
112
|
-
affectedFiles: affected.matchedFiles
|
|
113
|
-
};
|
|
114
|
-
if (gitCtx.branch) body.branch = gitCtx.branch;
|
|
115
|
-
if (gitCtx.sha) body.sha = gitCtx.sha;
|
|
116
|
-
if (gitCtx.repoFullName) body.repoFullName = gitCtx.repoFullName;
|
|
117
|
-
if (gitCtx.prNumber !== null) body.prNumber = gitCtx.prNumber;
|
|
118
|
-
const response = await client.post(`/api/cli/contexts/${contextId}/sync-diff`, body, {
|
|
119
|
-
signal: AbortSignal.timeout(HOOK_TIMEOUT_MS)
|
|
120
|
-
});
|
|
121
|
-
const name = ctx.name ?? "(unnamed)";
|
|
122
|
-
if (!response.analyzing && response.reason === "not_linked") {
|
|
123
|
-
console.log(
|
|
124
|
-
` [skip] ${contextId} \u2014 ${name} \u2014 not linked to ${gitCtx.branch ?? "(no branch)"}`
|
|
125
|
-
);
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
const spec = specsById.get(contextId);
|
|
129
|
-
const link = spec?.linkedBranches?.find((l) => l.branch === gitCtx.branch);
|
|
130
|
-
let linkSuffix = "";
|
|
131
|
-
if (link) {
|
|
132
|
-
const prBits = link.prNumber ? ` #${String(link.prNumber)}${link.prState ? ` ${link.prState}` : ""}` : "";
|
|
133
|
-
linkSuffix = ` (linked to ${link.branch}${prBits})`;
|
|
134
|
-
} else if (gitCtx.branch && spec?.linkedBranches?.length === 0) {
|
|
135
|
-
linkSuffix = ` (auto-linking to ${gitCtx.branch})`;
|
|
136
|
-
}
|
|
137
|
-
const review = link?.latestReviewSummary;
|
|
138
|
-
let reviewSuffix = "";
|
|
139
|
-
if (review?.status === "completed") {
|
|
140
|
-
const n = review.findingsCount ?? 0;
|
|
141
|
-
const urlSuffix = review.prCommentUrl ? ` \u2192 ${review.prCommentUrl.replace(/^https?:\/\//, "")}` : "";
|
|
142
|
-
reviewSuffix = ` (reviewed: ${String(n)} finding${n === 1 ? "" : "s"}${urlSuffix})`;
|
|
143
|
-
} else if (review?.status === "failed") {
|
|
144
|
-
reviewSuffix = " (review failed)";
|
|
145
|
-
}
|
|
146
|
-
linkSuffix += reviewSuffix;
|
|
147
|
-
if (response.truncated && response.sizeChars && response.limitChars) {
|
|
148
|
-
const sizeKiB = Math.round(response.sizeChars / 1024);
|
|
149
|
-
const limitKiB = Math.round(response.limitChars / 1024);
|
|
150
|
-
console.log(
|
|
151
|
-
` [synced] ${contextId} \u2014 ${name} (truncated: ${String(sizeKiB)} KiB \u2192 ${String(limitKiB)} KiB analyzed)${linkSuffix}`
|
|
152
|
-
);
|
|
153
|
-
} else {
|
|
154
|
-
console.log(` [synced] ${contextId} \u2014 ${name}${linkSuffix}`);
|
|
155
|
-
}
|
|
156
|
-
synced.push(contextId);
|
|
157
|
-
} catch (error) {
|
|
158
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
159
|
-
console.error(` [error] ${contextId} \u2014 ${message}`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return synced;
|
|
163
|
-
}
|
|
164
17
|
async function runDecisionsCheck() {
|
|
165
18
|
const stagedFiles = getStagedFiles();
|
|
166
19
|
if (stagedFiles.length === 0) {
|
|
@@ -169,7 +22,7 @@ async function runDecisionsCheck() {
|
|
|
169
22
|
return checkAffectedDecisions(stagedFiles);
|
|
170
23
|
}
|
|
171
24
|
async function main() {
|
|
172
|
-
const
|
|
25
|
+
const decisionsResult = await runDecisionsCheck();
|
|
173
26
|
const warning = formatDecisionsWarning(decisionsResult);
|
|
174
27
|
if (warning) {
|
|
175
28
|
console.error(warning);
|
|
@@ -182,9 +35,3 @@ if (!process.env.VITEST) {
|
|
|
182
35
|
process.exit(0);
|
|
183
36
|
});
|
|
184
37
|
}
|
|
185
|
-
export {
|
|
186
|
-
HOOK_TIMEOUT_MS,
|
|
187
|
-
findAffectedContexts,
|
|
188
|
-
matchPattern,
|
|
189
|
-
syncAffectedSpecs
|
|
190
|
-
};
|