@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.
Files changed (87) hide show
  1. package/README.md +113 -0
  2. package/agents/debug-sidecar.md +1 -1
  3. package/agents/doc-sidecar.md +1 -1
  4. package/agents/test-sidecar.md +1 -1
  5. package/dist/cli.js +78 -0
  6. package/dist/cli.js.map +1 -1
  7. package/dist/commands/DiffCommand.js +1 -1
  8. package/dist/commands/DiffCommand.js.map +1 -1
  9. package/dist/commands/InstallCommand.d.ts +41 -0
  10. package/dist/commands/InstallCommand.d.ts.map +1 -0
  11. package/dist/commands/InstallCommand.js +253 -0
  12. package/dist/commands/InstallCommand.js.map +1 -0
  13. package/dist/http/ApiClient.d.ts +5 -0
  14. package/dist/http/ApiClient.d.ts.map +1 -1
  15. package/dist/http/ApiClient.js.map +1 -1
  16. package/dist/install/DevCheckoutDetector.d.ts +66 -0
  17. package/dist/install/DevCheckoutDetector.d.ts.map +1 -0
  18. package/dist/install/DevCheckoutDetector.js +150 -0
  19. package/dist/install/DevCheckoutDetector.js.map +1 -0
  20. package/dist/install/HooksWirer.d.ts +29 -0
  21. package/dist/install/HooksWirer.d.ts.map +1 -0
  22. package/dist/install/HooksWirer.js +80 -0
  23. package/dist/install/HooksWirer.js.map +1 -0
  24. package/dist/install/LocalDiffReporter.d.ts +25 -0
  25. package/dist/install/LocalDiffReporter.d.ts.map +1 -0
  26. package/dist/install/LocalDiffReporter.js +50 -0
  27. package/dist/install/LocalDiffReporter.js.map +1 -0
  28. package/dist/install/Materializer.d.ts +67 -0
  29. package/dist/install/Materializer.d.ts.map +1 -0
  30. package/dist/install/Materializer.js +196 -0
  31. package/dist/install/Materializer.js.map +1 -0
  32. package/dist/install/McpRegistrar.d.ts +63 -0
  33. package/dist/install/McpRegistrar.d.ts.map +1 -0
  34. package/dist/install/McpRegistrar.js +114 -0
  35. package/dist/install/McpRegistrar.js.map +1 -0
  36. package/dist/install/ProcessRunner.d.ts +25 -0
  37. package/dist/install/ProcessRunner.d.ts.map +1 -0
  38. package/dist/install/ProcessRunner.js +41 -0
  39. package/dist/install/ProcessRunner.js.map +1 -0
  40. package/dist/install/ProjectSettingsNeutralizer.d.ts +39 -0
  41. package/dist/install/ProjectSettingsNeutralizer.d.ts.map +1 -0
  42. package/dist/install/ProjectSettingsNeutralizer.js +100 -0
  43. package/dist/install/ProjectSettingsNeutralizer.js.map +1 -0
  44. package/dist/install/types.d.ts +52 -0
  45. package/dist/install/types.d.ts.map +1 -0
  46. package/dist/install/types.js +8 -0
  47. package/dist/install/types.js.map +1 -0
  48. package/dist/manifest/ManifestLoader.d.ts.map +1 -1
  49. package/dist/manifest/ManifestLoader.js +3 -2
  50. package/dist/manifest/ManifestLoader.js.map +1 -1
  51. package/dist/manifest/types.d.ts +3 -0
  52. package/dist/manifest/types.d.ts.map +1 -1
  53. package/dist/scripts/bundle-hooks.d.ts +39 -0
  54. package/dist/scripts/bundle-hooks.d.ts.map +1 -0
  55. package/dist/scripts/bundle-hooks.js +165 -0
  56. package/dist/scripts/bundle-hooks.js.map +1 -0
  57. package/dist/scripts/generate-manifest.d.ts +22 -3
  58. package/dist/scripts/generate-manifest.d.ts.map +1 -1
  59. package/dist/scripts/generate-manifest.js +78 -35
  60. package/dist/scripts/generate-manifest.js.map +1 -1
  61. package/hooks/dangling-guard.mjs +7426 -0
  62. package/hooks/dangling-guard.ts +223 -6
  63. package/hooks/gate-guardian.mjs +463 -0
  64. package/hooks/lib/additional-directories-sync.ts +295 -0
  65. package/hooks/lib/api-client.ts +388 -7
  66. package/hooks/lib/session-selection.ts +81 -0
  67. package/hooks/mailbox-listener.mjs +259 -0
  68. package/hooks/post-compact.mjs +7931 -0
  69. package/hooks/post-implement.mjs +238 -0
  70. package/hooks/session-start.mjs +8234 -0
  71. package/hooks/session-start.ts +264 -23
  72. package/hooks/sprint-registry.mjs +59 -0
  73. package/hooks/subagent-start.mjs +289 -0
  74. package/hooks/subagent-stop.mjs +7793 -0
  75. package/hooks/subagent-stop.ts +4 -2
  76. package/hooks/user-prompt.mjs +329 -0
  77. package/hooks/workflow-guard.mjs +7805 -0
  78. package/hooks/workflow-guard.ts +360 -119
  79. package/hooks-settings-template.json +63 -0
  80. package/manifest.json +54 -68
  81. package/mcp-servers.json +48 -0
  82. package/package.json +10 -4
  83. package/personas/auditor-compliance.md +104 -0
  84. package/personas/cfo-conservador.md +88 -0
  85. package/personas/designer-perfeccionista.md +104 -0
  86. package/personas/diretor-cetico-fmcg.md +80 -0
  87. 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":"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"}
@@ -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"}