@orchestrator-claude/definitions 3.32.2 → 3.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -0
- package/agents/debug-sidecar.md +1 -1
- package/agents/doc-sidecar.md +1 -1
- package/agents/test-sidecar.md +1 -1
- package/dist/cli.js +78 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/DiffCommand.js +1 -1
- package/dist/commands/DiffCommand.js.map +1 -1
- package/dist/commands/InstallCommand.d.ts +41 -0
- package/dist/commands/InstallCommand.d.ts.map +1 -0
- package/dist/commands/InstallCommand.js +253 -0
- package/dist/commands/InstallCommand.js.map +1 -0
- package/dist/http/ApiClient.d.ts +5 -0
- package/dist/http/ApiClient.d.ts.map +1 -1
- package/dist/http/ApiClient.js.map +1 -1
- package/dist/install/DevCheckoutDetector.d.ts +66 -0
- package/dist/install/DevCheckoutDetector.d.ts.map +1 -0
- package/dist/install/DevCheckoutDetector.js +150 -0
- package/dist/install/DevCheckoutDetector.js.map +1 -0
- package/dist/install/HooksWirer.d.ts +29 -0
- package/dist/install/HooksWirer.d.ts.map +1 -0
- package/dist/install/HooksWirer.js +80 -0
- package/dist/install/HooksWirer.js.map +1 -0
- package/dist/install/LocalDiffReporter.d.ts +25 -0
- package/dist/install/LocalDiffReporter.d.ts.map +1 -0
- package/dist/install/LocalDiffReporter.js +50 -0
- package/dist/install/LocalDiffReporter.js.map +1 -0
- package/dist/install/Materializer.d.ts +67 -0
- package/dist/install/Materializer.d.ts.map +1 -0
- package/dist/install/Materializer.js +196 -0
- package/dist/install/Materializer.js.map +1 -0
- package/dist/install/McpRegistrar.d.ts +63 -0
- package/dist/install/McpRegistrar.d.ts.map +1 -0
- package/dist/install/McpRegistrar.js +114 -0
- package/dist/install/McpRegistrar.js.map +1 -0
- package/dist/install/ProcessRunner.d.ts +25 -0
- package/dist/install/ProcessRunner.d.ts.map +1 -0
- package/dist/install/ProcessRunner.js +41 -0
- package/dist/install/ProcessRunner.js.map +1 -0
- package/dist/install/ProjectSettingsNeutralizer.d.ts +39 -0
- package/dist/install/ProjectSettingsNeutralizer.d.ts.map +1 -0
- package/dist/install/ProjectSettingsNeutralizer.js +100 -0
- package/dist/install/ProjectSettingsNeutralizer.js.map +1 -0
- package/dist/install/types.d.ts +52 -0
- package/dist/install/types.d.ts.map +1 -0
- package/dist/install/types.js +8 -0
- package/dist/install/types.js.map +1 -0
- package/dist/manifest/ManifestLoader.d.ts.map +1 -1
- package/dist/manifest/ManifestLoader.js +3 -2
- package/dist/manifest/ManifestLoader.js.map +1 -1
- package/dist/manifest/types.d.ts +3 -0
- package/dist/manifest/types.d.ts.map +1 -1
- package/dist/scripts/bundle-hooks.d.ts +39 -0
- package/dist/scripts/bundle-hooks.d.ts.map +1 -0
- package/dist/scripts/bundle-hooks.js +165 -0
- package/dist/scripts/bundle-hooks.js.map +1 -0
- package/dist/scripts/generate-manifest.d.ts +22 -3
- package/dist/scripts/generate-manifest.d.ts.map +1 -1
- package/dist/scripts/generate-manifest.js +78 -35
- package/dist/scripts/generate-manifest.js.map +1 -1
- package/hooks/dangling-guard.mjs +7426 -0
- package/hooks/dangling-guard.ts +223 -6
- package/hooks/gate-guardian.mjs +463 -0
- package/hooks/lib/additional-directories-sync.ts +295 -0
- package/hooks/lib/api-client.ts +388 -7
- package/hooks/lib/session-selection.ts +81 -0
- package/hooks/mailbox-listener.mjs +259 -0
- package/hooks/post-compact.mjs +7931 -0
- package/hooks/post-implement.mjs +238 -0
- package/hooks/session-start.mjs +8234 -0
- package/hooks/session-start.ts +264 -23
- package/hooks/sprint-registry.mjs +59 -0
- package/hooks/subagent-start.mjs +289 -0
- package/hooks/subagent-stop.mjs +7793 -0
- package/hooks/subagent-stop.ts +4 -2
- package/hooks/user-prompt.mjs +329 -0
- package/hooks/workflow-guard.mjs +7805 -0
- package/hooks/workflow-guard.ts +360 -119
- package/hooks-settings-template.json +63 -0
- package/manifest.json +54 -68
- package/mcp-servers.json +48 -0
- package/package.json +10 -4
- package/personas/auditor-compliance.md +104 -0
- package/personas/cfo-conservador.md +88 -0
- package/personas/designer-perfeccionista.md +104 -0
- package/personas/diretor-cetico-fmcg.md +80 -0
- package/personas/engenheiro-cetico-construcao.md +107 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Materializer - stage→verify→swap atomic materialization of manifest
|
|
3
|
+
* entries into the target filesystem root (RF-001, ADR-001/ADR-003/ADR-004).
|
|
4
|
+
*
|
|
5
|
+
* Never mutates `targetRoot` directly: `stage()` writes to an isolated
|
|
6
|
+
* sibling staging directory (the `~/.claude.install-tmp-<pid>/` pattern);
|
|
7
|
+
* only `swap()` touches `targetRoot`, via an atomic rename per category
|
|
8
|
+
* (falling back to copy+remove across filesystem boundaries, ADR-003).
|
|
9
|
+
*/
|
|
10
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync, renameSync, cpSync, lstatSync, } from 'node:fs';
|
|
11
|
+
import { dirname, join, resolve } from 'node:path';
|
|
12
|
+
import { createHash } from 'node:crypto';
|
|
13
|
+
function sha256(content) {
|
|
14
|
+
return `sha256:${createHash('sha256').update(content).digest('hex')}`;
|
|
15
|
+
}
|
|
16
|
+
export class Materializer {
|
|
17
|
+
loader;
|
|
18
|
+
targetRoot;
|
|
19
|
+
constructor(loader, targetRoot) {
|
|
20
|
+
this.loader = loader;
|
|
21
|
+
this.targetRoot = targetRoot;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Stages every manifested definition entry into an isolated sibling
|
|
25
|
+
* directory (never `targetRoot`). Iterates exclusively via
|
|
26
|
+
* `loader.getAllEntries()` — never `package.json.files` (R-7 regression,
|
|
27
|
+
* spec finding 2.4.1: the dead `workflows` entry).
|
|
28
|
+
*/
|
|
29
|
+
async stage() {
|
|
30
|
+
const stagingDir = this.stagingDirPath();
|
|
31
|
+
if (existsSync(stagingDir)) {
|
|
32
|
+
rmSync(stagingDir, { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
mkdirSync(stagingDir, { recursive: true });
|
|
35
|
+
const entries = this.loader.getAllEntries();
|
|
36
|
+
for (const entry of entries) {
|
|
37
|
+
const destPath = join(stagingDir, entry.file);
|
|
38
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
39
|
+
writeFileSync(destPath, this.loader.readDefinitionContent(entry));
|
|
40
|
+
}
|
|
41
|
+
return { stagingDir, entries };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Recomputes the SHA-256 checksum of every staged file against the
|
|
45
|
+
* manifest. Pure read-only comparison — never mutates `targetRoot`, so
|
|
46
|
+
* aborting after a failed `verify()` is always safe.
|
|
47
|
+
*/
|
|
48
|
+
verify(stagingDir) {
|
|
49
|
+
const entries = this.loader.getAllEntries();
|
|
50
|
+
const mismatches = [];
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
const stagedPath = join(stagingDir, entry.file);
|
|
53
|
+
if (!existsSync(stagedPath)) {
|
|
54
|
+
mismatches.push(entry);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const content = readFileSync(stagedPath, 'utf-8');
|
|
58
|
+
if (sha256(content) !== entry.checksum) {
|
|
59
|
+
mismatches.push(entry);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return { valid: mismatches.length === 0, mismatches };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Atomically renames each staged category directory into `targetRoot`
|
|
66
|
+
* (ADR-003). Removes an INTERIM symlink at the target category before
|
|
67
|
+
* writing (FA-002) — never writes through it. Cleans up the staging
|
|
68
|
+
* directory in a `finally`, whether swap succeeds or aborts partway
|
|
69
|
+
* (ADR-003 consequence).
|
|
70
|
+
*/
|
|
71
|
+
async swap(stagingDir) {
|
|
72
|
+
try {
|
|
73
|
+
const entries = this.loader.getAllEntries();
|
|
74
|
+
const { filesWritten, filesUnchanged } = this.countChanges(stagingDir, entries);
|
|
75
|
+
const categories = Array.from(new Set(entries.map((entry) => entry.category)));
|
|
76
|
+
for (const category of categories) {
|
|
77
|
+
this.swapCategory(stagingDir, category);
|
|
78
|
+
}
|
|
79
|
+
return { filesWritten, filesUnchanged };
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
if (existsSync(stagingDir)) {
|
|
83
|
+
rmSync(stagingDir, { recursive: true, force: true });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Recursively copies skill asset files that are NOT covered by any
|
|
89
|
+
* manifest entry (spec finding 2.4.2 / risk R-5) — e.g.
|
|
90
|
+
* `skills/<slug>/scripts/*.js`, never scanned by generate-manifest.ts,
|
|
91
|
+
* which only tracks a single `.md` level directly under each skill
|
|
92
|
+
* directory (no recursion into subdirectories).
|
|
93
|
+
*/
|
|
94
|
+
async copySkillAssets(stagingDir) {
|
|
95
|
+
const packageRoot = this.loader.getPackageRoot();
|
|
96
|
+
const skillsDir = resolve(packageRoot, 'skills');
|
|
97
|
+
if (!existsSync(skillsDir)) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
const manifestedSkillFiles = new Set(this.loader
|
|
101
|
+
.getAllEntries()
|
|
102
|
+
.filter((entry) => entry.category === 'skills')
|
|
103
|
+
.map((entry) => entry.file));
|
|
104
|
+
const copied = [];
|
|
105
|
+
const slugs = readdirSync(skillsDir).filter((name) => statSync(join(skillsDir, name)).isDirectory());
|
|
106
|
+
for (const slug of slugs) {
|
|
107
|
+
const slugRelativeDir = join('skills', slug);
|
|
108
|
+
for (const relativeFile of this.walkRecursive(join(skillsDir, slug), slugRelativeDir)) {
|
|
109
|
+
if (manifestedSkillFiles.has(relativeFile)) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const destPath = join(stagingDir, relativeFile);
|
|
113
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
114
|
+
copyFileSync(resolve(packageRoot, relativeFile), destPath);
|
|
115
|
+
copied.push(relativeFile);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return copied;
|
|
119
|
+
}
|
|
120
|
+
countChanges(stagingDir, entries) {
|
|
121
|
+
let filesWritten = 0;
|
|
122
|
+
let filesUnchanged = 0;
|
|
123
|
+
for (const entry of entries) {
|
|
124
|
+
const targetPath = join(this.targetRoot, entry.file);
|
|
125
|
+
if (existsSync(targetPath)) {
|
|
126
|
+
const stagedPath = join(stagingDir, entry.file);
|
|
127
|
+
if (readFileSync(targetPath, 'utf-8') === readFileSync(stagedPath, 'utf-8')) {
|
|
128
|
+
filesUnchanged++;
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
filesWritten++;
|
|
133
|
+
}
|
|
134
|
+
return { filesWritten, filesUnchanged };
|
|
135
|
+
}
|
|
136
|
+
stagingDirPath() {
|
|
137
|
+
return `${this.targetRoot}.install-tmp-${process.pid}`;
|
|
138
|
+
}
|
|
139
|
+
swapCategory(stagingDir, category) {
|
|
140
|
+
const stagingCategoryPath = join(stagingDir, category);
|
|
141
|
+
if (!existsSync(stagingCategoryPath)) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const targetCategoryPath = join(this.targetRoot, category);
|
|
145
|
+
this.removeExistingTarget(targetCategoryPath);
|
|
146
|
+
mkdirSync(this.targetRoot, { recursive: true });
|
|
147
|
+
try {
|
|
148
|
+
renameSync(stagingCategoryPath, targetCategoryPath);
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
if (error.code === 'EXDEV') {
|
|
152
|
+
cpSync(stagingCategoryPath, targetCategoryPath, { recursive: true });
|
|
153
|
+
rmSync(stagingCategoryPath, { recursive: true, force: true });
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Removes whatever currently occupies `targetPath` before the atomic
|
|
162
|
+
* rename. An INTERIM symlink (dev-checkout, FA-002) is unlinked directly —
|
|
163
|
+
* never followed/recursed into — so its link target is left untouched.
|
|
164
|
+
*/
|
|
165
|
+
removeExistingTarget(targetPath) {
|
|
166
|
+
let stat;
|
|
167
|
+
try {
|
|
168
|
+
stat = lstatSync(targetPath);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (stat.isSymbolicLink()) {
|
|
174
|
+
rmSync(targetPath, { force: true });
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
rmSync(targetPath, { recursive: true, force: true });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
walkRecursive(dir, relativeBase) {
|
|
181
|
+
const results = [];
|
|
182
|
+
for (const name of readdirSync(dir)) {
|
|
183
|
+
const fullPath = join(dir, name);
|
|
184
|
+
const relativePath = join(relativeBase, name);
|
|
185
|
+
const stat = statSync(fullPath);
|
|
186
|
+
if (stat.isDirectory()) {
|
|
187
|
+
results.push(...this.walkRecursive(fullPath, relativePath));
|
|
188
|
+
}
|
|
189
|
+
else if (stat.isFile()) {
|
|
190
|
+
results.push(relativePath);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return results;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=Materializer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Materializer.js","sourceRoot":"","sources":["../../src/install/Materializer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACZ,MAAM,EACN,QAAQ,EACR,aAAa,EACb,UAAU,EACV,MAAM,EACN,SAAS,GACV,MAAM,SAAS,CAAC;AAEjB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAIzC,SAAS,MAAM,CAAC,OAAe;IAC7B,OAAO,UAAU,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AACxE,CAAC;AAED,MAAM,OAAO,YAAY;IAEJ;IACA;IAFnB,YACmB,MAAsB,EACtB,UAAkB;QADlB,WAAM,GAAN,MAAM,CAAgB;QACtB,eAAU,GAAV,UAAU,CAAQ;IAClC,CAAC;IAEJ;;;;;OAKG;IACH,KAAK,CAAC,KAAK;QAIT,MAAM,UAAU,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC;QAEzC,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3B,MAAM,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,CAAC;QACD,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAE3C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;QAC5C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAC9C,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAClD,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAC;QACpE,CAAC;QAED,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;IACjC,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,UAAkB;QACvB,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;QAC5C,MAAM,UAAU,GAAsB,EAAE,CAAC;QAEzC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAChD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC5B,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACvB,SAAS;YACX,CAAC;YAED,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YAClD,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,KAAK,CAAC,QAAQ,EAAE,CAAC;gBACvC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACxD,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,IAAI,CAAC,UAAkB;QAC3B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;YAC5C,MAAM,EAAE,YAAY,EAAE,cAAc,EAAE,GAAG,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YAEhF,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAC/E,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;gBAClC,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;YAC1C,CAAC;YAED,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC;QAC1C,CAAC;gBAAS,CAAC;YACT,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC3B,MAAM,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACvD,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,eAAe,CAAC,UAAkB;QACtC,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC;QACjD,MAAM,SAAS,GAAG,OAAO,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;QACjD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC3B,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,oBAAoB,GAAG,IAAI,GAAG,CAClC,IAAI,CAAC,MAAM;aACR,aAAa,EAAE;aACf,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,KAAK,QAAQ,CAAC;aAC9C,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAC9B,CAAC;QAEF,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,MAAM,KAAK,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CACnD,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE,CAC9C,CAAC;QAEF,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,eAAe,GAAG,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;YAC7C,KAAK,MAAM,YAAY,IAAI,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,eAAe,CAAC,EAAE,CAAC;gBACtF,IAAI,oBAAoB,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC;oBAC3C,SAAS;gBACX,CAAC;gBAED,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;gBAChD,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBAClD,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,YAAY,CAAC,EAAE,QAAQ,CAAC,CAAC;gBAC3D,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAEO,YAAY,CAClB,UAAkB,EAClB,OAAkE;QAElE,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,IAAI,cAAc,GAAG,CAAC,CAAC;QAEvB,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YACrD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC3B,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;gBAChD,IAAI,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,KAAK,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE,CAAC;oBAC5E,cAAc,EAAE,CAAC;oBACjB,SAAS;gBACX,CAAC;YACH,CAAC;YACD,YAAY,EAAE,CAAC;QACjB,CAAC;QAED,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC;IAC1C,CAAC;IAEO,cAAc;QACpB,OAAO,GAAG,IAAI,CAAC,UAAU,gBAAgB,OAAO,CAAC,GAAG,EAAE,CAAC;IACzD,CAAC;IAEO,YAAY,CAAC,UAAkB,EAAE,QAA4B;QACnE,MAAM,mBAAmB,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACvD,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,EAAE,CAAC;YACrC,OAAO;QACT,CAAC;QAED,MAAM,kBAAkB,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAC3D,IAAI,CAAC,oBAAoB,CAAC,kBAAkB,CAAC,CAAC;QAC9C,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAEhD,IAAI,CAAC;YACH,UAAU,CAAC,mBAAmB,EAAE,kBAAkB,CAAC,CAAC;QACtD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAK,KAA+B,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBACtD,MAAM,CAAC,mBAAmB,EAAE,kBAAkB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;gBACrE,MAAM,CAAC,mBAAmB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAChE,CAAC;iBAAM,CAAC;gBACN,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;OAIG;IACK,oBAAoB,CAAC,UAAkB;QAC7C,IAAI,IAAW,CAAC;QAChB,IAAI,CAAC;YACH,IAAI,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QAED,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;YAC1B,MAAM,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACtC,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAEO,aAAa,CAAC,GAAW,EAAE,YAAoB;QACrD,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;YACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YACjC,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;YAC9C,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAEhC,IAAI,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;gBACvB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC;YAC9D,CAAC;iBAAM,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;gBACzB,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;CACF"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* McpRegistrar - registers the bundled MCP servers via the `claude` CLI
|
|
3
|
+
* (RF-004, ADR-007). Delegates 100% to `ProcessRunner` (DIP, ADR-002) —
|
|
4
|
+
* never writes to `~/.claude.json`/`~/.claude/.mcp.json` directly (AC-003):
|
|
5
|
+
* no module in this file imports `node:fs` for that purpose, by construction.
|
|
6
|
+
*
|
|
7
|
+
* The exact `claude mcp add`/`claude mcp list` syntax used below was
|
|
8
|
+
* MEASURED empirically in TASK-014 (Universal Invariant 6), not inferred
|
|
9
|
+
* from the plan's original assumption — see
|
|
10
|
+
* `tests/install/fixtures/claude-mcp-cli-output.md` §5 for the 3 concrete
|
|
11
|
+
* divergences found (mandatory `--` separator for stdio subprocess args,
|
|
12
|
+
* explicit `--transport http` for HTTP-type servers, `-e KEY=value` per env
|
|
13
|
+
* var instead of a JSON block).
|
|
14
|
+
*/
|
|
15
|
+
import type { ProcessRunner } from './ProcessRunner.js';
|
|
16
|
+
import type { McpServerSpec } from './types.js';
|
|
17
|
+
export declare class McpRegistrar {
|
|
18
|
+
private readonly runner;
|
|
19
|
+
private readonly servers;
|
|
20
|
+
constructor(runner: ProcessRunner, servers: McpServerSpec[]);
|
|
21
|
+
/**
|
|
22
|
+
* Detects whether the `claude` CLI is reachable on PATH (AC-009, R-3),
|
|
23
|
+
* BEFORE any `claude mcp add` is attempted. Runs a lightweight `claude
|
|
24
|
+
* --version` via the injected `ProcessRunner`.
|
|
25
|
+
*/
|
|
26
|
+
checkClaudeAvailable(): Promise<boolean>;
|
|
27
|
+
/**
|
|
28
|
+
* Lists the names of every MCP server `claude` currently knows about
|
|
29
|
+
* (any scope — user, project, plugin), by parsing `claude mcp list`
|
|
30
|
+
* stdout. Used by `ensureRegistered()` for idempotency (AC-004 parcial).
|
|
31
|
+
*/
|
|
32
|
+
listRegistered(): Promise<string[]>;
|
|
33
|
+
/**
|
|
34
|
+
* Registers every configured server that isn't already known to `claude`
|
|
35
|
+
* (idempotent — AC-004 parcial), never touching a server already present.
|
|
36
|
+
* Never writes to `~/.claude.json`/`~/.claude/.mcp.json` directly
|
|
37
|
+
* (AC-003 parcial) — every mutation goes through `claude mcp add`.
|
|
38
|
+
*/
|
|
39
|
+
ensureRegistered(): Promise<{
|
|
40
|
+
registered: string[];
|
|
41
|
+
skipped: string[];
|
|
42
|
+
}>;
|
|
43
|
+
/**
|
|
44
|
+
* Builds the `claude mcp add` args for one server, per the syntax
|
|
45
|
+
* measured in TASK-014 (`claude mcp add --scope user --help`):
|
|
46
|
+
* - `--transport http` when `type === 'http'` (default is stdio).
|
|
47
|
+
* - `-e KEY=value` repeated, once per env var.
|
|
48
|
+
* - stdio: `-- <command> [args...]` (the `--` separator is mandatory —
|
|
49
|
+
* without it, subprocess args starting with `-` would be parsed as
|
|
50
|
+
* `claude mcp add`'s own flags).
|
|
51
|
+
* - http: the url as the final positional (no `--` separator, no args).
|
|
52
|
+
*/
|
|
53
|
+
private buildAddArgs;
|
|
54
|
+
/**
|
|
55
|
+
* Parses `claude mcp list` stdout into a flat list of server names.
|
|
56
|
+
* Format observed (TASK-014): `<name>: <details> - <status-glyph> ...`.
|
|
57
|
+
* Header/diagnostic lines (`⚠ ...`, `Checking MCP server health…`,
|
|
58
|
+
* `Location: ...`, `└ [Warning] ...`) never match `<something> - <glyph>`
|
|
59
|
+
* and are skipped naturally.
|
|
60
|
+
*/
|
|
61
|
+
private parseRegisteredNames;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=McpRegistrar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"McpRegistrar.d.ts","sourceRoot":"","sources":["../../src/install/McpRegistrar.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAKhD,qBAAa,YAAY;IAErB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,aAAa,EAAE;IAG3C;;;;OAIG;IACG,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC;IAK9C;;;;OAIG;IACG,cAAc,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAQzC;;;;;OAKG;IACG,gBAAgB,IAAI,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAuB9E;;;;;;;;;OASG;IACH,OAAO,CAAC,YAAY;IA4BpB;;;;;;OAMG;IACH,OAAO,CAAC,oBAAoB;CAoB7B"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/** Status glyphs `claude mcp list` prefixes each entry's health with. */
|
|
2
|
+
const STATUS_GLYPH_PATTERN = / - (✔|✘|!|⏸)/;
|
|
3
|
+
export class McpRegistrar {
|
|
4
|
+
runner;
|
|
5
|
+
servers;
|
|
6
|
+
constructor(runner, servers) {
|
|
7
|
+
this.runner = runner;
|
|
8
|
+
this.servers = servers;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Detects whether the `claude` CLI is reachable on PATH (AC-009, R-3),
|
|
12
|
+
* BEFORE any `claude mcp add` is attempted. Runs a lightweight `claude
|
|
13
|
+
* --version` via the injected `ProcessRunner`.
|
|
14
|
+
*/
|
|
15
|
+
async checkClaudeAvailable() {
|
|
16
|
+
const result = await this.runner.run('claude', ['--version']);
|
|
17
|
+
return result.exitCode === 0;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Lists the names of every MCP server `claude` currently knows about
|
|
21
|
+
* (any scope — user, project, plugin), by parsing `claude mcp list`
|
|
22
|
+
* stdout. Used by `ensureRegistered()` for idempotency (AC-004 parcial).
|
|
23
|
+
*/
|
|
24
|
+
async listRegistered() {
|
|
25
|
+
const result = await this.runner.run('claude', ['mcp', 'list']);
|
|
26
|
+
if (result.exitCode !== 0) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
return this.parseRegisteredNames(result.stdout);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Registers every configured server that isn't already known to `claude`
|
|
33
|
+
* (idempotent — AC-004 parcial), never touching a server already present.
|
|
34
|
+
* Never writes to `~/.claude.json`/`~/.claude/.mcp.json` directly
|
|
35
|
+
* (AC-003 parcial) — every mutation goes through `claude mcp add`.
|
|
36
|
+
*/
|
|
37
|
+
async ensureRegistered() {
|
|
38
|
+
const alreadyRegistered = new Set(await this.listRegistered());
|
|
39
|
+
const registered = [];
|
|
40
|
+
const skipped = [];
|
|
41
|
+
for (const server of this.servers) {
|
|
42
|
+
if (alreadyRegistered.has(server.name)) {
|
|
43
|
+
skipped.push(server.name);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const args = this.buildAddArgs(server);
|
|
47
|
+
const result = await this.runner.run('claude', args);
|
|
48
|
+
if (result.exitCode === 0) {
|
|
49
|
+
registered.push(server.name);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
skipped.push(server.name);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { registered, skipped };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Builds the `claude mcp add` args for one server, per the syntax
|
|
59
|
+
* measured in TASK-014 (`claude mcp add --scope user --help`):
|
|
60
|
+
* - `--transport http` when `type === 'http'` (default is stdio).
|
|
61
|
+
* - `-e KEY=value` repeated, once per env var.
|
|
62
|
+
* - stdio: `-- <command> [args...]` (the `--` separator is mandatory —
|
|
63
|
+
* without it, subprocess args starting with `-` would be parsed as
|
|
64
|
+
* `claude mcp add`'s own flags).
|
|
65
|
+
* - http: the url as the final positional (no `--` separator, no args).
|
|
66
|
+
*/
|
|
67
|
+
buildAddArgs(server) {
|
|
68
|
+
const args = ['mcp', 'add', '--scope', 'user'];
|
|
69
|
+
if (server.type === 'http') {
|
|
70
|
+
args.push('--transport', 'http');
|
|
71
|
+
}
|
|
72
|
+
for (const [key, value] of Object.entries(server.env ?? {})) {
|
|
73
|
+
args.push('-e', `${key}=${value}`);
|
|
74
|
+
}
|
|
75
|
+
args.push(server.name);
|
|
76
|
+
if (server.type === 'http') {
|
|
77
|
+
if (!server.url) {
|
|
78
|
+
throw new Error(`McpRegistrar: server "${server.name}" is type "http" but has no url`);
|
|
79
|
+
}
|
|
80
|
+
args.push(server.url);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
if (!server.command) {
|
|
84
|
+
throw new Error(`McpRegistrar: server "${server.name}" is stdio but has no command`);
|
|
85
|
+
}
|
|
86
|
+
args.push('--', server.command, ...(server.args ?? []));
|
|
87
|
+
}
|
|
88
|
+
return args;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Parses `claude mcp list` stdout into a flat list of server names.
|
|
92
|
+
* Format observed (TASK-014): `<name>: <details> - <status-glyph> ...`.
|
|
93
|
+
* Header/diagnostic lines (`⚠ ...`, `Checking MCP server health…`,
|
|
94
|
+
* `Location: ...`, `└ [Warning] ...`) never match `<something> - <glyph>`
|
|
95
|
+
* and are skipped naturally.
|
|
96
|
+
*/
|
|
97
|
+
parseRegisteredNames(stdout) {
|
|
98
|
+
const names = [];
|
|
99
|
+
for (const rawLine of stdout.split('\n')) {
|
|
100
|
+
const line = rawLine.trim();
|
|
101
|
+
const separatorIndex = line.indexOf(': ');
|
|
102
|
+
if (separatorIndex === -1) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const rest = line.slice(separatorIndex + 2);
|
|
106
|
+
if (!STATUS_GLYPH_PATTERN.test(rest)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
names.push(line.slice(0, separatorIndex));
|
|
110
|
+
}
|
|
111
|
+
return names;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=McpRegistrar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"McpRegistrar.js","sourceRoot":"","sources":["../../src/install/McpRegistrar.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProcessRunner - abstraction for running external processes (DIP, ADR-002).
|
|
3
|
+
*
|
|
4
|
+
* `NodeProcessRunner` is the production implementation, backed by
|
|
5
|
+
* `node:child_process.execFile`. It never throws on a non-zero exit code or
|
|
6
|
+
* on ENOENT (binary missing from PATH) — callers such as the future
|
|
7
|
+
* McpRegistrar (Fase 2) need to inspect `exitCode` directly, e.g. to detect
|
|
8
|
+
* a missing `claude` CLI (AC-009).
|
|
9
|
+
*/
|
|
10
|
+
export interface ProcessRunner {
|
|
11
|
+
run(command: string, args: string[]): Promise<{
|
|
12
|
+
stdout: string;
|
|
13
|
+
stderr: string;
|
|
14
|
+
exitCode: number;
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
export declare class NodeProcessRunner implements ProcessRunner {
|
|
18
|
+
run(command: string, args: string[]): Promise<{
|
|
19
|
+
stdout: string;
|
|
20
|
+
stderr: string;
|
|
21
|
+
exitCode: number;
|
|
22
|
+
}>;
|
|
23
|
+
private toResult;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=ProcessRunner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProcessRunner.d.ts","sourceRoot":"","sources":["../../src/install/ProcessRunner.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAUH,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACrG;AASD,qBAAa,iBAAkB,YAAW,aAAa;IAC/C,GAAG,CACP,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,GACb,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAShE,OAAO,CAAC,QAAQ;CAiBjB"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProcessRunner - abstraction for running external processes (DIP, ADR-002).
|
|
3
|
+
*
|
|
4
|
+
* `NodeProcessRunner` is the production implementation, backed by
|
|
5
|
+
* `node:child_process.execFile`. It never throws on a non-zero exit code or
|
|
6
|
+
* on ENOENT (binary missing from PATH) — callers such as the future
|
|
7
|
+
* McpRegistrar (Fase 2) need to inspect `exitCode` directly, e.g. to detect
|
|
8
|
+
* a missing `claude` CLI (AC-009).
|
|
9
|
+
*/
|
|
10
|
+
import { execFile } from 'node:child_process';
|
|
11
|
+
import { promisify } from 'node:util';
|
|
12
|
+
const execFileAsync = promisify(execFile);
|
|
13
|
+
/** Sentinel exit code used when the target binary cannot be found (ENOENT). */
|
|
14
|
+
const ENOENT_EXIT_CODE = 127;
|
|
15
|
+
export class NodeProcessRunner {
|
|
16
|
+
async run(command, args) {
|
|
17
|
+
try {
|
|
18
|
+
const { stdout, stderr } = await execFileAsync(command, args);
|
|
19
|
+
return { stdout, stderr, exitCode: 0 };
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
return this.toResult(error);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
toResult(error) {
|
|
26
|
+
if (error.code === 'ENOENT') {
|
|
27
|
+
return {
|
|
28
|
+
stdout: '',
|
|
29
|
+
stderr: error.message ?? `Command not found: ${error.code}`,
|
|
30
|
+
exitCode: ENOENT_EXIT_CODE,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const exitCode = typeof error.code === 'number' ? error.code : 1;
|
|
34
|
+
return {
|
|
35
|
+
stdout: error.stdout ?? '',
|
|
36
|
+
stderr: error.stderr ?? error.message ?? 'Unknown process error',
|
|
37
|
+
exitCode,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=ProcessRunner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProcessRunner.js","sourceRoot":"","sources":["../../src/install/ProcessRunner.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,+EAA+E;AAC/E,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAa7B,MAAM,OAAO,iBAAiB;IAC5B,KAAK,CAAC,GAAG,CACP,OAAe,EACf,IAAc;QAEd,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;QACzC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,IAAI,CAAC,QAAQ,CAAC,KAAsB,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAEO,QAAQ,CAAC,KAAoB;QACnC,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC5B,OAAO;gBACL,MAAM,EAAE,EAAE;gBACV,MAAM,EAAE,KAAK,CAAC,OAAO,IAAI,sBAAsB,KAAK,CAAC,IAAI,EAAE;gBAC3D,QAAQ,EAAE,gBAAgB;aAC3B,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAEjE,OAAO;YACL,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,EAAE;YAC1B,MAAM,EAAE,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,IAAI,uBAAuB;YAChE,QAAQ;SACT,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export declare class ProjectSettingsNeutralizer {
|
|
2
|
+
private readonly projectSettingsPath;
|
|
3
|
+
constructor(projectSettingsPath: string);
|
|
4
|
+
/**
|
|
5
|
+
* Copies `projectSettingsPath` to `<path>.orch-install-bak` (R-2). Safe to
|
|
6
|
+
* call more than once — always re-copies the CURRENT on-disk content, so
|
|
7
|
+
* a caller that already backed up before calling `neutralize()` (which
|
|
8
|
+
* also backs up internally) never loses the true pre-mutation state.
|
|
9
|
+
*/
|
|
10
|
+
backup(): string;
|
|
11
|
+
/**
|
|
12
|
+
* Removes only the `hooks` key from `projectSettingsPath`, preserving
|
|
13
|
+
* every other key untouched. Calls `backup()` unconditionally as its
|
|
14
|
+
* first action (R-2) — no branch in this method mutates the file before
|
|
15
|
+
* a backup exists.
|
|
16
|
+
*/
|
|
17
|
+
neutralize(): {
|
|
18
|
+
neutralized: boolean;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Restores `projectSettingsPath` from a previously-created backup path
|
|
22
|
+
* (as returned by `backup()`). Called by the ORCHESTRATOR's own `catch` —
|
|
23
|
+
* see module JSDoc — never invoked internally by this class.
|
|
24
|
+
*/
|
|
25
|
+
restore(bakPath: string): void;
|
|
26
|
+
private backupPath;
|
|
27
|
+
private readSettings;
|
|
28
|
+
/**
|
|
29
|
+
* Stage-then-swap for a SINGLE file (ADR-003 discipline, scaled down from
|
|
30
|
+
* `Materializer`'s per-category directory rename): write to a sibling
|
|
31
|
+
* temp path, then `renameSync` over the target. A failure at either step
|
|
32
|
+
* never touches `projectSettingsPath` — the temp file is the only thing
|
|
33
|
+
* that can be left in an inconsistent state, and it is cleaned up in the
|
|
34
|
+
* `catch`.
|
|
35
|
+
*/
|
|
36
|
+
private writeAtomic;
|
|
37
|
+
private tmpPath;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=ProjectSettingsNeutralizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectSettingsNeutralizer.d.ts","sourceRoot":"","sources":["../../src/install/ProjectSettingsNeutralizer.ts"],"names":[],"mappings":"AA8BA,qBAAa,0BAA0B;IACzB,OAAO,CAAC,QAAQ,CAAC,mBAAmB;gBAAnB,mBAAmB,EAAE,MAAM;IAExD;;;;;OAKG;IACH,MAAM,IAAI,MAAM;IAMhB;;;;;OAKG;IACH,UAAU,IAAI;QAAE,WAAW,EAAE,OAAO,CAAA;KAAE;IAYtC;;;;OAIG;IACH,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAI9B,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,YAAY;IAKpB;;;;;;;OAOG;IACH,OAAO,CAAC,WAAW;IAgBnB,OAAO,CAAC,OAAO;CAGhB"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProjectSettingsNeutralizer - backup + atomic removal of the `hooks` key
|
|
3
|
+
* from the target project's `.claude/settings.json`, with rollback
|
|
4
|
+
* (RF-003/RF-006, R-2).
|
|
5
|
+
*
|
|
6
|
+
* Two independent safety nets protect against a partial mutation corrupting
|
|
7
|
+
* the project's settings file:
|
|
8
|
+
* 1. **Atomic write** (this module, `writeAtomic()`): `neutralize()` never
|
|
9
|
+
* writes through `projectSettingsPath` directly — it writes the next
|
|
10
|
+
* content to a sibling temp file and `renameSync`s it over the target
|
|
11
|
+
* (same stage-then-swap discipline as `Materializer`, ADR-003). A
|
|
12
|
+
* rename replaces whatever directory entry currently sits at the
|
|
13
|
+
* target path (including a symlink, FA-002) without following it, so a
|
|
14
|
+
* mid-write failure (e.g. a mocked `ENOSPC`) always leaves the
|
|
15
|
+
* ORIGINAL file untouched — never a half-written one.
|
|
16
|
+
* 2. **Backup + restore** (R-2, AC-006): `neutralize()` unconditionally
|
|
17
|
+
* calls `backup()` as its very first action — there is no code path
|
|
18
|
+
* that mutates `projectSettingsPath` without a `.orch-install-bak`
|
|
19
|
+
* copy already on disk. `restore(bakPath)` is the counterpart the
|
|
20
|
+
* ORCHESTRATOR (`InstallCommand`, TASK-032, Fase 6 — out of this
|
|
21
|
+
* module's scope) calls from its own `catch` if a LATER step of the
|
|
22
|
+
* overall install sequence fails after `neutralize()` already
|
|
23
|
+
* succeeded. This class only implements the primitive — it never
|
|
24
|
+
* calls `restore()` on itself.
|
|
25
|
+
*
|
|
26
|
+
* `neutralize()` removes ONLY the `hooks` key — every other key (`env`,
|
|
27
|
+
* `permissions`, `enabledPlugins`, ...) is preserved untouched.
|
|
28
|
+
*/
|
|
29
|
+
import { copyFileSync, existsSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
30
|
+
export class ProjectSettingsNeutralizer {
|
|
31
|
+
projectSettingsPath;
|
|
32
|
+
constructor(projectSettingsPath) {
|
|
33
|
+
this.projectSettingsPath = projectSettingsPath;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Copies `projectSettingsPath` to `<path>.orch-install-bak` (R-2). Safe to
|
|
37
|
+
* call more than once — always re-copies the CURRENT on-disk content, so
|
|
38
|
+
* a caller that already backed up before calling `neutralize()` (which
|
|
39
|
+
* also backs up internally) never loses the true pre-mutation state.
|
|
40
|
+
*/
|
|
41
|
+
backup() {
|
|
42
|
+
const bakPath = this.backupPath();
|
|
43
|
+
copyFileSync(this.projectSettingsPath, bakPath);
|
|
44
|
+
return bakPath;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Removes only the `hooks` key from `projectSettingsPath`, preserving
|
|
48
|
+
* every other key untouched. Calls `backup()` unconditionally as its
|
|
49
|
+
* first action (R-2) — no branch in this method mutates the file before
|
|
50
|
+
* a backup exists.
|
|
51
|
+
*/
|
|
52
|
+
neutralize() {
|
|
53
|
+
this.backup();
|
|
54
|
+
const current = this.readSettings();
|
|
55
|
+
const hadHooks = Object.prototype.hasOwnProperty.call(current, 'hooks');
|
|
56
|
+
const { hooks: _hooks, ...rest } = current;
|
|
57
|
+
this.writeAtomic(JSON.stringify(rest, null, 2));
|
|
58
|
+
return { neutralized: hadHooks };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Restores `projectSettingsPath` from a previously-created backup path
|
|
62
|
+
* (as returned by `backup()`). Called by the ORCHESTRATOR's own `catch` —
|
|
63
|
+
* see module JSDoc — never invoked internally by this class.
|
|
64
|
+
*/
|
|
65
|
+
restore(bakPath) {
|
|
66
|
+
copyFileSync(bakPath, this.projectSettingsPath);
|
|
67
|
+
}
|
|
68
|
+
backupPath() {
|
|
69
|
+
return `${this.projectSettingsPath}.orch-install-bak`;
|
|
70
|
+
}
|
|
71
|
+
readSettings() {
|
|
72
|
+
const raw = readFileSync(this.projectSettingsPath, 'utf-8');
|
|
73
|
+
return JSON.parse(raw);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Stage-then-swap for a SINGLE file (ADR-003 discipline, scaled down from
|
|
77
|
+
* `Materializer`'s per-category directory rename): write to a sibling
|
|
78
|
+
* temp path, then `renameSync` over the target. A failure at either step
|
|
79
|
+
* never touches `projectSettingsPath` — the temp file is the only thing
|
|
80
|
+
* that can be left in an inconsistent state, and it is cleaned up in the
|
|
81
|
+
* `catch`.
|
|
82
|
+
*/
|
|
83
|
+
writeAtomic(content) {
|
|
84
|
+
const tmpPath = this.tmpPath();
|
|
85
|
+
try {
|
|
86
|
+
writeFileSync(tmpPath, content, 'utf-8');
|
|
87
|
+
renameSync(tmpPath, this.projectSettingsPath);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (existsSync(tmpPath)) {
|
|
91
|
+
rmSync(tmpPath, { force: true });
|
|
92
|
+
}
|
|
93
|
+
throw new Error(`ProjectSettingsNeutralizer.neutralize() failed writing "${this.projectSettingsPath}": ${error.message}`, { cause: error });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
tmpPath() {
|
|
97
|
+
return `${this.projectSettingsPath}.orch-neutralize-tmp-${process.pid}`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=ProjectSettingsNeutralizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProjectSettingsNeutralizer.js","sourceRoot":"","sources":["../../src/install/ProjectSettingsNeutralizer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAEpG,MAAM,OAAO,0BAA0B;IACR;IAA7B,YAA6B,mBAA2B;QAA3B,wBAAmB,GAAnB,mBAAmB,CAAQ;IAAG,CAAC;IAE5D;;;;;OAKG;IACH,MAAM;QACJ,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;QAClC,YAAY,CAAC,IAAI,CAAC,mBAAmB,EAAE,OAAO,CAAC,CAAC;QAChD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;;OAKG;IACH,UAAU;QACR,IAAI,CAAC,MAAM,EAAE,CAAC;QAEd,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QACpC,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;QAE3C,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAEhD,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC;IACnC,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,OAAe;QACrB,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAClD,CAAC;IAEO,UAAU;QAChB,OAAO,GAAG,IAAI,CAAC,mBAAmB,mBAAmB,CAAC;IACxD,CAAC;IAEO,YAAY;QAClB,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,mBAAmB,EAAE,OAAO,CAAC,CAAC;QAC5D,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAA4B,CAAC;IACpD,CAAC;IAED;;;;;;;OAOG;IACK,WAAW,CAAC,OAAe;QACjC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;YACzC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAChD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;gBACxB,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACnC,CAAC;YACD,MAAM,IAAI,KAAK,CACb,2DAA2D,IAAI,CAAC,mBAAmB,MAAO,KAAe,CAAC,OAAO,EAAE,EACnH,EAAE,KAAK,EAAE,KAAK,EAAE,CACjB,CAAC;QACJ,CAAC;IACH,CAAC;IAEO,OAAO;QACb,OAAO,GAAG,IAAI,CAAC,mBAAmB,wBAAwB,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1E,CAAC;CACF"}
|