@walwal-harness/cli 7.1.22 → 7.1.24
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/CHANGELOG.md +23 -0
- package/HR-Resource/cdo/SKILL.md +16 -3
- package/README.md +8 -0
- package/apps/harness-dashboard/components/Drawer.tsx +2 -0
- package/apps/harness-dashboard/components/OrgTree.tsx +197 -88
- package/apps/harness-dashboard/components/Scene.tsx +6 -0
- package/apps/harness-dashboard/components/drawer/DesignPreviewTab.tsx +160 -0
- package/apps/harness-dashboard/lib/harness-state.ts +1 -0
- package/apps/harness-dashboard/lib/types.ts +1 -0
- package/assets/templates/HARNESS.md +1 -0
- package/assets/templates/config.json +2 -0
- package/bin/init.js +60 -17
- package/package.json +1 -1
- package/scripts/harness-wake.sh +59 -14
package/CHANGELOG.md
CHANGED
|
@@ -31,6 +31,29 @@ docmeta:
|
|
|
31
31
|
|
|
32
32
|
## Unreleased
|
|
33
33
|
|
|
34
|
+
## 7.1.24 — Hourly CEO wake loop + migrate script refresh (2026-05-22)
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- Hourly wake prompting now explicitly wakes CEO to convene CXX + OPS, collect current progress and decisions, dispatch one NOW action, and continue through CQO/OPS verification without asking Owner for next steps.
|
|
38
|
+
- Hourly wake prompts now include external executive-function scaffolding: body-doubling status checks, context-transition summaries, time-guardian hidden-task detection, and NOW/NEXT/PARKED open-loop triage.
|
|
39
|
+
- Hourly wake now supports `HARNESS_WAKE_MODEL` / `company_mode.hourly_wake_model` for Claude model pinning.
|
|
40
|
+
- `migrate` now refreshes package-owned runtime scripts, including `scripts/harness-wake.sh`, while archiving the previous target scripts directory.
|
|
41
|
+
- Dashboard Org Tree now uses a left-to-right depth layout while keeping zoom, ctrl/meta-wheel zoom, and drag panning.
|
|
42
|
+
|
|
43
|
+
## 7.1.23 — Dashboard CDO preview + radial org tree (2026-05-21)
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
- Dashboard CDO Preview tab: clicking `harness-cdo` opens an iframe preview from `.harness/documents/{mission}/cdo/preview.html`.
|
|
47
|
+
- CDO skill and harness reference now require the preview artifact before CDO completion, with CEO/Owner handoff guidance to view it in the dashboard.
|
|
48
|
+
- Dashboard mission state now reads `cdo/preview.html` alongside CXX markdown docs.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
- Dashboard Org Tree now uses a compact radial/sunflower layout with Owner/CEO centered and CXX roles arranged around them.
|
|
52
|
+
- Org Tree now has independent zoom controls, ctrl/meta-wheel zoom, and drag panning inside a reduced-height scroll surface.
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
- CDO dashboard navigation now opens the visual Preview tab instead of only the markdown document flow.
|
|
56
|
+
|
|
34
57
|
### Fixed
|
|
35
58
|
- v7 `/goal`, `/submission`, and `/hot-fix` now mark both `company_state.state` and `conductor.state` as `running`, so Claude Stop hook auto-chain does not stop after CEO routing or hiring-only summaries.
|
|
36
59
|
- Stop hook now treats v7 CEO routing state (`current_agent=ceo`, `agent_status=running`, `owner_prompt.status=routing`) as an unfinished company loop, including migrated projects that do not yet have running conductor/company state.
|
package/HR-Resource/cdo/SKILL.md
CHANGED
|
@@ -18,13 +18,25 @@ Own design strategy for the mission.
|
|
|
18
18
|
5. Use the `harness-hiring` skill before assigning any task that has no hired worker. Do not complete that task yourself.
|
|
19
19
|
6. Delegate all design deliverables and review passes to hired workers in fresh sessions.
|
|
20
20
|
7. Evaluate worker feedback for usefulness, discomfort, novelty, clarity, and differentiation.
|
|
21
|
-
8. Select the final direction and
|
|
21
|
+
8. Select the final direction and write a CDO preview artifact at `.harness/documents/{mission_name}/cdo/preview.html`.
|
|
22
|
+
9. Report to CEO and CTO with a concise preview summary and mention that the visual sample is available in the dashboard by clicking harness-cdo.
|
|
22
23
|
|
|
23
24
|
## Rule
|
|
24
25
|
|
|
25
26
|
Design output must be usable by implementation teams, not just descriptive.
|
|
26
27
|
CDO must not directly produce palettes, typography, layouts, mockups, interaction specs, or design-review findings. CDO may only brief workers, compare their outputs, decide, and document the accepted direction with worker names and report paths.
|
|
27
28
|
|
|
29
|
+
## Preview Artifact Gate
|
|
30
|
+
|
|
31
|
+
CDO completion is invalid without `.harness/documents/{mission_name}/cdo/preview.html`.
|
|
32
|
+
|
|
33
|
+
- The preview must be a self-contained HTML document suitable for dashboard iframe rendering.
|
|
34
|
+
- The preview must summarize the worker-backed final direction visually, not just repeat prose from `cdo.md`.
|
|
35
|
+
- It must include the core screen/state CDO expects CTO to implement, using realistic layout, density, labels, and interaction affordances.
|
|
36
|
+
- It must not load remote scripts or depend on a dev server. Inline CSS is preferred.
|
|
37
|
+
- If the mission has no UI surface, CDO must still write a visual decision board preview explaining the non-UI design constraints.
|
|
38
|
+
- CDO's response to CEO must include a short note: "Dashboard: click harness-cdo to view the Preview."
|
|
39
|
+
|
|
28
40
|
## Owner Handoff Gate
|
|
29
41
|
|
|
30
42
|
Owner is the final acceptance reviewer, not a design QA substitute. CDO must use design/review workers to validate UI/UX readiness, accessibility concerns, responsive behavior assumptions, and visual acceptance risks before handoff. Do not ask the Owner to discover whether the UI is usable or visually broken; ask only for final product or brand acceptance after worker-backed review.
|
|
@@ -34,7 +46,8 @@ Required output sections:
|
|
|
34
46
|
1. Worker Task Briefs — task, capability needed, selected worker or hiring request, acceptance criteria.
|
|
35
47
|
2. Worker Evidence Manifest — worker name, report path, status.
|
|
36
48
|
3. CDO Decision — only decisions accepted from worker evidence.
|
|
37
|
-
4.
|
|
38
|
-
5.
|
|
49
|
+
4. Preview Artifact — path `.harness/documents/{mission_name}/cdo/preview.html`, visual summary, and dashboard viewing note.
|
|
50
|
+
5. Next Handoff — CTO-ready design constraints, inputs, blockers.
|
|
51
|
+
6. Implementation Notes — in English, with `Design Decisions`, `Deviations`, `Tradeoffs`, and `Open Questions`.
|
|
39
52
|
|
|
40
53
|
Every CDO worker brief must require the worker to append the same English `## Implementation Notes` block to the bottom of `.harness/documents/{mission_name}/cdo/workers/{worker-name}.md`, covering risks, self-corrections, chosen direction, and unresolved questions. Use `None` for empty subsections.
|
package/README.md
CHANGED
|
@@ -203,11 +203,19 @@ Features:
|
|
|
203
203
|
- **Org Tree** — live status of Owner → CEO → CXX → Workers hierarchy, with worker cards created only from hired `hr-roster.json` entries backed by `.harness/shared/HR-Resource/{worker}/SKILL.md`; running hired workers are highlighted under their owning CXX
|
|
204
204
|
- **Mission Timeline** — clickable history of goal/submission/hot-fix missions showing the full dispatch chain
|
|
205
205
|
- **Mission Flow tab** — per-mission flow: Owner prompt → CEO routing → CXX → owner-specific worker evidence → CQO verdict
|
|
206
|
+
- **CDO Preview** — clicking `harness-cdo` opens a dashboard iframe preview from `.harness/documents/{mission}/cdo/preview.html`; CDO completion requires this visual summary artifact
|
|
206
207
|
- **History tab** — mission-specific Owner request (from CEO summary + closest progress.log match)
|
|
207
208
|
- **Gotchas tab** — searchable `.harness/gotchas/*.md` knowledge base, click to read full markdown
|
|
208
209
|
- **Document tab** — per-CXX markdown doc viewer
|
|
209
210
|
- **Owner Acceptance Gate** — Owner is final acceptance only; CEO/CXX must complete worker-backed verification before asking for Owner review
|
|
210
211
|
|
|
212
|
+
Hourly autonomous wake:
|
|
213
|
+
|
|
214
|
+
- `HARNESS_WAKE_EXECUTOR=claude|codex` selects `claude -p` or `codex exec`.
|
|
215
|
+
- `HARNESS_WAKE_MODEL=<model>` optionally pins the Claude model for wake ticks.
|
|
216
|
+
- Each wake tick prompts CEO to convene CXX + OPS, collect progress and decisions, dispatch the next action without asking Owner, and require CQO/OPS verification until the active goal works.
|
|
217
|
+
- `npx @walwal-harness/cli migrate` refreshes package-owned runtime scripts, so existing projects receive wake prompt updates without a full re-init.
|
|
218
|
+
|
|
211
219
|
---
|
|
212
220
|
|
|
213
221
|
## Harness Runtime Paths
|
|
@@ -5,6 +5,7 @@ export type DrawerTab =
|
|
|
5
5
|
| "mission-flow" // Mission document hierarchy (PRIMARY)
|
|
6
6
|
| "history" // Owner prompt history
|
|
7
7
|
| "gotchas" // Harness gotcha knowledge base
|
|
8
|
+
| "design-preview" // CDO visual sample iframe
|
|
8
9
|
| "mission-doc" // Single doc markdown view
|
|
9
10
|
| "logs"; // Agent log (technical)
|
|
10
11
|
|
|
@@ -23,6 +24,7 @@ const TABS: Array<{ id: DrawerTab; label: string }> = [
|
|
|
23
24
|
{ id: "mission-flow", label: "Mission Flow" },
|
|
24
25
|
{ id: "history", label: "History" },
|
|
25
26
|
{ id: "gotchas", label: "Gotchas" },
|
|
27
|
+
{ id: "design-preview", label: "Preview" },
|
|
26
28
|
{ id: "mission-doc", label: "Document" },
|
|
27
29
|
{ id: "logs", label: "Agent Log" },
|
|
28
30
|
];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
2
3
|
import type { AgentId, HarnessSnapshot, MinifigState, OrgNodeDef } from "@/lib/types";
|
|
3
4
|
|
|
4
5
|
interface OrgTreeProps {
|
|
@@ -66,38 +67,12 @@ function OrgCard({
|
|
|
66
67
|
);
|
|
67
68
|
}
|
|
68
69
|
|
|
69
|
-
function BranchConnector({ count }: { count: number }) {
|
|
70
|
-
const h = 44;
|
|
71
|
-
const stepPct = 100 / count;
|
|
72
|
-
const midY = h * 0.5;
|
|
73
|
-
const centers = Array.from({ length: count }, (_, i) => stepPct * i + stepPct / 2);
|
|
74
|
-
return (
|
|
75
|
-
<svg width="100%" height={h} className="overflow-visible pointer-events-none">
|
|
76
|
-
<line x1="50%" y1="0" x2="50%" y2={midY} stroke="#374151" strokeWidth="1.5" />
|
|
77
|
-
<line
|
|
78
|
-
x1={`${centers[0]}%`}
|
|
79
|
-
y1={midY}
|
|
80
|
-
x2={`${centers[centers.length - 1]}%`}
|
|
81
|
-
y2={midY}
|
|
82
|
-
stroke="#374151"
|
|
83
|
-
strokeWidth="1.5"
|
|
84
|
-
/>
|
|
85
|
-
{centers.map((x, i) => (
|
|
86
|
-
<line
|
|
87
|
-
key={i}
|
|
88
|
-
x1={`${x}%`}
|
|
89
|
-
y1={midY}
|
|
90
|
-
x2={`${x}%`}
|
|
91
|
-
y2={h}
|
|
92
|
-
stroke="#374151"
|
|
93
|
-
strokeWidth="1.5"
|
|
94
|
-
/>
|
|
95
|
-
))}
|
|
96
|
-
</svg>
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
70
|
export function OrgTree({ snapshot, activeNodeId, onNodeClick }: OrgTreeProps) {
|
|
71
|
+
const defaultZoom = 0.74;
|
|
72
|
+
const [zoom, setZoom] = useState(defaultZoom);
|
|
73
|
+
const scrollRef = useRef<HTMLDivElement | null>(null);
|
|
74
|
+
const dragRef = useRef<{ x: number; y: number; left: number; top: number } | null>(null);
|
|
75
|
+
|
|
101
76
|
const agentMap = new Map(snapshot.agents.map((a) => [a.id, a]));
|
|
102
77
|
|
|
103
78
|
const getStatus = (ids: AgentId[]): MinifigState => {
|
|
@@ -229,68 +204,201 @@ export function OrgTree({ snapshot, activeNodeId, onNodeClick }: OrgTreeProps) {
|
|
|
229
204
|
// report-only workers remain visible in mission documents, but not as seats.
|
|
230
205
|
const workers = (currentMission?.workers ?? []).filter((w) => w.hired);
|
|
231
206
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
{currentMission && (
|
|
236
|
-
<div className="mb-4 text-center">
|
|
237
|
-
<span className="font-mono text-[10px] uppercase tracking-[0.2em] text-cyan-300/60">
|
|
238
|
-
current mission
|
|
239
|
-
</span>
|
|
240
|
-
<div className="mt-0.5 font-mono text-xs text-gray-400">{currentMission.missionId}</div>
|
|
241
|
-
</div>
|
|
242
|
-
)}
|
|
207
|
+
const setBoundedZoom = useCallback((next: number) => {
|
|
208
|
+
setZoom(Math.min(1.35, Math.max(0.62, Number(next.toFixed(2)))));
|
|
209
|
+
}, []);
|
|
243
210
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
</div>
|
|
211
|
+
const handleWheel = useCallback(
|
|
212
|
+
(event: React.WheelEvent<HTMLDivElement>) => {
|
|
213
|
+
if (!event.ctrlKey && !event.metaKey) return;
|
|
214
|
+
event.preventDefault();
|
|
215
|
+
setBoundedZoom(zoom + (event.deltaY > 0 ? -0.06 : 0.06));
|
|
216
|
+
},
|
|
217
|
+
[setBoundedZoom, zoom],
|
|
218
|
+
);
|
|
253
219
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
220
|
+
const handlePointerDown = useCallback((event: React.PointerEvent<HTMLDivElement>) => {
|
|
221
|
+
if (event.button !== 0 || (event.target as HTMLElement).closest("button")) return;
|
|
222
|
+
const node = scrollRef.current;
|
|
223
|
+
if (!node) return;
|
|
224
|
+
dragRef.current = {
|
|
225
|
+
x: event.clientX,
|
|
226
|
+
y: event.clientY,
|
|
227
|
+
left: node.scrollLeft,
|
|
228
|
+
top: node.scrollTop,
|
|
229
|
+
};
|
|
230
|
+
node.setPointerCapture(event.pointerId);
|
|
231
|
+
}, []);
|
|
232
|
+
|
|
233
|
+
const handlePointerMove = useCallback((event: React.PointerEvent<HTMLDivElement>) => {
|
|
234
|
+
const start = dragRef.current;
|
|
235
|
+
const node = scrollRef.current;
|
|
236
|
+
if (!start || !node) return;
|
|
237
|
+
node.scrollLeft = start.left - (event.clientX - start.x);
|
|
238
|
+
node.scrollTop = start.top - (event.clientY - start.y);
|
|
239
|
+
}, []);
|
|
240
|
+
|
|
241
|
+
const endDrag = useCallback((event: React.PointerEvent<HTMLDivElement>) => {
|
|
242
|
+
scrollRef.current?.releasePointerCapture(event.pointerId);
|
|
243
|
+
dragRef.current = null;
|
|
244
|
+
}, []);
|
|
245
|
+
|
|
246
|
+
const canvas = { width: 1280, height: 500 };
|
|
247
|
+
const ownerPos = { x: 70, y: 196 };
|
|
248
|
+
const ceoPos = { x: 320, y: 196 };
|
|
249
|
+
const cxxX = 610;
|
|
250
|
+
const workerX = 890;
|
|
258
251
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
252
|
+
const layout = useMemo(() => {
|
|
253
|
+
const startY = 50;
|
|
254
|
+
const rowGap = 86;
|
|
255
|
+
|
|
256
|
+
return cxxNodes.map((node, index) => {
|
|
257
|
+
const nodeWorkers = workers.filter((w) => w.owner === node.role);
|
|
258
|
+
const y = startY + index * rowGap;
|
|
259
|
+
const workerHeight = Math.max(58, nodeWorkers.length * 54 + Math.max(0, nodeWorkers.length - 1) * 6);
|
|
260
|
+
return { node, x: cxxX, y, workerX, workerY: y - workerHeight / 2 + 30, nodeWorkers };
|
|
261
|
+
});
|
|
262
|
+
}, [cxxNodes, workers]);
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
<div className="select-none">
|
|
266
|
+
<div className="flex items-center justify-between border-b border-white/10 bg-black/20 px-3 py-2">
|
|
267
|
+
<div className="min-w-0">
|
|
268
|
+
{currentMission && (
|
|
269
|
+
<div className="truncate font-mono text-[10px] text-cyan-300/70">
|
|
270
|
+
current mission · {currentMission.missionId}
|
|
271
|
+
</div>
|
|
272
|
+
)}
|
|
273
|
+
</div>
|
|
274
|
+
<div className="flex items-center gap-1">
|
|
275
|
+
<button
|
|
276
|
+
type="button"
|
|
277
|
+
onClick={() => setBoundedZoom(zoom - 0.08)}
|
|
278
|
+
className="h-7 w-7 rounded border border-gray-700 bg-gray-900/80 font-mono text-xs text-gray-200 hover:border-cyan-300/60"
|
|
279
|
+
title="Zoom out"
|
|
280
|
+
>
|
|
281
|
+
-
|
|
282
|
+
</button>
|
|
283
|
+
<button
|
|
284
|
+
type="button"
|
|
285
|
+
onClick={() => setBoundedZoom(defaultZoom)}
|
|
286
|
+
className="h-7 min-w-12 rounded border border-gray-700 bg-gray-900/80 px-2 font-mono text-[10px] text-gray-300 hover:border-cyan-300/60"
|
|
287
|
+
title="Reset zoom"
|
|
288
|
+
>
|
|
289
|
+
{Math.round(zoom * 100)}%
|
|
290
|
+
</button>
|
|
291
|
+
<button
|
|
292
|
+
type="button"
|
|
293
|
+
onClick={() => setBoundedZoom(zoom + 0.08)}
|
|
294
|
+
className="h-7 w-7 rounded border border-gray-700 bg-gray-900/80 font-mono text-xs text-gray-200 hover:border-cyan-300/60"
|
|
295
|
+
title="Zoom in"
|
|
296
|
+
>
|
|
297
|
+
+
|
|
298
|
+
</button>
|
|
299
|
+
</div>
|
|
267
300
|
</div>
|
|
268
301
|
|
|
269
|
-
|
|
270
|
-
|
|
302
|
+
<div
|
|
303
|
+
ref={scrollRef}
|
|
304
|
+
onWheel={handleWheel}
|
|
305
|
+
onPointerDown={handlePointerDown}
|
|
306
|
+
onPointerMove={handlePointerMove}
|
|
307
|
+
onPointerUp={endDrag}
|
|
308
|
+
onPointerCancel={endDrag}
|
|
309
|
+
className="scrollbar-hidden h-[390px] cursor-grab overflow-auto active:cursor-grabbing"
|
|
310
|
+
>
|
|
311
|
+
<div
|
|
312
|
+
className="relative"
|
|
313
|
+
style={{
|
|
314
|
+
width: canvas.width * zoom,
|
|
315
|
+
height: canvas.height * zoom,
|
|
316
|
+
}}
|
|
317
|
+
>
|
|
318
|
+
<div
|
|
319
|
+
className="absolute left-0 top-0 origin-top-left"
|
|
320
|
+
style={{
|
|
321
|
+
width: canvas.width,
|
|
322
|
+
height: canvas.height,
|
|
323
|
+
transform: `scale(${zoom})`,
|
|
324
|
+
}}
|
|
325
|
+
>
|
|
326
|
+
<svg className="pointer-events-none absolute inset-0" width={canvas.width} height={canvas.height}>
|
|
327
|
+
<path
|
|
328
|
+
d={`M ${ownerPos.x + 235} ${ownerPos.y + 56} H ${ceoPos.x}`}
|
|
329
|
+
stroke="#374151"
|
|
330
|
+
strokeWidth="1.5"
|
|
331
|
+
fill="none"
|
|
332
|
+
/>
|
|
333
|
+
<path
|
|
334
|
+
d={`M ${ceoPos.x + 235} ${ceoPos.y + 56} H ${cxxX - 54}`}
|
|
335
|
+
stroke="#374151"
|
|
336
|
+
strokeWidth="1.5"
|
|
337
|
+
fill="none"
|
|
338
|
+
/>
|
|
339
|
+
<path
|
|
340
|
+
d={`M ${cxxX - 54} ${layout[0]?.y ?? 80} V ${layout[layout.length - 1]?.y ?? 420}`}
|
|
341
|
+
stroke="#374151"
|
|
342
|
+
strokeWidth="1.5"
|
|
343
|
+
fill="none"
|
|
344
|
+
/>
|
|
345
|
+
{layout.map(({ node, x, y, nodeWorkers }) => (
|
|
346
|
+
<g key={node.id}>
|
|
347
|
+
<path
|
|
348
|
+
d={`M ${x - 54} ${y} H ${x - 10}`}
|
|
349
|
+
stroke="#374151"
|
|
350
|
+
strokeDasharray="4 5"
|
|
351
|
+
strokeWidth="1.5"
|
|
352
|
+
fill="none"
|
|
353
|
+
/>
|
|
354
|
+
{nodeWorkers.length > 0 && (
|
|
355
|
+
<path
|
|
356
|
+
d={`M ${x + 166} ${y} H ${workerX - 16}`}
|
|
357
|
+
stroke="#374151"
|
|
358
|
+
strokeWidth="1.5"
|
|
359
|
+
fill="none"
|
|
360
|
+
/>
|
|
361
|
+
)}
|
|
362
|
+
</g>
|
|
363
|
+
))}
|
|
364
|
+
</svg>
|
|
271
365
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const nodeWorkers = workers.filter((w) => w.owner === node.role);
|
|
276
|
-
const hasActiveWorker = nodeWorkers.some((w) => w.active);
|
|
277
|
-
const displayNode = hasActiveWorker
|
|
278
|
-
? { ...node, status: "typing" as const, activity: "Worker active" }
|
|
279
|
-
: node;
|
|
366
|
+
<div className="absolute" style={{ left: ownerPos.x, top: ownerPos.y }}>
|
|
367
|
+
<OrgCard node={ownerNode} size="lg" active={activeNodeId === "owner"} onClick={onNodeClick} />
|
|
368
|
+
</div>
|
|
280
369
|
|
|
281
|
-
|
|
282
|
-
<div key={node.id} className="flex flex-col items-center gap-2">
|
|
370
|
+
<div className="absolute" style={{ left: ceoPos.x, top: ceoPos.y }}>
|
|
283
371
|
<OrgCard
|
|
284
|
-
node={
|
|
285
|
-
size="
|
|
286
|
-
active={activeNodeId ===
|
|
372
|
+
node={ceoNode}
|
|
373
|
+
size="lg"
|
|
374
|
+
active={activeNodeId === "ceo"}
|
|
287
375
|
onClick={onNodeClick}
|
|
288
376
|
/>
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
377
|
+
</div>
|
|
378
|
+
|
|
379
|
+
{layout.map(({ node, x, y, workerX, workerY, nodeWorkers }) => {
|
|
380
|
+
const hasActiveWorker = nodeWorkers.some((w) => w.active);
|
|
381
|
+
const displayNode = hasActiveWorker
|
|
382
|
+
? { ...node, status: "typing" as const, activity: "Worker active" }
|
|
383
|
+
: node;
|
|
384
|
+
|
|
385
|
+
return (
|
|
386
|
+
<div
|
|
387
|
+
key={node.id}
|
|
388
|
+
className="absolute"
|
|
389
|
+
style={{ left: x, top: y - 38 }}
|
|
390
|
+
>
|
|
391
|
+
<OrgCard
|
|
392
|
+
node={displayNode}
|
|
393
|
+
size="md"
|
|
394
|
+
active={activeNodeId === node.id}
|
|
395
|
+
onClick={onNodeClick}
|
|
396
|
+
/>
|
|
397
|
+
{nodeWorkers.length > 0 && (
|
|
398
|
+
<div
|
|
399
|
+
className="absolute flex w-[170px] flex-col gap-1.5"
|
|
400
|
+
style={{ left: workerX - x, top: workerY - y }}
|
|
401
|
+
>
|
|
294
402
|
{nodeWorkers.map((w) => {
|
|
295
403
|
const workerNodeId = `worker-${node.role}-${w.name}`;
|
|
296
404
|
const workerActive = activeNodeId === workerNodeId || w.active;
|
|
@@ -339,11 +447,12 @@ export function OrgTree({ snapshot, activeNodeId, onNodeClick }: OrgTreeProps) {
|
|
|
339
447
|
);
|
|
340
448
|
})}
|
|
341
449
|
</div>
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
450
|
+
)}
|
|
451
|
+
</div>
|
|
452
|
+
);
|
|
453
|
+
})}
|
|
454
|
+
</div>
|
|
455
|
+
</div>
|
|
347
456
|
</div>
|
|
348
457
|
</div>
|
|
349
458
|
);
|
|
@@ -17,6 +17,7 @@ import { OwnerHistoryTab } from "./drawer/OwnerHistoryTab";
|
|
|
17
17
|
import { MissionDocTab } from "./drawer/MissionDocTab";
|
|
18
18
|
import { MissionFlowTab } from "./drawer/MissionFlowTab";
|
|
19
19
|
import { GotchasTab } from "./drawer/GotchasTab";
|
|
20
|
+
import { DesignPreviewTab } from "./drawer/DesignPreviewTab";
|
|
20
21
|
import { MissionTimeline } from "./MissionTimeline";
|
|
21
22
|
import { OrgTree } from "./OrgTree";
|
|
22
23
|
|
|
@@ -48,6 +49,8 @@ export function Scene({ snapshot: initial, lang = "ko" }: SceneProps) {
|
|
|
48
49
|
}
|
|
49
50
|
if (node.id === "owner") {
|
|
50
51
|
setDrawerTab("history");
|
|
52
|
+
} else if (node.id === "cdo") {
|
|
53
|
+
setDrawerTab("design-preview");
|
|
51
54
|
} else if (node.id.startsWith("worker-")) {
|
|
52
55
|
setDrawerTab("mission-doc");
|
|
53
56
|
} else {
|
|
@@ -167,6 +170,9 @@ export function Scene({ snapshot: initial, lang = "ko" }: SceneProps) {
|
|
|
167
170
|
{drawerTab === "gotchas" && (
|
|
168
171
|
<GotchasTab gotchas={snapshot.gotchas ?? []} />
|
|
169
172
|
)}
|
|
173
|
+
{drawerTab === "design-preview" && (
|
|
174
|
+
<DesignPreviewTab mission={selectedMission ?? snapshot.missions?.[0] ?? null} />
|
|
175
|
+
)}
|
|
170
176
|
{drawerTab === "mission-doc" && selectedNode && (
|
|
171
177
|
<MissionDocTab
|
|
172
178
|
missions={snapshot.missions ?? []}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import type { MissionDoc } from "@/lib/types";
|
|
3
|
+
|
|
4
|
+
interface DesignPreviewTabProps {
|
|
5
|
+
mission: MissionDoc | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function escapeHtml(value: string) {
|
|
9
|
+
return value
|
|
10
|
+
.replaceAll("&", "&")
|
|
11
|
+
.replaceAll("<", "<")
|
|
12
|
+
.replaceAll(">", ">")
|
|
13
|
+
.replaceAll('"', """);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function extractFirstBullet(markdown: string | null | undefined) {
|
|
17
|
+
if (!markdown) return null;
|
|
18
|
+
return (
|
|
19
|
+
markdown
|
|
20
|
+
.split("\n")
|
|
21
|
+
.map((line) => line.trim())
|
|
22
|
+
.find((line) => /^[-*]\s+\S/.test(line))
|
|
23
|
+
?.replace(/^[-*]\s+/, "")
|
|
24
|
+
.slice(0, 140) ?? null
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function buildPreviewDoc(mission: MissionDoc | null) {
|
|
29
|
+
if (mission?.cdoPreview?.trim()) {
|
|
30
|
+
return mission.cdoPreview;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const missionId = mission?.missionId ?? "No active mission";
|
|
34
|
+
const brief =
|
|
35
|
+
extractFirstBullet(mission?.cdo) ??
|
|
36
|
+
extractFirstBullet(mission?.ceo) ??
|
|
37
|
+
"Design direction will appear here after harness-cdo writes cdo.md.";
|
|
38
|
+
|
|
39
|
+
return `<!doctype html>
|
|
40
|
+
<html lang="en">
|
|
41
|
+
<head>
|
|
42
|
+
<meta charset="utf-8" />
|
|
43
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
44
|
+
<style>
|
|
45
|
+
:root {
|
|
46
|
+
color-scheme: dark;
|
|
47
|
+
--bg: #101216;
|
|
48
|
+
--panel: #171b22;
|
|
49
|
+
--line: #2d3543;
|
|
50
|
+
--text: #eef2f7;
|
|
51
|
+
--muted: #8a94a6;
|
|
52
|
+
--cyan: #22d3ee;
|
|
53
|
+
--amber: #fbbf24;
|
|
54
|
+
--rose: #fb7185;
|
|
55
|
+
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
56
|
+
}
|
|
57
|
+
* { box-sizing: border-box; }
|
|
58
|
+
body {
|
|
59
|
+
margin: 0;
|
|
60
|
+
min-height: 100vh;
|
|
61
|
+
background: radial-gradient(circle at 18% 0%, rgba(34, 211, 238, .12), transparent 26rem), var(--bg);
|
|
62
|
+
color: var(--text);
|
|
63
|
+
}
|
|
64
|
+
.shell { min-height: 100vh; display: grid; grid-template-rows: auto 1fr; }
|
|
65
|
+
header {
|
|
66
|
+
display: flex; align-items: center; justify-content: space-between; gap: 16px;
|
|
67
|
+
padding: 18px 22px; border-bottom: 1px solid var(--line); background: rgba(10, 12, 16, .72);
|
|
68
|
+
}
|
|
69
|
+
.brand { font-size: 12px; letter-spacing: .22em; text-transform: uppercase; color: var(--cyan); }
|
|
70
|
+
.mission { max-width: 54ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: 12px; }
|
|
71
|
+
main { display: grid; grid-template-columns: 280px 1fr; min-height: 0; }
|
|
72
|
+
aside { border-right: 1px solid var(--line); padding: 18px; background: rgba(255,255,255,.02); }
|
|
73
|
+
.nav { display: grid; gap: 8px; }
|
|
74
|
+
.nav div { padding: 10px 12px; border: 1px solid var(--line); border-radius: 8px; color: var(--muted); font-size: 12px; }
|
|
75
|
+
.nav div:first-child { color: var(--text); border-color: rgba(34,211,238,.55); background: rgba(34,211,238,.08); }
|
|
76
|
+
section { padding: 22px; min-width: 0; }
|
|
77
|
+
.hero {
|
|
78
|
+
border: 1px solid var(--line); border-radius: 10px; overflow: hidden; background: var(--panel);
|
|
79
|
+
display: grid; grid-template-columns: minmax(260px, 1fr) 280px;
|
|
80
|
+
}
|
|
81
|
+
.hero-copy { padding: 28px; }
|
|
82
|
+
h1 { margin: 0; font-size: 34px; line-height: 1.08; letter-spacing: 0; }
|
|
83
|
+
p { color: var(--muted); line-height: 1.55; }
|
|
84
|
+
.actions { display: flex; gap: 10px; margin-top: 22px; }
|
|
85
|
+
button { border: 0; border-radius: 8px; padding: 10px 14px; font-weight: 700; }
|
|
86
|
+
.primary { background: var(--cyan); color: #061014; }
|
|
87
|
+
.secondary { background: #242a35; color: var(--text); }
|
|
88
|
+
.visual { padding: 20px; background: linear-gradient(135deg, rgba(34,211,238,.18), rgba(251,191,36,.12)); display: grid; align-content: center; gap: 12px; }
|
|
89
|
+
.card { border: 1px solid rgba(255,255,255,.16); border-radius: 8px; padding: 14px; background: rgba(16,18,22,.72); box-shadow: 0 18px 40px rgba(0,0,0,.22); }
|
|
90
|
+
.metric { font-size: 26px; font-weight: 800; }
|
|
91
|
+
.grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; margin-top: 16px; }
|
|
92
|
+
.tile { border: 1px solid var(--line); border-radius: 8px; padding: 14px; background: rgba(255,255,255,.03); min-height: 112px; }
|
|
93
|
+
.tile b { display: block; margin-bottom: 8px; }
|
|
94
|
+
@media (max-width: 760px) {
|
|
95
|
+
main, .hero { grid-template-columns: 1fr; }
|
|
96
|
+
aside { display: none; }
|
|
97
|
+
h1 { font-size: 28px; }
|
|
98
|
+
.grid { grid-template-columns: 1fr; }
|
|
99
|
+
}
|
|
100
|
+
</style>
|
|
101
|
+
</head>
|
|
102
|
+
<body>
|
|
103
|
+
<div class="shell">
|
|
104
|
+
<header>
|
|
105
|
+
<div class="brand">CDO Visual Sample</div>
|
|
106
|
+
<div class="mission">${escapeHtml(missionId)}</div>
|
|
107
|
+
</header>
|
|
108
|
+
<main>
|
|
109
|
+
<aside>
|
|
110
|
+
<div class="nav">
|
|
111
|
+
<div>Overview</div>
|
|
112
|
+
<div>Interaction</div>
|
|
113
|
+
<div>Accessibility</div>
|
|
114
|
+
<div>Review</div>
|
|
115
|
+
</div>
|
|
116
|
+
</aside>
|
|
117
|
+
<section>
|
|
118
|
+
<div class="hero">
|
|
119
|
+
<div class="hero-copy">
|
|
120
|
+
<h1>${escapeHtml(brief)}</h1>
|
|
121
|
+
<p>Structured sample generated from the active CDO/CEO mission context. Replace this with a richer cdo.md mockup artifact when available.</p>
|
|
122
|
+
<div class="actions">
|
|
123
|
+
<button class="primary">Primary action</button>
|
|
124
|
+
<button class="secondary">Secondary</button>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="visual">
|
|
128
|
+
<div class="card"><div class="metric">92%</div><p>Readiness score</p></div>
|
|
129
|
+
<div class="card"><b>State</b><p>Focused, scannable, worker-backed.</p></div>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
<div class="grid">
|
|
133
|
+
<div class="tile"><b>Layout</b><p>Quiet hierarchy, constrained content, no nested cards.</p></div>
|
|
134
|
+
<div class="tile"><b>Flow</b><p>Primary workflow remains visible without explanation copy.</p></div>
|
|
135
|
+
<div class="tile"><b>Quality</b><p>Designed for CDO review before CQO evidence collection.</p></div>
|
|
136
|
+
</div>
|
|
137
|
+
</section>
|
|
138
|
+
</main>
|
|
139
|
+
</div>
|
|
140
|
+
</body>
|
|
141
|
+
</html>`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function DesignPreviewTab({ mission }: DesignPreviewTabProps) {
|
|
145
|
+
const srcDoc = buildPreviewDoc(mission);
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<div className="flex h-full min-h-[620px] flex-col gap-3">
|
|
149
|
+
<div className="rounded border border-purple-400/30 bg-purple-500/10 px-3 py-2 text-[11px] text-purple-200">
|
|
150
|
+
harness-cdo visual preview
|
|
151
|
+
</div>
|
|
152
|
+
<iframe
|
|
153
|
+
title="CDO visual sample"
|
|
154
|
+
sandbox=""
|
|
155
|
+
srcDoc={srcDoc}
|
|
156
|
+
className="min-h-0 flex-1 rounded-md border border-gray-700 bg-[#101216]"
|
|
157
|
+
/>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
@@ -1229,6 +1229,7 @@ function readMissions(rootDir: string, progress: RawProgress | null = null, limi
|
|
|
1229
1229
|
cqo: readMd("cqo.md"),
|
|
1230
1230
|
coo: readMd("coo.md"),
|
|
1231
1231
|
cdo: readMd("cdo.md"),
|
|
1232
|
+
cdoPreview: readMd("cdo/preview.html"),
|
|
1232
1233
|
ops: readMd("ops.md"),
|
|
1233
1234
|
workers,
|
|
1234
1235
|
cxxPresent: [...cxxPresent],
|
|
@@ -89,6 +89,7 @@ Flat `.harness/documents/{mission}/workers/` reports are legacy. New worker evid
|
|
|
89
89
|
- CEO must not dispatch, hire, or brief workers directly.
|
|
90
90
|
- CXX agents must not directly execute specialist deliverables.
|
|
91
91
|
- CTO worker dispatch requires `cto.md` first.
|
|
92
|
+
- CDO completion requires `.harness/documents/{mission}/cdo/preview.html`; CEO/Owner can view it from the dashboard by clicking `harness-cdo`.
|
|
92
93
|
- CQO verdicts require evaluator/tester worker evidence.
|
|
93
94
|
- Owner is final acceptance only. Do not report "done, please check" until worker-backed verification proves the goal is complete.
|
|
94
95
|
- Every `ceo.md`, `{cxx}.md`, and worker report must end with:
|
|
@@ -186,6 +186,8 @@
|
|
|
186
186
|
"worker_spawn_description": "claude 이면 idle worker 배정 시 `claude -p` 백그라운드 프로세스를 띄운다. record 로 두면 prompt/log만 생성한다.",
|
|
187
187
|
"hourly_wake_executor": "claude",
|
|
188
188
|
"hourly_wake_executor_description": "claude 이면 `claude -p`, codex 이면 `codex exec` 로 매시간 autonomous tick 을 실행한다.",
|
|
189
|
+
"hourly_wake_model": "",
|
|
190
|
+
"hourly_wake_model_description": "Claude executor 에서 특정 모델을 강제하려면 HARNESS_WAKE_MODEL 또는 이 값을 사용한다. 비워두면 CLI 기본 모델을 사용한다.",
|
|
189
191
|
"hourly_wake_mode": "headless",
|
|
190
192
|
"hourly_wake_mode_description": "headless 는 백그라운드 실행, tmux 는 매시간 별도 tmux 세션, record 는 prompt/log만 생성한다.",
|
|
191
193
|
"autonomous": true,
|
package/bin/init.js
CHANGED
|
@@ -99,6 +99,19 @@ function copyDir(src, dest) {
|
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
function chmodShellScripts(dir) {
|
|
103
|
+
if (!fs.existsSync(dir)) return;
|
|
104
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
105
|
+
for (const entry of entries) {
|
|
106
|
+
const full = path.join(dir, entry.name);
|
|
107
|
+
if (entry.isDirectory()) {
|
|
108
|
+
chmodShellScripts(full);
|
|
109
|
+
} else if (entry.name.endsWith('.sh')) {
|
|
110
|
+
try { fs.chmodSync(full, '755'); } catch (e) {}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
102
115
|
function fileExists(p) {
|
|
103
116
|
return fs.existsSync(p);
|
|
104
117
|
}
|
|
@@ -728,25 +741,29 @@ function installScripts() {
|
|
|
728
741
|
|
|
729
742
|
if (fs.existsSync(scriptsSrc)) {
|
|
730
743
|
copyDir(scriptsSrc, scriptsDest);
|
|
731
|
-
|
|
732
|
-
// chmod +x for all .sh files (recursive)
|
|
733
|
-
function chmodRecursive(dir) {
|
|
734
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
735
|
-
for (const entry of entries) {
|
|
736
|
-
const full = path.join(dir, entry.name);
|
|
737
|
-
if (entry.isDirectory()) {
|
|
738
|
-
chmodRecursive(full);
|
|
739
|
-
} else if (entry.name.endsWith('.sh')) {
|
|
740
|
-
try { fs.chmodSync(full, '755'); } catch (e) {}
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
chmodRecursive(scriptsDest);
|
|
744
|
+
chmodShellScripts(scriptsDest);
|
|
745
745
|
}
|
|
746
746
|
|
|
747
747
|
log('Scripts installation complete');
|
|
748
748
|
}
|
|
749
749
|
|
|
750
|
+
function refreshScriptsForMigrate({ dryRun = false, backupDir = null } = {}) {
|
|
751
|
+
const scriptsSrc = path.join(PKG_ROOT, 'scripts');
|
|
752
|
+
const scriptsDest = path.join(PROJECT_ROOT, 'scripts');
|
|
753
|
+
if (!fs.existsSync(scriptsSrc)) return;
|
|
754
|
+
|
|
755
|
+
log(' scripts/: package runtime scripts refresh');
|
|
756
|
+
if (dryRun) return;
|
|
757
|
+
|
|
758
|
+
if (backupDir && fs.existsSync(scriptsDest)) {
|
|
759
|
+
const scriptsBackup = path.join(backupDir, 'scripts');
|
|
760
|
+
copyDir(scriptsDest, scriptsBackup);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
copyDir(scriptsSrc, scriptsDest);
|
|
764
|
+
chmodShellScripts(scriptsDest);
|
|
765
|
+
}
|
|
766
|
+
|
|
750
767
|
// ─────────────────────────────────────────
|
|
751
768
|
// 3a. Commands → .claude/commands/
|
|
752
769
|
// ─────────────────────────────────────────
|
|
@@ -1205,6 +1222,7 @@ function detectMigrationNeeded() {
|
|
|
1205
1222
|
configMissingCompanyMode: false,
|
|
1206
1223
|
configLegacyRouting: false,
|
|
1207
1224
|
configRuntimeVerificationMissing: false,
|
|
1225
|
+
configWakeModelMissing: false,
|
|
1208
1226
|
coreSkillsStale: false,
|
|
1209
1227
|
hrResourcePoolStale: false,
|
|
1210
1228
|
harnessMdStale: false,
|
|
@@ -1307,6 +1325,9 @@ function detectMigrationNeeded() {
|
|
|
1307
1325
|
if (!c.runtime?.verification?.watch_during_cqo || !c.runtime?.verification?.require_ops_evidence_for_pass) {
|
|
1308
1326
|
flags.configRuntimeVerificationMissing = true;
|
|
1309
1327
|
}
|
|
1328
|
+
if (c.company_mode && c.company_mode.hourly_wake_model === undefined) {
|
|
1329
|
+
flags.configWakeModelMissing = true;
|
|
1330
|
+
}
|
|
1310
1331
|
if (
|
|
1311
1332
|
c.behavior?.auto_route_dispatcher !== undefined ||
|
|
1312
1333
|
c.behavior?.auto_route_dispatcher_description ||
|
|
@@ -1439,6 +1460,10 @@ function showMigrationProposal(flags) {
|
|
|
1439
1460
|
console.log(' • config.json: runtime.verification 섹션 추가 가능');
|
|
1440
1461
|
console.log(' CQO 검수 중 OPS runtime watch 와 PASS 차단 조건을 설정합니다.');
|
|
1441
1462
|
}
|
|
1463
|
+
if (flags.configWakeModelMissing) {
|
|
1464
|
+
console.log(' • config.json: company_mode.hourly_wake_model 추가 가능');
|
|
1465
|
+
console.log(' Claude wake tick 에서 --model 을 환경변수/설정으로 지정할 수 있습니다.');
|
|
1466
|
+
}
|
|
1442
1467
|
if (flags.configLegacyRouting) {
|
|
1443
1468
|
console.log(' • config.json: legacy dispatcher/conductor wording → v7 CEO/CXX wording');
|
|
1444
1469
|
}
|
|
@@ -1499,11 +1524,13 @@ function runMigrate(opts = {}) {
|
|
|
1499
1524
|
// Always refresh owner-facing commands (full replacement regardless of current state).
|
|
1500
1525
|
if (dryRun) {
|
|
1501
1526
|
log(' (dry-run) commands: /goal, /submission, /hot-fix 교체 예정');
|
|
1527
|
+
log(' (dry-run) scripts/: package runtime scripts refresh 예정');
|
|
1502
1528
|
} else {
|
|
1503
1529
|
installCommands();
|
|
1504
1530
|
}
|
|
1505
1531
|
|
|
1506
1532
|
const flags = detectMigrationNeeded();
|
|
1533
|
+
const runtimeScriptsAlwaysRefresh = true;
|
|
1507
1534
|
const gotchaMissingTotal = Object.values(flags.gotchaMissingEntries || {}).reduce((n, a) => n + a.length, 0);
|
|
1508
1535
|
const conventionMissingTotal = Object.values(flags.conventionMissingEntries || {}).reduce((n, a) => n + a.length, 0);
|
|
1509
1536
|
if (
|
|
@@ -1512,6 +1539,7 @@ function runMigrate(opts = {}) {
|
|
|
1512
1539
|
!flags.configMissingCompanyMode &&
|
|
1513
1540
|
!flags.configLegacyRouting &&
|
|
1514
1541
|
!flags.configRuntimeVerificationMissing &&
|
|
1542
|
+
!flags.configWakeModelMissing &&
|
|
1515
1543
|
!flags.coreSkillsStale &&
|
|
1516
1544
|
!flags.hrResourcePoolStale &&
|
|
1517
1545
|
!flags.harnessMdStale &&
|
|
@@ -1521,7 +1549,8 @@ function runMigrate(opts = {}) {
|
|
|
1521
1549
|
(!flags.memoryMissingSystemEntries || flags.memoryMissingSystemEntries.length === 0) &&
|
|
1522
1550
|
gotchaMissingTotal === 0 &&
|
|
1523
1551
|
conventionMissingTotal === 0 &&
|
|
1524
|
-
!flags.bundleVersionStale
|
|
1552
|
+
!flags.bundleVersionStale &&
|
|
1553
|
+
!runtimeScriptsAlwaysRefresh
|
|
1525
1554
|
) {
|
|
1526
1555
|
console.log('');
|
|
1527
1556
|
let pkgVer = 'unknown';
|
|
@@ -1537,6 +1566,10 @@ function runMigrate(opts = {}) {
|
|
|
1537
1566
|
const backupDir = path.join(HARNESS_DIR, 'archive', `migration-${ts}`);
|
|
1538
1567
|
if (!dryRun) ensureDir(backupDir);
|
|
1539
1568
|
|
|
1569
|
+
// Runtime scripts are package-owned. Migrate must refresh them so existing
|
|
1570
|
+
// projects receive wake/meeting/OPS fixes without requiring a full init.
|
|
1571
|
+
refreshScriptsForMigrate({ dryRun, backupDir });
|
|
1572
|
+
|
|
1540
1573
|
// 1. progress.json
|
|
1541
1574
|
const progressPath = path.join(HARNESS_DIR, 'progress.json');
|
|
1542
1575
|
if ((flags.progressV3toV4 || flags.progressLegacyRouting) && fs.existsSync(progressPath)) {
|
|
@@ -1568,10 +1601,11 @@ function runMigrate(opts = {}) {
|
|
|
1568
1601
|
// 2. config.json — inject company_mode/runtime verification from template if missing
|
|
1569
1602
|
const configPath = path.join(HARNESS_DIR, 'config.json');
|
|
1570
1603
|
const tplPath = path.join(PKG_ROOT, 'assets', 'templates', 'config.json');
|
|
1571
|
-
if ((flags.configMissingCompanyMode || flags.configLegacyRouting || flags.configRuntimeVerificationMissing) && fs.existsSync(configPath) && fs.existsSync(tplPath)) {
|
|
1604
|
+
if ((flags.configMissingCompanyMode || flags.configLegacyRouting || flags.configRuntimeVerificationMissing || flags.configWakeModelMissing) && fs.existsSync(configPath) && fs.existsSync(tplPath)) {
|
|
1572
1605
|
const original = fs.readFileSync(configPath, 'utf8');
|
|
1573
1606
|
const c = JSON.parse(original);
|
|
1574
1607
|
const tpl = JSON.parse(fs.readFileSync(tplPath, 'utf8'));
|
|
1608
|
+
let configChanged = false;
|
|
1575
1609
|
if (tpl.company_mode) {
|
|
1576
1610
|
if (flags.configMissingCompanyMode || flags.configLegacyRouting) {
|
|
1577
1611
|
c.company_mode = {
|
|
@@ -1588,14 +1622,23 @@ function runMigrate(opts = {}) {
|
|
|
1588
1622
|
delete c.behavior.auto_route_dispatcher;
|
|
1589
1623
|
delete c.behavior.auto_route_dispatcher_description;
|
|
1590
1624
|
delete c.mode_selection;
|
|
1625
|
+
configChanged = true;
|
|
1591
1626
|
log(' config.json: v7 CEO/CXX routing 섹션 동기화');
|
|
1592
1627
|
}
|
|
1628
|
+
if (flags.configWakeModelMissing) {
|
|
1629
|
+
c.company_mode = c.company_mode || {};
|
|
1630
|
+
c.company_mode.hourly_wake_model = tpl.company_mode.hourly_wake_model ?? '';
|
|
1631
|
+
c.company_mode.hourly_wake_model_description = tpl.company_mode.hourly_wake_model_description;
|
|
1632
|
+
configChanged = true;
|
|
1633
|
+
log(' config.json: hourly_wake_model 섹션 동기화');
|
|
1634
|
+
}
|
|
1593
1635
|
if (flags.configRuntimeVerificationMissing && tpl.runtime?.verification) {
|
|
1594
1636
|
c.runtime = c.runtime || {};
|
|
1595
1637
|
c.runtime.verification = deepMerge(tpl.runtime.verification, c.runtime.verification || {});
|
|
1638
|
+
configChanged = true;
|
|
1596
1639
|
log(' config.json: runtime.verification 섹션 동기화');
|
|
1597
1640
|
}
|
|
1598
|
-
if (!dryRun) {
|
|
1641
|
+
if (!dryRun && configChanged) {
|
|
1599
1642
|
fs.writeFileSync(path.join(backupDir, 'config.json'), original);
|
|
1600
1643
|
fs.writeFileSync(configPath, JSON.stringify(c, null, 2) + '\n');
|
|
1601
1644
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walwal-harness/cli",
|
|
3
|
-
"version": "7.1.
|
|
3
|
+
"version": "7.1.24",
|
|
4
4
|
"description": "Company-style AI agent harness for Claude and Codex. Installs commands, CXX agents, skills, HR-Resource hiring pool, and project-local .harness runtime state.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"walwal-harness": "bin/init.js"
|
package/scripts/harness-wake.sh
CHANGED
|
@@ -130,10 +130,17 @@ start_headless_tick() {
|
|
|
130
130
|
local idle="$2"
|
|
131
131
|
local force="$3"
|
|
132
132
|
local review_path="$4"
|
|
133
|
-
local mode executor agent_bin stamp wake_dir ops_dir prompt_rel log_rel prompt_path log_path pid_file old_pid pid status tmux_session session_name session_safe
|
|
133
|
+
local mode executor wake_model agent_bin stamp wake_dir ops_dir prompt_rel log_rel prompt_path log_path pid_file old_pid pid status tmux_session session_name session_safe
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
if command -v jq >/dev/null 2>&1 && [ -f "$project_root/.harness/config.json" ]; then
|
|
136
|
+
mode="${HARNESS_WAKE_MODE:-$(jq -r '.company_mode.hourly_wake_mode // "headless"' "$project_root/.harness/config.json" 2>/dev/null || echo headless)}"
|
|
137
|
+
executor="${HARNESS_WAKE_EXECUTOR:-$(jq -r '.company_mode.hourly_wake_executor // "claude"' "$project_root/.harness/config.json" 2>/dev/null || echo claude)}"
|
|
138
|
+
wake_model="${HARNESS_WAKE_MODEL:-$(jq -r '.company_mode.hourly_wake_model // ""' "$project_root/.harness/config.json" 2>/dev/null || true)}"
|
|
139
|
+
else
|
|
140
|
+
mode="${HARNESS_WAKE_MODE:-headless}"
|
|
141
|
+
executor="${HARNESS_WAKE_EXECUTOR:-claude}"
|
|
142
|
+
wake_model="${HARNESS_WAKE_MODEL:-}"
|
|
143
|
+
fi
|
|
137
144
|
stamp="$(date -u "+%Y%m%dT%H%M%SZ")"
|
|
138
145
|
wake_dir="$project_root/.harness/actions/wake"
|
|
139
146
|
ops_dir="$project_root/.harness/ops/wake"
|
|
@@ -154,28 +161,58 @@ start_headless_tick() {
|
|
|
154
161
|
fi
|
|
155
162
|
|
|
156
163
|
cat > "$prompt_path" <<EOF
|
|
157
|
-
You are the walwal-harness
|
|
164
|
+
You are harness-ceo waking for the walwal-harness hourly executive loop.
|
|
158
165
|
|
|
159
166
|
Project root: $project_root
|
|
160
167
|
Wake reason: idle=${idle}s force=${force}
|
|
161
168
|
Hourly review: ${review_path:-"(not available)"}
|
|
162
169
|
|
|
170
|
+
One-line command:
|
|
171
|
+
Convene CXX + OPS, collect current progress and decisions, decide the next action as CEO with the active goal in mind, drive all teams without asking Owner, and use CQO verification until the goal demonstrably works.
|
|
172
|
+
|
|
163
173
|
Policy:
|
|
164
|
-
- Owner input after the initial GOAL is an interrupt or additional request, never a reason to wait.
|
|
174
|
+
- Owner already provided the GOAL. Owner input after the initial GOAL is an interrupt or additional request, never a reason to wait.
|
|
175
|
+
- Do not ask Owner what to do next. Convert uncertainty into CXX/OPS tasks, CQO checks, or a bounded assumption written to artifacts.
|
|
165
176
|
- Never set conductor.state to waiting_owner. Normalize any waiting_owner state to waiting_meeting and continue.
|
|
166
|
-
- meeting-manager is the
|
|
177
|
+
- meeting-manager is the synchronization loop, not the stopping point. Incidents, service warnings, goal drift, and escalations go to meeting-manager first, then route execution.
|
|
167
178
|
- Keep this to one bounded autonomous tick. Do not run an endless loop.
|
|
168
179
|
- Do not write future timestamps to .harness/progress.log or artifacts.
|
|
169
180
|
- Keep terminal output concise; write evidence to project files.
|
|
181
|
+
- Treat paperwork-only output as insufficient. A meeting that does not create or advance executable next actions is incomplete.
|
|
182
|
+
|
|
183
|
+
ADHD/autonomy support pattern:
|
|
184
|
+
- Act as an external executive-function scaffold: break blocked work into tiny executable next steps, name the first action, and start it.
|
|
185
|
+
- Use body-doubling behavior: check the active work state, ask each role for a concise status through artifacts, and keep the loop moving.
|
|
186
|
+
- Use context-transition support: summarize what just finished, what starts next, and the handoff artifact path.
|
|
187
|
+
- Use time-guardian behavior: identify hidden sub-tasks that make the work longer than expected, set a realistic next checkpoint, and avoid vague "later" states.
|
|
188
|
+
- Use open-loop triage: classify discovered work as NOW / NEXT / PARKED. Execute or dispatch NOW items only.
|
|
170
189
|
|
|
171
190
|
Tasks:
|
|
172
191
|
1. Read CONVENTIONS.md if present, .harness/conventions/shared.md, relevant .harness/conventions/<role>.md, .harness/gotchas/<role>.md, .harness/memory.md, .harness/progress.json, and the hourly review above.
|
|
173
|
-
2.
|
|
174
|
-
3.
|
|
175
|
-
4.
|
|
176
|
-
5.
|
|
177
|
-
6.
|
|
178
|
-
|
|
192
|
+
2. Determine the active GOAL/submission/hot-fix from progress.json and .harness/documents. Restate it in one sentence for yourself.
|
|
193
|
+
3. Convene the executive loop through artifacts: CEO + COO + CDO + CTO + CQO + OPS. Collect only concise status, blocker, decision-needed, and evidence-path information.
|
|
194
|
+
4. If service_ops.requested_mode is monitor, run or trigger one service-ops monitor pass and record last_check. stream_active may be true only during the pass and must be false when finished.
|
|
195
|
+
5. Treat the hourly review as Executive Meeting Minutes. Verify it contains role positions, discussion, decision JSON, action items, and CQO/OPS evidence requirements.
|
|
196
|
+
6. As CEO, decide exactly one NOW action package:
|
|
197
|
+
- owner CXX/role,
|
|
198
|
+
- action_type,
|
|
199
|
+
- deliverable_path,
|
|
200
|
+
- success_condition,
|
|
201
|
+
- verifier, preferably CQO when goal behavior must be proven.
|
|
202
|
+
7. Route all teams needed for that NOW action. Do not stop after hiring, planning, status reporting, or meeting note creation.
|
|
203
|
+
8. Use CQO to verify whether the goal is actually satisfied. If verification fails or is missing, route implementation/fix work again.
|
|
204
|
+
9. If there is an active incident or operational warning, make sure meeting-manager has shared context and an action decision that names CTO/CQO/OPS responsibilities.
|
|
205
|
+
10. Split meaningful progress from paperwork-only artifacts in your summary.
|
|
206
|
+
11. Append a short factual summary to .harness/progress.log and update normal harness artifacts used by this project.
|
|
207
|
+
|
|
208
|
+
Required final shape for this tick:
|
|
209
|
+
- GOAL:
|
|
210
|
+
- CXX/OPS status collected:
|
|
211
|
+
- CEO decision:
|
|
212
|
+
- NOW action dispatched:
|
|
213
|
+
- CQO/OPS verification required:
|
|
214
|
+
- NEXT checkpoint:
|
|
215
|
+
- Evidence paths:
|
|
179
216
|
EOF
|
|
180
217
|
|
|
181
218
|
case "$mode" in
|
|
@@ -189,7 +226,11 @@ EOF
|
|
|
189
226
|
if [ "$executor" = "codex" ]; then
|
|
190
227
|
"$agent_bin" exec -C "$project_root" "$(cat "$prompt_path")" > "$log_path" 2>&1
|
|
191
228
|
else
|
|
192
|
-
|
|
229
|
+
if [ -n "$wake_model" ]; then
|
|
230
|
+
"$agent_bin" -p "$(cat "$prompt_path")" --model "$wake_model" > "$log_path" 2>&1
|
|
231
|
+
else
|
|
232
|
+
"$agent_bin" -p "$(cat "$prompt_path")" > "$log_path" 2>&1
|
|
233
|
+
fi
|
|
193
234
|
fi
|
|
194
235
|
) &
|
|
195
236
|
pid=$!
|
|
@@ -208,7 +249,11 @@ EOF
|
|
|
208
249
|
if [ "$executor" = "codex" ]; then
|
|
209
250
|
tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' exec -C '$project_root' - < '$prompt_path' 2>&1 | tee '$log_path'"
|
|
210
251
|
else
|
|
211
|
-
|
|
252
|
+
if [ -n "$wake_model" ]; then
|
|
253
|
+
tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' -p \"\$(cat '$prompt_path')\" --model '$wake_model' 2>&1 | tee '$log_path'"
|
|
254
|
+
else
|
|
255
|
+
tmux new-session -d -s "$session_name" "cd '$project_root' && '$agent_bin' -p \"\$(cat '$prompt_path')\" 2>&1 | tee '$log_path'"
|
|
256
|
+
fi
|
|
212
257
|
fi
|
|
213
258
|
status="spawned-tmux executor=$executor session=$session_name"
|
|
214
259
|
else
|