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.
Files changed (73) hide show
  1. package/README.md +99 -35
  2. package/README.zh-CN.md +135 -0
  3. package/package.json +5 -6
  4. package/spec-cli/README.md +86 -0
  5. package/spec-cli/bin/spex.mjs +15 -3
  6. package/spec-cli/hooks/dispatch.sh +20 -8
  7. package/spec-cli/hooks/harness.sh +18 -11
  8. package/spec-cli/src/board.ts +47 -18
  9. package/spec-cli/src/boardCache.ts +70 -0
  10. package/spec-cli/src/boardDelta.ts +90 -0
  11. package/spec-cli/src/boardStream.ts +178 -0
  12. package/spec-cli/src/cli.ts +184 -122
  13. package/spec-cli/src/client.ts +6 -4
  14. package/spec-cli/src/gateway.ts +64 -24
  15. package/spec-cli/src/git.ts +105 -92
  16. package/spec-cli/src/guide.ts +186 -19
  17. package/spec-cli/src/harness-select.ts +63 -0
  18. package/spec-cli/src/harness.ts +506 -100
  19. package/spec-cli/src/help.ts +362 -0
  20. package/spec-cli/src/hooks.ts +0 -14
  21. package/spec-cli/src/index.ts +279 -32
  22. package/spec-cli/src/init.ts +41 -1
  23. package/spec-cli/src/issues.ts +301 -0
  24. package/spec-cli/src/layout.ts +70 -28
  25. package/spec-cli/src/lint.ts +12 -10
  26. package/spec-cli/src/listen.ts +28 -0
  27. package/spec-cli/src/localIssues.ts +700 -0
  28. package/spec-cli/src/materialize.ts +182 -27
  29. package/spec-cli/src/mentions.ts +192 -0
  30. package/spec-cli/src/plugin-harness.ts +145 -0
  31. package/spec-cli/src/pty-bridge.ts +378 -81
  32. package/spec-cli/src/self.ts +123 -20
  33. package/spec-cli/src/sessions.ts +461 -298
  34. package/spec-cli/src/specs.ts +55 -14
  35. package/spec-cli/src/supervise.ts +23 -3
  36. package/spec-cli/src/tsx-bin.ts +14 -5
  37. package/spec-cli/src/uninstall.ts +146 -0
  38. package/spec-cli/templates/hooks/post-merge +27 -0
  39. package/spec-cli/templates/hooks/pre-commit +51 -31
  40. package/spec-cli/templates/hooks/prepare-commit-msg +31 -8
  41. package/spec-cli/templates/presets/careful/.config/clarify-before-code/spec.md +11 -0
  42. package/spec-cli/templates/spec/project/.config/core/spec-of-file/spec-of-file.sh +26 -3
  43. package/spec-cli/templates/spec/project/.config/core/spec.md +4 -0
  44. package/spec-cli/templates/spec/project/.config/core/stop-gate/stop-gate.sh +16 -4
  45. package/spec-cli/templates/spec/project/.config/extract/spec.md +1 -1
  46. package/spec-cli/templates/spec/project/.config/regroup/spec.md +1 -1
  47. package/spec-cli/templates/spec/project/.config/reproduce-before-fix/spec.md +18 -0
  48. package/spec-cli/templates/spec/project/.config/spec.md +3 -3
  49. package/spec-cli/templates/spec/project/.config/supervisor/spec.md +2 -2
  50. package/spec-cli/templates/spec/project/.config/tidy/spec.md +1 -1
  51. package/spec-cli/templates/spec/project/spec.md +2 -2
  52. package/spec-dashboard/dist/assets/index-Ct_ubwrd.css +32 -0
  53. package/spec-dashboard/dist/assets/index-DehTZ-h9.js +145 -0
  54. package/spec-dashboard/dist/index.html +17 -5
  55. package/spec-forge/src/cache.ts +16 -0
  56. package/spec-forge/src/cli.ts +4 -10
  57. package/spec-forge/src/drivers/github.ts +74 -4
  58. package/spec-forge/src/drivers.ts +13 -0
  59. package/spec-forge/src/port.ts +25 -0
  60. package/spec-forge/src/resident.ts +40 -6
  61. package/spec-yatsu/src/cli.ts +227 -38
  62. package/spec-yatsu/src/evaltab.ts +169 -19
  63. package/spec-yatsu/src/filing.ts +48 -0
  64. package/spec-yatsu/src/freshness.ts +55 -20
  65. package/spec-yatsu/src/proof.ts +89 -3
  66. package/spec-yatsu/src/scenariofresh.ts +92 -0
  67. package/spec-yatsu/src/sidecar.ts +75 -11
  68. package/spec-yatsu/src/timeline.ts +47 -0
  69. package/spec-yatsu/src/yatsu.ts +47 -3
  70. package/spec-cli/src/relay.ts +0 -28
  71. package/spec-cli/templates/spec/project/.config/scenario/spec.md +0 -32
  72. package/spec-dashboard/dist/assets/index-Bk4E1EQy.js +0 -139
  73. 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
- > 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.
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* 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
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
- 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.
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 don't clone this repo to *use* SpexCode. Install the published CLI once, then point it at any project.
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
- Adopt it in your project — `spex init` is **additive**, it never restructures your code:
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
- 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):
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 --port 8788 # the backend (API + sessions) for THIS repo
43
- spex dashboard --port 5174 --api-port 8788 # the board UI, pointed at that backend
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
- Then open <http://localhost:5174>. With no flags, `spex serve` defaults to `:8787` and `spex dashboard`
47
- to `:5173`.
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` file-format manuals |
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 **dogfoods itself**: every change to the tool lands as a
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-reload — this is what `npm run web` is for, as opposed
76
- to an installed user's `spex dashboard`:
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
- 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.
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).
@@ -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.6",
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": "npm --prefix spec-cli run serve",
28
- "web": "npm --prefix spec-dashboard run dev",
29
- "lint": "npm --prefix spec-cli run -s 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
- "prepublishOnly": "node scripts/prepublish.mjs",
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.
@@ -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 at the PUBLISHED package root's
15
- // node_modules (one level up: spec-cli is a subdir of the `spexcode` tarball) when installed. Try both.
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
- const tsx = tsxCandidates.find(existsSync) ?? tsxCandidates[0]
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 event
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
- ( flock 9
42
- if [ "$cur" != "$(cat "$rt/content-hash" 2>/dev/null || true)" ]; then # re-check: a sibling dispatch may have just rendered
43
- ( cd "$proj" && ${SPEX:-spex} materialize >/dev/null 2>&1 )
44
- fi
45
- ) 9>"$rt/.materialize.lock"
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
- # the GOVERNED record id the launcher baked into the session env (SPEXCODE_SESSION_ID) wins over the harness's
58
- # own payload session_id. This is what lets a dashboard-launched CODEX session feed its governed record: codex
59
- # mints its OWN thread id (un-pinnable), so the record can't be keyed by it instead the launcher keys the
60
- # record by a SpexCode id and exports it into the launch, and every hook resolves THAT. Claude's payload id is
61
- # already the record id, so the env (= same value) is a harmless no-op there. A self-launched agent sets no
62
- # env → falls back to the payload session_id (its own non-governed record). One resolver, both harnesses.
63
- hp_session_id() { printf '%s' "${SPEXCODE_SESSION_ID:-$(hp_field "$1" session_id)}"; }
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 carries NO
79
- # SPEXCODE_SESSION_ID, so hp_session_id falls back to the payload session_id = the codex THREAD id NOT the
80
- # SpexCode record id the dir is keyed by. So when no record sits at <id> directly, find the one record that
81
- # captured this id as `harness_session_id` (the backend stored it at thread/start, before the first tool turn).
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`.