@walwal-harness/cli 7.1.0 → 7.1.2

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.
@@ -34,3 +34,14 @@ You are the only direct conversation channel with the Owner.
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.
36
36
  - For service monitoring, collect the Owner's server mapping first: local PC, Docker, VM, AWS/cloud, host, port, health path, log path, and contact/source.
37
+
38
+ ## Worktree Isolation Failure — No git Repo
39
+
40
+ When an Agent spawn fails with a worktree or git error (e.g., "Cannot create agent worktree: not in a git repository"), this is an **isolation constraint**, not a hiring failure. The correct response is:
41
+
42
+ 1. **Do not skip `harness-hiring`.** Run `harness-hiring` as normal to register the worker in `hr-roster.json`.
43
+ 2. **Do not replace hired workers with inline "You are a…" prompts.** That is impersonation, not hiring.
44
+ 3. **Spawn the hired worker as a plain `Agent()` call** (no `subagent_type`, no worktree) and set the prompt to read the worker's SKILL.md from `.harness/shared/HR-Resource/{worker-name}/SKILL.md` before executing the task.
45
+ 4. **Report the isolation constraint to the Owner** in the final summary: state that worktree isolation was unavailable and workers ran without isolation.
46
+
47
+ The worktree error only affects **isolation**. `harness-hiring`, `harness-resource-manager`, and `hr-roster.json` registration are independent of git and must always run.
@@ -1,200 +1,28 @@
1
1
  {
2
2
  "harness": {
3
3
  "name": "walwal-harness — NEXUS company harness",
4
- "version": "6.1.4",
5
- "description": "NEXUS-adapted company metaphor for AI-agent engineering. Owner ↔ Dispatcher(CEO) ↔ Conductor + Meeting-Manager ↔ Planner(COO/HR) + CTO + CQO + Service-Ops. Generators (BE/FE/Designer/DevOps) + Evaluators (Functional/Visual/Code-Quality/Architecture/Security) + COO Hypothesis Cell. Always-on company mode, parallel worker pool, parallel-tracks fork-join meetings, hierarchical Conventions/Gotchas. React/Next.js/Flutter FE, NestJS/Laravel BE.",
4
+ "version": "7.1.1",
5
+ "description": "v7 company harness. Owner uses /goal or /hot-fix, CEO coordinates CXX agents, CXX agents use resource-manager/hiring, and hired workers produce mission evidence under .harness/documents/{mission}/workers/.",
6
6
  "source": "https://www.anthropic.com/engineering/harness-design-long-running-apps"
7
7
  },
8
8
  "agents": {
9
- "dispatcher": {
10
- "role": "사용자 요청 분석 → 파이프라인 결정 (FE-ONLY / BE-ONLY / FULLSTACK)",
11
- "skill": "harness-dispatcher",
12
- "outputs": ["actions/pipeline.json"],
13
- "always_first": true,
14
- "model": "opus",
15
- "thinking_mode": null
16
- },
17
- "brainstorming": {
18
- "role": "사용자의 러프한 요구사항을 대화형으로 구체화하여 Planner가 바로 쓸 수 있는 brainstorm-spec.md로 변환.",
19
- "skill": "harness-brainstorming",
20
- "inputs": ["(user conversation)"],
21
- "outputs": ["actions/brainstorm-spec.md"],
22
- "conditional": true,
23
- "trigger_conditions": {
24
- "comment": "Dispatcher가 아래 조건 중 하나 이상 충족 시 사용자에게 브레인스토밍 제안",
25
- "rules": [
26
- "사용자 요청에 구체적 기능명/엔드포인트/화면명이 없고 추상적 목표만 있을 때",
27
- "PRD/OpenAPI 등 구조화된 입력 문서가 없을 때",
28
- "'뭔가 만들고 싶은데', '아이디어가 있는데' 등 탐색적 표현이 포함될 때"
29
- ],
30
- "skip_when": [
31
- "사용자가 PRD, OpenAPI spec, 또는 구체적 feature list를 제공한 경우",
32
- "사용자가 '브레인스토밍 없이', 'skip brainstorming'을 명시한 경우",
33
- "이미 brainstorm-spec.md가 actions/에 존재하는 경우"
34
- ]
35
- },
36
- "invoked_by": "dispatcher (user opt-in after suggestion)",
37
- "next_on_complete": "planner",
38
- "model": "opus",
39
- "thinking_mode": "plan",
40
- "thinking_mode_description": "Claude Code plan 모드로 실행. 구조화된 계획 수립 후 실행.",
41
- "attribution": "Derived from obra/superpowers skills/brainstorming (MIT License)"
42
- },
43
- "planner": {
44
- "role": "제품 사양 + API 계약서 + 서비스 분할 설계",
45
- "skill": "harness-planner",
46
- "inputs": ["actions/brainstorm-spec.md (optional)"],
47
- "outputs": [
48
- "actions/plan.md",
49
- "actions/feature-list.json",
50
- "actions/api-contract.json"
51
- ],
52
- "model": "opus",
53
- "thinking_mode": null,
54
- "thinking_mode_description": null
55
- },
56
- "generator-backend": {
57
- "role": "NestJS MSA 서비스 구현, API 엔드포인트, DB 스키마",
58
- "skill": "harness-generator-backend",
59
- "inputs": ["actions/plan.md", "actions/feature-list.json", "actions/api-contract.json", "actions/sprint-contract.md"],
60
- "outputs": ["code:backend", "actions/sprint-contract.md"],
61
- "order": 1,
62
- "model": "sonnet",
63
- "thinking_mode": null,
64
- "model_rationale": "코드 생성은 Sonnet이 비용 대비 효율적. 계획은 Planner가 이미 완료."
65
- },
66
- "generator-frontend": {
67
- "role": "React/Next.js UI 컴포넌트, 상태관리, API 연동",
68
- "skill": "harness-generator-frontend",
69
- "inputs": ["actions/plan.md", "actions/feature-list.json", "actions/api-contract.json", "actions/sprint-contract.md"],
70
- "outputs": ["code:frontend", "actions/sprint-contract.md"],
71
- "order": 2,
72
- "model": "sonnet",
73
- "thinking_mode": null,
74
- "model_rationale": "코드 생성은 Sonnet이 비용 대비 효율적. 계획은 Planner가 이미 완료."
75
- },
76
- "evaluator-code-quality": {
77
- "role": "코드 자체의 유지보수성/아키텍처/Best Practice 검사 (BE/FE/libs 공통, 브라우저 없음)",
78
- "skill": "harness-evaluator-code-quality",
79
- "tools": ["Read", "Grep", "Glob", "Bash(tsc,eslint,biome,lint 스크립트 한정)"],
80
- "inputs": ["actions/sprint-contract.md", "actions/feature-list.json", "actions/api-contract.json"],
81
- "outputs": ["actions/evaluation-code-quality.md"],
82
- "model": "opus",
83
- "thinking_mode": "ultrathink",
84
- "thinking_mode_description": "시니어 리뷰어 관점. 변경 파일 전수 정적 분석 + C1-C5 축 적대적 채점.",
85
- "adversarial_rules": {
86
- "comment": "Code-Quality Evaluator 적대적 행동 규칙 — rubber-stamping 금지",
87
- "rules": [
88
- "브라우저 없이 코드만으로 판정. '동작하니 PASS' 금지.",
89
- "변경 파일(git diff)은 전수 검사. '시간 제약' 핑계 금지.",
90
- "모든 Score 주장은 파일:라인 evidence 동반. Evidence 없는 Score = 0.",
91
- "toolchain(tsc/eslint) 에러 1건 이상이면 나머지 축 채점 전 즉시 FAIL.",
92
- "api-contract.json ↔ 실제 DTO/시그니처 불일치 1건 이상 = FAIL.",
93
- "레이어 역방향 의존 / MSA 직접 DB 접근 / FE view→fetch 직결 1건 이상 = C1 Score 0.",
94
- "unhandled promise 1건 이상 또는 비타당한 any 3건 이상 = C4 Score 0.",
95
- "PASS를 주기 전에 자문하라: '내가 이 코드를 머지 승인할 수 있는가?' NO이면 FAIL.",
96
- "반복 페널티: 같은 축에서 이전 스프린트 대비 악화 시 Score 최대 1."
97
- ],
98
- "forbidden": [
99
- "'동작은 Functional 에서 확인됐으니 OK' 식의 책임 전가",
100
- "'관용적 any', '사소한 중복' 같은 자기설득",
101
- "'제안사항만 남기고 Pass' — 필수 개선은 FAIL",
102
- "파일 수정/커밋 (이 평가자는 읽기 전용)"
103
- ]
104
- }
105
- },
106
- "evaluator-functional": {
107
- "role": "Playwright로 E2E 기능 검증, API 응답/DB 상태 확인",
108
- "skill": "harness-evaluator-functional",
109
- "tools": ["playwright:browser_*"],
110
- "inputs": ["actions/sprint-contract.md", "actions/feature-list.json", "actions/api-contract.json"],
111
- "outputs": ["actions/evaluation-functional.md"],
112
- "model": "opus",
113
- "thinking_mode": "ultrathink",
114
- "thinking_mode_description": "최고 수준의 사고 모드. 적대적 검증에 깊은 추론이 필요. 각 항목을 비판적으로 분석.",
115
- "evaluation_template": "assets/templates/evaluation-functional.md.template",
116
- "adversarial_rules": {
117
- "comment": "Evaluator 적대적 행동 규칙 — 통과 편향 제거를 위한 강제 규칙",
118
- "rules": [
119
- "Generator의 '구현 완료' 주장을 절대 신뢰하지 마라. 모든 엔드포인트에 직접 요청을 보내고 응답을 확인하라.",
120
- "정상 경로(happy path) 1개당 비정상 경로(unhappy path) 최소 2개를 반드시 테스트하라.",
121
- "api-contract.json과 실제 응답을 필드 단위로 비교하라. 누락 필드, 타입 불일치, 추가 필드 모두 FAIL 사유.",
122
- "PASS를 주기 전에 자문하라: '내가 이 코드를 직접 작성했다면 이 수준으로 PR을 올리겠는가?' NO이면 FAIL.",
123
- "AC 항목 하나라도 미통과이면 전체 FAIL. 부분 통과는 인정하지 않는다.",
124
- "Evidence(증거)가 없는 Score는 0점이다. '확인했다'는 주장은 증거가 아니다.",
125
- "이전 Sprint에서 PASS된 기능이 하나라도 깨졌으면 신규 기능 점수와 무관하게 FAIL.",
126
- "Score 3을 주려면 엣지케이스(빈 값, 초과값, 동시 요청, 특수문자)까지 테스트한 증거가 있어야 한다."
127
- ],
128
- "forbidden": [
129
- "'전반적으로 잘 구현되었습니다' 류의 모호한 긍정 평가",
130
- "'사소한 이슈이므로 PASS' — 사소함은 Evaluator가 판단할 사항이 아니다",
131
- "'시간 제약상 일부만 테스트' — 전수 테스트가 불가하면 FAIL 처리",
132
- "Generator에게 유리한 방향으로 기준을 해석하는 행위"
133
- ]
134
- }
135
- },
136
- "evaluator-visual": {
137
- "role": "디자인 일관성, 반응형, 접근성, AI슬롭 감지",
138
- "skill": "harness-evaluator-visual",
139
- "tools": ["playwright:browser_take_screenshot", "playwright:browser_resize", "playwright:browser_snapshot"],
140
- "inputs": ["actions/sprint-contract.md", "actions/evaluation-functional.md", "actions/feature-list.json"],
141
- "outputs": ["actions/evaluation-visual.md"],
142
- "model": "opus",
143
- "thinking_mode": "ultrathink",
144
- "thinking_mode_description": "최고 수준의 사고 모드. 시각적 결함, 접근성, AI슬롭을 비판적으로 탐지.",
145
- "evaluation_template": "assets/templates/evaluation-visual.md.template",
146
- "adversarial_rules": {
147
- "comment": "Visual Evaluator 적대적 행동 규칙",
148
- "rules": [
149
- "모든 페이지를 반드시 3개 뷰포트(375px, 768px, 1280px)에서 스크린샷 촬영하라. 1개라도 누락이면 해당 페이지 Score 산정 불가.",
150
- "axe-core 접근성 검사를 실제로 실행하라. Critical 또는 Serious 위반이 1건이라도 있으면 V3 = 0.",
151
- "AI슬롭 패턴(무의미한 그라데이션, 과도한 그림자, 장식 아이콘 남발)을 적극 탐지하라. 2건 이상이면 V4 최대 1점.",
152
- "evaluation-functional.md의 Cross-Validation Data와 교차 검증하라. API 성공인데 UI에 에러 표시 = 불일치 = FAIL 사유.",
153
- "이전 Sprint 스크린샷 대비 의도치 않은 시각 변경이 있으면 점수 무관 FAIL.",
154
- "PASS를 주기 전에 자문하라: '이 UI를 실사용자에게 보여줄 수 있는가?' NO이면 FAIL.",
155
- "로딩/에러/빈 상태가 하나라도 미구현이면 V5 최대 1점.",
156
- "Evidence(스크린샷 경로 또는 DOM 스냅샷)가 없는 Score는 0점이다."
157
- ],
158
- "forbidden": [
159
- "'디자인이 깔끔합니다' 류의 주관적 긍정 평가",
160
- "'접근성은 추후 개선' — 접근성은 기본 요건이지 부가 기능이 아니다",
161
- "'모바일 뷰포트는 생략' — 3개 뷰포트 전수 검사는 필수",
162
- "스크린샷 없이 '확인했다'고 주장하는 행위"
163
- ]
164
- }
165
- }
9
+ "ceo": { "role": "Owner-facing mission intake, CXX orchestration, and final reports", "skill": "harness-ceo", "model": "sonnet" },
10
+ "coo": { "role": "Planning, research, hypothesis, mission fit", "skill": "harness-coo", "model": "sonnet" },
11
+ "cdo": { "role": "Branding, UI/UX, design review", "skill": "harness-cdo", "model": "sonnet" },
12
+ "cto": { "role": "Architecture, platform, API, implementation coordination", "skill": "harness-cto", "model": "opus" },
13
+ "cqo": { "role": "Quality gates, regression, archive criteria", "skill": "harness-cqo", "model": "opus" },
14
+ "ops": { "role": "Build/service environment monitoring and exception logging", "skill": "harness-ops", "model": "sonnet" },
15
+ "hiring": { "role": "Hire missing workers from HR-Resource", "skill": "harness-hiring", "model": "sonnet" },
16
+ "resource-manager": { "role": "Resolve hired-worker availability and invocation wording", "skill": "harness-resource-manager", "model": "haiku" },
17
+ "brick-office": { "role": "Company status and dashboard support", "skill": "harness-brick-office", "model": "haiku" }
166
18
  },
167
19
  "flow": {
168
- "sequence": ["dispatcher", "planner", "generator-backend", "generator-frontend", "evaluator-code-quality", "evaluator-functional", "evaluator-visual"],
169
- "evaluator_chain": {
170
- "comment": "Generator 완료 후 실행되는 평가자 체인. 직렬 실행 + 조기 종료 — 앞단 FAIL 시 이후 평가자는 생략하고 Generator 로 리라우팅. 모두 PASS 여야 다음 스프린트로 이동.",
171
- "execution": "serial_with_early_exit",
172
- "order_rationale": "code-quality(정적, 저비용) → functional(동작, 중비용) → visual(렌더, 고비용). 구조가 깨진 코드에 동작 테스트를 낭비하지 않기 위함.",
173
- "on_any_fail": "reroute_to_generator",
174
- "on_all_pass": "advance_sprint"
175
- },
176
- "pipeline_selection": {
177
- "comment": "Dispatcher가 pipeline.json으로 활성 에이전트를 결정.",
178
- "pipelines": {
179
- "FULLSTACK": ["planner", "generator-backend", "generator-frontend", "evaluator-code-quality", "evaluator-functional", "evaluator-visual"],
180
- "FE-ONLY": ["planner:light", "generator-frontend", "evaluator-code-quality", "evaluator-functional", "evaluator-visual"],
181
- "BE-ONLY": ["planner", "generator-backend", "evaluator-code-quality", "evaluator-functional:api-only"]
182
- },
183
- "evaluator_chains": {
184
- "comment": "파이프라인별 평가자 체인. Dispatcher가 pipeline.json.evaluator_chain 으로 기록.",
185
- "FULLSTACK": ["evaluator-code-quality", "evaluator-functional", "evaluator-visual"],
186
- "FE-ONLY": ["evaluator-code-quality", "evaluator-functional", "evaluator-visual"],
187
- "BE-ONLY": ["evaluator-code-quality", "evaluator-functional"]
188
- }
189
- },
190
- "sprint_execution": {
191
- "comment": "Backend 먼저 → API 안정 후 Frontend 연동 → 기능 QA → 비주얼 QA (FULLSTACK 기준)",
192
- "backend_first": true,
193
- "reason": "프론트가 존재하지 않는 API를 호출하는 실패 방지"
194
- },
20
+ "sequence": ["ceo", "coo", "cdo", "cto", "cqo", "ops"],
21
+ "worker_gate": "CXX agents must use harness-resource-manager and harness-hiring before specialist deliverables.",
22
+ "worker_reports_required": ".harness/documents/{mission}/workers/{worker-name}.md",
195
23
  "retry_on_fail": true,
196
24
  "max_retries_per_sprint": 5,
197
- "escalate_to_planner_after": 3,
25
+ "escalate_to_ceo_after": 3,
198
26
  "archive_on_sprint_complete": true,
199
27
  "pre_eval_gate": {
200
28
  "comment": "Generator → Evaluator 전환 전 결정론적 검증. 실패 시 Evaluator 세션을 열지 않고 Generator로 자동 리라우팅.",
@@ -338,9 +166,9 @@
338
166
  }
339
167
  },
340
168
  "behavior": {
341
- "comment": "하네스 동작 플래그. UserPromptSubmit 훅이 이 값을 읽어 자동 라우팅을 결정한다.",
342
- "auto_route_dispatcher": true,
343
- "auto_route_dispatcher_description": "true 이면 사용자의 모든 프롬프트가 harness-dispatcher 경유로 분류/라우팅 된다. 사용자가 'harness skip' 등을 말하면 단일 메시지 한정으로 건너뛴다."
169
+ "comment": "하네스 동작 플래그. UserPromptSubmit 훅이 이 값을 읽어 v7 CEO/CXX 라우팅 안내를 결정한다.",
170
+ "auto_route_ceo": true,
171
+ "auto_route_ceo_description": "true 이면 /goal 또는 /hot-fix 이후 Owner 입력을 v7 CEO/CXX mission flow 기준으로 안내한다. 사용자가 'harness skip' 등을 말하면 단일 메시지 한정으로 건너뛴다."
344
172
  },
345
173
  "token_limit": {
346
174
  "comment": "모델 토큰 제한으로 작업이 중단됐을 때의 저비용 재개 정책. 별도 probe 호출 없이 시간 기반으로만 재개 알림을 계산한다.",
@@ -350,9 +178,9 @@
350
178
  "owner": "session-start"
351
179
  },
352
180
  "company_mode": {
353
- "comment": "v6.3 — 회사모드는 항시 활성이다. 사용자 모드 선택과 tie-breaker 는 제거됐다. Conductor 는 feature-list/queue 를 기반으로 매 tick 가능한 병렬 작업을 자동 배정한다.",
181
+ "comment": "v7 — 회사모드는 항시 활성이다. Owner entrypoint는 /goal, /hot-fix이며 CEO가 CXX와 hired worker 흐름을 조정한다.",
354
182
  "default_mode": "company",
355
- "owner": "conductor",
183
+ "owner": "ceo",
356
184
  "worker_concurrency": 3,
357
185
  "worker_spawn": "claude",
358
186
  "worker_spawn_description": "claude 이면 idle worker 배정 시 `claude -p` 백그라운드 프로세스를 띄운다. record 로 두면 prompt/log만 생성한다.",
@@ -2,10 +2,10 @@
2
2
  "version": 4,
3
3
  "mode": "company",
4
4
  "mode_decision": {
5
- "owner": "conductor",
5
+ "owner": "ceo",
6
6
  "decided_at": null,
7
- "rationale": "company mode is always-on",
8
- "policy": "always_company_parallel"
7
+ "rationale": "v7 company mode is CEO-led",
8
+ "policy": "owner_goal_to_ceo_cxx_workers"
9
9
  },
10
10
  "project_name": "",
11
11
  "pipeline": null,
@@ -21,7 +21,7 @@
21
21
  "current_agent": null,
22
22
  "agent_status": "pending",
23
23
  "completed_agents": [],
24
- "next_agent": "dispatcher",
24
+ "next_agent": "ceo",
25
25
  "failure": {
26
26
  "agent": null,
27
27
  "location": null,
package/bin/init.js CHANGED
@@ -165,6 +165,26 @@ const V7_REMOVED_GOTCHA_FILES = [
165
165
  'generator-backend-laravel.md',
166
166
  ];
167
167
 
168
+ const LEGACY_V7_FILE_OWNER = {
169
+ 'conductor.md': 'ceo.md',
170
+ 'dispatcher.md': 'ceo.md',
171
+ 'coo-developer.md': 'coo.md',
172
+ 'planner.md': 'coo.md',
173
+ 'documentationer.md': 'coo.md',
174
+ 'generator-frontend.md': 'cto.md',
175
+ 'generator-backend.md': 'cto.md',
176
+ 'generator-backend-laravel.md': 'cto.md',
177
+ 'generator-devops.md': 'cto.md',
178
+ 'evaluator-architecture.md': 'cto.md',
179
+ 'generator-designer.md': 'cdo.md',
180
+ 'evaluator-visual.md': 'cdo.md',
181
+ 'evaluator-functional.md': 'cqo.md',
182
+ 'evaluator-code-quality.md': 'cqo.md',
183
+ 'evaluator-security.md': 'cqo.md',
184
+ 'meeting-manager.md': 'ops.md',
185
+ 'service-ops.md': 'ops.md',
186
+ };
187
+
168
188
  function removeLegacyV7Files(dir, files) {
169
189
  for (const file of files) {
170
190
  const target = path.join(dir, file);
@@ -174,6 +194,65 @@ function removeLegacyV7Files(dir, files) {
174
194
  }
175
195
  }
176
196
 
197
+ function extractMarkdownEntryBlocks(md, prefix) {
198
+ const escapedPrefix = prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
199
+ const re = new RegExp(
200
+ `^#{2,3}\\s+\\[(${escapedPrefix}-[A-Z0-9_-]+)\\][\\s\\S]*?(?=^#{2,3}\\s+\\[${escapedPrefix}-|$(?![\\s\\S]))`,
201
+ 'gm',
202
+ );
203
+ const blocks = [];
204
+ let match;
205
+ while ((match = re.exec(md)) !== null) {
206
+ blocks.push({ id: match[1], body: match[0].trimEnd() });
207
+ }
208
+ return blocks;
209
+ }
210
+
211
+ function migrateLegacyRoleEntries(dir, files, kind) {
212
+ const prefix = kind === 'gotchas' ? 'G' : 'C';
213
+ const migrated = [];
214
+ const backupDir = path.join(HARNESS_DIR, 'archive', 'pre-v7-role-migration');
215
+ for (const file of files) {
216
+ const sourcePath = path.join(dir, file);
217
+ if (!fs.existsSync(sourcePath)) continue;
218
+
219
+ const content = fs.readFileSync(sourcePath, 'utf8');
220
+ const blocks = extractMarkdownEntryBlocks(content, prefix);
221
+ if (!blocks.length) continue;
222
+
223
+ const targetFile = LEGACY_V7_FILE_OWNER[file] || 'shared.md';
224
+ const targetPath = path.join(dir, targetFile);
225
+ const targetOriginal = fs.existsSync(targetPath) ? fs.readFileSync(targetPath, 'utf8') : '';
226
+ const targetIds = new Set(
227
+ kind === 'gotchas'
228
+ ? extractGotchaEntryIds(targetOriginal)
229
+ : extractConventionEntryIds(targetOriginal),
230
+ );
231
+ const appendBlocks = [];
232
+ for (const block of blocks) {
233
+ if (targetIds.has(block.id)) continue;
234
+ appendBlocks.push([
235
+ block.body,
236
+ '',
237
+ `- **Migration source**: ${kind}/${file}`,
238
+ ].join('\n'));
239
+ targetIds.add(block.id);
240
+ }
241
+ if (!appendBlocks.length) continue;
242
+
243
+ ensureDir(backupDir);
244
+ fs.writeFileSync(path.join(backupDir, `${kind}-${file}`), content);
245
+ const sep = targetOriginal.trim() ? '\n\n' : '';
246
+ fs.writeFileSync(targetPath, targetOriginal.replace(/\s*$/, '') + sep + appendBlocks.join('\n\n') + '\n');
247
+ migrated.push(`${file} → ${targetFile} (${appendBlocks.length})`);
248
+ }
249
+
250
+ if (migrated.length) {
251
+ log(`${kind} legacy role entries migrated: ${migrated.join(', ')}`);
252
+ log(`Legacy role migration backup: ${backupDir}`);
253
+ }
254
+ }
255
+
177
256
  // ─────────────────────────────────────────
178
257
  // First-install migration — extract Convention/Gotcha-shaped sections from
179
258
  // existing CLAUDE.md / AGENTS.md into .harness/conventions and .harness/gotchas.
@@ -337,6 +416,9 @@ function scaffoldHarness() {
337
416
  ensureDir(path.join(HARNESS_DIR, 'memories'));
338
417
  ensureDir(path.join(HARNESS_DIR, 'shared'));
339
418
 
419
+ migrateLegacyRoleEntries(path.join(HARNESS_DIR, 'conventions'), V7_REMOVED_CONVENTION_FILES, 'conventions');
420
+ migrateLegacyRoleEntries(path.join(HARNESS_DIR, 'gotchas'), V7_REMOVED_GOTCHA_FILES, 'gotchas');
421
+
340
422
  removeLegacyV7Files(path.join(HARNESS_DIR, 'conventions'), V7_REMOVED_CONVENTION_FILES);
341
423
  removeLegacyV7Files(path.join(HARNESS_DIR, 'gotchas'), V7_REMOVED_GOTCHA_FILES);
342
424
 
@@ -362,7 +444,7 @@ function scaffoldHarness() {
362
444
  // a file that has such entries, or user learning history is lost.
363
445
  const gotchasSrc = path.join(PKG_ROOT, 'gotchas');
364
446
  if (fs.existsSync(gotchasSrc)) {
365
- const ENTRY_PATTERN = /^### \[G-\d+\]/m; // Gotcha entry heading
447
+ const ENTRY_PATTERN = /^### \[G-[A-Z0-9_-]+\]/m; // Gotcha entry heading
366
448
  const CUSTOM_MARKER = '## Custom Gotchas'; // Legacy marker still supported
367
449
  const files = fs.readdirSync(gotchasSrc);
368
450
  for (const file of files) {
@@ -777,10 +859,10 @@ function installStatusline() {
777
859
  }
778
860
 
779
861
  // ─────────────────────────────────────────
780
- // 3d. UserPromptSubmit hook (auto dispatcher routing)
862
+ // 3d. UserPromptSubmit hook (v7 CEO routing guidance)
781
863
  // ─────────────────────────────────────────
782
864
  function installUserPromptSubmitHook() {
783
- log('Installing UserPromptSubmit hook (auto dispatcher routing)...');
865
+ log('Installing UserPromptSubmit hook (v7 CEO routing guidance)...');
784
866
 
785
867
  const settingsDir = path.join(PROJECT_ROOT, '.claude');
786
868
 
@@ -814,9 +896,9 @@ function installUserPromptSubmitHook() {
814
896
  });
815
897
  writeClaudeSettings(settings);
816
898
  log('UserPromptSubmit hook installed in .claude/settings.json');
817
- log(' → All prompts will be routed through harness-dispatcher');
899
+ log(' → /goal and /hot-fix are guided through harness-ceo and CXX workflow');
818
900
  log(' → Opt-out per message: say "harness skip" or "without harness"');
819
- log(' → Disable globally: set .harness/config.json behavior.auto_route_dispatcher = false');
901
+ log(' → Disable globally: set .harness/config.json behavior.auto_route_ceo = false');
820
902
  } else {
821
903
  log('UserPromptSubmit hook already installed');
822
904
  }
@@ -1103,7 +1185,9 @@ function detectMigrationNeeded() {
1103
1185
  const memoryTplPath = path.join(PKG_ROOT, 'assets', 'templates', 'memory.md');
1104
1186
  const flags = {
1105
1187
  progressV3toV4: false,
1188
+ progressLegacyRouting: false,
1106
1189
  configMissingCompanyMode: false,
1190
+ configLegacyRouting: false,
1107
1191
  memoryMissingSystemEntries: [],
1108
1192
  gotchaMissingEntries: {}, // { "<filename>": [G-IDs...] }
1109
1193
  conventionMissingEntries: {}, // { "<filename>": [C-IDs...] }
@@ -1164,12 +1248,22 @@ function detectMigrationNeeded() {
1164
1248
  try {
1165
1249
  const p = JSON.parse(fs.readFileSync(progressPath, 'utf8'));
1166
1250
  if ((p.version ?? 0) < TARGET_PROGRESS_VERSION) flags.progressV3toV4 = true;
1251
+ if (
1252
+ ['dispatcher', 'planner', 'generator-backend', 'generator-frontend', 'evaluator-functional', 'evaluator-visual', 'service-ops'].includes(p.next_agent) ||
1253
+ p.mode_decision?.owner === 'conductor'
1254
+ ) flags.progressLegacyRouting = true;
1167
1255
  } catch {}
1168
1256
  }
1169
1257
  if (fs.existsSync(configPath)) {
1170
1258
  try {
1171
1259
  const c = JSON.parse(fs.readFileSync(configPath, 'utf8'));
1172
1260
  if (!c.company_mode || c.mode_selection) flags.configMissingCompanyMode = true;
1261
+ if (
1262
+ c.behavior?.auto_route_dispatcher !== undefined ||
1263
+ c.behavior?.auto_route_dispatcher_description ||
1264
+ c.company_mode?.owner === 'conductor' ||
1265
+ c.agents?.dispatcher?.skill === 'harness-dispatcher'
1266
+ ) flags.configLegacyRouting = true;
1173
1267
  } catch {}
1174
1268
  }
1175
1269
  if (fs.existsSync(memoryPath) && fs.existsSync(memoryTplPath)) {
@@ -1245,15 +1339,21 @@ function extractConventionEntryBlock(md, id) {
1245
1339
  function showMigrationProposal(flags) {
1246
1340
  console.log('');
1247
1341
  console.log('╔══════════════════════════════════════════════════════════╗');
1248
- console.log('║ walwal-harness v6 — 자동 마이그레이션 사용 가능 ║');
1342
+ console.log('║ walwal-harness v7 — 자동 마이그레이션 사용 가능 ║');
1249
1343
  console.log('╚══════════════════════════════════════════════════════════╝');
1250
1344
  if (flags.progressV3toV4) {
1251
1345
  console.log(' • progress.json: legacy mode → "company"');
1252
1346
  console.log(' legacy mode 값은 보존하지 않고 회사모드로 정규화됩니다.');
1253
1347
  }
1348
+ if (flags.progressLegacyRouting) {
1349
+ console.log(' • progress.json: legacy dispatcher/conductor routing → v7 CEO routing');
1350
+ }
1254
1351
  if (flags.configMissingCompanyMode) {
1255
1352
  console.log(' • config.json: company_mode 섹션 동기화 가능');
1256
- console.log(' 사용자 모드 선택 없이 회사모드 병렬 실행으로 고정합니다.');
1353
+ console.log(' Owner /goal, /hot-fix → CEO/CXX/worker 흐름으로 고정합니다.');
1354
+ }
1355
+ if (flags.configLegacyRouting) {
1356
+ console.log(' • config.json: legacy dispatcher/conductor wording → v7 CEO/CXX wording');
1257
1357
  }
1258
1358
  if (flags.memoryMissingSystemEntries && flags.memoryMissingSystemEntries.length) {
1259
1359
  console.log(' • memory.md: 시스템 entry 누락 — append 가능');
@@ -1291,17 +1391,21 @@ function runMigrate(opts = {}) {
1291
1391
  const dryRun = opts.dryRun || false;
1292
1392
  const flags = detectMigrationNeeded();
1293
1393
  const gotchaMissingTotal = Object.values(flags.gotchaMissingEntries || {}).reduce((n, a) => n + a.length, 0);
1394
+ const conventionMissingTotal = Object.values(flags.conventionMissingEntries || {}).reduce((n, a) => n + a.length, 0);
1294
1395
  if (
1295
1396
  !flags.progressV3toV4 &&
1397
+ !flags.progressLegacyRouting &&
1296
1398
  !flags.configMissingCompanyMode &&
1399
+ !flags.configLegacyRouting &&
1297
1400
  (!flags.memoryMissingSystemEntries || flags.memoryMissingSystemEntries.length === 0) &&
1298
1401
  gotchaMissingTotal === 0 &&
1402
+ conventionMissingTotal === 0 &&
1299
1403
  !flags.bundleVersionStale
1300
1404
  ) {
1301
1405
  console.log('');
1302
1406
  let pkgVer = 'unknown';
1303
1407
  try { pkgVer = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8')).version; } catch {}
1304
- log(`이미 최신 — bundle v${pkgVer} 일치, progress v${TARGET_PROGRESS_VERSION}, config.company_mode, memory 시스템 entry, gotcha entry 모두 sync.`);
1408
+ log(`이미 최신 — bundle v${pkgVer} 일치, progress v${TARGET_PROGRESS_VERSION} v7 routing, config.company_mode, memory 시스템 entry, gotcha entry 모두 sync.`);
1305
1409
  return;
1306
1410
  }
1307
1411
 
@@ -1314,7 +1418,7 @@ function runMigrate(opts = {}) {
1314
1418
 
1315
1419
  // 1. progress.json
1316
1420
  const progressPath = path.join(HARNESS_DIR, 'progress.json');
1317
- if (flags.progressV3toV4 && fs.existsSync(progressPath)) {
1421
+ if ((flags.progressV3toV4 || flags.progressLegacyRouting) && fs.existsSync(progressPath)) {
1318
1422
  const original = fs.readFileSync(progressPath, 'utf8');
1319
1423
  const p = JSON.parse(original);
1320
1424
  const oldMode = p.mode ?? 'company';
@@ -1323,14 +1427,17 @@ function runMigrate(opts = {}) {
1323
1427
  version: TARGET_PROGRESS_VERSION,
1324
1428
  mode: 'company',
1325
1429
  mode_decision: {
1326
- owner: 'conductor',
1430
+ owner: 'ceo',
1327
1431
  decided_at: null,
1328
- rationale: `migration — legacy mode="${oldMode}" normalized to company`,
1329
- policy: 'always_company_parallel',
1432
+ rationale: `migration — legacy mode="${oldMode}" normalized to v7 CEO-led company`,
1433
+ policy: 'owner_goal_to_ceo_cxx_workers',
1330
1434
  user_override: null,
1331
1435
  },
1436
+ next_agent: ['dispatcher', 'planner', 'generator-backend', 'generator-frontend', 'evaluator-functional', 'evaluator-visual', 'service-ops'].includes(p.next_agent)
1437
+ ? 'ceo'
1438
+ : (p.next_agent ?? 'ceo'),
1332
1439
  };
1333
- log(` progress.json: version 3 → ${TARGET_PROGRESS_VERSION}, mode "${oldMode}" → "company"`);
1440
+ log(` progress.json: v7 routing normalized, mode "${oldMode}" → "company", next_agent="${p.next_agent ?? 'null'}" → "${newP.next_agent}"`);
1334
1441
  if (!dryRun) {
1335
1442
  fs.writeFileSync(path.join(backupDir, 'progress.json'), original);
1336
1443
  fs.writeFileSync(progressPath, JSON.stringify(newP, null, 2) + '\n');
@@ -1340,14 +1447,26 @@ function runMigrate(opts = {}) {
1340
1447
  // 2. config.json — inject company_mode from template if missing
1341
1448
  const configPath = path.join(HARNESS_DIR, 'config.json');
1342
1449
  const tplPath = path.join(PKG_ROOT, 'assets', 'templates', 'config.json');
1343
- if (flags.configMissingCompanyMode && fs.existsSync(configPath) && fs.existsSync(tplPath)) {
1450
+ if ((flags.configMissingCompanyMode || flags.configLegacyRouting) && fs.existsSync(configPath) && fs.existsSync(tplPath)) {
1344
1451
  const original = fs.readFileSync(configPath, 'utf8');
1345
1452
  const c = JSON.parse(original);
1346
1453
  const tpl = JSON.parse(fs.readFileSync(tplPath, 'utf8'));
1347
1454
  if (tpl.company_mode) {
1348
- c.company_mode = tpl.company_mode;
1455
+ c.company_mode = {
1456
+ ...tpl.company_mode,
1457
+ comment: 'v7 — 회사모드는 CEO/CXX/worker 문서 흐름으로 운영된다.',
1458
+ owner: 'ceo',
1459
+ };
1460
+ c.harness = tpl.harness || c.harness;
1461
+ c.agents = tpl.agents || c.agents;
1462
+ c.flow = tpl.flow || c.flow;
1463
+ c.behavior = c.behavior || {};
1464
+ c.behavior.auto_route_ceo = c.behavior.auto_route_ceo ?? c.behavior.auto_route_dispatcher ?? true;
1465
+ c.behavior.auto_route_ceo_description = 'true 이면 /goal 또는 /hot-fix 이후 Owner 입력을 v7 CEO/CXX mission flow 기준으로 안내한다.';
1466
+ delete c.behavior.auto_route_dispatcher;
1467
+ delete c.behavior.auto_route_dispatcher_description;
1349
1468
  delete c.mode_selection;
1350
- log(' config.json: company_mode 섹션 주입 (always-on company parallel)');
1469
+ log(' config.json: v7 CEO/CXX routing 섹션 동기화');
1351
1470
  if (!dryRun) {
1352
1471
  fs.writeFileSync(path.join(backupDir, 'config.json'), original);
1353
1472
  fs.writeFileSync(configPath, JSON.stringify(c, null, 2) + '\n');
@@ -1440,60 +1559,90 @@ function runMigrate(opts = {}) {
1440
1559
  log('Dry-run 완료 — 실제 변경 적용하려면 `npx walwal-harness migrate` 실행');
1441
1560
  } else {
1442
1561
  log(`Migration 완료. 백업: ${backupDir}`);
1443
- log('Conductor 가 다음 sprint 시작 시 자동으로 모드 결정합니다.');
1562
+ log('v7 routing 기준으로 /goal 또는 /hot-fix 가 CEO/CXX/worker 흐름을 시작합니다.');
1444
1563
  log('회사모드는 기본값이며 사용자 override 는 사용하지 않습니다.');
1445
1564
  }
1446
1565
  console.log('');
1447
1566
  }
1448
1567
 
1449
1568
  // ─────────────────────────────────────────
1450
- // Verify — 14 SKILL invariants + spawn whitelist + progress schema
1569
+ // Verify — v7 CXX/core skill invariants + progress schema
1451
1570
  // ─────────────────────────────────────────
1452
1571
  function runVerify() {
1453
1572
  const expectedSkills = [
1454
- 'dispatcher', 'conductor', 'meeting-manager', 'planner',
1455
- 'cto', 'cqo', 'service-ops',
1456
- 'generator-backend', 'generator-frontend', 'generator-designer', 'generator-devops',
1457
- 'evaluator-code-quality', 'evaluator-functional', 'evaluator-visual',
1458
- 'evaluator-architecture', 'evaluator-security',
1459
- 'coo-developer', 'documentationer',
1460
- 'brainstorming',
1573
+ 'ceo', 'coo', 'cdo', 'cto', 'cqo', 'ops',
1574
+ 'hiring', 'resource-manager', 'brick-office',
1461
1575
  ];
1462
1576
  const requiredFrontmatter = ['name', 'description'];
1463
1577
 
1464
1578
  console.log('');
1465
- log('=== Verify: skill invariants + spawn whitelist + progress schema ===');
1579
+ log('=== Verify: v7 command/skill invariants + progress schema ===');
1466
1580
  let pass = 0;
1467
1581
  let fail = 0;
1468
1582
  const issues = [];
1469
1583
 
1470
1584
  // 1) skill files
1585
+ const skillRoots = [
1586
+ ['.claude', CLAUDE_SKILLS_DIR],
1587
+ ['.codex', CODEX_SKILLS_DIR],
1588
+ ];
1471
1589
  for (const s of expectedSkills) {
1472
- const local = path.join(CLAUDE_SKILLS_DIR, `harness-${s}`, 'SKILL.md');
1473
- const exists = fs.existsSync(local);
1474
- if (!exists) {
1475
- issues.push(` ✗ skills/harness-${s}/SKILL.md MISSING`);
1476
- fail++;
1477
- continue;
1590
+ for (const [toolName, root] of skillRoots) {
1591
+ const local = path.join(root, `harness-${s}`, 'SKILL.md');
1592
+ const exists = fs.existsSync(local);
1593
+ if (!exists) {
1594
+ issues.push(` ✗ ${toolName}/skills/harness-${s}/SKILL.md MISSING`);
1595
+ fail++;
1596
+ continue;
1597
+ }
1598
+ const body = fs.readFileSync(local, 'utf8');
1599
+ const fmMatch = body.match(/^---\n([\s\S]*?)\n---/);
1600
+ if (!fmMatch) {
1601
+ issues.push(` ✗ ${toolName}/skills/harness-${s}: frontmatter 누락`);
1602
+ fail++;
1603
+ continue;
1604
+ }
1605
+ const missing = requiredFrontmatter.filter((k) => !new RegExp(`^${k}:`, 'm').test(fmMatch[1]));
1606
+ if (missing.length) {
1607
+ issues.push(` ✗ ${toolName}/skills/harness-${s}: frontmatter [${missing.join(',')}] 누락`);
1608
+ fail++;
1609
+ continue;
1610
+ }
1611
+ pass++;
1478
1612
  }
1479
- const body = fs.readFileSync(local, 'utf8');
1480
- const fmMatch = body.match(/^---\n([\s\S]*?)\n---/);
1481
- if (!fmMatch) {
1482
- issues.push(` ✗ harness-${s}: frontmatter 누락`);
1483
- fail++;
1484
- continue;
1613
+ }
1614
+ log(` skills: ${pass}/${expectedSkills.length * skillRoots.length} OK`);
1615
+
1616
+ // 2) owner-facing commands
1617
+ const allowedCommands = new Set(['goal.md', 'hot-fix.md']);
1618
+ const forbiddenCommands = new Set(['ceo.md', 'coo.md', 'cdo.md', 'cto.md', 'cqo.md', 'ops.md', 'hiring.md', 'resource-manager.md', 'brick-office.md']);
1619
+ const commandRoots = [
1620
+ ['.claude', CLAUDE_COMMANDS_DIR],
1621
+ ['.codex', CODEX_COMMANDS_DIR],
1622
+ ];
1623
+ for (const [toolName, root] of commandRoots) {
1624
+ for (const cmd of allowedCommands) {
1625
+ if (!fs.existsSync(path.join(root, cmd))) {
1626
+ issues.push(` ✗ ${toolName}/commands/${cmd} MISSING`);
1627
+ fail++;
1628
+ } else {
1629
+ pass++;
1630
+ }
1485
1631
  }
1486
- const missing = requiredFrontmatter.filter((k) => !new RegExp(`^${k}:`, 'm').test(fmMatch[1]));
1487
- if (missing.length) {
1488
- issues.push(` ✗ harness-${s}: frontmatter [${missing.join(',')}] 누락`);
1489
- fail++;
1490
- continue;
1632
+ if (fs.existsSync(root)) {
1633
+ for (const cmd of fs.readdirSync(root).filter(f => f.endsWith('.md'))) {
1634
+ if (forbiddenCommands.has(cmd)) {
1635
+ issues.push(` ✗ ${toolName}/commands/${cmd} forbidden in v7`);
1636
+ fail++;
1637
+ } else if (!allowedCommands.has(cmd)) {
1638
+ issues.push(` ⚠ ${toolName}/commands/${cmd} is outside walwal v7 owner-facing command set`);
1639
+ }
1640
+ }
1491
1641
  }
1492
- pass++;
1493
1642
  }
1494
- log(` skills: ${pass}/${expectedSkills.length} OK`);
1643
+ log(` commands: /goal + /hot-fix checked`);
1495
1644
 
1496
- // 2) progress.json schema (v6 = version 4 + mode_decision)
1645
+ // 3) progress.json schema (v6+ = version 4 + mode_decision)
1497
1646
  const progressPath = path.join(HARNESS_DIR, 'progress.json');
1498
1647
  if (fs.existsSync(progressPath)) {
1499
1648
  try {
@@ -1502,6 +1651,10 @@ function runVerify() {
1502
1651
  if ((p.version ?? 0) < 4) schemaIssues.push(`version=${p.version} (<4)`);
1503
1652
  if (!p.mode_decision) schemaIssues.push('mode_decision 누락');
1504
1653
  if (!p.dispatch) schemaIssues.push('dispatch 누락');
1654
+ if (p.mode_decision?.owner === 'conductor') schemaIssues.push('mode_decision.owner=conductor legacy');
1655
+ if (['dispatcher', 'planner', 'generator-backend', 'generator-frontend', 'evaluator-functional', 'evaluator-visual', 'service-ops'].includes(p.next_agent)) {
1656
+ schemaIssues.push(`next_agent=${p.next_agent} legacy`);
1657
+ }
1505
1658
  if (schemaIssues.length) {
1506
1659
  issues.push(` ✗ progress.json schema: ${schemaIssues.join(', ')} → npx walwal-harness migrate`);
1507
1660
  fail++;
@@ -1523,6 +1676,13 @@ function runVerify() {
1523
1676
  if (!c.company_mode) {
1524
1677
  issues.push(' ✗ config.json: company_mode 누락 → npx walwal-harness migrate');
1525
1678
  fail++;
1679
+ } else if (
1680
+ c.company_mode.owner === 'conductor' ||
1681
+ c.behavior?.auto_route_dispatcher !== undefined ||
1682
+ c.agents?.dispatcher?.skill === 'harness-dispatcher'
1683
+ ) {
1684
+ issues.push(' ✗ config.json: legacy dispatcher/conductor routing → npx walwal-harness migrate');
1685
+ fail++;
1526
1686
  } else {
1527
1687
  pass++;
1528
1688
  log(` config.json: company_mode.owner=${c.company_mode.owner} OK`);
@@ -1552,6 +1712,20 @@ function runVerify() {
1552
1712
  issues.push(' ⚠ .claude/commands/harness-next.md 존재 — v6.0.3 부터 회사 내부 도구로 전환됨. `npx walwal-harness --force` 또는 직접 삭제 권장');
1553
1713
  }
1554
1714
 
1715
+ // 6) installed script guidance must not claim removed dispatcher/planner flow.
1716
+ const routeScript = path.join(PROJECT_ROOT, 'scripts', 'harness-user-prompt-submit.sh');
1717
+ const sessionScript = path.join(PROJECT_ROOT, 'scripts', 'harness-session-start.sh');
1718
+ for (const scriptPath of [routeScript, sessionScript]) {
1719
+ if (!fs.existsSync(scriptPath)) continue;
1720
+ const body = fs.readFileSync(scriptPath, 'utf8');
1721
+ if (/harness-dispatcher|pipeline=none\/dispatcher|dispatch -> CEO meeting|planner\(COO\)|gen\/eval/.test(body)) {
1722
+ issues.push(` ✗ ${path.relative(PROJECT_ROOT, scriptPath)}: legacy dispatcher/planner guidance → npx walwal-harness init migrate --force`);
1723
+ fail++;
1724
+ } else {
1725
+ pass++;
1726
+ }
1727
+ }
1728
+
1555
1729
  console.log('');
1556
1730
  if (fail === 0) {
1557
1731
  log(`✓ Verify PASS — ${pass} 개 invariant 통과.`);
@@ -1618,7 +1792,9 @@ function main() {
1618
1792
  const gotchaMissing = Object.keys(migFlags.gotchaMissingEntries || {}).length > 0;
1619
1793
  const hasContentDrift =
1620
1794
  migFlags.progressV3toV4 ||
1795
+ migFlags.progressLegacyRouting ||
1621
1796
  migFlags.configMissingCompanyMode ||
1797
+ migFlags.configLegacyRouting ||
1622
1798
  (migFlags.memoryMissingSystemEntries && migFlags.memoryMissingSystemEntries.length) ||
1623
1799
  gotchaMissing;
1624
1800
  if (hasContentDrift) {
@@ -1631,7 +1807,7 @@ function main() {
1631
1807
  } catch {}
1632
1808
  }
1633
1809
 
1634
- // v6.0 — Brick Office dashboard one-liner
1810
+ // Brick Office dashboard one-liner
1635
1811
  console.log('');
1636
1812
  console.log('╔══════════════════════════════════════════════════════════╗');
1637
1813
  console.log('║ Brick Office — 라이브 운영 대시보드 (선택) ║');
@@ -1648,8 +1824,7 @@ function main() {
1648
1824
  log('╔═══════════════════════════════════════════════════════════╗');
1649
1825
  log('║ Restart Claude Code for skills & commands to activate! ║');
1650
1826
  log('║ ║');
1651
- log('║ Then say: "하네스 엔지니어링 시작" ║');
1652
- log('║ Or invoke: /harness-dispatcher ║');
1827
+ log('║ Then invoke /goal or /hot-fix ║');
1653
1828
  log('║ ║');
1654
1829
  log('║ 기본은 회사모드: 병렬 · 자율 진행. 추가 입력 불필요. ║');
1655
1830
  log('╚═══════════════════════════════════════════════════════════╝');
@@ -1660,6 +1835,10 @@ function main() {
1660
1835
  log(' 3. Internal agents call hired agents/skills, not slash commands.');
1661
1836
  }
1662
1837
  console.log('');
1838
+
1839
+ if (subcommand === 'init' && subcommandArgs.includes('migrate')) {
1840
+ runMigrate({ dryRun: args.includes('--dry-run') });
1841
+ }
1663
1842
  }
1664
1843
 
1665
1844
  main();
package/init.sh ADDED
@@ -0,0 +1,208 @@
1
+ #!/bin/bash
2
+ # Harness Init Script — 적응형(adaptive) 하네스 진입점 허브
3
+ #
4
+ # 서브커맨드:
5
+ # help — 사용법 출력
6
+ # check (default · 무인자) — 환경 확인 + scan + ref 상태 출력 (기존 동작)
7
+ # init [project_root] — 신규 프로젝트 초기화 (scan → ref placeholder → AGENTS.md → migration preview)
8
+ # migrate [project_root] — v7.1 마이그레이션 적용
9
+ # refresh-ref <role> <stack> — 기존 ref 파일 archive 백업 후 재생성 유도
10
+ #
11
+ # 스택별 서버 기동은 scan-result.json.tech_stack 과 .harness/ref/<role>-<stack>.md.runner 에서
12
+ # 가져온다. NestJS/React 하드코딩은 제거되었다.
13
+
14
+ set -e
15
+
16
+ SUBCOMMAND="${1:-check}"
17
+ shift || true
18
+
19
+ usage() {
20
+ cat <<'USAGE'
21
+ Harness Init — 적응형 하네스 진입점
22
+
23
+ Usage:
24
+ bash init.sh help — 이 메시지
25
+ bash init.sh check [project_root] — 환경 확인 + scan + ref 상태 (기본)
26
+ bash init.sh init [project_root] — scan + ref placeholder 생성 + AGENTS.md + migration dry-run
27
+ bash init.sh init [project_root] --apply-migration
28
+ — init 후 v7.1 마이그레이션까지 적용
29
+ bash init.sh migrate [project_root] — v7.1 마이그레이션 적용
30
+ bash init.sh migrate [project_root] --dry-run
31
+ — v7.1 마이그레이션 미리보기
32
+ bash init.sh refresh-ref <role> <stack> [path] — 기존 ref 파일 백업 후 재생성 안내
33
+
34
+ 스택 독립: 개발 서버 기동 명령은 .harness/ref/<role>-<stack>.md 의 runner.dev_command 에서 조회한다.
35
+ USAGE
36
+ }
37
+
38
+ run_walwal_cli() {
39
+ local project_root="$1"
40
+ shift || true
41
+ local script_dir
42
+ script_dir="$(cd "$(dirname "$0")" && pwd)"
43
+ local cli_path="${script_dir}/bin/init.js"
44
+
45
+ if [ -f "$cli_path" ]; then
46
+ (cd "$project_root" && node "$cli_path" "$@")
47
+ else
48
+ (cd "$project_root" && npx walwal-harness "$@")
49
+ fi
50
+ }
51
+
52
+ run_migrate() {
53
+ local project_root="."
54
+ local mode=""
55
+
56
+ if [ "${1:-}" = "--dry-run" ]; then
57
+ mode="--dry-run"
58
+ else
59
+ project_root="${1:-.}"
60
+ mode="${2:-}"
61
+ fi
62
+
63
+ echo "=== Harness Migration: ${project_root} ==="
64
+ if [ "$mode" = "--dry-run" ]; then
65
+ run_walwal_cli "$project_root" migrate --dry-run
66
+ else
67
+ run_walwal_cli "$project_root" migrate
68
+ fi
69
+ }
70
+
71
+ run_check() {
72
+ local project_root="${1:-.}"
73
+ echo "=== Harness Environment Check ==="
74
+ echo "Working Directory: $(pwd)"
75
+ echo "Date: $(date)"
76
+ echo ""
77
+
78
+ # Git 상태
79
+ echo "--- Git Status ---"
80
+ if [ -d "${project_root}/.git" ]; then
81
+ git -C "$project_root" log --oneline -5 2>/dev/null || echo "No commits yet"
82
+ echo ""
83
+ git -C "$project_root" status --short
84
+ else
85
+ echo "Not a git repository — run: git init"
86
+ fi
87
+ echo ""
88
+
89
+ # Harness 상태
90
+ echo "--- Harness Status ---"
91
+ if [ -f "${project_root}/.harness/progress.json" ]; then
92
+ if [ -f "${project_root}/scripts/lib/harness-render-progress.sh" ]; then
93
+ (cd "$project_root" && source scripts/lib/harness-render-progress.sh && render_progress "." 2>/dev/null) || cat "${project_root}/.harness/progress.json"
94
+ else
95
+ cat "${project_root}/.harness/progress.json"
96
+ fi
97
+ else
98
+ echo "progress.json not found — run: bash init.sh init"
99
+ fi
100
+ echo ""
101
+
102
+ # scan 상태
103
+ echo "--- Scan Result ---"
104
+ if [ -f "${project_root}/.harness/actions/scan-result.json" ]; then
105
+ jq '{tech_stack, tech_stack_confidence, recommendation}' "${project_root}/.harness/actions/scan-result.json"
106
+ else
107
+ echo "scan-result.json not found — run: bash scripts/scan-project.sh ${project_root}"
108
+ fi
109
+ echo ""
110
+
111
+ # ref 상태
112
+ echo "--- Ref Docs ---"
113
+ if [ -d "${project_root}/.harness/ref" ]; then
114
+ ls -1 "${project_root}/.harness/ref/" 2>/dev/null | grep -v '^\.generated' | sed 's/^/ /' || echo " (none)"
115
+ else
116
+ echo " .harness/ref/ not present — run: bash init.sh init"
117
+ fi
118
+ echo ""
119
+
120
+ echo "=== Harness Ready ==="
121
+ }
122
+
123
+ run_init() {
124
+ local project_root="."
125
+ local migration_mode="--dry-run"
126
+ if [ "${1:-}" = "--apply-migration" ]; then
127
+ migration_mode="--apply-migration"
128
+ else
129
+ project_root="${1:-.}"
130
+ if [ "${2:-}" = "--apply-migration" ]; then
131
+ migration_mode="--apply-migration"
132
+ fi
133
+ fi
134
+ echo "=== Harness Init: ${project_root} ==="
135
+
136
+ # 1. scan
137
+ echo "[1/4] scan-project.sh"
138
+ bash "$(dirname "$0")/scripts/scan-project.sh" "$project_root"
139
+ echo ""
140
+
141
+ # 2. ref placeholder 생성 (감지된 스택 전체)
142
+ echo "[2/4] init-ref-docs.sh (placeholder)"
143
+ bash "$(dirname "$0")/scripts/init-ref-docs.sh" --yes --placeholder "$project_root"
144
+ echo ""
145
+
146
+ # 3. AGENTS.md 동적 생성
147
+ if [ -f "$(dirname "$0")/scripts/init-agents-md.sh" ]; then
148
+ echo "[3/4] init-agents-md.sh"
149
+ bash "$(dirname "$0")/scripts/init-agents-md.sh" "$project_root"
150
+ else
151
+ echo "[3/4] init-agents-md.sh — skip (not found)"
152
+ fi
153
+ echo ""
154
+
155
+ # 4. v7.1 migration preview/apply
156
+ if [ "$migration_mode" = "--apply-migration" ]; then
157
+ echo "[4/4] walwal-harness migrate"
158
+ run_walwal_cli "$project_root" migrate
159
+ else
160
+ echo "[4/4] walwal-harness migrate --dry-run"
161
+ run_walwal_cli "$project_root" migrate --dry-run
162
+ fi
163
+ echo ""
164
+
165
+ echo "=== Init Complete ==="
166
+ echo "다음 단계:"
167
+ echo " 1. 각 .harness/ref/<role>-<stack>.md 를 실제 컨텐츠로 채우려면:"
168
+ echo " bash init.sh refresh-ref <role> <stack> $project_root"
169
+ echo " 2. 그 결과 프롬프트를 Claude 세션에 붙여 WebSearch + WebFetch 로 ref 작성"
170
+ if [ "$migration_mode" != "--apply-migration" ]; then
171
+ echo " 3. 마이그레이션 미리보기가 적절하면 적용:"
172
+ echo " bash init.sh migrate $project_root"
173
+ fi
174
+ }
175
+
176
+ run_refresh_ref() {
177
+ local role="$1" stack="$2" project_root="${3:-.}"
178
+ if [ -z "$role" ] || [ -z "$stack" ]; then
179
+ echo "ERROR: bash init.sh refresh-ref <role> <stack> [project_root]" >&2
180
+ exit 1
181
+ fi
182
+
183
+ local ref_path="${project_root}/.harness/ref/${role}-${stack}.md"
184
+ local archive_dir="${project_root}/.harness/archive"
185
+ mkdir -p "$archive_dir"
186
+
187
+ if [ -f "$ref_path" ]; then
188
+ local backup="${archive_dir}/ref-${role}-${stack}-$(date +%Y%m%d%H%M%S).md"
189
+ mv "$ref_path" "$backup"
190
+ echo "archive: $backup"
191
+ fi
192
+
193
+ echo ""
194
+ bash "$(dirname "$0")/scripts/init-ref-docs.sh" --claude-prompt --stack "$stack" --role "$role" "$project_root"
195
+ }
196
+
197
+ case "$SUBCOMMAND" in
198
+ help|-h|--help) usage ;;
199
+ check) run_check "$@" ;;
200
+ init) run_init "$@" ;;
201
+ migrate) run_migrate "$@" ;;
202
+ refresh-ref) run_refresh_ref "$@" ;;
203
+ *)
204
+ echo "ERROR: unknown subcommand '$SUBCOMMAND'" >&2
205
+ usage
206
+ exit 1
207
+ ;;
208
+ esac
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "7.1.0",
3
+ "version": "7.1.2",
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"
@@ -48,6 +48,7 @@
48
48
  "assets/",
49
49
  "gotchas/",
50
50
  "conventions/",
51
+ "init.sh",
51
52
  "CHANGELOG.md"
52
53
  ]
53
54
  }
@@ -168,24 +168,24 @@ fi
168
168
  # init 상태: 첫 안내
169
169
  # ─────────────────────────────────────────
170
170
  if [ "$sprint_status" = "init" ]; then
171
- if [ "$next_agent" != "null" ] && [ "$next_agent" != "none" ] && [ "$next_agent" != "dispatcher" ]; then
171
+ if [ "$next_agent" != "null" ] && [ "$next_agent" != "none" ] && [ "$next_agent" != "ceo" ]; then
172
172
  echo "# Harness Company Mode active"
173
173
  echo "# Sprint is init, but next_agent=${next_agent}; continue the autonomous loop instead of waiting for Owner."
174
174
  else
175
- echo "# Harness ready — say \"하네스 엔지니어링 시작\" or /harness-dispatcher"
176
- echo "# 기본 경로는 회사 루프입니다: dispatch -> CEO meeting -> COO/CTO 분배 -> gen/eval -> CQO -> service-ops -> batch meeting."
175
+ echo "# Harness ready — use /goal or /hot-fix"
176
+ echo "# 기본 경로는 v7 회사 루프입니다: CEO -> 필요한 CXX -> resource-manager/hiring -> worker reports -> CXX review -> CEO report."
177
177
  exit 0
178
178
  fi
179
179
  fi
180
180
 
181
181
  # ─────────────────────────────────────────
182
- # Audit lifecycle: Planner/Dispatcher 시작 시 리셋
182
+ # Audit lifecycle: CEO 시작 시 리셋
183
183
  # ─────────────────────────────────────────
184
184
  init_audit "$PROJECT_ROOT"
185
- if [ "$next_agent" = "planner" ] || [ "$next_agent" = "dispatcher" ]; then
185
+ if [ "$next_agent" = "ceo" ]; then
186
186
  # 새 사이클 — 이전 audit을 archive로 이동하고 새로 시작
187
187
  reset_audit "$PROJECT_ROOT" "$sprint_num"
188
- audit_log "system" "cycle" "start" "sprint-${sprint_num}" "new plan/dispatch cycle"
188
+ audit_log "system" "cycle" "start" "sprint-${sprint_num}" "new CEO mission cycle"
189
189
  fi
190
190
 
191
191
  # ─────────────────────────────────────────
@@ -14,7 +14,7 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
14
14
  # 조건 2: opt-out 플래그 확인
15
15
  AUTO_ROUTE="true"
16
16
  if command -v jq >/dev/null 2>&1; then
17
- AUTO_ROUTE=$(jq -r '.behavior.auto_route_dispatcher // true' "$CWD/.harness/config.json" 2>/dev/null || echo "true")
17
+ AUTO_ROUTE=$(jq -r '.behavior.auto_route_ceo // .behavior.auto_route_dispatcher // true' "$CWD/.harness/config.json" 2>/dev/null || echo "true")
18
18
  fi
19
19
  if [ "$AUTO_ROUTE" != "true" ]; then exit 0; fi
20
20
 
@@ -114,13 +114,13 @@ cat <<EOF
114
114
  [harness] company | S${SPRINT_NUM} | ${PIPELINE} | agent=${CURRENT_AGENT} (${AGENT_STATUS}) | next=${NEXT_AGENT} | queue=${T_PASSED}/${T_TOTAL} passed | failed=${T_FAILED}
115
115
  ${CONTEXT_WARNING}
116
116
  ## Route
117
- - pipeline=none/dispatcher 미실행 → harness-dispatcher 스킬 호출
118
- - 기본 경로는 conductor 중심 회사 루프다: dispatch -> CEO meeting -> planner(COO) -> cto -> gen/eval -> cqo -> service-ops -> batch meeting
119
- - worker pool(기본 3)이 자율적으로 Gen→Eval 루프 실행 중이며 control-plane은 이 상한에 포함되지 않는다
117
+ - pipeline=none/init → harness-ceo 스킬로 Owner goal/hot-fix를 접수하고 mission 문서를 생성
118
+ - 기본 경로는 v7 CXX 회사 루프다: CEO -> COO/CDO/CTO/CQO/OPS -> harness-resource-manager -> harness-hiring -> hired workers -> CXX review -> CEO report
119
+ - CXX는 직접 전문 산출물을 만들지 않고 worker report를 `.harness/documents/{mission}/workers/`에 남겨야 한다
120
120
  - Owner에게 "계속 진행", "진행할까요?", "다음 명령을 입력하세요"를 요구하지 않는다. GOAL이 있고 escalation이 아니면 즉시 next_agent/current_agent 업무를 수행한다
121
121
  - Owner 입력은 목표 변경·사고·결과 확인용이지 회사 진행을 펌프하는 신호가 아니다
122
- - conductor 는 Planner 직행 대신 meeting-manager / cto / cqo / service-ops 로 next_agent를 재작성할 수 있다
123
- - 기능 요청 → pipeline flow | 실수 지적 → gotcha flow | 메타 질문 → 짧게 응답 (skip)
122
+ - CEO는 필요한 CXX만 호출하고, CXX는 resource-manager/hiring 없이 worker 업무를 수행하지 않는다
123
+ - 기능 요청 → CEO mission flow | 긴급 수정 → hot-fix flow | 메타 질문 → 짧게 응답 (skip)
124
124
  - 활성 pipeline → next_agent/current_agent 컨텍스트로 계속
125
125
  - skip: "harness skip", "just answer" 등 명시 시 단일 메시지 건너뜀
126
126
  EOF
@@ -1,22 +0,0 @@
1
- ---
2
- description: Run the harness play build and notify OPS monitoring.
3
- argument-hint: "[additional play args]"
4
- ---
5
-
6
- # /play-harness
7
-
8
- Run the target project's harness play mode.
9
-
10
- Required flow:
11
- 1. Confirm `.harness/` exists in the current project.
12
- 2. Run `bash scripts/play.sh $ARGUMENTS`.
13
- 3. Treat play mode and release mode as mutually exclusive. If release mode is active, stop it first with `/stop-harness` or report the conflict to the Owner.
14
- 4. After the script returns, read the emitted OPS report path and notify the installed `harness-ops` skill/agent that play monitoring has started.
15
- 5. Record the build result in `.harness/documents/{mission_name}/ops.md` when a mission is active.
16
- 6. Do not route this through `/goal`; this command is an Owner-facing runtime control entrypoint.
17
-
18
- Play arguments:
19
-
20
- ```
21
- $ARGUMENTS
22
- ```
@@ -1,22 +0,0 @@
1
- ---
2
- description: Run the harness release build, start service operation, and notify OPS monitoring.
3
- argument-hint: "[additional release args]"
4
- ---
5
-
6
- # /release-harness
7
-
8
- Run the target project's harness release mode and begin service operation.
9
-
10
- Required flow:
11
- 1. Confirm `.harness/` exists in the current project.
12
- 2. Run `bash scripts/release.sh release $ARGUMENTS`.
13
- 3. Treat play mode and release mode as mutually exclusive. If play mode is active, stop it first with `/stop-harness` or report the conflict to the Owner.
14
- 4. After the script returns, read the emitted OPS report path and notify the installed `harness-ops` skill/agent that release monitoring has started.
15
- 5. Record the release result in `.harness/documents/{mission_name}/ops.md` when a mission is active.
16
- 6. Do not route this through `/goal`; this command is an Owner-facing runtime control entrypoint.
17
-
18
- Release arguments:
19
-
20
- ```
21
- $ARGUMENTS
22
- ```
@@ -1,21 +0,0 @@
1
- ---
2
- description: Stop any harness play or release process/session.
3
- argument-hint: "[optional stop reason]"
4
- ---
5
-
6
- # /stop-harness
7
-
8
- Stop any active harness play or release runtime.
9
-
10
- Required flow:
11
- 1. Confirm `.harness/` exists in the current project.
12
- 2. Run `bash scripts/harness-runtime-stop.sh $ARGUMENTS`.
13
- 3. Verify both play and release runtime state files are cleared or marked stopped.
14
- 4. Notify the installed `harness-ops` skill/agent that monitoring should stop for the terminated runtime mode.
15
- 5. Record the stop result in `.harness/documents/{mission_name}/ops.md` when a mission is active.
16
-
17
- Stop reason:
18
-
19
- ```
20
- $ARGUMENTS
21
- ```