specsmith 0.1.4.dev30__tar.gz → 0.1.4.dev33__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. {specsmith-0.1.4.dev30/src/specsmith.egg-info → specsmith-0.1.4.dev33}/PKG-INFO +1 -1
  2. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/pyproject.toml +1 -1
  3. specsmith-0.1.4.dev33/src/specsmith/architect.py +154 -0
  4. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/auditor.py +48 -2
  5. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/cli.py +62 -0
  6. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/importer.py +46 -52
  7. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33/src/specsmith.egg-info}/PKG-INFO +1 -1
  8. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith.egg-info/SOURCES.txt +1 -0
  9. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/LICENSE +0 -0
  10. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/README.md +0 -0
  11. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/setup.cfg +0 -0
  12. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/__init__.py +0 -0
  13. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/__main__.py +0 -0
  14. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/commands/__init__.py +0 -0
  15. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/compressor.py +0 -0
  16. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/config.py +0 -0
  17. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/differ.py +0 -0
  18. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/doctor.py +0 -0
  19. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/exporter.py +0 -0
  20. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/integrations/__init__.py +0 -0
  21. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/integrations/aider.py +0 -0
  22. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/integrations/base.py +0 -0
  23. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/integrations/claude_code.py +0 -0
  24. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/integrations/copilot.py +0 -0
  25. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/integrations/cursor.py +0 -0
  26. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/integrations/gemini.py +0 -0
  27. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/integrations/warp.py +0 -0
  28. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/integrations/windsurf.py +0 -0
  29. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/ledger.py +0 -0
  30. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/plugins.py +0 -0
  31. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/releaser.py +0 -0
  32. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/requirements.py +0 -0
  33. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/scaffolder.py +0 -0
  34. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/session.py +0 -0
  35. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/agents.md.j2 +0 -0
  36. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/docs/architecture.md.j2 +0 -0
  37. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/docs/requirements.md.j2 +0 -0
  38. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/docs/test-spec.md.j2 +0 -0
  39. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/docs/workflow.md.j2 +0 -0
  40. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/editorconfig.j2 +0 -0
  41. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/gitattributes.j2 +0 -0
  42. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/gitignore.j2 +0 -0
  43. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/governance/context-budget.md.j2 +0 -0
  44. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/governance/drift-metrics.md.j2 +0 -0
  45. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/governance/roles.md.j2 +0 -0
  46. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/governance/rules.md.j2 +0 -0
  47. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/governance/verification.md.j2 +0 -0
  48. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/governance/workflow.md.j2 +0 -0
  49. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/ledger.md.j2 +0 -0
  50. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/pyproject.toml.j2 +0 -0
  51. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/python/cli.py.j2 +0 -0
  52. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/python/init.py.j2 +0 -0
  53. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/readme.md.j2 +0 -0
  54. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/scripts/exec.cmd.j2 +0 -0
  55. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/scripts/exec.sh.j2 +0 -0
  56. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/scripts/run.cmd.j2 +0 -0
  57. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/scripts/run.sh.j2 +0 -0
  58. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/scripts/setup.cmd.j2 +0 -0
  59. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/scripts/setup.sh.j2 +0 -0
  60. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/tools.py +0 -0
  61. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/updater.py +0 -0
  62. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/upgrader.py +0 -0
  63. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/validator.py +0 -0
  64. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/vcs/__init__.py +0 -0
  65. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/vcs/base.py +0 -0
  66. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/vcs/bitbucket.py +0 -0
  67. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/vcs/github.py +0 -0
  68. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/vcs/gitlab.py +0 -0
  69. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/vcs_commands.py +0 -0
  70. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith.egg-info/dependency_links.txt +0 -0
  71. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith.egg-info/entry_points.txt +0 -0
  72. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith.egg-info/requires.txt +0 -0
  73. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith.egg-info/top_level.txt +0 -0
  74. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_auditor.py +0 -0
  75. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_cli.py +0 -0
  76. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_compressor.py +0 -0
  77. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_importer.py +0 -0
  78. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_integrations.py +0 -0
  79. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_scaffolder.py +0 -0
  80. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_smoke.py +0 -0
  81. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_tools.py +0 -0
  82. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_validator.py +0 -0
  83. {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_vcs.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specsmith
3
- Version: 0.1.4.dev30
3
+ Version: 0.1.4.dev33
4
4
  Summary: Forge governed project scaffolds from the Agentic AI Development Workflow Specification.
5
5
  Author: BitConcepts
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "specsmith"
7
- version = "0.1.4.dev30"
7
+ version = "0.1.4.dev33"
8
8
  description = "Forge governed project scaffolds from the Agentic AI Development Workflow Specification."
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -0,0 +1,154 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 BitConcepts, LLC. All rights reserved.
3
+ """Architect — scan project and generate architecture documentation."""
4
+
5
+ from __future__ import annotations
6
+
7
+ from pathlib import Path
8
+
9
+
10
+ def scan_project_structure(root: Path) -> dict[str, object]: # noqa: C901
11
+ """Scan a project and extract architecture-relevant information.
12
+
13
+ Returns a dict with modules, entry_points, languages, dependencies,
14
+ git_summary, and existing_docs.
15
+ """
16
+ from specsmith.importer import (
17
+ _extract_git_commits,
18
+ _extract_git_contributors,
19
+ _extract_readme_summary,
20
+ _parse_dependencies,
21
+ detect_project,
22
+ )
23
+
24
+ result = detect_project(root)
25
+ commits = _extract_git_commits(root)
26
+ contributors = _extract_git_contributors(root)
27
+ readme = _extract_readme_summary(root)
28
+ deps = _parse_dependencies(root)
29
+
30
+ # Find existing architecture docs
31
+ existing_arch: list[str] = []
32
+ docs_dir = root / "docs"
33
+ if docs_dir.is_dir():
34
+ for p in docs_dir.rglob("*"):
35
+ if p.is_file() and "architecture" in p.name.lower():
36
+ existing_arch.append(str(p.relative_to(root)))
37
+
38
+ return {
39
+ "name": root.name,
40
+ "languages": result.languages,
41
+ "primary_language": result.primary_language,
42
+ "secondary_languages": result.secondary_languages,
43
+ "build_system": result.build_system,
44
+ "test_framework": result.test_framework,
45
+ "modules": result.modules,
46
+ "entry_points": result.entry_points,
47
+ "dependencies": deps,
48
+ "readme_summary": readme,
49
+ "recent_commits": commits[:10],
50
+ "contributors": contributors,
51
+ "existing_arch_docs": existing_arch,
52
+ "file_count": result.file_count,
53
+ "inferred_type": result.inferred_type.value if result.inferred_type else "unknown",
54
+ }
55
+
56
+
57
+ def generate_architecture(
58
+ root: Path,
59
+ *,
60
+ components: list[dict[str, str]] | None = None,
61
+ data_flow: str = "",
62
+ deployment: str = "",
63
+ scan: dict[str, object] | None = None,
64
+ ) -> Path:
65
+ """Generate docs/architecture.md from scan data + user input.
66
+
67
+ Returns the path to the generated file.
68
+ """
69
+ if scan is None:
70
+ scan = scan_project_structure(root)
71
+
72
+ name = str(scan.get("name", root.name))
73
+ langs: dict[str, int] = dict(scan.get("languages", {}) or {}) # type: ignore[call-overload]
74
+ primary = str(scan.get("primary_language", "unknown"))
75
+ secondary: list[str] = list(scan.get("secondary_languages", []) or []) # type: ignore[call-overload]
76
+ lang_list = [primary] + secondary
77
+ lang_display = ", ".join(str(l) for l in lang_list if l) # noqa: E741
78
+
79
+ doc = f"# Architecture — {name}\n\n"
80
+
81
+ # Overview
82
+ doc += "## Overview\n\n"
83
+ readme = scan.get("readme_summary", "")
84
+ if readme:
85
+ doc += f"{readme}\n\n"
86
+ doc += f"- **Languages**: {lang_display}\n"
87
+ doc += f"- **Build system**: {scan.get('build_system', 'not detected')}\n"
88
+ doc += f"- **Test framework**: {scan.get('test_framework', 'not detected')}\n"
89
+ doc += f"- **Project type**: {scan.get('inferred_type', 'unknown')}\n"
90
+ doc += f"- **Files**: {scan.get('file_count', 0)}\n\n"
91
+
92
+ # Components
93
+ if components:
94
+ doc += "## Components\n\n"
95
+ for comp in components:
96
+ doc += f"### {comp.get('name', 'unnamed')}\n"
97
+ if comp.get("purpose"):
98
+ doc += f"- **Purpose**: {comp['purpose']}\n"
99
+ if comp.get("interfaces"):
100
+ doc += f"- **Interfaces**: {comp['interfaces']}\n"
101
+ if comp.get("dependencies"):
102
+ doc += f"- **Dependencies**: {comp['dependencies']}\n"
103
+ doc += "\n"
104
+ elif scan.get("modules"):
105
+ doc += "## Modules\n\n"
106
+ for mod in list(scan.get("modules", []) or []): # type: ignore[call-overload]
107
+ doc += f"### {mod}\n- **Purpose**: [Describe {mod} purpose]\n\n"
108
+
109
+ # Data flow
110
+ if data_flow:
111
+ doc += f"## Data Flow\n\n{data_flow}\n\n"
112
+ else:
113
+ doc += "## Data Flow\n\n[Describe how data flows between components]\n\n"
114
+
115
+ # Dependencies
116
+ deps: list[str] = list(scan.get("dependencies", []) or []) # type: ignore[call-overload]
117
+ if deps:
118
+ doc += "## External Dependencies\n\n"
119
+ for dep in deps[:30]:
120
+ doc += f"- `{dep}`\n"
121
+ doc += "\n"
122
+
123
+ # Entry points
124
+ eps: list[str] = list(scan.get("entry_points", []) or []) # type: ignore[call-overload]
125
+ if eps:
126
+ doc += "## Entry Points\n\n"
127
+ for ep in eps:
128
+ doc += f"- `{ep}`\n"
129
+ doc += "\n"
130
+
131
+ # Language distribution
132
+ if langs and len(langs) > 1:
133
+ doc += "## Language Distribution\n\n"
134
+ for lang_name, count in sorted(langs.items(), key=lambda x: -x[1]):
135
+ doc += f"- {lang_name}: {count} files\n"
136
+ doc += "\n"
137
+
138
+ # Deployment
139
+ if deployment:
140
+ doc += f"## Deployment\n\n{deployment}\n\n"
141
+
142
+ # Existing architecture references
143
+ existing: list[str] = list(scan.get("existing_arch_docs", []) or []) # type: ignore[call-overload]
144
+ if existing:
145
+ doc += "## Related Documents\n\n"
146
+ for ref in existing:
147
+ doc += f"- [{ref}]({ref})\n"
148
+ doc += "\n"
149
+
150
+ # Write
151
+ arch_path = root / "docs" / "architecture.md"
152
+ arch_path.parent.mkdir(parents=True, exist_ok=True)
153
+ arch_path.write_text(doc, encoding="utf-8")
154
+ return arch_path
@@ -120,11 +120,19 @@ def check_governance_files(root: Path) -> list[AuditResult]:
120
120
 
121
121
  for f in RECOMMENDED_FILES:
122
122
  path = root / f
123
+ found = path.exists()
124
+ # For architecture.md, also search subdirectories (e.g. docs/architecture/*.md)
125
+ if not found and "architecture" in f:
126
+ found = bool(
127
+ list((root / "docs").glob("**/architecture*"))
128
+ + list((root / "docs").glob("**/ARCHITECTURE*"))
129
+ ) if (root / "docs").is_dir() else False
123
130
  results.append(
124
131
  AuditResult(
125
132
  name=f"recommended:{f}",
126
- passed=path.exists(),
127
- message=f"Recommended file {f} {'exists' if path.exists() else 'missing'}",
133
+ passed=found,
134
+ message=f"Recommended file {f} {'exists' if found else 'missing'}",
135
+ fixable=not found,
128
136
  )
129
137
  )
130
138
 
@@ -558,4 +566,42 @@ def run_auto_fix(root: Path, report: AuditReport) -> list[str]:
558
566
  except Exception: # noqa: BLE001
559
567
  pass # Best-effort
560
568
 
569
+ # Fix missing recommended files
570
+ elif result.name == "recommended:docs/architecture.md" and not result.passed:
571
+ from specsmith.architect import generate_architecture
572
+
573
+ try:
574
+ generate_architecture(root)
575
+ fixed.append("Generated docs/architecture.md from project scan")
576
+ except Exception: # noqa: BLE001
577
+ # Fallback stub
578
+ path = root / "docs" / "architecture.md"
579
+ path.parent.mkdir(parents=True, exist_ok=True)
580
+ path.write_text(
581
+ f"# Architecture — {root.name}\n\n"
582
+ "[Run `specsmith architect` to populate]\n",
583
+ encoding="utf-8",
584
+ )
585
+ fixed.append("Created stub docs/architecture.md")
586
+
587
+ elif result.name == "recommended:docs/REQUIREMENTS.md" and not result.passed:
588
+ path = root / "docs" / "REQUIREMENTS.md"
589
+ path.parent.mkdir(parents=True, exist_ok=True)
590
+ path.write_text(
591
+ "# Requirements\n\nNo requirements defined yet.\n\n"
592
+ "## REQ-CORE-001\n- **Component**: core\n"
593
+ "- **Status**: Draft\n- **Description**: [Define]\n",
594
+ encoding="utf-8",
595
+ )
596
+ fixed.append("Created stub docs/REQUIREMENTS.md")
597
+
598
+ elif result.name == "recommended:docs/TEST_SPEC.md" and not result.passed:
599
+ path = root / "docs" / "TEST_SPEC.md"
600
+ path.parent.mkdir(parents=True, exist_ok=True)
601
+ path.write_text(
602
+ "# Test Specification\n\nNo tests defined yet.\n",
603
+ encoding="utf-8",
604
+ )
605
+ fixed.append("Created stub docs/TEST_SPEC.md")
606
+
561
607
  return fixed
@@ -646,6 +646,68 @@ def _run_guided_architecture(cfg: ProjectConfig, target: Path) -> list[Path]:
646
646
  return created
647
647
 
648
648
 
649
+ @main.command()
650
+ @click.option("--project-dir", type=click.Path(exists=True), default=".", help="Project root.")
651
+ @click.option("--non-interactive", is_flag=True, default=False, help="Skip prompts, auto-generate.")
652
+ def architect(project_dir: str, non_interactive: bool) -> None:
653
+ """Generate or enrich architecture documentation.
654
+
655
+ Scans the project for modules, languages, dependencies, git history,
656
+ then optionally interviews you about components and data flow.
657
+ """
658
+ from specsmith.architect import generate_architecture, scan_project_structure
659
+
660
+ root = Path(project_dir).resolve()
661
+ console.print(f"[bold]Scanning[/bold] {root}...\n")
662
+ scan = scan_project_structure(root)
663
+
664
+ modules: list[str] = list(scan.get("modules", []) or []) # type: ignore[call-overload]
665
+ deps_list: list[str] = list(scan.get("dependencies", []) or []) # type: ignore[call-overload]
666
+ eps_list: list[str] = list(scan.get("entry_points", []) or []) # type: ignore[call-overload]
667
+ existing: list[str] = list(scan.get("existing_arch_docs", []) or []) # type: ignore[call-overload]
668
+
669
+ console.print(f" Languages: {scan.get('primary_language', '?')}")
670
+ console.print(f" Modules: {', '.join(modules) or 'none'}")
671
+ console.print(f" Dependencies: {len(deps_list)}")
672
+ console.print(f" Entry points: {', '.join(eps_list) or 'none'}")
673
+ if existing:
674
+ console.print(f" Existing arch docs: {', '.join(existing)}")
675
+ console.print()
676
+
677
+ components: list[dict[str, str]] | None = None
678
+ data_flow = ""
679
+ deployment = ""
680
+
681
+ if not non_interactive:
682
+ console.print("[bold]Architecture Interview[/bold]\n")
683
+ comp_str = click.prompt(
684
+ "Major components (comma-separated)",
685
+ default=", ".join(modules or ["core"]),
686
+ )
687
+ components = []
688
+ for name in [c.strip() for c in comp_str.split(",") if c.strip()]:
689
+ purpose = click.prompt(f" {name} purpose", default="")
690
+ interfaces = click.prompt(f" {name} interfaces", default="")
691
+ components.append({"name": name, "purpose": purpose, "interfaces": interfaces})
692
+
693
+ data_flow = click.prompt("\nData flow description", default="")
694
+ deployment = click.prompt("Deployment notes", default="")
695
+
696
+ path = generate_architecture(
697
+ root, components=components, data_flow=data_flow, deployment=deployment, scan=scan
698
+ )
699
+ rel = path.relative_to(root)
700
+ console.print(f"\n[green]\u2713[/green] Generated {rel}")
701
+ if existing:
702
+ console.print(
703
+ f" [yellow]Note:[/yellow] Existing docs at {', '.join(existing)} "
704
+ "are referenced but not merged. Review manually."
705
+ )
706
+ console.print(
707
+ " [dim]Run \"specsmith audit --project-dir .\" to verify governance health.[/dim]"
708
+ )
709
+
710
+
649
711
  # ---------------------------------------------------------------------------
650
712
  # Ledger subcommands
651
713
  # ---------------------------------------------------------------------------
@@ -997,73 +997,67 @@ def generate_overlay(
997
997
  existing_reqs = list(target.glob("docs/**/REQUIREMENTS*")) + list(
998
998
  target.glob("docs/**/requirements*")
999
999
  )
1000
- if existing_reqs and not force:
1001
- pass # Preserve existing requirements doc
1002
- else:
1000
+ if not (existing_reqs and not force):
1003
1001
  reqs = "# Requirements\n\nRequirements auto-generated from project detection.\n\n"
1004
- for module in result.modules:
1005
- mu = module.upper().replace(" ", "-")
1006
- reqs += (
1007
- f"## REQ-{mu}-001\n"
1008
- f"- **Component**: {module}\n"
1009
- f"- **Status**: Draft\n"
1010
- f"- **Description**: [Describe requirements for {module}]\n\n"
1011
- )
1012
- if result.build_system:
1013
- reqs += (
1014
- "## REQ-BUILD-001\n"
1015
- f"- **Build system**: {result.build_system}\n"
1016
- "- **Status**: Draft\n"
1017
- f"- **Description**: Project builds successfully with {result.build_system}\n\n"
1018
- )
1002
+ for module in result.modules:
1003
+ mu = module.upper().replace(" ", "-")
1004
+ reqs += (
1005
+ f"## REQ-{mu}-001\n"
1006
+ f"- **Component**: {module}\n"
1007
+ f"- **Status**: Draft\n"
1008
+ f"- **Description**: [Describe requirements for {module}]\n\n"
1009
+ )
1010
+ if result.build_system:
1011
+ reqs += (
1012
+ "## REQ-BUILD-001\n"
1013
+ f"- **Build system**: {result.build_system}\n"
1014
+ "- **Status**: Draft\n"
1015
+ f"- **Description**: Project builds successfully with {result.build_system}\n\n"
1016
+ )
1019
1017
  _write("docs/REQUIREMENTS.md", reqs)
1020
1018
 
1021
1019
  # docs/TEST_SPEC.md — skip if project already has one
1022
1020
  existing_tests = list(target.glob("docs/**/TEST_SPEC*")) + list(
1023
1021
  target.glob("docs/**/test_spec*")
1024
1022
  )
1025
- if existing_tests and not force:
1026
- pass # Preserve existing test spec
1027
- else:
1023
+ if not (existing_tests and not force):
1028
1024
  tests = "# Test Specification\n\nTests auto-generated from project detection.\n\n"
1029
- for i, test_file in enumerate(result.test_files[:20], 1):
1030
- tests += f"## TEST-{i:03d}\n- **File**: {test_file}\n- **Status**: Detected\n"
1031
- for module in result.modules:
1032
- if module in test_file:
1033
- tests += f"- **Requirement**: REQ-{module.upper()}-001\n"
1034
- break
1035
- tests += "\n"
1025
+ for i, test_file in enumerate(result.test_files[:20], 1):
1026
+ tests += f"## TEST-{i:03d}\n- **File**: {test_file}\n- **Status**: Detected\n"
1027
+ for module in result.modules:
1028
+ if module in test_file:
1029
+ tests += f"- **Requirement**: REQ-{module.upper()}-001\n"
1030
+ break
1031
+ tests += "\n"
1036
1032
  _write("docs/TEST_SPEC.md", tests)
1037
1033
 
1038
1034
  # docs/architecture.md — skip if project has architecture doc anywhere under docs/
1039
1035
  existing_arch = list(target.glob("docs/**/architecture*")) + list(
1040
1036
  target.glob("docs/**/ARCHITECTURE*")
1041
1037
  )
1042
- if existing_arch and not force:
1043
- pass # Preserve existing architecture doc
1044
- else:
1038
+ if not (existing_arch and not force):
1045
1039
  arch = (
1046
- f"# Architecture — {name}\n\n"
1047
- "Architecture auto-generated from project detection.\n\n"
1048
- "## Overview\n"
1049
- f"- **Languages**: {lang_display}\n"
1050
- f"- **Build system**: {result.build_system or 'not detected'}\n"
1051
- f"- **Test framework**: {result.test_framework or 'not detected'}\n\n"
1052
- )
1053
- if result.modules:
1054
- arch += "## Modules\n"
1055
- for module in result.modules:
1056
- arch += f"- **{module}**: [Describe module purpose]\n"
1057
- arch += "\n"
1058
- if result.entry_points:
1059
- arch += "## Entry Points\n"
1060
- for ep in result.entry_points:
1061
- arch += f"- `{ep}`\n"
1062
- arch += "\n"
1063
- if result.languages:
1064
- arch += "## Language Distribution\n"
1065
- for lang_name, count in sorted(result.languages.items(), key=lambda x: -x[1]):
1066
- arch += f"- {lang_name}: {count} files\n"
1040
+ f"# Architecture — {name}\n\n"
1041
+ "Architecture auto-generated from project detection.\n\n"
1042
+ "## Overview\n"
1043
+ f"- **Languages**: {lang_display}\n"
1044
+ f"- **Build system**: {result.build_system or 'not detected'}\n"
1045
+ f"- **Test framework**: {result.test_framework or 'not detected'}\n\n"
1046
+ )
1047
+ if result.modules:
1048
+ arch += "## Modules\n"
1049
+ for module in result.modules:
1050
+ arch += f"- **{module}**: [Describe module purpose]\n"
1051
+ arch += "\n"
1052
+ if result.entry_points:
1053
+ arch += "## Entry Points\n"
1054
+ for ep in result.entry_points:
1055
+ arch += f"- `{ep}`\n"
1056
+ arch += "\n"
1057
+ if result.languages:
1058
+ arch += "## Language Distribution\n"
1059
+ for lang_name, count in sorted(result.languages.items(), key=lambda x: -x[1]):
1060
+ arch += f"- {lang_name}: {count} files\n"
1067
1061
  _write("docs/architecture.md", arch)
1068
1062
 
1069
1063
  # --- Modular governance files ---
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specsmith
3
- Version: 0.1.4.dev30
3
+ Version: 0.1.4.dev33
4
4
  Summary: Forge governed project scaffolds from the Agentic AI Development Workflow Specification.
5
5
  Author: BitConcepts
6
6
  License: MIT
@@ -3,6 +3,7 @@ README.md
3
3
  pyproject.toml
4
4
  src/specsmith/__init__.py
5
5
  src/specsmith/__main__.py
6
+ src/specsmith/architect.py
6
7
  src/specsmith/auditor.py
7
8
  src/specsmith/cli.py
8
9
  src/specsmith/compressor.py
File without changes