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.
- {specsmith-0.1.4.dev30/src/specsmith.egg-info → specsmith-0.1.4.dev33}/PKG-INFO +1 -1
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/pyproject.toml +1 -1
- specsmith-0.1.4.dev33/src/specsmith/architect.py +154 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/auditor.py +48 -2
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/cli.py +62 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/importer.py +46 -52
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33/src/specsmith.egg-info}/PKG-INFO +1 -1
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith.egg-info/SOURCES.txt +1 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/LICENSE +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/README.md +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/setup.cfg +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/__init__.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/__main__.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/commands/__init__.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/compressor.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/config.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/differ.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/doctor.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/exporter.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/integrations/__init__.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/integrations/aider.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/integrations/base.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/integrations/claude_code.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/integrations/copilot.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/integrations/cursor.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/integrations/gemini.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/integrations/warp.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/integrations/windsurf.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/ledger.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/plugins.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/releaser.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/requirements.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/scaffolder.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/session.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/agents.md.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/docs/architecture.md.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/docs/requirements.md.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/docs/test-spec.md.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/docs/workflow.md.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/editorconfig.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/gitattributes.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/gitignore.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/governance/context-budget.md.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/governance/drift-metrics.md.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/governance/roles.md.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/governance/rules.md.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/governance/verification.md.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/governance/workflow.md.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/ledger.md.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/pyproject.toml.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/python/cli.py.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/python/init.py.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/readme.md.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/scripts/exec.cmd.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/scripts/exec.sh.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/scripts/run.cmd.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/scripts/run.sh.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/scripts/setup.cmd.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/scripts/setup.sh.j2 +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/tools.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/updater.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/upgrader.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/validator.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/vcs/__init__.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/vcs/base.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/vcs/bitbucket.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/vcs/github.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/vcs/gitlab.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/vcs_commands.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith.egg-info/dependency_links.txt +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith.egg-info/entry_points.txt +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith.egg-info/requires.txt +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith.egg-info/top_level.txt +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_auditor.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_cli.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_compressor.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_importer.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_integrations.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_scaffolder.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_smoke.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_tools.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_validator.py +0 -0
- {specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/tests/test_vcs.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specsmith"
|
|
7
|
-
version = "0.1.4.
|
|
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=
|
|
127
|
-
message=f"Recommended file {f} {'exists' if
|
|
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
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
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
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
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
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
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 ---
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/docs/architecture.md.j2
RENAMED
|
File without changes
|
{specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/docs/requirements.md.j2
RENAMED
|
File without changes
|
{specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/docs/test-spec.md.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/governance/roles.md.j2
RENAMED
|
File without changes
|
{specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/governance/rules.md.j2
RENAMED
|
File without changes
|
|
File without changes
|
{specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/governance/workflow.md.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{specsmith-0.1.4.dev30 → specsmith-0.1.4.dev33}/src/specsmith/templates/scripts/setup.cmd.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|