@walwal-harness/cli 6.1.4 → 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 (55) hide show
  1. package/CHANGELOG.md +28 -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 +8 -8
  6. package/assets/templates/config.json +25 -27
  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/shared.md +16 -0
  11. package/gotchas/conductor.md +16 -0
  12. package/gotchas/dispatcher.md +16 -0
  13. package/gotchas/meeting-manager.md +8 -0
  14. package/gotchas/service-ops.md +8 -0
  15. package/package.json +4 -4
  16. package/scripts/conductor-tick.sh +61 -53
  17. package/scripts/harness-archive.sh +7 -5
  18. package/scripts/harness-dashboard-up.sh +11 -3
  19. package/scripts/harness-hourly-review.sh +303 -0
  20. package/scripts/harness-meeting-doc.sh +55 -16
  21. package/scripts/harness-next.sh +1 -1
  22. package/scripts/harness-queue-manager.sh +12 -5
  23. package/scripts/harness-service-ops-monitor.sh +228 -0
  24. package/scripts/harness-session-start.sh +60 -31
  25. package/scripts/harness-statusline.sh +8 -12
  26. package/scripts/harness-stop.sh +85 -0
  27. package/scripts/harness-user-prompt-submit.sh +11 -29
  28. package/scripts/harness-wake-install.sh +179 -0
  29. package/scripts/harness-wake.sh +258 -0
  30. package/scripts/harness-worker-dispatch.sh +157 -0
  31. package/scripts/lib/harness-progress-migrate.sh +6 -1
  32. package/scripts/lib/harness-render-progress.sh +3 -3
  33. package/skills/brainstorming/SKILL.md +2 -2
  34. package/skills/conductor/SKILL.md +67 -55
  35. package/skills/cqo/SKILL.md +1 -0
  36. package/skills/cto/SKILL.md +1 -0
  37. package/skills/dispatcher/SKILL.md +33 -14
  38. package/skills/evaluator-code-quality/SKILL.md +4 -4
  39. package/skills/evaluator-functional/SKILL.md +6 -6
  40. package/skills/evaluator-visual/SKILL.md +4 -4
  41. package/skills/generator-backend/SKILL.md +4 -4
  42. package/skills/generator-frontend/SKILL.md +5 -5
  43. package/skills/meeting-manager/SKILL.md +82 -12
  44. package/skills/planner/SKILL.md +3 -3
  45. package/skills/service-ops/SKILL.md +25 -0
  46. package/commands/harness-solo.md +0 -103
  47. package/commands/harness-stop.md +0 -53
  48. package/commands/harness-team.md +0 -530
  49. package/scripts/harness-dashboard.sh +0 -509
  50. package/scripts/harness-goal-init.sh +0 -72
  51. package/scripts/harness-goal-show.sh +0 -37
  52. package/scripts/harness-gotcha-memory.sh +0 -348
  53. package/scripts/harness-monitor.sh +0 -398
  54. package/scripts/harness-prompt-history.sh +0 -164
  55. package/scripts/harness-tmux.sh +0 -372
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
  }
@@ -22,3 +22,19 @@ docmeta:
22
22
  > 사용자가 직접 편집해도 무방합니다. 항목 형식은 `.harness/conventions/README.md` 참고.
23
23
 
24
24
  <!-- 항목이 추가되면 아래에 기록됩니다 -->
25
+
26
+ ### [C-SYS-EXEC-ROLE-CONTRACT] 회사 구조와 임원 역할 계약은 하네스 공통 규칙이다
27
+
28
+ - **Status**: verified
29
+ - **Date**: 2026-05-10
30
+ - **Scope**: all agents
31
+ - **Rule**: walwal-harness는 "회사 구조 + 자율 구조 + 하네스"를 구현한다. CEO/COO/CTO/CQO/Service-Ops의 역할, 해야 할 일, 하면 안 되는 일은 개별 회의 프롬프트의 장식이 아니라 모든 에이전트가 세션 시작 시 적용해야 하는 하네스 공통 규칙이다.
32
+ - **Role contract**:
33
+ - `Dispatcher/CEO`: Owner와의 유일한 외부 창구. GOAL, 사업 우선순위, Owner escalation 필요성을 판정한다. 내부 해결 가능한 일을 Owner 대기로 끝내지 않는다.
34
+ - `Planner/COO`: GOAL을 work package, queue, 가설, plan으로 바꾼다. planning_drift/goal_drift를 판정하고 다음 operating cycle을 정의한다.
35
+ - `CTO`: 구현, 아키텍처, 기술선택, runtime recovery 책임자다. 서버 down이나 구현 drift를 Service-Ops 알림으로 방치하지 않고 복구/핫픽스 owner를 지정한다.
36
+ - `CQO`: 품질, 회귀, 검증 기준 책임자다. evidence 없는 PASS, 낙관론, 미검증 복구 주장을 통과시키지 않는다.
37
+ - `Service-Ops`: 운영 신호, KPI, incident, monitor cadence 책임자다. 경고를 혼자 남기고 끝내지 않고 meeting-manager를 통해 CTO/CQO action으로 연결한다.
38
+ - `Meeting-Manager`: 임원 회의를 소집/기록/디스패치한다. 회의록에는 각 role의 Position, Evidence, Action이 있어야 한다.
39
+ - **Autonomy rule**: 최초 GOAL 이후 Owner 입력은 interrupt/additional request다. 회사는 `waiting_owner`로 멈추지 않고 `meeting-manager`와 `conductor`를 통해 다음 내부 owner로 진행한다.
40
+ - **Evidence rule**: "회의했다"는 `.harness/actions/meetings/<id>/meeting-<id>.md`에 임원별 입장, 토론, decision JSON, action items가 있을 때만 말할 수 있다.
@@ -20,3 +20,19 @@ docmeta:
20
20
  > `### [G-NNN]` 항목을 append 합니다. 사용자가 직접 편집해도 무방합니다.
21
21
 
22
22
  <!-- 항목이 추가되면 아래에 기록됩니다 -->
23
+
24
+ ### [G-001] Team mode 큐 누락 금지
25
+
26
+ - Status: verified
27
+ - Date: 2026-05-08
28
+ - Trigger: `progress.json.mode == "team"` 인데 `.harness/actions/feature-queue.json` 이 없거나 teams 가 모두 idle 인 채 단일 `next_agent` 만 진행
29
+ - Rule: Team mode 진입 시 반드시 `scripts/harness-queue-manager.sh init|recover` 로 queue 를 준비하고, `auto-dispatch` 결과의 모든 pair 를 background worker 로 spawn 해야 한다. tmux monitor 만 띄우고 worker spawn 없이 종료하면 실패다.
30
+ - Evidence: okx/seller 최신 배포본에서 `mode=team` 이지만 `feature-queue.json` 이 없어 병렬 worker 가 시작될 수 없었다.
31
+
32
+ ### [G-002] Parallel tracks 직렬화 금지
33
+
34
+ - Status: verified
35
+ - Date: 2026-05-08
36
+ - Trigger: `tracks.length >= 2` 인 회의 결정 후 첫 트랙만 running, 나머지는 pending 으로 두고 완료 후 다음 트랙을 dispatch
37
+ - Rule: parallel tracks 는 같은 tick 에 모든 독립 트랙을 running/spawn 대상으로 노출해야 한다. 완료 순서만 rendezvous 에서 join 하며, dispatch 자체를 sequence 로 만들면 안 된다.
38
+ - Evidence: v6.2 fork-join 문서와 달리 conductor 구현/meeting-manager 문구가 “첫 번째 트랙 완료 후 다음 pending” 형태로 남아 있었다.
@@ -20,3 +20,19 @@ docmeta:
20
20
  > `### [G-NNN]` 항목을 append 합니다. 사용자가 직접 편집해도 무방합니다.
21
21
 
22
22
  <!-- 항목이 추가되면 아래에 기록됩니다 -->
23
+
24
+ ### [G-001] Owner GOAL 반복 확인 질문 금지
25
+
26
+ - Status: verified
27
+ - Date: 2026-05-08
28
+ - Trigger: Owner 가 이미 GOAL 을 명시했거나 `.harness/actions/pipeline.json`, `.harness/actions/goals.md`, `AGENTS.md Active dispatch` 중 하나가 현재 목표를 설명하는데도 “어떤 방식으로 진행할까요”, “다음 단계로 갈까요”, “브레인스토밍할까요”를 반복 질문
29
+ - Rule: GOAL 이 존재하면 합리적 해석으로 회사 루프를 진행한다. 질문은 GOAL 자체가 상호 배타적인 두 방향으로 해석되어 자동 선택이 위험할 때 한 번만 허용한다.
30
+ - Evidence: okx/seller에서 Owner가 목표를 준 뒤에도 같은 성격의 확인 질문이 3회 반복되어 “알아서 동작하는 회사” 모델을 깨뜨렸다.
31
+
32
+ ### [G-SYS-ROLE-CONTRACT-NOT-PROMPT-ONLY] 역할 계약을 일회성 프롬프트에만 넣지 말 것
33
+
34
+ - Status: verified
35
+ - Date: 2026-05-10
36
+ - Trigger: CEO/COO/CTO/CQO/Service-Ops가 각자 해야 할 일과 금지 사항을 이해하지 못하는데, 이를 개별 wake prompt나 회의 템플릿에만 임시로 적어 하위 에이전트에 자동 적용되지 않음.
37
+ - Rule: 회사 구조, 자율 구조, 역할별 해야 할 일/하면 안 되는 일은 하네스 공통 convention/gotcha/memory에 들어가야 한다. 개별 프롬프트는 이 SoT를 읽게 해야 하며, SoT 자체를 대체하면 안 된다.
38
+ - Fix: 구조적 규칙은 `conventions/shared.md`, 역할별 실수는 `gotchas/<role>.md`, 모든 에이전트 공통 교훈은 `memory.md`에 시스템 entry로 승격한다.
@@ -20,3 +20,11 @@ docmeta:
20
20
  > `### [G-NNN]` 항목을 append 합니다. 사용자가 직접 편집해도 무방합니다.
21
21
 
22
22
  <!-- 항목이 추가되면 아래에 기록됩니다 -->
23
+
24
+ ### [G-SYS-MINUTES-NOT-OPS-TABLE] 회의록을 Service-Ops 상태표로 대체하지 말 것
25
+
26
+ - Status: verified
27
+ - Date: 2026-05-10
28
+ - Trigger: hourly report 또는 meeting record가 서버 health table, queue 상태, "continue loop" 한 줄만 남기고 CEO/COO/CTO/CQO/Service-Ops가 무엇을 논의했는지 보여주지 않음.
29
+ - Rule: Meeting-Manager의 산출물은 운영 리포트가 아니라 임원 회의록이다. `## Role Briefs` 또는 `## Required Role Positions`에 Dispatcher/CEO, Planner/COO, CTO, CQO, Service-Ops 전원의 Position/Evidence/Action이 있어야 하며, `## Discussion`, `## Decision JSON`, `## Action Items`가 없으면 회의 완료로 보고하지 않는다.
30
+ - Fix: Service-Ops health table은 evidence로만 넣고, 토론과 decision owner/action_type으로 내부 실행 owner를 지정한다.
@@ -20,3 +20,11 @@ docmeta:
20
20
  > `### [G-NNN]` 항목을 append 합니다. 사용자가 직접 편집해도 무방합니다.
21
21
 
22
22
  <!-- 항목이 추가되면 아래에 기록됩니다 -->
23
+
24
+ ### [G-SYS-ALERT-MUST-BECOME-MEETING-ACTION] 운영 경고를 혼자 남기고 끝내지 말 것
25
+
26
+ - Status: verified
27
+ - Date: 2026-05-10
28
+ - Trigger: Service-Ops가 서버 down, health fail, KPI drift를 감지했지만 ops-report만 쓰고 CTO/CQO/Meeting-Manager action으로 연결하지 않음.
29
+ - Rule: Service-Ops의 경고는 최종 산출물이 아니라 회의 evidence다. red-alert/down/degraded가 있으면 incident-war-room 또는 followup-review를 요구하고, CTO runtime-recovery action과 CQO evidence gate가 회의록에 남도록 해야 한다.
30
+ - Fix: `.service_ops.incident.open[]`, ops-report path, health table을 evidence로 제출하고 `.meetings.requested_type`/`.meetings.requested_reason` 또는 meeting decision을 통해 다음 owner를 지정한다.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "6.1.4",
4
- "description": "Production harness for AI agent engineering — NEXUS-adapted company metaphor (Dispatcher/CEO + Conductor + Meeting-Manager + COO/Planner + CTO + CQO + Service-Ops). Solo/Team mode, Brainstormer, Planner, Generator(BE/FE/Designer/DevOps), Evaluator chain (Code-Quality → Functional → Visual + Architecture/Security). Supports React, Next.js, and Flutter FE stacks.",
3
+ "version": "6.1.5",
4
+ "description": "Production harness for AI agent engineering — NEXUS-adapted company metaphor (Dispatcher/CEO + Conductor + Meeting-Manager + COO/Planner + CTO + CQO + Service-Ops). Always-on company mode, parallel worker pool, Brainstormer, Planner, Generator(BE/FE/Designer/DevOps), Evaluator chain (Code-Quality → Functional → Visual + Architecture/Security). Supports React, Next.js, and Flutter FE stacks.",
5
5
  "bin": {
6
6
  "walwal-harness": "bin/init.js"
7
7
  },
@@ -18,8 +18,8 @@
18
18
  "codex",
19
19
  "skill",
20
20
  "command",
21
- "solo-mode",
22
- "team-mode",
21
+ "company-mode",
22
+ "parallel-workers",
23
23
  "brainstorming",
24
24
  "planner",
25
25
  "generator",