@walwal-harness/cli 6.1.3 → 6.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +43 -74
- package/assets/launchd/com.walwal.harness-wake.plist.template +32 -0
- package/assets/templates/CONVENTIONS.md +12 -0
- package/assets/templates/HARNESS.md +216 -403
- package/assets/templates/config.json +27 -29
- package/assets/templates/memory.md +16 -2
- package/assets/templates/progress.json.template +6 -5
- package/bin/init.js +179 -90
- package/conventions/README.md +92 -0
- package/conventions/conductor.md +24 -0
- package/conventions/coo-developer.md +24 -0
- package/conventions/cqo.md +24 -0
- package/conventions/cto.md +24 -0
- package/conventions/dispatcher.md +24 -0
- package/conventions/documentationer.md +24 -0
- package/conventions/evaluator-architecture.md +24 -0
- package/conventions/evaluator-code-quality.md +24 -0
- package/conventions/evaluator-functional.md +24 -0
- package/conventions/evaluator-security.md +24 -0
- package/conventions/evaluator-visual.md +24 -0
- package/conventions/generator-backend.md +24 -0
- package/conventions/generator-designer.md +24 -0
- package/conventions/generator-devops.md +24 -0
- package/conventions/generator-frontend.md +24 -0
- package/conventions/meeting-manager.md +24 -0
- package/conventions/planner.md +24 -0
- package/conventions/service-ops.md +24 -0
- package/conventions/shared.md +40 -0
- package/gotchas/conductor.md +16 -0
- package/gotchas/dispatcher.md +16 -0
- package/gotchas/meeting-manager.md +8 -0
- package/gotchas/service-ops.md +8 -0
- package/package.json +5 -4
- package/scripts/conductor-tick.sh +61 -53
- package/scripts/harness-archive.sh +7 -5
- package/scripts/harness-dashboard-up.sh +11 -3
- package/scripts/harness-hourly-review.sh +303 -0
- package/scripts/harness-meeting-doc.sh +55 -16
- package/scripts/harness-next.sh +1 -1
- package/scripts/harness-queue-manager.sh +12 -5
- package/scripts/harness-service-ops-monitor.sh +228 -0
- package/scripts/harness-session-start.sh +60 -31
- package/scripts/harness-statusline.sh +8 -12
- package/scripts/harness-stop.sh +85 -0
- package/scripts/harness-user-prompt-submit.sh +11 -29
- package/scripts/harness-wake-install.sh +179 -0
- package/scripts/harness-wake.sh +258 -0
- package/scripts/harness-worker-dispatch.sh +157 -0
- package/scripts/lib/harness-progress-migrate.sh +6 -1
- package/scripts/lib/harness-render-progress.sh +3 -3
- package/skills/brainstorming/SKILL.md +2 -2
- package/skills/conductor/SKILL.md +67 -55
- package/skills/cqo/SKILL.md +1 -0
- package/skills/cto/SKILL.md +1 -0
- package/skills/dispatcher/SKILL.md +33 -14
- package/skills/evaluator-code-quality/SKILL.md +4 -4
- package/skills/evaluator-functional/SKILL.md +6 -6
- package/skills/evaluator-visual/SKILL.md +4 -4
- package/skills/generator-backend/SKILL.md +4 -4
- package/skills/generator-frontend/SKILL.md +5 -5
- package/skills/meeting-manager/SKILL.md +82 -12
- package/skills/planner/SKILL.md +3 -3
- package/skills/service-ops/SKILL.md +25 -0
- package/commands/harness-solo.md +0 -103
- package/commands/harness-stop.md +0 -53
- package/commands/harness-team.md +0 -530
- package/scripts/harness-dashboard.sh +0 -509
- package/scripts/harness-goal-init.sh +0 -72
- package/scripts/harness-goal-show.sh +0 -37
- package/scripts/harness-gotcha-memory.sh +0 -348
- package/scripts/harness-monitor.sh +0 -398
- package/scripts/harness-prompt-history.sh +0 -164
- package/scripts/harness-tmux.sh +0 -372
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": {
|
|
3
|
-
"name": "
|
|
4
|
-
"version": "
|
|
5
|
-
"description": "
|
|
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.",
|
|
6
6
|
"source": "https://www.anthropic.com/engineering/harness-design-long-running-apps"
|
|
7
7
|
},
|
|
8
8
|
"agents": {
|
|
@@ -207,7 +207,7 @@
|
|
|
207
207
|
"on_fail": "reroute_to_generator"
|
|
208
208
|
},
|
|
209
209
|
"team": {
|
|
210
|
-
"comment": "
|
|
210
|
+
"comment": "Company worker pool 설정. 회사모드에서 항상 사용.",
|
|
211
211
|
"concurrency": 3,
|
|
212
212
|
"max_retries_per_feature": 5,
|
|
213
213
|
"escalate_after": 3,
|
|
@@ -349,32 +349,30 @@
|
|
|
349
349
|
"re_notify_every_seconds": 3600,
|
|
350
350
|
"owner": "session-start"
|
|
351
351
|
},
|
|
352
|
-
"
|
|
353
|
-
"comment": "v6.
|
|
354
|
-
"default_mode": "
|
|
352
|
+
"company_mode": {
|
|
353
|
+
"comment": "v6.3 — 회사모드는 항시 활성이다. 사용자 모드 선택과 tie-breaker 는 제거됐다. Conductor 는 feature-list/queue 를 기반으로 매 tick 가능한 병렬 작업을 자동 배정한다.",
|
|
354
|
+
"default_mode": "company",
|
|
355
355
|
"owner": "conductor",
|
|
356
|
-
"
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
"/harness-team (force team)",
|
|
377
|
-
"사용자 발화: 'solo 로', 'team 으로', 'auto 다시' (auto 복귀)"
|
|
356
|
+
"worker_concurrency": 3,
|
|
357
|
+
"worker_spawn": "claude",
|
|
358
|
+
"worker_spawn_description": "claude 이면 idle worker 배정 시 `claude -p` 백그라운드 프로세스를 띄운다. record 로 두면 prompt/log만 생성한다.",
|
|
359
|
+
"autonomous": true,
|
|
360
|
+
"parallel_by_default": true
|
|
361
|
+
},
|
|
362
|
+
"runtime": {
|
|
363
|
+
"production": {
|
|
364
|
+
"comment": "Owner+CEO+CTO 합의로만 services[] 를 수정한다. live=true 이고 services[] 가 있으면 Service-Ops 는 매 hourly review 마다 port/health/log 상태를 운영보고서에 남긴다.",
|
|
365
|
+
"live": false,
|
|
366
|
+
"services": [
|
|
367
|
+
{
|
|
368
|
+
"name": "example-api",
|
|
369
|
+
"host": "127.0.0.1",
|
|
370
|
+
"port": 3000,
|
|
371
|
+
"health_path": "/health",
|
|
372
|
+
"expected_status": 200,
|
|
373
|
+
"log_path": null,
|
|
374
|
+
"cadence_seconds": 3600
|
|
375
|
+
}
|
|
378
376
|
]
|
|
379
377
|
}
|
|
380
378
|
},
|
|
@@ -65,7 +65,7 @@ docmeta:
|
|
|
65
65
|
5. `result PASS`는 **SCORE ≥ 2.80**인 경우에만 기록. score=1.00을 PASS로 기록 금지.
|
|
66
66
|
6. 각 단계마다 **어떤 도구/방법으로 검증했는지**(tsc/eslint/curl/playwright 등) 명시.
|
|
67
67
|
- **Context**: 사용자가 대시보드 로그만 보고 진행 상황을 파악해야 하는데, 기존 축약 로그로는 4대 질문에 답이 안 보인다는 피드백.
|
|
68
|
-
- **Applies to**:
|
|
68
|
+
- **Applies to**: Company Worker(Generator + Evaluator), 모든 `logev` 호출 지점.
|
|
69
69
|
|
|
70
70
|
### [M-NEXUS-P3] Owner ↔ CEO 단일 창구 + 자율 실행 (Inviolable)
|
|
71
71
|
- **Date**: 2026-05-07
|
|
@@ -77,10 +77,24 @@ docmeta:
|
|
|
77
77
|
- **Context**: v6.0.0 publish 직후 dispatcher 응답이 사용자를 "CEO 직접 리뷰…" 로 호명하고 "/harness-next 자동 진행하시겠습니까?" 로 사용자 펌프를 요구함. Owner 의 명시적 교정.
|
|
78
78
|
- **Applies to**:
|
|
79
79
|
- **Dispatcher**: 사용자 호칭/응답 문구, GOAL 정립, 모든 inbound/outbound owner 통신. → anti-pattern: `.harness/gotchas/dispatcher.md` [G-001]~[G-004].
|
|
80
|
-
- **Conductor**: GOAL 확정 직후 자동 시동 (사용자 허락 X), Eval PASS/FAIL 직후 자동 advance/retry,
|
|
80
|
+
- **Conductor**: GOAL 확정 직후 자동 시동 (사용자 허락 X), Eval PASS/FAIL 직후 자동 advance/retry, 회사모드 병렬 worker pool 관리.
|
|
81
81
|
- **Planner / CTO / CQO / Generator / Evaluator / Service-Ops**: Owner 와 직접 대화 X. 모든 통신은 Dispatcher 경유.
|
|
82
82
|
- **Why this matters**: 회사가 매 단계 사용자 허락을 구하면 NEXUS 메타포 자체가 무너진다. P3 "Continuous Loop > End-of-Pipeline" 가 약속하는 95% 결함 차단·40~60% 단축은 자율 루프에서만 성립한다.
|
|
83
83
|
|
|
84
|
+
### [M-SYS-EXEC-ROLE-CONTRACT] 회사 역할 계약은 하네스 SoT에서 자동 적용되어야 한다
|
|
85
|
+
- **Date**: 2026-05-10
|
|
86
|
+
- **Status**: verified
|
|
87
|
+
- **TTL**: 영구
|
|
88
|
+
- **Lesson**: CEO/COO/CTO/CQO/Service-Ops가 무엇을 해야 하고 무엇을 하면 안 되는지는 일회성 wake prompt나 개별 회의 문구가 아니라 하네스의 convention/gotcha/memory SoT에 있어야 한다. 모든 에이전트는 세션 시작 시 `CONVENTIONS.md`, `.harness/conventions/shared.md`, 자기 role convention, 자기 gotcha, `.harness/memory.md` 순서로 읽고 이 계약을 적용한다.
|
|
89
|
+
- **Context**: Owner가 Paperclip과 비교하며 현재 하네스는 각 임원이 자기 역할을 명확히 이해하고 일한다는 느낌이 없다고 지적했다. 원인은 역할 계약이 문서에는 있었지만 hourly/review 실행 경로와 worker prompt가 이를 자동 적용하지 않고 상태 리포트만 만든 데 있었다.
|
|
90
|
+
- **Applies to**:
|
|
91
|
+
- **Dispatcher/CEO**: Owner-facing 창구와 GOAL/escalation 책임.
|
|
92
|
+
- **Planner/COO**: GOAL → work package/queue/가설 변환 책임.
|
|
93
|
+
- **CTO**: 구현/아키텍처/runtime recovery 책임.
|
|
94
|
+
- **CQO**: evidence/PASS/FAIL/회귀 검증 책임.
|
|
95
|
+
- **Service-Ops**: 운영 신호를 회의 evidence와 CTO/CQO action으로 연결.
|
|
96
|
+
- **Meeting-Manager**: 상태표가 아니라 임원 회의록을 남김.
|
|
97
|
+
|
|
84
98
|
### [M-002] FE Evaluation은 Playwright 필수
|
|
85
99
|
- **Date**: 2026-04-20
|
|
86
100
|
- **Status**: verified
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 4,
|
|
3
|
-
"mode": "
|
|
3
|
+
"mode": "company",
|
|
4
4
|
"mode_decision": {
|
|
5
5
|
"owner": "conductor",
|
|
6
6
|
"decided_at": null,
|
|
7
|
-
"rationale":
|
|
8
|
-
"
|
|
7
|
+
"rationale": "company mode is always-on",
|
|
8
|
+
"policy": "always_company_parallel"
|
|
9
9
|
},
|
|
10
10
|
"project_name": "",
|
|
11
11
|
"pipeline": null,
|
|
@@ -47,8 +47,9 @@
|
|
|
47
47
|
"api-contract.json": { "status": "pending", "updated_by": null, "updated_at": null },
|
|
48
48
|
"sprint-contract.md": { "status": "pending", "updated_by": null, "updated_at": null }
|
|
49
49
|
},
|
|
50
|
-
"
|
|
51
|
-
"
|
|
50
|
+
"company_state": {
|
|
51
|
+
"active_workers": 0,
|
|
52
|
+
"concurrency": 3,
|
|
52
53
|
"paused_at": null,
|
|
53
54
|
"resume_from": null
|
|
54
55
|
},
|
package/bin/init.js
CHANGED
|
@@ -286,7 +286,7 @@ function scaffoldHarness() {
|
|
|
286
286
|
// accumulated `### [C-NNN]` entries.
|
|
287
287
|
const conventionsSrc = path.join(PKG_ROOT, 'conventions');
|
|
288
288
|
if (fs.existsSync(conventionsSrc)) {
|
|
289
|
-
const CONV_ENTRY = /^### \[C
|
|
289
|
+
const CONV_ENTRY = /^### \[C-[A-Z0-9_-]+\]/m;
|
|
290
290
|
const files = fs.readdirSync(conventionsSrc);
|
|
291
291
|
for (const file of files) {
|
|
292
292
|
const destPath = path.join(HARNESS_DIR, 'conventions', file);
|
|
@@ -339,7 +339,7 @@ function scaffoldHarness() {
|
|
|
339
339
|
const progress = JSON.parse(fs.readFileSync(progressPath, 'utf8'));
|
|
340
340
|
if (!progress.version || progress.version < 2) {
|
|
341
341
|
progress.version = 2;
|
|
342
|
-
progress.mode =
|
|
342
|
+
progress.mode = 'company';
|
|
343
343
|
if (!progress.team_state) {
|
|
344
344
|
progress.team_state = { active_teams: 0, paused_at: null, resume_from: null };
|
|
345
345
|
}
|
|
@@ -709,6 +709,59 @@ function installUserPromptSubmitHook() {
|
|
|
709
709
|
}
|
|
710
710
|
}
|
|
711
711
|
|
|
712
|
+
// ─────────────────────────────────────────
|
|
713
|
+
// 3e. Stop hook (auto-chain conductor tick)
|
|
714
|
+
// ─────────────────────────────────────────
|
|
715
|
+
function installStopHook() {
|
|
716
|
+
log('Installing Stop hook (auto-chain conductor tick)...');
|
|
717
|
+
|
|
718
|
+
const settingsDir = path.join(PROJECT_ROOT, '.claude');
|
|
719
|
+
const settingsFile = path.join(settingsDir, 'settings.json');
|
|
720
|
+
|
|
721
|
+
ensureDir(settingsDir);
|
|
722
|
+
|
|
723
|
+
let settings = {};
|
|
724
|
+
if (fileExists(settingsFile)) {
|
|
725
|
+
try {
|
|
726
|
+
settings = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
|
|
727
|
+
} catch (e) {
|
|
728
|
+
log('WARNING: Could not parse existing .claude/settings.json, creating new');
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (!settings.hooks) settings.hooks = {};
|
|
733
|
+
if (!settings.hooks.Stop) settings.hooks.Stop = [];
|
|
734
|
+
|
|
735
|
+
const hookCmd = 'bash scripts/harness-stop.sh';
|
|
736
|
+
|
|
737
|
+
const alreadyInstalled = settings.hooks.Stop.some((entry) => {
|
|
738
|
+
if (!entry || typeof entry !== 'object') return false;
|
|
739
|
+
if (Array.isArray(entry.hooks)) {
|
|
740
|
+
return entry.hooks.some(
|
|
741
|
+
(h) => h && h.command && h.command.includes('harness-stop')
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
if (entry.type === 'command' && entry.command) {
|
|
745
|
+
return entry.command.includes('harness-stop');
|
|
746
|
+
}
|
|
747
|
+
return false;
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
if (!alreadyInstalled) {
|
|
751
|
+
settings.hooks.Stop.push({
|
|
752
|
+
matcher: '',
|
|
753
|
+
hooks: [{ type: 'command', command: hookCmd }]
|
|
754
|
+
});
|
|
755
|
+
fs.writeFileSync(settingsFile, JSON.stringify(settings, null, 2) + '\n');
|
|
756
|
+
log('Stop hook installed in .claude/settings.json');
|
|
757
|
+
log(' → Conductor 가 running 인 동안 turn 종료 시 자동으로 다음 tick 으로 연쇄');
|
|
758
|
+
log(' → 비활성: .harness/config.json behavior.auto_chain_on_stop = false');
|
|
759
|
+
log(' → 상한: behavior.auto_chain_max_per_sprint (기본 200)');
|
|
760
|
+
} else {
|
|
761
|
+
log('Stop hook already installed');
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
712
765
|
// ─────────────────────────────────────────
|
|
713
766
|
// 4. AGENTS.md + CLAUDE.md
|
|
714
767
|
// ─────────────────────────────────────────
|
|
@@ -734,7 +787,7 @@ function installAgentTeamsEnv() {
|
|
|
734
787
|
log('Agent Teams already enabled');
|
|
735
788
|
}
|
|
736
789
|
|
|
737
|
-
// Add git worktree permission for
|
|
790
|
+
// Add git worktree permission for parallel worker isolation
|
|
738
791
|
if (!settings.permissions) settings.permissions = {};
|
|
739
792
|
if (!settings.permissions.allow) settings.permissions.allow = [];
|
|
740
793
|
const worktreePerms = [
|
|
@@ -753,7 +806,7 @@ function installAgentTeamsEnv() {
|
|
|
753
806
|
if (changed) {
|
|
754
807
|
ensureDir(path.dirname(settingsPath));
|
|
755
808
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
756
|
-
log('Git worktree permissions added for
|
|
809
|
+
log('Git worktree permissions added for parallel worker isolation');
|
|
757
810
|
}
|
|
758
811
|
}
|
|
759
812
|
|
|
@@ -906,70 +959,35 @@ function showHelp() {
|
|
|
906
959
|
Usage:
|
|
907
960
|
npx walwal-harness Initialize project for harness engineering
|
|
908
961
|
npx walwal-harness --force Re-initialize (overwrites existing files)
|
|
909
|
-
npx walwal-harness
|
|
910
|
-
npx walwal-harness team --kill Kill Team Mode tmux session
|
|
911
|
-
npx walwal-harness migrate Apply v3 → v4 migration (mode→auto + Conductor)
|
|
962
|
+
npx walwal-harness migrate Apply migration (always-on company mode)
|
|
912
963
|
npx walwal-harness migrate --dry-run Preview migration changes without applying
|
|
964
|
+
npx walwal-harness verify Verify install integrity
|
|
913
965
|
npx walwal-harness --help Show this help
|
|
914
966
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
967
|
+
Runtime (always-on company mode):
|
|
968
|
+
- Stop 훅이 매 turn 종료 시 다음 부서로 자동 연쇄.
|
|
969
|
+
- 1시간 안전망 wake (선택): bash scripts/harness-wake-install.sh install .
|
|
970
|
+
- 3D 대시보드 (선택): bash scripts/harness-dashboard-up.sh
|
|
971
|
+
- Owner 입력은 GOAL 모호성, escalation, 결과 보고에만 필요합니다.
|
|
920
972
|
|
|
921
973
|
What it does:
|
|
922
974
|
1. Scaffolds .harness/ directory (actions, archive, gotchas, config)
|
|
923
975
|
2. Installs skills to .claude/skills/ (dispatcher, planner, generators, evaluators)
|
|
924
|
-
3.
|
|
925
|
-
4.
|
|
926
|
-
5.
|
|
927
|
-
6.
|
|
928
|
-
7. Creates AGENTS.md + CLAUDE.md symlink
|
|
976
|
+
3. Copies helper scripts to scripts/
|
|
977
|
+
4. Registers SessionStart + UserPromptSubmit + Stop hooks
|
|
978
|
+
5. Installs statusline (persistent 1-line status bar)
|
|
979
|
+
6. Creates AGENTS.md + CLAUDE.md symlink
|
|
929
980
|
|
|
930
981
|
After init:
|
|
931
982
|
1. Restart Claude Code session (/exit → re-enter directory)
|
|
932
983
|
2. Say "하네스 엔지니어링 시작" or /harness-dispatcher
|
|
933
|
-
3. After
|
|
984
|
+
3. After GOAL intake, Conductor runs the company loop autonomously.
|
|
985
|
+
4. (선택) bash scripts/harness-wake-install.sh install . — 1시간마다 자동 wake.
|
|
934
986
|
`);
|
|
935
987
|
}
|
|
936
988
|
|
|
937
989
|
// ─────────────────────────────────────────
|
|
938
|
-
//
|
|
939
|
-
// ─────────────────────────────────────────
|
|
940
|
-
function runTeamStudio() {
|
|
941
|
-
const killMode = args.includes('--kill');
|
|
942
|
-
const scriptsDir = path.join(PKG_ROOT, 'scripts');
|
|
943
|
-
const tmuxScript = path.join(scriptsDir, 'harness-tmux.sh');
|
|
944
|
-
|
|
945
|
-
if (!fs.existsSync(tmuxScript)) {
|
|
946
|
-
log('ERROR: harness-tmux.sh not found.');
|
|
947
|
-
process.exit(1);
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
try {
|
|
951
|
-
execSync('which tmux', { stdio: 'ignore' });
|
|
952
|
-
} catch {
|
|
953
|
-
log('ERROR: tmux is required. Install with: brew install tmux');
|
|
954
|
-
process.exit(1);
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
if (killMode) {
|
|
958
|
-
const cmd = `bash "${tmuxScript}" --kill`;
|
|
959
|
-
execSync(cmd, { stdio: 'inherit' });
|
|
960
|
-
return;
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
// Enable Agent Teams in project settings
|
|
964
|
-
installAgentTeamsEnv();
|
|
965
|
-
|
|
966
|
-
const cmd = `bash "${tmuxScript}" "${PROJECT_ROOT}" --team`;
|
|
967
|
-
log('Launching Team Mode Studio...');
|
|
968
|
-
execSync(cmd, { stdio: 'inherit' });
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
// ─────────────────────────────────────────
|
|
972
|
-
// Migration — v3 → v4 (mode auto + Conductor ownership)
|
|
990
|
+
// Migration — legacy mode fields → always-on company mode
|
|
973
991
|
// ─────────────────────────────────────────
|
|
974
992
|
const TARGET_PROGRESS_VERSION = 4;
|
|
975
993
|
|
|
@@ -986,9 +1004,10 @@ function detectMigrationNeeded() {
|
|
|
986
1004
|
const memoryTplPath = path.join(PKG_ROOT, 'assets', 'templates', 'memory.md');
|
|
987
1005
|
const flags = {
|
|
988
1006
|
progressV3toV4: false,
|
|
989
|
-
|
|
1007
|
+
configMissingCompanyMode: false,
|
|
990
1008
|
memoryMissingSystemEntries: [],
|
|
991
1009
|
gotchaMissingEntries: {}, // { "<filename>": [G-IDs...] }
|
|
1010
|
+
conventionMissingEntries: {}, // { "<filename>": [C-IDs...] }
|
|
992
1011
|
bundleVersionStale: null, // { current, installed }
|
|
993
1012
|
};
|
|
994
1013
|
|
|
@@ -1012,6 +1031,25 @@ function detectMigrationNeeded() {
|
|
|
1012
1031
|
}
|
|
1013
1032
|
}
|
|
1014
1033
|
|
|
1034
|
+
// Convention entry-level diff: preserve user [C-NNN] entries, append only
|
|
1035
|
+
// bundled system entries such as [C-SYS-*].
|
|
1036
|
+
const conventionsSrc = path.join(PKG_ROOT, 'conventions');
|
|
1037
|
+
const conventionsDest = path.join(HARNESS_DIR, 'conventions');
|
|
1038
|
+
if (fs.existsSync(conventionsSrc) && fs.existsSync(conventionsDest)) {
|
|
1039
|
+
const files = fs.readdirSync(conventionsSrc).filter((f) => f.endsWith('.md') && f !== 'README.md');
|
|
1040
|
+
for (const file of files) {
|
|
1041
|
+
const srcPath = path.join(conventionsSrc, file);
|
|
1042
|
+
const destPath = path.join(conventionsDest, file);
|
|
1043
|
+
if (!fs.existsSync(destPath)) continue;
|
|
1044
|
+
try {
|
|
1045
|
+
const srcIds = extractConventionEntryIds(fs.readFileSync(srcPath, 'utf8'));
|
|
1046
|
+
const dstIds = new Set(extractConventionEntryIds(fs.readFileSync(destPath, 'utf8')));
|
|
1047
|
+
const missing = srcIds.filter((id) => !dstIds.has(id));
|
|
1048
|
+
if (missing.length) flags.conventionMissingEntries[file] = missing;
|
|
1049
|
+
} catch {}
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1015
1053
|
// Bundle version stamp: detects fresh package vs. last-applied bundle.
|
|
1016
1054
|
// Even if the 3 schema flags pass, a newer bundle may have introduced new
|
|
1017
1055
|
// gotcha/SKILL/template content the user hasn't seen yet.
|
|
@@ -1032,7 +1070,7 @@ function detectMigrationNeeded() {
|
|
|
1032
1070
|
if (fs.existsSync(configPath)) {
|
|
1033
1071
|
try {
|
|
1034
1072
|
const c = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
1035
|
-
if (!c.mode_selection) flags.
|
|
1073
|
+
if (!c.company_mode || c.mode_selection) flags.configMissingCompanyMode = true;
|
|
1036
1074
|
} catch {}
|
|
1037
1075
|
}
|
|
1038
1076
|
if (fs.existsSync(memoryPath) && fs.existsSync(memoryTplPath)) {
|
|
@@ -1086,18 +1124,37 @@ function extractGotchaEntryBlock(md, id) {
|
|
|
1086
1124
|
return m ? m[1].trimEnd() : null;
|
|
1087
1125
|
}
|
|
1088
1126
|
|
|
1127
|
+
// Returns convention entry IDs ("[C-001]", "[C-SYS-*]" etc.) found in markdown.
|
|
1128
|
+
function extractConventionEntryIds(md) {
|
|
1129
|
+
const re = /^#{2,3}\s+\[(C-[A-Z0-9_-]+)\]/gm;
|
|
1130
|
+
const ids = [];
|
|
1131
|
+
let m;
|
|
1132
|
+
while ((m = re.exec(md)) !== null) ids.push(m[1]);
|
|
1133
|
+
return ids;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function extractConventionEntryBlock(md, id) {
|
|
1137
|
+
const escId = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1138
|
+
const re = new RegExp(
|
|
1139
|
+
`(^#{2,3}\\s+\\[${escId}\\][\\s\\S]*?)(?=^#{2,3}\\s+\\[C-|$(?![\\s\\S]))`,
|
|
1140
|
+
'm',
|
|
1141
|
+
);
|
|
1142
|
+
const m = md.match(re);
|
|
1143
|
+
return m ? m[1].trimEnd() : null;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1089
1146
|
function showMigrationProposal(flags) {
|
|
1090
1147
|
console.log('');
|
|
1091
1148
|
console.log('╔══════════════════════════════════════════════════════════╗');
|
|
1092
1149
|
console.log('║ walwal-harness v6 — 자동 마이그레이션 사용 가능 ║');
|
|
1093
1150
|
console.log('╚══════════════════════════════════════════════════════════╝');
|
|
1094
1151
|
if (flags.progressV3toV4) {
|
|
1095
|
-
console.log(' • progress.json:
|
|
1096
|
-
console.log('
|
|
1152
|
+
console.log(' • progress.json: legacy mode → "company"');
|
|
1153
|
+
console.log(' legacy mode 값은 보존하지 않고 회사모드로 정규화됩니다.');
|
|
1097
1154
|
}
|
|
1098
|
-
if (flags.
|
|
1099
|
-
console.log(' • config.json:
|
|
1100
|
-
console.log('
|
|
1155
|
+
if (flags.configMissingCompanyMode) {
|
|
1156
|
+
console.log(' • config.json: company_mode 섹션 동기화 가능');
|
|
1157
|
+
console.log(' 사용자 모드 선택 없이 회사모드 병렬 실행으로 고정합니다.');
|
|
1101
1158
|
}
|
|
1102
1159
|
if (flags.memoryMissingSystemEntries && flags.memoryMissingSystemEntries.length) {
|
|
1103
1160
|
console.log(' • memory.md: 시스템 entry 누락 — append 가능');
|
|
@@ -1111,6 +1168,13 @@ function showMigrationProposal(flags) {
|
|
|
1111
1168
|
console.log(` ${file}: [${ids.join(', ')}]`);
|
|
1112
1169
|
}
|
|
1113
1170
|
}
|
|
1171
|
+
const conventionSummary = Object.entries(flags.conventionMissingEntries || {});
|
|
1172
|
+
if (conventionSummary.length) {
|
|
1173
|
+
console.log(' • conventions/: 시스템 entry 누락 — entry-level append 가능 (사용자 [C-NNN] 보존)');
|
|
1174
|
+
for (const [file, ids] of conventionSummary) {
|
|
1175
|
+
console.log(` ${file}: [${ids.join(', ')}]`);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1114
1178
|
if (flags.bundleVersionStale) {
|
|
1115
1179
|
const { current, installed } = flags.bundleVersionStale;
|
|
1116
1180
|
console.log(` • bundle: ${installed ?? '(없음)'} → ${current} 스탬프 갱신 필요`);
|
|
@@ -1130,7 +1194,7 @@ function runMigrate(opts = {}) {
|
|
|
1130
1194
|
const gotchaMissingTotal = Object.values(flags.gotchaMissingEntries || {}).reduce((n, a) => n + a.length, 0);
|
|
1131
1195
|
if (
|
|
1132
1196
|
!flags.progressV3toV4 &&
|
|
1133
|
-
!flags.
|
|
1197
|
+
!flags.configMissingCompanyMode &&
|
|
1134
1198
|
(!flags.memoryMissingSystemEntries || flags.memoryMissingSystemEntries.length === 0) &&
|
|
1135
1199
|
gotchaMissingTotal === 0 &&
|
|
1136
1200
|
!flags.bundleVersionStale
|
|
@@ -1138,7 +1202,7 @@ function runMigrate(opts = {}) {
|
|
|
1138
1202
|
console.log('');
|
|
1139
1203
|
let pkgVer = 'unknown';
|
|
1140
1204
|
try { pkgVer = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8')).version; } catch {}
|
|
1141
|
-
log(`이미 최신 — bundle v${pkgVer} 일치, progress v${TARGET_PROGRESS_VERSION}, config.
|
|
1205
|
+
log(`이미 최신 — bundle v${pkgVer} 일치, progress v${TARGET_PROGRESS_VERSION}, config.company_mode, memory 시스템 entry, gotcha entry 모두 sync.`);
|
|
1142
1206
|
return;
|
|
1143
1207
|
}
|
|
1144
1208
|
|
|
@@ -1154,35 +1218,37 @@ function runMigrate(opts = {}) {
|
|
|
1154
1218
|
if (flags.progressV3toV4 && fs.existsSync(progressPath)) {
|
|
1155
1219
|
const original = fs.readFileSync(progressPath, 'utf8');
|
|
1156
1220
|
const p = JSON.parse(original);
|
|
1157
|
-
const oldMode = p.mode ?? '
|
|
1221
|
+
const oldMode = p.mode ?? 'company';
|
|
1158
1222
|
const newP = {
|
|
1159
1223
|
...p,
|
|
1160
1224
|
version: TARGET_PROGRESS_VERSION,
|
|
1161
|
-
mode: '
|
|
1225
|
+
mode: 'company',
|
|
1162
1226
|
mode_decision: {
|
|
1163
1227
|
owner: 'conductor',
|
|
1164
1228
|
decided_at: null,
|
|
1165
|
-
rationale: `migration
|
|
1166
|
-
|
|
1229
|
+
rationale: `migration — legacy mode="${oldMode}" normalized to company`,
|
|
1230
|
+
policy: 'always_company_parallel',
|
|
1231
|
+
user_override: null,
|
|
1167
1232
|
},
|
|
1168
1233
|
};
|
|
1169
|
-
log(` progress.json: version 3 → ${TARGET_PROGRESS_VERSION}, mode "${oldMode}" → "
|
|
1234
|
+
log(` progress.json: version 3 → ${TARGET_PROGRESS_VERSION}, mode "${oldMode}" → "company"`);
|
|
1170
1235
|
if (!dryRun) {
|
|
1171
1236
|
fs.writeFileSync(path.join(backupDir, 'progress.json'), original);
|
|
1172
1237
|
fs.writeFileSync(progressPath, JSON.stringify(newP, null, 2) + '\n');
|
|
1173
1238
|
}
|
|
1174
1239
|
}
|
|
1175
1240
|
|
|
1176
|
-
// 2. config.json — inject
|
|
1241
|
+
// 2. config.json — inject company_mode from template if missing
|
|
1177
1242
|
const configPath = path.join(HARNESS_DIR, 'config.json');
|
|
1178
1243
|
const tplPath = path.join(PKG_ROOT, 'assets', 'templates', 'config.json');
|
|
1179
|
-
if (flags.
|
|
1244
|
+
if (flags.configMissingCompanyMode && fs.existsSync(configPath) && fs.existsSync(tplPath)) {
|
|
1180
1245
|
const original = fs.readFileSync(configPath, 'utf8');
|
|
1181
1246
|
const c = JSON.parse(original);
|
|
1182
1247
|
const tpl = JSON.parse(fs.readFileSync(tplPath, 'utf8'));
|
|
1183
|
-
if (tpl.
|
|
1184
|
-
c.
|
|
1185
|
-
|
|
1248
|
+
if (tpl.company_mode) {
|
|
1249
|
+
c.company_mode = tpl.company_mode;
|
|
1250
|
+
delete c.mode_selection;
|
|
1251
|
+
log(' config.json: company_mode 섹션 주입 (always-on company parallel)');
|
|
1186
1252
|
if (!dryRun) {
|
|
1187
1253
|
fs.writeFileSync(path.join(backupDir, 'config.json'), original);
|
|
1188
1254
|
fs.writeFileSync(configPath, JSON.stringify(c, null, 2) + '\n');
|
|
@@ -1237,6 +1303,30 @@ function runMigrate(opts = {}) {
|
|
|
1237
1303
|
}
|
|
1238
1304
|
}
|
|
1239
1305
|
|
|
1306
|
+
// 4b. convention entry merge — append bundled system [C-*] entries while
|
|
1307
|
+
// preserving user-authored [C-NNN] history.
|
|
1308
|
+
const conventionMissing = flags.conventionMissingEntries || {};
|
|
1309
|
+
for (const [file, missingIds] of Object.entries(conventionMissing)) {
|
|
1310
|
+
if (!missingIds.length) continue;
|
|
1311
|
+
const srcPath = path.join(PKG_ROOT, 'conventions', file);
|
|
1312
|
+
const destPath = path.join(HARNESS_DIR, 'conventions', file);
|
|
1313
|
+
if (!fs.existsSync(srcPath) || !fs.existsSync(destPath)) continue;
|
|
1314
|
+
const original = fs.readFileSync(destPath, 'utf8');
|
|
1315
|
+
const tpl = fs.readFileSync(srcPath, 'utf8');
|
|
1316
|
+
const blocks = [];
|
|
1317
|
+
for (const id of missingIds) {
|
|
1318
|
+
const block = extractConventionEntryBlock(tpl, id);
|
|
1319
|
+
if (block) blocks.push(block);
|
|
1320
|
+
}
|
|
1321
|
+
if (!blocks.length) continue;
|
|
1322
|
+
log(` conventions/${file}: 시스템 entry ${blocks.length}개 append (${missingIds.join(', ')})`);
|
|
1323
|
+
if (!dryRun) {
|
|
1324
|
+
fs.writeFileSync(path.join(backupDir, `conventions-${file}`), original);
|
|
1325
|
+
const sep = original.endsWith('\n') ? '\n' : '\n\n';
|
|
1326
|
+
fs.writeFileSync(destPath, original + sep + blocks.join('\n\n') + '\n');
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1240
1330
|
// 5. Bundle version stamp — record which package version was last applied.
|
|
1241
1331
|
if (flags.bundleVersionStale) {
|
|
1242
1332
|
const { current, installed } = flags.bundleVersionStale;
|
|
@@ -1252,7 +1342,7 @@ function runMigrate(opts = {}) {
|
|
|
1252
1342
|
} else {
|
|
1253
1343
|
log(`Migration 완료. 백업: ${backupDir}`);
|
|
1254
1344
|
log('Conductor 가 다음 sprint 시작 시 자동으로 모드 결정합니다.');
|
|
1255
|
-
log('
|
|
1345
|
+
log('회사모드는 기본값이며 사용자 override 는 사용하지 않습니다.');
|
|
1256
1346
|
}
|
|
1257
1347
|
console.log('');
|
|
1258
1348
|
}
|
|
@@ -1325,17 +1415,17 @@ function runVerify() {
|
|
|
1325
1415
|
}
|
|
1326
1416
|
}
|
|
1327
1417
|
|
|
1328
|
-
// 3) config.json
|
|
1418
|
+
// 3) config.json company_mode
|
|
1329
1419
|
const configPath = path.join(HARNESS_DIR, 'config.json');
|
|
1330
1420
|
if (fs.existsSync(configPath)) {
|
|
1331
1421
|
try {
|
|
1332
1422
|
const c = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
1333
|
-
if (!c.
|
|
1334
|
-
issues.push(' ✗ config.json:
|
|
1423
|
+
if (!c.company_mode) {
|
|
1424
|
+
issues.push(' ✗ config.json: company_mode 누락 → npx walwal-harness migrate');
|
|
1335
1425
|
fail++;
|
|
1336
1426
|
} else {
|
|
1337
1427
|
pass++;
|
|
1338
|
-
log(` config.json:
|
|
1428
|
+
log(` config.json: company_mode.owner=${c.company_mode.owner} OK`);
|
|
1339
1429
|
}
|
|
1340
1430
|
} catch {}
|
|
1341
1431
|
}
|
|
@@ -1378,11 +1468,6 @@ function main() {
|
|
|
1378
1468
|
return;
|
|
1379
1469
|
}
|
|
1380
1470
|
|
|
1381
|
-
if (subcommand === 'team') {
|
|
1382
|
-
runTeamStudio();
|
|
1383
|
-
return;
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
1471
|
if (subcommand === 'migrate') {
|
|
1387
1472
|
runMigrate({ dryRun: args.includes('--dry-run') });
|
|
1388
1473
|
return;
|
|
@@ -1393,10 +1478,14 @@ function main() {
|
|
|
1393
1478
|
return;
|
|
1394
1479
|
}
|
|
1395
1480
|
|
|
1396
|
-
// Legacy subcommands —
|
|
1397
|
-
if (subcommand === '
|
|
1398
|
-
|
|
1399
|
-
|
|
1481
|
+
// Legacy subcommands — removed as of v6.x (always-on company mode).
|
|
1482
|
+
if (subcommand === 'company' || subcommand === 'studio' ||
|
|
1483
|
+
subcommand === 'studio-v4' || subcommand === 'v4') {
|
|
1484
|
+
log(`NOTE: "${subcommand}" subcommand was removed.`);
|
|
1485
|
+
log('회사모드는 항상 켜져 있는 기본 런타임입니다. 별도 launch 명령은 필요 없습니다.');
|
|
1486
|
+
log(' - turn 자동 연쇄: Stop 훅 (자동)');
|
|
1487
|
+
log(' - 1시간 안전망 wake: bash scripts/harness-wake-install.sh install .');
|
|
1488
|
+
log(' - 시각화: bash scripts/harness-dashboard-up.sh');
|
|
1400
1489
|
return;
|
|
1401
1490
|
}
|
|
1402
1491
|
|
|
@@ -1418,6 +1507,7 @@ function main() {
|
|
|
1418
1507
|
installSessionHook();
|
|
1419
1508
|
installStatusline();
|
|
1420
1509
|
installUserPromptSubmitHook();
|
|
1510
|
+
installStopHook();
|
|
1421
1511
|
installAgentTeamsEnv();
|
|
1422
1512
|
setupAgentsMd();
|
|
1423
1513
|
checkPlaywrightMcp();
|
|
@@ -1428,7 +1518,7 @@ function main() {
|
|
|
1428
1518
|
const gotchaMissing = Object.keys(migFlags.gotchaMissingEntries || {}).length > 0;
|
|
1429
1519
|
const hasContentDrift =
|
|
1430
1520
|
migFlags.progressV3toV4 ||
|
|
1431
|
-
migFlags.
|
|
1521
|
+
migFlags.configMissingCompanyMode ||
|
|
1432
1522
|
(migFlags.memoryMissingSystemEntries && migFlags.memoryMissingSystemEntries.length) ||
|
|
1433
1523
|
gotchaMissing;
|
|
1434
1524
|
if (hasContentDrift) {
|
|
@@ -1462,14 +1552,13 @@ function main() {
|
|
|
1462
1552
|
log('║ Then say: "하네스 엔지니어링 시작" ║');
|
|
1463
1553
|
log('║ Or invoke: /harness-dispatcher ║');
|
|
1464
1554
|
log('║ ║');
|
|
1465
|
-
log('║ 기본은
|
|
1466
|
-
log('║ 병렬 3팀 실행을 원하면 파이프라인 확정 후 /harness-team ║');
|
|
1555
|
+
log('║ 기본은 회사모드: 병렬 · 자율 진행. 추가 입력 불필요. ║');
|
|
1467
1556
|
log('╚═══════════════════════════════════════════════════════════╝');
|
|
1468
1557
|
} else {
|
|
1469
1558
|
log('Next steps:');
|
|
1470
1559
|
log(' 1. Restart Claude Code session (/exit → re-enter directory)');
|
|
1471
1560
|
log(' 2. Say "하네스 엔지니어링 시작" or /harness-dispatcher');
|
|
1472
|
-
log(' 3.
|
|
1561
|
+
log(' 3. 회사모드가 자동으로 병렬 실행합니다. 진행 여부를 묻지 않습니다.');
|
|
1473
1562
|
}
|
|
1474
1563
|
console.log('');
|
|
1475
1564
|
}
|