vibe-forge 0.4.0 → 0.8.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 (129) hide show
  1. package/.claude/commands/clear-attention.md +63 -63
  2. package/.claude/commands/compact-context.md +52 -0
  3. package/.claude/commands/configure-vcs.md +102 -102
  4. package/.claude/commands/forge.md +218 -171
  5. package/.claude/commands/need-help.md +77 -77
  6. package/.claude/commands/update-status.md +64 -64
  7. package/.claude/commands/worker-loop.md +106 -106
  8. package/.claude/hooks/worker-loop.js +217 -187
  9. package/.claude/scripts/setup-worker-loop.sh +45 -45
  10. package/.claude/settings.json +89 -0
  11. package/LICENSE +21 -21
  12. package/README.md +253 -232
  13. package/agents/aegis/personality.md +303 -269
  14. package/agents/anvil/personality.md +278 -240
  15. package/agents/architect/personality.md +260 -234
  16. package/agents/crucible/personality.md +362 -309
  17. package/agents/crucible-x/personality.md +210 -0
  18. package/agents/ember/personality.md +293 -265
  19. package/agents/flux/personality.md +248 -0
  20. package/agents/furnace/personality.md +342 -291
  21. package/agents/herald/personality.md +249 -247
  22. package/agents/loki/personality.md +108 -0
  23. package/agents/oracle/personality.md +284 -0
  24. package/agents/pixel/personality.md +140 -0
  25. package/agents/planning-hub/personality.md +473 -251
  26. package/agents/scribe/personality.md +253 -251
  27. package/agents/slag/personality.md +268 -0
  28. package/agents/temper/personality.md +270 -0
  29. package/bin/cli.js +372 -325
  30. package/bin/dashboard/api/agents.js +333 -0
  31. package/bin/dashboard/api/dispatch.js +507 -0
  32. package/bin/dashboard/api/tasks.js +416 -0
  33. package/bin/dashboard/public/assets/index-BpHfsx1r.js +2 -0
  34. package/bin/dashboard/public/assets/index-QODv4Zn9.css +1 -0
  35. package/bin/dashboard/public/index.html +14 -0
  36. package/bin/dashboard/server.js +645 -0
  37. package/bin/forge-daemon.sh +477 -851
  38. package/bin/forge-setup.sh +661 -645
  39. package/bin/forge-spawn.sh +164 -164
  40. package/bin/forge.cmd +83 -83
  41. package/bin/forge.sh +566 -387
  42. package/bin/lib/agents.sh +177 -177
  43. package/bin/lib/check-aliases.js +50 -0
  44. package/bin/lib/colors.sh +44 -44
  45. package/bin/lib/config.sh +347 -313
  46. package/bin/lib/constants.sh +241 -206
  47. package/bin/lib/daemon/budgets.sh +107 -0
  48. package/bin/lib/daemon/dependencies.sh +146 -0
  49. package/bin/lib/daemon/display.sh +128 -0
  50. package/bin/lib/daemon/notifications.sh +273 -0
  51. package/bin/lib/daemon/routing.sh +93 -0
  52. package/bin/lib/daemon/state.sh +163 -0
  53. package/bin/lib/daemon/sync.sh +103 -0
  54. package/bin/lib/database.sh +357 -305
  55. package/bin/lib/frontmatter.js +106 -0
  56. package/bin/lib/heimdall-setup.js +113 -0
  57. package/bin/lib/heimdall.js +265 -0
  58. package/bin/lib/json.sh +264 -258
  59. package/bin/lib/terminal.js +452 -446
  60. package/bin/lib/util.sh +126 -126
  61. package/bin/lib/vcs.js +349 -349
  62. package/config/agent-manifest.yaml +237 -243
  63. package/config/agents.json +207 -132
  64. package/config/task-template.md +159 -87
  65. package/config/task-types.yaml +111 -106
  66. package/config/templates/handoff-template.md +40 -0
  67. package/context/agent-overrides/README.md +41 -0
  68. package/context/architecture.md +42 -0
  69. package/context/modern-conventions.md +129 -129
  70. package/context/project-context-template.md +122 -122
  71. package/docs/agents.md +473 -409
  72. package/docs/architecture.md +194 -162
  73. package/docs/commands.md +451 -388
  74. package/docs/security.md +195 -144
  75. package/package.json +77 -50
  76. package/.claude/settings.local.json +0 -33
  77. package/agents/forge-master/capabilities.md +0 -144
  78. package/agents/forge-master/context-template.md +0 -128
  79. package/agents/forge-master/personality.md +0 -138
  80. package/agents/sentinel/personality.md +0 -194
  81. package/context/forge-state.yaml +0 -19
  82. package/docs/TODO.md +0 -150
  83. package/docs/getting-started.md +0 -243
  84. package/docs/npm-publishing.md +0 -95
  85. package/docs/workflows/README.md +0 -32
  86. package/docs/workflows/azure-devops.md +0 -108
  87. package/docs/workflows/bitbucket.md +0 -104
  88. package/docs/workflows/git-only.md +0 -130
  89. package/docs/workflows/gitea.md +0 -168
  90. package/docs/workflows/github.md +0 -103
  91. package/docs/workflows/gitlab.md +0 -105
  92. package/docs/workflows.md +0 -454
  93. package/tasks/completed/ARCH-001-duplicate-agent-config.md +0 -121
  94. package/tasks/completed/ARCH-002-mixed-bash-node-implementation.md +0 -88
  95. package/tasks/completed/ARCH-003-worker-loop-hook-duplication.md +0 -77
  96. package/tasks/completed/ARCH-009-test-organization.md +0 -78
  97. package/tasks/completed/ARCH-011-jq-vs-nodejs-json.md +0 -94
  98. package/tasks/completed/ARCH-012-tmp-files-in-root.md +0 -71
  99. package/tasks/completed/ARCH-013-exit-code-constants.md +0 -65
  100. package/tasks/completed/ARCH-014-sed-incompatibility.md +0 -96
  101. package/tasks/completed/ARCH-015-docs-todo-tracking.md +0 -83
  102. package/tasks/completed/CLEAN-001.md +0 -38
  103. package/tasks/completed/CLEAN-003.md +0 -47
  104. package/tasks/completed/CLEAN-004.md +0 -56
  105. package/tasks/completed/CLEAN-005.md +0 -75
  106. package/tasks/completed/CLEAN-006.md +0 -47
  107. package/tasks/completed/CLEAN-007.md +0 -34
  108. package/tasks/completed/CLEAN-008.md +0 -49
  109. package/tasks/completed/CLEAN-012.md +0 -58
  110. package/tasks/completed/CLEAN-013.md +0 -45
  111. package/tasks/completed/SEC-001-sql-injection-fix.md +0 -58
  112. package/tasks/completed/SEC-002-notification-injection-fix.md +0 -45
  113. package/tasks/completed/SEC-003-eval-injection-fix.md +0 -54
  114. package/tasks/completed/SEC-004-pid-race-condition-fix.md +0 -49
  115. package/tasks/completed/SEC-005-worker-loop-path-fix.md +0 -51
  116. package/tasks/completed/SEC-006-eval-agent-names.md +0 -55
  117. package/tasks/completed/SEC-007-spawn-escaping.md +0 -67
  118. package/tasks/pending/ARCH-004-git-bash-detection-duplication.md +0 -72
  119. package/tasks/pending/ARCH-005-missing-src-directory.md +0 -95
  120. package/tasks/pending/ARCH-006-task-template-location.md +0 -64
  121. package/tasks/pending/ARCH-007-daemon-monolith.md +0 -91
  122. package/tasks/pending/ARCH-008-forge-master-vs-hub.md +0 -81
  123. package/tasks/pending/ARCH-010-missing-index-files.md +0 -84
  124. package/tasks/pending/CLEAN-002.md +0 -29
  125. package/tasks/pending/CLEAN-009.md +0 -31
  126. package/tasks/pending/CLEAN-010.md +0 -30
  127. package/tasks/pending/CLEAN-011.md +0 -30
  128. package/tasks/pending/CLEAN-014.md +0 -32
  129. package/tasks/review/task-001.md +0 -78
package/bin/lib/vcs.js CHANGED
@@ -1,349 +1,349 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Vibe Forge - VCS Detection and Configuration
4
- *
5
- * Detects version control system and platform from project structure.
6
- * Used by forge-setup.sh and /configure-vcs skill.
7
- *
8
- * Usage:
9
- * node vcs.js detect [project-path] Detect VCS type
10
- * node vcs.js get [project-path] Get current VCS config
11
- * node vcs.js set <type> [project-path] Set VCS type manually
12
- * node vcs.js init <type> [project-path] Initialize VCS folders
13
- */
14
-
15
- const fs = require('fs');
16
- const path = require('path');
17
-
18
- // VCS types and their detection patterns
19
- const VCS_TYPES = {
20
- github: {
21
- name: 'GitHub',
22
- detect: ['.github/', '.github/workflows/'],
23
- folders: ['.github/workflows'],
24
- prCommand: 'gh pr create',
25
- ciFile: '.github/workflows/ci.yml',
26
- },
27
- gitlab: {
28
- name: 'GitLab',
29
- detect: ['.gitlab-ci.yml', '.gitlab/'],
30
- folders: ['.gitlab'],
31
- prCommand: 'git push -o merge_request.create',
32
- ciFile: '.gitlab-ci.yml',
33
- },
34
- gitea: {
35
- name: 'Gitea',
36
- detect: ['.gitea/', '.gitea/workflows/'],
37
- folders: ['.gitea/workflows'],
38
- prCommand: 'tea pr create',
39
- ciFile: '.gitea/workflows/ci.yml',
40
- },
41
- bitbucket: {
42
- name: 'Bitbucket',
43
- detect: ['bitbucket-pipelines.yml'],
44
- folders: [],
45
- prCommand: 'Manual PR via Bitbucket UI',
46
- ciFile: 'bitbucket-pipelines.yml',
47
- },
48
- 'azure-devops': {
49
- name: 'Azure DevOps',
50
- detect: ['azure-pipelines.yml', '.azure/'],
51
- folders: ['.azure'],
52
- prCommand: 'az repos pr create',
53
- ciFile: 'azure-pipelines.yml',
54
- },
55
- 'git-only': {
56
- name: 'Git (no platform)',
57
- detect: ['.git/'],
58
- folders: [],
59
- prCommand: null,
60
- ciFile: null,
61
- },
62
- none: {
63
- name: 'No VCS',
64
- detect: [],
65
- folders: [],
66
- prCommand: null,
67
- ciFile: null,
68
- },
69
- };
70
-
71
- /**
72
- * Detect VCS type from project structure
73
- */
74
- function detectVcs(projectPath = process.cwd()) {
75
- const result = {
76
- type: 'none',
77
- autoDetected: true,
78
- hasGit: false,
79
- platform: null,
80
- confidence: 'low',
81
- };
82
-
83
- // Check for .git directory first
84
- const gitDir = path.join(projectPath, '.git');
85
- if (fs.existsSync(gitDir)) {
86
- result.hasGit = true;
87
- result.type = 'git-only';
88
- result.confidence = 'medium';
89
- }
90
-
91
- // Check for platform-specific indicators (in order of specificity)
92
- const platforms = ['github', 'gitlab', 'gitea', 'azure-devops', 'bitbucket'];
93
-
94
- for (const platform of platforms) {
95
- const config = VCS_TYPES[platform];
96
- for (const pattern of config.detect) {
97
- const checkPath = path.join(projectPath, pattern);
98
- if (fs.existsSync(checkPath)) {
99
- result.type = platform;
100
- result.platform = config.name;
101
- result.confidence = 'high';
102
- return result;
103
- }
104
- }
105
- }
106
-
107
- // Try to detect from git remote
108
- if (result.hasGit) {
109
- try {
110
- const gitConfigPath = path.join(gitDir, 'config');
111
- if (fs.existsSync(gitConfigPath)) {
112
- const gitConfig = fs.readFileSync(gitConfigPath, 'utf8');
113
-
114
- if (gitConfig.includes('github.com')) {
115
- result.type = 'github';
116
- result.platform = 'GitHub';
117
- result.confidence = 'medium';
118
- } else if (gitConfig.includes('gitlab.com') || gitConfig.includes('gitlab')) {
119
- result.type = 'gitlab';
120
- result.platform = 'GitLab';
121
- result.confidence = 'medium';
122
- } else if (gitConfig.includes('gitea') || gitConfig.includes('codeberg.org')) {
123
- // Gitea instances (including Codeberg which runs on Gitea)
124
- result.type = 'gitea';
125
- result.platform = 'Gitea';
126
- result.confidence = 'medium';
127
- } else if (gitConfig.includes('bitbucket.org')) {
128
- result.type = 'bitbucket';
129
- result.platform = 'Bitbucket';
130
- result.confidence = 'medium';
131
- } else if (gitConfig.includes('dev.azure.com') || gitConfig.includes('visualstudio.com')) {
132
- result.type = 'azure-devops';
133
- result.platform = 'Azure DevOps';
134
- result.confidence = 'medium';
135
- }
136
- }
137
- } catch (e) {
138
- // Ignore git config read errors
139
- }
140
- }
141
-
142
- return result;
143
- }
144
-
145
- /**
146
- * Get current VCS configuration from forge config
147
- */
148
- function getVcsConfig(projectPath = process.cwd()) {
149
- const configFile = path.join(projectPath, '.forge', 'config.json');
150
-
151
- if (!fs.existsSync(configFile)) {
152
- return null;
153
- }
154
-
155
- try {
156
- const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
157
- return config.vcs || null;
158
- } catch (e) {
159
- return null;
160
- }
161
- }
162
-
163
- /**
164
- * Save VCS configuration to forge config
165
- */
166
- function setVcsConfig(vcsType, projectPath = process.cwd()) {
167
- const configDir = path.join(projectPath, '.forge');
168
- const configFile = path.join(configDir, 'config.json');
169
-
170
- // Ensure directory exists
171
- if (!fs.existsSync(configDir)) {
172
- fs.mkdirSync(configDir, { recursive: true });
173
- }
174
-
175
- // Load existing config or create new
176
- let config = {};
177
- if (fs.existsSync(configFile)) {
178
- try {
179
- config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
180
- } catch (e) {
181
- // Start fresh if invalid
182
- }
183
- }
184
-
185
- // Validate VCS type
186
- if (!VCS_TYPES[vcsType]) {
187
- console.error(`Invalid VCS type: ${vcsType}`);
188
- console.error(`Valid types: ${Object.keys(VCS_TYPES).join(', ')}`);
189
- process.exit(1);
190
- }
191
-
192
- // Update VCS config
193
- config.vcs = {
194
- type: vcsType,
195
- name: VCS_TYPES[vcsType].name,
196
- autoDetected: false,
197
- lastUpdated: new Date().toISOString(),
198
- };
199
-
200
- fs.writeFileSync(configFile, JSON.stringify(config, null, 2) + '\n');
201
-
202
- return config.vcs;
203
- }
204
-
205
- /**
206
- * Initialize VCS folders for the selected platform
207
- */
208
- function initVcsFolders(vcsType, projectPath = process.cwd()) {
209
- if (!VCS_TYPES[vcsType]) {
210
- console.error(`Invalid VCS type: ${vcsType}`);
211
- process.exit(1);
212
- }
213
-
214
- const config = VCS_TYPES[vcsType];
215
- const created = [];
216
-
217
- // Initialize .git if needed and type isn't 'none'
218
- if (vcsType !== 'none') {
219
- const gitDir = path.join(projectPath, '.git');
220
- if (!fs.existsSync(gitDir)) {
221
- // Don't auto-init git, just note it's needed
222
- console.log('Note: Run "git init" to initialize git repository');
223
- }
224
- }
225
-
226
- // Create platform-specific folders
227
- for (const folder of config.folders) {
228
- const folderPath = path.join(projectPath, folder);
229
- if (!fs.existsSync(folderPath)) {
230
- fs.mkdirSync(folderPath, { recursive: true });
231
- created.push(folder);
232
- }
233
- }
234
-
235
- return created;
236
- }
237
-
238
- /**
239
- * Get workflow guidance for the detected VCS type
240
- */
241
- function getWorkflowGuidance(vcsType) {
242
- const config = VCS_TYPES[vcsType];
243
- if (!config) return null;
244
-
245
- return {
246
- type: vcsType,
247
- name: config.name,
248
- prCommand: config.prCommand,
249
- ciFile: config.ciFile,
250
- branchWorkflow: vcsType !== 'none',
251
- hasPullRequests: config.prCommand !== null,
252
- hasCI: config.ciFile !== null,
253
- };
254
- }
255
-
256
- // CLI interface
257
- function main() {
258
- const args = process.argv.slice(2);
259
- const command = args[0] || 'detect';
260
- const arg1 = args[1];
261
- const arg2 = args[2];
262
-
263
- switch (command) {
264
- case 'detect': {
265
- const projectPath = arg1 || process.cwd();
266
- const result = detectVcs(projectPath);
267
- console.log(JSON.stringify(result, null, 2));
268
- break;
269
- }
270
-
271
- case 'get': {
272
- const projectPath = arg1 || process.cwd();
273
- const config = getVcsConfig(projectPath);
274
- if (config) {
275
- console.log(JSON.stringify(config, null, 2));
276
- } else {
277
- console.log('null');
278
- process.exit(1);
279
- }
280
- break;
281
- }
282
-
283
- case 'set': {
284
- const vcsType = arg1;
285
- const projectPath = arg2 || process.cwd();
286
- if (!vcsType) {
287
- console.error('Usage: node vcs.js set <type> [project-path]');
288
- console.error(`Types: ${Object.keys(VCS_TYPES).join(', ')}`);
289
- process.exit(1);
290
- }
291
- const result = setVcsConfig(vcsType, projectPath);
292
- console.log(JSON.stringify(result, null, 2));
293
- break;
294
- }
295
-
296
- case 'init': {
297
- const vcsType = arg1;
298
- const projectPath = arg2 || process.cwd();
299
- if (!vcsType) {
300
- console.error('Usage: node vcs.js init <type> [project-path]');
301
- process.exit(1);
302
- }
303
- const created = initVcsFolders(vcsType, projectPath);
304
- setVcsConfig(vcsType, projectPath);
305
- console.log(JSON.stringify({ type: vcsType, foldersCreated: created }, null, 2));
306
- break;
307
- }
308
-
309
- case 'guidance': {
310
- const vcsType = arg1;
311
- if (!vcsType) {
312
- console.error('Usage: node vcs.js guidance <type>');
313
- process.exit(1);
314
- }
315
- const guidance = getWorkflowGuidance(vcsType);
316
- console.log(JSON.stringify(guidance, null, 2));
317
- break;
318
- }
319
-
320
- case 'types': {
321
- const types = Object.entries(VCS_TYPES).map(([key, val]) => ({
322
- type: key,
323
- name: val.name,
324
- }));
325
- console.log(JSON.stringify(types, null, 2));
326
- break;
327
- }
328
-
329
- default:
330
- console.error(`Unknown command: ${command}`);
331
- console.error('Commands: detect, get, set, init, guidance, types');
332
- process.exit(1);
333
- }
334
- }
335
-
336
- // Export for use as module
337
- module.exports = {
338
- detectVcs,
339
- getVcsConfig,
340
- setVcsConfig,
341
- initVcsFolders,
342
- getWorkflowGuidance,
343
- VCS_TYPES,
344
- };
345
-
346
- // Run CLI if executed directly
347
- if (require.main === module) {
348
- main();
349
- }
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Vibe Forge - VCS Detection and Configuration
4
+ *
5
+ * Detects version control system and platform from project structure.
6
+ * Used by forge-setup.sh and /configure-vcs skill.
7
+ *
8
+ * Usage:
9
+ * node vcs.js detect [project-path] Detect VCS type
10
+ * node vcs.js get [project-path] Get current VCS config
11
+ * node vcs.js set <type> [project-path] Set VCS type manually
12
+ * node vcs.js init <type> [project-path] Initialize VCS folders
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ // VCS types and their detection patterns
19
+ const VCS_TYPES = {
20
+ github: {
21
+ name: 'GitHub',
22
+ detect: ['.github/', '.github/workflows/'],
23
+ folders: ['.github/workflows'],
24
+ prCommand: 'gh pr create',
25
+ ciFile: '.github/workflows/ci.yml',
26
+ },
27
+ gitlab: {
28
+ name: 'GitLab',
29
+ detect: ['.gitlab-ci.yml', '.gitlab/'],
30
+ folders: ['.gitlab'],
31
+ prCommand: 'git push -o merge_request.create',
32
+ ciFile: '.gitlab-ci.yml',
33
+ },
34
+ gitea: {
35
+ name: 'Gitea',
36
+ detect: ['.gitea/', '.gitea/workflows/'],
37
+ folders: ['.gitea/workflows'],
38
+ prCommand: 'tea pr create',
39
+ ciFile: '.gitea/workflows/ci.yml',
40
+ },
41
+ bitbucket: {
42
+ name: 'Bitbucket',
43
+ detect: ['bitbucket-pipelines.yml'],
44
+ folders: [],
45
+ prCommand: 'Manual PR via Bitbucket UI',
46
+ ciFile: 'bitbucket-pipelines.yml',
47
+ },
48
+ 'azure-devops': {
49
+ name: 'Azure DevOps',
50
+ detect: ['azure-pipelines.yml', '.azure/'],
51
+ folders: ['.azure'],
52
+ prCommand: 'az repos pr create',
53
+ ciFile: 'azure-pipelines.yml',
54
+ },
55
+ 'git-only': {
56
+ name: 'Git (no platform)',
57
+ detect: ['.git/'],
58
+ folders: [],
59
+ prCommand: null,
60
+ ciFile: null,
61
+ },
62
+ none: {
63
+ name: 'No VCS',
64
+ detect: [],
65
+ folders: [],
66
+ prCommand: null,
67
+ ciFile: null,
68
+ },
69
+ };
70
+
71
+ /**
72
+ * Detect VCS type from project structure
73
+ */
74
+ function detectVcs(projectPath = process.cwd()) {
75
+ const result = {
76
+ type: 'none',
77
+ autoDetected: true,
78
+ hasGit: false,
79
+ platform: null,
80
+ confidence: 'low',
81
+ };
82
+
83
+ // Check for .git directory first
84
+ const gitDir = path.join(projectPath, '.git');
85
+ if (fs.existsSync(gitDir)) {
86
+ result.hasGit = true;
87
+ result.type = 'git-only';
88
+ result.confidence = 'medium';
89
+ }
90
+
91
+ // Check for platform-specific indicators (in order of specificity)
92
+ const platforms = ['github', 'gitlab', 'gitea', 'azure-devops', 'bitbucket'];
93
+
94
+ for (const platform of platforms) {
95
+ const config = VCS_TYPES[platform];
96
+ for (const pattern of config.detect) {
97
+ const checkPath = path.join(projectPath, pattern);
98
+ if (fs.existsSync(checkPath)) {
99
+ result.type = platform;
100
+ result.platform = config.name;
101
+ result.confidence = 'high';
102
+ return result;
103
+ }
104
+ }
105
+ }
106
+
107
+ // Try to detect from git remote
108
+ if (result.hasGit) {
109
+ try {
110
+ const gitConfigPath = path.join(gitDir, 'config');
111
+ if (fs.existsSync(gitConfigPath)) {
112
+ const gitConfig = fs.readFileSync(gitConfigPath, 'utf8');
113
+
114
+ if (gitConfig.includes('github.com')) {
115
+ result.type = 'github';
116
+ result.platform = 'GitHub';
117
+ result.confidence = 'medium';
118
+ } else if (gitConfig.includes('gitlab.com') || gitConfig.includes('gitlab')) {
119
+ result.type = 'gitlab';
120
+ result.platform = 'GitLab';
121
+ result.confidence = 'medium';
122
+ } else if (gitConfig.includes('gitea') || gitConfig.includes('codeberg.org')) {
123
+ // Gitea instances (including Codeberg which runs on Gitea)
124
+ result.type = 'gitea';
125
+ result.platform = 'Gitea';
126
+ result.confidence = 'medium';
127
+ } else if (gitConfig.includes('bitbucket.org')) {
128
+ result.type = 'bitbucket';
129
+ result.platform = 'Bitbucket';
130
+ result.confidence = 'medium';
131
+ } else if (gitConfig.includes('dev.azure.com') || gitConfig.includes('visualstudio.com')) {
132
+ result.type = 'azure-devops';
133
+ result.platform = 'Azure DevOps';
134
+ result.confidence = 'medium';
135
+ }
136
+ }
137
+ } catch (e) {
138
+ // Ignore git config read errors
139
+ }
140
+ }
141
+
142
+ return result;
143
+ }
144
+
145
+ /**
146
+ * Get current VCS configuration from forge config
147
+ */
148
+ function getVcsConfig(projectPath = process.cwd()) {
149
+ const configFile = path.join(projectPath, '.forge', 'config.json');
150
+
151
+ if (!fs.existsSync(configFile)) {
152
+ return null;
153
+ }
154
+
155
+ try {
156
+ const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
157
+ return config.vcs || null;
158
+ } catch (e) {
159
+ return null;
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Save VCS configuration to forge config
165
+ */
166
+ function setVcsConfig(vcsType, projectPath = process.cwd()) {
167
+ const configDir = path.join(projectPath, '.forge');
168
+ const configFile = path.join(configDir, 'config.json');
169
+
170
+ // Ensure directory exists
171
+ if (!fs.existsSync(configDir)) {
172
+ fs.mkdirSync(configDir, { recursive: true });
173
+ }
174
+
175
+ // Load existing config or create new
176
+ let config = {};
177
+ if (fs.existsSync(configFile)) {
178
+ try {
179
+ config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
180
+ } catch (e) {
181
+ // Start fresh if invalid
182
+ }
183
+ }
184
+
185
+ // Validate VCS type
186
+ if (!VCS_TYPES[vcsType]) {
187
+ console.error(`Invalid VCS type: ${vcsType}`);
188
+ console.error(`Valid types: ${Object.keys(VCS_TYPES).join(', ')}`);
189
+ process.exit(1);
190
+ }
191
+
192
+ // Update VCS config
193
+ config.vcs = {
194
+ type: vcsType,
195
+ name: VCS_TYPES[vcsType].name,
196
+ autoDetected: false,
197
+ lastUpdated: new Date().toISOString(),
198
+ };
199
+
200
+ fs.writeFileSync(configFile, JSON.stringify(config, null, 2) + '\n');
201
+
202
+ return config.vcs;
203
+ }
204
+
205
+ /**
206
+ * Initialize VCS folders for the selected platform
207
+ */
208
+ function initVcsFolders(vcsType, projectPath = process.cwd()) {
209
+ if (!VCS_TYPES[vcsType]) {
210
+ console.error(`Invalid VCS type: ${vcsType}`);
211
+ process.exit(1);
212
+ }
213
+
214
+ const config = VCS_TYPES[vcsType];
215
+ const created = [];
216
+
217
+ // Initialize .git if needed and type isn't 'none'
218
+ if (vcsType !== 'none') {
219
+ const gitDir = path.join(projectPath, '.git');
220
+ if (!fs.existsSync(gitDir)) {
221
+ // Don't auto-init git, just note it's needed
222
+ console.log('Note: Run "git init" to initialize git repository');
223
+ }
224
+ }
225
+
226
+ // Create platform-specific folders
227
+ for (const folder of config.folders) {
228
+ const folderPath = path.join(projectPath, folder);
229
+ if (!fs.existsSync(folderPath)) {
230
+ fs.mkdirSync(folderPath, { recursive: true });
231
+ created.push(folder);
232
+ }
233
+ }
234
+
235
+ return created;
236
+ }
237
+
238
+ /**
239
+ * Get workflow guidance for the detected VCS type
240
+ */
241
+ function getWorkflowGuidance(vcsType) {
242
+ const config = VCS_TYPES[vcsType];
243
+ if (!config) return null;
244
+
245
+ return {
246
+ type: vcsType,
247
+ name: config.name,
248
+ prCommand: config.prCommand,
249
+ ciFile: config.ciFile,
250
+ branchWorkflow: vcsType !== 'none',
251
+ hasPullRequests: config.prCommand !== null,
252
+ hasCI: config.ciFile !== null,
253
+ };
254
+ }
255
+
256
+ // CLI interface
257
+ function main() {
258
+ const args = process.argv.slice(2);
259
+ const command = args[0] || 'detect';
260
+ const arg1 = args[1];
261
+ const arg2 = args[2];
262
+
263
+ switch (command) {
264
+ case 'detect': {
265
+ const projectPath = arg1 || process.cwd();
266
+ const result = detectVcs(projectPath);
267
+ console.log(JSON.stringify(result, null, 2));
268
+ break;
269
+ }
270
+
271
+ case 'get': {
272
+ const projectPath = arg1 || process.cwd();
273
+ const config = getVcsConfig(projectPath);
274
+ if (config) {
275
+ console.log(JSON.stringify(config, null, 2));
276
+ } else {
277
+ console.log('null');
278
+ process.exit(1);
279
+ }
280
+ break;
281
+ }
282
+
283
+ case 'set': {
284
+ const vcsType = arg1;
285
+ const projectPath = arg2 || process.cwd();
286
+ if (!vcsType) {
287
+ console.error('Usage: node vcs.js set <type> [project-path]');
288
+ console.error(`Types: ${Object.keys(VCS_TYPES).join(', ')}`);
289
+ process.exit(1);
290
+ }
291
+ const result = setVcsConfig(vcsType, projectPath);
292
+ console.log(JSON.stringify(result, null, 2));
293
+ break;
294
+ }
295
+
296
+ case 'init': {
297
+ const vcsType = arg1;
298
+ const projectPath = arg2 || process.cwd();
299
+ if (!vcsType) {
300
+ console.error('Usage: node vcs.js init <type> [project-path]');
301
+ process.exit(1);
302
+ }
303
+ const created = initVcsFolders(vcsType, projectPath);
304
+ setVcsConfig(vcsType, projectPath);
305
+ console.log(JSON.stringify({ type: vcsType, foldersCreated: created }, null, 2));
306
+ break;
307
+ }
308
+
309
+ case 'guidance': {
310
+ const vcsType = arg1;
311
+ if (!vcsType) {
312
+ console.error('Usage: node vcs.js guidance <type>');
313
+ process.exit(1);
314
+ }
315
+ const guidance = getWorkflowGuidance(vcsType);
316
+ console.log(JSON.stringify(guidance, null, 2));
317
+ break;
318
+ }
319
+
320
+ case 'types': {
321
+ const types = Object.entries(VCS_TYPES).map(([key, val]) => ({
322
+ type: key,
323
+ name: val.name,
324
+ }));
325
+ console.log(JSON.stringify(types, null, 2));
326
+ break;
327
+ }
328
+
329
+ default:
330
+ console.error(`Unknown command: ${command}`);
331
+ console.error('Commands: detect, get, set, init, guidance, types');
332
+ process.exit(1);
333
+ }
334
+ }
335
+
336
+ // Export for use as module
337
+ module.exports = {
338
+ detectVcs,
339
+ getVcsConfig,
340
+ setVcsConfig,
341
+ initVcsFolders,
342
+ getWorkflowGuidance,
343
+ VCS_TYPES,
344
+ };
345
+
346
+ // Run CLI if executed directly
347
+ if (require.main === module) {
348
+ main();
349
+ }