sillyspec 3.13.0 → 3.14.1
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/package.json +2 -1
- package/src/index.js +134 -0
- package/src/init.js +16 -0
- package/src/run.js +159 -0
- package/src/stages/archive.js +12 -34
- package/src/stages/scan.js +80 -86
- package/src/workflow.js +670 -0
- package/templates/workflows/archive-impact.yaml +79 -0
- package/templates/workflows/scan-docs.yaml +132 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sillyspec",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.14.1",
|
|
4
4
|
"description": "SillySpec CLI — 流程状态机,让 AI 严格按步骤来",
|
|
5
5
|
"icon": "logo.jpg",
|
|
6
6
|
"homepage": "https://sillyspec.ppdmq.top/",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"@inquirer/prompts": "^7.10.1",
|
|
28
28
|
"chalk": "^5.6.2",
|
|
29
29
|
"chokidar": "^4.0",
|
|
30
|
+
"js-yaml": "^4.2.0",
|
|
30
31
|
"open": "^10.1",
|
|
31
32
|
"ora": "^9.3.0",
|
|
32
33
|
"sql.js": "^1.14.1",
|
package/src/index.js
CHANGED
|
@@ -94,6 +94,7 @@ async function main() {
|
|
|
94
94
|
|
|
95
95
|
// 解析全局选项
|
|
96
96
|
let json = false;
|
|
97
|
+
let saveWorkflowRunFlag = false;
|
|
97
98
|
let targetDir = process.cwd();
|
|
98
99
|
let tool = null;
|
|
99
100
|
let interactive = false;
|
|
@@ -102,6 +103,8 @@ async function main() {
|
|
|
102
103
|
for (let i = 0; i < args.length; i++) {
|
|
103
104
|
if (args[i] === '--json') {
|
|
104
105
|
json = true;
|
|
106
|
+
} else if (args[i] === '--save') {
|
|
107
|
+
saveWorkflowRunFlag = true;
|
|
105
108
|
} else if (args[i] === '--dir' && args[i + 1]) {
|
|
106
109
|
targetDir = resolve(args[i + 1]);
|
|
107
110
|
i++;
|
|
@@ -485,6 +488,137 @@ SillySpec platform — SillyHub 平台同步
|
|
|
485
488
|
await pm.renameChange(dir, oldName, newName);
|
|
486
489
|
break;
|
|
487
490
|
}
|
|
491
|
+
case 'workflow': {
|
|
492
|
+
const wfSub = filteredArgs[1];
|
|
493
|
+
if (!wfSub || wfSub === 'help' || wfSub === '--help') {
|
|
494
|
+
console.log(`
|
|
495
|
+
SillySpec workflow — 工作流管理
|
|
496
|
+
|
|
497
|
+
用法:
|
|
498
|
+
sillyspec workflow check <name> [--project <project>] [--json]
|
|
499
|
+
sillyspec workflow list
|
|
500
|
+
`);
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
if (wfSub === 'list') {
|
|
504
|
+
const { listWorkflows } = await import('./workflow.js');
|
|
505
|
+
const names = listWorkflows(dir);
|
|
506
|
+
if (names.length === 0) {
|
|
507
|
+
console.log('未找到 workflow 定义(.sillyspec/workflows/*.yaml)');
|
|
508
|
+
} else {
|
|
509
|
+
console.log(`可用 workflow:`);
|
|
510
|
+
for (const name of names) {
|
|
511
|
+
const { loadWorkflow } = await import('./workflow.js');
|
|
512
|
+
const wf = loadWorkflow(dir, name);
|
|
513
|
+
const specVer = wf?.spec_version || wf?.version || '?';
|
|
514
|
+
const mode = wf?.orchestration?.mode || '?';
|
|
515
|
+
const roles = wf?.roles?.length || 0;
|
|
516
|
+
console.log(` ${name} (spec v${specVer}, ${mode}, ${roles} roles)`);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
if (wfSub === 'check') {
|
|
522
|
+
const { loadWorkflow, runPostCheck, listWorkflows, saveWorkflowRun } = await import('./workflow.js');
|
|
523
|
+
const wfName = filteredArgs[2];
|
|
524
|
+
if (!wfName) {
|
|
525
|
+
console.error('❌ 请指定 workflow 名称,例如:sillyspec workflow check scan-docs --project sillyspec');
|
|
526
|
+
process.exit(2);
|
|
527
|
+
}
|
|
528
|
+
const wf = loadWorkflow(dir, wfName);
|
|
529
|
+
if (!wf) {
|
|
530
|
+
console.error(`❌ 未找到 workflow: ${wfName}`);
|
|
531
|
+
console.error(`可用 workflow:${listWorkflows(dir).join(', ') || '无'}`);
|
|
532
|
+
process.exit(2);
|
|
533
|
+
}
|
|
534
|
+
// depends_on 校验
|
|
535
|
+
if (wf._validationErrors && wf._validationErrors.length > 0) {
|
|
536
|
+
console.error('❌ workflow YAML 校验失败:');
|
|
537
|
+
for (const err of wf._validationErrors) {
|
|
538
|
+
console.error(` ${err}`);
|
|
539
|
+
}
|
|
540
|
+
process.exit(2);
|
|
541
|
+
}
|
|
542
|
+
// spec_version 校验
|
|
543
|
+
const specVer = wf.spec_version || wf.version;
|
|
544
|
+
if (!specVer) {
|
|
545
|
+
console.error('❌ workflow YAML 缺少 spec_version 字段');
|
|
546
|
+
process.exit(2);
|
|
547
|
+
}
|
|
548
|
+
const SUPPORTED_SPECS = [1];
|
|
549
|
+
if (!SUPPORTED_SPECS.includes(specVer)) {
|
|
550
|
+
console.error(`❌ 不支持的 spec_version: ${specVer}(支持: ${SUPPORTED_SPECS.join(', ')})`);
|
|
551
|
+
process.exit(2);
|
|
552
|
+
}
|
|
553
|
+
// 解析 --project
|
|
554
|
+
const projectIdx = filteredArgs.indexOf('--project');
|
|
555
|
+
const project = projectIdx !== -1 && filteredArgs[projectIdx + 1] ? filteredArgs[projectIdx + 1] : null;
|
|
556
|
+
// 解析 --json(已在顶层解析)
|
|
557
|
+
const isJson = json;
|
|
558
|
+
// 解析 --change
|
|
559
|
+
const changeIdx = filteredArgs.indexOf('--change');
|
|
560
|
+
const changeName = changeIdx !== -1 && filteredArgs[changeIdx + 1] ? filteredArgs[changeIdx + 1] : null;
|
|
561
|
+
|
|
562
|
+
if (!project && wfName !== 'archive-impact') {
|
|
563
|
+
console.error('❌ 请指定 --project,例如:--project sillyspec');
|
|
564
|
+
process.exit(2);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// 执行检查
|
|
568
|
+
let resolvedWf = wf;
|
|
569
|
+
const placeholders = {};
|
|
570
|
+
if (changeName) placeholders['change-name'] = changeName;
|
|
571
|
+
// 替换占位符
|
|
572
|
+
let jsonStr = JSON.stringify(resolvedWf);
|
|
573
|
+
if (changeName) jsonStr = jsonStr.replace(/<change-name>/g, changeName);
|
|
574
|
+
resolvedWf = JSON.parse(jsonStr);
|
|
575
|
+
|
|
576
|
+
const projectName = project || 'sillyspec';
|
|
577
|
+
const result = runPostCheck(resolvedWf, dir, projectName, placeholders);
|
|
578
|
+
|
|
579
|
+
if (isJson) {
|
|
580
|
+
console.log(JSON.stringify(result, null, 2));
|
|
581
|
+
} else {
|
|
582
|
+
// 带项目维度前缀的输出(从统一结果对象格式化)
|
|
583
|
+
const lines = [`\n📋 Workflow Post-Check: ${result.workflow} (project: ${result.project})\n`];
|
|
584
|
+
for (const r of (result.roles || [])) {
|
|
585
|
+
const icon = r.status === 'pass' ? '✅' : '❌';
|
|
586
|
+
lines.push(`${icon} [${result.project}] ${r.name} (${r.id})`);
|
|
587
|
+
const roleFailures = (result.failures || []).filter(f => f.role_id === r.id);
|
|
588
|
+
for (const f of roleFailures) {
|
|
589
|
+
lines.push(` └─ ${f.message}`);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
const wfFailures = (result.workflow_checks || []).filter(c => c.status === 'fail');
|
|
593
|
+
if (wfFailures.length > 0) {
|
|
594
|
+
lines.push('');
|
|
595
|
+
for (const f of wfFailures) {
|
|
596
|
+
lines.push(`❌ [${result.project}] 全局: ${f.detail}`);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
lines.push('');
|
|
600
|
+
if (result.status === 'pass') {
|
|
601
|
+
lines.push('✅ 全部检查通过');
|
|
602
|
+
} else {
|
|
603
|
+
lines.push('❌ 存在失败项');
|
|
604
|
+
}
|
|
605
|
+
console.log(lines.join('\n'));
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// exit code: 0=通过, 1=检查失败, 2=参数/YAML错误
|
|
609
|
+
if (saveWorkflowRunFlag) {
|
|
610
|
+
const saved = saveWorkflowRun(result, { cwd: dir, source: 'cli' });
|
|
611
|
+
if (saved) {
|
|
612
|
+
if (!isJson) console.log(`\n📁 结果已归档:${saved}`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
process.exit(result.status === 'pass' ? 0 : 1);
|
|
616
|
+
} else {
|
|
617
|
+
console.error(`❌ 未知子命令: workflow ${wfSub}`);
|
|
618
|
+
process.exit(1);
|
|
619
|
+
}
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
488
622
|
case 'modules': {
|
|
489
623
|
const modulesSub = filteredArgs[1];
|
|
490
624
|
if (!modulesSub || modulesSub === 'help' || modulesSub === '--help') {
|
package/src/init.js
CHANGED
|
@@ -125,6 +125,22 @@ async function doInstall(projectDir, tools, subprojects = []) {
|
|
|
125
125
|
const gitkeepPath = join(scanDir, '.gitkeep');
|
|
126
126
|
if (!existsSync(gitkeepPath)) writeFileSync(gitkeepPath, '');
|
|
127
127
|
|
|
128
|
+
// 复制 workflow 模板到 .sillyspec/workflows/
|
|
129
|
+
const workflowsDir = join(projectDir, '.sillyspec', 'workflows');
|
|
130
|
+
const templatesDir = join(__dirname, '..', 'templates', 'workflows');
|
|
131
|
+
if (existsSync(templatesDir)) {
|
|
132
|
+
mkdirSync(workflowsDir, { recursive: true });
|
|
133
|
+
for (const file of readdirSync(templatesDir)) {
|
|
134
|
+
if (file.endsWith('.yaml')) {
|
|
135
|
+
const srcPath = join(templatesDir, file);
|
|
136
|
+
const dstPath = join(workflowsDir, file);
|
|
137
|
+
if (!existsSync(dstPath)) {
|
|
138
|
+
writeFileSync(dstPath, readFileSync(srcPath));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
128
144
|
// 创建 shared/workspace 目录
|
|
129
145
|
mkdirSync(join(projectDir, '.sillyspec', 'shared'), { recursive: true });
|
|
130
146
|
mkdirSync(join(projectDir, '.sillyspec', 'workspace'), { recursive: true });
|
package/src/run.js
CHANGED
|
@@ -584,6 +584,88 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
584
584
|
}
|
|
585
585
|
}
|
|
586
586
|
|
|
587
|
+
// scan 阶段 step 2 "构建扫描项目列表" 完成后,按项目展开 perProject 步骤
|
|
588
|
+
if (stageName === 'scan' && steps[currentIdx]?.name === '构建扫描项目列表') {
|
|
589
|
+
// 解析项目列表:从 step 2 输出提取,或回退读取 projects/*.yaml
|
|
590
|
+
let projectNames = []
|
|
591
|
+
if (outputText) {
|
|
592
|
+
// 匹配 "1. project-name" 格式
|
|
593
|
+
const matches = outputText.match(/^\s*\d+\.\s+(\S+)/gm)
|
|
594
|
+
if (matches) {
|
|
595
|
+
projectNames = matches.map(m => m.replace(/^\s*\d+\.\s+/, '').replace(/[—\-:].*$/, '').trim())
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (projectNames.length === 0) {
|
|
599
|
+
// 回退:读取所有已注册项目
|
|
600
|
+
console.warn('⚠️ 未能从 step 2 输出解析项目列表,回退扫描所有注册项目')
|
|
601
|
+
const projectsDir = join(cwd, '.sillyspec', 'projects')
|
|
602
|
+
if (existsSync(projectsDir)) {
|
|
603
|
+
projectNames = readdirSync(projectsDir)
|
|
604
|
+
.filter(f => f.endsWith('.yaml'))
|
|
605
|
+
.map(f => f.replace(/\.yaml$/, ''))
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
if (projectNames.length === 0) {
|
|
609
|
+
projectNames = ['sillyspec'] // 最终兜底
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// 保存到 runtime 供后续使用 + 防重复展开
|
|
613
|
+
const scanStatePath = join(cwd, '.sillyspec', '.runtime', 'scan-projects.json')
|
|
614
|
+
mkdirSync(join(cwd, '.sillyspec', '.runtime'), { recursive: true })
|
|
615
|
+
let scanState = { projects: projectNames, expanded: false }
|
|
616
|
+
if (existsSync(scanStatePath)) {
|
|
617
|
+
try { scanState = JSON.parse(readFileSync(scanStatePath, 'utf8')) } catch {}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// 收集当前步骤之后所有 perProject 步骤
|
|
621
|
+
const stageDef = stageRegistry[stageName]
|
|
622
|
+
const allSteps = stageDef?.steps || []
|
|
623
|
+
const perProjectSteps = allSteps.filter(s => s.perProject)
|
|
624
|
+
|
|
625
|
+
// 防重复展开:runtime 标记 或 steps 已含项目标识
|
|
626
|
+
const alreadyExpanded = scanState.expanded || steps.some(s => s.name?.match(/\[.+\]\s*$/))
|
|
627
|
+
if (!alreadyExpanded && perProjectSteps.length > 0) {
|
|
628
|
+
// 找到当前步骤(step 2)在动态 steps 中的位置
|
|
629
|
+
const insertBase = currentIdx + 1
|
|
630
|
+
let insertPos = insertBase
|
|
631
|
+
for (const pName of projectNames) {
|
|
632
|
+
// 读取项目配置获取 projectRoot
|
|
633
|
+
const projYaml = join(cwd, '.sillyspec', 'projects', `${pName}.yaml`)
|
|
634
|
+
let projectRoot = '.'
|
|
635
|
+
if (existsSync(projYaml)) {
|
|
636
|
+
const yamlContent = readFileSync(projYaml, 'utf8')
|
|
637
|
+
const pathMatch = yamlContent.match(/^path:\s*(.+)/m)
|
|
638
|
+
if (pathMatch) projectRoot = pathMatch[1].trim()
|
|
639
|
+
}
|
|
640
|
+
const docOutputDir = `.sillyspec/docs/${pName}`
|
|
641
|
+
const contextPrefix = `\n---\n## 当前项目\n- **项目名**: ${pName}\n- **项目路径**: ${projectRoot}\n- **文档输出**: ${docOutputDir}\n\n⚠️ 本步骤只处理上面这个项目,不要处理其他项目。\n---\n\n`
|
|
642
|
+
|
|
643
|
+
for (const ppStep of perProjectSteps) {
|
|
644
|
+
steps.splice(insertPos, 0, {
|
|
645
|
+
name: `${ppStep.name} [${pName}]`,
|
|
646
|
+
project: pName,
|
|
647
|
+
status: 'pending',
|
|
648
|
+
prompt: contextPrefix + ppStep.prompt,
|
|
649
|
+
outputHint: ppStep.outputHint,
|
|
650
|
+
optional: ppStep.optional
|
|
651
|
+
})
|
|
652
|
+
insertPos++
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
// 移除原始的 perProject 步骤(未展开的版本)
|
|
656
|
+
for (let i = steps.length - 1; i >= 0; i--) {
|
|
657
|
+
if (steps[i].perProject && !steps[i].name?.includes('[')) {
|
|
658
|
+
steps.splice(i, 1)
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
console.log(` 📝 已按项目展开 ${perProjectSteps.length} 个步骤 × ${projectNames.length} 个项目 = ${perProjectSteps.length * projectNames.length} 个项目步骤`)
|
|
662
|
+
console.log(` 📁 扫描项目:${projectNames.join(', ')}`)
|
|
663
|
+
// 标记已展开,防止 resume 重复插入
|
|
664
|
+
scanState.expanded = true
|
|
665
|
+
writeFileSync(scanStatePath, JSON.stringify(scanState))
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
587
669
|
const nextPendingIdx = steps.findIndex(s => s.status === 'pending')
|
|
588
670
|
|
|
589
671
|
if (nextPendingIdx === -1) {
|
|
@@ -687,6 +769,83 @@ async function completeStep(pm, progress, stageName, cwd, outputText, inputText
|
|
|
687
769
|
|
|
688
770
|
const defSteps = await getStageSteps(stageName, cwd, progress)
|
|
689
771
|
console.log(`✅ Step ${currentIdx + 1}/${steps.length} 完成:${steps[currentIdx].name}\n`)
|
|
772
|
+
|
|
773
|
+
// Workflow post_check:scan 深度扫描完成后自动检查产物
|
|
774
|
+
if (stageName === 'scan' && steps[currentIdx]?.name?.includes('深度扫描')) {
|
|
775
|
+
try {
|
|
776
|
+
const { loadWorkflow, runPostCheck, formatCheckReport, saveWorkflowRun } = await import('./workflow.js')
|
|
777
|
+
const wf = loadWorkflow(cwd, 'scan-docs')
|
|
778
|
+
if (wf) {
|
|
779
|
+
// 确定当前项目:优先从 step metadata 读取,回退从 display name 提取
|
|
780
|
+
const currentProjectName = steps[currentIdx].project
|
|
781
|
+
|| (steps[currentIdx].name.match(/\[([^\]]+)\]\s*$/) || [])[1]
|
|
782
|
+
|| null
|
|
783
|
+
|
|
784
|
+
// 确定要检查的项目列表
|
|
785
|
+
let projectsToCheck = []
|
|
786
|
+
if (currentProjectName) {
|
|
787
|
+
// 按项目展开模式:只检查当前项目
|
|
788
|
+
projectsToCheck = [currentProjectName]
|
|
789
|
+
} else {
|
|
790
|
+
// 兼容旧模式(未展开):检查所有项目
|
|
791
|
+
const projectsDir = join(cwd, '.sillyspec', 'projects')
|
|
792
|
+
const projectFiles = existsSync(projectsDir)
|
|
793
|
+
? readdirSync(projectsDir).filter(f => f.endsWith('.yaml'))
|
|
794
|
+
: []
|
|
795
|
+
projectsToCheck = projectFiles.map(f => f.replace(/\.yaml$/, ''))
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
let anyFailed = false
|
|
799
|
+
for (const pName of projectsToCheck) {
|
|
800
|
+
const result = runPostCheck(wf, cwd, pName)
|
|
801
|
+
const report = formatCheckReport(result)
|
|
802
|
+
console.log(report)
|
|
803
|
+
if (result.status === 'fail') {
|
|
804
|
+
anyFailed = true
|
|
805
|
+
// retry_prompts 由 _checkWorkflow 自动生成
|
|
806
|
+
for (const rp of (result.retry_prompts || [])) {
|
|
807
|
+
console.log(`\n🔄 重试提示(项目 ${pName}):\n`)
|
|
808
|
+
console.log(rp.prompt)
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const saved = saveWorkflowRun(result, { cwd, source: 'run.js', stage: 'verify', step: steps[currentIdx]?.name })
|
|
812
|
+
if (saved) console.log(`📁 结果已归档:${saved}`)
|
|
813
|
+
}
|
|
814
|
+
if (anyFailed) {
|
|
815
|
+
console.log(`\n⚠️ 存在检查失败项,请按上面的重试提示修复后再继续。`)
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
} catch (e) {
|
|
819
|
+
console.warn(`⚠️ workflow 检查跳过:${e.message}`)
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Workflow post_check:archive extract-module-impact 完成后检查产物
|
|
824
|
+
if (stageName === 'archive' && steps[currentIdx]?.name?.includes('extract-module-impact')) {
|
|
825
|
+
try {
|
|
826
|
+
const { loadWorkflow, runPostCheck, formatCheckReport, saveWorkflowRun } = await import('./workflow.js')
|
|
827
|
+
const wf = loadWorkflow(cwd, 'archive-impact')
|
|
828
|
+
if (wf && changeName) {
|
|
829
|
+
const raw = JSON.stringify(wf)
|
|
830
|
+
const resolved = JSON.parse(raw.replace(/<change-name>/g, changeName))
|
|
831
|
+
const result = runPostCheck(resolved, cwd, 'sillyspec')
|
|
832
|
+
// 只报告 impact-analyzer 的结果(doc-syncer 是后续步骤)
|
|
833
|
+
const impactResult = (result.roles || []).find(r => r.id === 'impact-analyzer')
|
|
834
|
+
if (impactResult) {
|
|
835
|
+
const icon = impactResult.status === 'pass' ? '✅' : '❌'
|
|
836
|
+
console.log(`${icon} module-impact.md 检查${impactResult.status === 'pass' ? '通过' : '失败'}`)
|
|
837
|
+
for (const f of (result.failures || []).filter(f => f.role_id === 'impact-analyzer')) {
|
|
838
|
+
console.log(` └─ ${f}`)
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
const saved = saveWorkflowRun(result, { cwd, source: 'run.js', stage: 'archive', step: steps[currentIdx]?.name })
|
|
842
|
+
if (saved) console.log(`📁 结果已归档:${saved}`)
|
|
843
|
+
}
|
|
844
|
+
} catch (e) {
|
|
845
|
+
console.warn(`⚠️ workflow 检查跳过:${e.message}`)
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
690
849
|
if (printNext) {
|
|
691
850
|
await outputStep(stageName, nextPendingIdx, defSteps, cwd, changeName, progress.project || null)
|
|
692
851
|
}
|
package/src/stages/archive.js
CHANGED
|
@@ -20,20 +20,21 @@ export const definition = {
|
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
name: 'extract-module-impact',
|
|
23
|
-
prompt:
|
|
23
|
+
prompt: `按照 \`.sillyspec/workflows/archive-impact.yaml\` 中定义的 \`impact-analyzer\` 角色规则,分析本次变更影响的模块。
|
|
24
24
|
|
|
25
25
|
### 操作
|
|
26
|
-
1.
|
|
27
|
-
2.
|
|
28
|
-
3.
|
|
26
|
+
1. 读取 \`.sillyspec/workflows/archive-impact.yaml\`,了解角色定义和检查规则
|
|
27
|
+
2. 读取变更目录下的 proposal.md、design.md、tasks.md
|
|
28
|
+
3. 运行 \`git diff --name-only HEAD~1\`(或 \`git diff --name-only --cached\`)获取真实修改文件列表
|
|
29
|
+
4. 读取 \`.sillyspec/docs/<project>/modules/_module-map.yaml\`
|
|
29
30
|
- **如果不存在**:提示"建议运行 scan 生成模块映射",但继续执行。跳到步骤 7 生成只有 unmapped 部分的 module-impact.md
|
|
30
|
-
|
|
31
|
+
5. 三重交叉验证:
|
|
31
32
|
- 声明范围:proposal.md / design.md 中的"变更范围"/"文件变更清单"
|
|
32
33
|
- 任务范围:tasks.md / plan.md 中的任务文件路径
|
|
33
34
|
- 真实变更:git diff 文件列表
|
|
34
35
|
- **以 git diff 为准**(真实 > 声明)
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
6. 将 git diff 文件按 \`_module-map.yaml\` 的 paths glob 匹配到模块
|
|
37
|
+
7. 生成模块影响矩阵:
|
|
37
38
|
|
|
38
39
|
| 模块 | 影响类型 | 相关文件 | 更新内容摘要 | needs_review |
|
|
39
40
|
|------|----------|----------|-------------|-------------|
|
|
@@ -41,33 +42,10 @@ export const definition = {
|
|
|
41
42
|
影响类型:逻辑变更 / 数据结构变更 / 接口变更 / 调用关系变更 / 配置变更 / 新增
|
|
42
43
|
needs_review:如果影响无法完全确定,标记为 true
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
# 模块影响分析
|
|
49
|
-
|
|
50
|
-
author: <git-user>
|
|
51
|
-
created_at: <now-datetime>
|
|
52
|
-
|
|
53
|
-
## 变更:<change-name>
|
|
54
|
-
|
|
55
|
-
## 模块影响矩阵
|
|
56
|
-
| 模块 | 影响类型 | 相关文件 | 更新内容摘要 | needs_review |
|
|
57
|
-
|------|----------|----------|-------------|-------------|
|
|
58
|
-
|
|
59
|
-
## 未匹配文件
|
|
60
|
-
| 文件路径 | 说明 |
|
|
61
|
-
|----------|------|
|
|
62
|
-
|
|
63
|
-
## 更新结果
|
|
64
|
-
(sync-module-docs 步骤完成后回填)
|
|
65
|
-
| 目标 | 操作 | 状态 |
|
|
66
|
-
|------|------|------|
|
|
67
|
-
\`\`\`
|
|
68
|
-
|
|
69
|
-
### 输出
|
|
70
|
-
module-impact.md 路径 + 影响模块数量 + 未匹配文件数量`,
|
|
45
|
+
8. 未匹配到任何模块的文件归入"未匹配文件"表格
|
|
46
|
+
9. 生成 \`.sillyspec/changes/<change-name>/module-impact.md\`
|
|
47
|
+
10. 完成后运行 workflow 检查:
|
|
48
|
+
\`node -e "import('./src/workflow.js').then(w => { /* 用 loadWorkflow 加载 archive-impact,用 runPostCheck 检查 */ })\``,
|
|
71
49
|
outputHint: 'module-impact.md 路径 + 影响摘要',
|
|
72
50
|
optional: false
|
|
73
51
|
},
|