@sniper.ai/cli 3.0.0 → 3.1.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 +1 -1
- package/dist/index.js +64 -5
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ CLI tool for scaffolding and managing [SNIPER](https://sniperai.dev/)-enabled pr
|
|
|
7
7
|
|
|
8
8
|
## What is SNIPER?
|
|
9
9
|
|
|
10
|
-
SNIPER
|
|
10
|
+
SNIPER is an AI-powered project lifecycle framework that orchestrates Claude Code agent teams through structured phases -- from discovery and planning through implementation and release. Each phase spawns coordinated teams of specialized agents composed from layered personas.
|
|
11
11
|
|
|
12
12
|
## Quick Start
|
|
13
13
|
|
package/dist/index.js
CHANGED
|
@@ -152,6 +152,12 @@ async function composeMixin(basePath, mixinPaths) {
|
|
|
152
152
|
}
|
|
153
153
|
return content;
|
|
154
154
|
}
|
|
155
|
+
function stableStringify(obj) {
|
|
156
|
+
if (obj === null || obj === void 0 || typeof obj !== "object") return JSON.stringify(obj ?? null);
|
|
157
|
+
if (Array.isArray(obj)) return "[" + obj.map(stableStringify).join(",") + "]";
|
|
158
|
+
const sorted = Object.keys(obj).sort();
|
|
159
|
+
return "{" + sorted.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
|
|
160
|
+
}
|
|
155
161
|
function mergeHooks(base, ...sources) {
|
|
156
162
|
const result = { ...base };
|
|
157
163
|
if (!result.hooks || typeof result.hooks !== "object") {
|
|
@@ -164,11 +170,22 @@ function mergeHooks(base, ...sources) {
|
|
|
164
170
|
if (!Array.isArray(entries)) continue;
|
|
165
171
|
if (!hooks[event]) hooks[event] = [];
|
|
166
172
|
for (const entry of entries) {
|
|
167
|
-
const
|
|
173
|
+
const typedEntry = entry;
|
|
174
|
+
const matcherKey = stableStringify(typedEntry.matcher || {});
|
|
168
175
|
const existing = hooks[event].find(
|
|
169
|
-
(h) => h.
|
|
176
|
+
(h) => stableStringify(h.matcher || {}) === matcherKey
|
|
170
177
|
);
|
|
171
|
-
if (
|
|
178
|
+
if (existing) {
|
|
179
|
+
const existingHooks = existing.hooks || [];
|
|
180
|
+
const newHooks = typedEntry.hooks || [];
|
|
181
|
+
for (const hook of newHooks) {
|
|
182
|
+
const alreadyExists = existingHooks.some((h) => h.description === hook.description);
|
|
183
|
+
if (!alreadyExists) {
|
|
184
|
+
existingHooks.push(hook);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
existing.hooks = existingHooks;
|
|
188
|
+
} else {
|
|
172
189
|
hooks[event].push(entry);
|
|
173
190
|
}
|
|
174
191
|
}
|
|
@@ -258,6 +275,43 @@ async function scaffoldProject(cwd, config, options = {}) {
|
|
|
258
275
|
const coreHooks = JSON.parse(await readFile2(coreHooksPath, "utf-8"));
|
|
259
276
|
settings = mergeHooks(settings, coreHooks);
|
|
260
277
|
}
|
|
278
|
+
const signalHooksPath = join2(corePath, "hooks", "signal-hooks.json");
|
|
279
|
+
if (await fileExists(signalHooksPath)) {
|
|
280
|
+
const signalHooks = JSON.parse(await readFile2(signalHooksPath, "utf-8"));
|
|
281
|
+
settings = mergeHooks(settings, signalHooks);
|
|
282
|
+
}
|
|
283
|
+
if (config.plugins) {
|
|
284
|
+
for (const plugin of config.plugins) {
|
|
285
|
+
const pluginName = plugin.name;
|
|
286
|
+
const pluginYamlPath = join2(corePath, "..", "plugins", `plugin-${pluginName}`, "plugin.yaml");
|
|
287
|
+
if (!await fileExists(pluginYamlPath)) {
|
|
288
|
+
log14.push(`Warning: plugin "${pluginName}" not found at ${pluginYamlPath}`);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
const pluginContent = YAML2.parse(await readFile2(pluginYamlPath, "utf-8"));
|
|
292
|
+
if (pluginContent?.hooks) {
|
|
293
|
+
const pluginHooksFormatted = {};
|
|
294
|
+
for (const [event, entries] of Object.entries(pluginContent.hooks)) {
|
|
295
|
+
if (!Array.isArray(entries)) continue;
|
|
296
|
+
pluginHooksFormatted[event] = entries.map((entry) => {
|
|
297
|
+
if (typeof entry === "object" && entry !== null && "matcher" in entry && typeof entry.matcher === "object" && "hooks" in entry && Array.isArray(entry.hooks)) {
|
|
298
|
+
return entry;
|
|
299
|
+
}
|
|
300
|
+
const cmd = String(entry);
|
|
301
|
+
return {
|
|
302
|
+
matcher: {},
|
|
303
|
+
hooks: [{
|
|
304
|
+
type: "command",
|
|
305
|
+
description: `${pluginName} plugin: ${cmd.split(" ")[0]}`,
|
|
306
|
+
command: cmd
|
|
307
|
+
}]
|
|
308
|
+
};
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
settings = mergeHooks(settings, { hooks: pluginHooksFormatted });
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
261
315
|
if (!settings.env || typeof settings.env !== "object") {
|
|
262
316
|
settings.env = {};
|
|
263
317
|
}
|
|
@@ -276,8 +330,13 @@ async function scaffoldProject(cwd, config, options = {}) {
|
|
|
276
330
|
} else {
|
|
277
331
|
log14.push("Skipped CLAUDE.md (preserved user customizations)");
|
|
278
332
|
}
|
|
279
|
-
|
|
280
|
-
|
|
333
|
+
await ensureDir(join2(cwd, "docs"));
|
|
334
|
+
const registryTemplate = join2(corePath, "templates", "registry.md");
|
|
335
|
+
const registryDest = join2(cwd, "docs", "registry.md");
|
|
336
|
+
if (await fileExists(registryTemplate) && !await fileExists(registryDest)) {
|
|
337
|
+
await cp(registryTemplate, registryDest);
|
|
338
|
+
log14.push(isUpdate ? "Created missing docs/registry.md" : "Created docs/ with registry.md");
|
|
339
|
+
} else if (!isUpdate) {
|
|
281
340
|
log14.push("Created docs/");
|
|
282
341
|
}
|
|
283
342
|
return log14;
|