sessioncast-cli 2.0.0 → 2.0.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/dist/agent/session-handler.d.ts +2 -1
- package/dist/agent/session-handler.js +79 -32
- package/dist/agent/tmux-executor.d.ts +33 -3
- package/dist/agent/tmux-executor.js +50 -3
- package/dist/agent/tmux.d.ts +6 -2
- package/dist/agent/tmux.js +9 -2
- package/dist/agent/types.d.ts +10 -0
- package/dist/agent/websocket.d.ts +21 -2
- package/dist/agent/websocket.js +46 -10
- package/dist/autopilot/index.d.ts +94 -0
- package/dist/autopilot/index.js +322 -0
- package/dist/autopilot/mission-analyzer.d.ts +27 -0
- package/dist/autopilot/mission-analyzer.js +232 -0
- package/dist/autopilot/project-detector.d.ts +12 -0
- package/dist/autopilot/project-detector.js +326 -0
- package/dist/autopilot/source-scanner.d.ts +26 -0
- package/dist/autopilot/source-scanner.js +285 -0
- package/dist/autopilot/speckit-generator.d.ts +60 -0
- package/dist/autopilot/speckit-generator.js +511 -0
- package/dist/autopilot/types.d.ts +110 -0
- package/dist/autopilot/types.js +6 -0
- package/dist/autopilot/workflow-generator.d.ts +33 -0
- package/dist/autopilot/workflow-generator.js +278 -0
- package/dist/project/executor.d.ts +73 -0
- package/dist/project/executor.js +437 -0
- package/dist/project/index.d.ts +4 -0
- package/dist/project/index.js +20 -0
- package/dist/project/manager.d.ts +66 -0
- package/dist/project/manager.js +290 -0
- package/dist/project/relay-client.d.ts +37 -0
- package/dist/project/relay-client.js +204 -0
- package/dist/project/types.d.ts +48 -0
- package/dist/project/types.js +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ProjectDetector - Auto-detect project type from directory
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.detectProjectType = detectProjectType;
|
|
40
|
+
exports.getProjectStructure = getProjectStructure;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const PROJECT_SIGNATURES = {
|
|
44
|
+
android: {
|
|
45
|
+
files: ['build.gradle', 'app/build.gradle', 'settings.gradle'],
|
|
46
|
+
contentPatterns: [
|
|
47
|
+
{ file: 'build.gradle', pattern: /android\s*\{/ },
|
|
48
|
+
{ file: 'app/build.gradle', pattern: /android\s*\{/ }
|
|
49
|
+
],
|
|
50
|
+
weight: 10
|
|
51
|
+
},
|
|
52
|
+
ios: {
|
|
53
|
+
files: ['*.xcodeproj', '*.xcworkspace', 'Podfile', 'Package.swift'],
|
|
54
|
+
weight: 10
|
|
55
|
+
},
|
|
56
|
+
react: {
|
|
57
|
+
files: ['package.json'],
|
|
58
|
+
contentPatterns: [
|
|
59
|
+
{ file: 'package.json', pattern: /"react":\s*"/ }
|
|
60
|
+
],
|
|
61
|
+
weight: 8
|
|
62
|
+
},
|
|
63
|
+
next: {
|
|
64
|
+
files: ['next.config.js', 'next.config.mjs', 'next.config.ts'],
|
|
65
|
+
weight: 9
|
|
66
|
+
},
|
|
67
|
+
vue: {
|
|
68
|
+
files: ['vue.config.js', 'vite.config.ts', 'vite.config.js'],
|
|
69
|
+
contentPatterns: [
|
|
70
|
+
{ file: 'package.json', pattern: /"vue":\s*"/ }
|
|
71
|
+
],
|
|
72
|
+
weight: 8
|
|
73
|
+
},
|
|
74
|
+
node: {
|
|
75
|
+
files: ['package.json'],
|
|
76
|
+
weight: 5
|
|
77
|
+
},
|
|
78
|
+
python: {
|
|
79
|
+
files: ['requirements.txt', 'pyproject.toml', 'setup.py', 'Pipfile'],
|
|
80
|
+
weight: 7
|
|
81
|
+
},
|
|
82
|
+
spring: {
|
|
83
|
+
files: ['pom.xml', 'build.gradle'],
|
|
84
|
+
contentPatterns: [
|
|
85
|
+
{ file: 'pom.xml', pattern: /spring-boot/ },
|
|
86
|
+
{ file: 'build.gradle', pattern: /spring-boot/ }
|
|
87
|
+
],
|
|
88
|
+
weight: 9
|
|
89
|
+
},
|
|
90
|
+
go: {
|
|
91
|
+
files: ['go.mod', 'go.sum'],
|
|
92
|
+
weight: 8
|
|
93
|
+
},
|
|
94
|
+
rust: {
|
|
95
|
+
files: ['Cargo.toml', 'Cargo.lock'],
|
|
96
|
+
weight: 8
|
|
97
|
+
},
|
|
98
|
+
unknown: {
|
|
99
|
+
files: [],
|
|
100
|
+
weight: 0
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const LANGUAGE_MAP = {
|
|
104
|
+
android: 'kotlin',
|
|
105
|
+
ios: 'swift',
|
|
106
|
+
react: 'typescript',
|
|
107
|
+
next: 'typescript',
|
|
108
|
+
vue: 'typescript',
|
|
109
|
+
node: 'javascript',
|
|
110
|
+
python: 'python',
|
|
111
|
+
spring: 'java',
|
|
112
|
+
go: 'go',
|
|
113
|
+
rust: 'rust'
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Detect project type from directory
|
|
117
|
+
*/
|
|
118
|
+
async function detectProjectType(dir) {
|
|
119
|
+
const scores = new Map();
|
|
120
|
+
const foundConfigFiles = [];
|
|
121
|
+
// Initialize scores
|
|
122
|
+
for (const type of Object.keys(PROJECT_SIGNATURES)) {
|
|
123
|
+
scores.set(type, 0);
|
|
124
|
+
}
|
|
125
|
+
// Check file signatures
|
|
126
|
+
for (const [type, signature] of Object.entries(PROJECT_SIGNATURES)) {
|
|
127
|
+
for (const filePattern of signature.files) {
|
|
128
|
+
const found = await findFiles(dir, filePattern);
|
|
129
|
+
if (found.length > 0) {
|
|
130
|
+
scores.set(type, (scores.get(type) || 0) + signature.weight);
|
|
131
|
+
foundConfigFiles.push(...found);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Check content patterns
|
|
135
|
+
if (signature.contentPatterns) {
|
|
136
|
+
for (const { file, pattern } of signature.contentPatterns) {
|
|
137
|
+
const filePath = path.join(dir, file);
|
|
138
|
+
if (fs.existsSync(filePath)) {
|
|
139
|
+
try {
|
|
140
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
141
|
+
if (pattern.test(content)) {
|
|
142
|
+
scores.set(type, (scores.get(type) || 0) + 5);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Ignore read errors
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Find the best match
|
|
153
|
+
let bestType = 'unknown';
|
|
154
|
+
let bestScore = 0;
|
|
155
|
+
let totalScore = 0;
|
|
156
|
+
for (const [type, score] of scores.entries()) {
|
|
157
|
+
totalScore += score;
|
|
158
|
+
if (score > bestScore) {
|
|
159
|
+
bestScore = score;
|
|
160
|
+
bestType = type;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Calculate confidence
|
|
164
|
+
const confidence = totalScore > 0 ? Math.min(bestScore / (totalScore * 0.5), 1) : 0;
|
|
165
|
+
// Get project name
|
|
166
|
+
const projectName = await getProjectName(dir, bestType);
|
|
167
|
+
return {
|
|
168
|
+
type: bestType,
|
|
169
|
+
name: projectName,
|
|
170
|
+
confidence,
|
|
171
|
+
configFiles: [...new Set(foundConfigFiles)],
|
|
172
|
+
mainLanguage: LANGUAGE_MAP[bestType]
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Find files matching pattern in directory
|
|
177
|
+
*/
|
|
178
|
+
async function findFiles(dir, pattern) {
|
|
179
|
+
const results = [];
|
|
180
|
+
try {
|
|
181
|
+
if (pattern.includes('*')) {
|
|
182
|
+
// Glob pattern
|
|
183
|
+
const ext = pattern.replace('*', '');
|
|
184
|
+
const entries = fs.readdirSync(dir);
|
|
185
|
+
for (const entry of entries) {
|
|
186
|
+
if (entry.endsWith(ext)) {
|
|
187
|
+
results.push(entry);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else if (pattern.includes('/')) {
|
|
192
|
+
// Path pattern
|
|
193
|
+
const fullPath = path.join(dir, pattern);
|
|
194
|
+
if (fs.existsSync(fullPath)) {
|
|
195
|
+
results.push(pattern);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
// Simple filename
|
|
200
|
+
const fullPath = path.join(dir, pattern);
|
|
201
|
+
if (fs.existsSync(fullPath)) {
|
|
202
|
+
results.push(pattern);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
// Ignore errors
|
|
208
|
+
}
|
|
209
|
+
return results;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Get project name from config files
|
|
213
|
+
*/
|
|
214
|
+
async function getProjectName(dir, type) {
|
|
215
|
+
// Try package.json
|
|
216
|
+
const packageJsonPath = path.join(dir, 'package.json');
|
|
217
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
218
|
+
try {
|
|
219
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
220
|
+
if (pkg.name)
|
|
221
|
+
return pkg.name;
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// Ignore
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Try settings.gradle (Android)
|
|
228
|
+
const settingsGradlePath = path.join(dir, 'settings.gradle');
|
|
229
|
+
if (fs.existsSync(settingsGradlePath)) {
|
|
230
|
+
try {
|
|
231
|
+
const content = fs.readFileSync(settingsGradlePath, 'utf-8');
|
|
232
|
+
const match = content.match(/rootProject\.name\s*=\s*['"](.+)['"]/);
|
|
233
|
+
if (match)
|
|
234
|
+
return match[1];
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// Ignore
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// Try pom.xml (Maven)
|
|
241
|
+
const pomPath = path.join(dir, 'pom.xml');
|
|
242
|
+
if (fs.existsSync(pomPath)) {
|
|
243
|
+
try {
|
|
244
|
+
const content = fs.readFileSync(pomPath, 'utf-8');
|
|
245
|
+
const match = content.match(/<artifactId>(.+?)<\/artifactId>/);
|
|
246
|
+
if (match)
|
|
247
|
+
return match[1];
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
// Ignore
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Try Cargo.toml (Rust)
|
|
254
|
+
const cargoPath = path.join(dir, 'Cargo.toml');
|
|
255
|
+
if (fs.existsSync(cargoPath)) {
|
|
256
|
+
try {
|
|
257
|
+
const content = fs.readFileSync(cargoPath, 'utf-8');
|
|
258
|
+
const match = content.match(/name\s*=\s*"(.+?)"/);
|
|
259
|
+
if (match)
|
|
260
|
+
return match[1];
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// Ignore
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Try go.mod (Go)
|
|
267
|
+
const goModPath = path.join(dir, 'go.mod');
|
|
268
|
+
if (fs.existsSync(goModPath)) {
|
|
269
|
+
try {
|
|
270
|
+
const content = fs.readFileSync(goModPath, 'utf-8');
|
|
271
|
+
const match = content.match(/module\s+(.+)/);
|
|
272
|
+
if (match) {
|
|
273
|
+
const parts = match[1].split('/');
|
|
274
|
+
return parts[parts.length - 1];
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
// Ignore
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Fallback to directory name
|
|
282
|
+
return path.basename(dir);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Get a summary of project structure for LLM context
|
|
286
|
+
*/
|
|
287
|
+
function getProjectStructure(dir, maxDepth = 3) {
|
|
288
|
+
const lines = [];
|
|
289
|
+
function walk(currentDir, prefix, depth) {
|
|
290
|
+
if (depth > maxDepth)
|
|
291
|
+
return;
|
|
292
|
+
try {
|
|
293
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
294
|
+
// Sort: directories first, then files
|
|
295
|
+
entries.sort((a, b) => {
|
|
296
|
+
if (a.isDirectory() && !b.isDirectory())
|
|
297
|
+
return -1;
|
|
298
|
+
if (!a.isDirectory() && b.isDirectory())
|
|
299
|
+
return 1;
|
|
300
|
+
return a.name.localeCompare(b.name);
|
|
301
|
+
});
|
|
302
|
+
// Filter out common non-essential directories
|
|
303
|
+
const filtered = entries.filter(e => !['node_modules', '.git', '.gradle', 'build', 'dist', '__pycache__',
|
|
304
|
+
'.idea', '.vscode', 'target', '.next', 'venv', 'env'].includes(e.name));
|
|
305
|
+
for (let i = 0; i < filtered.length; i++) {
|
|
306
|
+
const entry = filtered[i];
|
|
307
|
+
const isLast = i === filtered.length - 1;
|
|
308
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
309
|
+
const newPrefix = prefix + (isLast ? ' ' : '│ ');
|
|
310
|
+
if (entry.isDirectory()) {
|
|
311
|
+
lines.push(`${prefix}${connector}${entry.name}/`);
|
|
312
|
+
walk(path.join(currentDir, entry.name), newPrefix, depth + 1);
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
lines.push(`${prefix}${connector}${entry.name}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
// Ignore permission errors
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
lines.push(path.basename(dir) + '/');
|
|
324
|
+
walk(dir, '', 1);
|
|
325
|
+
return lines.slice(0, 100).join('\n'); // Limit to 100 lines
|
|
326
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SourceScanner - Scan and collect relevant source files for context
|
|
3
|
+
*/
|
|
4
|
+
import { SourceInfo, ProjectType } from './types';
|
|
5
|
+
interface ScanOptions {
|
|
6
|
+
maxFiles?: number;
|
|
7
|
+
maxFileSize?: number;
|
|
8
|
+
includeTests?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Scan directory for source files
|
|
12
|
+
*/
|
|
13
|
+
export declare function scanSources(dir: string, projectType: ProjectType, options?: ScanOptions): Promise<SourceInfo[]>;
|
|
14
|
+
/**
|
|
15
|
+
* Read source file content (for LLM context)
|
|
16
|
+
*/
|
|
17
|
+
export declare function readSourceContent(source: SourceInfo): string | null;
|
|
18
|
+
/**
|
|
19
|
+
* Get condensed context for LLM (file list with sizes)
|
|
20
|
+
*/
|
|
21
|
+
export declare function getSourcesSummary(sources: SourceInfo[]): string;
|
|
22
|
+
/**
|
|
23
|
+
* Get key files content for deep analysis
|
|
24
|
+
*/
|
|
25
|
+
export declare function getKeyFilesContent(sources: SourceInfo[], maxTotalSize?: number): string;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SourceScanner - Scan and collect relevant source files for context
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.scanSources = scanSources;
|
|
40
|
+
exports.readSourceContent = readSourceContent;
|
|
41
|
+
exports.getSourcesSummary = getSourcesSummary;
|
|
42
|
+
exports.getKeyFilesContent = getKeyFilesContent;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const DEFAULT_OPTIONS = {
|
|
46
|
+
maxFiles: 50,
|
|
47
|
+
maxFileSize: 100 * 1024, // 100KB
|
|
48
|
+
includeTests: false
|
|
49
|
+
};
|
|
50
|
+
// File extensions by project type
|
|
51
|
+
const LANGUAGE_EXTENSIONS = {
|
|
52
|
+
kotlin: ['.kt', '.kts'],
|
|
53
|
+
java: ['.java'],
|
|
54
|
+
swift: ['.swift'],
|
|
55
|
+
typescript: ['.ts', '.tsx'],
|
|
56
|
+
javascript: ['.js', '.jsx'],
|
|
57
|
+
python: ['.py'],
|
|
58
|
+
go: ['.go'],
|
|
59
|
+
rust: ['.rs']
|
|
60
|
+
};
|
|
61
|
+
// Config file patterns
|
|
62
|
+
const CONFIG_PATTERNS = [
|
|
63
|
+
'package.json',
|
|
64
|
+
'tsconfig.json',
|
|
65
|
+
'build.gradle',
|
|
66
|
+
'build.gradle.kts',
|
|
67
|
+
'settings.gradle',
|
|
68
|
+
'settings.gradle.kts',
|
|
69
|
+
'pom.xml',
|
|
70
|
+
'Cargo.toml',
|
|
71
|
+
'go.mod',
|
|
72
|
+
'pyproject.toml',
|
|
73
|
+
'requirements.txt',
|
|
74
|
+
'Podfile',
|
|
75
|
+
'.env.example',
|
|
76
|
+
'docker-compose.yml',
|
|
77
|
+
'Dockerfile'
|
|
78
|
+
];
|
|
79
|
+
// Directories to skip
|
|
80
|
+
const SKIP_DIRS = new Set([
|
|
81
|
+
'node_modules',
|
|
82
|
+
'.git',
|
|
83
|
+
'.gradle',
|
|
84
|
+
'build',
|
|
85
|
+
'dist',
|
|
86
|
+
'__pycache__',
|
|
87
|
+
'.idea',
|
|
88
|
+
'.vscode',
|
|
89
|
+
'target',
|
|
90
|
+
'.next',
|
|
91
|
+
'venv',
|
|
92
|
+
'env',
|
|
93
|
+
'.pytest_cache',
|
|
94
|
+
'coverage',
|
|
95
|
+
'.nyc_output',
|
|
96
|
+
'Pods',
|
|
97
|
+
'.build',
|
|
98
|
+
'DerivedData'
|
|
99
|
+
]);
|
|
100
|
+
// Test patterns
|
|
101
|
+
const TEST_PATTERNS = [
|
|
102
|
+
/\.test\.[jt]sx?$/,
|
|
103
|
+
/\.spec\.[jt]sx?$/,
|
|
104
|
+
/_test\.go$/,
|
|
105
|
+
/test_.*\.py$/,
|
|
106
|
+
/.*_test\.py$/,
|
|
107
|
+
/Test\.java$/,
|
|
108
|
+
/Test\.kt$/,
|
|
109
|
+
/Tests\.swift$/
|
|
110
|
+
];
|
|
111
|
+
/**
|
|
112
|
+
* Scan directory for source files
|
|
113
|
+
*/
|
|
114
|
+
async function scanSources(dir, projectType, options = {}) {
|
|
115
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
116
|
+
const sources = [];
|
|
117
|
+
const mainLanguage = getMainLanguage(projectType);
|
|
118
|
+
const extensions = getExtensionsForProject(projectType);
|
|
119
|
+
await walkDir(dir, dir, sources, extensions, opts);
|
|
120
|
+
// Sort by importance: config files first, then by size (smaller = more likely to be important)
|
|
121
|
+
sources.sort((a, b) => {
|
|
122
|
+
if (a.type === 'config' && b.type !== 'config')
|
|
123
|
+
return -1;
|
|
124
|
+
if (a.type !== 'config' && b.type === 'config')
|
|
125
|
+
return 1;
|
|
126
|
+
return a.size - b.size;
|
|
127
|
+
});
|
|
128
|
+
// Limit number of files
|
|
129
|
+
return sources.slice(0, opts.maxFiles);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get main language for project type
|
|
133
|
+
*/
|
|
134
|
+
function getMainLanguage(projectType) {
|
|
135
|
+
const languageMap = {
|
|
136
|
+
android: 'kotlin',
|
|
137
|
+
ios: 'swift',
|
|
138
|
+
react: 'typescript',
|
|
139
|
+
next: 'typescript',
|
|
140
|
+
vue: 'typescript',
|
|
141
|
+
node: 'javascript',
|
|
142
|
+
python: 'python',
|
|
143
|
+
spring: 'java',
|
|
144
|
+
go: 'go',
|
|
145
|
+
rust: 'rust',
|
|
146
|
+
unknown: 'javascript'
|
|
147
|
+
};
|
|
148
|
+
return languageMap[projectType];
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Get file extensions for project type
|
|
152
|
+
*/
|
|
153
|
+
function getExtensionsForProject(projectType) {
|
|
154
|
+
const mainLang = getMainLanguage(projectType);
|
|
155
|
+
const extensions = [...(LANGUAGE_EXTENSIONS[mainLang] || [])];
|
|
156
|
+
// Add related extensions
|
|
157
|
+
if (projectType === 'android') {
|
|
158
|
+
extensions.push(...LANGUAGE_EXTENSIONS.java, '.xml');
|
|
159
|
+
}
|
|
160
|
+
if (['react', 'next', 'vue', 'node'].includes(projectType)) {
|
|
161
|
+
extensions.push(...LANGUAGE_EXTENSIONS.javascript);
|
|
162
|
+
}
|
|
163
|
+
return extensions;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Recursively walk directory
|
|
167
|
+
*/
|
|
168
|
+
async function walkDir(rootDir, currentDir, sources, extensions, options) {
|
|
169
|
+
try {
|
|
170
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
171
|
+
for (const entry of entries) {
|
|
172
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
173
|
+
const relativePath = path.relative(rootDir, fullPath);
|
|
174
|
+
if (entry.isDirectory()) {
|
|
175
|
+
if (!SKIP_DIRS.has(entry.name)) {
|
|
176
|
+
await walkDir(rootDir, fullPath, sources, extensions, options);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else if (entry.isFile()) {
|
|
180
|
+
const ext = path.extname(entry.name);
|
|
181
|
+
const isConfig = CONFIG_PATTERNS.includes(entry.name);
|
|
182
|
+
const isSource = extensions.includes(ext);
|
|
183
|
+
const isTest = TEST_PATTERNS.some(p => p.test(entry.name));
|
|
184
|
+
// Skip tests unless explicitly included
|
|
185
|
+
if (isTest && !options.includeTests)
|
|
186
|
+
continue;
|
|
187
|
+
if (isConfig || isSource) {
|
|
188
|
+
try {
|
|
189
|
+
const stats = fs.statSync(fullPath);
|
|
190
|
+
// Skip files that are too large
|
|
191
|
+
if (stats.size > (options.maxFileSize || DEFAULT_OPTIONS.maxFileSize)) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
sources.push({
|
|
195
|
+
path: fullPath,
|
|
196
|
+
relativePath,
|
|
197
|
+
type: getFileType(entry.name, isConfig, isTest),
|
|
198
|
+
language: getLanguageFromExtension(ext),
|
|
199
|
+
size: stats.size
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// Skip files we can't stat
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// Ignore directory read errors
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Determine file type
|
|
215
|
+
*/
|
|
216
|
+
function getFileType(filename, isConfig, isTest) {
|
|
217
|
+
if (isConfig)
|
|
218
|
+
return 'config';
|
|
219
|
+
if (isTest)
|
|
220
|
+
return 'test';
|
|
221
|
+
if (filename.endsWith('.md') || filename.endsWith('.txt'))
|
|
222
|
+
return 'doc';
|
|
223
|
+
return 'code';
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Get language from file extension
|
|
227
|
+
*/
|
|
228
|
+
function getLanguageFromExtension(ext) {
|
|
229
|
+
for (const [lang, exts] of Object.entries(LANGUAGE_EXTENSIONS)) {
|
|
230
|
+
if (exts.includes(ext))
|
|
231
|
+
return lang;
|
|
232
|
+
}
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Read source file content (for LLM context)
|
|
237
|
+
*/
|
|
238
|
+
function readSourceContent(source) {
|
|
239
|
+
try {
|
|
240
|
+
return fs.readFileSync(source.path, 'utf-8');
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Get condensed context for LLM (file list with sizes)
|
|
248
|
+
*/
|
|
249
|
+
function getSourcesSummary(sources) {
|
|
250
|
+
const lines = ['Scanned source files:'];
|
|
251
|
+
const byType = new Map();
|
|
252
|
+
for (const source of sources) {
|
|
253
|
+
const list = byType.get(source.type) || [];
|
|
254
|
+
list.push(source);
|
|
255
|
+
byType.set(source.type, list);
|
|
256
|
+
}
|
|
257
|
+
for (const [type, files] of byType) {
|
|
258
|
+
lines.push(`\n[${type.toUpperCase()}]`);
|
|
259
|
+
for (const file of files) {
|
|
260
|
+
const sizeKB = (file.size / 1024).toFixed(1);
|
|
261
|
+
lines.push(` ${file.relativePath} (${sizeKB}KB)`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return lines.join('\n');
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Get key files content for deep analysis
|
|
268
|
+
*/
|
|
269
|
+
function getKeyFilesContent(sources, maxTotalSize = 50000) {
|
|
270
|
+
const content = [];
|
|
271
|
+
let totalSize = 0;
|
|
272
|
+
// Prioritize config files
|
|
273
|
+
const configFiles = sources.filter(s => s.type === 'config');
|
|
274
|
+
const codeFiles = sources.filter(s => s.type === 'code');
|
|
275
|
+
for (const source of [...configFiles, ...codeFiles]) {
|
|
276
|
+
if (totalSize >= maxTotalSize)
|
|
277
|
+
break;
|
|
278
|
+
const fileContent = readSourceContent(source);
|
|
279
|
+
if (fileContent && totalSize + fileContent.length <= maxTotalSize) {
|
|
280
|
+
content.push(`\n--- ${source.relativePath} ---\n${fileContent}`);
|
|
281
|
+
totalSize += fileContent.length;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return content.join('\n');
|
|
285
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Speckit Generator - Convert AutoPilot workflow to Speckit format
|
|
3
|
+
*
|
|
4
|
+
* Speckit is a structured markdown format for development plans:
|
|
5
|
+
* - plan.md: Technical context, goals, architecture decisions
|
|
6
|
+
* - tasks.md: Step-by-step task list with T-x.y IDs, dependencies, validation
|
|
7
|
+
*
|
|
8
|
+
* Reference: https://github.com/devload/claude-planflow-skills
|
|
9
|
+
*/
|
|
10
|
+
import { AutoPilotContext, ProjectType } from './types';
|
|
11
|
+
/**
|
|
12
|
+
* Speckit output structure
|
|
13
|
+
*/
|
|
14
|
+
export interface SpeckitOutput {
|
|
15
|
+
featureName: string;
|
|
16
|
+
planMd: string;
|
|
17
|
+
tasksMd: string;
|
|
18
|
+
outputDir: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Task in Speckit format
|
|
22
|
+
*/
|
|
23
|
+
export interface SpeckitTask {
|
|
24
|
+
id: string;
|
|
25
|
+
phase: number;
|
|
26
|
+
sequence: number;
|
|
27
|
+
title: string;
|
|
28
|
+
description: string;
|
|
29
|
+
files: string[];
|
|
30
|
+
dependencies: string[];
|
|
31
|
+
validation: string;
|
|
32
|
+
parallel: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Phase grouping
|
|
36
|
+
*/
|
|
37
|
+
export interface SpeckitPhase {
|
|
38
|
+
number: number;
|
|
39
|
+
name: string;
|
|
40
|
+
tasks: SpeckitTask[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Generate Speckit files from AutoPilot context
|
|
44
|
+
*/
|
|
45
|
+
export declare function generateSpeckit(context: AutoPilotContext): SpeckitOutput;
|
|
46
|
+
/**
|
|
47
|
+
* Save Speckit files to disk
|
|
48
|
+
*/
|
|
49
|
+
export declare function saveSpeckit(speckit: SpeckitOutput): {
|
|
50
|
+
planPath: string;
|
|
51
|
+
tasksPath: string;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Quick Speckit generation from just a prompt (without full analysis)
|
|
55
|
+
*/
|
|
56
|
+
export declare function generateQuickSpeckit(prompt: string, context: {
|
|
57
|
+
projectType: ProjectType;
|
|
58
|
+
projectName: string;
|
|
59
|
+
workingDir: string;
|
|
60
|
+
}): SpeckitOutput;
|