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