@walwal-harness/cli 7.1.26 → 7.1.28

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.
@@ -20,6 +20,8 @@
20
20
  "next": "^15.0.0",
21
21
  "react": "^19.0.0",
22
22
  "react-dom": "^19.0.0",
23
+ "react-markdown": "^10.1.0",
24
+ "remark-gfm": "^4.0.1",
23
25
  "three": "^0.184.0"
24
26
  },
25
27
  "devDependencies": {
@@ -121,6 +121,9 @@ Note: A docmeta skip decision on harness documents (ceo.md, cto.md, cqo.md, work
121
121
  | `.harness/documents/goal-{index}-{name}/` | Goal-level CXX decisions and worker reports |
122
122
  | `.harness/documents/goal-{index}-{name}/submission-{index}-{name}/` | Additional requirement under the active goal |
123
123
  | `.harness/documents/goal-{index}-{name}/hotfix-{index}-{name}/` | Emergency fix under the active goal |
124
+ | `.harness/events.jsonl` | Structured append-only runtime event stream |
125
+ | `.harness/todos/state.json` | Machine-readable CXX todo queues |
126
+ | `.harness/todos/events.jsonl` | Structured todo transition history |
124
127
  | `.harness/conventions/` | Durable rules (CQO writes, survives missions) |
125
128
  | `.harness/gotchas/` | Recurrence-prevention records (CQO writes per hot-fix) |
126
129
  | `.harness/memories/` | Long-term shared context |
@@ -145,10 +148,11 @@ Note: A docmeta skip decision on harness documents (ceo.md, cto.md, cqo.md, work
145
148
  9. **No verdict without worker evidence** — CQO cannot issue ACCEPTED/REJECTED without a Worker Evidence Manifest referencing at least one evaluator worker. Self-inspection by CQO is not valid evidence.
146
149
  10. **Hierarchical worker ownership** — Hired workers are installed under `.claude/skills/{owning-cxx}/{worker}/` and `.codex/skills/{owning-cxx}/{worker}/`; mission worker reports live under `.harness/documents/{goal-or-child-mission}/{owning-cxx}/workers/`. Flat `{mission}/workers/` reports are legacy and signal an ownership violation unless explicitly migrated.
147
150
  11. **Lazy convention/gotcha loading** — CXX roles read `.harness/conventions/shared.md`, `.harness/conventions/{cxx}.md`, `.harness/gotchas/shared.md`, and `.harness/gotchas/{cxx}.md`, then follow only the related topic links listed in those CXX files. Workers read only the related links supplied by their owning CXX.
148
- 11. **Implementation Notes required** — `ceo.md`, every `{cxx}.md`, and every worker report must end with one English `## Implementation Notes` section containing `Design Decisions`, `Deviations`, `Tradeoffs`, and `Open Questions`. Use `None` for empty subsections. Do not create a separate sidecar notes file; the notes belong at the bottom of the same role or worker report that produced the decision/evidence.
149
- 12. **Owner is final acceptance only** — Owner is not a tester or QA substitute. CEO/CXX must not report "done, please check" until worker-backed verification proves the goal can be completed. Use unit tests, E2E, Playwright, test accounts, seeded data, build/run checks, logs, and CQO evidence before requesting Owner acceptance.
150
- 13. **OPS watches CQO verification** — For runnable products, CQO PASS is invalid unless OPS has monitored the verification runtime or documented why OPS watch is not applicable. Open OPS incidents, missing runtime mapping, required log gaps, service down, health mismatch, or an unmonitored tested service block Owner acceptance.
151
- 14. **Production incidents are company events** — After launch, user-impacting OPS signals route through CEO to CTO/CQO/OPS. CTO owns recovery, CQO owns regression confirmation, and OPS owns evidence plus close criteria.
151
+ 12. **Implementation Notes required** — `ceo.md`, every `{cxx}.md`, and every worker report must end with one English `## Implementation Notes` section containing `Design Decisions`, `Deviations`, `Tradeoffs`, and `Open Questions`. Use `None` for empty subsections. Do not create a separate sidecar notes file; the notes belong at the bottom of the same role or worker report that produced the decision/evidence.
152
+ 13. **Structured runtime state** — If the harness must parse it, record it in JSON or JSONL. CXX todo queues, event history, heartbeat timestamps, preemption/resume state, and completion evidence belong in `.harness/todos/*.json*` or `.harness/events.jsonl`, not in free-form Markdown tables. Markdown remains for instructions, conventions, gotchas, skills, and human-readable mission narrative.
153
+ 14. **Owner is final acceptance only** — Owner is not a tester or QA substitute. CEO/CXX must not report "done, please check" until worker-backed verification proves the goal can be completed. Use unit tests, E2E, Playwright, test accounts, seeded data, build/run checks, logs, and CQO evidence before requesting Owner acceptance.
154
+ 15. **OPS watches CQO verification** — For runnable products, CQO PASS is invalid unless OPS has monitored the verification runtime or documented why OPS watch is not applicable. Open OPS incidents, missing runtime mapping, required log gaps, service down, health mismatch, or an unmonitored tested service block Owner acceptance.
155
+ 16. **Production incidents are company events** — After launch, user-impacting OPS signals route through CEO to CTO/CQO/OPS. CTO owns recovery, CQO owns regression confirmation, and OPS owns evidence plus close criteria.
152
156
 
153
157
  ---
154
158
 
@@ -51,11 +51,16 @@ Owner is the final acceptance reviewer, not a tester, QA substitute, debugger, o
51
51
  | `.harness/shared/HR-Resource/` | Hireable worker skill pool |
52
52
  | `.harness/shared/hr-roster.json` | Hired worker roster |
53
53
  | `.harness/archive/` | CQO-approved completed mission records |
54
- | `.harness/progress.log` | Append-only command and status history |
54
+ | `.harness/events.jsonl` | Structured append-only runtime event stream |
55
+ | `.harness/todos/state.json` | Machine-readable CXX todo queues |
56
+ | `.harness/todos/events.jsonl` | Structured todo transition history |
57
+ | `.harness/progress.log` | Legacy human-readable command and status history |
55
58
  | `.harness/progress.json` | Machine-readable current runtime state |
56
59
 
57
60
  Flat `.harness/documents/{mission}/workers/` reports are legacy. New worker evidence must be under `{owning-cxx}/workers/`.
58
61
 
62
+ Runtime state that the harness must parse belongs in JSON or JSONL, not Markdown. Markdown remains valid for instructions, skills, conventions, gotchas, and human-facing mission narrative, but queues, events, heartbeats, preemption/resume state, and completion evidence must be machine-readable.
63
+
59
64
  ## Required Flow
60
65
 
61
66
  ### Goal
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 1,
3
+ "updated_at": "{{DATE_ISO}}",
4
+ "owners": {
5
+ "ceo": [],
6
+ "coo": [],
7
+ "cdo": [],
8
+ "cto": [],
9
+ "cqo": [],
10
+ "ops": []
11
+ }
12
+ }
package/bin/init.js CHANGED
@@ -562,6 +562,7 @@ function scaffoldHarness() {
562
562
  // Copy templates as initial files
563
563
  const templateMap = {
564
564
  'progress.json.template': path.join(HARNESS_DIR, 'progress.json'),
565
+ 'todos-state.json.template': path.join(HARNESS_DIR, 'todos', 'state.json'),
565
566
  };
566
567
 
567
568
  const templatesDir = path.join(PKG_ROOT, 'assets', 'templates');
@@ -569,13 +570,22 @@ function scaffoldHarness() {
569
570
  for (const [template, dest] of Object.entries(templateMap)) {
570
571
  const src = path.join(templatesDir, template);
571
572
  if (fs.existsSync(src) && (!fileExists(dest) || isForce)) {
573
+ ensureDir(path.dirname(dest));
572
574
  let content = fs.readFileSync(src, 'utf8');
573
575
  content = content.replace(/\{\{DATE\}\}/g, new Date().toISOString().split('T')[0]);
576
+ content = content.replace(/\{\{DATE_ISO\}\}/g, new Date().toISOString());
574
577
  fs.writeFileSync(dest, content);
575
578
  }
576
579
  }
577
580
  }
578
581
 
582
+ ensureDir(path.join(HARNESS_DIR, 'todos'));
583
+ for (const rel of ['events.jsonl', path.join('todos', 'events.jsonl')]) {
584
+ const p = path.join(HARNESS_DIR, rel);
585
+ if (!fileExists(p) || isForce) fs.writeFileSync(p, '');
586
+ }
587
+ ensureStructuredRuntimeFiles();
588
+
579
589
  // Migrate progress.json v1 → v2 (add mode + team_state fields)
580
590
  const progressPath = path.join(HARNESS_DIR, 'progress.json');
581
591
  if (fileExists(progressPath)) {
@@ -711,6 +721,38 @@ function ensureHarnessEnv() {
711
721
  log('.env: HARNESS_BASE_PORT placeholder added — set value with Owner approval before starting services');
712
722
  }
713
723
 
724
+ function ensureStructuredRuntimeFiles({ dryRun = false } = {}) {
725
+ const todosDir = path.join(HARNESS_DIR, 'todos');
726
+ const statePath = path.join(todosDir, 'state.json');
727
+ const eventsPath = path.join(HARNESS_DIR, 'events.jsonl');
728
+ const todoEventsPath = path.join(todosDir, 'events.jsonl');
729
+ const todoTplPath = path.join(PKG_ROOT, 'assets', 'templates', 'todos-state.json.template');
730
+
731
+ const missing = [eventsPath, statePath, todoEventsPath].filter((p) => !fileExists(p));
732
+ if (!missing.length) return false;
733
+
734
+ log(` structured runtime: ${missing.map((p) => path.relative(HARNESS_DIR, p)).join(', ')} 생성`);
735
+ if (dryRun) return true;
736
+
737
+ ensureDir(todosDir);
738
+ if (!fileExists(eventsPath)) fs.writeFileSync(eventsPath, '');
739
+ if (!fileExists(todoEventsPath)) fs.writeFileSync(todoEventsPath, '');
740
+ if (!fileExists(statePath)) {
741
+ let content;
742
+ if (fileExists(todoTplPath)) {
743
+ content = fs.readFileSync(todoTplPath, 'utf8').replace(/\{\{DATE_ISO\}\}/g, new Date().toISOString());
744
+ } else {
745
+ content = JSON.stringify({
746
+ version: 1,
747
+ updated_at: new Date().toISOString(),
748
+ owners: { ceo: [], coo: [], cdo: [], cto: [], cqo: [], ops: [] },
749
+ }, null, 2) + '\n';
750
+ }
751
+ fs.writeFileSync(statePath, content);
752
+ }
753
+ return true;
754
+ }
755
+
714
756
  // ─────────────────────────────────────────
715
757
  // 2. Skills → .claude/skills/
716
758
  // ─────────────────────────────────────────
@@ -1742,6 +1784,7 @@ function runMigrate(opts = {}) {
1742
1784
  const ts = new Date().toISOString().replace(/[:.]/g, '-');
1743
1785
  const backupDir = path.join(HARNESS_DIR, 'archive', `migration-${ts}`);
1744
1786
  if (!dryRun) ensureDir(backupDir);
1787
+ ensureStructuredRuntimeFiles({ dryRun });
1745
1788
 
1746
1789
  // Runtime scripts are package-owned. Migrate must refresh them so existing
1747
1790
  // projects receive wake/meeting/OPS fixes without requiring a full init.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "7.1.26",
3
+ "version": "7.1.28",
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"
@@ -10,6 +10,8 @@ if [ -z "$CWD" ]; then CWD="$PWD"; fi
10
10
  # 조건 1: 하네스 초기화 확인
11
11
  if [ ! -f "$CWD/.harness/config.json" ]; then exit 0; fi
12
12
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
13
+ STRUCTURED_LIB="$SCRIPT_DIR/lib/harness-structured-log.sh"
14
+ [ -f "$STRUCTURED_LIB" ] && source "$STRUCTURED_LIB"
13
15
 
14
16
  # 조건 2: opt-out 플래그 확인
15
17
  AUTO_ROUTE="true"
@@ -71,7 +73,47 @@ if [ -n "$PROMPT" ] && [ -d "$CWD/.harness" ]; then
71
73
  COMMAND_TYPE="hot-fix"
72
74
  fi
73
75
  echo "$(date +"%Y-%m-%d %H:%M") | user-prompt | ${COMMAND_TYPE} | ${PROMPT_SHORT}" >> "$PROGRESS_LOG"
76
+ if declare -f harness_emit_event >/dev/null 2>&1; then
77
+ NOW_ISO_EVENT=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
78
+ EVENT_JSON=$(jq -nc \
79
+ --arg ts "$NOW_ISO_EVENT" \
80
+ --arg command "$COMMAND_TYPE" \
81
+ --arg summary "$PROMPT_SHORT" \
82
+ '{ts:$ts,type:"user_prompt",command:$command,summary:$summary,source:"UserPromptSubmit"}')
83
+ harness_emit_event "$CWD" "$EVENT_JSON"
84
+ fi
74
85
  if [ "$COMMAND_TYPE" = "goal" ] || [ "$COMMAND_TYPE" = "submission" ] || [ "$COMMAND_TYPE" = "hot-fix" ]; then
86
+ if declare -f harness_todo_upsert >/dev/null 2>&1; then
87
+ NOW_ISO_TODO=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
88
+ PRIORITY=50
89
+ KIND="$COMMAND_TYPE"
90
+ [ "$COMMAND_TYPE" = "hot-fix" ] && PRIORITY=100 && KIND="hotfix"
91
+ TODO_ID="ceo-${COMMAND_TYPE}-$(date -u +%Y%m%dT%H%M%SZ)"
92
+ TODO_JSON=$(jq -nc \
93
+ --arg id "$TODO_ID" \
94
+ --arg owner "ceo" \
95
+ --arg kind "$KIND" \
96
+ --arg title "$PROMPT_SHORT" \
97
+ --arg status "active" \
98
+ --arg now "$NOW_ISO_TODO" \
99
+ --argjson priority "$PRIORITY" \
100
+ '{
101
+ id:$id,
102
+ owner:$owner,
103
+ kind:$kind,
104
+ title:$title,
105
+ status:$status,
106
+ priority:$priority,
107
+ command:$kind,
108
+ mission_path:null,
109
+ required_artifacts:[],
110
+ created_at:$now,
111
+ updated_at:$now,
112
+ last_heartbeat_at:$now,
113
+ blocked_reason:null
114
+ }')
115
+ harness_todo_upsert "$CWD" "$TODO_JSON"
116
+ fi
75
117
  if [ -f "$CWD/.harness/progress.json" ] && command -v jq >/dev/null 2>&1; then
76
118
  TMP_PROGRESS="${CWD}/.harness/progress.json.tmp.$$"
77
119
  NOW_ISO=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
@@ -0,0 +1,59 @@
1
+ #!/bin/bash
2
+ # Structured runtime helpers for events.jsonl and todos/state.json.
3
+
4
+ harness_json_escape() {
5
+ jq -Rs . 2>/dev/null
6
+ }
7
+
8
+ harness_ensure_structured_runtime() {
9
+ local project_root="${1:-.}"
10
+ local harness_dir="$project_root/.harness"
11
+ [ -d "$harness_dir" ] || return 0
12
+ mkdir -p "$harness_dir/todos"
13
+ [ -f "$harness_dir/events.jsonl" ] || : > "$harness_dir/events.jsonl"
14
+ [ -f "$harness_dir/todos/events.jsonl" ] || : > "$harness_dir/todos/events.jsonl"
15
+ if [ ! -f "$harness_dir/todos/state.json" ]; then
16
+ jq -n --arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" '{
17
+ version: 1,
18
+ updated_at: $now,
19
+ owners: {ceo: [], coo: [], cdo: [], cto: [], cqo: [], ops: []}
20
+ }' > "$harness_dir/todos/state.json"
21
+ fi
22
+ }
23
+
24
+ harness_emit_event() {
25
+ local project_root="$1"
26
+ local event_json="$2"
27
+ harness_ensure_structured_runtime "$project_root"
28
+ local path="$project_root/.harness/events.jsonl"
29
+ jq -c --arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" '
30
+ . + {ts: (.ts // $now)}
31
+ ' <<<"$event_json" >> "$path" 2>/dev/null || true
32
+ }
33
+
34
+ harness_todo_upsert() {
35
+ local project_root="$1"
36
+ local todo_json="$2"
37
+ harness_ensure_structured_runtime "$project_root"
38
+ local state="$project_root/.harness/todos/state.json"
39
+ local events="$project_root/.harness/todos/events.jsonl"
40
+ local tmp="$state.tmp.$$"
41
+ local now
42
+ now="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
43
+
44
+ jq -c --arg now "$now" '. + {ts: (.ts // $now), type: (.type // "todo_upsert")}' <<<"$todo_json" >> "$events" 2>/dev/null || true
45
+ if jq --argjson todo "$todo_json" --arg now "$now" '
46
+ .version = (.version // 1) |
47
+ .updated_at = $now |
48
+ .owners = (.owners // {ceo: [], coo: [], cdo: [], cto: [], cqo: [], ops: []}) |
49
+ ($todo.owner // "ceo") as $owner |
50
+ .owners[$owner] = (.owners[$owner] // []) |
51
+ .owners[$owner] = ((.owners[$owner] | map(select(.id != $todo.id))) + [
52
+ ($todo + {updated_at: ($todo.updated_at // $now), created_at: ($todo.created_at // $now)})
53
+ ] | sort_by(-(.priority // 0), .created_at))
54
+ ' "$state" > "$tmp" 2>/dev/null; then
55
+ mv "$tmp" "$state"
56
+ else
57
+ rm -f "$tmp"
58
+ fi
59
+ }