vibe-forge 0.8.1 → 0.8.2
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.
- package/.claude/commands/configure-vcs.md +102 -102
- package/.claude/commands/forge.md +218 -218
- package/.claude/hooks/worker-loop.js +220 -217
- package/.claude/settings.json +89 -89
- package/README.md +149 -191
- package/agents/aegis/personality.md +303 -303
- package/agents/anvil/personality.md +278 -278
- package/agents/architect/personality.md +260 -260
- package/agents/crucible/personality.md +362 -362
- package/agents/crucible-x/personality.md +210 -210
- package/agents/ember/personality.md +293 -293
- package/agents/flux/personality.md +248 -248
- package/agents/furnace/personality.md +342 -342
- package/agents/herald/personality.md +249 -249
- package/agents/oracle/personality.md +284 -284
- package/agents/pixel/personality.md +140 -140
- package/agents/planning-hub/personality.md +473 -473
- package/agents/scribe/personality.md +253 -253
- package/agents/slag/personality.md +268 -268
- package/agents/temper/personality.md +270 -270
- package/bin/cli.js +372 -372
- package/bin/forge-daemon.sh +477 -477
- package/bin/forge-setup.sh +662 -661
- package/bin/forge-spawn.sh +164 -164
- package/bin/forge.sh +566 -566
- package/docs/commands.md +8 -8
- package/package.json +77 -77
- package/{bin → src}/lib/agents.sh +177 -177
- package/{bin → src}/lib/check-aliases.js +50 -50
- package/{bin → src}/lib/colors.sh +45 -44
- package/{bin → src}/lib/config.sh +347 -347
- package/{bin → src}/lib/constants.sh +241 -241
- package/{bin → src}/lib/daemon/budgets.sh +107 -107
- package/{bin → src}/lib/daemon/dependencies.sh +146 -146
- package/{bin → src}/lib/daemon/display.sh +128 -128
- package/{bin → src}/lib/daemon/notifications.sh +273 -273
- package/{bin → src}/lib/daemon/routing.sh +93 -93
- package/{bin → src}/lib/daemon/state.sh +163 -163
- package/{bin → src}/lib/daemon/sync.sh +103 -103
- package/{bin → src}/lib/database.sh +357 -357
- package/{bin → src}/lib/frontmatter.js +106 -106
- package/{bin → src}/lib/heimdall-setup.js +113 -113
- package/{bin → src}/lib/heimdall.js +265 -265
- package/src/lib/index.sh +25 -0
- package/{bin → src}/lib/json.sh +264 -264
- package/{bin → src}/lib/terminal.js +452 -452
- package/{bin → src}/lib/util.sh +126 -126
- package/{bin → src}/lib/vcs.js +349 -349
- package/{context → templates}/project-context-template.md +122 -122
- package/config/task-template.md +0 -159
- package/config/templates/handoff-template.md +0 -40
package/{bin → src}/lib/vcs.js
RENAMED
|
@@ -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
|
+
}
|