@gordon.gan/specflow 1.0.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.
Files changed (178) hide show
  1. package/LICENSE +31 -0
  2. package/README.md +442 -0
  3. package/bin/specflow.js +2 -0
  4. package/dist/cli/commands/change-archive.d.ts +26 -0
  5. package/dist/cli/commands/change-archive.js +50 -0
  6. package/dist/cli/commands/change-new.d.ts +18 -0
  7. package/dist/cli/commands/change-new.js +53 -0
  8. package/dist/cli/commands/change-phase.d.ts +33 -0
  9. package/dist/cli/commands/change-phase.js +66 -0
  10. package/dist/cli/commands/change-status.d.ts +35 -0
  11. package/dist/cli/commands/change-status.js +95 -0
  12. package/dist/cli/commands/doctor.d.ts +9 -0
  13. package/dist/cli/commands/doctor.js +48 -0
  14. package/dist/cli/commands/init.d.ts +16 -0
  15. package/dist/cli/commands/init.js +122 -0
  16. package/dist/cli/commands/instructions.d.ts +21 -0
  17. package/dist/cli/commands/instructions.js +75 -0
  18. package/dist/cli/commands/parity-report.d.ts +10 -0
  19. package/dist/cli/commands/parity-report.js +67 -0
  20. package/dist/cli/commands/sync.d.ts +4 -0
  21. package/dist/cli/commands/sync.js +40 -0
  22. package/dist/cli/commands/validate.d.ts +21 -0
  23. package/dist/cli/commands/validate.js +46 -0
  24. package/dist/cli/index.d.ts +1 -0
  25. package/dist/cli/index.js +52 -0
  26. package/dist/core/archive.d.ts +39 -0
  27. package/dist/core/archive.js +154 -0
  28. package/dist/core/artifact-graph/graph.d.ts +60 -0
  29. package/dist/core/artifact-graph/graph.js +141 -0
  30. package/dist/core/artifact-graph/index.d.ts +10 -0
  31. package/dist/core/artifact-graph/index.js +7 -0
  32. package/dist/core/artifact-graph/instruction-loader.d.ts +45 -0
  33. package/dist/core/artifact-graph/instruction-loader.js +37 -0
  34. package/dist/core/artifact-graph/outputs.d.ts +35 -0
  35. package/dist/core/artifact-graph/outputs.js +48 -0
  36. package/dist/core/artifact-graph/resolver.d.ts +54 -0
  37. package/dist/core/artifact-graph/resolver.js +139 -0
  38. package/dist/core/artifact-graph/schema.d.ts +23 -0
  39. package/dist/core/artifact-graph/schema.js +115 -0
  40. package/dist/core/artifact-graph/state.d.ts +9 -0
  41. package/dist/core/artifact-graph/state.js +93 -0
  42. package/dist/core/artifact-graph/types.d.ts +123 -0
  43. package/dist/core/artifact-graph/types.js +29 -0
  44. package/dist/core/parsers/change-parser.d.ts +20 -0
  45. package/dist/core/parsers/change-parser.js +22 -0
  46. package/dist/core/parsers/index.d.ts +7 -0
  47. package/dist/core/parsers/index.js +4 -0
  48. package/dist/core/parsers/markdown-parser.d.ts +35 -0
  49. package/dist/core/parsers/markdown-parser.js +193 -0
  50. package/dist/core/parsers/requirement-blocks.d.ts +21 -0
  51. package/dist/core/parsers/requirement-blocks.js +57 -0
  52. package/dist/core/parsers/spec-structure.d.ts +22 -0
  53. package/dist/core/parsers/spec-structure.js +105 -0
  54. package/dist/core/specs-apply.d.ts +22 -0
  55. package/dist/core/specs-apply.js +165 -0
  56. package/dist/core/templates/index.d.ts +31 -0
  57. package/dist/core/templates/index.js +80 -0
  58. package/dist/core/templates/types.d.ts +4 -0
  59. package/dist/core/templates/types.js +1 -0
  60. package/dist/core/validation/constants.d.ts +14 -0
  61. package/dist/core/validation/constants.js +14 -0
  62. package/dist/core/validation/types.d.ts +13 -0
  63. package/dist/core/validation/types.js +6 -0
  64. package/dist/core/validation/validator.d.ts +24 -0
  65. package/dist/core/validation/validator.js +250 -0
  66. package/dist/integrations/claude/adapter.d.ts +2 -0
  67. package/dist/integrations/claude/adapter.js +106 -0
  68. package/dist/integrations/codex/adapter.d.ts +2 -0
  69. package/dist/integrations/codex/adapter.js +141 -0
  70. package/dist/integrations/cursor/adapter.d.ts +2 -0
  71. package/dist/integrations/cursor/adapter.js +126 -0
  72. package/dist/integrations/shared/adapter-registry.d.ts +4 -0
  73. package/dist/integrations/shared/adapter-registry.js +27 -0
  74. package/dist/integrations/shared/asset-copy.d.ts +2 -0
  75. package/dist/integrations/shared/asset-copy.js +44 -0
  76. package/dist/integrations/shared/asset-hash.d.ts +4 -0
  77. package/dist/integrations/shared/asset-hash.js +54 -0
  78. package/dist/integrations/shared/capability-evidence.d.ts +1 -0
  79. package/dist/integrations/shared/capability-evidence.js +42 -0
  80. package/dist/integrations/shared/command-catalog.d.ts +6 -0
  81. package/dist/integrations/shared/command-catalog.js +13 -0
  82. package/dist/integrations/shared/managed-assets.d.ts +2 -0
  83. package/dist/integrations/shared/managed-assets.js +21 -0
  84. package/dist/integrations/shared/marker-write.d.ts +3 -0
  85. package/dist/integrations/shared/marker-write.js +48 -0
  86. package/dist/integrations/shared/migration-state.d.ts +2 -0
  87. package/dist/integrations/shared/migration-state.js +67 -0
  88. package/dist/integrations/shared/parity-comparator.d.ts +6 -0
  89. package/dist/integrations/shared/parity-comparator.js +97 -0
  90. package/dist/integrations/shared/parity-manifest.d.ts +13 -0
  91. package/dist/integrations/shared/parity-manifest.js +58 -0
  92. package/dist/integrations/shared/runtime-assets.d.ts +2 -0
  93. package/dist/integrations/shared/runtime-assets.js +21 -0
  94. package/dist/integrations/shared/skill-renderer.d.ts +5 -0
  95. package/dist/integrations/shared/skill-renderer.js +77 -0
  96. package/dist/integrations/shared/types.d.ts +35 -0
  97. package/dist/integrations/shared/types.js +1 -0
  98. package/dist/utils/change-metadata.d.ts +38 -0
  99. package/dist/utils/change-metadata.js +82 -0
  100. package/dist/utils/change-utils.d.ts +53 -0
  101. package/dist/utils/change-utils.js +97 -0
  102. package/dist/utils/file-system.d.ts +35 -0
  103. package/dist/utils/file-system.js +67 -0
  104. package/dist/utils/index.d.ts +7 -0
  105. package/dist/utils/index.js +5 -0
  106. package/dist/utils/project-root.d.ts +22 -0
  107. package/dist/utils/project-root.js +44 -0
  108. package/dist/utils/task-progress.d.ts +39 -0
  109. package/dist/utils/task-progress.js +62 -0
  110. package/package.json +72 -0
  111. package/prompts/build/ecc-go-reviewer.md +80 -0
  112. package/prompts/build/ecc-java-reviewer.md +185 -0
  113. package/prompts/build/ecc-kotlin-reviewer.md +163 -0
  114. package/prompts/build/ecc-python-reviewer.md +102 -0
  115. package/prompts/build/ecc-rust-reviewer.md +98 -0
  116. package/prompts/build/ecc-typescript-reviewer.md +116 -0
  117. package/prompts/build/language-router.md +22 -0
  118. package/prompts/build/phase-a-plan.md +288 -0
  119. package/prompts/build/phase-b-execute.md +276 -0
  120. package/prompts/build/phase-b-review.md +119 -0
  121. package/prompts/build/phase-b-worktree.md +215 -0
  122. package/prompts/build/plan-document-reviewer.md +51 -0
  123. package/prompts/build/tdd.md +184 -0
  124. package/prompts/done/branch-finish.md +204 -0
  125. package/prompts/fix/debug.md +249 -0
  126. package/prompts/plan/design-draft.md +123 -0
  127. package/prompts/plan/proposal.md +85 -0
  128. package/prompts/plan/specs.md +114 -0
  129. package/prompts/plan/tasks-draft.md +123 -0
  130. package/prompts/reference/openspec/apply-instruction.md +2 -0
  131. package/prompts/reference/openspec/design-instruction.md +20 -0
  132. package/prompts/reference/openspec/proposal-instruction.md +18 -0
  133. package/prompts/reference/openspec/specs-instruction.md +47 -0
  134. package/prompts/reference/openspec/tasks-instruction.md +26 -0
  135. package/prompts/reference/specflow/example-design.md +284 -0
  136. package/prompts/reference/superpowers/anthropic-best-practices.md +1150 -0
  137. package/prompts/reference/superpowers/codex-tools.md +29 -0
  138. package/prompts/reference/superpowers/condition-based-waiting-example.ts +158 -0
  139. package/prompts/reference/superpowers/condition-based-waiting.md +115 -0
  140. package/prompts/reference/superpowers/defense-in-depth.md +122 -0
  141. package/prompts/reference/superpowers/find-polluter.sh +63 -0
  142. package/prompts/reference/superpowers/graphviz-conventions.dot +172 -0
  143. package/prompts/reference/superpowers/persuasion-principles.md +187 -0
  144. package/prompts/reference/superpowers/root-cause-tracing.md +169 -0
  145. package/prompts/reference/superpowers/testing-anti-patterns.md +299 -0
  146. package/prompts/reference/superpowers/testing-skills-with-subagents.md +384 -0
  147. package/prompts/reference/superpowers/using-superpowers.md +117 -0
  148. package/prompts/reference/superpowers/writing-skills.md +655 -0
  149. package/prompts/refine/brainstorm.md +241 -0
  150. package/prompts/refine/design-output.md +126 -0
  151. package/prompts/refine/spec-document-reviewer.md +51 -0
  152. package/prompts/refine/update-artifacts.md +185 -0
  153. package/prompts/review/code-review.md +223 -0
  154. package/prompts/shared/code-reviewer-prompt.md +98 -0
  155. package/prompts/shared/dispatching-parallel-agents.md +143 -0
  156. package/prompts/shared/executing-plans.md +67 -0
  157. package/prompts/shared/implementer-prompt.md +115 -0
  158. package/prompts/shared/receiving-code-review.md +174 -0
  159. package/prompts/shared/spec-reviewer-prompt.md +63 -0
  160. package/prompts/test/tdd.md +236 -0
  161. package/prompts/test/verification.md +129 -0
  162. package/prompts/verify/ecc-security-reviewer.md +112 -0
  163. package/prompts/verify/verification.md +196 -0
  164. package/schemas/specflow/schema.yaml +48 -0
  165. package/skills/specflow-build/SKILL.md +129 -0
  166. package/skills/specflow-done/SKILL.md +68 -0
  167. package/skills/specflow-fix/SKILL.md +74 -0
  168. package/skills/specflow-plan/SKILL.md +82 -0
  169. package/skills/specflow-refine/SKILL.md +128 -0
  170. package/skills/specflow-review/SKILL.md +40 -0
  171. package/skills/specflow-scan/SKILL.md +48 -0
  172. package/skills/specflow-snap/SKILL.md +46 -0
  173. package/skills/specflow-test/SKILL.md +48 -0
  174. package/skills/specflow-verify/SKILL.md +77 -0
  175. package/templates/design.md +19 -0
  176. package/templates/proposal.md +23 -0
  177. package/templates/spec.md +42 -0
  178. package/templates/tasks.md +9 -0
@@ -0,0 +1,46 @@
1
+ /**
2
+ * CLI command: specflow validate <file>
3
+ *
4
+ * Validates a spec file for structural correctness.
5
+ */
6
+ import { promises as fs } from 'node:fs';
7
+ import { validateSpec } from '../../core/validation/validator.js';
8
+ /**
9
+ * Validates a spec file at the given path.
10
+ *
11
+ * Reads the file and runs the spec validator, returning structured results.
12
+ *
13
+ * @param filePath - Absolute path to the spec file
14
+ * @returns Validation result with valid flag and any errors
15
+ * @throws When the file cannot be read
16
+ */
17
+ export async function validateSpecFile(filePath) {
18
+ const content = await fs.readFile(filePath, 'utf-8');
19
+ return validateSpec(content);
20
+ }
21
+ /**
22
+ * Registers the `validate` command with Commander.
23
+ */
24
+ export function registerValidateCommand(program) {
25
+ program
26
+ .command('validate <file>')
27
+ .description('Validate a spec file for structural correctness')
28
+ .option('--json', 'Output as JSON')
29
+ .action(async (file, opts) => {
30
+ const result = await validateSpecFile(file);
31
+ if (opts.json) {
32
+ console.info(JSON.stringify(result, null, 2));
33
+ }
34
+ else if (result.valid) {
35
+ console.info('Valid: no errors found.');
36
+ }
37
+ else {
38
+ console.error(`Found ${result.errors.length} error(s):`);
39
+ for (const err of result.errors) {
40
+ const lineInfo = err.line ? ` (line ${err.line})` : '';
41
+ console.error(` - ${err.message}${lineInfo}`);
42
+ }
43
+ process.exitCode = 1;
44
+ }
45
+ });
46
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,52 @@
1
+ import { Command } from 'commander';
2
+ import { readFileSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { dirname, join } from 'node:path';
5
+ import { registerChangeNewCommand } from './commands/change-new.js';
6
+ import { registerChangeStatusCommand } from './commands/change-status.js';
7
+ import { registerChangeArchiveCommand } from './commands/change-archive.js';
8
+ import { registerChangePhaseCommand } from './commands/change-phase.js';
9
+ import { registerInstructionsCommand } from './commands/instructions.js';
10
+ import { registerValidateCommand } from './commands/validate.js';
11
+ import { registerInitCommand } from './commands/init.js';
12
+ import { registerSyncCommand } from './commands/sync.js';
13
+ import { registerDoctorCommand } from './commands/doctor.js';
14
+ import { registerParityReportCommand } from './commands/parity-report.js';
15
+ const __filename = fileURLToPath(import.meta.url);
16
+ const __dirname = dirname(__filename);
17
+ const pkg = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf-8'));
18
+ const program = new Command();
19
+ program
20
+ .name('specflow')
21
+ .description('Unified spec-driven development: OpenSpec planning + Superpowers execution')
22
+ .version(pkg.version);
23
+ // Group command: specflow change <new|status|archive>
24
+ const changeCmd = program
25
+ .command('change')
26
+ .description('Manage changes (new, status, archive)');
27
+ registerChangeNewCommand(changeCmd);
28
+ registerChangeStatusCommand(changeCmd);
29
+ registerChangeArchiveCommand(changeCmd);
30
+ registerChangePhaseCommand(changeCmd);
31
+ // Top-level commands
32
+ registerInstructionsCommand(program);
33
+ registerValidateCommand(program);
34
+ registerInitCommand(program);
35
+ registerSyncCommand(program);
36
+ registerDoctorCommand(program);
37
+ registerParityReportCommand(program);
38
+ program.exitOverride();
39
+ try {
40
+ await program.parseAsync();
41
+ }
42
+ catch (error) {
43
+ if (error instanceof Error && 'code' in error) {
44
+ const code = error.code;
45
+ if (code === 'commander.helpDisplayed' || code === 'commander.version') {
46
+ process.exit(0);
47
+ }
48
+ }
49
+ const message = error instanceof Error ? error.message : String(error);
50
+ console.error(`错误: ${message}`);
51
+ process.exit(1);
52
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Archive module.
3
+ *
4
+ * Archives a completed change by:
5
+ * 1. Validating all delta specs in the change
6
+ * 2. Applying deltas to their corresponding main specs
7
+ * 3. Moving the change directory to the archive
8
+ */
9
+ export interface ArchiveResult {
10
+ readonly success: boolean;
11
+ readonly errors: readonly string[];
12
+ readonly archivePath?: string;
13
+ }
14
+ export interface ArchiveOptions {
15
+ /**
16
+ * If true, archive even when the change is not in phase=built.
17
+ * A warning is emitted to stderr when force is applied.
18
+ */
19
+ readonly force?: boolean;
20
+ }
21
+ /**
22
+ * Archive a completed change.
23
+ *
24
+ * Steps:
25
+ * 0. Validate phase gate: require phase=built unless options.force is true
26
+ * 1. Find all delta spec files in `specflow/changes/<name>/specs/`
27
+ * 2. Validate each delta spec
28
+ * 3. For each delta spec, find the corresponding main spec in `specflow/specs/`
29
+ * 4. Apply deltas to main specs
30
+ * 5. Move the change directory to `specflow/changes/archive/<date>-<name>/`
31
+ * 6. Update archived .specflow.yaml with phase=archived
32
+ * 7. Return success/failure with details
33
+ *
34
+ * @param changeName - The name of the change to archive
35
+ * @param projectRoot - Absolute path to the project root
36
+ * @param options - Optional archive options (e.g. force)
37
+ * @returns An ArchiveResult with success flag, errors, and archive path
38
+ */
39
+ export declare function archiveChange(changeName: string, projectRoot: string, options?: ArchiveOptions): Promise<ArchiveResult>;
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Archive module.
3
+ *
4
+ * Archives a completed change by:
5
+ * 1. Validating all delta specs in the change
6
+ * 2. Applying deltas to their corresponding main specs
7
+ * 3. Moving the change directory to the archive
8
+ */
9
+ import { promises as fs } from 'node:fs';
10
+ import { join, relative } from 'node:path';
11
+ import { validateSpec } from './validation/validator.js';
12
+ import { applyDeltaSpec } from './specs-apply.js';
13
+ import { readChangeMetadata, writeChangeMetadata, } from '../utils/change-metadata.js';
14
+ /**
15
+ * Recursively list all `.md` files in a directory, returning paths relative to the base dir.
16
+ */
17
+ async function listMarkdownFiles(dir, base) {
18
+ const root = base ?? dir;
19
+ try {
20
+ const entries = await fs.readdir(dir, { withFileTypes: true });
21
+ const results = [];
22
+ for (const entry of entries) {
23
+ const fullPath = join(dir, entry.name);
24
+ if (entry.isDirectory()) {
25
+ const nested = await listMarkdownFiles(fullPath, root);
26
+ results.push(...nested);
27
+ }
28
+ else if (entry.isFile() && entry.name.endsWith('.md')) {
29
+ results.push(relative(root, fullPath));
30
+ }
31
+ }
32
+ return results;
33
+ }
34
+ catch (error) {
35
+ if (error instanceof Error &&
36
+ 'code' in error &&
37
+ error.code === 'ENOENT') {
38
+ return [];
39
+ }
40
+ throw error;
41
+ }
42
+ }
43
+ /**
44
+ * Get today's date as YYYY-MM-DD string.
45
+ */
46
+ function todayDatePrefix() {
47
+ const now = new Date();
48
+ const year = now.getFullYear();
49
+ const month = String(now.getMonth() + 1).padStart(2, '0');
50
+ const day = String(now.getDate()).padStart(2, '0');
51
+ return `${year}-${month}-${day}`;
52
+ }
53
+ /**
54
+ * Archive a completed change.
55
+ *
56
+ * Steps:
57
+ * 0. Validate phase gate: require phase=built unless options.force is true
58
+ * 1. Find all delta spec files in `specflow/changes/<name>/specs/`
59
+ * 2. Validate each delta spec
60
+ * 3. For each delta spec, find the corresponding main spec in `specflow/specs/`
61
+ * 4. Apply deltas to main specs
62
+ * 5. Move the change directory to `specflow/changes/archive/<date>-<name>/`
63
+ * 6. Update archived .specflow.yaml with phase=archived
64
+ * 7. Return success/failure with details
65
+ *
66
+ * @param changeName - The name of the change to archive
67
+ * @param projectRoot - Absolute path to the project root
68
+ * @param options - Optional archive options (e.g. force)
69
+ * @returns An ArchiveResult with success flag, errors, and archive path
70
+ */
71
+ export async function archiveChange(changeName, projectRoot, options = {}) {
72
+ const changeDir = join(projectRoot, 'specflow', 'changes', changeName);
73
+ const deltaSpecsDir = join(changeDir, 'specs');
74
+ const mainSpecsDir = join(projectRoot, 'specflow', 'specs');
75
+ // 0. Phase gate: require phase=built unless --force
76
+ const metadata = await readChangeMetadata(changeDir);
77
+ const currentPhase = metadata?.phase;
78
+ if (currentPhase !== 'built' && !options.force) {
79
+ const phaseLabel = currentPhase ?? 'unknown';
80
+ return {
81
+ success: false,
82
+ errors: [
83
+ `Cannot archive: change '${changeName}' is in phase '${phaseLabel}', expected 'built'. Complete '/specflow:build' first, or pass '--force' to archive anyway.`,
84
+ ],
85
+ };
86
+ }
87
+ if (options.force && currentPhase !== 'built') {
88
+ const phaseLabel = currentPhase ?? 'unknown';
89
+ console.warn(`Warning: archiving "${changeName}" in phase ${phaseLabel} with --force. ` +
90
+ `Consider running /specflow:build first.`);
91
+ }
92
+ // 1. Find delta spec files
93
+ const deltaFiles = await listMarkdownFiles(deltaSpecsDir);
94
+ if (deltaFiles.length === 0) {
95
+ return {
96
+ success: false,
97
+ errors: [`No delta spec files found in ${deltaSpecsDir}`],
98
+ };
99
+ }
100
+ // 2. Validate each delta spec
101
+ const allErrors = [];
102
+ for (const file of deltaFiles) {
103
+ const deltaContent = await fs.readFile(join(deltaSpecsDir, file), 'utf-8');
104
+ const validation = validateSpec(deltaContent);
105
+ if (!validation.valid) {
106
+ for (const err of validation.errors) {
107
+ allErrors.push(`${file}: ${err.message}`);
108
+ }
109
+ }
110
+ }
111
+ if (allErrors.length > 0) {
112
+ return { success: false, errors: allErrors };
113
+ }
114
+ // 3 & 4. Apply each delta to its corresponding main spec
115
+ for (const file of deltaFiles) {
116
+ const deltaContent = await fs.readFile(join(deltaSpecsDir, file), 'utf-8');
117
+ const mainSpecPath = join(mainSpecsDir, file);
118
+ const mainSpecDir = join(mainSpecPath, '..');
119
+ await fs.mkdir(mainSpecDir, { recursive: true });
120
+ let mainContent;
121
+ try {
122
+ mainContent = await fs.readFile(mainSpecPath, 'utf-8');
123
+ }
124
+ catch (error) {
125
+ if (error instanceof Error &&
126
+ 'code' in error &&
127
+ error.code === 'ENOENT') {
128
+ // If main spec doesn't exist yet, start with empty content
129
+ mainContent = '';
130
+ }
131
+ else {
132
+ throw error;
133
+ }
134
+ }
135
+ const updatedContent = applyDeltaSpec(mainContent, deltaContent);
136
+ await fs.writeFile(mainSpecPath, updatedContent, 'utf-8');
137
+ }
138
+ // 5. Move change directory to archive
139
+ const datePrefix = todayDatePrefix();
140
+ const archiveDir = join(projectRoot, 'specflow', 'changes', 'archive');
141
+ const archiveDest = join(archiveDir, `${datePrefix}-${changeName}`);
142
+ await fs.mkdir(archiveDir, { recursive: true });
143
+ await fs.rename(changeDir, archiveDest);
144
+ // 6. Update archived metadata to phase=archived (preserve other fields)
145
+ const archivedMeta = await readChangeMetadata(archiveDest);
146
+ if (archivedMeta !== null) {
147
+ await writeChangeMetadata(archiveDest, { ...archivedMeta, phase: 'archived' });
148
+ }
149
+ return {
150
+ success: true,
151
+ errors: [],
152
+ archivePath: archiveDest,
153
+ };
154
+ }
@@ -0,0 +1,60 @@
1
+ import type { Artifact, SchemaYaml, CompletedSet, BlockedArtifacts } from './types.js';
2
+ /**
3
+ * Represents an artifact dependency graph.
4
+ * Provides methods for querying build order, ready artifacts, and completion status.
5
+ *
6
+ * Immutable: all query methods return new arrays/objects, never mutate internal state.
7
+ */
8
+ export declare class ArtifactGraph {
9
+ private readonly artifacts;
10
+ private readonly schema;
11
+ private constructor();
12
+ /**
13
+ * Creates an ArtifactGraph from a YAML file path.
14
+ */
15
+ static fromYaml(filePath: string): ArtifactGraph;
16
+ /**
17
+ * Creates an ArtifactGraph from YAML content string.
18
+ */
19
+ static fromYamlContent(yamlContent: string): ArtifactGraph;
20
+ /**
21
+ * Creates an ArtifactGraph from a pre-validated schema object.
22
+ */
23
+ static fromSchema(schema: SchemaYaml): ArtifactGraph;
24
+ /**
25
+ * Gets a single artifact by ID.
26
+ */
27
+ getArtifact(id: string): Artifact | undefined;
28
+ /**
29
+ * Gets all artifacts in the graph (returns a new array).
30
+ */
31
+ getAllArtifacts(): readonly Artifact[];
32
+ /**
33
+ * Gets the schema name.
34
+ */
35
+ getName(): string;
36
+ /**
37
+ * Gets the schema version.
38
+ */
39
+ getVersion(): number;
40
+ /**
41
+ * Computes the topological build order using Kahn's algorithm.
42
+ * Returns artifact IDs in a valid order: dependencies before dependents.
43
+ * Ties are broken alphabetically for deterministic output.
44
+ */
45
+ getBuildOrder(): string[];
46
+ /**
47
+ * Gets artifacts that are ready to be created (all dependencies in completed set).
48
+ * Excludes already-completed artifacts.
49
+ */
50
+ getNextArtifacts(completed: CompletedSet): string[];
51
+ /**
52
+ * Checks if all artifacts in the graph are completed.
53
+ */
54
+ isComplete(completed: CompletedSet): boolean;
55
+ /**
56
+ * Gets blocked artifacts and their unmet dependencies.
57
+ * Returns a mapping of artifact ID to array of missing dependency IDs.
58
+ */
59
+ getBlocked(completed: CompletedSet): BlockedArtifacts;
60
+ }
@@ -0,0 +1,141 @@
1
+ import { loadSchema, parseSchema } from './schema.js';
2
+ /**
3
+ * Represents an artifact dependency graph.
4
+ * Provides methods for querying build order, ready artifacts, and completion status.
5
+ *
6
+ * Immutable: all query methods return new arrays/objects, never mutate internal state.
7
+ */
8
+ export class ArtifactGraph {
9
+ artifacts;
10
+ schema;
11
+ constructor(schema) {
12
+ this.schema = schema;
13
+ this.artifacts = new Map(schema.artifacts.map(a => [a.id, a]));
14
+ }
15
+ /**
16
+ * Creates an ArtifactGraph from a YAML file path.
17
+ */
18
+ static fromYaml(filePath) {
19
+ const schema = loadSchema(filePath);
20
+ return new ArtifactGraph(schema);
21
+ }
22
+ /**
23
+ * Creates an ArtifactGraph from YAML content string.
24
+ */
25
+ static fromYamlContent(yamlContent) {
26
+ const schema = parseSchema(yamlContent);
27
+ return new ArtifactGraph(schema);
28
+ }
29
+ /**
30
+ * Creates an ArtifactGraph from a pre-validated schema object.
31
+ */
32
+ static fromSchema(schema) {
33
+ return new ArtifactGraph(schema);
34
+ }
35
+ /**
36
+ * Gets a single artifact by ID.
37
+ */
38
+ getArtifact(id) {
39
+ return this.artifacts.get(id);
40
+ }
41
+ /**
42
+ * Gets all artifacts in the graph (returns a new array).
43
+ */
44
+ getAllArtifacts() {
45
+ return Array.from(this.artifacts.values());
46
+ }
47
+ /**
48
+ * Gets the schema name.
49
+ */
50
+ getName() {
51
+ return this.schema.name;
52
+ }
53
+ /**
54
+ * Gets the schema version.
55
+ */
56
+ getVersion() {
57
+ return this.schema.version;
58
+ }
59
+ /**
60
+ * Computes the topological build order using Kahn's algorithm.
61
+ * Returns artifact IDs in a valid order: dependencies before dependents.
62
+ * Ties are broken alphabetically for deterministic output.
63
+ */
64
+ getBuildOrder() {
65
+ const inDegree = new Map();
66
+ const dependents = new Map();
67
+ for (const artifact of this.artifacts.values()) {
68
+ inDegree.set(artifact.id, artifact.requires.length);
69
+ dependents.set(artifact.id, []);
70
+ }
71
+ for (const artifact of this.artifacts.values()) {
72
+ for (const req of artifact.requires) {
73
+ dependents.get(req).push(artifact.id);
74
+ }
75
+ }
76
+ // Start with roots (in-degree 0), sorted for determinism
77
+ const queue = [...this.artifacts.keys()]
78
+ .filter(id => inDegree.get(id) === 0)
79
+ .sort();
80
+ const result = [];
81
+ while (queue.length > 0) {
82
+ const current = queue.shift();
83
+ result.push(current);
84
+ const newlyReady = [];
85
+ for (const dep of dependents.get(current)) {
86
+ const newDegree = inDegree.get(dep) - 1;
87
+ inDegree.set(dep, newDegree);
88
+ if (newDegree === 0) {
89
+ newlyReady.push(dep);
90
+ }
91
+ }
92
+ queue.push(...newlyReady.sort());
93
+ }
94
+ return result;
95
+ }
96
+ /**
97
+ * Gets artifacts that are ready to be created (all dependencies in completed set).
98
+ * Excludes already-completed artifacts.
99
+ */
100
+ getNextArtifacts(completed) {
101
+ const ready = [];
102
+ for (const artifact of this.artifacts.values()) {
103
+ if (completed.has(artifact.id)) {
104
+ continue;
105
+ }
106
+ const allDepsCompleted = artifact.requires.every(req => completed.has(req));
107
+ if (allDepsCompleted) {
108
+ ready.push(artifact.id);
109
+ }
110
+ }
111
+ return ready.sort();
112
+ }
113
+ /**
114
+ * Checks if all artifacts in the graph are completed.
115
+ */
116
+ isComplete(completed) {
117
+ for (const artifact of this.artifacts.values()) {
118
+ if (!completed.has(artifact.id)) {
119
+ return false;
120
+ }
121
+ }
122
+ return true;
123
+ }
124
+ /**
125
+ * Gets blocked artifacts and their unmet dependencies.
126
+ * Returns a mapping of artifact ID to array of missing dependency IDs.
127
+ */
128
+ getBlocked(completed) {
129
+ const blocked = {};
130
+ for (const artifact of this.artifacts.values()) {
131
+ if (completed.has(artifact.id)) {
132
+ continue;
133
+ }
134
+ const unmetDeps = artifact.requires.filter(req => !completed.has(req));
135
+ if (unmetDeps.length > 0) {
136
+ blocked[artifact.id] = [...unmetDeps].sort();
137
+ }
138
+ }
139
+ return blocked;
140
+ }
141
+ }
@@ -0,0 +1,10 @@
1
+ export { ArtifactSchema, ApplyPhaseSchema, SchemaYamlSchema, } from './types.js';
2
+ export type { Artifact, ApplyPhase, SchemaYaml, CompletedSet, BlockedArtifacts, } from './types.js';
3
+ export { loadSchema, parseSchema, SchemaValidationError, } from './schema.js';
4
+ export { resolveSchema, getSchemaDir, getPackageSchemasDir, getProjectSchemasDir, listSchemas, SchemaLoadError, } from './resolver.js';
5
+ export { ArtifactGraph } from './graph.js';
6
+ export { getCompletedArtifacts } from './state.js';
7
+ export { loadInstructions } from './instruction-loader.js';
8
+ export type { ArtifactInstructions, DependencyInfo, ProjectConfig, } from './instruction-loader.js';
9
+ export { formatStatus, formatStatusHuman } from './outputs.js';
10
+ export type { ArtifactStatusEntry, StatusOutput } from './outputs.js';
@@ -0,0 +1,7 @@
1
+ export { ArtifactSchema, ApplyPhaseSchema, SchemaYamlSchema, } from './types.js';
2
+ export { loadSchema, parseSchema, SchemaValidationError, } from './schema.js';
3
+ export { resolveSchema, getSchemaDir, getPackageSchemasDir, getProjectSchemasDir, listSchemas, SchemaLoadError, } from './resolver.js';
4
+ export { ArtifactGraph } from './graph.js';
5
+ export { getCompletedArtifacts } from './state.js';
6
+ export { loadInstructions } from './instruction-loader.js';
7
+ export { formatStatus, formatStatusHuman } from './outputs.js';
@@ -0,0 +1,45 @@
1
+ import type { SchemaYaml } from './types.js';
2
+ /**
3
+ * Dependency information included in artifact instructions.
4
+ */
5
+ export interface DependencyInfo {
6
+ /** Artifact ID */
7
+ readonly id: string;
8
+ /** File path pattern this dependency generates */
9
+ readonly generates: string;
10
+ /** Description of the dependency artifact */
11
+ readonly description: string;
12
+ }
13
+ /**
14
+ * Loaded instructions for creating an artifact.
15
+ */
16
+ export interface ArtifactInstructions {
17
+ /** The artifact's instruction text from the schema */
18
+ readonly instruction: string | undefined;
19
+ /** Project context from the config */
20
+ readonly context: string | undefined;
21
+ /** Dependencies with their metadata */
22
+ readonly dependencies: readonly DependencyInfo[];
23
+ /** The artifact's generates path */
24
+ readonly generates: string;
25
+ /** The artifact's description */
26
+ readonly description: string;
27
+ }
28
+ /**
29
+ * Optional project config with context for instruction loading.
30
+ */
31
+ export interface ProjectConfig {
32
+ readonly context?: string;
33
+ readonly [key: string]: unknown;
34
+ }
35
+ /**
36
+ * Loads enriched instructions for creating an artifact.
37
+ *
38
+ * @param artifactId - The artifact ID to load instructions for
39
+ * @param changeDir - Path to the change directory
40
+ * @param schema - The workflow schema
41
+ * @param config - Optional project configuration with context
42
+ * @returns The loaded artifact instructions
43
+ * @throws Error if the artifact ID is not found in the schema
44
+ */
45
+ export declare function loadInstructions(artifactId: string, changeDir: string, schema: SchemaYaml, config?: ProjectConfig): ArtifactInstructions;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Loads enriched instructions for creating an artifact.
3
+ *
4
+ * @param artifactId - The artifact ID to load instructions for
5
+ * @param changeDir - Path to the change directory
6
+ * @param schema - The workflow schema
7
+ * @param config - Optional project configuration with context
8
+ * @returns The loaded artifact instructions
9
+ * @throws Error if the artifact ID is not found in the schema
10
+ */
11
+ export function loadInstructions(artifactId, changeDir, schema, config) {
12
+ const artifact = schema.artifacts.find(a => a.id === artifactId);
13
+ if (!artifact) {
14
+ throw new Error(`Artifact '${artifactId}' not found in schema '${schema.name}'`);
15
+ }
16
+ const dependencies = buildDependencyInfo(artifact, schema);
17
+ return {
18
+ instruction: artifact.instruction,
19
+ context: config?.context ?? undefined,
20
+ dependencies,
21
+ generates: artifact.generates,
22
+ description: artifact.description,
23
+ };
24
+ }
25
+ /**
26
+ * Builds dependency information for an artifact's requirements.
27
+ */
28
+ function buildDependencyInfo(artifact, schema) {
29
+ return artifact.requires.map(reqId => {
30
+ const depArtifact = schema.artifacts.find(a => a.id === reqId);
31
+ return {
32
+ id: reqId,
33
+ generates: depArtifact?.generates ?? reqId,
34
+ description: depArtifact?.description ?? '',
35
+ };
36
+ });
37
+ }
@@ -0,0 +1,35 @@
1
+ import type { Artifact } from './types.js';
2
+ /**
3
+ * Status of a single artifact.
4
+ */
5
+ export interface ArtifactStatusEntry {
6
+ readonly id: string;
7
+ readonly status: 'done' | 'ready' | 'blocked';
8
+ readonly missingDeps?: readonly string[];
9
+ }
10
+ /**
11
+ * JSON-friendly status object for all artifacts.
12
+ */
13
+ export interface StatusOutput {
14
+ readonly artifacts: readonly ArtifactStatusEntry[];
15
+ }
16
+ /**
17
+ * Formats artifact statuses as a structured JSON-friendly object.
18
+ *
19
+ * @param artifacts - The artifact definitions from the schema
20
+ * @param completed - Array of completed artifact IDs
21
+ * @returns Structured status with each artifact's state
22
+ */
23
+ export declare function formatStatus(artifacts: readonly Artifact[], completed: readonly string[]): StatusOutput;
24
+ /**
25
+ * Formats artifact statuses as human-readable checkbox text.
26
+ *
27
+ * Output format:
28
+ * [x] proposal
29
+ * [ ] specs (blocked by: proposal)
30
+ *
31
+ * @param artifacts - The artifact definitions from the schema
32
+ * @param completed - Array of completed artifact IDs
33
+ * @returns Checkbox-style text string
34
+ */
35
+ export declare function formatStatusHuman(artifacts: readonly Artifact[], completed: readonly string[]): string;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Formats artifact statuses as a structured JSON-friendly object.
3
+ *
4
+ * @param artifacts - The artifact definitions from the schema
5
+ * @param completed - Array of completed artifact IDs
6
+ * @returns Structured status with each artifact's state
7
+ */
8
+ export function formatStatus(artifacts, completed) {
9
+ const completedSet = new Set(completed);
10
+ const statuses = artifacts.map(artifact => {
11
+ if (completedSet.has(artifact.id)) {
12
+ return { id: artifact.id, status: 'done' };
13
+ }
14
+ const missingDeps = artifact.requires.filter(req => !completedSet.has(req));
15
+ if (missingDeps.length === 0) {
16
+ return { id: artifact.id, status: 'ready' };
17
+ }
18
+ return {
19
+ id: artifact.id,
20
+ status: 'blocked',
21
+ missingDeps,
22
+ };
23
+ });
24
+ return { artifacts: statuses };
25
+ }
26
+ /**
27
+ * Formats artifact statuses as human-readable checkbox text.
28
+ *
29
+ * Output format:
30
+ * [x] proposal
31
+ * [ ] specs (blocked by: proposal)
32
+ *
33
+ * @param artifacts - The artifact definitions from the schema
34
+ * @param completed - Array of completed artifact IDs
35
+ * @returns Checkbox-style text string
36
+ */
37
+ export function formatStatusHuman(artifacts, completed) {
38
+ const completedSet = new Set(completed);
39
+ const lines = artifacts.map(artifact => {
40
+ const checkbox = completedSet.has(artifact.id) ? '[x]' : '[ ]';
41
+ const missingDeps = artifact.requires.filter(req => !completedSet.has(req));
42
+ const suffix = missingDeps.length > 0
43
+ ? ` (blocked by: ${missingDeps.join(', ')})`
44
+ : '';
45
+ return `${checkbox} ${artifact.id}${suffix}`;
46
+ });
47
+ return lines.join('\n');
48
+ }