superpowers-mcp 6.4.1 → 6.4.3
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.es.md +258 -0
- package/README.hi.md +258 -0
- package/README.ja.md +32 -13
- package/README.ko.md +29 -10
- package/README.md +28 -9
- package/README.pt-BR.md +258 -0
- package/README.zh-TW.md +31 -12
- package/docs/desktop-setup.md +6 -46
- package/docs/skill-compositions.es.md +220 -0
- package/docs/skill-compositions.hi.md +220 -0
- package/docs/skill-compositions.ja.md +39 -17
- package/docs/skill-compositions.ko.md +41 -19
- package/docs/skill-compositions.md +1 -1
- package/docs/skill-compositions.pt-BR.md +220 -0
- package/docs/skill-compositions.zh-TW.md +40 -18
- package/out/server.js +82 -67
- package/out/setup-runner.js +34 -20
- package/out/setup.js +34 -20
- package/package.json +1 -1
- package/scripts/copy-skills.js +101 -26
- package/scripts/install.sh +1 -1
- package/scripts/setup.js +99 -6
- package/scripts/upstream-drift.js +40 -5
package/scripts/setup.js
CHANGED
|
@@ -7,16 +7,109 @@
|
|
|
7
7
|
const path = require("path");
|
|
8
8
|
const fs = require("fs");
|
|
9
9
|
|
|
10
|
-
const
|
|
10
|
+
const repoRoot = path.join(__dirname, "..");
|
|
11
|
+
const compiledRunner = path.join(repoRoot, "out", "setup-runner.js");
|
|
12
|
+
const lockPath = path.join(repoRoot, "out", ".setup-build.lock");
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
const inputFiles = [
|
|
15
|
+
path.join(repoRoot, "src", "setup-runner.ts"),
|
|
16
|
+
path.join(repoRoot, "src", "setup-cli.ts"),
|
|
17
|
+
path.join(repoRoot, "esbuild.js"),
|
|
18
|
+
path.join(repoRoot, "package.json"),
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function sleepSync(ms) {
|
|
22
|
+
// Portable synchronous sleep; blocks this thread while waiting on the lock.
|
|
23
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function newestInputMtime() {
|
|
27
|
+
let newest = 0;
|
|
28
|
+
for (const file of inputFiles) {
|
|
29
|
+
try {
|
|
30
|
+
newest = Math.max(newest, fs.statSync(file).mtimeMs);
|
|
31
|
+
} catch {
|
|
32
|
+
// Ignore missing inputs.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return newest;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Rebuild when the output is missing OR any build input is newer, so edits to the
|
|
39
|
+
// sources are never silently ignored (the old check only tested for existence).
|
|
40
|
+
function needsBuild() {
|
|
41
|
+
if (!fs.existsSync(compiledRunner)) {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
return fs.statSync(compiledRunner).mtimeMs < newestInputMtime();
|
|
46
|
+
} catch {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function acquireLock() {
|
|
52
|
+
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
53
|
+
try {
|
|
54
|
+
fs.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
55
|
+
return true;
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (err && err.code === "EEXIST") {
|
|
58
|
+
return false; // another build holds the lock
|
|
59
|
+
}
|
|
60
|
+
// Unexpected lock error (e.g. permissions) — proceed without exclusion; the
|
|
61
|
+
// build itself will surface any real failure.
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function releaseLock() {
|
|
14
67
|
try {
|
|
15
|
-
|
|
16
|
-
} catch
|
|
17
|
-
|
|
68
|
+
fs.unlinkSync(lockPath);
|
|
69
|
+
} catch {
|
|
70
|
+
// Ignore: we may not own it.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function lockIsStale() {
|
|
75
|
+
try {
|
|
76
|
+
return Date.now() - fs.statSync(lockPath).mtimeMs > 120000;
|
|
77
|
+
} catch {
|
|
78
|
+
return false; // already gone
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (needsBuild()) {
|
|
83
|
+
const { execSync } = require("child_process");
|
|
84
|
+
|
|
85
|
+
const deadline = Date.now() + 60000;
|
|
86
|
+
let owned = acquireLock();
|
|
87
|
+
|
|
88
|
+
// Wait for a concurrent build to finish (and release the lock) before building,
|
|
89
|
+
// so two invocations never run esbuild at once and interleave writes to out/*.
|
|
90
|
+
while (!owned && needsBuild() && Date.now() < deadline) {
|
|
91
|
+
if (lockIsStale()) {
|
|
92
|
+
releaseLock();
|
|
93
|
+
owned = acquireLock();
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
sleepSync(250);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (owned) {
|
|
100
|
+
try {
|
|
101
|
+
execSync("node esbuild.js", { cwd: repoRoot, stdio: "inherit" });
|
|
102
|
+
} catch (e) {
|
|
103
|
+
console.error("Failed to build out/setup-runner.js automatically:", e.message);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
} finally {
|
|
106
|
+
releaseLock();
|
|
107
|
+
}
|
|
108
|
+
} else if (needsBuild()) {
|
|
109
|
+
console.error("Timed out waiting for a concurrent build of out/setup-runner.js.");
|
|
18
110
|
process.exit(1);
|
|
19
111
|
}
|
|
112
|
+
// If !owned but !needsBuild(), another process finished the build — reuse it.
|
|
20
113
|
}
|
|
21
114
|
|
|
22
115
|
const runner = require(compiledRunner);
|
|
@@ -58,6 +58,7 @@ function parseArgs(argv) {
|
|
|
58
58
|
else if (arg === "--ref" || arg === "--repo" || arg === "--ignore") {
|
|
59
59
|
const value = argv[++i];
|
|
60
60
|
if (!value) throw new Error(`Missing value for ${arg}`);
|
|
61
|
+
if (value.startsWith("--")) throw new Error(`Missing value for ${arg} (got "${value}")`);
|
|
61
62
|
if (arg === "--ref") opts.ref = value;
|
|
62
63
|
else if (arg === "--repo") opts.repo = value;
|
|
63
64
|
else opts.ignore.push(value);
|
|
@@ -269,19 +270,46 @@ async function main() {
|
|
|
269
270
|
}
|
|
270
271
|
const upstreamSkills = [...new Set(Object.keys(tree.files).map(skillNameOf).filter(Boolean))].sort();
|
|
271
272
|
const recorded = { repo, ref, commit: tree.commit, capturedAt: new Date().toISOString().slice(0, 10), files, upstreamSkills, ignoredUpstreamSkills: ignored };
|
|
272
|
-
|
|
273
|
+
// Atomic write: temp file + rename so an interrupted --record can never
|
|
274
|
+
// truncate/corrupt the baseline (which a later run would then silently rebuild).
|
|
275
|
+
const tmpPath = `${BASELINE_PATH}.${process.pid}.tmp`;
|
|
276
|
+
try {
|
|
277
|
+
fs.writeFileSync(tmpPath, JSON.stringify(recorded, null, 2) + "\n", "utf-8");
|
|
278
|
+
fs.renameSync(tmpPath, BASELINE_PATH);
|
|
279
|
+
} catch (writeErr) {
|
|
280
|
+
try { fs.unlinkSync(tmpPath); } catch { /* ignore */ }
|
|
281
|
+
throw writeErr;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const coverageAfter = classifyCoverage(recorded, localPaths);
|
|
285
|
+
const movedCount = previousDrift.changed.length + previousDrift.added.length + previousDrift.removed.length;
|
|
286
|
+
if (opts.json) {
|
|
287
|
+
console.log(JSON.stringify({
|
|
288
|
+
recorded: true,
|
|
289
|
+
repo,
|
|
290
|
+
ref,
|
|
291
|
+
commit: recorded.commit,
|
|
292
|
+
capturedAt: recorded.capturedAt,
|
|
293
|
+
source: tree.source,
|
|
294
|
+
tracked: Object.keys(files).length,
|
|
295
|
+
upstreamTotal: Object.keys(tree.files).length,
|
|
296
|
+
movedSincePrevious: movedCount,
|
|
297
|
+
ignored: opts.ignore,
|
|
298
|
+
coverage: coverageAfter,
|
|
299
|
+
}, null, 2));
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
273
302
|
|
|
274
303
|
console.log(`Baseline recorded from ${tree.source}: ${repo}@${ref} ${tree.commit.slice(0, 12)} — ${Object.keys(files).length} tracked files of ${Object.keys(tree.files).length} upstream skill files`);
|
|
275
304
|
if (Object.keys(previousFiles).length > 0) {
|
|
276
|
-
|
|
277
|
-
console.log(moved === 0
|
|
305
|
+
console.log(movedCount === 0
|
|
278
306
|
? "No adopted skill file moved upstream since the previous baseline."
|
|
279
|
-
: `warning: ${
|
|
307
|
+
: `warning: ${movedCount} adopted skill file(s) moved upstream since the previous baseline — record only what you actually reviewed.`);
|
|
280
308
|
}
|
|
281
309
|
if (opts.ignore.length > 0) {
|
|
282
310
|
console.log(`Recorded as deliberately not adopted: ${opts.ignore.join(", ")}`);
|
|
283
311
|
}
|
|
284
|
-
reportCoverage(
|
|
312
|
+
reportCoverage(coverageAfter);
|
|
285
313
|
return;
|
|
286
314
|
}
|
|
287
315
|
|
|
@@ -305,6 +333,13 @@ async function main() {
|
|
|
305
333
|
|
|
306
334
|
const tree = await fetchUpstreamTree(repo, ref);
|
|
307
335
|
const drift = classifyDrift(baseline.files, tree.files, ignored);
|
|
336
|
+
// A truncated upstream tree has files missing from the listing; those baseline
|
|
337
|
+
// files would be misclassified as "removed". Suppress the unreliable removals so
|
|
338
|
+
// the report does not invent drift (changed/added only cover files that ARE listed
|
|
339
|
+
// and remain trustworthy).
|
|
340
|
+
if (tree.truncated) {
|
|
341
|
+
drift.removed = [];
|
|
342
|
+
}
|
|
308
343
|
const driftCount = drift.changed.length + drift.added.length + drift.removed.length;
|
|
309
344
|
if (opts.json) {
|
|
310
345
|
console.log(JSON.stringify({
|