@walwal-harness/cli 7.1.8 → 7.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/HR-Resource/ceo/SKILL.md +4 -4
  2. package/HR-Resource/cqo/SKILL.md +3 -3
  3. package/HR-Resource/cto/SKILL.md +4 -4
  4. package/README.md +58 -5
  5. package/apps/harness-dashboard/app/api/env/route.ts +62 -0
  6. package/apps/harness-dashboard/app/api/log/route.ts +19 -0
  7. package/apps/harness-dashboard/app/api/missions/route.ts +15 -0
  8. package/apps/harness-dashboard/app/api/snapshot/route.ts +15 -0
  9. package/apps/harness-dashboard/app/api/stream/route.ts +71 -0
  10. package/apps/harness-dashboard/app/globals.css +17 -0
  11. package/apps/harness-dashboard/app/layout.tsx +29 -0
  12. package/apps/harness-dashboard/app/page.tsx +35 -0
  13. package/apps/harness-dashboard/components/Drawer.tsx +97 -0
  14. package/apps/harness-dashboard/components/Header.tsx +40 -0
  15. package/apps/harness-dashboard/components/MissionTimeline.tsx +214 -0
  16. package/apps/harness-dashboard/components/OrgTree.tsx +330 -0
  17. package/apps/harness-dashboard/components/Scene.tsx +563 -0
  18. package/apps/harness-dashboard/components/Wordmark.tsx +14 -0
  19. package/apps/harness-dashboard/components/drawer/AgentLogTab.tsx +56 -0
  20. package/apps/harness-dashboard/components/drawer/GotchasTab.tsx +89 -0
  21. package/apps/harness-dashboard/components/drawer/HypothesisTab.tsx +42 -0
  22. package/apps/harness-dashboard/components/drawer/IncidentsTab.tsx +50 -0
  23. package/apps/harness-dashboard/components/drawer/MissionDocTab.tsx +92 -0
  24. package/apps/harness-dashboard/components/drawer/MissionFlowTab.tsx +520 -0
  25. package/apps/harness-dashboard/components/drawer/OwnerHistoryTab.tsx +160 -0
  26. package/apps/harness-dashboard/components/drawer/RoomMetricsTab.tsx +90 -0
  27. package/apps/harness-dashboard/components/drawer/TracksTab.tsx +47 -0
  28. package/apps/harness-dashboard/components/three/ArchiveBoxes.tsx +95 -0
  29. package/apps/harness-dashboard/components/three/DeptBoard.tsx +175 -0
  30. package/apps/harness-dashboard/components/three/Floor3D.tsx +404 -0
  31. package/apps/harness-dashboard/components/three/GoalCard3D.tsx +123 -0
  32. package/apps/harness-dashboard/components/three/HypothesisCards.tsx +71 -0
  33. package/apps/harness-dashboard/components/three/MeetingWhiteboard.tsx +98 -0
  34. package/apps/harness-dashboard/components/three/Minifig3D.tsx +391 -0
  35. package/apps/harness-dashboard/components/three/OpsSiren.tsx +99 -0
  36. package/apps/harness-dashboard/components/three/Stage3D.tsx +104 -0
  37. package/apps/harness-dashboard/components/three/TrackRibbons.tsx +81 -0
  38. package/apps/harness-dashboard/hooks/useHarnessStream.ts +53 -0
  39. package/apps/harness-dashboard/lib/__tests__/company-flow.test.ts +270 -0
  40. package/apps/harness-dashboard/lib/__tests__/harness-state.test.ts +194 -0
  41. package/apps/harness-dashboard/lib/__tests__/i18n.test.ts +16 -0
  42. package/apps/harness-dashboard/lib/__tests__/iso.test.ts +42 -0
  43. package/apps/harness-dashboard/lib/__tests__/safe-json.test.ts +46 -0
  44. package/apps/harness-dashboard/lib/__tests__/state-mapping.test.ts +159 -0
  45. package/apps/harness-dashboard/lib/__tests__/watcher.test.ts +48 -0
  46. package/apps/harness-dashboard/lib/agent-roster.ts +42 -0
  47. package/apps/harness-dashboard/lib/furniture.ts +165 -0
  48. package/apps/harness-dashboard/lib/harness-root.ts +20 -0
  49. package/apps/harness-dashboard/lib/harness-state.ts +1193 -0
  50. package/apps/harness-dashboard/lib/i18n.ts +26 -0
  51. package/apps/harness-dashboard/lib/iso.ts +150 -0
  52. package/apps/harness-dashboard/lib/log-filter.ts +23 -0
  53. package/apps/harness-dashboard/lib/markdown.tsx +306 -0
  54. package/apps/harness-dashboard/lib/path-planning.ts +72 -0
  55. package/apps/harness-dashboard/lib/safe-json.ts +24 -0
  56. package/apps/harness-dashboard/lib/state-mapping.ts +126 -0
  57. package/apps/harness-dashboard/lib/types.ts +329 -0
  58. package/apps/harness-dashboard/lib/watcher.ts +64 -0
  59. package/apps/harness-dashboard/next-env.d.ts +6 -0
  60. package/apps/harness-dashboard/next.config.mjs +9 -0
  61. package/apps/harness-dashboard/package-lock.json +4280 -0
  62. package/apps/harness-dashboard/package.json +38 -0
  63. package/apps/harness-dashboard/playwright.config.ts +23 -0
  64. package/apps/harness-dashboard/postcss.config.js +6 -0
  65. package/apps/harness-dashboard/tailwind.config.ts +49 -0
  66. package/apps/harness-dashboard/tsconfig.json +24 -0
  67. package/apps/harness-dashboard/vitest.config.ts +15 -0
  68. package/assets/templates/AGENTS-ko.md.template +19 -6
  69. package/assets/templates/AGENTS.md.template +20 -7
  70. package/assets/templates/HARNESS.md +75 -199
  71. package/assets/templates/config.json +2 -2
  72. package/bin/init.js +11 -11
  73. package/commands/goal.md +6 -4
  74. package/commands/hot-fix.md +16 -13
  75. package/commands/submission.md +28 -0
  76. package/conventions/ceo.md +3 -3
  77. package/conventions/shared.md +4 -3
  78. package/package.json +11 -1
  79. package/scripts/harness-dashboard-up.sh +24 -4
  80. package/scripts/harness-session-start.sh +1 -1
  81. package/scripts/harness-stop.sh +1 -1
  82. package/scripts/harness-user-prompt-submit.sh +12 -4
  83. package/scripts/harness-worker-evidence-validate.sh +8 -3
  84. package/scripts/import-agency-agents.js +1 -1
  85. package/scripts/init-agents-md.sh +17 -2
  86. package/assets/templates/AGENTS-ko /341/204/207/341/205/251/341/206/250/341/204/211/341/205/241/341/204/207/341/205/251/341/206/253.md" +0 -114
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: harness-ceo
3
- description: "CEO/Dispatcher. Owner-facing mission intake, CXX meeting orchestration, event routing, and final Owner reports. Trigger with /goal, /hot-fix, /ceo."
3
+ description: "CEO/Dispatcher. Owner-facing mission intake, CXX meeting orchestration, event routing, and final Owner reports. Trigger with /goal, /submission, /hot-fix, /ceo."
4
4
  model: sonnet
5
5
  disable-model-invocation: false
6
6
  ---
@@ -12,7 +12,7 @@ You are the only direct conversation channel with the Owner.
12
12
  ## Mission Protocol
13
13
 
14
14
  1. Read the Owner request and decide whether brainstorming is needed or execution can start.
15
- 2. Create or update `.harness/documents/{mission_name}/ceo.md`.
15
+ 2. Create or update `.harness/documents/{goal-or-child-mission}/ceo.md`.
16
16
  3. Ask each CXX only mission-relevant questions:
17
17
  - COO: planning, market/reference research, hypothesis validation, mission fit.
18
18
  - CDO: branding, UI/UX direction, mock selection, design review.
@@ -28,8 +28,8 @@ You are the only direct conversation channel with the Owner.
28
28
  - CXX agents do not execute specialist work directly. They only define scope, choose workers, review outputs, resolve blockers, and report decisions.
29
29
  - Every mission must use hired specialist workers for research, planning, design production, implementation, QA, ops checks, or any other domain deliverable. Small scope is not an exemption.
30
30
  - If a suitable hired worker is absent, invoke the `harness-hiring` skill before the CXX proceeds with that deliverable.
31
- - CEO must reject CXX reports that contain completed specialist deliverables without matching worker records under `.harness/documents/{mission_name}/workers/`.
32
- - Every CXX starts from fresh context and records decisions in `.harness/documents/{mission_name}/{cxx}.md`.
31
+ - CEO must reject CXX reports that contain completed specialist deliverables without matching worker records under `.harness/documents/{goal-or-child-mission}/{owning-cxx}/workers/`.
32
+ - Every CXX starts from fresh context and records decisions in `.harness/documents/{goal-or-child-mission}/{cxx}.md`.
33
33
  - Preserve DDD boundaries: domain decisions, application wiring, infrastructure, and quality policy are separate responsibilities.
34
34
  - Before CTO/CDO/OPS allocate runnable services, agree with the Owner on a `{xx}000` base port and write it to project `.env` as `HARNESS_BASE_PORT={xx}000`. Mentioning the value in `ceo.md` is not sufficient.
35
35
  - After writing `.env`, verify with `grep '^HARNESS_BASE_PORT=' .env` before routing service work.
@@ -12,7 +12,7 @@ Own quality, recurrence prevention, and archive eligibility.
12
12
  ## Workflow
13
13
 
14
14
  1. Read CEO and CTO mission context.
15
- 2. Record decisions in `.harness/documents/{mission_name}/cqo.md`.
15
+ 2. Record decisions in `.harness/documents/{goal-or-child-mission}/cqo.md`.
16
16
  3. Break the CQO scope into worker tasks: e2e, backtest, visual, API, security, performance, regression, and operational verification.
17
17
  4. Use the `harness-resource-manager` skill to check available evaluators or reviewers for every task.
18
18
  5. Use the `harness-hiring` skill before assigning any task that has no hired worker. Do not complete that task yourself.
@@ -28,7 +28,7 @@ CQO must not directly execute QA, visual review, security review, performance te
28
28
 
29
29
  **CQO does not communicate with dev workers.** CQO only communicates with CEO and with its own evaluator/tester workers. If CQO needs clarification on implementation details, it routes the question back to CEO → CTO.
30
30
 
31
- Every evaluator/tester dispatched by CQO must write its report under `.harness/documents/{mission_name}/cqo/workers/{worker-name}.md`.
31
+ Every evaluator/tester dispatched by CQO must write its report under `.harness/documents/{goal-or-child-mission}/cqo/workers/{worker-name}.md`.
32
32
 
33
33
  Required output sections in `cqo.md`:
34
34
 
@@ -40,7 +40,7 @@ Required output sections in `cqo.md`:
40
40
 
41
41
  ## Worker Report Note Requirement
42
42
 
43
- Every CQO evaluator/tester brief must require the worker to append this English block to the bottom of `.harness/documents/{mission_name}/cqo/workers/{worker-name}.md`:
43
+ Every CQO evaluator/tester brief must require the worker to append this English block to the bottom of `.harness/documents/{goal-or-child-mission}/cqo/workers/{worker-name}.md`:
44
44
 
45
45
  ```
46
46
  ## Implementation Notes
@@ -12,7 +12,7 @@ Own engineering execution for the mission.
12
12
  ## Workflow
13
13
 
14
14
  1. Read CEO, COO, and CDO mission documents.
15
- 2. Record decisions in `.harness/documents/{mission_name}/cto.md`. **This file must be created before any worker is dispatched.**
15
+ 2. Record decisions in `.harness/documents/{goal-or-child-mission}/cto.md`. **This file must be created before any worker is dispatched.**
16
16
  3. Break the CTO scope into worker tasks: architecture review, backend, frontend, app, web, data, DevOps, integration, implementation, and technical QA.
17
17
  4. Use the `harness-resource-manager` skill to find hired workers for every task.
18
18
  5. Use the `harness-hiring` skill before assigning any missing specialty. Do not complete that task yourself.
@@ -26,9 +26,9 @@ Own engineering execution for the mission.
26
26
 
27
27
  CTO must not directly write code, create build scripts, choose detailed implementation content, run technical QA as the evaluator, or produce final implementation artifacts. CTO may only design boundaries, brief workers, coordinate ports/config, review worker outputs, and record accepted decisions with worker names and report paths.
28
28
 
29
- **cto.md is a prerequisite gate.** No worker may be dispatched before `cto.md` exists. A mission where workers appear in `.harness/documents/{mission_name}/workers/` but no `cto.md` exists is a protocol violation — CEO bypassed CTO.
29
+ **cto.md is a prerequisite gate.** No worker may be dispatched before `cto.md` exists. A mission where workers appear in `.harness/documents/{goal-or-child-mission}/cto/workers/` but no `cto.md` exists is a protocol violation — CEO bypassed CTO.
30
30
 
31
- Every worker dispatched by CTO must be listed in the Worker Evidence Manifest section of `cto.md` with their report path and status. The report path must be `.harness/documents/{mission_name}/cto/workers/{worker-name}.md`. Workers not listed there are invisible to the harness and their output cannot be accepted.
31
+ Every worker dispatched by CTO must be listed in the Worker Evidence Manifest section of `cto.md` with their report path and status. The report path must be `.harness/documents/{goal-or-child-mission}/cto/workers/{worker-name}.md`. Workers not listed there are invisible to the harness and their output cannot be accepted.
32
32
 
33
33
  Required output sections in `cto.md`:
34
34
 
@@ -41,7 +41,7 @@ Required output sections in `cto.md`:
41
41
 
42
42
  ## Worker Report Note Requirement
43
43
 
44
- Every CTO worker brief must require the worker to append this English block to the bottom of `.harness/documents/{mission_name}/cto/workers/{worker-name}.md`:
44
+ Every CTO worker brief must require the worker to append this English block to the bottom of `.harness/documents/{goal-or-child-mission}/cto/workers/{worker-name}.md`:
45
45
 
46
46
  ```
47
47
  ## Implementation Notes
package/README.md CHANGED
@@ -10,7 +10,7 @@ One project = one company. The Owner speaks only to the CEO. The CEO speaks only
10
10
 
11
11
  ```
12
12
  Owner
13
- └─ /goal · /hot-fix
13
+ └─ /goal · /submission · /hot-fix
14
14
  └─ harness-ceo orchestrator — Owner's only contact
15
15
  ├─ harness-coo research, hypothesis, service direction
16
16
  ├─ harness-cdo branding, UI/UX, design review
@@ -48,6 +48,7 @@ What `init` installs:
48
48
  | Path | Contents |
49
49
  |---|---|
50
50
  | `.claude/commands/goal.md` | `/goal` Owner command |
51
+ | `.claude/commands/submission.md` | `/submission` Owner command |
51
52
  | `.claude/commands/hot-fix.md` | `/hot-fix` Owner command |
52
53
  | `.claude/skills/harness-{ceo,coo,cdo,cto,cqo,ops}/` | CXX agent skills |
53
54
  | `.harness/shared/HR-Resource/` | Hireable worker skill pool |
@@ -79,6 +80,16 @@ Owner /hot-fix → CEO → CTO → [dev workers] → CQO → [evaluator workers]
79
80
 
80
81
  **Complete when:** `cto.md` + `cqo.md` + at least one `.harness/gotchas/` or `.harness/conventions/` entry exist.
81
82
 
83
+ ### Submission
84
+
85
+ ```
86
+ Owner /submission → CEO → [relevant CXX] → [workers] → [convention updates]
87
+ ```
88
+
89
+ 1. CEO locates the active `.harness/documents/goal-{index}-{name}/`.
90
+ 2. CEO creates `.harness/documents/goal-{index}-{name}/submission-{index}-{name}/`.
91
+ 3. Relevant CXX update `.harness/conventions/` when the new requirement changes durable project rules.
92
+
82
93
  ---
83
94
 
84
95
  ## Hard Rules
@@ -93,7 +104,7 @@ Owner /hot-fix → CEO → CTO → [dev workers] → CQO → [evaluator workers]
93
104
  | 6 | CEO routes only to CXX — never directly to workers |
94
105
  | 7 | No CXX self-execution — deliverables without matching worker records are rejected |
95
106
  | 8 | No verdict without worker evidence — CQO self-inspection is not valid |
96
- | 9 | Hierarchical worker ownership — worker reports live under `.harness/documents/{mission}/{owning-cxx}/workers/` |
107
+ | 9 | Hierarchical worker ownership — worker reports live under `.harness/documents/{goal-or-child-mission}/{owning-cxx}/workers/` |
97
108
  | 10 | Implementation Notes required — `ceo.md`, every `{cxx}.md`, and every worker report must end with an English `## Implementation Notes` section |
98
109
 
99
110
  ### Implementation Notes Format
@@ -128,10 +139,49 @@ The harness ships with a real-time dashboard that reads `.harness/documents/` di
128
139
  bash scripts/harness-dashboard-up.sh
129
140
  ```
130
141
 
142
+ Run on a different port:
143
+
144
+ ```bash
145
+ bash scripts/harness-dashboard-up.sh --port 3002
146
+ ```
147
+
148
+ Force-refresh the isolated dashboard cache when an older dashboard is still shown:
149
+
150
+ ```bash
151
+ bash scripts/harness-dashboard-up.sh --port 3002 --reinstall
152
+ ```
153
+
154
+ Dashboard options:
155
+
156
+ | Option | Role |
157
+ |---|---|
158
+ | `--port <port>` | Run Brick Office on a custom port, e.g. `3002` |
159
+ | `--reinstall` | Delete and rebuild the isolated dashboard cache under `~/.walwal-harness/dashboard/` |
160
+
161
+ Existing users should update and migrate from the target project:
162
+
163
+ ```bash
164
+ npm i @walwal-harness/cli@latest
165
+ npx walwal-harness migrate
166
+ ```
167
+
168
+ If command discovery or installed harness files look stale, refresh the install contract:
169
+
170
+ ```bash
171
+ npx walwal-harness init --force
172
+ ```
173
+
174
+ For an explicit project root:
175
+
176
+ ```bash
177
+ npx walwal-harness migrate --project-root /path/to/project
178
+ npx walwal-harness init --force --project-root /path/to/project
179
+ ```
180
+
131
181
  Features:
132
182
 
133
183
  - **Org Tree** — live status of Owner → CEO → CXX → Workers hierarchy
134
- - **Mission Timeline** — clickable history of goal/hot-fix missions showing the full dispatch chain
184
+ - **Mission Timeline** — clickable history of goal/submission/hot-fix missions showing the full dispatch chain
135
185
  - **Mission Flow tab** — per-mission flow: Owner prompt → CEO routing → CXX → worker files changed → CQO verdict
136
186
  - **History tab** — mission-specific Owner request (from CEO summary + closest progress.log match)
137
187
  - **Gotchas tab** — searchable `.harness/gotchas/*.md` knowledge base, click to read full markdown
@@ -143,8 +193,10 @@ Features:
143
193
 
144
194
  | Path | Role |
145
195
  |---|---|
146
- | `.harness/documents/{mission}/` | CXX decisions and worker reports per mission |
147
- | `.harness/documents/{mission}/{cxx}/workers/` | Worker reports owned by that CXX |
196
+ | `.harness/documents/goal-{index}-{name}/` | Goal-level CXX decisions and worker reports |
197
+ | `.harness/documents/goal-{index}-{name}/submission-{index}-{name}/` | Additional requirement under the active goal |
198
+ | `.harness/documents/goal-{index}-{name}/hotfix-{index}-{name}/` | Emergency fix under the active goal |
199
+ | `.harness/documents/{goal-or-child-mission}/{cxx}/workers/` | Worker reports owned by that CXX |
148
200
  | `.harness/conventions/` | Durable rules (CQO writes, survives missions) |
149
201
  | `.harness/gotchas/` | Recurrence-prevention records (CQO registers per hot-fix) |
150
202
  | `.harness/shared/HR-Resource/` | Hireable worker skill pool |
@@ -169,6 +221,7 @@ harness-hiring → register and onboard worker
169
221
 
170
222
  | Version | Summary |
171
223
  |---|---|
224
+ | 7.1.9 | Dashboard is packaged with npm and auto-syncs isolated cache by package version; README documents dashboard port/reinstall and update/migrate commands |
172
225
  | 7.1.8 | HR-Resource: replace China-platform-specific workers with global equivalents; refine cross-border/video/livestream skills |
173
226
  | 7.1.7 | Implementation Notes mandatory in all CXX docs and worker reports; harness-worker-evidence-validate.sh |
174
227
  | 7.1.6 | CXX hierarchy enforcement: CEO→CXX-only gate, CTO prerequisite gate, CQO worker-evidence mandate; dashboard gotchas tab, mission-specific history tab, worker file list in flow |
@@ -0,0 +1,62 @@
1
+ import { NextResponse } from "next/server";
2
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { resolveHarnessRoot } from "@/lib/harness-root";
5
+
6
+ export const dynamic = "force-dynamic";
7
+ export const runtime = "nodejs";
8
+ export const revalidate = 0;
9
+
10
+ const ALLOWED_FILES = new Set([
11
+ ".env",
12
+ ".env.local",
13
+ ".env.production",
14
+ ".env.development",
15
+ ".env.example",
16
+ ]);
17
+
18
+ function isSafeKey(key: string): boolean {
19
+ return /^[A-Z_][A-Z0-9_]*$/i.test(key);
20
+ }
21
+
22
+ function quoteValue(value: string): string {
23
+ if (/^[A-Za-z0-9_./:@+-]*$/.test(value)) return value;
24
+ return JSON.stringify(value);
25
+ }
26
+
27
+ export async function POST(request: Request) {
28
+ const body = await request.json().catch(() => null);
29
+ const file = String(body?.file ?? "");
30
+ const key = String(body?.key ?? "");
31
+ const value = String(body?.value ?? "");
32
+
33
+ if (!ALLOWED_FILES.has(file) || !isSafeKey(key)) {
34
+ return NextResponse.json({ ok: false, error: "invalid env target" }, { status: 400 });
35
+ }
36
+
37
+ const root = resolveHarnessRoot();
38
+ const target = path.join(root, file);
39
+ if (!existsSync(target)) {
40
+ return NextResponse.json({ ok: false, error: "env file not found" }, { status: 404 });
41
+ }
42
+
43
+ const original = readFileSync(target, "utf-8");
44
+ const lines = original.split("\n");
45
+ const nextLine = `${key}=${quoteValue(value)}`;
46
+ let replaced = false;
47
+ const next = lines.map((line) => {
48
+ if (line.trimStart().startsWith("#")) return line;
49
+ const idx = line.indexOf("=");
50
+ if (idx < 0) return line;
51
+ if (line.slice(0, idx).trim() !== key) return line;
52
+ replaced = true;
53
+ return nextLine;
54
+ });
55
+ if (!replaced) next.push(nextLine);
56
+ writeFileSync(target, next.join("\n").replace(/\n*$/, "\n"));
57
+
58
+ return NextResponse.json(
59
+ { ok: true, file, key, replaced },
60
+ { status: 200, headers: { "Cache-Control": "no-store" } }
61
+ );
62
+ }
@@ -0,0 +1,19 @@
1
+ import { NextResponse } from "next/server";
2
+ import { readProgressLog } from "@/lib/log-filter";
3
+ import { resolveHarnessRoot } from "@/lib/harness-root";
4
+
5
+ export const dynamic = "force-dynamic";
6
+ export const runtime = "nodejs";
7
+ export const revalidate = 0;
8
+
9
+ export async function GET(request: Request) {
10
+ const url = new URL(request.url);
11
+ const agentId = url.searchParams.get("agent") ?? undefined;
12
+ const limitParam = url.searchParams.get("limit");
13
+ const limit = limitParam ? Math.max(1, Math.min(500, parseInt(limitParam, 10))) : 50;
14
+ const lines = readProgressLog(resolveHarnessRoot(), { agentId, limit });
15
+ return NextResponse.json(
16
+ { agentId: agentId ?? null, lines },
17
+ { status: 200, headers: { "Cache-Control": "no-store" } }
18
+ );
19
+ }
@@ -0,0 +1,15 @@
1
+ import { NextResponse } from "next/server";
2
+ import { readHarnessState } from "@/lib/harness-state";
3
+ import { resolveHarnessRoot } from "@/lib/harness-root";
4
+
5
+ export const dynamic = "force-dynamic";
6
+ export const runtime = "nodejs";
7
+ export const revalidate = 0;
8
+
9
+ export async function GET() {
10
+ const snapshot = readHarnessState(resolveHarnessRoot());
11
+ return NextResponse.json(
12
+ { missions: snapshot.missions },
13
+ { status: 200, headers: { "Cache-Control": "no-store" } }
14
+ );
15
+ }
@@ -0,0 +1,15 @@
1
+ import { NextResponse } from "next/server";
2
+ import { readHarnessState } from "@/lib/harness-state";
3
+ import { resolveHarnessRoot } from "@/lib/harness-root";
4
+
5
+ export const dynamic = "force-dynamic";
6
+ export const revalidate = 0;
7
+
8
+ export async function GET() {
9
+ const root = resolveHarnessRoot();
10
+ const snapshot = readHarnessState(root);
11
+ return NextResponse.json(snapshot, {
12
+ status: 200,
13
+ headers: { "Cache-Control": "no-store" },
14
+ });
15
+ }
@@ -0,0 +1,71 @@
1
+ import { readHarnessState } from "@/lib/harness-state";
2
+ import { resolveHarnessRoot } from "@/lib/harness-root";
3
+ import { watchHarness } from "@/lib/watcher";
4
+
5
+ export const dynamic = "force-dynamic";
6
+ export const runtime = "nodejs";
7
+ export const revalidate = 0;
8
+
9
+ const SSE_HEADERS = {
10
+ "Content-Type": "text/event-stream",
11
+ "Cache-Control": "no-cache, no-transform",
12
+ Connection: "keep-alive",
13
+ } as const;
14
+
15
+ function sseFrame(snapshotJson: string): string {
16
+ // Each SSE frame ends with a blank line. Default event = "message".
17
+ return `data: ${snapshotJson}\n\n`;
18
+ }
19
+
20
+ export async function GET(request: Request) {
21
+ const root = resolveHarnessRoot();
22
+ const encoder = new TextEncoder();
23
+
24
+ const stream = new ReadableStream<Uint8Array>({
25
+ start(controller) {
26
+ const send = () => {
27
+ try {
28
+ const snap = readHarnessState(root);
29
+ controller.enqueue(encoder.encode(sseFrame(JSON.stringify(snap))));
30
+ } catch (err) {
31
+ // readHarnessState 는 이미 errorBanner 로 fallback 하지만, 만약을
32
+ // 위해 여기서도 안전하게 처리.
33
+ const fallback = JSON.stringify({
34
+ version: "1.1.0",
35
+ ts: new Date().toISOString(),
36
+ errorBanner: {
37
+ level: "error",
38
+ message_ko: "스트림 처리 중 오류가 발생했습니다.",
39
+ message_en: "Stream processing error.",
40
+ },
41
+ });
42
+ controller.enqueue(encoder.encode(sseFrame(fallback)));
43
+ }
44
+ };
45
+
46
+ // Initial snapshot.
47
+ send();
48
+
49
+ const handle = watchHarness(root, () => send());
50
+
51
+ const cleanup = async () => {
52
+ try {
53
+ await handle.close();
54
+ } catch {
55
+ /* ignore */
56
+ }
57
+ try {
58
+ controller.close();
59
+ } catch {
60
+ /* ignore — already closed */
61
+ }
62
+ };
63
+
64
+ request.signal.addEventListener("abort", () => {
65
+ void cleanup();
66
+ });
67
+ },
68
+ });
69
+
70
+ return new Response(stream, { status: 200, headers: SSE_HEADERS });
71
+ }
@@ -0,0 +1,17 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ html,
6
+ body {
7
+ background:
8
+ radial-gradient(circle at 12% 0%, rgba(34, 211, 238, 0.08), transparent 26rem),
9
+ radial-gradient(circle at 88% 6%, rgba(251, 191, 36, 0.06), transparent 24rem),
10
+ #101216;
11
+ color: #e5e7eb;
12
+ }
13
+
14
+ body {
15
+ font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
16
+ sans-serif;
17
+ }
@@ -0,0 +1,29 @@
1
+ import path from "node:path";
2
+ import type { Metadata } from "next";
3
+ import "./globals.css";
4
+ import { Header } from "@/components/Header";
5
+ import { resolveHarnessRoot } from "@/lib/harness-root";
6
+
7
+ export async function generateMetadata(): Promise<Metadata> {
8
+ const root = resolveHarnessRoot();
9
+ const name = path.basename(root);
10
+ return {
11
+ title: `Brick Office — ${name}`,
12
+ description: `walwal-harness 라이브 운영 대시보드 — ${name}`,
13
+ };
14
+ }
15
+
16
+ export default function RootLayout({
17
+ children,
18
+ }: {
19
+ children: React.ReactNode;
20
+ }) {
21
+ return (
22
+ <html lang="ko">
23
+ <body>
24
+ <Header />
25
+ {children}
26
+ </body>
27
+ </html>
28
+ );
29
+ }
@@ -0,0 +1,35 @@
1
+ import { readHarnessState } from "@/lib/harness-state";
2
+ import { resolveHarnessRoot } from "@/lib/harness-root";
3
+ import { Scene } from "@/components/Scene";
4
+
5
+ export const dynamic = "force-dynamic";
6
+ export const revalidate = 0;
7
+
8
+ export default function Page() {
9
+ const snapshot = readHarnessState(resolveHarnessRoot());
10
+ const banner = snapshot.errorBanner;
11
+
12
+ return (
13
+ <main className="min-h-[100dvh]">
14
+ {banner && (
15
+ <div
16
+ role="alert"
17
+ data-testid="error-banner"
18
+ className={`mx-auto max-w-[1280px] mt-4 px-4 py-3 rounded border text-sm ${
19
+ banner.level === "error"
20
+ ? "border-aura-alert/60 bg-aura-alert/10 text-aura-alert"
21
+ : "border-aura-talking/60 bg-aura-talking/10 text-aura-talking"
22
+ }`}
23
+ >
24
+ <div>{banner.message_ko}</div>
25
+ <div className="opacity-60 text-xs mt-1">{banner.message_en}</div>
26
+ </div>
27
+ )}
28
+ <Scene snapshot={snapshot} />
29
+ <footer className="mx-auto max-w-[1280px] px-6 pb-8 text-xs text-gray-500 font-mono flex items-center justify-between">
30
+ <span>Sprint 1 / Phase C-1 — Foundation</span>
31
+ <span data-testid="snapshot-ts">{snapshot.ts}</span>
32
+ </footer>
33
+ </main>
34
+ );
35
+ }
@@ -0,0 +1,97 @@
1
+ "use client";
2
+ import { useEffect } from "react";
3
+
4
+ export type DrawerTab =
5
+ | "mission-flow" // Mission document hierarchy (PRIMARY)
6
+ | "history" // Owner prompt history
7
+ | "gotchas" // Harness gotcha knowledge base
8
+ | "mission-doc" // Single doc markdown view
9
+ | "logs"; // Agent log (technical)
10
+
11
+ interface DrawerProps {
12
+ open: boolean;
13
+ tab: DrawerTab;
14
+ title: string;
15
+ onClose: () => void;
16
+ onTabChange: (tab: DrawerTab) => void;
17
+ children: React.ReactNode;
18
+ }
19
+
20
+ const TABS: Array<{ id: DrawerTab; label: string }> = [
21
+ { id: "mission-flow", label: "Mission Flow" },
22
+ { id: "history", label: "History" },
23
+ { id: "gotchas", label: "Gotchas" },
24
+ { id: "mission-doc", label: "Document" },
25
+ { id: "logs", label: "Agent Log" },
26
+ ];
27
+
28
+ export function Drawer({ open, tab, title, onClose, onTabChange, children }: DrawerProps) {
29
+ useEffect(() => {
30
+ if (!open) return;
31
+ const onKey = (e: KeyboardEvent) => {
32
+ if (e.key === "Escape") onClose();
33
+ };
34
+ window.addEventListener("keydown", onKey);
35
+ return () => window.removeEventListener("keydown", onKey);
36
+ }, [open, onClose]);
37
+
38
+ return (
39
+ <>
40
+ {/* Click-outside backdrop. Pointer-events flip with `open`. */}
41
+ <div
42
+ data-testid="drawer-backdrop"
43
+ aria-hidden="true"
44
+ onClick={onClose}
45
+ className={`fixed inset-0 bg-black/30 transition-opacity duration-200 ${
46
+ open ? "opacity-100 pointer-events-auto" : "opacity-0 pointer-events-none"
47
+ }`}
48
+ />
49
+ <aside
50
+ role="complementary"
51
+ aria-label="Detail panel"
52
+ aria-hidden={!open}
53
+ data-testid="drawer"
54
+ data-open={open}
55
+ className={`fixed top-0 right-0 h-[100dvh] w-full sm:w-[50vw] sm:min-w-[600px] bg-brick-bg border-l border-brick-wall shadow-2xl flex flex-col transition-transform duration-300 ease-out ${
56
+ open ? "translate-x-0" : "translate-x-full"
57
+ }`}
58
+ >
59
+ <header className="flex items-center justify-between border-b border-brick-wall px-4 py-3">
60
+ <h2 className="text-sm font-mono uppercase tracking-widest text-gray-200">
61
+ {title}
62
+ </h2>
63
+ <button
64
+ type="button"
65
+ onClick={onClose}
66
+ aria-label="Close panel"
67
+ data-testid="drawer-close"
68
+ className="rounded p-1 text-gray-400 hover:text-gray-100 hover:bg-brick-wall/40"
69
+ >
70
+ ✕
71
+ </button>
72
+ </header>
73
+ <nav className="flex flex-wrap border-b border-brick-wall text-[11px] font-mono">
74
+ {TABS.map((t) => (
75
+ <button
76
+ key={t.id}
77
+ type="button"
78
+ data-testid={`drawer-tab-${t.id}`}
79
+ data-active={t.id === tab}
80
+ onClick={() => onTabChange(t.id)}
81
+ className={`min-w-[33%] flex-1 px-2 py-2 transition-colors ${
82
+ t.id === tab
83
+ ? "bg-brick-wall/50 text-gray-100 border-b-2 border-aura-typing"
84
+ : "text-gray-500 hover:text-gray-200"
85
+ }`}
86
+ >
87
+ {t.label}
88
+ </button>
89
+ ))}
90
+ </nav>
91
+ <div className="flex-1 overflow-y-auto px-4 py-3 text-xs text-gray-200 font-mono">
92
+ {children}
93
+ </div>
94
+ </aside>
95
+ </>
96
+ );
97
+ }
@@ -0,0 +1,40 @@
1
+ import path from "node:path";
2
+ import { Wordmark } from "./Wordmark";
3
+ import { resolveHarnessRoot } from "@/lib/harness-root";
4
+
5
+ export function Header() {
6
+ const root = resolveHarnessRoot();
7
+ const projectName = path.basename(root);
8
+ return (
9
+ <header className="border-b border-brick-wall bg-brick-bg/80 backdrop-blur">
10
+ <div className="mx-auto flex max-w-[1280px] items-center justify-between px-6 py-4">
11
+ <div className="flex items-baseline gap-4">
12
+ <Wordmark />
13
+ <span className="hidden items-baseline gap-2 sm:inline-flex">
14
+ <span
15
+ className="rounded bg-brick-wall/40 px-2 py-0.5 font-mono text-xs font-semibold text-amber-300"
16
+ title={root}
17
+ >
18
+ {projectName}
19
+ </span>
20
+ <span className="text-xs text-gray-400">walwal-harness 라이브 운영 대시보드</span>
21
+ </span>
22
+ </div>
23
+ <nav className="flex items-center gap-3 text-xs font-mono text-gray-500">
24
+ <span className="inline-flex items-center gap-1">
25
+ <span className="size-2 rounded-full bg-aura-idle" /> idle
26
+ </span>
27
+ <span className="inline-flex items-center gap-1">
28
+ <span className="size-2 rounded-full bg-aura-typing" /> typing
29
+ </span>
30
+ <span className="inline-flex items-center gap-1">
31
+ <span className="size-2 rounded-full bg-aura-talking" /> talking
32
+ </span>
33
+ <span className="inline-flex items-center gap-1">
34
+ <span className="size-2 rounded-full bg-aura-alert" /> alert
35
+ </span>
36
+ </nav>
37
+ </div>
38
+ </header>
39
+ );
40
+ }