@zhuan-ai/zhuanspec 1.3.0 → 2.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/README.zh.md +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/commands/artifact-workflow.js +45 -2
- package/dist/commands/validate.d.ts +14 -0
- package/dist/commands/validate.js +160 -4
- package/dist/core/skill-discovery.d.ts +2 -2
- package/dist/core/skill-discovery.js +16 -3
- package/dist/core/task-graph/execution-planner.d.ts +17 -0
- package/dist/core/task-graph/execution-planner.js +119 -0
- package/dist/core/task-graph/index.d.ts +13 -0
- package/dist/core/task-graph/index.js +16 -0
- package/dist/core/task-graph/mermaid-renderer.d.ts +22 -0
- package/dist/core/task-graph/mermaid-renderer.js +128 -0
- package/dist/core/task-graph/task-graph.d.ts +74 -0
- package/dist/core/task-graph/task-graph.js +219 -0
- package/dist/core/task-graph/task-parser.d.ts +14 -0
- package/dist/core/task-graph/task-parser.js +111 -0
- package/dist/core/task-graph/types.d.ts +53 -0
- package/dist/core/task-graph/types.js +7 -0
- package/dist/core/task-graph/xml-renderer.d.ts +21 -0
- package/dist/core/task-graph/xml-renderer.js +81 -0
- package/dist/core/templates/agents-template.d.ts +1 -1
- package/dist/core/templates/agents-template.js +23 -0
- package/dist/core/templates/skill-templates.js +42 -0
- package/dist/core/templates/slash-command-templates.js +25 -2
- package/dist/core/templates/tasks-template.d.ts +23 -0
- package/dist/core/templates/tasks-template.js +79 -0
- package/dist/core/templates/tdd-tasks-template.d.ts +24 -0
- package/dist/core/templates/tdd-tasks-template.js +116 -0
- package/dist/core/validation/strict-rules.d.ts +60 -0
- package/dist/core/validation/strict-rules.js +287 -0
- package/dist/core/validation/types.d.ts +10 -0
- package/dist/core/validation/validator.d.ts +5 -0
- package/dist/core/validation/validator.js +103 -1
- package/package.json +22 -20
- package/schemas/spec-driven/templates/tasks.md +17 -5
package/README.zh.md
CHANGED
|
@@ -415,7 +415,7 @@ npm install -g ./distributions/fission-ai-zhuanspec-1.0.0.tgz
|
|
|
415
415
|
|
|
416
416
|
1. **升级包**
|
|
417
417
|
```bash
|
|
418
|
-
npm install -g @
|
|
418
|
+
npm install -g @zhuan-ai/zhuanspec@latest
|
|
419
419
|
```
|
|
420
420
|
2. **刷新代理说明**
|
|
421
421
|
- 在每个项目内运行 `zhuanspec update` 以重新生成 AI 指导并确保最新的斜杠命令处于活动状态。
|
package/dist/cli/index.js
CHANGED
|
@@ -227,7 +227,7 @@ skillsCmd
|
|
|
227
227
|
.option('--json', 'Output as JSON for programmatic use')
|
|
228
228
|
.action(async (options) => {
|
|
229
229
|
try {
|
|
230
|
-
const skills = discoverSkills();
|
|
230
|
+
const skills = discoverSkills(process.cwd());
|
|
231
231
|
console.log(formatSkillsList(skills, !!options?.json));
|
|
232
232
|
}
|
|
233
233
|
catch (error) {
|
|
@@ -13,6 +13,7 @@ import { createChange, validateChangeName } from '../utils/change-utils.js';
|
|
|
13
13
|
import { discoverSkills } from '../core/skill-discovery.js';
|
|
14
14
|
import { getNewChangeSkillTemplate, getContinueChangeSkillTemplate, getApplyChangeSkillTemplate, getFfChangeSkillTemplate, getSyncSpecsSkillTemplate, getArchiveChangeSkillTemplate, getProposeChangeSkillTemplate, getExploreSkillTemplate, getVerifySkillTemplate, getOpsxNewCommandTemplate, getOpsxContinueCommandTemplate, getOpsxApplyCommandTemplate, getOpsxFfCommandTemplate, getOpsxSyncCommandTemplate, getOpsxArchiveCommandTemplate, getOpsxProposeCommandTemplate, getOpsxExploreCommandTemplate, getOpsxVerifyCommandTemplate } from '../core/templates/skill-templates.js';
|
|
15
15
|
import { FileSystemUtils } from '../utils/file-system.js';
|
|
16
|
+
import { parseTasks, generateExecutionPlan, renderExecutionPlanXml, renderMermaidDiagram, hasDependsAnnotations, } from '../core/task-graph/index.js';
|
|
16
17
|
const DEFAULT_SCHEMA = 'spec-driven';
|
|
17
18
|
/**
|
|
18
19
|
* Checks if color output is disabled via NO_COLOR env or --no-color flag.
|
|
@@ -176,7 +177,7 @@ async function instructionsCommand(artifactId, options) {
|
|
|
176
177
|
let discoveredSkillsJson;
|
|
177
178
|
if (artifactId === 'tasks' || artifactId === 'proposal') {
|
|
178
179
|
try {
|
|
179
|
-
const skills =
|
|
180
|
+
const skills = discoverSkills(projectRoot);
|
|
180
181
|
if (skills.length > 0) {
|
|
181
182
|
discoveredSkillsJson = JSON.stringify(skills.map((s) => ({ name: s.name, description: s.description })), null, 2);
|
|
182
183
|
}
|
|
@@ -408,12 +409,45 @@ async function generateApplyInstructions(projectRoot, changeName, schemaName) {
|
|
|
408
409
|
// Parse tasks if tracking file exists
|
|
409
410
|
let tasks = [];
|
|
410
411
|
let tracksFileExists = false;
|
|
412
|
+
let executionPlanXml;
|
|
413
|
+
let hasDependencies = false;
|
|
411
414
|
if (tracksFile) {
|
|
412
415
|
const tracksPath = path.join(changeDir, tracksFile);
|
|
413
416
|
tracksFileExists = fs.existsSync(tracksPath);
|
|
414
417
|
if (tracksFileExists) {
|
|
415
418
|
const tasksContent = await fs.promises.readFile(tracksPath, 'utf-8');
|
|
416
419
|
tasks = parseTasksFile(tasksContent);
|
|
420
|
+
// Parse tasks using task-graph module for dependency analysis
|
|
421
|
+
const parsedTasks = parseTasks(tasksContent);
|
|
422
|
+
hasDependencies = hasDependsAnnotations(parsedTasks);
|
|
423
|
+
// Only generate execution plan if:
|
|
424
|
+
// 1. Tasks have @depends annotations
|
|
425
|
+
// 2. Not all tasks are complete
|
|
426
|
+
const allTasksComplete = parsedTasks.length > 0 && parsedTasks.every(t => t.completed);
|
|
427
|
+
if (hasDependencies && !allTasksComplete) {
|
|
428
|
+
const executionPlan = generateExecutionPlan(parsedTasks);
|
|
429
|
+
executionPlanXml = renderExecutionPlanXml(executionPlan);
|
|
430
|
+
// Generate Mermaid diagram and embed in tasks.md
|
|
431
|
+
if (executionPlan && !executionPlan.hasCycle) {
|
|
432
|
+
const mermaidSection = renderMermaidDiagram(executionPlan, parsedTasks);
|
|
433
|
+
if (mermaidSection) {
|
|
434
|
+
// Read current tasks.md content
|
|
435
|
+
let currentTasksContent = tasksContent;
|
|
436
|
+
// Check if tasks.md already has Workflow Diagram section
|
|
437
|
+
const workflowDiagramRegex = /## Workflow Diagram[\s\S]*?(?=\n## |$)/;
|
|
438
|
+
if (workflowDiagramRegex.test(currentTasksContent)) {
|
|
439
|
+
// Replace existing Workflow Diagram section
|
|
440
|
+
currentTasksContent = currentTasksContent.replace(workflowDiagramRegex, mermaidSection);
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
// Append to file end
|
|
444
|
+
currentTasksContent = currentTasksContent.trimEnd() + '\n\n' + mermaidSection;
|
|
445
|
+
}
|
|
446
|
+
// Write back to tasks.md
|
|
447
|
+
fs.writeFileSync(tracksPath, currentTasksContent, 'utf-8');
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
417
451
|
}
|
|
418
452
|
}
|
|
419
453
|
// Calculate progress
|
|
@@ -480,6 +514,8 @@ See the apply command template for detailed code review workflow instructions.`;
|
|
|
480
514
|
state,
|
|
481
515
|
missingArtifacts: missingArtifacts.length > 0 ? missingArtifacts : undefined,
|
|
482
516
|
instruction,
|
|
517
|
+
executionPlanXml,
|
|
518
|
+
hasDependencies,
|
|
483
519
|
};
|
|
484
520
|
}
|
|
485
521
|
async function applyInstructionsCommand(options) {
|
|
@@ -506,7 +542,7 @@ async function applyInstructionsCommand(options) {
|
|
|
506
542
|
}
|
|
507
543
|
}
|
|
508
544
|
function printApplyInstructionsText(instructions) {
|
|
509
|
-
const { changeName, schemaName, contextFiles, progress, tasks, state, missingArtifacts, instruction } = instructions;
|
|
545
|
+
const { changeName, schemaName, contextFiles, progress, tasks, state, missingArtifacts, instruction, executionPlanXml } = instructions;
|
|
510
546
|
console.log(`## Apply: ${changeName}`);
|
|
511
547
|
console.log(`Schema: ${schemaName}`);
|
|
512
548
|
console.log();
|
|
@@ -547,6 +583,13 @@ function printApplyInstructionsText(instructions) {
|
|
|
547
583
|
}
|
|
548
584
|
console.log();
|
|
549
585
|
}
|
|
586
|
+
// Execution Plan (only if tasks have dependencies and not all complete)
|
|
587
|
+
if (executionPlanXml) {
|
|
588
|
+
console.log('### Execution Plan');
|
|
589
|
+
console.log();
|
|
590
|
+
console.log(executionPlanXml);
|
|
591
|
+
console.log();
|
|
592
|
+
}
|
|
550
593
|
// Instruction
|
|
551
594
|
console.log('### Instruction');
|
|
552
595
|
console.log(instruction);
|
|
@@ -18,6 +18,20 @@ export declare class ValidateCommand {
|
|
|
18
18
|
private validateByType;
|
|
19
19
|
private printReport;
|
|
20
20
|
private printNextSteps;
|
|
21
|
+
/**
|
|
22
|
+
* Validate task dependencies in tasks.md if present.
|
|
23
|
+
* Returns a report with validation status and execution plan.
|
|
24
|
+
*/
|
|
25
|
+
private validateTaskDependencies;
|
|
26
|
+
/**
|
|
27
|
+
* Print execution plan summary for tasks with dependencies.
|
|
28
|
+
*/
|
|
29
|
+
private printExecutionPlanSummary;
|
|
30
|
+
/**
|
|
31
|
+
* Write Mermaid diagram to tasks.md file.
|
|
32
|
+
* Replaces existing "## Workflow Diagram" section or appends at end.
|
|
33
|
+
*/
|
|
34
|
+
private writeMermaidDiagramToTasks;
|
|
21
35
|
private runBulkValidation;
|
|
22
36
|
}
|
|
23
37
|
export {};
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import ora from 'ora';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { promises as fs } from 'fs';
|
|
3
5
|
import { Validator } from '../core/validation/validator.js';
|
|
4
6
|
import { isInteractive, resolveNoInteractive } from '../utils/interactive.js';
|
|
5
7
|
import { getActiveChangeIds, getSpecIds } from '../utils/item-discovery.js';
|
|
6
8
|
import { nearestMatches } from '../utils/match.js';
|
|
9
|
+
import { parseTasks, TaskGraph, generateExecutionPlan } from '../core/task-graph/index.js';
|
|
7
10
|
export class ValidateCommand {
|
|
8
11
|
async execute(itemName, options = {}) {
|
|
9
12
|
const interactive = isInteractive(options);
|
|
@@ -102,8 +105,19 @@ export class ValidateCommand {
|
|
|
102
105
|
const changeDir = path.join(process.cwd(), 'zhuanspec', 'changes', id);
|
|
103
106
|
const start = Date.now();
|
|
104
107
|
const report = await validator.validateChangeDeltaSpecs(changeDir);
|
|
108
|
+
// Validate tasks.md dependencies if present
|
|
109
|
+
const taskReport = await this.validateTaskDependencies(changeDir);
|
|
110
|
+
// Merge task dependency issues into report
|
|
111
|
+
if (taskReport.issues.length > 0) {
|
|
112
|
+
report.issues.push(...taskReport.issues);
|
|
113
|
+
report.valid = report.valid && taskReport.valid;
|
|
114
|
+
}
|
|
115
|
+
// Write Mermaid diagram to tasks.md if generated
|
|
116
|
+
if (report.mermaidDiagram) {
|
|
117
|
+
await this.writeMermaidDiagramToTasks(changeDir, report.mermaidDiagram);
|
|
118
|
+
}
|
|
105
119
|
const durationMs = Date.now() - start;
|
|
106
|
-
this.printReport('change', id, report, durationMs, opts.json);
|
|
120
|
+
this.printReport('change', id, report, durationMs, opts.json, taskReport, { strict: opts.strict });
|
|
107
121
|
// Non-zero exit if invalid (keeps enriched output test semantics)
|
|
108
122
|
process.exitCode = report.valid ? 0 : 1;
|
|
109
123
|
return;
|
|
@@ -112,17 +126,41 @@ export class ValidateCommand {
|
|
|
112
126
|
const start = Date.now();
|
|
113
127
|
const report = await validator.validateSpec(file);
|
|
114
128
|
const durationMs = Date.now() - start;
|
|
115
|
-
this.printReport('spec', id, report, durationMs, opts.json);
|
|
129
|
+
this.printReport('spec', id, report, durationMs, opts.json, undefined, { strict: opts.strict });
|
|
116
130
|
process.exitCode = report.valid ? 0 : 1;
|
|
117
131
|
}
|
|
118
|
-
printReport(type, id, report, durationMs, json) {
|
|
132
|
+
printReport(type, id, report, durationMs, json, taskReport, opts) {
|
|
119
133
|
if (json) {
|
|
120
|
-
const out = {
|
|
134
|
+
const out = {
|
|
135
|
+
items: [{ id, type, valid: report.valid, issues: report.issues, durationMs }],
|
|
136
|
+
summary: { totals: { items: 1, passed: report.valid ? 1 : 0, failed: report.valid ? 0 : 1 }, byType: { [type]: { items: 1, passed: report.valid ? 1 : 0, failed: report.valid ? 0 : 1 } } },
|
|
137
|
+
version: '1.0'
|
|
138
|
+
};
|
|
139
|
+
// Add execution plan to JSON output if available
|
|
140
|
+
if (taskReport?.hasDependencies && taskReport.executionPlan) {
|
|
141
|
+
out.executionPlan = taskReport.executionPlan;
|
|
142
|
+
}
|
|
143
|
+
// Add strict checks to JSON output if available
|
|
144
|
+
if (report.strictChecks) {
|
|
145
|
+
out.strictChecks = report.strictChecks;
|
|
146
|
+
}
|
|
147
|
+
// Add mermaid diagram flag to JSON output if generated
|
|
148
|
+
if (report.mermaidDiagram) {
|
|
149
|
+
out.workflowDiagramGenerated = true;
|
|
150
|
+
}
|
|
121
151
|
console.log(JSON.stringify(out, null, 2));
|
|
122
152
|
return;
|
|
123
153
|
}
|
|
124
154
|
if (report.valid) {
|
|
125
155
|
console.log(`${type === 'change' ? 'Change' : 'Specification'} '${id}' is valid`);
|
|
156
|
+
// Print execution plan summary if available
|
|
157
|
+
if (taskReport?.hasDependencies && taskReport.executionPlan && !taskReport.executionPlan.hasCycle) {
|
|
158
|
+
this.printExecutionPlanSummary(taskReport.executionPlan);
|
|
159
|
+
}
|
|
160
|
+
// Print Mermaid diagram generated message
|
|
161
|
+
if (report.mermaidDiagram) {
|
|
162
|
+
console.log(' Workflow diagram generated');
|
|
163
|
+
}
|
|
126
164
|
}
|
|
127
165
|
else {
|
|
128
166
|
console.error(`${type === 'change' ? 'Change' : 'Specification'} '${id}' has issues`);
|
|
@@ -133,6 +171,21 @@ export class ValidateCommand {
|
|
|
133
171
|
}
|
|
134
172
|
this.printNextSteps(type);
|
|
135
173
|
}
|
|
174
|
+
// Print strict mode checks summary if available
|
|
175
|
+
if (opts?.strict && report.strictChecks && report.strictChecks.length > 0) {
|
|
176
|
+
console.log('');
|
|
177
|
+
console.log('Strict Mode Checks:');
|
|
178
|
+
for (const check of report.strictChecks) {
|
|
179
|
+
if (check.passed) {
|
|
180
|
+
console.log(` ${chalk.green('✓')} ${check.ruleName}`);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
const firstError = check.errors[0] || check.warnings[0] || '';
|
|
184
|
+
const errorSuffix = firstError ? ` — ${firstError}` : '';
|
|
185
|
+
console.log(` ${chalk.red('✗')} ${check.ruleName}${errorSuffix}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
136
189
|
}
|
|
137
190
|
printNextSteps(type) {
|
|
138
191
|
const bullets = [];
|
|
@@ -149,6 +202,109 @@ export class ValidateCommand {
|
|
|
149
202
|
console.error('Next steps:');
|
|
150
203
|
bullets.forEach(b => console.error(` ${b}`));
|
|
151
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Validate task dependencies in tasks.md if present.
|
|
207
|
+
* Returns a report with validation status and execution plan.
|
|
208
|
+
*/
|
|
209
|
+
async validateTaskDependencies(changeDir) {
|
|
210
|
+
const tasksFile = path.join(changeDir, 'tasks.md');
|
|
211
|
+
// Check if tasks.md exists
|
|
212
|
+
let content;
|
|
213
|
+
try {
|
|
214
|
+
content = await fs.readFile(tasksFile, 'utf-8');
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
// No tasks.md file - skip dependency validation
|
|
218
|
+
return { valid: true, hasDependencies: false, issues: [] };
|
|
219
|
+
}
|
|
220
|
+
// Parse tasks
|
|
221
|
+
const tasks = parseTasks(content);
|
|
222
|
+
if (tasks.length === 0) {
|
|
223
|
+
return { valid: true, hasDependencies: false, issues: [] };
|
|
224
|
+
}
|
|
225
|
+
// Check if any task has @depends annotation
|
|
226
|
+
const hasDependencies = tasks.some(t => t.depends.length > 0);
|
|
227
|
+
if (!hasDependencies) {
|
|
228
|
+
// No dependencies - skip validation, no execution plan output
|
|
229
|
+
return { valid: true, hasDependencies: false, issues: [] };
|
|
230
|
+
}
|
|
231
|
+
// Validate task dependencies
|
|
232
|
+
const graph = new TaskGraph(tasks);
|
|
233
|
+
const validation = graph.validate();
|
|
234
|
+
const issues = [];
|
|
235
|
+
if (!validation.valid) {
|
|
236
|
+
for (const error of validation.errors) {
|
|
237
|
+
issues.push({
|
|
238
|
+
level: 'ERROR',
|
|
239
|
+
path: 'tasks.md',
|
|
240
|
+
message: error,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Generate execution plan
|
|
245
|
+
const executionPlan = generateExecutionPlan(tasks);
|
|
246
|
+
// Add cycle-related errors from execution plan
|
|
247
|
+
if (executionPlan.hasCycle && executionPlan.errors.length > 0) {
|
|
248
|
+
for (const error of executionPlan.errors) {
|
|
249
|
+
// Avoid duplicates
|
|
250
|
+
if (!issues.some(i => i.message === error)) {
|
|
251
|
+
issues.push({
|
|
252
|
+
level: 'ERROR',
|
|
253
|
+
path: 'tasks.md',
|
|
254
|
+
message: error,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
valid: issues.filter(i => i.level === 'ERROR').length === 0,
|
|
261
|
+
hasDependencies: true,
|
|
262
|
+
issues,
|
|
263
|
+
executionPlan,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Print execution plan summary for tasks with dependencies.
|
|
268
|
+
*/
|
|
269
|
+
printExecutionPlanSummary(plan) {
|
|
270
|
+
const { waves, totalTasks, parallelTasks, serialTasks } = plan;
|
|
271
|
+
console.log(` Execution Plan: ${waves.length} waves, ${totalTasks} tasks (${parallelTasks} parallel, ${serialTasks} serial)`);
|
|
272
|
+
for (const wave of waves) {
|
|
273
|
+
const taskIds = wave.tasks.map(t => t.id).join(', ');
|
|
274
|
+
const parallelLabel = wave.parallel ? ' (parallel)' : '';
|
|
275
|
+
console.log(` Wave ${wave.wave}: [${taskIds}]${parallelLabel}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Write Mermaid diagram to tasks.md file.
|
|
280
|
+
* Replaces existing "## Workflow Diagram" section or appends at end.
|
|
281
|
+
*/
|
|
282
|
+
async writeMermaidDiagramToTasks(changeDir, mermaidDiagram) {
|
|
283
|
+
const tasksFile = path.join(changeDir, 'tasks.md');
|
|
284
|
+
let content;
|
|
285
|
+
try {
|
|
286
|
+
content = await fs.readFile(tasksFile, 'utf-8');
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
// tasks.md doesn't exist, nothing to write to
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
// Pattern to match existing "## Workflow Diagram" section
|
|
293
|
+
const workflowSectionPattern = /^## Workflow Diagram[\s\S]*?(?=^## |\z)/gm;
|
|
294
|
+
// Check if section exists
|
|
295
|
+
const hasExistingSection = workflowSectionPattern.test(content);
|
|
296
|
+
workflowSectionPattern.lastIndex = 0; // Reset regex state
|
|
297
|
+
let newContent;
|
|
298
|
+
if (hasExistingSection) {
|
|
299
|
+
// Replace existing section
|
|
300
|
+
newContent = content.replace(workflowSectionPattern, mermaidDiagram + '\n\n');
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
// Append at end
|
|
304
|
+
newContent = content.trimEnd() + '\n\n' + mermaidDiagram + '\n';
|
|
305
|
+
}
|
|
306
|
+
await fs.writeFile(tasksFile, newContent, 'utf-8');
|
|
307
|
+
}
|
|
152
308
|
async runBulkValidation(scope, opts) {
|
|
153
309
|
const spinner = !opts.json && !opts.noInteractive ? ora('Validating...').start() : undefined;
|
|
154
310
|
const [changeIds, specIds] = await Promise.all([
|
|
@@ -13,10 +13,10 @@ export interface DiscoveredSkill {
|
|
|
13
13
|
* Discover available skills by scanning priority paths.
|
|
14
14
|
* Uses short-circuit strategy: once a non-empty directory is found, stops scanning.
|
|
15
15
|
*
|
|
16
|
-
* @param
|
|
16
|
+
* @param projectRoot - Project root path for scanning project-local skills directory
|
|
17
17
|
* @returns Array of discovered skills
|
|
18
18
|
*/
|
|
19
|
-
export declare function discoverSkills(
|
|
19
|
+
export declare function discoverSkills(projectRoot?: string): DiscoveredSkill[];
|
|
20
20
|
/**
|
|
21
21
|
* Format discovered skills for display.
|
|
22
22
|
*/
|
|
@@ -73,15 +73,28 @@ function findSkillFiles(dir) {
|
|
|
73
73
|
}
|
|
74
74
|
return results;
|
|
75
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* Build the list of paths to scan for skills.
|
|
78
|
+
* Project-local skills directory is scanned first if projectRoot is provided.
|
|
79
|
+
*/
|
|
80
|
+
function buildScanPaths(projectRoot) {
|
|
81
|
+
const paths = [];
|
|
82
|
+
if (projectRoot) {
|
|
83
|
+
paths.push(path.join(projectRoot, 'skills'));
|
|
84
|
+
}
|
|
85
|
+
paths.push(...SCAN_PRIORITY);
|
|
86
|
+
return paths;
|
|
87
|
+
}
|
|
76
88
|
/**
|
|
77
89
|
* Discover available skills by scanning priority paths.
|
|
78
90
|
* Uses short-circuit strategy: once a non-empty directory is found, stops scanning.
|
|
79
91
|
*
|
|
80
|
-
* @param
|
|
92
|
+
* @param projectRoot - Project root path for scanning project-local skills directory
|
|
81
93
|
* @returns Array of discovered skills
|
|
82
94
|
*/
|
|
83
|
-
export function discoverSkills(
|
|
84
|
-
|
|
95
|
+
export function discoverSkills(projectRoot) {
|
|
96
|
+
const scanPaths = buildScanPaths(projectRoot);
|
|
97
|
+
for (const scanPath of scanPaths) {
|
|
85
98
|
const resolved = resolvePath(scanPath);
|
|
86
99
|
// Check if directory exists
|
|
87
100
|
try {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution Planner
|
|
3
|
+
*
|
|
4
|
+
* Generates wave-based execution plans from parsed tasks.
|
|
5
|
+
* Uses a layer-by-layer approach: each wave contains tasks with in-degree 0.
|
|
6
|
+
*/
|
|
7
|
+
import type { ParsedTask, ExecutionPlan } from './types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Generates an execution plan from parsed tasks.
|
|
10
|
+
*
|
|
11
|
+
* Algorithm: Iteratively peel off nodes with in-degree 0 to form waves.
|
|
12
|
+
*
|
|
13
|
+
* @param tasks - Array of parsed tasks
|
|
14
|
+
* @returns Execution plan with waves
|
|
15
|
+
*/
|
|
16
|
+
export declare function generateExecutionPlan(tasks: ParsedTask[]): ExecutionPlan;
|
|
17
|
+
//# sourceMappingURL=execution-planner.d.ts.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execution Planner
|
|
3
|
+
*
|
|
4
|
+
* Generates wave-based execution plans from parsed tasks.
|
|
5
|
+
* Uses a layer-by-layer approach: each wave contains tasks with in-degree 0.
|
|
6
|
+
*/
|
|
7
|
+
import { TaskGraph } from './task-graph.js';
|
|
8
|
+
/**
|
|
9
|
+
* Generates an execution plan from parsed tasks.
|
|
10
|
+
*
|
|
11
|
+
* Algorithm: Iteratively peel off nodes with in-degree 0 to form waves.
|
|
12
|
+
*
|
|
13
|
+
* @param tasks - Array of parsed tasks
|
|
14
|
+
* @returns Execution plan with waves
|
|
15
|
+
*/
|
|
16
|
+
export function generateExecutionPlan(tasks) {
|
|
17
|
+
// Filter out already completed tasks
|
|
18
|
+
const incompleteTasks = tasks.filter(t => !t.completed);
|
|
19
|
+
// Handle empty case
|
|
20
|
+
if (incompleteTasks.length === 0) {
|
|
21
|
+
return {
|
|
22
|
+
waves: [],
|
|
23
|
+
totalTasks: 0,
|
|
24
|
+
parallelTasks: 0,
|
|
25
|
+
serialTasks: 0,
|
|
26
|
+
hasCycle: false,
|
|
27
|
+
errors: [],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// Create graph and validate
|
|
31
|
+
const graph = new TaskGraph(incompleteTasks);
|
|
32
|
+
const validation = graph.validate();
|
|
33
|
+
// Check for cycles
|
|
34
|
+
if (validation.errors.some(e => e.includes('Circular dependency'))) {
|
|
35
|
+
return {
|
|
36
|
+
waves: [],
|
|
37
|
+
totalTasks: incompleteTasks.length,
|
|
38
|
+
parallelTasks: 0,
|
|
39
|
+
serialTasks: 0,
|
|
40
|
+
hasCycle: true,
|
|
41
|
+
errors: validation.errors,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// Collect non-cycle errors
|
|
45
|
+
const errors = validation.errors.filter(e => !e.includes('Circular dependency'));
|
|
46
|
+
// Build waves using modified Kahn's algorithm
|
|
47
|
+
const waves = [];
|
|
48
|
+
const completed = new Set();
|
|
49
|
+
// Track in-degrees locally
|
|
50
|
+
const inDegree = new Map();
|
|
51
|
+
const dependents = new Map();
|
|
52
|
+
// Initialize
|
|
53
|
+
for (const task of incompleteTasks) {
|
|
54
|
+
// Only count dependencies that exist in incomplete tasks
|
|
55
|
+
const validDeps = task.depends.filter(d => incompleteTasks.some(t => t.id === d));
|
|
56
|
+
inDegree.set(task.id, validDeps.length);
|
|
57
|
+
dependents.set(task.id, []);
|
|
58
|
+
}
|
|
59
|
+
// Build reverse adjacency
|
|
60
|
+
for (const task of incompleteTasks) {
|
|
61
|
+
for (const dep of task.depends) {
|
|
62
|
+
if (dependents.has(dep)) {
|
|
63
|
+
dependents.get(dep).push(task.id);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
let waveNumber = 0;
|
|
68
|
+
let remainingTasks = incompleteTasks.length;
|
|
69
|
+
while (remainingTasks > 0) {
|
|
70
|
+
waveNumber++;
|
|
71
|
+
// Find all tasks with in-degree 0
|
|
72
|
+
const readyIds = [...inDegree.keys()]
|
|
73
|
+
.filter(id => !completed.has(id) && inDegree.get(id) === 0)
|
|
74
|
+
.sort();
|
|
75
|
+
if (readyIds.length === 0) {
|
|
76
|
+
// Shouldn't happen if no cycles, but handle gracefully
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
const readyTasks = readyIds
|
|
80
|
+
.map(id => incompleteTasks.find(t => t.id === id))
|
|
81
|
+
.filter(Boolean);
|
|
82
|
+
waves.push({
|
|
83
|
+
wave: waveNumber,
|
|
84
|
+
tasks: readyTasks,
|
|
85
|
+
parallel: readyTasks.length > 1,
|
|
86
|
+
});
|
|
87
|
+
// Mark as completed and update in-degrees
|
|
88
|
+
for (const id of readyIds) {
|
|
89
|
+
completed.add(id);
|
|
90
|
+
remainingTasks--;
|
|
91
|
+
// Decrease in-degree of dependents
|
|
92
|
+
for (const dep of dependents.get(id) || []) {
|
|
93
|
+
if (!completed.has(dep)) {
|
|
94
|
+
inDegree.set(dep, inDegree.get(dep) - 1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Calculate statistics
|
|
100
|
+
let parallelTasks = 0;
|
|
101
|
+
let serialTasks = 0;
|
|
102
|
+
for (const wave of waves) {
|
|
103
|
+
if (wave.parallel) {
|
|
104
|
+
parallelTasks += wave.tasks.length;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
serialTasks += wave.tasks.length;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
waves,
|
|
112
|
+
totalTasks: incompleteTasks.length,
|
|
113
|
+
parallelTasks,
|
|
114
|
+
serialTasks,
|
|
115
|
+
hasCycle: false,
|
|
116
|
+
errors,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=execution-planner.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Graph Module
|
|
3
|
+
*
|
|
4
|
+
* Provides task dependency graph operations for tasks.md processing.
|
|
5
|
+
*/
|
|
6
|
+
export type { ParsedTask, ExecutionWave, ExecutionPlan } from './types.js';
|
|
7
|
+
export { parseTasks } from './task-parser.js';
|
|
8
|
+
export { TaskGraph, type ValidationResult } from './task-graph.js';
|
|
9
|
+
export { generateExecutionPlan } from './execution-planner.js';
|
|
10
|
+
export { renderExecutionPlanXml, hasDependsAnnotations } from './xml-renderer.js';
|
|
11
|
+
export { renderMermaidDiagram } from './mermaid-renderer.js';
|
|
12
|
+
export type { MermaidRenderOptions } from './mermaid-renderer.js';
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Graph Module
|
|
3
|
+
*
|
|
4
|
+
* Provides task dependency graph operations for tasks.md processing.
|
|
5
|
+
*/
|
|
6
|
+
// Parser
|
|
7
|
+
export { parseTasks } from './task-parser.js';
|
|
8
|
+
// Graph
|
|
9
|
+
export { TaskGraph } from './task-graph.js';
|
|
10
|
+
// Execution planner
|
|
11
|
+
export { generateExecutionPlan } from './execution-planner.js';
|
|
12
|
+
// XML Renderer
|
|
13
|
+
export { renderExecutionPlanXml, hasDependsAnnotations } from './xml-renderer.js';
|
|
14
|
+
// Mermaid Renderer
|
|
15
|
+
export { renderMermaidDiagram } from './mermaid-renderer.js';
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mermaid Renderer for Execution Plan
|
|
3
|
+
*
|
|
4
|
+
* Renders ExecutionPlan to Mermaid flowchart format for visualization in tasks.md.
|
|
5
|
+
*/
|
|
6
|
+
import type { ExecutionPlan, ParsedTask } from './types.js';
|
|
7
|
+
export interface MermaidRenderOptions {
|
|
8
|
+
/** Maximum parallelism threshold. Default: 3 */
|
|
9
|
+
maxParallelism?: number;
|
|
10
|
+
/** Whether to show @skill annotations in node labels. Default: true */
|
|
11
|
+
showSkills?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Renders an ExecutionPlan to Mermaid flowchart format.
|
|
15
|
+
*
|
|
16
|
+
* @param plan - The execution plan to render
|
|
17
|
+
* @param tasks - The parsed tasks (used for task details)
|
|
18
|
+
* @param options - Render options
|
|
19
|
+
* @returns Complete markdown section with Mermaid diagram
|
|
20
|
+
*/
|
|
21
|
+
export declare function renderMermaidDiagram(plan: ExecutionPlan, tasks: ParsedTask[], options?: MermaidRenderOptions): string;
|
|
22
|
+
//# sourceMappingURL=mermaid-renderer.d.ts.map
|