diff --git a/openspec/changes/add-instruction-loader/design.md b/openspec/changes/add-instruction-loader/design.md new file mode 100644 index 00000000..332f2db4 --- /dev/null +++ b/openspec/changes/add-instruction-loader/design.md @@ -0,0 +1,149 @@ +## Context + +This is Slice 3 of the artifact-graph POC. We have: +- `ArtifactGraph` class with graph operations (Slice 1) +- `detectCompleted()` for filesystem-based state detection (Slice 1) +- `resolveSchema()` for XDG schema resolution (Slice 1) +- `createChange()` and `validateChangeName()` utilities (Slice 2) + +After `restructure-schema-directories` is implemented, schemas will be self-contained directories: +``` +schemas// +├── schema.yaml +└── templates/ + └── *.md +``` + +This proposal adds template loading and instruction enrichment on top of that structure. + +## Goals / Non-Goals + +**Goals:** +- Load templates from schema directories +- Enrich templates with change-specific context (dependency status) +- Format change status for CLI output + +**Non-Goals:** +- Template authoring UI +- Dynamic template compilation/execution +- Caching (keep it stateless like the rest) + +## Decisions + +### 1. Pure functions over classes + +Follow the pattern in `resolver.ts` and `state.ts`. Use a simple `ChangeContext` interface with pure functions: + +```typescript +interface ChangeContext { + changeName: string; + changeDir: string; + schemaName: string; + graph: ArtifactGraph; + completed: CompletedSet; +} + +function loadChangeContext(projectRoot: string, changeName: string, schemaName?: string): ChangeContext +function loadTemplate(schemaName: string, templatePath: string): string +function getInstructions(artifactId: string, context: ChangeContext): string +function formatStatus(context: ChangeContext): string +``` + +**Why:** Matches existing codebase patterns. Easier to test. No hidden state. + +### 2. Template resolution from schema directory + +Templates are loaded from the schema's `templates/` subdirectory: + +```typescript +function loadTemplate(schemaName: string, templatePath: string): string { + const schemaDir = getSchemaDir(schemaName); // From resolver.ts + const fullPath = path.join(schemaDir, 'templates', templatePath); + return fs.readFileSync(fullPath, 'utf-8'); +} +``` + +Resolution is handled by `getSchemaDir()` which already checks user override → package built-in. + +**Why:** Leverages existing schema resolution. Templates are co-located with schemas. + +### 3. Template path from artifact definition + +The artifact's `template` field is a path relative to the schema's `templates/` directory: + +```yaml +artifacts: + - id: proposal + template: "proposal.md" # → schemas//templates/proposal.md +``` + +**Why:** Explicit, simple, no magic. + +### 4. Minimal context injection + +Templates are markdown. Injection prepends a header section with context: + +```markdown +--- +change: add-auth +artifact: proposal +schema: spec-driven +output: openspec/changes/add-auth/proposal.md +--- + +## Dependencies +- [x] (none - this is a root artifact) + +## Next Steps +After creating this artifact, you can work on: design, specs + +--- + +[original template content...] +``` + +**Why:** Simple string concatenation. No template engine dependency. Clear separation. + +### 5. Status output format + +```markdown +## Change: add-auth (spec-driven) + +| Artifact | Status | Output | +|----------|--------|--------| +| proposal | done | proposal.md | +| specs | ready | specs/*.md | +| design | blocked (needs: proposal) | design.md | +| tasks | blocked (needs: specs, design) | tasks.md | +``` + +**Why:** Markdown table is readable in terminal and docs. Matches CLI output style. + +## File Structure + +``` +src/core/artifact-graph/ +├── index.ts # Add new exports +├── template.ts # NEW: Template loading +├── context.ts # NEW: ChangeContext loading +└── instructions.ts # NEW: Enrichment and formatting +``` + +## Risks / Trade-offs + +**Dependency on restructure-schema-directories:** +- This proposal requires the schema restructure to be done first +- Mitigation: Clear dependency documented, implement in order + +**No template engine:** +- Pro: Zero dependencies, simple code +- Con: Limited expressiveness +- Mitigation: Current use case only needs static templates + header injection + +## Migration Plan + +N/A - new capability, no existing code to migrate. + +## Open Questions + +None. diff --git a/openspec/changes/add-instruction-loader/proposal.md b/openspec/changes/add-instruction-loader/proposal.md new file mode 100644 index 00000000..0112fb3b --- /dev/null +++ b/openspec/changes/add-instruction-loader/proposal.md @@ -0,0 +1,20 @@ +## Why + +Slice 1 (artifact-graph) provides graph operations and state detection. Slice 2 (change-utils) provides change creation. We now need the ability to load templates for artifacts and enrich them with change-specific context so users/agents know what to create next. + +## What Changes + +- Add template resolution from schema directories (uses structure from `restructure-schema-directories`) +- Add instruction enrichment that injects change context into templates +- Add status formatting for CLI output +- New `instruction-loader` capability + +## Dependencies + +- Requires `restructure-schema-directories` to be implemented first (schemas as directories with co-located templates) + +## Impact + +- Affected specs: New `instruction-loader` spec +- Affected code: `src/core/artifact-graph/` (new files) +- Builds on: `artifact-graph` (Slice 1), uses `ArtifactGraph`, `detectCompleted`, `resolveSchema` diff --git a/openspec/changes/add-instruction-loader/specs/instruction-loader/spec.md b/openspec/changes/add-instruction-loader/specs/instruction-loader/spec.md new file mode 100644 index 00000000..261a1ee1 --- /dev/null +++ b/openspec/changes/add-instruction-loader/specs/instruction-loader/spec.md @@ -0,0 +1,65 @@ +## ADDED Requirements + +### Requirement: Template Loading +The system SHALL load templates from schema directories. + +#### Scenario: Template loaded from schema directory +- **WHEN** `loadTemplate(schemaName, templatePath)` is called +- **THEN** the system loads the template from `schemas//templates/` + +#### Scenario: Template not found +- **WHEN** a template file does not exist in the schema's templates directory +- **THEN** the system throws an error with the template path + +### Requirement: Change Context Loading +The system SHALL load change context combining graph and completion state. + +#### Scenario: Load context for existing change +- **WHEN** `loadChangeContext(projectRoot, changeName)` is called for an existing change +- **THEN** the system returns a context with graph, completed set, schema name, and change info + +#### Scenario: Load context with custom schema +- **WHEN** `loadChangeContext(projectRoot, changeName, schemaName)` is called +- **THEN** the system uses the specified schema instead of default + +#### Scenario: Load context for missing change +- **WHEN** `loadChangeContext` is called for a non-existent change directory +- **THEN** the system returns context with empty completed set + +### Requirement: Instruction Enrichment +The system SHALL enrich templates with change-specific context. + +#### Scenario: Header with change info +- **WHEN** instructions are generated for an artifact +- **THEN** the output includes change name, artifact ID, schema name, and output path + +#### Scenario: Dependency status shown +- **WHEN** an artifact has dependencies +- **THEN** the output shows each dependency with completion status (done/missing) + +#### Scenario: Next steps shown +- **WHEN** instructions are generated +- **THEN** the output includes which artifacts become available after this one + +#### Scenario: Root artifact dependencies +- **WHEN** an artifact has no dependencies +- **THEN** the dependency section indicates this is a root artifact + +### Requirement: Status Formatting +The system SHALL format change status as readable output. + +#### Scenario: Format complete change +- **WHEN** all artifacts are completed +- **THEN** status shows all artifacts as "done" + +#### Scenario: Format partial change +- **WHEN** some artifacts are completed +- **THEN** status shows completed as "done", ready as "ready", blocked as "blocked" + +#### Scenario: Show blocked dependencies +- **WHEN** an artifact is blocked +- **THEN** status shows which dependencies are missing + +#### Scenario: Show output paths +- **WHEN** status is formatted +- **THEN** each artifact shows its output path pattern diff --git a/openspec/changes/add-instruction-loader/tasks.md b/openspec/changes/add-instruction-loader/tasks.md new file mode 100644 index 00000000..5184a9c6 --- /dev/null +++ b/openspec/changes/add-instruction-loader/tasks.md @@ -0,0 +1,34 @@ +## 1. Template Loading + +- [ ] 1.1 Create `src/core/artifact-graph/template.ts` +- [ ] 1.2 Implement `loadTemplate(schemaName, templatePath)` using schema directory structure +- [ ] 1.3 Add tests for template loading from schema directory +- [ ] 1.4 Add tests for error when template not found + +## 2. Change Context + +- [ ] 2.1 Create `src/core/artifact-graph/context.ts` +- [ ] 2.2 Define `ChangeContext` interface +- [ ] 2.3 Implement `loadChangeContext()` function +- [ ] 2.4 Add tests for context loading with existing change +- [ ] 2.5 Add tests for context loading with missing change directory + +## 3. Instruction Enrichment + +- [ ] 3.1 Create `src/core/artifact-graph/instructions.ts` +- [ ] 3.2 Implement `getInstructions()` with header injection +- [ ] 3.3 Add dependency status formatting (done/missing) +- [ ] 3.4 Add next steps calculation +- [ ] 3.5 Add tests for enrichment output + +## 4. Status Formatting + +- [ ] 4.1 Implement `formatStatus()` function in instructions.ts +- [ ] 4.2 Format as markdown table with status and output path +- [ ] 4.3 Show blocked dependencies +- [ ] 4.4 Add tests for status formatting + +## 5. Integration + +- [ ] 5.1 Export new functions from `src/core/artifact-graph/index.ts` +- [ ] 5.2 Ensure all tests pass diff --git a/openspec/changes/restructure-schema-directories/design.md b/openspec/changes/restructure-schema-directories/design.md new file mode 100644 index 00000000..f49cc692 --- /dev/null +++ b/openspec/changes/restructure-schema-directories/design.md @@ -0,0 +1,129 @@ +## Context + +Built-in schemas are currently embedded as TypeScript objects: + +```typescript +// src/core/artifact-graph/builtin-schemas.ts +export const SPEC_DRIVEN_SCHEMA: SchemaYaml = { + name: 'spec-driven', + version: 1, + artifacts: [...] +}; +``` + +This doesn't support templates co-located with schemas. The instruction loader (Slice 3) needs templates, and the cleanest approach is self-contained schema directories. + +## Goals / Non-Goals + +**Goals:** +- Schemas as self-contained directories (schema.yaml + templates/) +- User overrides via XDG data directory +- Simple 2-level resolution (user → package) +- Templates co-located with their schema + +**Non-Goals:** +- Shared template fallback (intentionally avoiding complexity) +- Runtime schema compilation +- Schema inheritance + +## Decisions + +### 1. Directory structure + +Each schema is a directory containing `schema.yaml` and `templates/`: + +``` +/schemas/ +├── spec-driven/ +│ ├── schema.yaml +│ └── templates/ +│ ├── proposal.md +│ ├── design.md +│ ├── spec.md +│ └── tasks.md +└── tdd/ + ├── schema.yaml + └── templates/ + ├── spec.md + ├── test.md + ├── implementation.md + └── docs.md +``` + +**Why:** Self-contained like Helm charts. No cross-schema dependencies. Each schema owns its templates. + +### 2. Resolution order (2 levels) + +``` +1. ${XDG_DATA_HOME}/openspec/schemas//schema.yaml # User override +2. /schemas//schema.yaml # Built-in +3. Error (not found) +``` + +**Why:** Simple mental model. User can override entire schema directory or just parts. + +### 3. Template path in schema.yaml + +The `template` field is relative to the schema's `templates/` directory: + +```yaml +# schemas/spec-driven/schema.yaml +artifacts: + - id: proposal + template: "proposal.md" # → schemas/spec-driven/templates/proposal.md +``` + +**Why:** Paths are relative to the schema, not a global templates directory. + +### 4. Resolve package directory via import.meta.url + +```typescript +function getPackageSchemasDir(): string { + const currentFile = fileURLToPath(import.meta.url); + // Navigate from src/core/artifact-graph/ to package root + return path.join(path.dirname(currentFile), '..', '..', '..', 'schemas'); +} +``` + +**Why:** Works in ESM. No hardcoded paths. + +### 5. Keep schema.yaml format unchanged + +The YAML format stays the same - only the storage location changes: + +```yaml +name: spec-driven +version: 1 +description: Specification-driven development +artifacts: + - id: proposal + generates: "proposal.md" + template: "proposal.md" + requires: [] +``` + +**Why:** No breaking changes to schema format. Just moving from TS to YAML files. + +## Migration + +1. Create `schemas/` directory at package root +2. Convert `SPEC_DRIVEN_SCHEMA` to `schemas/spec-driven/schema.yaml` +3. Convert `TDD_SCHEMA` to `schemas/tdd/schema.yaml` +4. Update `resolveSchema()` to load from directories +5. Remove `builtin-schemas.ts` +6. Update `listSchemas()` to scan directories + +## Risks / Trade-offs + +**File I/O at runtime:** +- Previously schemas were in-memory objects +- Now requires reading YAML files +- Mitigation: Schemas are small, loaded once per operation + +**Package distribution:** +- Must ensure `schemas/` directory is included in npm package +- Add to `files` in package.json + +## Open Questions + +None. diff --git a/openspec/changes/restructure-schema-directories/proposal.md b/openspec/changes/restructure-schema-directories/proposal.md new file mode 100644 index 00000000..47fe8719 --- /dev/null +++ b/openspec/changes/restructure-schema-directories/proposal.md @@ -0,0 +1,20 @@ +## Why + +Currently, built-in schemas are embedded as TypeScript objects in `builtin-schemas.ts`. This works for schemas but doesn't support co-located templates. To enable self-contained schema packages (schema + templates together), we need to restructure schemas as directories. + +## What Changes + +- **BREAKING (internal):** Move built-in schemas from embedded TS objects to actual directory structure +- Schemas become directories containing `schema.yaml` + `templates/` +- Update `resolveSchema()` to load from directory structure +- Remove `builtin-schemas.ts` (replaced by file-based schemas) +- Update resolution to check user dir → package dir + +## Impact + +- Affected specs: `artifact-graph` (schema resolution changes) +- Affected code: + - Remove `src/core/artifact-graph/builtin-schemas.ts` + - Update `src/core/artifact-graph/resolver.ts` + - Add `schemas/` directory at package root +- No external API changes (resolution still returns `SchemaYaml`) diff --git a/openspec/changes/restructure-schema-directories/specs/artifact-graph/spec.md b/openspec/changes/restructure-schema-directories/specs/artifact-graph/spec.md new file mode 100644 index 00000000..1568765d --- /dev/null +++ b/openspec/changes/restructure-schema-directories/specs/artifact-graph/spec.md @@ -0,0 +1,49 @@ +## MODIFIED Requirements + +### Requirement: Schema Loading +The system SHALL load artifact graph definitions from YAML schema files within schema directories. + +#### Scenario: Valid schema loaded +- **WHEN** a schema directory contains a valid `schema.yaml` file +- **THEN** the system returns an ArtifactGraph with all artifacts and dependencies + +#### Scenario: Invalid schema rejected +- **WHEN** a schema YAML file is missing required fields +- **THEN** the system throws an error with a descriptive message + +#### Scenario: Cyclic dependencies detected +- **WHEN** a schema contains cyclic artifact dependencies +- **THEN** the system throws an error listing the artifact IDs in the cycle + +#### Scenario: Invalid dependency reference +- **WHEN** an artifact's `requires` array references a non-existent artifact ID +- **THEN** the system throws an error identifying the invalid reference + +#### Scenario: Duplicate artifact IDs rejected +- **WHEN** a schema contains multiple artifacts with the same ID +- **THEN** the system throws an error identifying the duplicate + +#### Scenario: Schema directory not found +- **WHEN** resolving a schema name that has no corresponding directory +- **THEN** the system throws an error listing available schemas + +## ADDED Requirements + +### Requirement: Schema Directory Structure +The system SHALL support self-contained schema directories with co-located templates. + +#### Scenario: Schema with templates +- **WHEN** a schema directory contains `schema.yaml` and `templates/` subdirectory +- **THEN** artifacts can reference templates relative to the schema's templates directory + +#### Scenario: User schema override +- **WHEN** a schema directory exists at `${XDG_DATA_HOME}/openspec/schemas//` +- **THEN** the system uses that directory instead of the built-in + +#### Scenario: Built-in schema fallback +- **WHEN** no user override exists for a schema +- **THEN** the system uses the package built-in schema directory + +#### Scenario: List available schemas +- **WHEN** listing schemas +- **THEN** the system returns schema names from both user and package directories diff --git a/openspec/changes/restructure-schema-directories/tasks.md b/openspec/changes/restructure-schema-directories/tasks.md new file mode 100644 index 00000000..dd18908d --- /dev/null +++ b/openspec/changes/restructure-schema-directories/tasks.md @@ -0,0 +1,32 @@ +## 1. Create Schema Directories + +- [ ] 1.1 Create `schemas/` directory at package root +- [ ] 1.2 Create `schemas/spec-driven/schema.yaml` from `SPEC_DRIVEN_SCHEMA` +- [ ] 1.3 Create `schemas/spec-driven/templates/` with placeholder templates +- [ ] 1.4 Create `schemas/tdd/schema.yaml` from `TDD_SCHEMA` +- [ ] 1.5 Create `schemas/tdd/templates/` with placeholder templates + +## 2. Update Schema Resolution + +- [ ] 2.1 Add `getPackageSchemasDir()` function using `import.meta.url` +- [ ] 2.2 Add `getSchemaDir(name)` to resolve schema directory path +- [ ] 2.3 Update `resolveSchema()` to load from directory structure +- [ ] 2.4 Update `listSchemas()` to scan directories instead of object keys +- [ ] 2.5 Add tests for user override resolution +- [ ] 2.6 Add tests for built-in fallback + +## 3. Cleanup + +- [ ] 3.1 Remove `builtin-schemas.ts` +- [ ] 3.2 Update `index.ts` exports (remove `BUILTIN_SCHEMAS`, `SPEC_DRIVEN_SCHEMA`, `TDD_SCHEMA`) +- [ ] 3.3 Update any code that imports removed exports + +## 4. Package Distribution + +- [ ] 4.1 Add `schemas/` to `files` array in `package.json` +- [ ] 4.2 Verify schemas are included in built package + +## 5. Fix Template Paths + +- [ ] 5.1 Update `template` field in schema.yaml files (remove `templates/` prefix) +- [ ] 5.2 Ensure template paths are relative to schema's templates directory