spec-agent 1.0.3

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 (86) hide show
  1. package/README.md +256 -0
  2. package/bin/spec-agent.js +14 -0
  3. package/dist/commands/analyze.d.ts +16 -0
  4. package/dist/commands/analyze.d.ts.map +1 -0
  5. package/dist/commands/analyze.js +283 -0
  6. package/dist/commands/analyze.js.map +1 -0
  7. package/dist/commands/clean.d.ts +9 -0
  8. package/dist/commands/clean.d.ts.map +1 -0
  9. package/dist/commands/clean.js +109 -0
  10. package/dist/commands/clean.js.map +1 -0
  11. package/dist/commands/dispatch.d.ts +12 -0
  12. package/dist/commands/dispatch.d.ts.map +1 -0
  13. package/dist/commands/dispatch.js +232 -0
  14. package/dist/commands/dispatch.js.map +1 -0
  15. package/dist/commands/doctor.d.ts +9 -0
  16. package/dist/commands/doctor.d.ts.map +1 -0
  17. package/dist/commands/doctor.js +153 -0
  18. package/dist/commands/doctor.js.map +1 -0
  19. package/dist/commands/learn.d.ts +13 -0
  20. package/dist/commands/learn.d.ts.map +1 -0
  21. package/dist/commands/learn.js +234 -0
  22. package/dist/commands/learn.js.map +1 -0
  23. package/dist/commands/merge.d.ts +11 -0
  24. package/dist/commands/merge.d.ts.map +1 -0
  25. package/dist/commands/merge.js +335 -0
  26. package/dist/commands/merge.js.map +1 -0
  27. package/dist/commands/pipeline.d.ts +19 -0
  28. package/dist/commands/pipeline.d.ts.map +1 -0
  29. package/dist/commands/pipeline.js +266 -0
  30. package/dist/commands/pipeline.js.map +1 -0
  31. package/dist/commands/plan.d.ts +13 -0
  32. package/dist/commands/plan.d.ts.map +1 -0
  33. package/dist/commands/plan.js +314 -0
  34. package/dist/commands/plan.js.map +1 -0
  35. package/dist/commands/scan.d.ts +28 -0
  36. package/dist/commands/scan.d.ts.map +1 -0
  37. package/dist/commands/scan.js +488 -0
  38. package/dist/commands/scan.js.map +1 -0
  39. package/dist/commands/status.d.ts +8 -0
  40. package/dist/commands/status.d.ts.map +1 -0
  41. package/dist/commands/status.js +146 -0
  42. package/dist/commands/status.js.map +1 -0
  43. package/dist/index.d.ts +2 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +126 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/services/document-parser.d.ts +49 -0
  48. package/dist/services/document-parser.d.ts.map +1 -0
  49. package/dist/services/document-parser.js +499 -0
  50. package/dist/services/document-parser.js.map +1 -0
  51. package/dist/services/llm.d.ts +61 -0
  52. package/dist/services/llm.d.ts.map +1 -0
  53. package/dist/services/llm.js +716 -0
  54. package/dist/services/llm.js.map +1 -0
  55. package/dist/types.d.ts +159 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +4 -0
  58. package/dist/types.js.map +1 -0
  59. package/dist/utils/file.d.ts +10 -0
  60. package/dist/utils/file.d.ts.map +1 -0
  61. package/dist/utils/file.js +96 -0
  62. package/dist/utils/file.js.map +1 -0
  63. package/dist/utils/logger.d.ts +13 -0
  64. package/dist/utils/logger.d.ts.map +1 -0
  65. package/dist/utils/logger.js +55 -0
  66. package/dist/utils/logger.js.map +1 -0
  67. package/package.json +48 -0
  68. package/scripts/publish-npm.js +174 -0
  69. package/spec-agent-implementation.md +750 -0
  70. package/src/commands/analyze.ts +322 -0
  71. package/src/commands/clean.ts +88 -0
  72. package/src/commands/dispatch.ts +250 -0
  73. package/src/commands/doctor.ts +136 -0
  74. package/src/commands/learn.ts +261 -0
  75. package/src/commands/merge.ts +377 -0
  76. package/src/commands/pipeline.ts +306 -0
  77. package/src/commands/plan.ts +331 -0
  78. package/src/commands/scan.ts +568 -0
  79. package/src/commands/status.ts +129 -0
  80. package/src/index.ts +137 -0
  81. package/src/services/document-parser.ts +548 -0
  82. package/src/services/llm.ts +857 -0
  83. package/src/types.ts +161 -0
  84. package/src/utils/file.ts +60 -0
  85. package/src/utils/logger.ts +58 -0
  86. package/tsconfig.json +19 -0
@@ -0,0 +1,136 @@
1
+ import * as path from 'path';
2
+ import { Command } from 'commander';
3
+ import { Logger } from '../utils/logger';
4
+ import { ensureDir, fileExists } from '../utils/file';
5
+ import { callLLM, getLLMConfig, validateLLMConfig } from '../services/llm';
6
+
7
+ interface DoctorOptions {
8
+ workspace: string;
9
+ checkLlm?: boolean;
10
+ format: string;
11
+ }
12
+
13
+ interface CheckResult {
14
+ name: string;
15
+ status: 'pass' | 'warn' | 'fail';
16
+ message: string;
17
+ }
18
+
19
+ function parseNodeMajorVersion(version: string): number {
20
+ const raw = version.startsWith('v') ? version.slice(1) : version;
21
+ const major = parseInt(raw.split('.')[0], 10);
22
+ return Number.isFinite(major) ? major : 0;
23
+ }
24
+
25
+ export async function doctorCommand(options: DoctorOptions, command: Command): Promise<void> {
26
+ const logger = new Logger();
27
+ const checks: CheckResult[] = [];
28
+ const workspacePath = path.resolve(options.workspace);
29
+
30
+ try {
31
+ const nodeMajor = parseNodeMajorVersion(process.version);
32
+ checks.push({
33
+ name: 'node_version',
34
+ status: nodeMajor >= 18 ? 'pass' : 'fail',
35
+ message: `当前 Node 版本: ${process.version}(要求 >= 18)`,
36
+ });
37
+
38
+ try {
39
+ await ensureDir(workspacePath);
40
+ checks.push({
41
+ name: 'workspace_access',
42
+ status: 'pass',
43
+ message: `工作目录可访问: ${workspacePath}`,
44
+ });
45
+ } catch (error) {
46
+ checks.push({
47
+ name: 'workspace_access',
48
+ status: 'fail',
49
+ message: `工作目录不可写: ${error instanceof Error ? error.message : String(error)}`,
50
+ });
51
+ }
52
+
53
+ const llmConfig = getLLMConfig();
54
+ try {
55
+ validateLLMConfig(llmConfig);
56
+ checks.push({
57
+ name: 'llm_config',
58
+ status: 'pass',
59
+ message: `LLM 配置有效: ${llmConfig.model} @ ${llmConfig.baseUrl}`,
60
+ });
61
+ } catch (error) {
62
+ checks.push({
63
+ name: 'llm_config',
64
+ status: 'fail',
65
+ message: error instanceof Error ? error.message : String(error),
66
+ });
67
+ }
68
+
69
+ const patternsPath = path.join(workspacePath, '.patterns.json');
70
+ const hasPatterns = await fileExists(patternsPath);
71
+ checks.push({
72
+ name: 'learned_patterns',
73
+ status: hasPatterns ? 'pass' : 'warn',
74
+ message: hasPatterns
75
+ ? `检测到模式文件: ${patternsPath}`
76
+ : '未检测到 .patterns.json(首次运行可忽略)',
77
+ });
78
+
79
+ if (options.checkLlm) {
80
+ try {
81
+ validateLLMConfig(llmConfig);
82
+ const probeConfig = {
83
+ ...llmConfig,
84
+ maxTokens: 32,
85
+ temperature: 0,
86
+ };
87
+ await callLLM(
88
+ '请返回 JSON:{"ok": true, "purpose": "healthcheck"}',
89
+ probeConfig,
90
+ logger
91
+ );
92
+ checks.push({
93
+ name: 'llm_connectivity',
94
+ status: 'pass',
95
+ message: 'LLM 连通性检查通过',
96
+ });
97
+ } catch (error) {
98
+ checks.push({
99
+ name: 'llm_connectivity',
100
+ status: 'fail',
101
+ message: `LLM 连通性检查失败: ${error instanceof Error ? error.message : String(error)}`,
102
+ });
103
+ }
104
+ }
105
+
106
+ const failCount = checks.filter(c => c.status === 'fail').length;
107
+ const warnCount = checks.filter(c => c.status === 'warn').length;
108
+
109
+ if (options.format === 'json') {
110
+ logger.json({
111
+ status: failCount > 0 ? 'fail' : warnCount > 0 ? 'warn' : 'pass',
112
+ checks,
113
+ });
114
+ } else {
115
+ logger.info(`Doctor workspace: ${workspacePath}`);
116
+ for (const check of checks) {
117
+ const prefix = check.status === 'pass' ? 'PASS' : check.status === 'warn' ? 'WARN' : 'FAIL';
118
+ logger.info(` [${prefix}] ${check.name}: ${check.message}`);
119
+ }
120
+ if (failCount === 0 && warnCount === 0) {
121
+ logger.success('所有检查通过');
122
+ } else if (failCount === 0) {
123
+ logger.warn(`检查完成:${warnCount} 警告`);
124
+ } else {
125
+ logger.error(`检查失败:${failCount} 错误, ${warnCount} 警告`);
126
+ }
127
+ }
128
+
129
+ if (failCount > 0) {
130
+ process.exit(2);
131
+ }
132
+ } catch (error) {
133
+ logger.error(`Doctor failed: ${error instanceof Error ? error.message : String(error)}`);
134
+ process.exit(1);
135
+ }
136
+ }
@@ -0,0 +1,261 @@
1
+ import * as path from 'path';
2
+ import { Command } from 'commander';
3
+ import { Logger } from '../utils/logger';
4
+ import {
5
+ ensureDir,
6
+ fileExists,
7
+ readJson,
8
+ writeJson,
9
+ findFiles
10
+ } from '../utils/file';
11
+ import { ChunkSummary, Patterns, LearnedPattern } from '../types';
12
+
13
+ interface LearnOptions {
14
+ workspace: string;
15
+ from?: string;
16
+ pattern?: string;
17
+ rule?: string;
18
+ list?: boolean;
19
+ export?: string;
20
+ apply?: boolean;
21
+ }
22
+
23
+ const PATTERNS_FILE = '.patterns.json';
24
+
25
+ export async function learnCommand(options: LearnOptions, command: Command): Promise<void> {
26
+ const logger = new Logger();
27
+ const workspacePath = path.resolve(options.workspace);
28
+ const patternsPath = path.join(workspacePath, PATTERNS_FILE);
29
+
30
+ try {
31
+ // Load existing patterns
32
+ let patterns: Patterns = {
33
+ version: '1.0.0',
34
+ workspace: workspacePath,
35
+ patterns: [],
36
+ };
37
+
38
+ if (await fileExists(patternsPath)) {
39
+ patterns = await readJson<Patterns>(patternsPath);
40
+ }
41
+
42
+ // Handle list command
43
+ if (options.list) {
44
+ if (patterns.patterns.length === 0) {
45
+ logger.info('No learned patterns yet.');
46
+ } else {
47
+ logger.info(`Learned patterns (${patterns.patterns.length}):`);
48
+ for (const p of patterns.patterns) {
49
+ logger.info(` - ${p.name}: ${p.rule} (confidence: ${(p.confidence * 100).toFixed(0)}%)`);
50
+ }
51
+ }
52
+ return;
53
+ }
54
+
55
+ // Handle export command
56
+ if (options.export) {
57
+ const exportPath = path.resolve(options.export);
58
+ await writeJson(exportPath, patterns);
59
+ logger.success(`Patterns exported to ${exportPath}`);
60
+ return;
61
+ }
62
+
63
+ // Handle manual pattern learning
64
+ if (options.pattern && options.rule) {
65
+ const newPattern: LearnedPattern = {
66
+ name: options.pattern,
67
+ rule: options.rule,
68
+ context: 'manual',
69
+ confidence: 1.0,
70
+ learnedAt: new Date().toISOString(),
71
+ };
72
+
73
+ // Check if pattern already exists
74
+ const existingIndex = patterns.patterns.findIndex(p => p.name === options.pattern);
75
+ if (existingIndex >= 0) {
76
+ patterns.patterns[existingIndex] = newPattern;
77
+ logger.info(`Updated pattern: ${options.pattern}`);
78
+ } else {
79
+ patterns.patterns.push(newPattern);
80
+ logger.info(`Learned new pattern: ${options.pattern}`);
81
+ }
82
+
83
+ await writeJson(patternsPath, patterns);
84
+ return;
85
+ }
86
+
87
+ // Handle automatic learning from phase results
88
+ if (options.from) {
89
+ const learnedCount = await learnFromPhase(workspacePath, options.from, patterns, logger);
90
+
91
+ if (learnedCount > 0) {
92
+ await writeJson(patternsPath, patterns);
93
+ logger.success(`Learned ${learnedCount} new patterns from ${options.from}`);
94
+ } else {
95
+ logger.info('No new patterns learned.');
96
+ }
97
+ return;
98
+ }
99
+
100
+ // Handle apply command
101
+ if (options.apply) {
102
+ if (patterns.patterns.length === 0) {
103
+ logger.warn('No patterns to apply.');
104
+ return;
105
+ }
106
+
107
+ logger.info(`Applied ${patterns.patterns.length} learned patterns to workspace`);
108
+ logger.json({
109
+ status: 'success',
110
+ patternsApplied: patterns.patterns.length,
111
+ patterns: patterns.patterns.map(p => p.name),
112
+ });
113
+ return;
114
+ }
115
+
116
+ // Default: show help
117
+ logger.info('Usage:');
118
+ logger.info(' spec-agent learn --workspace ./workspace --from summaries');
119
+ logger.info(' spec-agent learn --workspace ./workspace --pattern "API规范" --rule "/api/v{version}/{resource}"');
120
+ logger.info(' spec-agent learn --workspace ./workspace --list');
121
+ logger.info(' spec-agent learn --workspace ./workspace --export ./patterns.json');
122
+
123
+ } catch (error) {
124
+ logger.error(`Learn failed: ${error instanceof Error ? error.message : String(error)}`);
125
+ process.exit(1);
126
+ }
127
+ }
128
+
129
+ async function learnFromPhase(
130
+ workspacePath: string,
131
+ phase: string,
132
+ patterns: Patterns,
133
+ logger: Logger
134
+ ): Promise<number> {
135
+ let learnedCount = 0;
136
+
137
+ switch (phase) {
138
+ case 'summaries':
139
+ learnedCount = await learnFromSummaries(workspacePath, patterns, logger);
140
+ break;
141
+ case 'plan':
142
+ learnedCount = await learnFromPlan(workspacePath, patterns, logger);
143
+ break;
144
+ case 'dispatch':
145
+ learnedCount = await learnFromDispatch(workspacePath, patterns, logger);
146
+ break;
147
+ default:
148
+ logger.error(`Unknown phase: ${phase}`);
149
+ return 0;
150
+ }
151
+
152
+ return learnedCount;
153
+ }
154
+
155
+ async function learnFromSummaries(
156
+ workspacePath: string,
157
+ patterns: Patterns,
158
+ logger: Logger
159
+ ): Promise<number> {
160
+ const summariesDir = path.join(workspacePath, 'summaries');
161
+
162
+ if (!(await fileExists(summariesDir))) {
163
+ logger.error('No summaries directory found. Run analyze first.');
164
+ return 0;
165
+ }
166
+
167
+ const summaryFiles = await findFiles('chunk_*_summary.json', summariesDir);
168
+ if (summaryFiles.length === 0) {
169
+ logger.error('No summary files found.');
170
+ return 0;
171
+ }
172
+
173
+ let learnedCount = 0;
174
+
175
+ for (const file of summaryFiles) {
176
+ const summary = await readJson<ChunkSummary>(file);
177
+
178
+ // Learn naming patterns from features
179
+ for (const feature of summary.features) {
180
+ const namingPattern = extractNamingPattern(feature.name);
181
+ if (namingPattern && !patternExists(patterns, namingPattern.name)) {
182
+ patterns.patterns.push({
183
+ ...namingPattern,
184
+ context: `feature:${feature.id}`,
185
+ confidence: 0.8,
186
+ learnedAt: new Date().toISOString(),
187
+ });
188
+ learnedCount++;
189
+ }
190
+ }
191
+
192
+ // Learn API patterns
193
+ for (const api of summary.apis) {
194
+ const apiPattern = extractApiPattern(api.path);
195
+ if (apiPattern && !patternExists(patterns, apiPattern.name)) {
196
+ patterns.patterns.push({
197
+ ...apiPattern,
198
+ context: `api:${api.method}`,
199
+ confidence: 0.9,
200
+ learnedAt: new Date().toISOString(),
201
+ });
202
+ learnedCount++;
203
+ }
204
+ }
205
+ }
206
+
207
+ return learnedCount;
208
+ }
209
+
210
+ async function learnFromPlan(
211
+ workspacePath: string,
212
+ patterns: Patterns,
213
+ logger: Logger
214
+ ): Promise<number> {
215
+ // Learn from task planning patterns
216
+ // This would analyze task_plan.json to learn estimation patterns, etc.
217
+ return 0;
218
+ }
219
+
220
+ async function learnFromDispatch(
221
+ workspacePath: string,
222
+ patterns: Patterns,
223
+ logger: Logger
224
+ ): Promise<number> {
225
+ // Learn from dispatch patterns
226
+ // This would analyze dispatch_plan.json to learn agent assignment patterns
227
+ return 0;
228
+ }
229
+
230
+ function extractNamingPattern(name: string): { name: string; rule: string } | null {
231
+ // Detect naming conventions
232
+ if (/^[A-Z][a-zA-Z0-9]*$/.test(name)) {
233
+ return { name: '组件命名规范', rule: 'PascalCase' };
234
+ }
235
+ if (/^[a-z][a-z0-9-]*$/.test(name)) {
236
+ return { name: '路由命名规范', rule: 'kebab-case' };
237
+ }
238
+ if (/^[a-z][a-zA-Z0-9]*$/.test(name)) {
239
+ return { name: '变量命名规范', rule: 'camelCase' };
240
+ }
241
+ return null;
242
+ }
243
+
244
+ function extractApiPattern(path: string): { name: string; rule: string } | null {
245
+ // Detect API patterns
246
+ const versionMatch = path.match(/\/api\/v(\d+)\//);
247
+ if (versionMatch) {
248
+ return { name: 'API版本规范', rule: `/api/v{version}/...` };
249
+ }
250
+
251
+ const restMatch = path.match(/\/api\/\w+\/(\d+|\w+)/);
252
+ if (restMatch) {
253
+ return { name: 'RESTful路径规范', rule: `/api/{resource}/{id}` };
254
+ }
255
+
256
+ return null;
257
+ }
258
+
259
+ function patternExists(patterns: Patterns, name: string): boolean {
260
+ return patterns.patterns.some(p => p.name === name);
261
+ }