@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.
Files changed (320) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/LICENSE +21 -0
  3. package/README.md +275 -0
  4. package/assets/README.md +53 -0
  5. package/assets/banner.png +0 -0
  6. package/assets/banner.svg +66 -0
  7. package/assets/favicon-32.png +0 -0
  8. package/assets/favicon-64.png +0 -0
  9. package/assets/icon-256.png +0 -0
  10. package/assets/icon.svg +23 -0
  11. package/assets/logo-256.png +0 -0
  12. package/assets/logo-512.png +0 -0
  13. package/assets/logo-wordmark.svg +35 -0
  14. package/assets/logo.svg +32 -0
  15. package/bin/workspace.js +130 -0
  16. package/dist/cli/ai-prompt.js +421 -0
  17. package/dist/cli/dashboard/AgentList.js +61 -0
  18. package/dist/cli/dashboard/DashboardSidebar.js +280 -0
  19. package/dist/cli/dashboard/HelpFooter.js +73 -0
  20. package/dist/cli/dashboard/StatusBadge.js +50 -0
  21. package/dist/cli/dashboard/index.js +11 -0
  22. package/dist/cli/dashboard/types.js +2 -0
  23. package/dist/cli/tui.js +48 -0
  24. package/dist/commands/ai-prompt.js +22 -0
  25. package/dist/commands/analyze-deps.js +138 -0
  26. package/dist/commands/archive.js +113 -0
  27. package/dist/commands/branch/create.js +119 -0
  28. package/dist/commands/branch/index.js +104 -0
  29. package/dist/commands/branch/merge.js +90 -0
  30. package/dist/commands/branch/rebase.js +84 -0
  31. package/dist/commands/branch/switch.js +165 -0
  32. package/dist/commands/cache/index.js +80 -0
  33. package/dist/commands/cache/manager.js +198 -0
  34. package/dist/commands/cleanup.js +153 -0
  35. package/dist/commands/configure-claude.js +55 -0
  36. package/dist/commands/configure.js +231 -0
  37. package/dist/commands/create.js +204 -0
  38. package/dist/commands/dashboard/hook-handler.js +162 -0
  39. package/dist/commands/dashboard/index.js +258 -0
  40. package/dist/commands/dashboard/launcher.js +40 -0
  41. package/dist/commands/dashboard/session-picker.js +106 -0
  42. package/dist/commands/dashboard/sidebar.js +39 -0
  43. package/dist/commands/dashboard/workspace-picker.js +95 -0
  44. package/dist/commands/delete.js +194 -0
  45. package/dist/commands/deprecated-aliases.js +44 -0
  46. package/dist/commands/diff.js +129 -0
  47. package/dist/commands/doctor.js +141 -0
  48. package/dist/commands/generate-docs.js +77 -0
  49. package/dist/commands/ghq-status.js +72 -0
  50. package/dist/commands/go.js +154 -0
  51. package/dist/commands/history.js +124 -0
  52. package/dist/commands/list.js +191 -0
  53. package/dist/commands/mcp/index.js +69 -0
  54. package/dist/commands/migrate.js +296 -0
  55. package/dist/commands/remove-repo.js +217 -0
  56. package/dist/commands/report-bug.js +112 -0
  57. package/dist/commands/run.js +147 -0
  58. package/dist/commands/save-context.js +171 -0
  59. package/dist/commands/sessions.js +129 -0
  60. package/dist/commands/status.js +138 -0
  61. package/dist/commands/suite/create.js +212 -0
  62. package/dist/commands/suite/delete.js +64 -0
  63. package/dist/commands/suite/export.js +126 -0
  64. package/dist/commands/suite/import.js +128 -0
  65. package/dist/commands/suite/index.js +94 -0
  66. package/dist/commands/suite/list.js +49 -0
  67. package/dist/commands/suite/use.js +227 -0
  68. package/dist/commands/sync.js +117 -0
  69. package/dist/commands/update.js +203 -0
  70. package/dist/mcp/install.js +550 -0
  71. package/dist/mcp/server.js +398 -0
  72. package/dist/mcp/tools.js +922 -0
  73. package/dist/pi-extensions/permission-sync.ts.template +41 -0
  74. package/dist/pi-extensions/permissions-gate.ts.template +69 -0
  75. package/dist/pi-extensions/pr-fix-trigger.ts.template +124 -0
  76. package/dist/pi-extensions/review-reminder.ts.template +58 -0
  77. package/dist/pi-extensions/workspace-input-status.ts.template +919 -0
  78. package/dist/program.js +136 -0
  79. package/dist/scripts/workspace-table.py +394 -0
  80. package/dist/types/dashboard.js +11 -0
  81. package/dist/types/index.js +2 -0
  82. package/dist/utils/agent-config.js +263 -0
  83. package/dist/utils/agent-state.js +178 -0
  84. package/dist/utils/banner.js +24 -0
  85. package/dist/utils/branch-operations.js +232 -0
  86. package/dist/utils/bug-report.js +355 -0
  87. package/dist/utils/cache.js +168 -0
  88. package/dist/utils/claude-integration.js +617 -0
  89. package/dist/utils/claude-sessions.js +260 -0
  90. package/dist/utils/cleanup-operations.js +120 -0
  91. package/dist/utils/colors.js +26 -0
  92. package/dist/utils/command-helpers.js +44 -0
  93. package/dist/utils/config.js +150 -0
  94. package/dist/utils/context-file.js +33 -0
  95. package/dist/utils/dashboard-hooks.js +172 -0
  96. package/dist/utils/dependency-analyzer.js +234 -0
  97. package/dist/utils/diff-operations.js +80 -0
  98. package/dist/utils/doc-generator.js +184 -0
  99. package/dist/utils/ghq-integration.js +332 -0
  100. package/dist/utils/git-operations.js +237 -0
  101. package/dist/utils/git-status.js +187 -0
  102. package/dist/utils/github.js +68 -0
  103. package/dist/utils/health-checks.js +345 -0
  104. package/dist/utils/history.js +150 -0
  105. package/dist/utils/hooks.js +100 -0
  106. package/dist/utils/logger.js +40 -0
  107. package/dist/utils/permission-sync.js +681 -0
  108. package/dist/utils/pi-extensions.js +224 -0
  109. package/dist/utils/progress.js +92 -0
  110. package/dist/utils/prompts.js +279 -0
  111. package/dist/utils/repo-resolver.js +260 -0
  112. package/dist/utils/retry.js +65 -0
  113. package/dist/utils/run-operations.js +30 -0
  114. package/dist/utils/suite.js +159 -0
  115. package/dist/utils/sync-operations.js +107 -0
  116. package/dist/utils/tmux-dashboard.js +184 -0
  117. package/dist/utils/validation.js +153 -0
  118. package/dist/utils/version-check.js +121 -0
  119. package/dist/utils/workspace-meta.js +185 -0
  120. package/install-shell-integration.sh +532 -0
  121. package/install.sh +401 -0
  122. package/package.json +90 -0
  123. package/remove-shell-block.awk +44 -0
  124. package/scripts/postinstall.js +85 -0
  125. package/skills/analyze-deps.md +23 -0
  126. package/skills/archive-workspace.md +26 -0
  127. package/skills/branch-create.md +18 -0
  128. package/skills/create-workspace.md +39 -0
  129. package/skills/delete-workspace.md +37 -0
  130. package/skills/list-org-repos.md +26 -0
  131. package/skills/list-suites.md +24 -0
  132. package/skills/list-workspaces.md +27 -0
  133. package/skills/refresh-workspace-docs.md +89 -0
  134. package/skills/remove-repo.md +25 -0
  135. package/skills/run-command.md +23 -0
  136. package/skills/save-context.md +68 -0
  137. package/skills/search-repos.md +31 -0
  138. package/skills/snapshot-list.md +18 -0
  139. package/skills/snapshot-save.md +12 -0
  140. package/skills/switch-branch.md +18 -0
  141. package/skills/update-cache.md +21 -0
  142. package/skills/update-workspace.md +66 -0
  143. package/skills/workspace-cleanup.md +18 -0
  144. package/skills/workspace-diff.md +23 -0
  145. package/skills/workspace-doctor.md +23 -0
  146. package/skills/workspace-info.md +45 -0
  147. package/skills/workspace-manager/SKILL.md +201 -0
  148. package/skills/workspace-manager/references/ai-prompt.md +44 -0
  149. package/skills/workspace-manager/references/analyze-deps.md +45 -0
  150. package/skills/workspace-manager/references/archive-workspace.md +38 -0
  151. package/skills/workspace-manager/references/branch-create.md +31 -0
  152. package/skills/workspace-manager/references/branch-merge.md +22 -0
  153. package/skills/workspace-manager/references/branch-rebase.md +22 -0
  154. package/skills/workspace-manager/references/cache-clear.md +18 -0
  155. package/skills/workspace-manager/references/cache-info.md +22 -0
  156. package/skills/workspace-manager/references/configure-claude.md +31 -0
  157. package/skills/workspace-manager/references/configure.md +35 -0
  158. package/skills/workspace-manager/references/create-workspace.md +45 -0
  159. package/skills/workspace-manager/references/dashboard.md +61 -0
  160. package/skills/workspace-manager/references/delete-workspace.md +33 -0
  161. package/skills/workspace-manager/references/generate-docs.md +36 -0
  162. package/skills/workspace-manager/references/ghq-status.md +28 -0
  163. package/skills/workspace-manager/references/go.md +33 -0
  164. package/skills/workspace-manager/references/history.md +41 -0
  165. package/skills/workspace-manager/references/list-org-repos.md +26 -0
  166. package/skills/workspace-manager/references/list-suites.md +21 -0
  167. package/skills/workspace-manager/references/list-workspaces.md +27 -0
  168. package/skills/workspace-manager/references/mcp.md +49 -0
  169. package/skills/workspace-manager/references/remove-repo.md +26 -0
  170. package/skills/workspace-manager/references/run-command.md +44 -0
  171. package/skills/workspace-manager/references/search-repos.md +24 -0
  172. package/skills/workspace-manager/references/sessions.md +27 -0
  173. package/skills/workspace-manager/references/suite-create.md +22 -0
  174. package/skills/workspace-manager/references/suite-delete.md +21 -0
  175. package/skills/workspace-manager/references/suite-export.md +19 -0
  176. package/skills/workspace-manager/references/suite-import.md +19 -0
  177. package/skills/workspace-manager/references/suite-use.md +27 -0
  178. package/skills/workspace-manager/references/switch-branch.md +25 -0
  179. package/skills/workspace-manager/references/update-cache.md +22 -0
  180. package/skills/workspace-manager/references/update-workspace.md +50 -0
  181. package/skills/workspace-manager/references/workspace-cleanup.md +33 -0
  182. package/skills/workspace-manager/references/workspace-diff.md +27 -0
  183. package/skills/workspace-manager/references/workspace-doctor.md +27 -0
  184. package/skills/workspace-manager/references/workspace-info.md +33 -0
  185. package/skills/workspace-manager/references/workspace-status.md +43 -0
  186. package/skills/workspace-manager/references/workspace-sync.md +26 -0
  187. package/skills/workspace-status.md +26 -0
  188. package/skills/workspace-sync.md +23 -0
  189. package/src/cli/ai-prompt.test.ts +667 -0
  190. package/src/cli/ai-prompt.ts +421 -0
  191. package/src/cli/dashboard/AgentList.tsx +44 -0
  192. package/src/cli/dashboard/DashboardSidebar.tsx +291 -0
  193. package/src/cli/dashboard/HelpFooter.tsx +22 -0
  194. package/src/cli/dashboard/StatusBadge.tsx +23 -0
  195. package/src/cli/dashboard/index.ts +5 -0
  196. package/src/cli/dashboard/types.ts +16 -0
  197. package/src/cli/tui.tsx +51 -0
  198. package/src/cli/workspace-cli.test.ts +30 -0
  199. package/src/commands/ai-prompt.ts +21 -0
  200. package/src/commands/analyze-deps.ts +119 -0
  201. package/src/commands/archive.ts +114 -0
  202. package/src/commands/branch/create.ts +94 -0
  203. package/src/commands/branch/index.ts +74 -0
  204. package/src/commands/branch/merge.ts +61 -0
  205. package/src/commands/branch/rebase.ts +54 -0
  206. package/src/commands/branch/switch.ts +150 -0
  207. package/src/commands/cache/index.ts +51 -0
  208. package/src/commands/cache/manager.ts +168 -0
  209. package/src/commands/cleanup.ts +132 -0
  210. package/src/commands/configure-claude.ts +55 -0
  211. package/src/commands/configure.ts +204 -0
  212. package/src/commands/create.ts +196 -0
  213. package/src/commands/dashboard/hook-handler.ts +138 -0
  214. package/src/commands/dashboard/index.ts +248 -0
  215. package/src/commands/dashboard/launcher.ts +42 -0
  216. package/src/commands/dashboard/session-picker.ts +73 -0
  217. package/src/commands/dashboard/sidebar.js +39 -0
  218. package/src/commands/dashboard/workspace-picker.ts +62 -0
  219. package/src/commands/delete.test.ts +250 -0
  220. package/src/commands/delete.ts +171 -0
  221. package/src/commands/deprecated-aliases.ts +55 -0
  222. package/src/commands/diff.ts +106 -0
  223. package/src/commands/doctor.ts +110 -0
  224. package/src/commands/generate-docs.ts +45 -0
  225. package/src/commands/ghq-status.ts +70 -0
  226. package/src/commands/go.ts +125 -0
  227. package/src/commands/history.ts +136 -0
  228. package/src/commands/list.test.ts +219 -0
  229. package/src/commands/list.ts +176 -0
  230. package/src/commands/mcp/index.ts +39 -0
  231. package/src/commands/migrate.ts +277 -0
  232. package/src/commands/remove-repo.ts +194 -0
  233. package/src/commands/report-bug.ts +88 -0
  234. package/src/commands/run.ts +125 -0
  235. package/src/commands/save-context.test.ts +113 -0
  236. package/src/commands/save-context.ts +141 -0
  237. package/src/commands/sessions.ts +99 -0
  238. package/src/commands/status.ts +111 -0
  239. package/src/commands/suite/create.ts +240 -0
  240. package/src/commands/suite/delete.ts +65 -0
  241. package/src/commands/suite/export.ts +97 -0
  242. package/src/commands/suite/import.ts +100 -0
  243. package/src/commands/suite/index.ts +66 -0
  244. package/src/commands/suite/list.ts +54 -0
  245. package/src/commands/suite/use.ts +232 -0
  246. package/src/commands/sync.ts +97 -0
  247. package/src/commands/update.ts +197 -0
  248. package/src/mcp/install.ts +529 -0
  249. package/src/mcp/server.ts +579 -0
  250. package/src/mcp/tools.test.ts +545 -0
  251. package/src/mcp/tools.ts +1019 -0
  252. package/src/mcp/update-workspace.test.ts +220 -0
  253. package/src/pi-extensions/permission-sync.ts.template +41 -0
  254. package/src/pi-extensions/permissions-gate.ts.template +69 -0
  255. package/src/pi-extensions/pr-fix-trigger.ts.template +124 -0
  256. package/src/pi-extensions/review-reminder.ts.template +58 -0
  257. package/src/pi-extensions/workspace-input-status.ts.template +919 -0
  258. package/src/program.ts +112 -0
  259. package/src/scripts/workspace-table.py +394 -0
  260. package/src/types/dashboard.ts +29 -0
  261. package/src/types/index.ts +109 -0
  262. package/src/types/ink.d.ts +171 -0
  263. package/src/utils/agent-config.test.ts +303 -0
  264. package/src/utils/agent-config.ts +264 -0
  265. package/src/utils/agent-state.test.ts +166 -0
  266. package/src/utils/agent-state.ts +135 -0
  267. package/src/utils/banner.ts +24 -0
  268. package/src/utils/branch-operations.ts +237 -0
  269. package/src/utils/bug-report.test.ts +174 -0
  270. package/src/utils/bug-report.ts +355 -0
  271. package/src/utils/cache.ts +150 -0
  272. package/src/utils/claude-integration.test.ts +381 -0
  273. package/src/utils/claude-integration.ts +649 -0
  274. package/src/utils/claude-sessions.test.ts +262 -0
  275. package/src/utils/claude-sessions.ts +251 -0
  276. package/src/utils/cleanup-operations.ts +94 -0
  277. package/src/utils/colors.ts +25 -0
  278. package/src/utils/command-helpers.ts +46 -0
  279. package/src/utils/config.test.ts +203 -0
  280. package/src/utils/config.ts +133 -0
  281. package/src/utils/context-file.ts +36 -0
  282. package/src/utils/dashboard-hooks.test.ts +168 -0
  283. package/src/utils/dashboard-hooks.ts +163 -0
  284. package/src/utils/dependency-analyzer.ts +243 -0
  285. package/src/utils/diff-operations.ts +59 -0
  286. package/src/utils/doc-generator.ts +187 -0
  287. package/src/utils/ghq-integration.test.ts +195 -0
  288. package/src/utils/ghq-integration.ts +317 -0
  289. package/src/utils/git-operations.test.ts +265 -0
  290. package/src/utils/git-operations.ts +233 -0
  291. package/src/utils/git-status.ts +171 -0
  292. package/src/utils/github.ts +74 -0
  293. package/src/utils/health-checks.ts +359 -0
  294. package/src/utils/history.ts +140 -0
  295. package/src/utils/hooks.ts +92 -0
  296. package/src/utils/logger.ts +37 -0
  297. package/src/utils/permission-sync.test.ts +593 -0
  298. package/src/utils/permission-sync.ts +720 -0
  299. package/src/utils/pi-extensions.test.ts +182 -0
  300. package/src/utils/pi-extensions.ts +226 -0
  301. package/src/utils/progress.ts +73 -0
  302. package/src/utils/prompts.test.ts +169 -0
  303. package/src/utils/prompts.ts +296 -0
  304. package/src/utils/repo-resolver.test.ts +313 -0
  305. package/src/utils/repo-resolver.ts +294 -0
  306. package/src/utils/retry.test.ts +124 -0
  307. package/src/utils/retry.ts +85 -0
  308. package/src/utils/run-operations.ts +38 -0
  309. package/src/utils/suite.test.ts +256 -0
  310. package/src/utils/suite.ts +144 -0
  311. package/src/utils/sync-operations.ts +87 -0
  312. package/src/utils/tmux-dashboard.ts +193 -0
  313. package/src/utils/validation.test.ts +139 -0
  314. package/src/utils/validation.ts +128 -0
  315. package/src/utils/version-check.test.ts +108 -0
  316. package/src/utils/version-check.ts +96 -0
  317. package/src/utils/workspace-meta.ts +173 -0
  318. package/sync-permissions.sh +137 -0
  319. package/tsconfig.json +20 -0
  320. 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
+ }