@nemus-cli/nemus 0.2.1
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/CHANGELOG.md +86 -0
- package/LICENSE +21 -0
- package/README.md +275 -0
- package/assets/README.md +53 -0
- package/assets/banner.png +0 -0
- package/assets/banner.svg +66 -0
- package/assets/favicon-32.png +0 -0
- package/assets/favicon-64.png +0 -0
- package/assets/icon-256.png +0 -0
- package/assets/icon.svg +23 -0
- package/assets/logo-256.png +0 -0
- package/assets/logo-512.png +0 -0
- package/assets/logo-wordmark.svg +35 -0
- package/assets/logo.svg +32 -0
- package/bin/workspace.js +130 -0
- package/dist/cli/ai-prompt.js +421 -0
- package/dist/cli/dashboard/AgentList.js +61 -0
- package/dist/cli/dashboard/DashboardSidebar.js +280 -0
- package/dist/cli/dashboard/HelpFooter.js +73 -0
- package/dist/cli/dashboard/StatusBadge.js +50 -0
- package/dist/cli/dashboard/index.js +11 -0
- package/dist/cli/dashboard/types.js +2 -0
- package/dist/cli/tui.js +48 -0
- package/dist/commands/ai-prompt.js +22 -0
- package/dist/commands/analyze-deps.js +138 -0
- package/dist/commands/archive.js +113 -0
- package/dist/commands/branch/create.js +119 -0
- package/dist/commands/branch/index.js +104 -0
- package/dist/commands/branch/merge.js +90 -0
- package/dist/commands/branch/rebase.js +84 -0
- package/dist/commands/branch/switch.js +165 -0
- package/dist/commands/cache/index.js +80 -0
- package/dist/commands/cache/manager.js +198 -0
- package/dist/commands/cleanup.js +153 -0
- package/dist/commands/configure-claude.js +55 -0
- package/dist/commands/configure.js +231 -0
- package/dist/commands/create.js +204 -0
- package/dist/commands/dashboard/hook-handler.js +162 -0
- package/dist/commands/dashboard/index.js +258 -0
- package/dist/commands/dashboard/launcher.js +40 -0
- package/dist/commands/dashboard/session-picker.js +106 -0
- package/dist/commands/dashboard/sidebar.js +39 -0
- package/dist/commands/dashboard/workspace-picker.js +95 -0
- package/dist/commands/delete.js +194 -0
- package/dist/commands/deprecated-aliases.js +44 -0
- package/dist/commands/diff.js +129 -0
- package/dist/commands/doctor.js +141 -0
- package/dist/commands/generate-docs.js +77 -0
- package/dist/commands/ghq-status.js +72 -0
- package/dist/commands/go.js +154 -0
- package/dist/commands/history.js +124 -0
- package/dist/commands/list.js +191 -0
- package/dist/commands/mcp/index.js +69 -0
- package/dist/commands/migrate.js +296 -0
- package/dist/commands/remove-repo.js +217 -0
- package/dist/commands/report-bug.js +112 -0
- package/dist/commands/run.js +147 -0
- package/dist/commands/save-context.js +171 -0
- package/dist/commands/sessions.js +129 -0
- package/dist/commands/status.js +138 -0
- package/dist/commands/suite/create.js +212 -0
- package/dist/commands/suite/delete.js +64 -0
- package/dist/commands/suite/export.js +126 -0
- package/dist/commands/suite/import.js +128 -0
- package/dist/commands/suite/index.js +94 -0
- package/dist/commands/suite/list.js +49 -0
- package/dist/commands/suite/use.js +227 -0
- package/dist/commands/sync.js +117 -0
- package/dist/commands/update.js +203 -0
- package/dist/mcp/install.js +550 -0
- package/dist/mcp/server.js +398 -0
- package/dist/mcp/tools.js +922 -0
- package/dist/pi-extensions/permission-sync.ts.template +41 -0
- package/dist/pi-extensions/permissions-gate.ts.template +69 -0
- package/dist/pi-extensions/pr-fix-trigger.ts.template +124 -0
- package/dist/pi-extensions/review-reminder.ts.template +58 -0
- package/dist/pi-extensions/workspace-input-status.ts.template +919 -0
- package/dist/program.js +136 -0
- package/dist/scripts/workspace-table.py +394 -0
- package/dist/types/dashboard.js +11 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/agent-config.js +263 -0
- package/dist/utils/agent-state.js +178 -0
- package/dist/utils/banner.js +24 -0
- package/dist/utils/branch-operations.js +232 -0
- package/dist/utils/bug-report.js +355 -0
- package/dist/utils/cache.js +168 -0
- package/dist/utils/claude-integration.js +617 -0
- package/dist/utils/claude-sessions.js +260 -0
- package/dist/utils/cleanup-operations.js +120 -0
- package/dist/utils/colors.js +26 -0
- package/dist/utils/command-helpers.js +44 -0
- package/dist/utils/config.js +150 -0
- package/dist/utils/context-file.js +33 -0
- package/dist/utils/dashboard-hooks.js +172 -0
- package/dist/utils/dependency-analyzer.js +234 -0
- package/dist/utils/diff-operations.js +80 -0
- package/dist/utils/doc-generator.js +184 -0
- package/dist/utils/ghq-integration.js +332 -0
- package/dist/utils/git-operations.js +237 -0
- package/dist/utils/git-status.js +187 -0
- package/dist/utils/github.js +68 -0
- package/dist/utils/health-checks.js +345 -0
- package/dist/utils/history.js +150 -0
- package/dist/utils/hooks.js +100 -0
- package/dist/utils/logger.js +40 -0
- package/dist/utils/permission-sync.js +681 -0
- package/dist/utils/pi-extensions.js +224 -0
- package/dist/utils/progress.js +92 -0
- package/dist/utils/prompts.js +279 -0
- package/dist/utils/repo-resolver.js +260 -0
- package/dist/utils/retry.js +65 -0
- package/dist/utils/run-operations.js +30 -0
- package/dist/utils/suite.js +159 -0
- package/dist/utils/sync-operations.js +107 -0
- package/dist/utils/tmux-dashboard.js +184 -0
- package/dist/utils/validation.js +153 -0
- package/dist/utils/version-check.js +121 -0
- package/dist/utils/workspace-meta.js +185 -0
- package/install-shell-integration.sh +532 -0
- package/install.sh +401 -0
- package/package.json +90 -0
- package/remove-shell-block.awk +44 -0
- package/scripts/postinstall.js +85 -0
- package/skills/analyze-deps.md +23 -0
- package/skills/archive-workspace.md +26 -0
- package/skills/branch-create.md +18 -0
- package/skills/create-workspace.md +39 -0
- package/skills/delete-workspace.md +37 -0
- package/skills/list-org-repos.md +26 -0
- package/skills/list-suites.md +24 -0
- package/skills/list-workspaces.md +27 -0
- package/skills/refresh-workspace-docs.md +89 -0
- package/skills/remove-repo.md +25 -0
- package/skills/run-command.md +23 -0
- package/skills/save-context.md +68 -0
- package/skills/search-repos.md +31 -0
- package/skills/snapshot-list.md +18 -0
- package/skills/snapshot-save.md +12 -0
- package/skills/switch-branch.md +18 -0
- package/skills/update-cache.md +21 -0
- package/skills/update-workspace.md +66 -0
- package/skills/workspace-cleanup.md +18 -0
- package/skills/workspace-diff.md +23 -0
- package/skills/workspace-doctor.md +23 -0
- package/skills/workspace-info.md +45 -0
- package/skills/workspace-manager/SKILL.md +201 -0
- package/skills/workspace-manager/references/ai-prompt.md +44 -0
- package/skills/workspace-manager/references/analyze-deps.md +45 -0
- package/skills/workspace-manager/references/archive-workspace.md +38 -0
- package/skills/workspace-manager/references/branch-create.md +31 -0
- package/skills/workspace-manager/references/branch-merge.md +22 -0
- package/skills/workspace-manager/references/branch-rebase.md +22 -0
- package/skills/workspace-manager/references/cache-clear.md +18 -0
- package/skills/workspace-manager/references/cache-info.md +22 -0
- package/skills/workspace-manager/references/configure-claude.md +31 -0
- package/skills/workspace-manager/references/configure.md +35 -0
- package/skills/workspace-manager/references/create-workspace.md +45 -0
- package/skills/workspace-manager/references/dashboard.md +61 -0
- package/skills/workspace-manager/references/delete-workspace.md +33 -0
- package/skills/workspace-manager/references/generate-docs.md +36 -0
- package/skills/workspace-manager/references/ghq-status.md +28 -0
- package/skills/workspace-manager/references/go.md +33 -0
- package/skills/workspace-manager/references/history.md +41 -0
- package/skills/workspace-manager/references/list-org-repos.md +26 -0
- package/skills/workspace-manager/references/list-suites.md +21 -0
- package/skills/workspace-manager/references/list-workspaces.md +27 -0
- package/skills/workspace-manager/references/mcp.md +49 -0
- package/skills/workspace-manager/references/remove-repo.md +26 -0
- package/skills/workspace-manager/references/run-command.md +44 -0
- package/skills/workspace-manager/references/search-repos.md +24 -0
- package/skills/workspace-manager/references/sessions.md +27 -0
- package/skills/workspace-manager/references/suite-create.md +22 -0
- package/skills/workspace-manager/references/suite-delete.md +21 -0
- package/skills/workspace-manager/references/suite-export.md +19 -0
- package/skills/workspace-manager/references/suite-import.md +19 -0
- package/skills/workspace-manager/references/suite-use.md +27 -0
- package/skills/workspace-manager/references/switch-branch.md +25 -0
- package/skills/workspace-manager/references/update-cache.md +22 -0
- package/skills/workspace-manager/references/update-workspace.md +50 -0
- package/skills/workspace-manager/references/workspace-cleanup.md +33 -0
- package/skills/workspace-manager/references/workspace-diff.md +27 -0
- package/skills/workspace-manager/references/workspace-doctor.md +27 -0
- package/skills/workspace-manager/references/workspace-info.md +33 -0
- package/skills/workspace-manager/references/workspace-status.md +43 -0
- package/skills/workspace-manager/references/workspace-sync.md +26 -0
- package/skills/workspace-status.md +26 -0
- package/skills/workspace-sync.md +23 -0
- package/src/cli/ai-prompt.test.ts +667 -0
- package/src/cli/ai-prompt.ts +421 -0
- package/src/cli/dashboard/AgentList.tsx +44 -0
- package/src/cli/dashboard/DashboardSidebar.tsx +291 -0
- package/src/cli/dashboard/HelpFooter.tsx +22 -0
- package/src/cli/dashboard/StatusBadge.tsx +23 -0
- package/src/cli/dashboard/index.ts +5 -0
- package/src/cli/dashboard/types.ts +16 -0
- package/src/cli/tui.tsx +51 -0
- package/src/cli/workspace-cli.test.ts +30 -0
- package/src/commands/ai-prompt.ts +21 -0
- package/src/commands/analyze-deps.ts +119 -0
- package/src/commands/archive.ts +114 -0
- package/src/commands/branch/create.ts +94 -0
- package/src/commands/branch/index.ts +74 -0
- package/src/commands/branch/merge.ts +61 -0
- package/src/commands/branch/rebase.ts +54 -0
- package/src/commands/branch/switch.ts +150 -0
- package/src/commands/cache/index.ts +51 -0
- package/src/commands/cache/manager.ts +168 -0
- package/src/commands/cleanup.ts +132 -0
- package/src/commands/configure-claude.ts +55 -0
- package/src/commands/configure.ts +204 -0
- package/src/commands/create.ts +196 -0
- package/src/commands/dashboard/hook-handler.ts +138 -0
- package/src/commands/dashboard/index.ts +248 -0
- package/src/commands/dashboard/launcher.ts +42 -0
- package/src/commands/dashboard/session-picker.ts +73 -0
- package/src/commands/dashboard/sidebar.js +39 -0
- package/src/commands/dashboard/workspace-picker.ts +62 -0
- package/src/commands/delete.test.ts +250 -0
- package/src/commands/delete.ts +171 -0
- package/src/commands/deprecated-aliases.ts +55 -0
- package/src/commands/diff.ts +106 -0
- package/src/commands/doctor.ts +110 -0
- package/src/commands/generate-docs.ts +45 -0
- package/src/commands/ghq-status.ts +70 -0
- package/src/commands/go.ts +125 -0
- package/src/commands/history.ts +136 -0
- package/src/commands/list.test.ts +219 -0
- package/src/commands/list.ts +176 -0
- package/src/commands/mcp/index.ts +39 -0
- package/src/commands/migrate.ts +277 -0
- package/src/commands/remove-repo.ts +194 -0
- package/src/commands/report-bug.ts +88 -0
- package/src/commands/run.ts +125 -0
- package/src/commands/save-context.test.ts +113 -0
- package/src/commands/save-context.ts +141 -0
- package/src/commands/sessions.ts +99 -0
- package/src/commands/status.ts +111 -0
- package/src/commands/suite/create.ts +240 -0
- package/src/commands/suite/delete.ts +65 -0
- package/src/commands/suite/export.ts +97 -0
- package/src/commands/suite/import.ts +100 -0
- package/src/commands/suite/index.ts +66 -0
- package/src/commands/suite/list.ts +54 -0
- package/src/commands/suite/use.ts +232 -0
- package/src/commands/sync.ts +97 -0
- package/src/commands/update.ts +197 -0
- package/src/mcp/install.ts +529 -0
- package/src/mcp/server.ts +579 -0
- package/src/mcp/tools.test.ts +545 -0
- package/src/mcp/tools.ts +1019 -0
- package/src/mcp/update-workspace.test.ts +220 -0
- package/src/pi-extensions/permission-sync.ts.template +41 -0
- package/src/pi-extensions/permissions-gate.ts.template +69 -0
- package/src/pi-extensions/pr-fix-trigger.ts.template +124 -0
- package/src/pi-extensions/review-reminder.ts.template +58 -0
- package/src/pi-extensions/workspace-input-status.ts.template +919 -0
- package/src/program.ts +112 -0
- package/src/scripts/workspace-table.py +394 -0
- package/src/types/dashboard.ts +29 -0
- package/src/types/index.ts +109 -0
- package/src/types/ink.d.ts +171 -0
- package/src/utils/agent-config.test.ts +303 -0
- package/src/utils/agent-config.ts +264 -0
- package/src/utils/agent-state.test.ts +166 -0
- package/src/utils/agent-state.ts +135 -0
- package/src/utils/banner.ts +24 -0
- package/src/utils/branch-operations.ts +237 -0
- package/src/utils/bug-report.test.ts +174 -0
- package/src/utils/bug-report.ts +355 -0
- package/src/utils/cache.ts +150 -0
- package/src/utils/claude-integration.test.ts +381 -0
- package/src/utils/claude-integration.ts +649 -0
- package/src/utils/claude-sessions.test.ts +262 -0
- package/src/utils/claude-sessions.ts +251 -0
- package/src/utils/cleanup-operations.ts +94 -0
- package/src/utils/colors.ts +25 -0
- package/src/utils/command-helpers.ts +46 -0
- package/src/utils/config.test.ts +203 -0
- package/src/utils/config.ts +133 -0
- package/src/utils/context-file.ts +36 -0
- package/src/utils/dashboard-hooks.test.ts +168 -0
- package/src/utils/dashboard-hooks.ts +163 -0
- package/src/utils/dependency-analyzer.ts +243 -0
- package/src/utils/diff-operations.ts +59 -0
- package/src/utils/doc-generator.ts +187 -0
- package/src/utils/ghq-integration.test.ts +195 -0
- package/src/utils/ghq-integration.ts +317 -0
- package/src/utils/git-operations.test.ts +265 -0
- package/src/utils/git-operations.ts +233 -0
- package/src/utils/git-status.ts +171 -0
- package/src/utils/github.ts +74 -0
- package/src/utils/health-checks.ts +359 -0
- package/src/utils/history.ts +140 -0
- package/src/utils/hooks.ts +92 -0
- package/src/utils/logger.ts +37 -0
- package/src/utils/permission-sync.test.ts +593 -0
- package/src/utils/permission-sync.ts +720 -0
- package/src/utils/pi-extensions.test.ts +182 -0
- package/src/utils/pi-extensions.ts +226 -0
- package/src/utils/progress.ts +73 -0
- package/src/utils/prompts.test.ts +169 -0
- package/src/utils/prompts.ts +296 -0
- package/src/utils/repo-resolver.test.ts +313 -0
- package/src/utils/repo-resolver.ts +294 -0
- package/src/utils/retry.test.ts +124 -0
- package/src/utils/retry.ts +85 -0
- package/src/utils/run-operations.ts +38 -0
- package/src/utils/suite.test.ts +256 -0
- package/src/utils/suite.ts +144 -0
- package/src/utils/sync-operations.ts +87 -0
- package/src/utils/tmux-dashboard.ts +193 -0
- package/src/utils/validation.test.ts +139 -0
- package/src/utils/validation.ts +128 -0
- package/src/utils/version-check.test.ts +108 -0
- package/src/utils/version-check.ts +96 -0
- package/src/utils/workspace-meta.ts +173 -0
- package/sync-permissions.sh +137 -0
- package/tsconfig.json +20 -0
- package/uninstall.sh +162 -0
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.AGENT_RULES_VERSION = void 0;
|
|
37
|
+
exports.buildAgentRulesSection = buildAgentRulesSection;
|
|
38
|
+
exports.backfillAgentRules = backfillAgentRules;
|
|
39
|
+
exports.loadClaudeConfig = loadClaudeConfig;
|
|
40
|
+
exports.saveClaudeConfig = saveClaudeConfig;
|
|
41
|
+
exports.generateClaudeContext = generateClaudeContext;
|
|
42
|
+
exports.preserveOperatorNotes = preserveOperatorNotes;
|
|
43
|
+
exports.generateMcpConfig = generateMcpConfig;
|
|
44
|
+
exports.buildPerRepoContextSection = buildPerRepoContextSection;
|
|
45
|
+
const fs = __importStar(require("fs/promises"));
|
|
46
|
+
const fsSync = __importStar(require("fs"));
|
|
47
|
+
const path = __importStar(require("path"));
|
|
48
|
+
const logger_1 = require("./logger");
|
|
49
|
+
const colors_1 = require("./colors");
|
|
50
|
+
const config_1 = require("./config");
|
|
51
|
+
const agent_config_1 = require("./agent-config");
|
|
52
|
+
const DEFAULT_CONFIG = {
|
|
53
|
+
autoLaunch: false,
|
|
54
|
+
generateContext: true,
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* Version of the AI Agent Rules block. Bump whenever the rules text in
|
|
58
|
+
* {@link buildAgentRulesSection} changes, so {@link backfillAgentRules}
|
|
59
|
+
* replaces a previously-embedded (now stale) block in existing workspaces.
|
|
60
|
+
*/
|
|
61
|
+
exports.AGENT_RULES_VERSION = 2;
|
|
62
|
+
/** Locate + version the rules block in an existing context file. */
|
|
63
|
+
const AGENT_RULES_MARKER_RE = /<!-- ws-rules:v(\d+) -->/;
|
|
64
|
+
/**
|
|
65
|
+
* Build the "AI Agent Rules" markdown section for a given workspace.
|
|
66
|
+
* Used both in new-workspace generation and in the upgrade backfill.
|
|
67
|
+
*/
|
|
68
|
+
function buildAgentRulesSection(workspaceName) {
|
|
69
|
+
let s = `## AI Agent Rules\n\n`;
|
|
70
|
+
s += `<!-- ws-rules:v${exports.AGENT_RULES_VERSION} -->\n`;
|
|
71
|
+
s += `**NEVER** use \`git clone\` directly to add repositories to this workspace.\n`;
|
|
72
|
+
s += `**ALWAYS** use workspace manager commands for all repo management:\n\n`;
|
|
73
|
+
s += `| Intent | Command |\n`;
|
|
74
|
+
s += `|---|---|\n`;
|
|
75
|
+
s += `| Add a repo to this workspace | \`nemus update --workspace ${workspaceName} --repos <repo-name> --yes\` |\n`;
|
|
76
|
+
s += `| Sync / pull latest on all repos | \`w sync ${workspaceName}\` |\n`;
|
|
77
|
+
s += `| Check git status across repos | \`w status ${workspaceName}\` |\n`;
|
|
78
|
+
s += `| Create a new workspace | \`nemus create --workspace <name> --repos <repos> --yes\` |\n`;
|
|
79
|
+
s += `\nUsing \`git clone\` directly bypasses workspace metadata tracking and breaks \`w status\`, \`w sync\`, and context file updates.\n\n`;
|
|
80
|
+
s += `**ONLY read code from repos in THIS workspace.** If you need to read or reference code from a repo that isn't here, add it with \`nemus update --workspace ${workspaceName} --repos <repo-name> --yes\` and read it from this workspace. **NEVER** read that repo's code from another workspace, a global ghq/clone path, or anywhere outside this workspace — other workspaces may be on different branches or stale, and reading them silently breaks context isolation.\n\n`;
|
|
81
|
+
return s;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Backfill (or update) the "AI Agent Rules" section in existing context files
|
|
85
|
+
* (AGENTS.md / .claude.md).
|
|
86
|
+
*
|
|
87
|
+
* - If the section is missing entirely, it is inserted.
|
|
88
|
+
* - If the section is present but carries an older `ws-rules:vN` marker (or no
|
|
89
|
+
* marker at all, i.e. a pre-versioning block), it is REPLACED in place with
|
|
90
|
+
* the current version. This is how rule changes reach existing workspaces
|
|
91
|
+
* on `w mcp upgrade` / migrate.
|
|
92
|
+
* - If the section is already at the current version, the file is left untouched.
|
|
93
|
+
*
|
|
94
|
+
* Idempotent. Returns the number of files updated.
|
|
95
|
+
*/
|
|
96
|
+
async function backfillAgentRules(workspacePath, workspaceName) {
|
|
97
|
+
const contextFileNames = (0, agent_config_1.getContextFileNames)();
|
|
98
|
+
let updated = 0;
|
|
99
|
+
for (const fileName of contextFileNames) {
|
|
100
|
+
const filePath = path.join(workspacePath, fileName);
|
|
101
|
+
let existing;
|
|
102
|
+
try {
|
|
103
|
+
existing = await fs.readFile(filePath, 'utf-8');
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
continue; // file doesn't exist — skip
|
|
107
|
+
}
|
|
108
|
+
const rulesBlock = buildAgentRulesSection(workspaceName);
|
|
109
|
+
let patched;
|
|
110
|
+
if (existing.includes('## AI Agent Rules')) {
|
|
111
|
+
// Section already present — check its version marker.
|
|
112
|
+
const marker = existing.match(AGENT_RULES_MARKER_RE);
|
|
113
|
+
const embeddedVersion = marker ? parseInt(marker[1], 10) : 0;
|
|
114
|
+
if (embeddedVersion >= exports.AGENT_RULES_VERSION)
|
|
115
|
+
continue; // already current
|
|
116
|
+
// Replace the stale block (from its "## AI Agent Rules" heading up to,
|
|
117
|
+
// but not including, the next "## " heading or EOF).
|
|
118
|
+
// Match the heading and body up to the next "## " heading or EOF.
|
|
119
|
+
// Tolerate CRLF (\r\n) line endings so Windows-authored context files
|
|
120
|
+
// also get their stale block replaced.
|
|
121
|
+
const sectionRe = /## AI Agent Rules\r?\n[\s\S]*?(?=\r?\n## |$)/;
|
|
122
|
+
// buildAgentRulesSection ends with a trailing blank line; trim one so the
|
|
123
|
+
// spacing before the following heading stays consistent on replace.
|
|
124
|
+
patched = existing.replace(sectionRe, rulesBlock.replace(/\n+$/, '\n'));
|
|
125
|
+
if (patched === existing)
|
|
126
|
+
continue; // nothing changed (defensive)
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// Section missing — insert it before Tips, else before Notes, else append.
|
|
130
|
+
if (existing.includes('## Tips for Working with AI Agents')) {
|
|
131
|
+
patched = existing.replace('## Tips for Working with AI Agents', `${rulesBlock}## Tips for Working with AI Agents`);
|
|
132
|
+
}
|
|
133
|
+
else if (existing.includes('## Notes')) {
|
|
134
|
+
patched = existing.replace('## Notes', `${rulesBlock}## Notes`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
patched = existing + `\n${rulesBlock}`;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
await fs.writeFile(filePath, patched, 'utf-8');
|
|
141
|
+
updated++;
|
|
142
|
+
}
|
|
143
|
+
return updated;
|
|
144
|
+
}
|
|
145
|
+
async function loadClaudeConfig() {
|
|
146
|
+
const configFile = path.join(process.env.HOME || '~', '.workspace-manager-claude-config.json');
|
|
147
|
+
try {
|
|
148
|
+
const content = await fs.readFile(configFile, 'utf-8');
|
|
149
|
+
return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return DEFAULT_CONFIG;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Save Claude integration config
|
|
157
|
+
*/
|
|
158
|
+
async function saveClaudeConfig(config) {
|
|
159
|
+
const configFile = path.join(process.env.HOME || '~', '.workspace-manager-claude-config.json');
|
|
160
|
+
try {
|
|
161
|
+
await fs.writeFile(configFile, JSON.stringify(config, null, 2), 'utf-8');
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
(0, logger_1.logWarning)('Failed to save Claude config');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Generate context files (e.g. .claude.md, AGENTS.md) with workspace context.
|
|
169
|
+
* Writes to all context file names configured for active agents.
|
|
170
|
+
*/
|
|
171
|
+
async function generateClaudeContext(workspacePath, workspaceName, repos, metadata) {
|
|
172
|
+
try {
|
|
173
|
+
const contextFileNames = (0, agent_config_1.getContextFileNames)();
|
|
174
|
+
// Build a lookup from repo name to GitHubRepo for description/url
|
|
175
|
+
const repoInfoMap = new Map();
|
|
176
|
+
for (const r of repos) {
|
|
177
|
+
repoInfoMap.set(r.name, r);
|
|
178
|
+
}
|
|
179
|
+
const entries = [];
|
|
180
|
+
if (metadata && metadata.repositories.length > 0) {
|
|
181
|
+
for (const r of metadata.repositories.filter(r => r.status === 'success')) {
|
|
182
|
+
const info = repoInfoMap.get(r.name);
|
|
183
|
+
entries.push({
|
|
184
|
+
name: r.name,
|
|
185
|
+
directoryName: r.directoryName,
|
|
186
|
+
description: info?.description || '',
|
|
187
|
+
url: info?.url || r.cloneUrl,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
for (const r of repos) {
|
|
193
|
+
entries.push({
|
|
194
|
+
name: r.name,
|
|
195
|
+
directoryName: r.name,
|
|
196
|
+
description: r.description || '',
|
|
197
|
+
url: r.url,
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const { githubOrg } = (0, config_1.getUserConfig)();
|
|
202
|
+
let content = `# Workspace: ${workspaceName}
|
|
203
|
+
|
|
204
|
+
This workspace was created with [Workspace Manager](https://github.com/${githubOrg}/workspace-manager).
|
|
205
|
+
|
|
206
|
+
## Organization
|
|
207
|
+
|
|
208
|
+
All repositories in this workspace belong to the **${githubOrg}** GitHub organization (\`github.com/${githubOrg}\`). When searching for code, packages, or dependencies, assume the \`@${githubOrg}\` npm scope and the \`${githubOrg}/\` GitHub org prefix.
|
|
209
|
+
|
|
210
|
+
## Overview
|
|
211
|
+
|
|
212
|
+
This workspace contains ${entries.length} repositories for focused development work.
|
|
213
|
+
|
|
214
|
+
`;
|
|
215
|
+
// Add creation date if available
|
|
216
|
+
if (metadata?.createdAt) {
|
|
217
|
+
const createdDate = new Date(metadata.createdAt).toLocaleDateString('en-US', {
|
|
218
|
+
year: 'numeric',
|
|
219
|
+
month: 'long',
|
|
220
|
+
day: 'numeric',
|
|
221
|
+
});
|
|
222
|
+
content += `**Created:** ${createdDate}\n\n`;
|
|
223
|
+
}
|
|
224
|
+
// Add original prompt if available
|
|
225
|
+
if (metadata?.prompt) {
|
|
226
|
+
content += `**Original prompt:** ${metadata.prompt}\n\n`;
|
|
227
|
+
}
|
|
228
|
+
// Add repositories section
|
|
229
|
+
content += `## Repositories\n\n`;
|
|
230
|
+
// Group entries by prefix for display
|
|
231
|
+
const grouped = groupEntriesByPrefix(entries);
|
|
232
|
+
if (Object.keys(grouped).length > 1) {
|
|
233
|
+
for (const [prefix, groupEntries] of Object.entries(grouped)) {
|
|
234
|
+
if (prefix !== 'other') {
|
|
235
|
+
content += `### ${prefix.charAt(0).toUpperCase() + prefix.slice(1)} Services\n\n`;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
content += `### Other Repositories\n\n`;
|
|
239
|
+
}
|
|
240
|
+
for (const entry of groupEntries) {
|
|
241
|
+
const description = entry.description ? ` - ${entry.description}` : '';
|
|
242
|
+
const aliasNote = entry.directoryName !== entry.name ? ` (instance: ${entry.directoryName})` : '';
|
|
243
|
+
content += `- **${entry.name}**${aliasNote}${description}\n`;
|
|
244
|
+
content += ` - Path: \`${entry.directoryName}/\`\n`;
|
|
245
|
+
content += ` - GitHub: ${entry.url}\n\n`;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
for (const entry of entries) {
|
|
251
|
+
const description = entry.description ? ` - ${entry.description}` : '';
|
|
252
|
+
const aliasNote = entry.directoryName !== entry.name ? ` (instance: ${entry.directoryName})` : '';
|
|
253
|
+
content += `### ${entry.directoryName}${aliasNote}\n\n`;
|
|
254
|
+
content += `${description}\n\n`;
|
|
255
|
+
content += `- Path: \`${entry.directoryName}/\`\n`;
|
|
256
|
+
content += `- GitHub: ${entry.url}\n\n`;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
// Workspace structure will be added per-file (different context file names)
|
|
260
|
+
const allDirNames = entries.map(e => e.directoryName);
|
|
261
|
+
content += '{{WORKSPACE_STRUCTURE}}\n';
|
|
262
|
+
// Embed per-repo context files HERE — right after the workspace structure,
|
|
263
|
+
// before generic workflow/management boilerplate. LLMs weight earlier
|
|
264
|
+
// content more heavily, so per-repo rules need to be at the top of the
|
|
265
|
+
// file, not buried 80 lines down after generic tips.
|
|
266
|
+
const perRepoSection = await buildPerRepoContextSection(workspacePath, allDirNames);
|
|
267
|
+
if (perRepoSection) {
|
|
268
|
+
content += perRepoSection + '\n';
|
|
269
|
+
}
|
|
270
|
+
// Add common workflows
|
|
271
|
+
content += `## Common Workflows\n\n`;
|
|
272
|
+
content += `### Opening Specific Repository\n\n`;
|
|
273
|
+
content += '```bash\n';
|
|
274
|
+
content += `cd ${allDirNames[0] || '<repo-name>'}\n`;
|
|
275
|
+
content += '# Work on specific repository\n';
|
|
276
|
+
content += '```\n\n';
|
|
277
|
+
content += `### Running Commands Across All Repos\n\n`;
|
|
278
|
+
content += '```bash\n';
|
|
279
|
+
content += '# Example: Check git status for all repos\n';
|
|
280
|
+
content += 'for dir in */; do\n';
|
|
281
|
+
content += ' echo "=== $dir ===" && cd "$dir" && git status -s && cd ..\n';
|
|
282
|
+
content += 'done\n';
|
|
283
|
+
content += '```\n\n';
|
|
284
|
+
// Add workspace management commands
|
|
285
|
+
content += `## Workspace Management\n\n`;
|
|
286
|
+
content += `These commands help you manage this workspace:\n\n`;
|
|
287
|
+
content += '```bash\n';
|
|
288
|
+
content += 'workspace sync # Pull latest changes for all repos\n';
|
|
289
|
+
content += 'workspace switch-branch # Switch all repos to same branch\n';
|
|
290
|
+
content += 'workspace update # Add more repositories\n';
|
|
291
|
+
content += 'workspace suite create # Save repos as a reusable suite\n';
|
|
292
|
+
content += '```\n\n';
|
|
293
|
+
// Add explicit AI agent rules — prevent direct git clone
|
|
294
|
+
content += buildAgentRulesSection(workspaceName);
|
|
295
|
+
// Add tips section
|
|
296
|
+
content += `## Tips for Working with AI Agents\n\n`;
|
|
297
|
+
content += `- **Multi-repo context**: Your AI agent can see all repositories in this workspace\n`;
|
|
298
|
+
content += `- **Cross-repo changes**: Ask the agent to make changes across multiple repos\n`;
|
|
299
|
+
content += `- **Architecture questions**: The agent has context of your entire stack\n`;
|
|
300
|
+
content += `- **Workspace commands**: Use the workspace manager commands above\n\n`;
|
|
301
|
+
// Add notes section
|
|
302
|
+
content += `## Notes\n\n`;
|
|
303
|
+
content += `Add your own notes here:\n\n`;
|
|
304
|
+
content += `- \n\n`;
|
|
305
|
+
// Reference CONTEXT.md if it exists (saved progress from w save-context)
|
|
306
|
+
content += `## Saved Context\n\n`;
|
|
307
|
+
content += `If a \`CONTEXT.md\` file exists in this workspace, it contains saved progress and summaries from previous sessions. Read it for continuity after \`/clear\` or when resuming work.\n\n`;
|
|
308
|
+
// Write context file for each active agent (customize structure per-file)
|
|
309
|
+
for (const fileName of contextFileNames) {
|
|
310
|
+
// Build workspace structure block with correct filename
|
|
311
|
+
const padding = ' '.repeat(Math.max(1, 28 - fileName.length));
|
|
312
|
+
const structureBlock = '## Workspace Structure\n\n' +
|
|
313
|
+
'```\n' +
|
|
314
|
+
`${workspaceName}/\n` +
|
|
315
|
+
`├── ${fileName}${padding}# Workspace context\n` +
|
|
316
|
+
`├── .workspace-meta.json # Workspace metadata\n` +
|
|
317
|
+
`├── CONTEXT.md # Saved progress (if exists)\n` +
|
|
318
|
+
allDirNames.slice(0, 5).map(d => `├── ${d}/\n`).join('') +
|
|
319
|
+
(allDirNames.length > 5 ? `└── ... (${allDirNames.length - 5} more repositories)\n` : '') +
|
|
320
|
+
'```\n\n';
|
|
321
|
+
// Insert structure block at placeholder
|
|
322
|
+
let fileContent = content.replace('{{WORKSPACE_STRUCTURE}}\n', structureBlock);
|
|
323
|
+
const contextFile = path.join(workspacePath, fileName);
|
|
324
|
+
// Regeneration (e.g. after `nemus update`/`remove-repo`) overwrites the
|
|
325
|
+
// file, so carry over any operator-authored "## Notes" the user added
|
|
326
|
+
// since the last generation instead of silently discarding it.
|
|
327
|
+
fileContent = await preserveOperatorNotes(contextFile, fileContent);
|
|
328
|
+
await fs.writeFile(contextFile, fileContent, 'utf-8');
|
|
329
|
+
(0, logger_1.logSuccess)(`Generated ${(0, colors_1.colorize)(fileName, 'cyan')} with workspace context`);
|
|
330
|
+
}
|
|
331
|
+
// Generate .mcp.json with workspace-manager MCP server (if enabled)
|
|
332
|
+
const { installMcp } = (0, config_1.getUserConfig)();
|
|
333
|
+
if (installMcp) {
|
|
334
|
+
await generateMcpConfig(workspacePath);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
catch (error) {
|
|
338
|
+
(0, logger_1.logWarning)('Failed to generate context file(s)');
|
|
339
|
+
if (error instanceof Error) {
|
|
340
|
+
(0, logger_1.logWarning)(error.message);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Extract a top-level markdown section (heading + body up to the next `## `
|
|
346
|
+
* heading at the start of a line, or EOF). Returns null when the heading is
|
|
347
|
+
* absent. The heading must be a WHOLE top-level heading line — anchored to the
|
|
348
|
+
* start of a line and terminated by end-of-line — so a deeper heading like
|
|
349
|
+
* `### Notes`, inline prose, or fenced-code text that merely contains the
|
|
350
|
+
* string can't be mistaken for the real section.
|
|
351
|
+
*/
|
|
352
|
+
function extractSection(content, heading) {
|
|
353
|
+
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
354
|
+
const headingRe = new RegExp(`(^|\\n)${escaped}[ \\t]*(?:\\n|$)`);
|
|
355
|
+
const m = headingRe.exec(content);
|
|
356
|
+
if (!m)
|
|
357
|
+
return null;
|
|
358
|
+
const start = m.index + (m[1] ? m[1].length : 0); // position of the heading itself
|
|
359
|
+
const afterHeading = start + heading.length;
|
|
360
|
+
const nextIdx = content.indexOf('\n## ', afterHeading);
|
|
361
|
+
return nextIdx === -1 ? content.slice(start) : content.slice(start, nextIdx + 1);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* When regenerating a context file, carry over the operator-authored "## Notes"
|
|
365
|
+
* section from the existing file if the user filled it in. The freshly built
|
|
366
|
+
* content always contains an empty "## Notes" placeholder; if the existing file
|
|
367
|
+
* has a non-default Notes section, splice it into the new content so an add/
|
|
368
|
+
* remove-repo regeneration doesn't silently delete the operator's notes.
|
|
369
|
+
* Best-effort: on any read/parse issue we fall back to the new content.
|
|
370
|
+
*/
|
|
371
|
+
async function preserveOperatorNotes(contextFile, newContent) {
|
|
372
|
+
try {
|
|
373
|
+
const existing = await fs.readFile(contextFile, 'utf-8');
|
|
374
|
+
const existingNotes = extractSection(existing, '## Notes');
|
|
375
|
+
if (!existingNotes)
|
|
376
|
+
return newContent;
|
|
377
|
+
// Default placeholder the generator emits — treat as "no operator notes".
|
|
378
|
+
const isDefault = existingNotes.replace(/\s+/g, ' ').trim()
|
|
379
|
+
=== '## Notes Add your own notes here: -';
|
|
380
|
+
if (isDefault)
|
|
381
|
+
return newContent;
|
|
382
|
+
const newNotes = extractSection(newContent, '## Notes');
|
|
383
|
+
if (!newNotes)
|
|
384
|
+
return newContent;
|
|
385
|
+
// Preserve the trailing separator style of the section being replaced.
|
|
386
|
+
const tail = newNotes.endsWith('\n\n') ? '\n\n' : (newNotes.endsWith('\n') ? '\n' : '');
|
|
387
|
+
const replacement = existingNotes.replace(/\s*$/, '') + tail;
|
|
388
|
+
// Use a function replacer so `$&`, `$$`, `$1`, etc. in the operator's notes
|
|
389
|
+
// are inserted literally rather than interpreted by String.replace.
|
|
390
|
+
return newContent.replace(newNotes, () => replacement);
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
return newContent;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Resolve the MCP server.js path from the installed package.
|
|
398
|
+
* When compiled, __dirname is dist/utils/ — server.js is at dist/mcp/server.js.
|
|
399
|
+
*/
|
|
400
|
+
function resolveMcpServerPath() {
|
|
401
|
+
const candidates = [
|
|
402
|
+
path.join(__dirname, '..', 'mcp', 'server.js'), // dist/utils/ -> dist/mcp/server.js
|
|
403
|
+
path.join(__dirname, '..', '..', 'dist', 'mcp', 'server.js'), // src/utils/ -> dist/mcp/server.js
|
|
404
|
+
];
|
|
405
|
+
for (const p of candidates) {
|
|
406
|
+
if (fsSync.existsSync(p)) {
|
|
407
|
+
return path.resolve(p);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Generate .mcp.json with workspace-manager MCP server config.
|
|
414
|
+
* This ensures Claude Code sessions in the workspace have access to
|
|
415
|
+
* workspace-manager tools even without global MCP registration.
|
|
416
|
+
*/
|
|
417
|
+
async function generateMcpConfig(workspacePath) {
|
|
418
|
+
try {
|
|
419
|
+
const mcpFile = path.join(workspacePath, '.mcp.json');
|
|
420
|
+
const serverPath = resolveMcpServerPath();
|
|
421
|
+
if (!serverPath) {
|
|
422
|
+
(0, logger_1.logWarning)('Could not resolve MCP server path — skipping .mcp.json generation');
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
let existingConfig = {};
|
|
426
|
+
try {
|
|
427
|
+
const existingContent = await fs.readFile(mcpFile, 'utf-8');
|
|
428
|
+
existingConfig = JSON.parse(existingContent);
|
|
429
|
+
}
|
|
430
|
+
catch {
|
|
431
|
+
// No existing config or invalid JSON — start fresh
|
|
432
|
+
}
|
|
433
|
+
const mcpConfig = {
|
|
434
|
+
...existingConfig,
|
|
435
|
+
mcpServers: {
|
|
436
|
+
...(existingConfig.mcpServers || {}),
|
|
437
|
+
'workspace-manager': {
|
|
438
|
+
command: 'node',
|
|
439
|
+
args: [serverPath],
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
await fs.writeFile(mcpFile, JSON.stringify(mcpConfig, null, 2) + '\n', 'utf-8');
|
|
444
|
+
(0, logger_1.logSuccess)(`Generated ${(0, colors_1.colorize)('.mcp.json', 'cyan')} with workspace-manager MCP server`);
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
(0, logger_1.logWarning)('Failed to generate .mcp.json file');
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const KNOWN_PREFIXES = ['api', 'service', 'lib', 'tool', 'app', 'web', 'mobile', 'backend', 'frontend'];
|
|
453
|
+
/**
|
|
454
|
+
* How many directory levels below the repo root to scan for context files.
|
|
455
|
+
* 0 = repo root only (e.g. repo/AGENTS.md)
|
|
456
|
+
* 1 = one level deep (e.g. repo/packages/AGENTS.md)
|
|
457
|
+
* 2 = two levels deep (e.g. repo/packages/ui/AGENTS.md)
|
|
458
|
+
* Keep this low — deeper scans grow quickly and context files are rarely nested.
|
|
459
|
+
*/
|
|
460
|
+
const PER_REPO_MAX_DEPTH = 2;
|
|
461
|
+
/**
|
|
462
|
+
* Directories that are never useful to scan for context files.
|
|
463
|
+
*/
|
|
464
|
+
const SCAN_SKIP_DIRS = new Set([
|
|
465
|
+
'node_modules', '.git', 'dist', 'build', '.next', '.nuxt',
|
|
466
|
+
'coverage', '__pycache__', '.turbo', '.cache', 'out', 'tmp', '.tmp',
|
|
467
|
+
]);
|
|
468
|
+
/**
|
|
469
|
+
* Recursively find context files within a directory, up to maxDepth levels
|
|
470
|
+
* below the starting point. Skips SCAN_SKIP_DIRS to avoid scanning build
|
|
471
|
+
* artifacts, dependencies, or git internals.
|
|
472
|
+
*
|
|
473
|
+
* @param dirPath Absolute path of the directory to scan.
|
|
474
|
+
* @param candidates File names to look for (from getAllKnownContextFileNames).
|
|
475
|
+
* @param repoRoot Absolute path of the repo root (used to build relative labels).
|
|
476
|
+
* @param maxDepth Maximum levels to descend from dirPath.
|
|
477
|
+
* @param depth Current recursion depth (0 = dirPath itself).
|
|
478
|
+
*/
|
|
479
|
+
async function findContextFilesInDir(dirPath, candidates, repoRoot, maxDepth, depth = 0) {
|
|
480
|
+
const results = [];
|
|
481
|
+
// Check for a context file at this level (first candidate that exists wins).
|
|
482
|
+
for (const fileName of candidates) {
|
|
483
|
+
let content;
|
|
484
|
+
try {
|
|
485
|
+
content = await fs.readFile(path.join(dirPath, fileName), 'utf-8');
|
|
486
|
+
}
|
|
487
|
+
catch {
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
if (!content?.trim())
|
|
491
|
+
continue;
|
|
492
|
+
const relPath = path.relative(repoRoot, dirPath) || '.';
|
|
493
|
+
results.push({ relPath, fileName, content: content.trim() });
|
|
494
|
+
break; // one file per directory level
|
|
495
|
+
}
|
|
496
|
+
// Recurse into subdirectories if we haven't hit the depth limit.
|
|
497
|
+
if (depth < maxDepth) {
|
|
498
|
+
let entries;
|
|
499
|
+
try {
|
|
500
|
+
entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
501
|
+
}
|
|
502
|
+
catch {
|
|
503
|
+
return results;
|
|
504
|
+
}
|
|
505
|
+
for (const entry of entries) {
|
|
506
|
+
if (!entry.isDirectory() || SCAN_SKIP_DIRS.has(entry.name))
|
|
507
|
+
continue;
|
|
508
|
+
const sub = await findContextFilesInDir(path.join(dirPath, entry.name), candidates, repoRoot, maxDepth, depth + 1);
|
|
509
|
+
results.push(...sub);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return results;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Scan each repo subdirectory for context files (AGENTS.md / .claude.md / …)
|
|
516
|
+
* and embed the contents inline in the workspace-level context file.
|
|
517
|
+
* Covers ALL known agents (not just the configured one) because individual
|
|
518
|
+
* repos may have been set up with a different agent.
|
|
519
|
+
*
|
|
520
|
+
* Scans up to PER_REPO_MAX_DEPTH levels inside each repo so monorepo
|
|
521
|
+
* packages are included, while still having a stop condition that prevents
|
|
522
|
+
* runaway recursion into large dependency trees.
|
|
523
|
+
*
|
|
524
|
+
* Returns a markdown section string, or null when no per-repo files exist.
|
|
525
|
+
*/
|
|
526
|
+
async function buildPerRepoContextSection(workspacePath, dirNames) {
|
|
527
|
+
const candidates = (0, agent_config_1.getAllKnownContextFileNames)();
|
|
528
|
+
const sections = [];
|
|
529
|
+
for (const dir of dirNames) {
|
|
530
|
+
const repoRoot = path.join(workspacePath, dir);
|
|
531
|
+
const found = await findContextFilesInDir(repoRoot, candidates, repoRoot, PER_REPO_MAX_DEPTH);
|
|
532
|
+
for (const { relPath, fileName, content } of found) {
|
|
533
|
+
const label = relPath === '.' ? dir : `${dir}/${relPath}`;
|
|
534
|
+
// Demote heading levels by 3 so the repo's content nests properly
|
|
535
|
+
// under '### `repo/` ...' — prevents repo's '## Architecture' from
|
|
536
|
+
// colliding with workspace headings like '## Notes'.
|
|
537
|
+
// Caps at h6 (markdown’s deepest level).
|
|
538
|
+
// Critically, only touch lines OUTSIDE fenced code blocks so that
|
|
539
|
+
// shell comments like '# Build the project' inside ```bash blocks
|
|
540
|
+
// are not corrupted.
|
|
541
|
+
const demoted = (() => {
|
|
542
|
+
const out = [];
|
|
543
|
+
let inFence = false;
|
|
544
|
+
let fenceMarker = '';
|
|
545
|
+
for (const line of content.split('\n')) {
|
|
546
|
+
const fenceMatch = line.match(/^(```+|~~~+)/);
|
|
547
|
+
if (fenceMatch) {
|
|
548
|
+
if (!inFence) {
|
|
549
|
+
inFence = true;
|
|
550
|
+
fenceMarker = fenceMatch[1];
|
|
551
|
+
}
|
|
552
|
+
else if (line.startsWith(fenceMarker)) {
|
|
553
|
+
inFence = false;
|
|
554
|
+
fenceMarker = '';
|
|
555
|
+
}
|
|
556
|
+
out.push(line);
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
if (!inFence) {
|
|
560
|
+
const headingMatch = line.match(/^(#{1,6})(\s|$)/);
|
|
561
|
+
if (headingMatch) {
|
|
562
|
+
const newLevel = Math.min(6, headingMatch[1].length + 3);
|
|
563
|
+
out.push('#'.repeat(newLevel) + line.slice(headingMatch[1].length));
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
out.push(line);
|
|
568
|
+
}
|
|
569
|
+
return out.join('\n');
|
|
570
|
+
})();
|
|
571
|
+
sections.push(`### \`${label}/\` — repo-specific rules (${fileName})\n\n` +
|
|
572
|
+
`**You MUST follow these rules whenever you work with files inside ` +
|
|
573
|
+
`\`${label}/\`. They override generic workspace guidance for that repo.**\n\n` +
|
|
574
|
+
demoted.trim() + '\n\n---\n');
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (sections.length === 0)
|
|
578
|
+
return null;
|
|
579
|
+
return (`## Per-Repository Context\n\n` +
|
|
580
|
+
`Each repository in this workspace below has its own \`AGENTS.md\` / ` +
|
|
581
|
+
`\`CLAUDE.md\` file. Their contents are embedded verbatim here.\n\n` +
|
|
582
|
+
`**When you work on files inside a specific repo subdirectory, the rules ` +
|
|
583
|
+
`in that repo’s section are AUTHORITATIVE for that repo — follow them ` +
|
|
584
|
+
`literally. They take precedence over generic workspace-level guidance.**\n\n` +
|
|
585
|
+
`---\n\n` +
|
|
586
|
+
sections.join('\n'));
|
|
587
|
+
}
|
|
588
|
+
function getPrefixForName(name) {
|
|
589
|
+
const parts = name.split('-');
|
|
590
|
+
if (parts.length > 1 && KNOWN_PREFIXES.includes(parts[0].toLowerCase())) {
|
|
591
|
+
return parts[0].toLowerCase();
|
|
592
|
+
}
|
|
593
|
+
return 'other';
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Group repositories by common prefix
|
|
597
|
+
*/
|
|
598
|
+
function groupReposByPrefix(repos) {
|
|
599
|
+
const groups = {};
|
|
600
|
+
for (const repo of repos) {
|
|
601
|
+
const prefix = getPrefixForName(repo.name);
|
|
602
|
+
if (!groups[prefix])
|
|
603
|
+
groups[prefix] = [];
|
|
604
|
+
groups[prefix].push(repo);
|
|
605
|
+
}
|
|
606
|
+
return groups;
|
|
607
|
+
}
|
|
608
|
+
function groupEntriesByPrefix(entries) {
|
|
609
|
+
const groups = {};
|
|
610
|
+
for (const entry of entries) {
|
|
611
|
+
const prefix = getPrefixForName(entry.name);
|
|
612
|
+
if (!groups[prefix])
|
|
613
|
+
groups[prefix] = [];
|
|
614
|
+
groups[prefix].push(entry);
|
|
615
|
+
}
|
|
616
|
+
return groups;
|
|
617
|
+
}
|