Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
17a3171
feat: lifecycle phase 1 — review/archived statuses, per-status valida…
corvid-agent Apr 11, 2026
f1ae90f
feat: lifecycle phase 2 — promote/demote/set/status commands
corvid-agent Apr 11, 2026
3208185
docs: comprehensive documentation update — all commands, flags, and a…
corvid-agent Apr 11, 2026
c5a70a2
feat: lifecycle phase 3 — transition guards, history tracking, guard/…
corvid-agent Apr 11, 2026
ef8348c
fix: resolve CI failures — fmt, clippy, and spec-check
corvid-agent Apr 11, 2026
9bf46ad
fix: update specs for rebased lifecycle + guard exports
corvid-agent Apr 11, 2026
663e457
fix: fmt formatting and missing companion requirements.md
corvid-agent Apr 11, 2026
f42bd93
feat: lifecycle phase 4 — auto-promote, CI enforcement, stale-status …
corvid-agent Apr 11, 2026
9af84c1
feat: add `specsync migrate` command for 3.x → 4.0.0 upgrade path
corvid-agent Apr 11, 2026
518d58a
feat: TOML config format, 3.x auto-detection, cross-project migration
corvid-agent Apr 11, 2026
a7509cd
fix: document new exports in specs to fix strict spec-check
corvid-agent Apr 11, 2026
b02cfdc
fix: address Copilot review — 8 correctness fixes across lifecycle/va…
corvid-agent Apr 11, 2026
72cd515
fix: address Copilot review — 11 fixes across migrate/lifecycle/confi…
corvid-agent Apr 11, 2026
019deaa
fix: resolve CI failures (fmt, clippy, spec-check)
corvid-agent Apr 11, 2026
97f920b
fix: remaining cargo fmt issue in config.rs
corvid-agent Apr 11, 2026
3f1daff
fix: address 23 Copilot review comments on PR #199
corvid-agent Apr 11, 2026
7eacac3
fix: remove duplicate load_config_from_path in config spec
corvid-agent Apr 11, 2026
5563b5c
fix: address final 3 Copilot review comments on PR #199
corvid-agent Apr 11, 2026
14ad60d
fix: address Rook's 4 security review findings on PR #199
corvid-agent Apr 11, 2026
26aaee9
fix: rustfmt — inline eprintln in load_lifecycle_json
corvid-agent Apr 11, 2026
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
154 changes: 152 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,15 @@ specsync compact --keep 10 # Compact old changelog entries in sp
specsync archive-tasks # Archive completed tasks from tasks.md
specsync view --role dev # View specs filtered by role
specsync merge # Auto-resolve merge conflicts in specs
specsync new auth # Quick-create a minimal spec (auto-detects sources)
specsync stale # Find specs that haven't kept up with code changes
specsync rules # Show configured validation rules
specsync lifecycle status # Show lifecycle status of all specs
specsync lifecycle promote auth # Advance auth spec to next status
specsync lifecycle history auth # View transition history for a spec
specsync lifecycle guard auth # Dry-run: check if guards would pass
specsync lifecycle auto-promote # Promote all specs that pass guards
specsync lifecycle enforce --all # CI: validate lifecycle rules
specsync hooks install # Install agent instructions + git hooks
specsync hooks status # Check what's installed
specsync mcp # Start MCP server for AI agent integration
Expand Down Expand Up @@ -295,6 +304,18 @@ specsync [command] [flags]
| `archive-tasks` | Archive completed tasks from companion `tasks.md` files |
| `view` | View specs filtered by role (`--role dev\|qa\|product\|agent`) |
| `merge` | Auto-resolve git merge conflicts in spec files |
| `new <name>` | Quick-create a minimal spec with auto-detected source files. `--full` includes companion files |
| `stale` | Identify specs that haven't been updated since their source files changed |
| `rules` | Display configured validation rules and built-in rule status |
| `migrate` | Upgrade from 3.x to 4.0.0 layout (`.specsync/` directory, TOML config). `--dry-run` to preview, `--no-backup` to skip |
| `lifecycle promote <spec>` | Advance spec to next status (draft→review→active→stable) |
| `lifecycle demote <spec>` | Step back one status level |
| `lifecycle set <spec> <status>` | Set spec to any status (with transition validation) |
| `lifecycle status [spec]` | Show lifecycle status of one or all specs |
| `lifecycle history <spec>` | Show transition history (audit log) for a spec |
| `lifecycle guard <spec> [target]` | Dry-run guard evaluation — check if transition would pass |
| `lifecycle auto-promote` | Promote all specs that pass their transition guards. `--dry-run` to preview |
| `lifecycle enforce` | CI enforcement — validate lifecycle rules, exit non-zero on violations. `--all` for all checks |
| `issues` | Verify GitHub issue references in spec frontmatter. `--create` to create missing issues |
| `hooks` | Install/uninstall agent instructions and git hooks (`install`, `uninstall`, `status`) |
| `mcp` | Start MCP server for AI agent integration (Claude Code, Cursor, etc.) |
Expand All @@ -313,6 +334,12 @@ specsync [command] [flags]
| `--force` | Skip hash cache and re-validate all specs |
| `--create-issues` | Create GitHub issues for specs with validation errors (on `check`) |
| `--dry-run` | Preview changes without writing files (on `compact`, `archive-tasks`, `merge`) |
| `--stale N` | Flag specs N+ commits behind their source files (on `check`) |
| `--exclude-status <s>` | Exclude specs with the given status. Repeatable |
| `--only-status <s>` | Only process specs with the given status. Repeatable |
| `--mermaid` | Output dependency graph as Mermaid diagram (on `deps`) |
| `--dot` | Output dependency graph as Graphviz DOT (on `deps`) |
| `--full` | Include companion files (on `new`) |
| `--json` | Structured JSON output |

### Exit Codes
Expand Down Expand Up @@ -499,8 +526,12 @@ Available on the [GitHub Marketplace](https://github.com/marketplace/actions/spe
| `require-coverage` | `0` | Minimum file coverage % |
| `root` | `.` | Project root directory |
| `args` | `''` | Extra CLI arguments |
| `comment` | `false` | Post spec drift results as a PR comment (requires `pull_request` event) |
| `token` | `${{ github.token }}` | GitHub token for posting PR comments (needs write permissions) |

### Workflow example
### Workflow examples

**Basic CI check:**

```yaml
name: Spec Check
Expand All @@ -517,6 +548,29 @@ jobs:
require-coverage: '100'
```

**With PR comments:**

```yaml
name: Spec Check
on:
pull_request:
types: [opened, synchronize]

jobs:
specsync:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: CorvidLabs/spec-sync@v3
with:
strict: 'true'
comment: 'true'
```

When `comment: 'true'` is set, SpecSync posts (or updates) a PR comment showing spec drift — added/removed exports since the base branch. The comment is automatically updated on subsequent pushes.

---

## Configuration
Expand Down Expand Up @@ -562,7 +616,99 @@ ai_provider = "claude"
ai_timeout = 120
```

Config resolution order: `specsync.json` → `.specsync.toml` → defaults with auto-detected source dirs.
Config resolution order: `.specsync/config.toml` → `.specsync/config.json` → `.specsync.toml` → `specsync.json` → defaults with auto-detected source dirs.

### Lifecycle Guards

Configure transition guards in `specsync.json` to enforce quality gates before specs can be promoted:

```json
{
"lifecycle": {
"trackHistory": true,
"guards": {
"review→active": {
"minScore": 70,
"requireSections": ["Public API", "Invariants"]
},
"active→stable": {
"minScore": 85,
"noStale": true,
"requireSections": ["Public API", "Behavioral Examples", "Error Cases"]
},
"*→stable": {
"minScore": 85,
"message": "Stable specs require high quality scores"
}
}
}
}
```

| Guard Option | Type | Description |
|-------------|------|-------------|
| `minScore` | `number?` | Minimum spec quality score (0-100) required |
| `requireSections` | `string[]` | Sections that must exist with non-empty content |
| `noStale` | `bool?` | Spec must not be stale (source files ahead of spec) |
| `staleThreshold` | `number?` | Max commits behind when `noStale` is true (default: 5) |
| `message` | `string?` | Custom message shown when guard blocks transition |

Guard keys use `"from→to"` format (e.g., `"review→active"`) or `"*→to"` for wildcard. ASCII arrows (`->`) also work.

When `trackHistory` is enabled (default: `true`), every status transition is recorded in the spec's frontmatter:

```yaml
lifecycle_log:
- "2026-04-11: draft → review"
- "2026-04-12: review → active"
```

Use `specsync lifecycle guard <spec>` to dry-run guard evaluation without making changes.

### Auto-Promote & CI Enforcement

**Auto-promote** scans all specs and promotes any whose next transition passes all configured guards:

```bash
specsync lifecycle auto-promote # promote eligible specs
specsync lifecycle auto-promote --dry-run # preview without modifying
```

**Enforce** validates lifecycle rules for CI pipelines (exits non-zero on violations):

```bash
specsync lifecycle enforce --all # run all checks
specsync lifecycle enforce --require-status # every spec needs a status field
specsync lifecycle enforce --max-age # flag stale statuses
specsync lifecycle enforce --allowed # check allowed statuses
```

Configure enforcement rules in `specsync.json`:

```json
{
"lifecycle": {
"maxAge": {
"draft": 30,
"review": 14
},
"allowedStatuses": ["draft", "review", "active", "stable"]
}
}
```

| Config Key | Type | Description |
|-----------|------|-------------|
| `maxAge` | `object` | Maximum days a spec may stay in each status (e.g., `"draft": 30`) |
| `allowedStatuses` | `string[]` | Restrict specs to these statuses only |

**GitHub Action** — add `lifecycle-enforce: 'true'` to the spec-sync action to enforce lifecycle rules in CI:

```yaml
- uses: CorvidLabs/spec-sync@v3
with:
lifecycle-enforce: 'true'
```

---

Expand Down Expand Up @@ -673,13 +819,17 @@ src/
├── hash_cache.rs Incremental validation via content hashing
├── hooks.rs Agent instruction + git hook management
├── importer.rs External importers (GitHub Issues, Jira, Confluence)
├── lifecycle.rs Spec status transitions (promote, demote, set, status, history, guard, auto-promote, enforce)
├── manifest.rs Package manifest parsing (Cargo.toml, package.json, etc.)
├── mcp.rs MCP server for AI agent integration (JSON-RPC stdio)
├── merge.rs Auto-resolve merge conflicts in spec files
├── new.rs Quick-create minimal spec with source auto-detection
├── parser.rs Frontmatter + spec body parsing
├── rules.rs Display configured validation rules
├── registry.rs Registry loading, generation, and remote fetching
├── schema.rs SQL schema parsing for column validation
├── scoring.rs Spec quality scoring (0–100, weighted rubric)
├── stale.rs Staleness detection (spec vs source modification)
├── types.rs Data types + config schema
├── validator.rs Validation + coverage + cross-project ref detection
├── view.rs Role-filtered spec viewing (dev, qa, product, agent)
Expand Down
12 changes: 12 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ inputs:
description: 'Additional arguments to pass to specsync check'
required: false
default: ''
lifecycle-enforce:
description: 'Run lifecycle enforcement checks (--all). Fails CI if specs violate lifecycle rules.'
required: false
default: 'false'
comment:
description: 'Post spec drift results as a PR comment (requires pull_request event and write permissions)'
required: false
Expand Down Expand Up @@ -130,6 +134,7 @@ runs:
INPUT_REQUIRE_COVERAGE: ${{ inputs.require-coverage }}
INPUT_ARGS: ${{ inputs.args }}
INPUT_COMMENT: ${{ inputs.comment }}
INPUT_LIFECYCLE_ENFORCE: ${{ inputs.lifecycle-enforce }}
run: |
set -euo pipefail

Expand All @@ -153,6 +158,13 @@ runs:
eval "$CMD" || EXIT_CODE=$?
echo "::endgroup::"

# If lifecycle enforcement is enabled, run it (may override exit code)
if [ "$INPUT_LIFECYCLE_ENFORCE" = "true" ]; then
echo "::group::SpecSync Lifecycle Enforce"
specsync lifecycle enforce --all || EXIT_CODE=$?
echo "::endgroup::"
fi

# If comment mode is enabled, also run diff with markdown output
if [ "$INPUT_COMMENT" = "true" ]; then
DIFF_OUTPUT=$(specsync diff --format markdown 2>&1) || true
Expand Down
Loading
Loading