@mmerterden/multi-agent-pipeline 20.8.1 → 20.8.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/CHANGELOG.md +42 -0
- package/docs/facts.json +3 -3
- package/index.js +1 -0
- package/install/_codex-agents.mjs +1 -1
- package/install/_common.mjs +535 -53
- package/install/_dev-only-files.mjs +1 -0
- package/install/_mcp-register.mjs +173 -117
- package/install/catalog-history.json +1 -0
- package/install/claude.mjs +293 -226
- package/install/codex.mjs +7 -7
- package/install/copilot.mjs +13 -11
- package/install/index.mjs +92 -27
- package/install/templates/claude-hooks.json +9 -9
- package/manifest.json +77 -79
- package/package.json +6 -2
- package/pipeline/commands/multi-agent/update/SKILL.md +28 -17
- package/pipeline/lib/confusables.json +79 -33
- package/pipeline/lib/extract-conventions.sh +3 -3
- package/pipeline/lib/json-file-lock.mjs +27 -7
- package/pipeline/lib/normalize-text.mjs +86 -17
- package/pipeline/lib/outbound-gate.mjs +13 -4
- package/pipeline/lib/redact.mjs +87 -14
- package/pipeline/multi-agent-refs/analysis/evidence.md +1 -1
- package/pipeline/multi-agent-refs/analysis/synthesis.md +1 -1
- package/pipeline/multi-agent-refs/component-dispatch.md +1 -1
- package/pipeline/multi-agent-refs/conventions-defaults.md +1 -1
- package/pipeline/multi-agent-refs/features/unattended-security.md +2 -2
- package/pipeline/scripts/agent-guard.py +150 -25
- package/pipeline/scripts/audit-log.sh +3 -4
- package/pipeline/scripts/autopilot-runner.mjs +14 -5
- package/pipeline/scripts/doctor.mjs +8 -2
- package/pipeline/scripts/log-metric.sh +9 -3
- package/pipeline/scripts/migrate-prefs.mjs +18 -4
- package/pipeline/scripts/pre-commit-check.sh +119 -31
- package/pipeline/scripts/scan-agent-config.sh +9 -9
- package/pipeline/scripts/unattended_policy.py +12 -3
- package/pipeline/scripts/uninstall.mjs +88 -1
- package/pipeline/scripts/usage-identity.mjs +1 -1
- package/pipeline/scripts/usage-register.mjs +1 -1
- package/pipeline/skills/.skill-manifest.json +11 -11
- package/pipeline/skills/shared/external/callkit-voip/SKILL.md +6 -3
- package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +43 -0
- package/pipeline/skills/shared/external/core-nfc/SKILL.md +31 -0
- package/pipeline/skills/shared/external/ios-coding-standard/references/rules.yml +2 -2
- package/pipeline/skills/shared/external/ios-module-structure/modules/_TEMPLATE.yml +1 -1
- package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +3 -3
- package/pipeline/skills/shared/external/localization-reuse-map/example-mapping.json +2 -1
- package/pipeline/skills/shared/external/localization-reuse-map/reference/format-and-output.md +14 -13
- package/pipeline/skills/shared/external/localization-reuse-map/reference/publish-and-snapshot.md +2 -2
- package/pipeline/skills/shared/external/localization-reuse-map/reference/sources-and-recipes.md +7 -7
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/_shared.py +105 -4
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/build-artifact.py +20 -5
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-annotations.py +8 -7
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/fetch-legacy-labels.py +7 -8
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/publish-confluence.py +20 -7
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-key-shots.py +4 -3
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/render-overlay.py +8 -7
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-legacy-values.py +15 -8
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/resolve-new-values.py +17 -9
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/scan-screen-keys.py +39 -17
- package/pipeline/skills/shared/external/localization-reuse-map/scripts/verify-map.py +28 -7
- package/pipeline/skills/shared/external/passkit-wallet/SKILL.md +5 -4
- package/pipeline/skills/shared/external/passkit-wallet/references/wallet-passes.md +3 -2
- package/pipeline/skills/shared/external/pencilkit-drawing/SKILL.md +2 -2
- package/pipeline/skills/shared/external/pencilkit-drawing/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/pencilkit-drawing/references/pencilkit-patterns.md +4 -4
- package/pipeline/skills/shared/external/permissionkit/SKILL.md +15 -6
- package/pipeline/skills/shared/external/permissionkit/references/permissionkit-patterns.md +2 -1
- package/pipeline/skills/shared/external/push-notifications/SKILL.md +8 -4
- package/pipeline/skills/shared/external/push-notifications/references/notification-patterns.md +1 -1
- package/pipeline/skills/shared/external/realitykit-ar/SKILL.md +25 -6
- package/pipeline/skills/shared/external/realitykit-ar/evals/evals.json +1 -1
- package/pipeline/skills/shared/external/skill-creator/template.md +1 -1
- package/pipeline/skills/shared/external/vision-framework/SKILL.md +3 -1
- package/pipeline/scripts/gen-ref-toc.mjs +0 -279
- package/pipeline/scripts/make-manifest.mjs +0 -199
- package/pipeline/scripts/scorecard-snapshot.mjs +0 -178
package/install/claude.mjs
CHANGED
|
@@ -5,46 +5,51 @@
|
|
|
5
5
|
* `CLAUDE.md` template, preferences template, and the secret-detection
|
|
6
6
|
* pre-commit hook in `settings.json`.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* trees on every release. (v11.4.1 intentionally changed two behaviours
|
|
11
|
-
* without changing the layout: agents are cleaned per-file instead of wiping
|
|
12
|
-
* the dir, and symlinked install targets are replaced before writing.)
|
|
8
|
+
* The install layout smoke (`smoke-install-layout.sh`) compares the installed
|
|
9
|
+
* tree against a committed fixture on every release.
|
|
13
10
|
*
|
|
14
11
|
* @module install/claude
|
|
15
12
|
*/
|
|
16
13
|
|
|
17
|
-
import { existsSync, readFileSync, readdirSync,
|
|
14
|
+
import { cpSync, existsSync, readFileSync, readdirSync, rmSync } from "fs";
|
|
18
15
|
import { join, dirname } from "path";
|
|
19
16
|
import { execSync } from "child_process";
|
|
20
17
|
|
|
21
18
|
import { localizeCommands } from "../pipeline/scripts/localize-commands.mjs";
|
|
22
19
|
|
|
23
20
|
import {
|
|
21
|
+
classifyInstallDir,
|
|
24
22
|
copyDir,
|
|
25
23
|
copyFile,
|
|
26
24
|
copySkillsIndex,
|
|
27
25
|
countFiles,
|
|
28
26
|
dirsIdentical,
|
|
27
|
+
loadCatalogHistory,
|
|
28
|
+
matchesShippedVersion,
|
|
29
29
|
ensureDir,
|
|
30
30
|
ensureRealDir,
|
|
31
31
|
isDryRun,
|
|
32
32
|
isLocalOnlySkill,
|
|
33
|
+
listFiles,
|
|
33
34
|
pruneAbandonedTrees,
|
|
34
35
|
pruneLegacyMultiAgentSkills,
|
|
35
36
|
pruneOrphanSkillFiles,
|
|
37
|
+
readInstallManifest,
|
|
36
38
|
removePipelineAgentFiles,
|
|
39
|
+
stageAndSwapTrees,
|
|
40
|
+
syncTrackedFiles,
|
|
37
41
|
wipeDir,
|
|
38
42
|
writeFile,
|
|
43
|
+
writeInstallManifest,
|
|
39
44
|
} from "./_common.mjs";
|
|
40
45
|
import { EXTERNAL_SKILLS_MANIFEST } from "./_platform-filter.mjs";
|
|
41
46
|
import { DEV_ONLY_SCRIPTS, countDevOnlyFiles } from "./_dev-only-files.mjs";
|
|
42
47
|
import { registerMcpServer } from "./_mcp-register.mjs";
|
|
43
48
|
|
|
44
49
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
50
|
+
* A matcher an older install wrote. Hook matchers only ever see the tool name,
|
|
51
|
+
* so this string never matches and the hook never runs. Kept so installs can
|
|
52
|
+
* migrate it and uninstall can clean it.
|
|
48
53
|
*/
|
|
49
54
|
export const LEGACY_PRE_COMMIT_MATCHER = "Bash(git commit:*)";
|
|
50
55
|
export const AGENT_GUARD_FILE_MATCHER = "Edit|Write|NotebookEdit";
|
|
@@ -87,33 +92,35 @@ export const PIPELINE_LOCAL_SKILLS = ["apple-archive-compliance", "google-play-c
|
|
|
87
92
|
export function installClaude(ctx) {
|
|
88
93
|
const { home, pipelineSrc, indexOnly, useSymlinks, platformFlag, pruneExternal } = ctx;
|
|
89
94
|
|
|
90
|
-
const
|
|
91
|
-
const CLAUDE_AGENTS = join(
|
|
92
|
-
const PREFS_PATH = join(
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
const CLAUDE_SKILLS = join(home, ".claude", "skills");
|
|
96
|
-
const CLAUDE_SCHEMAS = join(home, ".claude", "schemas");
|
|
97
|
-
const CLAUDE_LIB = join(home, ".claude", "lib");
|
|
98
|
-
const CLAUDE_MA_REFS = join(home, ".claude", "multi-agent-refs");
|
|
99
|
-
const CLAUDE_TEMPLATES = join(home, ".claude", "templates");
|
|
95
|
+
const CLAUDE_DIR = join(home, ".claude");
|
|
96
|
+
const CLAUDE_AGENTS = join(CLAUDE_DIR, "agents");
|
|
97
|
+
const PREFS_PATH = join(CLAUDE_DIR, "multi-agent-preferences.json");
|
|
98
|
+
const CLAUDE_RULES = join(CLAUDE_DIR, "rules");
|
|
99
|
+
const CLAUDE_SKILLS = join(CLAUDE_DIR, "skills");
|
|
100
100
|
|
|
101
101
|
// Before laying anything down: drop trees an older install created that no
|
|
102
102
|
// current installer manages. Wipe-before-copy only protects trees still being
|
|
103
|
-
// written, so an abandoned one
|
|
104
|
-
const prunedTrees = pruneAbandonedTrees(
|
|
103
|
+
// written, so an abandoned one would otherwise stay on disk indefinitely.
|
|
104
|
+
const prunedTrees = pruneAbandonedTrees(CLAUDE_DIR);
|
|
105
105
|
if (prunedTrees > 0) {
|
|
106
106
|
console.log(` [Claude Code] Removed ${prunedTrees} abandoned tree(s) from an older install`);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
const manifest = readInstallManifest(CLAUDE_DIR);
|
|
110
|
+
// An install without a manifest overwrote every file it shipped on each run,
|
|
111
|
+
// so after one (the version stamp says one happened) a file carrying a
|
|
112
|
+
// shipped name is that installer's copy. On a machine with no prior install
|
|
113
|
+
// the same file is the user's.
|
|
114
|
+
const adopt = !manifest.present && existsSync(join(CLAUDE_DIR, ".pipeline-version"));
|
|
115
|
+
|
|
116
|
+
if (useSymlinks) {
|
|
117
|
+
linkOwnedTrees(pipelineSrc, CLAUDE_DIR);
|
|
118
|
+
} else {
|
|
119
|
+
installOwnedTrees({ pipelineSrc, claudeDir: CLAUDE_DIR, manifest, adopt });
|
|
120
|
+
installTopLevelCommands({ pipelineSrc, claudeDir: CLAUDE_DIR, manifest, adopt });
|
|
121
|
+
}
|
|
112
122
|
installAgents(pipelineSrc, CLAUDE_AGENTS, useSymlinks);
|
|
113
123
|
installRules(pipelineSrc, CLAUDE_RULES, useSymlinks);
|
|
114
|
-
installSchemas(pipelineSrc, CLAUDE_SCHEMAS, useSymlinks);
|
|
115
|
-
installLib(pipelineSrc, CLAUDE_LIB, useSymlinks);
|
|
116
|
-
installTemplates(pipelineSrc, CLAUDE_TEMPLATES, useSymlinks);
|
|
117
124
|
runPreDeployScans(pipelineSrc);
|
|
118
125
|
installSkills({
|
|
119
126
|
pipelineSrc,
|
|
@@ -126,72 +133,179 @@ export function installClaude(ctx) {
|
|
|
126
133
|
ensureClaudeMd(home, pipelineSrc);
|
|
127
134
|
ensurePreferences(PREFS_PATH, pipelineSrc);
|
|
128
135
|
configureSettings(home);
|
|
136
|
+
if (!useSymlinks) writeInstallManifest(CLAUDE_DIR, manifest);
|
|
129
137
|
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
// reaches for one of them. `--scope user` is not optional here: `claude mcp add`
|
|
134
|
-
// defaults to project scope, which would register the server only for whichever
|
|
135
|
-
// directory the installer ran in.
|
|
138
|
+
// The multi-agent-toolkit server backs design-check, every ios_* / android_*
|
|
139
|
+
// simulator call and the archive audits; the skills fail only when a run
|
|
140
|
+
// reaches for one of its tools, so the installer registers it up front.
|
|
136
141
|
registerMcpServer("claude", "Claude Code");
|
|
137
142
|
|
|
138
143
|
console.log("");
|
|
139
144
|
}
|
|
140
145
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
146
|
+
/**
|
|
147
|
+
* The trees under ~/.claude the pipeline owns outright, laid down as one
|
|
148
|
+
* transaction: each is built beside its destination and all are swapped in
|
|
149
|
+
* together, so a failed copy leaves the previous install whole.
|
|
150
|
+
*
|
|
151
|
+
* `commands/multi-agent` is the pipeline's namespace; local-only alias
|
|
152
|
+
* wrappers (frontmatter `local-only: true`, written by /multi-agent:save and
|
|
153
|
+
* never shipped) are carried into the new copy. `lib/` and `templates/` can
|
|
154
|
+
* hold files the user put there, so they are built from the current copy and
|
|
155
|
+
* updated file by file against the install manifest.
|
|
156
|
+
*/
|
|
157
|
+
function installOwnedTrees({ pipelineSrc, claudeDir, manifest, adopt }) {
|
|
158
|
+
const commandsDir = ensureRealDir(join(claudeDir, "commands"), {
|
|
159
|
+
pipelineSrc,
|
|
160
|
+
userOwned: true,
|
|
161
|
+
});
|
|
162
|
+
const nsDest = join(commandsDir, "multi-agent");
|
|
163
|
+
const stashDir = join(commandsDir, ".multi-agent-wrapper-stash");
|
|
164
|
+
const trees = [];
|
|
165
|
+
const logs = [];
|
|
166
|
+
const add = (dest, label, src, build) => {
|
|
167
|
+
if (!src || !existsSync(src)) return;
|
|
168
|
+
if (classifyInstallDir(dest, pipelineSrc) === "foreign-link") {
|
|
169
|
+
ensureRealDir(dest, { pipelineSrc });
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
trees.push({ dest, label, build });
|
|
173
|
+
};
|
|
145
174
|
|
|
146
|
-
|
|
175
|
+
console.log(
|
|
176
|
+
" [Claude Code] Installing pipeline commands, refs, scripts, schemas, lib, templates...",
|
|
177
|
+
);
|
|
147
178
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
179
|
+
const nsSrc = join(pipelineSrc, "commands", "multi-agent");
|
|
180
|
+
add(nsDest, "commands", nsSrc, (staged) => {
|
|
181
|
+
copyDir(nsSrc, staged);
|
|
182
|
+
const restored = carryLocalOnlyWrappers(nsDest, stashDir, staged, pipelineSrc);
|
|
183
|
+
localizeCommandDescriptions(claudeDir, staged);
|
|
184
|
+
logs.push(` -> ${countFiles(nsSrc)} files copied to ${nsDest}`);
|
|
185
|
+
if (restored > 0) logs.push(` -> preserved ${restored} local-only alias wrapper(s)`);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// refs/ and the internal pickers live outside commands/ so Claude Code does
|
|
189
|
+
// not register them as `/multi-agent:refs:*` slash commands; commands read
|
|
190
|
+
// them by absolute path.
|
|
191
|
+
const refsSrc = join(pipelineSrc, "multi-agent-refs");
|
|
192
|
+
const refsDest = join(claudeDir, "multi-agent-refs");
|
|
193
|
+
add(refsDest, "refs", refsSrc, (staged) => {
|
|
194
|
+
copyDir(refsSrc, staged);
|
|
195
|
+
logs.push(` -> ${countFiles(refsSrc)} ref files copied to ${refsDest}`);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
const scriptsSrc = join(pipelineSrc, "scripts");
|
|
199
|
+
const scriptsDest = join(claudeDir, "scripts");
|
|
200
|
+
add(scriptsDest, "scripts", scriptsSrc, (staged) => {
|
|
201
|
+
copyDir(scriptsSrc, staged, { exclude: DEV_ONLY_SCRIPTS });
|
|
202
|
+
// DEV_ONLY_SCRIPTS mixes literal names, directories and regexes, so the
|
|
203
|
+
// excluded count comes from walking the tree.
|
|
204
|
+
const excluded = countDevOnlyFiles(scriptsSrc);
|
|
205
|
+
logs.push(
|
|
206
|
+
` -> ${countFiles(scriptsSrc) - excluded} files copied to ${scriptsDest} (${excluded} dev-only excluded)`,
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const schemasSrc = join(pipelineSrc, "schemas");
|
|
211
|
+
const schemasDest = join(claudeDir, "schemas");
|
|
212
|
+
add(schemasDest, "schemas", schemasSrc, (staged) => {
|
|
213
|
+
copyDir(schemasSrc, staged);
|
|
214
|
+
logs.push(` -> ${countFiles(schemasSrc)} files copied to ${schemasDest}`);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
for (const [name, src] of [
|
|
218
|
+
["lib", join(pipelineSrc, "lib")],
|
|
219
|
+
["templates", join(dirname(pipelineSrc), "install", "templates")],
|
|
220
|
+
]) {
|
|
221
|
+
const dest = join(claudeDir, name);
|
|
222
|
+
add(dest, name, src, (staged) => {
|
|
223
|
+
if (classifyInstallDir(dest, pipelineSrc) === "real") {
|
|
224
|
+
cpSync(dest, staged, { recursive: true });
|
|
225
|
+
} else {
|
|
226
|
+
ensureDir(staged);
|
|
227
|
+
}
|
|
228
|
+
const res = syncTrackedFiles({
|
|
229
|
+
srcDir: src,
|
|
230
|
+
destDir: staged,
|
|
231
|
+
displayDir: dest,
|
|
232
|
+
rels: listFiles(src),
|
|
233
|
+
prefix: `${name}/`,
|
|
234
|
+
manifest,
|
|
235
|
+
adopt,
|
|
236
|
+
reportUnknown: true,
|
|
237
|
+
label: "shipped",
|
|
238
|
+
});
|
|
239
|
+
logs.push(
|
|
240
|
+
` -> ${countFiles(src) - res.kept.length} files in place at ${dest}` +
|
|
241
|
+
(res.removed ? ` (${res.removed} retired file(s) removed)` : ""),
|
|
242
|
+
);
|
|
243
|
+
});
|
|
155
244
|
}
|
|
156
245
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
246
|
+
stageAndSwapTrees(trees, { swapRoot: join(claudeDir, ".pipeline-swap") });
|
|
247
|
+
for (const line of logs) console.log(line);
|
|
248
|
+
if (!isDryRun() && existsSync(stashDir) && trees.some((t) => t.dest === nsDest)) {
|
|
249
|
+
rmSync(stashDir, { recursive: true, force: true });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Top-level command files (`commands/*.md` outside the multi-agent namespace)
|
|
255
|
+
* share `~/.claude/commands/` with the user's own commands, so each is written
|
|
256
|
+
* only when absent or unchanged since the last install (install manifest).
|
|
257
|
+
*/
|
|
258
|
+
function installTopLevelCommands({ pipelineSrc, claudeDir, manifest, adopt }) {
|
|
259
|
+
const commandsSrc = join(pipelineSrc, "commands");
|
|
260
|
+
if (!existsSync(commandsSrc)) return;
|
|
261
|
+
const commandsDir = ensureRealDir(join(claudeDir, "commands"), {
|
|
262
|
+
pipelineSrc,
|
|
263
|
+
userOwned: true,
|
|
264
|
+
});
|
|
265
|
+
const rels = listFiles(commandsSrc).filter((r) => !r.startsWith("multi-agent/"));
|
|
266
|
+
if (!isDryRun()) ensureDir(commandsDir);
|
|
267
|
+
const res = syncTrackedFiles({
|
|
268
|
+
srcDir: commandsSrc,
|
|
269
|
+
destDir: commandsDir,
|
|
270
|
+
rels,
|
|
271
|
+
prefix: "commands/",
|
|
272
|
+
manifest,
|
|
273
|
+
adopt,
|
|
274
|
+
label: "shipped",
|
|
275
|
+
});
|
|
276
|
+
if (res.written || res.removed) {
|
|
277
|
+
console.log(
|
|
278
|
+
` -> ${res.written} top-level command file(s) written, ${res.removed} retired one(s) removed`,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** `--link` (dev) mode: every owned tree becomes a symlink into the checkout. */
|
|
284
|
+
function linkOwnedTrees(pipelineSrc, claudeDir) {
|
|
285
|
+
const links = [
|
|
286
|
+
["commands", join(pipelineSrc, "commands")],
|
|
287
|
+
["multi-agent-refs", join(pipelineSrc, "multi-agent-refs")],
|
|
288
|
+
["scripts", join(pipelineSrc, "scripts")],
|
|
289
|
+
["schemas", join(pipelineSrc, "schemas")],
|
|
290
|
+
["lib", join(pipelineSrc, "lib")],
|
|
291
|
+
["templates", join(dirname(pipelineSrc), "install", "templates")],
|
|
292
|
+
];
|
|
293
|
+
console.log(" [Claude Code] Linking pipeline trees (--link)...");
|
|
294
|
+
for (const [name, src] of links) {
|
|
295
|
+
if (!existsSync(src)) continue;
|
|
296
|
+
copyDir(src, join(claudeDir, name), { useSymlinks: true });
|
|
297
|
+
console.log(` -> ${join(claudeDir, name)} -> ${src}`);
|
|
182
298
|
}
|
|
183
|
-
localizeCommandDescriptions(pipelineSrc, ownedCmdDir);
|
|
184
299
|
}
|
|
185
300
|
|
|
186
301
|
// Picker descriptions follow prefs.global.outputLanguage: files shipping a
|
|
187
302
|
// description-tr frontmatter line get their description swapped in the
|
|
188
303
|
// INSTALLED tree only (repo and npm package stay English; /multi-agent:sync
|
|
189
304
|
// restores English before mirroring back). Failure is non-fatal.
|
|
190
|
-
function localizeCommandDescriptions(
|
|
305
|
+
function localizeCommandDescriptions(claudeDir, ownedCmdDir) {
|
|
191
306
|
if (isDryRun()) return;
|
|
192
307
|
try {
|
|
193
|
-
const
|
|
194
|
-
const prefsPath = join(home, "multi-agent-preferences.json");
|
|
308
|
+
const prefsPath = join(claudeDir, "multi-agent-preferences.json");
|
|
195
309
|
if (!existsSync(prefsPath)) return;
|
|
196
310
|
const prefs = JSON.parse(readFileSync(prefsPath, "utf-8"));
|
|
197
311
|
if (prefs?.global?.outputLanguage !== "tr") return;
|
|
@@ -204,87 +318,45 @@ function localizeCommandDescriptions(pipelineSrc, ownedCmdDir) {
|
|
|
204
318
|
}
|
|
205
319
|
}
|
|
206
320
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
for (const name of readdirSync(ownedCmdDir)) {
|
|
214
|
-
const dir = join(ownedCmdDir, name);
|
|
215
|
-
const skill = join(dir, "SKILL.md");
|
|
216
|
-
if (!existsSync(skill)) continue;
|
|
217
|
-
if (!/^local-only:\s*true\s*$/m.test(readFileSync(skill, "utf-8"))) continue;
|
|
218
|
-
ensureDir(stashDir);
|
|
219
|
-
const target = join(stashDir, name);
|
|
220
|
-
if (existsSync(target)) rmSync(target, { recursive: true, force: true });
|
|
221
|
-
renameSync(dir, target);
|
|
321
|
+
const isLocalOnlyDir = (dir) => {
|
|
322
|
+
const skill = join(dir, "SKILL.md");
|
|
323
|
+
try {
|
|
324
|
+
return existsSync(skill) && /^local-only:\s*true\s*$/m.test(readFileSync(skill, "utf-8"));
|
|
325
|
+
} catch {
|
|
326
|
+
return false;
|
|
222
327
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
function
|
|
230
|
-
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// Copy local-only wrappers from the current namespace, and from a stash an
|
|
331
|
+
// older installer's interrupted run left beside it, into the staged namespace.
|
|
332
|
+
// A wrapper whose name the release now ships cannot be carried; say whose
|
|
333
|
+
// content is dropped, since /multi-agent:save writes exactly these.
|
|
334
|
+
function carryLocalOnlyWrappers(nsDest, stashDir, staged, pipelineSrc) {
|
|
335
|
+
const sources = [];
|
|
336
|
+
if (classifyInstallDir(nsDest, pipelineSrc) === "real") sources.push(nsDest);
|
|
337
|
+
if (existsSync(stashDir)) sources.push(stashDir);
|
|
231
338
|
let n = 0;
|
|
232
|
-
for (const
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
339
|
+
for (const from of sources) {
|
|
340
|
+
for (const name of readdirSync(from)) {
|
|
341
|
+
const dir = join(from, name);
|
|
342
|
+
if (from === nsDest && !isLocalOnlyDir(dir)) continue;
|
|
343
|
+
const to = join(staged, name);
|
|
344
|
+
if (existsSync(to)) {
|
|
345
|
+
if (from === nsDest && existsSync(join(pipelineSrc, "commands", "multi-agent", name))) {
|
|
346
|
+
console.log(
|
|
347
|
+
` -> WARNING: dropped local-only wrapper '${name}' - this release ships a command ` +
|
|
348
|
+
`with that name. Re-save it under a different name if you still need it.`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
cpSync(dir, to, { recursive: true });
|
|
354
|
+
n++;
|
|
246
355
|
}
|
|
247
|
-
ensureDir(ownedCmdDir);
|
|
248
|
-
renameSync(from, to);
|
|
249
|
-
n++;
|
|
250
356
|
}
|
|
251
|
-
rmSync(stashDir, { recursive: true, force: true });
|
|
252
357
|
return n;
|
|
253
358
|
}
|
|
254
359
|
|
|
255
|
-
// The pipeline's reference docs (refs/) + internal pickers live OUTSIDE the
|
|
256
|
-
// commands/ tree so Claude Code does NOT register them as invocable
|
|
257
|
-
// `/multi-agent:refs:*` slash commands. Commands read them by absolute path
|
|
258
|
-
// ($HOME/.claude/multi-agent-refs/...). Wipe-before-copy so renamed/removed
|
|
259
|
-
// refs don't linger.
|
|
260
|
-
function installMultiAgentRefs(pipelineSrc, dest, useSymlinks) {
|
|
261
|
-
const refsSrc = join(pipelineSrc, "multi-agent-refs");
|
|
262
|
-
if (!existsSync(refsSrc)) return;
|
|
263
|
-
console.log(" [Claude Code] Installing multi-agent refs (non-command)...");
|
|
264
|
-
if (!useSymlinks) ensureRealDir(dest);
|
|
265
|
-
wipeDir(dest);
|
|
266
|
-
copyDir(refsSrc, dest, { useSymlinks });
|
|
267
|
-
console.log(` -> ${countFiles(refsSrc)} ref files copied to ${dest}`);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function installScripts(pipelineSrc, dest, useSymlinks) {
|
|
271
|
-
console.log(" [Claude Code] Installing scripts...");
|
|
272
|
-
const scriptsSrc = join(pipelineSrc, "scripts");
|
|
273
|
-
if (!existsSync(scriptsSrc)) return;
|
|
274
|
-
|
|
275
|
-
// Wipe-before-copy - scripts/ is a 100% pipeline-managed tree, so files
|
|
276
|
-
// removed upstream should not linger.
|
|
277
|
-
if (!useSymlinks) ensureRealDir(dest);
|
|
278
|
-
wipeDir(dest);
|
|
279
|
-
copyDir(scriptsSrc, dest, { exclude: DEV_ONLY_SCRIPTS, useSymlinks });
|
|
280
|
-
// Count files actually excluded by walking the tree: DEV_ONLY_SCRIPTS mixes
|
|
281
|
-
// literal names, whole directories, and regexes (the maintainer-smoke rule),
|
|
282
|
-
// so summing countFiles() over the array reported 0 for every regex entry.
|
|
283
|
-
const excludedCount = countDevOnlyFiles(scriptsSrc);
|
|
284
|
-
const scriptCount = countFiles(scriptsSrc) - excludedCount;
|
|
285
|
-
console.log(` -> ${scriptCount} files copied to ${dest} (${excludedCount} dev-only excluded)`);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
360
|
function installAgents(pipelineSrc, dest, useSymlinks) {
|
|
289
361
|
console.log(" [Claude Code] Installing agent definitions...");
|
|
290
362
|
const agentsSrc = join(pipelineSrc, "agents");
|
|
@@ -292,11 +364,12 @@ function installAgents(pipelineSrc, dest, useSymlinks) {
|
|
|
292
364
|
// ~/.claude/agents also holds user-authored agent files. Never wipe the
|
|
293
365
|
// whole dir; remove only the pipeline-shipped agent files (name set derived
|
|
294
366
|
// from the source tree so it cannot go stale), then copy the fresh set in.
|
|
367
|
+
let target = dest;
|
|
295
368
|
if (!useSymlinks) {
|
|
296
|
-
ensureRealDir(dest);
|
|
297
|
-
removePipelineAgentFiles(
|
|
369
|
+
target = ensureRealDir(dest, { pipelineSrc, userOwned: true });
|
|
370
|
+
removePipelineAgentFiles(target, agentsSrc);
|
|
298
371
|
}
|
|
299
|
-
copyDir(agentsSrc,
|
|
372
|
+
copyDir(agentsSrc, target, { useSymlinks });
|
|
300
373
|
console.log(` -> ${countFiles(agentsSrc)} files copied to ${dest}`);
|
|
301
374
|
}
|
|
302
375
|
|
|
@@ -304,59 +377,19 @@ function installRules(pipelineSrc, dest, useSymlinks) {
|
|
|
304
377
|
console.log(" [Claude Code] Installing rules...");
|
|
305
378
|
const rulesSrc = join(pipelineSrc, "rules");
|
|
306
379
|
if (!existsSync(rulesSrc)) return;
|
|
307
|
-
// rules/ is
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
// user's existing rules untouched. (Symlink mode is a dev choice and keeps its
|
|
312
|
-
// link-replace behavior.)
|
|
380
|
+
// rules/ is user-owned: users evolve their global rules locally and the sync
|
|
381
|
+
// never pushes rules/ back to the repo. An update installs only files that
|
|
382
|
+
// do not exist yet and leaves the user's rules untouched. (Symlink mode is a
|
|
383
|
+
// dev choice and keeps its link-replace behavior.)
|
|
313
384
|
if (useSymlinks) {
|
|
314
385
|
copyDir(rulesSrc, dest, { useSymlinks });
|
|
315
386
|
} else {
|
|
316
|
-
ensureRealDir(dest);
|
|
317
|
-
copyDir(rulesSrc,
|
|
387
|
+
const target = ensureRealDir(dest, { pipelineSrc, userOwned: true });
|
|
388
|
+
copyDir(rulesSrc, target, { skipExisting: true });
|
|
318
389
|
}
|
|
319
390
|
console.log(` -> rules present (existing local rules preserved)`);
|
|
320
391
|
}
|
|
321
392
|
|
|
322
|
-
function installSchemas(pipelineSrc, dest, useSymlinks) {
|
|
323
|
-
console.log(" [Claude Code] Installing JSON schemas...");
|
|
324
|
-
const schemasSrc = join(pipelineSrc, "schemas");
|
|
325
|
-
if (!existsSync(schemasSrc)) return;
|
|
326
|
-
// Wipe-before-copy - schemas/ is a 100% pipeline-managed tree, so files
|
|
327
|
-
// removed upstream should not linger.
|
|
328
|
-
if (!useSymlinks) ensureRealDir(dest);
|
|
329
|
-
wipeDir(dest);
|
|
330
|
-
copyDir(schemasSrc, dest, { useSymlinks });
|
|
331
|
-
console.log(` -> ${countFiles(schemasSrc)} files copied to ${dest}`);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
function installLib(pipelineSrc, dest, useSymlinks) {
|
|
335
|
-
console.log(" [Claude Code] Installing shell libraries...");
|
|
336
|
-
const libSrc = join(pipelineSrc, "lib");
|
|
337
|
-
if (!existsSync(libSrc)) return;
|
|
338
|
-
// Wipe-before-copy - lib/ is a 100% pipeline-managed tree.
|
|
339
|
-
if (!useSymlinks) ensureRealDir(dest);
|
|
340
|
-
wipeDir(dest);
|
|
341
|
-
copyDir(libSrc, dest, { useSymlinks });
|
|
342
|
-
console.log(` -> ${countFiles(libSrc)} files copied to ${dest}`);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function installTemplates(pipelineSrc, dest, useSymlinks) {
|
|
346
|
-
// The templates shipped in the tarball but never reached the user tree, so
|
|
347
|
-
// `setup` told people to merge `install/templates/claude-hooks.json` - a path
|
|
348
|
-
// that exists only in a checkout. From an install the instruction named a file
|
|
349
|
-
// the reader did not have, and nothing said so.
|
|
350
|
-
console.log(" [Claude Code] Installing templates...");
|
|
351
|
-
const templatesSrc = join(dirname(pipelineSrc), "install", "templates");
|
|
352
|
-
if (!existsSync(templatesSrc)) return;
|
|
353
|
-
// Wipe-before-copy - templates/ is a 100% pipeline-managed tree.
|
|
354
|
-
if (!useSymlinks) ensureRealDir(dest);
|
|
355
|
-
wipeDir(dest);
|
|
356
|
-
copyDir(templatesSrc, dest, { useSymlinks });
|
|
357
|
-
console.log(` -> ${countFiles(templatesSrc)} files copied to ${dest}`);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
393
|
function runPreDeployScans(pipelineSrc) {
|
|
361
394
|
// Pre-deploy security scan - warn-only, never halts install on its own.
|
|
362
395
|
const scanScript = join(pipelineSrc, "scripts", "scan-skills.sh");
|
|
@@ -398,12 +431,13 @@ function runPreDeployScans(pipelineSrc) {
|
|
|
398
431
|
}
|
|
399
432
|
|
|
400
433
|
function installSkills(opts) {
|
|
401
|
-
const { pipelineSrc,
|
|
434
|
+
const { pipelineSrc, indexOnly, useSymlinks, pruneExternal } = opts;
|
|
402
435
|
console.log(" [Claude Code] Installing skills...");
|
|
403
436
|
|
|
404
437
|
// Same symlink guard as the other owned trees: never prune or copy through
|
|
405
|
-
// a --link-era symlink into the repo checkout.
|
|
406
|
-
|
|
438
|
+
// a --link-era symlink into the repo checkout. The skills root also holds
|
|
439
|
+
// the user's own skills, so a symlink elsewhere is written through.
|
|
440
|
+
const dest = useSymlinks ? opts.dest : ensureRealDir(opts.dest, { pipelineSrc, userOwned: true });
|
|
407
441
|
|
|
408
442
|
if (indexOnly) {
|
|
409
443
|
ensureDir(dest);
|
|
@@ -417,12 +451,11 @@ function installSkills(opts) {
|
|
|
417
451
|
|
|
418
452
|
let claudeSkillCount = 0;
|
|
419
453
|
|
|
420
|
-
// Claude Code
|
|
421
|
-
//
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
//
|
|
425
|
-
// they are deliberately not published to the marketplace.
|
|
454
|
+
// Claude Code gets its stack skills from the marketplace plugins
|
|
455
|
+
// (`ai-<stack>-toolkit`) only: a local copy would duplicate them against the
|
|
456
|
+
// enabled plugins and shadow a plugin once the copy went stale. Only the two
|
|
457
|
+
// pipeline-owned compliance catalogs stay local (PIPELINE_LOCAL_SKILLS); they
|
|
458
|
+
// are deliberately not published to the marketplace.
|
|
426
459
|
ensureDir(dest);
|
|
427
460
|
for (const name of PIPELINE_LOCAL_SKILLS) {
|
|
428
461
|
const from = join(sharedCoreSrc, name);
|
|
@@ -434,9 +467,8 @@ function installSkills(opts) {
|
|
|
434
467
|
|
|
435
468
|
// Single-standard enforcement: Claude Code invokes pipeline commands via
|
|
436
469
|
// the `/multi-agent:*` slash-command namespace, so prune duplicate skill
|
|
437
|
-
// dirs left by older installs that copied shared/core wholesale.
|
|
438
|
-
//
|
|
439
|
-
// the 51 multi-agent-* dirs return on the first install after a migration.
|
|
470
|
+
// dirs left by older installs that copied shared/core wholesale. It runs on
|
|
471
|
+
// every install, so such dirs cannot survive a migration.
|
|
440
472
|
const pruned = pruneLegacyMultiAgentSkills(dest);
|
|
441
473
|
if (pruned > 0) {
|
|
442
474
|
console.log(
|
|
@@ -457,11 +489,13 @@ function installSkills(opts) {
|
|
|
457
489
|
// Pre-manifest installs (<= v14.x) never wrote that manifest, so the branch
|
|
458
490
|
// above cannot fire for them and the full 151-dir catalog would outlive every
|
|
459
491
|
// upgrade. For those, prune by proof instead of by name: a dir byte-identical
|
|
460
|
-
// to the shipped catalog
|
|
492
|
+
// to the shipped catalog, or to any version of it ever shipped
|
|
493
|
+
// (install/catalog-history.json), contains nothing of the user's and goes now; a dir
|
|
461
494
|
// that differs is kept unless --prune-external explicitly opts in (and even
|
|
462
495
|
// then a `local-only: true` SKILL.md keeps it).
|
|
463
496
|
migratePreManifestExternalSkills(dest, join(pipelineSrc, "skills", "shared", "external"), {
|
|
464
497
|
pruneExternal: Boolean(pruneExternal),
|
|
498
|
+
history: loadCatalogHistory(join(dirname(pipelineSrc), "install", "catalog-history.json")),
|
|
465
499
|
});
|
|
466
500
|
|
|
467
501
|
// Figma component skills moved to the ai-<platform>-toolkit marketplace
|
|
@@ -482,12 +516,9 @@ function installSkills(opts) {
|
|
|
482
516
|
}
|
|
483
517
|
}
|
|
484
518
|
|
|
485
|
-
// The index ships on
|
|
486
|
-
//
|
|
487
|
-
//
|
|
488
|
-
// --index-only copied it, so flipping the pref on a normal install produced an
|
|
489
|
-
// exit-1 "cannot read index" on the first dispatch. Two small files, and the
|
|
490
|
-
// feature is either wired or it is not.
|
|
519
|
+
// The index ships on full installs too: `match-skills.mjs` reads it when
|
|
520
|
+
// prefs.global.dynamicSkillLoading is on, and the preferences schema states
|
|
521
|
+
// that a full install provides it.
|
|
491
522
|
copySkillsIndex(pipelineSrc, dest);
|
|
492
523
|
|
|
493
524
|
// Same pre-directory-layout leftovers Copilot accumulated. Claude Code's tree is
|
|
@@ -570,8 +601,8 @@ function pruneManifestDeliveredSkills(dest) {
|
|
|
570
601
|
/**
|
|
571
602
|
* Migration for pre-manifest installs (<= v14.x): the external catalog was
|
|
572
603
|
* copied here with no delivery manifest, so ours and the user's are told apart
|
|
573
|
-
* by content, not by name. Byte-identical to the shipped catalog
|
|
574
|
-
* ours, removed. Different = kept, reported, and removable only via the
|
|
604
|
+
* by content, not by name. Byte-identical to the shipped catalog, or every
|
|
605
|
+
* file matching a version the catalog once shipped = provably ours, removed. Different = kept, reported, and removable only via the
|
|
575
606
|
* explicit `--prune-external` flag (which still honors `local-only: true`).
|
|
576
607
|
*
|
|
577
608
|
* @param {string} dest - the installed skills directory
|
|
@@ -580,7 +611,7 @@ function pruneManifestDeliveredSkills(dest) {
|
|
|
580
611
|
*/
|
|
581
612
|
function migratePreManifestExternalSkills(dest, externalSrc, opts) {
|
|
582
613
|
if (!existsSync(externalSrc) || !existsSync(dest)) return;
|
|
583
|
-
const { pruneExternal } = opts;
|
|
614
|
+
const { pruneExternal, history } = opts;
|
|
584
615
|
let identical = 0;
|
|
585
616
|
let flagged = 0;
|
|
586
617
|
const leftover = [];
|
|
@@ -608,13 +639,15 @@ function migratePreManifestExternalSkills(dest, externalSrc, opts) {
|
|
|
608
639
|
|
|
609
640
|
let provablyOurs;
|
|
610
641
|
try {
|
|
611
|
-
provablyOurs =
|
|
642
|
+
provablyOurs =
|
|
643
|
+
dirsIdentical(join(externalSrc, name), stale) ||
|
|
644
|
+
matchesShippedVersion(stale, history, name);
|
|
612
645
|
} catch {
|
|
613
646
|
provablyOurs = false;
|
|
614
647
|
}
|
|
615
648
|
if (provablyOurs) {
|
|
616
649
|
if (isDryRun()) {
|
|
617
|
-
console.log(` [dry-run] would prune ${name}/ (
|
|
650
|
+
console.log(` [dry-run] would prune ${name}/ (matches a shipped catalog version)`);
|
|
618
651
|
identical++;
|
|
619
652
|
continue;
|
|
620
653
|
}
|
|
@@ -718,6 +751,39 @@ export function collapseDuplicateHooks(hooks) {
|
|
|
718
751
|
return changed;
|
|
719
752
|
}
|
|
720
753
|
|
|
754
|
+
/**
|
|
755
|
+
* The hook command for a script installed under ~/.claude/scripts. The path is
|
|
756
|
+
* quoted: Claude Code runs the command through a shell, and an unquoted $HOME
|
|
757
|
+
* containing a space splits into two words, bash exits 126/127 and the gate
|
|
758
|
+
* stops running without anyone seeing it.
|
|
759
|
+
*
|
|
760
|
+
* @param {string} script file name under ~/.claude/scripts
|
|
761
|
+
*/
|
|
762
|
+
export const pipelineHookCommand = (script) => `bash "$HOME/.claude/scripts/${script}"`;
|
|
763
|
+
|
|
764
|
+
const UNQUOTED_PIPELINE_HOOK =
|
|
765
|
+
/^bash (?:\$HOME|\$\{HOME\})\/\.claude\/scripts\/([\w.-]+\.sh)(\s.*)?$/;
|
|
766
|
+
|
|
767
|
+
// Rewrite pipeline hook commands registered with an unquoted $HOME (by this
|
|
768
|
+
// installer or a merged hooks template) to the quoted form, in place, so a
|
|
769
|
+
// re-install fixes them instead of adding a second copy.
|
|
770
|
+
export function quotePipelineHookCommands(hooks) {
|
|
771
|
+
if (!hooks || typeof hooks !== "object") return false;
|
|
772
|
+
let changed = false;
|
|
773
|
+
for (const groups of Object.values(hooks)) {
|
|
774
|
+
if (!Array.isArray(groups)) continue;
|
|
775
|
+
for (const group of groups) {
|
|
776
|
+
for (const h of Array.isArray(group?.hooks) ? group.hooks : []) {
|
|
777
|
+
const m = typeof h?.command === "string" ? UNQUOTED_PIPELINE_HOOK.exec(h.command) : null;
|
|
778
|
+
if (!m) continue;
|
|
779
|
+
h.command = `${pipelineHookCommand(m[1])}${m[2] || ""}`;
|
|
780
|
+
changed = true;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
return changed;
|
|
785
|
+
}
|
|
786
|
+
|
|
721
787
|
export function configureSettings(home) {
|
|
722
788
|
console.log(" [Claude Code] Configuring hooks + context management...");
|
|
723
789
|
const SETTINGS_PATH = join(home, ".claude", "settings.json");
|
|
@@ -736,10 +802,11 @@ export function configureSettings(home) {
|
|
|
736
802
|
|
|
737
803
|
if (!settings.hooks) settings.hooks = {};
|
|
738
804
|
if (!settings.hooks.PreToolUse) settings.hooks.PreToolUse = [];
|
|
805
|
+
if (quotePipelineHookCommands(settings.hooks)) settingsChanged = true;
|
|
739
806
|
// PreToolUse matchers are regexes over the TOOL NAME only, so the correct
|
|
740
|
-
// matcher is "Bash".
|
|
741
|
-
//
|
|
742
|
-
//
|
|
807
|
+
// matcher is "Bash". A "Bash(git commit:*)" matcher (LEGACY_PRE_COMMIT_MATCHER)
|
|
808
|
+
// never matches a tool name, so the gate would never fire; such an entry
|
|
809
|
+
// is migrated in place instead of stacking a duplicate.
|
|
743
810
|
// Command-level filtering (only scan on actual `git commit`) happens
|
|
744
811
|
// inside pre-commit-check.sh via the hook's stdin JSON.
|
|
745
812
|
const isPreCommitEntry = (h) =>
|
|
@@ -762,7 +829,7 @@ export function configureSettings(home) {
|
|
|
762
829
|
hooks: [
|
|
763
830
|
{
|
|
764
831
|
type: "command",
|
|
765
|
-
command: "
|
|
832
|
+
command: pipelineHookCommand("pre-commit-check.sh"),
|
|
766
833
|
timeout: 15,
|
|
767
834
|
statusMessage: "Scanning staged changes for secrets...",
|
|
768
835
|
},
|
|
@@ -813,7 +880,7 @@ export function configureSettings(home) {
|
|
|
813
880
|
hooks: [
|
|
814
881
|
{
|
|
815
882
|
type: "command",
|
|
816
|
-
command: "
|
|
883
|
+
command: pipelineHookCommand("agent-guard.sh"),
|
|
817
884
|
timeout: AGENT_GUARD_TIMEOUT,
|
|
818
885
|
statusMessage,
|
|
819
886
|
},
|