continuous-improvement 3.8.0 → 3.9.2
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/.claude-plugin/marketplace.json +2 -14
- package/CHANGELOG.md +25 -0
- package/LICENSE +21 -21
- package/QUICKSTART.md +148 -101
- package/README.md +50 -18
- package/action.yml +33 -33
- package/bin/analyze.sh +161 -161
- package/bin/backfill.mjs +19 -19
- package/bin/check-doc-runtime-claims.mjs +120 -0
- package/bin/generate-plugin-manifests.mjs +2 -0
- package/bin/hook-stats.mjs +21 -21
- package/bin/install.mjs +51 -44
- package/bin/mcp-server.mjs +43 -40
- package/bin/refresh-third-party.mjs +416 -416
- package/commands/continuous-improvement.md +115 -115
- package/commands/dashboard.md +56 -56
- package/commands/discipline.md +51 -51
- package/commands/proceed-with-the-recommendation.md +62 -62
- package/commands/seven-laws.md +16 -16
- package/commands/superpowers.md +119 -92
- package/commands/workspace-surface-audit.md +77 -77
- package/hooks/gateguard.mjs +172 -0
- package/hooks/observe.sh +172 -172
- package/hooks/session.sh +106 -106
- package/hooks/three-section-close.mjs +181 -181
- package/instinct-packs/go.json +58 -58
- package/instinct-packs/meta.json +16 -16
- package/instinct-packs/python.json +58 -58
- package/instinct-packs/react.json +58 -58
- package/lib/gateguard-state.mjs +85 -0
- package/lib/plugin-metadata.mjs +25 -18
- package/llms.txt +68 -68
- package/package.json +3 -2
- package/plugins/beginner.json +1 -1
- package/plugins/continuous-improvement/.claude-plugin/marketplace.json +1 -1
- package/plugins/continuous-improvement/.claude-plugin/plugin.json +1 -1
- package/plugins/continuous-improvement/LICENSE +21 -21
- package/plugins/continuous-improvement/README.md +1 -0
- package/plugins/continuous-improvement/agents/README.md +120 -0
- package/plugins/continuous-improvement/agents/code-reviewer.md +97 -0
- package/plugins/continuous-improvement/agents/security-auditor.md +101 -0
- package/plugins/continuous-improvement/agents/test-engineer.md +95 -0
- package/plugins/continuous-improvement/bin/backfill.mjs +19 -19
- package/plugins/continuous-improvement/bin/mcp-server.mjs +43 -40
- package/plugins/continuous-improvement/commands/continuous-improvement.md +115 -115
- package/plugins/continuous-improvement/commands/dashboard.md +56 -56
- package/plugins/continuous-improvement/commands/discipline.md +51 -51
- package/plugins/continuous-improvement/commands/proceed-with-the-recommendation.md +62 -62
- package/plugins/continuous-improvement/commands/seven-laws.md +16 -16
- package/plugins/continuous-improvement/commands/superpowers.md +119 -92
- package/plugins/continuous-improvement/commands/workspace-surface-audit.md +77 -77
- package/plugins/continuous-improvement/hooks/gateguard.mjs +172 -0
- package/plugins/continuous-improvement/hooks/hooks.json +6 -1
- package/plugins/continuous-improvement/hooks/observe.sh +172 -172
- package/plugins/continuous-improvement/hooks/session.sh +106 -106
- package/plugins/continuous-improvement/hooks/three-section-close.mjs +181 -181
- package/plugins/continuous-improvement/instinct-packs/go.json +58 -58
- package/plugins/continuous-improvement/instinct-packs/meta.json +16 -16
- package/plugins/continuous-improvement/instinct-packs/python.json +58 -58
- package/plugins/continuous-improvement/instinct-packs/react.json +58 -58
- package/plugins/continuous-improvement/lib/plugin-metadata.mjs +25 -18
- package/plugins/continuous-improvement/skills/README.md +4 -1
- package/plugins/continuous-improvement/skills/deploy-receipt/SKILL.md +47 -0
- package/plugins/continuous-improvement/skills/gateguard/SKILL.md +17 -9
- package/plugins/continuous-improvement/skills/para-memory-files/SKILL.md +108 -108
- package/plugins/continuous-improvement/skills/proceed-with-the-recommendation/SKILL.md +1 -8
- package/plugins/continuous-improvement/skills/ralph/SKILL.md +221 -221
- package/plugins/continuous-improvement/skills/recovery-classification/SKILL.md +73 -0
- package/plugins/continuous-improvement/skills/safety-guard/SKILL.md +76 -76
- package/plugins/continuous-improvement/skills/state-reconciliation/SKILL.md +63 -0
- package/plugins/continuous-improvement/skills/strategic-compact/SKILL.md +104 -104
- package/plugins/continuous-improvement/skills/superpowers/SKILL.md +22 -15
- package/plugins/continuous-improvement/skills/tdd-workflow/SKILL.md +411 -411
- package/plugins/continuous-improvement/skills/token-budget-advisor/SKILL.md +136 -136
- package/plugins/continuous-improvement/skills/verification-loop/SKILL.md +32 -0
- package/plugins/continuous-improvement/skills/wild-risa-balance/SKILL.md +191 -191
- package/plugins/continuous-improvement/skills/worktree-safety/SKILL.md +66 -0
- package/plugins/expert.json +1 -1
- package/skills/README.md +79 -79
- package/skills/deploy-receipt.md +47 -0
- package/skills/gateguard.md +17 -9
- package/skills/para-memory-files.md +108 -108
- package/skills/proceed-with-the-recommendation.md +1 -8
- package/skills/ralph.md +221 -221
- package/skills/recovery-classification.md +73 -0
- package/skills/safety-guard.md +76 -76
- package/skills/state-reconciliation.md +63 -0
- package/skills/strategic-compact.md +104 -104
- package/skills/superpowers.md +22 -15
- package/skills/tdd-workflow.md +411 -411
- package/skills/token-budget-advisor.md +136 -136
- package/skills/verification-loop.md +32 -0
- package/skills/wild-risa-balance.md +191 -191
- package/skills/worktree-safety.md +66 -0
- package/templates/insights-claude-md.md +91 -0
- package/templates/verify-ladder.example.json +37 -24
|
@@ -1,416 +1,416 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Refresh vendored third-party snapshots.
|
|
4
|
-
*
|
|
5
|
-
* Reads pinned SHAs from third-party/MANIFEST.md and rerunns the documented
|
|
6
|
-
* selective copy for each snapshot, in code, so refreshes are reproducible
|
|
7
|
-
* across Windows / macOS / Linux without bash.
|
|
8
|
-
*
|
|
9
|
-
* The bash recipes in MANIFEST.md remain the authoritative human-readable
|
|
10
|
-
* spec; this driver mirrors them. If you change the selective surface for
|
|
11
|
-
* a snapshot, update MANIFEST.md and the SNAPSHOTS table below together.
|
|
12
|
-
*
|
|
13
|
-
* Usage:
|
|
14
|
-
* node bin/refresh-third-party.mjs --list
|
|
15
|
-
* List configured snapshots with their pinned SHAs.
|
|
16
|
-
*
|
|
17
|
-
* node bin/refresh-third-party.mjs <name> --check
|
|
18
|
-
* Shallow-clone upstream, compare HEAD to the pinned SHA. No file writes.
|
|
19
|
-
*
|
|
20
|
-
* node bin/refresh-third-party.mjs <name>
|
|
21
|
-
* Full refresh: shallow clone, verify HEAD matches the pinned SHA,
|
|
22
|
-
* wipe the local path, recreate it, copy the selective surface
|
|
23
|
-
* verbatim, strip every CLAUDE.md inside the snapshot, print a diff
|
|
24
|
-
* stat. Aborts if the local snapshot path has uncommitted changes
|
|
25
|
-
* unless --force is passed.
|
|
26
|
-
*
|
|
27
|
-
* node bin/refresh-third-party.mjs --all
|
|
28
|
-
* node bin/refresh-third-party.mjs --all --check
|
|
29
|
-
* Same as above, applied to every configured snapshot in order.
|
|
30
|
-
*
|
|
31
|
-
* node bin/refresh-third-party.mjs --help
|
|
32
|
-
* Print this usage block.
|
|
33
|
-
*
|
|
34
|
-
* Exit codes:
|
|
35
|
-
* 0 — success (or --check found everything up-to-date)
|
|
36
|
-
* 1 — refresh failed, dirty working tree without --force, or --check found drift
|
|
37
|
-
* 2 — bad CLI usage / unknown snapshot name
|
|
38
|
-
*/
|
|
39
|
-
import { spawnSync } from "node:child_process";
|
|
40
|
-
import { cp, mkdir, readFile, rm, stat } from "node:fs/promises";
|
|
41
|
-
import { tmpdir } from "node:os";
|
|
42
|
-
import { dirname, join, relative, resolve } from "node:path";
|
|
43
|
-
import { argv, cwd, exit, stderr, stdout } from "node:process";
|
|
44
|
-
import { fileURLToPath } from "node:url";
|
|
45
|
-
|
|
46
|
-
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
47
|
-
const REPO_ROOT = resolve(SCRIPT_DIR, "..");
|
|
48
|
-
const MANIFEST_PATH = join(REPO_ROOT, "third-party", "MANIFEST.md");
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Per-snapshot config. Mirrors the bash recipe blocks in MANIFEST.md.
|
|
52
|
-
* `manifestHeading` is the literal `### <heading>` text used to locate the
|
|
53
|
-
* pinned SHA row in MANIFEST.md.
|
|
54
|
-
*/
|
|
55
|
-
const SNAPSHOTS = [
|
|
56
|
-
{
|
|
57
|
-
name: "oh-my-claudecode",
|
|
58
|
-
manifestHeading: "oh-my-claudecode",
|
|
59
|
-
upstream: "https://github.com/Yeachan-Heo/oh-my-claudecode.git",
|
|
60
|
-
localPath: "third-party/oh-my-claudecode",
|
|
61
|
-
selectiveDirs: [
|
|
62
|
-
"agents",
|
|
63
|
-
"skills",
|
|
64
|
-
"missions",
|
|
65
|
-
"templates",
|
|
66
|
-
"examples",
|
|
67
|
-
"hooks",
|
|
68
|
-
"docs",
|
|
69
|
-
".claude-plugin",
|
|
70
|
-
],
|
|
71
|
-
selectiveFiles: [
|
|
72
|
-
"LICENSE",
|
|
73
|
-
"README.md",
|
|
74
|
-
"AGENTS.md",
|
|
75
|
-
"CHANGELOG.md",
|
|
76
|
-
"SECURITY.md",
|
|
77
|
-
],
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
name: "superpowers",
|
|
81
|
-
manifestHeading: "obra/superpowers",
|
|
82
|
-
upstream: "https://github.com/obra/superpowers.git",
|
|
83
|
-
localPath: "third-party/superpowers",
|
|
84
|
-
selectiveDirs: ["skills", "hooks", "docs", "assets", ".claude-plugin"],
|
|
85
|
-
selectiveFiles: [
|
|
86
|
-
"LICENSE",
|
|
87
|
-
"README.md",
|
|
88
|
-
"AGENTS.md",
|
|
89
|
-
"CODE_OF_CONDUCT.md",
|
|
90
|
-
"RELEASE-NOTES.md",
|
|
91
|
-
],
|
|
92
|
-
},
|
|
93
|
-
];
|
|
94
|
-
|
|
95
|
-
const SHA_RE = /^[0-9a-f]{40}$/i;
|
|
96
|
-
|
|
97
|
-
function log(msg) {
|
|
98
|
-
stdout.write(msg + "\n");
|
|
99
|
-
}
|
|
100
|
-
function err(msg) {
|
|
101
|
-
stderr.write(msg + "\n");
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function usage() {
|
|
105
|
-
err(
|
|
106
|
-
[
|
|
107
|
-
"Usage:",
|
|
108
|
-
" node bin/refresh-third-party.mjs --list",
|
|
109
|
-
" node bin/refresh-third-party.mjs <name> --check",
|
|
110
|
-
" node bin/refresh-third-party.mjs <name> [--force]",
|
|
111
|
-
" node bin/refresh-third-party.mjs --all [--check] [--force]",
|
|
112
|
-
" node bin/refresh-third-party.mjs --help",
|
|
113
|
-
"",
|
|
114
|
-
"Configured snapshots: " + SNAPSHOTS.map((s) => s.name).join(", "),
|
|
115
|
-
].join("\n"),
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Parse the pinned SHA for a snapshot from MANIFEST.md.
|
|
121
|
-
* Strategy: locate `### <heading>`, then the first table row in that
|
|
122
|
-
* section beginning with `| Pinned SHA |`.
|
|
123
|
-
*/
|
|
124
|
-
async function readPinnedSha(snapshot) {
|
|
125
|
-
const text = await readFile(MANIFEST_PATH, "utf8");
|
|
126
|
-
const lines = text.split(/\r?\n/);
|
|
127
|
-
const headingIdx = lines.findIndex(
|
|
128
|
-
(line) => line.trim() === `### ${snapshot.manifestHeading}`,
|
|
129
|
-
);
|
|
130
|
-
if (headingIdx === -1) {
|
|
131
|
-
throw new Error(
|
|
132
|
-
`MANIFEST.md missing heading "### ${snapshot.manifestHeading}"`,
|
|
133
|
-
);
|
|
134
|
-
}
|
|
135
|
-
for (let i = headingIdx + 1; i < lines.length; i++) {
|
|
136
|
-
const line = lines[i];
|
|
137
|
-
if (line.startsWith("### ")) break;
|
|
138
|
-
const m = line.match(/^\|\s*Pinned SHA\s*\|\s*`?([0-9a-fA-F]{7,40})`?\s*\|/);
|
|
139
|
-
if (m) {
|
|
140
|
-
const sha = m[1].toLowerCase();
|
|
141
|
-
if (!SHA_RE.test(sha)) {
|
|
142
|
-
throw new Error(
|
|
143
|
-
`MANIFEST.md "${snapshot.manifestHeading}": pinned SHA "${sha}" is not a 40-char hex`,
|
|
144
|
-
);
|
|
145
|
-
}
|
|
146
|
-
return sha;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
throw new Error(
|
|
150
|
-
`MANIFEST.md "${snapshot.manifestHeading}": no "| Pinned SHA |" row found`,
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
function runGit(args, opts = {}) {
|
|
155
|
-
const result = spawnSync("git", args, {
|
|
156
|
-
encoding: "utf8",
|
|
157
|
-
...opts,
|
|
158
|
-
});
|
|
159
|
-
if (result.error) {
|
|
160
|
-
throw new Error(`git ${args.join(" ")} failed: ${result.error.message}`);
|
|
161
|
-
}
|
|
162
|
-
return result;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function gitOk(args, opts = {}) {
|
|
166
|
-
const r = runGit(args, opts);
|
|
167
|
-
if (r.status !== 0) {
|
|
168
|
-
throw new Error(
|
|
169
|
-
`git ${args.join(" ")} exited ${r.status}: ${r.stderr || r.stdout}`,
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
return r.stdout.trim();
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Shallow-clone upstream into a temp dir and return { dir, headSha }.
|
|
177
|
-
*/
|
|
178
|
-
async function shallowClone(upstream) {
|
|
179
|
-
const dir = join(
|
|
180
|
-
tmpdir(),
|
|
181
|
-
`ci-refresh-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
182
|
-
);
|
|
183
|
-
gitOk(["clone", "--depth", "1", upstream, dir]);
|
|
184
|
-
const headSha = gitOk(["-C", dir, "rev-parse", "HEAD"]);
|
|
185
|
-
return { dir, headSha };
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
async function pathExists(p) {
|
|
189
|
-
try {
|
|
190
|
-
await stat(p);
|
|
191
|
-
return true;
|
|
192
|
-
} catch {
|
|
193
|
-
return false;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Ask git whether the local snapshot path has uncommitted changes.
|
|
199
|
-
* Runs from REPO_ROOT so worktrees are handled correctly.
|
|
200
|
-
*/
|
|
201
|
-
function localPathDirty(relPath) {
|
|
202
|
-
const r = runGit(
|
|
203
|
-
["status", "--porcelain", "--", relPath],
|
|
204
|
-
{ cwd: REPO_ROOT },
|
|
205
|
-
);
|
|
206
|
-
if (r.status !== 0) {
|
|
207
|
-
throw new Error(
|
|
208
|
-
`git status failed for ${relPath}: ${r.stderr || r.stdout}`,
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
return r.stdout.trim().length > 0;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
async function deleteClaudeMdRecursive(root) {
|
|
215
|
-
// Node-only equivalent of `find <root> -name CLAUDE.md -type f -delete`.
|
|
216
|
-
const { readdir } = await import("node:fs/promises");
|
|
217
|
-
const stack = [root];
|
|
218
|
-
let deleted = 0;
|
|
219
|
-
while (stack.length) {
|
|
220
|
-
const dir = stack.pop();
|
|
221
|
-
let entries;
|
|
222
|
-
try {
|
|
223
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
224
|
-
} catch {
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
for (const ent of entries) {
|
|
228
|
-
const full = join(dir, ent.name);
|
|
229
|
-
if (ent.isDirectory()) {
|
|
230
|
-
stack.push(full);
|
|
231
|
-
} else if (ent.isFile() && ent.name === "CLAUDE.md") {
|
|
232
|
-
await rm(full, { force: true });
|
|
233
|
-
deleted++;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
return deleted;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async function checkOne(snapshot) {
|
|
241
|
-
const pinned = await readPinnedSha(snapshot);
|
|
242
|
-
const { dir, headSha } = await shallowClone(snapshot.upstream);
|
|
243
|
-
try {
|
|
244
|
-
const upToDate = headSha.toLowerCase() === pinned.toLowerCase();
|
|
245
|
-
log(`[${snapshot.name}]`);
|
|
246
|
-
log(` upstream : ${snapshot.upstream}`);
|
|
247
|
-
log(` pinned SHA : ${pinned}`);
|
|
248
|
-
log(` upstream HEAD: ${headSha}`);
|
|
249
|
-
log(` status : ${upToDate ? "up-to-date" : "would-update"}`);
|
|
250
|
-
return upToDate;
|
|
251
|
-
} finally {
|
|
252
|
-
await rm(dir, { recursive: true, force: true });
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
async function refreshOne(snapshot, { force }) {
|
|
257
|
-
const pinned = await readPinnedSha(snapshot);
|
|
258
|
-
const localAbs = join(REPO_ROOT, snapshot.localPath);
|
|
259
|
-
|
|
260
|
-
if (!force && (await pathExists(localAbs))) {
|
|
261
|
-
if (localPathDirty(snapshot.localPath)) {
|
|
262
|
-
throw new Error(
|
|
263
|
-
`local snapshot path "${snapshot.localPath}" has uncommitted changes; ` +
|
|
264
|
-
`commit/stash first, or rerun with --force`,
|
|
265
|
-
);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const { dir, headSha } = await shallowClone(snapshot.upstream);
|
|
270
|
-
try {
|
|
271
|
-
if (headSha.toLowerCase() !== pinned.toLowerCase()) {
|
|
272
|
-
throw new Error(
|
|
273
|
-
`[${snapshot.name}] upstream HEAD ${headSha} != pinned ${pinned}; ` +
|
|
274
|
-
`bump the pinned SHA in third-party/MANIFEST.md first`,
|
|
275
|
-
);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// Wipe + recreate destination.
|
|
279
|
-
await rm(localAbs, { recursive: true, force: true });
|
|
280
|
-
await mkdir(localAbs, { recursive: true });
|
|
281
|
-
|
|
282
|
-
// Copy selective directories.
|
|
283
|
-
let copiedDirs = 0;
|
|
284
|
-
for (const d of snapshot.selectiveDirs) {
|
|
285
|
-
const src = join(dir, d);
|
|
286
|
-
if (!(await pathExists(src))) {
|
|
287
|
-
throw new Error(
|
|
288
|
-
`[${snapshot.name}] expected directory "${d}" missing in upstream`,
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
await cp(src, join(localAbs, d), { recursive: true });
|
|
292
|
-
copiedDirs++;
|
|
293
|
-
}
|
|
294
|
-
// Copy selective top-level files.
|
|
295
|
-
let copiedFiles = 0;
|
|
296
|
-
for (const f of snapshot.selectiveFiles) {
|
|
297
|
-
const src = join(dir, f);
|
|
298
|
-
if (!(await pathExists(src))) {
|
|
299
|
-
throw new Error(
|
|
300
|
-
`[${snapshot.name}] expected file "${f}" missing in upstream`,
|
|
301
|
-
);
|
|
302
|
-
}
|
|
303
|
-
await cp(src, join(localAbs, f));
|
|
304
|
-
copiedFiles++;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Strip every CLAUDE.md (auto-loads as session context).
|
|
308
|
-
const stripped = await deleteClaudeMdRecursive(localAbs);
|
|
309
|
-
|
|
310
|
-
// Print git diff stat scoped to the snapshot path.
|
|
311
|
-
const diffStat = runGit(
|
|
312
|
-
["diff", "--stat", "--", snapshot.localPath],
|
|
313
|
-
{ cwd: REPO_ROOT },
|
|
314
|
-
);
|
|
315
|
-
log(`[${snapshot.name}]`);
|
|
316
|
-
log(` upstream : ${snapshot.upstream}`);
|
|
317
|
-
log(` pinned SHA : ${pinned}`);
|
|
318
|
-
log(` upstream HEAD: ${headSha}`);
|
|
319
|
-
log(
|
|
320
|
-
` status : updated (${copiedDirs} dirs, ${copiedFiles} files, ${stripped} CLAUDE.md stripped)`,
|
|
321
|
-
);
|
|
322
|
-
if (diffStat.stdout.trim()) {
|
|
323
|
-
log(" diff --stat :");
|
|
324
|
-
for (const line of diffStat.stdout.trimEnd().split(/\r?\n/)) {
|
|
325
|
-
log(` ${line}`);
|
|
326
|
-
}
|
|
327
|
-
} else {
|
|
328
|
-
log(" diff --stat : (no working-tree change)");
|
|
329
|
-
}
|
|
330
|
-
return true;
|
|
331
|
-
} finally {
|
|
332
|
-
await rm(dir, { recursive: true, force: true });
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
async function listAll() {
|
|
337
|
-
log("Configured third-party snapshots:");
|
|
338
|
-
for (const s of SNAPSHOTS) {
|
|
339
|
-
let pinned;
|
|
340
|
-
try {
|
|
341
|
-
pinned = await readPinnedSha(s);
|
|
342
|
-
} catch (e) {
|
|
343
|
-
pinned = `<error: ${e.message}>`;
|
|
344
|
-
}
|
|
345
|
-
log(` - ${s.name}`);
|
|
346
|
-
log(` upstream : ${s.upstream}`);
|
|
347
|
-
log(` localPath : ${s.localPath}`);
|
|
348
|
-
log(` pinnedSHA : ${pinned}`);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function findSnapshot(name) {
|
|
353
|
-
return SNAPSHOTS.find((s) => s.name === name);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
async function main() {
|
|
357
|
-
const args = argv.slice(2);
|
|
358
|
-
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
359
|
-
usage();
|
|
360
|
-
exit(args.length === 0 ? 2 : 0);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
if (args.includes("--list")) {
|
|
364
|
-
await listAll();
|
|
365
|
-
exit(0);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
const force = args.includes("--force");
|
|
369
|
-
const check = args.includes("--check");
|
|
370
|
-
const all = args.includes("--all");
|
|
371
|
-
|
|
372
|
-
const positional = args.filter((a) => !a.startsWith("--"));
|
|
373
|
-
|
|
374
|
-
let targets;
|
|
375
|
-
if (all) {
|
|
376
|
-
targets = SNAPSHOTS;
|
|
377
|
-
} else if (positional.length === 1) {
|
|
378
|
-
const snap = findSnapshot(positional[0]);
|
|
379
|
-
if (!snap) {
|
|
380
|
-
err(
|
|
381
|
-
`Unknown snapshot "${positional[0]}". Known: ${SNAPSHOTS.map((s) => s.name).join(", ")}`,
|
|
382
|
-
);
|
|
383
|
-
exit(2);
|
|
384
|
-
}
|
|
385
|
-
targets = [snap];
|
|
386
|
-
} else {
|
|
387
|
-
usage();
|
|
388
|
-
exit(2);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
let allUpToDate = true;
|
|
392
|
-
let firstErr;
|
|
393
|
-
for (const snap of targets) {
|
|
394
|
-
try {
|
|
395
|
-
if (check) {
|
|
396
|
-
const ok = await checkOne(snap);
|
|
397
|
-
if (!ok) allUpToDate = false;
|
|
398
|
-
} else {
|
|
399
|
-
await refreshOne(snap, { force });
|
|
400
|
-
}
|
|
401
|
-
} catch (e) {
|
|
402
|
-
firstErr = firstErr || e;
|
|
403
|
-
err(`[${snap.name}] ERROR: ${e.message}`);
|
|
404
|
-
allUpToDate = false;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if (firstErr) exit(1);
|
|
409
|
-
if (check && !allUpToDate) exit(1);
|
|
410
|
-
exit(0);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
main().catch((e) => {
|
|
414
|
-
err(`fatal: ${e.stack || e.message}`);
|
|
415
|
-
exit(1);
|
|
416
|
-
});
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Refresh vendored third-party snapshots.
|
|
4
|
+
*
|
|
5
|
+
* Reads pinned SHAs from third-party/MANIFEST.md and rerunns the documented
|
|
6
|
+
* selective copy for each snapshot, in code, so refreshes are reproducible
|
|
7
|
+
* across Windows / macOS / Linux without bash.
|
|
8
|
+
*
|
|
9
|
+
* The bash recipes in MANIFEST.md remain the authoritative human-readable
|
|
10
|
+
* spec; this driver mirrors them. If you change the selective surface for
|
|
11
|
+
* a snapshot, update MANIFEST.md and the SNAPSHOTS table below together.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* node bin/refresh-third-party.mjs --list
|
|
15
|
+
* List configured snapshots with their pinned SHAs.
|
|
16
|
+
*
|
|
17
|
+
* node bin/refresh-third-party.mjs <name> --check
|
|
18
|
+
* Shallow-clone upstream, compare HEAD to the pinned SHA. No file writes.
|
|
19
|
+
*
|
|
20
|
+
* node bin/refresh-third-party.mjs <name>
|
|
21
|
+
* Full refresh: shallow clone, verify HEAD matches the pinned SHA,
|
|
22
|
+
* wipe the local path, recreate it, copy the selective surface
|
|
23
|
+
* verbatim, strip every CLAUDE.md inside the snapshot, print a diff
|
|
24
|
+
* stat. Aborts if the local snapshot path has uncommitted changes
|
|
25
|
+
* unless --force is passed.
|
|
26
|
+
*
|
|
27
|
+
* node bin/refresh-third-party.mjs --all
|
|
28
|
+
* node bin/refresh-third-party.mjs --all --check
|
|
29
|
+
* Same as above, applied to every configured snapshot in order.
|
|
30
|
+
*
|
|
31
|
+
* node bin/refresh-third-party.mjs --help
|
|
32
|
+
* Print this usage block.
|
|
33
|
+
*
|
|
34
|
+
* Exit codes:
|
|
35
|
+
* 0 — success (or --check found everything up-to-date)
|
|
36
|
+
* 1 — refresh failed, dirty working tree without --force, or --check found drift
|
|
37
|
+
* 2 — bad CLI usage / unknown snapshot name
|
|
38
|
+
*/
|
|
39
|
+
import { spawnSync } from "node:child_process";
|
|
40
|
+
import { cp, mkdir, readFile, rm, stat } from "node:fs/promises";
|
|
41
|
+
import { tmpdir } from "node:os";
|
|
42
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
43
|
+
import { argv, cwd, exit, stderr, stdout } from "node:process";
|
|
44
|
+
import { fileURLToPath } from "node:url";
|
|
45
|
+
|
|
46
|
+
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
47
|
+
const REPO_ROOT = resolve(SCRIPT_DIR, "..");
|
|
48
|
+
const MANIFEST_PATH = join(REPO_ROOT, "third-party", "MANIFEST.md");
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Per-snapshot config. Mirrors the bash recipe blocks in MANIFEST.md.
|
|
52
|
+
* `manifestHeading` is the literal `### <heading>` text used to locate the
|
|
53
|
+
* pinned SHA row in MANIFEST.md.
|
|
54
|
+
*/
|
|
55
|
+
const SNAPSHOTS = [
|
|
56
|
+
{
|
|
57
|
+
name: "oh-my-claudecode",
|
|
58
|
+
manifestHeading: "oh-my-claudecode",
|
|
59
|
+
upstream: "https://github.com/Yeachan-Heo/oh-my-claudecode.git",
|
|
60
|
+
localPath: "third-party/oh-my-claudecode",
|
|
61
|
+
selectiveDirs: [
|
|
62
|
+
"agents",
|
|
63
|
+
"skills",
|
|
64
|
+
"missions",
|
|
65
|
+
"templates",
|
|
66
|
+
"examples",
|
|
67
|
+
"hooks",
|
|
68
|
+
"docs",
|
|
69
|
+
".claude-plugin",
|
|
70
|
+
],
|
|
71
|
+
selectiveFiles: [
|
|
72
|
+
"LICENSE",
|
|
73
|
+
"README.md",
|
|
74
|
+
"AGENTS.md",
|
|
75
|
+
"CHANGELOG.md",
|
|
76
|
+
"SECURITY.md",
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "superpowers",
|
|
81
|
+
manifestHeading: "obra/superpowers",
|
|
82
|
+
upstream: "https://github.com/obra/superpowers.git",
|
|
83
|
+
localPath: "third-party/superpowers",
|
|
84
|
+
selectiveDirs: ["skills", "hooks", "docs", "assets", ".claude-plugin"],
|
|
85
|
+
selectiveFiles: [
|
|
86
|
+
"LICENSE",
|
|
87
|
+
"README.md",
|
|
88
|
+
"AGENTS.md",
|
|
89
|
+
"CODE_OF_CONDUCT.md",
|
|
90
|
+
"RELEASE-NOTES.md",
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
const SHA_RE = /^[0-9a-f]{40}$/i;
|
|
96
|
+
|
|
97
|
+
function log(msg) {
|
|
98
|
+
stdout.write(msg + "\n");
|
|
99
|
+
}
|
|
100
|
+
function err(msg) {
|
|
101
|
+
stderr.write(msg + "\n");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function usage() {
|
|
105
|
+
err(
|
|
106
|
+
[
|
|
107
|
+
"Usage:",
|
|
108
|
+
" node bin/refresh-third-party.mjs --list",
|
|
109
|
+
" node bin/refresh-third-party.mjs <name> --check",
|
|
110
|
+
" node bin/refresh-third-party.mjs <name> [--force]",
|
|
111
|
+
" node bin/refresh-third-party.mjs --all [--check] [--force]",
|
|
112
|
+
" node bin/refresh-third-party.mjs --help",
|
|
113
|
+
"",
|
|
114
|
+
"Configured snapshots: " + SNAPSHOTS.map((s) => s.name).join(", "),
|
|
115
|
+
].join("\n"),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Parse the pinned SHA for a snapshot from MANIFEST.md.
|
|
121
|
+
* Strategy: locate `### <heading>`, then the first table row in that
|
|
122
|
+
* section beginning with `| Pinned SHA |`.
|
|
123
|
+
*/
|
|
124
|
+
async function readPinnedSha(snapshot) {
|
|
125
|
+
const text = await readFile(MANIFEST_PATH, "utf8");
|
|
126
|
+
const lines = text.split(/\r?\n/);
|
|
127
|
+
const headingIdx = lines.findIndex(
|
|
128
|
+
(line) => line.trim() === `### ${snapshot.manifestHeading}`,
|
|
129
|
+
);
|
|
130
|
+
if (headingIdx === -1) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`MANIFEST.md missing heading "### ${snapshot.manifestHeading}"`,
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
for (let i = headingIdx + 1; i < lines.length; i++) {
|
|
136
|
+
const line = lines[i];
|
|
137
|
+
if (line.startsWith("### ")) break;
|
|
138
|
+
const m = line.match(/^\|\s*Pinned SHA\s*\|\s*`?([0-9a-fA-F]{7,40})`?\s*\|/);
|
|
139
|
+
if (m) {
|
|
140
|
+
const sha = m[1].toLowerCase();
|
|
141
|
+
if (!SHA_RE.test(sha)) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
`MANIFEST.md "${snapshot.manifestHeading}": pinned SHA "${sha}" is not a 40-char hex`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return sha;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
throw new Error(
|
|
150
|
+
`MANIFEST.md "${snapshot.manifestHeading}": no "| Pinned SHA |" row found`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function runGit(args, opts = {}) {
|
|
155
|
+
const result = spawnSync("git", args, {
|
|
156
|
+
encoding: "utf8",
|
|
157
|
+
...opts,
|
|
158
|
+
});
|
|
159
|
+
if (result.error) {
|
|
160
|
+
throw new Error(`git ${args.join(" ")} failed: ${result.error.message}`);
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function gitOk(args, opts = {}) {
|
|
166
|
+
const r = runGit(args, opts);
|
|
167
|
+
if (r.status !== 0) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
`git ${args.join(" ")} exited ${r.status}: ${r.stderr || r.stdout}`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return r.stdout.trim();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Shallow-clone upstream into a temp dir and return { dir, headSha }.
|
|
177
|
+
*/
|
|
178
|
+
async function shallowClone(upstream) {
|
|
179
|
+
const dir = join(
|
|
180
|
+
tmpdir(),
|
|
181
|
+
`ci-refresh-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
182
|
+
);
|
|
183
|
+
gitOk(["clone", "--depth", "1", upstream, dir]);
|
|
184
|
+
const headSha = gitOk(["-C", dir, "rev-parse", "HEAD"]);
|
|
185
|
+
return { dir, headSha };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function pathExists(p) {
|
|
189
|
+
try {
|
|
190
|
+
await stat(p);
|
|
191
|
+
return true;
|
|
192
|
+
} catch {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Ask git whether the local snapshot path has uncommitted changes.
|
|
199
|
+
* Runs from REPO_ROOT so worktrees are handled correctly.
|
|
200
|
+
*/
|
|
201
|
+
function localPathDirty(relPath) {
|
|
202
|
+
const r = runGit(
|
|
203
|
+
["status", "--porcelain", "--", relPath],
|
|
204
|
+
{ cwd: REPO_ROOT },
|
|
205
|
+
);
|
|
206
|
+
if (r.status !== 0) {
|
|
207
|
+
throw new Error(
|
|
208
|
+
`git status failed for ${relPath}: ${r.stderr || r.stdout}`,
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
return r.stdout.trim().length > 0;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function deleteClaudeMdRecursive(root) {
|
|
215
|
+
// Node-only equivalent of `find <root> -name CLAUDE.md -type f -delete`.
|
|
216
|
+
const { readdir } = await import("node:fs/promises");
|
|
217
|
+
const stack = [root];
|
|
218
|
+
let deleted = 0;
|
|
219
|
+
while (stack.length) {
|
|
220
|
+
const dir = stack.pop();
|
|
221
|
+
let entries;
|
|
222
|
+
try {
|
|
223
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
224
|
+
} catch {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
for (const ent of entries) {
|
|
228
|
+
const full = join(dir, ent.name);
|
|
229
|
+
if (ent.isDirectory()) {
|
|
230
|
+
stack.push(full);
|
|
231
|
+
} else if (ent.isFile() && ent.name === "CLAUDE.md") {
|
|
232
|
+
await rm(full, { force: true });
|
|
233
|
+
deleted++;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return deleted;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function checkOne(snapshot) {
|
|
241
|
+
const pinned = await readPinnedSha(snapshot);
|
|
242
|
+
const { dir, headSha } = await shallowClone(snapshot.upstream);
|
|
243
|
+
try {
|
|
244
|
+
const upToDate = headSha.toLowerCase() === pinned.toLowerCase();
|
|
245
|
+
log(`[${snapshot.name}]`);
|
|
246
|
+
log(` upstream : ${snapshot.upstream}`);
|
|
247
|
+
log(` pinned SHA : ${pinned}`);
|
|
248
|
+
log(` upstream HEAD: ${headSha}`);
|
|
249
|
+
log(` status : ${upToDate ? "up-to-date" : "would-update"}`);
|
|
250
|
+
return upToDate;
|
|
251
|
+
} finally {
|
|
252
|
+
await rm(dir, { recursive: true, force: true });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function refreshOne(snapshot, { force }) {
|
|
257
|
+
const pinned = await readPinnedSha(snapshot);
|
|
258
|
+
const localAbs = join(REPO_ROOT, snapshot.localPath);
|
|
259
|
+
|
|
260
|
+
if (!force && (await pathExists(localAbs))) {
|
|
261
|
+
if (localPathDirty(snapshot.localPath)) {
|
|
262
|
+
throw new Error(
|
|
263
|
+
`local snapshot path "${snapshot.localPath}" has uncommitted changes; ` +
|
|
264
|
+
`commit/stash first, or rerun with --force`,
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const { dir, headSha } = await shallowClone(snapshot.upstream);
|
|
270
|
+
try {
|
|
271
|
+
if (headSha.toLowerCase() !== pinned.toLowerCase()) {
|
|
272
|
+
throw new Error(
|
|
273
|
+
`[${snapshot.name}] upstream HEAD ${headSha} != pinned ${pinned}; ` +
|
|
274
|
+
`bump the pinned SHA in third-party/MANIFEST.md first`,
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Wipe + recreate destination.
|
|
279
|
+
await rm(localAbs, { recursive: true, force: true });
|
|
280
|
+
await mkdir(localAbs, { recursive: true });
|
|
281
|
+
|
|
282
|
+
// Copy selective directories.
|
|
283
|
+
let copiedDirs = 0;
|
|
284
|
+
for (const d of snapshot.selectiveDirs) {
|
|
285
|
+
const src = join(dir, d);
|
|
286
|
+
if (!(await pathExists(src))) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
`[${snapshot.name}] expected directory "${d}" missing in upstream`,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
await cp(src, join(localAbs, d), { recursive: true });
|
|
292
|
+
copiedDirs++;
|
|
293
|
+
}
|
|
294
|
+
// Copy selective top-level files.
|
|
295
|
+
let copiedFiles = 0;
|
|
296
|
+
for (const f of snapshot.selectiveFiles) {
|
|
297
|
+
const src = join(dir, f);
|
|
298
|
+
if (!(await pathExists(src))) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
`[${snapshot.name}] expected file "${f}" missing in upstream`,
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
await cp(src, join(localAbs, f));
|
|
304
|
+
copiedFiles++;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Strip every CLAUDE.md (auto-loads as session context).
|
|
308
|
+
const stripped = await deleteClaudeMdRecursive(localAbs);
|
|
309
|
+
|
|
310
|
+
// Print git diff stat scoped to the snapshot path.
|
|
311
|
+
const diffStat = runGit(
|
|
312
|
+
["diff", "--stat", "--", snapshot.localPath],
|
|
313
|
+
{ cwd: REPO_ROOT },
|
|
314
|
+
);
|
|
315
|
+
log(`[${snapshot.name}]`);
|
|
316
|
+
log(` upstream : ${snapshot.upstream}`);
|
|
317
|
+
log(` pinned SHA : ${pinned}`);
|
|
318
|
+
log(` upstream HEAD: ${headSha}`);
|
|
319
|
+
log(
|
|
320
|
+
` status : updated (${copiedDirs} dirs, ${copiedFiles} files, ${stripped} CLAUDE.md stripped)`,
|
|
321
|
+
);
|
|
322
|
+
if (diffStat.stdout.trim()) {
|
|
323
|
+
log(" diff --stat :");
|
|
324
|
+
for (const line of diffStat.stdout.trimEnd().split(/\r?\n/)) {
|
|
325
|
+
log(` ${line}`);
|
|
326
|
+
}
|
|
327
|
+
} else {
|
|
328
|
+
log(" diff --stat : (no working-tree change)");
|
|
329
|
+
}
|
|
330
|
+
return true;
|
|
331
|
+
} finally {
|
|
332
|
+
await rm(dir, { recursive: true, force: true });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function listAll() {
|
|
337
|
+
log("Configured third-party snapshots:");
|
|
338
|
+
for (const s of SNAPSHOTS) {
|
|
339
|
+
let pinned;
|
|
340
|
+
try {
|
|
341
|
+
pinned = await readPinnedSha(s);
|
|
342
|
+
} catch (e) {
|
|
343
|
+
pinned = `<error: ${e.message}>`;
|
|
344
|
+
}
|
|
345
|
+
log(` - ${s.name}`);
|
|
346
|
+
log(` upstream : ${s.upstream}`);
|
|
347
|
+
log(` localPath : ${s.localPath}`);
|
|
348
|
+
log(` pinnedSHA : ${pinned}`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function findSnapshot(name) {
|
|
353
|
+
return SNAPSHOTS.find((s) => s.name === name);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async function main() {
|
|
357
|
+
const args = argv.slice(2);
|
|
358
|
+
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
359
|
+
usage();
|
|
360
|
+
exit(args.length === 0 ? 2 : 0);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (args.includes("--list")) {
|
|
364
|
+
await listAll();
|
|
365
|
+
exit(0);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const force = args.includes("--force");
|
|
369
|
+
const check = args.includes("--check");
|
|
370
|
+
const all = args.includes("--all");
|
|
371
|
+
|
|
372
|
+
const positional = args.filter((a) => !a.startsWith("--"));
|
|
373
|
+
|
|
374
|
+
let targets;
|
|
375
|
+
if (all) {
|
|
376
|
+
targets = SNAPSHOTS;
|
|
377
|
+
} else if (positional.length === 1) {
|
|
378
|
+
const snap = findSnapshot(positional[0]);
|
|
379
|
+
if (!snap) {
|
|
380
|
+
err(
|
|
381
|
+
`Unknown snapshot "${positional[0]}". Known: ${SNAPSHOTS.map((s) => s.name).join(", ")}`,
|
|
382
|
+
);
|
|
383
|
+
exit(2);
|
|
384
|
+
}
|
|
385
|
+
targets = [snap];
|
|
386
|
+
} else {
|
|
387
|
+
usage();
|
|
388
|
+
exit(2);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
let allUpToDate = true;
|
|
392
|
+
let firstErr;
|
|
393
|
+
for (const snap of targets) {
|
|
394
|
+
try {
|
|
395
|
+
if (check) {
|
|
396
|
+
const ok = await checkOne(snap);
|
|
397
|
+
if (!ok) allUpToDate = false;
|
|
398
|
+
} else {
|
|
399
|
+
await refreshOne(snap, { force });
|
|
400
|
+
}
|
|
401
|
+
} catch (e) {
|
|
402
|
+
firstErr = firstErr || e;
|
|
403
|
+
err(`[${snap.name}] ERROR: ${e.message}`);
|
|
404
|
+
allUpToDate = false;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (firstErr) exit(1);
|
|
409
|
+
if (check && !allUpToDate) exit(1);
|
|
410
|
+
exit(0);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
main().catch((e) => {
|
|
414
|
+
err(`fatal: ${e.stack || e.message}`);
|
|
415
|
+
exit(1);
|
|
416
|
+
});
|