Skip to content

Commit 945f624

Browse files
authored
feat: add design.md companion files (opt-in) (#226)
Adds design.md companion file support (opt-in via config), building on testing.md companions (#225) and YAML language support (#224).
1 parent 593cb29 commit 945f624

17 files changed

Lines changed: 307 additions & 46 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- **`testing.md` companion file** — a new default companion file scaffolded alongside every spec. Contains sections for automated test locations, manual QA checklists, and edge cases/boundary conditions. Generated by `specsync generate`, `specsync add-spec`, and `specsync new --full` (#225).
13+
- **`design.md` companion file (opt-in)** — layout, component hierarchy, design tokens, and asset references. Enabled via `[companions] design = true` in `.specsync/config.toml`. Generated alongside other companions when enabled (#226).
14+
- **YAML as source language**`Language::Yaml` variant added to export extraction. Detects `.yaml`/`.yml` files and extracts top-level mapping keys as symbols (#224).
15+
1016
## [4.1.3] - 2026-04-11
1117

1218
### Fixed

README.md

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
[![Downloads](https://img.shields.io/crates/d/specsync.svg)](https://crates.io/crates/specsync)
99
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
1010

11-
**Bidirectional spec-to-code validation with cross-project references, dependency graphs, and AI-powered generation.** Written in Rust. Single binary. 11 languages. VS Code extension.
11+
**Bidirectional spec-to-code validation with cross-project references, dependency graphs, and AI-powered generation.** Written in Rust. Single binary. 12 languages. VS Code extension.
1212

1313
[Quick Start](#quick-start) • [Spec Format](#spec-format) • [CLI](#cli-reference) • [VS Code Extension](#vs-code-extension) • [Cross-Project Refs](#cross-project-references) • [GitHub Action](#github-action) • [Config](#configuration) • [Docs Site](https://corvidlabs.github.io/spec-sync)
1414

@@ -48,6 +48,7 @@ Auto-detected from file extensions. Same spec format for all.
4848
| **Dart** | Top-level (no `_` prefix), class/mixin/enum/typedef | `*_test.dart` |
4949
| **PHP** | `class/interface/trait/enum`, `public` function/const, skips `private/protected` and `__` magic methods | `*Test.php` |
5050
| **Ruby** | `class`/`module`, `public` methods with visibility tracking, `attr_accessor`/`attr_reader`/`attr_writer`, constants | `*_test.rb`, `*_spec.rb` |
51+
| **YAML** | Top-level mapping keys (anchors, aliases supported) ||
5152

5253
---
5354

@@ -293,7 +294,7 @@ specsync [command] [flags]
293294
| `report` | Per-module coverage report with stale and incomplete detection |
294295
| `generate` | Scaffold specs for modules missing one (`--provider` for AI-powered content) |
295296
| `scaffold <name>` | Scaffold spec + auto-detect source files + register in registry |
296-
| `add-spec <name>` | Scaffold a single spec + companion files (`requirements.md`, `tasks.md`, `context.md`) |
297+
| `add-spec <name>` | Scaffold a single spec + companion files (`requirements.md`, `tasks.md`, `context.md`, `testing.md`, and `design.md` if enabled) |
297298
| `deps` | Validate cross-module dependency graph (cycles, missing deps, undeclared imports) |
298299
| `changelog <range>` | Generate changelog of spec changes between two git refs |
299300
| `comment` | Post spec-sync check summary as a PR comment (same validation pipeline as `check`). `--pr N` to post, omit to print |
@@ -399,14 +400,23 @@ Remote resolution fetches `specsync-registry.toml` from each referenced repo and
399400

400401
## Companion Files
401402

402-
When you run `specsync generate` or `specsync add-spec`, three companion files are created alongside each spec:
403+
When you run `specsync generate` or `specsync add-spec`, four companion files are created alongside each spec, plus one opt-in:
403404

404405
| File | Author | Validated? | Purpose |
405406
|------|--------|-----------|---------|
406407
| `{module}.spec.md` | Dev/Architect | Yes — against code | Technical contract |
407408
| `tasks.md` | Anyone | No | Work coordination |
408409
| `context.md` | Dev/Agent | No | Architecture notes |
409410
| `requirements.md` | Product/Design | No | The ask, acceptance criteria |
411+
| `testing.md` | QA/Dev | No | Test strategy, edge cases, manual checklists |
412+
| `design.md` *(opt-in)* | Design/Frontend | No | Layout, component hierarchy, design tokens |
413+
414+
`design.md` is opt-in — enable it in `.specsync/config.toml`:
415+
416+
```toml
417+
[companions]
418+
design = true
419+
```
410420

411421
All scaffolded by SpecSync, all human-filled. Only the spec gets bidirectional validation.
412422

@@ -472,6 +482,43 @@ spec: auth.spec.md
472482
<!-- Free-form notes, links, context -->
473483
```
474484

485+
**`testing.md`** — Test strategy and QA checklist:
486+
487+
```markdown
488+
---
489+
spec: auth.spec.md
490+
---
491+
492+
## Automated Tests
493+
<!-- Test file locations and what they cover -->
494+
495+
## Manual QA Checklist
496+
- [ ] <!-- Manual verification steps -->
497+
498+
## Edge Cases
499+
<!-- Boundary conditions, race conditions, unusual inputs -->
500+
```
501+
502+
**`design.md`** *(opt-in)* — Design and layout documentation:
503+
504+
```markdown
505+
---
506+
spec: auth.spec.md
507+
---
508+
509+
## Layout
510+
<!-- Page/component layout, responsive breakpoints, positioning -->
511+
512+
## Components
513+
<!-- Component tree, props, slots -->
514+
515+
## Tokens
516+
<!-- Design token overrides from global design system -->
517+
518+
## Assets
519+
<!-- Icons, images, illustrations needed -->
520+
```
521+
475522
These files are designed for team coordination and AI agent context — they give any contributor (human or AI) the full picture of where a module stands.
476523

477524
---
@@ -858,7 +905,8 @@ src/
858905
├── csharp.rs C# public items
859906
├── dart.rs Dart public items
860907
├── php.rs PHP public items
861-
└── ruby.rs Ruby public items
908+
├── ruby.rs Ruby public items
909+
└── yaml.rs YAML top-level keys
862910
```
863911
864912
**Design:** Single binary, no runtime deps. Frontmatter parsed with regex (no YAML library). Language backends use regex, not ASTs — works without compilers installed. Release builds use LTO + strip + opt-level 3.

docs/src/architecture.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ src/
3939
├── csharp.rs C# public items
4040
├── dart.rs Dart public items
4141
├── php.rs PHP public classes/functions
42-
└── ruby.rs Ruby public methods/classes
42+
├── ruby.rs Ruby public methods/classes
43+
└── yaml.rs YAML top-level keys
4344
```
4445

4546
---

docs/src/cli.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ Exposes tools: `specsync_check`, `specsync_generate`, `specsync_coverage`, `spec
8585

8686
### `add-spec`
8787

88-
Scaffold a single spec with companion files (`requirements.md`, `tasks.md`, `context.md`).
88+
Scaffold a single spec with companion files (`requirements.md`, `tasks.md`, `context.md`, `testing.md`, and `design.md` if enabled).
8989

9090
```bash
9191
specsync add-spec auth # creates specs/auth/auth.spec.md + companions
@@ -166,7 +166,7 @@ Quick-create a minimal spec with auto-detected source files. Faster than `add-sp
166166

167167
```bash
168168
specsync new auth # creates specs/auth/auth.spec.md
169-
specsync new auth --full # also creates companion files (requirements.md, tasks.md, context.md)
169+
specsync new auth --full # also creates companion files (requirements.md, tasks.md, context.md, testing.md, and design.md if enabled)
170170
```
171171

172172
Scans `sourceDirs` for files matching the module name to auto-populate the `files:` frontmatter field.

docs/src/quickstart.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,15 @@ specs/
8181
│ ├── auth.spec.md ← The spec (validated)
8282
│ ├── requirements.md ← User stories & acceptance criteria
8383
│ ├── tasks.md ← Work items & sign-offs
84-
│ └── context.md ← Architecture notes & key files
84+
│ ├── context.md ← Architecture notes & key files
85+
│ ├── testing.md ← Test strategy & QA checklist
86+
│ └── design.md ← (opt-in) Layout & design tokens
8587
├── database/
8688
│ ├── database.spec.md
8789
│ ├── requirements.md
8890
│ ├── tasks.md
89-
│ └── context.md
91+
│ ├── context.md
92+
│ └── testing.md
9093
└── ...
9194
```
9295

docs/src/workflow.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ Creates `specs/auth/` with five files:
6565
| `tasks.md` | Outstanding work items, review sign-offs | Anyone |
6666
| `context.md` | Design decisions, key files, current status | Developer / Agent |
6767
| `testing.md` | Test strategy, QA checklists, edge cases | QA / Developer |
68+
| `design.md` *(opt-in)* | Layout, component hierarchy, design tokens | Design / Frontend |
6869

6970
The spec file is the only one SpecSync validates against code. The companion files provide structured context for humans and AI agents working on the module.
7071

specs/exports/exports.spec.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ files:
1515
- src/exports/csharp.rs
1616
- src/exports/php.rs
1717
- src/exports/ruby.rs
18+
- src/exports/yaml.rs
1819
- src/exports/ast/mod.rs
1920
- src/exports/ast/typescript.rs
2021
- src/exports/ast/python.rs
@@ -29,7 +30,7 @@ depends_on:
2930

3031
## Purpose
3132

32-
Language-aware export extraction from source files. Auto-detects the programming language from file extension and extracts public/exported symbol names using regex-based parsing or tree-sitter AST analysis. Supports 11 languages: TypeScript/JS, Rust, Go, Python, Swift, Kotlin, Java, C#, Dart, PHP, and Ruby.
33+
Language-aware export extraction from source files. Auto-detects the programming language from file extension and extracts public/exported symbol names using regex-based parsing or tree-sitter AST analysis. Supports 12 languages: TypeScript/JS, Rust, Go, Python, Swift, Kotlin, Java, C#, Dart, PHP, Ruby, and YAML.
3334

3435
## Public API
3536

@@ -79,6 +80,7 @@ Each language backend exposes a single `extract_exports(content: &str) -> Vec<St
7980
| C# | `csharp.rs` | `public class/struct/interface/enum/record/delegate` types and `public` members; handles `static`, `partial`, `sealed`, `abstract`, `virtual`, `override`, `async` modifiers |
8081
| PHP | `php.rs` | `class/interface/trait/enum` types (always public); `public`/unqualified `function` and `const` declarations; skips `private`/`protected` members and `__` magic methods; handles `abstract`, `final`, `readonly`, `static` modifiers; strips `//`, `/* */`, and `#` comments |
8182
| Ruby | `ruby.rs` | `class`/`module` declarations; top-level `def` (always public); class methods with visibility tracking (`public``private``protected``public` toggles); `CONSTANT` assignments; `attr_accessor`/`attr_reader`/`attr_writer` symbols; skips `_`-prefixed names and `initialize`; strips `#` and `=begin/=end` comments |
83+
| YAML | `yaml.rs` | Top-level mapping keys from `.yaml`/`.yml` files; supports anchors and aliases |
8284

8385
## Invariants
8486

@@ -104,6 +106,7 @@ Each language backend exposes a single `extract_exports(content: &str) -> Vec<St
104106
16. Go backend deduplicates methods that might also match top-level declarations
105107
17. PHP backend treats types (class/interface/trait/enum) as always public; methods and constants require `public` or unqualified visibility; `private`/`protected` are excluded; magic methods (`__construct`, `__toString`, etc.) are excluded
106108
18. Ruby backend tracks visibility state via `public`/`private`/`protected` toggle statements; defaults to public; `initialize` is excluded; `_`-prefixed names are excluded; `attr_accessor`/`attr_reader`/`attr_writer` emit attribute names as symbols
109+
19. YAML backend extracts top-level mapping keys; no test file patterns (YAML files are not test files)
107110

108111
## Behavioral Examples
109112

@@ -230,3 +233,4 @@ Each language backend exposes a single `extract_exports(content: &str) -> Vec<St
230233
| 2026-03-25 | Initial spec |
231234
| 2026-03-28 | Document get_exported_symbols_with_level |
232235
| 2026-03-29 | Add PHP and Ruby language support |
236+
| 2026-04-12 | Add YAML language support (yaml.rs) |

specs/generator/generator.spec.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ depends_on:
1616

1717
## Purpose
1818

19-
Scaffolds spec files and companion files (tasks.md, context.md) for unspecced modules. Supports both template-based generation (using a default or custom `_template.spec.md`) and AI-powered generation that reads source code and calls an LLM to produce meaningful specs.
19+
Scaffolds spec files and companion files (tasks.md, context.md, requirements.md, testing.md, and optionally design.md) for unspecced modules. Supports both template-based generation (using a default or custom `_template.spec.md`) and AI-powered generation that reads source code and calls an LLM to produce meaningful specs.
2020

2121
## Public API
2222

@@ -26,7 +26,7 @@ Scaffolds spec files and companion files (tasks.md, context.md) for unspecced mo
2626
|----------|-----------|---------|-------------|
2727
| `generate_specs_for_unspecced_modules` | `root, report, config, provider` | `usize` | Generate specs for all unspecced modules, returning count of generated specs |
2828
| `generate_specs_for_unspecced_modules_paths` | `root, report, config, provider` | `Vec<String>` | Generate specs for all unspecced modules, returning paths of generated files |
29-
| `generate_companion_files_for_spec` | `spec_dir, module_name` | `()` | Generate tasks.md and context.md companion files alongside a spec |
29+
| `generate_companion_files_for_spec` | `spec_dir, module_name, design_enabled` | `()` | Generate companion files (tasks.md, context.md, requirements.md, testing.md, and design.md if enabled) alongside a spec |
3030
| `find_files_for_module` | `root, module_name, config` | `Vec<String>` | Find source files for a module by checking config definitions, subdirectories, then flat files |
3131
| `generate_spec` | `module_name, source_files, root, specs_dir` | `String` | Generate a spec from a template (custom or language-aware default) |
3232
| `generate_spec_from_custom_template` | `template_dir, module_name, source_files, root` | `String` | Generate a spec using files from a custom template directory |
@@ -38,7 +38,7 @@ Scaffolds spec files and companion files (tasks.md, context.md) for unspecced mo
3838
2. Custom templates at `specs/_template.spec.md` take precedence over the built-in default
3939
3. Template generation fills in module name, version (1), status (draft), and discovered source files
4040
4. Module title is derived from the module name with dashes converted to title case (e.g. "api-gateway" -> "Api Gateway")
41-
5. Companion files (tasks.md, context.md) are only created if they don't already exist
41+
5. Companion files (tasks.md, context.md, requirements.md, testing.md, and design.md when enabled) are only created if they don't already exist
4242
6. AI generation falls back to template on failure (with a warning to stderr)
4343
7. Source file paths in frontmatter are relative to the project root
4444
8. Module source files are discovered by checking subdirectory-based modules first, then flat files
@@ -49,7 +49,7 @@ Scaffolds spec files and companion files (tasks.md, context.md) for unspecced mo
4949

5050
- **Given** a module "auth" with source files in `src/auth/` and no existing spec
5151
- **When** `generate_specs_for_unspecced_modules` is called
52-
- **Then** creates `specs/auth/auth.spec.md`, `specs/auth/tasks.md`, and `specs/auth/context.md`
52+
- **Then** creates `specs/auth/auth.spec.md`, `specs/auth/tasks.md`, `specs/auth/context.md`, `specs/auth/requirements.md`, and `specs/auth/testing.md`
5353

5454
### Scenario: Skip existing spec
5555

@@ -95,3 +95,4 @@ Scaffolds spec files and companion files (tasks.md, context.md) for unspecced mo
9595
|------|--------|
9696
| 2026-03-25 | Initial spec |
9797
| 2026-04-07 | Document find_files_for_module, generate_spec, generate_spec_from_custom_template, generate_companion_files_from_template |
98+
| 2026-04-12 | Update companion files list to include requirements.md, testing.md, and opt-in design.md; add design_enabled parameter |

specs/types/types.spec.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Core data structures and enums shared across the entire spec-sync codebase. Defi
2222
| Type | Description |
2323
|------|-------------|
2424
| `AiProvider` | Supported AI provider presets: Claude, Cursor, Copilot, Ollama, Anthropic, OpenAi, Custom |
25-
| `Language` | Detected source language for export extraction: TypeScript, Rust, Go, Python, Swift, Kotlin, Java, CSharp, Dart, Php, Ruby |
25+
| `Language` | Detected source language for export extraction: TypeScript, Rust, Go, Python, Swift, Kotlin, Java, CSharp, Dart, Php, Ruby, Yaml |
2626
| `OutputFormat` | CLI output format: Text (colored terminal, default), Json (machine-readable), Markdown (PR comments / agent consumption) |
2727
| `ExportLevel` | Export extraction granularity: Type (top-level declarations only) or Member (all public symbols, default) |
2828
| `SpecStatus` | Spec lifecycle status: draft, review, active, stable, deprecated, archived. Parsed from frontmatter `status` field |
@@ -47,6 +47,7 @@ Core data structures and enums shared across the entire spec-sync codebase. Defi
4747
| `RuleFilter` | Filter to restrict which specs a custom rule applies to — optional status and module regex match |
4848
| `LifecycleConfig` | Lifecycle configuration for transition guards and history tracking (guards map, track_history flag) |
4949
| `TransitionGuard` | A transition guard — min_score, require_sections, no_stale, stale_threshold, message |
50+
| `CompanionConfig` | Configuration for companion file generation — controls opt-in companions like design.md |
5051

5152
### Exported AiProvider Functions
5253

@@ -173,3 +174,4 @@ Core data structures and enums shared across the entire spec-sync codebase. Defi
173174
| 2026-04-10 | Document CustomRule, CustomRuleType, RuleSeverity, RuleFilter for declarative custom validation rules |
174175
| 2026-04-11 | Document SpecStatus lifecycle methods (all, ordinal, next, prev, valid_transitions, can_transition_to) |
175176
| 2026-04-11 | Move parsed_status to Frontmatter section; fix next/prev descriptions to include deprecated/archived |
177+
| 2026-04-12 | Document CompanionConfig struct for opt-in companion file settings |

src/commands/import.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,11 @@ fn cmd_import_single(root: &Path, source: &str, id: &str, repo_override: Option<
146146
Ok(_) => {
147147
let rel = spec_file.strip_prefix(root).unwrap_or(&spec_file).display();
148148
println!(" {} Created {rel}", "✓".green());
149-
generator::generate_companion_files_for_spec(&spec_dir, &item.module_name);
149+
generator::generate_companion_files_for_spec(
150+
&spec_dir,
151+
&item.module_name,
152+
config.companions.design,
153+
);
150154
println!(
151155
"\n{} Run {} to validate and fill in the details.",
152156
"Tip:".cyan().bold(),
@@ -249,7 +253,11 @@ fn cmd_import_all_issues(root: &Path, repo_override: Option<&str>, label: Option
249253
Ok(_) => {
250254
let rel = spec_file.strip_prefix(root).unwrap_or(&spec_file).display();
251255
println!("{} #{} → {}", "✓".green(), issue.number, rel);
252-
generator::generate_companion_files_for_spec(&spec_dir, &item.module_name);
256+
generator::generate_companion_files_for_spec(
257+
&spec_dir,
258+
&item.module_name,
259+
config.companions.design,
260+
);
253261
stats.imported += 1;
254262
}
255263
Err(e) => {
@@ -349,7 +357,11 @@ fn cmd_import_from_dir(root: &Path, dir: &Path) {
349357
Ok(_) => {
350358
let rel = spec_file.strip_prefix(root).unwrap_or(&spec_file).display();
351359
println!("{} → {}", "✓".green(), rel);
352-
generator::generate_companion_files_for_spec(&spec_dir, &item.module_name);
360+
generator::generate_companion_files_for_spec(
361+
&spec_dir,
362+
&item.module_name,
363+
config.companions.design,
364+
);
353365
stats.imported += 1;
354366
}
355367
Err(e) => {

0 commit comments

Comments
 (0)