golden-hoop-spell-opencode 0.1.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/README.md +184 -0
- package/package.json +51 -0
- package/shared/SPIKE_RESULTS.md +597 -0
- package/shared/agents/ghs-context-haiku.md.template +124 -0
- package/shared/agents/ghs-plan-designer.md.template +128 -0
- package/shared/agents/ghs-plan-reviewer.md.template +170 -0
- package/shared/assets/features.json +67 -0
- package/shared/assets/progress.md +35 -0
- package/shared/ghs.default.json +7 -0
- package/shared/ghs.default.json.notes.md +34 -0
- package/shared/ghs.json.example +7 -0
- package/shared/opencode.json.example +11 -0
- package/shared/references/coding-agent.md +533 -0
- package/shared/references/context-snapshot-guide.md +98 -0
- package/shared/references/examples.md +299 -0
- package/shared/references/plan-designer.md +163 -0
- package/shared/references/plan-reviewer.md +193 -0
- package/shared/references/sprint-agent.md +261 -0
- package/src/index.ts +9 -0
- package/src/lib/assets.ts +31 -0
- package/src/lib/codegraph.ts +66 -0
- package/src/lib/config.ts +278 -0
- package/src/lib/nonce.ts +56 -0
- package/src/lib/parse.ts +175 -0
- package/src/lib/paths.ts +26 -0
- package/src/lib/project.ts +28 -0
- package/src/lib/scripts/append-progress-session.ts +178 -0
- package/src/lib/scripts/append-sprint.ts +121 -0
- package/src/lib/scripts/archive-sprint.ts +583 -0
- package/src/lib/scripts/init-project.ts +291 -0
- package/src/lib/scripts/parallel-utils.ts +380 -0
- package/src/lib/scripts/parse-completion-signal.ts +584 -0
- package/src/lib/scripts/parse-delimited-output.ts +632 -0
- package/src/lib/scripts/resolve-project-dir.ts +130 -0
- package/src/lib/scripts/status.ts +292 -0
- package/src/lib/scripts/update-feature-status.ts +169 -0
- package/src/lib/scripts/validate-structure.ts +290 -0
- package/src/lib/state.ts +305 -0
- package/src/plugin.ts +76 -0
- package/src/prompts/context-codegraph.ts +65 -0
- package/src/prompts/context-grep.ts +68 -0
- package/src/prompts/feature-impl.ts +78 -0
- package/src/prompts/plan-designer.ts +59 -0
- package/src/prompts/plan-reviewer.ts +61 -0
- package/src/prompts/sprint-planning.ts +47 -0
- package/src/tools/archive.ts +278 -0
- package/src/tools/code.ts +448 -0
- package/src/tools/config.ts +182 -0
- package/src/tools/force-archive.ts +195 -0
- package/src/tools/init.ts +193 -0
- package/src/tools/plan-finalize.ts +333 -0
- package/src/tools/plan-review.ts +759 -0
- package/src/tools/plan-start.ts +232 -0
- package/src/tools/sprint.ts +213 -0
- package/src/tools/status.ts +51 -0
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
# Phase 0 Spike Results
|
|
2
|
+
|
|
3
|
+
**Date**: 2026-06-20
|
|
4
|
+
**Environment**: opencode 1.17.8, bun 1.3.11, darwin 25.5.0
|
|
5
|
+
**Provider used for verification**: `zai-coding-plan` + `zhipuai-coding-plan` (GLM series — Anthropic not configured locally; plan's `anthropic/claude-haiku-4-20250514` ID was substituted with `zai-coding-plan/glm-4.5-air` for the dispatch-mechanism spikes. The substitution mechanism is model-agnostic — see spike 004).
|
|
6
|
+
**Codegraph CLI**: globally installed at `/Users/tom/.nvm/versions/node/v22.20.0/bin/codegraph` (not `bun x codegraph-mcp` as plan §3.3 speculated; see spike 003).
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Summary
|
|
11
|
+
|
|
12
|
+
| Spike | Title | Status | Critical? |
|
|
13
|
+
|---|---|---|---|
|
|
14
|
+
| s1-feat-001 | hyphenated tool key + system.transform | ✅ PASS | hard requirement (D1) |
|
|
15
|
+
| s1-feat-002 | subagent + Task tool dispatch | ✅ PASS | load-bearing (R2) |
|
|
16
|
+
| s1-feat-003 | codegraph MCP server + tool exposure | ✅ PASS | load-bearing (R1) |
|
|
17
|
+
| s1-feat-004 | markdown agent template substitution + reload | ✅ PASS | load-bearing (R3) |
|
|
18
|
+
|
|
19
|
+
**All 5 architectural assumptions de-risked.** No fallback paths required. Proceed to Phase 1 (s1-feat-005 scaffold).
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## s1-feat-001: hyphenated tool key + system.transform hook
|
|
24
|
+
|
|
25
|
+
**Status**: ✅ PASS
|
|
26
|
+
|
|
27
|
+
### Verified
|
|
28
|
+
|
|
29
|
+
1. **Hyphenated tool key loads**: A tool registered under `ghs-spike-test` (with the hyphen) appears in the AI's available tool list and round-trips JSON args/result correctly.
|
|
30
|
+
2. **`experimental.chat.system.transform` hook injects marker**: A string pushed via `output.system.push("...")` lands in the AI's system prompt and the AI can verbatim echo it back.
|
|
31
|
+
3. **Local plugin loading works**: `opencode.jsonc` field `plugin: ["./plugin.ts"]` resolves a relative TS file (no npm publish needed for dev).
|
|
32
|
+
|
|
33
|
+
### Minimal repro
|
|
34
|
+
|
|
35
|
+
`spikes/spike-01-tool-key-and-transform/plugin.ts` — 25-line plugin:
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import type { Plugin } from "@opencode-ai/plugin";
|
|
39
|
+
import { tool } from "@opencode-ai/plugin";
|
|
40
|
+
|
|
41
|
+
const plugin: Plugin = async () => ({
|
|
42
|
+
tool: {
|
|
43
|
+
"ghs-spike-test": tool({
|
|
44
|
+
description: "Spike 001 verification tool.",
|
|
45
|
+
args: { echo: tool.schema.string() },
|
|
46
|
+
async execute(args) {
|
|
47
|
+
return JSON.stringify({ ok: true, received: args.echo });
|
|
48
|
+
},
|
|
49
|
+
}),
|
|
50
|
+
},
|
|
51
|
+
"experimental.chat.system.transform": async (_input, output) => {
|
|
52
|
+
output.system.push("SPIKE MARKER 001 — ghs-spike-test tool is registered");
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export default plugin;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
`spikes/spike-01-tool-key-and-transform/opencode.json`:
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"$schema": "https://opencode.ai/config.json",
|
|
64
|
+
"plugin": ["./plugin.ts"]
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Evidence
|
|
69
|
+
|
|
70
|
+
Tool invocation captured from `opencode run --format json`:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"type": "tool_use",
|
|
75
|
+
"part": {
|
|
76
|
+
"tool": "ghs-spike-test",
|
|
77
|
+
"state": {
|
|
78
|
+
"status": "completed",
|
|
79
|
+
"input": { "echo": "ping-from-spike-001" },
|
|
80
|
+
"output": "{\"ok\":true,\"received\":\"ping-from-spike-001\",\"echoed_at\":\"2026-06-19T22:38:47.869Z\"}"
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Marker verification (separate run, focused prompt):
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"type": "text",
|
|
91
|
+
"part": { "text": "SPIKE MARKER 001 — ghs-spike-test tool is registered" }
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The AI verbatim echoed the exact string pushed by `experimental.chat.system.transform`.
|
|
96
|
+
|
|
97
|
+
### Implications for downstream features
|
|
98
|
+
|
|
99
|
+
- **D1 holds**: All `ghs-*` tools can use hyphenated keys as planned. No rename needed.
|
|
100
|
+
- `s1-feat-009`/`s1-feat-010`/`s1-feat-011` can use the exact `tool()` helper + Plugin signature shown above.
|
|
101
|
+
- `s1-feat-011`'s `experimental.chat.system.transform` hook can push the workflow hint per plan §3.4 D5.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## s1-feat-002: subagent + Task tool dispatch
|
|
106
|
+
|
|
107
|
+
**Status**: ✅ PASS
|
|
108
|
+
|
|
109
|
+
### Verified
|
|
110
|
+
|
|
111
|
+
1. **Markdown subagent file loads**: `.opencode/agents/test-subagent.md` with `mode: subagent`, `model: <provider/model>`, `hidden: true` is recognized as a subagent invokable via Task tool.
|
|
112
|
+
2. **Task tool dispatch creates isolated session**: Primary AI calls `task` tool with `subagent_type: "test-subagent"`; a child session is created with `parentID` pointing to the primary's session.
|
|
113
|
+
3. **Declared model applied**: The subagent runs with the model from its frontmatter (verified via logs: `stream providerID=... agent=test-subagent mode=subagent`).
|
|
114
|
+
4. **Output returns to primary AI**: Subagent result captured as `<task_result>...</task_result>` in the primary's tool result.
|
|
115
|
+
5. **Edit model + restart applies new model**: Editing the markdown's `model:` field and starting a fresh `opencode run` process causes the subagent to run with the new model. (Confirmed by swapping `zai-coding-plan/glm-4.5-air` → `zhipuai-coding-plan/glm-4.5-air` and observing the new providerID in the dispatch logs.)
|
|
116
|
+
6. **Subagent system prompt body applied**: After model swap (which also changed the marker phrase in the prompt body), the subagent's response reflected the new marker phrase, proving the markdown body is wired into the subagent's system prompt.
|
|
117
|
+
|
|
118
|
+
### Minimal repro
|
|
119
|
+
|
|
120
|
+
`spikes/spike-02-subagent-task-dispatch/.opencode/agents/test-subagent.md`:
|
|
121
|
+
|
|
122
|
+
```markdown
|
|
123
|
+
---
|
|
124
|
+
description: Spike 002 verification subagent. Returns a fixed marker string.
|
|
125
|
+
mode: subagent
|
|
126
|
+
model: zai-coding-plan/glm-4.5-air
|
|
127
|
+
hidden: true
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
You are a verification subagent for spike 002. When invoked, respond with EXACTLY this single line and nothing else:
|
|
131
|
+
|
|
132
|
+
HELLO FROM TEST-SUBAGENT (model=zai-coding-plan/glm-4.5-air)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Invoke from primary:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
opencode run --model 'zai-coding-plan/glm-4.5-air' 'Dispatch test-subagent via Task tool with prompt="say hello".'
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Evidence
|
|
142
|
+
|
|
143
|
+
Dispatch event:
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"type": "tool_use",
|
|
148
|
+
"part": {
|
|
149
|
+
"tool": "task",
|
|
150
|
+
"state": {
|
|
151
|
+
"status": "completed",
|
|
152
|
+
"input": {
|
|
153
|
+
"description": "Test subagent dispatch",
|
|
154
|
+
"prompt": "say hello",
|
|
155
|
+
"subagent_type": "test-subagent"
|
|
156
|
+
},
|
|
157
|
+
"output": "<task id=\"ses_...\" state=\"completed\">\n<task_result>HELLO FROM TEST-SUBAGENT (model=zhipuai-coding-plan/glm-4.5-air)\n</task_result>\n</task>",
|
|
158
|
+
"metadata": {
|
|
159
|
+
"parentSessionId": "ses_...",
|
|
160
|
+
"sessionId": "ses_...",
|
|
161
|
+
"model": { "providerID": "zhipuai-coding-plan", "modelID": "glm-4.5-air" }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Child session creation log:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
stream providerID=zhipuai-coding-plan modelID=glm-4.5-air session.id=ses_... agent=test-subagent mode=subagent
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Implications for downstream features
|
|
175
|
+
|
|
176
|
+
- **R2 holds**: The 3-role plan dispatcher (`ghs-plan-start` → designer subagent → reviewer subagent) is feasible. Each role = one markdown file under `.opencode/agents/ghs-*.md`.
|
|
177
|
+
- **No restart-on-edit hot reload**: Model changes require a fresh `opencode run` process. This is why `ghs-config` tool's output must include the "Restart your OpenCode session" hint (per `s1-feat-010` acceptance criteria).
|
|
178
|
+
- **Subagent model independence**: Each subagent can declare its own model, enabling per-role model selection (R2's per-task model selection requirement).
|
|
179
|
+
- **Task tool args**: `description`, `prompt`, `subagent_type`. Future `ghs-plan-start` etc. should construct these when programmatically dispatching.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## s1-feat-003: codegraph MCP server declaration + tool exposure
|
|
184
|
+
|
|
185
|
+
**Status**: ✅ PASS (with two divergences from plan §3.3 — see below)
|
|
186
|
+
|
|
187
|
+
### Verified
|
|
188
|
+
|
|
189
|
+
1. **MCP server starts**: `mcp.codegraph` declaration in opencode.json starts the MCP server process; `opencode mcp list` reports `✓ codegraph connected`.
|
|
190
|
+
2. **Tools exposed to primary AI**: 8 codegraph tools appear in primary AI's tool list.
|
|
191
|
+
3. **AI can invoke tools**: Primary AI calling `codegraph_codegraph_status` returns a real status object (files/nodes/edges/backend).
|
|
192
|
+
4. **Subagent `tools` frontmatter restricts MCP access**: A subagent with `tools: { codegraph_codegraph_*: false }` cannot invoke codegraph tools — it reports "BLOCKED: codegraph tools not available" when prompted to try.
|
|
193
|
+
|
|
194
|
+
### Divergences from plan §3.3 (IMPORTANT)
|
|
195
|
+
|
|
196
|
+
**Divergence 1 — command**:
|
|
197
|
+
- **Plan says**: `command: ["bun", "x", "codegraph-mcp"]`
|
|
198
|
+
- **Actually works**: `command: ["codegraph", "serve", "--mcp"]` (codegraph CLI's `serve` subcommand)
|
|
199
|
+
- **Impact**: `s1-feat-005`'s `shared/opencode.json.example` and `s1-feat-008`/`s1-feat-009`'s MCP-related defaults must use `["codegraph", "serve", "--mcp"]` to match the user's environment.
|
|
200
|
+
- **Mitigation for other users**: The plan's `bun x codegraph-mcp` may work in environments where `codegraph` CLI isn't globally installed — both commands ultimately start the same MCP server. Document both options in `shared/opencode.json.example` with comments.
|
|
201
|
+
|
|
202
|
+
**Divergence 2 — tool naming**:
|
|
203
|
+
- **Plan implies**: tools named `codegraph_status`, `codegraph_explore`, etc.
|
|
204
|
+
- **Actually exposed**: tools named `codegraph_codegraph_status`, `codegraph_codegraph_explore`, etc. — i.e., `<server_name>_<original_tool_name>`.
|
|
205
|
+
- **Impact**: The `experimental.chat.system.transform` hint text in `s1-feat-011` should NOT hardcode `codegraph_status` etc.; instead it should say "codegraph MCP tools" or use the double-prefix form. Plan §3.4 D5's hint text needs revising.
|
|
206
|
+
- **Subagent permission patterns**: When restricting MCP tools via `tools: { ... }`, the glob pattern must use the double-prefixed name: `codegraph_codegraph_*` (a single-prefix `codegraph_*` pattern also matched in testing, possibly because both layers are checked — but the double-prefix form is the safer canonical form).
|
|
207
|
+
|
|
208
|
+
**Divergence 3 — permission field**:
|
|
209
|
+
- **Plan implies**: modern `permission` frontmatter key restricts MCP tools.
|
|
210
|
+
- **Actually works**: only the deprecated `tools: { <pattern>: false }` form successfully restricts MCP tools. The modern `permission:` field has keys for built-in tools (bash, edit, etc.) but no key for arbitrary MCP tool patterns.
|
|
211
|
+
- **Impact**: Future `ghs-context-haiku`, `ghs-plan-designer`, `ghs-plan-reviewer` agent markdowns that need to restrict MCP access must use the `tools:` form, not `permission:`.
|
|
212
|
+
|
|
213
|
+
### Minimal repro
|
|
214
|
+
|
|
215
|
+
`spikes/spike-03-codegraph-mcp/opencode.json`:
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"$schema": "https://opencode.ai/config.json",
|
|
220
|
+
"mcp": {
|
|
221
|
+
"codegraph": {
|
|
222
|
+
"type": "local",
|
|
223
|
+
"command": ["codegraph", "serve", "--mcp"],
|
|
224
|
+
"enabled": true
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`spikes/spike-03-codegraph-mcp/.opencode/agents/codegraph-probe.md`:
|
|
231
|
+
|
|
232
|
+
```markdown
|
|
233
|
+
---
|
|
234
|
+
description: Spike 003 probe subagent.
|
|
235
|
+
mode: subagent
|
|
236
|
+
model: zai-coding-plan/glm-4.5-air
|
|
237
|
+
hidden: true
|
|
238
|
+
tools:
|
|
239
|
+
codegraph_codegraph_*: false
|
|
240
|
+
codegraph_*: false
|
|
241
|
+
---
|
|
242
|
+
... (prompts AI to try invoking codegraph_codegraph_status)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Evidence
|
|
246
|
+
|
|
247
|
+
Tool list visible to primary AI (from `/tmp` test dir, using global config):
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
codegraph_codegraph_callees
|
|
251
|
+
codegraph_codegraph_callers
|
|
252
|
+
codegraph_codegraph_explore
|
|
253
|
+
codegraph_codegraph_files
|
|
254
|
+
codegraph_codegraph_impact
|
|
255
|
+
codegraph_codegraph_node
|
|
256
|
+
codegraph_codegraph_search
|
|
257
|
+
codegraph_codegraph_status
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Successful invocation result:
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
## CodeGraph Status
|
|
264
|
+
**Files indexed:** 1
|
|
265
|
+
**Total nodes:** 4
|
|
266
|
+
**Total edges:** 5
|
|
267
|
+
**Database size:** 0.14 MB
|
|
268
|
+
**Backend:** node:sqlite (Node built-in) — full WAL + FTS5
|
|
269
|
+
**Journal mode:** wal (concurrent reads safe)
|
|
270
|
+
### Nodes by Kind:
|
|
271
|
+
- file: 1
|
|
272
|
+
- function: 1
|
|
273
|
+
- import: 2
|
|
274
|
+
### Languages:
|
|
275
|
+
- typescript: 1
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Restricted subagent response:
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
<task_result>BLOCKED: codegraph tools not available</task_result>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Implications for downstream features
|
|
285
|
+
|
|
286
|
+
- **R1 holds**: codegraph MCP is the primary context-extraction mechanism. Grep fallback is not needed for users with `codegraph` CLI installed.
|
|
287
|
+
- **s1-feat-005** `shared/opencode.json.example` must declare `mcp.codegraph` with `["codegraph", "serve", "--mcp"]` (and document the `bun x codegraph-mcp` alternative in a comment).
|
|
288
|
+
- **s1-feat-011** system.transform hint must reference MCP tools via descriptive text ("codegraph MCP tools") rather than hardcoding bare tool names.
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## s1-feat-004: markdown agent template substitution + reload
|
|
293
|
+
|
|
294
|
+
**Status**: ✅ PASS (load-bearing R3 spike — fully de-risked)
|
|
295
|
+
|
|
296
|
+
### Verified
|
|
297
|
+
|
|
298
|
+
1. **Template substitution**: A TypeScript script reading a `.md.template` file with `__GHS_MODEL_TEST__` placeholder, substituting it with a real model ID string, and writing to `.opencode/agents/ghs-test.md` produces a valid agent file.
|
|
299
|
+
2. **Substituted file loads as agent**: After a fresh `opencode run` process, the `ghs-test` subagent dispatches successfully with the substituted model.
|
|
300
|
+
3. **Frontmatter `model:` field substituted**: The substituted model ID appears in the running subagent's stream log (`stream providerID=... modelID=...`).
|
|
301
|
+
4. **Body placeholder substituted**: The subagent's prompt body containing `__GHS_MODEL_TEST__` was also substituted; the subagent's response reflected the substituted value verbatim.
|
|
302
|
+
5. **Change substitution value + re-render + restart → new model applies**: Re-rendering with a different model ID and starting a new opencode process caused the subagent to run with the new model.
|
|
303
|
+
|
|
304
|
+
### Minimal repro
|
|
305
|
+
|
|
306
|
+
`spikes/spike-04-template-substitution/ghs-test.md.template`:
|
|
307
|
+
|
|
308
|
+
```markdown
|
|
309
|
+
---
|
|
310
|
+
description: Spike 04 verification subagent.
|
|
311
|
+
mode: subagent
|
|
312
|
+
model: __GHS_MODEL_TEST__
|
|
313
|
+
hidden: true
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
You are a verification subagent for spike 004. When invoked, respond with EXACTLY:
|
|
317
|
+
|
|
318
|
+
HELLO FROM GHS-TEST (model=__GHS_MODEL_TEST__)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
`spikes/spike-04-template-substitution/render.ts` (Bun script):
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
325
|
+
import { dirname, resolve } from "node:path";
|
|
326
|
+
|
|
327
|
+
const PLACEHOLDER = "__GHS_MODEL_TEST__";
|
|
328
|
+
const modelId = process.env.GHS_MODEL_ID; // required
|
|
329
|
+
const templatePath = resolve(import.meta.dirname, "ghs-test.md.template");
|
|
330
|
+
const outPath = resolve(import.meta.dirname, ".opencode/agents/ghs-test.md");
|
|
331
|
+
|
|
332
|
+
const template = await readFile(templatePath, "utf8");
|
|
333
|
+
const rendered = template.replaceAll(PLACEHOLDER, modelId);
|
|
334
|
+
await mkdir(dirname(outPath), { recursive: true });
|
|
335
|
+
await writeFile(outPath, rendered, "utf8");
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Run + dispatch:
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
GHS_MODEL_ID='zai-coding-plan/glm-4.5-air' bun run render.ts
|
|
342
|
+
opencode run 'Dispatch ghs-test via Task tool with prompt="go".'
|
|
343
|
+
|
|
344
|
+
# Then re-render with different model + new opencode process:
|
|
345
|
+
GHS_MODEL_ID='zhipuai-coding-plan/glm-5.1' bun run render.ts
|
|
346
|
+
opencode run 'Dispatch ghs-test via Task tool with prompt="go".'
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Evidence
|
|
350
|
+
|
|
351
|
+
First dispatch (model = `zai-coding-plan/glm-4.5-air`):
|
|
352
|
+
|
|
353
|
+
```
|
|
354
|
+
stream providerID=zai-coding-plan modelID=glm-4.5-air session.id=ses_... agent=ghs-test mode=subagent
|
|
355
|
+
"model": { "providerID": "zai-coding-plan", "modelID": "glm-4.5-air" }
|
|
356
|
+
<task_result>HELLO FROM GHS-TEST (model=zai-coding-plan/glm-4.5-air)</task_result>
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Second dispatch (model = `zhipuai-coding-plan/glm-5.1`):
|
|
360
|
+
|
|
361
|
+
```
|
|
362
|
+
stream providerID=zhipuai-coding-plan modelID=glm-5.1 session.id=ses_... agent=ghs-test mode=subagent
|
|
363
|
+
"model": { "providerID": "zhipuai-coding-plan", "modelID": "glm-5.1" }
|
|
364
|
+
<task_result>HELLO FROM GHS-TEST (model=zhipuai-coding-plan/glm-5.1)</task_result>
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Implications for downstream features
|
|
368
|
+
|
|
369
|
+
- **R3 holds**: User-configurable model IDs via `.ghs/ghs.json` is feasible. `s1-feat-007` (config.ts) can implement `renderAgentTemplate()` using `String.replaceAll()` over the template body.
|
|
370
|
+
- **`s1-feat-007` design confirmed**: Three placeholders (`__GHS_MODEL_CONTEXT__`, `__GHS_MODEL_DESIGNER__`, `__GHS_MODEL_REVIEWER__`) can all be substituted in one pass over each template file.
|
|
371
|
+
- **`s1-feat-010` (ghs-config tool) restart hint is required**: Substituted agent files don't hot-reload — users must restart opencode for changes to take effect.
|
|
372
|
+
- **No template engine needed**: `String.replaceAll()` is sufficient. No need for Handlebars/Mustache/etc.
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Cross-cutting findings
|
|
377
|
+
|
|
378
|
+
### Provider/model ID format
|
|
379
|
+
|
|
380
|
+
Confirmed: `provider/model-id` (e.g., `zai-coding-plan/glm-4.5-air`). The provider is everything before the first `/`; the model ID is everything after (and may itself contain slashes for some providers, though not for the GLM series tested here).
|
|
381
|
+
|
|
382
|
+
### Cost observations
|
|
383
|
+
|
|
384
|
+
All 4 spikes combined used the user's free-plan `zai-coding-plan` and `zhipuai-coding-plan` credentials. `opencode run` reported `cost: 0` for every session. The `glm-4.5-air` model is sufficient for verification prompts (though it sometimes truncates output after a tool call — a known small-model behavior, not a mechanism issue).
|
|
385
|
+
|
|
386
|
+
### OpenCode process lifecycle
|
|
387
|
+
|
|
388
|
+
Each `opencode run` invocation starts a fresh opencode server process that reads configs and agent files at startup. There is no in-process reload. This is why:
|
|
389
|
+
- Editing agent markdown requires a new `opencode run` to take effect.
|
|
390
|
+
- Editing `opencode.json` requires a new `opencode run`.
|
|
391
|
+
- Editing MCP server config requires a new `opencode run`.
|
|
392
|
+
|
|
393
|
+
Implication: `ghs-config` tool cannot "apply" changes by writing files alone — it must instruct the user to restart.
|
|
394
|
+
|
|
395
|
+
### Hidden subagents
|
|
396
|
+
|
|
397
|
+
`hidden: true` in frontmatter removes the subagent from `@` autocomplete but does NOT prevent Task tool dispatch (verified in spike 002 + spike 003 + spike 004 — all subagents had `hidden: true` and were successfully dispatched). This matches the documented behavior. All future `ghs-*` subagents should set `hidden: true` since they're internal orchestrator-roles, not user-facing.
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Follow-up: equivalence test harness divergences (s1-feat-012)
|
|
402
|
+
|
|
403
|
+
While building the equivalence tests (`test/equivalence/*.test.ts`) two **test-harness-only** divergences between Bun's test runner and CPython were found. Neither indicates a real bug in the TS ports; both are worked around in `test/equivalence/_helpers.ts`.
|
|
404
|
+
|
|
405
|
+
1. **Timezone forcing**: `bun test` runs every test with the JS locale pinned to UTC, regardless of the host system's actual TZ (verified: `Intl.DateTimeFormat().resolvedOptions().timeZone === "UTC"` inside tests, while `bun -e` reports `Asia/Shanghai`). This makes `new Date().getHours()` return UTC hours under test, but local hours in production. The Python oracle (`datetime.now()`) honours the host TZ by default, so the two would diverge by the TZ offset.
|
|
406
|
+
- **Workaround**: `runPython()` in `_helpers.ts` explicitly sets `TZ=UTC` on the spawned Python's env, so both sides run in UTC inside tests. In production (non-test runtime) both honour the actual process TZ — no fix needed in the TS port itself.
|
|
407
|
+
|
|
408
|
+
2. **`Bun.mkdtemp` does not exist**: the feature spec referenced `Bun.mkdtemp` as the canonical temp-dir primitive. The installed Bun 1.3.11 exposes no such API.
|
|
409
|
+
- **Workaround**: `makeTempDir()` in `_helpers.ts` uses Node's `fs.promises.mkdtemp(join(tmpdir(), prefix))` and then `realpathSync()`-resolves the result so the path matches Python's `Path.resolve()` semantics (matters on macOS where `/tmp` and `/var` are symlinks into `/private/...`).
|
|
410
|
+
|
|
411
|
+
3. **Python `re` vs JS `RegExp` for the H2 splitter**: not encountered as a divergence. Python's `re.split(r"^## ", content, flags=re.MULTILINE)` and JS's `content.split(/^## /m)` produce identical arrays for all fixture inputs exercised by the status/archive tests (verified empirically). No assertion relaxation needed.
|
|
412
|
+
|
|
413
|
+
4. **Archive dry-run still creates `.ghs/archived/`**: both the Python source and the TS port call `create_archive_structure` before checking `dry_run`. This is faithful port behaviour, not a bug — the equivalence test asserts the directory exists (but is empty) in dry-run mode for both impls.
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## Sprint 1 E2E Verification (s1-feat-014)
|
|
418
|
+
|
|
419
|
+
**日期**: 2026-06-20
|
|
420
|
+
**方法**: 直接通过 Bun 脚本调用 plugin 的 tool.execute 函数(模拟 ToolContext),不重启完整 OpenCode 会话。与派发真实 AI 相比,这条路径更确定性、更快、覆盖面也更全(每个场景都能拿到结构化的 pass/fail 证据)。
|
|
421
|
+
**环境**: bun 1.3.11, darwin 25.5.0;验证用的 plugin 代码为 s1-feat-011 提交版本(`src/plugin.ts` + `src/index.ts`)。
|
|
422
|
+
**Agent 模板来源**: 由于生产模板 `shared/agents/ghs-*.md.template` 属于 Phase 3 / Sprint 3 范畴,本验证按 feature 文档「Option A」的指引,临时把 `test/fixtures/agents/*.md.template` 的 3 个 stub 复制到 `shared/agents/`,跑完测试后删除该目录(验证脚本运行前后 `git status` 干净)。
|
|
423
|
+
|
|
424
|
+
### 验证矩阵
|
|
425
|
+
|
|
426
|
+
| 场景 | 描述 | 结果 |
|
|
427
|
+
|---|---|---|
|
|
428
|
+
| 1 | `ghs-init` 在 temp dir 中创建全部期望文件 | ✅ PASS |
|
|
429
|
+
| 2 | 生成的 `.ghs/ghs.json` 与 `shared/ghs.default.json` 字节级一致 | ✅ PASS |
|
|
430
|
+
| 3 | 3 个生成的 agent markdown frontmatter `model:` 字段被默认 model ID 填充 | ✅ PASS |
|
|
431
|
+
| 4 | 编辑 `.ghs/ghs.json` 把 `models.context` 改为 `openai/gpt-5` 后调 `ghs-config({})` → context-haiku 的 model 被替换,另两个保持不变 | ✅ PASS(**修复后重跑通过**;初次执行时因 s1-feat-010 的目录 gate bug 失败,根因 + 修复见下方) |
|
|
432
|
+
| 4b | 直接调 `syncAgents()`(绕开 ghs-config 的前置 gate)→ context-haiku 被替换,另两个保持不变 | ✅ PASS(控制组,证明渲染机制本身没问题) |
|
|
433
|
+
| 5 | `ghs-status({})` 返回格式化的状态字符串 | ✅ PASS |
|
|
434
|
+
| 6 | 在没有 `.ghs/` 的空目录调 `ghs-config({})` → 返回 "Run ghs-init first." 且不写任何文件 | ✅ PASS |
|
|
435
|
+
|
|
436
|
+
### 场景 1:`ghs-init` 创建全部期望文件
|
|
437
|
+
|
|
438
|
+
- **Setup**: 用 `fs.mkdtemp` 在 `os.tmpdir()` 下建 temp project dir,模拟 ToolContext 指向它(`worktree` + `directory` 都设为该目录)。
|
|
439
|
+
- **Action**: `hooks.tool["ghs-init"].execute({ project_name: "temp-test" }, mockCtx)`。
|
|
440
|
+
- **Result**: PASS。
|
|
441
|
+
- **Evidence**: `ghs-init` 返回 1400 字符的成功摘要,以下 6 个文件全部存在且非空:
|
|
442
|
+
- `.ghs/features.json` — 2369 字节;解析后 `project.name === "temp-test"`。
|
|
443
|
+
- `.ghs/progress.md` — 636 字节。
|
|
444
|
+
- `.ghs/ghs.json` — 161 字节。
|
|
445
|
+
- `.opencode/agents/ghs-context-haiku.md` — 474 字节。
|
|
446
|
+
- `.opencode/agents/ghs-plan-designer.md` — 475 字节。
|
|
447
|
+
- `.opencode/agents/ghs-plan-reviewer.md` — 475 字节。
|
|
448
|
+
|
|
449
|
+
### 场景 2:生成的 `.ghs/ghs.json` 与 `shared/ghs.default.json` 字节级一致
|
|
450
|
+
|
|
451
|
+
- **Action**: 把场景 1 生成的 `.ghs/ghs.json` 内容与 `shared/ghs.default.json` 字节级比较。
|
|
452
|
+
- **Result**: PASS。两文件完全相同(包括缩进、换行)。
|
|
453
|
+
|
|
454
|
+
### 场景 3:3 个 agent markdown frontmatter `model:` 字段被默认 model ID 填充
|
|
455
|
+
|
|
456
|
+
- **Action**: 解析 3 个生成的 `.opencode/agents/ghs-*.md`,提取首行 `model:` 字段。
|
|
457
|
+
- **Result**: PASS。三个 agent 的 model 与 `shared/ghs.default.json` 一一对应:
|
|
458
|
+
- `ghs-context-haiku.md` → `zai-coding-plan/glm-4.5-air`
|
|
459
|
+
- `ghs-plan-designer.md` → `zhipuai-coding-plan/glm-4.6`
|
|
460
|
+
- `ghs-plan-reviewer.md` → `zhipuai-coding-plan/glm-4.6`
|
|
461
|
+
|
|
462
|
+
### 场景 4:`ghs-config` 应在 `.ghs/ghs.json` 修改后重新生成(初 FAIL,已修复后 PASS)
|
|
463
|
+
|
|
464
|
+
- **Setup**: 在场景 1 生成的基础上,编辑 `.ghs/ghs.json`,把 `models.context` 改为 `openai/gpt-5`(其余两个字段保留)。
|
|
465
|
+
- **Action**: `hooks.tool["ghs-config"].execute({}, mockCtx)`。
|
|
466
|
+
- **Result(初次执行)**: **FAIL**。`ghs-config` 返回 19 字符的字符串 `"Run ghs-init first."`,3 个 agent markdown 文件未被重新渲染(`ghs-context-haiku.md` 的 model 仍是默认的 `zai-coding-plan/glm-4.5-air`)。
|
|
467
|
+
- **Root cause**: `src/tools/config.ts` 第 138 行的前置 gate 写的是 `await fileExists(resolve(projectDir, ".ghs"))`,而 `fileExists()` 的实现是 `Bun.file(path).exists()`。Bun 的 `Bun.file()` 语义只识别常规文件——**对目录路径始终返回 `false`**(独立验证:`Bun.file("/some/dir").exists() === false`,而 `Bun.file("/some/dir/file").exists() === true`)。
|
|
468
|
+
- **Impact**: 任何已通过 `ghs-init` 初始化的项目调 `ghs-config({})` 都会被错误拒绝。也就是说,**s1-feat-010 的验收标准 #2/#3 在运行时无法满足**(单测 `test/config.test.ts` 没有覆盖 tool 层,只测了 `config.ts` 的纯函数,所以没发现)。
|
|
469
|
+
- **Fix applied**: 把 gate 改成 `await fileExists(resolve(projectDir, ".ghs", "ghs.json"))`(检查文件而非目录),与 ghs-init 实际写入的文件集合对齐。
|
|
470
|
+
- **Result(修复后重跑)**: **PASS**。`ghs-config` 正常返回 3 个写入路径 + 解析的 model ID + 重启提示;`ghs-context-haiku.md` 的 model 字段变成 `openai/gpt-5`,另两个 agent 文件保持不变;`defaults_used: false`。
|
|
471
|
+
- **Test coverage gap**: tool 层 gate 缺集成测试。`test/config.test.ts` 只测了 `config.ts` 的纯函数,没触达 `src/tools/config.ts` 的 gate 分支——Sprint 2 应补一个 tool-layer 集成测试。
|
|
472
|
+
|
|
473
|
+
### 场景 4b(控制组):直接调 `syncAgents()` 验证渲染机制本身
|
|
474
|
+
|
|
475
|
+
- **Motivation**: 场景 4 失败是 tool 层 gate 的问题,不是渲染管线的问题。这个场景把 ghs-config tool 绕开,直接调 `src/lib/config.ts` 的 `syncAgents(tempProjectDir, pluginRoot())`,证明 s1-feat-007 的核心机制(load config → per-field fallback → renderAgentTemplate → Bun.write)行为正确。
|
|
476
|
+
- **Action**: 在场景 4 已经把 `.ghs/ghs.json` 改好后,直接 `await syncAgents(tempProjectDir, root)`。
|
|
477
|
+
- **Result**: PASS。
|
|
478
|
+
- **Evidence**:
|
|
479
|
+
- `syncAgents` 返回 `{ written: [3 paths], models: { context: "openai/gpt-5", designer: "zhipuai-coding-plan/glm-4.6", reviewer: "zhipuai-coding-plan/glm-4.6" }, defaults_used: false }`。
|
|
480
|
+
- `ghs-context-haiku.md` 的 model 字段已被替换为 `openai/gpt-5`。
|
|
481
|
+
- `ghs-plan-designer.md` 和 `ghs-plan-reviewer.md` 的 model 字段保持不变(都是默认值)。
|
|
482
|
+
- `defaults_used: false` 表示用户配置完全接管了三个字段——这符合 s1-feat-007 的 per-field fallback 设计。
|
|
483
|
+
- **Conclusion**: 渲染机制 + 配置合并逻辑是健全的;Sprint 2 修掉 ghs-config 的目录 gate bug 后,s1-feat-010 的 AC #2/#3 应当自动满足。
|
|
484
|
+
|
|
485
|
+
### 场景 5:`ghs-status({})` 返回格式化的状态字符串
|
|
486
|
+
|
|
487
|
+
- **Action**: 在场景 1-4b 留下的项目状态下调 `hooks.tool["ghs-status"].execute({}, mockCtx)`。
|
|
488
|
+
- **Result**: PASS。
|
|
489
|
+
- **Evidence**: 返回 167 字符的格式化字符串,前 4 行:
|
|
490
|
+
```
|
|
491
|
+
=== Project Status ===
|
|
492
|
+
|
|
493
|
+
📦 Project: temp-test
|
|
494
|
+
📝 Description: temp-test project
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
### 场景 6:`ghs-config` 在 `.ghs/` 缺失时拒绝执行(gate 的正向用例)
|
|
498
|
+
|
|
499
|
+
- **Action**: 在一个新的空 temp dir 上调 `ghs-config({})`。
|
|
500
|
+
- **Result**: PASS。返回 `"Run ghs-init first."`,且 `.opencode/agents/` 未被创建。说明 gate 在「真的需要拒绝」的路径上工作正常——这恰好是因为空目录下 `Bun.file(".ghs").exists()` 也是 `false`,与「目录存在但 `Bun.file` 不识别」的失败路径在代码层面是同一条分支。
|
|
501
|
+
|
|
502
|
+
### 已知限制 / Sprint 2+ 后续事项
|
|
503
|
+
|
|
504
|
+
1. **~~ghs-config 的 `.ghs/` 目录 gate bug~~(已修复)**: 见场景 4 根因 + fix。修复 commit 把 gate 从检查 `.ghs/` 目录改为检查 `.ghs/ghs.json` 文件,场景 4 重跑通过。**遗留**:tool-layer 集成测试仍待补,覆盖「初始化后的项目再调 ghs-config」这条路径——目前 `test/config.test.ts` 只测了 `config.ts` 的纯函数。
|
|
505
|
+
2. **生产 agent 模板仍是 stub**: Phase 3 / Sprint 3 才会交付 `shared/agents/ghs-{context-haiku,plan-designer,plan-reviewer}.md.template` 的生产版本。本验证用 `test/fixtures/agents/*.md.template`(s1-feat-007 创建的 stub)临时顶替。stub 里每个模板只有该角色对应的一个 placeholder,所以场景 3/4 的「另两个 model 保持不变」断言在 stub 上天然成立——生产模板上线后需要重跑本场景,确认三 placeholder 互不串扰。
|
|
506
|
+
3. **未在真实 OpenCode 进程内验证**: 本测试用 Bun 脚本直接调 `tool.execute()`,绕开了 OpenCode 的 plugin loader / system.transform hook 注入。s1-feat-001 的 spike 已经独立验证过 hyphenated tool key 加载 + system.transform marker 注入,所以这部分是有 prior evidence 的;但「在真实 OpenCode 会话里让 AI 主动调用 ghs-init」这条完整路径未走通。建议 Sprint 2 在接入第一个真实 plan/sprint workflow 时顺带做一次 smoke test。
|
|
507
|
+
4. **ghs-sprint / ghs-code / ghs-plan-* 尚未实现**: Sprint 1 只交付了 5 个基础工具(init/status/archive/force-archive/config)。Plan 派发链和工作流循环要等 Sprint 2-4。
|
|
508
|
+
5. **ghs-archive / ghs-force-archive 未在本 E2E 中覆盖**: 这两个工具的等价性已经在 `test/equivalence/archive.test.ts` 里和 Python 源码做过字节级比对(s1-feat-012),所以本场景聚焦在 init/config/status 这条 R3(用户可配置 model)的关键链路上,避免与已有等价性测试重复。
|
|
509
|
+
|
|
510
|
+
### 结论
|
|
511
|
+
|
|
512
|
+
Sprint 1 的核心交付物(plugin 加载 + 5 个基础工具 + R3 模型配置链路)整体可用。验证过程中发现的 ghs-config 目录 gate bug 已在同一 commit 内修复并通过 e2e 重跑确认。所有 7 个场景(含修复后的场景 4)全部 PASS,渲染机制在直接调用路径下表现完全符合设计预期。
|
|
513
|
+
|
|
514
|
+
---
|
|
515
|
+
|
|
516
|
+
## Next steps
|
|
517
|
+
|
|
518
|
+
All 5 architectural assumptions de-risked. Proceed with:
|
|
519
|
+
|
|
520
|
+
1. **s1-feat-005** (project scaffold) — `shared/opencode.json.example` must use `["codegraph", "serve", "--mcp"]` per spike 003 finding.
|
|
521
|
+
2. **s1-feat-006** through **s1-feat-014** — proceed per existing plan, with the divergences noted above baked into the relevant features.
|
|
522
|
+
|
|
523
|
+
## Sprint 2 E2E Verification (s2-feat-005)
|
|
524
|
+
|
|
525
|
+
**日期**: 2026-06-20
|
|
526
|
+
**方法**: 通过 Bun 脚本(`spikes/sprint-e2e/run.ts`,不 commit)直接调 `ghsPlugin({}).tool[...]execute(args, mockCtx)`,用 mock ToolContext 指向 `os.tmpdir()` 下的临时项目目录。与 s1-feat-014 同一套验证路径——确定性高、覆盖面全,每个场景都拿到结构化 pass/fail 证据。
|
|
527
|
+
**覆盖范围**: s2 全栈——ghs-init / ghs-sprint / ghs-status tool 层 + append-sprint / update-feature-status / append-progress-session writer 层 + auto-archive 串联。
|
|
528
|
+
**前置条件**: 生产模板 `shared/agents/*.md.template` 仍是 Sprint 3 交付物,本验证临时把 `test/fixtures/agents/*.md.template` 的 3 个 stub 复制到 `shared/agents/`,跑完后删除(验证脚本运行前后 `git status` 干净)。
|
|
529
|
+
|
|
530
|
+
### 验证矩阵
|
|
531
|
+
|
|
532
|
+
| 场景 | 描述 | 结果 |
|
|
533
|
+
|---|---|---|
|
|
534
|
+
| Scenario 1 | ghs-init creates .ghs/ + .opencode/agents/ | ✅ PASS |
|
|
535
|
+
| Scenario 2 | ghs-sprint appends s1 skeleton + returns planning prompt | ✅ PASS |
|
|
536
|
+
| Scenario 3 | update-feature-status flips 3 features to completed | ✅ PASS |
|
|
537
|
+
| Scenario 4 | ghs-status reports sprint + feature counts | ✅ PASS |
|
|
538
|
+
| Scenario 5 | ghs-sprint auto-archives completed s1 when creating s2 | ✅ PASS |
|
|
539
|
+
|
|
540
|
+
**总结**: 5/5 场景通过。整体状态: ✅ ALL PASS。
|
|
541
|
+
|
|
542
|
+
### 场景 1: ghs-init creates .ghs/ + .opencode/agents/
|
|
543
|
+
|
|
544
|
+
- **结果**: ✅ PASS
|
|
545
|
+
- **证据**:
|
|
546
|
+
ghs-init returned 1436 chars
|
|
547
|
+
✓ .ghs/features.json exists
|
|
548
|
+
✓ .ghs/progress.md exists
|
|
549
|
+
✓ .ghs/ghs.json exists
|
|
550
|
+
✓ .opencode/agents/ghs-context-haiku.md exists
|
|
551
|
+
✓ .opencode/agents/ghs-plan-designer.md exists
|
|
552
|
+
✓ .opencode/agents/ghs-plan-reviewer.md exists
|
|
553
|
+
✓ features.json starts with empty sprints array
|
|
554
|
+
|
|
555
|
+
### 场景 2: ghs-sprint appends s1 skeleton + returns planning prompt
|
|
556
|
+
|
|
557
|
+
- **结果**: ✅ PASS
|
|
558
|
+
- **证据**:
|
|
559
|
+
✓ result contains ghs-sprint header
|
|
560
|
+
✓ result names the new sprint as s1
|
|
561
|
+
✓ result appends the sprint-planning prompt section
|
|
562
|
+
✓ features.json now has 1 sprint
|
|
563
|
+
✓ s1 skeleton written with status=planning, empty features
|
|
564
|
+
|
|
565
|
+
### 场景 3: update-feature-status flips 3 features to completed
|
|
566
|
+
|
|
567
|
+
- **结果**: ✅ PASS
|
|
568
|
+
- **证据**:
|
|
569
|
+
Injected 3 pending features (s1-feat-001/002/003)
|
|
570
|
+
✓ s1-feat-001: completed
|
|
571
|
+
✓ s1-feat-002: completed
|
|
572
|
+
✓ s1-feat-003: completed
|
|
573
|
+
|
|
574
|
+
### 场景 4: ghs-status reports sprint + feature counts
|
|
575
|
+
|
|
576
|
+
- **结果**: ✅ PASS
|
|
577
|
+
- **证据**:
|
|
578
|
+
✓ status output has the expected header
|
|
579
|
+
✓ status output mentions the sprint name
|
|
580
|
+
✓ status output reports completed features
|
|
581
|
+
status output length: 365 chars
|
|
582
|
+
|
|
583
|
+
### 场景 5: ghs-sprint auto-archives completed s1 when creating s2
|
|
584
|
+
|
|
585
|
+
- **结果**: ✅ PASS
|
|
586
|
+
- **证据**:
|
|
587
|
+
Marked s1 status=completed to trigger auto-archive
|
|
588
|
+
✓ second ghs-sprint returned its header
|
|
589
|
+
✓ result reports auto-archiving 1 completed sprint
|
|
590
|
+
✓ new sprint is numbered s2 (s1 retired, not reused)
|
|
591
|
+
archived/ entries: s1_test_sprint_20260620_151602
|
|
592
|
+
✓ s1 archived under .ghs/archived/s1_*
|
|
593
|
+
✓ active sprints array now contains only s2
|
|
594
|
+
|
|
595
|
+
### 结论
|
|
596
|
+
|
|
597
|
+
Sprint 2 的核心交付物(ghs-sprint tool + 3 个 writer 模块 + sprint-planning prompt + auto-archive 链路)端到端可用。ghs-sprint 正确组合了 archive-sprint(s1-feat-008)、append-sprint(s2-feat-001)与 SPRINT_PLANNING_PROMPT(s2-feat-002);update-feature-status 正确驱动 pending→in_progress→completed 的状态机;ghs-status 正确反映新 sprint 与 feature 计数;auto-archive 在创建 s2 时把已完成的 s1 归档到 `.ghs/archived/s1_*`。未发现 bug。
|