@shirlytaylor73/superharness 1.5.0
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/LICENSE +21 -0
- package/README.md +202 -0
- package/bin/lib/codex-installer.js +228 -0
- package/bin/lib/interactive-select.js +96 -0
- package/bin/superharness.js +67 -0
- package/package.json +52 -0
- package/plugins/superharness/.claude-plugin/plugin.json +19 -0
- package/plugins/superharness/.codex-plugin/plugin.json +31 -0
- package/plugins/superharness/.mcp.json +9 -0
- package/plugins/superharness/CODE_OF_CONDUCT.md +79 -0
- package/plugins/superharness/LICENSE +21 -0
- package/plugins/superharness/README.md +57 -0
- package/plugins/superharness/agents/code-reviewer.md +48 -0
- package/plugins/superharness/archived-skills/using-superpowers/SKILL.md +140 -0
- package/plugins/superharness/archived-skills/using-superpowers/references/codex-tools.md +25 -0
- package/plugins/superharness/archived-skills/using-superpowers/references/copilot-tools.md +52 -0
- package/plugins/superharness/archived-skills/using-superpowers/references/gemini-tools.md +33 -0
- package/plugins/superharness/archived-skills/using-superpowers/references/hermes-tools.md +44 -0
- package/plugins/superharness/commands/free.md +6 -0
- package/plugins/superharness/commands/rollback.md +30 -0
- package/plugins/superharness/commands-codex/free.md +29 -0
- package/plugins/superharness/commands-codex/rollback.md +33 -0
- package/plugins/superharness/hooks/hooks-codex.json +50 -0
- package/plugins/superharness/hooks/hooks.json +50 -0
- package/plugins/superharness/hooks/lib/free-mode-check.mjs +27 -0
- package/plugins/superharness/hooks/run-hook.cmd +58 -0
- package/plugins/superharness/hooks/workflow-context +4 -0
- package/plugins/superharness/hooks/workflow-context.mjs +184 -0
- package/plugins/superharness/hooks/workflow-post-transition +4 -0
- package/plugins/superharness/hooks/workflow-post-transition.mjs +89 -0
- package/plugins/superharness/hooks/workflow-pre-tool-use +4 -0
- package/plugins/superharness/hooks/workflow-pre-tool-use.mjs +97 -0
- package/plugins/superharness/hooks/workflow-stop +4 -0
- package/plugins/superharness/hooks/workflow-stop.mjs +136 -0
- package/plugins/superharness/scripts/rollback.mjs +86 -0
- package/plugins/superharness/scripts/set-free-mode.mjs +77 -0
- package/plugins/superharness/skills/brainstorming/SKILL.md +182 -0
- package/plugins/superharness/skills/brainstorming/scripts/frame-template.html +214 -0
- package/plugins/superharness/skills/brainstorming/scripts/helper.js +88 -0
- package/plugins/superharness/skills/brainstorming/scripts/server.cjs +338 -0
- package/plugins/superharness/skills/brainstorming/scripts/start-server.sh +153 -0
- package/plugins/superharness/skills/brainstorming/scripts/stop-server.sh +55 -0
- package/plugins/superharness/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/plugins/superharness/skills/brainstorming/visual-companion.md +286 -0
- package/plugins/superharness/skills/chinese-code-review/SKILL.md +277 -0
- package/plugins/superharness/skills/chinese-commit-conventions/SKILL.md +364 -0
- package/plugins/superharness/skills/chinese-documentation/SKILL.md +448 -0
- package/plugins/superharness/skills/chinese-git-workflow/SKILL.md +547 -0
- package/plugins/superharness/skills/dispatching-parallel-agents/SKILL.md +186 -0
- package/plugins/superharness/skills/exploration/SKILL.md +197 -0
- package/plugins/superharness/skills/finishing/SKILL.md +200 -0
- package/plugins/superharness/skills/intake/SKILL.md +134 -0
- package/plugins/superharness/skills/mcp-builder/SKILL.md +255 -0
- package/plugins/superharness/skills/parallel-execution/SKILL.md +368 -0
- package/plugins/superharness/skills/parallel-execution/implementer-prompt.md +144 -0
- package/plugins/superharness/skills/parallel-execution/spec-reviewer-prompt.md +84 -0
- package/plugins/superharness/skills/parallel-execution/wave-final-manual-qa-prompt.md +61 -0
- package/plugins/superharness/skills/parallel-execution/wave-final-quality-prompt.md +59 -0
- package/plugins/superharness/skills/parallel-execution/wave-final-scope-fidelity-prompt.md +69 -0
- package/plugins/superharness/skills/parallel-execution/wave-final-spec-prompt.md +56 -0
- package/plugins/superharness/skills/planning/SKILL.md +265 -0
- package/plugins/superharness/skills/planning/plan-document-reviewer-prompt.md +80 -0
- package/plugins/superharness/skills/receiving-code-review/SKILL.md +213 -0
- package/plugins/superharness/skills/requesting-code-review/SKILL.md +107 -0
- package/plugins/superharness/skills/requesting-code-review/code-reviewer.md +146 -0
- package/plugins/superharness/skills/serial-execution/SKILL.md +183 -0
- package/plugins/superharness/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/plugins/superharness/skills/systematic-debugging/SKILL.md +320 -0
- package/plugins/superharness/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/plugins/superharness/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/plugins/superharness/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/plugins/superharness/skills/systematic-debugging/find-polluter.sh +63 -0
- package/plugins/superharness/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/plugins/superharness/skills/systematic-debugging/test-academic.md +14 -0
- package/plugins/superharness/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/plugins/superharness/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/plugins/superharness/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/plugins/superharness/skills/test-driven-development/SKILL.md +371 -0
- package/plugins/superharness/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/plugins/superharness/skills/trivial/SKILL.md +118 -0
- package/plugins/superharness/skills/using-git-worktrees/SKILL.md +218 -0
- package/plugins/superharness/skills/verification/SKILL.md +139 -0
- package/plugins/superharness/skills/workflow-runner/SKILL.md +172 -0
- package/plugins/superharness/skills/writing-skills/SKILL.md +655 -0
- package/plugins/superharness/skills/writing-skills/anthropic-best-practices.md +1149 -0
- package/plugins/superharness/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/plugins/superharness/skills/writing-skills/graphviz-conventions.dot +172 -0
- package/plugins/superharness/skills/writing-skills/persuasion-principles.md +187 -0
- package/plugins/superharness/skills/writing-skills/render-graphs.js +168 -0
- package/plugins/superharness/skills/writing-skills/testing-skills-with-subagents.md +385 -0
- package/plugins/superharness/workflow/default-workflow.yaml +84 -0
- package/plugins/superharness/workflow-state-server/bootstrap.js +44 -0
- package/plugins/superharness/workflow-state-server/package-lock.json +2853 -0
- package/plugins/superharness/workflow-state-server/package.json +22 -0
- package/plugins/superharness/workflow-state-server/render-context.js +124 -0
- package/plugins/superharness/workflow-state-server/schema.sql +39 -0
- package/plugins/superharness/workflow-state-server/server.js +290 -0
- package/plugins/superharness/workflow-state-server/state.js +424 -0
- package/plugins/superharness/workflow-state-server/validate-workflow.js +165 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jnMetaCode
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Superharness
|
|
2
|
+
|
|
3
|
+
Superharness 是面向 AI 编程代理的程序化工作流运行时。它把“什么时候该做需求分析、什么时候该写计划、什么时候该执行、什么时候必须验证”从纯 prompt 纪律升级为可校验、可审计、可恢复的状态机。
|
|
4
|
+
|
|
5
|
+
核心能力:
|
|
6
|
+
|
|
7
|
+
- **状态机驱动的 active skills**:覆盖会话入口分流(intake)、只读探索、轻量改动、需求澄清、计划编写、串行/并行执行、TDD、系统化调试、代码审查、完成前验证、分支收尾、中文工程规范等开发环节。
|
|
8
|
+
- **程序化状态机**:默认工作流由 `workflow/default-workflow.yaml` 描述,状态切换通过 MCP 工具管理,而不是让 agent 直接改状态文件。
|
|
9
|
+
- **Hook 动态注入**:Claude Code / Codex 通过 `UserPromptSubmit` 注入当前工作流上下文。
|
|
10
|
+
- **状态保护**:`PreToolUse` 阻止直接写入 `.superharness/`,要求通过工作流状态工具完成状态变更。
|
|
11
|
+
- **双端支持**:Claude Code、Codex 都接入同一套 skills、状态机与上下文渲染机制。
|
|
12
|
+
- **用户控制命令**:`/rollback` 和 `/free` 把“何时回退、何时暂停 workflow”的决策权交还给用户。
|
|
13
|
+
- **`/rollback [state]`**:把当前 workflow state 回退到 `transition_log` 中走过的某个历史 state。
|
|
14
|
+
- 无参 → 列出最近 5 个独特 state 供选择。
|
|
15
|
+
- 有参(如 `/rollback brainstorming`)→ 直接回到指定 state。
|
|
16
|
+
- 仅允许回退到日志里真实出现过的 state,避免凭空跳转。
|
|
17
|
+
- **`/free on|off|status`**:会话级暂停 / 恢复 workflow context 注入。
|
|
18
|
+
- free-mode 期间 hook 不再注入 SKILL.md,MCP 的 mutating 工具(`transition_state` / `classify_request` / `release_stop_block`)全部锁定。
|
|
19
|
+
- `.superharness/` 写保护始终生效,free mode 不影响审计与状态文件的安全性。
|
|
20
|
+
- 适合临时跳出状态机做一些不想被规范约束的探索或对话。
|
|
21
|
+
|
|
22
|
+
## 架构
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
用户请求
|
|
26
|
+
-> Hook 注入当前工作流状态
|
|
27
|
+
-> Agent 按 active skill 执行
|
|
28
|
+
-> superharness-workflow-state MCP 管理状态跳转
|
|
29
|
+
-> .superharness/ 持久化状态与审计事实
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 工作流状态机(v3)
|
|
33
|
+
|
|
34
|
+
每次会话都从 `intake` 开始,由它把请求分流到三条支线之一;执行支线结束后再回到 `intake` 等待下一任务。`systematic_debugging` 是抢占式分支,任何执行/验证/收尾节点失败都可以转入,调试完成后通过 `previous_state` 回边返回原状态:
|
|
35
|
+
|
|
36
|
+
`intake → exploration / trivial / (brainstorming → planning → serial_execution | parallel_execution → verification → finishing) → intake`
|
|
37
|
+
|
|
38
|
+
```mermaid
|
|
39
|
+
flowchart TD
|
|
40
|
+
START([Session start]) --> Intake
|
|
41
|
+
Intake[intake]:::interactive
|
|
42
|
+
Explor[exploration]:::interactive
|
|
43
|
+
Triv[trivial]:::execution
|
|
44
|
+
BS[brainstorming]:::interactive
|
|
45
|
+
PL[planning]:::interactive
|
|
46
|
+
SE[serial_execution]:::execution
|
|
47
|
+
PE[parallel_execution]:::execution
|
|
48
|
+
VF[verification]:::gate
|
|
49
|
+
FN[finishing]:::gate
|
|
50
|
+
DBG[/systematic_debugging/]:::preempt
|
|
51
|
+
|
|
52
|
+
Intake -->|只读探索| Explor
|
|
53
|
+
Intake -->|轻量改动| Triv
|
|
54
|
+
Intake -->|功能/bugfix| BS
|
|
55
|
+
|
|
56
|
+
Explor --> Intake
|
|
57
|
+
Triv --> Intake
|
|
58
|
+
|
|
59
|
+
BS --> PL
|
|
60
|
+
PL -->|串行| SE
|
|
61
|
+
PL -->|并行| PE
|
|
62
|
+
SE --> VF
|
|
63
|
+
PE --> VF
|
|
64
|
+
VF --> FN
|
|
65
|
+
FN -->|下一任务| Intake
|
|
66
|
+
|
|
67
|
+
Triv -.->|测试失败/报错| DBG
|
|
68
|
+
SE -.->|遇到 bug| DBG
|
|
69
|
+
PE -.->|遇到 bug| DBG
|
|
70
|
+
VF -.->|验证失败| DBG
|
|
71
|
+
FN -.->|git/CI 失败| DBG
|
|
72
|
+
|
|
73
|
+
DBG -.->|previous_state| Intake
|
|
74
|
+
DBG -->|重写计划| PL
|
|
75
|
+
DBG -->|重新执行| SE
|
|
76
|
+
|
|
77
|
+
classDef interactive fill:#065f46,stroke:#10b981,color:#fff
|
|
78
|
+
classDef router fill:#78350f,stroke:#f59e0b,color:#fff
|
|
79
|
+
classDef execution fill:#1e40af,stroke:#3b82f6,color:#fff
|
|
80
|
+
classDef gate fill:#7f1d1d,stroke:#ef4444,color:#fff
|
|
81
|
+
classDef preempt fill:#581c87,stroke:#a855f7,color:#fff
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
关键目录:
|
|
85
|
+
|
|
86
|
+
| 路径 | 说明 |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `plugins/superharness/skills/` | 当前 active skills 目录 |
|
|
89
|
+
| `plugins/superharness/workflow/` | 默认工作流配置 |
|
|
90
|
+
| `plugins/superharness/workflow-state-server/` | 状态机 MCP 与渲染逻辑 |
|
|
91
|
+
| `plugins/superharness/hooks/` | Claude Code / Codex hook 配置与入口 |
|
|
92
|
+
| `plugins/superharness/archived-skills/` | 已归档、不可发现的历史 skill |
|
|
93
|
+
|
|
94
|
+
## 安装
|
|
95
|
+
|
|
96
|
+
### Claude Code
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
/plugin marketplace add ShirlyTaylor73/superharness
|
|
100
|
+
/plugin install superharness@superharness
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
更新 / 卸载:
|
|
104
|
+
|
|
105
|
+
```text
|
|
106
|
+
/plugin update superharness
|
|
107
|
+
/plugin uninstall superharness
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Codex CLI
|
|
111
|
+
|
|
112
|
+
#### Codex 快速安装
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npx @shirlytaylor73/superharness@latest
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
安装器会用方向键询问安装到当前项目目录还是用户级目录。项目级会写入 `.agents/skills/` 和 `.codex/`;用户级会写入 `~/.agents/skills/` 和 `~/.codex/`。非交互环境使用:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npx @shirlytaylor73/superharness@latest --project
|
|
122
|
+
npx @shirlytaylor73/superharness@latest --user
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
安装器会把 active skills 平铺安装到 `.agents/skills/`,并把 Codex 版 `free` / `rollback` command 写入 `.codex/commands/`。由于 Codex 不支持 Claude Code 的 `!node` 原生 command 执行语法,Codex command 会指示 agent 用 shell tool 执行等价 Node 脚本。
|
|
126
|
+
|
|
127
|
+
#### Codex 插件市场
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
codex plugin marketplace add ShirlyTaylor73/superharness
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
然后启动 Codex,输入 `/plugins`,选择 `superharness` 安装。
|
|
134
|
+
|
|
135
|
+
更新 / 移除:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
codex plugin marketplace upgrade superharness
|
|
139
|
+
codex plugin marketplace remove superharness
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 本地开发安装
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
git clone https://github.com/ShirlyTaylor73/superharness.git
|
|
146
|
+
cd superharness
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Claude / Codex 推荐使用插件市场安装。若只想手动使用 skills,可把 `plugins/superharness/skills/` 复制或链接到目标工具的 skills 目录。
|
|
150
|
+
|
|
151
|
+
常见目标目录:
|
|
152
|
+
|
|
153
|
+
| 工具 | skills 目录 |
|
|
154
|
+
|---|---|
|
|
155
|
+
| Claude Code | `.claude/skills/` |
|
|
156
|
+
| Codex CLI | `.agents/skills/` |
|
|
157
|
+
| Gemini CLI | `.gemini/skills/` |
|
|
158
|
+
| Aider | `.aider/skills/` |
|
|
159
|
+
| Windsurf | `.windsurf/skills/` |
|
|
160
|
+
| OpenClaw | `skills/` |
|
|
161
|
+
|
|
162
|
+
## Active Skills
|
|
163
|
+
|
|
164
|
+
| Skill | 用途 |
|
|
165
|
+
|---|---|
|
|
166
|
+
| `intake` | 会话入口与请求 triage(分流到 exploration / trivial / brainstorming) |
|
|
167
|
+
| `exploration` | 只读深度探索,不写文件 |
|
|
168
|
+
| `trivial` | 单点轻量改动 + 自带验证 |
|
|
169
|
+
| `brainstorming` | 需求澄清与设计规格 |
|
|
170
|
+
| `planning` | 编写可执行实现计划 |
|
|
171
|
+
| `serial-execution` | 小计划串行执行 |
|
|
172
|
+
| `parallel-execution` | 大计划 wave 化并行执行 |
|
|
173
|
+
| `test-driven-development` | TDD 红绿重构 |
|
|
174
|
+
| `systematic-debugging` | 系统化定位和修复问题 |
|
|
175
|
+
| `requesting-code-review` | 请求代码审查 |
|
|
176
|
+
| `receiving-code-review` | 处理代码审查反馈 |
|
|
177
|
+
| `verification` | 完成前验证 |
|
|
178
|
+
| `finishing` | 开发分支收尾 |
|
|
179
|
+
| `dispatching-parallel-agents` | 非 plan 场景并行派发 |
|
|
180
|
+
| `using-git-worktrees` | 隔离式开发工作树 |
|
|
181
|
+
| `writing-skills` | 创建和改进 skills |
|
|
182
|
+
| `chinese-code-review` | 中文团队代码审查规范 |
|
|
183
|
+
| `chinese-git-workflow` | 国内 Git 平台工作流 |
|
|
184
|
+
| `chinese-documentation` | 中文技术文档规范 |
|
|
185
|
+
| `chinese-commit-conventions` | 中文提交规范 |
|
|
186
|
+
| `mcp-builder` | MCP 服务器构建方法论 |
|
|
187
|
+
| `workflow-runner` | 多角色 YAML 工作流执行 |
|
|
188
|
+
|
|
189
|
+
## 废弃机制
|
|
190
|
+
|
|
191
|
+
历史入口 skill `using-superpowers` 已归档到 `archived-skills/`,不再注册、不再注入、不再作为运行时入口。当前入口由 hook 注入的 workflow context 和 `superharness-workflow-state` MCP 接管。
|
|
192
|
+
|
|
193
|
+
## 验证
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
cd plugins/superharness/workflow-state-server
|
|
197
|
+
npm.cmd test
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
MIT
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export const INSTALLER_TOKEN = `{{SUPERHARNESS_${'PLUGIN_ROOT'}}}`;
|
|
6
|
+
export const COMMAND_NAMES = ['free.md', 'rollback.md'];
|
|
7
|
+
const LEGACY_PLUGIN_ROOT_PLACEHOLDER = `installed-${'plugin-root'}`;
|
|
8
|
+
|
|
9
|
+
export function parseArgs(argv) {
|
|
10
|
+
const parsed = { mode: null, force: false, help: false };
|
|
11
|
+
|
|
12
|
+
for (const arg of argv) {
|
|
13
|
+
if (arg === '--project') {
|
|
14
|
+
setMode(parsed, 'project');
|
|
15
|
+
} else if (arg === '--user' || arg === '--global') {
|
|
16
|
+
setMode(parsed, 'user');
|
|
17
|
+
} else if (arg === '--force') {
|
|
18
|
+
parsed.force = true;
|
|
19
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
20
|
+
parsed.help = true;
|
|
21
|
+
} else {
|
|
22
|
+
throw new Error(`unknown argument: ${arg}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return parsed;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function setMode(parsed, mode) {
|
|
30
|
+
if (parsed.mode && parsed.mode !== mode) {
|
|
31
|
+
throw new Error('choose only one install target');
|
|
32
|
+
}
|
|
33
|
+
parsed.mode = mode;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function resolveInstallTarget({
|
|
37
|
+
mode,
|
|
38
|
+
cwd = process.cwd(),
|
|
39
|
+
homeDir = process.env.USERPROFILE || process.env.HOME,
|
|
40
|
+
}) {
|
|
41
|
+
if (mode !== 'project' && mode !== 'user') {
|
|
42
|
+
throw new Error('install target must be project or user');
|
|
43
|
+
}
|
|
44
|
+
if (mode === 'user' && !homeDir) {
|
|
45
|
+
throw new Error('cannot resolve user home directory');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const codexRoot = mode === 'project'
|
|
49
|
+
? path.resolve(cwd, '.codex')
|
|
50
|
+
: path.resolve(homeDir, '.codex');
|
|
51
|
+
const agentsRoot = mode === 'project'
|
|
52
|
+
? path.resolve(cwd, '.agents')
|
|
53
|
+
: path.resolve(homeDir, '.agents');
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
mode,
|
|
57
|
+
codexRoot,
|
|
58
|
+
agentsRoot,
|
|
59
|
+
pluginRoot: path.join(codexRoot, 'plugins', 'superharness'),
|
|
60
|
+
commandsRoot: path.join(codexRoot, 'commands'),
|
|
61
|
+
skillsRoot: path.join(agentsRoot, 'skills'),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function backupExistingPath(target, timestamp) {
|
|
66
|
+
if (!(await exists(target))) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let backupPath = `${target}.bak-${timestamp}`;
|
|
71
|
+
let counter = 1;
|
|
72
|
+
while (await exists(backupPath)) {
|
|
73
|
+
backupPath = `${target}.bak-${timestamp}-${counter}`;
|
|
74
|
+
counter += 1;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await fs.rename(target, backupPath);
|
|
78
|
+
return backupPath;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function copyPluginRuntime({ packageRoot, pluginRoot, timestamp }) {
|
|
82
|
+
const source = path.join(packageRoot, 'plugins', 'superharness');
|
|
83
|
+
const backup = await backupExistingPath(pluginRoot, timestamp);
|
|
84
|
+
await fs.mkdir(path.dirname(pluginRoot), { recursive: true });
|
|
85
|
+
await fs.cp(source, pluginRoot, {
|
|
86
|
+
recursive: true,
|
|
87
|
+
filter: (sourcePath) => path.basename(sourcePath) !== 'node_modules',
|
|
88
|
+
});
|
|
89
|
+
return backup;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export async function copyAgentSkills({ packageRoot, skillsRoot, timestamp }) {
|
|
93
|
+
const source = path.join(packageRoot, 'plugins', 'superharness', 'skills');
|
|
94
|
+
const backups = [];
|
|
95
|
+
await fs.mkdir(skillsRoot, { recursive: true });
|
|
96
|
+
|
|
97
|
+
const entries = await fs.readdir(source, { withFileTypes: true });
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
const sourcePath = path.join(source, entry.name);
|
|
100
|
+
const destination = path.join(skillsRoot, entry.name);
|
|
101
|
+
const backup = await backupExistingPath(destination, timestamp);
|
|
102
|
+
if (backup) {
|
|
103
|
+
backups.push(backup);
|
|
104
|
+
}
|
|
105
|
+
await fs.cp(sourcePath, destination, { recursive: entry.isDirectory() });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return backups;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function renderCommandTemplate(template, pluginRoot) {
|
|
112
|
+
const rendered = template.replaceAll(INSTALLER_TOKEN, pluginRoot);
|
|
113
|
+
if (rendered.includes(INSTALLER_TOKEN) || rendered.includes(LEGACY_PLUGIN_ROOT_PLACEHOLDER)) {
|
|
114
|
+
throw new Error('command template still contains unresolved plugin root placeholder');
|
|
115
|
+
}
|
|
116
|
+
return rendered;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function installWorkflowDependencies({ pluginRoot, runCommand = spawnCommand }) {
|
|
120
|
+
const cwd = path.join(pluginRoot, 'workflow-state-server');
|
|
121
|
+
try {
|
|
122
|
+
await runCommand('npm', ['install', '--omit=dev'], { cwd });
|
|
123
|
+
} catch (error) {
|
|
124
|
+
throw new Error(`npm install --omit=dev failed in ${cwd}: ${error.message}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function installCodexSupport({
|
|
129
|
+
mode,
|
|
130
|
+
cwd = process.cwd(),
|
|
131
|
+
homeDir = process.env.USERPROFILE || process.env.HOME,
|
|
132
|
+
packageRoot,
|
|
133
|
+
now = () => new Date(),
|
|
134
|
+
runCommand,
|
|
135
|
+
} = {}) {
|
|
136
|
+
assertSupportedNode();
|
|
137
|
+
if (!packageRoot) {
|
|
138
|
+
throw new Error('packageRoot is required');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const target = resolveInstallTarget({ mode, cwd, homeDir });
|
|
142
|
+
const timestamp = formatTimestamp(now());
|
|
143
|
+
const backups = [];
|
|
144
|
+
|
|
145
|
+
await fs.mkdir(target.commandsRoot, { recursive: true });
|
|
146
|
+
const pluginBackup = await copyPluginRuntime({
|
|
147
|
+
packageRoot,
|
|
148
|
+
pluginRoot: target.pluginRoot,
|
|
149
|
+
timestamp,
|
|
150
|
+
});
|
|
151
|
+
if (pluginBackup) {
|
|
152
|
+
backups.push(pluginBackup);
|
|
153
|
+
}
|
|
154
|
+
backups.push(...await copyAgentSkills({
|
|
155
|
+
packageRoot,
|
|
156
|
+
skillsRoot: target.skillsRoot,
|
|
157
|
+
timestamp,
|
|
158
|
+
}));
|
|
159
|
+
|
|
160
|
+
for (const commandName of COMMAND_NAMES) {
|
|
161
|
+
const source = path.join(packageRoot, 'plugins', 'superharness', 'commands-codex', commandName);
|
|
162
|
+
const destination = path.join(target.commandsRoot, commandName);
|
|
163
|
+
const template = await fs.readFile(source, 'utf8');
|
|
164
|
+
const rendered = renderCommandTemplate(template, target.pluginRoot);
|
|
165
|
+
const backup = await backupExistingPath(destination, timestamp);
|
|
166
|
+
if (backup) {
|
|
167
|
+
backups.push(backup);
|
|
168
|
+
}
|
|
169
|
+
await fs.writeFile(destination, rendered, 'utf8');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
await installWorkflowDependencies({ pluginRoot: target.pluginRoot, runCommand });
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
mode: target.mode,
|
|
176
|
+
pluginRoot: target.pluginRoot,
|
|
177
|
+
commandsRoot: target.commandsRoot,
|
|
178
|
+
skillsRoot: target.skillsRoot,
|
|
179
|
+
backups,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function assertSupportedNode() {
|
|
184
|
+
const major = Number.parseInt(process.versions.node.split('.')[0], 10);
|
|
185
|
+
if (major < 20) {
|
|
186
|
+
throw new Error(`Superharness Codex installer requires Node.js >=20; current version is ${process.version}`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function formatTimestamp(date) {
|
|
191
|
+
const parts = [
|
|
192
|
+
date.getUTCFullYear(),
|
|
193
|
+
date.getUTCMonth() + 1,
|
|
194
|
+
date.getUTCDate(),
|
|
195
|
+
date.getUTCHours(),
|
|
196
|
+
date.getUTCMinutes(),
|
|
197
|
+
date.getUTCSeconds(),
|
|
198
|
+
].map((part, index) => (index === 0 ? String(part) : String(part).padStart(2, '0')));
|
|
199
|
+
|
|
200
|
+
return `${parts[0]}${parts[1]}${parts[2]}-${parts[3]}${parts[4]}${parts[5]}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function exists(filePath) {
|
|
204
|
+
try {
|
|
205
|
+
await fs.access(filePath);
|
|
206
|
+
return true;
|
|
207
|
+
} catch {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function spawnCommand(command, args, options) {
|
|
213
|
+
return new Promise((resolve, reject) => {
|
|
214
|
+
const child = spawn(command, args, {
|
|
215
|
+
...options,
|
|
216
|
+
stdio: 'inherit',
|
|
217
|
+
shell: process.platform === 'win32',
|
|
218
|
+
});
|
|
219
|
+
child.on('error', reject);
|
|
220
|
+
child.on('exit', (code, signal) => {
|
|
221
|
+
if (code === 0) {
|
|
222
|
+
resolve();
|
|
223
|
+
} else {
|
|
224
|
+
reject(new Error(signal ? `terminated by ${signal}` : `exited with code ${code}`));
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import readline from 'node:readline';
|
|
4
|
+
|
|
5
|
+
const OPTIONS = [
|
|
6
|
+
{
|
|
7
|
+
value: 'project',
|
|
8
|
+
label: 'Project install',
|
|
9
|
+
description: "Install into this repository's .agents and .codex directories",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
value: 'user',
|
|
13
|
+
label: 'User install',
|
|
14
|
+
description: 'Install into ~/.agents and ~/.codex for all Codex projects',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
value: 'cancel',
|
|
18
|
+
label: 'Cancel',
|
|
19
|
+
description: '',
|
|
20
|
+
},
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
export async function selectInstallTarget({
|
|
24
|
+
cwd = process.cwd(),
|
|
25
|
+
input = process.stdin,
|
|
26
|
+
output = process.stdout,
|
|
27
|
+
} = {}) {
|
|
28
|
+
let selectedIndex = await hasCodexDir(cwd) ? 0 : 1;
|
|
29
|
+
|
|
30
|
+
readline.emitKeypressEvents(input);
|
|
31
|
+
const canSetRawMode = typeof input.setRawMode === 'function';
|
|
32
|
+
if (canSetRawMode) {
|
|
33
|
+
input.setRawMode(true);
|
|
34
|
+
}
|
|
35
|
+
input.resume();
|
|
36
|
+
render(output, selectedIndex, false);
|
|
37
|
+
|
|
38
|
+
return new Promise((resolve) => {
|
|
39
|
+
const finish = (value) => {
|
|
40
|
+
input.off('keypress', onKeypress);
|
|
41
|
+
if (canSetRawMode) {
|
|
42
|
+
input.setRawMode(false);
|
|
43
|
+
}
|
|
44
|
+
output.write('\n');
|
|
45
|
+
resolve(value);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const onKeypress = (_text, key = {}) => {
|
|
49
|
+
if (key.ctrl && key.name === 'c') {
|
|
50
|
+
finish('cancel');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (key.name === 'up') {
|
|
54
|
+
selectedIndex = (selectedIndex + OPTIONS.length - 1) % OPTIONS.length;
|
|
55
|
+
render(output, selectedIndex, true);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (key.name === 'down') {
|
|
59
|
+
selectedIndex = (selectedIndex + 1) % OPTIONS.length;
|
|
60
|
+
render(output, selectedIndex, true);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (key.name === 'return' || key.name === 'enter') {
|
|
64
|
+
finish(OPTIONS[selectedIndex].value);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
input.on('keypress', onKeypress);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function hasCodexDir(cwd) {
|
|
73
|
+
try {
|
|
74
|
+
const stat = await fs.stat(path.join(cwd, '.codex'));
|
|
75
|
+
return stat.isDirectory();
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function render(output, selectedIndex, rerender) {
|
|
82
|
+
if (rerender) {
|
|
83
|
+
output.write('\x1b[5A\x1b[J');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const lines = [
|
|
87
|
+
'Where should Superharness Codex support be installed?',
|
|
88
|
+
'',
|
|
89
|
+
...OPTIONS.map((option, index) => {
|
|
90
|
+
const prefix = index === selectedIndex ? '>' : ' ';
|
|
91
|
+
return `${prefix} ${option.label.padEnd(16)}${option.description}`;
|
|
92
|
+
}),
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
output.write(`${lines.join('\n')}\n`);
|
|
96
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { installCodexSupport, parseArgs } from './lib/codex-installer.js';
|
|
5
|
+
import { selectInstallTarget } from './lib/interactive-select.js';
|
|
6
|
+
|
|
7
|
+
export async function main(argv = process.argv.slice(2), env = process.env) {
|
|
8
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
9
|
+
const options = parseArgs(argv);
|
|
10
|
+
|
|
11
|
+
if (options.help) {
|
|
12
|
+
process.stdout.write(usage());
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let mode = options.mode;
|
|
17
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY && !env.CI);
|
|
18
|
+
if (!mode) {
|
|
19
|
+
if (!interactive) {
|
|
20
|
+
throw new Error('non-interactive install requires --project or --user');
|
|
21
|
+
}
|
|
22
|
+
mode = await selectInstallTarget();
|
|
23
|
+
if (mode === 'cancel') {
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const result = await installCodexSupport({ mode, packageRoot });
|
|
29
|
+
process.stdout.write(successMessage(result));
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function usage() {
|
|
34
|
+
return `Usage: superharness [--project|--user] [--force]
|
|
35
|
+
|
|
36
|
+
Install Superharness Codex support into project or user .agents and .codex directories.
|
|
37
|
+
|
|
38
|
+
Options:
|
|
39
|
+
--project Install into the current repository's .agents and .codex directories
|
|
40
|
+
--user Install into ~/.agents and ~/.codex for all Codex projects
|
|
41
|
+
--global Alias for --user
|
|
42
|
+
--force Backup and overwrite existing installed assets
|
|
43
|
+
-h, --help Show this help
|
|
44
|
+
`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function successMessage({ mode, pluginRoot, commandsRoot, skillsRoot, backups }) {
|
|
48
|
+
const scope = mode === 'project' ? 'project' : 'user';
|
|
49
|
+
const backupLine = backups.length > 0
|
|
50
|
+
? `Backups created:\n${backups.map((backup) => ` - ${backup}`).join('\n')}\n`
|
|
51
|
+
: '';
|
|
52
|
+
|
|
53
|
+
return `Superharness Codex support installed for ${scope} scope.
|
|
54
|
+
Skills: ${skillsRoot}
|
|
55
|
+
Plugin: ${pluginRoot}
|
|
56
|
+
Commands: ${commandsRoot}
|
|
57
|
+
${backupLine}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
61
|
+
main().then((code) => {
|
|
62
|
+
process.exitCode = code;
|
|
63
|
+
}).catch((error) => {
|
|
64
|
+
process.stderr.write(`${error.message}\n`);
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
});
|
|
67
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@shirlytaylor73/superharness",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"engines": {
|
|
5
|
+
"node": ">=20.0.0"
|
|
6
|
+
},
|
|
7
|
+
"description": "Programmatic workflow harness for AI coding agents with skills, hooks, MCP state management, and cross-client support.",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"bin": {
|
|
10
|
+
"superharness": "bin/superharness.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"plugins/superharness/"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test:installer": "node --test tests/codex-installer/*.test.mjs"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"superharness",
|
|
21
|
+
"claude-code",
|
|
22
|
+
"codex",
|
|
23
|
+
"gemini",
|
|
24
|
+
"kiro",
|
|
25
|
+
"deerflow",
|
|
26
|
+
"trae",
|
|
27
|
+
"antigravity",
|
|
28
|
+
"openclaw",
|
|
29
|
+
"windsurf",
|
|
30
|
+
"aider",
|
|
31
|
+
"qwen",
|
|
32
|
+
"vscode",
|
|
33
|
+
"copilot",
|
|
34
|
+
"hermes",
|
|
35
|
+
"hermes-agent",
|
|
36
|
+
"claw-code",
|
|
37
|
+
"ai-coding",
|
|
38
|
+
"skills",
|
|
39
|
+
"chinese",
|
|
40
|
+
"中文",
|
|
41
|
+
"tdd",
|
|
42
|
+
"debugging",
|
|
43
|
+
"code-review"
|
|
44
|
+
],
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/ShirlyTaylor73/superharness.git"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://github.com/ShirlyTaylor73/superharness",
|
|
50
|
+
"author": "ShirlyTaylor73",
|
|
51
|
+
"license": "MIT"
|
|
52
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "superharness",
|
|
3
|
+
"description": "Programmatic workflow harness for AI coding agents: state-machine driven skills, hooks, and MCP state management.",
|
|
4
|
+
"version": "1.5.0",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "ShirlyTaylor73",
|
|
7
|
+
"url": "https://github.com/ShirlyTaylor73"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://github.com/ShirlyTaylor73/superharness",
|
|
10
|
+
"repository": "https://github.com/ShirlyTaylor73/superharness",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": ["skills", "tdd", "debugging", "chinese", "中文", "best-practices", "workflows"],
|
|
13
|
+
"mcpServers": {
|
|
14
|
+
"superharness-workflow-state": {
|
|
15
|
+
"command": "node",
|
|
16
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/workflow-state-server/bootstrap.js"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|