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 +4 -4
- package/dist/index.js +97 -64
- package/package.json +1 -1
- package/templates/agents/en/analysis-team.md +66 -0
- package/templates/agents/en/cofounder.md +100 -0
- package/templates/agents/en/content-qa-team.md +107 -0
- package/templates/agents/en/director.md +165 -2
- package/templates/agents/en/edit-team.md +107 -0
- package/templates/agents/en/image-team.md +87 -0
- package/templates/agents/en/research-team.md +63 -0
- package/templates/agents/en/script-team.md +75 -0
- package/templates/agents/en/voice-team.md +82 -0
- package/templates/agents/ko/analysis-team.md +68 -0
- package/templates/agents/ko/cofounder.md +100 -0
- package/templates/agents/ko/content-qa-team.md +108 -0
- package/templates/agents/ko/director.md +165 -2
- package/templates/agents/ko/edit-team.md +107 -0
- package/templates/agents/ko/image-team.md +87 -0
- package/templates/agents/ko/research-team.md +63 -0
- package/templates/agents/ko/script-team.md +76 -0
- package/templates/agents/ko/voice-team.md +93 -0
- package/templates/project-template/app/admin/harness/harness-client.tsx +38 -14
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
|
-
#
|
|
17
|
-
npx harness-bujang init --lang=
|
|
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:
|
|
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: "
|
|
412
|
-
{ name: "
|
|
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") ?? "
|
|
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':
|
|
1169
|
-
'\
|
|
1170
|
-
'
|
|
1171
|
-
'
|
|
1172
|
-
|
|
1173
|
-
'
|
|
1174
|
-
'
|
|
1175
|
-
'
|
|
1176
|
-
'
|
|
1177
|
-
'
|
|
1178
|
-
'
|
|
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
|
-
|
|
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: '
|
|
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
|
-
|
|
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 =
|
|
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
|
-
//
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
html += '<
|
|
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
|
|
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:
|
|
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.
|
|
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.
|
|
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
|