specthis 0.0.1__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.
@@ -0,0 +1,21 @@
1
+ name: test
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.10", "3.11", "3.12"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - run: pip install -e ".[dev]"
20
+ - run: pytest -q
21
+ - run: ruff check src tests
@@ -0,0 +1,28 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ build/
6
+ dist/
7
+ .pytest_cache/
8
+ .coverage
9
+ htmlcov/
10
+ .tox/
11
+ .ruff_cache/
12
+ .venv/
13
+ venv/
14
+
15
+ # IDE
16
+ .vscode/
17
+ .idea/
18
+
19
+ # OS
20
+ .DS_Store
21
+
22
+ # Local config
23
+ .env
24
+ .envrc
25
+
26
+ # specthis dev artefacts
27
+ /tmp_specs/
28
+ *.local.toml
specthis-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Thibaut Lamadon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.4
2
+ Name: specthis
3
+ Version: 0.0.1
4
+ Summary: Spec-driven research workflow: a dashboard, Claude Code subagents, and a refresh/cache pipeline for reproducible compute + LaTeX reports.
5
+ Project-URL: Homepage, https://github.com/tlamadon/specthis
6
+ Project-URL: Repository, https://github.com/tlamadon/specthis
7
+ Project-URL: Issues, https://github.com/tlamadon/specthis/issues
8
+ Author: Thibaut Lamadon
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: claude-code,latex,reproducibility,research,specs,workflow
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: click>=8.1
22
+ Requires-Dist: markdown>=3.5
23
+ Requires-Dist: pyyaml>=6.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest-cov>=4.1; extra == 'dev'
26
+ Requires-Dist: pytest>=7.4; extra == 'dev'
27
+ Requires-Dist: ruff>=0.3; extra == 'dev'
28
+ Provides-Extra: s3
29
+ Requires-Dist: boto3>=1.28; extra == 's3'
30
+ Provides-Extra: serve
31
+ Requires-Dist: starlette>=0.36; extra == 'serve'
32
+ Requires-Dist: uvicorn>=0.27; extra == 'serve'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # specthis
36
+
37
+ A spec-driven research workflow: a dashboard, Claude Code subagents, and
38
+ a refresh/cache pipeline for projects that produce reproducible compute
39
+ artefacts (typically JSON) and assemble them into a LaTeX (or similar)
40
+ report.
41
+
42
+ > Status: **early scaffold**. The CLI shell and the agent / spec
43
+ > templates work. The dashboard renderer, lock manager, S3 cache, and
44
+ > refresh orchestrator are stubs awaiting porting from the reference
45
+ > implementation. See [Roadmap](#roadmap).
46
+
47
+ ## What it is
48
+
49
+ A small package that gives a research project:
50
+
51
+ 1. **A spec format.** Each `compute-*.md` / `report-*.md` file in
52
+ `specs/` declares the path of a script that produces a JSON
53
+ artefact (compute) or a LaTeX figure / table (report), plus a
54
+ `Status:` line that pins whether the script satisfies the
55
+ contract.
56
+ 2. **A dashboard.** `specs/specs.html` renders the directory as a
57
+ browsable view of entries, their statuses, the produced
58
+ artefacts, and the cross-references between compute outputs and
59
+ report inputs.
60
+ 3. **A content-hash lock.** `specs/_lock.json` records the hash of
61
+ each entry's spec + script content at the moment it was certified
62
+ `script ready`. The refresh orchestrator refuses to rerun an
63
+ entry whose spec or script has drifted from the certified hash
64
+ without an explicit re-audit.
65
+ 4. **Three Claude Code subagents.** Drop-in agents for the three
66
+ operations a human or LLM does daily on a spec directory:
67
+ - `spec-auditor` — read-only consistency check (operation 1).
68
+ - `spec-implementer` — author a missing script for a
69
+ `script TBD` entry (operation 3).
70
+ - `experiment-runner` — kick off a long-running script in the
71
+ background, monitor its log for milestones / errors, report
72
+ completion.
73
+ 5. **(Optional) An S3 compute cache.** Push the `results/<entry>/`
74
+ directory to S3 keyed by the spec's `inputs_certified` hash, so
75
+ collaborators can pull instead of re-running.
76
+
77
+ ## Install
78
+
79
+ ```bash
80
+ pip install specthis # core: CLI + agent templates
81
+ pip install "specthis[s3]" # adds the S3 cache backend
82
+ ```
83
+
84
+ ## Scaffold a project
85
+
86
+ In any project directory:
87
+
88
+ ```bash
89
+ specthis install # writes the three agents into .claude/agents/
90
+ specthis init # creates specs/ with README.md + AGENTS.md templates
91
+ ```
92
+
93
+ After `init`, edit `specs/README.md` and add your first
94
+ `compute-<name>.md` / `report-<name>.md` pair.
95
+
96
+ ## CLI
97
+
98
+ ```
99
+ specthis install Copy agent templates into <cwd>/.claude/agents/
100
+ specthis init Create specs/ skeleton (README.md + AGENTS.md)
101
+ specthis audit Run the consistency audit (stub — port pending)
102
+ specthis refresh Rerun stale entries respecting the lock (stub — port pending)
103
+ specthis serve Start a local dev server for specs.html (stub — port pending)
104
+ specthis lock Manage the content-hash lock file (stub — port pending)
105
+ ```
106
+
107
+ ## The spec format in one paragraph
108
+
109
+ Every spec file under `specs/` carries YAML frontmatter
110
+ (`name`, `kind`, `depends_on`) and one of two body shapes:
111
+
112
+ - **`kind: compute`** — a `## Script` prose section describing how
113
+ the script is laid out, plus one or more `### entry-name` blocks
114
+ each carrying `Script:`, `Output:` (a JSON path), and `Status:`.
115
+ - **`kind: report`** — same shape, but per-entry fields are
116
+ `Export script:`, `Export outputs:` (LaTeX paths), and `Status:`,
117
+ plus frontmatter `host_doc:` and `section_label:` that route the
118
+ artefacts into a section of a top-level `.tex` document.
119
+
120
+ `Status:` is `script TBD` (the script does not satisfy the contract)
121
+ or `script ready` (it does, and a smoke-test passed). The
122
+ `scripts ran` vs `outputs exist on disk` axis is observed by the
123
+ auditor and the dashboard — it is not part of the spec.
124
+
125
+ See `src/specthis/templates/specs/README.md` for the full convention.
126
+
127
+ ## Roadmap
128
+
129
+ The reference implementation (a ~7000 LOC private codebase) is being
130
+ ported into this package one module at a time. Order:
131
+
132
+ 1. **agent templates + spec format docs** — done (this scaffold).
133
+ 2. **`specthis install` / `specthis init`** — done.
134
+ 3. **`specthis audit`** — port the index-based auditor.
135
+ 4. **`specthis serve`** — port the HTML dashboard renderer +
136
+ `_index.json` / `_routing.json` exporter.
137
+ 5. **`specthis lock`** — port the content-hash lock manager.
138
+ 6. **`specthis refresh`** — port the Makefile-driven refresh
139
+ orchestrator.
140
+ 7. **`specthis cache`** — port the S3 backend.
141
+
142
+ Each module ships with a config-driven surface (paths configurable
143
+ via `specthis.toml`), no hard-coded assumptions about the host
144
+ project's layout beyond the defaults documented in the spec format.
145
+
146
+ ## License
147
+
148
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,114 @@
1
+ # specthis
2
+
3
+ A spec-driven research workflow: a dashboard, Claude Code subagents, and
4
+ a refresh/cache pipeline for projects that produce reproducible compute
5
+ artefacts (typically JSON) and assemble them into a LaTeX (or similar)
6
+ report.
7
+
8
+ > Status: **early scaffold**. The CLI shell and the agent / spec
9
+ > templates work. The dashboard renderer, lock manager, S3 cache, and
10
+ > refresh orchestrator are stubs awaiting porting from the reference
11
+ > implementation. See [Roadmap](#roadmap).
12
+
13
+ ## What it is
14
+
15
+ A small package that gives a research project:
16
+
17
+ 1. **A spec format.** Each `compute-*.md` / `report-*.md` file in
18
+ `specs/` declares the path of a script that produces a JSON
19
+ artefact (compute) or a LaTeX figure / table (report), plus a
20
+ `Status:` line that pins whether the script satisfies the
21
+ contract.
22
+ 2. **A dashboard.** `specs/specs.html` renders the directory as a
23
+ browsable view of entries, their statuses, the produced
24
+ artefacts, and the cross-references between compute outputs and
25
+ report inputs.
26
+ 3. **A content-hash lock.** `specs/_lock.json` records the hash of
27
+ each entry's spec + script content at the moment it was certified
28
+ `script ready`. The refresh orchestrator refuses to rerun an
29
+ entry whose spec or script has drifted from the certified hash
30
+ without an explicit re-audit.
31
+ 4. **Three Claude Code subagents.** Drop-in agents for the three
32
+ operations a human or LLM does daily on a spec directory:
33
+ - `spec-auditor` — read-only consistency check (operation 1).
34
+ - `spec-implementer` — author a missing script for a
35
+ `script TBD` entry (operation 3).
36
+ - `experiment-runner` — kick off a long-running script in the
37
+ background, monitor its log for milestones / errors, report
38
+ completion.
39
+ 5. **(Optional) An S3 compute cache.** Push the `results/<entry>/`
40
+ directory to S3 keyed by the spec's `inputs_certified` hash, so
41
+ collaborators can pull instead of re-running.
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ pip install specthis # core: CLI + agent templates
47
+ pip install "specthis[s3]" # adds the S3 cache backend
48
+ ```
49
+
50
+ ## Scaffold a project
51
+
52
+ In any project directory:
53
+
54
+ ```bash
55
+ specthis install # writes the three agents into .claude/agents/
56
+ specthis init # creates specs/ with README.md + AGENTS.md templates
57
+ ```
58
+
59
+ After `init`, edit `specs/README.md` and add your first
60
+ `compute-<name>.md` / `report-<name>.md` pair.
61
+
62
+ ## CLI
63
+
64
+ ```
65
+ specthis install Copy agent templates into <cwd>/.claude/agents/
66
+ specthis init Create specs/ skeleton (README.md + AGENTS.md)
67
+ specthis audit Run the consistency audit (stub — port pending)
68
+ specthis refresh Rerun stale entries respecting the lock (stub — port pending)
69
+ specthis serve Start a local dev server for specs.html (stub — port pending)
70
+ specthis lock Manage the content-hash lock file (stub — port pending)
71
+ ```
72
+
73
+ ## The spec format in one paragraph
74
+
75
+ Every spec file under `specs/` carries YAML frontmatter
76
+ (`name`, `kind`, `depends_on`) and one of two body shapes:
77
+
78
+ - **`kind: compute`** — a `## Script` prose section describing how
79
+ the script is laid out, plus one or more `### entry-name` blocks
80
+ each carrying `Script:`, `Output:` (a JSON path), and `Status:`.
81
+ - **`kind: report`** — same shape, but per-entry fields are
82
+ `Export script:`, `Export outputs:` (LaTeX paths), and `Status:`,
83
+ plus frontmatter `host_doc:` and `section_label:` that route the
84
+ artefacts into a section of a top-level `.tex` document.
85
+
86
+ `Status:` is `script TBD` (the script does not satisfy the contract)
87
+ or `script ready` (it does, and a smoke-test passed). The
88
+ `scripts ran` vs `outputs exist on disk` axis is observed by the
89
+ auditor and the dashboard — it is not part of the spec.
90
+
91
+ See `src/specthis/templates/specs/README.md` for the full convention.
92
+
93
+ ## Roadmap
94
+
95
+ The reference implementation (a ~7000 LOC private codebase) is being
96
+ ported into this package one module at a time. Order:
97
+
98
+ 1. **agent templates + spec format docs** — done (this scaffold).
99
+ 2. **`specthis install` / `specthis init`** — done.
100
+ 3. **`specthis audit`** — port the index-based auditor.
101
+ 4. **`specthis serve`** — port the HTML dashboard renderer +
102
+ `_index.json` / `_routing.json` exporter.
103
+ 5. **`specthis lock`** — port the content-hash lock manager.
104
+ 6. **`specthis refresh`** — port the Makefile-driven refresh
105
+ orchestrator.
106
+ 7. **`specthis cache`** — port the S3 backend.
107
+
108
+ Each module ships with a config-driven surface (paths configurable
109
+ via `specthis.toml`), no hard-coded assumptions about the host
110
+ project's layout beyond the defaults documented in the spec format.
111
+
112
+ ## License
113
+
114
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,57 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "specthis"
7
+ version = "0.0.1"
8
+ description = "Spec-driven research workflow: a dashboard, Claude Code subagents, and a refresh/cache pipeline for reproducible compute + LaTeX reports."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "Thibaut Lamadon" },
14
+ ]
15
+ keywords = ["specs", "research", "reproducibility", "claude-code", "latex", "workflow"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Science/Research",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3 :: Only",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ ]
26
+ dependencies = [
27
+ "click>=8.1",
28
+ "pyyaml>=6.0",
29
+ "markdown>=3.5",
30
+ ]
31
+
32
+ [project.optional-dependencies]
33
+ s3 = ["boto3>=1.28"]
34
+ serve = ["uvicorn>=0.27", "starlette>=0.36"]
35
+ dev = [
36
+ "pytest>=7.4",
37
+ "pytest-cov>=4.1",
38
+ "ruff>=0.3",
39
+ ]
40
+
41
+ [project.urls]
42
+ Homepage = "https://github.com/tlamadon/specthis"
43
+ Repository = "https://github.com/tlamadon/specthis"
44
+ Issues = "https://github.com/tlamadon/specthis/issues"
45
+
46
+ [project.scripts]
47
+ specthis = "specthis.cli:main"
48
+
49
+ [tool.hatch.build.targets.wheel]
50
+ packages = ["src/specthis"]
51
+
52
+ [tool.ruff]
53
+ line-length = 100
54
+ target-version = "py310"
55
+
56
+ [tool.pytest.ini_options]
57
+ testpaths = ["tests"]
@@ -0,0 +1,3 @@
1
+ """specthis — spec-driven research workflow tooling."""
2
+
3
+ __version__ = "0.0.1"
@@ -0,0 +1,29 @@
1
+ """Spec consistency audit (operation 1 from specs/AGENTS.md).
2
+
3
+ Status: **stub**. The reference implementation is index-based: it
4
+ reads ``specs/_index.json`` and ``specs/_routing.json`` (produced by
5
+ :mod:`specthis.export`) and reports each entry's script existence,
6
+ contract-in-spirit, output schema, export routing, and freshness in a
7
+ single markdown table.
8
+
9
+ Port plan:
10
+ - ``walk_index(specs_dir) -> list[EntryReport]``
11
+ - ``check_compute_entry(entry, index) -> EntryReport``
12
+ - ``check_report_entry(entry, index, routing) -> EntryReport``
13
+ - ``format_table(reports) -> str``
14
+
15
+ Until ported, invoke the bundled ``spec-auditor`` subagent in Claude
16
+ Code instead — it implements the same checks by reading the index
17
+ files directly.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ from pathlib import Path
23
+
24
+
25
+ def run_audit(specs_dir: Path) -> str: # pragma: no cover - stub
26
+ raise NotImplementedError(
27
+ "specthis.audit is not yet ported. Use the spec-auditor subagent "
28
+ "(installed by `specthis install`) in the meantime."
29
+ )
@@ -0,0 +1,26 @@
1
+ """S3-backed compute cache for spec entries.
2
+
3
+ Status: **stub**. The reference implementation provides four
4
+ operations keyed by an entry's ``inputs_certified`` hash:
5
+
6
+ - ``push <entry>``: tar the entry's ``results/<entry>/`` directory and
7
+ upload to ``s3://<bucket>/cache/<input_sig>/<entry>.tar.gz``.
8
+ - ``fetch <entry>``: download and unpack into ``results/<entry>/``.
9
+ - ``has <entry>``: HEAD-check S3 for the artefact.
10
+ - ``list``: list cached entries with their input signatures.
11
+
12
+ Requires ``specthis[s3]`` extra (boto3) and AWS credentials available
13
+ in the standard chain (env, profile, instance role).
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from pathlib import Path
19
+
20
+
21
+ def push(entry: str, bucket: str, specs_dir: Path) -> None: # pragma: no cover - stub
22
+ raise NotImplementedError("specthis.cache is not yet ported.")
23
+
24
+
25
+ def fetch(entry: str, bucket: str, specs_dir: Path) -> None: # pragma: no cover - stub
26
+ raise NotImplementedError("specthis.cache is not yet ported.")
@@ -0,0 +1,132 @@
1
+ """specthis command-line entry point."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import click
9
+
10
+ from . import __version__
11
+ from .install import install_agents, init_specs_dir
12
+
13
+
14
+ @click.group(context_settings={"help_option_names": ["-h", "--help"]})
15
+ @click.version_option(__version__, prog_name="specthis")
16
+ def main() -> None:
17
+ """Spec-driven research workflow: dashboard, agents, refresh pipeline."""
18
+
19
+
20
+ @main.command("install")
21
+ @click.option(
22
+ "--path",
23
+ "project_path",
24
+ type=click.Path(file_okay=False, path_type=Path),
25
+ default=Path.cwd(),
26
+ show_default="current directory",
27
+ help="Project root in which to install .claude/agents/.",
28
+ )
29
+ @click.option(
30
+ "--force",
31
+ is_flag=True,
32
+ help="Overwrite existing agent files.",
33
+ )
34
+ @click.option(
35
+ "--agent",
36
+ "selected",
37
+ multiple=True,
38
+ type=click.Choice(["spec-auditor", "spec-implementer", "experiment-runner"]),
39
+ help="Install only the named agent(s). Repeatable. Default: all three.",
40
+ )
41
+ def install_cmd(project_path: Path, force: bool, selected: tuple[str, ...]) -> None:
42
+ """Copy the specthis subagent templates into <project>/.claude/agents/."""
43
+ installed, skipped = install_agents(
44
+ project_path=project_path,
45
+ force=force,
46
+ agents=list(selected) if selected else None,
47
+ )
48
+ for name in installed:
49
+ click.echo(f" installed {name}")
50
+ for name, reason in skipped:
51
+ click.echo(f" skipped {name} ({reason})", err=True)
52
+ if not installed and skipped:
53
+ click.echo("\nNothing changed. Re-run with --force to overwrite.", err=True)
54
+ sys.exit(1)
55
+
56
+
57
+ @main.command("init")
58
+ @click.option(
59
+ "--path",
60
+ "project_path",
61
+ type=click.Path(file_okay=False, path_type=Path),
62
+ default=Path.cwd(),
63
+ show_default="current directory",
64
+ help="Project root in which to create specs/.",
65
+ )
66
+ @click.option(
67
+ "--force",
68
+ is_flag=True,
69
+ help="Overwrite existing template files in specs/.",
70
+ )
71
+ def init_cmd(project_path: Path, force: bool) -> None:
72
+ """Create specs/ with README.md and AGENTS.md spec-format templates."""
73
+ created, skipped = init_specs_dir(project_path=project_path, force=force)
74
+ for path in created:
75
+ click.echo(f" created {path}")
76
+ for path, reason in skipped:
77
+ click.echo(f" skipped {path} ({reason})", err=True)
78
+
79
+
80
+ @main.command("audit")
81
+ @click.option(
82
+ "--specs",
83
+ "specs_dir",
84
+ type=click.Path(file_okay=False, exists=True, path_type=Path),
85
+ default=Path("specs"),
86
+ show_default=True,
87
+ help="specs/ directory to audit.",
88
+ )
89
+ def audit_cmd(specs_dir: Path) -> None:
90
+ """Run the consistency audit over specs/. (stub — port pending)"""
91
+ click.echo(
92
+ f"specthis audit: not yet implemented. Would audit {specs_dir}.\n"
93
+ "Until then, invoke the spec-auditor subagent in Claude Code.",
94
+ err=True,
95
+ )
96
+ sys.exit(2)
97
+
98
+
99
+ @main.command("refresh")
100
+ @click.option(
101
+ "--specs",
102
+ "specs_dir",
103
+ type=click.Path(file_okay=False, exists=True, path_type=Path),
104
+ default=Path("specs"),
105
+ show_default=True,
106
+ )
107
+ def refresh_cmd(specs_dir: Path) -> None:
108
+ """Re-run stale entries respecting the lock file. (stub — port pending)"""
109
+ click.echo("specthis refresh: not yet implemented.", err=True)
110
+ sys.exit(2)
111
+
112
+
113
+ @main.command("serve")
114
+ @click.option("--host", default="127.0.0.1", show_default=True)
115
+ @click.option("--port", type=int, default=8765, show_default=True)
116
+ def serve_cmd(host: str, port: int) -> None:
117
+ """Serve the specs.html dashboard with live reload. (stub — port pending)"""
118
+ click.echo("specthis serve: not yet implemented.", err=True)
119
+ sys.exit(2)
120
+
121
+
122
+ @main.command("lock")
123
+ @click.argument("subcommand", type=click.Choice(["status", "record", "clear"]))
124
+ @click.argument("entry", required=False)
125
+ def lock_cmd(subcommand: str, entry: str | None) -> None:
126
+ """Manage the spec inputs_certified content-hash lock. (stub — port pending)"""
127
+ click.echo(f"specthis lock {subcommand}: not yet implemented.", err=True)
128
+ sys.exit(2)
129
+
130
+
131
+ if __name__ == "__main__":
132
+ main()
@@ -0,0 +1,30 @@
1
+ """Dashboard renderer: builds specs/specs.html + _index.json + _routing.json.
2
+
3
+ Status: **stub**. The reference implementation walks ``specs/*.md``,
4
+ parses each frontmatter + entry block, joins against the working tree
5
+ (script existence, output existence + top-level keys, export
6
+ artefacts, host-doc routing), and emits three artefacts:
7
+
8
+ - ``specs/specs.html`` — a single-file browsable dashboard of every
9
+ spec, entry, and pairing.
10
+ - ``specs/_index.json`` — per-spec frontmatter + per-entry facts,
11
+ consumed by :mod:`specthis.audit` and the auditor subagent.
12
+ - ``specs/_routing.json`` — per host-doc, per label section, the
13
+ ``\\input{}`` / ``\\includegraphics{}`` lines found inside, plus
14
+ ``\\sectionversion`` proximity flags.
15
+
16
+ Port plan:
17
+ - ``parse_spec(path) -> SpecFile``
18
+ - ``join_against_worktree(specs, project_root) -> IndexData``
19
+ - ``walk_host_docs(reports_dir) -> RoutingData``
20
+ - ``render_html(index, routing) -> str``
21
+ - ``write_artefacts(specs_dir, index, routing, html) -> None``
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from pathlib import Path
27
+
28
+
29
+ def render(specs_dir: Path, project_root: Path) -> None: # pragma: no cover - stub
30
+ raise NotImplementedError("specthis.export is not yet ported.")
@@ -0,0 +1,69 @@
1
+ """Scaffolder: copy bundled templates into a project directory."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib import resources
6
+ from pathlib import Path
7
+
8
+ AGENT_NAMES = ("spec-auditor", "spec-implementer", "experiment-runner")
9
+ SPEC_TEMPLATE_NAMES = ("README.md", "AGENTS.md")
10
+
11
+
12
+ def _read_template(subdir: str, filename: str) -> str:
13
+ """Read a bundled template file from the installed package."""
14
+ package = f"specthis.templates.{subdir}"
15
+ return resources.files(package).joinpath(filename).read_text(encoding="utf-8")
16
+
17
+
18
+ def install_agents(
19
+ project_path: Path,
20
+ force: bool = False,
21
+ agents: list[str] | None = None,
22
+ ) -> tuple[list[str], list[tuple[str, str]]]:
23
+ """Copy agent templates into ``<project_path>/.claude/agents/``.
24
+
25
+ Returns ``(installed, skipped)`` where ``installed`` is a list of agent
26
+ names written and ``skipped`` is a list of ``(name, reason)``.
27
+ """
28
+ selected = agents or list(AGENT_NAMES)
29
+ target_dir = project_path / ".claude" / "agents"
30
+ target_dir.mkdir(parents=True, exist_ok=True)
31
+
32
+ installed: list[str] = []
33
+ skipped: list[tuple[str, str]] = []
34
+ for name in selected:
35
+ if name not in AGENT_NAMES:
36
+ skipped.append((name, "unknown agent"))
37
+ continue
38
+ target = target_dir / f"{name}.md"
39
+ if target.exists() and not force:
40
+ skipped.append((name, "already exists; use --force"))
41
+ continue
42
+ body = _read_template("agents", f"{name}.md")
43
+ target.write_text(body, encoding="utf-8")
44
+ installed.append(name)
45
+ return installed, skipped
46
+
47
+
48
+ def init_specs_dir(
49
+ project_path: Path,
50
+ force: bool = False,
51
+ ) -> tuple[list[Path], list[tuple[Path, str]]]:
52
+ """Create ``<project_path>/specs/`` with the README and AGENTS templates.
53
+
54
+ Returns ``(created, skipped)``.
55
+ """
56
+ target_dir = project_path / "specs"
57
+ target_dir.mkdir(parents=True, exist_ok=True)
58
+
59
+ created: list[Path] = []
60
+ skipped: list[tuple[Path, str]] = []
61
+ for filename in SPEC_TEMPLATE_NAMES:
62
+ target = target_dir / filename
63
+ if target.exists() and not force:
64
+ skipped.append((target, "already exists; use --force"))
65
+ continue
66
+ body = _read_template("specs", filename)
67
+ target.write_text(body, encoding="utf-8")
68
+ created.append(target)
69
+ return created, skipped