@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,163 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ import { HOOK_MARKER } from '../types/dashboard';
5
+
6
+ const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
7
+
8
+ /** Events we register hooks for */
9
+ const HOOK_EVENTS = ['SessionStart', 'PreToolUse', 'PostToolUse', 'UserPromptSubmit', 'Notification', 'Stop', 'SessionEnd'] as const;
10
+
11
+ interface HookEntry {
12
+ type: string;
13
+ command: string;
14
+ timeout?: number;
15
+ }
16
+
17
+ interface HookGroup {
18
+ matcher?: string;
19
+ hooks: HookEntry[];
20
+ }
21
+
22
+ interface ClaudeSettings {
23
+ hooks?: Record<string, HookGroup[]>;
24
+ [key: string]: any;
25
+ }
26
+
27
+ /**
28
+ * Resolve path to the compiled hook-handler.js.
29
+ * This file (dashboard-hooks.ts) compiles to dist/utils/dashboard-hooks.js,
30
+ * so hook-handler.js is at ../commands/dashboard/hook-handler.js relative to dist/utils/.
31
+ */
32
+ function getHookHandlerPath(): string {
33
+ return path.join(__dirname, '..', 'commands', 'dashboard', 'hook-handler.js');
34
+ }
35
+
36
+ /**
37
+ * Build the hook command string for a given event.
38
+ */
39
+ function buildHookCommand(event: string): string {
40
+ const handlerPath = getHookHandlerPath();
41
+ return `node "${handlerPath}" --event ${event} --marker ${HOOK_MARKER}`;
42
+ }
43
+
44
+ /**
45
+ * Read Claude settings from disk. Returns empty settings if file doesn't exist.
46
+ */
47
+ function readClaudeSettings(): ClaudeSettings {
48
+ try {
49
+ const content = fs.readFileSync(CLAUDE_SETTINGS_PATH, 'utf-8');
50
+ return JSON.parse(content);
51
+ } catch {
52
+ return {};
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Write Claude settings to disk atomically (write to .tmp, then rename).
58
+ * Prevents corruption if the process is interrupted mid-write.
59
+ */
60
+ function writeClaudeSettings(settings: ClaudeSettings): void {
61
+ const dir = path.dirname(CLAUDE_SETTINGS_PATH);
62
+ fs.mkdirSync(dir, { recursive: true });
63
+ const tmp = CLAUDE_SETTINGS_PATH + '.tmp';
64
+ try {
65
+ fs.writeFileSync(tmp, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
66
+ fs.renameSync(tmp, CLAUDE_SETTINGS_PATH);
67
+ } catch (err) {
68
+ try { fs.unlinkSync(tmp); } catch { /* ignore */ }
69
+ throw err;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Check if a hook group contains our dashboard hook marker.
75
+ */
76
+ function groupContainsMarker(group: HookGroup): boolean {
77
+ return group.hooks.some(h => h.command && h.command.includes(HOOK_MARKER));
78
+ }
79
+
80
+ /**
81
+ * Install dashboard hooks into Claude settings.
82
+ * Appends new hook groups — never modifies existing hooks.
83
+ * Idempotent: skips events that already have the marker.
84
+ */
85
+ export function installDashboardHooks(): void {
86
+ const settings = readClaudeSettings();
87
+
88
+ if (!settings.hooks) {
89
+ settings.hooks = {};
90
+ }
91
+
92
+ for (const event of HOOK_EVENTS) {
93
+ if (!settings.hooks[event]) {
94
+ settings.hooks[event] = [];
95
+ }
96
+
97
+ // Check if already installed
98
+ const alreadyInstalled = settings.hooks[event].some(groupContainsMarker);
99
+ if (alreadyInstalled) {
100
+ continue;
101
+ }
102
+
103
+ // Append a new hook group
104
+ const hookGroup: HookGroup = {
105
+ hooks: [
106
+ {
107
+ type: 'command',
108
+ command: buildHookCommand(event),
109
+ timeout: 5,
110
+ },
111
+ ],
112
+ };
113
+
114
+ // PreToolUse and PostToolUse hooks use a matcher — use empty string to match all tools
115
+ if (event === 'PreToolUse' || event === 'PostToolUse') {
116
+ hookGroup.matcher = '';
117
+ }
118
+
119
+ settings.hooks[event].push(hookGroup);
120
+ }
121
+
122
+ writeClaudeSettings(settings);
123
+ }
124
+
125
+ /**
126
+ * Check if all dashboard hooks are installed.
127
+ */
128
+ export function areDashboardHooksInstalled(): boolean {
129
+ const settings = readClaudeSettings();
130
+ if (!settings.hooks) return false;
131
+
132
+ return HOOK_EVENTS.every(event => {
133
+ const groups = settings.hooks?.[event];
134
+ if (!groups || !Array.isArray(groups)) return false;
135
+ return groups.some(groupContainsMarker);
136
+ });
137
+ }
138
+
139
+ /**
140
+ * Remove all dashboard hooks from Claude settings.
141
+ * Only removes hook groups containing the HOOK_MARKER — leaves everything else untouched.
142
+ */
143
+ export function uninstallDashboardHooks(): void {
144
+ const settings = readClaudeSettings();
145
+ if (!settings.hooks) return;
146
+
147
+ for (const event of HOOK_EVENTS) {
148
+ if (!settings.hooks[event]) continue;
149
+ settings.hooks[event] = settings.hooks[event].filter(g => !groupContainsMarker(g));
150
+
151
+ // Clean up empty arrays
152
+ if (settings.hooks[event].length === 0) {
153
+ delete settings.hooks[event];
154
+ }
155
+ }
156
+
157
+ // Clean up empty hooks object
158
+ if (Object.keys(settings.hooks).length === 0) {
159
+ delete settings.hooks;
160
+ }
161
+
162
+ writeClaudeSettings(settings);
163
+ }
@@ -0,0 +1,243 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { WorkspaceMetadata, DependencyInfo } from '../types';
4
+ import { getUserConfig } from './config';
5
+
6
+ export interface DependencyAnalysis {
7
+ repoName: string;
8
+ dependencies: string[];
9
+ dependents: string[];
10
+ missingDependencies: string[];
11
+ }
12
+
13
+ export const analyzePackageJson = async (repoPath: string): Promise<string[]> => {
14
+ try {
15
+ const packageJsonPath = path.join(repoPath, 'package.json');
16
+ const content = await fs.readFile(packageJsonPath, 'utf-8');
17
+ const packageJson = JSON.parse(content);
18
+
19
+ const dependencies: string[] = [];
20
+
21
+ // Check dependencies and devDependencies
22
+ const allDeps = {
23
+ ...packageJson.dependencies,
24
+ ...packageJson.devDependencies,
25
+ };
26
+
27
+ const { githubOrg } = getUserConfig();
28
+ const scopePrefix = githubOrg ? `@${githubOrg}/` : '';
29
+ for (const dep of Object.keys(allDeps)) {
30
+ // Match packages under the configured org scope (e.g. @your-org/*), or any
31
+ // scoped package as a fallback so cross-repo links are still detected.
32
+ if (dep.startsWith('@')) {
33
+ dependencies.push(
34
+ scopePrefix && dep.startsWith(scopePrefix)
35
+ ? dep.slice(scopePrefix.length)
36
+ : dep.replace(/^@[^/]+\//, '').replace('@', '')
37
+ );
38
+ }
39
+ }
40
+
41
+ return dependencies;
42
+ } catch {
43
+ return [];
44
+ }
45
+ };
46
+
47
+ export const analyzeImports = async (repoPath: string): Promise<string[]> => {
48
+ try {
49
+ const dependencies: Set<string> = new Set();
50
+
51
+ // Scan source files for relative imports that might indicate dependencies
52
+ const scanDirs = ['src', 'lib', 'app'];
53
+
54
+ for (const dir of scanDirs) {
55
+ const dirPath = path.join(repoPath, dir);
56
+ try {
57
+ await fs.access(dirPath);
58
+ // For simplicity, we're not doing deep file scanning here
59
+ // In a real implementation, you'd recursively read .ts, .js, .tsx files
60
+ } catch {
61
+ continue;
62
+ }
63
+ }
64
+
65
+ return Array.from(dependencies);
66
+ } catch {
67
+ return [];
68
+ }
69
+ };
70
+
71
+ export const analyzeDockerfile = async (repoPath: string): Promise<string[]> => {
72
+ try {
73
+ const dockerfilePath = path.join(repoPath, 'Dockerfile');
74
+ const content = await fs.readFile(dockerfilePath, 'utf-8');
75
+
76
+ const dependencies: string[] = [];
77
+ const fromRegex = /FROM\s+([^\s]+)/g;
78
+ let match;
79
+
80
+ while ((match = fromRegex.exec(content)) !== null) {
81
+ const image = match[1];
82
+ // Extract potential dependency references to other org images. When an org
83
+ // is configured, treat images named after it (e.g. `your-org-foo`) as deps.
84
+ const { githubOrg } = getUserConfig();
85
+ if (githubOrg && image.includes(githubOrg)) {
86
+ dependencies.push(image.split(':')[0].replace(`${githubOrg}-`, ''));
87
+ }
88
+ }
89
+
90
+ return dependencies;
91
+ } catch {
92
+ return [];
93
+ }
94
+ };
95
+
96
+ export const analyzeDockerCompose = async (repoPath: string): Promise<string[]> => {
97
+ try {
98
+ const composePath = path.join(repoPath, 'docker-compose.yml');
99
+ const content = await fs.readFile(composePath, 'utf-8');
100
+
101
+ const dependencies: string[] = [];
102
+
103
+ // Simple pattern matching for service references
104
+ const serviceRegex = /depends_on:\s*\n((?:\s+-\s+\w+\n)+)/g;
105
+ let match;
106
+
107
+ while ((match = serviceRegex.exec(content)) !== null) {
108
+ const services = match[1].match(/\w+/g);
109
+ if (services) {
110
+ dependencies.push(...services);
111
+ }
112
+ }
113
+
114
+ return dependencies;
115
+ } catch {
116
+ return [];
117
+ }
118
+ };
119
+
120
+ export const analyzeDependencies = async (
121
+ workspacePath: string,
122
+ repoNames: string[]
123
+ ): Promise<Map<string, DependencyAnalysis>> => {
124
+ const analyses = new Map<string, DependencyAnalysis>();
125
+
126
+ for (const repoName of repoNames) {
127
+ const repoPath = path.join(workspacePath, repoName);
128
+
129
+ const [packageDeps, dockerDeps, composeDeps] = await Promise.all([
130
+ analyzePackageJson(repoPath),
131
+ analyzeDockerfile(repoPath),
132
+ analyzeDockerCompose(repoPath),
133
+ ]);
134
+
135
+ const allDeps = [...new Set([...packageDeps, ...dockerDeps, ...composeDeps])];
136
+
137
+ // Filter to only dependencies that are in the workspace
138
+ const validDeps = allDeps.filter(dep => repoNames.includes(dep));
139
+
140
+ // Find missing dependencies (referenced but not in workspace)
141
+ const missingDeps = allDeps.filter(dep => !repoNames.includes(dep));
142
+
143
+ analyses.set(repoName, {
144
+ repoName,
145
+ dependencies: validDeps,
146
+ dependents: [], // Will be filled in next pass
147
+ missingDependencies: missingDeps,
148
+ });
149
+ }
150
+
151
+ // Build dependents lists
152
+ for (const [repoName, analysis] of analyses) {
153
+ for (const dep of analysis.dependencies) {
154
+ const depAnalysis = analyses.get(dep);
155
+ if (depAnalysis && !depAnalysis.dependents.includes(repoName)) {
156
+ depAnalysis.dependents.push(repoName);
157
+ }
158
+ }
159
+ }
160
+
161
+ return analyses;
162
+ };
163
+
164
+ export const detectCircularDependencies = (
165
+ analyses: Map<string, DependencyAnalysis>
166
+ ): string[][] => {
167
+ const visited = new Set<string>();
168
+ const recursionStack = new Set<string>();
169
+ const cycles: string[][] = [];
170
+
171
+ const dfs = (repo: string, path: string[]): void => {
172
+ visited.add(repo);
173
+ recursionStack.add(repo);
174
+ path.push(repo);
175
+
176
+ const analysis = analyses.get(repo);
177
+ if (analysis) {
178
+ for (const dep of analysis.dependencies) {
179
+ if (!visited.has(dep)) {
180
+ dfs(dep, [...path]);
181
+ } else if (recursionStack.has(dep)) {
182
+ // Found a cycle
183
+ const cycleStart = path.indexOf(dep);
184
+ const cycle = path.slice(cycleStart);
185
+ cycle.push(dep);
186
+ cycles.push(cycle);
187
+ }
188
+ }
189
+ }
190
+
191
+ recursionStack.delete(repo);
192
+ };
193
+
194
+ for (const repoName of analyses.keys()) {
195
+ if (!visited.has(repoName)) {
196
+ dfs(repoName, []);
197
+ }
198
+ }
199
+
200
+ return cycles;
201
+ };
202
+
203
+ export const generateMermaidDiagram = (analyses: Map<string, DependencyAnalysis>): string => {
204
+ const lines = ['```mermaid', 'graph TD'];
205
+
206
+ for (const [repoName, analysis] of analyses) {
207
+ const nodeId = repoName.replace(/[^a-zA-Z0-9]/g, '_');
208
+
209
+ for (const dep of analysis.dependencies) {
210
+ const depId = dep.replace(/[^a-zA-Z0-9]/g, '_');
211
+ lines.push(` ${nodeId}[${repoName}] --> ${depId}[${dep}]`);
212
+ }
213
+
214
+ // Add standalone nodes (no dependencies)
215
+ if (analysis.dependencies.length === 0 && analysis.dependents.length === 0) {
216
+ lines.push(` ${nodeId}[${repoName}]`);
217
+ }
218
+ }
219
+
220
+ lines.push('```');
221
+ return lines.join('\n');
222
+ };
223
+
224
+ export const updateWorkspaceMetadata = (
225
+ metadata: WorkspaceMetadata,
226
+ analyses: Map<string, DependencyAnalysis>
227
+ ): WorkspaceMetadata => {
228
+ const dependencies: { [repoName: string]: DependencyInfo } = {};
229
+
230
+ for (const [repoName, analysis] of analyses) {
231
+ dependencies[repoName] = {
232
+ dependsOn: analysis.dependencies,
233
+ dependedBy: analysis.dependents,
234
+ lastAnalyzed: new Date().toISOString(),
235
+ };
236
+ }
237
+
238
+ return {
239
+ ...metadata,
240
+ dependencies,
241
+ lastModified: new Date().toISOString(),
242
+ };
243
+ };
@@ -0,0 +1,59 @@
1
+ import { exec } from 'child_process';
2
+ import { promisify } from 'util';
3
+ import * as fs from 'fs/promises';
4
+
5
+ const execAsync = promisify(exec);
6
+
7
+ export interface DiffStats {
8
+ files: number;
9
+ insertions: number;
10
+ deletions: number;
11
+ }
12
+
13
+ export interface RepoDiff {
14
+ repo: string;
15
+ staged: DiffStats;
16
+ unstaged: DiffStats;
17
+ error?: string;
18
+ }
19
+
20
+ export function parseDiffStat(output: string): DiffStats {
21
+ const match = output.match(/(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/);
22
+ if (!match) {
23
+ return { files: 0, insertions: 0, deletions: 0 };
24
+ }
25
+ return {
26
+ files: parseInt(match[1], 10) || 0,
27
+ insertions: parseInt(match[2], 10) || 0,
28
+ deletions: parseInt(match[3], 10) || 0,
29
+ };
30
+ }
31
+
32
+ export async function getRepoDiff(repoPath: string, repoName: string): Promise<RepoDiff> {
33
+ try {
34
+ await fs.access(repoPath);
35
+ } catch {
36
+ return { repo: repoName, staged: { files: 0, insertions: 0, deletions: 0 }, unstaged: { files: 0, insertions: 0, deletions: 0 }, error: 'Directory not found' };
37
+ }
38
+
39
+ try {
40
+ const [unstagedResult, stagedResult] = await Promise.all([
41
+ execAsync('git diff --stat', { cwd: repoPath }).catch(() => ({ stdout: '' })),
42
+ execAsync('git diff --cached --stat', { cwd: repoPath }).catch(() => ({ stdout: '' })),
43
+ ]);
44
+
45
+ return {
46
+ repo: repoName,
47
+ unstaged: parseDiffStat((unstagedResult as { stdout: string }).stdout),
48
+ staged: parseDiffStat((stagedResult as { stdout: string }).stdout),
49
+ };
50
+ } catch (error) {
51
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
52
+ return {
53
+ repo: repoName,
54
+ staged: { files: 0, insertions: 0, deletions: 0 },
55
+ unstaged: { files: 0, insertions: 0, deletions: 0 },
56
+ error: errorMessage,
57
+ };
58
+ }
59
+ }
@@ -0,0 +1,187 @@
1
+ import * as fs from 'fs/promises';
2
+ import * as path from 'path';
3
+ import { WorkspaceMetadata } from '../types';
4
+ import { generateMermaidDiagram } from './dependency-analyzer';
5
+ import * as markdownTable from 'markdown-table';
6
+
7
+ export const generateWorkspaceReadme = async (
8
+ metadata: WorkspaceMetadata,
9
+ workspacePath: string
10
+ ): Promise<string> => {
11
+ const lines: string[] = [];
12
+
13
+ lines.push(`# ${metadata.workspaceName}`);
14
+ lines.push('');
15
+
16
+ if (metadata.description) {
17
+ lines.push(metadata.description);
18
+ lines.push('');
19
+ }
20
+
21
+ lines.push('## Overview');
22
+ lines.push('');
23
+ lines.push(`- **Created:** ${new Date(metadata.createdAt).toLocaleString()}`);
24
+ if (metadata.lastModified) {
25
+ lines.push(`- **Last Modified:** ${new Date(metadata.lastModified).toLocaleString()}`);
26
+ }
27
+ lines.push(`- **Repositories:** ${metadata.repositories.length}`);
28
+ lines.push('');
29
+
30
+ lines.push('## Repositories');
31
+ lines.push('');
32
+
33
+ const tableData: string[][] = [
34
+ ['Repository', 'Owner', 'Cloned At', 'Status'],
35
+ ];
36
+
37
+ for (const repo of metadata.repositories) {
38
+ tableData.push([
39
+ repo.name,
40
+ repo.owner,
41
+ new Date(repo.clonedAt).toLocaleDateString(),
42
+ repo.status === 'success' ? '✓' : '✗',
43
+ ]);
44
+ }
45
+
46
+ // Simple table generation without markdown-table for now
47
+ for (const row of tableData) {
48
+ lines.push('| ' + row.join(' | ') + ' |');
49
+ if (tableData.indexOf(row) === 0) {
50
+ lines.push('| ' + row.map(() => '---').join(' | ') + ' |');
51
+ }
52
+ }
53
+ lines.push('');
54
+
55
+ lines.push('## Quick Start');
56
+ lines.push('');
57
+ lines.push('```bash');
58
+ lines.push(`# Navigate to workspace`);
59
+ lines.push(`wgo ${metadata.workspaceName}`);
60
+ lines.push('');
61
+ lines.push('# Check status');
62
+ lines.push(`workspace status ${metadata.workspaceName}`);
63
+ lines.push('');
64
+ lines.push('# Sync all repositories');
65
+ lines.push(`workspace sync ${metadata.workspaceName}`);
66
+ lines.push('```');
67
+ lines.push('');
68
+
69
+ lines.push('## Commands');
70
+ lines.push('');
71
+ lines.push('- `workspace status` - Check git status across all repos');
72
+ lines.push('- `workspace doctor` - Run health checks');
73
+ lines.push('- `workspace sync` - Pull latest changes');
74
+ lines.push('- `workspace switch-branch` - Switch all repos to a branch');
75
+ lines.push('- `workspace analyze-deps` - Analyze dependencies');
76
+ lines.push('');
77
+
78
+ return lines.join('\n');
79
+ };
80
+
81
+ export const generateDependencyGraph = (metadata: WorkspaceMetadata): string => {
82
+ const lines: string[] = [];
83
+
84
+ lines.push('# Dependency Graph');
85
+ lines.push('');
86
+
87
+ if (metadata.dependencies) {
88
+ const analyses = new Map();
89
+
90
+ for (const [repoName, depInfo] of Object.entries(metadata.dependencies)) {
91
+ analyses.set(repoName, {
92
+ repoName,
93
+ dependencies: depInfo.dependsOn,
94
+ dependents: depInfo.dependedBy,
95
+ missingDependencies: [],
96
+ });
97
+ }
98
+
99
+ const diagram = generateMermaidDiagram(analyses);
100
+ lines.push(diagram);
101
+ } else {
102
+ lines.push('No dependency analysis available. Run `workspace analyze-deps` to generate.');
103
+ }
104
+
105
+ lines.push('');
106
+ lines.push('## Dependency Details');
107
+ lines.push('');
108
+
109
+ if (metadata.dependencies) {
110
+ for (const [repoName, depInfo] of Object.entries(metadata.dependencies)) {
111
+ lines.push(`### ${repoName}`);
112
+ lines.push('');
113
+
114
+ if (depInfo.dependsOn.length > 0) {
115
+ lines.push(`**Depends on:** ${depInfo.dependsOn.join(', ')}`);
116
+ lines.push('');
117
+ }
118
+
119
+ if (depInfo.dependedBy.length > 0) {
120
+ lines.push(`**Depended by:** ${depInfo.dependedBy.join(', ')}`);
121
+ lines.push('');
122
+ }
123
+
124
+ if (depInfo.dependsOn.length === 0 && depInfo.dependedBy.length === 0) {
125
+ lines.push('No dependencies');
126
+ lines.push('');
127
+ }
128
+ }
129
+ }
130
+
131
+ return lines.join('\n');
132
+ };
133
+
134
+ export const generateRepositoryIndex = async (
135
+ metadata: WorkspaceMetadata,
136
+ workspacePath: string
137
+ ): Promise<string> => {
138
+ const lines: string[] = [];
139
+
140
+ lines.push('# Repository Index');
141
+ lines.push('');
142
+
143
+ for (const repo of metadata.repositories) {
144
+ const repoPath = path.join(workspacePath, repo.name);
145
+
146
+ lines.push(`## ${repo.name}`);
147
+ lines.push('');
148
+ lines.push(`- **Owner:** ${repo.owner}`);
149
+ lines.push(`- **Clone URL:** ${repo.cloneUrl}`);
150
+ lines.push(`- **Cloned At:** ${new Date(repo.clonedAt).toLocaleString()}`);
151
+
152
+ // Try to read package.json for tech stack info
153
+ try {
154
+ const packageJsonPath = path.join(repoPath, 'package.json');
155
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
156
+
157
+ if (packageJson.description) {
158
+ lines.push(`- **Description:** ${packageJson.description}`);
159
+ }
160
+
161
+ if (packageJson.scripts) {
162
+ const scripts = Object.keys(packageJson.scripts).slice(0, 5);
163
+ lines.push(`- **Scripts:** ${scripts.join(', ')}`);
164
+ }
165
+ } catch {
166
+ // No package.json or can't read
167
+ }
168
+
169
+ lines.push('');
170
+ }
171
+
172
+ return lines.join('\n');
173
+ };
174
+
175
+ export const writeDocs = async (
176
+ workspacePath: string,
177
+ metadata: WorkspaceMetadata
178
+ ): Promise<void> => {
179
+ const readme = await generateWorkspaceReadme(metadata, workspacePath);
180
+ await fs.writeFile(path.join(workspacePath, 'WORKSPACE-README.md'), readme, 'utf-8');
181
+
182
+ const depGraph = generateDependencyGraph(metadata);
183
+ await fs.writeFile(path.join(workspacePath, 'DEPENDENCY-GRAPH.md'), depGraph, 'utf-8');
184
+
185
+ const repoIndex = await generateRepositoryIndex(metadata, workspacePath);
186
+ await fs.writeFile(path.join(workspacePath, 'REPOSITORY-INDEX.md'), repoIndex, 'utf-8');
187
+ };