agent-watch 1.0.0

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 (77) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/LICENSE +21 -0
  3. package/README.md +95 -0
  4. package/dist/cli-nMe9-VkJ.d.ts +1 -0
  5. package/dist/cli.cjs +770 -0
  6. package/dist/cli.cjs.map +1 -0
  7. package/dist/cli.d.ts +3 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +771 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/commands/init.d.ts +2 -0
  12. package/dist/commands/init.d.ts.map +1 -0
  13. package/dist/commands/run.d.ts +5 -0
  14. package/dist/commands/run.d.ts.map +1 -0
  15. package/dist/config.d.ts +31 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/constants.d.ts +21 -0
  18. package/dist/constants.d.ts.map +1 -0
  19. package/dist/detect-BMnM34-m.cjs +177 -0
  20. package/dist/detect-BMnM34-m.cjs.map +1 -0
  21. package/dist/detect-BWGm1KGQ.js +122 -0
  22. package/dist/detect-BWGm1KGQ.js.map +1 -0
  23. package/dist/detect-B_DDBj5N.cjs +182 -0
  24. package/dist/detect-B_DDBj5N.cjs.map +1 -0
  25. package/dist/detect-CPW1RRIq.js +117 -0
  26. package/dist/detect-CPW1RRIq.js.map +1 -0
  27. package/dist/detect-Dii2e4wf.cjs +174 -0
  28. package/dist/detect-Dii2e4wf.cjs.map +1 -0
  29. package/dist/detect-Pkaqn3YG.js +120 -0
  30. package/dist/detect-Pkaqn3YG.js.map +1 -0
  31. package/dist/detect.d.ts +16 -0
  32. package/dist/detect.d.ts.map +1 -0
  33. package/dist/hooks.d.ts +13 -0
  34. package/dist/hooks.d.ts.map +1 -0
  35. package/dist/index-CXIlEXUx.d.ts +51 -0
  36. package/dist/index.cjs +13 -0
  37. package/dist/index.cjs.map +1 -0
  38. package/dist/index.d.cts +5 -0
  39. package/dist/index.d.ts +5 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +3 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/logger-BNjXChov.js +164 -0
  44. package/dist/logger-BNjXChov.js.map +1 -0
  45. package/dist/logger-CdAUnlsG.cjs +271 -0
  46. package/dist/logger-CdAUnlsG.cjs.map +1 -0
  47. package/dist/logger-hSIaaw_k.js +166 -0
  48. package/dist/logger-hSIaaw_k.js.map +1 -0
  49. package/dist/logger-oq2Z7oYf.cjs +269 -0
  50. package/dist/logger-oq2Z7oYf.cjs.map +1 -0
  51. package/dist/sessions-90kmJrQI.js +360 -0
  52. package/dist/sessions-90kmJrQI.js.map +1 -0
  53. package/dist/sessions-Bmk48zTI.js +311 -0
  54. package/dist/sessions-Bmk48zTI.js.map +1 -0
  55. package/dist/sessions-BpNk9YjU.cjs +431 -0
  56. package/dist/sessions-BpNk9YjU.cjs.map +1 -0
  57. package/dist/sessions-CkCQikpl.cjs +444 -0
  58. package/dist/sessions-CkCQikpl.cjs.map +1 -0
  59. package/dist/sessions-Cy-_zIh6.js +315 -0
  60. package/dist/sessions-Cy-_zIh6.js.map +1 -0
  61. package/dist/sessions-DZgPENb6.cjs +434 -0
  62. package/dist/sessions-DZgPENb6.cjs.map +1 -0
  63. package/dist/sessions-_HBb3nIW.cjs +495 -0
  64. package/dist/sessions-_HBb3nIW.cjs.map +1 -0
  65. package/dist/sessions-tBeR9gKG.js +308 -0
  66. package/dist/sessions-tBeR9gKG.js.map +1 -0
  67. package/dist/utils/copilot.d.ts +27 -0
  68. package/dist/utils/copilot.d.ts.map +1 -0
  69. package/dist/utils/git.d.ts +35 -0
  70. package/dist/utils/git.d.ts.map +1 -0
  71. package/dist/utils/gitignore.d.ts +5 -0
  72. package/dist/utils/gitignore.d.ts.map +1 -0
  73. package/dist/utils/logger.d.ts +14 -0
  74. package/dist/utils/logger.d.ts.map +1 -0
  75. package/dist/utils/sessions.d.ts +48 -0
  76. package/dist/utils/sessions.d.ts.map +1 -0
  77. package/package.json +79 -0
package/dist/cli.js ADDED
@@ -0,0 +1,771 @@
1
+ #!/usr/bin/env node
2
+ import { a as detectAgentFiles, c as loadConfig, f as FILE_SELECTION_PAGE_SIZE, g as SUPPORTED_AI_AGENTS, i as logger, l as saveConfig, m as KNOWN_AGENT_FILES, p as IGNORED_FILE_PATTERNS, r as processNewSessions, s as createDefaultConfig, u as AGENT_WATCH_DIR } from "./sessions-90kmJrQI.js";
3
+ import { createRequire } from "node:module";
4
+ import { program } from "commander";
5
+ import { checkbox, confirm, select } from "@inquirer/prompts";
6
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { execSync } from "node:child_process";
9
+ import { homedir } from "node:os";
10
+
11
+ //#region src/utils/git.ts
12
+ /**
13
+ * Find the git repository root from a given directory.
14
+ * Returns null if not in a git repository.
15
+ */
16
+ function findGitRoot(cwd = process.cwd()) {
17
+ try {
18
+ return execSync("git rev-parse --show-toplevel", {
19
+ cwd,
20
+ encoding: "utf-8",
21
+ stdio: [
22
+ "pipe",
23
+ "pipe",
24
+ "pipe"
25
+ ]
26
+ }).trim();
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+ /**
32
+ * Get the path to the .git/hooks directory.
33
+ */
34
+ function getGitHooksDir(gitRoot) {
35
+ return join(gitRoot, ".git", "hooks");
36
+ }
37
+ /**
38
+ * Check if lefthook is installed in the project.
39
+ */
40
+ function hasLefthook(projectRoot) {
41
+ return existsSync(join(projectRoot, "lefthook.yml")) || existsSync(join(projectRoot, "lefthook.yaml"));
42
+ }
43
+ /**
44
+ * Check if husky is installed in the project.
45
+ */
46
+ function hasHusky(projectRoot) {
47
+ return existsSync(join(projectRoot, ".husky"));
48
+ }
49
+ /**
50
+ * Get the path to lefthook.yml or lefthook.yaml.
51
+ * Returns null if not found.
52
+ */
53
+ function getLefthookPath(projectRoot) {
54
+ const ymlPath = join(projectRoot, "lefthook.yml");
55
+ const yamlPath = join(projectRoot, "lefthook.yaml");
56
+ if (existsSync(ymlPath)) return ymlPath;
57
+ if (existsSync(yamlPath)) return yamlPath;
58
+ return null;
59
+ }
60
+ /**
61
+ * Check if lefthook CLI is available.
62
+ */
63
+ function isLefthookAvailable() {
64
+ try {
65
+ execSync("npx lefthook version", {
66
+ stdio: "pipe",
67
+ timeout: 5e3
68
+ });
69
+ return true;
70
+ } catch {
71
+ return false;
72
+ }
73
+ }
74
+ /**
75
+ * Check if husky CLI is available.
76
+ */
77
+ function isHuskyAvailable() {
78
+ try {
79
+ execSync("npx husky --version", {
80
+ stdio: "pipe",
81
+ timeout: 5e3
82
+ });
83
+ return true;
84
+ } catch {
85
+ return false;
86
+ }
87
+ }
88
+
89
+ //#endregion
90
+ //#region src/hooks.ts
91
+ const HOOK_MARKER_START = "# >>> agent-watch hook start >>>";
92
+ const HOOK_MARKER_END = "# <<< agent-watch hook end <<<";
93
+ function getHookScript() {
94
+ return `${HOOK_MARKER_START}
95
+ # agent-watch: auto-update agent configuration files
96
+ npx agent-watch run 2>/dev/null || true
97
+ ${HOOK_MARKER_END}`;
98
+ }
99
+ /**
100
+ * Validate basic lefthook.yml structure before attempting modification.
101
+ * Checks: not empty, contains colons, no tabs, reasonable line/indentation length.
102
+ */
103
+ function isValidLefthookStructure(content) {
104
+ if (!content.trim()) return false;
105
+ if (!content.includes(":")) return false;
106
+ if (content.includes(" ")) return false;
107
+ const lines = content.split("\n");
108
+ if (lines.length > 1e3) return false;
109
+ for (const line of lines) {
110
+ if (line.length > 500) return false;
111
+ const leadingSpaces = line.search(/\S/);
112
+ if (leadingSpaces > 50 && leadingSpaces !== -1) return false;
113
+ }
114
+ return true;
115
+ }
116
+ /**
117
+ * Remove existing agent-watch hook section from lefthook.yml content.
118
+ * Removes ANY agent-watch: section to prevent duplicates.
119
+ */
120
+ function removeExistingAgentWatchHook(lines) {
121
+ const result = [];
122
+ let inAgentWatchSection = false;
123
+ let sectionIndentation = 0;
124
+ for (const line of lines) {
125
+ const trimmed = line.trim();
126
+ if (trimmed.startsWith("agent-watch:")) {
127
+ inAgentWatchSection = true;
128
+ sectionIndentation = line.search(/\S/);
129
+ continue;
130
+ }
131
+ if (inAgentWatchSection) {
132
+ const currentIndent = line.search(/\S/);
133
+ if (currentIndent !== -1 && currentIndent <= sectionIndentation && trimmed.length > 0) {
134
+ inAgentWatchSection = false;
135
+ result.push(line);
136
+ }
137
+ continue;
138
+ }
139
+ result.push(line);
140
+ }
141
+ return result;
142
+ }
143
+ /**
144
+ * Insert agent-watch command into lefthook.yml content.
145
+ * Handles: missing hook section, missing commands section, proper indentation.
146
+ */
147
+ function insertAgentWatchCommand(lines, hookName) {
148
+ const result = [];
149
+ let inserted = false;
150
+ for (let i = 0; i < lines.length; i++) {
151
+ const line = lines[i];
152
+ result.push(line);
153
+ if (line.trim() === `${hookName}:`) {
154
+ let commandsLineIndex = -1;
155
+ for (let j = i + 1; j < lines.length; j++) {
156
+ const trimmedLine = lines[j].trim();
157
+ if (trimmedLine.startsWith("commands:")) {
158
+ commandsLineIndex = j;
159
+ break;
160
+ }
161
+ if (trimmedLine && !lines[j].startsWith(" ")) break;
162
+ }
163
+ if (commandsLineIndex === -1) {
164
+ result.push(" commands:", " agent-watch:", " run: npx agent-watch run");
165
+ inserted = true;
166
+ continue;
167
+ }
168
+ for (let j = i + 1; j <= commandsLineIndex; j++) result.push(lines[j]);
169
+ result.push(" agent-watch:", " run: npx agent-watch run");
170
+ inserted = true;
171
+ i = commandsLineIndex;
172
+ }
173
+ }
174
+ if (!inserted) return {
175
+ success: false,
176
+ error: `Could not find proper insertion point in lefthook.yml for ${hookName}`
177
+ };
178
+ return {
179
+ success: true,
180
+ content: result.join("\n")
181
+ };
182
+ }
183
+ /**
184
+ * Update lefthook.yml content by adding/replacing agent-watch hook.
185
+ * Coordinates removal and insertion, handles edge cases.
186
+ */
187
+ function updateLefthookContent(content, hookName) {
188
+ const lines = content.split("\n");
189
+ const hasHookSection = new RegExp(`^${hookName}:\\s*$`, "m").test(content);
190
+ const withoutOldHook = removeExistingAgentWatchHook(lines);
191
+ if (!hasHookSection) {
192
+ const newSection = `
193
+ ${hookName}:
194
+ commands:
195
+ agent-watch:
196
+ run: npx agent-watch run
197
+ `;
198
+ return {
199
+ success: true,
200
+ content: withoutOldHook.join("\n") + newSection
201
+ };
202
+ }
203
+ return insertAgentWatchCommand(withoutOldHook, hookName);
204
+ }
205
+ /**
206
+ * Map the trigger name to the git hook file name.
207
+ */
208
+ function getGitHookName(trigger) {
209
+ switch (trigger) {
210
+ case "commit": return "post-commit";
211
+ case "push": return "pre-push";
212
+ }
213
+ }
214
+ /**
215
+ * Install agent-watch hook into lefthook.yml.
216
+ * Flow: read file → validate → update content → write → run lefthook install
217
+ */
218
+ function installLefthookHook(projectRoot, hookName) {
219
+ const lefthookPath = getLefthookPath(projectRoot);
220
+ if (!lefthookPath) return {
221
+ success: false,
222
+ message: "lefthook.yml not found",
223
+ method: "manual"
224
+ };
225
+ let currentContent;
226
+ try {
227
+ currentContent = readFileSync(lefthookPath, "utf-8");
228
+ } catch (error) {
229
+ return {
230
+ success: false,
231
+ message: `Cannot read lefthook.yml: ${error instanceof Error ? error.message : String(error)}. Please add manually:\n\n${hookName}:\n commands:\n agent-watch:\n run: npx agent-watch run`,
232
+ method: "manual"
233
+ };
234
+ }
235
+ if (!(currentContent.trim().length === 0) && !isValidLefthookStructure(currentContent)) return {
236
+ success: true,
237
+ message: `lefthook.yml has complex structure. Please add manually:\n\n${hookName}:\n commands:\n agent-watch:\n run: npx agent-watch run`,
238
+ method: "lefthook"
239
+ };
240
+ const result = updateLefthookContent(currentContent, hookName);
241
+ if (!result.success || !result.content) return {
242
+ success: false,
243
+ message: result.error || "Failed to update lefthook.yml",
244
+ method: "manual"
245
+ };
246
+ try {
247
+ const tempPath = `${lefthookPath}.tmp`;
248
+ writeFileSync(tempPath, result.content, "utf-8");
249
+ renameSync(tempPath, lefthookPath);
250
+ } catch (error) {
251
+ return {
252
+ success: false,
253
+ message: `Failed to write lefthook.yml: ${error instanceof Error ? error.message : String(error)}`,
254
+ method: "manual"
255
+ };
256
+ }
257
+ if (isLefthookAvailable()) try {
258
+ execSync("npx lefthook install", {
259
+ cwd: projectRoot,
260
+ stdio: "inherit",
261
+ timeout: 3e4
262
+ });
263
+ return {
264
+ success: true,
265
+ message: `Lefthook hook installed successfully in ${hookName}`,
266
+ method: "lefthook"
267
+ };
268
+ } catch (error) {
269
+ return {
270
+ success: true,
271
+ message: `Hook added to lefthook.yml. Failed to run lefthook install: ${error instanceof Error ? error.message : String(error)}\nPlease run: npx lefthook install`,
272
+ method: "lefthook"
273
+ };
274
+ }
275
+ return {
276
+ success: true,
277
+ message: "Hook added to lefthook.yml. Please run: npx lefthook install",
278
+ method: "lefthook"
279
+ };
280
+ }
281
+ /**
282
+ * Install agent-watch hook using husky CLI.
283
+ * Runs: npx husky add .husky/{hookName} "npx agent-watch run"
284
+ */
285
+ function installHuskyHook(projectRoot, hookName) {
286
+ const huskyDir = join(projectRoot, ".husky");
287
+ if (!existsSync(huskyDir)) return {
288
+ success: false,
289
+ message: ".husky directory not found. Initialize husky first with: npx husky init",
290
+ method: "manual"
291
+ };
292
+ const hookPath = join(huskyDir, hookName);
293
+ if (existsSync(hookPath)) try {
294
+ if (readFileSync(hookPath, "utf-8").includes("agent-watch run")) return {
295
+ success: true,
296
+ message: `Husky hook already configured in .husky/${hookName}`,
297
+ method: "husky"
298
+ };
299
+ } catch {}
300
+ if (isHuskyAvailable()) try {
301
+ execSync(`npx husky add .husky/${hookName} "npx agent-watch run"`, {
302
+ cwd: projectRoot,
303
+ stdio: "pipe",
304
+ timeout: 3e4
305
+ });
306
+ return {
307
+ success: true,
308
+ message: `Husky hook installed successfully in .husky/${hookName}`,
309
+ method: "husky"
310
+ };
311
+ } catch {}
312
+ return {
313
+ success: true,
314
+ message: `Husky detected. Run: npx husky add .husky/${hookName} "npx agent-watch run"`,
315
+ method: "husky"
316
+ };
317
+ }
318
+ /**
319
+ * Install the agent-watch hook into the git hooks directory.
320
+ * If a hook file already exists, appends our section.
321
+ * If lefthook or husky is detected, provides instructions instead.
322
+ */
323
+ function installGitHook(projectRoot, gitRoot, trigger) {
324
+ const hookName = getGitHookName(trigger);
325
+ if (hasLefthook(projectRoot)) return installLefthookHook(projectRoot, hookName);
326
+ if (hasHusky(projectRoot)) return installHuskyHook(projectRoot, hookName);
327
+ const hooksDir = getGitHooksDir(gitRoot);
328
+ const hookPath = join(hooksDir, hookName);
329
+ try {
330
+ if (!existsSync(hooksDir)) mkdirSync(hooksDir, { recursive: true });
331
+ let hookContent;
332
+ if (existsSync(hookPath)) {
333
+ const existing = readFileSync(hookPath, "utf-8");
334
+ if (existing.includes(HOOK_MARKER_START)) {
335
+ const regex = new RegExp(`${escapeRegex(HOOK_MARKER_START)}[\\s\\S]*?${escapeRegex(HOOK_MARKER_END)}`);
336
+ hookContent = existing.replace(regex, getHookScript());
337
+ } else hookContent = `${existing.trimEnd()}\n\n${getHookScript()}\n`;
338
+ } else hookContent = `#!/bin/sh\n\n${getHookScript()}\n`;
339
+ writeFileSync(hookPath, hookContent, "utf-8");
340
+ chmodSync(hookPath, 493);
341
+ return {
342
+ success: true,
343
+ message: `Git ${hookName} hook installed successfully.`,
344
+ method: "direct"
345
+ };
346
+ } catch (error) {
347
+ return {
348
+ success: false,
349
+ message: `Failed to install git hook: ${error instanceof Error ? error.message : String(error)}`,
350
+ method: "manual"
351
+ };
352
+ }
353
+ }
354
+ function escapeRegex(str) {
355
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
356
+ }
357
+
358
+ //#endregion
359
+ //#region src/utils/copilot.ts
360
+ const COPILOT_CONFIG_FILE = join(join(homedir(), ".copilot"), "config.json");
361
+ /**
362
+ * Check if the standalone GitHub Copilot CLI is installed
363
+ */
364
+ function isCopilotInstalled() {
365
+ try {
366
+ const output = execSync("copilot -v", {
367
+ encoding: "utf-8",
368
+ stdio: "pipe"
369
+ });
370
+ return {
371
+ installed: true,
372
+ version: /(\d+\.\d+\.\d+)/.exec(output)?.[1]
373
+ };
374
+ } catch {
375
+ return { installed: false };
376
+ }
377
+ }
378
+ /**
379
+ * Check if the user is authenticated with GitHub Copilot CLI
380
+ * by reading the config file at ~/.copilot/config.json
381
+ */
382
+ function getCopilotAuth() {
383
+ try {
384
+ if (!existsSync(COPILOT_CONFIG_FILE)) return { authenticated: false };
385
+ const configContent = readFileSync(COPILOT_CONFIG_FILE, "utf-8");
386
+ const config = JSON.parse(configContent);
387
+ if (config.logged_in_users && config.logged_in_users.length > 0) return {
388
+ authenticated: true,
389
+ username: (config.last_logged_in_user ?? config.logged_in_users[0]).login
390
+ };
391
+ return { authenticated: false };
392
+ } catch {
393
+ return { authenticated: false };
394
+ }
395
+ }
396
+ /**
397
+ * Check the status of GitHub Copilot CLI
398
+ */
399
+ function checkCopilotStatus() {
400
+ const { installed, version } = isCopilotInstalled();
401
+ const { authenticated, username } = getCopilotAuth();
402
+ return {
403
+ installed,
404
+ authenticated,
405
+ version,
406
+ username
407
+ };
408
+ }
409
+ /**
410
+ * Prompt the user to install GitHub Copilot CLI
411
+ */
412
+ async function promptInstallCopilot() {
413
+ logger.step("GitHub Copilot CLI is not installed. Please install it first:");
414
+ logger.info(" macOS: brew install gh-copilot");
415
+ logger.info(" npm: npm install -g @githubnext/github-copilot-cli");
416
+ logger.info(" See: https://docs.github.com/en/copilot/how-tos/copilot-cli");
417
+ logger.blank();
418
+ logger.info("After installation, please run 'agent-watch init' again.");
419
+ return false;
420
+ }
421
+ /**
422
+ * Authenticate with GitHub Copilot CLI
423
+ */
424
+ async function authenticateCopilot() {
425
+ try {
426
+ logger.step("Authenticating with GitHub Copilot CLI...");
427
+ logger.info("Follow the prompts to authenticate:");
428
+ execSync("copilot login", { stdio: "inherit" });
429
+ logger.success("GitHub Copilot CLI authenticated successfully!");
430
+ return true;
431
+ } catch (error) {
432
+ logger.error("Failed to authenticate with GitHub Copilot CLI");
433
+ logger.error(error instanceof Error ? error.message : String(error));
434
+ return false;
435
+ }
436
+ }
437
+ /**
438
+ * Setup GitHub Copilot CLI - install and configure if needed
439
+ */
440
+ async function setupGithubCopilotCli() {
441
+ const status = checkCopilotStatus();
442
+ if (!status.installed) return await promptInstallCopilot();
443
+ if (!status.authenticated) {
444
+ logger.warn("GitHub Copilot CLI is not authenticated");
445
+ if (!await authenticateCopilot()) return false;
446
+ } else logger.success(`Copilot CLI authenticated as ${status.username}`);
447
+ return true;
448
+ }
449
+ /**
450
+ * Create missing agent files using Copilot CLI.
451
+ * For .github/copilot-instructions.md, uses `copilot init`.
452
+ * For other files, uses `copilot -p` to generate content based on the codebase.
453
+ */
454
+ function createMissingAgentFiles(projectRoot, selectedFiles, detectedFiles) {
455
+ const missingFiles = selectedFiles.filter((filePath) => {
456
+ return !detectedFiles.find((d) => d.pattern.path === filePath)?.exists;
457
+ });
458
+ if (missingFiles.length === 0) return;
459
+ for (const filePath of missingFiles) {
460
+ const label = KNOWN_AGENT_FILES.find((f) => f.path === filePath)?.label ?? filePath;
461
+ try {
462
+ logger.step(`Creating ${label}...`);
463
+ if (filePath === ".github/copilot-instructions.md") execSync("copilot init", {
464
+ cwd: projectRoot,
465
+ stdio: "pipe",
466
+ timeout: 12e4
467
+ });
468
+ else execSync(`copilot -p "Create a ${filePath} file for this project. Analyze the codebase to understand the project structure, tech stack, and conventions. Write the file directly." --allow-all-tools -s`, {
469
+ cwd: projectRoot,
470
+ stdio: "pipe",
471
+ timeout: 12e4
472
+ });
473
+ logger.success(`Created ${label}`);
474
+ } catch {
475
+ logger.warn(`Failed to create ${label}`);
476
+ }
477
+ }
478
+ }
479
+
480
+ //#endregion
481
+ //#region src/utils/gitignore.ts
482
+ /**
483
+ * Add entries to .gitignore if they don't already exist
484
+ */
485
+ function addToGitignore(projectRoot, entries) {
486
+ const gitignorePath = join(projectRoot, ".gitignore");
487
+ if (!existsSync(projectRoot)) return;
488
+ let content = "";
489
+ if (existsSync(gitignorePath)) content = readFileSync(gitignorePath, "utf-8");
490
+ const lines = content.split("\n");
491
+ const entriesToAdd = [];
492
+ for (const entry of entries) if (!lines.some((line) => {
493
+ const trimmed = line.trim();
494
+ return trimmed === entry || trimmed === `/${entry}`;
495
+ })) entriesToAdd.push(entry);
496
+ if (entriesToAdd.length === 0) return;
497
+ const newContent = `${content.trim()}\n\n# agent-watch\n${entriesToAdd.join("\n")}\n`;
498
+ try {
499
+ writeFileSync(gitignorePath, newContent, "utf-8");
500
+ } catch {}
501
+ }
502
+
503
+ //#endregion
504
+ //#region src/commands/init.ts
505
+ async function initCommand() {
506
+ logger.asciiArt();
507
+ const gitRoot = findGitRoot(process.cwd());
508
+ if (!gitRoot) {
509
+ logger.error("Not inside a git repository. Please run this command from a git repository.");
510
+ process.exit(1);
511
+ }
512
+ const projectRoot = gitRoot;
513
+ if (loadConfig(projectRoot)) {
514
+ if (!await confirm({
515
+ message: "An existing agent-watch configuration was found. Do you want to overwrite it?",
516
+ default: false
517
+ })) {
518
+ logger.info("Init cancelled. Existing configuration preserved.");
519
+ return;
520
+ }
521
+ }
522
+ const detectedFiles = detectAgentFiles(projectRoot);
523
+ const existingFiles = detectedFiles.filter((f) => f.exists);
524
+ if (existingFiles.length > 0) logger.success(`Found ${existingFiles.length} existing agent file(s)`);
525
+ const selectedFiles = await checkbox({
526
+ message: "Which agent files should agent-watch manage?",
527
+ pageSize: FILE_SELECTION_PAGE_SIZE,
528
+ choices: KNOWN_AGENT_FILES.map((pattern) => {
529
+ const exists = detectedFiles.find((d) => d.pattern.path === pattern.path)?.exists ?? false;
530
+ return {
531
+ name: exists ? ` ${pattern.label} ✓` : ` ${pattern.label}`,
532
+ value: pattern.path,
533
+ checked: exists
534
+ };
535
+ })
536
+ });
537
+ if (selectedFiles.length === 0) {
538
+ logger.warn("No files selected. You can re-run 'agent-watch init' to configure.");
539
+ return;
540
+ }
541
+ createMissingAgentFiles(projectRoot, selectedFiles, detectedFiles);
542
+ const contextOptions = await checkbox({
543
+ message: "What should agent-watch track?",
544
+ choices: [{
545
+ name: " File changes (git diff, modified files)",
546
+ value: "watchFileChanges",
547
+ checked: true
548
+ }, {
549
+ name: " Chat sessions (Copilot conversation context)",
550
+ value: "includeChatSession",
551
+ checked: true
552
+ }]
553
+ });
554
+ const watchFileChanges = contextOptions.includes("watchFileChanges");
555
+ const includeChatSession = contextOptions.includes("includeChatSession");
556
+ const hookTrigger = await select({
557
+ message: "When should agent-watch trigger?",
558
+ choices: [{
559
+ name: "After git commit (post-commit hook)",
560
+ value: "commit"
561
+ }, {
562
+ name: "Before git push (pre-push hook)",
563
+ value: "push"
564
+ }],
565
+ default: "commit"
566
+ });
567
+ const selectedAgents = await checkbox({
568
+ message: "Which AI agents would you like to configure?",
569
+ choices: SUPPORTED_AI_AGENTS.map((agent) => ({
570
+ name: ` ${agent.name}`,
571
+ value: agent.value,
572
+ checked: true
573
+ }))
574
+ });
575
+ if (selectedAgents.includes("github-copilot-cli")) {
576
+ if (!await setupGithubCopilotCli()) logger.warn("Copilot CLI setup incomplete. You can set it up manually later.");
577
+ }
578
+ saveConfig(projectRoot, createDefaultConfig({
579
+ agentFiles: selectedFiles,
580
+ watchFileChanges,
581
+ includeChatSession,
582
+ hookTrigger,
583
+ agents: selectedAgents
584
+ }));
585
+ addToGitignore(projectRoot, [AGENT_WATCH_DIR]);
586
+ const hookResult = installGitHook(projectRoot, gitRoot, hookTrigger);
587
+ if (hookResult.success) if (hookResult.message.includes("Please run:") || hookResult.message.includes("please add manually")) logger.warn(hookResult.message);
588
+ else logger.success(hookResult.message);
589
+ else logger.error(hookResult.message);
590
+ logger.blank();
591
+ logger.success("Setup complete!");
592
+ logger.info(`Files: ${selectedFiles.join(", ")} • Hook: ${hookTrigger} • Agents: ${selectedAgents.join(", ")}`);
593
+ logger.blank();
594
+ }
595
+
596
+ //#endregion
597
+ //#region src/commands/run.ts
598
+ /**
599
+ * Get git commit message
600
+ */
601
+ function getCommitMessage() {
602
+ try {
603
+ return execSync("git log -1 --pretty=%B", {
604
+ encoding: "utf-8",
605
+ stdio: "pipe"
606
+ }).trim();
607
+ } catch {
608
+ return "";
609
+ }
610
+ }
611
+ /**
612
+ * Get git diff stats (summary, not full diff)
613
+ */
614
+ function getGitDiffStats() {
615
+ try {
616
+ return execSync("git diff HEAD~1..HEAD --stat", {
617
+ encoding: "utf-8",
618
+ stdio: "pipe"
619
+ }).trim();
620
+ } catch {
621
+ return "";
622
+ }
623
+ }
624
+ /**
625
+ * Get list of modified files
626
+ */
627
+ function getModifiedFiles() {
628
+ try {
629
+ return execSync("git diff --name-only HEAD~1..HEAD", {
630
+ encoding: "utf-8",
631
+ stdio: "pipe"
632
+ }).trim().split("\n").filter(Boolean);
633
+ } catch {
634
+ return [];
635
+ }
636
+ }
637
+ /**
638
+ * Check if a file path should be ignored (doesn't trigger agent-watch analysis)
639
+ */
640
+ function shouldIgnoreFile(filePath) {
641
+ const normalizedPath = filePath.replace(/\\/g, "/");
642
+ for (const pattern of IGNORED_FILE_PATTERNS) {
643
+ const normalizedPattern = pattern.replace(/\\/g, "/");
644
+ if (normalizedPath === normalizedPattern) return true;
645
+ if (normalizedPath.startsWith(`${normalizedPattern}/`)) return true;
646
+ if (normalizedPath.split("/").pop() === normalizedPattern) return true;
647
+ }
648
+ return false;
649
+ }
650
+ /**
651
+ * Check if all modified files should be ignored
652
+ */
653
+ function shouldSkipAnalysis(modifiedFiles) {
654
+ if (modifiedFiles.length === 0) return true;
655
+ return modifiedFiles.every(shouldIgnoreFile);
656
+ }
657
+ /**
658
+ * Use Copilot CLI to intelligently update agent file content
659
+ */
660
+ function updateAgentFileWithCopilot(projectRoot, context, agentFilePath, currentContent) {
661
+ try {
662
+ const output = execSync(`copilot -p "${`You are maintaining ${agentFilePath}, a living document of patterns, conventions, and code rules.
663
+
664
+ Your task:
665
+ 1. Read the CURRENT content of this file carefully
666
+ 2. Analyze the RECENT changes and conversations below
667
+ 3. Determine if there are new patterns, rules, or conventions to document
668
+ 4. If updates are needed, output the COMPLETE UPDATED file content
669
+ 5. If no meaningful updates needed, output exactly: NO_UPDATE
670
+
671
+ Guidelines:
672
+ - Intelligently merge new insights into existing sections
673
+ - Update or refine existing rules if patterns have evolved
674
+ - Remove outdated information
675
+ - Keep it concise - no code snippets unless absolutely necessary
676
+ - Maintain the file's existing structure and tone
677
+ - Focus on patterns, conventions, and learnings - not changelogs
678
+
679
+ CURRENT FILE CONTENT:
680
+ ${currentContent}
681
+
682
+ RECENT CONTEXT:
683
+ ${context}
684
+
685
+ Output the complete updated file, or NO_UPDATE if nothing significant to add:`.replaceAll("\"", String.raw`\"`)}" -s`, {
686
+ cwd: projectRoot,
687
+ encoding: "utf-8",
688
+ stdio: "pipe",
689
+ timeout: 9e4
690
+ }).trim();
691
+ if (output === "NO_UPDATE" || output.length === 0 || output === currentContent) return null;
692
+ return output;
693
+ } catch {
694
+ return null;
695
+ }
696
+ }
697
+ /**
698
+ * Run command - update agent files with extracted patterns and learnings
699
+ */
700
+ async function runCommand() {
701
+ const gitRoot = findGitRoot(process.cwd());
702
+ if (!gitRoot) {
703
+ logger.error("Not inside a git repository");
704
+ process.exit(1);
705
+ }
706
+ const config = loadConfig(gitRoot);
707
+ if (!config) {
708
+ logger.error("No agent-watch configuration found. Run 'agent-watch init' first.");
709
+ process.exit(1);
710
+ }
711
+ if (config.agentFiles.length === 0) {
712
+ logger.warn("No agent files configured");
713
+ return;
714
+ }
715
+ const contextParts = [];
716
+ if (config.watchFileChanges) {
717
+ const modifiedFiles = getModifiedFiles();
718
+ if (shouldSkipAnalysis(modifiedFiles)) {
719
+ logger.info("Skipping analysis - only config/documentation files changed");
720
+ return;
721
+ }
722
+ const commitMessage = getCommitMessage();
723
+ const diffStats = getGitDiffStats();
724
+ if (modifiedFiles.length > 0) {
725
+ const fileContext = [
726
+ commitMessage ? `Commit: ${commitMessage}` : "",
727
+ `Files changed: ${modifiedFiles.join(", ")}`,
728
+ diffStats ? `Stats:\n${diffStats}` : ""
729
+ ].filter(Boolean).join("\n");
730
+ contextParts.push(fileContext);
731
+ }
732
+ }
733
+ if (config.includeChatSession) {
734
+ const sessionContext = processNewSessions(gitRoot);
735
+ if (sessionContext) contextParts.push(`Conversations:\n${sessionContext}`);
736
+ }
737
+ if (contextParts.length === 0) {
738
+ logger.info("No new context to analyze");
739
+ return;
740
+ }
741
+ const context = contextParts.join("\n\n");
742
+ logger.step("Analyzing context for patterns and rules...");
743
+ let updatedCount = 0;
744
+ for (const filePath of config.agentFiles) {
745
+ const fullPath = join(gitRoot, filePath);
746
+ const updatedContent = updateAgentFileWithCopilot(gitRoot, context, filePath, readFileSync(fullPath, "utf-8"));
747
+ if (updatedContent) {
748
+ writeFileSync(fullPath, updatedContent, "utf-8");
749
+ logger.success(`Updated ${filePath}`);
750
+ updatedCount++;
751
+ }
752
+ }
753
+ if (updatedCount > 0) logger.success(`Updated ${updatedCount} agent file(s) with new insights`);
754
+ else logger.info("No significant patterns found to update");
755
+ }
756
+
757
+ //#endregion
758
+ //#region src/cli.ts
759
+ const { version } = createRequire(import.meta.url)("../package.json");
760
+ program.name("agent-watch").description("Keep your AI agent configuration files in sync with your codebase").version(version);
761
+ program.command("init").description("Initialize agent-watch in the current project").action(async () => {
762
+ await initCommand();
763
+ });
764
+ program.command("run").description("Update agent files with recent changes and chat sessions").action(async () => {
765
+ await runCommand();
766
+ });
767
+ program.parse();
768
+
769
+ //#endregion
770
+ export { };
771
+ //# sourceMappingURL=cli.js.map