proteum 2.5.5 → 2.5.6
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/cli/commands/configure.ts +63 -4
- package/cli/index.ts +24 -18
- package/cli/presentation/commands.ts +12 -7
- package/cli/runtime/monorepoCommands.ts +625 -0
- package/cli/runtime/worktreeBootstrap.ts +163 -0
- package/cli/utils/agents.ts +156 -38
- package/package.json +1 -1
- package/tests/agents-utils.test.cjs +63 -3
- package/tests/cli-mcp-command.test.cjs +60 -11
- package/tests/worktree-bootstrap.test.cjs +98 -0
|
@@ -14,10 +14,12 @@ import { renderRows } from '../presentation/layout';
|
|
|
14
14
|
import { isLikelyProteumAppRoot } from '../presentation/commands';
|
|
15
15
|
import { renderStep, renderSuccess, renderTitle, renderWarning } from '../presentation/ink';
|
|
16
16
|
import {
|
|
17
|
+
configureMonorepoProjectAgentInstructions,
|
|
17
18
|
configureProjectAgentInstructions,
|
|
18
19
|
findLikelyRepoRoot,
|
|
19
20
|
isInsideDirectory,
|
|
20
21
|
resolveCanonicalPath,
|
|
22
|
+
type TConfigureMonorepoProjectAgentInstructionsResult,
|
|
21
23
|
type TConfigureProjectAgentInstructionsResult,
|
|
22
24
|
} from '../utils/agents';
|
|
23
25
|
|
|
@@ -68,20 +70,22 @@ const promptMonorepoRoot = async ({
|
|
|
68
70
|
return resolveCanonicalPath(String(response.value || defaultRoot || ''));
|
|
69
71
|
};
|
|
70
72
|
|
|
71
|
-
const promptBlockedOverwritePaths = async (blockedPaths: string[]) => {
|
|
72
|
-
|
|
73
|
+
export const promptBlockedOverwritePaths = async (blockedPaths: string[]) => {
|
|
74
|
+
const uniqueBlockedPaths = [...new Set(blockedPaths)];
|
|
75
|
+
|
|
76
|
+
if (uniqueBlockedPaths.length === 0) return [];
|
|
73
77
|
|
|
74
78
|
console.info(await renderWarning('Proteum found existing paths that block managed instruction updates.'));
|
|
75
79
|
console.info(
|
|
76
80
|
[
|
|
77
81
|
'Choose whether to overwrite each path with a Proteum-managed instruction path:',
|
|
78
|
-
...
|
|
82
|
+
...uniqueBlockedPaths.map((entry) => `- ${entry}`),
|
|
79
83
|
].join('\n'),
|
|
80
84
|
);
|
|
81
85
|
|
|
82
86
|
const overwriteBlockedPaths: string[] = [];
|
|
83
87
|
|
|
84
|
-
for (const blockedPath of
|
|
88
|
+
for (const blockedPath of uniqueBlockedPaths) {
|
|
85
89
|
const response = await prompts(
|
|
86
90
|
{
|
|
87
91
|
type: 'confirm',
|
|
@@ -133,6 +137,12 @@ const renderConfigureResultSections = (result: TConfigureProjectAgentInstruction
|
|
|
133
137
|
return sections;
|
|
134
138
|
};
|
|
135
139
|
|
|
140
|
+
const renderConfigureMonorepoResultSections = (result: TConfigureMonorepoProjectAgentInstructionsResult) =>
|
|
141
|
+
renderConfigureResultSections({
|
|
142
|
+
...result,
|
|
143
|
+
appRoot: result.monorepoRoot,
|
|
144
|
+
});
|
|
145
|
+
|
|
136
146
|
/*----------------------------------
|
|
137
147
|
- COMMAND
|
|
138
148
|
----------------------------------*/
|
|
@@ -218,3 +228,52 @@ export const runConfigureAgentsWizard = async ({
|
|
|
218
228
|
|
|
219
229
|
if (sections.length > 0) console.info(`\n${sections.join('\n\n')}`);
|
|
220
230
|
};
|
|
231
|
+
|
|
232
|
+
export const runConfigureAgentsMonorepoWizard = async ({
|
|
233
|
+
appRoots,
|
|
234
|
+
coreRoot = cli.paths.core.root,
|
|
235
|
+
monorepoRoot,
|
|
236
|
+
}: {
|
|
237
|
+
appRoots: string[];
|
|
238
|
+
coreRoot?: string;
|
|
239
|
+
monorepoRoot: string;
|
|
240
|
+
}) => {
|
|
241
|
+
if (appRoots.length === 0) throw new UsageError(`No Proteum app roots were found under ${monorepoRoot}.`);
|
|
242
|
+
for (const appRoot of appRoots) assertProteumAppRoot(appRoot);
|
|
243
|
+
|
|
244
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
245
|
+
throw new UsageError('`proteum configure agents` is interactive and requires a TTY.');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
console.info(
|
|
249
|
+
[
|
|
250
|
+
await renderTitle('PROTEUM CONFIGURE AGENTS', 'Configure monorepo Proteum instruction files and Claude aliases.'),
|
|
251
|
+
renderRows([
|
|
252
|
+
{ label: 'monorepo root', value: monorepoRoot === process.cwd() ? '.' : monorepoRoot },
|
|
253
|
+
{ label: 'apps', value: String(appRoots.length) },
|
|
254
|
+
]),
|
|
255
|
+
].join('\n\n'),
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const preview = configureMonorepoProjectAgentInstructions({
|
|
259
|
+
appRoots,
|
|
260
|
+
coreRoot,
|
|
261
|
+
dryRun: true,
|
|
262
|
+
monorepoRoot,
|
|
263
|
+
});
|
|
264
|
+
const overwriteBlockedPaths = await promptBlockedOverwritePaths(preview.blocked);
|
|
265
|
+
|
|
266
|
+
console.info(await renderStep('[1/1]', 'Writing monorepo root and app instruction files and Claude aliases.'));
|
|
267
|
+
|
|
268
|
+
const result = configureMonorepoProjectAgentInstructions({
|
|
269
|
+
appRoots,
|
|
270
|
+
coreRoot,
|
|
271
|
+
monorepoRoot,
|
|
272
|
+
overwriteBlockedPaths,
|
|
273
|
+
});
|
|
274
|
+
const sections = renderConfigureMonorepoResultSections(result);
|
|
275
|
+
|
|
276
|
+
console.info(await renderSuccess('Proteum-managed monorepo instruction files and Claude aliases are configured.'));
|
|
277
|
+
|
|
278
|
+
if (sections.length > 0) console.info(`\n${sections.join('\n\n')}`);
|
|
279
|
+
};
|
package/cli/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { renderCliOverview, renderCommandHelp, resolveCustomHelpRequest } from '
|
|
|
9
9
|
import { renderCliWelcomeBanner } from './presentation/welcome';
|
|
10
10
|
import { normalizeHelpArgv, normalizeLegacyArgv } from './runtime/argv';
|
|
11
11
|
import { createCli, registeredCommands } from './runtime/commands';
|
|
12
|
+
import { isMonorepoFanoutChild, maybeRunMonorepoCommand } from './runtime/monorepoCommands';
|
|
12
13
|
|
|
13
14
|
const formatInvocation = (argv: string[]) => ['proteum', ...argv].join(' ').trim();
|
|
14
15
|
|
|
@@ -21,6 +22,7 @@ const shouldRenderSharedWelcomeBanner = ({
|
|
|
21
22
|
argv: string[];
|
|
22
23
|
helpRequestKind: 'none' | 'overview' | 'command';
|
|
23
24
|
}) => {
|
|
25
|
+
if (isMonorepoFanoutChild()) return false;
|
|
24
26
|
if (helpRequestKind !== 'none') return false;
|
|
25
27
|
if (argv.length !== 1) return false;
|
|
26
28
|
|
|
@@ -32,27 +34,9 @@ const shouldRenderSharedWelcomeBanner = ({
|
|
|
32
34
|
export const runCli = async (argv: string[] = process.argv.slice(2)) => {
|
|
33
35
|
const normalizedArgv = normalizeHelpArgv(normalizeLegacyArgv(argv), proteumCommandNames);
|
|
34
36
|
const version = String(cli.packageJson.version || '');
|
|
35
|
-
const proteumInstall = resolveFrameworkInstallInfo({
|
|
36
|
-
appRoot: cli.paths.appRoot,
|
|
37
|
-
framework: cli.paths.framework,
|
|
38
|
-
});
|
|
39
37
|
const clipanion = createCli(version);
|
|
40
38
|
const initAvailable = true;
|
|
41
39
|
const helpRequest = resolveCustomHelpRequest(normalizedArgv);
|
|
42
|
-
const shouldRenderWelcomeBanner = shouldRenderSharedWelcomeBanner({
|
|
43
|
-
argv: normalizedArgv,
|
|
44
|
-
helpRequestKind: helpRequest.kind,
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
if (shouldRenderWelcomeBanner) {
|
|
48
|
-
process.stderr.write(
|
|
49
|
-
`${await renderCliWelcomeBanner({
|
|
50
|
-
command: formatInvocation(normalizedArgv),
|
|
51
|
-
installSummary: proteumInstall.summary,
|
|
52
|
-
version,
|
|
53
|
-
})}\n\n`,
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
40
|
|
|
57
41
|
if (helpRequest.kind === 'overview') {
|
|
58
42
|
process.stdout.write(
|
|
@@ -77,6 +61,28 @@ export const runCli = async (argv: string[] = process.argv.slice(2)) => {
|
|
|
77
61
|
return;
|
|
78
62
|
}
|
|
79
63
|
|
|
64
|
+
if (await maybeRunMonorepoCommand(normalizedArgv)) return;
|
|
65
|
+
|
|
66
|
+
const shouldRenderWelcomeBanner = shouldRenderSharedWelcomeBanner({
|
|
67
|
+
argv: normalizedArgv,
|
|
68
|
+
helpRequestKind: helpRequest.kind,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (shouldRenderWelcomeBanner) {
|
|
72
|
+
const proteumInstall = resolveFrameworkInstallInfo({
|
|
73
|
+
appRoot: cli.paths.appRoot,
|
|
74
|
+
framework: cli.paths.framework,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
process.stderr.write(
|
|
78
|
+
`${await renderCliWelcomeBanner({
|
|
79
|
+
command: formatInvocation(normalizedArgv),
|
|
80
|
+
installSummary: proteumInstall.summary,
|
|
81
|
+
version,
|
|
82
|
+
})}\n\n`,
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
80
86
|
await clipanion.runExit(normalizedArgv, Cli.defaultContext);
|
|
81
87
|
};
|
|
82
88
|
|
|
@@ -130,6 +130,7 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
130
130
|
'This command is interactive. It asks whether the current Proteum app belongs to a monorepo and, if so, which ancestor path should receive the reusable root instruction files.',
|
|
131
131
|
'Standalone mode writes tracked instruction files into the current Proteum app root and creates `CLAUDE.md` symlinks beside each `AGENTS.md`.',
|
|
132
132
|
'Monorepo mode writes reusable root documents such as `AGENTS.md`, `DOCUMENTATION.md`, `CODING_STYLE.md`, `diagnostics.md`, and `optimizations.md` into the chosen monorepo root, then writes only app-root and area instruction files into the current Proteum app root.',
|
|
133
|
+
'When run from a monorepo wrapper root that contains Proteum apps, configure writes the shared root documents once and app-root instruction files for every discovered app.',
|
|
133
134
|
'Every generated `CLAUDE.md` is a sibling symlink pointing to `AGENTS.md`.',
|
|
134
135
|
'Every managed instruction file contains a `# Proteum Instructions` section with the full embedded Proteum project instruction corpus.',
|
|
135
136
|
'Existing content outside `# Proteum Instructions` is preserved. Directories and foreign symlinks are replaced only after confirmation.',
|
|
@@ -167,6 +168,8 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
167
168
|
'Bootstrap writes `.proteum/worktree-bootstrap.json` with hashes, step timestamps, dependency status, runtime status, and Proteum version.',
|
|
168
169
|
'When `.env` is missing, `--source` is required and must point to an app root with a readable `.env`.',
|
|
169
170
|
'For monorepos with root tooling, bootstrap also ensures the workspace-root `.env` exists from the source root or source app env.',
|
|
171
|
+
'When `worktree init` runs from a monorepo wrapper root, Proteum bootstraps every discovered app, matches source apps by relative path, and deduplicates dependency install by shared package-lock root.',
|
|
172
|
+
'When `worktree create` runs from a source monorepo wrapper root, Proteum creates the Git worktree once and then bootstraps every matching target app.',
|
|
170
173
|
'Guarded commands block inside `/.codex/worktrees/` until bootstrap is complete or explicitly bypassed with `PROTEUM_ALLOW_UNBOOTSTRAPPED_WORKTREE=1`.',
|
|
171
174
|
'`worktree create` preserves the source app root path relative to the source repository root, so monorepo app roots are bootstrapped in the matching target location.',
|
|
172
175
|
],
|
|
@@ -207,13 +210,14 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
207
210
|
},
|
|
208
211
|
],
|
|
209
212
|
notes: [
|
|
210
|
-
'
|
|
213
|
+
'From a monorepo wrapper root, eligible app-scoped commands run for every discovered Proteum app. Use an app root or `--cwd` when supported to target exactly one app.',
|
|
214
|
+
'From a monorepo wrapper root, bare `proteum dev` supervises all discovered apps with app-local session files and unique router/HMR port pairs; `dev list` and `dev stop` aggregate every app.',
|
|
211
215
|
'Proteum writes a machine-readable dev session file under `var/run/proteum/dev/<port>.json` by default; override it with `--session-file` when an agent needs a stable path.',
|
|
212
216
|
'Before registering a new session, Proteum removes stale same-worktree session files and fails fast if another live tracked session remains.',
|
|
213
217
|
'Before the dev loop starts, Proteum ensures tracked instruction files contain the current managed `# Proteum Instructions` section.',
|
|
214
218
|
'Use `--replace-existing` only when retrying the exact requested session file.',
|
|
215
|
-
'`proteum dev list` inspects tracked sessions for the current app root. Add `--stale` to show only orphaned or dead sessions.',
|
|
216
|
-
'`proteum dev stop` targets the current session file by default. Add `--all` to stop every tracked session for the current app root.',
|
|
219
|
+
'`proteum dev list` inspects tracked sessions for the current app root, or every discovered app from a monorepo wrapper root. Add `--stale` to show only orphaned or dead sessions.',
|
|
220
|
+
'`proteum dev stop` targets the current session file by default. Add `--all` to stop every tracked session for the current app root, or run from a monorepo wrapper root to apply the stop command to every app.',
|
|
217
221
|
'`proteum dev` clears the interactive terminal once at startup, then shows `CTRL+R` reload and `CTRL+C` shutdown hotkeys in the session banner.',
|
|
218
222
|
'Legacy single-dash long options remain supported, for example `proteum dev -port 3001`.',
|
|
219
223
|
],
|
|
@@ -229,7 +233,7 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
229
233
|
examples: [
|
|
230
234
|
{ description: 'Refresh generated contracts after source edits', command: 'proteum refresh' },
|
|
231
235
|
],
|
|
232
|
-
notes: ['Use this when you need deterministic regeneration without starting the full dev loop.'],
|
|
236
|
+
notes: ['Use this when you need deterministic regeneration without starting the full dev loop.', 'From a monorepo wrapper root, refresh runs once per discovered Proteum app.'],
|
|
233
237
|
status: 'stable',
|
|
234
238
|
},
|
|
235
239
|
build: {
|
|
@@ -257,6 +261,7 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
257
261
|
'`--analyze-serve` switches the analyzer to HTTP server mode and keeps the process open until you stop it.',
|
|
258
262
|
'`--analyze-host` and `--analyze-port` require `--analyze-serve`; use `auto` to let the OS assign a free port.',
|
|
259
263
|
'Use `--strict` when the build must refresh generated typings and fail on any TypeScript error before compilation starts.',
|
|
264
|
+
'From a monorepo wrapper root, build runs once per discovered Proteum app. `--analyze-serve` must be run from one app root because the analyzer server stays open.',
|
|
260
265
|
'The production output is emitted under `bin/`.',
|
|
261
266
|
],
|
|
262
267
|
status: 'stable',
|
|
@@ -270,7 +275,7 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
270
275
|
examples: [
|
|
271
276
|
{ description: 'Typecheck every discovered client and server app tsconfig', command: 'proteum typecheck' },
|
|
272
277
|
],
|
|
273
|
-
notes: ['Proteum refreshes generated typings before running TypeScript.'],
|
|
278
|
+
notes: ['Proteum refreshes generated typings before running TypeScript.', 'From a monorepo wrapper root, typecheck runs once per discovered Proteum app.'],
|
|
274
279
|
status: 'stable',
|
|
275
280
|
},
|
|
276
281
|
lint: {
|
|
@@ -283,7 +288,7 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
283
288
|
{ description: 'Run ESLint in check mode', command: 'proteum lint' },
|
|
284
289
|
{ description: 'Apply fixable lint changes', command: 'proteum lint --fix' },
|
|
285
290
|
],
|
|
286
|
-
notes: ['Legacy positional usage such as `proteum lint fix` remains supported.'],
|
|
291
|
+
notes: ['Legacy positional usage such as `proteum lint fix` remains supported.', 'From a monorepo wrapper root, lint runs once per discovered Proteum app.'],
|
|
287
292
|
status: 'stable',
|
|
288
293
|
},
|
|
289
294
|
check: {
|
|
@@ -293,7 +298,7 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
293
298
|
usage: 'proteum check',
|
|
294
299
|
bestFor: 'One command before commits, pushes, or CI when you want the standard local validation path.',
|
|
295
300
|
examples: [{ description: 'Run the full default validation pipeline', command: 'proteum check' }],
|
|
296
|
-
notes: ['This command executes refresh, typecheck, then lint in that order.'],
|
|
301
|
+
notes: ['This command executes refresh, typecheck, then lint in that order.', 'From a monorepo wrapper root, check runs once per discovered Proteum app.'],
|
|
297
302
|
status: 'stable',
|
|
298
303
|
},
|
|
299
304
|
e2e: {
|