@walwal-harness/cli 6.0.3 → 6.0.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 CHANGED
@@ -29,6 +29,49 @@ docmeta:
29
29
 
30
30
  # Changelog
31
31
 
32
+ ## 6.0.5 — migrate 가 gotcha entry / bundle version 까지 sync (2026-05-07)
33
+
34
+ ### Why
35
+ Owner 보고 — `npm install @walwal-harness/cli@latest && npx walwal-harness migrate` 가 "이미 최신 버전입니다" 로 종료. 그러나 v6.0.4 에서 새로 추가된 `gotchas/conductor.md` 의 [G-005], [G-006], `gotchas/service-ops.md`, `gotchas/generator-frontend.md [G-001]` 가 사용자 프로젝트에 merge 되지 않음. **존재 여부 (3 항목 — progress v4 / config.mode_selection / M-NEXUS-P3) 만 확인하고 콘텐츠는 비교 안 함** → 본질적 버그.
36
+
37
+ ### Changes
38
+ - **bin/init.js `detectMigrationNeeded`** — 두 신규 시그널 추가:
39
+ - `gotchaMissingEntries`: `gotchas/*.md` 별로 패키지 → 사용자 [G-NNN] entry 차이 산출 (memory.md 패턴과 동일)
40
+ - `bundleVersionStale`: `.harness/.bundle-version` 스탬프와 패키지 버전 비교
41
+ - **bin/init.js `runMigrate`** — gotcha entry append + bundle stamp 갱신 단계 추가. 사용자가 직접 추가한 [G-NNN] entry 는 절대 건드리지 않으며, 패키지의 신규 시스템 entry 만 끝에 추가.
42
+ - **bin/init.js `extractGotchaEntryBlock` / `extractEntryBlock`** — JS 정규식 `\Z` 미지원 fix. 마지막 entry 가 추출 누락되던 버그 동시 해결 (v6.0.2 부터 잠재).
43
+ - **bin/init.js main flow** — 콘텐츠 드리프트 없이 stamp 만 누락된 경우 init 종료 시 자동 갱신. stamp 만으로 매번 migrate 권유 알림이 뜨는 것 방지.
44
+
45
+ ### Migration
46
+ ```bash
47
+ npm install @walwal-harness/cli@latest
48
+ npx walwal-harness migrate # gotchas/conductor.md G-005/G-006, gotchas/service-ops.md, gotchas/generator-frontend.md G-001 자동 append + stamp 갱신
49
+ npx walwal-harness verify
50
+ ```
51
+ 사용자 [G-NNN] entry 는 보존되고 시스템 entry 만 끝에 append. 변경 전 `.harness/archive/migration-<ts>/gotchas-<file>` 백업.
52
+
53
+ ## 6.0.4 — Sub-skill 협업 + Service-Ops 상시 + Team 병렬 spawn (2026-05-07)
54
+
55
+ ### Why
56
+ moon_web 사용자 보고 — v6.0.3 이후에도 (a) CTO-Frontend 가 흡수된 agency-agents sub-skill (UX/UI/A11y/Perf) 을 호출하지 않고 단독 작업, (b) build/test 중 Service-Ops 자리 비움 → stderr 의 컴파일/테스트 에러 무감시, (c) `mode=team` 인데도 Conductor 가 직렬로 1개씩 spawn 하여 Team 자동 결정 룰 (ready≥3, features≥6, depth≤2) 만족이 무의미해짐. 세 가지는 형식상 v6 였지만 실효는 6.0 이전 수준.
57
+
58
+ ### Changes
59
+ - **gotchas/generator-frontend.md [G-001]** — Sprint Workflow Step 2 에 5개 sub-skill 순차 호출 의무 (engineering-{react,flutter}-developer / design/ux-strategy / design/ui-component-spec / engineering-accessibility-reviewer / engineering-performance-engineer). 결과는 sprint-contract.md FE 섹션 `## Sub-skill Findings` 에 인용. 빈 블록 = Evaluator-Code-Quality FAIL.
60
+ - **gotchas/service-ops.md [G-001]** (신규) — build/test/deploy spawn 직전 동일 tick 에서 monitor stream-mode 동반 활성. 정규식 `(error|exception|TestFailure|Cannot find|Failed to compile)` 매칭 시 즉시 red-alert.
61
+ - **gotchas/conductor.md [G-005]** — Team mode 직렬 회귀 금지. 매 tick 시작 시 ready 목록 계산 → `min(ready, 3)` 동시 spawn + `team_state.team_<n>.assigned_feature/assigned_agent` 갱신 + `meetings.active=["t1-lead","t2-lead","t3-lead"]` standup 시각화.
62
+ - **gotchas/conductor.md [G-006]** — Service-Ops monitor 동반 spawn 룰을 Conductor SKILL §5 spawn 결정 트리에 명시.
63
+ - **skills/generator-frontend/SKILL.md** — Sprint Workflow §2.1 sub-skill 호출 시퀀스 표 + visibility partial update 패턴.
64
+ - **skills/conductor/SKILL.md** — §5 spawn 결정 트리에 두 라인 추가 + §5.1 Team mode 병렬 알고리즘 + §5.2 Service-Ops 동반 spawn 의사코드.
65
+
66
+ ### Migration
67
+ 기존 사용자:
68
+ ```bash
69
+ npm install @walwal-harness/cli@latest
70
+ npx walwal-harness migrate # gotchas/service-ops.md 자동 추가
71
+ npx walwal-harness verify
72
+ ```
73
+ sub-skill 호출은 generator-frontend SKILL.md 가 갱신되면 다음 sprint 부터 자동 적용. 진행 중 sprint 의 FE 섹션은 Eval-Code-Quality 가 빈 `## Sub-skill Findings` 를 잡아내며 retry 유도.
74
+
32
75
  ## 6.0.3 — Visibility + Spawn 검증 + 사용자 슬래시 노출 제거 (2026-05-07)
33
76
 
34
77
  Owner 의 두 지적을 동시 해결:
package/bin/init.js CHANGED
@@ -988,7 +988,41 @@ function detectMigrationNeeded() {
988
988
  progressV3toV4: false,
989
989
  configMissingModeSelection: false,
990
990
  memoryMissingSystemEntries: [],
991
+ gotchaMissingEntries: {}, // { "<filename>": [G-IDs...] }
992
+ bundleVersionStale: null, // { current, installed }
991
993
  };
994
+
995
+ // Gotcha entry-level diff: for each bundled gotcha file, compare entry IDs.
996
+ // 사용자가 직접 추가한 [G-NNN] 은 절대 건드리지 않으며, 패키지에서 새로
997
+ // 도입된 시스템 entry 만 append 대상.
998
+ const gotchasSrc = path.join(PKG_ROOT, 'gotchas');
999
+ const gotchasDest = path.join(HARNESS_DIR, 'gotchas');
1000
+ if (fs.existsSync(gotchasSrc) && fs.existsSync(gotchasDest)) {
1001
+ const files = fs.readdirSync(gotchasSrc).filter((f) => f.endsWith('.md') && f !== 'README.md');
1002
+ for (const file of files) {
1003
+ const srcPath = path.join(gotchasSrc, file);
1004
+ const destPath = path.join(gotchasDest, file);
1005
+ if (!fs.existsSync(destPath)) continue; // installSkills/init 이 처리
1006
+ try {
1007
+ const srcIds = extractGotchaEntryIds(fs.readFileSync(srcPath, 'utf8'));
1008
+ const dstIds = new Set(extractGotchaEntryIds(fs.readFileSync(destPath, 'utf8')));
1009
+ const missing = srcIds.filter((id) => !dstIds.has(id));
1010
+ if (missing.length) flags.gotchaMissingEntries[file] = missing;
1011
+ } catch {}
1012
+ }
1013
+ }
1014
+
1015
+ // Bundle version stamp: detects fresh package vs. last-applied bundle.
1016
+ // Even if the 3 schema flags pass, a newer bundle may have introduced new
1017
+ // gotcha/SKILL/template content the user hasn't seen yet.
1018
+ try {
1019
+ const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8'));
1020
+ const stampPath = path.join(HARNESS_DIR, '.bundle-version');
1021
+ const installed = fs.existsSync(stampPath) ? fs.readFileSync(stampPath, 'utf8').trim() : null;
1022
+ if (installed !== pkg.version) {
1023
+ flags.bundleVersionStale = { current: pkg.version, installed };
1024
+ }
1025
+ } catch {}
992
1026
  if (fs.existsSync(progressPath)) {
993
1027
  try {
994
1028
  const p = JSON.parse(fs.readFileSync(progressPath, 'utf8'));
@@ -1025,7 +1059,29 @@ function extractEntryIds(md) {
1025
1059
  // Extracts a single entry block (heading + body until next ### or EOF) from the template.
1026
1060
  function extractEntryBlock(md, id) {
1027
1061
  const escId = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1028
- const re = new RegExp(`(^### \\[${escId}\\][\\s\\S]*?)(?=^### \\[|\\Z)`, 'm');
1062
+ const re = new RegExp(`(^### \\[${escId}\\][\\s\\S]*?)(?=^### \\[|$(?![\\s\\S]))`, 'm');
1063
+ const m = md.match(re);
1064
+ return m ? m[1].trimEnd() : null;
1065
+ }
1066
+
1067
+ // Returns gotcha entry IDs ("[G-001]", "[G-NEXUS-P3]" etc.) found in a markdown body.
1068
+ // Supports both `### [G-NNN]` and `## [G-NNN] ...` heading levels.
1069
+ function extractGotchaEntryIds(md) {
1070
+ const re = /^#{2,3}\s+\[(G-[A-Z0-9_-]+)\]/gm;
1071
+ const ids = [];
1072
+ let m;
1073
+ while ((m = re.exec(md)) !== null) ids.push(m[1]);
1074
+ return ids;
1075
+ }
1076
+
1077
+ // Extracts a single gotcha entry block from a markdown body.
1078
+ function extractGotchaEntryBlock(md, id) {
1079
+ const escId = id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1080
+ // JS lacks \Z; emulate end-of-string via (?![\s\S]) negative lookahead.
1081
+ const re = new RegExp(
1082
+ `(^#{2,3}\\s+\\[${escId}\\][\\s\\S]*?)(?=^#{2,3}\\s+\\[G-|$(?![\\s\\S]))`,
1083
+ 'm',
1084
+ );
1029
1085
  const m = md.match(re);
1030
1086
  return m ? m[1].trimEnd() : null;
1031
1087
  }
@@ -1048,6 +1104,17 @@ function showMigrationProposal(flags) {
1048
1104
  console.log(' [' + flags.memoryMissingSystemEntries.join(', ') + ']');
1049
1105
  console.log(' 사용자 [M-NNN] entry 는 보존, 시스템 entry 만 끝에 추가.');
1050
1106
  }
1107
+ const gotchaSummary = Object.entries(flags.gotchaMissingEntries || {});
1108
+ if (gotchaSummary.length) {
1109
+ console.log(' • gotchas/: 시스템 entry 누락 — entry-level append 가능 (사용자 [G-NNN] 보존)');
1110
+ for (const [file, ids] of gotchaSummary) {
1111
+ console.log(` ${file}: [${ids.join(', ')}]`);
1112
+ }
1113
+ }
1114
+ if (flags.bundleVersionStale) {
1115
+ const { current, installed } = flags.bundleVersionStale;
1116
+ console.log(` • bundle: ${installed ?? '(없음)'} → ${current} 스탬프 갱신 필요`);
1117
+ }
1051
1118
  console.log('');
1052
1119
  console.log(' 적용: npx walwal-harness migrate');
1053
1120
  console.log(' 미리보기: npx walwal-harness migrate --dry-run');
@@ -1060,13 +1127,18 @@ function showMigrationProposal(flags) {
1060
1127
  function runMigrate(opts = {}) {
1061
1128
  const dryRun = opts.dryRun || false;
1062
1129
  const flags = detectMigrationNeeded();
1130
+ const gotchaMissingTotal = Object.values(flags.gotchaMissingEntries || {}).reduce((n, a) => n + a.length, 0);
1063
1131
  if (
1064
1132
  !flags.progressV3toV4 &&
1065
1133
  !flags.configMissingModeSelection &&
1066
- (!flags.memoryMissingSystemEntries || flags.memoryMissingSystemEntries.length === 0)
1134
+ (!flags.memoryMissingSystemEntries || flags.memoryMissingSystemEntries.length === 0) &&
1135
+ gotchaMissingTotal === 0 &&
1136
+ !flags.bundleVersionStale
1067
1137
  ) {
1068
1138
  console.log('');
1069
- log('이미 최신 버전입니다 (progress v' + TARGET_PROGRESS_VERSION + ' + config.mode_selection + memory 시스템 entry 모두 존재).');
1139
+ let pkgVer = 'unknown';
1140
+ 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.`);
1070
1142
  return;
1071
1143
  }
1072
1144
 
@@ -1141,6 +1213,39 @@ function runMigrate(opts = {}) {
1141
1213
  }
1142
1214
  }
1143
1215
 
1216
+ // 4. gotcha entry merge — for each bundled gotcha file, append missing
1217
+ // [G-NNN] entries while preserving everything the user already has.
1218
+ const gotchaMissing = flags.gotchaMissingEntries || {};
1219
+ for (const [file, missingIds] of Object.entries(gotchaMissing)) {
1220
+ if (!missingIds.length) continue;
1221
+ const srcPath = path.join(PKG_ROOT, 'gotchas', file);
1222
+ const destPath = path.join(HARNESS_DIR, 'gotchas', file);
1223
+ if (!fs.existsSync(srcPath) || !fs.existsSync(destPath)) continue;
1224
+ const original = fs.readFileSync(destPath, 'utf8');
1225
+ const tpl = fs.readFileSync(srcPath, 'utf8');
1226
+ const blocks = [];
1227
+ for (const id of missingIds) {
1228
+ const block = extractGotchaEntryBlock(tpl, id);
1229
+ if (block) blocks.push(block);
1230
+ }
1231
+ if (!blocks.length) continue;
1232
+ log(` gotchas/${file}: 시스템 entry ${blocks.length}개 append (${missingIds.join(', ')})`);
1233
+ if (!dryRun) {
1234
+ fs.writeFileSync(path.join(backupDir, `gotchas-${file}`), original);
1235
+ const sep = original.endsWith('\n') ? '\n' : '\n\n';
1236
+ fs.writeFileSync(destPath, original + sep + blocks.join('\n\n') + '\n');
1237
+ }
1238
+ }
1239
+
1240
+ // 5. Bundle version stamp — record which package version was last applied.
1241
+ if (flags.bundleVersionStale) {
1242
+ const { current, installed } = flags.bundleVersionStale;
1243
+ log(` .bundle-version: ${installed ?? '(none)'} → ${current}`);
1244
+ if (!dryRun) {
1245
+ fs.writeFileSync(path.join(HARNESS_DIR, '.bundle-version'), current + '\n');
1246
+ }
1247
+ }
1248
+
1144
1249
  console.log('');
1145
1250
  if (dryRun) {
1146
1251
  log('Dry-run 완료 — 실제 변경 적용하려면 `npx walwal-harness migrate` 실행');
@@ -1320,8 +1425,20 @@ function main() {
1320
1425
 
1321
1426
  // v6.0 — propose migration if existing project is on v3
1322
1427
  const migFlags = detectMigrationNeeded();
1323
- if (migFlags.progressV3toV4 || migFlags.configMissingModeSelection) {
1428
+ const gotchaMissing = Object.keys(migFlags.gotchaMissingEntries || {}).length > 0;
1429
+ const hasContentDrift =
1430
+ migFlags.progressV3toV4 ||
1431
+ migFlags.configMissingModeSelection ||
1432
+ (migFlags.memoryMissingSystemEntries && migFlags.memoryMissingSystemEntries.length) ||
1433
+ gotchaMissing;
1434
+ if (hasContentDrift) {
1324
1435
  showMigrationProposal(migFlags);
1436
+ } else if (migFlags.bundleVersionStale) {
1437
+ // 콘텐츠 드리프트 없이 stamp 만 누락/오래됨 → 정합성 차원에서 직접 갱신.
1438
+ try {
1439
+ const pkg = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8'));
1440
+ fs.writeFileSync(path.join(HARNESS_DIR, '.bundle-version'), pkg.version + '\n');
1441
+ } catch {}
1325
1442
  }
1326
1443
 
1327
1444
  // v6.0 — Brick Office dashboard one-liner
@@ -68,6 +68,21 @@ docmeta:
68
68
  4. **tick 완료**: `current_agent` 그대로 (다음 에이전트 작업 중) 또는 `null` + `agent_status="completed"`
69
69
  - **Scope**: 모든 tick 사이클. 1~3 시점 누락 시 dashboard 의 회의실 텔레포트 / 미니피규어 활동 시각화 깨짐.
70
70
 
71
+ ### [G-005] Team mode 직렬 회귀 금지 — ready ≥ 2 면 병렬 spawn (status: verified)
72
+ - **Date**: 2026-05-07
73
+ - **Trigger**: Owner 보고 "솔로모드로 만 동작하는것처럼 다중 에이전트 상태가 아님" (moon_web 2026-05-07). progress.json.mode="team" 인데도 Conductor 가 한 번에 한 generator 만 spawn 하고 evaluator 핸드오프도 직렬.
74
+ - **Wrong**: Tick 마다 ready 목록에서 1개만 골라 spawn. tmux 의 T1/T2/T3 패널이 비어있어도 무시.
75
+ - **Right**: 매 tick 시작 시 ready 목록 (depends_on 충족 + agent_status≠running) 을 계산. mode="team" 이면 **min(ready, 3)** 만큼 동시 spawn — 각 팀 슬롯 (T1/T2/T3) 에 배정 + progress.json.team_state.team_<n>.assigned_feature, .assigned_agent 갱신. 동시 spawn 직전 meetings.active=["t1-lead","t2-lead","t3-lead"] 로 짧은 standup 시각화.
76
+ - **Scope**: §4 Tick Loop, §5 Spawn 결정 트리. Solo 모드는 1개 spawn 유지.
77
+ - **Why**: Team 자동 결정 룰 (ready≥3, features≥6, depth≤2) 을 만족시켜놓고 직렬로 돌리면 Team mode 가 형식상 활성이지만 실효 없음. 비용 + 시각적 신뢰 모두 낭비.
78
+
79
+ ### [G-006] Build/Deploy 실행 중 service-ops/monitor 동반 spawn (status: verified)
80
+ - **Date**: 2026-05-07
81
+ - **Trigger**: Owner 보고 "Service ops 는 build/배포 환경 로그 모니터링하면서 에러 캐치해야 하는데 공석". moon_web 빌드 중 `flutter test --platform chrome` 의 WebSocketChannelException 이 stderr 에 흘렀지만 service-ops 룸은 빈 채.
82
+ - **Wrong**: generator-frontend / evaluator-functional 이 build·test 명령을 단독 실행. cron 사이 공백 동안 stderr 무감시.
83
+ - **Right**: build/test/deploy 를 트리거하는 spawn 직전, 동일 tick 에 service-ops/monitor 를 stream-mode 로 함께 spawn (handoff-bridge). 자식 프로세스 종료 시까지 stream_active=true. 스트림 매칭 정규식 (error|exception|TestFailure|Cannot find|Failed to compile) 시 즉시 red-alert.
84
+ - **Scope**: §5 Spawn 결정 트리에 "[generator-* or evaluator-functional spawn 직전] → service-ops/monitor stream-mode 동시 spawn" 라인 추가.
85
+
71
86
  ### [G-004] Owner 에게 진행 동의 묻지 말 것 (Dispatcher [G-002] 의 Conductor 적용)
72
87
  - **Date**: 2026-05-07
73
88
  - **Status**: verified
@@ -1,5 +1,31 @@
1
+ ---
2
+ docmeta:
3
+ id: generator-frontend
4
+ title: Gotchas — Generator-Frontend
5
+ type: intermediate
6
+ createdAt: 2026-05-07T00:00:00Z
7
+ updatedAt: 2026-05-07T00:00:00Z
8
+ source:
9
+ producer: agent
10
+ skillId: harness-dispatcher
11
+ inputs: []
12
+ tags: [gotcha, generator-frontend, sub-skill, agency-agents]
13
+ ---
14
+
1
15
  # Gotchas — Generator-Frontend
2
16
 
3
17
  > Dispatcher가 관리. Generator-Frontend는 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
4
18
 
5
- <!-- 항목이 추가되면 아래에 기록됩니다 -->
19
+ ## [G-001] CTO-Frontend 단독 작업 금지 — 흡수된 agency-agents sub-skill 명시 호출 (status: verified)
20
+
21
+ - **Why**: walwal-harness v6 는 agency-agents (MIT) 의 design/UX/A11y/perf 패턴을 부서별로 흡수했지만, generator-frontend SKILL 에는 sub-skill 호출 의무가 누락되어 CTO-Frontend 가 단독으로 컴포넌트를 찍어내고 끝나는 회귀가 있었다 (moon_web 2026-05-07).
22
+ - **How to apply**:
23
+ - **Sprint Workflow Step 2 (구현)** 진입 시, 다음 sub-skill 결과를 **순차 호출 후 본 구현에 반영**한다 — 결과 미반영은 PASS 금지:
24
+ 1. **engineering-react-developer** (또는 -flutter-developer) — 컴포넌트 골격 패턴 결정
25
+ 2. **design/ux-strategy** (CTO-Designer 흡수) — 사용자 흐름 / 정보 구조 검증
26
+ 3. **design/ui-component-spec** — 토큰 / variant / state 매핑
27
+ 4. **engineering-accessibility-reviewer** — WCAG AA + 키보드 네비
28
+ 5. **engineering-performance-engineer** — RSC 우선 / hydration 비용 / IntersectionObserver
29
+ - 호출 결과는 sprint-contract.md FE 섹션의 `## Sub-skill Findings` 블록에 요약 반영. 빈 블록 = FAIL.
30
+ - tmux/3D 대시보드에 visibility 보장: 각 sub-skill 호출 직전 `progress.json.meetings.active = ["generator-frontend", "<sub-skill>"]` partial update 후 1.5s, 호출 후 `[]` 로 복귀.
31
+ - **References**: skills/cto/SKILL.md "agency-agents (MIT) 흡수" 섹션, skills/generator-designer/SKILL.md.
@@ -0,0 +1,27 @@
1
+ ---
2
+ docmeta:
3
+ id: service-ops
4
+ title: Gotchas — Service-Ops
5
+ type: intermediate
6
+ createdAt: 2026-05-07T00:00:00Z
7
+ updatedAt: 2026-05-07T00:00:00Z
8
+ source:
9
+ producer: agent
10
+ skillId: harness-dispatcher
11
+ inputs: []
12
+ tags: [gotcha, service-ops, monitor, presence]
13
+ ---
14
+
15
+ # Gotchas — Service-Ops
16
+
17
+ > Dispatcher가 관리. Service-Ops 는 세션 시작 시 이 파일을 읽고 같은 실수를 반복하지 않습니다.
18
+
19
+ ## [G-001] Build/Deploy 진행 중 자리 비움 금지 — monitor 모듈 활성 의무 (status: verified)
20
+
21
+ - **Why**: Service-Ops 의 monitor 는 cron + red-alert 이벤트로만 spawn 되도록 설계됐으나, generator/evaluator 가 build·flutter test·deploy 를 실행하는 동안에는 cron 주기 사이 공백이 길어 대시보드에서 "SERVICE-OPS 룸 빈 채" 가 관찰됨 (moon_web 2026-05-07). 빌드 stderr 의 WebSocketChannelException, 컴파일 에러, dart analyze warning 을 실시간으로 캐치하지 못하면 이후 evaluator-functional 단계에서야 발견 → 비용 증가.
22
+ - **How to apply**:
23
+ - Conductor 가 generator-* 또는 evaluator-functional 을 spawn 하기 **직전**, 동일 tick 에 service-ops/monitor 를 **stream-mode 로 함께 spawn** (handoff-bridge). progress.json.service_ops.monitor.stream_active = true.
24
+ - Stream-mode monitor 의 책임: 자식 프로세스 stdout/stderr 를 tail → 정규식 (`error:|exception|TestFailure|Cannot find|Failed to compile`) 매칭 시 즉시 conductor 에 red-alert 발행.
25
+ - 자식 프로세스 종료 시 stream_active = false + ops-report 짧은 요약 append.
26
+ - Visibility: monitor 활성 동안 progress.json.agents 에 service-ops minifig 가 service-ops 룸 (또는 모니터링 대상 룸 인접) 에 위치하도록 partial update.
27
+ - **References**: skills/service-ops/SKILL.md service_ops.monitor 섹션.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@walwal-harness/cli",
3
- "version": "6.0.3",
3
+ "version": "6.0.5",
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.",
5
5
  "bin": {
6
6
  "walwal-harness": "bin/init.js"
@@ -142,8 +142,36 @@ idle ─► running ─► (waiting_meeting | waiting_owner | running) ─► co
142
142
  [all features PASS] → Phase Gate Meeting
143
143
  [Service-Ops cron due] → spawn service-ops monitor
144
144
  [ops-report ready] → handoff to CTO (spawn cto-review)
145
+ [generator-* / eval-functional spawn 직전] → 동시 spawn service-ops/monitor (stream-mode, G-006)
146
+ [mode=team & ready ≥ 2] → 동시 spawn min(ready,3) generator/evaluator (G-005)
145
147
  ```
146
148
 
149
+ ### 5.1 Team mode 병렬 spawn (G-005)
150
+
151
+ `progress.json.mode == "team"` 이면 매 tick 시작 시 ready 목록을 계산하여 **동시 다발 spawn**:
152
+
153
+ ```bash
154
+ # ready = depends_on 충족 + agent_status != "running" 인 feature 의 다음 에이전트 목록
155
+ ready_count=$(jq '[.features[] | select(.depends_on // [] | all(. as $d | (.[$d].passes // []) | length > 0))] | length' .harness/feature-list.json)
156
+ slots=$(( ready_count < 3 ? ready_count : 3 ))
157
+ # slots 만큼 team_state.team_<n>.assigned_feature/assigned_agent 갱신 후 동시 Agent 호출
158
+ ```
159
+
160
+ 직렬 회귀 (1 spawn → 완료 대기 → 다음 spawn) 는 Solo 모드에서만 허용. Team 모드에서 1개씩 처리하면 GOTCHA G-005 위반.
161
+
162
+ ### 5.2 Service-Ops monitor 동반 spawn (G-006)
163
+
164
+ generator-{backend,frontend,frontend-flutter,devops} 또는 evaluator-functional* 을 spawn 하기 직전, **동일 tick 에서** service-ops/monitor 를 stream-mode 로 함께 spawn:
165
+
166
+ ```bash
167
+ bash scripts/harness-progress-set.sh . \
168
+ '.service_ops.monitor.stream_active = true |
169
+ .service_ops.monitor.stream_target = "generator-frontend" |
170
+ .agents += [{"id":"service-ops","room":"service-ops","minifigState":"watching"}]'
171
+ ```
172
+
173
+ 자식 프로세스 종료 시 stream_active=false + ops-report append. 빌드 stderr 의 (error|exception|TestFailure|Cannot find|Failed to compile) 매칭 → 즉시 red-alert.
174
+
147
175
  ## 6. Escalation 트리거 & 양식
148
176
 
149
177
  | 트리거 | 양식 | Owner 응답 옵션 |
@@ -86,9 +86,29 @@ Team Mode 에서 Team Worker 가 호출할 때, 프롬프트에 `FEATURE_ID` 가
86
86
  ## Sprint Workflow
87
87
 
88
88
  1. **Sprint Contract FE 섹션 추가** — 컴포넌트 / API 연동 / 성공 기준
89
- 2. **구현** — 아래 "스택 치환 규칙" 엄수
90
- 3. **Self-Verification** — `ref.validation.pre_eval_gate` 에 나열된 명령 전부 실행
91
- 4. **Handoff** → Evaluator-Functional
89
+ 2. **Sub-skill 협업 (Inviolable, gotcha G-001)** — 본 구현 전, 흡수된 agency-agents (MIT) sub-skill 결과를 순차 반영. 누락 시 Eval FAIL.
90
+ 3. **구현** — 아래 "스택 치환 규칙" 엄수, sub-skill findings 를 sprint-contract.md FE 섹션 `## Sub-skill Findings` 블록에 인용
91
+ 4. **Self-Verification** — `ref.validation.pre_eval_gate` 에 나열된 명령 전부 실행
92
+ 5. **Handoff** → Evaluator-Functional
93
+
94
+ ### 2.1 Sub-skill 호출 시퀀스 (필수)
95
+
96
+ 각 sub-skill 호출 직전 visibility partial update:
97
+ ```bash
98
+ bash scripts/harness-progress-set.sh . \
99
+ '.meetings.active = ["generator-frontend","<sub-skill>"] | .meetings.cadence = "sub-skill"'
100
+ ```
101
+ 호출 후 `meetings.active = []` 복귀.
102
+
103
+ | # | Sub-skill (agency-agents 흡수처) | 산출물 (FE 섹션 인용) |
104
+ |---|----------------------------------|------------------------|
105
+ | 1 | `engineering-{react,flutter}-developer` (CTO 흡수) | 컴포넌트 골격 + state 관리 결정 |
106
+ | 2 | `design/ux-strategy` (Generator-Designer 흡수) | 사용자 흐름 / 정보 구조 검증 |
107
+ | 3 | `design/ui-component-spec` (Generator-Designer 흡수) | 토큰 / variant / state 매핑 |
108
+ | 4 | `engineering-accessibility-reviewer` (CTO 흡수) | WCAG AA + 키보드 네비 체크 |
109
+ | 5 | `engineering-performance-engineer` (CTO 흡수) | RSC / hydration / observer 결정 |
110
+
111
+ 빈 `## Sub-skill Findings` = Eval FAIL 으로 처리됨 (Evaluator-Code-Quality 게이트).
92
112
 
93
113
  ## 스택 치환 규칙 (Adaptive Core)
94
114
 
@@ -1,109 +0,0 @@
1
- #!/bin/bash
2
- # harness-archive.sh — 스프린트 종료 시 자동 아카이빙
3
- #
4
- # 동작:
5
- # 1. .harness/actions/ 의 스프린트 산출물을 .harness/archive/D-NNN/S-NNN/ 로 이동
6
- # 2. progress.json 의 sprint 상태를 초기화 (신규 dispatch 준비)
7
- # 3. dispatch.id 가 없으면 counter++ 로 새 dispatch 시작
8
- #
9
- # 유지되는 파일 (이동하지 않음):
10
- # - .harness/gotchas/**, .harness/conventions/**, .harness/ref/**
11
- # - .harness/config.json, .harness/memory.md
12
- # - .harness/progress.json (초기화만)
13
- #
14
- # 호출 주체: harness-next.sh (next_agent="archive" 도달 시 자동)
15
-
16
- set -e
17
-
18
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
19
- source "$SCRIPT_DIR/lib/harness-render-progress.sh" 2>/dev/null || true
20
-
21
- PROJECT_ROOT="${1:-.}"
22
- PROJECT_ROOT="$(cd "$PROJECT_ROOT" && pwd)"
23
-
24
- PROGRESS="$PROJECT_ROOT/.harness/progress.json"
25
- ACTIONS_DIR="$PROJECT_ROOT/.harness/actions"
26
- ARCHIVE_ROOT="$PROJECT_ROOT/.harness/archive"
27
-
28
- if [ ! -f "$PROGRESS" ]; then
29
- echo "[archive] ERROR: progress.json not found" >&2
30
- exit 1
31
- fi
32
-
33
- command -v jq >/dev/null 2>&1 || { echo "[archive] ERROR: jq required" >&2; exit 1; }
34
-
35
- # ── Read current dispatch/sprint numbers ──
36
- dispatch_counter=$(jq -r '.dispatch.counter // 0' "$PROGRESS")
37
- dispatch_id=$(jq -r '.dispatch.id // empty' "$PROGRESS")
38
- sprint_num=$(jq -r '.sprint.number // 0' "$PROGRESS")
39
-
40
- # Ensure dispatch id exists (fallback for legacy progress.json without dispatch)
41
- if [ -z "$dispatch_id" ] || [ "$dispatch_id" = "null" ]; then
42
- if [ "$dispatch_counter" -lt 1 ]; then
43
- dispatch_counter=1
44
- fi
45
- dispatch_id=$(printf 'D-%03d' "$dispatch_counter")
46
- fi
47
-
48
- sprint_id=$(printf 'S-%03d' "$sprint_num")
49
- target_dir="$ARCHIVE_ROOT/$dispatch_id/$sprint_id"
50
-
51
- echo ""
52
- echo " ── Archive ────────────────────────────"
53
- echo " Dispatch : $dispatch_id"
54
- echo " Sprint : $sprint_id"
55
- echo " Target : ${target_dir#$PROJECT_ROOT/}"
56
-
57
- # ── Move actions/ contents into archive ──
58
- if [ -d "$ACTIONS_DIR" ] && [ "$(ls -A "$ACTIONS_DIR" 2>/dev/null)" ]; then
59
- mkdir -p "$target_dir"
60
- moved=0
61
- for f in "$ACTIONS_DIR"/*; do
62
- [ -e "$f" ] || continue
63
- name="$(basename "$f")"
64
- if [ -e "$target_dir/$name" ]; then
65
- # Collision — suffix with timestamp to avoid overwrite
66
- ts=$(date +%Y%m%d-%H%M%S)
67
- mv "$f" "$target_dir/${name%.}.${ts}"
68
- else
69
- mv "$f" "$target_dir/"
70
- fi
71
- moved=$((moved + 1))
72
- done
73
- echo " Moved : $moved file(s)"
74
- else
75
- echo " Moved : 0 file(s) (actions/ empty)"
76
- fi
77
-
78
- # ── Reset progress.json for next dispatch ──
79
- # - sprint → init
80
- # - agents → cleared
81
- # - artifacts → pending
82
- # - dispatch.id cleared (next dispatcher run will allocate new D-NNN)
83
- # - failure → cleared
84
- jq --arg now "$(date -u +%Y-%m-%dT%H:%M:%SZ)" '
85
- .pipeline = null
86
- | .dispatch.id = null
87
- | .sprint = { number: 0, status: "init", retry_count: 0 }
88
- | .current_agent = null
89
- | .agent_status = "pending"
90
- | .completed_agents = []
91
- | .next_agent = "dispatcher"
92
- | .failure = { agent: null, location: null, message: null, retry_target: null }
93
- | (.artifacts // {}) as $a
94
- | .artifacts = ($a | with_entries(
95
- if (.value | type) == "object"
96
- then .value = { status: "pending", updated_by: null, updated_at: null }
97
- else .
98
- end))
99
- | .updated_at = $now
100
- ' "$PROGRESS" > "${PROGRESS}.tmp" && mv "${PROGRESS}.tmp" "$PROGRESS"
101
-
102
- # ── Clear handoff so next session starts fresh ──
103
- HANDOFF="$PROJECT_ROOT/.harness/handoff.json"
104
- echo '{}' > "$HANDOFF"
105
-
106
- echo " Status : archived, progress reset"
107
- echo ""
108
- echo " ✓ 다음 요청은 새로운 dispatch 로 시작합니다."
109
- echo ""
@@ -1,72 +0,0 @@
1
- #!/usr/bin/env bash
2
- # walwal-harness — Brick Office dashboard launcher
3
- #
4
- # 처음 쓰는 사용자도 한 줄로 대시보드를 띄워볼 수 있게 만든 helper.
5
- # walwal-harness CLI 패키지에는 R3F + Next.js 무게 때문에 dashboard 가 미포함이고,
6
- # 본 스크립트가 git sparse-checkout 으로 apps/harness-dashboard/ 만 가져와서
7
- # 사용자 프로젝트 외부 (~/.walwal-harness/dashboard) 에 격리 설치 후 dev 실행한다.
8
- #
9
- # Usage:
10
- # bash scripts/harness-dashboard-up.sh # install + dev (port 3001)
11
- # bash scripts/harness-dashboard-up.sh --port 3050 # override port
12
- # HARNESS_ROOT=/path/to/project bash ... # explicit harness root
13
- set -euo pipefail
14
-
15
- REPO_URL="${WALWAL_HARNESS_REPO:-https://github.com/30eggis/walwal-harness.git}"
16
- DASHBOARD_PATH="apps/harness-dashboard"
17
- LOCAL_DIR="${HOME}/.walwal-harness/dashboard"
18
- PORT="${PORT:-3001}"
19
-
20
- while [[ $# -gt 0 ]]; do
21
- case "$1" in
22
- --port) PORT="$2"; shift 2 ;;
23
- --reinstall) rm -rf "$LOCAL_DIR"; shift ;;
24
- --help|-h)
25
- sed -n '2,16p' "$0"
26
- exit 0 ;;
27
- *) echo "[brick-office] unknown arg: $1" >&2; exit 2 ;;
28
- esac
29
- done
30
-
31
- if [[ -z "${HARNESS_ROOT:-}" ]]; then
32
- HARNESS_ROOT="$(pwd)"
33
- fi
34
- if [[ ! -d "${HARNESS_ROOT}/.harness" ]]; then
35
- echo "[brick-office] FAIL: ${HARNESS_ROOT} 에 .harness/ 가 없습니다."
36
- echo " 먼저 'npx walwal-harness' 로 초기화하세요."
37
- exit 3
38
- fi
39
-
40
- echo "[brick-office] HARNESS_ROOT = ${HARNESS_ROOT}"
41
- echo "[brick-office] dashboard 격리 경로 = ${LOCAL_DIR}"
42
-
43
- if [[ ! -d "${LOCAL_DIR}/${DASHBOARD_PATH}" ]]; then
44
- echo "[brick-office] 첫 실행 — git sparse-checkout 으로 dashboard 만 가져옵니다 (~5MB)."
45
- mkdir -p "${LOCAL_DIR}"
46
- cd "${LOCAL_DIR}"
47
- git init -q
48
- git remote add origin "${REPO_URL}" 2>/dev/null || true
49
- git config core.sparseCheckout true
50
- echo "${DASHBOARD_PATH}/" > .git/info/sparse-checkout
51
- git fetch --depth=1 origin main -q
52
- git checkout main -q
53
- echo "[brick-office] 다운로드 완료."
54
- fi
55
-
56
- cd "${LOCAL_DIR}/${DASHBOARD_PATH}"
57
-
58
- if [[ ! -d node_modules ]]; then
59
- echo "[brick-office] npm install (한 번만 실행, ~30s)..."
60
- npm install --no-audit --no-fund --loglevel=error
61
- fi
62
-
63
- echo ""
64
- echo "╔══════════════════════════════════════════════════════════╗"
65
- echo "║ Brick Office — http://localhost:${PORT}"
66
- echo "║ ║"
67
- echo "║ HARNESS_ROOT 이 가리키는 프로젝트의 .harness/ 상태를 ║"
68
- echo "║ 실시간 (SSE) 시각화합니다. Ctrl+C 로 종료. ║"
69
- echo "╚══════════════════════════════════════════════════════════╝"
70
- echo ""
71
-
72
- HARNESS_ROOT="${HARNESS_ROOT}" npm run dev:dashboard -- -p "${PORT}"