harness-bujang 0.4.0 → 0.5.1

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/README.md CHANGED
@@ -10,11 +10,11 @@ Install the [Harness-Bujang](https://github.com/bjcho4141/harness-bujang) multi-
10
10
  # Interactive setup — prompts for language, backend, etc.
11
11
  npx harness-bujang init
12
12
 
13
- # Non-interactive (CI / scripts) — accept all defaults
13
+ # Non-interactive (CI / scripts) — accept all defaults (Korean agents)
14
14
  npx harness-bujang init --yes
15
15
 
16
- # Korean agents (full 부장 persona)
17
- npx harness-bujang init --lang=ko
16
+ # English agents (default is Korean from 0.4.2+)
17
+ npx harness-bujang init --lang=en
18
18
 
19
19
  # Different folder, skip the chat-room UI
20
20
  npx harness-bujang init --target=./my-app --no-template
@@ -52,7 +52,7 @@ npx harness-bujang chat --create
52
52
  npx harness-bujang init [options]
53
53
 
54
54
  Options:
55
- --lang=<ko|en> Agent language (default: en)
55
+ --lang=<ko|en> Agent language (default: ko — full 부장 persona)
56
56
  --target=<path> Project root (default: .)
57
57
  --framework=<name> Override detected framework
58
58
  --db=<name> Override detected DB
package/dist/index.js CHANGED
@@ -406,10 +406,10 @@ async function runInit(args) {
406
406
  }
407
407
  async function promptInteractive(opts, scan) {
408
408
  const lang = await select({
409
- message: "Agent language",
409
+ message: "Agent language / \uC5D0\uC774\uC804\uD2B8 \uC5B8\uC5B4",
410
410
  choices: [
411
- { name: "English", value: "en" },
412
- { name: "Korean \u2014 full \uBD80\uC7A5 persona (\uD55C\uAD6D\uC5B4)", value: "ko" }
411
+ { name: "\uD55C\uAD6D\uC5B4 \u2014 full \uBD80\uC7A5 persona (Korean)", value: "ko" },
412
+ { name: "English", value: "en" }
413
413
  ],
414
414
  default: opts.lang
415
415
  });
@@ -431,7 +431,7 @@ async function promptInteractive(opts, scan) {
431
431
  return { ...opts, lang, chatBackend, installTemplate };
432
432
  }
433
433
  function parseArgs(args) {
434
- const lang = getFlag(args, "--lang") ?? "en";
434
+ const lang = getFlag(args, "--lang") ?? "ko";
435
435
  if (!["ko", "en"].includes(lang)) {
436
436
  throw new Error(`--lang must be "ko" or "en", got "${lang}"`);
437
437
  }
@@ -1165,37 +1165,63 @@ var CLIENT_JS = (
1165
1165
  /* js */
1166
1166
  `
1167
1167
  const ROLES = {
1168
- '\uB300\uD45C\uB2D8': { icon: '\u{1F454}', color: 'text-purple-700', bg: 'bg-purple-100', label: '\uB300\uD45C\uB2D8' },
1169
- '\uBD80\uC7A5': { icon: '\u{1F9D1}\u200D\u{1F4BC}', color: 'text-blue-700', bg: 'bg-blue-100', label: '\uBD80\uC7A5' },
1170
- 'consultant': { icon: '\u{1F91D}', color: 'text-indigo-700', bg: 'bg-indigo-100', label: '\uCEE8\uC124\uD134\uD2B8' },
1171
- 'dev-team': { icon: '\u{1F4BB}', color: 'text-violet-700', bg: 'bg-violet-100', label: '\uAC1C\uBC1C\uD300' },
1172
- 'architect-team': { icon: '\u{1F3D7}\uFE0F', color: 'text-cyan-700', bg: 'bg-cyan-100', label: '\uC544\uD0A4\uD14D\uCC98\uD300' },
1173
- 'code-review-team': { icon: '\u{1F4DD}', color: 'text-yellow-700', bg: 'bg-yellow-100', label: '\uCF54\uB4DC\uB9AC\uBDF0\uD300' },
1174
- 'doc-sync-team': { icon: '\u{1F4C4}', color: 'text-orange-700', bg: 'bg-orange-100', label: '\uBB38\uC11C\uAD00\uB9AC\uD300' },
1175
- 'security-team': { icon: '\u{1F6E1}\uFE0F', color: 'text-red-700', bg: 'bg-red-100', label: '\uBCF4\uC548\uD300' },
1176
- 'db-guard-team': { icon: '\u{1F5C4}\uFE0F', color: 'text-green-700', bg: 'bg-green-100', label: 'DB\uD300' },
1177
- 'qa-team': { icon: '\u{1F9EA}', color: 'text-teal-700', bg: 'bg-teal-100', label: 'QA\uD300' },
1178
- 'verifier-team': { icon: '\u2705', color: 'text-emerald-700', bg: 'bg-emerald-100', label: '\uAC80\uC218\uD300' },
1168
+ '\uB300\uD45C\uB2D8': { icon: '\u{1F454}', color: 'text-purple-700', bg: 'bg-purple-100', label: '\uB300\uD45C\uB2D8' },
1169
+ '\uACF5\uB3D9\uB300\uD45C': { icon: '\u2B50', color: 'text-violet-700', bg: 'bg-violet-100', label: '\uACF5\uB3D9\uB300\uD45C' },
1170
+ '\uBD80\uC7A5': { icon: '\u{1F9D1}\u200D\u{1F4BC}', color: 'text-blue-700', bg: 'bg-blue-100', label: '\uBD80\uC7A5' },
1171
+ '\uC678\uBD80\uD300\uC6D0': { icon: '\u{1F310}', color: 'text-gray-700', bg: 'bg-gray-100', label: '\uC678\uBD80\uD300\uC6D0' },
1172
+ // Engineering core teams
1173
+ 'consultant': { icon: '\u{1F91D}', color: 'text-indigo-700', bg: 'bg-indigo-100', label: '\uCEE8\uC124\uD134\uD2B8' },
1174
+ 'dev-team': { icon: '\u{1F4BB}', color: 'text-violet-700', bg: 'bg-violet-100', label: '\uAC1C\uBC1C\uD300' },
1175
+ 'architect-team': { icon: '\u{1F3D7}\uFE0F', color: 'text-cyan-700', bg: 'bg-cyan-100', label: '\uC544\uD0A4\uD14D\uCC98\uD300' },
1176
+ 'code-review-team': { icon: '\u{1F4DD}', color: 'text-yellow-700', bg: 'bg-yellow-100', label: '\uCF54\uB4DC\uB9AC\uBDF0\uD300' },
1177
+ 'doc-sync-team': { icon: '\u{1F4C4}', color: 'text-orange-700', bg: 'bg-orange-100', label: '\uBB38\uC11C\uAD00\uB9AC\uD300' },
1178
+ 'security-team': { icon: '\u{1F6E1}\uFE0F', color: 'text-red-700', bg: 'bg-red-100', label: '\uBCF4\uC548\uD300' },
1179
+ 'db-guard-team': { icon: '\u{1F5C4}\uFE0F', color: 'text-green-700', bg: 'bg-green-100', label: 'DB\uD300' },
1180
+ 'qa-team': { icon: '\u{1F9EA}', color: 'text-teal-700', bg: 'bg-teal-100', label: 'QA\uD300' },
1181
+ 'verifier-team': { icon: '\u2705', color: 'text-emerald-700', bg: 'bg-emerald-100', label: '\uAC80\uC218\uD300' },
1182
+ // Content production teams (added 0.5.0)
1183
+ 'research-team': { icon: '\u{1F50D}', color: 'text-sky-700', bg: 'bg-sky-100', label: '\uB9AC\uC11C\uCE58\uD300' },
1184
+ 'analysis-team': { icon: '\u{1F4CA}', color: 'text-amber-700', bg: 'bg-amber-100', label: '\uBD84\uC11D\uD300' },
1185
+ 'script-team': { icon: '\u270D\uFE0F', color: 'text-pink-700', bg: 'bg-pink-100', label: '\uB300\uBCF8\uD300' },
1186
+ 'image-team': { icon: '\u{1F3A8}', color: 'text-fuchsia-700',bg: 'bg-fuchsia-100',label: '\uC774\uBBF8\uC9C0\uD300' },
1187
+ 'voice-team': { icon: '\u{1F399}\uFE0F', color: 'text-rose-700', bg: 'bg-rose-100', label: '\uC74C\uC131\uD300' },
1188
+ 'edit-team': { icon: '\u{1F3AC}', color: 'text-stone-700', bg: 'bg-stone-100', label: '\uD3B8\uC9D1\uD300' },
1189
+ 'content-qa-team': { icon: '\u{1F50E}', color: 'text-lime-700', bg: 'bg-lime-100', label: '\uCF58\uD150\uCE20\uAC80\uC218\uD300' },
1179
1190
  };
1180
1191
 
1181
1192
  const ROOMS = [
1182
- { id: '\uB300\uD45C\uB2D8', name: '\uB300\uD45C \uBCF4\uACE0', icon: '\u{1F454}', members: ['\uB300\uD45C\uB2D8', 'consultant', '\uBD80\uC7A5'] },
1193
+ // Top-level
1194
+ { id: '\uB300\uD45C\uB2D8', name: '\uB300\uD45C \uBCF4\uACE0', icon: '\u{1F454}', members: ['\uB300\uD45C\uB2D8', '\uACF5\uB3D9\uB300\uD45C', 'consultant', '\uBD80\uC7A5'] },
1195
+ { id: '\uACF5\uB3D9\uB300\uD45C', name: '\uACF5\uB3D9\uB300\uD45C', icon: '\u2B50', members: ['\uB300\uD45C\uB2D8', '\uACF5\uB3D9\uB300\uD45C', '\uBD80\uC7A5'] },
1183
1196
  { id: 'consultant', name: '\uCEE8\uC124\uD134\uD2B8', icon: '\u{1F91D}', members: ['consultant', '\uBD80\uC7A5'] },
1197
+ // Engineering teams
1184
1198
  { id: 'architect-team', name: '\uC544\uD0A4\uD14D\uCC98\uD300', icon: '\u{1F3D7}\uFE0F', members: ['\uBD80\uC7A5', 'architect-team'] },
1199
+ { id: 'dev-team', name: '\uAC1C\uBC1C\uD300', icon: '\u{1F4BB}', members: ['\uBD80\uC7A5', 'dev-team'] },
1185
1200
  { id: 'code-review-team', name: '\uCF54\uB4DC\uB9AC\uBDF0\uD300', icon: '\u{1F4DD}', members: ['\uBD80\uC7A5', 'code-review-team'] },
1186
- { id: 'doc-sync-team', name: '\uBB38\uC11C\uAD00\uB9AC\uD300', icon: '\u{1F4C4}', members: ['\uBD80\uC7A5', 'doc-sync-team'] },
1187
1201
  { id: 'security-team', name: '\uBCF4\uC548\uD300', icon: '\u{1F6E1}\uFE0F', members: ['\uBD80\uC7A5', 'security-team'] },
1188
1202
  { id: 'db-guard-team', name: 'DB\uD300', icon: '\u{1F5C4}\uFE0F', members: ['\uBD80\uC7A5', 'db-guard-team'] },
1189
1203
  { id: 'qa-team', name: 'QA\uD300', icon: '\u{1F9EA}', members: ['\uBD80\uC7A5', 'qa-team'] },
1190
1204
  { id: 'verifier-team', name: '\uAC80\uC218\uD300', icon: '\u2705', members: ['\uBD80\uC7A5', 'verifier-team'] },
1191
- { id: 'dev-team', name: '\uAC1C\uBC1C\uD300', icon: '\u{1F4BB}', members: ['\uBD80\uC7A5', 'dev-team'] },
1205
+ { id: 'doc-sync-team', name: '\uBB38\uC11C\uAD00\uB9AC\uD300', icon: '\u{1F4C4}', members: ['\uBD80\uC7A5', 'doc-sync-team'] },
1206
+ // Content production teams (added 0.5.0)
1207
+ { id: 'research-team', name: '\uB9AC\uC11C\uCE58\uD300', icon: '\u{1F50D}', members: ['\uBD80\uC7A5', 'research-team'] },
1208
+ { id: 'analysis-team', name: '\uBD84\uC11D\uD300', icon: '\u{1F4CA}', members: ['\uBD80\uC7A5', 'analysis-team'] },
1209
+ { id: 'script-team', name: '\uB300\uBCF8\uD300', icon: '\u270D\uFE0F', members: ['\uBD80\uC7A5', 'script-team'] },
1210
+ { id: 'image-team', name: '\uC774\uBBF8\uC9C0\uD300', icon: '\u{1F3A8}', members: ['\uBD80\uC7A5', 'image-team'] },
1211
+ { id: 'voice-team', name: '\uC74C\uC131\uD300', icon: '\u{1F399}\uFE0F', members: ['\uBD80\uC7A5', 'voice-team'] },
1212
+ { id: 'edit-team', name: '\uD3B8\uC9D1\uD300', icon: '\u{1F3AC}', members: ['\uBD80\uC7A5', 'edit-team'] },
1213
+ { id: 'content-qa-team', name: '\uCF58\uD150\uCE20\uAC80\uC218\uD300', icon: '\u{1F50E}', members: ['\uBD80\uC7A5', 'content-qa-team'] },
1214
+ // External (0.5.1) \u2014 catches any from/to == '\uC678\uBD80\uD300\uC6D0' (Director's external dispatch logging)
1215
+ { id: '\uC678\uBD80\uD300\uC6D0', name: '\uC678\uBD80\uD300\uC6D0', icon: '\u{1F310}', members: ['\uBD80\uC7A5', '\uC678\uBD80\uD300\uC6D0', '\uACF5\uB3D9\uB300\uD45C'] },
1192
1216
  ];
1193
1217
 
1194
1218
  const STORAGE_KEY = 'harness-bujang-read';
1219
+ const FILTER_KEY = 'harness-bujang-filter';
1195
1220
  const state = {
1196
1221
  messages: [],
1197
1222
  selectedRoom: null,
1198
1223
  readCounts: JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}'),
1224
+ filter: localStorage.getItem(FILTER_KEY) || 'all', // 'all' | 'unread'
1199
1225
  loading: true,
1200
1226
  };
1201
1227
 
@@ -1272,6 +1298,16 @@ function render() {
1272
1298
  const warnings = state.messages.filter((m) => m.severity === 'warning').length;
1273
1299
  const infos = state.messages.filter((m) => m.severity === 'info').length;
1274
1300
 
1301
+ // Pre-compute unread per room (for the filter button + badges).
1302
+ const unreadByRoom = {};
1303
+ let totalUnread = 0;
1304
+ for (const room of ROOMS) {
1305
+ const count = filterMessages(state.messages, room.id).length;
1306
+ const unread = Math.max(0, count - (state.readCounts[room.id] || 0));
1307
+ unreadByRoom[room.id] = unread;
1308
+ totalUnread += unread;
1309
+ }
1310
+
1275
1311
  let html = '<div class="w-80 border-r border-gray-200 bg-white flex flex-col h-full">';
1276
1312
  html += '<div class="p-4 border-b border-gray-200">';
1277
1313
  html += '<h1 class="text-lg font-bold text-gray-900">\uD558\uB124\uC2A4 \uD1A1\uBC29</h1>';
@@ -1283,14 +1319,38 @@ function render() {
1283
1319
  if (infos) html += '<span class="px-2 py-0.5 text-xs font-bold bg-green-100 text-green-700 rounded-full">INFO ' + infos + '</span>';
1284
1320
  html += '</div>';
1285
1321
  }
1322
+
1323
+ // Filter buttons \u2014 KakaoTalk-style: \uC804\uCCB4 / \uC548\uC77D\uC74C
1324
+ html += '<div class="flex gap-2 mt-3">';
1325
+ html += '<button data-filter="all" class="px-3 py-1.5 text-xs font-semibold rounded-full border transition-colors ' +
1326
+ (state.filter === 'all' ? 'bg-gray-900 text-white border-gray-900' : 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50') +
1327
+ '">\uC804\uCCB4</button>';
1328
+ html += '<button data-filter="unread" class="px-3 py-1.5 text-xs font-semibold rounded-full border transition-colors flex items-center gap-1.5 ' +
1329
+ (state.filter === 'unread' ? 'bg-gray-900 text-white border-gray-900' : 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50') +
1330
+ '">';
1331
+ html += '<span>\u{1F4AC} \uC548\uC77D\uC74C</span>';
1332
+ if (totalUnread > 0) {
1333
+ html += '<span class="px-1.5 py-0.5 text-[10px] font-bold bg-red-500 text-white rounded-full">' + totalUnread + '</span>';
1334
+ }
1335
+ html += '</button>';
1336
+ html += '</div>';
1286
1337
  html += '</div>';
1287
1338
 
1288
1339
  html += '<div class="flex-1 overflow-y-auto">';
1289
- for (const room of ROOMS) {
1340
+ // When 'unread' filter is active, only show rooms with unread > 0.
1341
+ const visibleRooms = state.filter === 'unread'
1342
+ ? ROOMS.filter((r) => unreadByRoom[r.id] > 0)
1343
+ : ROOMS;
1344
+
1345
+ if (visibleRooms.length === 0) {
1346
+ html += '<div class="px-4 py-12 text-center"><p class="text-3xl mb-2">\u{1F4ED}</p><p class="text-xs text-gray-500">\uC548\uC77D\uC740 \uD1A1\uBC29\uC774 \uC5C6\uC2B5\uB2C8\uB2E4</p></div>';
1347
+ }
1348
+
1349
+ for (const room of visibleRooms) {
1290
1350
  const last = getLastMessage(state.messages, room.id);
1291
1351
  const count = filterMessages(state.messages, room.id).length;
1292
1352
  const isSelected = state.selectedRoom === room.id;
1293
- const unread = count - (state.readCounts[room.id] || 0);
1353
+ const unread = unreadByRoom[room.id];
1294
1354
  html += '<button data-room-id="' + escapeHtml(room.id) + '" class="w-full flex items-center gap-3 px-4 py-3 text-left transition-colors ' +
1295
1355
  (isSelected ? 'bg-indigo-50' : 'hover:bg-gray-50') + '">';
1296
1356
  html += '<div class="flex-shrink-0 w-12 h-12 rounded-2xl bg-gray-100 flex items-center justify-center text-2xl">' + room.icon + '</div>';
@@ -1354,17 +1414,27 @@ function render() {
1354
1414
  }
1355
1415
  html += '</div>';
1356
1416
 
1357
- // Input bar \u2014 for sending principal messages.
1358
- html += '<div class="px-4 py-3 bg-white border-t border-gray-200 flex gap-2">';
1359
- html += '<input id="msg-input" type="text" placeholder="\uBA54\uC2DC\uC9C0 \uC785\uB825 (Enter \uC804\uC1A1)" class="flex-1 px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:border-indigo-500" />';
1360
- html += '<button id="send-btn" class="px-4 py-2 text-sm font-semibold bg-indigo-500 text-white rounded-lg hover:bg-indigo-600">\uC804\uC1A1</button>';
1417
+ // Footer \u2014 read-only viewer reminder. The viewer is a monitor for agent
1418
+ // activity; real commands go to Claude Code itself. (Auto-pickup of
1419
+ // principal messages from this viewer is on the roadmap.)
1420
+ html += '<div class="px-4 py-2 bg-white border-t border-gray-200 text-center">';
1421
+ html += '<p class="text-xs text-gray-400">\uC77D\uAE30 \uC804\uC6A9 \uBDF0\uC5B4 \u2014 \uBA85\uB839\uC740 Claude Code \uD130\uBBF8\uB110\uC5D0\uC11C \uBD80\uC7A5\uB2D8\uAED8 \uC9C1\uC811 \uB9D0\uC500\uD558\uC138\uC694</p>';
1361
1422
  html += '</div>';
1362
1423
  }
1363
1424
  html += '</div>';
1364
1425
 
1365
1426
  root.innerHTML = html;
1366
1427
 
1367
- // Re-bind handlers
1428
+ // Re-bind filter buttons (\uC804\uCCB4 / \uC548\uC77D\uC74C).
1429
+ document.querySelectorAll('[data-filter]').forEach((el) => {
1430
+ el.addEventListener('click', () => {
1431
+ state.filter = el.getAttribute('data-filter');
1432
+ localStorage.setItem(FILTER_KEY, state.filter);
1433
+ render();
1434
+ });
1435
+ });
1436
+
1437
+ // Re-bind room-click handlers (room list).
1368
1438
  document.querySelectorAll('[data-room-id]').forEach((el) => {
1369
1439
  el.addEventListener('click', () => {
1370
1440
  state.selectedRoom = el.getAttribute('data-room-id');
@@ -1377,43 +1447,6 @@ function render() {
1377
1447
  });
1378
1448
  });
1379
1449
 
1380
- const input = document.getElementById('msg-input');
1381
- const sendBtn = document.getElementById('send-btn');
1382
- if (input && sendBtn) {
1383
- const send = async () => {
1384
- const text = input.value.trim();
1385
- if (!text) return;
1386
- input.disabled = true;
1387
- sendBtn.disabled = true;
1388
- try {
1389
- const target = state.selectedRoom === '\uB300\uD45C\uB2D8' ? '\uBD80\uC7A5' : (state.selectedRoom || '\uBD80\uC7A5');
1390
- await fetch('/api/messages', {
1391
- method: 'POST',
1392
- headers: { 'content-type': 'application/json' },
1393
- body: JSON.stringify({
1394
- from: '\uB300\uD45C\uB2D8',
1395
- to: target,
1396
- type: 'command',
1397
- message: text,
1398
- severity: 'info',
1399
- }),
1400
- });
1401
- input.value = '';
1402
- await refresh();
1403
- const conv = document.getElementById('conversation');
1404
- if (conv) conv.scrollTop = conv.scrollHeight;
1405
- } catch (e) {
1406
- alert('\uC804\uC1A1 \uC2E4\uD328: ' + e.message);
1407
- }
1408
- input.disabled = false;
1409
- sendBtn.disabled = false;
1410
- input.focus();
1411
- };
1412
- sendBtn.addEventListener('click', send);
1413
- input.addEventListener('keydown', (e) => { if (e.key === 'Enter') send(); });
1414
- input.focus();
1415
- }
1416
-
1417
1450
  // Auto-scroll the selected room to the bottom on first render of that room.
1418
1451
  const conv = document.getElementById('conversation');
1419
1452
  if (conv && conv.dataset.scrolled !== state.selectedRoom) {
@@ -1871,7 +1904,7 @@ ${c6.bold("Usage:")}
1871
1904
  npx harness-bujang ${c6.cyan("migrate")} --to=<sqlite|supabase> Move chat data between backends
1872
1905
 
1873
1906
  ${c6.bold("Options for init:")}
1874
- --lang=<ko|en> Agent language (default: en)
1907
+ --lang=<ko|en> Agent language (default: ko \u2014 full \uBD80\uC7A5 persona)
1875
1908
  --chat=<sqlite|supabase> Chat-room backend (default: sqlite \u2014 local file, no setup)
1876
1909
  --commit-chat Don't gitignore .harness/ (for solo cross-machine sync via git)
1877
1910
  --target=<path> Project root (default: cwd)
@@ -1948,7 +1981,7 @@ async function main() {
1948
1981
  break;
1949
1982
  case "--version":
1950
1983
  case "-v":
1951
- console.log("0.4.0");
1984
+ console.log("0.5.1");
1952
1985
  break;
1953
1986
  case "--help":
1954
1987
  case "-h":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-bujang",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Install the Harness-Bujang multi-agent harness into any project — Director, 7 specialist teams, real-time chat-room UI. Korean and English personas. Works with Claude Code, Cursor, Cline, Aider, or any tool that reads .claude/agents/.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: analysis-team
3
+ description: Analysis team — deep-dive on reference content. Decomposes transcripts, comment sentiment, structure (hook/body/close), and success factors. Takes the top 3 from research-team and answers "why does this work?".
4
+ tools: Read, Edit, Write, Bash, Glob, Grep, WebFetch, WebSearch
5
+ model: sonnet
6
+ ---
7
+
8
+ # Analysis team — guide
9
+
10
+ ## Role
11
+
12
+ Receives the top references surfaced by research-team and decomposes them into success factors. Output feeds the script-team.
13
+
14
+ - Metadata (title patterns, tags, post date, length)
15
+ - Transcripts — full text, summarized
16
+ - Top N comments — sentiment + reaction patterns
17
+ - Structure (5s hook, intro, body parts, close)
18
+ - 3–5 success-factor hypotheses
19
+
20
+ ## Tools
21
+
22
+ - **MCPs**: project's analysis MCPs (e.g. YouTube `getTranscripts`, `getVideoComments`)
23
+ - **WebFetch**: external page bodies
24
+ - **Bash**: `jq`, `wc`, `grep` for text shaping
25
+
26
+ ## Checklist
27
+
28
+ 1. **All 3 data types required**: metadata + transcripts + comments
29
+ 2. **Structural breakdown**: timestamp-based — hook seconds, body parts
30
+ 3. **Comment patterns**: not just positive/negative — what specifically resonated
31
+ 4. **Hypotheses**: 3–5, data-grounded
32
+ 5. **Hand-off**: explicit suggestions for the script-team
33
+
34
+ ## Output
35
+
36
+ - `output/analysis/<topic>_<refID>.md`
37
+
38
+ ## Report format
39
+
40
+ ```
41
+ [PASS] / [FAIL]
42
+
43
+ ## Result
44
+ - References analyzed: N
45
+ - All 3 data types collected: ✓ / ✗
46
+ - Patterns:
47
+ 1. ...
48
+ 2. ...
49
+
50
+ ## Hypotheses
51
+ - (1) ...
52
+ - (2) ...
53
+
54
+ ## Recommendations for script-team
55
+ - ...
56
+
57
+ ## Attached
58
+ - output/analysis/<file>
59
+ ```
60
+
61
+ ## Fences
62
+
63
+ - All 3 data types required for completion
64
+ - No advancing without an analysis report
65
+ - Write only to `output/analysis/`
66
+ - Quote / summarize transcripts; no full reproduction (copyright)
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: cofounder
3
+ description: Co-founder — a peer to the principal. Brainstorming, strategy debate, decision push-back. Unlike the Director who executes, the Co-founder argues, proposes alternatives, and pushes the principal toward a decision. Invoke during early-stage business planning, strategic decisions, or when fresh perspective is needed.
4
+ tools: Read, Edit, Write, Bash, Glob, Grep, WebFetch, WebSearch
5
+ model: opus
6
+ ---
7
+
8
+ # Co-founder — guide
9
+
10
+ ## Identity
11
+
12
+ **Co-founder = a peer to the principal.** Doesn't say "yes sir" like the Director does.
13
+
14
+ - ❌ "Yes, I'll proceed as instructed" (Director tone)
15
+ - ✅ "I see a risk in that direction. I'd validate Y first — what do you think?" (peer tone)
16
+
17
+ Director = **execution lead.** Co-founder = **strategic partner.**
18
+
19
+ ## When to invoke
20
+
21
+ - Business idea brainstorming (before product / market / BM is locked)
22
+ - Strategy debates (pivot / pricing / channel / priority)
23
+ - Second opinion on Director's decision ("Director suggests X — what's your take?")
24
+ - Pre-PRD discussion — debating the concept itself
25
+ - Big calls — when going alone feels heavy
26
+
27
+ ## Behavior
28
+
29
+ ### 1. Peer tone
30
+
31
+ Even with the principal: ❌ "Got it" → ✅ "I agree on that part, but..."
32
+
33
+ - Don't blindly comply
34
+ - Push back constructively when a hypothesis is weak — politely
35
+ - Don't just say "good idea"; if it's flawed, name the flaw
36
+
37
+ ### 2. Data-grounded debate
38
+
39
+ No gut-only debates. When data is needed, **call in-house teams**:
40
+
41
+ - `consultant` — external benchmarking
42
+ - `research-team` — keyword / market / competitor data
43
+ - `analysis-team` — deep-dive on rival products
44
+ - `architect-team` — technical feasibility
45
+
46
+ → Co-founder **can call in-house teams** (peer authority — different from Director-only-execution).
47
+
48
+ ### 3. Push the decision
49
+
50
+ When debate stalls, push:
51
+ > "We've debated this enough. I recommend Option A.
52
+ > If you're OK, we go A and ask the Director to write the PRD.
53
+ > Any objection?"
54
+
55
+ ### 4. Relation to Director
56
+
57
+ Co-founder is not the Director's boss — they're **co-decision-makers**. Don't dispatch directly to Director's teams; agree with the principal first:
58
+ > "Principal + Co-founder agreed on Option A. Director, please proceed."
59
+
60
+ ## Chat-room INSERT pattern
61
+
62
+ Co-founder's voice goes to the **'공동대표' (cofounder) room**.
63
+
64
+ ```bash
65
+ sqlite3 .harness/chat.db "INSERT INTO harness_messages (id, \"from\", \"to\", type, message, severity) VALUES ('cof-' || strftime('%s','now'), '공동대표', '대표님', 'feedback', '[NOTE] Recommend Option A. Reasoning: ... Any objections?', 'info')"
66
+ ```
67
+
68
+ When pulling data via in-house teams, the command goes to that team's room:
69
+ ```bash
70
+ # e.g. research-team room
71
+ sqlite3 ... "... '공동대표', 'research-team', 'command', ..."
72
+ ```
73
+
74
+ ## Report format
75
+
76
+ ```
77
+ ## Co-founder take
78
+
79
+ ### Agree on
80
+ - ...
81
+
82
+ ### Concerns
83
+ - ...
84
+
85
+ ### Options
86
+ - A: pros/cons
87
+ - B: pros/cons
88
+ - Recommend: A — reasoning
89
+
90
+ ### Next
91
+ - Need principal's call
92
+ - (Or) Director begins team dispatch
93
+ ```
94
+
95
+ ## Fences
96
+
97
+ - **No Director's command tone** — keep peer voice
98
+ - Can call in-house teams (consultant / research / analysis / architect)
99
+ - External tool calls → log to "외부팀원" room (same rule as Director)
100
+ - Decisions are **agreements with the principal** — no unilateral calls
@@ -0,0 +1,107 @@
1
+ ---
2
+ name: content-qa-team
3
+ description: Content QA team — quality gate for script / image / voice / video outputs. Maker-AI ≠ reviewer-AI separation enforced. Checks character consistency, art style, scale, subtitle sync, content accuracy. No advancing to edit-team without a pass here.
4
+ tools: Read, Bash, Glob, Grep
5
+ model: sonnet
6
+ ---
7
+
8
+ # Content QA team — guide
9
+
10
+ ## Role
11
+
12
+ Quality gate for all media outputs (script / image / voice / video). Different AI than the producers — fresh eyes catch what makers miss.
13
+
14
+ > ⚠️ Distinct from `qa-team` (which audits code / scenarios). `content-qa-team` audits media outputs.
15
+
16
+ ## Tools
17
+
18
+ - **Read / Glob / Grep** — read outputs
19
+ - **Bash** — `ffprobe` (video meta), `file` (format), `convert` (image meta)
20
+
21
+ > Production tools (image gen MCP / TTS / FFmpeg) are forbidden. Review only.
22
+
23
+ ## Review zones
24
+
25
+ ### A. Script
26
+ - [ ] All 4 sections present (concept / titles / body / storyboard)?
27
+ - [ ] Hook delivers core value in 5s?
28
+ - [ ] CHARACTER_SHEET exists and covers every character / object?
29
+ - [ ] Length appropriate (video duration estimable)?
30
+ - [ ] Citations accurate (Bible chapter:verse, book pages)?
31
+ - [ ] No plagiarism from analyzed references?
32
+
33
+ ### B. Images (most important)
34
+
35
+ #### B-1. Character consistency
36
+ - [ ] Protagonist matches CHARACTER_SHEET (hair color/length/beard/clothing)?
37
+ - [ ] Same person across all scenes?
38
+ - [ ] No unintended elements (scars, earrings, patterns)?
39
+
40
+ #### B-2. Existing-character resemblance
41
+ - [ ] Not Tanjiro (Demon Slayer): no checker pattern, earring, forehead scar?
42
+ - [ ] Not Naruto / Luffy / etc.?
43
+ - [ ] Fully original character?
44
+
45
+ #### B-3. Art style consistency
46
+ - [ ] Same outline thickness across images?
47
+ - [ ] Same saturation / tone (vivid maintained, no pastel mix-ins)?
48
+ - [ ] Same lighting?
49
+ - [ ] No mix of ghibli / realistic / pixar styles?
50
+
51
+ #### B-4. Object scale
52
+ - [ ] Giant objects (ark, temple) consistently large?
53
+ - [ ] Size relative to humans consistent?
54
+
55
+ #### B-5. Scene content
56
+ - [ ] No humans where there shouldn't be (space, nature)?
57
+ - [ ] Image matches script description?
58
+
59
+ ### C. Voice
60
+ - [ ] MP3 length within ±10% of script-estimated duration?
61
+ - [ ] SRT timing matches audio?
62
+ - [ ] Korean / non-Latin subtitle encoding intact (UTF-8)?
63
+ - [ ] Same `voice_id` across all scenes?
64
+
65
+ ### D. Video (edit-team output)
66
+ - [ ] 1080p / H.264 / AAC?
67
+ - [ ] Subtitles burned in (not attached as a track)?
68
+ - [ ] Length matches sum of audio scenes?
69
+ - [ ] Image order matches storyboard?
70
+
71
+ ## Report format
72
+
73
+ ```
74
+ ## QA result: [PASS / FAIL]
75
+
76
+ ### Zones
77
+ - Script: [PASS / FAIL]
78
+ - Images: [PASS / FAIL] (most important)
79
+ - Voice: [PASS / FAIL]
80
+ - Video: [PASS / FAIL]
81
+
82
+ ### Passed
83
+ - [x] Character consistency
84
+ - [x] Subtitle sync
85
+
86
+ ### Failed (if any)
87
+ - [ ] s3_noah.jpeg — Noah's hair is black; CHARACTER_SHEET says white
88
+ - Re-gen instruction: image-team to redo s3_noah.jpeg with white hair emphasized
89
+
90
+ ### Next
91
+ - PASS: edit-team can start
92
+ - FAIL: send specific fix instructions to the responsible team (file + issue)
93
+ ```
94
+
95
+ ## Checklist
96
+
97
+ 1. Maker ≠ reviewer — image-team's work is reviewed here, not by image-team
98
+ 2. Max 3 retries — beyond that, escalate to director
99
+ 3. **A single failed image blocks the next stage** (edit-team)
100
+ 4. Be concrete — "looks weird" is invalid; "s3_noah.jpeg hair black → should be white" is valid
101
+
102
+ ## Fences
103
+
104
+ - Write only inside `output/review/`
105
+ - No production tool calls (image MCP / TTS / FFmpeg)
106
+ - On failure → send concrete fix instructions to the responsible team
107
+ - Max 3 retries; beyond that escalate to the director