@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.
- package/LICENSE +31 -0
- package/README.md +442 -0
- package/bin/specflow.js +2 -0
- package/dist/cli/commands/change-archive.d.ts +26 -0
- package/dist/cli/commands/change-archive.js +50 -0
- package/dist/cli/commands/change-new.d.ts +18 -0
- package/dist/cli/commands/change-new.js +53 -0
- package/dist/cli/commands/change-phase.d.ts +33 -0
- package/dist/cli/commands/change-phase.js +66 -0
- package/dist/cli/commands/change-status.d.ts +35 -0
- package/dist/cli/commands/change-status.js +95 -0
- package/dist/cli/commands/doctor.d.ts +9 -0
- package/dist/cli/commands/doctor.js +48 -0
- package/dist/cli/commands/init.d.ts +16 -0
- package/dist/cli/commands/init.js +122 -0
- package/dist/cli/commands/instructions.d.ts +21 -0
- package/dist/cli/commands/instructions.js +75 -0
- package/dist/cli/commands/parity-report.d.ts +10 -0
- package/dist/cli/commands/parity-report.js +67 -0
- package/dist/cli/commands/sync.d.ts +4 -0
- package/dist/cli/commands/sync.js +40 -0
- package/dist/cli/commands/validate.d.ts +21 -0
- package/dist/cli/commands/validate.js +46 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +52 -0
- package/dist/core/archive.d.ts +39 -0
- package/dist/core/archive.js +154 -0
- package/dist/core/artifact-graph/graph.d.ts +60 -0
- package/dist/core/artifact-graph/graph.js +141 -0
- package/dist/core/artifact-graph/index.d.ts +10 -0
- package/dist/core/artifact-graph/index.js +7 -0
- package/dist/core/artifact-graph/instruction-loader.d.ts +45 -0
- package/dist/core/artifact-graph/instruction-loader.js +37 -0
- package/dist/core/artifact-graph/outputs.d.ts +35 -0
- package/dist/core/artifact-graph/outputs.js +48 -0
- package/dist/core/artifact-graph/resolver.d.ts +54 -0
- package/dist/core/artifact-graph/resolver.js +139 -0
- package/dist/core/artifact-graph/schema.d.ts +23 -0
- package/dist/core/artifact-graph/schema.js +115 -0
- package/dist/core/artifact-graph/state.d.ts +9 -0
- package/dist/core/artifact-graph/state.js +93 -0
- package/dist/core/artifact-graph/types.d.ts +123 -0
- package/dist/core/artifact-graph/types.js +29 -0
- package/dist/core/parsers/change-parser.d.ts +20 -0
- package/dist/core/parsers/change-parser.js +22 -0
- package/dist/core/parsers/index.d.ts +7 -0
- package/dist/core/parsers/index.js +4 -0
- package/dist/core/parsers/markdown-parser.d.ts +35 -0
- package/dist/core/parsers/markdown-parser.js +193 -0
- package/dist/core/parsers/requirement-blocks.d.ts +21 -0
- package/dist/core/parsers/requirement-blocks.js +57 -0
- package/dist/core/parsers/spec-structure.d.ts +22 -0
- package/dist/core/parsers/spec-structure.js +105 -0
- package/dist/core/specs-apply.d.ts +22 -0
- package/dist/core/specs-apply.js +165 -0
- package/dist/core/templates/index.d.ts +31 -0
- package/dist/core/templates/index.js +80 -0
- package/dist/core/templates/types.d.ts +4 -0
- package/dist/core/templates/types.js +1 -0
- package/dist/core/validation/constants.d.ts +14 -0
- package/dist/core/validation/constants.js +14 -0
- package/dist/core/validation/types.d.ts +13 -0
- package/dist/core/validation/types.js +6 -0
- package/dist/core/validation/validator.d.ts +24 -0
- package/dist/core/validation/validator.js +250 -0
- package/dist/integrations/claude/adapter.d.ts +2 -0
- package/dist/integrations/claude/adapter.js +106 -0
- package/dist/integrations/codex/adapter.d.ts +2 -0
- package/dist/integrations/codex/adapter.js +141 -0
- package/dist/integrations/cursor/adapter.d.ts +2 -0
- package/dist/integrations/cursor/adapter.js +126 -0
- package/dist/integrations/shared/adapter-registry.d.ts +4 -0
- package/dist/integrations/shared/adapter-registry.js +27 -0
- package/dist/integrations/shared/asset-copy.d.ts +2 -0
- package/dist/integrations/shared/asset-copy.js +44 -0
- package/dist/integrations/shared/asset-hash.d.ts +4 -0
- package/dist/integrations/shared/asset-hash.js +54 -0
- package/dist/integrations/shared/capability-evidence.d.ts +1 -0
- package/dist/integrations/shared/capability-evidence.js +42 -0
- package/dist/integrations/shared/command-catalog.d.ts +6 -0
- package/dist/integrations/shared/command-catalog.js +13 -0
- package/dist/integrations/shared/managed-assets.d.ts +2 -0
- package/dist/integrations/shared/managed-assets.js +21 -0
- package/dist/integrations/shared/marker-write.d.ts +3 -0
- package/dist/integrations/shared/marker-write.js +48 -0
- package/dist/integrations/shared/migration-state.d.ts +2 -0
- package/dist/integrations/shared/migration-state.js +67 -0
- package/dist/integrations/shared/parity-comparator.d.ts +6 -0
- package/dist/integrations/shared/parity-comparator.js +97 -0
- package/dist/integrations/shared/parity-manifest.d.ts +13 -0
- package/dist/integrations/shared/parity-manifest.js +58 -0
- package/dist/integrations/shared/runtime-assets.d.ts +2 -0
- package/dist/integrations/shared/runtime-assets.js +21 -0
- package/dist/integrations/shared/skill-renderer.d.ts +5 -0
- package/dist/integrations/shared/skill-renderer.js +77 -0
- package/dist/integrations/shared/types.d.ts +35 -0
- package/dist/integrations/shared/types.js +1 -0
- package/dist/utils/change-metadata.d.ts +38 -0
- package/dist/utils/change-metadata.js +82 -0
- package/dist/utils/change-utils.d.ts +53 -0
- package/dist/utils/change-utils.js +97 -0
- package/dist/utils/file-system.d.ts +35 -0
- package/dist/utils/file-system.js +67 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/project-root.d.ts +22 -0
- package/dist/utils/project-root.js +44 -0
- package/dist/utils/task-progress.d.ts +39 -0
- package/dist/utils/task-progress.js +62 -0
- package/package.json +72 -0
- package/prompts/build/ecc-go-reviewer.md +80 -0
- package/prompts/build/ecc-java-reviewer.md +185 -0
- package/prompts/build/ecc-kotlin-reviewer.md +163 -0
- package/prompts/build/ecc-python-reviewer.md +102 -0
- package/prompts/build/ecc-rust-reviewer.md +98 -0
- package/prompts/build/ecc-typescript-reviewer.md +116 -0
- package/prompts/build/language-router.md +22 -0
- package/prompts/build/phase-a-plan.md +288 -0
- package/prompts/build/phase-b-execute.md +276 -0
- package/prompts/build/phase-b-review.md +119 -0
- package/prompts/build/phase-b-worktree.md +215 -0
- package/prompts/build/plan-document-reviewer.md +51 -0
- package/prompts/build/tdd.md +184 -0
- package/prompts/done/branch-finish.md +204 -0
- package/prompts/fix/debug.md +249 -0
- package/prompts/plan/design-draft.md +123 -0
- package/prompts/plan/proposal.md +85 -0
- package/prompts/plan/specs.md +114 -0
- package/prompts/plan/tasks-draft.md +123 -0
- package/prompts/reference/openspec/apply-instruction.md +2 -0
- package/prompts/reference/openspec/design-instruction.md +20 -0
- package/prompts/reference/openspec/proposal-instruction.md +18 -0
- package/prompts/reference/openspec/specs-instruction.md +47 -0
- package/prompts/reference/openspec/tasks-instruction.md +26 -0
- package/prompts/reference/specflow/example-design.md +284 -0
- package/prompts/reference/superpowers/anthropic-best-practices.md +1150 -0
- package/prompts/reference/superpowers/codex-tools.md +29 -0
- package/prompts/reference/superpowers/condition-based-waiting-example.ts +158 -0
- package/prompts/reference/superpowers/condition-based-waiting.md +115 -0
- package/prompts/reference/superpowers/defense-in-depth.md +122 -0
- package/prompts/reference/superpowers/find-polluter.sh +63 -0
- package/prompts/reference/superpowers/graphviz-conventions.dot +172 -0
- package/prompts/reference/superpowers/persuasion-principles.md +187 -0
- package/prompts/reference/superpowers/root-cause-tracing.md +169 -0
- package/prompts/reference/superpowers/testing-anti-patterns.md +299 -0
- package/prompts/reference/superpowers/testing-skills-with-subagents.md +384 -0
- package/prompts/reference/superpowers/using-superpowers.md +117 -0
- package/prompts/reference/superpowers/writing-skills.md +655 -0
- package/prompts/refine/brainstorm.md +241 -0
- package/prompts/refine/design-output.md +126 -0
- package/prompts/refine/spec-document-reviewer.md +51 -0
- package/prompts/refine/update-artifacts.md +185 -0
- package/prompts/review/code-review.md +223 -0
- package/prompts/shared/code-reviewer-prompt.md +98 -0
- package/prompts/shared/dispatching-parallel-agents.md +143 -0
- package/prompts/shared/executing-plans.md +67 -0
- package/prompts/shared/implementer-prompt.md +115 -0
- package/prompts/shared/receiving-code-review.md +174 -0
- package/prompts/shared/spec-reviewer-prompt.md +63 -0
- package/prompts/test/tdd.md +236 -0
- package/prompts/test/verification.md +129 -0
- package/prompts/verify/ecc-security-reviewer.md +112 -0
- package/prompts/verify/verification.md +196 -0
- package/schemas/specflow/schema.yaml +48 -0
- package/skills/specflow-build/SKILL.md +129 -0
- package/skills/specflow-done/SKILL.md +68 -0
- package/skills/specflow-fix/SKILL.md +74 -0
- package/skills/specflow-plan/SKILL.md +82 -0
- package/skills/specflow-refine/SKILL.md +128 -0
- package/skills/specflow-review/SKILL.md +40 -0
- package/skills/specflow-scan/SKILL.md +48 -0
- package/skills/specflow-snap/SKILL.md +46 -0
- package/skills/specflow-test/SKILL.md +48 -0
- package/skills/specflow-verify/SKILL.md +77 -0
- package/templates/design.md +19 -0
- package/templates/proposal.md +23 -0
- package/templates/spec.md +42 -0
- package/templates/tasks.md +9 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { SchemaYaml } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Error thrown when loading a schema file fails.
|
|
4
|
+
*/
|
|
5
|
+
export declare class SchemaLoadError extends Error {
|
|
6
|
+
readonly schemaPath: string;
|
|
7
|
+
readonly cause?: Error | undefined;
|
|
8
|
+
constructor(message: string, schemaPath: string, cause?: Error | undefined);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Gets the package's built-in schemas directory path.
|
|
12
|
+
* Resolves from the compiled module location back to the package root.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getPackageSchemasDir(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Gets the project-local schemas directory path.
|
|
17
|
+
*
|
|
18
|
+
* @param projectRoot - The project root directory
|
|
19
|
+
* @returns The path to the project's schemas directory
|
|
20
|
+
*/
|
|
21
|
+
export declare function getProjectSchemasDir(projectRoot: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Resolves a schema name to its directory path.
|
|
24
|
+
*
|
|
25
|
+
* Resolution order:
|
|
26
|
+
* 1. Project-local: <projectRoot>/specflow/schemas/<name>/schema.yaml
|
|
27
|
+
* 2. Package built-in: <package>/schemas/<name>/schema.yaml
|
|
28
|
+
*
|
|
29
|
+
* @param name - Schema name (e.g., "specflow")
|
|
30
|
+
* @param projectRoot - Optional project root directory for project-local resolution
|
|
31
|
+
* @returns The path to the schema directory, or null if not found
|
|
32
|
+
*/
|
|
33
|
+
export declare function getSchemaDir(name: string, projectRoot?: string): string | null;
|
|
34
|
+
/**
|
|
35
|
+
* Resolves a schema name to a validated SchemaYaml object.
|
|
36
|
+
*
|
|
37
|
+
* Resolution order:
|
|
38
|
+
* 1. Project-local: <projectRoot>/specflow/schemas/<name>/schema.yaml
|
|
39
|
+
* 2. Package built-in: <package>/schemas/<name>/schema.yaml
|
|
40
|
+
*
|
|
41
|
+
* @param name - Schema name (e.g., "specflow")
|
|
42
|
+
* @param projectRoot - Optional project root directory for project-local resolution
|
|
43
|
+
* @returns The resolved and validated schema object
|
|
44
|
+
* @throws Error if schema not found in any location
|
|
45
|
+
* @throws SchemaLoadError if schema file cannot be read or parsed
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveSchema(name: string, projectRoot?: string): SchemaYaml;
|
|
48
|
+
/**
|
|
49
|
+
* Lists all available schema names.
|
|
50
|
+
*
|
|
51
|
+
* @param projectRoot - Optional project root directory for project-local resolution
|
|
52
|
+
* @returns Sorted array of schema names
|
|
53
|
+
*/
|
|
54
|
+
export declare function listSchemas(projectRoot?: string): string[];
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { parseSchema, SchemaValidationError } from './schema.js';
|
|
5
|
+
/**
|
|
6
|
+
* Error thrown when loading a schema file fails.
|
|
7
|
+
*/
|
|
8
|
+
export class SchemaLoadError extends Error {
|
|
9
|
+
schemaPath;
|
|
10
|
+
cause;
|
|
11
|
+
constructor(message, schemaPath, cause) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.schemaPath = schemaPath;
|
|
14
|
+
this.cause = cause;
|
|
15
|
+
this.name = 'SchemaLoadError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Gets the package's built-in schemas directory path.
|
|
20
|
+
* Resolves from the compiled module location back to the package root.
|
|
21
|
+
*/
|
|
22
|
+
export function getPackageSchemasDir() {
|
|
23
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
24
|
+
// Navigate from src/core/artifact-graph/ (or dist/core/artifact-graph/) to package root
|
|
25
|
+
return path.join(path.dirname(currentFile), '..', '..', '..', 'schemas');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Gets the project-local schemas directory path.
|
|
29
|
+
*
|
|
30
|
+
* @param projectRoot - The project root directory
|
|
31
|
+
* @returns The path to the project's schemas directory
|
|
32
|
+
*/
|
|
33
|
+
export function getProjectSchemasDir(projectRoot) {
|
|
34
|
+
return path.join(projectRoot, 'specflow', 'schemas');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolves a schema name to its directory path.
|
|
38
|
+
*
|
|
39
|
+
* Resolution order:
|
|
40
|
+
* 1. Project-local: <projectRoot>/specflow/schemas/<name>/schema.yaml
|
|
41
|
+
* 2. Package built-in: <package>/schemas/<name>/schema.yaml
|
|
42
|
+
*
|
|
43
|
+
* @param name - Schema name (e.g., "specflow")
|
|
44
|
+
* @param projectRoot - Optional project root directory for project-local resolution
|
|
45
|
+
* @returns The path to the schema directory, or null if not found
|
|
46
|
+
*/
|
|
47
|
+
export function getSchemaDir(name, projectRoot) {
|
|
48
|
+
// 1. Check project-local directory
|
|
49
|
+
if (projectRoot) {
|
|
50
|
+
const projectDir = path.join(getProjectSchemasDir(projectRoot), name);
|
|
51
|
+
const projectSchemaPath = path.join(projectDir, 'schema.yaml');
|
|
52
|
+
if (fs.existsSync(projectSchemaPath)) {
|
|
53
|
+
return projectDir;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// 2. Check package built-in directory
|
|
57
|
+
const packageDir = path.join(getPackageSchemasDir(), name);
|
|
58
|
+
const packageSchemaPath = path.join(packageDir, 'schema.yaml');
|
|
59
|
+
if (fs.existsSync(packageSchemaPath)) {
|
|
60
|
+
return packageDir;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolves a schema name to a validated SchemaYaml object.
|
|
66
|
+
*
|
|
67
|
+
* Resolution order:
|
|
68
|
+
* 1. Project-local: <projectRoot>/specflow/schemas/<name>/schema.yaml
|
|
69
|
+
* 2. Package built-in: <package>/schemas/<name>/schema.yaml
|
|
70
|
+
*
|
|
71
|
+
* @param name - Schema name (e.g., "specflow")
|
|
72
|
+
* @param projectRoot - Optional project root directory for project-local resolution
|
|
73
|
+
* @returns The resolved and validated schema object
|
|
74
|
+
* @throws Error if schema not found in any location
|
|
75
|
+
* @throws SchemaLoadError if schema file cannot be read or parsed
|
|
76
|
+
*/
|
|
77
|
+
export function resolveSchema(name, projectRoot) {
|
|
78
|
+
const normalizedName = name.replace(/\.ya?ml$/, '');
|
|
79
|
+
const schemaDir = getSchemaDir(normalizedName, projectRoot);
|
|
80
|
+
if (!schemaDir) {
|
|
81
|
+
const available = listSchemas(projectRoot);
|
|
82
|
+
throw new Error(`Schema '${normalizedName}' not found. Available schemas: ${available.join(', ')}`);
|
|
83
|
+
}
|
|
84
|
+
const schemaPath = path.join(schemaDir, 'schema.yaml');
|
|
85
|
+
let content;
|
|
86
|
+
try {
|
|
87
|
+
content = fs.readFileSync(schemaPath, 'utf-8');
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
const ioError = err instanceof Error ? err : new Error(String(err));
|
|
91
|
+
throw new SchemaLoadError(`Failed to read schema at '${schemaPath}': ${ioError.message}`, schemaPath, ioError);
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
return parseSchema(content);
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
if (err instanceof SchemaValidationError) {
|
|
98
|
+
throw new SchemaLoadError(`Invalid schema at '${schemaPath}': ${err.message}`, schemaPath, err);
|
|
99
|
+
}
|
|
100
|
+
const parseError = err instanceof Error ? err : new Error(String(err));
|
|
101
|
+
throw new SchemaLoadError(`Failed to parse schema at '${schemaPath}': ${parseError.message}`, schemaPath, parseError);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Lists all available schema names.
|
|
106
|
+
*
|
|
107
|
+
* @param projectRoot - Optional project root directory for project-local resolution
|
|
108
|
+
* @returns Sorted array of schema names
|
|
109
|
+
*/
|
|
110
|
+
export function listSchemas(projectRoot) {
|
|
111
|
+
const schemas = new Set();
|
|
112
|
+
// Package built-in schemas
|
|
113
|
+
const packageDir = getPackageSchemasDir();
|
|
114
|
+
if (fs.existsSync(packageDir)) {
|
|
115
|
+
for (const entry of fs.readdirSync(packageDir, { withFileTypes: true })) {
|
|
116
|
+
if (entry.isDirectory()) {
|
|
117
|
+
const schemaPath = path.join(packageDir, entry.name, 'schema.yaml');
|
|
118
|
+
if (fs.existsSync(schemaPath)) {
|
|
119
|
+
schemas.add(entry.name);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Project-local schemas
|
|
125
|
+
if (projectRoot) {
|
|
126
|
+
const projectDir = getProjectSchemasDir(projectRoot);
|
|
127
|
+
if (fs.existsSync(projectDir)) {
|
|
128
|
+
for (const entry of fs.readdirSync(projectDir, { withFileTypes: true })) {
|
|
129
|
+
if (entry.isDirectory()) {
|
|
130
|
+
const schemaPath = path.join(projectDir, entry.name, 'schema.yaml');
|
|
131
|
+
if (fs.existsSync(schemaPath)) {
|
|
132
|
+
schemas.add(entry.name);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return Array.from(schemas).sort();
|
|
139
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type SchemaYaml } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Error thrown when schema validation fails.
|
|
4
|
+
*/
|
|
5
|
+
export declare class SchemaValidationError extends Error {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Loads and validates an artifact schema from a YAML file path.
|
|
10
|
+
*
|
|
11
|
+
* @param filePath - Absolute path to the schema.yaml file
|
|
12
|
+
* @returns The validated schema object
|
|
13
|
+
* @throws SchemaValidationError if the YAML is invalid
|
|
14
|
+
*/
|
|
15
|
+
export declare function loadSchema(filePath: string): SchemaYaml;
|
|
16
|
+
/**
|
|
17
|
+
* Parses and validates an artifact schema from YAML content string.
|
|
18
|
+
*
|
|
19
|
+
* @param yamlContent - Raw YAML string
|
|
20
|
+
* @returns The validated schema object
|
|
21
|
+
* @throws SchemaValidationError if validation fails
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseSchema(yamlContent: string): SchemaYaml;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import yaml from 'js-yaml';
|
|
3
|
+
import { SchemaYamlSchema } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Error thrown when schema validation fails.
|
|
6
|
+
*/
|
|
7
|
+
export class SchemaValidationError extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = 'SchemaValidationError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Loads and validates an artifact schema from a YAML file path.
|
|
15
|
+
*
|
|
16
|
+
* @param filePath - Absolute path to the schema.yaml file
|
|
17
|
+
* @returns The validated schema object
|
|
18
|
+
* @throws SchemaValidationError if the YAML is invalid
|
|
19
|
+
*/
|
|
20
|
+
export function loadSchema(filePath) {
|
|
21
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
22
|
+
return parseSchema(content);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Parses and validates an artifact schema from YAML content string.
|
|
26
|
+
*
|
|
27
|
+
* @param yamlContent - Raw YAML string
|
|
28
|
+
* @returns The validated schema object
|
|
29
|
+
* @throws SchemaValidationError if validation fails
|
|
30
|
+
*/
|
|
31
|
+
export function parseSchema(yamlContent) {
|
|
32
|
+
const parsed = yaml.load(yamlContent);
|
|
33
|
+
const result = SchemaYamlSchema.safeParse(parsed);
|
|
34
|
+
if (!result.success) {
|
|
35
|
+
const errors = result.error.issues
|
|
36
|
+
.map(e => `${e.path.join('.')}: ${e.message}`)
|
|
37
|
+
.join(', ');
|
|
38
|
+
throw new SchemaValidationError(`Invalid schema: ${errors}`);
|
|
39
|
+
}
|
|
40
|
+
const schema = result.data;
|
|
41
|
+
validateNoDuplicateIds(schema.artifacts);
|
|
42
|
+
validateRequiresReferences(schema.artifacts);
|
|
43
|
+
validateNoCycles(schema.artifacts);
|
|
44
|
+
return schema;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Validates that there are no duplicate artifact IDs.
|
|
48
|
+
*/
|
|
49
|
+
function validateNoDuplicateIds(artifacts) {
|
|
50
|
+
const seen = new Set();
|
|
51
|
+
for (const artifact of artifacts) {
|
|
52
|
+
if (seen.has(artifact.id)) {
|
|
53
|
+
throw new SchemaValidationError(`Duplicate artifact ID: ${artifact.id}`);
|
|
54
|
+
}
|
|
55
|
+
seen.add(artifact.id);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Validates that all `requires` references point to valid artifact IDs.
|
|
60
|
+
*/
|
|
61
|
+
function validateRequiresReferences(artifacts) {
|
|
62
|
+
const validIds = new Set(artifacts.map(a => a.id));
|
|
63
|
+
for (const artifact of artifacts) {
|
|
64
|
+
for (const req of artifact.requires) {
|
|
65
|
+
if (!validIds.has(req)) {
|
|
66
|
+
throw new SchemaValidationError(`Invalid dependency reference in artifact '${artifact.id}': '${req}' does not exist`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Validates that there are no cyclic dependencies.
|
|
73
|
+
* Uses DFS with path reconstruction for clear error messages.
|
|
74
|
+
*/
|
|
75
|
+
function validateNoCycles(artifacts) {
|
|
76
|
+
const artifactMap = new Map(artifacts.map(a => [a.id, a]));
|
|
77
|
+
const visited = new Set();
|
|
78
|
+
const inStack = new Set();
|
|
79
|
+
const parent = new Map();
|
|
80
|
+
function dfs(id) {
|
|
81
|
+
visited.add(id);
|
|
82
|
+
inStack.add(id);
|
|
83
|
+
const artifact = artifactMap.get(id);
|
|
84
|
+
if (!artifact)
|
|
85
|
+
return null;
|
|
86
|
+
for (const dep of artifact.requires) {
|
|
87
|
+
if (!visited.has(dep)) {
|
|
88
|
+
parent.set(dep, id);
|
|
89
|
+
const cycle = dfs(dep);
|
|
90
|
+
if (cycle)
|
|
91
|
+
return cycle;
|
|
92
|
+
}
|
|
93
|
+
else if (inStack.has(dep)) {
|
|
94
|
+
const cyclePath = [dep];
|
|
95
|
+
let current = id;
|
|
96
|
+
while (current !== dep) {
|
|
97
|
+
cyclePath.unshift(current);
|
|
98
|
+
current = parent.get(current);
|
|
99
|
+
}
|
|
100
|
+
cyclePath.unshift(dep);
|
|
101
|
+
return cyclePath.join(' -> ');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
inStack.delete(id);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
for (const artifact of artifacts) {
|
|
108
|
+
if (!visited.has(artifact.id)) {
|
|
109
|
+
const cycle = dfs(artifact.id);
|
|
110
|
+
if (cycle) {
|
|
111
|
+
throw new SchemaValidationError(`Cyclic dependency detected: ${cycle}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SchemaYaml } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Detects which artifacts are completed by checking file existence in the change directory.
|
|
4
|
+
*
|
|
5
|
+
* @param changeDir - The change directory to scan
|
|
6
|
+
* @param schema - The workflow schema with artifact definitions
|
|
7
|
+
* @returns Array of completed artifact IDs
|
|
8
|
+
*/
|
|
9
|
+
export declare function getCompletedArtifacts(changeDir: string, schema: SchemaYaml): string[];
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Checks if an artifact's generated file(s) exist in the change directory.
|
|
5
|
+
* Supports simple file paths and basic glob patterns with "**".
|
|
6
|
+
*/
|
|
7
|
+
function artifactFileExists(changeDir, generates) {
|
|
8
|
+
if (!isGlobPattern(generates)) {
|
|
9
|
+
const fullPath = path.join(changeDir, generates);
|
|
10
|
+
try {
|
|
11
|
+
return fs.statSync(fullPath).isFile();
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
// For glob patterns, check if any matching files exist
|
|
18
|
+
return globHasMatch(changeDir, generates);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Checks if a path pattern contains glob characters.
|
|
22
|
+
*/
|
|
23
|
+
function isGlobPattern(pattern) {
|
|
24
|
+
return pattern.includes('*') || pattern.includes('?') || pattern.includes('[');
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Simple glob matching: checks if any files match a glob-style pattern.
|
|
28
|
+
* Avoids external dependencies by walking the directory tree.
|
|
29
|
+
*/
|
|
30
|
+
function globHasMatch(baseDir, pattern) {
|
|
31
|
+
// Extract the extension pattern (e.g., "*.md" from "specs/**/*.md")
|
|
32
|
+
const parts = pattern.split('/');
|
|
33
|
+
const filePattern = parts[parts.length - 1];
|
|
34
|
+
const extension = filePattern.startsWith('*') ? filePattern.slice(1) : '';
|
|
35
|
+
// Get the starting directory path (before any glob segment)
|
|
36
|
+
const dirParts = [];
|
|
37
|
+
for (const part of parts) {
|
|
38
|
+
if (part.includes('*') || part.includes('?'))
|
|
39
|
+
break;
|
|
40
|
+
dirParts.push(part);
|
|
41
|
+
}
|
|
42
|
+
const startDir = dirParts.length > 0
|
|
43
|
+
? path.join(baseDir, ...dirParts)
|
|
44
|
+
: baseDir;
|
|
45
|
+
if (!fs.existsSync(startDir)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
return walkForMatch(startDir, extension);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Recursively walks a directory looking for files matching the extension.
|
|
52
|
+
*/
|
|
53
|
+
function walkForMatch(dir, extension) {
|
|
54
|
+
let entries;
|
|
55
|
+
try {
|
|
56
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
const fullPath = path.join(dir, entry.name);
|
|
63
|
+
if (entry.isDirectory()) {
|
|
64
|
+
if (walkForMatch(fullPath, extension))
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
else if (entry.isFile()) {
|
|
68
|
+
if (extension === '' || entry.name.endsWith(extension)) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Detects which artifacts are completed by checking file existence in the change directory.
|
|
77
|
+
*
|
|
78
|
+
* @param changeDir - The change directory to scan
|
|
79
|
+
* @param schema - The workflow schema with artifact definitions
|
|
80
|
+
* @returns Array of completed artifact IDs
|
|
81
|
+
*/
|
|
82
|
+
export function getCompletedArtifacts(changeDir, schema) {
|
|
83
|
+
if (!fs.existsSync(changeDir)) {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
const completed = [];
|
|
87
|
+
for (const artifact of schema.artifacts) {
|
|
88
|
+
if (artifactFileExists(changeDir, artifact.generates)) {
|
|
89
|
+
completed.push(artifact.id);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return completed;
|
|
93
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Zod schema for a single artifact definition in a workflow schema.
|
|
4
|
+
*/
|
|
5
|
+
export declare const ArtifactSchema: z.ZodObject<{
|
|
6
|
+
id: z.ZodString;
|
|
7
|
+
generates: z.ZodString;
|
|
8
|
+
description: z.ZodString;
|
|
9
|
+
instruction: z.ZodOptional<z.ZodString>;
|
|
10
|
+
requires: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
11
|
+
}, "strip", z.ZodTypeAny, {
|
|
12
|
+
id: string;
|
|
13
|
+
generates: string;
|
|
14
|
+
description: string;
|
|
15
|
+
requires: string[];
|
|
16
|
+
instruction?: string | undefined;
|
|
17
|
+
}, {
|
|
18
|
+
id: string;
|
|
19
|
+
generates: string;
|
|
20
|
+
description: string;
|
|
21
|
+
instruction?: string | undefined;
|
|
22
|
+
requires?: string[] | undefined;
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* Zod schema for the apply phase configuration.
|
|
26
|
+
*/
|
|
27
|
+
export declare const ApplyPhaseSchema: z.ZodObject<{
|
|
28
|
+
requires: z.ZodArray<z.ZodString, "many">;
|
|
29
|
+
tracks: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
30
|
+
instruction: z.ZodOptional<z.ZodString>;
|
|
31
|
+
}, "strip", z.ZodTypeAny, {
|
|
32
|
+
requires: string[];
|
|
33
|
+
instruction?: string | undefined;
|
|
34
|
+
tracks?: string | null | undefined;
|
|
35
|
+
}, {
|
|
36
|
+
requires: string[];
|
|
37
|
+
instruction?: string | undefined;
|
|
38
|
+
tracks?: string | null | undefined;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Zod schema for the full schema YAML structure.
|
|
42
|
+
*/
|
|
43
|
+
export declare const SchemaYamlSchema: z.ZodObject<{
|
|
44
|
+
name: z.ZodString;
|
|
45
|
+
version: z.ZodNumber;
|
|
46
|
+
description: z.ZodOptional<z.ZodString>;
|
|
47
|
+
artifacts: z.ZodArray<z.ZodObject<{
|
|
48
|
+
id: z.ZodString;
|
|
49
|
+
generates: z.ZodString;
|
|
50
|
+
description: z.ZodString;
|
|
51
|
+
instruction: z.ZodOptional<z.ZodString>;
|
|
52
|
+
requires: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
53
|
+
}, "strip", z.ZodTypeAny, {
|
|
54
|
+
id: string;
|
|
55
|
+
generates: string;
|
|
56
|
+
description: string;
|
|
57
|
+
requires: string[];
|
|
58
|
+
instruction?: string | undefined;
|
|
59
|
+
}, {
|
|
60
|
+
id: string;
|
|
61
|
+
generates: string;
|
|
62
|
+
description: string;
|
|
63
|
+
instruction?: string | undefined;
|
|
64
|
+
requires?: string[] | undefined;
|
|
65
|
+
}>, "many">;
|
|
66
|
+
apply: z.ZodOptional<z.ZodObject<{
|
|
67
|
+
requires: z.ZodArray<z.ZodString, "many">;
|
|
68
|
+
tracks: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
69
|
+
instruction: z.ZodOptional<z.ZodString>;
|
|
70
|
+
}, "strip", z.ZodTypeAny, {
|
|
71
|
+
requires: string[];
|
|
72
|
+
instruction?: string | undefined;
|
|
73
|
+
tracks?: string | null | undefined;
|
|
74
|
+
}, {
|
|
75
|
+
requires: string[];
|
|
76
|
+
instruction?: string | undefined;
|
|
77
|
+
tracks?: string | null | undefined;
|
|
78
|
+
}>>;
|
|
79
|
+
}, "strip", z.ZodTypeAny, {
|
|
80
|
+
name: string;
|
|
81
|
+
version: number;
|
|
82
|
+
artifacts: {
|
|
83
|
+
id: string;
|
|
84
|
+
generates: string;
|
|
85
|
+
description: string;
|
|
86
|
+
requires: string[];
|
|
87
|
+
instruction?: string | undefined;
|
|
88
|
+
}[];
|
|
89
|
+
description?: string | undefined;
|
|
90
|
+
apply?: {
|
|
91
|
+
requires: string[];
|
|
92
|
+
instruction?: string | undefined;
|
|
93
|
+
tracks?: string | null | undefined;
|
|
94
|
+
} | undefined;
|
|
95
|
+
}, {
|
|
96
|
+
name: string;
|
|
97
|
+
version: number;
|
|
98
|
+
artifacts: {
|
|
99
|
+
id: string;
|
|
100
|
+
generates: string;
|
|
101
|
+
description: string;
|
|
102
|
+
instruction?: string | undefined;
|
|
103
|
+
requires?: string[] | undefined;
|
|
104
|
+
}[];
|
|
105
|
+
description?: string | undefined;
|
|
106
|
+
apply?: {
|
|
107
|
+
requires: string[];
|
|
108
|
+
instruction?: string | undefined;
|
|
109
|
+
tracks?: string | null | undefined;
|
|
110
|
+
} | undefined;
|
|
111
|
+
}>;
|
|
112
|
+
/** A single artifact definition. */
|
|
113
|
+
export type Artifact = z.infer<typeof ArtifactSchema>;
|
|
114
|
+
/** Apply phase configuration. */
|
|
115
|
+
export type ApplyPhase = z.infer<typeof ApplyPhaseSchema>;
|
|
116
|
+
/** Full schema YAML structure. */
|
|
117
|
+
export type SchemaYaml = z.infer<typeof SchemaYamlSchema>;
|
|
118
|
+
/** Set of completed artifact IDs. */
|
|
119
|
+
export type CompletedSet = Set<string>;
|
|
120
|
+
/** Blocked artifacts mapped to their missing dependency IDs. */
|
|
121
|
+
export interface BlockedArtifacts {
|
|
122
|
+
readonly [artifactId: string]: readonly string[];
|
|
123
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Zod schema for a single artifact definition in a workflow schema.
|
|
4
|
+
*/
|
|
5
|
+
export const ArtifactSchema = z.object({
|
|
6
|
+
id: z.string().min(1, { message: 'Artifact ID is required' }),
|
|
7
|
+
generates: z.string().min(1, { message: 'generates field is required' }),
|
|
8
|
+
description: z.string(),
|
|
9
|
+
instruction: z.string().optional(),
|
|
10
|
+
requires: z.array(z.string()).default([]),
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* Zod schema for the apply phase configuration.
|
|
14
|
+
*/
|
|
15
|
+
export const ApplyPhaseSchema = z.object({
|
|
16
|
+
requires: z.array(z.string()).min(1, { message: 'At least one required artifact' }),
|
|
17
|
+
tracks: z.string().nullable().optional(),
|
|
18
|
+
instruction: z.string().optional(),
|
|
19
|
+
});
|
|
20
|
+
/**
|
|
21
|
+
* Zod schema for the full schema YAML structure.
|
|
22
|
+
*/
|
|
23
|
+
export const SchemaYamlSchema = z.object({
|
|
24
|
+
name: z.string().min(1, { message: 'Schema name is required' }),
|
|
25
|
+
version: z.number().int().positive({ message: 'Version must be a positive integer' }),
|
|
26
|
+
description: z.string().optional(),
|
|
27
|
+
artifacts: z.array(ArtifactSchema).min(1, { message: 'At least one artifact required' }),
|
|
28
|
+
apply: ApplyPhaseSchema.optional(),
|
|
29
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Change parser.
|
|
3
|
+
*
|
|
4
|
+
* Higher-level API that delegates to the markdown delta-spec parser.
|
|
5
|
+
* Produces a DeltaPlan from a delta-spec file's content.
|
|
6
|
+
*/
|
|
7
|
+
import type { RequirementBlock, RemovedRequirement, RenamedRequirement } from './markdown-parser.js';
|
|
8
|
+
export interface DeltaPlan {
|
|
9
|
+
readonly added: readonly RequirementBlock[];
|
|
10
|
+
readonly modified: readonly RequirementBlock[];
|
|
11
|
+
readonly removed: readonly RemovedRequirement[];
|
|
12
|
+
readonly renamed: readonly RenamedRequirement[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Parse a delta-spec markdown file into a DeltaPlan.
|
|
16
|
+
*
|
|
17
|
+
* This is a thin wrapper around `parseDeltaSpec` that produces
|
|
18
|
+
* the `DeltaPlan` type used by the rest of the system.
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseDeltaSpecFile(content: string): DeltaPlan;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Change parser.
|
|
3
|
+
*
|
|
4
|
+
* Higher-level API that delegates to the markdown delta-spec parser.
|
|
5
|
+
* Produces a DeltaPlan from a delta-spec file's content.
|
|
6
|
+
*/
|
|
7
|
+
import { parseDeltaSpec } from './markdown-parser.js';
|
|
8
|
+
/**
|
|
9
|
+
* Parse a delta-spec markdown file into a DeltaPlan.
|
|
10
|
+
*
|
|
11
|
+
* This is a thin wrapper around `parseDeltaSpec` that produces
|
|
12
|
+
* the `DeltaPlan` type used by the rest of the system.
|
|
13
|
+
*/
|
|
14
|
+
export function parseDeltaSpecFile(content) {
|
|
15
|
+
const spec = parseDeltaSpec(content);
|
|
16
|
+
return {
|
|
17
|
+
added: spec.added,
|
|
18
|
+
modified: spec.modified,
|
|
19
|
+
removed: spec.removed,
|
|
20
|
+
renamed: spec.renamed,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { parseDeltaSpec } from './markdown-parser.js';
|
|
2
|
+
export type { DeltaSpec, RequirementBlock, Scenario, RemovedRequirement, RenamedRequirement, } from './markdown-parser.js';
|
|
3
|
+
export { extractRequirementName, extractRequirementBlock, } from './requirement-blocks.js';
|
|
4
|
+
export { parseDeltaSpecFile } from './change-parser.js';
|
|
5
|
+
export type { DeltaPlan } from './change-parser.js';
|
|
6
|
+
export { parseMainSpec } from './spec-structure.js';
|
|
7
|
+
export type { Requirement, ParsedSpec } from './spec-structure.js';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown delta-spec parser.
|
|
3
|
+
*
|
|
4
|
+
* Parses a delta-spec markdown file into structured data with
|
|
5
|
+
* ADDED, MODIFIED, REMOVED, and RENAMED sections.
|
|
6
|
+
*/
|
|
7
|
+
export interface Scenario {
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly when: readonly string[];
|
|
10
|
+
readonly then: readonly string[];
|
|
11
|
+
}
|
|
12
|
+
export interface RequirementBlock {
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly description: string;
|
|
15
|
+
readonly scenarios: readonly Scenario[];
|
|
16
|
+
}
|
|
17
|
+
export interface RemovedRequirement {
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly reason: string;
|
|
20
|
+
readonly migration: string;
|
|
21
|
+
}
|
|
22
|
+
export interface RenamedRequirement {
|
|
23
|
+
readonly from: string;
|
|
24
|
+
readonly to: string;
|
|
25
|
+
}
|
|
26
|
+
export interface DeltaSpec {
|
|
27
|
+
readonly added: readonly RequirementBlock[];
|
|
28
|
+
readonly modified: readonly RequirementBlock[];
|
|
29
|
+
readonly removed: readonly RemovedRequirement[];
|
|
30
|
+
readonly renamed: readonly RenamedRequirement[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Parse a delta-spec markdown file into structured data.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseDeltaSpec(content: string): DeltaSpec;
|