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.
- specthis-0.0.1/.github/workflows/test.yml +21 -0
- specthis-0.0.1/.gitignore +28 -0
- specthis-0.0.1/LICENSE +21 -0
- specthis-0.0.1/PKG-INFO +148 -0
- specthis-0.0.1/README.md +114 -0
- specthis-0.0.1/pyproject.toml +57 -0
- specthis-0.0.1/src/specthis/__init__.py +3 -0
- specthis-0.0.1/src/specthis/audit.py +29 -0
- specthis-0.0.1/src/specthis/cache.py +26 -0
- specthis-0.0.1/src/specthis/cli.py +132 -0
- specthis-0.0.1/src/specthis/export.py +30 -0
- specthis-0.0.1/src/specthis/install.py +69 -0
- specthis-0.0.1/src/specthis/lock.py +29 -0
- specthis-0.0.1/src/specthis/refresh.py +28 -0
- specthis-0.0.1/src/specthis/serve.py +22 -0
- specthis-0.0.1/src/specthis/templates/__init__.py +0 -0
- specthis-0.0.1/src/specthis/templates/agents/__init__.py +0 -0
- specthis-0.0.1/src/specthis/templates/agents/experiment-runner.md +87 -0
- specthis-0.0.1/src/specthis/templates/agents/spec-auditor.md +126 -0
- specthis-0.0.1/src/specthis/templates/agents/spec-implementer.md +103 -0
- specthis-0.0.1/src/specthis/templates/specs/AGENTS.md +308 -0
- specthis-0.0.1/src/specthis/templates/specs/README.md +128 -0
- specthis-0.0.1/src/specthis/templates/specs/__init__.py +0 -0
- specthis-0.0.1/tests/__init__.py +0 -0
- specthis-0.0.1/tests/test_install.py +72 -0
|
@@ -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.
|
specthis-0.0.1/PKG-INFO
ADDED
|
@@ -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).
|
specthis-0.0.1/README.md
ADDED
|
@@ -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,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
|