Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- **Fresh `specsync init` now creates the v4 layout** — init writes `.specsync/config.toml`, a `4.0.0` version stamp, `.specsync/.gitignore`, and the `lifecycle/`/`changes/`/`archive/` directories (what `specsync migrate` produces) instead of a legacy root-level `specsync.json`, so a brand-new project no longer sees the "Legacy 3.x layout detected" migration nag on its first `check`.
- **`init-registry` respects the v4 layout** — the registry is written to `.specsync/registry.toml` in v4 projects instead of recreating a root-level `specsync-registry.toml` (which re-triggered the legacy nag after migration). Un-migrated 3.x projects keep the legacy path. `load_registry`/`register_module` now resolve the same location via the new `registry::local_registry_path`.
- **Draft specs no longer pass validation silently** — when a draft skips section/export checks (by design), `check` now prints explicit "Section validation skipped (status: draft)" / "Export validation skipped (status: draft)" notices instead of misleading "✓ All required sections present" lines, plus a summary hint: "N draft spec(s) skipped section and export validation — set `status: active` to enable full checks".
- **`check --fix` routes exports to the matching table** — functions/values are appended to the "… Functions"/"… Methods" table and type exports to the "… Types" table (previously everything landed in the last export table, e.g. functions `add`/`subtract` in "Exported Types"). New rows are padded to the target table's column count.
- **`generate` exits non-zero when AI generation fails** — a failed provider call (e.g. missing API key) still falls back to the template, but the failures are re-printed prominently on stderr *after* the check report and the command exits 1 instead of burying the error and exiting 0. JSON output gains an `ai_errors` array; the MCP `specsync_generate` tool reports `ai_errors` too. Both generation entry points now return a `GenerationOutcome` (count, paths, AI errors).
- **`watch` footer no longer contradicts the report** — the footer parses the child check's summary line instead of trusting only its exit code (which is 0 under the default `enforcement = warn`), so "All checks passed!" is never printed beneath a "… 1 failed" summary.
- **Failing checks render negated labels** — a failing frontmatter check now prints "✗ Frontmatter invalid" instead of "✗ Frontmatter valid".
- **`check <name>` with an unmatched spec filter exits 1** — and no longer follows the "No specs matched" warning with a contradictory "No spec files found in specs/" message when specs exist.
- **`--root` pointing at a nonexistent path now errors (exit 2)** — previously the CLI silently exited 0 having checked nothing.
- **Spec scoring no longer false-flags documented HTML-comment syntax** — the `placeholder_free` check strips fenced and inline code before counting `<!-- ... -->`, so a spec that *documents* an HTML-comment directive (e.g. ``a `<!-- specsync-ignore -->` directive``) isn't penalized for showing real syntax.

## [4.4.0] - 2026-06-07
Expand Down
9 changes: 5 additions & 4 deletions specs/cli/cli.spec.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
module: cli
version: 3
version: 4
status: stable
files:
- src/main.rs
Expand Down Expand Up @@ -130,7 +130,7 @@ All functions in main.rs are private (no pub keyword). Key internal functions:
## Invariants

1. When no subcommand is given, `check` runs by default
2. `--root` defaults to the current working directory; the path is canonicalized
2. `--root` defaults to the current working directory; the path is validated (must be an existing directory — otherwise an error is printed and the process exits 2) and canonicalized
3. `--strict` causes warnings to produce a non-zero exit code
4. `--require-coverage N` causes exit 1 if file coverage percent < N
5. `--json` switches all output to machine-readable JSON (no ANSI colors)
Expand Down Expand Up @@ -175,9 +175,9 @@ All functions in main.rs are private (no pub keyword). Key internal functions:

### Scenario: Init idempotency

- **Given** `specsync.json` already exists in the project root
- **Given** a config (v4 `.specsync/config.toml` or legacy `specsync.json`) already exists
- **When** `specsync init` is run
- **Then** prints "specsync.json already exists" and returns without modifying it
- **Then** prints an "already exists" message and returns without modifying it

### Scenario: Coverage threshold

Expand Down Expand Up @@ -347,6 +347,7 @@ Cold start times (first run after boot) may be 2-3x higher due to disk cache war

| Date | Change |
|------|--------|
| 2026-06-11 | v4: `--root` now errors (exit 2) for nonexistent paths; init scenario covers the v4 config layout |
| 2026-04-10 | Add Performance Requirements section with response time targets, cache requirements, resource limits, and scalability targets |
| 2026-03-25 | Initial spec |
| 2026-04-06 | Add compact, archive-tasks, view, merge, issues subcommands; add --force, --create-issues, --format flags; add hash_cache/github/archive/compact/view/merge dependencies |
Expand Down
8 changes: 5 additions & 3 deletions specs/cmd_check/cmd_check.spec.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
module: cmd_check
version: 2
version: 3
status: stable
files:
- src/commands/check.rs
Expand Down Expand Up @@ -35,7 +35,7 @@ Implements the `specsync check` command — the primary validation entry point.

## Invariants

1. When `--fix` is passed, auto-fix runs in two phases: (a) add undocumented exports to spec markdown tables with generated review prompts, (b) AI-regenerate specs whose requirements have drifted
1. When `--fix` is passed, auto-fix runs in two phases: (a) add undocumented exports to spec markdown tables with generated review prompts — type exports are routed to the "… Types" table and functions/values to the "… Functions"/"… Methods" table (falling back to the last export subsection), with rows padded to the target table's column count, (b) AI-regenerate specs whose requirements have drifted
2. Near-miss header correction (e.g., "Exported Functions" → "### Exported Functions") runs as part of auto-fix
3. Hash cache is consulted before validation unless `--force` is set — unchanged specs are skipped
4. After auto-fix, validation is re-run to verify fixes resolved the issues
Expand All @@ -56,7 +56,7 @@ Implements the `specsync check` command — the primary validation entry point.

- **Given** spec is missing export `pub fn new_function()`
- **When** `cmd_check` runs with `--fix`
- **Then** the export is appended to the spec's Public API table with a generated description prompt and the file is rewritten
- **Then** the export is appended to the matching Public API table (functions to the functions table, types to the types table) with a generated description prompt and the file is rewritten

### Scenario: JSON output format

Expand All @@ -70,6 +70,7 @@ Implements the `specsync check` command — the primary validation entry point.
|-----------|----------|
| AI provider not available during `--fix` regen | Prints error per spec, continues with remaining specs |
| Auto-fix changes a spec but validation still fails | Reports remaining errors, does not loop |
| Spec name filter matches nothing while specs exist | Prints "No specs matched" error (no contradictory "No spec files found" message) and exits 1 |
| Hash cache file is corrupted | Falls back to full validation (cache miss) |
| `--create-issues` with no GitHub repo | Prints error, skips issue creation |

Expand Down Expand Up @@ -99,5 +100,6 @@ Implements the `specsync check` command — the primary validation entry point.

| Date | Change |
|------|--------|
| 2026-06-11 | v3: `--fix` routes exports to the matching table by kind; unmatched spec filters exit 1 without contradictory output |
| 2026-06-07 | Document generated review prompts for `--fix` export rows |
| 2026-04-09 | Initial spec |
6 changes: 4 additions & 2 deletions specs/cmd_generate/cmd_generate.spec.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
module: cmd_generate
version: 1
version: 2
status: stable
files:
- src/commands/generate.rs
Expand Down Expand Up @@ -36,6 +36,7 @@ Implements the `specsync generate` command. Scaffolds spec files for unspecced m
2. With `--provider`, resolves AI provider and generates from source code
3. Re-runs validation after generation to verify new specs
4. Exits 1 if AI provider resolution fails
5. If any AI generation fails (template fallback used), the failures are re-printed prominently on stderr after the check report and the command exits 1; JSON output includes an `ai_errors` array

## Behavioral Examples

Expand All @@ -50,7 +51,7 @@ Implements the `specsync generate` command. Scaffolds spec files for unspecced m
| Condition | Behavior |
|-----------|----------|
| AI provider not found | Exits 1 |
| AI fails for one module | Error printed, continues |
| AI fails for one module | Error printed, continues, then exits 1 with the failures summarized last on stderr |
| All modules already specced | Prints "all covered" |

## Dependencies
Expand Down Expand Up @@ -78,3 +79,4 @@ Implements the `specsync generate` command. Scaffolds spec files for unspecced m
| Date | Change |
|------|--------|
| 2026-04-09 | Initial spec |
| 2026-06-11 | v2: Exit non-zero when AI generation fails, with the errors re-printed last on stderr and `ai_errors` in JSON output |
20 changes: 11 additions & 9 deletions specs/cmd_init/cmd_init.spec.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
module: cmd_init
version: 1
version: 2
status: stable
files:
- src/commands/init.rs
Expand All @@ -14,34 +14,35 @@ depends_on:

## Purpose

Implements the `specsync init` command. Creates a `specsync.json` configuration file with auto-detected source directories.
Implements the `specsync init` command. Creates the v4 `.specsync/` layout — `config.toml` with auto-detected source directories, a `version` stamp, `.gitignore`, and the `lifecycle/`, `changes/`, and `archive/` state directories — matching what `specsync migrate` produces.

## Public API

### Exported Functions

| Function | Parameters | Returns | Description |
|----------|-----------|---------|-------------|
| `cmd_init` | `root: &Path` | `()` | Create specsync.json with auto-detected source dirs |
| `cmd_init` | `root: &Path` | `()` | Create the v4 `.specsync/` layout with auto-detected source dirs |
| `ensure_hashes_gitignored` | `root: &Path` | `Result<bool, String>` | Add `.specsync/hashes.json` to the root `.gitignore` (idempotent); returns `Ok(true)` if the entry was added, `Ok(false)` if already present, `Err` if the write fails |

## Invariants

1. Auto-detects source directories via `config::detect_source_dirs()`
2. Will not overwrite existing `specsync.json`
3. Writes default config with detected dirs and standard required sections
2. Will not overwrite an existing config (v4 `.specsync/config.toml`/`config.json` or legacy `specsync.json`/`.specsync.toml`); legacy configs get a `specsync migrate` hint
3. Writes default config with detected dirs and standard required sections via `config::config_to_toml()`
4. A fresh init never triggers the legacy 3.x layout migration nag — `.specsync/version` is stamped with 4.0.0

## Behavioral Examples

### Scenario: First init

- **Given** no `specsync.json` exists
- **Given** no config exists
- **When** `cmd_init(root)` runs
- **Then** creates config with detected source dirs
- **Then** creates `.specsync/config.toml`, `.specsync/version`, `.specsync/.gitignore`, and the `lifecycle/`, `changes/`, `archive/` directories

### Scenario: Config exists

- **Given** `specsync.json` already exists
- **Given** `.specsync/config.toml` (or a legacy config) already exists
- **When** `cmd_init(root)` runs
- **Then** prints message and returns without changes

Expand All @@ -58,7 +59,7 @@ Implements the `specsync init` command. Creates a `specsync.json` configuration

| Module | What is used |
|--------|-------------|
| config | `detect_source_dirs` |
| config | `detect_source_dirs`, `config_to_toml` |

### Consumed By

Expand All @@ -71,3 +72,4 @@ Implements the `specsync init` command. Creates a `specsync.json` configuration
| Date | Change |
|------|--------|
| 2026-04-09 | Initial spec |
| 2026-06-11 | v2: Init the v4 `.specsync/` layout instead of the legacy `specsync.json` so a fresh project never sees the migration nag |
14 changes: 8 additions & 6 deletions specs/cmd_init_registry/cmd_init_registry.spec.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
module: cmd_init_registry
version: 1
version: 2
status: stable
files:
- src/commands/init_registry.rs
Expand All @@ -15,7 +15,7 @@ depends_on:

## Purpose

Implements the `specsync init-registry` command. Creates a `specsync-registry.toml` for cross-project spec references with auto-detected entries.
Implements the `specsync init-registry` command. Creates a registry file for cross-project spec references with auto-detected entries. The registry is written to the v4 location (`.specsync/registry.toml`); the legacy root-level `specsync-registry.toml` is only used for un-migrated 3.x layouts.

## Public API

Expand All @@ -27,17 +27,18 @@ Implements the `specsync init-registry` command. Creates a `specsync-registry.to

## Invariants

1. Delegates to `registry::generate_registry()`
2. Will not overwrite existing `specsync-registry.toml`
1. Delegates to `registry::generate_registry()` and resolves the target path via `registry::local_registry_path()`
2. Will not overwrite an existing registry file (v4 or legacy location)
3. `--name` overrides auto-detected project name
4. Never recreates the legacy root-level registry in a v4 project — doing so would re-trigger the legacy-layout migration nag

## Behavioral Examples

### Scenario: Generate registry

- **Given** 25 specs, no existing registry
- **When** `cmd_init_registry(root, None)` runs
- **Then** creates TOML with 25 entries
- **Then** creates TOML with 25 entries at `.specsync/registry.toml` (or `specsync-registry.toml` on a legacy 3.x layout)

## Error Cases

Expand All @@ -53,7 +54,7 @@ Implements the `specsync init-registry` command. Creates a `specsync-registry.to
| Module | What is used |
|--------|-------------|
| config | `load_config` |
| registry | `generate_registry` |
| registry | `generate_registry`, `local_registry_path` |

### Consumed By

Expand All @@ -66,3 +67,4 @@ Implements the `specsync init-registry` command. Creates a `specsync-registry.to
| Date | Change |
|------|--------|
| 2026-04-09 | Initial spec |
| 2026-06-11 | v2: Write to v4 `.specsync/registry.toml` (legacy root path only for un-migrated 3.x projects) |
7 changes: 5 additions & 2 deletions specs/commands/commands.spec.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
module: commands
version: 1
version: 2
status: stable
files:
- src/commands/mod.rs
Expand Down Expand Up @@ -74,6 +74,8 @@ Shared command infrastructure used by all CLI subcommands. Provides config loadi
1. `load_and_discover` excludes spec files starting with `_` (underscore prefix marks internal/template specs)
2. `filter_specs` matches against four forms: exact path, relative path, filename stem, and module name (stem minus `.spec` suffix)
3. `run_validation` applies ignore rules (global, inline, per-spec) to filter warnings before counting
4. In text mode, draft specs show explicit "Section validation skipped (status: draft)" and "Export validation skipped (status: draft)" notices instead of misleading green checkmarks, plus a closing hint to set `status: active`
5. Failing checks render negated labels (e.g. "✗ Frontmatter invalid"), never a ✗ next to a passing label
4. Exit code logic by enforcement mode: Warn → always 0; EnforceNew → 1 if unspecced files; Strict → 1 on errors, also 1 on warnings when `--strict`
5. `--require-coverage N` triggers exit 1 if file coverage percent < N regardless of enforcement mode
6. `create_drift_issues` groups errors by spec path and creates one GitHub issue per spec, not per error
Expand Down Expand Up @@ -103,7 +105,7 @@ Shared command infrastructure used by all CLI subcommands. Provides config loadi
| Condition | Behavior |
|-----------|----------|
| No spec files found and `allow_empty` is false | Prints suggestion to run `specsync generate` and exits 0 |
| Filter matches no specs | Prints warning listing unmatched filters, returns empty vec |
| Filter matches no specs | Prints warning listing unmatched filters, returns empty vec (cmd_check then exits 1) |
| `schema_dir` not configured | `build_schema_columns` returns empty map (no error) |
| GitHub repo unresolvable for drift issues | Prints error and returns without creating issues |
| `gh` CLI fails to create issue | Prints per-spec error but continues with remaining specs |
Expand Down Expand Up @@ -141,5 +143,6 @@ Shared command infrastructure used by all CLI subcommands. Provides config loadi

| Date | Change |
|------|--------|
| 2026-06-11 | v2: Draft specs report skipped section/export validation explicitly; failing frontmatter renders a negated label |
| 2026-04-09 | Initial spec |
| 2026-04-11 | Add lifecycle submodule and filter_by_status function |
Loading
Loading