From ed62dc76fbb7836c7bb5325ec12c00871a1a85b9 Mon Sep 17 00:00:00 2001 From: Tristen Pierson Date: Thu, 2 Apr 2026 16:30:09 -0400 Subject: [PATCH 1/7] =?UTF-8?q?fix:=20PyPI=20badge=20cache=20=E2=80=94=20a?= =?UTF-8?q?dd=20cacheSeconds=3D60=20for=20faster=20refresh=20[skip=20ci]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Oz --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d917e20..11dccbb 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ [![CI](https://github.com/BitConcepts/specsmith/actions/workflows/ci.yml/badge.svg)](https://github.com/BitConcepts/specsmith/actions/workflows/ci.yml) [![Docs](https://readthedocs.org/projects/specsmith/badge/?version=stable)](https://specsmith.readthedocs.io/en/stable/) -[![PyPI Stable](https://img.shields.io/pypi/v/specsmith?label=stable&style=flat&color=blue)](https://pypi.org/project/specsmith/) -[![PyPI Dev](https://img.shields.io/pypi/v/specsmith?include_prereleases&label=dev&style=flat&color=orange)](https://pypi.org/project/specsmith/#history) +[![PyPI Stable](https://img.shields.io/pypi/v/specsmith?label=stable&style=flat&color=blue&cacheSeconds=60)](https://pypi.org/project/specsmith/) +[![PyPI Dev](https://img.shields.io/pypi/v/specsmith?include_prereleases&label=dev&style=flat&color=orange&cacheSeconds=60)](https://pypi.org/project/specsmith/#history) [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/) [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) From a9c8251138639c44311b7ed33ce4132bf92ae539 Mon Sep 17 00:00:00 2001 From: Tristen Pierson Date: Thu, 2 Apr 2026 16:35:55 -0400 Subject: [PATCH 2/7] chore: trigger dev build to test RTD develop version Co-Authored-By: Oz From 104db76dd255e32358d184016b6dd36a96c4850f Mon Sep 17 00:00:00 2001 From: Tristen Pierson Date: Thu, 2 Apr 2026 16:59:58 -0400 Subject: [PATCH 3/7] =?UTF-8?q?fix:=20auto-update=20AGENTS.md=20references?= =?UTF-8?q?=20when=20migrating=20lowercase=E2=86=92uppercase=20filenames?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When upgrade renames governance files (rules.md→RULES.md etc.), it now also rewrites path references in AGENTS.md, CLAUDE.md, GEMINI.md, SKILL.md, .cursor/rules/, .windsurfrules, and .aider.conf.yml. Eliminates the need for manual reference fixup after upgrade. Co-Authored-By: Oz --- src/specsmith/upgrader.py | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/specsmith/upgrader.py b/src/specsmith/upgrader.py index a8225d1..973cdcd 100644 --- a/src/specsmith/upgrader.py +++ b/src/specsmith/upgrader.py @@ -255,9 +255,13 @@ def _migrate_legacy_filenames(root: Path, result: UpgradeResult) -> None: Handles both case-sensitive (Linux) and case-insensitive (Windows/macOS) filesystems. On case-insensitive FS, uses a two-step rename via a temporary name to avoid conflicts. + + Also updates references in AGENTS.md so the hub links stay valid. """ import shutil + renamed: list[tuple[str, str]] = [] + for old_rel, new_rel in _LEGACY_RENAMES: old_path = root / old_rel new_path = root / new_rel @@ -276,4 +280,41 @@ def _migrate_legacy_filenames(root: Path, result: UpgradeResult) -> None: shutil.move(str(old_path), str(new_path)) else: continue # Both exist as truly separate files — skip + renamed.append((old_rel, new_rel)) result.updated_files.append(f"{old_rel} → {new_rel}") + + # Update references in user-owned docs that point to renamed files + if renamed: + _update_references(root, renamed, result) + + +def _update_references( + root: Path, + renames: list[tuple[str, str]], + result: UpgradeResult, +) -> None: + """Rewrite old paths to new paths in AGENTS.md and other hub files. + + Only performs safe string replacement of exact path references. + """ + docs_to_patch = [ + "AGENTS.md", + "CLAUDE.md", + "GEMINI.md", + ".warp/skills/SKILL.md", + ".cursor/rules/governance.mdc", + ".windsurfrules", + ".aider.conf.yml", + ] + + for doc_name in docs_to_patch: + doc_path = root / doc_name + if not doc_path.exists(): + continue + content = doc_path.read_text(encoding="utf-8") + original = content + for old_rel, new_rel in renames: + content = content.replace(old_rel, new_rel) + if content != original: + doc_path.write_text(content, encoding="utf-8") + result.updated_files.append(f"{doc_name} (references updated)") From ef76880330f61becde9456914ea926efb3ba62f3 Mon Sep 17 00:00:00 2001 From: Tristen Pierson Date: Thu, 2 Apr 2026 17:25:24 -0400 Subject: [PATCH 4/7] feat: upgrade --full now creates missing LEDGER.md, ARCHITECTURE.md, credit tracking Full sync creates all essential files so audit passes after a single upgrade --full command. No more needing audit --fix as a second step. Co-Authored-By: Oz --- src/specsmith/upgrader.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/specsmith/upgrader.py b/src/specsmith/upgrader.py index 973cdcd..2c57a95 100644 --- a/src/specsmith/upgrader.py +++ b/src/specsmith/upgrader.py @@ -246,6 +246,35 @@ def _sync_full( out.write_text(tmpl.render(**ctx), encoding="utf-8") synced.append(f"{output_rel} (created)") + # 6. Essential docs — create stubs if missing (these are needed for audit to pass) + if not (root / "LEDGER.md").exists(): + (root / "LEDGER.md").write_text("# Ledger\n\nNo entries yet.\n", encoding="utf-8") + synced.append("LEDGER.md (created)") + + if not (root / "docs" / "ARCHITECTURE.md").exists(): + try: + from specsmith.architect import generate_architecture + + generate_architecture(root) + synced.append("docs/ARCHITECTURE.md (generated from scan)") + except Exception: # noqa: BLE001 + arch_path = root / "docs" / "ARCHITECTURE.md" + arch_path.parent.mkdir(parents=True, exist_ok=True) + arch_path.write_text( + f"# Architecture \u2014 {config.name}\n\n[Run `specsmith architect` to populate]\n", + encoding="utf-8", + ) + synced.append("docs/ARCHITECTURE.md (stub created)") + + # Initialize credit tracking if not present + specsmith_dir = root / ".specsmith" + credit_budget = specsmith_dir / "credit-budget.json" + if not credit_budget.exists(): + from specsmith.credits import CreditBudget, save_budget + + save_budget(root, CreditBudget()) + synced.append(".specsmith/credit-budget.json (created)") + return synced From b6a43fe3f00154fe2ee81786fd81c71e29075e19 Mon Sep 17 00:00:00 2001 From: Tristen Pierson Date: Thu, 2 Apr 2026 17:30:29 -0400 Subject: [PATCH 5/7] fix: check alternate paths before creating LEDGER.md/ARCHITECTURE.md stubs Auditor and upgrader now check docs/LEDGER.md and docs/architecture/** before reporting missing or creating stubs. Prevents duplicate files when projects store these at non-standard paths. Co-Authored-By: Oz --- src/specsmith/auditor.py | 28 ++++++++++++++++++---------- src/specsmith/upgrader.py | 16 +++++++++++----- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/specsmith/auditor.py b/src/specsmith/auditor.py index 2791356..9f4466c 100644 --- a/src/specsmith/auditor.py +++ b/src/specsmith/auditor.py @@ -79,11 +79,15 @@ def check_governance_files(root: Path) -> list[AuditResult]: for f in REQUIRED_FILES: path = root / f + found = path.exists() + # LEDGER.md: also check docs/LEDGER.md (some imported projects place it there) + if not found and f == "LEDGER.md": + found = (root / "docs" / "LEDGER.md").exists() results.append( AuditResult( name=f"file-exists:{f}", - passed=path.exists(), - message=f"Required file {f} {'exists' if path.exists() else 'MISSING'}", + passed=found, + message=f"Required file {f} {'exists' if found else 'MISSING'}", ) ) @@ -232,16 +236,20 @@ def check_ledger_health(root: Path) -> list[AuditResult]: """Check ledger quality and staleness.""" results: list[AuditResult] = [] ledger_path = root / "LEDGER.md" - if not ledger_path.exists(): - results.append( - AuditResult( - name="ledger-exists", - passed=False, - message="LEDGER.md not found", + # Also check docs/LEDGER.md (some imported projects place it there) + alt = root / "docs" / "LEDGER.md" + if alt.exists(): + ledger_path = alt + else: + results.append( + AuditResult( + name="ledger-exists", + passed=False, + message="LEDGER.md not found", + ) ) - ) - return results + return results text = ledger_path.read_text(encoding="utf-8") lines = text.splitlines() diff --git a/src/specsmith/upgrader.py b/src/specsmith/upgrader.py index 2c57a95..2e395ca 100644 --- a/src/specsmith/upgrader.py +++ b/src/specsmith/upgrader.py @@ -246,12 +246,20 @@ def _sync_full( out.write_text(tmpl.render(**ctx), encoding="utf-8") synced.append(f"{output_rel} (created)") - # 6. Essential docs — create stubs if missing (these are needed for audit to pass) - if not (root / "LEDGER.md").exists(): + # 6. Essential docs — create stubs only if truly missing (check alternate paths) + has_ledger = (root / "LEDGER.md").exists() or (root / "docs" / "LEDGER.md").exists() + if not has_ledger: (root / "LEDGER.md").write_text("# Ledger\n\nNo entries yet.\n", encoding="utf-8") synced.append("LEDGER.md (created)") - if not (root / "docs" / "ARCHITECTURE.md").exists(): + has_arch = (root / "docs" / "ARCHITECTURE.md").exists() + if not has_arch and (root / "docs").is_dir(): + # Check subdirectories (e.g. docs/architecture/CPSC-RE-ARCHITECTURE.md) + has_arch = bool( + list((root / "docs").glob("**/architecture*")) + + list((root / "docs").glob("**/ARCHITECTURE*")) + ) + if not has_arch: try: from specsmith.architect import generate_architecture @@ -265,8 +273,6 @@ def _sync_full( encoding="utf-8", ) synced.append("docs/ARCHITECTURE.md (stub created)") - - # Initialize credit tracking if not present specsmith_dir = root / ".specsmith" credit_budget = specsmith_dir / "credit-budget.json" if not credit_budget.exists(): From 013ecb66628e63d6b8e485116a0e1661c6c96857 Mon Sep 17 00:00:00 2001 From: Tristen Pierson Date: Thu, 2 Apr 2026 17:39:38 -0400 Subject: [PATCH 6/7] fix: case-insensitive architecture check in auditor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 'architecture' in f failed for 'docs/ARCHITECTURE.md' — now uses f.lower() Co-Authored-By: Oz --- src/specsmith/auditor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/specsmith/auditor.py b/src/specsmith/auditor.py index 9f4466c..794dbb6 100644 --- a/src/specsmith/auditor.py +++ b/src/specsmith/auditor.py @@ -127,8 +127,8 @@ def check_governance_files(root: Path) -> list[AuditResult]: for f in RECOMMENDED_FILES: path = root / f found = path.exists() - # For architecture.md, also search subdirectories (e.g. docs/architecture/*.md) - if not found and "architecture" in f: + # For ARCHITECTURE.md, also search subdirectories (e.g. docs/architecture/*.md) + if not found and "architecture" in f.lower(): found = ( bool( list((root / "docs").glob("**/architecture*")) From 6817dacbdb8bddc9f793a650fc92b814231cc383 Mon Sep 17 00:00:00 2001 From: Tristen Pierson Date: Thu, 2 Apr 2026 17:48:24 -0400 Subject: [PATCH 7/7] =?UTF-8?q?release:=20v0.2.2=20=E2=80=94=20upgrade=20r?= =?UTF-8?q?eference=20auto-fix,=20alternate=20path=20detection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Oz --- CHANGELOG.md | 11 ++++++++++- pyproject.toml | 2 +- src/specsmith/__init__.py | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60b8154..8f3289f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.2.2] - 2026-04-02 + +### Fixed +- **Upgrade auto-fixes AGENTS.md references**: when `upgrade` renames governance files (lowercase→uppercase), it now rewrites path references in AGENTS.md, CLAUDE.md, GEMINI.md, SKILL.md, and all agent config files automatically. +- **Alternate path detection**: auditor and upgrader now find LEDGER.md at `docs/LEDGER.md` and architecture docs in subdirectories (e.g. `docs/architecture/`). No more false "missing" reports or duplicate stub creation. +- **Case-insensitive architecture check**: `docs/ARCHITECTURE.md` recommended check now works regardless of filename casing. +- **CI-gated dev releases**: dev-release workflow now runs full test suite (ruff check+format, mypy, pytest) before PyPI publish. + ## [0.2.1] - 2026-04-02 ### Added @@ -186,7 +194,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **G9**: Session start file list now marks services.md as conditional ("if it exists"). - **G10**: Open TODOs format specified as `- [ ]` / `- [x]` checkbox syntax. -[Unreleased]: https://github.com/BitConcepts/specsmith/compare/v0.2.1...HEAD +[Unreleased]: https://github.com/BitConcepts/specsmith/compare/v0.2.2...HEAD +[0.2.2]: https://github.com/BitConcepts/specsmith/compare/v0.2.1...v0.2.2 [0.2.1]: https://github.com/BitConcepts/specsmith/compare/v0.2.0...v0.2.1 [0.2.0]: https://github.com/BitConcepts/specsmith/compare/v0.1.3...v0.2.0 [0.1.3]: https://github.com/BitConcepts/specsmith/compare/v0.1.2...v0.1.3 diff --git a/pyproject.toml b/pyproject.toml index 395185f..141364e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "specsmith" -version = "0.2.1" +version = "0.2.2" description = "Forge governed project scaffolds from the Agentic AI Development Workflow Specification." readme = "README.md" license = {text = "MIT"} diff --git a/src/specsmith/__init__.py b/src/specsmith/__init__.py index 71804e1..f8ef7b2 100644 --- a/src/specsmith/__init__.py +++ b/src/specsmith/__init__.py @@ -8,4 +8,4 @@ try: __version__: str = _pkg_version("specsmith") except PackageNotFoundError: # running from source without install - __version__ = "0.2.1" + __version__ = "0.2.2"