@zhuan-ai/zhuanspec 2.2.4 → 2.4.8
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/bin/zhuanspec-hook.js +3 -0
- package/dist/cli/hooks.d.ts +14 -0
- package/dist/cli/hooks.js +465 -0
- package/dist/cli/index.js +100 -0
- package/dist/commands/artifact-workflow.js +15 -34
- package/dist/commands/design.d.ts +42 -0
- package/dist/commands/design.js +337 -0
- package/dist/commands/progress.d.ts +32 -0
- package/dist/commands/progress.js +278 -0
- package/dist/commands/review.d.ts +32 -0
- package/dist/commands/review.js +472 -0
- package/dist/commands/validate.d.ts +14 -0
- package/dist/commands/validate.js +161 -10
- package/dist/core/archive.d.ts +1 -0
- package/dist/core/archive.js +45 -3
- package/dist/core/completions/command-registry.js +67 -0
- package/dist/core/configurators/slash/amazon-q.js +32 -2
- package/dist/core/configurators/slash/antigravity.js +8 -2
- package/dist/core/configurators/slash/auggie.js +16 -1
- package/dist/core/configurators/slash/base.js +1 -1
- package/dist/core/configurators/slash/claude.d.ts +4 -0
- package/dist/core/configurators/slash/claude.js +56 -1
- package/dist/core/configurators/slash/cline.js +8 -2
- package/dist/core/configurators/slash/codebuddy.js +22 -1
- package/dist/core/configurators/slash/codex.js +21 -0
- package/dist/core/configurators/slash/costrict.js +15 -0
- package/dist/core/configurators/slash/crush.js +22 -1
- package/dist/core/configurators/slash/cursor.js +22 -1
- package/dist/core/configurators/slash/factory.js +16 -1
- package/dist/core/configurators/slash/gemini.js +8 -2
- package/dist/core/configurators/slash/github-copilot.js +19 -1
- package/dist/core/configurators/slash/iflow.js +22 -1
- package/dist/core/configurators/slash/kilocode.js +4 -1
- package/dist/core/configurators/slash/opencode.js +27 -0
- package/dist/core/configurators/slash/qoder.d.ts +4 -0
- package/dist/core/configurators/slash/qoder.js +59 -1
- package/dist/core/configurators/slash/qwen.js +8 -2
- package/dist/core/configurators/slash/roocode.js +8 -2
- package/dist/core/configurators/slash/windsurf.js +8 -2
- package/dist/core/dashboard/metrics.d.ts +33 -0
- package/dist/core/dashboard/metrics.js +114 -0
- package/dist/core/hooks/collect-knowledge.d.ts +16 -0
- package/dist/core/hooks/collect-knowledge.js +203 -0
- package/dist/core/hooks/context-load-hook.d.ts +25 -0
- package/dist/core/hooks/context-load-hook.js +159 -0
- package/dist/core/hooks/deviation-check.d.ts +27 -0
- package/dist/core/hooks/deviation-check.js +403 -0
- package/dist/core/hooks/deviation-handler.d.ts +43 -0
- package/dist/core/hooks/deviation-handler.js +98 -0
- package/dist/core/hooks/init.d.ts +14 -0
- package/dist/core/hooks/init.js +244 -0
- package/dist/core/hooks/notify-milestone.d.ts +14 -0
- package/dist/core/hooks/notify-milestone.js +170 -0
- package/dist/core/hooks/post-apply.d.ts +29 -0
- package/dist/core/hooks/post-apply.js +173 -0
- package/dist/core/hooks/post-archive.d.ts +7 -0
- package/dist/core/hooks/post-archive.js +208 -0
- package/dist/core/hooks/pre-apply.d.ts +34 -0
- package/dist/core/hooks/pre-apply.js +139 -0
- package/dist/core/hooks/pre-archive.d.ts +7 -0
- package/dist/core/hooks/pre-archive.js +50 -0
- package/dist/core/hooks/record-progress.d.ts +49 -0
- package/dist/core/hooks/record-progress.js +494 -0
- package/dist/core/hooks/review-hooks.d.ts +89 -0
- package/dist/core/hooks/review-hooks.js +345 -0
- package/dist/core/hooks/review-orchestrator.d.ts +40 -0
- package/dist/core/hooks/review-orchestrator.js +146 -0
- package/dist/core/hooks/summarize.d.ts +15 -0
- package/dist/core/hooks/summarize.js +282 -0
- package/dist/core/hooks/user-input-hook.d.ts +25 -0
- package/dist/core/hooks/user-input-hook.js +179 -0
- package/dist/core/init.d.ts +8 -0
- package/dist/core/init.js +251 -23
- package/dist/core/parsers/requirement-blocks.js +13 -10
- package/dist/core/templates/agents-template.d.ts +1 -1
- package/dist/core/templates/agents-template.js +510 -243
- package/dist/core/templates/index.d.ts +1 -0
- package/dist/core/templates/index.js +1 -0
- package/dist/core/templates/skill-templates.js +46 -152
- package/dist/core/templates/slash-command-templates.d.ts +1 -1
- package/dist/core/templates/slash-command-templates.js +352 -20
- package/dist/core/templates/tasks-template.d.ts +7 -0
- package/dist/core/templates/tasks-template.js +130 -24
- package/dist/core/templates/tdd-tasks-template.d.ts +3 -0
- package/dist/core/templates/tdd-tasks-template.js +91 -38
- package/dist/core/templates/test-cases-template.d.ts +41 -0
- package/dist/core/templates/test-cases-template.js +128 -0
- package/dist/core/validation/strict-rules.d.ts +44 -5
- package/dist/core/validation/strict-rules.js +302 -8
- package/dist/core/validation/validator.js +52 -2
- package/dist/core/view.d.ts +1 -0
- package/dist/core/view.js +60 -2
- package/dist/mcp/index.d.ts +28 -0
- package/dist/mcp/index.js +31 -0
- package/dist/utils/file-system.d.ts +1 -0
- package/dist/utils/file-system.js +11 -0
- package/dist/utils/item-discovery.js +24 -2
- package/dist/utils/phase-utils.d.ts +36 -0
- package/dist/utils/phase-utils.js +117 -0
- package/package.json +22 -23
- package/schemas/spec-driven/schema.yaml +45 -31
- package/schemas/spec-driven/templates/spec.md +142 -5
- package/schemas/spec-driven/templates/tasks.md +73 -9
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
3
|
+
import { readdirSync, rmSync } from 'fs';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { getBeijingTime, getBeijingTimeForFilename } from './record-progress.js';
|
|
6
|
+
export async function postArchiveHook(options) {
|
|
7
|
+
const output = await runPostArchiveHook(options.archived || '');
|
|
8
|
+
if (options.json) {
|
|
9
|
+
console.log(JSON.stringify(output));
|
|
10
|
+
}
|
|
11
|
+
else if (output.systemMessage) {
|
|
12
|
+
console.log(output.systemMessage);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function runPostArchiveHook(archivedChange) {
|
|
16
|
+
if (!archivedChange) {
|
|
17
|
+
return { continue: true, systemMessage: 'Post-archive hook skipped: no archived change provided' };
|
|
18
|
+
}
|
|
19
|
+
const repoRoot = process.cwd();
|
|
20
|
+
const zhuanspecDir = path.join(repoRoot, 'zhuanspec');
|
|
21
|
+
const knowledgeDir = path.join(zhuanspecDir, 'knowledge');
|
|
22
|
+
// 1. Update archive log
|
|
23
|
+
console.log(chalk.gray(' → 更新知识索引...'));
|
|
24
|
+
await FileSystemUtils.createDirectory(knowledgeDir);
|
|
25
|
+
const indexPath = path.join(knowledgeDir, 'index.md');
|
|
26
|
+
const line = `- ${getBeijingTime()} archived: ${archivedChange}`;
|
|
27
|
+
if (await FileSystemUtils.fileExists(indexPath)) {
|
|
28
|
+
const existing = await FileSystemUtils.readFile(indexPath);
|
|
29
|
+
await FileSystemUtils.writeFile(indexPath, `${existing.trimEnd()}\n${line}\n`);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
await FileSystemUtils.writeFile(indexPath, `# Knowledge Index\n\n## Archive Log\n${line}\n`);
|
|
33
|
+
}
|
|
34
|
+
// 2. Collect change knowledge (extract decisions, best practices, implicit conventions)
|
|
35
|
+
console.log(chalk.gray(' → 提取设计文档中的知识记录...'));
|
|
36
|
+
const knowledgeResult = await collectChangeKnowledge(zhuanspecDir, archivedChange, knowledgeDir);
|
|
37
|
+
if (knowledgeResult.decisions > 0) {
|
|
38
|
+
console.log(chalk.gray(` - 决策记录:${knowledgeResult.decisions} 条`));
|
|
39
|
+
}
|
|
40
|
+
if (knowledgeResult.bestPractices > 0) {
|
|
41
|
+
console.log(chalk.gray(` - 最佳实践:${knowledgeResult.bestPractices} 条`));
|
|
42
|
+
}
|
|
43
|
+
if (knowledgeResult.implicitConventions > 0) {
|
|
44
|
+
console.log(chalk.gray(` - 隐式约定:${knowledgeResult.implicitConventions} 条`));
|
|
45
|
+
}
|
|
46
|
+
// 3. Cleanup temp files
|
|
47
|
+
console.log(chalk.gray(' → 清理临时文件...'));
|
|
48
|
+
const tempFilesCleaned = await cleanupTempFiles(zhuanspecDir);
|
|
49
|
+
if (tempFilesCleaned > 0) {
|
|
50
|
+
console.log(chalk.gray(` - 已清理:${tempFilesCleaned} 个`));
|
|
51
|
+
}
|
|
52
|
+
const totalEntries = knowledgeResult.decisions + knowledgeResult.bestPractices + knowledgeResult.implicitConventions;
|
|
53
|
+
console.log(chalk.green(`✅ 知识收集完成:共 ${totalEntries} 条记录`));
|
|
54
|
+
return {
|
|
55
|
+
continue: true,
|
|
56
|
+
systemMessage: `Post-archive: knowledge updated for ${archivedChange}, ${knowledgeResult.decisions} decisions, ${knowledgeResult.bestPractices} best practices, ${knowledgeResult.implicitConventions} implicit conventions, ${tempFilesCleaned} temp files cleaned`,
|
|
57
|
+
hookSpecificOutput: {
|
|
58
|
+
decisionsCollected: knowledgeResult.decisions,
|
|
59
|
+
bestPracticesCollected: knowledgeResult.bestPractices,
|
|
60
|
+
implicitConventionsCollected: knowledgeResult.implicitConventions,
|
|
61
|
+
tempFilesCleaned,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Extract knowledge records from change directory (proposal.md, tasks.md, design.md, specs/*.md)
|
|
67
|
+
* and save to knowledge subdirectories
|
|
68
|
+
*/
|
|
69
|
+
async function collectChangeKnowledge(zhuanspecDir, archivedChange, knowledgeDir) {
|
|
70
|
+
const result = {
|
|
71
|
+
decisions: 0,
|
|
72
|
+
bestPractices: 0,
|
|
73
|
+
implicitConventions: 0,
|
|
74
|
+
};
|
|
75
|
+
// Find archived change directory
|
|
76
|
+
const archiveDir = path.join(zhuanspecDir, 'changes', 'archive', archivedChange);
|
|
77
|
+
// Source files to extract knowledge from (in priority order)
|
|
78
|
+
const sourceFiles = [
|
|
79
|
+
path.join(archiveDir, 'design.md'), // Optional, highest priority
|
|
80
|
+
path.join(archiveDir, 'proposal.md'), // Required
|
|
81
|
+
path.join(archiveDir, 'tasks.md'), // Required
|
|
82
|
+
];
|
|
83
|
+
// Also check specs subdirectory
|
|
84
|
+
const specsDir = path.join(archiveDir, 'specs');
|
|
85
|
+
if (await FileSystemUtils.directoryExists(specsDir)) {
|
|
86
|
+
try {
|
|
87
|
+
const specEntries = readdirSync(specsDir, { withFileTypes: true });
|
|
88
|
+
for (const entry of specEntries) {
|
|
89
|
+
if (entry.isDirectory()) {
|
|
90
|
+
const specMd = path.join(specsDir, entry.name, 'spec.md');
|
|
91
|
+
if (await FileSystemUtils.fileExists(specMd)) {
|
|
92
|
+
sourceFiles.push(specMd);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// Ignore directory read errors
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Collect content from all available source files
|
|
102
|
+
let combinedContent = '';
|
|
103
|
+
for (const filePath of sourceFiles) {
|
|
104
|
+
if (await FileSystemUtils.fileExists(filePath)) {
|
|
105
|
+
try {
|
|
106
|
+
const content = await FileSystemUtils.readFile(filePath);
|
|
107
|
+
combinedContent += `\n\n--- Source: ${path.basename(filePath)} ---\n\n${content}`;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Ignore file read errors
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (!combinedContent.trim()) {
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
// Create knowledge subdirectories
|
|
119
|
+
const decisionsDir = path.join(knowledgeDir, 'decisions');
|
|
120
|
+
const bestPracticesDir = path.join(knowledgeDir, 'best-practices');
|
|
121
|
+
const implicitConventionsDir = path.join(knowledgeDir, 'implicit-conventions');
|
|
122
|
+
await FileSystemUtils.createDirectory(decisionsDir);
|
|
123
|
+
await FileSystemUtils.createDirectory(bestPracticesDir);
|
|
124
|
+
await FileSystemUtils.createDirectory(implicitConventionsDir);
|
|
125
|
+
// Define regex patterns for each knowledge type (Chinese and English)
|
|
126
|
+
const patterns = {
|
|
127
|
+
decisions: /^##\s+(决策|Decision|技术决策|架构决策)[\s\S]*?(?=^##[^#]|$)/gm,
|
|
128
|
+
bestPractices: /^##\s+(最佳实践|Best\s+Practice|Best\s+Practices|推荐做法|建议|注意事项)[\s\S]*?(?=^##[^#]|$)/gm,
|
|
129
|
+
implicitConventions: /^##\s+(隐式约定|Implicit\s+Convention|默认行为|约定|惯例|约定事项)[\s\S]*?(?=^##[^#]|$)/gm,
|
|
130
|
+
};
|
|
131
|
+
const targetDirs = {
|
|
132
|
+
decisions: decisionsDir,
|
|
133
|
+
bestPractices: bestPracticesDir,
|
|
134
|
+
implicitConventions: implicitConventionsDir,
|
|
135
|
+
};
|
|
136
|
+
// Extract and save each knowledge type
|
|
137
|
+
for (const [type, pattern] of Object.entries(patterns)) {
|
|
138
|
+
const matches = combinedContent.matchAll(pattern);
|
|
139
|
+
const entries = [];
|
|
140
|
+
for (const match of matches) {
|
|
141
|
+
const content = match[0].trim();
|
|
142
|
+
if (content.length > 50) {
|
|
143
|
+
entries.push(content);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (entries.length > 0) {
|
|
147
|
+
const timestamp = getBeijingTimeForFilename();
|
|
148
|
+
const fileName = `${archivedChange}-${type}-${timestamp}.md`;
|
|
149
|
+
const filePath = path.join(targetDirs[type], fileName);
|
|
150
|
+
const typeLabel = type === 'decisions' ? 'Decision Records' :
|
|
151
|
+
type === 'bestPractices' ? 'Best Practices' :
|
|
152
|
+
'Implicit Conventions';
|
|
153
|
+
const doc = `# ${typeLabel} from ${archivedChange}
|
|
154
|
+
|
|
155
|
+
Extracted on: ${getBeijingTime()}
|
|
156
|
+
|
|
157
|
+
${entries.join('\n\n---\n\n')}
|
|
158
|
+
`;
|
|
159
|
+
await FileSystemUtils.writeFile(filePath, doc);
|
|
160
|
+
result[type] = entries.length;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return result;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Cleanup temporary files from zhuanspec directory
|
|
171
|
+
*/
|
|
172
|
+
async function cleanupTempFiles(zhuanspecDir) {
|
|
173
|
+
const tempPatterns = [
|
|
174
|
+
'.temp-session',
|
|
175
|
+
'.temp-progress',
|
|
176
|
+
'.temp-',
|
|
177
|
+
'.cache-',
|
|
178
|
+
];
|
|
179
|
+
let cleanedCount = 0;
|
|
180
|
+
try {
|
|
181
|
+
const entries = readdirSync(zhuanspecDir, { withFileTypes: true });
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
const name = entry.name;
|
|
184
|
+
// Check if file/directory matches temp patterns
|
|
185
|
+
const isTemp = tempPatterns.some(pattern => name.startsWith(pattern));
|
|
186
|
+
if (isTemp) {
|
|
187
|
+
const fullPath = path.join(zhuanspecDir, name);
|
|
188
|
+
try {
|
|
189
|
+
if (entry.isDirectory()) {
|
|
190
|
+
rmSync(fullPath, { recursive: true, force: true });
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
rmSync(fullPath, { force: true });
|
|
194
|
+
}
|
|
195
|
+
cleanedCount++;
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// Ignore cleanup failures
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Directory read failed, return 0
|
|
205
|
+
}
|
|
206
|
+
return cleanedCount;
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=post-archive.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-Apply Hook
|
|
3
|
+
*
|
|
4
|
+
* Checks before entering Apply phase:
|
|
5
|
+
* 1. proposal.md exists
|
|
6
|
+
* 2. tasks.md exists
|
|
7
|
+
* 3. specs/ directory exists and non-empty
|
|
8
|
+
* 4. validate --strict passes
|
|
9
|
+
* 5. .approved file exists (Plan Approved gate)
|
|
10
|
+
*/
|
|
11
|
+
interface PreApplyOptions {
|
|
12
|
+
json?: boolean;
|
|
13
|
+
change?: string;
|
|
14
|
+
}
|
|
15
|
+
interface HookOutput {
|
|
16
|
+
continue: boolean;
|
|
17
|
+
stopReason?: string;
|
|
18
|
+
systemMessage?: string;
|
|
19
|
+
checks?: {
|
|
20
|
+
proposalExists: boolean;
|
|
21
|
+
tasksExists: boolean;
|
|
22
|
+
specsExists: boolean;
|
|
23
|
+
validationPassed: boolean;
|
|
24
|
+
approved: boolean;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export declare function preApplyHook(options: PreApplyOptions): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Check pre-apply conditions for a change directory.
|
|
30
|
+
* This function is exported for use by other hooks (e.g., deviation-check).
|
|
31
|
+
*/
|
|
32
|
+
export declare function checkPreApplyConditions(changeDir: string): Promise<HookOutput>;
|
|
33
|
+
export {};
|
|
34
|
+
//# sourceMappingURL=pre-apply.d.ts.map
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-Apply Hook
|
|
3
|
+
*
|
|
4
|
+
* Checks before entering Apply phase:
|
|
5
|
+
* 1. proposal.md exists
|
|
6
|
+
* 2. tasks.md exists
|
|
7
|
+
* 3. specs/ directory exists and non-empty
|
|
8
|
+
* 4. validate --strict passes
|
|
9
|
+
* 5. .approved file exists (Plan Approved gate)
|
|
10
|
+
*/
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { Validator } from '../validation/validator.js';
|
|
13
|
+
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
14
|
+
import { readdirSync } from 'fs';
|
|
15
|
+
export async function preApplyHook(options) {
|
|
16
|
+
const output = await runPreApplyHook(options.change || '');
|
|
17
|
+
if (options.json) {
|
|
18
|
+
console.log(JSON.stringify(output));
|
|
19
|
+
}
|
|
20
|
+
else if (output.systemMessage) {
|
|
21
|
+
if (output.continue) {
|
|
22
|
+
console.log(`✓ ${output.systemMessage}`);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
console.error(`✗ ${output.systemMessage}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (!output.continue) {
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function runPreApplyHook(changeId) {
|
|
33
|
+
if (!changeId) {
|
|
34
|
+
return {
|
|
35
|
+
continue: false,
|
|
36
|
+
stopReason: 'missing-change',
|
|
37
|
+
systemMessage: 'Pre-apply check failed: Missing --change parameter',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const changeDir = path.join(process.cwd(), 'zhuanspec', 'changes', changeId);
|
|
41
|
+
return await checkPreApplyConditions(changeDir);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Check pre-apply conditions for a change directory.
|
|
45
|
+
* This function is exported for use by other hooks (e.g., deviation-check).
|
|
46
|
+
*/
|
|
47
|
+
export async function checkPreApplyConditions(changeDir) {
|
|
48
|
+
const checks = {
|
|
49
|
+
proposalExists: false,
|
|
50
|
+
tasksExists: false,
|
|
51
|
+
specsExists: false,
|
|
52
|
+
validationPassed: false,
|
|
53
|
+
approved: false,
|
|
54
|
+
};
|
|
55
|
+
// Check 1: proposal.md exists
|
|
56
|
+
const proposalPath = path.join(changeDir, 'proposal.md');
|
|
57
|
+
checks.proposalExists = await FileSystemUtils.fileExists(proposalPath);
|
|
58
|
+
if (!checks.proposalExists) {
|
|
59
|
+
return {
|
|
60
|
+
continue: false,
|
|
61
|
+
stopReason: 'missing-proposal',
|
|
62
|
+
systemMessage: 'Pre-apply check failed: missing proposal.md',
|
|
63
|
+
checks,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// Check 2: tasks.md exists
|
|
67
|
+
const tasksPath = path.join(changeDir, 'tasks.md');
|
|
68
|
+
checks.tasksExists = await FileSystemUtils.fileExists(tasksPath);
|
|
69
|
+
if (!checks.tasksExists) {
|
|
70
|
+
return {
|
|
71
|
+
continue: false,
|
|
72
|
+
stopReason: 'missing-tasks',
|
|
73
|
+
systemMessage: 'Pre-apply check failed: missing tasks.md',
|
|
74
|
+
checks,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
// Check 3: specs/ directory exists and non-empty
|
|
78
|
+
const specsDir = path.join(changeDir, 'specs');
|
|
79
|
+
checks.specsExists = await FileSystemUtils.directoryExists(specsDir);
|
|
80
|
+
if (!checks.specsExists) {
|
|
81
|
+
return {
|
|
82
|
+
continue: false,
|
|
83
|
+
stopReason: 'missing-specs',
|
|
84
|
+
systemMessage: 'Pre-apply check failed: missing specs directory',
|
|
85
|
+
checks,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Check specs directory has content
|
|
89
|
+
try {
|
|
90
|
+
const entries = readdirSync(specsDir, { withFileTypes: true });
|
|
91
|
+
const hasContent = entries.some(e => e.isDirectory() || e.name === 'spec.md');
|
|
92
|
+
if (!hasContent) {
|
|
93
|
+
return {
|
|
94
|
+
continue: false,
|
|
95
|
+
stopReason: 'empty-specs',
|
|
96
|
+
systemMessage: 'Pre-apply check failed: specs directory is empty',
|
|
97
|
+
checks,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return {
|
|
103
|
+
continue: false,
|
|
104
|
+
stopReason: 'specs-read-error',
|
|
105
|
+
systemMessage: 'Pre-apply check failed: cannot read specs directory',
|
|
106
|
+
checks,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// Check 4: validate --strict passes
|
|
110
|
+
const validator = new Validator(true);
|
|
111
|
+
const report = await validator.validateChangeDeltaSpecs(changeDir);
|
|
112
|
+
checks.validationPassed = report.valid;
|
|
113
|
+
if (!checks.validationPassed) {
|
|
114
|
+
const errorCount = report.issues.filter(i => i.level === 'ERROR').length;
|
|
115
|
+
return {
|
|
116
|
+
continue: false,
|
|
117
|
+
stopReason: 'validation-failed',
|
|
118
|
+
systemMessage: `Pre-apply check failed: validate --strict has ${errorCount} errors`,
|
|
119
|
+
checks,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// Check 5: .approved file exists (Plan Approved gate)
|
|
123
|
+
const approvedPath = path.join(changeDir, '.approved');
|
|
124
|
+
checks.approved = await FileSystemUtils.fileExists(approvedPath);
|
|
125
|
+
if (!checks.approved) {
|
|
126
|
+
return {
|
|
127
|
+
continue: false,
|
|
128
|
+
stopReason: 'not-approved',
|
|
129
|
+
systemMessage: 'Pre-apply check failed: Plan not approved (missing .approved file)',
|
|
130
|
+
checks,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
continue: true,
|
|
135
|
+
systemMessage: 'Pre-apply check passed - all checks OK',
|
|
136
|
+
checks,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=pre-apply.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { FileSystemUtils } from '../../utils/file-system.js';
|
|
3
|
+
export async function preArchiveHook(options) {
|
|
4
|
+
const output = await runPreArchiveHook(options.change || '');
|
|
5
|
+
if (options.json) {
|
|
6
|
+
console.log(JSON.stringify(output));
|
|
7
|
+
}
|
|
8
|
+
else if (output.systemMessage) {
|
|
9
|
+
console.log(output.systemMessage);
|
|
10
|
+
}
|
|
11
|
+
if (!output.continue) {
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function runPreArchiveHook(changeId) {
|
|
16
|
+
if (!changeId) {
|
|
17
|
+
return { continue: false, stopReason: 'missing-change', systemMessage: 'Missing --change for pre-archive check' };
|
|
18
|
+
}
|
|
19
|
+
const changeDir = path.join(process.cwd(), 'zhuanspec', 'changes', changeId);
|
|
20
|
+
const reviewPath = path.join(changeDir, 'metrics', 'review.json');
|
|
21
|
+
const testsPath = path.join(changeDir, 'metrics', 'tests.json');
|
|
22
|
+
const reviewReportPath = path.join(changeDir, 'review-report.md');
|
|
23
|
+
if (!(await FileSystemUtils.fileExists(reviewPath)) ||
|
|
24
|
+
!(await FileSystemUtils.fileExists(testsPath)) ||
|
|
25
|
+
!(await FileSystemUtils.fileExists(reviewReportPath))) {
|
|
26
|
+
return {
|
|
27
|
+
continue: false,
|
|
28
|
+
stopReason: 'review-or-tests-missing',
|
|
29
|
+
systemMessage: 'Pre-archive check failed: missing review.json/tests.json/review-report.md',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const review = JSON.parse(await FileSystemUtils.readFile(reviewPath));
|
|
33
|
+
const tests = JSON.parse(await FileSystemUtils.readFile(testsPath));
|
|
34
|
+
const reviewPassed = Boolean(review.passed);
|
|
35
|
+
const testPassed = Number(tests.failed ?? 1) === 0;
|
|
36
|
+
const specConsistencyPassed = review?.checks?.specCompliance === true;
|
|
37
|
+
const critical = Number(review?.summary?.critical ?? 0);
|
|
38
|
+
if (!reviewPassed || !testPassed || critical > 0 || !specConsistencyPassed) {
|
|
39
|
+
return {
|
|
40
|
+
continue: false,
|
|
41
|
+
stopReason: 'quality-gate-failed',
|
|
42
|
+
systemMessage: `Pre-archive check failed: review=${reviewPassed}, tests=${testPassed}, specConsistency=${specConsistencyPassed}, critical=${critical}`,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
continue: true,
|
|
47
|
+
systemMessage: 'Pre-archive check passed',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=pre-archive.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostToolUse Hook - Record Progress
|
|
3
|
+
*
|
|
4
|
+
* This hook runs after Write/Edit operations to:
|
|
5
|
+
* 1. Record the modification to progress.json
|
|
6
|
+
* 2. Update tasks.md status if applicable
|
|
7
|
+
* 3. Track tool calls for metrics
|
|
8
|
+
*/
|
|
9
|
+
import { Phase } from '../../utils/phase-utils.js';
|
|
10
|
+
/**
|
|
11
|
+
* Get Beijing time (UTC+8) as ISO string format
|
|
12
|
+
* Returns format: "2026-04-19T18:35:22.934+08:00"
|
|
13
|
+
*/
|
|
14
|
+
export declare function getBeijingTime(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Get Beijing time date string for session ID and filenames
|
|
17
|
+
* Returns format: "2026-04-19T18-35-22"
|
|
18
|
+
*/
|
|
19
|
+
export declare function getBeijingTimeForFilename(): string;
|
|
20
|
+
interface RecordProgressOptions {
|
|
21
|
+
json?: boolean;
|
|
22
|
+
file?: string;
|
|
23
|
+
tool?: string;
|
|
24
|
+
success?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function recordProgressHook(options: RecordProgressOptions): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Initialize progress.json at phase start
|
|
29
|
+
* This function should be called when a change is created, before any files are written.
|
|
30
|
+
* @param changeId - The change ID
|
|
31
|
+
* @param initialPhase - The initial phase to set (default: 'propose')
|
|
32
|
+
*/
|
|
33
|
+
export declare function initializeProgress(changeId: string, initialPhase?: Phase): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Record a hook trigger event
|
|
36
|
+
* This function can be called from other hooks to record their execution
|
|
37
|
+
* @param hookName - The name of the hook (e.g., 'init', 'deviation-check')
|
|
38
|
+
* @param hookPhase - The lifecycle phase (SessionStart, PreToolUse, PostToolUse, Stop)
|
|
39
|
+
* @param success - Whether the hook executed successfully
|
|
40
|
+
* @param options - Additional context (toolName, filePath, message, durationMs)
|
|
41
|
+
*/
|
|
42
|
+
export declare function recordHookTrigger(hookName: string, hookPhase: string, success: boolean, options?: {
|
|
43
|
+
toolName?: string;
|
|
44
|
+
filePath?: string;
|
|
45
|
+
message?: string;
|
|
46
|
+
durationMs?: number;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
export {};
|
|
49
|
+
//# sourceMappingURL=record-progress.d.ts.map
|