@cluesmith/codev 1.5.10 → 1.5.11

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 (45) hide show
  1. package/dist/agent-farm/commands/architect.d.ts.map +1 -1
  2. package/dist/agent-farm/commands/architect.js +5 -20
  3. package/dist/agent-farm/commands/architect.js.map +1 -1
  4. package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
  5. package/dist/agent-farm/commands/spawn.js +35 -22
  6. package/dist/agent-farm/commands/spawn.js.map +1 -1
  7. package/dist/agent-farm/commands/start.d.ts.map +1 -1
  8. package/dist/agent-farm/commands/start.js +35 -21
  9. package/dist/agent-farm/commands/start.js.map +1 -1
  10. package/dist/agent-farm/servers/dashboard-server.js +5 -53
  11. package/dist/agent-farm/servers/dashboard-server.js.map +1 -1
  12. package/dist/agent-farm/servers/open-server.js +2 -11
  13. package/dist/agent-farm/servers/open-server.js.map +1 -1
  14. package/dist/agent-farm/servers/tower-server.js +2 -54
  15. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  16. package/dist/agent-farm/utils/roles.d.ts +32 -0
  17. package/dist/agent-farm/utils/roles.d.ts.map +1 -0
  18. package/dist/agent-farm/utils/roles.js +43 -0
  19. package/dist/agent-farm/utils/roles.js.map +1 -0
  20. package/dist/agent-farm/utils/server-utils.d.ts +24 -0
  21. package/dist/agent-farm/utils/server-utils.d.ts.map +1 -0
  22. package/dist/agent-farm/utils/server-utils.js +66 -0
  23. package/dist/agent-farm/utils/server-utils.js.map +1 -0
  24. package/dist/commands/adopt.d.ts.map +1 -1
  25. package/dist/commands/adopt.js +37 -157
  26. package/dist/commands/adopt.js.map +1 -1
  27. package/dist/commands/init.d.ts.map +1 -1
  28. package/dist/commands/init.js +26 -138
  29. package/dist/commands/init.js.map +1 -1
  30. package/dist/lib/cli-prompts.d.ts +20 -0
  31. package/dist/lib/cli-prompts.d.ts.map +1 -0
  32. package/dist/lib/cli-prompts.js +51 -0
  33. package/dist/lib/cli-prompts.js.map +1 -0
  34. package/dist/lib/scaffold.d.ts +81 -0
  35. package/dist/lib/scaffold.d.ts.map +1 -0
  36. package/dist/lib/scaffold.js +189 -0
  37. package/dist/lib/scaffold.js.map +1 -0
  38. package/package.json +1 -1
  39. package/templates/dashboard/js/activity.js +4 -130
  40. package/templates/dashboard/js/dialogs.js +4 -49
  41. package/templates/dashboard/js/files.js +2 -29
  42. package/templates/dashboard/js/main.js +2 -31
  43. package/templates/dashboard/js/tabs.js +6 -30
  44. package/templates/dashboard/js/utils.js +199 -0
  45. package/templates/open.html +8 -8
@@ -0,0 +1,20 @@
1
+ /**
2
+ * CLI prompt utilities
3
+ * Extracted from init.ts and adopt.ts to eliminate duplication
4
+ * (Maintenance Run 0004)
5
+ */
6
+ /**
7
+ * Prompt user for text input
8
+ * @param question - The question to display
9
+ * @param defaultValue - Optional default value shown in brackets
10
+ * @returns The user's input or the default value
11
+ */
12
+ export declare function prompt(question: string, defaultValue?: string): Promise<string>;
13
+ /**
14
+ * Prompt for yes/no confirmation
15
+ * @param question - The question to display
16
+ * @param defaultYes - Whether 'yes' is the default (true) or 'no' is (false)
17
+ * @returns true if user confirmed, false otherwise
18
+ */
19
+ export declare function confirm(question: string, defaultYes?: boolean): Promise<boolean>;
20
+ //# sourceMappingURL=cli-prompts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-prompts.d.ts","sourceRoot":"","sources":["../../src/lib/cli-prompts.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;;;;GAKG;AACH,wBAAsB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAarF;AAED;;;;;GAKG;AACH,wBAAsB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,UAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAkBnF"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * CLI prompt utilities
3
+ * Extracted from init.ts and adopt.ts to eliminate duplication
4
+ * (Maintenance Run 0004)
5
+ */
6
+ import * as readline from 'node:readline';
7
+ /**
8
+ * Prompt user for text input
9
+ * @param question - The question to display
10
+ * @param defaultValue - Optional default value shown in brackets
11
+ * @returns The user's input or the default value
12
+ */
13
+ export async function prompt(question, defaultValue) {
14
+ const rl = readline.createInterface({
15
+ input: process.stdin,
16
+ output: process.stdout,
17
+ });
18
+ return new Promise((resolve) => {
19
+ const promptText = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
20
+ rl.question(promptText, (answer) => {
21
+ rl.close();
22
+ resolve(answer.trim() || defaultValue || '');
23
+ });
24
+ });
25
+ }
26
+ /**
27
+ * Prompt for yes/no confirmation
28
+ * @param question - The question to display
29
+ * @param defaultYes - Whether 'yes' is the default (true) or 'no' is (false)
30
+ * @returns true if user confirmed, false otherwise
31
+ */
32
+ export async function confirm(question, defaultYes = true) {
33
+ const rl = readline.createInterface({
34
+ input: process.stdin,
35
+ output: process.stdout,
36
+ });
37
+ return new Promise((resolve) => {
38
+ const hint = defaultYes ? '[Y/n]' : '[y/N]';
39
+ rl.question(`${question} ${hint}: `, (answer) => {
40
+ rl.close();
41
+ const normalized = answer.trim().toLowerCase();
42
+ if (normalized === '') {
43
+ resolve(defaultYes);
44
+ }
45
+ else {
46
+ resolve(normalized === 'y' || normalized === 'yes');
47
+ }
48
+ });
49
+ });
50
+ }
51
+ //# sourceMappingURL=cli-prompts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-prompts.js","sourceRoot":"","sources":["../../src/lib/cli-prompts.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,QAAgB,EAAE,YAAqB;IAClE,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;QAClC,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAC;IAEH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,UAAU,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,QAAQ,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,QAAQ,IAAI,CAAC;QACtF,EAAE,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE;YACjC,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,YAAY,IAAI,EAAE,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,QAAgB,EAAE,UAAU,GAAG,IAAI;IAC/D,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;QAClC,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAC;IAEH,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;QAC5C,EAAE,CAAC,QAAQ,CAAC,GAAG,QAAQ,IAAI,IAAI,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE;YAC9C,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAC/C,IAAI,UAAU,KAAK,EAAE,EAAE,CAAC;gBACtB,OAAO,CAAC,UAAU,CAAC,CAAC;YACtB,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,KAAK,CAAC,CAAC;YACtD,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Scaffold utilities for codev init and adopt commands
3
+ * Extracted to eliminate duplication (Maintenance Run 0004)
4
+ */
5
+ /**
6
+ * Standard gitignore entries for codev projects
7
+ */
8
+ export declare const CODEV_GITIGNORE_ENTRIES = "# Codev\n.agent-farm/\n.consult/\ncodev/.update-hashes.json\n.builders/\n";
9
+ /**
10
+ * Full gitignore content for new projects
11
+ */
12
+ export declare const FULL_GITIGNORE_CONTENT = "# Codev\n.agent-farm/\n.consult/\ncodev/.update-hashes.json\n.builders/\n\n# Dependencies\nnode_modules/\n\n# Build output\ndist/\n\n# OS files\n.DS_Store\n*.swp\n*.swo\n";
13
+ interface CreateUserDirsOptions {
14
+ skipExisting?: boolean;
15
+ }
16
+ interface CreateUserDirsResult {
17
+ created: string[];
18
+ skipped: string[];
19
+ }
20
+ /**
21
+ * Create user data directories (specs, plans, reviews) with .gitkeep files
22
+ */
23
+ export declare function createUserDirs(targetDir: string, options?: CreateUserDirsOptions): CreateUserDirsResult;
24
+ interface CopyProjectlistOptions {
25
+ skipExisting?: boolean;
26
+ }
27
+ interface CopyProjectlistResult {
28
+ copied: boolean;
29
+ skipped?: boolean;
30
+ usedFallback?: boolean;
31
+ }
32
+ /**
33
+ * Copy projectlist.md from skeleton template, with inline fallback
34
+ */
35
+ export declare function copyProjectlist(targetDir: string, skeletonDir: string, options?: CopyProjectlistOptions): CopyProjectlistResult;
36
+ interface CopyProjectlistArchiveResult {
37
+ copied: boolean;
38
+ skipped?: boolean;
39
+ templateNotFound?: boolean;
40
+ }
41
+ /**
42
+ * Copy projectlist-archive.md from skeleton template
43
+ */
44
+ export declare function copyProjectlistArchive(targetDir: string, skeletonDir: string, options?: CopyProjectlistOptions): CopyProjectlistArchiveResult;
45
+ interface CopyResourceTemplatesOptions {
46
+ skipExisting?: boolean;
47
+ }
48
+ interface CopyResourceTemplatesResult {
49
+ copied: string[];
50
+ skipped: string[];
51
+ }
52
+ /**
53
+ * Copy resource templates (lessons-learned.md, arch.md)
54
+ */
55
+ export declare function copyResourceTemplates(targetDir: string, skeletonDir: string, options?: CopyResourceTemplatesOptions): CopyResourceTemplatesResult;
56
+ interface CopyRootFilesOptions {
57
+ handleConflicts?: boolean;
58
+ }
59
+ interface CopyRootFilesResult {
60
+ copied: string[];
61
+ conflicts: string[];
62
+ }
63
+ /**
64
+ * Copy root files (CLAUDE.md, AGENTS.md) with project name substitution
65
+ */
66
+ export declare function copyRootFiles(targetDir: string, skeletonDir: string, projectName: string, options?: CopyRootFilesOptions): CopyRootFilesResult;
67
+ /**
68
+ * Create a new .gitignore file with full content (for init)
69
+ */
70
+ export declare function createGitignore(targetDir: string): void;
71
+ interface UpdateGitignoreResult {
72
+ updated: boolean;
73
+ created: boolean;
74
+ alreadyPresent: boolean;
75
+ }
76
+ /**
77
+ * Update existing .gitignore or create if not exists (for adopt)
78
+ */
79
+ export declare function updateGitignore(targetDir: string): UpdateGitignoreResult;
80
+ export {};
81
+ //# sourceMappingURL=scaffold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../src/lib/scaffold.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH;;GAEG;AACH,eAAO,MAAM,uBAAuB,8EAKnC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,sBAAsB,+KAWlC,CAAC;AA0BF,UAAU,qBAAqB;IAC7B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,UAAU,oBAAoB;IAC5B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE,qBAA0B,GAClC,oBAAoB,CAkBtB;AAED,UAAU,sBAAsB;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,UAAU,qBAAqB;IAC7B,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,sBAA2B,GACnC,qBAAqB,CAiBvB;AAED,UAAU,4BAA4B;IACpC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,sBAA2B,GACnC,4BAA4B,CAe9B;AAED,UAAU,4BAA4B;IACpC,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,UAAU,2BAA2B;IACnC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,4BAAiC,GACzC,2BAA2B,CA4B7B;AAED,UAAU,oBAAoB;IAC5B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,UAAU,mBAAmB;IAC3B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,oBAAyB,GACjC,mBAAmB,CA+BrB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAGvD;AAED,UAAU,qBAAqB;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,qBAAqB,CAexE"}
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Scaffold utilities for codev init and adopt commands
3
+ * Extracted to eliminate duplication (Maintenance Run 0004)
4
+ */
5
+ import * as fs from 'node:fs';
6
+ import * as path from 'node:path';
7
+ /**
8
+ * Standard gitignore entries for codev projects
9
+ */
10
+ export const CODEV_GITIGNORE_ENTRIES = `# Codev
11
+ .agent-farm/
12
+ .consult/
13
+ codev/.update-hashes.json
14
+ .builders/
15
+ `;
16
+ /**
17
+ * Full gitignore content for new projects
18
+ */
19
+ export const FULL_GITIGNORE_CONTENT = `${CODEV_GITIGNORE_ENTRIES}
20
+ # Dependencies
21
+ node_modules/
22
+
23
+ # Build output
24
+ dist/
25
+
26
+ # OS files
27
+ .DS_Store
28
+ *.swp
29
+ *.swo
30
+ `;
31
+ /**
32
+ * Inline fallback template for projectlist.md
33
+ */
34
+ const PROJECTLIST_FALLBACK = `# Project List
35
+
36
+ Track all projects here. See codev documentation for status values.
37
+
38
+ \`\`\`yaml
39
+ projects:
40
+ - id: "0001"
41
+ title: "Example Project"
42
+ summary: "Brief description"
43
+ status: conceived
44
+ priority: medium
45
+ files:
46
+ spec: null
47
+ plan: null
48
+ review: null
49
+ dependencies: []
50
+ tags: []
51
+ notes: "Replace with your first project"
52
+ \`\`\`
53
+ `;
54
+ /**
55
+ * Create user data directories (specs, plans, reviews) with .gitkeep files
56
+ */
57
+ export function createUserDirs(targetDir, options = {}) {
58
+ const { skipExisting = false } = options;
59
+ const userDirs = ['specs', 'plans', 'reviews'];
60
+ const created = [];
61
+ const skipped = [];
62
+ for (const dir of userDirs) {
63
+ const dirPath = path.join(targetDir, 'codev', dir);
64
+ if (skipExisting && fs.existsSync(dirPath)) {
65
+ skipped.push(dir);
66
+ continue;
67
+ }
68
+ fs.mkdirSync(dirPath, { recursive: true });
69
+ fs.writeFileSync(path.join(dirPath, '.gitkeep'), '');
70
+ created.push(dir);
71
+ }
72
+ return { created, skipped };
73
+ }
74
+ /**
75
+ * Copy projectlist.md from skeleton template, with inline fallback
76
+ */
77
+ export function copyProjectlist(targetDir, skeletonDir, options = {}) {
78
+ const { skipExisting = false } = options;
79
+ const projectlistPath = path.join(targetDir, 'codev', 'projectlist.md');
80
+ if (skipExisting && fs.existsSync(projectlistPath)) {
81
+ return { copied: false, skipped: true };
82
+ }
83
+ const templatePath = path.join(skeletonDir, 'templates', 'projectlist.md');
84
+ if (fs.existsSync(templatePath)) {
85
+ fs.copyFileSync(templatePath, projectlistPath);
86
+ return { copied: true };
87
+ }
88
+ // Fallback to inline template
89
+ fs.writeFileSync(projectlistPath, PROJECTLIST_FALLBACK);
90
+ return { copied: true, usedFallback: true };
91
+ }
92
+ /**
93
+ * Copy projectlist-archive.md from skeleton template
94
+ */
95
+ export function copyProjectlistArchive(targetDir, skeletonDir, options = {}) {
96
+ const { skipExisting = false } = options;
97
+ const archivePath = path.join(targetDir, 'codev', 'projectlist-archive.md');
98
+ if (skipExisting && fs.existsSync(archivePath)) {
99
+ return { copied: false, skipped: true };
100
+ }
101
+ const templatePath = path.join(skeletonDir, 'templates', 'projectlist-archive.md');
102
+ if (!fs.existsSync(templatePath)) {
103
+ return { copied: false, templateNotFound: true };
104
+ }
105
+ fs.copyFileSync(templatePath, archivePath);
106
+ return { copied: true };
107
+ }
108
+ /**
109
+ * Copy resource templates (lessons-learned.md, arch.md)
110
+ */
111
+ export function copyResourceTemplates(targetDir, skeletonDir, options = {}) {
112
+ const { skipExisting = false } = options;
113
+ const resourcesDir = path.join(targetDir, 'codev', 'resources');
114
+ const copied = [];
115
+ const skipped = [];
116
+ // Ensure resources directory exists
117
+ if (!fs.existsSync(resourcesDir)) {
118
+ fs.mkdirSync(resourcesDir, { recursive: true });
119
+ }
120
+ const templates = ['lessons-learned.md', 'arch.md'];
121
+ for (const template of templates) {
122
+ const destPath = path.join(resourcesDir, template);
123
+ const srcPath = path.join(skeletonDir, 'templates', template);
124
+ if (skipExisting && fs.existsSync(destPath)) {
125
+ skipped.push(template);
126
+ continue;
127
+ }
128
+ if (fs.existsSync(srcPath)) {
129
+ fs.copyFileSync(srcPath, destPath);
130
+ copied.push(template);
131
+ }
132
+ }
133
+ return { copied, skipped };
134
+ }
135
+ /**
136
+ * Copy root files (CLAUDE.md, AGENTS.md) with project name substitution
137
+ */
138
+ export function copyRootFiles(targetDir, skeletonDir, projectName, options = {}) {
139
+ const { handleConflicts = false } = options;
140
+ const copied = [];
141
+ const conflicts = [];
142
+ const rootFiles = ['CLAUDE.md', 'AGENTS.md'];
143
+ for (const file of rootFiles) {
144
+ const srcPath = path.join(skeletonDir, 'templates', file);
145
+ const destPath = path.join(targetDir, file);
146
+ if (!fs.existsSync(srcPath)) {
147
+ continue;
148
+ }
149
+ const content = fs.readFileSync(srcPath, 'utf-8')
150
+ .replace(/\{\{PROJECT_NAME\}\}/g, projectName);
151
+ if (fs.existsSync(destPath)) {
152
+ if (handleConflicts) {
153
+ // Create .codev-new for merge
154
+ fs.writeFileSync(destPath + '.codev-new', content);
155
+ conflicts.push(file);
156
+ }
157
+ // Skip if exists and not handling conflicts
158
+ }
159
+ else {
160
+ fs.writeFileSync(destPath, content);
161
+ copied.push(file);
162
+ }
163
+ }
164
+ return { copied, conflicts };
165
+ }
166
+ /**
167
+ * Create a new .gitignore file with full content (for init)
168
+ */
169
+ export function createGitignore(targetDir) {
170
+ const gitignorePath = path.join(targetDir, '.gitignore');
171
+ fs.writeFileSync(gitignorePath, FULL_GITIGNORE_CONTENT);
172
+ }
173
+ /**
174
+ * Update existing .gitignore or create if not exists (for adopt)
175
+ */
176
+ export function updateGitignore(targetDir) {
177
+ const gitignorePath = path.join(targetDir, '.gitignore');
178
+ if (!fs.existsSync(gitignorePath)) {
179
+ fs.writeFileSync(gitignorePath, CODEV_GITIGNORE_ENTRIES.trim() + '\n');
180
+ return { updated: false, created: true, alreadyPresent: false };
181
+ }
182
+ const existing = fs.readFileSync(gitignorePath, 'utf-8');
183
+ if (existing.includes('.agent-farm/')) {
184
+ return { updated: false, created: false, alreadyPresent: true };
185
+ }
186
+ fs.appendFileSync(gitignorePath, '\n' + CODEV_GITIGNORE_ENTRIES);
187
+ return { updated: true, created: false, alreadyPresent: false };
188
+ }
189
+ //# sourceMappingURL=scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/lib/scaffold.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cluesmith/codev",
3
- "version": "1.5.10",
3
+ "version": "1.5.11",
4
4
  "description": "Codev CLI - AI-assisted software development framework",
5
5
  "type": "module",
6
6
  "bin": {
@@ -51,143 +51,17 @@ async function renderActivityTab() {
51
51
  }
52
52
 
53
53
  // Render activity tab content
54
+ // Uses shared renderActivityContentHtml from utils.js (Maintenance Run 0004)
54
55
  function renderActivityTabContent(data) {
55
56
  const content = document.getElementById('tab-content');
56
-
57
- if (data.commits.length === 0 && data.prs.length === 0 && data.builders.length === 0) {
58
- content.innerHTML = `
59
- <div class="activity-tab-container">
60
- <div class="activity-empty">
61
- <p>No activity recorded today</p>
62
- <p style="font-size: 12px; margin-top: 8px;">Make some commits or create PRs to see your daily summary!</p>
63
- </div>
64
- </div>
65
- `;
66
- return;
67
- }
68
-
69
- const hours = Math.floor(data.timeTracking.activeMinutes / 60);
70
- const mins = data.timeTracking.activeMinutes % 60;
71
- const uniqueBranches = new Set(data.commits.map(c => c.branch)).size;
72
- const mergedPrs = data.prs.filter(p => p.state === 'MERGED').length;
73
-
74
- const formatTime = (isoString) => {
75
- if (!isoString) return '--';
76
- const date = new Date(isoString);
77
- return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
78
- };
79
-
80
- let html = '<div class="activity-tab-container"><div class="activity-summary">';
81
-
82
- if (data.aiSummary) {
83
- html += `<div class="activity-ai-summary">${escapeHtml(data.aiSummary)}</div>`;
84
- }
85
-
86
- html += `
87
- <div class="activity-section">
88
- <h4>Activity</h4>
89
- <ul>
90
- <li>${data.commits.length} commits across ${uniqueBranches} branch${uniqueBranches !== 1 ? 'es' : ''}</li>
91
- <li>${data.files.length} files modified</li>
92
- <li>${data.prs.length} PR${data.prs.length !== 1 ? 's' : ''} created${mergedPrs > 0 ? `, ${mergedPrs} merged` : ''}</li>
93
- </ul>
94
- </div>
95
- `;
96
-
97
- if (data.projectChanges && data.projectChanges.length > 0) {
98
- html += `
99
- <div class="activity-section">
100
- <h4>Projects Touched</h4>
101
- <ul>
102
- ${data.projectChanges.map(p => `<li>${escapeHtml(p.id)}: ${escapeHtml(p.title)} (${escapeHtml(p.oldStatus)} → ${escapeHtml(p.newStatus)})</li>`).join('')}
103
- </ul>
104
- </div>
105
- `;
106
- }
107
-
108
- html += `
109
- <div class="activity-section">
110
- <h4>Time</h4>
111
- <p><span class="activity-time-value">~${hours}h ${mins}m</span> active time</p>
112
- <p>First activity: ${formatTime(data.timeTracking.firstActivity)}</p>
113
- <p>Last activity: ${formatTime(data.timeTracking.lastActivity)}</p>
114
- </div>
115
- `;
116
-
117
- html += `
118
- <div class="activity-actions">
119
- <button class="btn" onclick="copyActivityToClipboard()">Copy to Clipboard</button>
120
- </div>
121
- `;
122
-
123
- html += '</div></div>';
124
- content.innerHTML = html;
57
+ content.innerHTML = renderActivityContentHtml(data, { isTab: true });
125
58
  }
126
59
 
127
60
  // Render activity summary content (for modal)
61
+ // Uses shared renderActivityContentHtml from utils.js (Maintenance Run 0004)
128
62
  function renderActivitySummary(data) {
129
63
  const content = document.getElementById('activity-content');
130
-
131
- if (data.commits.length === 0 && data.prs.length === 0 && data.builders.length === 0) {
132
- content.innerHTML = `
133
- <div class="activity-empty">
134
- <p>No activity recorded today</p>
135
- <p style="font-size: 12px; margin-top: 8px;">Make some commits or create PRs to see your daily summary!</p>
136
- </div>
137
- `;
138
- return;
139
- }
140
-
141
- const hours = Math.floor(data.timeTracking.activeMinutes / 60);
142
- const mins = data.timeTracking.activeMinutes % 60;
143
- const uniqueBranches = new Set(data.commits.map(c => c.branch)).size;
144
- const mergedPrs = data.prs.filter(p => p.state === 'MERGED').length;
145
-
146
- const formatTime = (isoString) => {
147
- if (!isoString) return '--';
148
- const date = new Date(isoString);
149
- return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
150
- };
151
-
152
- let html = '<div class="activity-summary">';
153
-
154
- if (data.aiSummary) {
155
- html += `<div class="activity-ai-summary">${escapeHtml(data.aiSummary)}</div>`;
156
- }
157
-
158
- html += `
159
- <div class="activity-section">
160
- <h4>Activity</h4>
161
- <ul>
162
- <li>${data.commits.length} commits across ${uniqueBranches} branch${uniqueBranches !== 1 ? 'es' : ''}</li>
163
- <li>${data.files.length} files modified</li>
164
- <li>${data.prs.length} PR${data.prs.length !== 1 ? 's' : ''} created${mergedPrs > 0 ? `, ${mergedPrs} merged` : ''}</li>
165
- </ul>
166
- </div>
167
- `;
168
-
169
- if (data.projectChanges && data.projectChanges.length > 0) {
170
- html += `
171
- <div class="activity-section">
172
- <h4>Projects Touched</h4>
173
- <ul>
174
- ${data.projectChanges.map(p => `<li>${escapeHtml(p.id)}: ${escapeHtml(p.title)} (${escapeHtml(p.oldStatus)} → ${escapeHtml(p.newStatus)})</li>`).join('')}
175
- </ul>
176
- </div>
177
- `;
178
- }
179
-
180
- html += `
181
- <div class="activity-section">
182
- <h4>Time</h4>
183
- <p><span class="activity-time-value">~${hours}h ${mins}m</span> active time</p>
184
- <p>First activity: ${formatTime(data.timeTracking.firstActivity)}</p>
185
- <p>Last activity: ${formatTime(data.timeTracking.lastActivity)}</p>
186
- </div>
187
- `;
188
-
189
- html += '</div>';
190
- content.innerHTML = html;
64
+ content.innerHTML = renderActivityContentHtml(data, { isTab: false });
191
65
  }
192
66
 
193
67
  // Close activity modal
@@ -108,38 +108,9 @@ function hideContextMenu() {
108
108
  }
109
109
 
110
110
  // Handle keyboard navigation in context menu
111
+ // Uses shared handleMenuKeydown from utils.js (Maintenance Run 0004)
111
112
  function handleContextMenuKeydown(event) {
112
- const menu = document.getElementById('context-menu');
113
- const items = Array.from(menu.querySelectorAll('.context-menu-item'));
114
- const currentIndex = items.findIndex(item => item === document.activeElement);
115
-
116
- switch (event.key) {
117
- case 'ArrowDown':
118
- event.preventDefault();
119
- const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
120
- items[nextIndex].focus();
121
- break;
122
- case 'ArrowUp':
123
- event.preventDefault();
124
- const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
125
- items[prevIndex].focus();
126
- break;
127
- case 'Enter':
128
- case ' ':
129
- event.preventDefault();
130
- const actionName = event.target.dataset.action;
131
- if (actionName && typeof window[actionName] === 'function') {
132
- window[actionName]();
133
- }
134
- break;
135
- case 'Escape':
136
- event.preventDefault();
137
- hideContextMenu();
138
- break;
139
- case 'Tab':
140
- hideContextMenu();
141
- break;
142
- }
113
+ handleMenuKeydown(event, 'context-menu', 'context-menu-item', hideContextMenu);
143
114
  }
144
115
 
145
116
  function closeActiveTab() {
@@ -186,27 +157,11 @@ function setFilePath(path) {
186
157
  document.getElementById('file-path-input').focus();
187
158
  }
188
159
 
160
+ // Uses shared openFileTab from utils.js (Maintenance Run 0004)
189
161
  async function openFile() {
190
162
  const path = document.getElementById('file-path-input').value.trim();
191
163
  if (!path) return;
192
-
193
- try {
194
- const response = await fetch('/api/tabs/file', {
195
- method: 'POST',
196
- headers: { 'Content-Type': 'application/json' },
197
- body: JSON.stringify({ path })
198
- });
199
-
200
- if (!response.ok) {
201
- throw new Error(await response.text());
202
- }
203
-
204
- hideFileDialog();
205
- await refresh();
206
- showToast(`Opened ${path}`, 'success');
207
- } catch (err) {
208
- showToast('Failed to open file: ' + err.message, 'error');
209
- }
164
+ await openFileTab(path, { onSuccess: hideFileDialog });
210
165
  }
211
166
 
212
167
  // Spawn worktree builder
@@ -181,36 +181,9 @@ async function refreshFilesTree() {
181
181
  }
182
182
 
183
183
  // Open file from tree click
184
+ // Uses shared openFileTab from utils.js (Maintenance Run 0004)
184
185
  async function openFileFromTree(filePath) {
185
- try {
186
- const existingTab = tabs.find(t => t.type === 'file' && t.path === filePath);
187
- if (existingTab) {
188
- selectTab(existingTab.id);
189
- refreshFileTab(existingTab.id);
190
- return;
191
- }
192
-
193
- const response = await fetch('/api/tabs/file', {
194
- method: 'POST',
195
- headers: { 'Content-Type': 'application/json' },
196
- body: JSON.stringify({ path: filePath })
197
- });
198
-
199
- if (!response.ok) {
200
- throw new Error(await response.text());
201
- }
202
-
203
- await refresh();
204
-
205
- const newTab = tabs.find(t => t.type === 'file' && t.path === filePath);
206
- if (newTab) {
207
- selectTab(newTab.id);
208
- }
209
-
210
- showToast(`Opened ${getFileName(filePath)}`, 'success');
211
- } catch (err) {
212
- showToast('Failed to open file: ' + err.message, 'error');
213
- }
186
+ await openFileTab(filePath, { showSwitchToast: false });
214
187
  }
215
188
 
216
189
  // ========================================
@@ -26,38 +26,9 @@ function setupBroadcastChannel() {
26
26
  }
27
27
 
28
28
  // Open a file from a BroadcastChannel message
29
+ // Uses shared openFileTab from utils.js (Maintenance Run 0004)
29
30
  async function openFileFromMessage(filePath, lineNumber) {
30
- try {
31
- const existingTab = tabs.find(t => t.type === 'file' && t.path === filePath);
32
- if (existingTab) {
33
- selectTab(existingTab.id);
34
- refreshFileTab(existingTab.id);
35
- showToast(`Switched to ${getFileName(filePath)}`, 'success');
36
- return;
37
- }
38
-
39
- const response = await fetch('/api/tabs/file', {
40
- method: 'POST',
41
- headers: { 'Content-Type': 'application/json' },
42
- body: JSON.stringify({ path: filePath })
43
- });
44
-
45
- if (!response.ok) {
46
- throw new Error(await response.text());
47
- }
48
-
49
- const result = await response.json();
50
- await refresh();
51
-
52
- const newTab = tabs.find(t => t.type === 'file' && (t.path === filePath || t.annotationId === result.id));
53
- if (newTab) {
54
- selectTab(newTab.id);
55
- }
56
-
57
- showToast(`Opened ${getFileName(filePath)}${lineNumber ? ':' + lineNumber : ''}`, 'success');
58
- } catch (err) {
59
- showToast('Failed to open file: ' + err.message, 'error');
60
- }
31
+ await openFileTab(filePath, { lineNumber });
61
32
  }
62
33
 
63
34
  // Refresh state from API