@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.
- package/HR-Resource/ceo/SKILL.md +11 -0
- package/assets/templates/config.json +20 -192
- package/assets/templates/progress.json.template +4 -4
- package/bin/init.js +228 -49
- package/init.sh +208 -0
- package/package.json +2 -1
- package/scripts/harness-session-start.sh +6 -6
- package/scripts/harness-user-prompt-submit.sh +6 -6
- package/commands/play-harness.md +0 -22
- package/commands/release-harness.md +0 -22
- package/commands/stop-harness.md +0 -21
package/HR-Resource/ceo/SKILL.md
CHANGED
|
@@ -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": "
|
|
5
|
-
"description": "
|
|
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
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
},
|
|
17
|
-
"
|
|
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": ["
|
|
169
|
-
"
|
|
170
|
-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
343
|
-
"
|
|
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": "
|
|
181
|
+
"comment": "v7 — 회사모드는 항시 활성이다. Owner entrypoint는 /goal, /hot-fix이며 CEO가 CXX와 hired worker 흐름을 조정한다.",
|
|
354
182
|
"default_mode": "company",
|
|
355
|
-
"owner": "
|
|
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": "
|
|
5
|
+
"owner": "ceo",
|
|
6
6
|
"decided_at": null,
|
|
7
|
-
"rationale": "company mode is
|
|
8
|
-
"policy": "
|
|
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": "
|
|
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
|
|
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 (
|
|
862
|
+
// 3d. UserPromptSubmit hook (v7 CEO routing guidance)
|
|
781
863
|
// ─────────────────────────────────────────
|
|
782
864
|
function installUserPromptSubmitHook() {
|
|
783
|
-
log('Installing UserPromptSubmit hook (
|
|
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(' →
|
|
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.
|
|
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
|
|
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: '
|
|
1430
|
+
owner: 'ceo',
|
|
1327
1431
|
decided_at: null,
|
|
1328
|
-
rationale: `migration — legacy mode="${oldMode}" normalized to company`,
|
|
1329
|
-
policy: '
|
|
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:
|
|
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 =
|
|
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:
|
|
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('
|
|
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 —
|
|
1569
|
+
// Verify — v7 CXX/core skill invariants + progress schema
|
|
1451
1570
|
// ─────────────────────────────────────────
|
|
1452
1571
|
function runVerify() {
|
|
1453
1572
|
const expectedSkills = [
|
|
1454
|
-
'
|
|
1455
|
-
'
|
|
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 +
|
|
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
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
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
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
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
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
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(`
|
|
1643
|
+
log(` commands: /goal + /hot-fix checked`);
|
|
1495
1644
|
|
|
1496
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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.
|
|
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" != "
|
|
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 —
|
|
176
|
-
echo "# 기본 경로는 회사 루프입니다:
|
|
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:
|
|
182
|
+
# Audit lifecycle: CEO 시작 시 리셋
|
|
183
183
|
# ─────────────────────────────────────────
|
|
184
184
|
init_audit "$PROJECT_ROOT"
|
|
185
|
-
if [ "$next_agent" = "
|
|
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
|
|
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/
|
|
118
|
-
- 기본 경로는
|
|
119
|
-
-
|
|
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
|
-
-
|
|
123
|
-
- 기능 요청 →
|
|
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
|
package/commands/play-harness.md
DELETED
|
@@ -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
|
-
```
|
package/commands/stop-harness.md
DELETED
|
@@ -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
|
-
```
|