@c-d-cc/reap 0.5.0 → 0.7.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.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/cli.js +84 -9
- package/dist/templates/artifacts/01-objective.md +17 -0
- package/dist/templates/brainstorm/frame.html +125 -0
- package/dist/templates/brainstorm/server.cjs +306 -0
- package/dist/templates/brainstorm/spec-reviewer-prompt.md +52 -0
- package/dist/templates/brainstorm/start-server.sh +67 -0
- package/dist/templates/brainstorm/visual-companion-guide.md +120 -0
- package/dist/templates/commands/reap.help.md +9 -1
- package/dist/templates/commands/reap.objective.md +105 -9
- package/dist/templates/commands/reap.pull.md +3 -1
- package/dist/templates/hooks/reap-guide.md +1 -1
- package/dist/templates/hooks/session-start.cjs +42 -1
- package/package.json +1 -1
package/README.ja.md
CHANGED
|
@@ -112,7 +112,7 @@ Objective → Planning → Implementation ⟷ Validation → Completion
|
|
|
112
112
|
|
|
113
113
|
| ステージ | 内容 | 成果物 |
|
|
114
114
|
|----------|------|--------|
|
|
115
|
-
| **Objective** |
|
|
115
|
+
| **Objective** | 構造化ブレインストーミングによる目標・設計定義:明確化質問、アプローチ代案、セクション別設計、ビジュアルコンパニオン、Specレビュー | `01-objective.md` |
|
|
116
116
|
| **Planning** | タスク分解、実装アプローチ、依存関係 | `02-planning.md` |
|
|
117
117
|
| **Implementation** | AI+Human協力でコード実装 | `03-implementation.md` |
|
|
118
118
|
| **Validation** | テスト実行、完了条件の確認 | `04-validation.md` |
|
package/README.ko.md
CHANGED
|
@@ -112,7 +112,7 @@ Objective → Planning → Implementation ⟷ Validation → Completion
|
|
|
112
112
|
|
|
113
113
|
| 단계 | 하는 일 | 산출물 |
|
|
114
114
|
|------|---------|--------|
|
|
115
|
-
| **Objective** | 목표
|
|
115
|
+
| **Objective** | 구조화된 브레인스토밍으로 목표 및 설계 정의: 명확화 질문, 접근법 대안, 섹션별 설계, 비주얼 컴패니언, Spec 리뷰 | `01-objective.md` |
|
|
116
116
|
| **Planning** | 태스크 분해, 구현 접근법, 의존관계 | `02-planning.md` |
|
|
117
117
|
| **Implementation** | AI+Human 협업으로 코드 구현 | `03-implementation.md` |
|
|
118
118
|
| **Validation** | 테스트 실행, 완료 조건 점검 | `04-validation.md` |
|
package/README.md
CHANGED
|
@@ -111,7 +111,7 @@ Objective → Planning → Implementation ⟷ Validation → Completion
|
|
|
111
111
|
|
|
112
112
|
| Stage | What happens | Artifact |
|
|
113
113
|
|-------|-------------|----------|
|
|
114
|
-
| **Objective** | Define goal,
|
|
114
|
+
| **Objective** | Define goal through structured brainstorming: clarifying questions, 2-3 approach alternatives, sectional design approval, optional visual companion, and spec review loop | `01-objective.md` |
|
|
115
115
|
| **Planning** | Break down tasks, choose approach, map dependencies | `02-planning.md` |
|
|
116
116
|
| **Implementation** | Build with AI + human collaboration | `03-implementation.md` |
|
|
117
117
|
| **Validation** | Run tests, verify completion criteria | `04-validation.md` |
|
package/README.zh-CN.md
CHANGED
|
@@ -112,7 +112,7 @@ Objective → Planning → Implementation ⟷ Validation → Completion
|
|
|
112
112
|
|
|
113
113
|
| 阶段 | 内容 | 产出物 |
|
|
114
114
|
|------|------|--------|
|
|
115
|
-
| **Objective** |
|
|
115
|
+
| **Objective** | 通过结构化头脑风暴定义目标与设计:澄清问题、方案替代、分段设计、视觉伴侣、Spec审查 | `01-objective.md` |
|
|
116
116
|
| **Planning** | 任务分解、实施方案、依赖关系 | `02-planning.md` |
|
|
117
117
|
| **Implementation** | AI+人类协作编写代码 | `03-implementation.md` |
|
|
118
118
|
| **Validation** | 执行测试、检查完成条件 | `04-validation.md` |
|
package/dist/cli.js
CHANGED
|
@@ -8918,6 +8918,9 @@ class ReapPaths {
|
|
|
8918
8918
|
static get userReapTemplates() {
|
|
8919
8919
|
return join(ReapPaths.userReapDir, "templates");
|
|
8920
8920
|
}
|
|
8921
|
+
static get userReapCommands() {
|
|
8922
|
+
return join(ReapPaths.userReapDir, "commands");
|
|
8923
|
+
}
|
|
8921
8924
|
static get packageTemplatesDir() {
|
|
8922
8925
|
const dir = dirname(fileURLToPath(import.meta.url));
|
|
8923
8926
|
const distPath = join(dir, "templates");
|
|
@@ -9045,12 +9048,22 @@ class ClaudeCodeAdapter {
|
|
|
9045
9048
|
return this.commandsDir;
|
|
9046
9049
|
}
|
|
9047
9050
|
async installCommands(commandNames, sourceDir) {
|
|
9048
|
-
await mkdir(
|
|
9051
|
+
await mkdir(ReapPaths.userReapCommands, { recursive: true });
|
|
9049
9052
|
for (const cmd of commandNames) {
|
|
9050
9053
|
const src = join2(sourceDir, `${cmd}.md`);
|
|
9051
|
-
const dest = join2(
|
|
9054
|
+
const dest = join2(ReapPaths.userReapCommands, `${cmd}.md`);
|
|
9052
9055
|
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
9053
9056
|
}
|
|
9057
|
+
await mkdir(this.commandsDir, { recursive: true });
|
|
9058
|
+
for (const cmd of commandNames) {
|
|
9059
|
+
const dest = join2(this.commandsDir, `${cmd}.md`);
|
|
9060
|
+
const redirectContent = `---
|
|
9061
|
+
description: "REAP — redirected to ~/.reap/commands/"
|
|
9062
|
+
---
|
|
9063
|
+
Read \`~/.reap/commands/${cmd}.md\` and follow the instructions there.
|
|
9064
|
+
`;
|
|
9065
|
+
await writeTextFile(dest, redirectContent);
|
|
9066
|
+
}
|
|
9054
9067
|
}
|
|
9055
9068
|
async removeStaleCommands(validNames) {
|
|
9056
9069
|
try {
|
|
@@ -9265,12 +9278,22 @@ class OpenCodeAdapter {
|
|
|
9265
9278
|
return this.commandsDir;
|
|
9266
9279
|
}
|
|
9267
9280
|
async installCommands(commandNames, sourceDir) {
|
|
9268
|
-
await mkdir2(
|
|
9281
|
+
await mkdir2(ReapPaths.userReapCommands, { recursive: true });
|
|
9269
9282
|
for (const cmd of commandNames) {
|
|
9270
9283
|
const src = join3(sourceDir, `${cmd}.md`);
|
|
9271
|
-
const dest = join3(
|
|
9284
|
+
const dest = join3(ReapPaths.userReapCommands, `${cmd}.md`);
|
|
9272
9285
|
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
9273
9286
|
}
|
|
9287
|
+
await mkdir2(this.commandsDir, { recursive: true });
|
|
9288
|
+
for (const cmd of commandNames) {
|
|
9289
|
+
const dest = join3(this.commandsDir, `${cmd}.md`);
|
|
9290
|
+
const redirectContent = `---
|
|
9291
|
+
description: "REAP — redirected to ~/.reap/commands/"
|
|
9292
|
+
---
|
|
9293
|
+
Read \`~/.reap/commands/${cmd}.md\` and follow the instructions there.
|
|
9294
|
+
`;
|
|
9295
|
+
await writeTextFile(dest, redirectContent);
|
|
9296
|
+
}
|
|
9274
9297
|
}
|
|
9275
9298
|
async removeStaleCommands(validNames) {
|
|
9276
9299
|
try {
|
|
@@ -9500,6 +9523,18 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
9500
9523
|
const dest = join4(mergeTemplatesDir, file);
|
|
9501
9524
|
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
9502
9525
|
}
|
|
9526
|
+
log("Installing brainstorm server...");
|
|
9527
|
+
const brainstormSourceDir = join4(ReapPaths.packageTemplatesDir, "brainstorm");
|
|
9528
|
+
const brainstormDestDir = join4(paths.root, "brainstorm");
|
|
9529
|
+
await mkdir3(brainstormDestDir, { recursive: true });
|
|
9530
|
+
const brainstormFiles = ["server.cjs", "frame.html", "start-server.sh"];
|
|
9531
|
+
for (const file of brainstormFiles) {
|
|
9532
|
+
const src = join4(brainstormSourceDir, file);
|
|
9533
|
+
const dest = join4(brainstormDestDir, file);
|
|
9534
|
+
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
9535
|
+
if (file.endsWith(".sh"))
|
|
9536
|
+
await chmod(dest, 493);
|
|
9537
|
+
}
|
|
9503
9538
|
log("Installing hook conditions...");
|
|
9504
9539
|
const conditionsSourceDir = join4(ReapPaths.packageTemplatesDir, "conditions");
|
|
9505
9540
|
const conditionsDestDir = join4(paths.hooks, "conditions");
|
|
@@ -9529,7 +9564,7 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
9529
9564
|
}
|
|
9530
9565
|
|
|
9531
9566
|
// src/cli/commands/update.ts
|
|
9532
|
-
import { readdir as readdir8, unlink as unlink3, rm as rm2, mkdir as mkdir6 } from "fs/promises";
|
|
9567
|
+
import { readdir as readdir8, unlink as unlink3, rm as rm2, mkdir as mkdir6, chmod as chmod2 } from "fs/promises";
|
|
9533
9568
|
import { join as join9 } from "path";
|
|
9534
9569
|
|
|
9535
9570
|
// src/core/hooks.ts
|
|
@@ -10318,21 +10353,41 @@ async function updateProject(projectRoot, dryRun = false) {
|
|
|
10318
10353
|
const adapters = await AgentRegistry.getActiveAdapters(config ?? undefined);
|
|
10319
10354
|
const commandsDir = ReapPaths.packageCommandsDir;
|
|
10320
10355
|
const commandFiles = await readdir8(commandsDir);
|
|
10356
|
+
await mkdir6(ReapPaths.userReapCommands, { recursive: true });
|
|
10357
|
+
for (const file of commandFiles) {
|
|
10358
|
+
if (!file.endsWith(".md"))
|
|
10359
|
+
continue;
|
|
10360
|
+
const src = await readTextFileOrThrow(join9(commandsDir, file));
|
|
10361
|
+
const dest = join9(ReapPaths.userReapCommands, file);
|
|
10362
|
+
const existing = await readTextFile(dest);
|
|
10363
|
+
if (existing !== null && existing === src) {
|
|
10364
|
+
result.skipped.push(`~/.reap/commands/${file}`);
|
|
10365
|
+
} else {
|
|
10366
|
+
if (!dryRun)
|
|
10367
|
+
await writeTextFile(dest, src);
|
|
10368
|
+
result.updated.push(`~/.reap/commands/${file}`);
|
|
10369
|
+
}
|
|
10370
|
+
}
|
|
10321
10371
|
for (const adapter of adapters) {
|
|
10322
10372
|
const agentCmdDir = adapter.getCommandsDir();
|
|
10323
10373
|
const label = `${adapter.displayName}`;
|
|
10324
10374
|
for (const file of commandFiles) {
|
|
10325
10375
|
if (!file.endsWith(".md"))
|
|
10326
10376
|
continue;
|
|
10327
|
-
const
|
|
10377
|
+
const cmdName = file.replace(/\.md$/, "");
|
|
10378
|
+
const redirectContent = `---
|
|
10379
|
+
description: "REAP — redirected to ~/.reap/commands/"
|
|
10380
|
+
---
|
|
10381
|
+
Read \`~/.reap/commands/${cmdName}.md\` and follow the instructions there.
|
|
10382
|
+
`;
|
|
10328
10383
|
const dest = join9(agentCmdDir, file);
|
|
10329
10384
|
const existingContent = await readTextFile(dest);
|
|
10330
|
-
if (existingContent !== null && existingContent ===
|
|
10385
|
+
if (existingContent !== null && existingContent === redirectContent) {
|
|
10331
10386
|
result.skipped.push(`[${label}] commands/${file}`);
|
|
10332
10387
|
} else {
|
|
10333
10388
|
if (!dryRun) {
|
|
10334
10389
|
await mkdir6(agentCmdDir, { recursive: true });
|
|
10335
|
-
await writeTextFile(dest,
|
|
10390
|
+
await writeTextFile(dest, redirectContent);
|
|
10336
10391
|
}
|
|
10337
10392
|
result.updated.push(`[${label}] commands/${file}`);
|
|
10338
10393
|
}
|
|
@@ -10382,6 +10437,26 @@ async function updateProject(projectRoot, dryRun = false) {
|
|
|
10382
10437
|
result.updated.push(`~/.reap/templates/merge/${file}`);
|
|
10383
10438
|
}
|
|
10384
10439
|
}
|
|
10440
|
+
const brainstormSourceDir = join9(ReapPaths.packageTemplatesDir, "brainstorm");
|
|
10441
|
+
const brainstormDestDir = join9(paths.root, "brainstorm");
|
|
10442
|
+
await mkdir6(brainstormDestDir, { recursive: true });
|
|
10443
|
+
const brainstormFiles = ["server.cjs", "frame.html", "start-server.sh"];
|
|
10444
|
+
for (const file of brainstormFiles) {
|
|
10445
|
+
const src = await readTextFileOrThrow(join9(brainstormSourceDir, file));
|
|
10446
|
+
const dest = join9(brainstormDestDir, file);
|
|
10447
|
+
const existing = await readTextFile(dest);
|
|
10448
|
+
if (existing !== null && existing === src) {
|
|
10449
|
+
result.skipped.push(`.reap/brainstorm/${file}`);
|
|
10450
|
+
} else {
|
|
10451
|
+
if (!dryRun) {
|
|
10452
|
+
await writeTextFile(dest, src);
|
|
10453
|
+
if (file.endsWith(".sh")) {
|
|
10454
|
+
await chmod2(dest, 493);
|
|
10455
|
+
}
|
|
10456
|
+
}
|
|
10457
|
+
result.updated.push(`.reap/brainstorm/${file}`);
|
|
10458
|
+
}
|
|
10459
|
+
}
|
|
10385
10460
|
const migrations = await migrateHooks(dryRun);
|
|
10386
10461
|
for (const m of migrations.results) {
|
|
10387
10462
|
if (m.action === "migrated") {
|
|
@@ -10557,7 +10632,7 @@ async function fixProject(projectRoot) {
|
|
|
10557
10632
|
|
|
10558
10633
|
// src/cli/index.ts
|
|
10559
10634
|
import { join as join10 } from "path";
|
|
10560
|
-
program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.
|
|
10635
|
+
program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.7.0");
|
|
10561
10636
|
program.command("init").description("Initialize a new REAP project (Genesis)").argument("[project-name]", "Project name (defaults to current directory name)").option("-m, --mode <mode>", "Entry mode: greenfield, migration, adoption", "greenfield").option("-p, --preset <preset>", "Bootstrap with a genome preset (e.g., bun-hono-react)").action(async (projectName, options) => {
|
|
10562
10637
|
try {
|
|
10563
10638
|
const cwd = process.cwd();
|
|
@@ -14,6 +14,23 @@
|
|
|
14
14
|
### Non-Functional Requirements
|
|
15
15
|
|
|
16
16
|
|
|
17
|
+
## Design
|
|
18
|
+
|
|
19
|
+
### Approaches Considered
|
|
20
|
+
|
|
21
|
+
| Aspect | Approach A | Approach B |
|
|
22
|
+
|--------|-----------|-----------|
|
|
23
|
+
| Summary | | |
|
|
24
|
+
| Pros | | |
|
|
25
|
+
| Cons | | |
|
|
26
|
+
| Recommendation | | |
|
|
27
|
+
|
|
28
|
+
### Selected Design
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### Design Approval History
|
|
32
|
+
|
|
33
|
+
|
|
17
34
|
## Scope
|
|
18
35
|
- **Related Genome Areas**:
|
|
19
36
|
- **Expected Change Scope**:
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>REAP Brainstorm</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #0f1117;
|
|
10
|
+
--surface: #1a1d27;
|
|
11
|
+
--border: #2a2d3a;
|
|
12
|
+
--text: #e1e4ed;
|
|
13
|
+
--text-muted: #8b8fa3;
|
|
14
|
+
--accent: #6c8cff;
|
|
15
|
+
--accent-soft: rgba(108,140,255,0.12);
|
|
16
|
+
--success: #4ade80;
|
|
17
|
+
--warning: #fbbf24;
|
|
18
|
+
--radius: 8px;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
22
|
+
|
|
23
|
+
body {
|
|
24
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
25
|
+
background: var(--bg);
|
|
26
|
+
color: var(--text);
|
|
27
|
+
line-height: 1.6;
|
|
28
|
+
padding: 2rem;
|
|
29
|
+
max-width: 960px;
|
|
30
|
+
margin: 0 auto;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
h2 { font-size: 1.25rem; font-weight: 600; margin-bottom: 1rem; color: var(--text); }
|
|
34
|
+
h3 { font-size: 1rem; font-weight: 600; margin-bottom: 0.75rem; color: var(--text-muted); }
|
|
35
|
+
.subtitle { color: var(--text-muted); font-size: 0.95rem; }
|
|
36
|
+
.section { margin-bottom: 2rem; }
|
|
37
|
+
.label { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted); margin-bottom: 0.5rem; }
|
|
38
|
+
|
|
39
|
+
/* Options: single/multi select */
|
|
40
|
+
.options { display: flex; flex-direction: column; gap: 0.5rem; }
|
|
41
|
+
.option {
|
|
42
|
+
padding: 1rem 1.25rem;
|
|
43
|
+
border: 1px solid var(--border);
|
|
44
|
+
border-radius: var(--radius);
|
|
45
|
+
cursor: pointer;
|
|
46
|
+
transition: all 0.15s;
|
|
47
|
+
background: var(--surface);
|
|
48
|
+
}
|
|
49
|
+
.option:hover { border-color: var(--accent); background: var(--accent-soft); }
|
|
50
|
+
.option.selected { border-color: var(--accent); background: var(--accent-soft); box-shadow: 0 0 0 1px var(--accent); }
|
|
51
|
+
|
|
52
|
+
/* Cards: visual design cards */
|
|
53
|
+
.cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1rem; }
|
|
54
|
+
.card {
|
|
55
|
+
padding: 1.25rem;
|
|
56
|
+
border: 1px solid var(--border);
|
|
57
|
+
border-radius: var(--radius);
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
transition: all 0.15s;
|
|
60
|
+
background: var(--surface);
|
|
61
|
+
}
|
|
62
|
+
.card:hover { border-color: var(--accent); transform: translateY(-2px); }
|
|
63
|
+
.card.selected { border-color: var(--accent); background: var(--accent-soft); }
|
|
64
|
+
.card h3 { margin-bottom: 0.5rem; }
|
|
65
|
+
|
|
66
|
+
/* Mockup containers */
|
|
67
|
+
.mockup {
|
|
68
|
+
border: 1px solid var(--border);
|
|
69
|
+
border-radius: var(--radius);
|
|
70
|
+
overflow: hidden;
|
|
71
|
+
background: var(--surface);
|
|
72
|
+
}
|
|
73
|
+
.mockup-header {
|
|
74
|
+
padding: 0.5rem 1rem;
|
|
75
|
+
background: var(--border);
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
gap: 0.5rem;
|
|
79
|
+
}
|
|
80
|
+
.mockup-header::before {
|
|
81
|
+
content: '';
|
|
82
|
+
display: inline-flex;
|
|
83
|
+
gap: 4px;
|
|
84
|
+
width: 48px;
|
|
85
|
+
height: 12px;
|
|
86
|
+
background: radial-gradient(circle at 6px 6px, #ff5f56 5px, transparent 5px),
|
|
87
|
+
radial-gradient(circle at 22px 6px, #ffbd2e 5px, transparent 5px),
|
|
88
|
+
radial-gradient(circle at 38px 6px, #27c93f 5px, transparent 5px);
|
|
89
|
+
}
|
|
90
|
+
.mockup-body { padding: 1.25rem; }
|
|
91
|
+
|
|
92
|
+
/* Split comparison */
|
|
93
|
+
.split { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
|
94
|
+
@media (max-width: 600px) { .split { grid-template-columns: 1fr; } }
|
|
95
|
+
|
|
96
|
+
/* Pros and cons */
|
|
97
|
+
.pros-cons { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
|
98
|
+
.pros-cons .pros { color: var(--success); }
|
|
99
|
+
.pros-cons .cons { color: var(--warning); }
|
|
100
|
+
.pros-cons ul { list-style: none; padding: 0; }
|
|
101
|
+
.pros-cons li { padding: 0.25rem 0; }
|
|
102
|
+
.pros-cons .pros li::before { content: '+ '; font-weight: bold; }
|
|
103
|
+
.pros-cons .cons li::before { content: '- '; font-weight: bold; }
|
|
104
|
+
|
|
105
|
+
/* Mock UI elements */
|
|
106
|
+
.mock-nav { height: 48px; background: var(--border); border-radius: var(--radius) var(--radius) 0 0; display: flex; align-items: center; padding: 0 1rem; }
|
|
107
|
+
.mock-sidebar { background: var(--surface); border-right: 1px solid var(--border); padding: 1rem; min-height: 200px; }
|
|
108
|
+
.mock-content { padding: 1rem; flex: 1; }
|
|
109
|
+
.mock-button { display: inline-block; padding: 0.5rem 1rem; background: var(--accent); border-radius: var(--radius); color: white; font-size: 0.85rem; cursor: pointer; }
|
|
110
|
+
.mock-input { display: block; width: 100%; padding: 0.5rem 0.75rem; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); font-size: 0.9rem; }
|
|
111
|
+
.placeholder { background: var(--border); border-radius: 4px; height: 1em; margin: 0.25rem 0; }
|
|
112
|
+
|
|
113
|
+
/* Table for trade-offs */
|
|
114
|
+
table { width: 100%; border-collapse: collapse; margin: 1rem 0; }
|
|
115
|
+
th, td { padding: 0.75rem 1rem; text-align: left; border-bottom: 1px solid var(--border); }
|
|
116
|
+
th { color: var(--text-muted); font-weight: 500; font-size: 0.85rem; }
|
|
117
|
+
</style>
|
|
118
|
+
</head>
|
|
119
|
+
<body>
|
|
120
|
+
{{CONTENT}}
|
|
121
|
+
<script>
|
|
122
|
+
{{WS_SCRIPT}}
|
|
123
|
+
</script>
|
|
124
|
+
</body>
|
|
125
|
+
</html>
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// REAP Visual Companion Server
|
|
3
|
+
// Zero-dependency HTTP + WebSocket server using Node.js built-in modules only.
|
|
4
|
+
// Serves HTML fragments from a screen directory, auto-wraps in frame template,
|
|
5
|
+
// watches for file changes and pushes updates via WebSocket.
|
|
6
|
+
|
|
7
|
+
const http = require('http');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const crypto = require('crypto');
|
|
11
|
+
const url = require('url');
|
|
12
|
+
|
|
13
|
+
// --- Configuration ---
|
|
14
|
+
const PORT = parseInt(process.env.BRAINSTORM_PORT || '3210', 10);
|
|
15
|
+
const HOST = process.env.BRAINSTORM_HOST || '127.0.0.1';
|
|
16
|
+
const URL_HOST = process.env.BRAINSTORM_URL_HOST || `http://${HOST}:${PORT}`;
|
|
17
|
+
const IDLE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
18
|
+
|
|
19
|
+
// Screen directory: where HTML fragments are written by the AI agent
|
|
20
|
+
const SCREEN_DIR = process.env.BRAINSTORM_DIR || path.join(process.cwd(), '.reap', 'brainstorm');
|
|
21
|
+
const SERVER_INFO_FILE = path.join(SCREEN_DIR, '.server-info');
|
|
22
|
+
const SERVER_STOPPED_FILE = path.join(SCREEN_DIR, '.server-stopped');
|
|
23
|
+
const EVENTS_FILE = path.join(SCREEN_DIR, '.events');
|
|
24
|
+
|
|
25
|
+
// Frame template path (same directory as this script)
|
|
26
|
+
const FRAME_TEMPLATE_PATH = path.join(__dirname, 'frame.html');
|
|
27
|
+
|
|
28
|
+
// --- State ---
|
|
29
|
+
let idleTimer = null;
|
|
30
|
+
const wsClients = new Set();
|
|
31
|
+
|
|
32
|
+
// --- Helpers ---
|
|
33
|
+
|
|
34
|
+
function ensureDir(dir) {
|
|
35
|
+
if (!fs.existsSync(dir)) {
|
|
36
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function resetIdleTimer() {
|
|
41
|
+
if (idleTimer) clearTimeout(idleTimer);
|
|
42
|
+
idleTimer = setTimeout(() => {
|
|
43
|
+
console.log('[brainstorm] Idle timeout reached. Shutting down.');
|
|
44
|
+
shutdown();
|
|
45
|
+
}, IDLE_TIMEOUT_MS);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function shutdown() {
|
|
49
|
+
try {
|
|
50
|
+
fs.writeFileSync(SERVER_STOPPED_FILE, new Date().toISOString());
|
|
51
|
+
if (fs.existsSync(SERVER_INFO_FILE)) fs.unlinkSync(SERVER_INFO_FILE);
|
|
52
|
+
} catch (_) { /* best effort */ }
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getNewestHtmlFile() {
|
|
57
|
+
ensureDir(SCREEN_DIR);
|
|
58
|
+
const files = fs.readdirSync(SCREEN_DIR)
|
|
59
|
+
.filter(f => f.endsWith('.html') && !f.startsWith('.'))
|
|
60
|
+
.map(f => ({ name: f, mtime: fs.statSync(path.join(SCREEN_DIR, f)).mtimeMs }))
|
|
61
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
62
|
+
return files.length > 0 ? files[0].name : null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function loadFrame() {
|
|
66
|
+
if (fs.existsSync(FRAME_TEMPLATE_PATH)) {
|
|
67
|
+
return fs.readFileSync(FRAME_TEMPLATE_PATH, 'utf-8');
|
|
68
|
+
}
|
|
69
|
+
return '<!DOCTYPE html><html><head><meta charset="utf-8"><title>REAP Brainstorm</title></head><body>{{CONTENT}}<script>{{WS_SCRIPT}}</script></body></html>';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function wrapInFrame(content) {
|
|
73
|
+
const frame = loadFrame();
|
|
74
|
+
const wsScript = `
|
|
75
|
+
(function() {
|
|
76
|
+
var ws = new WebSocket('ws://' + location.host + '/ws');
|
|
77
|
+
ws.onmessage = function(e) {
|
|
78
|
+
var data = JSON.parse(e.data);
|
|
79
|
+
if (data.type === 'reload') location.reload();
|
|
80
|
+
};
|
|
81
|
+
ws.onclose = function() { setTimeout(function() { location.reload(); }, 2000); };
|
|
82
|
+
|
|
83
|
+
document.addEventListener('click', function(e) {
|
|
84
|
+
var el = e.target.closest('[data-choice]');
|
|
85
|
+
if (!el) return;
|
|
86
|
+
var container = el.closest('.options, .cards');
|
|
87
|
+
var isMulti = container && container.hasAttribute('data-multiselect');
|
|
88
|
+
if (!isMulti) {
|
|
89
|
+
container.querySelectorAll('[data-choice]').forEach(function(s) { s.classList.remove('selected'); });
|
|
90
|
+
}
|
|
91
|
+
el.classList.toggle('selected');
|
|
92
|
+
var event = {
|
|
93
|
+
type: 'click',
|
|
94
|
+
choice: el.getAttribute('data-choice'),
|
|
95
|
+
text: el.textContent.trim().substring(0, 200),
|
|
96
|
+
timestamp: Math.floor(Date.now() / 1000)
|
|
97
|
+
};
|
|
98
|
+
ws.send(JSON.stringify(event));
|
|
99
|
+
});
|
|
100
|
+
})();`;
|
|
101
|
+
|
|
102
|
+
// Check if content is a full HTML document
|
|
103
|
+
if (content.trim().startsWith('<!DOCTYPE') || content.trim().startsWith('<html')) {
|
|
104
|
+
return content;
|
|
105
|
+
}
|
|
106
|
+
return frame.replace('{{CONTENT}}', content).replace('{{WS_SCRIPT}}', wsScript);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// --- WebSocket (RFC 6455 minimal implementation) ---
|
|
110
|
+
|
|
111
|
+
function parseWsFrame(buffer) {
|
|
112
|
+
if (buffer.length < 2) return null;
|
|
113
|
+
const secondByte = buffer[1];
|
|
114
|
+
const masked = (secondByte & 0x80) !== 0;
|
|
115
|
+
let payloadLen = secondByte & 0x7f;
|
|
116
|
+
let offset = 2;
|
|
117
|
+
|
|
118
|
+
if (payloadLen === 126) {
|
|
119
|
+
if (buffer.length < 4) return null;
|
|
120
|
+
payloadLen = buffer.readUInt16BE(2);
|
|
121
|
+
offset = 4;
|
|
122
|
+
} else if (payloadLen === 127) {
|
|
123
|
+
if (buffer.length < 10) return null;
|
|
124
|
+
payloadLen = Number(buffer.readBigUInt64BE(2));
|
|
125
|
+
offset = 10;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let maskKey = null;
|
|
129
|
+
if (masked) {
|
|
130
|
+
if (buffer.length < offset + 4) return null;
|
|
131
|
+
maskKey = buffer.slice(offset, offset + 4);
|
|
132
|
+
offset += 4;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (buffer.length < offset + payloadLen) return null;
|
|
136
|
+
|
|
137
|
+
let payload = buffer.slice(offset, offset + payloadLen);
|
|
138
|
+
if (masked && maskKey) {
|
|
139
|
+
for (let i = 0; i < payload.length; i++) {
|
|
140
|
+
payload[i] ^= maskKey[i & 3];
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const opcode = buffer[0] & 0x0f;
|
|
145
|
+
return { opcode, payload, totalLength: offset + payloadLen };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function createWsFrame(data) {
|
|
149
|
+
const payload = Buffer.from(data, 'utf-8');
|
|
150
|
+
const len = payload.length;
|
|
151
|
+
let header;
|
|
152
|
+
if (len < 126) {
|
|
153
|
+
header = Buffer.alloc(2);
|
|
154
|
+
header[0] = 0x81; // FIN + text
|
|
155
|
+
header[1] = len;
|
|
156
|
+
} else if (len < 65536) {
|
|
157
|
+
header = Buffer.alloc(4);
|
|
158
|
+
header[0] = 0x81;
|
|
159
|
+
header[1] = 126;
|
|
160
|
+
header.writeUInt16BE(len, 2);
|
|
161
|
+
} else {
|
|
162
|
+
header = Buffer.alloc(10);
|
|
163
|
+
header[0] = 0x81;
|
|
164
|
+
header[1] = 127;
|
|
165
|
+
header.writeBigUInt64BE(BigInt(len), 2);
|
|
166
|
+
}
|
|
167
|
+
return Buffer.concat([header, payload]);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function broadcastWs(message) {
|
|
171
|
+
const frame = createWsFrame(JSON.stringify(message));
|
|
172
|
+
for (const client of wsClients) {
|
|
173
|
+
try { client.write(frame); } catch (_) { wsClients.delete(client); }
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// --- HTTP Server ---
|
|
178
|
+
|
|
179
|
+
const server = http.createServer((req, res) => {
|
|
180
|
+
resetIdleTimer();
|
|
181
|
+
const parsed = url.parse(req.url, true);
|
|
182
|
+
const pathname = parsed.pathname;
|
|
183
|
+
|
|
184
|
+
// Serve newest HTML file
|
|
185
|
+
if (pathname === '/') {
|
|
186
|
+
const newest = getNewestHtmlFile();
|
|
187
|
+
if (!newest) {
|
|
188
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
189
|
+
res.end(wrapInFrame('<div style="display:flex;align-items:center;justify-content:center;min-height:60vh"><p style="color:#888;font-size:1.2em;">Waiting for content...</p></div>'));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const content = fs.readFileSync(path.join(SCREEN_DIR, newest), 'utf-8');
|
|
193
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
194
|
+
res.end(wrapInFrame(content));
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Serve specific files from screen directory
|
|
199
|
+
if (pathname.startsWith('/files/')) {
|
|
200
|
+
const filename = path.basename(pathname);
|
|
201
|
+
const filePath = path.join(SCREEN_DIR, filename);
|
|
202
|
+
if (fs.existsSync(filePath)) {
|
|
203
|
+
const ext = path.extname(filename).toLowerCase();
|
|
204
|
+
const mimeTypes = { '.html': 'text/html', '.css': 'text/css', '.js': 'application/javascript', '.png': 'image/png', '.jpg': 'image/jpeg', '.svg': 'image/svg+xml' };
|
|
205
|
+
res.writeHead(200, { 'Content-Type': (mimeTypes[ext] || 'application/octet-stream') + '; charset=utf-8' });
|
|
206
|
+
res.end(fs.readFileSync(filePath));
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
res.writeHead(404);
|
|
212
|
+
res.end('Not Found');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// WebSocket upgrade
|
|
216
|
+
server.on('upgrade', (req, socket) => {
|
|
217
|
+
if (req.url !== '/ws') { socket.destroy(); return; }
|
|
218
|
+
|
|
219
|
+
const key = req.headers['sec-websocket-key'];
|
|
220
|
+
const accept = crypto.createHash('sha1')
|
|
221
|
+
.update(key + '258EAFA5-E914-47DA-95CA-5AB5DC085B11')
|
|
222
|
+
.digest('base64');
|
|
223
|
+
|
|
224
|
+
socket.write(
|
|
225
|
+
'HTTP/1.1 101 Switching Protocols\r\n' +
|
|
226
|
+
'Upgrade: websocket\r\n' +
|
|
227
|
+
'Connection: Upgrade\r\n' +
|
|
228
|
+
`Sec-WebSocket-Accept: ${accept}\r\n\r\n`
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
wsClients.add(socket);
|
|
232
|
+
let buffer = Buffer.alloc(0);
|
|
233
|
+
|
|
234
|
+
socket.on('data', (data) => {
|
|
235
|
+
resetIdleTimer();
|
|
236
|
+
buffer = Buffer.concat([buffer, data]);
|
|
237
|
+
while (true) {
|
|
238
|
+
const frame = parseWsFrame(buffer);
|
|
239
|
+
if (!frame) break;
|
|
240
|
+
buffer = buffer.slice(frame.totalLength);
|
|
241
|
+
|
|
242
|
+
if (frame.opcode === 0x08) { // close
|
|
243
|
+
wsClients.delete(socket);
|
|
244
|
+
socket.end();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (frame.opcode === 0x09) { // ping
|
|
248
|
+
const pong = Buffer.alloc(2);
|
|
249
|
+
pong[0] = 0x8a; pong[1] = 0;
|
|
250
|
+
socket.write(pong);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
if (frame.opcode === 0x01) { // text
|
|
254
|
+
try {
|
|
255
|
+
const event = frame.payload.toString('utf-8');
|
|
256
|
+
fs.appendFileSync(EVENTS_FILE, event + '\n');
|
|
257
|
+
} catch (_) { /* best effort */ }
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
socket.on('close', () => wsClients.delete(socket));
|
|
263
|
+
socket.on('error', () => wsClients.delete(socket));
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// --- File Watcher ---
|
|
267
|
+
|
|
268
|
+
function startWatcher() {
|
|
269
|
+
ensureDir(SCREEN_DIR);
|
|
270
|
+
let debounce = null;
|
|
271
|
+
try {
|
|
272
|
+
fs.watch(SCREEN_DIR, (eventType, filename) => {
|
|
273
|
+
if (!filename || filename.startsWith('.') || !filename.endsWith('.html')) return;
|
|
274
|
+
if (debounce) clearTimeout(debounce);
|
|
275
|
+
debounce = setTimeout(() => {
|
|
276
|
+
// Clear events file when new HTML is pushed
|
|
277
|
+
try { fs.writeFileSync(EVENTS_FILE, ''); } catch (_) {}
|
|
278
|
+
broadcastWs({ type: 'reload' });
|
|
279
|
+
}, 100);
|
|
280
|
+
});
|
|
281
|
+
} catch (err) {
|
|
282
|
+
console.error('[brainstorm] File watcher error:', err.message);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// --- Startup ---
|
|
287
|
+
|
|
288
|
+
ensureDir(SCREEN_DIR);
|
|
289
|
+
|
|
290
|
+
// Remove stale stopped marker
|
|
291
|
+
if (fs.existsSync(SERVER_STOPPED_FILE)) {
|
|
292
|
+
fs.unlinkSync(SERVER_STOPPED_FILE);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
server.listen(PORT, HOST, () => {
|
|
296
|
+
const info = { url: URL_HOST, port: PORT, pid: process.pid, startedAt: new Date().toISOString() };
|
|
297
|
+
fs.writeFileSync(SERVER_INFO_FILE, JSON.stringify(info, null, 2));
|
|
298
|
+
console.log(`[brainstorm] Visual Companion running at ${URL_HOST}`);
|
|
299
|
+
console.log(`[brainstorm] Screen directory: ${SCREEN_DIR}`);
|
|
300
|
+
console.log(`[brainstorm] Idle timeout: 30 minutes`);
|
|
301
|
+
resetIdleTimer();
|
|
302
|
+
startWatcher();
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
process.on('SIGINT', shutdown);
|
|
306
|
+
process.on('SIGTERM', shutdown);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Spec Document Review
|
|
2
|
+
|
|
3
|
+
You are a spec-document-reviewer subagent. Your job is to review the REAP Objective artifact (`01-objective.md`) for quality issues that would cause problems during planning and implementation.
|
|
4
|
+
|
|
5
|
+
## What to Check
|
|
6
|
+
|
|
7
|
+
| Category | What to Look For |
|
|
8
|
+
|----------|------------------|
|
|
9
|
+
| Completeness | TODOs, placeholders, "TBD", incomplete sections, missing completion criteria |
|
|
10
|
+
| Consistency | Internal contradictions, conflicting requirements, mismatched scope vs requirements |
|
|
11
|
+
| Clarity | Requirements ambiguous enough to cause someone to build the wrong thing |
|
|
12
|
+
| Scope | Focused enough for a single generation — not covering multiple independent subsystems |
|
|
13
|
+
| YAGNI | Unrequested features, over-engineering, unnecessary complexity |
|
|
14
|
+
| Verifiability | Completion criteria that cannot be objectively verified (vague: "improve", "better") |
|
|
15
|
+
|
|
16
|
+
## Calibration
|
|
17
|
+
|
|
18
|
+
Only flag issues that would cause **real problems** during planning or implementation.
|
|
19
|
+
|
|
20
|
+
**Flag these:**
|
|
21
|
+
- Missing sections that would block planning
|
|
22
|
+
- Contradictions between requirements
|
|
23
|
+
- Ambiguous requirements with multiple valid interpretations
|
|
24
|
+
- Scope too large for a single generation
|
|
25
|
+
|
|
26
|
+
**Do NOT flag:**
|
|
27
|
+
- Minor wording improvements
|
|
28
|
+
- Stylistic preferences
|
|
29
|
+
- Suggestions for additional nice-to-have features
|
|
30
|
+
- Formatting issues
|
|
31
|
+
|
|
32
|
+
## Output Format
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
## Spec Review
|
|
36
|
+
|
|
37
|
+
**Status:** Approved | Issues Found
|
|
38
|
+
|
|
39
|
+
**Issues (if any):**
|
|
40
|
+
- [Section]: [specific issue] — [why it matters for planning]
|
|
41
|
+
|
|
42
|
+
**Recommendations (advisory, do not block approval):**
|
|
43
|
+
- [suggestions for improvement]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Important
|
|
47
|
+
|
|
48
|
+
- Read the full `01-objective.md` before starting the review
|
|
49
|
+
- Cross-reference requirements against completion criteria — every criterion should map to at least one FR
|
|
50
|
+
- Check that exclusions are explicitly stated
|
|
51
|
+
- Verify that FR numbering is consistent (FR-001, FR-002, ...)
|
|
52
|
+
- Maximum 3 review iterations — if issues persist after 3 rounds, escalate to the human
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# REAP Visual Companion — Server Start Script
|
|
3
|
+
# Usage: start-server.sh [--project-dir /path/to/project] [--port 3210] [--foreground]
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
PROJECT_DIR="$(pwd)"
|
|
8
|
+
PORT="${BRAINSTORM_PORT:-3210}"
|
|
9
|
+
FOREGROUND=false
|
|
10
|
+
|
|
11
|
+
while [[ $# -gt 0 ]]; do
|
|
12
|
+
case "$1" in
|
|
13
|
+
--project-dir) PROJECT_DIR="$2"; shift 2 ;;
|
|
14
|
+
--port) PORT="$2"; shift 2 ;;
|
|
15
|
+
--foreground) FOREGROUND=true; shift ;;
|
|
16
|
+
*) shift ;;
|
|
17
|
+
esac
|
|
18
|
+
done
|
|
19
|
+
|
|
20
|
+
SCREEN_DIR="${PROJECT_DIR}/.reap/brainstorm"
|
|
21
|
+
SERVER_INFO="${SCREEN_DIR}/.server-info"
|
|
22
|
+
SERVER_STOPPED="${SCREEN_DIR}/.server-stopped"
|
|
23
|
+
|
|
24
|
+
# Find server.cjs relative to this script
|
|
25
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
26
|
+
SERVER_JS="${SCRIPT_DIR}/server.cjs"
|
|
27
|
+
|
|
28
|
+
# Ensure screen directory exists
|
|
29
|
+
mkdir -p "${SCREEN_DIR}"
|
|
30
|
+
|
|
31
|
+
# Remove stale stopped marker
|
|
32
|
+
rm -f "${SERVER_STOPPED}"
|
|
33
|
+
|
|
34
|
+
# Check if already running
|
|
35
|
+
if [ -f "${SERVER_INFO}" ]; then
|
|
36
|
+
PID=$(node -e "try{console.log(JSON.parse(require('fs').readFileSync('${SERVER_INFO}','utf-8')).pid)}catch(e){console.log('')}")
|
|
37
|
+
if [ -n "${PID}" ] && kill -0 "${PID}" 2>/dev/null; then
|
|
38
|
+
echo "[brainstorm] Server already running (PID: ${PID})"
|
|
39
|
+
cat "${SERVER_INFO}"
|
|
40
|
+
exit 0
|
|
41
|
+
fi
|
|
42
|
+
# Stale info file
|
|
43
|
+
rm -f "${SERVER_INFO}"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
export BRAINSTORM_PORT="${PORT}"
|
|
47
|
+
export BRAINSTORM_DIR="${SCREEN_DIR}"
|
|
48
|
+
|
|
49
|
+
if [ "${FOREGROUND}" = true ]; then
|
|
50
|
+
exec node "${SERVER_JS}"
|
|
51
|
+
else
|
|
52
|
+
nohup node "${SERVER_JS}" > "${SCREEN_DIR}/.server.log" 2>&1 &
|
|
53
|
+
NOHUP_PID=$!
|
|
54
|
+
|
|
55
|
+
# Wait for server-info to appear (max 5 seconds)
|
|
56
|
+
for i in $(seq 1 50); do
|
|
57
|
+
if [ -f "${SERVER_INFO}" ]; then
|
|
58
|
+
echo "[brainstorm] Server started."
|
|
59
|
+
cat "${SERVER_INFO}"
|
|
60
|
+
exit 0
|
|
61
|
+
fi
|
|
62
|
+
sleep 0.1
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
echo "[brainstorm] Warning: server may have failed to start. Check ${SCREEN_DIR}/.server.log"
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Visual Companion Guide
|
|
2
|
+
|
|
3
|
+
> REAP Objective 단계에서 비주얼 컴패니언을 사용하는 가이드.
|
|
4
|
+
> `reap.objective` 슬래시 커맨드가 이 파일을 참조한다.
|
|
5
|
+
|
|
6
|
+
## 비주얼 컴패니언이란
|
|
7
|
+
|
|
8
|
+
로컬 Node.js 서버를 통해 브라우저에 목업, 다이어그램, 비교 카드 등을 표시하여 설계 논의를 시각적으로 보조하는 도구.
|
|
9
|
+
외부 의존 없이 Node.js 내장 모듈만 사용한다.
|
|
10
|
+
|
|
11
|
+
## 제안 시점
|
|
12
|
+
|
|
13
|
+
Objective Step 5(Goal + Spec Definition) 진입 시, 시각적 질문이 예상되면 컴패니언을 제안한다.
|
|
14
|
+
제안 메시지는 **독립 메시지**로 보내야 한다 (다른 질문과 합치지 않는다):
|
|
15
|
+
|
|
16
|
+
> "이번 설계에서 목업이나 다이어그램으로 보여드리면 이해하기 쉬운 부분이 있을 수 있습니다.
|
|
17
|
+
> 브라우저에서 시각 자료를 보여드릴 수 있는 비주얼 컴패니언을 사용할까요?
|
|
18
|
+
> (로컬 서버를 띄워 브라우저에서 확인하는 방식입니다)"
|
|
19
|
+
|
|
20
|
+
유저가 거부하면 터미널 전용으로 진행한다.
|
|
21
|
+
|
|
22
|
+
## 브라우저 vs 터미널 판단 규칙
|
|
23
|
+
|
|
24
|
+
각 질문마다 판단: **유저가 읽는 것보다 보는 것이 이해에 도움이 되는가?**
|
|
25
|
+
|
|
26
|
+
### 브라우저 사용
|
|
27
|
+
- UI 목업, 와이어프레임, 레이아웃
|
|
28
|
+
- 아키텍처 다이어그램, 시스템 구성도, 데이터 흐름 맵
|
|
29
|
+
- 나란히 비교 (레이아웃, 색상, 디자인 방향)
|
|
30
|
+
- 디자인 폴리시 (느낌, 간격, 비주얼 위계)
|
|
31
|
+
- 공간 관계 (상태 머신, 플로우차트, ERD를 다이어그램으로)
|
|
32
|
+
|
|
33
|
+
### 터미널 사용
|
|
34
|
+
- 요구사항, 범위 질문 ("X는 무슨 뜻인가요?")
|
|
35
|
+
- 개념적 A/B/C 선택 (텍스트로 설명 가능한 접근법)
|
|
36
|
+
- 트레이드오프 목록, 비교표
|
|
37
|
+
- 기술 결정 (API 설계, 데이터 모델링, 아키텍처 접근)
|
|
38
|
+
- 명확화 질문 (답이 시각적 선호가 아닌 말)
|
|
39
|
+
|
|
40
|
+
### 핵심 테스트
|
|
41
|
+
|
|
42
|
+
UI 관련 질문이라도 자동으로 비주얼은 아니다.
|
|
43
|
+
- "어떤 종류의 마법사를 원하시나요?" → 개념적 → **터미널**
|
|
44
|
+
- "어떤 마법사 레이아웃이 좋으세요?" → 시각적 → **브라우저**
|
|
45
|
+
|
|
46
|
+
## 서버 기동
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# 프로젝트 루트에서 실행
|
|
50
|
+
bash .reap/brainstorm/start-server.sh
|
|
51
|
+
# 또는 직접
|
|
52
|
+
node .reap/brainstorm/server.cjs
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- `BRAINSTORM_PORT` 환경 변수로 포트 변경 (기본: 3210)
|
|
56
|
+
- `BRAINSTORM_DIR` 환경 변수로 스크린 디렉토리 변경 (기본: `.reap/brainstorm/`)
|
|
57
|
+
|
|
58
|
+
## 서버 상태 확인
|
|
59
|
+
|
|
60
|
+
- `.reap/brainstorm/.server-info` — 서버 실행 중이면 JSON 존재 (url, port, pid)
|
|
61
|
+
- `.reap/brainstorm/.server-stopped` — 서버가 종료되면 생성됨
|
|
62
|
+
- 서버가 종료된 상태에서 재기동 필요: `start-server.sh` 재실행
|
|
63
|
+
|
|
64
|
+
## HTML 작성 규칙
|
|
65
|
+
|
|
66
|
+
1. `.reap/brainstorm/` 디렉토리에 HTML 파일을 Write 도구로 작성
|
|
67
|
+
2. 시맨틱 파일명 사용 (`architecture.html`, `layout-options.html`)
|
|
68
|
+
3. 파일명 재사용 금지 (수정 시 `layout-v2.html` 사용)
|
|
69
|
+
4. **Content fragment 기본** — `<!DOCTYPE` 없이 본문만 작성하면 프레임 템플릿이 자동 래핑
|
|
70
|
+
5. 전체 HTML 제어가 필요한 경우만 full document 작성
|
|
71
|
+
|
|
72
|
+
## 사용 가능한 CSS 클래스
|
|
73
|
+
|
|
74
|
+
| 클래스 | 용도 |
|
|
75
|
+
|--------|------|
|
|
76
|
+
| `.options` + `.option[data-choice]` | A/B/C 단일 선택 |
|
|
77
|
+
| `.options[data-multiselect]` | 다중 선택 |
|
|
78
|
+
| `.cards` + `.card[data-choice]` | 비주얼 디자인 카드 |
|
|
79
|
+
| `.mockup` + `.mockup-header` + `.mockup-body` | 목업 컨테이너 |
|
|
80
|
+
| `.split` | 나란히 비교 |
|
|
81
|
+
| `.pros-cons` + `.pros` + `.cons` | 장단점 |
|
|
82
|
+
| `.mock-nav`, `.mock-sidebar`, `.mock-content` | 목업 UI 요소 |
|
|
83
|
+
| `.mock-button`, `.mock-input` | 목업 인터랙티브 요소 |
|
|
84
|
+
| `.placeholder` | 플레이스홀더 블록 |
|
|
85
|
+
| `table`, `h2`, `h3`, `.subtitle`, `.section`, `.label` | 타이포그래피 |
|
|
86
|
+
|
|
87
|
+
## 이벤트 읽기
|
|
88
|
+
|
|
89
|
+
유저가 브라우저에서 `[data-choice]` 요소를 클릭하면 WebSocket을 통해 `.events` 파일에 JSON Lines로 기록된다:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{"type":"click","choice":"a","text":"Option A","timestamp":1706000101}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- 터미널 메시지가 주 피드백 채널
|
|
96
|
+
- `.events` 파일은 보조 인터랙션 데이터
|
|
97
|
+
- 새 HTML 파일 푸시 시 `.events`는 자동 초기화
|
|
98
|
+
|
|
99
|
+
## 턴 기반 흐름
|
|
100
|
+
|
|
101
|
+
1. HTML 파일을 Write 도구로 작성
|
|
102
|
+
2. 유저에게 URL 안내 + 간략한 텍스트 설명 → **턴 종료**
|
|
103
|
+
3. 다음 턴에서 `.events` 파일 읽기 (유저 인터랙션 확인)
|
|
104
|
+
4. 터미널 메시지 + `.events`를 종합하여 다음 단계 진행
|
|
105
|
+
5. 터미널로 돌아갈 때 대기 화면 푸시:
|
|
106
|
+
```html
|
|
107
|
+
<div style="display:flex;align-items:center;justify-content:center;min-height:60vh">
|
|
108
|
+
<p class="subtitle">터미널에서 계속 진행 중...</p>
|
|
109
|
+
</div>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## 조건부 실행
|
|
113
|
+
|
|
114
|
+
비주얼 컴패니언은 brainstorming이 활성화된 경우에만 제안된다.
|
|
115
|
+
brainstorming 자체가 목표 복잡도에 따라 조건부로 실행되므로, 단순 태스크(bugfix, config, docs-only)에서는 비주얼 컴패니언도 제안되지 않는다.
|
|
116
|
+
|
|
117
|
+
## evolve 모드에서의 동작
|
|
118
|
+
|
|
119
|
+
`/reap.evolve`의 Autonomous Override가 활성화되어 있어도, brainstorming 진입 시 비주얼 컴패니언 제안은 수행한다.
|
|
120
|
+
유저가 명시적으로 거부한 경우에만 스킵한다.
|
|
@@ -56,8 +56,14 @@ Genome(`.reap/genome/`)에 프로젝트의 설계 원칙과 규칙을 기록하
|
|
|
56
56
|
| `/reap.sync` | 현재 소스코드와 Genome 간 차이를 분석하고 동기화 |
|
|
57
57
|
| `/reap.update` | REAP 최신 버전 확인 및 업그레이드 |
|
|
58
58
|
| `/reap.help` | 도움말. `/reap.help {topic}`으로 주제별 상세 설명 |
|
|
59
|
+
| | **Collaboration** |
|
|
60
|
+
| `/reap.pull {branch}` | fetch + divergence 감지 + merge generation 자동 실행 |
|
|
61
|
+
| `/reap.push` | REAP 상태 검증 + git push |
|
|
62
|
+
| `/reap.merge {branch}` | merge generation 전체 lifecycle 실행 |
|
|
63
|
+
| `/reap.merge.start` | merge generation 생성 + detect |
|
|
64
|
+
| `/reap.merge.evolve` | merge 6단계 자율 실행 |
|
|
59
65
|
|
|
60
|
-
💡 `/reap.help {topic}` — workflow, genome, backlog, strict, agents, hooks, config, evolve, regression, author ...
|
|
66
|
+
💡 `/reap.help {topic}` — workflow, genome, backlog, strict, agents, hooks, config, evolve, regression, merge, pull, push, author ...
|
|
61
67
|
|
|
62
68
|
**Config**: Strict: {on/off} · Auto-Update: {on/off} · Language: {value}
|
|
63
69
|
|
|
@@ -82,5 +88,7 @@ For command-name topics: read `reap.{name}.md` from the same directory as this f
|
|
|
82
88
|
- **regression** — `/reap.back`: 이전 stage 회귀. timeline + artifact에 Regression 섹션 기록.
|
|
83
89
|
- **minor-fix** — 5분 이내, 설계 변경 없는 수정. stage 전환 없이 현재 artifact에 기록.
|
|
84
90
|
- **compression** — 10,000줄 + 5세대 이상 시 lineage 자동 압축. L1(40줄), L2(60줄).
|
|
91
|
+
- **merge** / **collaboration** — 분산 협업: genome-first merge 6단계 (detect→mate→merge→sync→validation→completion). `/reap.pull`로 fetch+merge, `/reap.push`로 검증+push. 상세: `domain/collaboration.md`, `domain/merge-lifecycle.md`.
|
|
85
92
|
- **author** — HyeonIL Choi. Email: hichoi@c-d.cc / Homepage: https://c-d.cc / LinkedIn: https://www.linkedin.com/in/hichoi-dev / GitHub: https://github.com/casamia918
|
|
86
93
|
- **start** / **objective** / **planning** / **implementation** / **validation** / **completion** / **next** / **back** / **sync** / **status** / **update** / **help** — Read `reap.{name}.md` from same directory, explain.
|
|
94
|
+
- **pull** / **push** / **merge.start** / **merge.detect** / **merge.mate** / **merge.merge** / **merge.sync** / **merge.validation** / **merge.completion** / **merge.evolve** — Read `reap.{name}.md` from same directory, explain.
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
description: "REAP Objective — Define the goal and specification for this Generation"
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
-
# Objective (Goal Definition)
|
|
5
|
+
# Objective (Goal Definition + Brainstorming Design)
|
|
6
6
|
|
|
7
7
|
<HARD-GATE>
|
|
8
8
|
Do NOT write any code until the artifact (01-objective.md) has been confirmed by the human.
|
|
9
9
|
If the goal is ambiguous, do NOT guess — STOP and ask the human. This is non-negotiable.
|
|
10
|
+
Brainstorming is triggered based on goal complexity — simple tasks skip it, complex tasks require it. The human can always override the AI's assessment.
|
|
10
11
|
</HARD-GATE>
|
|
11
12
|
|
|
12
13
|
## Gate (Preconditions)
|
|
@@ -107,13 +108,97 @@ If the goal is ambiguous, do NOT guess — STOP and ask the human. This is non-n
|
|
|
107
108
|
- **constraints.md has empty Validation Commands** → flag as "test commands undefined"
|
|
108
109
|
- Report the genome health status to the human
|
|
109
110
|
|
|
110
|
-
### 5. Goal + Spec Definition
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
111
|
+
### 5. Goal + Spec Definition (with optional Brainstorming)
|
|
112
|
+
|
|
113
|
+
Before entering brainstorming, evaluate whether the goal requires it.
|
|
114
|
+
|
|
115
|
+
#### Complexity Gate
|
|
116
|
+
Assess the goal's complexity based on these criteria:
|
|
117
|
+
|
|
118
|
+
**Skip brainstorming** (proceed directly to Step 6 with simple goal+spec definition):
|
|
119
|
+
- Simple bugfix with clear cause and fix
|
|
120
|
+
- Configuration change or settings adjustment
|
|
121
|
+
- Documentation-only changes
|
|
122
|
+
- Single-file refactoring with obvious approach
|
|
123
|
+
- Tasks where the "what" and "how" are both already clear
|
|
124
|
+
|
|
125
|
+
**Enter brainstorming** (follow sub-steps 5a–5e):
|
|
126
|
+
- New feature development spanning multiple components
|
|
127
|
+
- Architecture changes or new module design
|
|
128
|
+
- Tasks with multiple valid implementation approaches
|
|
129
|
+
- Requirements that need further exploration or clarification
|
|
130
|
+
- Integration with external systems or complex data flows
|
|
131
|
+
|
|
132
|
+
**Under Autonomous Override**: Assess automatically. If the goal from `current.yml` clearly fits "skip" criteria, skip brainstorming.
|
|
133
|
+
**Human override**: The human can always request brainstorming ("let's brainstorm this") or skip it ("just do it", "skip brainstorming") regardless of the AI's assessment.
|
|
134
|
+
|
|
135
|
+
When skipping brainstorming, converse with the human to refine the goal:
|
|
136
|
+
- Criteria for a good goal: achievable within a single Generation, verifiable completion criteria (no vague wording), relevant genome areas identified
|
|
137
|
+
- Then proceed directly to Step 6 (Genome Gap Analysis)
|
|
138
|
+
|
|
139
|
+
When entering brainstorming, follow the structured brainstorming process below to produce a well-designed objective. Follow the sub-steps in order.
|
|
140
|
+
|
|
141
|
+
#### 5a. Visual Companion Proposal
|
|
142
|
+
- Evaluate whether this generation's goal involves visual questions (UI design, architecture diagrams, layout comparisons, etc.)
|
|
143
|
+
- If visual questions are likely, propose the Visual Companion in a **standalone message** (do NOT combine with other questions):
|
|
144
|
+
> "이번 설계에서 목업이나 다이어그램으로 보여드리면 이해하기 쉬운 부분이 있을 수 있습니다.
|
|
145
|
+
> 브라우저에서 시각 자료를 보여드릴 수 있는 비주얼 컴패니언을 사용할까요?
|
|
146
|
+
> (로컬 서버를 띄워 브라우저에서 확인하는 방식입니다)"
|
|
147
|
+
- If the human accepts: start the brainstorm server (`bash` the start script in `.reap/brainstorm/start-server.sh` or `node` the server directly). Read `src/templates/brainstorm/visual-companion-guide.md` for usage rules.
|
|
148
|
+
- If the human declines: proceed terminal-only. Do NOT offer again.
|
|
149
|
+
- **This step applies even under `/reap.evolve` Autonomous Override** — only skip if the human has explicitly declined.
|
|
150
|
+
|
|
151
|
+
#### 5b. Clarifying Questions (One at a Time)
|
|
152
|
+
- **CRITICAL**: Ask only ONE question per message. Never bundle multiple questions.
|
|
153
|
+
- Prefer **multiple choice** over open-ended questions when possible.
|
|
154
|
+
- Goal: understand purpose, constraints, success criteria, and scope.
|
|
155
|
+
- Continue asking until you have enough information to propose approaches.
|
|
156
|
+
- Good question flow:
|
|
157
|
+
1. Purpose/motivation: "What problem does this solve?"
|
|
158
|
+
2. Users/stakeholders: "Who benefits from this?"
|
|
159
|
+
3. Constraints: "Are there any hard constraints?" (with examples as choices)
|
|
160
|
+
4. Success criteria: "How will you know this is done?"
|
|
161
|
+
- Under Autonomous Override: Use existing context (genome, backlog, goal from `current.yml`) to answer these questions yourself. Only STOP and ask if genuinely ambiguous.
|
|
162
|
+
|
|
163
|
+
#### 5c. Approach Exploration (2-3 Alternatives)
|
|
164
|
+
- Propose **2-3 approaches** with clear trade-offs.
|
|
165
|
+
- Present as a comparison table:
|
|
166
|
+
|
|
167
|
+
| Aspect | Approach A | Approach B | Approach C |
|
|
168
|
+
|--------|-----------|-----------|-----------|
|
|
169
|
+
| Summary | ... | ... | ... |
|
|
170
|
+
| Pros | ... | ... | ... |
|
|
171
|
+
| Cons | ... | ... | ... |
|
|
172
|
+
| Complexity | ... | ... | ... |
|
|
173
|
+
| Recommendation | ... | ... | ... |
|
|
174
|
+
|
|
175
|
+
- Include a clear recommendation with reasoning.
|
|
176
|
+
- If using Visual Companion: show approaches visually in the browser for comparison.
|
|
177
|
+
- If only one sensible approach exists, state why alternatives were considered but dismissed.
|
|
178
|
+
- Wait for the human to choose (or confirm the recommendation).
|
|
179
|
+
- Under Autonomous Override: choose the recommended approach and proceed.
|
|
180
|
+
|
|
181
|
+
#### 5d. Sectional Design Approval
|
|
182
|
+
- Present the design in sections, scaled to complexity:
|
|
183
|
+
- **Simple** (few sentences per section): small feature, bug fix, config change
|
|
184
|
+
- **Medium** (1-2 paragraphs per section): new module, refactoring, integration
|
|
185
|
+
- **Detailed** (200-300 words per section): new system, major architecture change
|
|
186
|
+
- Sections to cover (skip if not applicable):
|
|
187
|
+
1. **Architecture** — high-level structure, module relationships
|
|
188
|
+
2. **Components** — key components and their responsibilities
|
|
189
|
+
3. **Data Flow** — how data moves through the system
|
|
190
|
+
4. **Error Handling** — failure modes and recovery strategies
|
|
191
|
+
5. **Testing Strategy** — what and how to test
|
|
192
|
+
- **After each section**, ask: "Does this look right so far?" (or confirm under Autonomous Override)
|
|
193
|
+
- If using Visual Companion: show architecture/data flow diagrams in the browser.
|
|
194
|
+
- Iterate until the human approves each section.
|
|
195
|
+
|
|
196
|
+
#### 5e. Scope Decomposition Check
|
|
197
|
+
- **Check for multi-subsystem scope**: If the goal describes 2+ independent subsystems (e.g., "build chat + file storage + billing"):
|
|
198
|
+
- Flag immediately: "This goal covers multiple independent subsystems. I recommend splitting into separate Generations."
|
|
199
|
+
- Help decompose into sub-goals, each getting its own Generation.
|
|
200
|
+
- **Check FR count**: If functional requirements exceed 10, warn and suggest splitting.
|
|
201
|
+
- Under Autonomous Override: if scope is clearly single-subsystem, proceed without asking.
|
|
117
202
|
|
|
118
203
|
### 6. Genome Gap Analysis
|
|
119
204
|
- Identify information required to achieve the goal that is missing from the genome
|
|
@@ -135,6 +220,14 @@ If the goal is ambiguous, do NOT guess — STOP and ask the human. This is non-n
|
|
|
135
220
|
- **Limit**: Maximum 7 completion criteria. Each must be verifiable.
|
|
136
221
|
- Finalize with the human
|
|
137
222
|
|
|
223
|
+
### 8. Spec Review Loop
|
|
224
|
+
- After the artifact is complete, dispatch a **spec-document-reviewer subagent** (using the Agent tool with the prompt from `src/templates/brainstorm/spec-reviewer-prompt.md`):
|
|
225
|
+
- The subagent reads `.reap/life/01-objective.md` and reviews for completeness, consistency, clarity, scope, YAGNI, and verifiability.
|
|
226
|
+
- If **Issues Found**: fix the issues in the artifact and re-dispatch the reviewer.
|
|
227
|
+
- If **Approved**: proceed to human review.
|
|
228
|
+
- **Maximum 3 iterations**. If issues persist after 3 rounds, present remaining issues to the human for decision.
|
|
229
|
+
- Under Autonomous Override: run the review loop automatically. Only escalate if the reviewer flags blocking issues after 3 rounds.
|
|
230
|
+
|
|
138
231
|
## Escalation
|
|
139
232
|
In the following situations, do NOT guess — **STOP and ask the human**:
|
|
140
233
|
- When the scope of the goal is unclear
|
|
@@ -147,6 +240,8 @@ Before saving the artifact, verify:
|
|
|
147
240
|
- [ ] Are all completion criteria verifiable? (No vague wording like "improve" or "make better"?)
|
|
148
241
|
- [ ] Are exclusions explicitly stated in the scope?
|
|
149
242
|
- [ ] Do functional requirements have FR-XXX numbering?
|
|
243
|
+
- [ ] Does the Design section include the chosen approach with rationale?
|
|
244
|
+
- [ ] Has the spec review loop completed (approved or human-overridden)?
|
|
150
245
|
|
|
151
246
|
❌ Bad completion criterion: "Stabilize the service"
|
|
152
247
|
✅ Good completion criterion: "`npm run lint` reports 0 errors, `npm run build` succeeds"
|
|
@@ -159,9 +254,10 @@ Before saving the artifact, verify:
|
|
|
159
254
|
- After Previous Generation Reference → update Background section
|
|
160
255
|
- After Backlog Review → update Background section
|
|
161
256
|
- After Genome Health Check → update Genome Reference section
|
|
162
|
-
- After
|
|
257
|
+
- After Brainstorming Design (5a-5e) → update Goal, Scope, Design sections
|
|
163
258
|
- After Genome Gap Analysis → update Backlog section
|
|
164
259
|
- After Requirements Finalization → update Requirements, Completion Criteria sections
|
|
260
|
+
- After Spec Review Loop → update with any review-driven changes
|
|
165
261
|
- The artifact should reflect the **current state of work at all times**
|
|
166
262
|
- Do NOT wait until the end to write the artifact
|
|
167
263
|
|
|
@@ -34,6 +34,7 @@ If the target branch already includes all local work (fast-forward), skip the me
|
|
|
34
34
|
9. Check if the local latest generation is an ancestor of the remote latest generation:
|
|
35
35
|
- **If yes (fast-forward possible)**:
|
|
36
36
|
- Run `git merge --ff {branch}`
|
|
37
|
+
- Run `git submodule update --init` to sync submodules
|
|
37
38
|
- Report: "Fast-forwarded to {branch}. Local lineage now includes {remote-latest-id}. No merge generation needed."
|
|
38
39
|
- **STOP** — no merge lifecycle needed
|
|
39
40
|
- **If same generation**: "Already up to date." → **STOP**
|
|
@@ -44,7 +45,8 @@ If the target branch already includes all local work (fast-forward), skip the me
|
|
|
44
45
|
- This creates the merge generation and runs detect (01-detect.md)
|
|
45
46
|
11. Execute `/reap.merge.evolve` to run the full merge lifecycle:
|
|
46
47
|
- Detect → Mate → Merge → Sync → Validation → Completion
|
|
47
|
-
12.
|
|
48
|
+
12. Run `git submodule update --init` to sync submodules after merge
|
|
49
|
+
13. The merge generation is archived upon completion
|
|
48
50
|
|
|
49
51
|
## Completion
|
|
50
52
|
- Fast-forward: "Fast-forwarded to {branch}. No merge generation needed."
|
|
@@ -47,7 +47,7 @@ Objective → Planning → Implementation ⟷ Validation → Completion
|
|
|
47
47
|
|
|
48
48
|
| Stage | Description | What it does | Artifact |
|
|
49
49
|
|-------|-------------|--------------|----------|
|
|
50
|
-
| **Objective** | Goal definition | Define
|
|
50
|
+
| **Objective** | Goal definition + brainstorming design | Define goal + requirements through structured brainstorming: clarifying questions, 2-3 approach alternatives, sectional design approval, visual companion (optional), and spec review loop. Reference environment, backlog, genome | `01-objective.md` |
|
|
51
51
|
| **Planning** | Plan formulation | Task decomposition, dependencies, implementation approach | `02-planning.md` |
|
|
52
52
|
| **Implementation** | Implementation | AI+Human collaboration to write code. Record genome defects in backlog when found | `03-implementation.md` |
|
|
53
53
|
| **Validation** | Verification | Run tests, check completion criteria. Can regress to Implementation on failure | `04-validation.md` |
|
|
@@ -6,7 +6,7 @@ const gl = require('./genome-loader.cjs');
|
|
|
6
6
|
|
|
7
7
|
const startTime = Date.now();
|
|
8
8
|
let step = 0;
|
|
9
|
-
const totalSteps =
|
|
9
|
+
const totalSteps = 7;
|
|
10
10
|
|
|
11
11
|
function log(msg) {
|
|
12
12
|
step++;
|
|
@@ -28,6 +28,47 @@ if (!gl.dirExists(reapDir)) {
|
|
|
28
28
|
process.exit(0);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
// Step 0: Install project-level command symlinks
|
|
32
|
+
const fs = require('fs');
|
|
33
|
+
const os = require('os');
|
|
34
|
+
const userReapCommands = path.join(os.homedir(), '.reap', 'commands');
|
|
35
|
+
const projectClaudeCommands = path.join(projectRoot, '.claude', 'commands');
|
|
36
|
+
|
|
37
|
+
if (gl.dirExists(userReapCommands)) {
|
|
38
|
+
try {
|
|
39
|
+
fs.mkdirSync(projectClaudeCommands, { recursive: true });
|
|
40
|
+
const cmdFiles = fs.readdirSync(userReapCommands).filter(f => f.startsWith('reap.') && f.endsWith('.md'));
|
|
41
|
+
for (const file of cmdFiles) {
|
|
42
|
+
const src = path.join(userReapCommands, file);
|
|
43
|
+
const dest = path.join(projectClaudeCommands, file);
|
|
44
|
+
try {
|
|
45
|
+
const stat = fs.lstatSync(dest);
|
|
46
|
+
if (stat.isSymbolicLink()) {
|
|
47
|
+
const target = fs.readlinkSync(dest);
|
|
48
|
+
if (target === src) continue; // already correct
|
|
49
|
+
fs.unlinkSync(dest); // stale symlink
|
|
50
|
+
} else {
|
|
51
|
+
// Regular file (old redirect or original) — replace with symlink
|
|
52
|
+
fs.unlinkSync(dest);
|
|
53
|
+
}
|
|
54
|
+
} catch { /* dest doesn't exist */ }
|
|
55
|
+
fs.symlinkSync(src, dest);
|
|
56
|
+
}
|
|
57
|
+
// Ensure .gitignore excludes these symlinks
|
|
58
|
+
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
59
|
+
const gitignoreEntry = '.claude/commands/reap.*';
|
|
60
|
+
try {
|
|
61
|
+
const gitignore = fs.existsSync(gitignorePath) ? fs.readFileSync(gitignorePath, 'utf-8') : '';
|
|
62
|
+
if (!gitignore.includes(gitignoreEntry)) {
|
|
63
|
+
fs.appendFileSync(gitignorePath, `\n# REAP command symlinks (managed by session-start hook)\n${gitignoreEntry}\n`);
|
|
64
|
+
}
|
|
65
|
+
} catch { /* best effort */ }
|
|
66
|
+
process.stderr.write(`[REAP] Symlinked ${cmdFiles.length} commands to .claude/commands/\n`);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
process.stderr.write(`[REAP] Warning: failed to create command symlinks: ${err.message}\n`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
31
72
|
// Step 1: Version check + Auto-update
|
|
32
73
|
log('Checking for updates...');
|
|
33
74
|
let autoUpdateMessage = '';
|