harness-bujang 0.5.5 → 0.5.7

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/dist/index.js CHANGED
@@ -925,9 +925,8 @@ console.log(' \u2713 Transferred ' + total + ' messages');
925
925
  import * as http from "http";
926
926
  import * as path5 from "path";
927
927
  import * as fs5 from "fs";
928
- import { execFile, spawn } from "child_process";
929
- import { promisify } from "util";
930
- var execFileP = promisify(execFile);
928
+ import { spawn } from "child_process";
929
+ import Database from "better-sqlite3";
931
930
  var c4 = {
932
931
  bold: (s) => `\x1B[1m${s}\x1B[22m`,
933
932
  dim: (s) => `\x1B[2m${s}\x1B[22m`,
@@ -938,34 +937,37 @@ var c4 = {
938
937
  };
939
938
  async function runChat(args) {
940
939
  const opts = parseArgs3(args);
940
+ const dbPath = resolveDbPath(opts.target);
941
+ const dbIsNew = !fs5.existsSync(dbPath);
942
+ if (dbIsNew) fs5.mkdirSync(path5.dirname(dbPath), { recursive: true });
943
+ let db;
941
944
  try {
942
- await execFileP("sqlite3", ["--version"]);
943
- } catch {
945
+ db = new Database(dbPath);
946
+ } catch (err) {
944
947
  console.log();
945
- console.log(c4.red("\u2716 The `sqlite3` command-line tool is required for `bujang chat`."));
948
+ console.log(c4.red("\u2716 Failed to open chat DB at " + dbPath));
949
+ console.log(" " + c4.dim(String(err)));
946
950
  console.log();
947
- console.log(" macOS: already installed");
948
- console.log(" Ubuntu/WSL: " + c4.bold("sudo apt-get install sqlite3"));
949
- console.log(" Fedora: " + c4.bold("sudo dnf install sqlite"));
950
- console.log(" Windows: https://www.sqlite.org/download.html (sqlite-tools-win-x64)");
951
+ console.log(" This usually means better-sqlite3 could not load its native binding.");
952
+ console.log(" Try " + c4.bold("npm i -g harness-bujang@latest") + " to fetch a fresh prebuild.");
951
953
  console.log();
952
954
  process.exitCode = 1;
953
955
  return;
954
956
  }
955
- const dbPath = resolveDbPath(opts.target);
956
- if (!fs5.existsSync(dbPath)) {
957
- fs5.mkdirSync(path5.dirname(dbPath), { recursive: true });
958
- await runSql(dbPath, SCHEMA_SQL);
957
+ db.pragma("journal_mode = WAL");
958
+ db.exec(SCHEMA_SQL);
959
+ if (dbIsNew) {
959
960
  const seedId = `seed-${Date.now()}`;
960
- await runSql(
961
- dbPath,
961
+ db.prepare(
962
962
  `INSERT INTO harness_messages (id, "from", "to", type, message, severity)
963
- VALUES ('${seedId}', '\uBD80\uC7A5', '\uB300\uD45C\uB2D8', 'info', '\uD1A1\uBC29\uC774 \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uCCAB \uBA85\uB839\uC744 \uB0B4\uB824\uC8FC\uC138\uC694.', 'info');`
964
- );
963
+ VALUES (?, ?, ?, ?, ?, ?)`
964
+ ).run(seedId, "\uBD80\uC7A5", "\uB300\uD45C\uB2D8", "info", "\uD1A1\uBC29\uC774 \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uCCAB \uBA85\uB839\uC744 \uB0B4\uB824\uC8FC\uC138\uC694.", "info");
965
965
  console.log(c4.dim(` created empty chat DB at ${dbPath}`));
966
- } else {
967
- await runSql(dbPath, SCHEMA_SQL);
968
966
  }
967
+ const insertStmt = db.prepare(
968
+ `INSERT INTO harness_messages (id, "from", "to", type, message, severity)
969
+ VALUES (?, ?, ?, ?, ?, ?)`
970
+ );
969
971
  const port = await findOpenPort(opts.port);
970
972
  const server = http.createServer(async (req, res) => {
971
973
  const url2 = new URL(req.url ?? "/", `http://localhost:${port}`);
@@ -977,7 +979,7 @@ async function runChat(args) {
977
979
  if (req.method === "GET" && url2.pathname === "/api/messages") {
978
980
  const days = parseInt(url2.searchParams.get("days") ?? "7", 10);
979
981
  try {
980
- const rows = await readMessages(dbPath, days);
982
+ const rows = readMessages(db, days);
981
983
  res.writeHead(200, { "content-type": "application/json" });
982
984
  res.end(JSON.stringify({ data: rows }));
983
985
  } catch (err) {
@@ -1001,11 +1003,7 @@ async function runChat(args) {
1001
1003
  res.end(JSON.stringify({ error: "message is required" }));
1002
1004
  return;
1003
1005
  }
1004
- await runSql(
1005
- dbPath,
1006
- `INSERT INTO harness_messages (id, "from", "to", type, message, severity)
1007
- VALUES (${q(id)}, ${q(from)}, ${q(to)}, ${q(type)}, ${q(message)}, ${q(severity)});`
1008
- );
1006
+ insertStmt.run(id, from, to, type, message, severity);
1009
1007
  res.writeHead(200, { "content-type": "application/json" });
1010
1008
  res.end(JSON.stringify({ data: { id } }));
1011
1009
  } catch (err) {
@@ -1031,6 +1029,7 @@ async function runChat(args) {
1031
1029
  console.log();
1032
1030
  console.log(c4.dim(" bye \u{1F44B}"));
1033
1031
  server.close();
1032
+ db.close();
1034
1033
  process.exit(0);
1035
1034
  });
1036
1035
  }
@@ -1095,18 +1094,15 @@ function readBody(req) {
1095
1094
  req.on("error", reject);
1096
1095
  });
1097
1096
  }
1098
- async function readMessages(dbPath, days) {
1099
- const sql = `
1100
- SELECT id, timestamp, "from" AS sender, "to" AS recipient, type, message, severity
1101
- FROM harness_messages
1102
- WHERE timestamp >= datetime('now', '-${Math.max(1, days | 0)} day')
1103
- ORDER BY timestamp ASC;
1104
- `;
1105
- const { stdout } = await execFileP("sqlite3", ["-json", dbPath, sql], {
1106
- maxBuffer: 32 * 1024 * 1024
1107
- });
1108
- if (!stdout.trim()) return [];
1109
- const raw = JSON.parse(stdout);
1097
+ function readMessages(db, days) {
1098
+ const safeDays = Math.max(1, days | 0);
1099
+ const stmt = db.prepare(
1100
+ `SELECT id, timestamp, "from" AS sender, "to" AS recipient, type, message, severity
1101
+ FROM harness_messages
1102
+ WHERE timestamp >= datetime('now', '-' || ? || ' day')
1103
+ ORDER BY timestamp ASC`
1104
+ );
1105
+ const raw = stmt.all(safeDays);
1110
1106
  return raw.map((r) => ({
1111
1107
  id: r.id,
1112
1108
  timestamp: r.timestamp,
@@ -1117,12 +1113,6 @@ async function readMessages(dbPath, days) {
1117
1113
  severity: r.severity
1118
1114
  }));
1119
1115
  }
1120
- async function runSql(dbPath, sql) {
1121
- await execFileP("sqlite3", [dbPath, sql], { maxBuffer: 1024 * 1024 });
1122
- }
1123
- function q(value) {
1124
- return `'${value.replace(/'/g, "''")}'`;
1125
- }
1126
1116
  var SCHEMA_SQL = `
1127
1117
  CREATE TABLE IF NOT EXISTS harness_messages (
1128
1118
  id TEXT PRIMARY KEY,
@@ -1198,8 +1188,9 @@ const ROLES = {
1198
1188
  };
1199
1189
 
1200
1190
  const ROOMS = [
1201
- // Top-level
1202
- { id: '\uB300\uD45C\uB2D8', name: '\uB300\uD45C \uBCF4\uACE0', icon: '\u{1F454}', members: ['\uB300\uD45C\uB2D8', '\uACF5\uB3D9\uB300\uD45C', 'consultant', '\uBD80\uC7A5'] },
1191
+ // Top-level \u2014 kept narrow on purpose so the "smallest matching room wins"
1192
+ // filter routes director\u2192principal reports to \uB300\uD45C \uBCF4\uACE0 (not \uACF5\uB3D9\uB300\uD45C).
1193
+ { id: '\uB300\uD45C\uB2D8', name: '\uB300\uD45C \uBCF4\uACE0', icon: '\u{1F454}', members: ['\uB300\uD45C\uB2D8', '\uBD80\uC7A5'] },
1203
1194
  { id: '\uACF5\uB3D9\uB300\uD45C', name: '\uACF5\uB3D9\uB300\uD45C', icon: '\u2B50', members: ['\uB300\uD45C\uB2D8', '\uACF5\uB3D9\uB300\uD45C', '\uBD80\uC7A5'] },
1204
1195
  { id: 'consultant', name: '\uCEE8\uC124\uD134\uD2B8', icon: '\u{1F91D}', members: ['consultant', '\uBD80\uC7A5'] },
1205
1196
  // Engineering teams
@@ -2199,7 +2190,7 @@ async function main() {
2199
2190
  break;
2200
2191
  case "--version":
2201
2192
  case "-v":
2202
- console.log("0.5.5");
2193
+ console.log("0.5.6");
2203
2194
  break;
2204
2195
  case "--help":
2205
2196
  case "-h":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-bujang",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
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",
@@ -47,6 +47,7 @@
47
47
  "prepublishOnly": "npm run build"
48
48
  },
49
49
  "devDependencies": {
50
+ "@types/better-sqlite3": "^7.6.10",
50
51
  "@types/node": "^20.11.0",
51
52
  "tsup": "^8.3.0",
52
53
  "tsx": "^4.19.0",
@@ -56,6 +57,7 @@
56
57
  "node": ">=20"
57
58
  },
58
59
  "dependencies": {
59
- "@inquirer/prompts": "^8.4.2"
60
+ "@inquirer/prompts": "^8.4.2",
61
+ "better-sqlite3": "^11.7.0"
60
62
  }
61
63
  }
@@ -51,8 +51,10 @@ const ROLES: Record<string, { icon: string; color: string; bg: string; label?: s
51
51
  };
52
52
 
53
53
  const ROOMS = [
54
- // Top
55
- { id: '대표님', name: '대표 보고', icon: '👔', members: ['대표님', '공동대표', 'consultant', '부장'] },
54
+ // Top — kept narrow on purpose so director→principal reports route to 대표 보고
55
+ // (not 공동대표). The "smallest matching room wins" filter would otherwise
56
+ // funnel them to 공동대표 because that room also includes both 부장 and 대표님.
57
+ { id: '대표님', name: '대표 보고', icon: '👔', members: ['대표님', '부장'] },
56
58
  { id: '공동대표', name: '공동대표', icon: '⭐', members: ['대표님', '공동대표', '부장'] },
57
59
  { id: 'consultant', name: '컨설턴트', icon: '🤝', members: ['consultant', '부장'] },
58
60
  // Engineering