spexcode 0.1.6 → 0.2.1
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.
- package/README.md +99 -35
- package/README.zh-CN.md +135 -0
- package/package.json +5 -6
- package/spec-cli/README.md +86 -0
- package/spec-cli/bin/spex.mjs +15 -3
- package/spec-cli/hooks/dispatch.sh +20 -8
- package/spec-cli/hooks/harness.sh +18 -11
- package/spec-cli/src/board.ts +47 -18
- package/spec-cli/src/boardCache.ts +70 -0
- package/spec-cli/src/boardDelta.ts +90 -0
- package/spec-cli/src/boardStream.ts +178 -0
- package/spec-cli/src/cli.ts +184 -122
- package/spec-cli/src/client.ts +6 -4
- package/spec-cli/src/gateway.ts +64 -24
- package/spec-cli/src/git.ts +105 -92
- package/spec-cli/src/guide.ts +186 -19
- package/spec-cli/src/harness-select.ts +63 -0
- package/spec-cli/src/harness.ts +506 -100
- package/spec-cli/src/help.ts +362 -0
- package/spec-cli/src/hooks.ts +0 -14
- package/spec-cli/src/index.ts +279 -32
- package/spec-cli/src/init.ts +41 -1
- package/spec-cli/src/issues.ts +301 -0
- package/spec-cli/src/layout.ts +70 -28
- package/spec-cli/src/lint.ts +12 -10
- package/spec-cli/src/listen.ts +28 -0
- package/spec-cli/src/localIssues.ts +700 -0
- package/spec-cli/src/materialize.ts +182 -27
- package/spec-cli/src/mentions.ts +192 -0
- package/spec-cli/src/plugin-harness.ts +145 -0
- package/spec-cli/src/pty-bridge.ts +378 -81
- package/spec-cli/src/self.ts +123 -20
- package/spec-cli/src/sessions.ts +461 -298
- package/spec-cli/src/specs.ts +55 -14
- package/spec-cli/src/supervise.ts +23 -3
- package/spec-cli/src/tsx-bin.ts +14 -5
- package/spec-cli/src/uninstall.ts +146 -0
- package/spec-cli/templates/hooks/post-merge +27 -0
- package/spec-cli/templates/hooks/pre-commit +51 -31
- package/spec-cli/templates/hooks/prepare-commit-msg +31 -8
- package/spec-cli/templates/presets/careful/.config/clarify-before-code/spec.md +11 -0
- package/spec-cli/templates/spec/project/.config/core/spec-of-file/spec-of-file.sh +26 -3
- package/spec-cli/templates/spec/project/.config/core/spec.md +4 -0
- package/spec-cli/templates/spec/project/.config/core/stop-gate/stop-gate.sh +16 -4
- package/spec-cli/templates/spec/project/.config/extract/spec.md +1 -1
- package/spec-cli/templates/spec/project/.config/regroup/spec.md +1 -1
- package/spec-cli/templates/spec/project/.config/reproduce-before-fix/spec.md +18 -0
- package/spec-cli/templates/spec/project/.config/spec.md +3 -3
- package/spec-cli/templates/spec/project/.config/supervisor/spec.md +2 -2
- package/spec-cli/templates/spec/project/.config/tidy/spec.md +1 -1
- package/spec-cli/templates/spec/project/spec.md +2 -2
- package/spec-dashboard/dist/assets/index-Ct_ubwrd.css +32 -0
- package/spec-dashboard/dist/assets/index-DehTZ-h9.js +145 -0
- package/spec-dashboard/dist/index.html +17 -5
- package/spec-forge/src/cache.ts +16 -0
- package/spec-forge/src/cli.ts +4 -10
- package/spec-forge/src/drivers/github.ts +74 -4
- package/spec-forge/src/drivers.ts +13 -0
- package/spec-forge/src/port.ts +25 -0
- package/spec-forge/src/resident.ts +40 -6
- package/spec-yatsu/src/cli.ts +227 -38
- package/spec-yatsu/src/evaltab.ts +169 -19
- package/spec-yatsu/src/filing.ts +48 -0
- package/spec-yatsu/src/freshness.ts +55 -20
- package/spec-yatsu/src/proof.ts +89 -3
- package/spec-yatsu/src/scenariofresh.ts +92 -0
- package/spec-yatsu/src/sidecar.ts +75 -11
- package/spec-yatsu/src/timeline.ts +47 -0
- package/spec-yatsu/src/yatsu.ts +47 -3
- package/spec-cli/src/relay.ts +0 -28
- package/spec-cli/templates/spec/project/.config/scenario/spec.md +0 -32
- package/spec-dashboard/dist/assets/index-Bk4E1EQy.js +0 -139
- package/spec-dashboard/dist/assets/index-Cq7hwngj.css +0 -32
package/README.md
CHANGED
|
@@ -1,69 +1,127 @@
|
|
|
1
1
|
<img src="docs/sdd-tuxedo-pooh.png" alt="Writing code vs. authoring a living, executable specification artifact" width="420">
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
>
|
|
3
|
+
English | [中文](./README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
> Spec-driven development fails two ways: the spec drifts out of sync with the code, or it
|
|
6
|
+
> bloats into stale ceremony. SpexCode keeps each spec short and current: rewritten in place
|
|
7
|
+
> and versioned by git, not an accumulating changelog.
|
|
6
8
|
|
|
7
9
|
**SpexCode** is a spec-driven, self-developing dev tool. Every part of a project becomes a versioned
|
|
8
|
-
*spec node
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
*spec node*: a `.spec/**/spec.md` whose body states the part's present intent. Git is the database:
|
|
11
|
+
a node's version is its count of content commits, and "drift" is governed code that moved ahead of
|
|
12
|
+
its spec. The `spex` CLI and the live dashboard read everything straight from git; there is no
|
|
11
13
|
separate store.
|
|
12
14
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- **[
|
|
16
|
-
- **[Contributing to SpexCode](#contributing-to-spexcode)** — develop the tool itself, in this repo.
|
|
15
|
+
- **[Using SpexCode](#using-spexcode)** — adopt the `spex` CLI and drive it through your coding agent to govern *your own* project.
|
|
16
|
+
- **[Contributing to SpexCode](#contributing-to-spexcode)** — hack on the tool itself, in this repo.
|
|
17
|
+
- **[Working with agents →](https://spexcode.net/working-with-agents/)** — the longer write-up on the docs site.
|
|
17
18
|
|
|
18
19
|
---
|
|
19
20
|
|
|
20
21
|
## Using SpexCode
|
|
21
22
|
|
|
22
|
-
You
|
|
23
|
+
You set SpexCode up once (`npm i -g spexcode`, then `spex init` in your repo). After that, the main
|
|
24
|
+
way to use it is by talking to your coding agent. You describe what you want in plain language
|
|
25
|
+
(*"add a spec node for the auth flow", "extract specs for this package", "dispatch a worker to
|
|
26
|
+
implement Y"*) and the agent runs the `spex` CLI for you while you supervise on the board. The
|
|
27
|
+
manual CLI below is the substrate; your agent is the daily interface.
|
|
28
|
+
|
|
29
|
+
That works because a freshly launched agent already knows SpexCode. `spex init` materializes the
|
|
30
|
+
contract (the spec-node ritual, the commit-before-declare gate, the merge style) into a
|
|
31
|
+
`<!-- spexcode -->` managed block in your repo's `CLAUDE.md`/`AGENTS.md`, which
|
|
32
|
+
**[Claude Code](https://www.anthropic.com/claude-code)** and **Codex** auto-discover as always-on
|
|
33
|
+
context. From there the agent self-serves detail on demand from the built-in manual: `spex guide`
|
|
34
|
+
(the workflow), `spex guide spec` / `spex guide yatsu` (the file formats), and `spex guide config`
|
|
35
|
+
(every `spexcode.json` setting). You can tell it *"run `spex guide config` and set me up a
|
|
36
|
+
launcher"*.
|
|
37
|
+
|
|
38
|
+
The docs site covers this in full: **[working with agents](https://spexcode.net/working-with-agents/)**
|
|
39
|
+
for the agent-driven workflow, **[getting started](https://spexcode.net/getting-started/)** for the
|
|
40
|
+
setup end to end.
|
|
41
|
+
|
|
42
|
+
> **It's also just plain tooling.** Strip the agent away and the core is still useful on its own:
|
|
43
|
+
> spec files versioned by git, checked by `spex lint` and shown on a read-only dashboard. No AI,
|
|
44
|
+
> nothing to run but Node and git. The vibe-coding path sits on top of that; it doesn't replace it.
|
|
45
|
+
|
|
46
|
+
> **Requirements.** Core: **Node ≥ 22** and **git**. Driving SpexCode through an agent (or
|
|
47
|
+
> dispatching workers onto your nodes) also needs **tmux** and an authenticated **Claude Code or
|
|
48
|
+
> Codex** on your PATH. Those agents run commands on your machine, so read
|
|
49
|
+
> [`SECURITY.md`](./docs/SECURITY.md) before exposing the backend.
|
|
50
|
+
|
|
51
|
+
### Set it up
|
|
52
|
+
|
|
53
|
+
Install the published CLI once, then adopt it in any project:
|
|
23
54
|
|
|
24
55
|
```sh
|
|
25
56
|
npm i -g spexcode # installs the `spex` command (needs Node ≥ 22)
|
|
57
|
+
cd ~/my-app
|
|
58
|
+
spex init # additive — never restructures your code
|
|
26
59
|
```
|
|
27
60
|
|
|
28
|
-
|
|
61
|
+
`spex init` is additive: it seeds a starter **`.spec/`** tree (a root `project` node plus the
|
|
62
|
+
`.config` plugins that define the dev flow), a starter **`spexcode.json`**, and the per-clone
|
|
63
|
+
**git hooks** (a `pre-commit` hook that runs **spec-lint**, blocking on broken spec↔code links, and
|
|
64
|
+
**main-guard**, which blocks direct commits to `main`, plus a `prepare-commit-msg` hook that stamps
|
|
65
|
+
each commit's session attribution). It also **materializes** the harness artifacts that make the
|
|
66
|
+
agent path work: the `<!-- spexcode -->` contract block in `CLAUDE.md`/`AGENTS.md`, and the
|
|
67
|
+
`.claude/` / `.codex/` shims (the `settings.json` hooks) a self-launched agent discovers. Those
|
|
68
|
+
artifacts are generated and gitignored; they are regenerated on each machine, never committed.
|
|
69
|
+
|
|
70
|
+
Then make it yours — either ask your agent to, or do it by hand: edit `.spec/project/spec.md` to
|
|
71
|
+
describe the project, point `spexcode.json`'s `lint.governedRoots` at your real source dir(s), and
|
|
72
|
+
check the graph:
|
|
29
73
|
|
|
30
74
|
```sh
|
|
31
|
-
cd ~/my-app
|
|
32
|
-
spex init # seed .spec/, a starter spexcode.json, and git hooks — nothing destructive
|
|
33
|
-
# 1. edit .spec/project/spec.md to describe your project
|
|
34
|
-
# 2. point spexcode.json's lint.governedRoots at your real source dir(s)
|
|
35
75
|
spex lint # the "coverage" warnings are your adoption TODO list
|
|
36
76
|
```
|
|
37
77
|
|
|
38
|
-
|
|
39
|
-
|
|
78
|
+
### Configure it
|
|
79
|
+
|
|
80
|
+
Two optional JSON files at the repo root hold every setting, split by portability:
|
|
81
|
+
|
|
82
|
+
- **`spexcode.json`** — *committed, portable*: layout, dashboard identity (`title` + `icon`), lint
|
|
83
|
+
budgets, and launcher **names**. Facts that are true for the project.
|
|
84
|
+
- **`spexcode.local.json`** — *gitignored, host-specific*: absolute launcher command paths,
|
|
85
|
+
cert/secret paths, and the private-overlay switch (`private: true`, which makes `spex materialize`
|
|
86
|
+
leave zero trace in the tracked tree — ignores go to the local git exclude, not the committed
|
|
87
|
+
`.gitignore` — so you can run SpexCode on a repo you share but don't own). Facts that are true for
|
|
88
|
+
one machine.
|
|
89
|
+
|
|
90
|
+
There is no `spex config set` — you (or your agent) edit the files directly. **`spex guide config`**
|
|
91
|
+
is the authoritative manual for every field and which of the two files it belongs in.
|
|
92
|
+
|
|
93
|
+
### Run it
|
|
94
|
+
|
|
95
|
+
Start the backend and the dashboard, then open the board:
|
|
40
96
|
|
|
41
97
|
```sh
|
|
42
|
-
spex serve
|
|
43
|
-
spex dashboard
|
|
98
|
+
spex serve # the backend (API + sessions), on :8787
|
|
99
|
+
spex dashboard # the board UI on :5173, proxying /api to the backend
|
|
44
100
|
```
|
|
45
101
|
|
|
46
|
-
|
|
47
|
-
|
|
102
|
+
Open <http://localhost:5173>.
|
|
103
|
+
|
|
104
|
+
Both ports are flags (`spex serve --port 8788`, `spex dashboard --port 5174 --api-port 8788`), so
|
|
105
|
+
you can run several projects' boards side by side; the working directory picks which project each
|
|
106
|
+
serves. Give each tab its own identity in that project's `spexcode.json`: `dashboard.title` names it
|
|
107
|
+
and `dashboard.icon` sets the favicon — an emoji (`"🔭"`), an Iconify name (`"mdi:rocket-launch"`),
|
|
108
|
+
or a URL.
|
|
48
109
|
|
|
49
|
-
Day to day:
|
|
110
|
+
Day to day (the commands your agent runs for you — and that you can run yourself):
|
|
50
111
|
|
|
51
112
|
| command | what it does |
|
|
52
113
|
| --- | --- |
|
|
53
114
|
| `spex lint` | check the spec↔code graph — coverage, drift, and the living-body rules |
|
|
54
115
|
| `spex watch` | stream session / board transitions as they happen |
|
|
55
|
-
| `spex guide` | print the full workflow, plus the `spec.md` / `yatsu.md`
|
|
116
|
+
| `spex guide` | print the full workflow, plus the `spec.md` / `yatsu.md` / `config` manuals |
|
|
56
117
|
| `spex board` | dump the current board state as JSON |
|
|
57
118
|
|
|
58
|
-
The spec tree is ground truth and git is its database: every change is a `spec.md` node, **rewritten in
|
|
59
|
-
place** (never a `## vN` changelog) and versioned by its commits.
|
|
60
|
-
|
|
61
119
|
---
|
|
62
120
|
|
|
63
121
|
## Contributing to SpexCode
|
|
64
122
|
|
|
65
|
-
This repository *is* the SpexCode source, and it
|
|
66
|
-
spec node merged into `main`. Set up a checkout:
|
|
123
|
+
This repository *is* the SpexCode source, and it dogfoods itself: every change to the tool lands as
|
|
124
|
+
a spec node merged into `main`. Set up a checkout:
|
|
67
125
|
|
|
68
126
|
```sh
|
|
69
127
|
git clone https://github.com/shuxueshuxue/spexcode && cd spexcode
|
|
@@ -72,15 +130,21 @@ npm --prefix spec-dashboard install
|
|
|
72
130
|
npm run hooks # install the per-clone git hooks (main-guard + the session-stamp hook)
|
|
73
131
|
```
|
|
74
132
|
|
|
75
|
-
The development loop runs from source, with hot
|
|
76
|
-
|
|
133
|
+
The development loop runs from source, with hot reload (`npm run web`, as opposed to an installed
|
|
134
|
+
user's `spex dashboard`):
|
|
77
135
|
|
|
78
136
|
```sh
|
|
79
137
|
npm run api # backend on :8787, hot-reloads on spec-cli/src changes
|
|
80
138
|
npm run web # the dashboard via Vite (HMR), proxying /api → :8787
|
|
81
139
|
```
|
|
82
140
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Credits
|
|
144
|
+
|
|
145
|
+
SpexCode was first introduced publicly on [LINUX DO](https://linux.do) — thanks to the community
|
|
146
|
+
there for the first round of discussion and feedback.
|
|
147
|
+
|
|
148
|
+
## License
|
|
149
|
+
|
|
150
|
+
[MIT](./LICENSE).
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<img src="docs/sdd-tuxedo-pooh.png" alt="写代码 vs. 维护一份活的、可执行的规格文档" width="420">
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | 中文
|
|
4
|
+
|
|
5
|
+
> Spec 驱动开发通常死于两种方式:spec 和代码脱节,或者膨胀成一堆过时的仪式文档。SpexCode 让每个
|
|
6
|
+
> spec 保持简短、保持当前态:原地重写,由 git 记版本,不做累积式的 changelog。
|
|
7
|
+
|
|
8
|
+
**SpexCode** 是一个 spec 驱动、用自己开发自己的开发工具。项目的每个部分都是一个带版本的
|
|
9
|
+
*spec 节点*,即一个 `.spec/**/spec.md`,正文描述这个部分当前的意图。git 就是数据库:节点的版本号
|
|
10
|
+
是它的内容 commit 数,"drift" 指被管辖的代码走到了 spec 前面。`spex` CLI 和实时看板直接从 git
|
|
11
|
+
读取这一切,没有第二份存储。
|
|
12
|
+
|
|
13
|
+
- **[使用 SpexCode](#使用-spexcode)**:安装 `spex` CLI,通过你的 coding agent 治理*你自己的*项目。
|
|
14
|
+
- **[参与开发](#参与开发)**:在本仓库里改进工具本身。
|
|
15
|
+
- **[Working with agents →](https://spexcode.net/working-with-agents/)**:文档站上更完整的介绍。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 使用 SpexCode
|
|
20
|
+
|
|
21
|
+
SpexCode 只需要设置一次(`npm i -g spexcode`,然后在你的仓库里 `spex init`)。之后的主要用法是和
|
|
22
|
+
你的 coding agent 对话:用自然语言说你要什么(*"给鉴权流程加一个 spec 节点""把这个包的 spec
|
|
23
|
+
提取出来""派一个 worker 去实现 Y"*),agent 替你执行 `spex` CLI,你在看板上盯进度。下面的手动
|
|
24
|
+
CLI 是底座,agent 才是日常界面。
|
|
25
|
+
|
|
26
|
+
这套之所以成立,是因为新启动的 agent 已经认识 SpexCode。`spex init` 会把整套约定(spec 节点流程、
|
|
27
|
+
先 commit 再声明完成、merge 方式)生成到仓库 `CLAUDE.md`/`AGENTS.md` 里的 `<!-- spexcode -->`
|
|
28
|
+
托管块中,**[Claude Code](https://www.anthropic.com/claude-code)** 和 **Codex** 启动时自动读到。
|
|
29
|
+
更细的内容 agent 自己按需查内置手册:`spex guide`(工作流)、`spex guide spec` /
|
|
30
|
+
`spex guide yatsu`(文件格式)、`spex guide config`(`spexcode.json` 的全部配置项)。你可以直接
|
|
31
|
+
对它说*"跑一下 `spex guide config`,帮我配好 launcher"*。
|
|
32
|
+
|
|
33
|
+
完整的说明在文档站:**[working with agents](https://spexcode.net/working-with-agents/)** 讲
|
|
34
|
+
agent 驱动的用法,**[getting started](https://spexcode.net/getting-started/)** 从头到尾走一遍安装。
|
|
35
|
+
|
|
36
|
+
> **抛开 agent,它也是一套普通工具。** 核心部分单独用也成立:git 记版本的 spec 文件,`spex lint`
|
|
37
|
+
> 检查,只读看板展示。不含 AI,除了 Node 和 git 什么都不用跑。vibe coding 的玩法建立在这层之上,
|
|
38
|
+
> 不是替代它。
|
|
39
|
+
|
|
40
|
+
> **环境要求。** 核心:**Node ≥ 22** 和 **git**。要通过 agent 驱动(或者往节点上派 worker),
|
|
41
|
+
> 还需要 **tmux** 和 PATH 里已登录的 **Claude Code 或 Codex**。这些 agent 会在你的机器上执行
|
|
42
|
+
> 命令,对外暴露后端之前请先读 [`SECURITY.md`](./docs/SECURITY.md)。
|
|
43
|
+
|
|
44
|
+
### 安装
|
|
45
|
+
|
|
46
|
+
装一次发布版 CLI,然后在任何项目里接入:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
npm i -g spexcode # 安装 spex 命令(需要 Node ≥ 22)
|
|
50
|
+
cd ~/my-app
|
|
51
|
+
spex init # 纯增量,不会动你的代码结构
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`spex init` 是增量式的:生成一棵起始 **`.spec/`** 树(根 `project` 节点,加上定义开发流程的
|
|
55
|
+
`.config` 插件)、一份起始 **`spexcode.json`**、每个 clone 各自的 **git hooks**(`pre-commit`
|
|
56
|
+
跑 **spec-lint**,spec↔code 链接坏了会拦下;**main-guard** 拦截直接向 `main` 的提交;
|
|
57
|
+
`prepare-commit-msg` 给每个 commit 盖 session 归属戳)。同时生成 agent 路径需要的产物:
|
|
58
|
+
`CLAUDE.md`/`AGENTS.md` 里的 `<!-- spexcode -->` 契约块,以及 `.claude/`、`.codex/` 的
|
|
59
|
+
settings hooks。这些产物是生成物,已被 gitignore,每台机器各自重新生成,不进版本库。
|
|
60
|
+
|
|
61
|
+
然后把它变成你的项目,让 agent 改或者自己动手都行:编辑 `.spec/project/spec.md` 描述项目,把
|
|
62
|
+
`spexcode.json` 的 `lint.governedRoots` 指向真实源码目录,再检查一遍:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
spex lint # coverage 警告就是你的接入 TODO 清单
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 配置
|
|
69
|
+
|
|
70
|
+
仓库根目录两个可选的 JSON 文件承载全部设置,按可移植性分开:
|
|
71
|
+
|
|
72
|
+
- **`spexcode.json`**:提交进仓库,可移植。布局、看板标识(`title` + `icon`)、lint 预算、
|
|
73
|
+
launcher 的**名字**。对整个项目成立的事实。
|
|
74
|
+
- **`spexcode.local.json`**:gitignore,单机。launcher 命令的绝对路径、证书和密钥路径、私有覆盖
|
|
75
|
+
开关(`private: true`,让 `spex materialize` 在被跟踪的文件里不留任何痕迹,ignore 写进本地
|
|
76
|
+
git exclude 而不是提交的 `.gitignore`,适合你参与但不拥有的仓库)。只对这台机器成立的事实。
|
|
77
|
+
|
|
78
|
+
没有 `spex config set`,你(或你的 agent)直接编辑文件。每个字段的含义、该放哪个文件,
|
|
79
|
+
**`spex guide config`** 是权威手册。
|
|
80
|
+
|
|
81
|
+
### 运行
|
|
82
|
+
|
|
83
|
+
启动后端和看板,打开页面:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
spex serve # 后端(API + sessions),:8787
|
|
87
|
+
spex dashboard # 看板 UI,:5173,/api 代理到后端
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
打开 <http://localhost:5173>。
|
|
91
|
+
|
|
92
|
+
两个端口都是参数(`spex serve --port 8788`、`spex dashboard --port 5174 --api-port 8788`),
|
|
93
|
+
多个项目的看板可以并排跑,工作目录决定各自服务哪个项目。每个标签页的身份在各自项目的
|
|
94
|
+
`spexcode.json` 里配:`dashboard.title` 定名字,`dashboard.icon` 定 favicon,emoji(`"🔭"`)、
|
|
95
|
+
Iconify 名字(`"mdi:rocket-launch"`)或 URL 都行。
|
|
96
|
+
|
|
97
|
+
日常命令(agent 替你跑,你也可以自己跑):
|
|
98
|
+
|
|
99
|
+
| 命令 | 作用 |
|
|
100
|
+
| --- | --- |
|
|
101
|
+
| `spex lint` | 检查 spec↔code 图:coverage、drift、living 规则 |
|
|
102
|
+
| `spex watch` | 实时输出 session / 看板的状态变化 |
|
|
103
|
+
| `spex guide` | 打印完整工作流,以及 `spec.md` / `yatsu.md` / `config` 手册 |
|
|
104
|
+
| `spex board` | 以 JSON 输出当前看板状态 |
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 参与开发
|
|
109
|
+
|
|
110
|
+
这个仓库就是 SpexCode 的源码,而且它 dogfood 自己:工具的每个改动都以 spec 节点的形式合入
|
|
111
|
+
`main`。搭一个开发环境:
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
git clone https://github.com/shuxueshuxue/spexcode && cd spexcode
|
|
115
|
+
npm --prefix spec-cli install
|
|
116
|
+
npm --prefix spec-dashboard install
|
|
117
|
+
npm run hooks # 安装每个 clone 各自的 git hooks(main-guard + session 戳)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
开发循环直接跑源码,带热重载(开发用 `npm run web`,区别于安装用户的 `spex dashboard`):
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
npm run api # 后端 :8787,spec-cli/src 改动即热重载
|
|
124
|
+
npm run web # Vite 起看板(HMR),/api 代理到 :8787
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 致谢
|
|
130
|
+
|
|
131
|
+
SpexCode 的首次公开介绍发在 [LINUX DO](https://linux.do),感谢佬友们的第一轮讨论和反馈。
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
[MIT](./LICENSE)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spexcode",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "SpexCode — a spec-driven, self-developing dev tool. The `spex` CLI + spec server reads the .spec tree and its git history, and serves the dashboard.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -24,12 +24,11 @@
|
|
|
24
24
|
"node": ">=22"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
|
-
"api": "
|
|
28
|
-
"web": "
|
|
29
|
-
"lint": "
|
|
30
|
-
"seed": "bash scripts/seed-spec-history.sh",
|
|
27
|
+
"api": "cd spec-cli && npm run serve",
|
|
28
|
+
"web": "cd spec-dashboard && npm run dev",
|
|
29
|
+
"lint": "cd spec-cli && npm run -s lint",
|
|
31
30
|
"hooks": "bash scripts/install-hooks.sh",
|
|
32
|
-
"
|
|
31
|
+
"prepack": "node scripts/prepack.mjs",
|
|
33
32
|
"postinstall": "node -e \"try{require('fs').readdirSync('node_modules/node-pty/prebuilds').forEach(d=>{try{require('fs').chmodSync('node_modules/node-pty/prebuilds/'+d+'/spawn-helper',0o755)}catch{}})}catch{}\""
|
|
34
33
|
},
|
|
35
34
|
"dependencies": {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<img src="docs/sdd-tuxedo-pooh.png" alt="Writing code vs. authoring a living, executable specification artifact" width="420">
|
|
2
|
+
|
|
3
|
+
> Spec-driven development gets wrecked by spec drift and spec bloat. SpexCode's bet
|
|
4
|
+
> is to keep the spec the cheap, honest twin of the code — rewritten in place, never
|
|
5
|
+
> a tuxedo of stale ceremony.
|
|
6
|
+
|
|
7
|
+
**SpexCode** is a spec-driven, self-developing dev tool. Every part of a project becomes a versioned
|
|
8
|
+
*spec node* — a `.spec/**/spec.md` whose body states the part's *present* intent — and **git is the
|
|
9
|
+
database**: a node's version is its count of content commits, and "drift" is governed code that moved
|
|
10
|
+
ahead of its spec. A `spex` CLI plus a live dashboard read all of it straight from git; there is no
|
|
11
|
+
separate store.
|
|
12
|
+
|
|
13
|
+
There are two ways to meet SpexCode, and they are kept separate on purpose:
|
|
14
|
+
|
|
15
|
+
- **[Using SpexCode](#using-spexcode)** — install the `spex` CLI from npm and govern *your own* project.
|
|
16
|
+
- **[Contributing to SpexCode](#contributing-to-spexcode)** — develop the tool itself, in this repo.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Using SpexCode
|
|
21
|
+
|
|
22
|
+
You don't clone this repo to *use* SpexCode. Install the published CLI once, then point it at any project.
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
npm i -g spexcode # installs the `spex` command (needs Node ≥ 22)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Adopt it in your project — `spex init` is **additive**, it never restructures your code:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
cd ~/my-app
|
|
32
|
+
spex init # seed .spec/, a starter spexcode.json, and git hooks — nothing destructive
|
|
33
|
+
# 1. edit .spec/project/spec.md to describe your project
|
|
34
|
+
# 2. point spexcode.json's lint.governedRoots at your real source dir(s)
|
|
35
|
+
spex lint # the "coverage" warnings are your adoption TODO list
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Run it. The backend and the dashboard are **two commands on two ports**, so several projects can run
|
|
39
|
+
side by side on one host (the cwd picks which project is served):
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
spex serve --port 8788 # the backend (API + sessions) for THIS repo
|
|
43
|
+
spex dashboard --port 5174 --api-port 8788 # the board UI, pointed at that backend
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then open <http://localhost:5174>. With no flags, `spex serve` defaults to `:8787` and `spex dashboard`
|
|
47
|
+
to `:5173`.
|
|
48
|
+
|
|
49
|
+
Day to day:
|
|
50
|
+
|
|
51
|
+
| command | what it does |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `spex lint` | check the spec↔code graph — coverage, drift, and the living-body rules |
|
|
54
|
+
| `spex watch` | stream session / board transitions as they happen |
|
|
55
|
+
| `spex guide` | print the full workflow, plus the `spec.md` / `yatsu.md` file-format manuals |
|
|
56
|
+
| `spex board` | dump the current board state as JSON |
|
|
57
|
+
|
|
58
|
+
The spec tree is ground truth and git is its database: every change is a `spec.md` node, **rewritten in
|
|
59
|
+
place** (never a `## vN` changelog) and versioned by its commits.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Contributing to SpexCode
|
|
64
|
+
|
|
65
|
+
This repository *is* the SpexCode source, and it **dogfoods itself**: every change to the tool lands as a
|
|
66
|
+
spec node merged into `main`. Set up a checkout:
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
git clone https://github.com/shuxueshuxue/spexcode && cd spexcode
|
|
70
|
+
npm --prefix spec-cli install
|
|
71
|
+
npm --prefix spec-dashboard install
|
|
72
|
+
npm run hooks # install the per-clone git hooks (main-guard + the session-stamp hook)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The development loop runs from source, with hot-reload — this is what `npm run web` is for, as opposed
|
|
76
|
+
to an installed user's `spex dashboard`:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
npm run api # backend on :8787, hot-reloads on spec-cli/src changes
|
|
80
|
+
npm run web # the dashboard via Vite (HMR), proxying /api → :8787
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The contribution ritual in one breath: branch `node/<id>` off `main`, make the code change **and** its
|
|
84
|
+
`spec.md` *together*, commit, then `spex session done --propose merge` — a human performs the `--no-ff`
|
|
85
|
+
merge. That ritual, the spec-node model, the lint rules, and the reflexive config system are all spelled
|
|
86
|
+
out in **[`CLAUDE.md`](./CLAUDE.md)** — read it before your first change.
|
package/spec-cli/bin/spex.mjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// run the TypeScript CLI directly. After `npm link` (or a global install) `spex lint` works anywhere.
|
|
4
4
|
import { spawn } from 'node:child_process'
|
|
5
5
|
import { existsSync } from 'node:fs'
|
|
6
|
+
import { createRequire } from 'node:module'
|
|
6
7
|
import { fileURLToPath } from 'node:url'
|
|
7
8
|
import { dirname, join } from 'node:path'
|
|
8
9
|
|
|
@@ -11,9 +12,20 @@ import { dirname, join } from 'node:path'
|
|
|
11
12
|
// `spex` work from any cwd (agents, git hooks) against this package's code, operating on the cwd.
|
|
12
13
|
const pkg = join(dirname(fileURLToPath(import.meta.url)), '..') // spec-cli/
|
|
13
14
|
const cli = join(pkg, 'src', 'cli.ts')
|
|
14
|
-
// tsx lives in spec-cli/node_modules in the dev monorepo, but
|
|
15
|
-
//
|
|
15
|
+
// tsx lives in spec-cli/node_modules in the dev monorepo, but npm may hoist it above the installed
|
|
16
|
+
// `spexcode` package in a real consumer project. Try local candidates first, then let Node resolve upward
|
|
17
|
+
// from spec-cli so project-local and global installs work the same way.
|
|
16
18
|
const tsxCandidates = [join(pkg, 'node_modules', '.bin', 'tsx'), join(pkg, '..', 'node_modules', '.bin', 'tsx')]
|
|
17
|
-
|
|
19
|
+
function resolveTsx() {
|
|
20
|
+
const local = tsxCandidates.find(existsSync)
|
|
21
|
+
if (local) return local
|
|
22
|
+
try {
|
|
23
|
+
const req = createRequire(join(pkg, 'package.json'))
|
|
24
|
+
return join(dirname(req.resolve('tsx/package.json')), 'dist', 'cli.mjs')
|
|
25
|
+
} catch {
|
|
26
|
+
return tsxCandidates[0]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const tsx = resolveTsx()
|
|
18
30
|
spawn(tsx, [cli, ...process.argv.slice(2)], { stdio: 'inherit' })
|
|
19
31
|
.on('exit', (code) => process.exit(code ?? 0))
|
|
@@ -17,10 +17,12 @@
|
|
|
17
17
|
# the harness propagates. Pure bash, no node boot on the hot path (node runs only inside the gate, only
|
|
18
18
|
# on actual change). cwd = the project/worktree. $SPEX (abs tsx+cli) is inherited from the shim env.
|
|
19
19
|
set -u
|
|
20
|
-
# args: `<harness> <Event>`. A harness id as $1 (claude|codex) is consumed; otherwise we keep $1 as the
|
|
21
|
-
# and default the harness to claude — so a stale shim still rendered as `dispatch.sh <Event>` keeps working.
|
|
20
|
+
# args: `<harness> <Event>`. A harness id as $1 (claude|codex|plugin) is consumed; otherwise we keep $1 as the
|
|
21
|
+
# event and default the harness to claude — so a stale shim still rendered as `dispatch.sh <Event>` keeps working.
|
|
22
|
+
# `plugin` is the bundle form ([[plugin-harness]]): it parses payloads as the claude family in harness.sh (z-code/
|
|
23
|
+
# Claude share Claude's tool names + file_path), so it joins the claude branch there via the default case.
|
|
22
24
|
harness=claude
|
|
23
|
-
case "${1:-}" in claude|codex) harness="$1"; shift ;; esac
|
|
25
|
+
case "${1:-}" in claude|codex|plugin) harness="$1"; shift ;; esac
|
|
24
26
|
event="${1:?usage: dispatch.sh <harness> <Event>}"
|
|
25
27
|
export SPEXCODE_HARNESS="$harness"
|
|
26
28
|
# the harness.sh path (the adapter's shell mirror) — sibling of this script; hook handlers source it, and we
|
|
@@ -38,11 +40,21 @@ rt="$(cd "$proj" 2>/dev/null && hp_runtime_dir)" || rt=""
|
|
|
38
40
|
cur="$( (cd "$proj" 2>/dev/null && hp_config_hash) )"
|
|
39
41
|
if [ -n "$rt" ] && [ -n "$cur" ] && [ "$cur" != "$(cat "$rt/content-hash" 2>/dev/null || true)" ]; then
|
|
40
42
|
mkdir -p "$rt" 2>/dev/null
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
# POSIX-portable mutex: mkdir is atomic on every POSIX fs, so it serializes the render with NO dependency on
|
|
44
|
+
# util-linux `flock` (absent on macOS — where it silently no-op'd, letting concurrent sessions race the write).
|
|
45
|
+
# Spin on `mkdir` with a bounded wait; after ~10s (200 * 0.05s, well above the ~0.85s materialize) treat the
|
|
46
|
+
# dir as orphaned by a dead entrant and clear it, so a stale lock can never deadlock a launch. Held only across
|
|
47
|
+
# the re-check + render, released right after.
|
|
48
|
+
lockd="$rt/.materialize.lock.d"
|
|
49
|
+
_lk=0
|
|
50
|
+
until mkdir "$lockd" 2>/dev/null; do
|
|
51
|
+
_lk=$((_lk+1)); [ "$_lk" -ge 200 ] && { rm -rf "$lockd" 2>/dev/null; _lk=0; }
|
|
52
|
+
sleep 0.05
|
|
53
|
+
done
|
|
54
|
+
if [ "$cur" != "$(cat "$rt/content-hash" 2>/dev/null || true)" ]; then # re-check: a sibling dispatch may have just rendered
|
|
55
|
+
( cd "$proj" && ${SPEX:-spex} materialize >/dev/null 2>&1 )
|
|
56
|
+
fi
|
|
57
|
+
rmdir "$lockd" 2>/dev/null
|
|
46
58
|
fi
|
|
47
59
|
|
|
48
60
|
# --- (2) dispatch ---------------------------------------------------------------------------------------
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
# HERE; the hook scripts stay harness-agnostic and just call hp_* (the one place Claude's tool names appear in
|
|
7
7
|
# shell, plus codex's Bash-command mapping). The session-id + global-store resolution is harness-agnostic and
|
|
8
8
|
# lives here too, so the six hooks no longer each repeat the git-common-dir → project-key dance.
|
|
9
|
+
# SPEXCODE_HARNESS is claude|codex|plugin: `plugin` is the bundle form ([[plugin-harness]]) and its host (z-code/
|
|
10
|
+
# Claude) shares Claude's payload shape, so every `case "$SPEXCODE_HARNESS"` below routes it through the claude
|
|
11
|
+
# branch via the default case — there is no separate `plugin)` arm to maintain.
|
|
9
12
|
|
|
10
13
|
# the string value of a top-level JSON string field (first match). Harness-agnostic — both harnesses' payloads
|
|
11
14
|
# carry session_id / tool_name as plain string fields. $1 = payload, $2 = field name. The value is scanned as a
|
|
@@ -54,13 +57,16 @@ hp_field() {
|
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
# the session id from a payload (both harnesses use session_id).
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
# Codex hooks run inside ONE shared per-project app-server. That process can inherit the FIRST launched
|
|
61
|
+
# session's SPEXCODE_SESSION_ID, so on codex the payload session_id (the acting thread id) must win and then
|
|
62
|
+
# hp_store_dir aliases it to the governed record. Claude's payload id already equals its governed record id,
|
|
63
|
+
# so preferring SPEXCODE_SESSION_ID there preserves the launcher's pinned id path.
|
|
64
|
+
hp_session_id() {
|
|
65
|
+
case "$SPEXCODE_HARNESS" in
|
|
66
|
+
codex) hp_field "$1" session_id ;;
|
|
67
|
+
*) printf '%s' "${SPEXCODE_SESSION_ID:-$(hp_field "$1" session_id)}" ;;
|
|
68
|
+
esac
|
|
69
|
+
}
|
|
64
70
|
|
|
65
71
|
# the per-PROJECT GLOBAL runtime dir (mirrors spec-cli/src/layout.ts `runtimeRoot`): <store>/projects/<enc>,
|
|
66
72
|
# keyed by the project (dirname of the ABSOLUTE git-common-dir, so the answer is identical from main or any
|
|
@@ -75,10 +81,11 @@ hp_runtime_dir() {
|
|
|
75
81
|
|
|
76
82
|
# the per-session GLOBAL store dir for a session id — <runtime>/sessions/<id> (sibling of the per-project
|
|
77
83
|
# runtime above). Echoes the dir; returns non-zero (echoing nothing) when git can't resolve.
|
|
78
|
-
# ALIAS resolution: a codex hook fires from the shared per-PROJECT app-server process, whose env
|
|
79
|
-
# SPEXCODE_SESSION_ID
|
|
80
|
-
# SpexCode record id the dir is keyed by. So when no
|
|
81
|
-
#
|
|
84
|
+
# ALIAS resolution: a codex hook fires from the shared per-PROJECT app-server process, whose env may carry a
|
|
85
|
+
# stale SPEXCODE_SESSION_ID from the first launched codex session. hp_session_id therefore returns the acting
|
|
86
|
+
# payload session_id on codex: the codex THREAD id, NOT the SpexCode record id the dir is keyed by. So when no
|
|
87
|
+
# record sits at <id> directly, find the one record that captured this id as `harness_session_id` (the backend
|
|
88
|
+
# stored it at thread/start, before the first tool turn).
|
|
82
89
|
# A grep over the few session.json files — no jq on the hot path; the trailing quote anchors the value so a
|
|
83
90
|
# thread id can't match a longer one as a prefix. Direct hit wins; a miss with no alias echoes the direct path
|
|
84
91
|
# unchanged, so the caller's `[ -e "$rec" ]` still no-ops gracefully. Mirrors layout.ts `readAliasedRawRecord`.
|