proteum 2.5.5 → 2.5.7

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.
@@ -2,6 +2,7 @@ import cp from 'child_process';
2
2
  import crypto from 'crypto';
3
3
  import fs from 'fs-extra';
4
4
  import path from 'path';
5
+ import { findProteumAppRootsUnder } from '../utils/appRoots';
5
6
 
6
7
  /*----------------------------------
7
8
  - TYPES
@@ -124,6 +125,30 @@ export type TRunWorktreeBootstrapCreateOptions = TRunWorktreeBootstrapInitOption
124
125
  targetRepoRoot: string;
125
126
  };
126
127
 
128
+ export type TMonorepoWorktreeBootstrapAppResult = {
129
+ appRoot: string;
130
+ error?: string;
131
+ markerFilepath?: string;
132
+ ok: boolean;
133
+ refresh?: string;
134
+ relativeAppRoot: string;
135
+ runtimeStatus?: string;
136
+ sourceAppRoot?: string;
137
+ status?: TWorktreeBootstrapStatus;
138
+ };
139
+
140
+ export type TRunMonorepoWorktreeBootstrapInitOptions = Omit<TRunWorktreeBootstrapInitOptions, 'appRoot' | 'source'> & {
141
+ appRoots?: string[];
142
+ monorepoRoot: string;
143
+ source?: string;
144
+ };
145
+
146
+ export type TRunMonorepoWorktreeBootstrapCreateOptions = TRunMonorepoWorktreeBootstrapInitOptions & {
147
+ base?: string;
148
+ branch: string;
149
+ targetRepoRoot: string;
150
+ };
151
+
127
152
  /*----------------------------------
128
153
  - CONSTANTS
129
154
  ----------------------------------*/
@@ -139,6 +164,16 @@ const codexWorktreeSegment = `${path.sep}.codex${path.sep}worktrees${path.sep}`;
139
164
 
140
165
  const normalizePath = (value: string) => path.normalize(path.resolve(value));
141
166
 
167
+ const normalizeExistingPath = (value: string) => {
168
+ const normalized = normalizePath(value);
169
+
170
+ try {
171
+ return path.normalize(fs.realpathSync(normalized));
172
+ } catch {
173
+ return normalized;
174
+ }
175
+ };
176
+
142
177
  const isTruthyEnv = (value: string | undefined) => value === '1' || value === 'true' || value === 'yes';
143
178
 
144
179
  const nowIso = () => new Date().toISOString();
@@ -699,3 +734,131 @@ export const runWorktreeBootstrapCreate = async ({
699
734
  worktreeBootstrap: initResult,
700
735
  };
701
736
  };
737
+
738
+ const findBootstrapInstallRoot = (appRoot: string) => {
739
+ const packageLockFilepath = findNearestExistingPath(appRoot, 'package-lock.json');
740
+ return packageLockFilepath ? path.dirname(packageLockFilepath) : normalizePath(appRoot);
741
+ };
742
+
743
+ const createSharedDependencyRunner = (runDependencies: (appRoot: string) => Promise<void> = runNpmInstall) => {
744
+ const completedInstallRoots = new Set<string>();
745
+
746
+ return async (appRoot: string) => {
747
+ const installRoot = findBootstrapInstallRoot(appRoot);
748
+ if (completedInstallRoots.has(installRoot)) return;
749
+
750
+ completedInstallRoots.add(installRoot);
751
+ await runDependencies(installRoot);
752
+ };
753
+ };
754
+
755
+ const resolveSourceAppRoot = ({
756
+ relativeAppRoot,
757
+ sourceRoot,
758
+ }: {
759
+ relativeAppRoot: string;
760
+ sourceRoot?: string;
761
+ }) => {
762
+ if (!sourceRoot) return undefined;
763
+
764
+ const normalizedSourceRoot = normalizeExistingPath(sourceRoot);
765
+ const sourceAppRoot = path.join(normalizedSourceRoot, relativeAppRoot);
766
+
767
+ if (fs.existsSync(sourceAppRoot)) return sourceAppRoot;
768
+
769
+ return undefined;
770
+ };
771
+
772
+ export const runMonorepoWorktreeBootstrapInit = async ({
773
+ appRoots,
774
+ monorepoRoot,
775
+ runDependencies,
776
+ source,
777
+ ...initOptions
778
+ }: TRunMonorepoWorktreeBootstrapInitOptions) => {
779
+ const normalizedMonorepoRoot = normalizeExistingPath(monorepoRoot);
780
+ const targetAppRoots = (appRoots || findProteumAppRootsUnder(normalizedMonorepoRoot))
781
+ .map((appRoot) => normalizeExistingPath(appRoot))
782
+ .sort((left, right) => left.localeCompare(right));
783
+ const sharedDependencyRunner = createSharedDependencyRunner(runDependencies);
784
+ const apps: TMonorepoWorktreeBootstrapAppResult[] = [];
785
+
786
+ if (targetAppRoots.length === 0) throw new Error(`No Proteum app roots were found under ${normalizedMonorepoRoot}.`);
787
+
788
+ for (const appRoot of targetAppRoots) {
789
+ const relativeAppRoot = path.relative(normalizedMonorepoRoot, appRoot) || '.';
790
+ const sourceAppRoot = resolveSourceAppRoot({
791
+ relativeAppRoot,
792
+ sourceRoot: source,
793
+ });
794
+
795
+ try {
796
+ const result = await runWorktreeBootstrapInit({
797
+ ...initOptions,
798
+ appRoot,
799
+ runDependencies: sharedDependencyRunner,
800
+ source: sourceAppRoot,
801
+ });
802
+
803
+ apps.push({
804
+ appRoot,
805
+ markerFilepath: result.markerFilepath,
806
+ ok: true,
807
+ refresh: result.refresh,
808
+ relativeAppRoot,
809
+ runtimeStatus: result.runtimeStatus,
810
+ sourceAppRoot,
811
+ status: result.status,
812
+ });
813
+ } catch (error) {
814
+ apps.push({
815
+ appRoot,
816
+ error: error instanceof Error ? error.message : String(error),
817
+ ok: false,
818
+ relativeAppRoot,
819
+ sourceAppRoot,
820
+ });
821
+ }
822
+ }
823
+
824
+ return {
825
+ appRoots: targetAppRoots,
826
+ apps,
827
+ failed: apps.filter((entry) => !entry.ok).length,
828
+ monorepoRoot: normalizedMonorepoRoot,
829
+ ok: apps.every((entry) => entry.ok),
830
+ sourceRoot: source ? normalizeExistingPath(source) : undefined,
831
+ };
832
+ };
833
+
834
+ export const runMonorepoWorktreeBootstrapCreate = async ({
835
+ base = 'HEAD',
836
+ branch,
837
+ source,
838
+ targetRepoRoot,
839
+ ...initOptions
840
+ }: TRunMonorepoWorktreeBootstrapCreateOptions) => {
841
+ if (!branch.trim()) throw new Error('worktree create requires --branch <branch>.');
842
+ if (!targetRepoRoot.trim()) throw new Error('worktree create requires <target-repo-root>.');
843
+
844
+ const normalizedSourceRoot = normalizeExistingPath(source || initOptions.monorepoRoot);
845
+ const normalizedTargetRepoRoot = path.resolve(targetRepoRoot);
846
+ const sourceRepoRoot = await findGitRepoRoot(normalizedSourceRoot);
847
+
848
+ await runCapture('git', ['worktree', 'add', '-b', branch, normalizedTargetRepoRoot, base], { cwd: sourceRepoRoot });
849
+
850
+ const initResult = await runMonorepoWorktreeBootstrapInit({
851
+ ...initOptions,
852
+ monorepoRoot: normalizedTargetRepoRoot,
853
+ refresh: true,
854
+ source: normalizedSourceRoot,
855
+ });
856
+
857
+ return {
858
+ branch,
859
+ sourceMonorepoRoot: normalizedSourceRoot,
860
+ sourceRepoRoot,
861
+ targetRepoRoot: normalizedTargetRepoRoot,
862
+ worktreeBootstrap: initResult,
863
+ };
864
+ };
@@ -12,14 +12,31 @@ import { createStartDevCommand, findProteumAppRootsUnder, readProteumAppRootSumm
12
12
  - TYPES
13
13
  ----------------------------------*/
14
14
 
15
- type TProjectInstructionArgs = { appRoot?: string; coreRoot: string; includeMonorepoRegistry?: boolean; monorepoRoot?: string };
15
+ type TProjectInstructionArgs = {
16
+ appRoot?: string;
17
+ coreRoot: string;
18
+ includeMonorepoRegistry?: boolean;
19
+ instructionRoot: string;
20
+ monorepoRegistryCurrentAppRoot?: string;
21
+ monorepoRoot?: string;
22
+ };
16
23
  type TConfigureProjectAgentInstructionsArgs = {
17
24
  appRoot: string;
18
25
  coreRoot: string;
19
26
  dryRun?: boolean;
27
+ includeAppInstructions?: boolean;
28
+ includeRootInstructions?: boolean;
29
+ markCurrentAppInMonorepoRegistry?: boolean;
20
30
  monorepoRoot?: string;
21
31
  overwriteBlockedPaths?: string[];
22
32
  };
33
+ type TConfigureMonorepoProjectAgentInstructionsArgs = {
34
+ appRoots: string[];
35
+ coreRoot: string;
36
+ dryRun?: boolean;
37
+ monorepoRoot: string;
38
+ overwriteBlockedPaths?: string[];
39
+ };
23
40
 
24
41
  type TAgentInstructionDefinition = {
25
42
  projectPath: string;
@@ -49,6 +66,12 @@ export type TConfigureProjectAgentInstructionsResult = {
49
66
  updatedGitignores: string[];
50
67
  };
51
68
 
69
+ export type TConfigureMonorepoProjectAgentInstructionsResult = Omit<TConfigureProjectAgentInstructionsResult, 'appRoot'> & {
70
+ appRoots: string[];
71
+ monorepoRoot: string;
72
+ mode: 'monorepo';
73
+ };
74
+
52
75
  /*----------------------------------
53
76
  - CONSTANTS
54
77
  ----------------------------------*/
@@ -84,6 +107,14 @@ const sharedTestAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
84
107
  { projectPath: path.join('tests', 'e2e', 'REAL_WORLD_JOURNEY_TESTS.md'), ensureParentDir: true, content: 'source' },
85
108
  ];
86
109
 
110
+ const projectInstructionSourceMapEntries = [
111
+ { label: 'Root contract fallback', projectPath: 'AGENTS.md' },
112
+ { label: 'Documentation fallback', projectPath: 'DOCUMENTATION.md' },
113
+ { label: 'Diagnostics fallback', projectPath: 'diagnostics.md' },
114
+ { label: 'Optimization fallback', projectPath: 'optimizations.md' },
115
+ { label: 'Coding style fallback', projectPath: 'CODING_STYLE.md' },
116
+ ];
117
+
87
118
  const standaloneAppAgentInstructionDefinitions: TAgentInstructionDefinition[] = [
88
119
  { projectPath: 'AGENTS.md', content: 'router' },
89
120
  ...sharedRootDocumentInstructionDefinitions,
@@ -115,6 +146,9 @@ export function configureProjectAgentInstructions({
115
146
  appRoot,
116
147
  coreRoot,
117
148
  dryRun = false,
149
+ includeAppInstructions = true,
150
+ includeRootInstructions = true,
151
+ markCurrentAppInMonorepoRegistry = true,
118
152
  monorepoRoot,
119
153
  overwriteBlockedPaths = [],
120
154
  }: TConfigureProjectAgentInstructionsArgs): TConfigureProjectAgentInstructionsResult {
@@ -139,6 +173,7 @@ export function configureProjectAgentInstructions({
139
173
  const appEmbeddedInstructions = renderEmbeddedProjectInstructions({
140
174
  appRoot: normalizedAppRoot,
141
175
  coreRoot,
176
+ instructionRoot: normalizedAppRoot,
142
177
  monorepoRoot: normalizedMonorepoRoot,
143
178
  });
144
179
  const rootEmbeddedInstructions =
@@ -147,11 +182,13 @@ export function configureProjectAgentInstructions({
147
182
  appRoot: normalizedAppRoot,
148
183
  coreRoot,
149
184
  includeMonorepoRegistry: true,
185
+ instructionRoot: normalizedMonorepoRoot,
186
+ monorepoRegistryCurrentAppRoot: markCurrentAppInMonorepoRegistry ? normalizedAppRoot : undefined,
150
187
  monorepoRoot: normalizedMonorepoRoot,
151
188
  })
152
189
  : appEmbeddedInstructions;
153
190
 
154
- if (mode === 'monorepo' && normalizedMonorepoRoot) {
191
+ if (includeRootInstructions && mode === 'monorepo' && normalizedMonorepoRoot) {
155
192
  result.monorepoRoot = normalizedMonorepoRoot;
156
193
 
157
194
  const rootInstructions = getRootAgentInstructionDefinitions();
@@ -172,52 +209,127 @@ export function configureProjectAgentInstructions({
172
209
  result.updatedGitignores.push(path.join(normalizedMonorepoRoot, '.gitignore'));
173
210
  }
174
211
 
175
- const appInstructions = getAppAgentInstructionDefinitions({ mode });
176
- const appFiles = ensureInstructionFiles(
177
- normalizedAppRoot,
178
- appInstructions,
179
- '[agents]',
180
- path.join(coreRoot, 'agents', 'project'),
181
- appEmbeddedInstructions,
182
- {
183
- dryRun,
184
- overwriteBlockedPaths: normalizedOverwriteBlockedPaths,
185
- },
186
- );
187
- mergeInstructionResults(result, appFiles, normalizedAppRoot);
188
-
189
- if (mode === 'monorepo') {
190
- const retiredAppRootFiles = removeManagedInstructionFiles(
212
+ if (includeAppInstructions) {
213
+ const appInstructions = getAppAgentInstructionDefinitions({ mode });
214
+ const appFiles = ensureInstructionFiles(
191
215
  normalizedAppRoot,
192
- [...sharedRootDocumentInstructionDefinitions, ...sharedTestAgentInstructionDefinitions],
216
+ appInstructions,
193
217
  '[agents]',
194
218
  path.join(coreRoot, 'agents', 'project'),
219
+ appEmbeddedInstructions,
195
220
  {
196
221
  dryRun,
222
+ overwriteBlockedPaths: normalizedOverwriteBlockedPaths,
197
223
  },
198
224
  );
199
- mergeInstructionResults(result, retiredAppRootFiles, normalizedAppRoot);
200
- }
225
+ mergeInstructionResults(result, appFiles, normalizedAppRoot);
226
+
227
+ if (mode === 'monorepo') {
228
+ const retiredAppRootFiles = removeManagedInstructionFiles(
229
+ normalizedAppRoot,
230
+ [...sharedRootDocumentInstructionDefinitions, ...sharedTestAgentInstructionDefinitions],
231
+ '[agents]',
232
+ path.join(coreRoot, 'agents', 'project'),
233
+ {
234
+ dryRun,
235
+ },
236
+ );
237
+ mergeInstructionResults(result, retiredAppRootFiles, normalizedAppRoot);
238
+ }
201
239
 
202
- const appGitignoreCleanupInstructions =
203
- mode === 'monorepo'
204
- ? [...appInstructions, ...sharedRootDocumentInstructionDefinitions, ...sharedTestAgentInstructionDefinitions]
205
- : appInstructions;
206
-
207
- if (
208
- !dryRun &&
209
- removeInstructionGitignoreEntries({
210
- rootDir: normalizedAppRoot,
211
- instructionDefinitions: appGitignoreCleanupInstructions,
212
- })
213
- )
214
- result.updatedGitignores.push(path.join(normalizedAppRoot, '.gitignore'));
240
+ const appGitignoreCleanupInstructions =
241
+ mode === 'monorepo'
242
+ ? [...appInstructions, ...sharedRootDocumentInstructionDefinitions, ...sharedTestAgentInstructionDefinitions]
243
+ : appInstructions;
244
+
245
+ if (
246
+ !dryRun &&
247
+ removeInstructionGitignoreEntries({
248
+ rootDir: normalizedAppRoot,
249
+ instructionDefinitions: appGitignoreCleanupInstructions,
250
+ })
251
+ )
252
+ result.updatedGitignores.push(path.join(normalizedAppRoot, '.gitignore'));
253
+ }
215
254
 
216
255
  return result;
217
256
  }
218
257
 
219
258
  export const configureProjectAgentSymlinks = configureProjectAgentInstructions;
220
259
 
260
+ export function configureMonorepoProjectAgentInstructions({
261
+ appRoots,
262
+ coreRoot,
263
+ dryRun = false,
264
+ monorepoRoot,
265
+ overwriteBlockedPaths = [],
266
+ }: TConfigureMonorepoProjectAgentInstructionsArgs): TConfigureMonorepoProjectAgentInstructionsResult {
267
+ const normalizedMonorepoRoot = path.resolve(monorepoRoot);
268
+ const normalizedAppRoots = [...new Set(appRoots.map((appRoot) => path.resolve(appRoot)))].sort((left, right) =>
269
+ left.localeCompare(right),
270
+ );
271
+ const [firstAppRoot] = normalizedAppRoots;
272
+
273
+ if (!firstAppRoot) throw new Error('No Proteum app roots were found under the monorepo root.');
274
+
275
+ const result: TConfigureMonorepoProjectAgentInstructionsResult = {
276
+ appRoots: normalizedAppRoots,
277
+ blocked: [],
278
+ created: [],
279
+ mode: 'monorepo',
280
+ monorepoRoot: normalizedMonorepoRoot,
281
+ overwritten: [],
282
+ removed: [],
283
+ skipped: [],
284
+ updated: [],
285
+ updatedGitignores: [],
286
+ };
287
+ const mergeProjectResult = (next: TConfigureProjectAgentInstructionsResult) => {
288
+ result.blocked = [...new Set([...result.blocked, ...next.blocked])];
289
+ result.created = [...new Set([...result.created, ...next.created])];
290
+ result.overwritten = [...new Set([...result.overwritten, ...next.overwritten])];
291
+ result.removed = [...new Set([...result.removed, ...next.removed])];
292
+ result.skipped = [...new Set([...result.skipped, ...next.skipped])];
293
+ result.updated = [...new Set([...result.updated, ...next.updated])];
294
+ result.updatedGitignores = [...new Set([...result.updatedGitignores, ...next.updatedGitignores])];
295
+ };
296
+
297
+ mergeProjectResult(
298
+ configureProjectAgentInstructions({
299
+ appRoot: firstAppRoot,
300
+ coreRoot,
301
+ dryRun,
302
+ includeAppInstructions: false,
303
+ markCurrentAppInMonorepoRegistry: false,
304
+ monorepoRoot: normalizedMonorepoRoot,
305
+ overwriteBlockedPaths,
306
+ }),
307
+ );
308
+
309
+ for (const appRoot of normalizedAppRoots) {
310
+ mergeProjectResult(
311
+ configureProjectAgentInstructions({
312
+ appRoot,
313
+ coreRoot,
314
+ dryRun,
315
+ includeRootInstructions: false,
316
+ monorepoRoot: normalizedMonorepoRoot,
317
+ overwriteBlockedPaths,
318
+ }),
319
+ );
320
+ }
321
+
322
+ result.blocked = [...new Set(result.blocked)];
323
+ result.created = [...new Set(result.created)];
324
+ result.overwritten = [...new Set(result.overwritten)];
325
+ result.removed = [...new Set(result.removed)];
326
+ result.skipped = [...new Set(result.skipped)];
327
+ result.updated = [...new Set(result.updated)];
328
+ result.updatedGitignores = [...new Set(result.updatedGitignores)];
329
+
330
+ return result;
331
+ }
332
+
221
333
  export function resolveProjectAgentMonorepoRoot(appRoot: string) {
222
334
  const normalizedAppRoot = resolveCanonicalPath(appRoot);
223
335
  const likelyRepoRoot = findLikelyRepoRoot(normalizedAppRoot);
@@ -831,9 +943,11 @@ function renderSingleProjectInstruction({
831
943
 
832
944
  function renderMonorepoAppRegistry({
833
945
  appRoot,
946
+ currentAppRoot,
834
947
  monorepoRoot,
835
948
  }: {
836
949
  appRoot?: string;
950
+ currentAppRoot?: string;
837
951
  monorepoRoot?: string;
838
952
  }) {
839
953
  if (!monorepoRoot || !appRoot || path.resolve(monorepoRoot) === path.resolve(appRoot)) return [];
@@ -846,10 +960,13 @@ function renderMonorepoAppRegistry({
846
960
  return [
847
961
  '## Known Proteum Apps',
848
962
  '',
849
- 'This is a monorepo root wrapper. Do not start `npx proteum dev` from this root; start it from one app root below.',
963
+ 'This is a monorepo root wrapper. Eligible Proteum commands run across the apps below from this root; use an app root when you need to target exactly one app.',
850
964
  '',
851
965
  ...summaries.map((summary) => {
852
- const marker = path.resolve(summary.appRoot) === path.resolve(appRoot) ? ' (current configured app)' : '';
966
+ const marker =
967
+ currentAppRoot && path.resolve(summary.appRoot) === path.resolve(currentAppRoot)
968
+ ? ' (current configured app)'
969
+ : '';
853
970
  const port = summary.manifest?.routerPort ? `, default port ${summary.manifest.routerPort}` : '';
854
971
  const command = createStartDevCommand({
855
972
  appRoot: summary.appRoot,
@@ -863,7 +980,14 @@ function renderMonorepoAppRegistry({
863
980
  ];
864
981
  }
865
982
 
866
- function renderEmbeddedProjectInstructions({ appRoot, coreRoot, includeMonorepoRegistry = false, monorepoRoot }: TProjectInstructionArgs) {
983
+ function renderEmbeddedProjectInstructions({
984
+ appRoot,
985
+ coreRoot,
986
+ includeMonorepoRegistry = false,
987
+ instructionRoot,
988
+ monorepoRegistryCurrentAppRoot,
989
+ monorepoRoot,
990
+ }: TProjectInstructionArgs) {
867
991
  const agentSourceRoot = path.join(coreRoot, 'agents', 'project');
868
992
  if (!fs.existsSync(agentSourceRoot)) throw new Error(`Missing project instruction source root: ${agentSourceRoot}`);
869
993
 
@@ -895,7 +1019,13 @@ function renderEmbeddedProjectInstructions({ appRoot, coreRoot, includeMonorepoR
895
1019
  '',
896
1020
  'CLI remains the reproducible surface for `dev`, `build`, `check`, `verify`, migrations, and final command evidence. MCP remains read-only and returns compact `proteum-mcp-v1` JSON.',
897
1021
  '',
898
- ...(includeMonorepoRegistry ? renderMonorepoAppRegistry({ appRoot, monorepoRoot }) : []),
1022
+ ...(includeMonorepoRegistry
1023
+ ? renderMonorepoAppRegistry({
1024
+ appRoot,
1025
+ currentAppRoot: monorepoRegistryCurrentAppRoot,
1026
+ monorepoRoot,
1027
+ })
1028
+ : []),
899
1029
  '## Always-On Safety',
900
1030
  '',
901
1031
  '- Never edit generated files under `.proteum`.',
@@ -945,11 +1075,14 @@ function renderEmbeddedProjectInstructions({ appRoot, coreRoot, includeMonorepoR
945
1075
  '',
946
1076
  '## Canonical Source Map',
947
1077
  '',
948
- `- Root contract fallback: ${normalizeProjectPathForGitignore(path.join(coreRoot, 'agents', 'project', 'AGENTS.md'))}`,
949
- `- Documentation fallback: ${normalizeProjectPathForGitignore(path.join(coreRoot, 'agents', 'project', 'DOCUMENTATION.md'))}`,
950
- `- Diagnostics fallback: ${normalizeProjectPathForGitignore(path.join(coreRoot, 'agents', 'project', 'diagnostics.md'))}`,
951
- `- Optimization fallback: ${normalizeProjectPathForGitignore(path.join(coreRoot, 'agents', 'project', 'optimizations.md'))}`,
952
- `- Coding style fallback: ${normalizeProjectPathForGitignore(path.join(coreRoot, 'agents', 'project', 'CODING_STYLE.md'))}`,
1078
+ ...projectInstructionSourceMapEntries.map(
1079
+ ({ label, projectPath }) =>
1080
+ `- ${label}: ${formatProjectInstructionSourceMapPath({
1081
+ coreRoot,
1082
+ instructionRoot,
1083
+ projectPath,
1084
+ })}`,
1085
+ ),
953
1086
  '',
954
1087
  ];
955
1088
 
@@ -1091,6 +1224,41 @@ function formatResultPath(rootDir: string, relativePath: string) {
1091
1224
  return normalizeProjectPathForGitignore(path.join(rootDir, relativePath));
1092
1225
  }
1093
1226
 
1227
+ function formatProjectInstructionSourceMapPath({
1228
+ coreRoot,
1229
+ instructionRoot,
1230
+ projectPath,
1231
+ }: {
1232
+ coreRoot: string;
1233
+ instructionRoot: string;
1234
+ projectPath: string;
1235
+ }) {
1236
+ const sourceRoot = resolveProjectInstructionSourceMapRoot({ coreRoot, instructionRoot });
1237
+ const sourcePath = path.resolve(sourceRoot, 'agents', 'project', projectPath);
1238
+ const relativePath = path.relative(path.resolve(instructionRoot), sourcePath);
1239
+
1240
+ return normalizeProjectPathForGitignore(relativePath);
1241
+ }
1242
+
1243
+ function resolveProjectInstructionSourceMapRoot({ coreRoot, instructionRoot }: { coreRoot: string; instructionRoot: string }) {
1244
+ const visibleInstallRoot = findVisibleProteumInstructionRoot(instructionRoot);
1245
+
1246
+ return visibleInstallRoot || coreRoot;
1247
+ }
1248
+
1249
+ function findVisibleProteumInstructionRoot(startRoot: string) {
1250
+ let currentPath = path.resolve(startRoot);
1251
+
1252
+ while (true) {
1253
+ const candidate = path.join(currentPath, 'node_modules', 'proteum');
1254
+ if (fs.existsSync(path.join(candidate, 'agents', 'project'))) return candidate;
1255
+
1256
+ const parentPath = path.dirname(currentPath);
1257
+ if (parentPath === currentPath) return undefined;
1258
+ currentPath = parentPath;
1259
+ }
1260
+ }
1261
+
1094
1262
  export function resolveCanonicalPath(inputPath: string) {
1095
1263
  const resolvedPath = path.resolve(inputPath);
1096
1264
 
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.5.5",
4
+ "version": "2.5.7",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/proteum.git",
7
7
  "license": "MIT",