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
15 changes: 9 additions & 6 deletions src/specsmith/auditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,14 @@ def check_governance_files(root: Path) -> list[AuditResult]:
found = path.exists()
# For architecture.md, also search subdirectories (e.g. docs/architecture/*.md)
if not found and "architecture" in f:
found = bool(
list((root / "docs").glob("**/architecture*"))
+ list((root / "docs").glob("**/ARCHITECTURE*"))
) if (root / "docs").is_dir() else False
found = (
bool(
list((root / "docs").glob("**/architecture*"))
+ list((root / "docs").glob("**/ARCHITECTURE*"))
)
if (root / "docs").is_dir()
else False
)
results.append(
AuditResult(
name=f"recommended:{f}",
Expand Down Expand Up @@ -580,8 +584,7 @@ def run_auto_fix(root: Path, report: AuditReport) -> list[str]:
path = root / "docs" / "ARCHITECTURE.md"
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(
f"# Architecture — {root.name}\n\n"
"[Run `specsmith architect` to populate]\n",
f"# Architecture — {root.name}\n\n[Run `specsmith architect` to populate]\n",
encoding="utf-8",
)
fixed.append("Created stub docs/ARCHITECTURE.md")
Expand Down
9 changes: 5 additions & 4 deletions src/specsmith/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,9 +703,7 @@ def architect(project_dir: str, non_interactive: bool) -> None:
f" [yellow]Note:[/yellow] Existing docs at {', '.join(existing)} "
"are referenced but not merged. Review manually."
)
console.print(
" [dim]Run \"specsmith audit --project-dir .\" to verify governance health.[/dim]"
)
console.print(' [dim]Run "specsmith audit --project-dir ." to verify governance health.[/dim]')


# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -1463,7 +1461,10 @@ def credits_analyze(project_dir: str) -> None:
"--watermarks", default=None, help="Comma-separated USD watermark alerts (e.g. 5,10,25,50)."
)
def credits_budget(
project_dir: str, cap: float | None, alert_pct: int | None, watermarks: str | None,
project_dir: str,
cap: float | None,
alert_pct: int | None,
watermarks: str | None,
) -> None:
"""View or set credit budget and alert thresholds."""
from specsmith.credits import load_budget, save_budget
Expand Down
6 changes: 2 additions & 4 deletions src/specsmith/credit_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ def analyze_spend(root: Path) -> AnalysisReport:
severity="info",
message="No credit data yet.",
recommendation=(
"Record usage with `specsmith credits record` "
"or integrate with your AI agent."
"Record usage with `specsmith credits record` or integrate with your AI agent."
),
)
)
Expand Down Expand Up @@ -112,8 +111,7 @@ def analyze_spend(root: Path) -> AnalysisReport:
category="governance",
severity="info",
message=(
f"Governance files total {total_gov_lines} lines "
f"across {len(gov_files)} files."
f"Governance files total {total_gov_lines} lines across {len(gov_files)} files."
),
recommendation=(
"Ensure agents lazy-load governance files. Only rules.md + workflow.md "
Expand Down
14 changes: 8 additions & 6 deletions src/specsmith/credits.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,9 @@ def record_usage(
provider=provider,
tokens_in=tokens_in,
tokens_out=tokens_out,
estimated_cost_usd=cost_usd if cost_usd is not None else estimate_cost(
model, tokens_in, tokens_out
),
estimated_cost_usd=cost_usd
if cost_usd is not None
else estimate_cost(model, tokens_in, tokens_out),
task=task,
duration_seconds=duration_seconds,
)
Expand All @@ -180,7 +180,10 @@ def record_usage(


def get_summary(
root: Path, *, since: str = "", month: str = "",
root: Path,
*,
since: str = "",
month: str = "",
) -> CreditSummary:
"""Get aggregate credit summary with budget alerts."""
entries = _load_entries(root)
Expand Down Expand Up @@ -222,8 +225,7 @@ def get_summary(
pct = (month_cost / budget.monthly_cap_usd) * 100 if budget.monthly_cap_usd else 0
if pct >= 100:
summary.alerts.append(
f"BUDGET EXCEEDED: ${month_cost:.2f} / ${budget.monthly_cap_usd:.2f} "
f"({pct:.0f}%)"
f"BUDGET EXCEEDED: ${month_cost:.2f} / ${budget.monthly_cap_usd:.2f} ({pct:.0f}%)"
)
elif pct >= budget.alert_threshold_pct:
summary.alerts.append(
Expand Down
30 changes: 23 additions & 7 deletions src/specsmith/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -790,15 +790,31 @@ def _extract_governance_sections(root: Path) -> dict[str, str]:
# Body-level content keywords for secondary classification.
# Used when heading doesn't match — scan body text for strong signals.
_BODY_ARCHITECTURE_KW = [
"register map", "address offset", "0x0", "register name",
"block diagram", "data flow", "interface spec",
"directory layout", "src/", "repository structure",
"milestone", "roadmap", "completion", "phase 2 target",
"register map",
"address offset",
"0x0",
"register name",
"block diagram",
"data flow",
"interface spec",
"directory layout",
"src/",
"repository structure",
"milestone",
"roadmap",
"completion",
"phase 2 target",
]
_BODY_DRIFT_KW = [
"subst v:", "path-length", "one-time setup", "per-machine",
"environment variable", "install once", "bootstrap",
"windows path", "ntfs",
"subst v:",
"path-length",
"one-time setup",
"per-machine",
"environment variable",
"install once",
"bootstrap",
"windows path",
"ntfs",
]

for heading, body in sections.items():
Expand Down
4 changes: 1 addition & 3 deletions src/specsmith/scaffolder.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,9 +406,7 @@ def _build_community_files(config: ProjectConfig) -> list[tuple[str, str]]:
files.append(("community/code_of_conduct.md.j2", "CODE_OF_CONDUCT.md"))

if "pr-template" in cf and config.vcs_platform == "github":
files.append(
("community/pull_request_template.md.j2", ".github/PULL_REQUEST_TEMPLATE.md")
)
files.append(("community/pull_request_template.md.j2", ".github/PULL_REQUEST_TEMPLATE.md"))

if "issue-templates" in cf and config.vcs_platform == "github":
files.extend(
Expand Down
3 changes: 1 addition & 2 deletions src/specsmith/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,7 @@ def run_session_end(root: Path) -> SessionReport:
name="credits",
status="ok",
message=(
f"Credits: ${cs.total_cost_usd:.4f} total, "
f"{cs.session_count} session(s)"
f"Credits: ${cs.total_cost_usd:.4f} total, {cs.session_count} session(s)"
),
)
)
Expand Down
7 changes: 5 additions & 2 deletions src/specsmith/updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ def get_update_channel() -> str:


def check_latest_version(
*, channel: str = "",
*,
channel: str = "",
) -> tuple[str, str, str]:
"""Check PyPI for the latest specsmith version.

Expand Down Expand Up @@ -60,7 +61,9 @@ def is_outdated() -> bool:


def run_self_update(
*, channel: str = "", target_version: str = "",
*,
channel: str = "",
target_version: str = "",
) -> tuple[bool, str]:
"""Update specsmith via pip.

Expand Down
9 changes: 3 additions & 6 deletions tests/sandbox/test_sandbox_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import yaml
from click.testing import CliRunner

from specsmith.cli import main


Expand Down Expand Up @@ -337,14 +338,10 @@ def test_import_force_overwrites_existing_docs(self, tmp_path: Path) -> None:

docs = root / "docs"
docs.mkdir(exist_ok=True)
(docs / "REQUIREMENTS.md").write_text(
"# Existing Requirements\n", encoding="utf-8"
)
(docs / "REQUIREMENTS.md").write_text("# Existing Requirements\n", encoding="utf-8")

runner = CliRunner()
result = runner.invoke(
main, ["import", "--project-dir", str(root), "--force"], input="y\n"
)
result = runner.invoke(main, ["import", "--project-dir", str(root), "--force"], input="y\n")
assert result.exit_code == 0

reqs = (docs / "REQUIREMENTS.md").read_text(encoding="utf-8")
Expand Down
1 change: 1 addition & 0 deletions tests/sandbox/test_sandbox_new.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import yaml
from click.testing import CliRunner

from specsmith.cli import main


Expand Down
1 change: 1 addition & 0 deletions tests/sandbox/test_sandbox_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import yaml
from click.testing import CliRunner

from specsmith.cli import main


Expand Down
1 change: 1 addition & 0 deletions tests/test_auditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from pathlib import Path

import pytest

from specsmith.auditor import run_audit


Expand Down
1 change: 1 addition & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import yaml
from click.testing import CliRunner

from specsmith.cli import main
from specsmith.config import ProjectConfig, ProjectType
from specsmith.scaffolder import scaffold_project
Expand Down
1 change: 1 addition & 0 deletions tests/test_integrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from pathlib import Path

import pytest

from specsmith.config import ProjectConfig, ProjectType
from specsmith.integrations import get_adapter, list_adapters
from specsmith.integrations.claude_code import ClaudeCodeAdapter
Expand Down
1 change: 1 addition & 0 deletions tests/test_scaffolder.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from pathlib import Path

import pytest

from specsmith.config import Platform, ProjectConfig, ProjectType
from specsmith.scaffolder import scaffold_project

Expand Down
3 changes: 1 addition & 2 deletions tests/test_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@

from importlib.metadata import version as _pkg_version

from specsmith.config import Platform, ProjectConfig, ProjectType

from specsmith import __version__
from specsmith.config import Platform, ProjectConfig, ProjectType


def test_version():
Expand Down
1 change: 1 addition & 0 deletions tests/test_vcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from pathlib import Path

import pytest

from specsmith.config import Platform, ProjectConfig, ProjectType
from specsmith.vcs import get_platform, list_platforms
from specsmith.vcs.base import CommandResult
Expand Down
Loading