openspec-mcp 0.1.0
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/LICENSE +21 -0
- package/README.md +205 -0
- package/README.zh.md +205 -0
- package/dist/api/routes/approvals.d.ts +7 -0
- package/dist/api/routes/approvals.d.ts.map +1 -0
- package/dist/api/routes/approvals.js +99 -0
- package/dist/api/routes/approvals.js.map +1 -0
- package/dist/api/routes/changes.d.ts +7 -0
- package/dist/api/routes/changes.d.ts.map +1 -0
- package/dist/api/routes/changes.js +55 -0
- package/dist/api/routes/changes.js.map +1 -0
- package/dist/api/routes/specs.d.ts +7 -0
- package/dist/api/routes/specs.d.ts.map +1 -0
- package/dist/api/routes/specs.js +34 -0
- package/dist/api/routes/specs.js.map +1 -0
- package/dist/api/routes/tasks.d.ts +7 -0
- package/dist/api/routes/tasks.d.ts.map +1 -0
- package/dist/api/routes/tasks.js +61 -0
- package/dist/api/routes/tasks.js.map +1 -0
- package/dist/api/server.d.ts +23 -0
- package/dist/api/server.d.ts.map +1 -0
- package/dist/api/server.js +142 -0
- package/dist/api/server.js.map +1 -0
- package/dist/core/approval-manager.d.ts +69 -0
- package/dist/core/approval-manager.d.ts.map +1 -0
- package/dist/core/approval-manager.js +268 -0
- package/dist/core/approval-manager.js.map +1 -0
- package/dist/core/file-watcher.d.ts +38 -0
- package/dist/core/file-watcher.d.ts.map +1 -0
- package/dist/core/file-watcher.js +128 -0
- package/dist/core/file-watcher.js.map +1 -0
- package/dist/core/openspec-cli.d.ts +94 -0
- package/dist/core/openspec-cli.d.ts.map +1 -0
- package/dist/core/openspec-cli.js +436 -0
- package/dist/core/openspec-cli.js.map +1 -0
- package/dist/core/task-parser.d.ts +48 -0
- package/dist/core/task-parser.d.ts.map +1 -0
- package/dist/core/task-parser.js +172 -0
- package/dist/core/task-parser.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -0
- package/dist/server/tools/approval.d.ts +8 -0
- package/dist/server/tools/approval.d.ts.map +1 -0
- package/dist/server/tools/approval.js +197 -0
- package/dist/server/tools/approval.js.map +1 -0
- package/dist/server/tools/archive.d.ts +8 -0
- package/dist/server/tools/archive.d.ts.map +1 -0
- package/dist/server/tools/archive.js +41 -0
- package/dist/server/tools/archive.js.map +1 -0
- package/dist/server/tools/guides.d.ts +8 -0
- package/dist/server/tools/guides.d.ts.map +1 -0
- package/dist/server/tools/guides.js +25 -0
- package/dist/server/tools/guides.js.map +1 -0
- package/dist/server/tools/management.d.ts +8 -0
- package/dist/server/tools/management.d.ts.map +1 -0
- package/dist/server/tools/management.js +96 -0
- package/dist/server/tools/management.js.map +1 -0
- package/dist/server/tools/tasks.d.ts +8 -0
- package/dist/server/tools/tasks.d.ts.map +1 -0
- package/dist/server/tools/tasks.js +124 -0
- package/dist/server/tools/tasks.js.map +1 -0
- package/dist/server/tools/validation.d.ts +8 -0
- package/dist/server/tools/validation.d.ts.map +1 -0
- package/dist/server/tools/validation.js +119 -0
- package/dist/server/tools/validation.js.map +1 -0
- package/dist/types/openspec.d.ts +141 -0
- package/dist/types/openspec.d.ts.map +1 -0
- package/dist/types/openspec.js +5 -0
- package/dist/types/openspec.js.map +1 -0
- package/package.json +58 -0
- package/web/dist/assets/index--LppUKpS.js +67 -0
- package/web/dist/assets/index-DdJQfs9Z.css +1 -0
- package/web/dist/index.html +14 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文件监控器
|
|
3
|
+
* 使用 Chokidar 监控 openspec 目录变化
|
|
4
|
+
*/
|
|
5
|
+
import chokidar from 'chokidar';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { EventEmitter } from 'events';
|
|
8
|
+
export class FileWatcher extends EventEmitter {
|
|
9
|
+
cwd;
|
|
10
|
+
watcher = null;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
super();
|
|
13
|
+
this.cwd = options.cwd;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 获取监控目录路径
|
|
17
|
+
*/
|
|
18
|
+
getWatchPath() {
|
|
19
|
+
return path.join(this.cwd, 'openspec');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 启动监控
|
|
23
|
+
*/
|
|
24
|
+
async start() {
|
|
25
|
+
const watchPath = this.getWatchPath();
|
|
26
|
+
this.watcher = chokidar.watch(watchPath, {
|
|
27
|
+
persistent: true,
|
|
28
|
+
ignoreInitial: true,
|
|
29
|
+
depth: 10,
|
|
30
|
+
awaitWriteFinish: {
|
|
31
|
+
stabilityThreshold: 300,
|
|
32
|
+
pollInterval: 100,
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
this.watcher
|
|
36
|
+
.on('add', (filePath) => {
|
|
37
|
+
this.handleChange('add', filePath);
|
|
38
|
+
})
|
|
39
|
+
.on('change', (filePath) => {
|
|
40
|
+
this.handleChange('change', filePath);
|
|
41
|
+
})
|
|
42
|
+
.on('unlink', (filePath) => {
|
|
43
|
+
this.handleChange('unlink', filePath);
|
|
44
|
+
})
|
|
45
|
+
.on('addDir', (filePath) => {
|
|
46
|
+
this.handleChange('addDir', filePath);
|
|
47
|
+
})
|
|
48
|
+
.on('unlinkDir', (filePath) => {
|
|
49
|
+
this.handleChange('unlinkDir', filePath);
|
|
50
|
+
})
|
|
51
|
+
.on('error', (error) => {
|
|
52
|
+
console.error('File watcher error:', error);
|
|
53
|
+
this.emit('error', error);
|
|
54
|
+
})
|
|
55
|
+
.on('ready', () => {
|
|
56
|
+
console.log(`File watcher ready: ${watchPath}`);
|
|
57
|
+
this.emit('ready');
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 处理文件变化
|
|
62
|
+
*/
|
|
63
|
+
handleChange(event, filePath) {
|
|
64
|
+
// 获取相对路径
|
|
65
|
+
const relativePath = path.relative(this.cwd, filePath);
|
|
66
|
+
// 解析文件类型
|
|
67
|
+
const fileType = this.getFileType(relativePath);
|
|
68
|
+
console.log(`[${event}] ${relativePath} (${fileType})`);
|
|
69
|
+
// 发射事件
|
|
70
|
+
this.emit('change', event, {
|
|
71
|
+
path: relativePath,
|
|
72
|
+
absolutePath: filePath,
|
|
73
|
+
type: fileType,
|
|
74
|
+
timestamp: new Date().toISOString(),
|
|
75
|
+
});
|
|
76
|
+
// 发射特定类型事件
|
|
77
|
+
this.emit(`change:${fileType}`, event, relativePath);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 获取文件类型
|
|
81
|
+
*/
|
|
82
|
+
getFileType(relativePath) {
|
|
83
|
+
if (relativePath.includes('openspec/specs/')) {
|
|
84
|
+
return 'spec';
|
|
85
|
+
}
|
|
86
|
+
if (relativePath.includes('openspec/changes/archive/')) {
|
|
87
|
+
return 'archive';
|
|
88
|
+
}
|
|
89
|
+
if (relativePath.includes('openspec/changes/')) {
|
|
90
|
+
if (relativePath.endsWith('proposal.md'))
|
|
91
|
+
return 'proposal';
|
|
92
|
+
if (relativePath.endsWith('design.md'))
|
|
93
|
+
return 'design';
|
|
94
|
+
if (relativePath.endsWith('tasks.md'))
|
|
95
|
+
return 'tasks';
|
|
96
|
+
if (relativePath.includes('/specs/'))
|
|
97
|
+
return 'delta';
|
|
98
|
+
return 'change';
|
|
99
|
+
}
|
|
100
|
+
if (relativePath.includes('openspec/approvals/')) {
|
|
101
|
+
return 'approval';
|
|
102
|
+
}
|
|
103
|
+
if (relativePath.endsWith('AGENTS.md')) {
|
|
104
|
+
return 'agents';
|
|
105
|
+
}
|
|
106
|
+
if (relativePath.endsWith('project.md')) {
|
|
107
|
+
return 'project';
|
|
108
|
+
}
|
|
109
|
+
return 'other';
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 停止监控
|
|
113
|
+
*/
|
|
114
|
+
async stop() {
|
|
115
|
+
if (this.watcher) {
|
|
116
|
+
await this.watcher.close();
|
|
117
|
+
this.watcher = null;
|
|
118
|
+
console.log('File watcher stopped');
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 是否正在监控
|
|
123
|
+
*/
|
|
124
|
+
isWatching() {
|
|
125
|
+
return this.watcher !== null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=file-watcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-watcher.js","sourceRoot":"","sources":["../../src/core/file-watcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenSpec CLI 包装器
|
|
3
|
+
* 通过调用 openspec CLI 命令获取数据
|
|
4
|
+
*/
|
|
5
|
+
import type { Change, ChangeDetail, Spec, SpecDetail, ValidationResult, Task, Progress } from '../types/openspec.js';
|
|
6
|
+
export interface OpenSpecCliOptions {
|
|
7
|
+
cwd?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class OpenSpecCli {
|
|
10
|
+
private cwd;
|
|
11
|
+
private taskParser;
|
|
12
|
+
constructor(options?: OpenSpecCliOptions);
|
|
13
|
+
/**
|
|
14
|
+
* 获取 openspec 目录路径
|
|
15
|
+
*/
|
|
16
|
+
private getOpenSpecDir;
|
|
17
|
+
/**
|
|
18
|
+
* 检查 openspec 是否已初始化
|
|
19
|
+
*/
|
|
20
|
+
isInitialized(): Promise<boolean>;
|
|
21
|
+
/**
|
|
22
|
+
* 获取 AGENTS.md 内容(使用指南)
|
|
23
|
+
*/
|
|
24
|
+
getInstructions(): Promise<string>;
|
|
25
|
+
/**
|
|
26
|
+
* 获取 project.md 内容(项目上下文)
|
|
27
|
+
*/
|
|
28
|
+
getProjectContext(): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* 列出所有变更
|
|
31
|
+
*/
|
|
32
|
+
listChanges(options?: {
|
|
33
|
+
includeArchived?: boolean;
|
|
34
|
+
}): Promise<Change[]>;
|
|
35
|
+
/**
|
|
36
|
+
* 列出已归档的变更
|
|
37
|
+
*/
|
|
38
|
+
private listArchivedChanges;
|
|
39
|
+
/**
|
|
40
|
+
* 解析变更目录
|
|
41
|
+
*/
|
|
42
|
+
private parseChangeDir;
|
|
43
|
+
/**
|
|
44
|
+
* 显示变更详情
|
|
45
|
+
*/
|
|
46
|
+
showChange(changeId: string, options?: {
|
|
47
|
+
deltasOnly?: boolean;
|
|
48
|
+
}): Promise<ChangeDetail | null>;
|
|
49
|
+
/**
|
|
50
|
+
* 列出所有规格
|
|
51
|
+
*/
|
|
52
|
+
listSpecs(): Promise<Spec[]>;
|
|
53
|
+
/**
|
|
54
|
+
* 显示规格详情
|
|
55
|
+
*/
|
|
56
|
+
showSpec(specId: string): Promise<SpecDetail | null>;
|
|
57
|
+
/**
|
|
58
|
+
* 验证变更
|
|
59
|
+
*/
|
|
60
|
+
validateChange(changeId: string, options?: {
|
|
61
|
+
strict?: boolean;
|
|
62
|
+
}): Promise<ValidationResult>;
|
|
63
|
+
/**
|
|
64
|
+
* 验证规格
|
|
65
|
+
*/
|
|
66
|
+
validateSpec(specId: string, options?: {
|
|
67
|
+
strict?: boolean;
|
|
68
|
+
}): Promise<ValidationResult>;
|
|
69
|
+
/**
|
|
70
|
+
* 归档变更
|
|
71
|
+
*/
|
|
72
|
+
archiveChange(changeId: string, options?: {
|
|
73
|
+
skipSpecs?: boolean;
|
|
74
|
+
}): Promise<{
|
|
75
|
+
success: boolean;
|
|
76
|
+
archivedPath: string;
|
|
77
|
+
error?: string;
|
|
78
|
+
}>;
|
|
79
|
+
/**
|
|
80
|
+
* 获取变更的任务列表
|
|
81
|
+
*/
|
|
82
|
+
getTasks(changeId: string): Promise<{
|
|
83
|
+
tasks: Task[];
|
|
84
|
+
progress: Progress;
|
|
85
|
+
}>;
|
|
86
|
+
/**
|
|
87
|
+
* 更新任务状态
|
|
88
|
+
*/
|
|
89
|
+
updateTaskStatus(changeId: string, taskId: string, status: 'pending' | 'in_progress' | 'done'): Promise<{
|
|
90
|
+
success: boolean;
|
|
91
|
+
error?: string;
|
|
92
|
+
}>;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=openspec-cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openspec-cli.d.ts","sourceRoot":"","sources":["../../src/core/openspec-cli.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EACV,MAAM,EACN,YAAY,EACZ,IAAI,EACJ,UAAU,EACV,gBAAgB,EAChB,IAAI,EACJ,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAK9B,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,UAAU,CAAa;gBAEnB,OAAO,CAAC,EAAE,kBAAkB;IAKxC;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC;IASvC;;OAEG;IACG,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC;IASxC;;OAEG;IACG,iBAAiB,IAAI,OAAO,CAAC,MAAM,CAAC;IAS1C;;OAEG;IACG,WAAW,CAAC,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAgC7E;;OAEG;YACW,mBAAmB;IAuBjC;;OAEG;YACW,cAAc;IAkD5B;;OAEG;IACG,UAAU,CACd,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,GACjC,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAuF/B;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;IAwClC;;OAEG;IACG,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IA4B1D;;OAEG;IACG,cAAc,CAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAC7B,OAAO,CAAC,gBAAgB,CAAC;IAwB5B;;OAEG;IACG,YAAY,CAChB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,OAAO,CAAA;KAAE,GAC7B,OAAO,CAAC,gBAAgB,CAAC;IAsB5B;;OAEG;IACG,aAAa,CACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,GAChC,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAiCtE;;OAEG;IACG,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAAC,QAAQ,EAAE,QAAQ,CAAA;KAAE,CAAC;IAsBhF;;OAEG;IACG,gBAAgB,CACpB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,SAAS,GAAG,aAAa,GAAG,MAAM,GACzC,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAWjD"}
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenSpec CLI 包装器
|
|
3
|
+
* 通过调用 openspec CLI 命令获取数据
|
|
4
|
+
*/
|
|
5
|
+
import { exec } from 'child_process';
|
|
6
|
+
import { promisify } from 'util';
|
|
7
|
+
import * as fs from 'fs/promises';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import { TaskParser } from './task-parser.js';
|
|
10
|
+
const execAsync = promisify(exec);
|
|
11
|
+
export class OpenSpecCli {
|
|
12
|
+
cwd;
|
|
13
|
+
taskParser;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.cwd = options?.cwd || process.cwd();
|
|
16
|
+
this.taskParser = new TaskParser();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 获取 openspec 目录路径
|
|
20
|
+
*/
|
|
21
|
+
getOpenSpecDir() {
|
|
22
|
+
return path.join(this.cwd, 'openspec');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* 检查 openspec 是否已初始化
|
|
26
|
+
*/
|
|
27
|
+
async isInitialized() {
|
|
28
|
+
try {
|
|
29
|
+
await fs.access(this.getOpenSpecDir());
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* 获取 AGENTS.md 内容(使用指南)
|
|
38
|
+
*/
|
|
39
|
+
async getInstructions() {
|
|
40
|
+
const agentsPath = path.join(this.getOpenSpecDir(), 'AGENTS.md');
|
|
41
|
+
try {
|
|
42
|
+
return await fs.readFile(agentsPath, 'utf-8');
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return 'AGENTS.md not found. Run `openspec init` to initialize.';
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 获取 project.md 内容(项目上下文)
|
|
50
|
+
*/
|
|
51
|
+
async getProjectContext() {
|
|
52
|
+
const projectPath = path.join(this.getOpenSpecDir(), 'project.md');
|
|
53
|
+
try {
|
|
54
|
+
return await fs.readFile(projectPath, 'utf-8');
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return 'project.md not found. Run `openspec init` to initialize.';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* 列出所有变更
|
|
62
|
+
*/
|
|
63
|
+
async listChanges(options) {
|
|
64
|
+
const changesDir = path.join(this.getOpenSpecDir(), 'changes');
|
|
65
|
+
const changes = [];
|
|
66
|
+
try {
|
|
67
|
+
const entries = await fs.readdir(changesDir, { withFileTypes: true });
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
if (!entry.isDirectory())
|
|
70
|
+
continue;
|
|
71
|
+
// 跳过 archive 目录(除非指定包含)
|
|
72
|
+
if (entry.name === 'archive') {
|
|
73
|
+
if (options?.includeArchived) {
|
|
74
|
+
const archivedChanges = await this.listArchivedChanges();
|
|
75
|
+
changes.push(...archivedChanges);
|
|
76
|
+
}
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
const changeDir = path.join(changesDir, entry.name);
|
|
80
|
+
const change = await this.parseChangeDir(entry.name, changeDir, 'active');
|
|
81
|
+
if (change) {
|
|
82
|
+
changes.push(change);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
// 目录不存在,返回空数组
|
|
88
|
+
}
|
|
89
|
+
return changes;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 列出已归档的变更
|
|
93
|
+
*/
|
|
94
|
+
async listArchivedChanges() {
|
|
95
|
+
const archiveDir = path.join(this.getOpenSpecDir(), 'changes', 'archive');
|
|
96
|
+
const changes = [];
|
|
97
|
+
try {
|
|
98
|
+
const entries = await fs.readdir(archiveDir, { withFileTypes: true });
|
|
99
|
+
for (const entry of entries) {
|
|
100
|
+
if (!entry.isDirectory())
|
|
101
|
+
continue;
|
|
102
|
+
const changeDir = path.join(archiveDir, entry.name);
|
|
103
|
+
const change = await this.parseChangeDir(entry.name, changeDir, 'archived');
|
|
104
|
+
if (change) {
|
|
105
|
+
changes.push(change);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// archive 目录不存在
|
|
111
|
+
}
|
|
112
|
+
return changes;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 解析变更目录
|
|
116
|
+
*/
|
|
117
|
+
async parseChangeDir(id, changeDir, status) {
|
|
118
|
+
try {
|
|
119
|
+
const proposalPath = path.join(changeDir, 'proposal.md');
|
|
120
|
+
const tasksPath = path.join(changeDir, 'tasks.md');
|
|
121
|
+
// 读取 proposal 获取标题
|
|
122
|
+
let title = id;
|
|
123
|
+
try {
|
|
124
|
+
const proposal = await fs.readFile(proposalPath, 'utf-8');
|
|
125
|
+
const titleMatch = proposal.match(/^#\s+(.+)/m);
|
|
126
|
+
if (titleMatch) {
|
|
127
|
+
title = titleMatch[1].trim();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// 没有 proposal.md
|
|
132
|
+
}
|
|
133
|
+
// 读取 tasks 获取进度
|
|
134
|
+
let tasksCompleted = 0;
|
|
135
|
+
let tasksTotal = 0;
|
|
136
|
+
try {
|
|
137
|
+
const tasks = await this.taskParser.parseTasks(tasksPath);
|
|
138
|
+
const progress = this.taskParser.calculateProgress(tasks);
|
|
139
|
+
tasksCompleted = progress.completed;
|
|
140
|
+
tasksTotal = progress.total;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// 没有 tasks.md
|
|
144
|
+
}
|
|
145
|
+
// 获取文件修改时间
|
|
146
|
+
const stats = await fs.stat(changeDir);
|
|
147
|
+
return {
|
|
148
|
+
id,
|
|
149
|
+
title,
|
|
150
|
+
status,
|
|
151
|
+
tasksCompleted,
|
|
152
|
+
tasksTotal,
|
|
153
|
+
createdAt: stats.birthtime.toISOString(),
|
|
154
|
+
updatedAt: stats.mtime.toISOString(),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* 显示变更详情
|
|
163
|
+
*/
|
|
164
|
+
async showChange(changeId, options) {
|
|
165
|
+
// 先在活跃变更中查找
|
|
166
|
+
let changeDir = path.join(this.getOpenSpecDir(), 'changes', changeId);
|
|
167
|
+
try {
|
|
168
|
+
await fs.access(changeDir);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// 在归档中查找
|
|
172
|
+
const archiveDir = path.join(this.getOpenSpecDir(), 'changes', 'archive');
|
|
173
|
+
try {
|
|
174
|
+
const archives = await fs.readdir(archiveDir);
|
|
175
|
+
const match = archives.find((a) => a.endsWith(changeId) || a === changeId);
|
|
176
|
+
if (match) {
|
|
177
|
+
changeDir = path.join(archiveDir, match);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const change = await this.parseChangeDir(changeId, changeDir, changeDir.includes('archive') ? 'archived' : 'active');
|
|
188
|
+
if (!change)
|
|
189
|
+
return null;
|
|
190
|
+
// 读取 proposal
|
|
191
|
+
let proposal = '';
|
|
192
|
+
try {
|
|
193
|
+
proposal = await fs.readFile(path.join(changeDir, 'proposal.md'), 'utf-8');
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// 没有 proposal.md
|
|
197
|
+
}
|
|
198
|
+
// 读取 design (可选)
|
|
199
|
+
let design;
|
|
200
|
+
try {
|
|
201
|
+
design = await fs.readFile(path.join(changeDir, 'design.md'), 'utf-8');
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// 没有 design.md
|
|
205
|
+
}
|
|
206
|
+
// 读取 tasks
|
|
207
|
+
let tasks = [];
|
|
208
|
+
try {
|
|
209
|
+
const tasksPath = path.join(changeDir, 'tasks.md');
|
|
210
|
+
tasks = await this.taskParser.parseTasks(tasksPath);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// 没有 tasks.md
|
|
214
|
+
}
|
|
215
|
+
// 读取 deltas (specs 目录下的变更)
|
|
216
|
+
const deltas = [];
|
|
217
|
+
const specsDir = path.join(changeDir, 'specs');
|
|
218
|
+
try {
|
|
219
|
+
const specDirs = await fs.readdir(specsDir, { withFileTypes: true });
|
|
220
|
+
for (const specDir of specDirs) {
|
|
221
|
+
if (!specDir.isDirectory())
|
|
222
|
+
continue;
|
|
223
|
+
const specPath = path.join(specsDir, specDir.name, 'spec.md');
|
|
224
|
+
try {
|
|
225
|
+
const content = await fs.readFile(specPath, 'utf-8');
|
|
226
|
+
deltas.push({
|
|
227
|
+
specName: specDir.name,
|
|
228
|
+
content,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// 没有 spec.md
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// 没有 specs 目录
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
...change,
|
|
241
|
+
proposal,
|
|
242
|
+
design,
|
|
243
|
+
tasks,
|
|
244
|
+
deltas,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* 列出所有规格
|
|
249
|
+
*/
|
|
250
|
+
async listSpecs() {
|
|
251
|
+
const specsDir = path.join(this.getOpenSpecDir(), 'specs');
|
|
252
|
+
const specs = [];
|
|
253
|
+
try {
|
|
254
|
+
const entries = await fs.readdir(specsDir, { withFileTypes: true });
|
|
255
|
+
for (const entry of entries) {
|
|
256
|
+
if (!entry.isDirectory())
|
|
257
|
+
continue;
|
|
258
|
+
const specPath = path.join(specsDir, entry.name, 'spec.md');
|
|
259
|
+
try {
|
|
260
|
+
const content = await fs.readFile(specPath, 'utf-8');
|
|
261
|
+
const stats = await fs.stat(specPath);
|
|
262
|
+
// 解析标题
|
|
263
|
+
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
264
|
+
const title = titleMatch ? titleMatch[1].trim() : entry.name;
|
|
265
|
+
// 计算需求数量
|
|
266
|
+
const requirementsMatch = content.match(/###\s+Requirement:/g);
|
|
267
|
+
const requirementsCount = requirementsMatch ? requirementsMatch.length : 0;
|
|
268
|
+
specs.push({
|
|
269
|
+
id: entry.name,
|
|
270
|
+
title,
|
|
271
|
+
requirementsCount,
|
|
272
|
+
updatedAt: stats.mtime.toISOString(),
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
// 没有 spec.md
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
// specs 目录不存在
|
|
282
|
+
}
|
|
283
|
+
return specs;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* 显示规格详情
|
|
287
|
+
*/
|
|
288
|
+
async showSpec(specId) {
|
|
289
|
+
const specPath = path.join(this.getOpenSpecDir(), 'specs', specId, 'spec.md');
|
|
290
|
+
try {
|
|
291
|
+
const content = await fs.readFile(specPath, 'utf-8');
|
|
292
|
+
const stats = await fs.stat(specPath);
|
|
293
|
+
// 解析标题
|
|
294
|
+
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
295
|
+
const title = titleMatch ? titleMatch[1].trim() : specId;
|
|
296
|
+
// 计算需求数量
|
|
297
|
+
const requirementsMatch = content.match(/###\s+Requirement:/g);
|
|
298
|
+
const requirementsCount = requirementsMatch ? requirementsMatch.length : 0;
|
|
299
|
+
return {
|
|
300
|
+
id: specId,
|
|
301
|
+
title,
|
|
302
|
+
requirementsCount,
|
|
303
|
+
updatedAt: stats.mtime.toISOString(),
|
|
304
|
+
content,
|
|
305
|
+
requirements: [], // TODO: 详细解析需求
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* 验证变更
|
|
314
|
+
*/
|
|
315
|
+
async validateChange(changeId, options) {
|
|
316
|
+
try {
|
|
317
|
+
const flags = options?.strict ? '--strict' : '';
|
|
318
|
+
await execAsync(`openspec validate ${changeId} ${flags}`, { cwd: this.cwd });
|
|
319
|
+
return { valid: true, errors: [] };
|
|
320
|
+
}
|
|
321
|
+
catch (error) {
|
|
322
|
+
// 解析错误输出
|
|
323
|
+
const errors = [];
|
|
324
|
+
const output = error.stderr || error.stdout || '';
|
|
325
|
+
// 简单解析错误信息
|
|
326
|
+
const lines = output.split('\n').filter((l) => l.trim());
|
|
327
|
+
for (const line of lines) {
|
|
328
|
+
if (line.includes('Error') || line.includes('error')) {
|
|
329
|
+
errors.push({ type: 'error', message: line.trim() });
|
|
330
|
+
}
|
|
331
|
+
else if (line.includes('Warning') || line.includes('warning')) {
|
|
332
|
+
errors.push({ type: 'warning', message: line.trim() });
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return { valid: false, errors };
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* 验证规格
|
|
340
|
+
*/
|
|
341
|
+
async validateSpec(specId, options) {
|
|
342
|
+
try {
|
|
343
|
+
const flags = options?.strict ? '--strict' : '';
|
|
344
|
+
await execAsync(`openspec spec validate ${specId} ${flags}`, { cwd: this.cwd });
|
|
345
|
+
return { valid: true, errors: [] };
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
const errors = [];
|
|
349
|
+
const output = error.stderr || error.stdout || '';
|
|
350
|
+
const lines = output.split('\n').filter((l) => l.trim());
|
|
351
|
+
for (const line of lines) {
|
|
352
|
+
if (line.includes('Error') || line.includes('error')) {
|
|
353
|
+
errors.push({ type: 'error', message: line.trim() });
|
|
354
|
+
}
|
|
355
|
+
else if (line.includes('Warning') || line.includes('warning')) {
|
|
356
|
+
errors.push({ type: 'warning', message: line.trim() });
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return { valid: false, errors };
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* 归档变更
|
|
364
|
+
*/
|
|
365
|
+
async archiveChange(changeId, options) {
|
|
366
|
+
try {
|
|
367
|
+
const flags = options?.skipSpecs ? '--skip-specs --yes' : '--yes';
|
|
368
|
+
const { stdout, stderr } = await execAsync(`openspec archive ${changeId} ${flags}`, { cwd: this.cwd });
|
|
369
|
+
const date = new Date().toISOString().slice(0, 10);
|
|
370
|
+
const archivedPath = `openspec/changes/archive/${date}-${changeId}`;
|
|
371
|
+
// 验证归档目录是否存在
|
|
372
|
+
const fullPath = path.join(this.cwd, archivedPath);
|
|
373
|
+
try {
|
|
374
|
+
await fs.access(fullPath);
|
|
375
|
+
return { success: true, archivedPath };
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
// 归档目录不存在,说明归档失败
|
|
379
|
+
const errorOutput = stderr || stdout || 'Archive command did not create the archive directory';
|
|
380
|
+
return {
|
|
381
|
+
success: false,
|
|
382
|
+
archivedPath: '',
|
|
383
|
+
error: errorOutput,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
// 提取 stderr 信息
|
|
389
|
+
const errorMsg = error.stderr || error.stdout || error.message || 'Archive failed';
|
|
390
|
+
return {
|
|
391
|
+
success: false,
|
|
392
|
+
archivedPath: '',
|
|
393
|
+
error: errorMsg,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* 获取变更的任务列表
|
|
399
|
+
*/
|
|
400
|
+
async getTasks(changeId) {
|
|
401
|
+
const changeDir = path.join(this.getOpenSpecDir(), 'changes', changeId);
|
|
402
|
+
const tasksPath = path.join(changeDir, 'tasks.md');
|
|
403
|
+
try {
|
|
404
|
+
const tasks = await this.taskParser.parseTasks(tasksPath);
|
|
405
|
+
const progress = this.taskParser.calculateProgress(tasks);
|
|
406
|
+
return { tasks, progress };
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
return {
|
|
410
|
+
tasks: [],
|
|
411
|
+
progress: {
|
|
412
|
+
total: 0,
|
|
413
|
+
completed: 0,
|
|
414
|
+
inProgress: 0,
|
|
415
|
+
pending: 0,
|
|
416
|
+
percentage: 0,
|
|
417
|
+
},
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* 更新任务状态
|
|
423
|
+
*/
|
|
424
|
+
async updateTaskStatus(changeId, taskId, status) {
|
|
425
|
+
const changeDir = path.join(this.getOpenSpecDir(), 'changes', changeId);
|
|
426
|
+
const tasksPath = path.join(changeDir, 'tasks.md');
|
|
427
|
+
try {
|
|
428
|
+
await this.taskParser.updateTaskStatus(tasksPath, taskId, status);
|
|
429
|
+
return { success: true };
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
return { success: false, error: error.message };
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
//# sourceMappingURL=openspec-cli.js.map
|