proteum 2.5.23 → 2.5.24

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.
@@ -780,14 +780,23 @@ const resolveGuidance = ({
780
780
  manifest: TProteumManifest;
781
781
  ownerFilepath?: string;
782
782
  }) => {
783
- // `agentInstructions: false`: the project owns one hand-written CLAUDE.md and deleted the routed
784
- // copies, so every guidance slot points at it instead of Proteum's bundled fallbacks.
783
+ // `agentInstructions: false`: the project owns one hand-written instruction file and deleted the
784
+ // routed copies, so every guidance slot points at it instead of Proteum's bundled fallbacks.
785
+ // Claude Code reads CLAUDE.md when it exists and AGENTS.md otherwise; follow the same order.
785
786
  if (manifest.app.setup.agentInstructions === false) {
786
- const claudeInstructions = resolveGuidanceFile({
787
+ const claudeFile = resolveGuidanceFile({
787
788
  appRoot: manifest.app.root,
788
- fallbackFilepath: joinPath(manifest.app.root, 'CLAUDE.md'),
789
+ fallbackFilepath: '',
789
790
  relativePath: 'CLAUDE.md',
790
- }).filepath;
791
+ });
792
+ const claudeInstructions =
793
+ claudeFile.warning === undefined
794
+ ? claudeFile.filepath
795
+ : resolveGuidanceFile({
796
+ appRoot: manifest.app.root,
797
+ fallbackFilepath: joinPath(manifest.app.root, 'AGENTS.md'),
798
+ relativePath: 'AGENTS.md',
799
+ }).filepath;
791
800
 
792
801
  return {
793
802
  guidance: {
@@ -940,12 +940,13 @@ export const resolveInstructionRouting = ({
940
940
  const selected = new Map<string, ReturnType<typeof createSelectedInstruction>>();
941
941
  const readWhen: Array<{ file?: string; when: string }> = [];
942
942
 
943
- // `agentInstructions: false`: the routed AGENTS.md copies no longer exist; route to the hand-owned CLAUDE.md.
943
+ // `agentInstructions: false`: the routed copies no longer exist; route to the hand-owned file the
944
+ // agent actually loads (Claude Code reads CLAUDE.md when it exists and AGENTS.md otherwise).
944
945
  if (!readsProteumManagedInstructions(appRoot)) {
945
- const claudeFile = resolveDocumentFile({ appRoot, repoRoot, relativeFilepath: 'CLAUDE.md' });
946
- if (claudeFile && fileExists(claudeFile)) {
947
- selected.set(claudeFile, createSelectedInstruction(claudeFile, 'Project-owned agent instructions.'));
948
- }
946
+ const ownedFile = ['CLAUDE.md', 'AGENTS.md']
947
+ .map((relativeFilepath) => resolveDocumentFile({ appRoot, repoRoot, relativeFilepath }))
948
+ .find((filepath) => filepath !== undefined && fileExists(filepath));
949
+ if (ownedFile) selected.set(ownedFile, createSelectedInstruction(ownedFile, 'Project-owned agent instructions.'));
949
950
  const selectedFiles = [...selected.values()];
950
951
  return createMcpPayload({
951
952
  summary: `${selectedFiles.length} instruction files selected for ${normalizedQuery || 'current app'}`,
@@ -141,4 +141,4 @@ The result confirms the intended routing:
141
141
 
142
142
  ## Hand-Owned Instructions
143
143
 
144
- A project that writes its own agent instructions sets `agentInstructions: false` in each app's `proteum.config.ts`. Proteum then never writes `AGENTS.md`, `CLAUDE.md` or the routed instruction copies for that app: `proteum dev` skips the sync, `proteum configure agents` refuses to run, and a monorepo root is managed only when no app opted out. MCP `workflow_start` and `instructions_resolve` route such apps to their `CLAUDE.md`, and orientation guidance points there instead of Proteum's bundled fallbacks.
144
+ A project that writes its own agent instructions sets `agentInstructions: false` in each app's `proteum.config.ts`. Proteum then never writes `AGENTS.md`, `CLAUDE.md` or the routed instruction copies for that app: `proteum dev` skips the sync, `proteum configure agents` refuses to run, and a monorepo root is managed only when no app opted out. MCP `workflow_start` and `instructions_resolve` route such apps to their hand-owned instruction file (`CLAUDE.md` when it exists, else `AGENTS.md`, the order Claude Code reads them in), and orientation guidance points there instead of Proteum's bundled fallbacks.
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.23",
4
+ "version": "2.5.24",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/proteum.git",
7
7
  "license": "MIT",
@@ -6,7 +6,7 @@ process.env.TS_NODE_PROJECT = path.join(coreRoot, 'cli', 'tsconfig.json');
6
6
  process.env.TS_NODE_TRANSPILE_ONLY = '1';
7
7
  require('ts-node/register/transpile-only');
8
8
 
9
- const { explainOwner } = require('../common/dev/inspection.ts');
9
+ const { buildOrientationResponse, explainOwner } = require('../common/dev/inspection.ts');
10
10
 
11
11
  const createRoute = (routePath, filepath) => ({
12
12
  chunkFilepath: filepath,
@@ -64,3 +64,21 @@ test('root owner lookup returns only the literal root route when present', () =>
64
64
  assert.equal(matches.length, 1);
65
65
  assert.equal(matches[0].label, '/');
66
66
  });
67
+
68
+ test('orientation guidance of an opted-out app points at its hand-owned instruction file', () => {
69
+ const fs = require('node:fs');
70
+ const os = require('node:os');
71
+ const appRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'proteum-owned-guidance-'));
72
+ fs.mkdirSync(path.join(appRoot, '.git'));
73
+ fs.writeFileSync(path.join(appRoot, 'AGENTS.md'), '# Owned\n');
74
+ const manifest = createManifest([]);
75
+ manifest.app = { coreRoot, root: appRoot, identity: { identifier: 'OwnedApp', name: 'Owned App' }, setup: { agentInstructions: false } };
76
+
77
+ const agentsOnly = buildOrientationResponse(manifest, '/').guidance;
78
+ assert.equal(agentsOnly.agents, path.join(appRoot, 'AGENTS.md'));
79
+ assert.equal(agentsOnly.documentation, path.join(appRoot, 'AGENTS.md'));
80
+ assert.deepEqual(agentsOnly.areaAgents, []);
81
+
82
+ fs.writeFileSync(path.join(appRoot, 'CLAUDE.md'), '# Owned for Claude\n');
83
+ assert.equal(buildOrientationResponse(manifest, '/').guidance.agents, path.join(appRoot, 'CLAUDE.md'));
84
+ });
@@ -156,6 +156,23 @@ const writeFreshCopyFixture = (appRoot, manifestOverrides = {}) => {
156
156
  writeFile(path.join(appRoot, '.proteum', 'manifest.json'), JSON.stringify(createManifest(appRoot, manifestOverrides), null, 2));
157
157
  };
158
158
 
159
+ test('instruction routing sends an opted-out app to its hand-owned instruction file', () => {
160
+ const appRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'proteum-mcp-owned-'));
161
+
162
+ writeFile(path.join(appRoot, 'proteum.config.ts'), 'export default { agentInstructions: false };\n');
163
+ writeFile(path.join(appRoot, 'AGENTS.md'), '# Owned\n');
164
+ writeFile(path.join(appRoot, 'client', 'AGENTS.md'), '# Stale routed copy\n');
165
+
166
+ const agentsOnly = resolveInstructionRouting({ appRoot, query: 'client/pages/domain.tsx' });
167
+ assert.deepEqual(agentsOnly.data.selected.map((entry) => path.relative(appRoot, entry.file)), ['AGENTS.md']);
168
+ assert.deepEqual(agentsOnly.data.readWhen, []);
169
+
170
+ // Claude Code reads CLAUDE.md first when both exist, so routing follows it.
171
+ writeFile(path.join(appRoot, 'CLAUDE.md'), '# Owned for Claude\n');
172
+ const withClaude = resolveInstructionRouting({ appRoot, query: 'client/pages/domain.tsx' });
173
+ assert.deepEqual(withClaude.data.selected.map((entry) => path.relative(appRoot, entry.file)), ['CLAUDE.md']);
174
+ });
175
+
159
176
  test('instruction routing returns compact selected files for a page query', () => {
160
177
  const appRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'proteum-mcp-app-'));
161
178