@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.
Files changed (74) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +43 -74
  3. package/assets/launchd/com.walwal.harness-wake.plist.template +32 -0
  4. package/assets/templates/CONVENTIONS.md +12 -0
  5. package/assets/templates/HARNESS.md +216 -403
  6. package/assets/templates/config.json +27 -29
  7. package/assets/templates/memory.md +16 -2
  8. package/assets/templates/progress.json.template +6 -5
  9. package/bin/init.js +179 -90
  10. package/conventions/README.md +92 -0
  11. package/conventions/conductor.md +24 -0
  12. package/conventions/coo-developer.md +24 -0
  13. package/conventions/cqo.md +24 -0
  14. package/conventions/cto.md +24 -0
  15. package/conventions/dispatcher.md +24 -0
  16. package/conventions/documentationer.md +24 -0
  17. package/conventions/evaluator-architecture.md +24 -0
  18. package/conventions/evaluator-code-quality.md +24 -0
  19. package/conventions/evaluator-functional.md +24 -0
  20. package/conventions/evaluator-security.md +24 -0
  21. package/conventions/evaluator-visual.md +24 -0
  22. package/conventions/generator-backend.md +24 -0
  23. package/conventions/generator-designer.md +24 -0
  24. package/conventions/generator-devops.md +24 -0
  25. package/conventions/generator-frontend.md +24 -0
  26. package/conventions/meeting-manager.md +24 -0
  27. package/conventions/planner.md +24 -0
  28. package/conventions/service-ops.md +24 -0
  29. package/conventions/shared.md +40 -0
  30. package/gotchas/conductor.md +16 -0
  31. package/gotchas/dispatcher.md +16 -0
  32. package/gotchas/meeting-manager.md +8 -0
  33. package/gotchas/service-ops.md +8 -0
  34. package/package.json +5 -4
  35. package/scripts/conductor-tick.sh +61 -53
  36. package/scripts/harness-archive.sh +7 -5
  37. package/scripts/harness-dashboard-up.sh +11 -3
  38. package/scripts/harness-hourly-review.sh +303 -0
  39. package/scripts/harness-meeting-doc.sh +55 -16
  40. package/scripts/harness-next.sh +1 -1
  41. package/scripts/harness-queue-manager.sh +12 -5
  42. package/scripts/harness-service-ops-monitor.sh +228 -0
  43. package/scripts/harness-session-start.sh +60 -31
  44. package/scripts/harness-statusline.sh +8 -12
  45. package/scripts/harness-stop.sh +85 -0
  46. package/scripts/harness-user-prompt-submit.sh +11 -29
  47. package/scripts/harness-wake-install.sh +179 -0
  48. package/scripts/harness-wake.sh +258 -0
  49. package/scripts/harness-worker-dispatch.sh +157 -0
  50. package/scripts/lib/harness-progress-migrate.sh +6 -1
  51. package/scripts/lib/harness-render-progress.sh +3 -3
  52. package/skills/brainstorming/SKILL.md +2 -2
  53. package/skills/conductor/SKILL.md +67 -55
  54. package/skills/cqo/SKILL.md +1 -0
  55. package/skills/cto/SKILL.md +1 -0
  56. package/skills/dispatcher/SKILL.md +33 -14
  57. package/skills/evaluator-code-quality/SKILL.md +4 -4
  58. package/skills/evaluator-functional/SKILL.md +6 -6
  59. package/skills/evaluator-visual/SKILL.md +4 -4
  60. package/skills/generator-backend/SKILL.md +4 -4
  61. package/skills/generator-frontend/SKILL.md +5 -5
  62. package/skills/meeting-manager/SKILL.md +82 -12
  63. package/skills/planner/SKILL.md +3 -3
  64. package/skills/service-ops/SKILL.md +25 -0
  65. package/commands/harness-solo.md +0 -103
  66. package/commands/harness-stop.md +0 -53
  67. package/commands/harness-team.md +0 -530
  68. package/scripts/harness-dashboard.sh +0 -509
  69. package/scripts/harness-goal-init.sh +0 -72
  70. package/scripts/harness-goal-show.sh +0 -37
  71. package/scripts/harness-gotcha-memory.sh +0 -348
  72. package/scripts/harness-monitor.sh +0 -398
  73. package/scripts/harness-prompt-history.sh +0 -164
  74. package/scripts/harness-tmux.sh +0 -372
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "harness": {
3
- "name": "7-Agent Production Harness",
4
- "version": "5.6.0",
5
- "description": "Solo/Team 통합 하네스 — Dispatcher + 3-Evaluator Chain (Code-Quality → Functional → Visual) + Hierarchical Conventions/Gotchas + NestJS MSA + React/Next.js + Playwright",
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": "Team Mode 설정. /harness-team 커맨드로 실행.",
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
- "mode_selection": {
353
- "comment": "v6.0 — Solo/Team 모드를 사용자가 아닌 Conductor 가 결정. progress.json.mode='auto' 일 때 Conductor 가 feature-list 의 ready_count + 의존성 깊이 + sprint feature 수를 보고 룰을 적용. 사용자는 /harness-solo 또는 /harness-team 으로 override 가능하며 그 결정은 progress.json.mode_decision.user_override 에 기록된다.",
354
- "default_mode": "auto",
352
+ "company_mode": {
353
+ "comment": "v6.3 — 회사모드는 항시 활성이다. 사용자 모드 선택과 tie-breaker 는 제거됐다. Conductor 는 feature-list/queue 를 기반으로 매 tick 가능한 병렬 작업을 자동 배정한다.",
354
+ "default_mode": "company",
355
355
  "owner": "conductor",
356
- "rules": {
357
- "force_team_when": {
358
- "ready_at_start_gte": 3,
359
- "feature_count_gte": 6,
360
- "critical_path_depth_lte": 2,
361
- "rationale": "ready≥3 + 작은 critical path = 3팀 동시 가동의 P4 Parallel Workstreams 효과 충족 (NEXUS 40~60% 단축)"
362
- },
363
- "force_solo_when": {
364
- "ready_at_start_lte": 2,
365
- "or_feature_count_lte": 3,
366
- "or_critical_path_depth_gte": 4,
367
- "rationale": "직렬 의존 우세 → Team 으로 띄워도 2팀이 유휴. tmux 운영 비용·토큰 3x 정당화 안 됨."
368
- },
369
- "tie_breaker": "solo",
370
- "tie_breaker_rationale": "동률 시 비용 안전한 Solo 선호. 사용자가 명시적으로 team 선택 시만 force."
371
- },
372
- "user_override": {
373
- "comment": "사용자가 명시적으로 모드 강제하면 Conductor 결정 무시. progress.json.mode_decision.user_override 에 기록.",
374
- "channels": [
375
- "/harness-solo (force solo)",
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**: Team Worker(Generator + Evaluator), Solo Mode Generator/Evaluator, 모든 `logev` 호출 지점.
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, mode 결정 (config.json `mode_selection.rules`).
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": "auto",
3
+ "mode": "company",
4
4
  "mode_decision": {
5
5
  "owner": "conductor",
6
6
  "decided_at": null,
7
- "rationale": null,
8
- "user_override": null
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
- "team_state": {
51
- "active_teams": 0,
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-\d+\]/m;
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 = progress.mode || 'solo';
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 Team Mode isolation
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 Team Mode isolation');
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 team Launch Team Mode (tmux studio + auto teams)
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
- Modes (v6.0+ — Conductor 자동 결정, 사용자 override 가능):
916
- /harness-solo Solo Override — Conductor 결정을 Solo 로 강제
917
- /harness-team Team Override — Conductor 결정을 Team 으로 강제 (tmux 부팅)
918
- /harness-stop Stop Team mode (preserves queue state)
919
- 발화 "auto 로 돌려" → Conductor 자동결정 복귀
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. Installs commands to .claude/commands/ (solo, team, stop)
925
- 4. Copies helper scripts to scripts/
926
- 5. Registers SessionStart + UserPromptSubmit hooks
927
- 6. Installs statusline (persistent 1-line status bar)
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 Planner completes: /harness-team (team) or continue prompting (solo)
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
- // Main
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
- configMissingModeSelection: false,
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.configMissingModeSelection = true;
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: v3 → v4 (mode → "auto" + mode_decision 추가)');
1096
- console.log(' 기존 mode 값은 user_override 로 보존됩니다.');
1152
+ console.log(' • progress.json: legacy mode → "company"');
1153
+ console.log(' legacy mode 값은 보존하지 않고 회사모드로 정규화됩니다.');
1097
1154
  }
1098
- if (flags.configMissingModeSelection) {
1099
- console.log(' • config.json: mode_selection 섹션 누락 — 자동 주입 가능');
1100
- console.log(' Conductor 가 ready≥3 + features≥6 + depth≤2 로 자동 모드 결정.');
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.configMissingModeSelection &&
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.mode_selection, memory 시스템 entry, gotcha entry 모두 sync.`);
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 ?? 'solo';
1221
+ const oldMode = p.mode ?? 'company';
1158
1222
  const newP = {
1159
1223
  ...p,
1160
1224
  version: TARGET_PROGRESS_VERSION,
1161
- mode: 'auto',
1225
+ mode: 'company',
1162
1226
  mode_decision: {
1163
1227
  owner: 'conductor',
1164
1228
  decided_at: null,
1165
- rationale: `migration v3→v4 — 기존 mode="${oldMode}" 를 user_override 로 보존`,
1166
- user_override: oldMode === 'team' || oldMode === 'solo' ? oldMode : null,
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}" → "auto" (user_override="${newP.mode_decision.user_override}")`);
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 mode_selection from template if missing
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.configMissingModeSelection && fs.existsSync(configPath) && fs.existsSync(tplPath)) {
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.mode_selection) {
1184
- c.mode_selection = tpl.mode_selection;
1185
- log(' config.json: mode_selection 섹션 주입 (default_mode=auto, owner=conductor, force_team_when ready≥3+features≥6+depth≤2, tie_breaker=solo)');
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('자동 결정 무시하려면 /harness-solo 또는 /harness-team override.');
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 mode_selection
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.mode_selection) {
1334
- issues.push(' ✗ config.json: mode_selection 누락 → npx walwal-harness migrate');
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: mode_selection.owner=${c.mode_selection.owner} OK`);
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 — redirect to new equivalents
1397
- if (subcommand === 'studio' || subcommand === 'studio-v4' || subcommand === 'v4') {
1398
- log('NOTE: "studio" and "v4" subcommands are replaced by "team".');
1399
- runTeamStudio();
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.configMissingModeSelection ||
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('║ 기본은 Solo 모드 (순차 진행) — 입력 불필요. ║');
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. Solo 모드는 자동. Team 모드 원할 때만 파이프라인 확정 후 /harness-team');
1561
+ log(' 3. 회사모드가 자동으로 병렬 실행합니다. 진행 여부를 묻지 않습니다.');
1473
1562
  }
1474
1563
  console.log('');
1475
1564
  }