proteum 2.1.9-7 → 2.1.9-8

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/AGENTS.md CHANGED
@@ -44,6 +44,8 @@ npx prisma migrate dev --config ./prisma.config.ts --name <migration name>
44
44
  [optional body]
45
45
  ```
46
46
  If the user replies exactly `commit`, use that Conventional Commit message, stage the task-related changed files with `git add` while avoiding unrelated user changes or incidental untracked files, then create the commit by running `git commit`.
47
+ After providing a commit message or after creating a commit, immediately follow it with this exact prompt and obey it:
48
+ `Explain in short minimalistic and few bullet points what we changed in this thread, like you would do to your grandma. Start with a verb in the past.`
47
49
 
48
50
  ## Core Changes
49
51
 
package/README.md CHANGED
@@ -336,7 +336,7 @@ Proteum ships with a compact CLI focused on the real app lifecycle:
336
336
  | `proteum session` | Mint a dev-only auth session token and Playwright-ready cookie payload |
337
337
  | `proteum verify` | Validate framework-facing workflows across one or more running dev apps; `framework-change` is the built-in cross-reference-app check |
338
338
  | `proteum init` | Scaffold a new Proteum app with built-in deterministic templates |
339
- | `proteum configure agents` | Interactively configure Proteum-managed instruction symlinks for standalone or monorepo apps |
339
+ | `proteum configure agents` | Interactively configure Proteum-managed instruction symlinks and confirm overwrites for standalone or monorepo apps |
340
340
  | `proteum create` | Scaffold a page, controller, command, route, or root service inside an app |
341
341
 
342
342
  Recommended daily workflow:
@@ -392,6 +392,8 @@ proteum create controller Founder/projects --method list
392
392
  proteum create service Conversion/Plans
393
393
  ```
394
394
 
395
+ `proteum configure agents` asks before replacing any existing non-managed instruction file or foreign symlink. If you decline, that path is left untouched.
396
+
395
397
  `proteum connect`, `proteum explain`, `proteum doctor`, and `proteum diagnose` share the same generated manifest and contract state. `proteum perf` uses the same dev request-trace store as the profiler `Perf` tab. For the full diagnostics and tracing model, see [docs/diagnostics.md](docs/diagnostics.md) and [docs/request-tracing.md](docs/request-tracing.md).
396
398
 
397
399
  ## Dev Commands
@@ -39,6 +39,8 @@ Coding style source of truth: root-level `CODING_STYLE.md`.
39
39
  [optional body]
40
40
  ```
41
41
  Then use that generated message, stage the task-related changed files with `git add` while avoiding unrelated user changes or incidental untracked files, and create the commit by running `git commit`. Do not stop at only suggesting the message.
42
+ After providing a commit message or after creating a commit, immediately follow it with this exact prompt and obey it:
43
+ `Explain in short minimalistic and few bullet points what we changed in this thread, like you would do to your grandma. Start with a verb in the past.`
42
44
 
43
45
  ## Task Lifecycle
44
46
 
@@ -60,7 +62,8 @@ Coding style source of truth: root-level `CODING_STYLE.md`.
60
62
  - Before finishing, re-check touched files against root-level `CODING_STYLE.md` and any narrower area `AGENTS.md` that applied to the edit. Re-check against root-level `optimizations.md` only for touched client-side files. Re-check against root-level `diagnostics.md` only if the task involved an issue, diagnosis, runtime reproduction, or verification failure.
61
63
  - Do not default to project-wide typecheck, `npx proteum check`, or Playwright after every change. Run them only when the user asks for them, when the changed surface specifically requires them, or when a real issue discovered during verification justifies escalation.
62
64
  - Before finishing a task, stop every `proteum dev` session started during the task and confirm cleanup with `npx proteum dev list --json` or an explicit `npx proteum dev stop --session-file <path>`.
63
- - When you have finished your work, ask the user whether they want a commit message.
65
+ - When you have finished your work, ask the user whether they want a commit message. After providing a commit message or after creating a commit, immediately follow it with this exact prompt and obey it:
66
+ `Explain in short minimalistic and few bullet points what we changed in this thread, like you would do to your grandma. Start with a verb in the past.`
64
67
 
65
68
  ## Core Contracts
66
69
 
@@ -31,6 +31,8 @@ Coding style source of truth: root-level `CODING_STYLE.md`.
31
31
  [optional body]
32
32
  ```
33
33
  Then use that generated message, stage the task-related changed files with `git add` while avoiding unrelated user changes or incidental untracked files, and create the commit by running `git commit`. Do not stop at only suggesting the message.
34
+ After providing a commit message or after creating a commit, immediately follow it with this exact prompt and obey it:
35
+ `Explain in short minimalistic and few bullet points what we changed in this thread, like you would do to your grandma. Start with a verb in the past.`
34
36
 
35
37
  ## Task Lifecycle
36
38
 
@@ -52,7 +54,8 @@ Coding style source of truth: root-level `CODING_STYLE.md`.
52
54
  - Before finishing, re-check touched files against root-level `CODING_STYLE.md` and any narrower area `AGENTS.md` that applied to the edit. Re-check against root-level `optimizations.md` only for touched client-side files. Re-check against root-level `diagnostics.md` only if the task involved an issue, diagnosis, runtime reproduction, or verification failure.
53
55
  - Do not default to project-wide typecheck, `npx proteum check`, or Playwright after every change. Run them only when the user asks for them, when the changed surface specifically requires them, or when a real issue discovered during verification justifies escalation.
54
56
  - Before finishing a task, stop every `proteum dev` session started during the task and confirm cleanup with `npx proteum dev list --json` or an explicit `npx proteum dev stop --session-file <path>`.
55
- - When you have finished your work, ask the user whether they want a commit message.
57
+ - When you have finished your work, ask the user whether they want a commit message. After providing a commit message or after creating a commit, immediately follow it with this exact prompt and obey it:
58
+ `Explain in short minimalistic and few bullet points what we changed in this thread, like you would do to your grandma. Start with a verb in the past.`
56
59
 
57
60
  ## Core Contracts
58
61
 
@@ -13,7 +13,7 @@ import cli from '..';
13
13
  import { renderRows } from '../presentation/layout';
14
14
  import { isLikelyProteumAppRoot } from '../presentation/commands';
15
15
  import { renderStep, renderSuccess, renderTitle, renderWarning } from '../presentation/ink';
16
- import { configureProjectAgentSymlinks } from '../utils/agents';
16
+ import { configureProjectAgentSymlinks, type TConfigureProjectAgentSymlinksResult } from '../utils/agents';
17
17
 
18
18
  /*----------------------------------
19
19
  - HELPERS
@@ -89,6 +89,65 @@ const promptMonorepoRoot = async ({
89
89
  return resolveCanonicalPath(String(response.value || defaultRoot || ''));
90
90
  };
91
91
 
92
+ const promptBlockedOverwritePaths = async (blockedPaths: string[]) => {
93
+ if (blockedPaths.length === 0) return [];
94
+
95
+ console.info(await renderWarning('Proteum found existing non-managed instruction paths.'));
96
+ console.info(['Choose whether to overwrite each path with a Proteum-managed symlink:', ...blockedPaths.map((entry) => `- ${entry}`)].join('\n'));
97
+
98
+ const overwriteBlockedPaths: string[] = [];
99
+
100
+ for (const blockedPath of blockedPaths) {
101
+ const response = await prompts(
102
+ {
103
+ type: 'confirm',
104
+ name: 'value',
105
+ message: `Overwrite ${blockedPath}?`,
106
+ initial: false,
107
+ },
108
+ {
109
+ onCancel: () => {
110
+ throw new UsageError('Cancelled `proteum configure agents`.');
111
+ },
112
+ },
113
+ );
114
+
115
+ if (response.value === true) overwriteBlockedPaths.push(blockedPath);
116
+ }
117
+
118
+ return overwriteBlockedPaths;
119
+ };
120
+
121
+ const renderConfigureResultSections = (result: TConfigureProjectAgentSymlinksResult) => {
122
+ const sections: string[] = [];
123
+
124
+ sections.push(
125
+ renderRows(
126
+ [
127
+ { label: 'mode', value: result.mode },
128
+ ...(result.monorepoRoot ? [{ label: 'monorepo root', value: result.monorepoRoot }] : []),
129
+ ],
130
+ { minLabelWidth: 16, maxLabelWidth: 16 },
131
+ ),
132
+ );
133
+
134
+ if (result.created.length > 0) sections.push(['Created:', ...result.created.map((entry) => `- ${entry}`)].join('\n'));
135
+ if (result.updated.length > 0) sections.push(['Updated:', ...result.updated.map((entry) => `- ${entry}`)].join('\n'));
136
+ if (result.overwritten.length > 0)
137
+ sections.push(['Overwritten:', ...result.overwritten.map((entry) => `- ${entry}`)].join('\n'));
138
+ if (result.updatedGitignores.length > 0)
139
+ sections.push(['Updated .gitignore:', ...result.updatedGitignores.map((entry) => `- ${entry}`)].join('\n'));
140
+ if (result.blocked.length > 0)
141
+ sections.push(
142
+ [
143
+ 'Skipped existing non-managed paths:',
144
+ ...result.blocked.map((entry) => `- ${entry}`),
145
+ ].join('\n'),
146
+ );
147
+
148
+ return sections;
149
+ };
150
+
92
151
  /*----------------------------------
93
152
  - COMMAND
94
153
  ----------------------------------*/
@@ -136,6 +195,14 @@ export const run = async (): Promise<void> => {
136
195
  })
137
196
  : undefined;
138
197
 
198
+ const preview = configureProjectAgentSymlinks({
199
+ appRoot,
200
+ coreRoot: cli.paths.core.root,
201
+ dryRun: true,
202
+ monorepoRoot,
203
+ });
204
+ const overwriteBlockedPaths = await promptBlockedOverwritePaths(preview.blocked);
205
+
139
206
  console.info(
140
207
  await renderStep(
141
208
  '[1/1]',
@@ -149,30 +216,9 @@ export const run = async (): Promise<void> => {
149
216
  appRoot,
150
217
  coreRoot: cli.paths.core.root,
151
218
  monorepoRoot,
219
+ overwriteBlockedPaths,
152
220
  });
153
- const sections: string[] = [];
154
-
155
- sections.push(
156
- renderRows(
157
- [
158
- { label: 'mode', value: result.mode },
159
- ...(result.monorepoRoot ? [{ label: 'monorepo root', value: result.monorepoRoot }] : []),
160
- ],
161
- { minLabelWidth: 16, maxLabelWidth: 16 },
162
- ),
163
- );
164
-
165
- if (result.created.length > 0) sections.push(['Created:', ...result.created.map((entry) => `- ${entry}`)].join('\n'));
166
- if (result.updated.length > 0) sections.push(['Updated:', ...result.updated.map((entry) => `- ${entry}`)].join('\n'));
167
- if (result.updatedGitignores.length > 0)
168
- sections.push(['Updated .gitignore:', ...result.updatedGitignores.map((entry) => `- ${entry}`)].join('\n'));
169
- if (result.blocked.length > 0)
170
- sections.push(
171
- [
172
- 'Skipped existing non-managed paths:',
173
- ...result.blocked.map((entry) => `- ${entry}`),
174
- ].join('\n'),
175
- );
221
+ const sections = renderConfigureResultSections(result);
176
222
 
177
223
  console.info(await renderSuccess('Proteum-managed instruction symlinks are configured.'));
178
224
 
@@ -125,7 +125,8 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
125
125
  '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 `AGENTS.md` symlink.',
126
126
  'Standalone mode writes the full app-root instruction set into the current Proteum app root.',
127
127
  'Monorepo mode writes the reusable root `AGENTS.md` into the chosen monorepo root and switches the current app root `AGENTS.md` to the app-root addendum.',
128
- 'Existing non-managed files are never overwritten; Proteum only creates missing symlinks or updates symlinks it already manages.',
128
+ 'If a target path already contains a non-managed file or foreign symlink, the interactive flow asks whether to overwrite it with the Proteum-managed symlink.',
129
+ 'Declined non-managed paths are left untouched; Proteum still creates missing symlinks and updates symlinks it already manages.',
129
130
  ],
130
131
  status: 'experimental',
131
132
  },
@@ -12,13 +12,20 @@ import { logVerbose } from '../runtime/verbose';
12
12
  ----------------------------------*/
13
13
 
14
14
  type TProjectInstructionArgs = { coreRoot: string };
15
- type TConfigureProjectAgentSymlinksArgs = { appRoot: string; coreRoot: string; monorepoRoot?: string };
15
+ type TConfigureProjectAgentSymlinksArgs = {
16
+ appRoot: string;
17
+ coreRoot: string;
18
+ dryRun?: boolean;
19
+ monorepoRoot?: string;
20
+ overwriteBlockedPaths?: string[];
21
+ };
16
22
 
17
23
  type TAgentLinkDefinition = { projectPath: string; sourcePath: string; ensureParentDir?: boolean };
18
24
 
19
25
  type TEnsureSymlinksResult = {
20
26
  blocked: string[];
21
27
  created: string[];
28
+ overwritten: string[];
22
29
  skipped: string[];
23
30
  updated: string[];
24
31
  };
@@ -29,6 +36,7 @@ export type TConfigureProjectAgentSymlinksResult = {
29
36
  created: string[];
30
37
  monorepoRoot?: string;
31
38
  mode: 'monorepo' | 'standalone';
39
+ overwritten: string[];
32
40
  skipped: string[];
33
41
  updated: string[];
34
42
  updatedGitignores: string[];
@@ -76,10 +84,15 @@ const projectInstructionGitignoreBlockEnd = '# End Proteum-managed instruction s
76
84
  export function configureProjectAgentSymlinks({
77
85
  appRoot,
78
86
  coreRoot,
87
+ dryRun = false,
79
88
  monorepoRoot,
89
+ overwriteBlockedPaths = [],
80
90
  }: TConfigureProjectAgentSymlinksArgs): TConfigureProjectAgentSymlinksResult {
81
91
  const normalizedAppRoot = path.resolve(appRoot);
82
92
  const normalizedMonorepoRoot = monorepoRoot ? path.resolve(monorepoRoot) : undefined;
93
+ const normalizedOverwriteBlockedPaths = new Set(
94
+ overwriteBlockedPaths.map((blockedPath) => normalizeAbsolutePath(path.resolve(blockedPath))),
95
+ );
83
96
  const mode =
84
97
  normalizedMonorepoRoot && normalizedMonorepoRoot !== normalizedAppRoot ? ('monorepo' as const) : ('standalone' as const);
85
98
  const result: TConfigureProjectAgentSymlinksResult = {
@@ -87,6 +100,7 @@ export function configureProjectAgentSymlinks({
87
100
  blocked: [],
88
101
  created: [],
89
102
  mode,
103
+ overwritten: [],
90
104
  skipped: [],
91
105
  updated: [],
92
106
  updatedGitignores: [],
@@ -96,18 +110,24 @@ export function configureProjectAgentSymlinks({
96
110
  result.monorepoRoot = normalizedMonorepoRoot;
97
111
 
98
112
  const rootLinks = getRootAgentLinkDefinitions({ coreRoot });
99
- const rootSymlinks = ensureSymlinks(normalizedMonorepoRoot, rootLinks, '[agents]', path.join(coreRoot, 'agents', 'project'));
113
+ const rootSymlinks = ensureSymlinks(normalizedMonorepoRoot, rootLinks, '[agents]', path.join(coreRoot, 'agents', 'project'), {
114
+ dryRun,
115
+ overwriteBlockedPaths: normalizedOverwriteBlockedPaths,
116
+ });
100
117
  mergeSymlinkResults(result, rootSymlinks, normalizedMonorepoRoot);
101
118
 
102
- if (ensureInstructionGitignoreEntries({ rootDir: normalizedMonorepoRoot, linkDefinitions: rootLinks }))
119
+ if (!dryRun && ensureInstructionGitignoreEntries({ rootDir: normalizedMonorepoRoot, linkDefinitions: rootLinks }))
103
120
  result.updatedGitignores.push(path.join(normalizedMonorepoRoot, '.gitignore'));
104
121
  }
105
122
 
106
123
  const appLinks = getAppAgentLinkDefinitions({ coreRoot, mode });
107
- const appSymlinks = ensureSymlinks(normalizedAppRoot, appLinks, '[agents]', path.join(coreRoot, 'agents', 'project'));
124
+ const appSymlinks = ensureSymlinks(normalizedAppRoot, appLinks, '[agents]', path.join(coreRoot, 'agents', 'project'), {
125
+ dryRun,
126
+ overwriteBlockedPaths: normalizedOverwriteBlockedPaths,
127
+ });
108
128
  mergeSymlinkResults(result, appSymlinks, normalizedAppRoot);
109
129
 
110
- if (ensureInstructionGitignoreEntries({ rootDir: normalizedAppRoot, linkDefinitions: appLinks }))
130
+ if (!dryRun && ensureInstructionGitignoreEntries({ rootDir: normalizedAppRoot, linkDefinitions: appLinks }))
111
131
  result.updatedGitignores.push(path.join(normalizedAppRoot, '.gitignore'));
112
132
 
113
133
  return result;
@@ -223,10 +243,18 @@ function ensureSymlinks(
223
243
  linkDefinitions: TAgentLinkDefinition[],
224
244
  logPrefix: string,
225
245
  managedSourceRoot: string,
246
+ {
247
+ dryRun,
248
+ overwriteBlockedPaths,
249
+ }: {
250
+ dryRun: boolean;
251
+ overwriteBlockedPaths: Set<string>;
252
+ },
226
253
  ): TEnsureSymlinksResult {
227
254
  const result: TEnsureSymlinksResult = {
228
255
  blocked: [],
229
256
  created: [],
257
+ overwritten: [],
230
258
  skipped: [],
231
259
  updated: [],
232
260
  };
@@ -256,7 +284,8 @@ function ensureSymlinks(
256
284
  continue;
257
285
  }
258
286
 
259
- if (existingState.kind === 'blocked') {
287
+ const normalizedProjectFilepath = normalizeAbsolutePath(projectFilepath);
288
+ if (existingState.kind === 'blocked' && !overwriteBlockedPaths.has(normalizedProjectFilepath)) {
260
289
  result.blocked.push(relativeProjectPath);
261
290
  continue;
262
291
  }
@@ -264,14 +293,26 @@ function ensureSymlinks(
264
293
  const symlinkTarget = path.relative(projectParentDir, sourceFilepath);
265
294
 
266
295
  if (existingState.kind === 'managed-different') {
267
- fs.unlinkSync(projectFilepath);
268
- fs.symlinkSync(symlinkTarget, projectFilepath);
296
+ if (!dryRun) {
297
+ fs.unlinkSync(projectFilepath);
298
+ fs.symlinkSync(symlinkTarget, projectFilepath);
299
+ }
269
300
  result.updated.push(relativeProjectPath);
270
301
  logVerbose(`${logPrefix} Updated ${relativeProjectPath} -> ${symlinkTarget}`);
271
302
  continue;
272
303
  }
273
304
 
274
- fs.symlinkSync(symlinkTarget, projectFilepath);
305
+ if (existingState.kind === 'blocked') {
306
+ if (!dryRun) {
307
+ fs.removeSync(projectFilepath);
308
+ fs.symlinkSync(symlinkTarget, projectFilepath);
309
+ }
310
+ result.overwritten.push(relativeProjectPath);
311
+ logVerbose(`${logPrefix} Replaced ${relativeProjectPath} -> ${symlinkTarget}`);
312
+ continue;
313
+ }
314
+
315
+ if (!dryRun) fs.symlinkSync(symlinkTarget, projectFilepath);
275
316
  result.created.push(relativeProjectPath);
276
317
  logVerbose(`${logPrefix} Created ${relativeProjectPath} -> ${symlinkTarget}`);
277
318
  }
@@ -320,6 +361,7 @@ function mergeSymlinkResults(
320
361
  rootDir: string,
321
362
  ) {
322
363
  result.created.push(...next.created.map((entry) => formatResultPath(rootDir, entry)));
364
+ result.overwritten.push(...next.overwritten.map((entry) => formatResultPath(rootDir, entry)));
323
365
  result.updated.push(...next.updated.map((entry) => formatResultPath(rootDir, entry)));
324
366
  result.skipped.push(...next.skipped.map((entry) => formatResultPath(rootDir, entry)));
325
367
  result.blocked.push(...next.blocked.map((entry) => formatResultPath(rootDir, entry)));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "proteum",
3
3
  "description": "LLM-first Opinionated Typescript Framework for web applications.",
4
- "version": "2.1.9-7",
4
+ "version": "2.1.9-8",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/proteum.git",
7
7
  "license": "MIT",