@c-d-cc/reap 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.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 +34 -2
- 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.objective.md +105 -9
- package/dist/templates/hooks/reap-guide.md +1 -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
|
@@ -9500,6 +9500,18 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
9500
9500
|
const dest = join4(mergeTemplatesDir, file);
|
|
9501
9501
|
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
9502
9502
|
}
|
|
9503
|
+
log("Installing brainstorm server...");
|
|
9504
|
+
const brainstormSourceDir = join4(ReapPaths.packageTemplatesDir, "brainstorm");
|
|
9505
|
+
const brainstormDestDir = join4(paths.root, "brainstorm");
|
|
9506
|
+
await mkdir3(brainstormDestDir, { recursive: true });
|
|
9507
|
+
const brainstormFiles = ["server.cjs", "frame.html", "start-server.sh"];
|
|
9508
|
+
for (const file of brainstormFiles) {
|
|
9509
|
+
const src = join4(brainstormSourceDir, file);
|
|
9510
|
+
const dest = join4(brainstormDestDir, file);
|
|
9511
|
+
await writeTextFile(dest, await readTextFileOrThrow(src));
|
|
9512
|
+
if (file.endsWith(".sh"))
|
|
9513
|
+
await chmod(dest, 493);
|
|
9514
|
+
}
|
|
9503
9515
|
log("Installing hook conditions...");
|
|
9504
9516
|
const conditionsSourceDir = join4(ReapPaths.packageTemplatesDir, "conditions");
|
|
9505
9517
|
const conditionsDestDir = join4(paths.hooks, "conditions");
|
|
@@ -9529,7 +9541,7 @@ async function initProject(projectRoot, projectName, entryMode, preset, onProgre
|
|
|
9529
9541
|
}
|
|
9530
9542
|
|
|
9531
9543
|
// src/cli/commands/update.ts
|
|
9532
|
-
import { readdir as readdir8, unlink as unlink3, rm as rm2, mkdir as mkdir6 } from "fs/promises";
|
|
9544
|
+
import { readdir as readdir8, unlink as unlink3, rm as rm2, mkdir as mkdir6, chmod as chmod2 } from "fs/promises";
|
|
9533
9545
|
import { join as join9 } from "path";
|
|
9534
9546
|
|
|
9535
9547
|
// src/core/hooks.ts
|
|
@@ -10382,6 +10394,26 @@ async function updateProject(projectRoot, dryRun = false) {
|
|
|
10382
10394
|
result.updated.push(`~/.reap/templates/merge/${file}`);
|
|
10383
10395
|
}
|
|
10384
10396
|
}
|
|
10397
|
+
const brainstormSourceDir = join9(ReapPaths.packageTemplatesDir, "brainstorm");
|
|
10398
|
+
const brainstormDestDir = join9(paths.root, "brainstorm");
|
|
10399
|
+
await mkdir6(brainstormDestDir, { recursive: true });
|
|
10400
|
+
const brainstormFiles = ["server.cjs", "frame.html", "start-server.sh"];
|
|
10401
|
+
for (const file of brainstormFiles) {
|
|
10402
|
+
const src = await readTextFileOrThrow(join9(brainstormSourceDir, file));
|
|
10403
|
+
const dest = join9(brainstormDestDir, file);
|
|
10404
|
+
const existing = await readTextFile(dest);
|
|
10405
|
+
if (existing !== null && existing === src) {
|
|
10406
|
+
result.skipped.push(`.reap/brainstorm/${file}`);
|
|
10407
|
+
} else {
|
|
10408
|
+
if (!dryRun) {
|
|
10409
|
+
await writeTextFile(dest, src);
|
|
10410
|
+
if (file.endsWith(".sh")) {
|
|
10411
|
+
await chmod2(dest, 493);
|
|
10412
|
+
}
|
|
10413
|
+
}
|
|
10414
|
+
result.updated.push(`.reap/brainstorm/${file}`);
|
|
10415
|
+
}
|
|
10416
|
+
}
|
|
10385
10417
|
const migrations = await migrateHooks(dryRun);
|
|
10386
10418
|
for (const m of migrations.results) {
|
|
10387
10419
|
if (m.action === "migrated") {
|
|
@@ -10557,7 +10589,7 @@ async function fixProject(projectRoot) {
|
|
|
10557
10589
|
|
|
10558
10590
|
// src/cli/index.ts
|
|
10559
10591
|
import { join as join10 } from "path";
|
|
10560
|
-
program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.
|
|
10592
|
+
program.name("reap").description("REAP — Recursive Evolutionary Autonomous Pipeline").version("0.6.1");
|
|
10561
10593
|
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
10594
|
try {
|
|
10563
10595
|
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
|
+
유저가 명시적으로 거부한 경우에만 스킵한다.
|
|
@@ -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
|
|
|
@@ -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` |
|