hanoman 0.1.52 → 0.1.53

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/server.js CHANGED
@@ -4189,10 +4189,10 @@ function convertPayload(to, payload) {
4189
4189
  const fromShape = shapeOfPayload(payload);
4190
4190
  const toShape = payloadShapeFor(to);
4191
4191
  const fromAudit = str3("fromAudit");
4192
- const done = (out3, dropped) => ({
4193
- payload: out3,
4192
+ const done = (out4, dropped) => ({
4193
+ payload: out4,
4194
4194
  dropped,
4195
- missing: SHAPE_REQUIRED[toShape].filter((f) => typeof out3[f] !== "string" || out3[f] === "")
4195
+ missing: SHAPE_REQUIRED[toShape].filter((f) => typeof out4[f] !== "string" || out4[f] === "")
4196
4196
  });
4197
4197
  if (fromShape === toShape) return { payload: { ...p }, dropped: [], missing: [] };
4198
4198
  if (toShape === "qa") {
@@ -4967,25 +4967,25 @@ var init_agent = __esm({
4967
4967
  // ../shared/src/custom-agent.ts
4968
4968
  function mentionsOf(v) {
4969
4969
  if (!Array.isArray(v)) return [];
4970
- const out3 = [];
4971
- for (const x of v) if (typeof x === "string" && x && !out3.includes(x)) out3.push(x);
4972
- return out3;
4970
+ const out4 = [];
4971
+ for (const x of v) if (typeof x === "string" && x && !out4.includes(x)) out4.push(x);
4972
+ return out4;
4973
4973
  }
4974
4974
  function runtimeOf(v) {
4975
4975
  return typeof v === "string" && AGENT_RUNTIMES.includes(v) ? v : null;
4976
4976
  }
4977
4977
  function toolsOf(v) {
4978
4978
  if (!Array.isArray(v)) return null;
4979
- const out3 = [];
4980
- for (const x of v) if (typeof x === "string" && x && !out3.includes(x)) out3.push(x);
4981
- return out3;
4979
+ const out4 = [];
4980
+ for (const x of v) if (typeof x === "string" && x && !out4.includes(x)) out4.push(x);
4981
+ return out4;
4982
4982
  }
4983
4983
  function resolveTools(a) {
4984
4984
  const base2 = a.tools ?? [...DEFAULT_AGENT_TOOLS];
4985
4985
  const canMention = (a.mentions ?? []).length > 0;
4986
- const out3 = base2.filter((t) => t !== MENTION_TOOL);
4987
- if (canMention) out3.push(MENTION_TOOL);
4988
- return out3;
4986
+ const out4 = base2.filter((t) => t !== MENTION_TOOL);
4987
+ if (canMention) out4.push(MENTION_TOOL);
4988
+ return out4;
4989
4989
  }
4990
4990
  function detectCycle(nodes) {
4991
4991
  const edges = new Map(nodes.map((n2) => [n2.name, n2.mentions]));
@@ -5392,7 +5392,7 @@ var init_prd_status = __esm({
5392
5392
 
5393
5393
  // ../shared/src/cron-expr.ts
5394
5394
  function parseField(raw2, lo, hi) {
5395
- const out3 = /* @__PURE__ */ new Set();
5395
+ const out4 = /* @__PURE__ */ new Set();
5396
5396
  for (const part of raw2.split(",")) {
5397
5397
  const slash = part.indexOf("/");
5398
5398
  const rangePart = slash === -1 ? part : part.slice(0, slash);
@@ -5418,9 +5418,9 @@ function parseField(raw2, lo, hi) {
5418
5418
  to = Number(m[2]);
5419
5419
  }
5420
5420
  if (from < lo || to > hi || from > to) return null;
5421
- for (let v = from; v <= to; v += step) out3.add(v);
5421
+ for (let v = from; v <= to; v += step) out4.add(v);
5422
5422
  }
5423
- return out3.size ? out3 : null;
5423
+ return out4.size ? out4 : null;
5424
5424
  }
5425
5425
  function parseCron(expr) {
5426
5426
  const parts = expr.trim().split(/\s+/);
@@ -6055,9 +6055,20 @@ var init_api = __esm({
6055
6055
  // SPEC-255 · ADR-0064 · rename slug project (efek: DSN, Help Center, sync).
6056
6056
  projectRename: (id) => `${API}/projects/${encodeURIComponent(id)}/rename`,
6057
6057
  branches: (id) => `${API}/projects/${id}/branches`,
6058
- // SPEC-360 · ADR-0077 · branch ter-merge (nilai turunan git) + hapus batch local/origin.
6059
- branchesUnused: (id, base2) => `${API}/projects/${id}/branches/unused${base2 ? `?base=${encodeURIComponent(base2)}` : ""}`,
6058
+ // SPEC-360 · ADR-0077 · daftar branch (nilai turunan git) + hapus batch local/origin.
6059
+ // SPEC-859 · `include=all` memuat branch yang belum ter-merge; tanpa itu hanya yang ter-merge.
6060
+ branchesUnused: (id, base2, include) => {
6061
+ const q = new URLSearchParams();
6062
+ if (base2) q.set("base", base2);
6063
+ if (include) q.set("include", include);
6064
+ const s2 = q.toString();
6065
+ return `${API}/projects/${id}/branches/unused${s2 ? `?${s2}` : ""}`;
6066
+ },
6060
6067
  branchesDelete: (id) => `${API}/projects/${id}/branches/delete`,
6068
+ // SPEC-861 · ADR-0132 · worktree HIDUP (nilai turunan git) + sinyal mahal per baris + hapus batch.
6069
+ worktrees: (id) => `${API}/projects/${id}/worktrees`,
6070
+ worktreeStats: (id, name2) => `${API}/projects/${id}/worktrees/stats?name=${encodeURIComponent(name2)}`,
6071
+ worktreesDelete: (id) => `${API}/projects/${id}/worktrees/delete`,
6061
6072
  // SPEC-217 · path per-mesin (LocalBinding, tak disync) + clone dari gitRemote
6062
6073
  binding: (id) => `${API}/projects/${id}/binding`,
6063
6074
  clone: (id) => `${API}/projects/${id}/clone`,
@@ -6307,13 +6318,13 @@ function resolveLink(fromRel, target2) {
6307
6318
  if (clean2.startsWith("/")) return clean2.slice(1).split("/").filter((p) => p && p !== ".").join("/");
6308
6319
  const dir2 = fromRel.includes("/") ? fromRel.slice(0, fromRel.lastIndexOf("/")) : "";
6309
6320
  const parts = (dir2 ? dir2.split("/") : []).concat(clean2.replace(/^\.\//, "").split("/"));
6310
- const out3 = [];
6321
+ const out4 = [];
6311
6322
  for (const p of parts) {
6312
6323
  if (p === "" || p === ".") continue;
6313
- if (p === "..") out3.pop();
6314
- else out3.push(p);
6324
+ if (p === "..") out4.pop();
6325
+ else out4.push(p);
6315
6326
  }
6316
- return out3.join("/");
6327
+ return out4.join("/");
6317
6328
  }
6318
6329
  function linkedSetFrom(indexRel, docs, read2) {
6319
6330
  const inCorpus = new Set(docs);
@@ -6755,17 +6766,17 @@ function matchesEvent(subscribed, type) {
6755
6766
  return false;
6756
6767
  }
6757
6768
  function projectRow(def2, row) {
6758
- const out3 = {};
6759
- for (const f of def2.fields) if (f in row) out3[f] = iso2(row[f]);
6760
- return out3;
6769
+ const out4 = {};
6770
+ for (const f of def2.fields) if (f in row) out4[f] = iso2(row[f]);
6771
+ return out4;
6761
6772
  }
6762
6773
  function diffFields(def2, before2, after) {
6763
- const out3 = [];
6774
+ const out4 = [];
6764
6775
  for (const f of def2.fields) {
6765
6776
  if (IGNORED_FIELDS.has(f)) continue;
6766
- if (JSON.stringify(iso2(before2[f]) ?? null) !== JSON.stringify(iso2(after[f]) ?? null)) out3.push(f);
6777
+ if (JSON.stringify(iso2(before2[f]) ?? null) !== JSON.stringify(iso2(after[f]) ?? null)) out4.push(f);
6767
6778
  }
6768
- return out3;
6779
+ return out4;
6769
6780
  }
6770
6781
  function eventTypeFor(def2, action, changed) {
6771
6782
  if (action === "updated") {
@@ -7298,9 +7309,9 @@ var init_mcp_shape = __esm({
7298
7309
  OBJECTIVE_CLIP = 200;
7299
7310
  MARK = "\u2026 (dipotong)";
7300
7311
  pick = (row, keys) => {
7301
- const out3 = {};
7302
- for (const k of keys) if (row[k] !== void 0) out3[k] = row[k];
7303
- return out3;
7312
+ const out4 = {};
7313
+ for (const k of keys) if (row[k] !== void 0) out4[k] = row[k];
7314
+ return out4;
7304
7315
  };
7305
7316
  shapeProject = (row) => pick(row, ["id", "name", "kind", "desc", "backlog", "topStage", "coverage", "schedulerOptIn", "leadOptIn"]);
7306
7317
  shapeProjectDetail = (row) => pick(row, [
@@ -7361,9 +7372,9 @@ var init_mcp_shape = __esm({
7361
7372
 
7362
7373
  // ../shared/src/mcp-catalog.ts
7363
7374
  function query(pairs) {
7364
- const out3 = {};
7365
- for (const [k, v] of Object.entries(pairs)) if (v !== void 0) out3[k] = v;
7366
- return out3;
7375
+ const out4 = {};
7376
+ for (const [k, v] of Object.entries(pairs)) if (v !== void 0) out4[k] = v;
7377
+ return out4;
7367
7378
  }
7368
7379
  function reshapePage(raw2, fn2) {
7369
7380
  const p = raw2;
@@ -8634,9 +8645,9 @@ function resolveGoalCondition(a, override, template) {
8634
8645
  return (picked ? picked.trim() : defaultGoalCondition(a)).slice(0, GOAL_MAX);
8635
8646
  }
8636
8647
  function goalChunks(line, size = GOAL_CHUNK) {
8637
- const out3 = [];
8638
- for (let i = 0; i < line.length; i += size) out3.push(line.slice(i, i + size));
8639
- return out3;
8648
+ const out4 = [];
8649
+ for (let i = 0; i < line.length; i += size) out4.push(line.slice(i, i + size));
8650
+ return out4;
8640
8651
  }
8641
8652
  var GOAL_MAX, goalOneLine, GOAL_CHUNK;
8642
8653
  var init_goal = __esm({
@@ -8782,16 +8793,16 @@ function agentPromptOf(def2, roster) {
8782
8793
  }
8783
8794
  function renderAgentsJson(defs) {
8784
8795
  if (defs.length === 0) return "";
8785
- const out3 = {};
8796
+ const out4 = {};
8786
8797
  for (const d of defs) {
8787
- out3[d.name] = {
8798
+ out4[d.name] = {
8788
8799
  description: d.description,
8789
8800
  prompt: agentPromptOf(d, defs),
8790
8801
  tools: resolveTools({ tools: d.tools, mentions: d.mentions }),
8791
8802
  ...d.model ? { model: d.model } : {}
8792
8803
  };
8793
8804
  }
8794
- return JSON.stringify(out3);
8805
+ return JSON.stringify(out4);
8795
8806
  }
8796
8807
  function agentRosterBlock(defs) {
8797
8808
  if (defs.length === 0) return "";
@@ -8924,27 +8935,27 @@ function asSkill(dir2, pkg, name2 = basename2(dir2)) {
8924
8935
  return { id: pkg ? `${pkg}:${name2}` : name2, name: name2, pkg, dir: dir2 };
8925
8936
  }
8926
8937
  function skillsUnder(dir2, pkg, depth = 2) {
8927
- const out3 = [];
8938
+ const out4 = [];
8928
8939
  for (const name2 of dirsIn(dir2)) {
8929
8940
  const sub = join2(dir2, name2);
8930
- if (isSkillDir(sub)) out3.push(asSkill(sub, pkg, name2));
8931
- else if (depth > 1) out3.push(...skillsUnder(sub, pkg, depth - 1));
8941
+ if (isSkillDir(sub)) out4.push(asSkill(sub, pkg, name2));
8942
+ else if (depth > 1) out4.push(...skillsUnder(sub, pkg, depth - 1));
8932
8943
  }
8933
- return out3;
8944
+ return out4;
8934
8945
  }
8935
8946
  function manifestSkills(installPath, pkg) {
8936
8947
  for (const marker of [".claude-plugin", ".codex-plugin"]) {
8937
8948
  const j = readJson(join2(installPath, marker, "plugin.json"));
8938
8949
  const declared = j?.skills;
8939
8950
  if (!Array.isArray(declared)) continue;
8940
- const out3 = [];
8951
+ const out4 = [];
8941
8952
  for (const rel of declared) {
8942
8953
  if (typeof rel !== "string" || !rel) continue;
8943
8954
  const p = resolve6(installPath, rel);
8944
8955
  const dir2 = basename2(p) === "SKILL.md" ? dirname3(p) : p;
8945
- if (isSkillDir(dir2)) out3.push(asSkill(dir2, pkg));
8956
+ if (isSkillDir(dir2)) out4.push(asSkill(dir2, pkg));
8946
8957
  }
8947
- if (out3.length) return out3;
8958
+ if (out4.length) return out4;
8948
8959
  }
8949
8960
  return [];
8950
8961
  }
@@ -8953,15 +8964,15 @@ function pluginSkills(installPath, pkg) {
8953
8964
  return declared.length ? declared : skillsUnder(join2(installPath, "skills"), pkg);
8954
8965
  }
8955
8966
  function lockPluginNames(agentsHome) {
8956
- const out3 = /* @__PURE__ */ new Map();
8967
+ const out4 = /* @__PURE__ */ new Map();
8957
8968
  const j = readJson(join2(agentsHome, ".skill-lock.json"));
8958
8969
  const skills = j?.skills;
8959
- if (!skills || typeof skills !== "object") return out3;
8970
+ if (!skills || typeof skills !== "object") return out4;
8960
8971
  for (const [name2, entry] of Object.entries(skills)) {
8961
8972
  const pkg = entry?.pluginName;
8962
- if (typeof pkg === "string" && pkg.trim()) out3.set(name2, pkg.trim());
8973
+ if (typeof pkg === "string" && pkg.trim()) out4.set(name2, pkg.trim());
8963
8974
  }
8964
- return out3;
8975
+ return out4;
8965
8976
  }
8966
8977
  function splitPluginKey(key) {
8967
8978
  const at = key.lastIndexOf("@");
@@ -8971,39 +8982,39 @@ function manifestRoots(home3) {
8971
8982
  const j = readJson(join2(home3, "plugins", "installed_plugins.json"));
8972
8983
  const plugins = j?.plugins;
8973
8984
  if (!plugins || typeof plugins !== "object") return [];
8974
- const out3 = [];
8985
+ const out4 = [];
8975
8986
  for (const [key, entries] of Object.entries(plugins)) {
8976
8987
  const { pkg, marketplace } = splitPluginKey(key);
8977
8988
  for (const e of Array.isArray(entries) ? entries : []) {
8978
8989
  const p = e?.installPath;
8979
- if (typeof p === "string" && p) out3.push({ pkg, marketplace, dir: p });
8990
+ if (typeof p === "string" && p) out4.push({ pkg, marketplace, dir: p });
8980
8991
  }
8981
8992
  }
8982
- return out3;
8993
+ return out4;
8983
8994
  }
8984
8995
  function cacheRoots(home3) {
8985
8996
  const cache7 = join2(home3, "plugins", "cache");
8986
- const out3 = [];
8997
+ const out4 = [];
8987
8998
  for (const marketplace of dirsIn(cache7))
8988
8999
  for (const pkg of dirsIn(join2(cache7, marketplace)))
8989
9000
  for (const version2 of dirsIn(join2(cache7, marketplace, pkg)))
8990
- out3.push({ pkg, marketplace, dir: join2(cache7, marketplace, pkg, version2) });
8991
- return out3;
9001
+ out4.push({ pkg, marketplace, dir: join2(cache7, marketplace, pkg, version2) });
9002
+ return out4;
8992
9003
  }
8993
9004
  function disabledPlugins(agent, home3) {
8994
- const out3 = /* @__PURE__ */ new Set();
9005
+ const out4 = /* @__PURE__ */ new Set();
8995
9006
  if (agent === "claude") {
8996
9007
  const en = readJson(join2(home3, "settings.json"))?.enabledPlugins;
8997
9008
  if (en && typeof en === "object") {
8998
- for (const [k, v] of Object.entries(en)) if (v === false) out3.add(k);
9009
+ for (const [k, v] of Object.entries(en)) if (v === false) out4.add(k);
8999
9010
  }
9000
- return out3;
9011
+ return out4;
9001
9012
  }
9002
9013
  let toml;
9003
9014
  try {
9004
9015
  toml = readFileSync2(join2(home3, "config.toml"), "utf8");
9005
9016
  } catch {
9006
- return out3;
9017
+ return out4;
9007
9018
  }
9008
9019
  let current = null;
9009
9020
  for (const raw2 of toml.split("\n")) {
@@ -9014,9 +9025,9 @@ function disabledPlugins(agent, home3) {
9014
9025
  current = m ? m[1] : null;
9015
9026
  continue;
9016
9027
  }
9017
- if (current && /^enabled\s*=\s*false\b/.test(line)) out3.add(current);
9028
+ if (current && /^enabled\s*=\s*false\b/.test(line)) out4.add(current);
9018
9029
  }
9019
- return out3;
9030
+ return out4;
9020
9031
  }
9021
9032
  function scanAgentSkills(agent, env = process.env, osHome = homedir2()) {
9022
9033
  const home3 = agentSkillHome(agent, env, osHome);
@@ -9159,10 +9170,10 @@ function ensureSpawnHelpersExecutable(paths2, ops) {
9159
9170
  }
9160
9171
  return fixed;
9161
9172
  }
9162
- function repairSpawnHelper(resolve19, notify) {
9173
+ function repairSpawnHelper(resolve21, notify) {
9163
9174
  let ptyDir;
9164
9175
  try {
9165
- ptyDir = dirname4(resolve19("node-pty/package.json"));
9176
+ ptyDir = dirname4(resolve21("node-pty/package.json"));
9166
9177
  } catch {
9167
9178
  return [];
9168
9179
  }
@@ -9180,10 +9191,10 @@ function repairSpawnHelper(resolve19, notify) {
9180
9191
  if (fixed.length) notify?.("hanoman \xB7 memperbaiki izin `spawn-helper` node-pty (sekali per instalasi)\n");
9181
9192
  return fixed;
9182
9193
  }
9183
- function ensureSpawnHelperOnce(resolve19, notify) {
9194
+ function ensureSpawnHelperOnce(resolve21, notify) {
9184
9195
  if (repaired) return [];
9185
9196
  repaired = true;
9186
- return repairSpawnHelper(resolve19, notify);
9197
+ return repairSpawnHelper(resolve21, notify);
9187
9198
  }
9188
9199
  var repaired;
9189
9200
  var init_spawn_helper = __esm({
@@ -9222,14 +9233,14 @@ function registerWebhookTap(s2) {
9222
9233
  }
9223
9234
  async function cascadeCounts(base2, def2, id) {
9224
9235
  if (!def2.cascade?.length) return void 0;
9225
- const out3 = {};
9236
+ const out4 = {};
9226
9237
  for (const child of def2.cascade) {
9227
9238
  try {
9228
- out3[child] = await base2[child].count({ where: { projectId: id } });
9239
+ out4[child] = await base2[child].count({ where: { projectId: id } });
9229
9240
  } catch {
9230
9241
  }
9231
9242
  }
9232
- return out3;
9243
+ return out4;
9233
9244
  }
9234
9245
  function webhookTap(base2) {
9235
9246
  const pre = async (model, where) => {
@@ -9276,9 +9287,9 @@ function webhookTap(base2) {
9276
9287
  const def2 = on(model);
9277
9288
  const before2 = def2 ? await pre(model, args.where) : null;
9278
9289
  const cascade = def2 && before2 ? await cascadeCounts(base2, def2, String(before2.id)) : void 0;
9279
- const out3 = await query2(args);
9290
+ const out4 = await query2(args);
9280
9291
  if (def2 && before2) fire({ def: def2, action: "deleted", before: before2, after: null, changed: [], cascade });
9281
- return out3;
9292
+ return out4;
9282
9293
  },
9283
9294
  // `liveSpecs` memajukan stage lewat CAS `updateMany` — jalur perubahan stage yang PALING
9284
9295
  // sering dipakai. Melewatkannya berarti melewatkan peristiwa yang paling diminta.
@@ -9290,7 +9301,7 @@ function webhookTap(base2) {
9290
9301
  before2 = await del(base2, model).findMany({ where: args.where });
9291
9302
  } catch {
9292
9303
  }
9293
- const out3 = await query2(args);
9304
+ const out4 = await query2(args);
9294
9305
  if (before2.length) {
9295
9306
  const ids = before2.map((r) => r.id);
9296
9307
  let after = [];
@@ -9306,7 +9317,7 @@ function webhookTap(base2) {
9306
9317
  if (changed.length) fire({ def: def2, action: "updated", before: b, after: aft, changed });
9307
9318
  }
9308
9319
  }
9309
- return out3;
9320
+ return out4;
9310
9321
  },
9311
9322
  async deleteMany({ model, args, query: query2 }) {
9312
9323
  const def2 = on(model);
@@ -9316,10 +9327,10 @@ function webhookTap(base2) {
9316
9327
  before2 = await del(base2, model).findMany({ where: args.where });
9317
9328
  } catch {
9318
9329
  }
9319
- const out3 = await query2(args);
9330
+ const out4 = await query2(args);
9320
9331
  for (const b of before2)
9321
9332
  fire({ def: def2, action: "deleted", before: b, after: null, changed: [] });
9322
- return out3;
9333
+ return out4;
9323
9334
  }
9324
9335
  }
9325
9336
  }
@@ -9366,6 +9377,7 @@ var init_db = __esm({
9366
9377
  process.env.DATABASE_URL = url;
9367
9378
  mkdirSync2(dirname5(dbFilePath(url)), { recursive: true, mode: 448 });
9368
9379
  base = new PrismaClient();
9380
+ void base.$queryRawUnsafe("PRAGMA journal_mode=WAL").catch((e) => console.warn("tak bisa menyetel journal_mode=WAL:", e));
9369
9381
  prisma = base.$extends(webhookTap(base));
9370
9382
  }
9371
9383
  });
@@ -9382,12 +9394,12 @@ var init_stage_machine = __esm({
9382
9394
  // src/services/session-phases.ts
9383
9395
  import { readFileSync as readFileSync3, readdirSync as readdirSync3 } from "node:fs";
9384
9396
  function recorded(file) {
9385
- const out3 = /* @__PURE__ */ new Map();
9397
+ const out4 = /* @__PURE__ */ new Map();
9386
9398
  let raw2;
9387
9399
  try {
9388
9400
  raw2 = readFileSync3(file, "utf8");
9389
9401
  } catch {
9390
- return out3;
9402
+ return out4;
9391
9403
  }
9392
9404
  for (const line of raw2.split("\n")) {
9393
9405
  const trimmed = line.trimEnd();
@@ -9395,9 +9407,9 @@ function recorded(file) {
9395
9407
  if (i < 1) continue;
9396
9408
  const state = trimmed.slice(i + 1);
9397
9409
  if (state !== "done" && state !== "skipped") continue;
9398
- out3.set(trimmed.slice(0, i).trim(), state);
9410
+ out4.set(trimmed.slice(0, i).trim(), state);
9399
9411
  }
9400
- return out3;
9412
+ return out4;
9401
9413
  }
9402
9414
  function readPhases(file, flow) {
9403
9415
  const seen = recorded(file);
@@ -9736,7 +9748,7 @@ import { lookup as dnsLookup2 } from "node:dns/promises";
9736
9748
  import { request as httpRequest } from "node:http";
9737
9749
  import { request as httpsRequest } from "node:https";
9738
9750
  async function pinnedRequest(input) {
9739
- return new Promise((resolve19, reject2) => {
9751
+ return new Promise((resolve21, reject2) => {
9740
9752
  const transport = input.url.protocol === "https:" ? httpsRequest : httpRequest;
9741
9753
  const req = transport({
9742
9754
  protocol: input.url.protocol,
@@ -9764,7 +9776,7 @@ async function pinnedRequest(input) {
9764
9776
  }
9765
9777
  chunks.push(chunk);
9766
9778
  });
9767
- response.on("end", () => resolve19({
9779
+ response.on("end", () => resolve21({
9768
9780
  status: response.statusCode ?? 0,
9769
9781
  headers: response.headers,
9770
9782
  body: Buffer.concat(chunks)
@@ -10206,6 +10218,12 @@ import { randomUUID as randomUUID2 } from "node:crypto";
10206
10218
  import { mkdirSync as mkdirSync4, statSync as statSync2, writeFileSync as writeFileSync2 } from "node:fs";
10207
10219
  import { dirname as dirname7 } from "node:path";
10208
10220
  import { tmpdir } from "node:os";
10221
+ function noTtyPromptEnv() {
10222
+ const path2 = askpassDenyPath();
10223
+ mkdirSync4(dirname7(path2), { recursive: true, mode: 448 });
10224
+ writeFileSync2(path2, ASKPASS_DENY, { mode: 448 });
10225
+ return { SSH_ASKPASS: path2, SSH_ASKPASS_REQUIRE: "force", GIT_TERMINAL_PROMPT: "0" };
10226
+ }
10209
10227
  function tmux(...args) {
10210
10228
  try {
10211
10229
  return execFileSync("tmux", ["-L", socket(), "-f", "/dev/null", ...args], {
@@ -10222,15 +10240,28 @@ function tmux(...args) {
10222
10240
  }
10223
10241
  }
10224
10242
  function listPanes() {
10225
- let out3;
10243
+ let out4;
10226
10244
  try {
10227
- out3 = tmux("list-panes", "-a", "-F", FMT);
10245
+ out4 = tmux("list-panes", "-a", "-F", FMT);
10228
10246
  } catch (e) {
10229
10247
  if (e instanceof TmuxError && e.noServer) return [];
10230
10248
  throw e;
10231
10249
  }
10232
- return out3.split("\n").filter(Boolean).flatMap((line) => {
10233
- const [n2, projectId, specId, flow, phaseFile, cwd, dead, code, decisionFile, branch, agent] = line.split(" ");
10250
+ return out4.split("\n").filter(Boolean).flatMap((line) => {
10251
+ const [
10252
+ n2,
10253
+ projectId,
10254
+ specId,
10255
+ flow,
10256
+ phaseFile,
10257
+ cwd,
10258
+ dead,
10259
+ code,
10260
+ decisionFile,
10261
+ branch,
10262
+ agent,
10263
+ alternate
10264
+ ] = line.split(" ");
10234
10265
  if (!n2?.startsWith(PREFIX)) return [];
10235
10266
  const exited = dead === "1";
10236
10267
  return [{
@@ -10248,7 +10279,8 @@ function listPanes() {
10248
10279
  // SPEC-196 · sesi hidup dengan marker keputusan terisi = menunggu manusia.
10249
10280
  decision: !exited && !!decisionFile && markerFilled(decisionFile),
10250
10281
  // SPEC-338 · sesi yang lahir sebelum ADR-0074 tak punya opsi ini → claude.
10251
- agent: agent === "codex" ? "codex" : "claude"
10282
+ agent: agent === "codex" ? "codex" : "claude",
10283
+ altScreen: alternate === "1"
10252
10284
  }];
10253
10285
  });
10254
10286
  }
@@ -10269,8 +10301,8 @@ function sessionKind(o, projectId, cwd) {
10269
10301
  }
10270
10302
  function captureTranscript(id) {
10271
10303
  try {
10272
- const out3 = tmux("capture-pane", "-p", "-J", "-S", "-50000", "-t", name(id));
10273
- return out3.trim() ? out3 : null;
10304
+ const out4 = tmux("capture-pane", "-p", "-J", "-S", "-50000", "-t", name(id));
10305
+ return out4.trim() ? out4 : null;
10274
10306
  } catch {
10275
10307
  return null;
10276
10308
  }
@@ -10334,6 +10366,9 @@ function createSession(projectId, cwd, opts = {}) {
10334
10366
  if (!opts.command && agent === "claude") {
10335
10367
  for (const [k, v] of Object.entries(rootBypassEnv())) envPairs.push(`${k}=${sq(v)}`);
10336
10368
  }
10369
+ if (!opts.command) {
10370
+ for (const [k, v] of Object.entries(noTtyPromptEnv())) envPairs.push(`${k}=${sq(v)}`);
10371
+ }
10337
10372
  if (opts.phaseFile) {
10338
10373
  mkdirSync4(dirname7(opts.phaseFile), { recursive: true });
10339
10374
  envPairs.push(`HANOMAN_PHASE_FILE=${sq(opts.phaseFile)}`);
@@ -10534,18 +10569,18 @@ function sessionPhases(id) {
10534
10569
  return readPhases(p.phaseFile, p.flow);
10535
10570
  }
10536
10571
  function sessionPhasesBySpec() {
10537
- const out3 = /* @__PURE__ */ new Map();
10572
+ const out4 = /* @__PURE__ */ new Map();
10538
10573
  let panes;
10539
10574
  try {
10540
10575
  panes = listPanes();
10541
10576
  } catch {
10542
- return out3;
10577
+ return out4;
10543
10578
  }
10544
10579
  for (const p of panes) {
10545
10580
  if (!p.specId || !p.flow || !p.phaseFile) continue;
10546
- out3.set(p.specId, { phases: readPhases(p.phaseFile, p.flow), cwd: p.cwd });
10581
+ out4.set(p.specId, { phases: readPhases(p.phaseFile, p.flow), cwd: p.cwd });
10547
10582
  }
10548
- return out3;
10583
+ return out4;
10549
10584
  }
10550
10585
  function broadcast(a, f) {
10551
10586
  const msg = frame(f);
@@ -10602,6 +10637,11 @@ function pollPhases(p, a) {
10602
10637
  a.lastPhases = json;
10603
10638
  broadcast(a, { t: "phase", phases, complete });
10604
10639
  }
10640
+ function pollAlt(p, a) {
10641
+ if (p.altScreen === a.lastAlt) return;
10642
+ a.lastAlt = p.altScreen;
10643
+ broadcast(a, { t: "alt", on: p.altScreen });
10644
+ }
10605
10645
  function startPoll() {
10606
10646
  if (poll) return;
10607
10647
  poll = setInterval(() => {
@@ -10616,7 +10656,11 @@ function startPoll() {
10616
10656
  const p = live.get(id);
10617
10657
  if (!p) end(id, 0);
10618
10658
  else if (p.exited) end(id, p.code);
10619
- else pollPhases(p, attached.get(id));
10659
+ else {
10660
+ const a = attached.get(id);
10661
+ pollPhases(p, a);
10662
+ pollAlt(p, a);
10663
+ }
10620
10664
  }
10621
10665
  if (attached.size === 0 && poll) {
10622
10666
  clearInterval(poll);
@@ -10633,14 +10677,18 @@ function attach(id, c) {
10633
10677
  }
10634
10678
  if (p.exited) {
10635
10679
  const screen = tmux("capture-pane", "-p", "-e", "-J", "-S", "-2000", "-t", name(id));
10636
- if (screen.trim()) c.send(frame({ t: "data", d: screen.replace(/\n/g, "\r\n") }));
10680
+ if (screen.trim()) {
10681
+ c.send(frame({ t: "data", d: stripTerminalQueries(screen.replace(/\n/g, "\r\n")) }));
10682
+ }
10637
10683
  c.send(frame({ t: "exit", code: p.code }));
10638
10684
  c.close();
10639
10685
  return;
10640
10686
  }
10641
10687
  const a = attached.get(id) ?? open(id);
10642
10688
  a.clients.add(c);
10643
- if (a.scrollback) c.send(frame({ t: "data", d: a.scrollback }));
10689
+ if (a.scrollback) c.send(frame({ t: "data", d: stripTerminalQueries(a.scrollback) }));
10690
+ a.lastAlt = p.altScreen;
10691
+ c.send(frame({ t: "alt", on: p.altScreen }));
10644
10692
  if (p.flow && p.phaseFile) {
10645
10693
  const phases = readPhases(p.phaseFile, p.flow);
10646
10694
  const complete = paneComplete(p);
@@ -10648,8 +10696,11 @@ function attach(id, c) {
10648
10696
  c.send(frame({ t: "phase", phases, complete }));
10649
10697
  }
10650
10698
  }
10651
- function writeTo(id, d) {
10652
- attached.get(id)?.pty.write(d);
10699
+ function writeTo(id, d, from) {
10700
+ const a = attached.get(id);
10701
+ if (!a) return;
10702
+ if (from && isTerminalResponse(d) && a.clients.values().next().value !== from) return;
10703
+ a.pty.write(d);
10653
10704
  }
10654
10705
  function resize(id, cols, rows) {
10655
10706
  attached.get(id)?.pty.resize(cols, rows);
@@ -10685,7 +10736,7 @@ function spawnPty(...args) {
10685
10736
  );
10686
10737
  }
10687
10738
  }
10688
- var socket, PREFIX, MAX_SCROLLBACK, SCROLLBACK_SLACK, POLL_MS, markerFilled, attached, claudeBin, shellBin, codexBin, agentBin, rootBypassEnv, frame, name, promptFilePath, goalGatePath, goalStatePath, agentsFilePath, NO_SERVER, TmuxError, sq, idFor, FMT, listSessions, liveDecisions, getSession, hooks, emitBirth, emitDeath, customAgentSource, customAgentsFor, sleep, paneText, dialogIO, DIALOG_CAPTURE_LINES, GOAL_ARMED_MARKERS, goalArmed, COALESCE_MS, COALESCE_MAX_BYTES, trimScrollback, phaseKey, paneComplete, sessionFinished, poll, detach;
10739
+ var socket, PREFIX, MAX_SCROLLBACK, SCROLLBACK_SLACK, POLL_MS, markerFilled, attached, claudeBin, shellBin, codexBin, agentBin, rootBypassEnv, frame, name, promptFilePath, goalGatePath, goalStatePath, agentsFilePath, askpassDenyPath, ASKPASS_DENY, NO_SERVER, TmuxError, sq, idFor, FMT, listSessions, liveDecisions, getSession, hooks, emitBirth, emitDeath, customAgentSource, customAgentsFor, sleep, paneText, dialogIO, DIALOG_CAPTURE_LINES, GOAL_ARMED_MARKERS, goalArmed, COALESCE_MS, COALESCE_MAX_BYTES, trimScrollback, phaseKey, paneComplete, sessionFinished, poll, TERMINAL_QUERY, stripTerminalQueries, TERMINAL_RESPONSE, isTerminalResponse, detach;
10689
10740
  var init_pty = __esm({
10690
10741
  "src/services/pty.ts"() {
10691
10742
  "use strict";
@@ -10721,6 +10772,12 @@ var init_pty = __esm({
10721
10772
  goalGatePath = (id) => `${tmpdir()}/hanoman-goal-gates/${id}.sh`;
10722
10773
  goalStatePath = (id) => `${tmpdir()}/hanoman-goal-gates/${id}.count`;
10723
10774
  agentsFilePath = (id) => `${tmpdir()}/hanoman-agents/${id}.json`;
10775
+ askpassDenyPath = () => `${tmpdir()}/hanoman-askpass/deny.sh`;
10776
+ ASKPASS_DENY = `#!/bin/sh
10777
+ echo "hanoman: tak ada manusia di pane ini \u2014 permintaan ketikan ditolak: $1" >&2
10778
+ echo "hanoman: buka kuncinya di luar sesi (mis. ssh-add ~/.ssh/id_rsa), lalu ulangi." >&2
10779
+ exit 1
10780
+ `;
10724
10781
  NO_SERVER = /no server running|error connecting to/i;
10725
10782
  TmuxError = class extends Error {
10726
10783
  constructor(message, noServer) {
@@ -10741,7 +10798,8 @@ var init_pty = __esm({
10741
10798
  "#{pane_dead_status}",
10742
10799
  "#{@hanoman_decision_file}",
10743
10800
  "#{@hanoman_branch}",
10744
- "#{@hanoman_agent}"
10801
+ "#{@hanoman_agent}",
10802
+ "#{alternate_on}"
10745
10803
  ].join(" ");
10746
10804
  listSessions = () => listPanes().map(({ id, projectId, specId, flow, cwd, exited, code, branch, decision, agent }) => ({
10747
10805
  id,
@@ -10823,6 +10881,38 @@ var init_pty = __esm({
10823
10881
  const p = getSession(id);
10824
10882
  return !!p && paneComplete(p);
10825
10883
  };
10884
+ TERMINAL_QUERY = new RegExp([
10885
+ "\\x1b\\[[?>=]?[0-9;]*c",
10886
+ // Device Attributes 1/2/3
10887
+ "\\x1b\\[>[0-9;]*q",
10888
+ // XTVERSION — `>` wajib, DECSCUSR `\\x1b[2 q` bukan ini
10889
+ "\\x1b\\[\\??[0-9;]*n",
10890
+ // DSR/DECDSR, termasuk `?996n` (skema warna)
10891
+ "\\x1b\\[\\??[0-9;]*\\$[py]",
10892
+ // DECRQM & balasannya
10893
+ "\\x1b\\][0-9][0-9;]*\\?(?:\\x07|\\x1b\\\\)",
10894
+ // OSC 4/10/11/12 `…;?`
10895
+ "\\x1bP[0-9]*[$+][a-z][^\\x1b]*\\x1b\\\\",
10896
+ // DECRQSS & XTGETTCAP
10897
+ "\\x1b\\[(?:11|13|14|15|16|18|19|20|21)t"
10898
+ // XTWINOPS laporan — berparameter TUNGGAL
10899
+ ].join("|"), "g");
10900
+ stripTerminalQueries = (s2) => s2.replace(TERMINAL_QUERY, "");
10901
+ TERMINAL_RESPONSE = new RegExp([
10902
+ "\\x1b\\[[?>][0-9;]*c",
10903
+ // balasan DA
10904
+ "\\x1b\\[\\??[0-9]+;[0-9]+R",
10905
+ // CPR / DECXCPR
10906
+ "\\x1b\\[\\??[0-9;]*n",
10907
+ // balasan DSR
10908
+ "\\x1b\\[\\??[0-9;]*\\$y",
10909
+ // DECRPM
10910
+ "\\x1b\\][0-9][0-9;]*;[^\\x07\\x1b]*(?:\\x07|\\x1b\\\\)",
10911
+ // balasan warna OSC
10912
+ "\\x1bP[0-9]*[$+>][a-z|][^\\x1b]*\\x1b\\\\"
10913
+ // DECRPSS / XTGETTCAP / XTVERSION
10914
+ ].join("|"), "g");
10915
+ isTerminalResponse = (d) => d.length > 0 && d.replace(TERMINAL_RESPONSE, "") === "";
10826
10916
  detach = (id, c) => {
10827
10917
  attached.get(id)?.clients.delete(c);
10828
10918
  };
@@ -11003,14 +11093,14 @@ function jsonSafe(v) {
11003
11093
  return v === void 0 ? null : JSON.parse(JSON.stringify(v));
11004
11094
  }
11005
11095
  function coerce2(entity, data) {
11006
- const out3 = {};
11096
+ const out4 = {};
11007
11097
  for (const f of FIELDS[entity]) {
11008
11098
  if (!(f in data)) continue;
11009
11099
  let v = data[f];
11010
11100
  if (DATE_FIELDS[entity].includes(f) && typeof v === "string") v = new Date(v);
11011
- out3[f] = v;
11101
+ out4[f] = v;
11012
11102
  }
11013
- return out3;
11103
+ return out4;
11014
11104
  }
11015
11105
  async function snapshot(entity, id) {
11016
11106
  const row = await DELEGATE[entity].findUnique({ where: { id } });
@@ -12326,14 +12416,14 @@ var require_filesystem = __commonJS({
12326
12416
  });
12327
12417
  return buffer2.subarray(0, bytesRead);
12328
12418
  };
12329
- var readFile6 = (path2) => new Promise((resolve19, reject2) => {
12419
+ var readFile6 = (path2) => new Promise((resolve21, reject2) => {
12330
12420
  fs.open(path2, "r", (err, fd) => {
12331
12421
  if (err) {
12332
12422
  reject2(err);
12333
12423
  } else {
12334
12424
  const buffer2 = Buffer.alloc(MAX_LENGTH);
12335
12425
  fs.read(fd, buffer2, 0, MAX_LENGTH, 0, (_, bytesRead) => {
12336
- resolve19(buffer2.subarray(0, bytesRead));
12426
+ resolve21(buffer2.subarray(0, bytesRead));
12337
12427
  fs.close(fd, () => {
12338
12428
  });
12339
12429
  });
@@ -12401,10 +12491,10 @@ var require_detect_libc = __commonJS({
12401
12491
  var commandOut = "";
12402
12492
  var safeCommand = () => {
12403
12493
  if (!commandOut) {
12404
- return new Promise((resolve19) => {
12405
- childProcess.exec(command, (err, out3) => {
12406
- commandOut = err ? " " : out3;
12407
- resolve19(commandOut);
12494
+ return new Promise((resolve21) => {
12495
+ childProcess.exec(command, (err, out4) => {
12496
+ commandOut = err ? " " : out4;
12497
+ resolve21(commandOut);
12408
12498
  });
12409
12499
  });
12410
12500
  }
@@ -12436,8 +12526,8 @@ var require_detect_libc = __commonJS({
12436
12526
  }
12437
12527
  return null;
12438
12528
  };
12439
- var familyFromCommand = (out3) => {
12440
- const [getconf, ldd1] = out3.split(/[\r\n]+/);
12529
+ var familyFromCommand = (out4) => {
12530
+ const [getconf, ldd1] = out4.split(/[\r\n]+/);
12441
12531
  if (getconf && getconf.includes(GLIBC)) {
12442
12532
  return GLIBC;
12443
12533
  }
@@ -12526,8 +12616,8 @@ var require_detect_libc = __commonJS({
12526
12616
  family2 = familyFromReport();
12527
12617
  }
12528
12618
  if (!family2) {
12529
- const out3 = await safeCommand();
12530
- family2 = familyFromCommand(out3);
12619
+ const out4 = await safeCommand();
12620
+ family2 = familyFromCommand(out4);
12531
12621
  }
12532
12622
  }
12533
12623
  }
@@ -12543,8 +12633,8 @@ var require_detect_libc = __commonJS({
12543
12633
  family2 = familyFromReport();
12544
12634
  }
12545
12635
  if (!family2) {
12546
- const out3 = safeCommandSync();
12547
- family2 = familyFromCommand(out3);
12636
+ const out4 = safeCommandSync();
12637
+ family2 = familyFromCommand(out4);
12548
12638
  }
12549
12639
  }
12550
12640
  }
@@ -12590,8 +12680,8 @@ var require_detect_libc = __commonJS({
12590
12680
  return null;
12591
12681
  };
12592
12682
  var versionSuffix = (s2) => s2.trim().split(/\s+/)[1];
12593
- var versionFromCommand = (out3) => {
12594
- const [getconf, ldd1, ldd2] = out3.split(/[\r\n]+/);
12683
+ var versionFromCommand = (out4) => {
12684
+ const [getconf, ldd1, ldd2] = out4.split(/[\r\n]+/);
12595
12685
  if (getconf && getconf.includes(GLIBC)) {
12596
12686
  return versionSuffix(getconf);
12597
12687
  }
@@ -12608,8 +12698,8 @@ var require_detect_libc = __commonJS({
12608
12698
  version3 = versionFromReport();
12609
12699
  }
12610
12700
  if (!version3) {
12611
- const out3 = await safeCommand();
12612
- version3 = versionFromCommand(out3);
12701
+ const out4 = await safeCommand();
12702
+ version3 = versionFromCommand(out4);
12613
12703
  }
12614
12704
  }
12615
12705
  return version3;
@@ -12622,8 +12712,8 @@ var require_detect_libc = __commonJS({
12622
12712
  version3 = versionFromReport();
12623
12713
  }
12624
12714
  if (!version3) {
12625
- const out3 = safeCommandSync();
12626
- version3 = versionFromCommand(out3);
12715
+ const out4 = safeCommandSync();
12716
+ version3 = versionFromCommand(out4);
12627
12717
  }
12628
12718
  }
12629
12719
  return version3;
@@ -16868,7 +16958,7 @@ var init_gateway = __esm({
16868
16958
  const message = sanitizeTelegramOutput(error.message, this.deps.exactSecrets).slice(0, 500);
16869
16959
  updateTelegramRuntimeStatus({ running: false, readiness: "error", lastError: message });
16870
16960
  if (error instanceof TelegramApiError && error.code === 409) return;
16871
- await new Promise((resolve19) => setTimeout(resolve19, 1e3));
16961
+ await new Promise((resolve21) => setTimeout(resolve21, 1e3));
16872
16962
  }
16873
16963
  }
16874
16964
  }
@@ -22999,7 +23089,7 @@ var require_parseParams = __commonJS({
22999
23089
  var require_basename = __commonJS({
23000
23090
  "../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/lib/utils/basename.js"(exports, module) {
23001
23091
  "use strict";
23002
- module.exports = function basename6(path2) {
23092
+ module.exports = function basename7(path2) {
23003
23093
  if (typeof path2 !== "string") {
23004
23094
  return "";
23005
23095
  }
@@ -23026,7 +23116,7 @@ var require_multipart = __commonJS({
23026
23116
  var Dicer = require_Dicer();
23027
23117
  var parseParams = require_parseParams();
23028
23118
  var decodeText = require_decodeText();
23029
- var basename6 = require_basename();
23119
+ var basename7 = require_basename();
23030
23120
  var getLimit = require_getLimit();
23031
23121
  var RE_BOUNDARY = /^boundary$/i;
23032
23122
  var RE_FIELD = /^form-data$/i;
@@ -23143,7 +23233,7 @@ var require_multipart = __commonJS({
23143
23233
  } else if (RE_FILENAME.test(parsed[i][0])) {
23144
23234
  filename = parsed[i][1];
23145
23235
  if (!preservePath) {
23146
- filename = basename6(filename);
23236
+ filename = basename7(filename);
23147
23237
  }
23148
23238
  }
23149
23239
  }
@@ -24226,14 +24316,14 @@ var require_stream_consumer = __commonJS({
24226
24316
  "../node_modules/.pnpm/@fastify+multipart@10.1.0/node_modules/@fastify/multipart/lib/stream-consumer.js"(exports, module) {
24227
24317
  "use strict";
24228
24318
  module.exports = function streamToNull(stream2) {
24229
- return new Promise((resolve19, reject2) => {
24319
+ return new Promise((resolve21, reject2) => {
24230
24320
  stream2.on("data", () => {
24231
24321
  });
24232
24322
  stream2.on("close", () => {
24233
- resolve19();
24323
+ resolve21();
24234
24324
  });
24235
24325
  stream2.on("end", () => {
24236
- resolve19();
24326
+ resolve21();
24237
24327
  });
24238
24328
  stream2.on("error", (error) => {
24239
24329
  reject2(error);
@@ -24682,10 +24772,10 @@ var require_multipart2 = __commonJS({
24682
24772
  }
24683
24773
  };
24684
24774
  const parts = () => {
24685
- return new Promise((resolve19, reject2) => {
24775
+ return new Promise((resolve21, reject2) => {
24686
24776
  handle((val) => {
24687
24777
  if (val instanceof Error) return reject2(val);
24688
- resolve19(val);
24778
+ resolve21(val);
24689
24779
  });
24690
24780
  });
24691
24781
  };
@@ -25073,10 +25163,10 @@ function components(rel) {
25073
25163
  return parts;
25074
25164
  }
25075
25165
  async function canonicalRoot(root) {
25076
- const out3 = await realpath(root).catch(() => denied("root tidak ada"));
25077
- const stat3 = await lstat(out3);
25078
- if (!stat3.isDirectory() || stat3.isSymbolicLink()) denied("root bukan direktori regular");
25079
- return out3;
25166
+ const out4 = await realpath(root).catch(() => denied("root tidak ada"));
25167
+ const stat4 = await lstat(out4);
25168
+ if (!stat4.isDirectory() || stat4.isSymbolicLink()) denied("root bukan direktori regular");
25169
+ return out4;
25080
25170
  }
25081
25171
  function beneath(root, candidate) {
25082
25172
  const rel = relative(root, candidate);
@@ -25093,8 +25183,8 @@ async function ensureRepoParents(root, rel) {
25093
25183
  } catch (error) {
25094
25184
  if (error.code !== "EEXIST") throw error;
25095
25185
  }
25096
- const stat3 = await lstat(current);
25097
- if (stat3.isSymbolicLink() || !stat3.isDirectory()) denied("parent tidak aman");
25186
+ const stat4 = await lstat(current);
25187
+ if (stat4.isSymbolicLink() || !stat4.isDirectory()) denied("parent tidak aman");
25098
25188
  const canonical = await realpath(current);
25099
25189
  if (!beneath(base2, canonical) || canonical !== current) denied("parent keluar root");
25100
25190
  }
@@ -25110,8 +25200,8 @@ function ensureRepoParentsSync(root, rel) {
25110
25200
  } catch (error) {
25111
25201
  if (error.code !== "EEXIST") throw error;
25112
25202
  }
25113
- const stat3 = lstatSync(current);
25114
- if (stat3.isSymbolicLink() || !stat3.isDirectory()) denied("parent tidak aman");
25203
+ const stat4 = lstatSync(current);
25204
+ if (stat4.isSymbolicLink() || !stat4.isDirectory()) denied("parent tidak aman");
25115
25205
  if (realpathSync2(current) !== current || !beneath(base2, current)) denied("parent keluar root");
25116
25206
  }
25117
25207
  }
@@ -25121,14 +25211,14 @@ async function resolveRepoEntry(root, rel, opts = {}) {
25121
25211
  let current = base2;
25122
25212
  for (let i = 0; i < parts.length; i++) {
25123
25213
  current = join6(current, parts[i]);
25124
- const stat3 = await lstat(current).catch((error) => {
25214
+ const stat4 = await lstat(current).catch((error) => {
25125
25215
  if (error.code === "ENOENT" && opts.allowMissingTail) return null;
25126
25216
  if (error.code === "ENOENT" && opts.allowMissingFinal && i === parts.length - 1) return null;
25127
25217
  return denied("komponen tidak ada");
25128
25218
  });
25129
- if (!stat3) break;
25130
- if (stat3.isSymbolicLink()) denied("symlink");
25131
- if (i < parts.length - 1 && !stat3.isDirectory()) denied("parent bukan direktori");
25219
+ if (!stat4) break;
25220
+ if (stat4.isSymbolicLink()) denied("symlink");
25221
+ if (i < parts.length - 1 && !stat4.isDirectory()) denied("parent bukan direktori");
25132
25222
  }
25133
25223
  if (!beneath(base2, resolve9(current))) denied("keluar root");
25134
25224
  const parent = resolve9(current, "..");
@@ -25143,9 +25233,9 @@ function assertSafeRepoPathSync(root, rel, allowMissingFinal = false, allowMissi
25143
25233
  for (let i = 0; i < parts.length; i++) {
25144
25234
  current = join6(current, parts[i]);
25145
25235
  try {
25146
- const stat3 = lstatSync(current);
25147
- if (stat3.isSymbolicLink()) denied("symlink");
25148
- if (i < parts.length - 1 && !stat3.isDirectory()) denied("parent bukan direktori");
25236
+ const stat4 = lstatSync(current);
25237
+ if (stat4.isSymbolicLink()) denied("symlink");
25238
+ if (i < parts.length - 1 && !stat4.isDirectory()) denied("parent bukan direktori");
25149
25239
  } catch (error) {
25150
25240
  if (allowMissingTail && error.code === "ENOENT") break;
25151
25241
  if (allowMissingFinal && error.code === "ENOENT" && i === parts.length - 1) break;
@@ -25200,8 +25290,8 @@ async function readRepoFile(root, rel) {
25200
25290
  const entry = await resolveRepoEntry(root, rel);
25201
25291
  const handle = await open2(entry.path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0)).catch(() => denied("final component tidak aman"));
25202
25292
  try {
25203
- const stat3 = await handle.stat();
25204
- if (!stat3.isFile()) denied("bukan file regular");
25293
+ const stat4 = await handle.stat();
25294
+ if (!stat4.isFile()) denied("bukan file regular");
25205
25295
  return await handle.readFile();
25206
25296
  } finally {
25207
25297
  await handle.close();
@@ -25430,13 +25520,13 @@ async function deleteSynced(entity, id, deviceId) {
25430
25520
  return true;
25431
25521
  }
25432
25522
  async function listPendingDeletes() {
25433
- const out3 = [];
25523
+ const out4 = [];
25434
25524
  for (const item of await listOutbox()) {
25435
25525
  const tomb = await findTombstone(item.entity, item.recordId);
25436
25526
  if (!tomb) continue;
25437
- out3.push({ entity: item.entity, recordId: item.recordId, deletedAt: tomb.deletedAt.toISOString() });
25527
+ out4.push({ entity: item.entity, recordId: item.recordId, deletedAt: tomb.deletedAt.toISOString() });
25438
25528
  }
25439
- return out3;
25529
+ return out4;
25440
25530
  }
25441
25531
 
25442
25532
  // src/services/branches.ts
@@ -26651,8 +26741,8 @@ var FileTokenizer = class _FileTokenizer extends AbstractTokenizer {
26651
26741
  */
26652
26742
  static async fromFile(sourceFilePath) {
26653
26743
  const fileHandle = await fsOpen(sourceFilePath, "r");
26654
- const stat3 = await fileHandle.stat();
26655
- return new _FileTokenizer(fileHandle, { fileInfo: { path: sourceFilePath, size: stat3.size } });
26744
+ const stat4 = await fileHandle.stat();
26745
+ return new _FileTokenizer(fileHandle, { fileInfo: { path: sourceFilePath, size: stat4.size } });
26656
26746
  }
26657
26747
  constructor(fileHandle, options2) {
26658
26748
  super(options2);
@@ -26931,18 +27021,18 @@ function decodeLatin1(bytes) {
26931
27021
  }
26932
27022
  function decodeWindows1252(bytes) {
26933
27023
  const parts = [];
26934
- let out3 = "";
27024
+ let out4 = "";
26935
27025
  for (let i = 0; i < bytes.length; i++) {
26936
27026
  const b = bytes[i];
26937
27027
  const extra = b >= 128 && b <= 159 ? WINDOWS_1252_EXTRA[b] : void 0;
26938
- out3 += extra !== null && extra !== void 0 ? extra : String.fromCharCode(b);
26939
- if (out3.length >= CHUNK) {
26940
- parts.push(out3);
26941
- out3 = "";
27028
+ out4 += extra !== null && extra !== void 0 ? extra : String.fromCharCode(b);
27029
+ if (out4.length >= CHUNK) {
27030
+ parts.push(out4);
27031
+ out4 = "";
26942
27032
  }
26943
27033
  }
26944
- if (out3)
26945
- parts.push(out3);
27034
+ if (out4)
27035
+ parts.push(out4);
26946
27036
  return parts.join("");
26947
27037
  }
26948
27038
 
@@ -28224,7 +28314,7 @@ function readByobReaderWithSignal(reader, buffer2, signal) {
28224
28314
  return reader.read(buffer2);
28225
28315
  }
28226
28316
  signal.throwIfAborted();
28227
- return new Promise((resolve19, reject2) => {
28317
+ return new Promise((resolve21, reject2) => {
28228
28318
  const cleanup = () => {
28229
28319
  signal.removeEventListener("abort", onAbort);
28230
28320
  };
@@ -28244,7 +28334,7 @@ function readByobReaderWithSignal(reader, buffer2, signal) {
28244
28334
  try {
28245
28335
  const result = await reader.read(buffer2);
28246
28336
  cleanup();
28247
- resolve19(result);
28337
+ resolve21(result);
28248
28338
  } catch (error) {
28249
28339
  cleanup();
28250
28340
  reject2(error);
@@ -31364,14 +31454,14 @@ function metadata(callback) {
31364
31454
  return this;
31365
31455
  } else {
31366
31456
  if (this._isStreamInput()) {
31367
- return new Promise((resolve19, reject2) => {
31457
+ return new Promise((resolve21, reject2) => {
31368
31458
  const finished = () => {
31369
31459
  this._flattenBufferIn();
31370
31460
  sharp_default.metadata(this.options, (err, metadata2) => {
31371
31461
  if (err) {
31372
31462
  reject2(is_default.nativeError(err, stack));
31373
31463
  } else {
31374
- resolve19(metadata2);
31464
+ resolve21(metadata2);
31375
31465
  }
31376
31466
  });
31377
31467
  };
@@ -31382,12 +31472,12 @@ function metadata(callback) {
31382
31472
  }
31383
31473
  });
31384
31474
  } else {
31385
- return new Promise((resolve19, reject2) => {
31475
+ return new Promise((resolve21, reject2) => {
31386
31476
  sharp_default.metadata(this.options, (err, metadata2) => {
31387
31477
  if (err) {
31388
31478
  reject2(is_default.nativeError(err, stack));
31389
31479
  } else {
31390
- resolve19(metadata2);
31480
+ resolve21(metadata2);
31391
31481
  }
31392
31482
  });
31393
31483
  });
@@ -31420,25 +31510,25 @@ function stats(callback) {
31420
31510
  return this;
31421
31511
  } else {
31422
31512
  if (this._isStreamInput()) {
31423
- return new Promise((resolve19, reject2) => {
31513
+ return new Promise((resolve21, reject2) => {
31424
31514
  this.on("finish", function() {
31425
31515
  this._flattenBufferIn();
31426
31516
  sharp_default.stats(this.options, (err, stats2) => {
31427
31517
  if (err) {
31428
31518
  reject2(is_default.nativeError(err, stack));
31429
31519
  } else {
31430
- resolve19(stats2);
31520
+ resolve21(stats2);
31431
31521
  }
31432
31522
  });
31433
31523
  });
31434
31524
  });
31435
31525
  } else {
31436
- return new Promise((resolve19, reject2) => {
31526
+ return new Promise((resolve21, reject2) => {
31437
31527
  sharp_default.stats(this.options, (err, stats2) => {
31438
31528
  if (err) {
31439
31529
  reject2(is_default.nativeError(err, stack));
31440
31530
  } else {
31441
- resolve19(stats2);
31531
+ resolve21(stats2);
31442
31532
  }
31443
31533
  });
31444
31534
  });
@@ -33267,7 +33357,7 @@ function _pipeline(callback, stack) {
33267
33357
  return this;
33268
33358
  } else {
33269
33359
  if (this._isStreamInput()) {
33270
- return new Promise((resolve19, reject2) => {
33360
+ return new Promise((resolve21, reject2) => {
33271
33361
  this.once("finish", () => {
33272
33362
  this._flattenBufferIn();
33273
33363
  sharp_default.pipeline(this.options, (err, data, info) => {
@@ -33275,24 +33365,24 @@ function _pipeline(callback, stack) {
33275
33365
  reject2(is_default.nativeError(err, stack));
33276
33366
  } else {
33277
33367
  if (this.options.resolveWithObject) {
33278
- resolve19({ data, info });
33368
+ resolve21({ data, info });
33279
33369
  } else {
33280
- resolve19(data);
33370
+ resolve21(data);
33281
33371
  }
33282
33372
  }
33283
33373
  });
33284
33374
  });
33285
33375
  });
33286
33376
  } else {
33287
- return new Promise((resolve19, reject2) => {
33377
+ return new Promise((resolve21, reject2) => {
33288
33378
  sharp_default.pipeline(this.options, (err, data, info) => {
33289
33379
  if (err) {
33290
33380
  reject2(is_default.nativeError(err, stack));
33291
33381
  } else {
33292
33382
  if (this.options.resolveWithObject) {
33293
- resolve19({ data, info });
33383
+ resolve21({ data, info });
33294
33384
  } else {
33295
- resolve19(data);
33385
+ resolve21(data);
33296
33386
  }
33297
33387
  }
33298
33388
  });
@@ -33497,11 +33587,11 @@ var UploadError = class extends Error {
33497
33587
  }
33498
33588
  };
33499
33589
  function timeout2(promise, ms, code) {
33500
- return new Promise((resolve19, reject2) => {
33590
+ return new Promise((resolve21, reject2) => {
33501
33591
  const timer9 = setTimeout(() => reject2(new UploadError(code, "upload operation timed out")), ms);
33502
33592
  promise.then((value) => {
33503
33593
  clearTimeout(timer9);
33504
- resolve19(value);
33594
+ resolve21(value);
33505
33595
  }, (error) => {
33506
33596
  clearTimeout(timer9);
33507
33597
  reject2(error);
@@ -33522,7 +33612,7 @@ function scannerFromEnv(path2) {
33522
33612
  return Promise.resolve();
33523
33613
  }
33524
33614
  if (!isAbsolute4(command)) return Promise.reject(new UploadError("UPLOAD_SCAN", "scanner path must be absolute"));
33525
- return new Promise((resolve19, reject2) => {
33615
+ return new Promise((resolve21, reject2) => {
33526
33616
  const child = spawn2(command, [path2], { shell: false, stdio: "ignore" });
33527
33617
  const timer9 = setTimeout(() => {
33528
33618
  child.kill("SIGKILL");
@@ -33534,7 +33624,7 @@ function scannerFromEnv(path2) {
33534
33624
  });
33535
33625
  child.once("exit", (code) => {
33536
33626
  clearTimeout(timer9);
33537
- if (code === 0) resolve19();
33627
+ if (code === 0) resolve21();
33538
33628
  else reject2(new UploadError("UPLOAD_SCAN", `scanner exit ${code}`));
33539
33629
  });
33540
33630
  });
@@ -33961,29 +34051,29 @@ var REASON_LABEL = {
33961
34051
  function dependsOnOf(spec) {
33962
34052
  const v = spec.dependsOn;
33963
34053
  if (!Array.isArray(v)) return [];
33964
- const out3 = [];
33965
- for (const x of v) if (typeof x === "string" && x !== "" && !out3.includes(x)) out3.push(x);
33966
- return out3;
34054
+ const out4 = [];
34055
+ for (const x of v) if (typeof x === "string" && x !== "" && !out4.includes(x)) out4.push(x);
34056
+ return out4;
33967
34057
  }
33968
34058
  function blockersFor(spec, deps, tipOf, isMerged) {
33969
34059
  const ids = dependsOnOf(spec);
33970
34060
  if (ids.length === 0) return [];
33971
34061
  const base2 = spec.branchFrom ?? "HEAD";
33972
- const out3 = [];
34062
+ const out4 = [];
33973
34063
  for (const id of ids) {
33974
34064
  const d = deps.get(id);
33975
34065
  if (!d) {
33976
- out3.push({ id, reason: "missing" });
34066
+ out4.push({ id, reason: "missing" });
33977
34067
  continue;
33978
34068
  }
33979
34069
  if (d.stage !== "done") {
33980
- out3.push({ id, reason: "unfinished" });
34070
+ out4.push({ id, reason: "unfinished" });
33981
34071
  continue;
33982
34072
  }
33983
34073
  const tip = tipOf(d);
33984
- if (tip && !isMerged(tip, base2)) out3.push({ id, reason: "unmerged" });
34074
+ if (tip && !isMerged(tip, base2)) out4.push({ id, reason: "unmerged" });
33985
34075
  }
33986
- return out3;
34076
+ return out4;
33987
34077
  }
33988
34078
  function blockedNote(bl) {
33989
34079
  return `menunggu ${bl.map((b) => `${b.id} (${REASON_LABEL[b.reason]})`).join(", ")}`;
@@ -34051,18 +34141,18 @@ async function decorateBlocked(specs) {
34051
34141
  const rows = await depRows(ids);
34052
34142
  const deps = new Map(rows.map((r) => [r.id, r]));
34053
34143
  const repos = /* @__PURE__ */ new Map();
34054
- const out3 = [];
34144
+ const out4 = [];
34055
34145
  for (const s2 of specs) {
34056
34146
  const own = dependsOnOf(s2);
34057
34147
  if (own.length === 0) {
34058
- out3.push({ ...s2, dependsOn: [], blockedBy: [] });
34148
+ out4.push({ ...s2, dependsOn: [], blockedBy: [] });
34059
34149
  continue;
34060
34150
  }
34061
34151
  if (!repos.has(s2.projectId)) repos.set(s2.projectId, await resolveRepoDir(s2.projectId));
34062
34152
  const repo = repos.get(s2.projectId);
34063
- out3.push({ ...s2, dependsOn: own, blockedBy: blockersFor(s2, deps, tipper(repo), merger(repo)) });
34153
+ out4.push({ ...s2, dependsOn: own, blockedBy: blockersFor(s2, deps, tipper(repo), merger(repo)) });
34064
34154
  }
34065
- return out3;
34155
+ return out4;
34066
34156
  }
34067
34157
  async function validateDependsOn(specId, projectId, raw2) {
34068
34158
  const ids = dependsOnOf({ dependsOn: raw2 });
@@ -35479,12 +35569,12 @@ ${content}</tr>
35479
35569
  return text;
35480
35570
  }
35481
35571
  href = cleanHref;
35482
- let out3 = '<a href="' + href + '"';
35572
+ let out4 = '<a href="' + href + '"';
35483
35573
  if (title) {
35484
- out3 += ' title="' + title + '"';
35574
+ out4 += ' title="' + title + '"';
35485
35575
  }
35486
- out3 += ">" + text + "</a>";
35487
- return out3;
35576
+ out4 += ">" + text + "</a>";
35577
+ return out4;
35488
35578
  }
35489
35579
  image(href, title, text) {
35490
35580
  const cleanHref = cleanUrl(href);
@@ -35492,12 +35582,12 @@ ${content}</tr>
35492
35582
  return text;
35493
35583
  }
35494
35584
  href = cleanHref;
35495
- let out3 = `<img src="${href}" alt="${text}"`;
35585
+ let out4 = `<img src="${href}" alt="${text}"`;
35496
35586
  if (title) {
35497
- out3 += ` title="${title}"`;
35587
+ out4 += ` title="${title}"`;
35498
35588
  }
35499
- out3 += ">";
35500
- return out3;
35589
+ out4 += ">";
35590
+ return out4;
35501
35591
  }
35502
35592
  text(text) {
35503
35593
  return text;
@@ -35562,14 +35652,14 @@ var _Parser = class __Parser {
35562
35652
  * Parse Loop
35563
35653
  */
35564
35654
  parse(tokens, top = true) {
35565
- let out3 = "";
35655
+ let out4 = "";
35566
35656
  for (let i = 0; i < tokens.length; i++) {
35567
35657
  const token = tokens[i];
35568
35658
  if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) {
35569
35659
  const genericToken = token;
35570
35660
  const ret = this.options.extensions.renderers[genericToken.type].call({ parser: this }, genericToken);
35571
35661
  if (ret !== false || !["space", "hr", "heading", "code", "table", "blockquote", "list", "html", "paragraph", "text"].includes(genericToken.type)) {
35572
- out3 += ret || "";
35662
+ out4 += ret || "";
35573
35663
  continue;
35574
35664
  }
35575
35665
  }
@@ -35578,17 +35668,17 @@ var _Parser = class __Parser {
35578
35668
  continue;
35579
35669
  }
35580
35670
  case "hr": {
35581
- out3 += this.renderer.hr();
35671
+ out4 += this.renderer.hr();
35582
35672
  continue;
35583
35673
  }
35584
35674
  case "heading": {
35585
35675
  const headingToken = token;
35586
- out3 += this.renderer.heading(this.parseInline(headingToken.tokens), headingToken.depth, unescape(this.parseInline(headingToken.tokens, this.textRenderer)));
35676
+ out4 += this.renderer.heading(this.parseInline(headingToken.tokens), headingToken.depth, unescape(this.parseInline(headingToken.tokens, this.textRenderer)));
35587
35677
  continue;
35588
35678
  }
35589
35679
  case "code": {
35590
35680
  const codeToken = token;
35591
- out3 += this.renderer.code(codeToken.text, codeToken.lang, !!codeToken.escaped);
35681
+ out4 += this.renderer.code(codeToken.text, codeToken.lang, !!codeToken.escaped);
35592
35682
  continue;
35593
35683
  }
35594
35684
  case "table": {
@@ -35608,13 +35698,13 @@ var _Parser = class __Parser {
35608
35698
  }
35609
35699
  body += this.renderer.tablerow(cell);
35610
35700
  }
35611
- out3 += this.renderer.table(header2, body);
35701
+ out4 += this.renderer.table(header2, body);
35612
35702
  continue;
35613
35703
  }
35614
35704
  case "blockquote": {
35615
35705
  const blockquoteToken = token;
35616
35706
  const body = this.parse(blockquoteToken.tokens);
35617
- out3 += this.renderer.blockquote(body);
35707
+ out4 += this.renderer.blockquote(body);
35618
35708
  continue;
35619
35709
  }
35620
35710
  case "list": {
@@ -35649,17 +35739,17 @@ var _Parser = class __Parser {
35649
35739
  itemBody += this.parse(item.tokens, loose);
35650
35740
  body += this.renderer.listitem(itemBody, task, !!checked);
35651
35741
  }
35652
- out3 += this.renderer.list(body, ordered, start);
35742
+ out4 += this.renderer.list(body, ordered, start);
35653
35743
  continue;
35654
35744
  }
35655
35745
  case "html": {
35656
35746
  const htmlToken = token;
35657
- out3 += this.renderer.html(htmlToken.text, htmlToken.block);
35747
+ out4 += this.renderer.html(htmlToken.text, htmlToken.block);
35658
35748
  continue;
35659
35749
  }
35660
35750
  case "paragraph": {
35661
35751
  const paragraphToken = token;
35662
- out3 += this.renderer.paragraph(this.parseInline(paragraphToken.tokens));
35752
+ out4 += this.renderer.paragraph(this.parseInline(paragraphToken.tokens));
35663
35753
  continue;
35664
35754
  }
35665
35755
  case "text": {
@@ -35669,7 +35759,7 @@ var _Parser = class __Parser {
35669
35759
  textToken = tokens[++i];
35670
35760
  body += "\n" + (textToken.tokens ? this.parseInline(textToken.tokens) : textToken.text);
35671
35761
  }
35672
- out3 += top ? this.renderer.paragraph(body) : body;
35762
+ out4 += top ? this.renderer.paragraph(body) : body;
35673
35763
  continue;
35674
35764
  }
35675
35765
  default: {
@@ -35683,71 +35773,71 @@ var _Parser = class __Parser {
35683
35773
  }
35684
35774
  }
35685
35775
  }
35686
- return out3;
35776
+ return out4;
35687
35777
  }
35688
35778
  /**
35689
35779
  * Parse Inline Tokens
35690
35780
  */
35691
35781
  parseInline(tokens, renderer) {
35692
35782
  renderer = renderer || this.renderer;
35693
- let out3 = "";
35783
+ let out4 = "";
35694
35784
  for (let i = 0; i < tokens.length; i++) {
35695
35785
  const token = tokens[i];
35696
35786
  if (this.options.extensions && this.options.extensions.renderers && this.options.extensions.renderers[token.type]) {
35697
35787
  const ret = this.options.extensions.renderers[token.type].call({ parser: this }, token);
35698
35788
  if (ret !== false || !["escape", "html", "link", "image", "strong", "em", "codespan", "br", "del", "text"].includes(token.type)) {
35699
- out3 += ret || "";
35789
+ out4 += ret || "";
35700
35790
  continue;
35701
35791
  }
35702
35792
  }
35703
35793
  switch (token.type) {
35704
35794
  case "escape": {
35705
35795
  const escapeToken = token;
35706
- out3 += renderer.text(escapeToken.text);
35796
+ out4 += renderer.text(escapeToken.text);
35707
35797
  break;
35708
35798
  }
35709
35799
  case "html": {
35710
35800
  const tagToken = token;
35711
- out3 += renderer.html(tagToken.text);
35801
+ out4 += renderer.html(tagToken.text);
35712
35802
  break;
35713
35803
  }
35714
35804
  case "link": {
35715
35805
  const linkToken = token;
35716
- out3 += renderer.link(linkToken.href, linkToken.title, this.parseInline(linkToken.tokens, renderer));
35806
+ out4 += renderer.link(linkToken.href, linkToken.title, this.parseInline(linkToken.tokens, renderer));
35717
35807
  break;
35718
35808
  }
35719
35809
  case "image": {
35720
35810
  const imageToken = token;
35721
- out3 += renderer.image(imageToken.href, imageToken.title, imageToken.text);
35811
+ out4 += renderer.image(imageToken.href, imageToken.title, imageToken.text);
35722
35812
  break;
35723
35813
  }
35724
35814
  case "strong": {
35725
35815
  const strongToken = token;
35726
- out3 += renderer.strong(this.parseInline(strongToken.tokens, renderer));
35816
+ out4 += renderer.strong(this.parseInline(strongToken.tokens, renderer));
35727
35817
  break;
35728
35818
  }
35729
35819
  case "em": {
35730
35820
  const emToken = token;
35731
- out3 += renderer.em(this.parseInline(emToken.tokens, renderer));
35821
+ out4 += renderer.em(this.parseInline(emToken.tokens, renderer));
35732
35822
  break;
35733
35823
  }
35734
35824
  case "codespan": {
35735
35825
  const codespanToken = token;
35736
- out3 += renderer.codespan(codespanToken.text);
35826
+ out4 += renderer.codespan(codespanToken.text);
35737
35827
  break;
35738
35828
  }
35739
35829
  case "br": {
35740
- out3 += renderer.br();
35830
+ out4 += renderer.br();
35741
35831
  break;
35742
35832
  }
35743
35833
  case "del": {
35744
35834
  const delToken = token;
35745
- out3 += renderer.del(this.parseInline(delToken.tokens, renderer));
35835
+ out4 += renderer.del(this.parseInline(delToken.tokens, renderer));
35746
35836
  break;
35747
35837
  }
35748
35838
  case "text": {
35749
35839
  const textToken = token;
35750
- out3 += renderer.text(textToken.text);
35840
+ out4 += renderer.text(textToken.text);
35751
35841
  break;
35752
35842
  }
35753
35843
  default: {
@@ -35761,7 +35851,7 @@ var _Parser = class __Parser {
35761
35851
  }
35762
35852
  }
35763
35853
  }
35764
- return out3;
35854
+ return out4;
35765
35855
  }
35766
35856
  };
35767
35857
  var _Hooks = class {
@@ -36155,26 +36245,26 @@ function isDropped(cp) {
36155
36245
  return cp >= 126976 && cp <= 131071 || cp >= 9728 && cp <= 10175 || cp >= 65024 && cp <= 65039 || cp === 8205;
36156
36246
  }
36157
36247
  function toWinAnsi(s2) {
36158
- let out3 = "";
36248
+ let out4 = "";
36159
36249
  for (const ch of s2 || "") {
36160
36250
  const t = TRANSLIT[ch];
36161
36251
  if (t !== void 0) {
36162
- out3 += t;
36252
+ out4 += t;
36163
36253
  continue;
36164
36254
  }
36165
36255
  const cp = ch.codePointAt(0);
36166
36256
  if (cp <= 255) {
36167
- out3 += ch;
36257
+ out4 += ch;
36168
36258
  continue;
36169
36259
  }
36170
36260
  if (WINANSI_EXTRA.has(ch)) {
36171
- out3 += ch;
36261
+ out4 += ch;
36172
36262
  continue;
36173
36263
  }
36174
36264
  if (isDropped(cp)) continue;
36175
- out3 += "?";
36265
+ out4 += "?";
36176
36266
  }
36177
- return out3;
36267
+ return out4;
36178
36268
  }
36179
36269
  function downloadFilename(prefix, path2, ext) {
36180
36270
  const base2 = (path2.split("/").pop() ?? "").replace(/\.[^.]+$/, "");
@@ -36214,40 +36304,40 @@ function decodeEntities(s2) {
36214
36304
  }
36215
36305
  var inlineText2 = (s2) => decodeEntities(s2).replace(/\s*\n\s*/g, " ");
36216
36306
  function flatten2(tokens, base2 = { text: "" }) {
36217
- const out3 = [];
36307
+ const out4 = [];
36218
36308
  for (const t of tokens ?? []) {
36219
36309
  switch (t.type) {
36220
36310
  case "strong":
36221
- out3.push(...flatten2(t.tokens, { ...base2, bold: true }));
36311
+ out4.push(...flatten2(t.tokens, { ...base2, bold: true }));
36222
36312
  break;
36223
36313
  case "em":
36224
- out3.push(...flatten2(t.tokens, { ...base2, italic: true }));
36314
+ out4.push(...flatten2(t.tokens, { ...base2, italic: true }));
36225
36315
  break;
36226
36316
  case "del":
36227
- out3.push(...flatten2(t.tokens, { ...base2, strike: true }));
36317
+ out4.push(...flatten2(t.tokens, { ...base2, strike: true }));
36228
36318
  break;
36229
36319
  case "codespan":
36230
- out3.push({ ...base2, text: inlineText2(t.text ?? ""), mono: true });
36320
+ out4.push({ ...base2, text: inlineText2(t.text ?? ""), mono: true });
36231
36321
  break;
36232
36322
  case "link":
36233
- out3.push(...flatten2(t.tokens, { ...base2, link: t.href }));
36323
+ out4.push(...flatten2(t.tokens, { ...base2, link: t.href }));
36234
36324
  break;
36235
36325
  case "br":
36236
- out3.push({ ...base2, text: "\n" });
36326
+ out4.push({ ...base2, text: "\n" });
36237
36327
  break;
36238
36328
  case "image":
36239
- out3.push({ ...base2, text: `[gambar: ${t.href ?? ""}]`, italic: true });
36329
+ out4.push({ ...base2, text: `[gambar: ${t.href ?? ""}]`, italic: true });
36240
36330
  break;
36241
36331
  case "escape":
36242
36332
  case "text":
36243
- if (t.tokens?.length) out3.push(...flatten2(t.tokens, base2));
36244
- else out3.push({ ...base2, text: inlineText2(t.text ?? "") });
36333
+ if (t.tokens?.length) out4.push(...flatten2(t.tokens, base2));
36334
+ else out4.push({ ...base2, text: inlineText2(t.text ?? "") });
36245
36335
  break;
36246
36336
  default:
36247
- out3.push({ ...base2, text: inlineText2(t.text ?? "") });
36337
+ out4.push({ ...base2, text: inlineText2(t.text ?? "") });
36248
36338
  }
36249
36339
  }
36250
- return out3;
36340
+ return out4;
36251
36341
  }
36252
36342
  function drawRuns(doc, runs, size, color2, opts = {}) {
36253
36343
  const items = runs.filter((r) => r.text !== "");
@@ -36417,7 +36507,7 @@ function renderDocPdf(text, name2, meta) {
36417
36507
  });
36418
36508
  const chunks = [];
36419
36509
  doc.on("data", (c) => chunks.push(c));
36420
- const done = new Promise((resolve19) => doc.on("end", () => resolve19(Buffer.concat(chunks))));
36510
+ const done = new Promise((resolve21) => doc.on("end", () => resolve21(Buffer.concat(chunks))));
36421
36511
  doc.font("Helvetica-Bold").fontSize(8).fillColor(BRASS).text(toWinAnsi(meta.eyebrow.toUpperCase()), { characterSpacing: 0.8 });
36422
36512
  doc.moveDown(0.25);
36423
36513
  doc.font("Helvetica-Bold").fontSize(17).fillColor(STRONG).text(toWinAnsi(name2.split("/").pop() ?? name2));
@@ -36521,7 +36611,7 @@ async function liveSpecs(filter = {}) {
36521
36611
  if (live.size === 0) return decorateBlocked(specs);
36522
36612
  const advanced = [];
36523
36613
  const doneNow = [];
36524
- const out3 = specs.map((s2) => {
36614
+ const out4 = specs.map((s2) => {
36525
36615
  const entry = live.get(s2.id);
36526
36616
  if (!entry) return s2;
36527
36617
  const next = stageForRun(entry.phases, entry.cwd, s2.id);
@@ -36538,7 +36628,7 @@ async function liveSpecs(filter = {}) {
36538
36628
  await notifySynced("spec", a.id);
36539
36629
  }));
36540
36630
  await Promise.all(doneNow.map((d) => recordCompletion(d.specId, d.title, d.projectId)));
36541
- return decorateBlocked(out3);
36631
+ return decorateBlocked(out4);
36542
36632
  }
36543
36633
 
36544
36634
  // src/services/launch-authority.ts
@@ -37060,12 +37150,12 @@ function parseBreakdown(md) {
37060
37150
  return [];
37061
37151
  }
37062
37152
  const arr = data && typeof data === "object" && Array.isArray(data.items) ? data.items : [];
37063
- const out3 = [];
37153
+ const out4 = [];
37064
37154
  for (const it of arr) {
37065
37155
  const p = zBreakdownItem.safeParse(it);
37066
- if (p.success) out3.push(p.data);
37156
+ if (p.success) out4.push(p.data);
37067
37157
  }
37068
- return out3;
37158
+ return out4;
37069
37159
  }
37070
37160
  async function resolveDir3(projectId, sessions) {
37071
37161
  const live = sessions.find((s2) => s2.projectId === projectId && s2.flow === "breakdown" && !s2.exited && s2.cwd);
@@ -37135,8 +37225,9 @@ async function docs_default(app2) {
37135
37225
 
37136
37226
  // src/routes/ide.ts
37137
37227
  init_src2();
37138
- import { basename as basename4 } from "node:path";
37139
- import { spawn as spawn3 } from "node:child_process";
37228
+ import { basename as basename5, resolve as resolve16 } from "node:path";
37229
+ import { execFile as execFile10, spawn as spawn3 } from "node:child_process";
37230
+ import { promisify as promisify10 } from "node:util";
37140
37231
 
37141
37232
  // src/services/git-remotes.ts
37142
37233
  import { execFile as execFile5 } from "node:child_process";
@@ -37550,53 +37641,53 @@ function gitArgs(op) {
37550
37641
  }
37551
37642
  }
37552
37643
  async function afterMergeDelete(repoDir, branch, mergeOut, mergeErr) {
37553
- const out3 = [mergeOut], err = [mergeErr];
37644
+ const out4 = [mergeOut], err = [mergeErr];
37554
37645
  const step = async (args) => {
37555
37646
  const r = await exec6("git", args, { cwd: repoDir, ...GIT5 });
37556
- out3.push(r.stdout);
37647
+ out4.push(r.stdout);
37557
37648
  err.push(r.stderr);
37558
37649
  };
37559
37650
  try {
37560
37651
  await step(["branch", "-D", branch]);
37561
37652
  const hasOrigin = await exec6("git", ["rev-parse", "--verify", "--quiet", `refs/remotes/origin/${branch}`], { cwd: repoDir, ...GIT5 }).then(() => true).catch(() => false);
37562
37653
  if (hasOrigin) await step(["push", "origin", "--delete", branch]);
37563
- return { ok: true, stdout: out3.join("\n").trim(), stderr: err.join("\n").trim(), current: await currentBranch(repoDir) };
37654
+ return { ok: true, stdout: out4.join("\n").trim(), stderr: err.join("\n").trim(), current: await currentBranch(repoDir) };
37564
37655
  } catch (e) {
37565
37656
  const ee = e;
37566
- return { ok: false, stdout: [...out3, ee.stdout ?? ""].join("\n").trim(), stderr: [...err, ee.stderr ?? String(e)].join("\n").trim(), current: await currentBranch(repoDir) };
37657
+ return { ok: false, stdout: [...out4, ee.stdout ?? ""].join("\n").trim(), stderr: [...err, ee.stderr ?? String(e)].join("\n").trim(), current: await currentBranch(repoDir) };
37567
37658
  }
37568
37659
  }
37569
37660
  async function runDeleteBranch(repoDir, op) {
37570
- const out3 = [], err = [];
37661
+ const out4 = [], err = [];
37571
37662
  const step = async (args) => {
37572
37663
  const r = await exec6("git", args, { cwd: repoDir, ...GIT5 });
37573
- out3.push(r.stdout);
37664
+ out4.push(r.stdout);
37574
37665
  err.push(r.stderr);
37575
37666
  };
37576
37667
  try {
37577
37668
  if (op.local !== false) await step(gitArgs({ op: "delete-branch", name: op.name, force: op.force }));
37578
37669
  if (op.remote) await step(["push", "origin", "--delete", "--end-of-options", op.name]);
37579
- return { ok: true, stdout: out3.join("\n").trim(), stderr: err.join("\n").trim(), current: await currentBranch(repoDir) };
37670
+ return { ok: true, stdout: out4.join("\n").trim(), stderr: err.join("\n").trim(), current: await currentBranch(repoDir) };
37580
37671
  } catch (e) {
37581
37672
  const ee = e;
37582
- return { ok: false, stdout: [...out3, ee.stdout ?? ""].join("\n").trim(), stderr: [...err, ee.stderr ?? String(e)].join("\n").trim(), current: await currentBranch(repoDir) };
37673
+ return { ok: false, stdout: [...out4, ee.stdout ?? ""].join("\n").trim(), stderr: [...err, ee.stderr ?? String(e)].join("\n").trim(), current: await currentBranch(repoDir) };
37583
37674
  }
37584
37675
  }
37585
37676
  async function runTagOp(repoDir, op) {
37586
- const out3 = [], err = [];
37677
+ const out4 = [], err = [];
37587
37678
  const step = async (args) => {
37588
37679
  const r = await exec6("git", args, { cwd: repoDir, ...GIT5 });
37589
- out3.push(r.stdout);
37680
+ out4.push(r.stdout);
37590
37681
  err.push(r.stderr);
37591
37682
  };
37592
37683
  try {
37593
37684
  await step(gitArgs(op));
37594
37685
  if (op.op === "tag" && op.push) await step(["push", "origin", "--end-of-options", op.name]);
37595
37686
  if (op.op === "delete-tag" && op.remote) await step(["push", "origin", "--delete", "--end-of-options", op.name]);
37596
- return { ok: true, stdout: out3.join("\n").trim(), stderr: err.join("\n").trim(), current: await currentBranch(repoDir) };
37687
+ return { ok: true, stdout: out4.join("\n").trim(), stderr: err.join("\n").trim(), current: await currentBranch(repoDir) };
37597
37688
  } catch (e) {
37598
37689
  const ee = e;
37599
- return { ok: false, stdout: [...out3, ee.stdout ?? ""].join("\n").trim(), stderr: [...err, ee.stderr ?? String(e)].join("\n").trim(), current: await currentBranch(repoDir) };
37690
+ return { ok: false, stdout: [...out4, ee.stdout ?? ""].join("\n").trim(), stderr: [...err, ee.stderr ?? String(e)].join("\n").trim(), current: await currentBranch(repoDir) };
37600
37691
  }
37601
37692
  }
37602
37693
  async function runGitOp(repoDir, op) {
@@ -37679,22 +37770,32 @@ async function resolveBase(repoDir, want, current) {
37679
37770
  return current && current !== "HEAD" ? current : "HEAD";
37680
37771
  }
37681
37772
  async function worktreeBranches(repoDir) {
37682
- const s2 = /* @__PURE__ */ new Set();
37683
- for (const l of lines(await out2(repoDir, ["worktree", "list", "--porcelain"])))
37684
- if (l.startsWith("branch refs/heads/")) s2.add(l.slice("branch refs/heads/".length));
37685
- return s2;
37773
+ const m = /* @__PURE__ */ new Map();
37774
+ let path2 = "";
37775
+ for (const l of lines(await out2(repoDir, ["worktree", "list", "--porcelain"]))) {
37776
+ if (l.startsWith("worktree ")) path2 = l.slice("worktree ".length);
37777
+ else if (l.startsWith("branch refs/heads/")) m.set(l.slice("branch refs/heads/".length), path2);
37778
+ }
37779
+ return m;
37686
37780
  }
37687
37781
  var SEP = "";
37688
- async function lastCommits(repoDir) {
37689
- const fmt = ["%(refname:short)", "%(objectname)", "%(committerdate:iso-strict)", "%(contents:subject)"].join(SEP);
37690
- const m = /* @__PURE__ */ new Map();
37782
+ async function refIndex(repoDir) {
37783
+ const fmt = [
37784
+ "%(refname)",
37785
+ "%(refname:short)",
37786
+ "%(objectname)",
37787
+ "%(committerdate:iso-strict)",
37788
+ "%(contents:subject)"
37789
+ ].join(SEP);
37790
+ const idx = { locals: /* @__PURE__ */ new Set(), remotes: /* @__PURE__ */ new Set(), meta: /* @__PURE__ */ new Map() };
37691
37791
  for (const l of lines(await out2(repoDir, ["for-each-ref", `--format=${fmt}`, "refs/heads", "refs/remotes/origin"]))) {
37692
- const [ref, sha, at, ...rest] = l.split(SEP);
37792
+ const [full, ref, sha, at, ...rest] = l.split(SEP);
37693
37793
  const name2 = shortName(ref ?? "");
37694
37794
  if (!name2 || !sha) continue;
37695
- if (!m.has(name2)) m.set(name2, { sha, at: at ?? "", subject: rest.join(SEP) });
37795
+ (full?.startsWith("refs/heads/") ? idx.locals : idx.remotes).add(name2);
37796
+ if (!idx.meta.has(name2)) idx.meta.set(name2, { sha, at: at ?? "", subject: rest.join(SEP) });
37696
37797
  }
37697
- return m;
37798
+ return idx;
37698
37799
  }
37699
37800
  async function listUnusedBranches(repoDir, opts) {
37700
37801
  if (!repoDir) return EMPTY;
@@ -37705,26 +37806,45 @@ async function listUnusedBranches(repoDir, opts) {
37705
37806
  if (!baseSha) return { ...EMPTY, current };
37706
37807
  const baseRemote = await revSha(repoDir, `origin/${base2}`) ? `origin/${base2}` : null;
37707
37808
  const baseRemoteSha = baseRemote ? await revSha(repoDir, baseRemote) : baseSha;
37708
- const [localMerged, remoteMerged, wt, meta] = await Promise.all([
37809
+ const [localMerged, remoteMerged, wt, refs2] = await Promise.all([
37709
37810
  out2(repoDir, ["branch", "--merged", baseSha, "--format=%(refname:short)"]),
37710
37811
  out2(repoDir, ["branch", "-r", "--merged", baseRemoteSha, "--format=%(refname:short)"]),
37711
37812
  worktreeBranches(repoDir),
37712
- lastCommits(repoDir)
37813
+ refIndex(repoDir)
37713
37814
  ]);
37714
- const locals = new Set(lines(localMerged).map(shortName).filter(Boolean));
37715
- const remotes = new Set(
37815
+ const mergedLocals = new Set(lines(localMerged).map(shortName).filter(Boolean));
37816
+ const mergedRemotes = new Set(
37716
37817
  lines(remoteMerged).filter((r) => r.startsWith("origin/")).map(shortName).filter(Boolean)
37717
37818
  );
37718
- const names = [.../* @__PURE__ */ new Set([...locals, ...remotes])].sort();
37719
- const branches = names.map((name2) => {
37819
+ const all = opts.include === "all";
37820
+ const names = [...new Set(all ? [...refs2.locals, ...refs2.remotes] : [...mergedLocals, ...mergedRemotes])].sort();
37821
+ const branches = [];
37822
+ for (const name2 of names) {
37823
+ const local = refs2.locals.has(name2);
37824
+ const remote = refs2.remotes.has(name2);
37825
+ const mergedLocal = local && mergedLocals.has(name2);
37826
+ const mergedRemote = remote && mergedRemotes.has(name2);
37827
+ const merged = (!local || mergedLocal) && (!remote || mergedRemote);
37828
+ if (!all && !merged) continue;
37720
37829
  const locks = [];
37721
37830
  if (name2 === current) locks.push("current");
37722
37831
  if (name2 === base2) locks.push("base");
37723
37832
  if (wt.has(name2)) locks.push("worktree");
37724
37833
  if (opts.openSpecBranches.has(name2)) locks.push("spec-open");
37725
37834
  if (opts.sessionBranches.has(name2)) locks.push("session");
37726
- return { name: name2, local: locals.has(name2), remote: remotes.has(name2), lastCommit: meta.get(name2) ?? null, locks };
37727
- });
37835
+ const wtPath = wt.get(name2);
37836
+ branches.push({
37837
+ name: name2,
37838
+ local,
37839
+ remote,
37840
+ mergedLocal,
37841
+ mergedRemote,
37842
+ merged,
37843
+ lastCommit: refs2.meta.get(name2) ?? null,
37844
+ locks,
37845
+ ...wtPath ? { worktree: wtPath } : {}
37846
+ });
37847
+ }
37728
37848
  return { base: base2, baseRemote, current, branches };
37729
37849
  }
37730
37850
  function effectiveScope(want, b) {
@@ -37736,26 +37856,30 @@ function effectiveScope(want, b) {
37736
37856
  return "none";
37737
37857
  }
37738
37858
  async function deleteBranches(repoDir, names, opts) {
37739
- const report = await listUnusedBranches(repoDir, opts);
37859
+ const report = await listUnusedBranches(repoDir, { ...opts, include: "all" });
37740
37860
  const byName = new Map(report.branches.map((b) => [b.name, b]));
37741
37861
  const results = [];
37742
37862
  for (const name2 of names) {
37743
37863
  const b = byName.get(name2);
37744
37864
  if (!b) {
37865
+ results.push({ name: name2, ok: false, scope: "none", error: "branch tak ditemukan di repo" });
37866
+ continue;
37867
+ }
37868
+ if (b.locks.length) {
37745
37869
  results.push({
37746
37870
  name: name2,
37747
37871
  ok: false,
37748
37872
  scope: "none",
37749
- error: `branch tak ditemukan di daftar ter-merge ke ${report.base}`
37873
+ error: `terkunci: ${b.locks.map((l) => LOCK_REASON[l]).join(", ")}`
37750
37874
  });
37751
37875
  continue;
37752
37876
  }
37753
- if (b.locks.length) {
37877
+ if (!b.merged && !opts.allowUnmerged) {
37754
37878
  results.push({
37755
37879
  name: name2,
37756
37880
  ok: false,
37757
37881
  scope: "none",
37758
- error: `terkunci: ${b.locks.map((l) => LOCK_REASON[l]).join(", ")}`
37882
+ error: `belum ter-merge ke ${report.base} \u2014 commit-nya bisa hilang; butuh konfirmasi terpisah`
37759
37883
  });
37760
37884
  continue;
37761
37885
  }
@@ -37769,21 +37893,353 @@ async function deleteBranches(repoDir, names, opts) {
37769
37893
  });
37770
37894
  continue;
37771
37895
  }
37896
+ const forced = scope !== "remote" && !b.mergedLocal;
37772
37897
  const r = await runGitOp(repoDir, {
37773
37898
  op: "delete-branch",
37774
37899
  name: name2,
37775
37900
  local: scope !== "remote",
37776
- remote: scope !== "local"
37901
+ remote: scope !== "local",
37902
+ force: forced
37777
37903
  });
37778
- results.push(r.ok ? { name: name2, ok: true, scope } : { name: name2, ok: false, scope, error: r.stderr || "hapus branch gagal" });
37904
+ results.push(r.ok ? { name: name2, ok: true, scope, ...forced ? { forced: true } : {} } : { name: name2, ok: false, scope, error: r.stderr || "hapus branch gagal" });
37779
37905
  }
37780
37906
  return { base: report.base, results };
37781
37907
  }
37782
37908
 
37909
+ // src/services/worktree-list.ts
37910
+ import { execFile as execFile8 } from "node:child_process";
37911
+ import { realpathSync as realpathSync4 } from "node:fs";
37912
+ import { stat } from "node:fs/promises";
37913
+ import { basename as basename4, join as join11, resolve as resolve14, sep as sep4 } from "node:path";
37914
+ import { promisify as promisify8 } from "node:util";
37915
+
37916
+ // src/services/session-worktree.ts
37917
+ import { resolve as resolve13, sep as sep3 } from "node:path";
37918
+ function ownsWorktree(repoDir, cwd) {
37919
+ const base2 = resolve13(repoDir);
37920
+ const dir2 = resolve13(cwd);
37921
+ const container = resolve13(base2, ".worktrees");
37922
+ return dir2 !== base2 && dir2 !== container && dir2.startsWith(container + sep3);
37923
+ }
37924
+
37925
+ // src/services/worktree-list.ts
37926
+ var exec8 = promisify8(execFile8);
37927
+ var GIT7 = { timeout: 6e4, maxBuffer: 1 << 24, encoding: "utf8" };
37928
+ async function out3(cwd, args) {
37929
+ try {
37930
+ return (await exec8("git", args, { cwd, ...GIT7 })).stdout;
37931
+ } catch {
37932
+ return "";
37933
+ }
37934
+ }
37935
+ function parseWorktreePorcelain(text) {
37936
+ const rows = [];
37937
+ let cur = null;
37938
+ for (const raw2 of text.split("\n")) {
37939
+ const line = raw2.replace(/\r$/, "");
37940
+ if (line.startsWith("worktree ")) {
37941
+ cur = {
37942
+ path: line.slice("worktree ".length),
37943
+ head: "",
37944
+ branch: null,
37945
+ prunable: false,
37946
+ locked: false,
37947
+ bare: false
37948
+ };
37949
+ rows.push(cur);
37950
+ } else if (!cur) continue;
37951
+ else if (line.startsWith("HEAD ")) cur.head = line.slice("HEAD ".length);
37952
+ else if (line.startsWith("branch refs/heads/")) cur.branch = line.slice("branch refs/heads/".length);
37953
+ else if (line === "bare") cur.bare = true;
37954
+ else if (line === "locked" || line.startsWith("locked ")) cur.locked = true;
37955
+ else if (line === "prunable" || line.startsWith("prunable ")) cur.prunable = true;
37956
+ }
37957
+ return rows;
37958
+ }
37959
+ var EMPTY2 = { repoDir: "", worktrees: [] };
37960
+ var real = (p) => {
37961
+ try {
37962
+ return realpathSync4(p);
37963
+ } catch {
37964
+ return resolve14(p);
37965
+ }
37966
+ };
37967
+ async function bornAt(path2) {
37968
+ try {
37969
+ const st = await stat(join11(path2, ".git"));
37970
+ const ms = st.birthtimeMs > 0 ? st.birthtimeMs : st.mtimeMs;
37971
+ return new Date(ms).toISOString();
37972
+ } catch {
37973
+ return null;
37974
+ }
37975
+ }
37976
+ async function listWorktrees(repoDir, inputs) {
37977
+ if (!repoDir) return EMPTY2;
37978
+ const base2 = resolve14(repoDir);
37979
+ const baseReal = real(base2);
37980
+ const text = await out3(base2, ["worktree", "list", "--porcelain"]);
37981
+ const trash = resolve14(baseReal, ".worktrees", ".trash");
37982
+ const sessions = new Map([...inputs.sessions].map(([cwd, s2]) => [real(cwd), s2]));
37983
+ const rows = [];
37984
+ for (const w of parseWorktreePorcelain(text)) {
37985
+ const path2 = resolve14(w.path);
37986
+ if (path2 === trash || path2.startsWith(trash + sep4)) continue;
37987
+ const name2 = basename4(path2);
37988
+ const deletable = ownsWorktree(baseReal, path2);
37989
+ const spec = inputs.specs.get(name2);
37990
+ rows.push({
37991
+ path: path2,
37992
+ name: name2,
37993
+ head: w.head,
37994
+ branch: w.branch,
37995
+ prunable: w.prunable,
37996
+ locked: w.locked,
37997
+ deletable,
37998
+ blocked: deletable ? null : path2 === baseReal ? "checkout project" : "di luar .worktrees project ini",
37999
+ spec: spec ? { id: spec.id, stage: spec.stage } : null,
38000
+ session: sessions.get(path2) ?? null,
38001
+ createdAt: await bornAt(path2)
38002
+ });
38003
+ }
38004
+ rows.sort((a, b) => Number(a.deletable) - Number(b.deletable) || a.name.localeCompare(b.name));
38005
+ return { repoDir: base2, worktrees: rows };
38006
+ }
38007
+ async function worktreeStats(repoDir, w) {
38008
+ const [sizeBytes, dirtyFiles, orphanCommits] = await Promise.all([
38009
+ diskBytes(w),
38010
+ dirtyCount(w),
38011
+ orphanCount(resolve14(repoDir), w)
38012
+ ]);
38013
+ return { name: w.name, sizeBytes, dirtyFiles, orphanCommits };
38014
+ }
38015
+ async function diskBytes(w) {
38016
+ if (w.prunable) return null;
38017
+ try {
38018
+ const { stdout } = await exec8(
38019
+ "du",
38020
+ ["-sk", w.path],
38021
+ { timeout: 3e4, maxBuffer: 1 << 20, encoding: "utf8" }
38022
+ );
38023
+ const kb = Number.parseInt(stdout.trim().split(/\s+/)[0] ?? "", 10);
38024
+ return Number.isFinite(kb) ? kb * 1024 : null;
38025
+ } catch {
38026
+ return null;
38027
+ }
38028
+ }
38029
+ async function dirtyCount(w) {
38030
+ if (w.prunable) return 0;
38031
+ const s2 = await out3(w.path, ["status", "--porcelain"]);
38032
+ return s2.split("\n").filter((l) => l.trim()).length;
38033
+ }
38034
+ async function orphanCount(repoDir, w) {
38035
+ if (!w.head) return 0;
38036
+ const args = ["rev-list", "--count", w.head, "--not"];
38037
+ if (w.branch) args.push(`--exclude=${w.branch}`, "--branches", `--exclude=*/${w.branch}`, "--remotes", "--tags");
38038
+ else args.push("--branches", "--remotes", "--tags");
38039
+ const n2 = Number.parseInt((await out3(repoDir, args)).trim(), 10);
38040
+ return Number.isFinite(n2) ? n2 : 0;
38041
+ }
38042
+ async function deleteWorktrees(repoDir, names, opts) {
38043
+ const report = await listWorktrees(repoDir, opts);
38044
+ const byName = new Map(report.worktrees.map((w) => [w.name, w]));
38045
+ const results = [];
38046
+ for (const name2 of names) {
38047
+ const w = byName.get(name2);
38048
+ if (!w) {
38049
+ results.push({ name: name2, ok: false, cleanup: null, error: "worktree tak ditemukan" });
38050
+ continue;
38051
+ }
38052
+ if (!w.deletable) {
38053
+ results.push({ name: name2, ok: false, cleanup: null, error: `tak bisa dihapus: ${w.blocked}` });
38054
+ continue;
38055
+ }
38056
+ const row = { name: name2, ok: true, cleanup: null };
38057
+ try {
38058
+ if (w.session) {
38059
+ const closed = await opts.closeSession(w.session.id);
38060
+ row.closedSession = w.session.id;
38061
+ row.cleanup = closed?.cleanup ?? null;
38062
+ }
38063
+ if (!row.cleanup) row.cleanup = opts.release(report.repoDir, w.path);
38064
+ await opts.prune(report.repoDir);
38065
+ if (opts.withBranch && w.branch) {
38066
+ const b = await opts.deleteBranch(report.repoDir, w.branch);
38067
+ row.branch = { name: w.branch, ok: b.ok, ...b.error ? { error: b.error } : {} };
38068
+ }
38069
+ } catch (e) {
38070
+ row.ok = false;
38071
+ row.error = e.message;
38072
+ }
38073
+ results.push(row);
38074
+ }
38075
+ return { results };
38076
+ }
38077
+
38078
+ // src/services/session-close.ts
38079
+ init_src2();
38080
+ init_db();
38081
+ init_pty();
38082
+ init_session_phases();
38083
+ init_notifications();
38084
+ init_stage_machine();
38085
+
38086
+ // src/services/worktree-reaper.ts
38087
+ init_src2();
38088
+ init_db();
38089
+ import { execFile as execFile9 } from "node:child_process";
38090
+ import { readdir as readdir2, rm as rm4 } from "node:fs/promises";
38091
+ import { promisify as promisify9 } from "node:util";
38092
+ import { join as join12, resolve as resolve15 } from "node:path";
38093
+ init_notifications();
38094
+ var TICK_MS = 6e4;
38095
+ var trashDirOf = (repoDir) => resolve15(repoDir, ".worktrees", ".trash");
38096
+ var exec9 = promisify9(execFile9);
38097
+ var prodReaperDeps = {
38098
+ rm: (path2) => rm4(path2, { recursive: true, force: true }),
38099
+ prune: async (repoDir) => {
38100
+ try {
38101
+ await exec9("git", ["worktree", "prune"], { cwd: repoDir, timeout: 3e4 });
38102
+ } catch {
38103
+ }
38104
+ },
38105
+ repos: async () => {
38106
+ const projects = await prisma.project.findMany({ select: { id: true } });
38107
+ const seen = /* @__PURE__ */ new Set();
38108
+ const out4 = [];
38109
+ for (const p of projects) {
38110
+ const repoDir = await resolveRepoDir(p.id);
38111
+ if (!repoDir || seen.has(repoDir)) continue;
38112
+ seen.add(repoDir);
38113
+ out4.push({ projectId: p.id, repoDir });
38114
+ }
38115
+ return out4;
38116
+ }
38117
+ };
38118
+ var pending = /* @__PURE__ */ new Map();
38119
+ var sessionIdOf = (entry) => entry.split(".")[0] ?? entry;
38120
+ function releaseWorktree(repoDir, cwd, projectId, deps = prodReaperDeps) {
38121
+ let path2;
38122
+ try {
38123
+ path2 = realGit.trashWorktree(repoDir, cwd);
38124
+ } catch {
38125
+ realGit.removeWorktree(repoDir, cwd);
38126
+ return null;
38127
+ }
38128
+ if (!path2) return null;
38129
+ const entry = path2.slice(trashDirOf(repoDir).length + 1);
38130
+ pending.set(path2, { path: path2, repoDir, projectId, entry, sessionId: sessionIdOf(entry), since: Date.now() });
38131
+ reapSoon(repoDir, projectId, deps);
38132
+ return entry;
38133
+ }
38134
+ var listCleanups = () => [...pending.values()].sort((a, b) => a.since - b.since).map((p) => ({
38135
+ sessionId: p.sessionId,
38136
+ projectId: p.projectId,
38137
+ entry: p.entry,
38138
+ since: new Date(p.since).toISOString(),
38139
+ state: p.error ? "failed" : "closing",
38140
+ ...p.error ? { error: p.error } : {}
38141
+ }));
38142
+ async function sweepRepo(repoDir, projectId, deps = prodReaperDeps) {
38143
+ const dir2 = trashDirOf(repoDir);
38144
+ let entries;
38145
+ try {
38146
+ entries = await readdir2(dir2);
38147
+ } catch {
38148
+ return 0;
38149
+ }
38150
+ let removed = 0;
38151
+ for (const entry of entries) {
38152
+ const path2 = join12(dir2, entry);
38153
+ const known = pending.get(path2);
38154
+ const row = known ?? { path: path2, repoDir, projectId, entry, sessionId: sessionIdOf(entry), since: Date.now() };
38155
+ pending.set(path2, row);
38156
+ try {
38157
+ await deps.rm(path2);
38158
+ pending.delete(path2);
38159
+ removed++;
38160
+ } catch (e) {
38161
+ row.error = e.message;
38162
+ await recordCleanupFailure(row.sessionId, row.projectId, entry, row.error);
38163
+ }
38164
+ }
38165
+ if (removed) await deps.prune(repoDir);
38166
+ return removed;
38167
+ }
38168
+ async function sweepAll(deps = prodReaperDeps) {
38169
+ let removed = 0;
38170
+ for (const { projectId, repoDir } of await deps.repos()) {
38171
+ removed += await sweepRepo(repoDir, projectId, deps).catch(() => 0);
38172
+ }
38173
+ return removed;
38174
+ }
38175
+ var inFlight = /* @__PURE__ */ new Set();
38176
+ function reapSoon(repoDir, projectId, deps = prodReaperDeps) {
38177
+ if (inFlight.has(repoDir)) return;
38178
+ inFlight.add(repoDir);
38179
+ void sweepRepo(repoDir, projectId, deps).catch((e) => console.error("sapuan worktree:", e)).finally(() => inFlight.delete(repoDir));
38180
+ }
38181
+ var timer;
38182
+ function startWorktreeReaper(deps = prodReaperDeps) {
38183
+ if (timer) return;
38184
+ void sweepAll(deps).catch((e) => console.error("sapuan worktree saat boot:", e));
38185
+ timer = setInterval(() => {
38186
+ void sweepAll(deps).catch(() => {
38187
+ });
38188
+ }, TICK_MS);
38189
+ timer.unref();
38190
+ }
38191
+
38192
+ // src/services/session-close.ts
38193
+ async function advanceStage(specId, repoDir, sessionId2, flow, worktree) {
38194
+ const next = stageForRun(readPhases(phaseFilePath(repoDir, sessionId2), flow), worktree, specId);
38195
+ if (!next) return;
38196
+ const spec = await prisma.spec.findUnique({ where: { id: specId }, select: { stage: true, title: true, projectId: true } });
38197
+ if (!spec || STAGES.indexOf(next) <= STAGES.indexOf(spec.stage)) return;
38198
+ const { count } = await prisma.spec.updateMany({ where: { id: specId, stage: spec.stage }, data: { stage: next } });
38199
+ if (count === 0) return;
38200
+ let commitSha = null;
38201
+ try {
38202
+ commitSha = realGit.headSha(worktree);
38203
+ } catch {
38204
+ }
38205
+ await recordSessionResult({
38206
+ projectId: spec.projectId,
38207
+ specId,
38208
+ oldStage: spec.stage,
38209
+ newStage: next,
38210
+ commitSha,
38211
+ branch: `hanoman/${sessionId2}`,
38212
+ status: next === "done" ? "done" : "progress"
38213
+ }).catch(() => {
38214
+ });
38215
+ if (next === "done") await recordCompletion(specId, spec.title, spec.projectId);
38216
+ }
38217
+ async function closeSession(id) {
38218
+ const s2 = getSession(id);
38219
+ if (!s2) return null;
38220
+ if (s2.flow || s2.cwd.includes("/.worktrees/")) {
38221
+ const repoDir = await resolveRepoDir(s2.projectId);
38222
+ if (repoDir) {
38223
+ if (s2.specId) {
38224
+ if (s2.flow) await advanceStage(s2.specId, repoDir, id, s2.flow, s2.cwd);
38225
+ await recordHeadSha(s2.specId, s2.cwd);
38226
+ }
38227
+ killSession(id);
38228
+ const cleanup = ownsWorktree(repoDir, s2.cwd) ? releaseWorktree(repoDir, s2.cwd, s2.projectId) : null;
38229
+ return { cleanup };
38230
+ }
38231
+ }
38232
+ killSession(id);
38233
+ return { cleanup: null };
38234
+ }
38235
+
38236
+ // src/routes/ide.ts
38237
+ init_session_id();
38238
+
37783
38239
  // src/services/repo-fs.ts
37784
- import { lstat as lstat2, rename as rename3, rm as rm4 } from "node:fs/promises";
38240
+ import { lstat as lstat2, rename as rename3, rm as rm5 } from "node:fs/promises";
37785
38241
  import { createWriteStream } from "node:fs";
37786
- import { join as join11 } from "node:path";
38242
+ import { join as join13 } from "node:path";
37787
38243
  import { randomUUID as randomUUID6 } from "node:crypto";
37788
38244
  import { pipeline } from "node:stream/promises";
37789
38245
  var EntryExistsError = class extends Error {
@@ -37833,7 +38289,7 @@ async function deleteEntry(repoDir, rel) {
37833
38289
  const kind = await entryKind(repoDir, rel);
37834
38290
  if (!kind) throw new EntryMissingError(`tidak ada: ${rel}`);
37835
38291
  const { path: path2 } = await resolveRepoEntry(repoDir, rel);
37836
- await rm4(path2, { recursive: kind === "dir", force: false });
38292
+ await rm5(path2, { recursive: kind === "dir", force: false });
37837
38293
  return { path: rel, kind };
37838
38294
  }
37839
38295
  async function saveUpload(repoDir, rel, source, opts = {}) {
@@ -37844,17 +38300,17 @@ async function saveUpload(repoDir, rel, source, opts = {}) {
37844
38300
  if (current && !opts.overwrite) return { status: "exists" };
37845
38301
  if (current && !current.isFile())
37846
38302
  throw new PathContainmentError("repository path ditolak: target bukan file regular");
37847
- const temp = join11(entry.parent, `.hanoman-${randomUUID6()}.tmp`);
38303
+ const temp = join13(entry.parent, `.hanoman-${randomUUID6()}.tmp`);
37848
38304
  try {
37849
38305
  await pipeline(source, createWriteStream(temp, { flags: "wx", mode: 384 }));
37850
38306
  if (opts.isTruncated?.()) {
37851
- await rm4(temp, { force: true });
38307
+ await rm5(temp, { force: true });
37852
38308
  return { status: "too-large" };
37853
38309
  }
37854
38310
  await rename3(temp, entry.path);
37855
38311
  return { status: "written" };
37856
38312
  } catch (error) {
37857
- await rm4(temp, { force: true });
38313
+ await rm5(temp, { force: true });
37858
38314
  throw error;
37859
38315
  }
37860
38316
  }
@@ -37877,6 +38333,14 @@ async function lockInputs(id) {
37877
38333
  sessionBranches: new Set(sessions)
37878
38334
  };
37879
38335
  }
38336
+ async function worktreeInputs(id) {
38337
+ const specs = await prisma.spec.findMany({ where: { projectId: id }, select: { id: true, stage: true } });
38338
+ return {
38339
+ specs: new Map(specs.map((s2) => [sessionIdForSpec(s2.id), { id: s2.id, stage: s2.stage }])),
38340
+ sessions: new Map(listSessions().filter((s2) => s2.projectId === id && !s2.exited).map((s2) => [resolve16(s2.cwd), { id: s2.id, specId: s2.specId ?? null }]))
38341
+ };
38342
+ }
38343
+ var execAsync = promisify10(execFile10);
37880
38344
  function entryError(reply, e) {
37881
38345
  if (e instanceof EntryMissingError) return reply.code(404).send({ error: "not found" });
37882
38346
  if (e instanceof EntryExistsError) return reply.code(409).send({ error: "sudah ada" });
@@ -38129,7 +38593,7 @@ async function ide_default(app2) {
38129
38593
  const fmt = q.format === "tar" ? "tar" : "zip";
38130
38594
  const child = spawn3("git", ["archive", `--format=${fmt}`, "--end-of-options", ref], { cwd: repoDir });
38131
38595
  reply.header("content-type", fmt === "zip" ? "application/zip" : "application/x-tar");
38132
- reply.header("content-disposition", `attachment; filename="${basename4(repoDir)}-${ref.replace(/[^\w.-]/g, "_")}.${fmt}"`);
38596
+ reply.header("content-disposition", `attachment; filename="${basename5(repoDir)}-${ref.replace(/[^\w.-]/g, "_")}.${fmt}"`);
38133
38597
  return reply.send(child.stdout);
38134
38598
  });
38135
38599
  app2.get("/projects/:id/commit/:sha", async (req, reply) => {
@@ -38248,8 +38712,12 @@ async function ide_default(app2) {
38248
38712
  const { id } = req.params;
38249
38713
  const repoDir = await repoOf(id);
38250
38714
  if (repoDir === void 0) return reply.code(404).send({ error: "not found" });
38251
- const { base: base2 } = req.query;
38252
- return listUnusedBranches(repoDir, { base: base2, ...await lockInputs(id) });
38715
+ const { base: base2, include } = req.query;
38716
+ return listUnusedBranches(repoDir, {
38717
+ base: base2,
38718
+ include: include === "all" ? "all" : "merged",
38719
+ ...await lockInputs(id)
38720
+ });
38253
38721
  });
38254
38722
  app2.post("/projects/:id/branches/delete", async (req, reply) => {
38255
38723
  const { id } = req.params;
@@ -38261,12 +38729,60 @@ async function ide_default(app2) {
38261
38729
  return reply.code(400).send({ error: "names wajib berisi nama branch" });
38262
38730
  if (b.scope !== void 0 && b.scope !== "local" && b.scope !== "remote" && b.scope !== "both")
38263
38731
  return reply.code(400).send({ error: "scope harus local, remote, atau both" });
38732
+ if (b.allowUnmerged !== void 0 && typeof b.allowUnmerged !== "boolean")
38733
+ return reply.code(400).send({ error: "allowUnmerged harus boolean" });
38264
38734
  return deleteBranches(repoDir, b.names, {
38265
38735
  scope: b.scope ?? "both",
38266
38736
  base: typeof b.base === "string" && b.base ? b.base : void 0,
38737
+ allowUnmerged: b.allowUnmerged === true,
38267
38738
  ...await lockInputs(id)
38268
38739
  });
38269
38740
  });
38741
+ app2.get("/projects/:id/worktrees", async (req, reply) => {
38742
+ const { id } = req.params;
38743
+ const repoDir = await repoOf(id);
38744
+ if (repoDir === void 0) return reply.code(404).send({ error: "not found" });
38745
+ return listWorktrees(repoDir, await worktreeInputs(id));
38746
+ });
38747
+ app2.get("/projects/:id/worktrees/stats", async (req, reply) => {
38748
+ const { id } = req.params;
38749
+ const repoDir = await repoOf(id);
38750
+ if (repoDir === void 0) return reply.code(404).send({ error: "not found" });
38751
+ if (!repoDir) return reply.code(400).send({ error: "project tidak punya repoDir" });
38752
+ const { name: name2 } = req.query;
38753
+ const report = await listWorktrees(repoDir, await worktreeInputs(id));
38754
+ const w = report.worktrees.find((x) => x.name === name2);
38755
+ if (!w) return reply.code(404).send({ error: "not found" });
38756
+ return worktreeStats(report.repoDir, w);
38757
+ });
38758
+ app2.post("/projects/:id/worktrees/delete", async (req, reply) => {
38759
+ const { id } = req.params;
38760
+ const repoDir = await repoOf(id);
38761
+ if (repoDir === void 0) return reply.code(404).send({ error: "not found" });
38762
+ if (!repoDir) return reply.code(400).send({ error: "project tidak punya repoDir" });
38763
+ const b = req.body;
38764
+ if (!Array.isArray(b?.names) || b.names.some((n2) => typeof n2 !== "string" || !n2))
38765
+ return reply.code(400).send({ error: "names wajib berisi nama worktree" });
38766
+ const locks = await lockInputs(id);
38767
+ return deleteWorktrees(repoDir, b.names, {
38768
+ withBranch: b.deleteBranch === true,
38769
+ ...await worktreeInputs(id),
38770
+ closeSession,
38771
+ release: (repo, path2) => releaseWorktree(repo, path2, id),
38772
+ prune: async (repo) => {
38773
+ try {
38774
+ await execAsync("git", ["worktree", "prune"], { cwd: repo, timeout: 3e4 });
38775
+ } catch {
38776
+ }
38777
+ },
38778
+ // Pagar kunci ADR-0077 ikut apa adanya — satu-satunya jalur hapus branch di codebase.
38779
+ deleteBranch: async (repo, name2) => {
38780
+ const r = await deleteBranches(repo, [name2], { scope: "both", ...locks });
38781
+ const first = r.results[0];
38782
+ return { ok: !!first?.ok, ...first?.error ? { error: first.error } : {} };
38783
+ }
38784
+ });
38785
+ });
38270
38786
  }
38271
38787
  async function finishGraphOp(reply, id, repoDir, r, verb) {
38272
38788
  if (r.status === "error") return reply.code(r.code).send({ error: r.error });
@@ -38281,21 +38797,21 @@ async function finishGraphOp(reply, id, repoDir, r, verb) {
38281
38797
  CODE_STYLE_CLAUSE,
38282
38798
  `${verb} via git graph project ${id}.`
38283
38799
  ].join("\n\n");
38284
- const s2 = createSession(id, r.worktree, { id: basename4(r.worktree), model, effort, agent, prompt });
38800
+ const s2 = createSession(id, r.worktree, { id: basename5(r.worktree), model, effort, agent, prompt });
38285
38801
  return { status: "conflict", sessionId: s2.id };
38286
38802
  }
38287
38803
 
38288
38804
  // src/routes/fs.ts
38289
- import { readdir as readdir2 } from "node:fs/promises";
38805
+ import { readdir as readdir3 } from "node:fs/promises";
38290
38806
  import { homedir as homedir4 } from "node:os";
38291
- import { resolve as resolve13, dirname as dirname9, join as join12 } from "node:path";
38807
+ import { resolve as resolve17, dirname as dirname9, join as join14 } from "node:path";
38292
38808
  async function fs_default(app2) {
38293
38809
  app2.get("/fs/browse", async (req, reply) => {
38294
38810
  const q = req.query.path;
38295
- const dir2 = q && q.trim() ? resolve13(q.trim()) : homedir4();
38811
+ const dir2 = q && q.trim() ? resolve17(q.trim()) : homedir4();
38296
38812
  try {
38297
- const ents = await readdir2(dir2, { withFileTypes: true });
38298
- const entries = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: join12(dir2, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
38813
+ const ents = await readdir3(dir2, { withFileTypes: true });
38814
+ const entries = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: join14(dir2, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
38299
38815
  const parent = dirname9(dir2);
38300
38816
  return { path: dir2, parent: parent === dir2 ? null : parent, entries };
38301
38817
  } catch {
@@ -38498,8 +39014,8 @@ function setBounded(map, key, value, maxKeys) {
38498
39014
  // src/services/auth.ts
38499
39015
  init_db();
38500
39016
  import { randomBytes as randomBytes3, scrypt as scryptCb, timingSafeEqual as timingSafeEqual2, createHash as createHash6 } from "node:crypto";
38501
- import { promisify as promisify8 } from "node:util";
38502
- var scrypt = promisify8(scryptCb);
39017
+ import { promisify as promisify11 } from "node:util";
39018
+ var scrypt = promisify11(scryptCb);
38503
39019
  var COOKIE_NAME = "hn_session";
38504
39020
  var SESSION_TTL_MS = 7 * 24 * 3600 * 1e3;
38505
39021
  async function hashPassword(pw) {
@@ -38734,121 +39250,6 @@ function installCommand(m, agent, shell = shellBin()) {
38734
39250
  return [shell, "-lc", `${m.install[agent].join(" && ")}; exec '${shell}' -l`];
38735
39251
  }
38736
39252
 
38737
- // src/services/session-worktree.ts
38738
- import { resolve as resolve14, sep as sep3 } from "node:path";
38739
- function ownsWorktree(repoDir, cwd) {
38740
- const base2 = resolve14(repoDir);
38741
- const dir2 = resolve14(cwd);
38742
- const container = resolve14(base2, ".worktrees");
38743
- return dir2 !== base2 && dir2 !== container && dir2.startsWith(container + sep3);
38744
- }
38745
-
38746
- // src/services/worktree-reaper.ts
38747
- init_src2();
38748
- init_db();
38749
- import { execFile as execFile8 } from "node:child_process";
38750
- import { readdir as readdir3, rm as rm5 } from "node:fs/promises";
38751
- import { promisify as promisify9 } from "node:util";
38752
- import { join as join13, resolve as resolve15 } from "node:path";
38753
- init_notifications();
38754
- var TICK_MS = 6e4;
38755
- var trashDirOf = (repoDir) => resolve15(repoDir, ".worktrees", ".trash");
38756
- var exec8 = promisify9(execFile8);
38757
- var prodReaperDeps = {
38758
- rm: (path2) => rm5(path2, { recursive: true, force: true }),
38759
- prune: async (repoDir) => {
38760
- try {
38761
- await exec8("git", ["worktree", "prune"], { cwd: repoDir, timeout: 3e4 });
38762
- } catch {
38763
- }
38764
- },
38765
- repos: async () => {
38766
- const projects = await prisma.project.findMany({ select: { id: true } });
38767
- const seen = /* @__PURE__ */ new Set();
38768
- const out3 = [];
38769
- for (const p of projects) {
38770
- const repoDir = await resolveRepoDir(p.id);
38771
- if (!repoDir || seen.has(repoDir)) continue;
38772
- seen.add(repoDir);
38773
- out3.push({ projectId: p.id, repoDir });
38774
- }
38775
- return out3;
38776
- }
38777
- };
38778
- var pending = /* @__PURE__ */ new Map();
38779
- var sessionIdOf = (entry) => entry.split(".")[0] ?? entry;
38780
- function releaseWorktree(repoDir, cwd, projectId, deps = prodReaperDeps) {
38781
- let path2;
38782
- try {
38783
- path2 = realGit.trashWorktree(repoDir, cwd);
38784
- } catch {
38785
- realGit.removeWorktree(repoDir, cwd);
38786
- return null;
38787
- }
38788
- if (!path2) return null;
38789
- const entry = path2.slice(trashDirOf(repoDir).length + 1);
38790
- pending.set(path2, { path: path2, repoDir, projectId, entry, sessionId: sessionIdOf(entry), since: Date.now() });
38791
- reapSoon(repoDir, projectId, deps);
38792
- return entry;
38793
- }
38794
- var listCleanups = () => [...pending.values()].sort((a, b) => a.since - b.since).map((p) => ({
38795
- sessionId: p.sessionId,
38796
- projectId: p.projectId,
38797
- entry: p.entry,
38798
- since: new Date(p.since).toISOString(),
38799
- state: p.error ? "failed" : "closing",
38800
- ...p.error ? { error: p.error } : {}
38801
- }));
38802
- async function sweepRepo(repoDir, projectId, deps = prodReaperDeps) {
38803
- const dir2 = trashDirOf(repoDir);
38804
- let entries;
38805
- try {
38806
- entries = await readdir3(dir2);
38807
- } catch {
38808
- return 0;
38809
- }
38810
- let removed = 0;
38811
- for (const entry of entries) {
38812
- const path2 = join13(dir2, entry);
38813
- const known = pending.get(path2);
38814
- const row = known ?? { path: path2, repoDir, projectId, entry, sessionId: sessionIdOf(entry), since: Date.now() };
38815
- pending.set(path2, row);
38816
- try {
38817
- await deps.rm(path2);
38818
- pending.delete(path2);
38819
- removed++;
38820
- } catch (e) {
38821
- row.error = e.message;
38822
- await recordCleanupFailure(row.sessionId, row.projectId, entry, row.error);
38823
- }
38824
- }
38825
- if (removed) await deps.prune(repoDir);
38826
- return removed;
38827
- }
38828
- async function sweepAll(deps = prodReaperDeps) {
38829
- let removed = 0;
38830
- for (const { projectId, repoDir } of await deps.repos()) {
38831
- removed += await sweepRepo(repoDir, projectId, deps).catch(() => 0);
38832
- }
38833
- return removed;
38834
- }
38835
- var inFlight = /* @__PURE__ */ new Set();
38836
- function reapSoon(repoDir, projectId, deps = prodReaperDeps) {
38837
- if (inFlight.has(repoDir)) return;
38838
- inFlight.add(repoDir);
38839
- void sweepRepo(repoDir, projectId, deps).catch((e) => console.error("sapuan worktree:", e)).finally(() => inFlight.delete(repoDir));
38840
- }
38841
- var timer;
38842
- function startWorktreeReaper(deps = prodReaperDeps) {
38843
- if (timer) return;
38844
- void sweepAll(deps).catch((e) => console.error("sapuan worktree saat boot:", e));
38845
- timer = setInterval(() => {
38846
- void sweepAll(deps).catch(() => {
38847
- });
38848
- }, TICK_MS);
38849
- timer.unref();
38850
- }
38851
-
38852
39253
  // src/routes/terminal.ts
38853
39254
  init_notifications();
38854
39255
  init_stage_machine();
@@ -38856,30 +39257,6 @@ init_pty();
38856
39257
  init_uploads();
38857
39258
  var ATTACHMENT_MIME = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp"]);
38858
39259
  var TERMINAL_WS_MESSAGES_PER_MINUTE = 6e3;
38859
- async function advanceStage(specId, repoDir, sessionId2, flow, worktree) {
38860
- const next = stageForRun(readPhases(phaseFilePath(repoDir, sessionId2), flow), worktree, specId);
38861
- if (!next) return;
38862
- const spec = await prisma.spec.findUnique({ where: { id: specId }, select: { stage: true, title: true, projectId: true } });
38863
- if (!spec || STAGES.indexOf(next) <= STAGES.indexOf(spec.stage)) return;
38864
- const { count } = await prisma.spec.updateMany({ where: { id: specId, stage: spec.stage }, data: { stage: next } });
38865
- if (count === 0) return;
38866
- let commitSha = null;
38867
- try {
38868
- commitSha = realGit.headSha(worktree);
38869
- } catch {
38870
- }
38871
- await recordSessionResult({
38872
- projectId: spec.projectId,
38873
- specId,
38874
- oldStage: spec.stage,
38875
- newStage: next,
38876
- commitSha,
38877
- branch: `hanoman/${sessionId2}`,
38878
- status: next === "done" ? "done" : "progress"
38879
- }).catch(() => {
38880
- });
38881
- if (next === "done") await recordCompletion(specId, spec.title, spec.projectId);
38882
- }
38883
39260
  var ensureWorktree = (repoDir, wt, branchFrom) => {
38884
39261
  if (realGit.worktreeAlive(wt)) return true;
38885
39262
  realGit.addWorktree(repoDir, wt, branchFrom);
@@ -39161,22 +39538,9 @@ async function terminal_default(app2, opts) {
39161
39538
  app2.get("/terminal/cleanups", async () => ({ items: listCleanups() }));
39162
39539
  app2.delete("/terminal/sessions/:id", async (req, reply) => {
39163
39540
  const { id } = req.params;
39164
- const s2 = getSession(id);
39165
- if (!s2) return reply.code(404).send({ error: "not found" });
39166
- if (s2.flow || s2.cwd.includes("/.worktrees/")) {
39167
- const repoDir = await resolveRepoDir(s2.projectId);
39168
- if (repoDir) {
39169
- if (s2.specId) {
39170
- if (s2.flow) await advanceStage(s2.specId, repoDir, id, s2.flow, s2.cwd);
39171
- await recordHeadSha(s2.specId, s2.cwd);
39172
- }
39173
- killSession(id);
39174
- const cleanup = ownsWorktree(repoDir, s2.cwd) ? releaseWorktree(repoDir, s2.cwd, s2.projectId) : null;
39175
- return reply.code(202).send({ cleanup });
39176
- }
39177
- }
39178
- killSession(id);
39179
- return reply.code(202).send({ cleanup: null });
39541
+ const r = await closeSession(id);
39542
+ if (!r) return reply.code(404).send({ error: "not found" });
39543
+ return reply.code(202).send(r);
39180
39544
  });
39181
39545
  app2.post("/terminal/sessions/:id/attachments", async (req, reply) => {
39182
39546
  const { id } = req.params;
@@ -39231,7 +39595,7 @@ async function terminal_default(app2, opts) {
39231
39595
  } catch {
39232
39596
  return;
39233
39597
  }
39234
- if (m.t === "in" && typeof m.d === "string") writeTo(id, m.d);
39598
+ if (m.t === "in" && typeof m.d === "string") writeTo(id, m.d, client);
39235
39599
  else if (m.t === "resize" && m.cols && m.rows) resize(id, m.cols, m.rows);
39236
39600
  });
39237
39601
  const revalidate = setInterval(() => {
@@ -39428,7 +39792,7 @@ init_config2();
39428
39792
  import { spawn as spawn4 } from "node:child_process";
39429
39793
  import { mkdtempSync, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "node:fs";
39430
39794
  import { tmpdir as tmpdir3 } from "node:os";
39431
- import { dirname as dirname10, join as join14 } from "node:path";
39795
+ import { dirname as dirname10, join as join15 } from "node:path";
39432
39796
  var sshBin = () => effectiveStr("HANOMAN_SSH_BIN") ?? "ssh";
39433
39797
  function consoleArgv(t) {
39434
39798
  return [
@@ -39443,8 +39807,8 @@ function consoleArgv(t) {
39443
39807
  ];
39444
39808
  }
39445
39809
  function askpassScript() {
39446
- const dir2 = mkdtempSync(join14(tmpdir3(), "hanoman-askpass-"));
39447
- const path2 = join14(dir2, "askpass.sh");
39810
+ const dir2 = mkdtempSync(join15(tmpdir3(), "hanoman-askpass-"));
39811
+ const path2 = join15(dir2, "askpass.sh");
39448
39812
  writeFileSync4(path2, `#!/bin/sh
39449
39813
  printf '%s' "$HANOMAN_SSH_PASSWORD"
39450
39814
  `, { mode: 448 });
@@ -39478,24 +39842,24 @@ function sshExec(t, remoteCmd, opts = {}) {
39478
39842
  SSH_ASKPASS_REQUIRE: "force",
39479
39843
  HANOMAN_SSH_PASSWORD: opts.password
39480
39844
  } : process.env;
39481
- return new Promise((resolve19) => {
39845
+ return new Promise((resolve21) => {
39482
39846
  const p = spawn4(sshBin(), args, { stdio: ["pipe", "pipe", "pipe"], env });
39483
- let out3 = "";
39847
+ let out4 = "";
39484
39848
  const timer9 = setTimeout(() => p.kill("SIGKILL"), opts.timeoutMs ?? 6e4);
39485
39849
  const done = (r) => {
39486
39850
  clearTimeout(timer9);
39487
39851
  if (askpass) rmSync4(dirname10(askpass), { recursive: true, force: true });
39488
- resolve19(r);
39852
+ resolve21(r);
39489
39853
  };
39490
39854
  p.stdout.on("data", (d) => {
39491
- out3 += d;
39855
+ out4 += d;
39492
39856
  });
39493
39857
  p.stderr.on("data", (d) => {
39494
- out3 += d;
39858
+ out4 += d;
39495
39859
  });
39496
39860
  p.stdin.on("error", () => {
39497
39861
  });
39498
- p.on("close", (code) => done({ code: code ?? 1, out: out3 }));
39862
+ p.on("close", (code) => done({ code: code ?? 1, out: out4 }));
39499
39863
  p.on("error", (e) => done({ code: 127, out: String(e) }));
39500
39864
  if (opts.stdin) p.stdin.write(opts.stdin);
39501
39865
  p.stdin.end();
@@ -39505,15 +39869,15 @@ function sshExec(t, remoteCmd, opts = {}) {
39505
39869
  // src/services/vps-audit.ts
39506
39870
  init_db();
39507
39871
  import { readFileSync as readFileSync7 } from "node:fs";
39508
- import { join as join16 } from "node:path";
39872
+ import { join as join17 } from "node:path";
39509
39873
 
39510
39874
  // src/runner/deps.ts
39511
39875
  import { existsSync as existsSync11 } from "node:fs";
39512
- import { dirname as dirname11, join as join15 } from "node:path";
39876
+ import { dirname as dirname11, join as join16 } from "node:path";
39513
39877
  function repoRootFrom(startDir) {
39514
39878
  let dir2 = startDir;
39515
39879
  for (let i = 0; i < 8; i++) {
39516
- if (existsSync11(join15(dir2, "pnpm-workspace.yaml"))) return dir2;
39880
+ if (existsSync11(join16(dir2, "pnpm-workspace.yaml"))) return dir2;
39517
39881
  const parent = dirname11(dir2);
39518
39882
  if (parent === dir2) break;
39519
39883
  dir2 = parent;
@@ -39564,14 +39928,14 @@ function scoreCompliance(probeStatus, states, items = CATALOG) {
39564
39928
  // src/vps/drift.ts
39565
39929
  var REGRESS = /* @__PURE__ */ new Set(["fail", "warn"]);
39566
39930
  function computeDrift(prev, curr) {
39567
- const out3 = [];
39931
+ const out4 = [];
39568
39932
  for (const [id, c] of Object.entries(curr)) {
39569
39933
  const p = prev[id];
39570
39934
  if (p && p.status === "pass" && REGRESS.has(c.status)) {
39571
- out3.push({ itemId: id, from: p.status, to: c.status });
39935
+ out4.push({ itemId: id, from: p.status, to: c.status });
39572
39936
  }
39573
39937
  }
39574
- return out3;
39938
+ return out4;
39575
39939
  }
39576
39940
 
39577
39941
  // src/services/vps-audit.ts
@@ -39585,33 +39949,33 @@ var CRITICAL = [
39585
39949
  "fail2ban",
39586
39950
  "auto_updates"
39587
39951
  ];
39588
- function parseAudit(out3) {
39589
- return out3.split("\n").flatMap((line) => {
39952
+ function parseAudit(out4) {
39953
+ return out4.split("\n").flatMap((line) => {
39590
39954
  const m = line.match(/^CHECK (\S+) (pass|fail|warn|na)(?: (.*))?$/);
39591
39955
  return m ? [{ check: m[1], status: m[2], detail: (m[3] ?? "").trim() }] : [];
39592
39956
  });
39593
39957
  }
39594
- function parseStack(out3) {
39958
+ function parseStack(out4) {
39595
39959
  const d = {};
39596
- for (const line of out3.split("\n")) {
39960
+ for (const line of out4.split("\n")) {
39597
39961
  const m = line.match(/^STACK (\S+) (present|absent)(?: (.*))?$/);
39598
39962
  if (m) d[m[1]] = { present: m[2] === "present", detail: (m[3] ?? "").trim() };
39599
39963
  }
39600
39964
  return d;
39601
39965
  }
39602
39966
  function mapToCatalog(checks) {
39603
- const out3 = {};
39967
+ const out4 = {};
39604
39968
  for (const c of checks) {
39605
39969
  if (!byId(c.check)) continue;
39606
- out3[c.check] = c.status === "na" ? "unknown" : c.status;
39970
+ out4[c.check] = c.status === "na" ? "unknown" : c.status;
39607
39971
  }
39608
- return out3;
39972
+ return out4;
39609
39973
  }
39610
39974
  var isHardened = (checks) => CRITICAL.every((c) => checks.find((x) => x.check === c)?.status === "pass");
39611
39975
  var HEALTH_CMD = `echo HEALTH uptime $(uptime -p 2>/dev/null || uptime); echo HEALTH disk $(df -P / | awk 'NR==2{print $5}'); echo HEALTH mem $(free -m 2>/dev/null | awk 'NR==2{printf "%d/%dMB", $3, $2}'); echo HEALTH load $(cut -d" " -f1-3 /proc/loadavg)`;
39612
- function parseHealth(out3) {
39976
+ function parseHealth(out4) {
39613
39977
  const h = {};
39614
- for (const line of out3.split("\n")) {
39978
+ for (const line of out4.split("\n")) {
39615
39979
  const m = line.match(/^HEALTH (\S+) ?(.*)$/);
39616
39980
  if (m) h[m[1]] = m[2].trim();
39617
39981
  }
@@ -39619,7 +39983,7 @@ function parseHealth(out3) {
39619
39983
  return { uptime: h.uptime ?? "", disk: h.disk ?? "", mem: h.mem ?? "", load: h.load ?? "" };
39620
39984
  }
39621
39985
  var target = (v) => ({ host: v.host, port: v.port, user: v.user, keyPath: v.keyPath });
39622
- var scriptPath = (f) => join16(repoRoot(), "server", "scripts", "vps", f);
39986
+ var scriptPath = (f) => join17(repoRoot(), "server", "scripts", "vps", f);
39623
39987
  async function itemStatesOf(vpsId) {
39624
39988
  const rows = await prisma.vpsItemState.findMany({ where: { vpsId } });
39625
39989
  return Object.fromEntries(rows.map((s2) => [s2.itemId, { na: s2.na, attested: s2.attested }]));
@@ -39677,8 +40041,8 @@ async function runHealth(v) {
39677
40041
  }
39678
40042
 
39679
40043
  // src/services/vps-remediate.ts
39680
- function parseSteps(out3) {
39681
- return out3.split("\n").flatMap((line) => {
40044
+ function parseSteps(out4) {
40045
+ return out4.split("\n").flatMap((line) => {
39682
40046
  const m = line.match(/^STEP (\S+) (would|ok|fail)(?: (.*))?$/);
39683
40047
  return m ? [{ item: m[1], status: m[2], detail: (m[3] ?? "").trim() }] : [];
39684
40048
  });
@@ -39769,25 +40133,25 @@ init_config2();
39769
40133
  import { execFileSync as execFileSync2 } from "node:child_process";
39770
40134
  import { chmodSync as chmodSync3, copyFileSync, existsSync as existsSync12, mkdirSync as mkdirSync8, readFileSync as readFileSync9, unlinkSync as unlinkSync2 } from "node:fs";
39771
40135
  import { homedir as homedir5 } from "node:os";
39772
- import { join as join17 } from "node:path";
40136
+ import { join as join18 } from "node:path";
39773
40137
  var keyDir = () => effectiveStr("HANOMAN_SSH_KEY_DIR") ?? resolveDataDirs().sshKeys;
39774
40138
  var KEY_FILES = ["id_ed25519", "id_ed25519.pub"];
39775
40139
  function adoptLegacyKey(dir2) {
39776
40140
  if (effectiveStr("HANOMAN_SSH_KEY_DIR")) return;
39777
- const legacy = join17(homedir5(), ".hanoman");
40141
+ const legacy = join18(homedir5(), ".hanoman");
39778
40142
  if (legacy === dir2) return;
39779
- if (!KEY_FILES.every((f) => existsSync12(join17(legacy, f)))) return;
40143
+ if (!KEY_FILES.every((f) => existsSync12(join18(legacy, f)))) return;
39780
40144
  mkdirSync8(dir2, { recursive: true, mode: 448 });
39781
40145
  for (const f of KEY_FILES) {
39782
- copyFileSync(join17(legacy, f), join17(dir2, f));
39783
- chmodSync3(join17(dir2, f), 384);
40146
+ copyFileSync(join18(legacy, f), join18(dir2, f));
40147
+ chmodSync3(join18(dir2, f), 384);
39784
40148
  }
39785
- for (const f of KEY_FILES) unlinkSync2(join17(legacy, f));
40149
+ for (const f of KEY_FILES) unlinkSync2(join18(legacy, f));
39786
40150
  console.log(`vps: key SSH dipindah dari ${legacy} ke ${dir2} (SPEC-846 \u2014 satu batas backup)`);
39787
40151
  }
39788
40152
  function ensureHanomanKey() {
39789
40153
  const dir2 = keyDir();
39790
- const privPath = join17(dir2, KEY_FILES[0]);
40154
+ const privPath = join18(dir2, KEY_FILES[0]);
39791
40155
  const pubPath = `${privPath}.pub`;
39792
40156
  if (!existsSync12(privPath)) adoptLegacyKey(dir2);
39793
40157
  if (!existsSync12(privPath)) {
@@ -40026,7 +40390,7 @@ init_config2();
40026
40390
  import { execFileSync as execFileSync3 } from "node:child_process";
40027
40391
  import { readFileSync as readFileSync11 } from "node:fs";
40028
40392
  import { homedir as homedir7 } from "node:os";
40029
- import { join as join18 } from "node:path";
40393
+ import { join as join19 } from "node:path";
40030
40394
  var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
40031
40395
  var TTL_MS2 = 3e4;
40032
40396
  var lastOk = null;
@@ -40039,7 +40403,7 @@ var LABELS = {
40039
40403
  var humanize = (k) => k.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
40040
40404
  var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
40041
40405
  function credsFile() {
40042
- return join18(effectiveStr("CLAUDE_CONFIG_DIR") ?? join18(homedir7(), ".claude"), ".credentials.json");
40406
+ return join19(effectiveStr("CLAUDE_CONFIG_DIR") ?? join19(homedir7(), ".claude"), ".credentials.json");
40043
40407
  }
40044
40408
  function readAccessToken() {
40045
40409
  if (process.platform === "darwin" && !effectiveStr("CLAUDE_CONFIG_DIR")) {
@@ -40111,16 +40475,16 @@ async function getLimits() {
40111
40475
  }
40112
40476
 
40113
40477
  // src/services/codex-limits.ts
40114
- import { open as open3, readdir as readdir4, stat } from "node:fs/promises";
40478
+ import { open as open3, readdir as readdir4, stat as stat2 } from "node:fs/promises";
40115
40479
  import { homedir as homedir8 } from "node:os";
40116
- import { join as join19 } from "node:path";
40480
+ import { join as join20 } from "node:path";
40117
40481
  var TTL_MS3 = 3e4;
40118
40482
  var STALE_AFTER_MS = 12 * 36e5;
40119
40483
  var TAIL_BYTES = 512 * 1024;
40120
40484
  var MAX_FILES = 8;
40121
40485
  var cache3 = null;
40122
40486
  var freshUntil2 = 0;
40123
- var codexSessionsDir = () => join19(process.env.CODEX_HOME ?? join19(homedir8(), ".codex"), "sessions");
40487
+ var codexSessionsDir = () => join20(process.env.CODEX_HOME ?? join20(homedir8(), ".codex"), "sessions");
40124
40488
  var UNAVAILABLE = { status: "unavailable", windows: [], fetchedAt: null, plan: null };
40125
40489
  function windowLabel(minutes) {
40126
40490
  if (minutes === 300) return "Sesi 5 jam";
@@ -40153,9 +40517,9 @@ async function recentRollouts(dir2) {
40153
40517
  return [];
40154
40518
  }
40155
40519
  const stamped = await Promise.all(entries.map(async (rel) => {
40156
- const full = join19(dir2, rel);
40520
+ const full = join20(dir2, rel);
40157
40521
  try {
40158
- return { full, mtime: (await stat(full)).mtimeMs };
40522
+ return { full, mtime: (await stat2(full)).mtimeMs };
40159
40523
  } catch {
40160
40524
  return null;
40161
40525
  }
@@ -40233,15 +40597,15 @@ async function limits(app2) {
40233
40597
  // src/services/codex-version.ts
40234
40598
  init_src();
40235
40599
  init_config2();
40236
- import { execFile as execFile9 } from "node:child_process";
40237
- import { promisify as promisify10 } from "node:util";
40238
- var run = promisify10(execFile9);
40600
+ import { execFile as execFile11 } from "node:child_process";
40601
+ import { promisify as promisify12 } from "node:util";
40602
+ var run = promisify12(execFile11);
40239
40603
  var CODEX_MIN_CLIENT = "0.144.0";
40240
40604
  var TTL_MS4 = 5 * 6e4;
40241
40605
  var cache4 = null;
40242
40606
  var codexBin2 = () => effectiveStr("HANOMAN_CODEX_BIN") ?? "codex";
40243
- function parseCodexVersion(out3) {
40244
- const m = /(\d+)\.(\d+)\.(\d+)/.exec(out3);
40607
+ function parseCodexVersion(out4) {
40608
+ const m = /(\d+)\.(\d+)\.(\d+)/.exec(out4);
40245
40609
  return m ? `${m[1]}.${m[2]}.${m[3]}` : null;
40246
40610
  }
40247
40611
  async function getCodexVersion(now = Date.now()) {
@@ -40281,7 +40645,7 @@ init_src();
40281
40645
  init_src();
40282
40646
  init_config2();
40283
40647
  import { readFileSync as readFileSync12 } from "node:fs";
40284
- import { resolve as resolve16, dirname as dirname12 } from "node:path";
40648
+ import { resolve as resolve18, dirname as dirname12 } from "node:path";
40285
40649
  import { fileURLToPath as fileURLToPath3 } from "node:url";
40286
40650
  var UPDATE_COMMAND = "npm i -g hanoman@latest --prefer-online";
40287
40651
  function composeUpdate(x) {
@@ -40307,7 +40671,7 @@ var lastLatest = null;
40307
40671
  var lastStatus = "unavailable";
40308
40672
  function runningVersion() {
40309
40673
  const here = dirname12(fileURLToPath3(import.meta.url));
40310
- for (const p of [resolve16(here, "build-info.json"), resolve16(here, "../package.json")]) {
40674
+ for (const p of [resolve18(here, "build-info.json"), resolve18(here, "../package.json")]) {
40311
40675
  try {
40312
40676
  const v = JSON.parse(readFileSync12(p, "utf8")).version;
40313
40677
  if (typeof v === "string" && v) return v;
@@ -40823,11 +41187,11 @@ import { randomUUID as randomUUID8 } from "node:crypto";
40823
41187
  init_config2();
40824
41188
  init_src2();
40825
41189
  import { randomUUID as randomUUID7 } from "node:crypto";
40826
- import { mkdir as mkdir5, writeFile as writeFile4, readFile as readFile4, unlink as unlink4, readdir as readdir5, stat as stat2 } from "node:fs/promises";
40827
- import { join as join20, resolve as resolve17, basename as basename5 } from "node:path";
41190
+ import { mkdir as mkdir5, writeFile as writeFile4, readFile as readFile4, unlink as unlink4, readdir as readdir5, stat as stat3 } from "node:fs/promises";
41191
+ import { join as join21, resolve as resolve19, basename as basename6 } from "node:path";
40828
41192
  var MAX_TRANSCRIPT_BYTES = 1024 * 1024;
40829
41193
  function transcriptDir() {
40830
- return resolve17(effectiveStr("HANOMAN_TRANSCRIPT_DIR")?.trim() || resolveDataDirs().transcripts);
41194
+ return resolve19(effectiveStr("HANOMAN_TRANSCRIPT_DIR")?.trim() || resolveDataDirs().transcripts);
40831
41195
  }
40832
41196
  function clamp(text) {
40833
41197
  const buf = Buffer.from(text, "utf8");
@@ -40845,13 +41209,13 @@ async function saveTranscript(text) {
40845
41209
  const dir2 = transcriptDir();
40846
41210
  await mkdir5(dir2, { recursive: true, mode: 448 });
40847
41211
  const key = `${randomUUID7()}.log`;
40848
- await writeFile4(join20(dir2, key), body, { encoding: "utf8", mode: 384 });
41212
+ await writeFile4(join21(dir2, key), body, { encoding: "utf8", mode: 384 });
40849
41213
  return { key, bytes: Buffer.byteLength(body, "utf8"), truncated };
40850
41214
  }
40851
41215
  async function readTranscript(key) {
40852
41216
  if (!key) return null;
40853
41217
  try {
40854
- return await readFile4(join20(transcriptDir(), basename5(key)), "utf8");
41218
+ return await readFile4(join21(transcriptDir(), basename6(key)), "utf8");
40855
41219
  } catch {
40856
41220
  return null;
40857
41221
  }
@@ -40859,7 +41223,7 @@ async function readTranscript(key) {
40859
41223
  async function deleteTranscript(key) {
40860
41224
  if (!key) return;
40861
41225
  try {
40862
- await unlink4(join20(transcriptDir(), basename5(key)));
41226
+ await unlink4(join21(transcriptDir(), basename6(key)));
40863
41227
  } catch (e) {
40864
41228
  if (e.code !== "ENOENT") throw e;
40865
41229
  }
@@ -40877,7 +41241,7 @@ async function listTranscripts() {
40877
41241
  for (const name2 of names) {
40878
41242
  if (!name2.endsWith(".log")) continue;
40879
41243
  try {
40880
- rows.push({ key: name2, mtimeMs: (await stat2(join20(dir2, name2))).mtimeMs });
41244
+ rows.push({ key: name2, mtimeMs: (await stat3(join21(dir2, name2))).mtimeMs });
40881
41245
  } catch {
40882
41246
  }
40883
41247
  }
@@ -42171,12 +42535,12 @@ function parseLeadVerdict(raw2) {
42171
42535
  var SHA = /^[0-9a-f]{7,40}$/i;
42172
42536
  var isNonFileRef = (ref) => SHA.test(ref) || /^ADR-\d{4}$/i.test(ref);
42173
42537
  function keepExistingRefs(refs2, repoDir) {
42174
- const out3 = [];
42538
+ const out4 = [];
42175
42539
  for (const raw2 of refs2) {
42176
42540
  const ref = raw2.trim();
42177
42541
  if (!ref) continue;
42178
42542
  if (isNonFileRef(ref)) {
42179
- out3.push(ref);
42543
+ out4.push(ref);
42180
42544
  continue;
42181
42545
  }
42182
42546
  if (!repoDir) continue;
@@ -42184,9 +42548,9 @@ function keepExistingRefs(refs2, repoDir) {
42184
42548
  const full = resolvePath(repoDir, ref);
42185
42549
  const rel = relative2(repoDir, full);
42186
42550
  if (rel.startsWith("..")) continue;
42187
- if (existsSync14(full)) out3.push(ref);
42551
+ if (existsSync14(full)) out4.push(ref);
42188
42552
  }
42189
- return [...new Set(out3)];
42553
+ return [...new Set(out4)];
42190
42554
  }
42191
42555
 
42192
42556
  // src/services/lead/trail.ts
@@ -42335,11 +42699,11 @@ function toDecisionView(r) {
42335
42699
  init_config2();
42336
42700
  init_pty();
42337
42701
  init_session_sandbox();
42338
- import { execFile as execFile10 } from "node:child_process";
42702
+ import { execFile as execFile12 } from "node:child_process";
42339
42703
  import { randomUUID as randomUUID9 } from "node:crypto";
42340
42704
  import { mkdirSync as mkdirSync9, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "node:fs";
42341
42705
  import { tmpdir as tmpdir4 } from "node:os";
42342
- import { join as join21 } from "node:path";
42706
+ import { join as join22 } from "node:path";
42343
42707
  var binFor = (agent) => agent === "codex" ? effectiveStr("HANOMAN_CODEX_BIN") ?? "codex" : effectiveStr("HANOMAN_CLAUDE_BIN") ?? "claude";
42344
42708
  function leadArgv(o) {
42345
42709
  if (o.agent === "codex") {
@@ -42367,11 +42731,11 @@ function leadProcess(prompt, o, env = process.env) {
42367
42731
  const mode = env.HANOMAN_SESSION_SANDBOX ?? (env.NODE_ENV === "production" ? "required" : "off");
42368
42732
  if (mode === "off") return { file, args: directArgs, cwd: o.cwd, cleanup: () => {
42369
42733
  } };
42370
- const promptDir = join21(tmpdir4(), "hanoman-prompts");
42734
+ const promptDir = join22(tmpdir4(), "hanoman-prompts");
42371
42735
  mkdirSync9(promptDir, { recursive: true, mode: 448 });
42372
- const promptFile = join21(promptDir, `oneshot-${randomUUID9()}`);
42736
+ const promptFile = join22(promptDir, `oneshot-${randomUUID9()}`);
42373
42737
  writeFileSync5(promptFile, prompt, { flag: "wx", mode: 384 });
42374
- const workspace = o.cwd ?? join21(tmpdir4(), `hanoman-oneshot-${randomUUID9()}`);
42738
+ const workspace = o.cwd ?? join22(tmpdir4(), `hanoman-oneshot-${randomUUID9()}`);
42375
42739
  if (!o.cwd) mkdirSync9(workspace, { recursive: false, mode: 448 });
42376
42740
  try {
42377
42741
  const argsWithoutPrompt = directArgs.slice(0, -1);
@@ -42413,8 +42777,8 @@ function leadFailureReason(agent, timeoutMs, err, stdout, stderr) {
42413
42777
  }
42414
42778
  function think(prompt, o) {
42415
42779
  const process2 = leadProcess(prompt, o);
42416
- return new Promise((resolve19, reject2) => {
42417
- const child = execFile10(process2.file, process2.args, {
42780
+ return new Promise((resolve21, reject2) => {
42781
+ const child = execFile12(process2.file, process2.args, {
42418
42782
  cwd: process2.cwd,
42419
42783
  timeout: o.timeoutMs,
42420
42784
  maxBuffer: 16 * 1024 * 1024,
@@ -42427,7 +42791,7 @@ function think(prompt, o) {
42427
42791
  reject2(new Error(leadFailureReason(o.agent, o.timeoutMs, err, stdout, stderr)));
42428
42792
  return;
42429
42793
  }
42430
- resolve19(stdout);
42794
+ resolve21(stdout);
42431
42795
  });
42432
42796
  child.stdin?.end();
42433
42797
  });
@@ -42464,9 +42828,9 @@ function acquire(cap, waitMs) {
42464
42828
  return Promise.resolve();
42465
42829
  }
42466
42830
  const startedAt = Date.now();
42467
- return new Promise((resolve19, reject2) => {
42831
+ return new Promise((resolve21, reject2) => {
42468
42832
  const w = {
42469
- grant: resolve19,
42833
+ grant: resolve21,
42470
42834
  deny: reject2,
42471
42835
  timer: setTimeout(() => {
42472
42836
  const i = queue2.indexOf(w);
@@ -42567,12 +42931,12 @@ async function expireFlows(now) {
42567
42931
  orderBy: { createdAt: "asc" },
42568
42932
  take: 100
42569
42933
  });
42570
- const out3 = [];
42934
+ const out4 = [];
42571
42935
  for (const f of due) {
42572
42936
  const closed = await closeFlow(f.id, "kedaluwarsa");
42573
- if (closed) out3.push(closed);
42937
+ if (closed) out4.push(closed);
42574
42938
  }
42575
- return out3;
42939
+ return out4;
42576
42940
  }
42577
42941
  function toFlowView(r) {
42578
42942
  return {
@@ -43083,15 +43447,15 @@ var prodDetectDeps = {
43083
43447
  cfg: getLead
43084
43448
  };
43085
43449
  async function scanAndAnswer(deps = prodDetectDeps) {
43086
- const out3 = { answered: [], skipped: [] };
43450
+ const out4 = { answered: [], skipped: [] };
43087
43451
  const cfg = await deps.cfg();
43088
- if (!cfg.enabled || cfg.paused) return out3;
43452
+ if (!cfg.enabled || cfg.paused) return out4;
43089
43453
  const optIn = new Set(await deps.optIn());
43090
43454
  const sessions = deps.live();
43091
43455
  sweep(sessions.map((s2) => s2.id));
43092
43456
  const ready = [];
43093
43457
  for (const s2 of sessions) {
43094
- const skip = (reason) => out3.skipped.push({ id: s2.id, reason });
43458
+ const skip = (reason) => out4.skipped.push({ id: s2.id, reason });
43095
43459
  if (!optIn.has(s2.projectId)) {
43096
43460
  skip("project tak opt-in lead");
43097
43461
  continue;
@@ -43181,16 +43545,16 @@ async function scanAndAnswer(deps = prodDetectDeps) {
43181
43545
  deps.clearMarker(s2.decisionFile);
43182
43546
  answers.set(s2.id, (answers.get(s2.id) ?? 0) + 1);
43183
43547
  failures.delete(s2.id);
43184
- out3.answered.push(s2.id);
43548
+ out4.answered.push(s2.id);
43185
43549
  return;
43186
43550
  }
43187
43551
  if (chain.failed) {
43188
43552
  failures.set(s2.id, (failures.get(s2.id) ?? 0) + 1);
43189
43553
  failedAt.set(s2.id, deps.now());
43190
43554
  }
43191
- out3.skipped.push({ id: s2.id, reason: chain.reason });
43555
+ out4.skipped.push({ id: s2.id, reason: chain.reason });
43192
43556
  });
43193
- return out3;
43557
+ return out4;
43194
43558
  }
43195
43559
  async function runChain(s2, agent, deps) {
43196
43560
  let acted = false;
@@ -43300,7 +43664,7 @@ init_session_phases();
43300
43664
  init_notifications();
43301
43665
  var moduleOf = (p) => p.split("/").slice(0, 2).join("/");
43302
43666
  function findCollisions(areas) {
43303
- const out3 = [];
43667
+ const out4 = [];
43304
43668
  for (let i = 0; i < areas.length; i++) {
43305
43669
  for (let j = i + 1; j < areas.length; j++) {
43306
43670
  const a = areas[i], b = areas[j];
@@ -43309,10 +43673,10 @@ function findCollisions(areas) {
43309
43673
  const shared = a.paths.filter((p) => bp.has(p));
43310
43674
  const bm = new Set(b.paths.map(moduleOf));
43311
43675
  const nearby = [...new Set(a.paths.map(moduleOf))].filter((m) => bm.has(m) && !shared.some((s2) => moduleOf(s2) === m));
43312
- if (shared.length || nearby.length) out3.push({ a, b, shared, nearby });
43676
+ if (shared.length || nearby.length) out4.push({ a, b, shared, nearby });
43313
43677
  }
43314
43678
  }
43315
- return out3;
43679
+ return out4;
43316
43680
  }
43317
43681
  var prodPulseDeps = {
43318
43682
  sessions: () => {
@@ -43828,8 +44192,8 @@ init_db();
43828
44192
 
43829
44193
  // src/services/changelog/collect.ts
43830
44194
  init_db();
43831
- import { execFile as execFile11 } from "node:child_process";
43832
- import { promisify as promisify11 } from "node:util";
44195
+ import { execFile as execFile13 } from "node:child_process";
44196
+ import { promisify as promisify13 } from "node:util";
43833
44197
  import { existsSync as existsSync15 } from "node:fs";
43834
44198
 
43835
44199
  // src/services/changelog/scrub.ts
@@ -43891,8 +44255,8 @@ async function collectBacklog(projectId, from, to) {
43891
44255
  notes.push(`${stampless} item selesai tanpa stempel waktu (selesai sebelum stempel ini ada) dan tak ikut dihitung.`);
43892
44256
  return { ok: true, input: { mode: "backlog", title: `${from} \u2013 ${to}`, items, notes } };
43893
44257
  }
43894
- var exec9 = promisify11(execFile11);
43895
- var GIT7 = { maxBuffer: 1 << 24, encoding: "utf8" };
44258
+ var exec10 = promisify13(execFile13);
44259
+ var GIT8 = { maxBuffer: 1 << 24, encoding: "utf8" };
43896
44260
  var US2 = "";
43897
44261
  var RS = "";
43898
44262
  var NO_REPO = "project ini belum ditautkan ke repo di mesin ini";
@@ -43901,25 +44265,25 @@ var usable = (repoDir) => !!repoDir && existsSync15(repoDir);
43901
44265
  async function listTags(repoDir) {
43902
44266
  if (!usable(repoDir)) return { tags: [], head: null, reason: NO_REPO };
43903
44267
  try {
43904
- const { stdout } = await exec9("git", ["tag", "--list", "--sort=-creatordate"], { cwd: repoDir, ...GIT7 });
44268
+ const { stdout } = await exec10("git", ["tag", "--list", "--sort=-creatordate"], { cwd: repoDir, ...GIT8 });
43905
44269
  const tags = stdout.split("\n").map((s2) => s2.trim()).filter(Boolean);
43906
- const head = await exec9("git", ["rev-parse", "--short", "HEAD"], { cwd: repoDir, ...GIT7 }).then((r) => r.stdout.trim()).catch(() => null);
44270
+ const head = await exec10("git", ["rev-parse", "--short", "HEAD"], { cwd: repoDir, ...GIT8 }).then((r) => r.stdout.trim()).catch(() => null);
43907
44271
  return { tags, head, reason: tags.length ? null : NO_TAG };
43908
44272
  } catch {
43909
44273
  return { tags: [], head: null, reason: NO_TAG };
43910
44274
  }
43911
44275
  }
43912
44276
  async function previousTag(repoDir, tag2) {
43913
- return exec9("git", ["describe", "--tags", "--abbrev=0", "--end-of-options", `${tag2}^`], { cwd: repoDir, ...GIT7 }).then((r) => r.stdout.trim() || null).catch(() => null);
44277
+ return exec10("git", ["describe", "--tags", "--abbrev=0", "--end-of-options", `${tag2}^`], { cwd: repoDir, ...GIT8 }).then((r) => r.stdout.trim() || null).catch(() => null);
43914
44278
  }
43915
44279
  async function revExists(repoDir, rev) {
43916
- return exec9("git", ["rev-parse", "--verify", "--quiet", "--end-of-options", `${rev}^{commit}`], { cwd: repoDir, ...GIT7 }).then(() => true).catch(() => false);
44280
+ return exec10("git", ["rev-parse", "--verify", "--quiet", "--end-of-options", `${rev}^{commit}`], { cwd: repoDir, ...GIT8 }).then(() => true).catch(() => false);
43917
44281
  }
43918
44282
  async function logRange(repoDir, range) {
43919
- const { stdout } = await exec9(
44283
+ const { stdout } = await exec10(
43920
44284
  "git",
43921
44285
  ["log", "--no-merges", `--pretty=format:%s${US2}%b${RS}`, ...range],
43922
- { cwd: repoDir, ...GIT7 }
44286
+ { cwd: repoDir, ...GIT8 }
43923
44287
  );
43924
44288
  return stdout.split(RS).map((rec) => rec.replace(/^\n+/, "")).filter((rec) => rec.trim()).map((rec) => {
43925
44289
  const [subject, body] = rec.split(US2);
@@ -44160,7 +44524,7 @@ init_db();
44160
44524
  init_src();
44161
44525
  import { readFileSync as readFileSync13 } from "node:fs";
44162
44526
  import { homedir as homedir9 } from "node:os";
44163
- import { join as join22 } from "node:path";
44527
+ import { join as join23 } from "node:path";
44164
44528
  var home = () => process.env.HOME || homedir9();
44165
44529
  var readJson2 = (path2) => {
44166
44530
  try {
@@ -44177,25 +44541,25 @@ var serversOf = (node) => {
44177
44541
  var codexServers = () => {
44178
44542
  let text;
44179
44543
  try {
44180
- text = readFileSync13(join22(home(), ".codex", "config.toml"), "utf8");
44544
+ text = readFileSync13(join23(home(), ".codex", "config.toml"), "utf8");
44181
44545
  } catch {
44182
44546
  return [];
44183
44547
  }
44184
- const out3 = [];
44548
+ const out4 = [];
44185
44549
  for (const m of text.matchAll(/^\s*\[mcp_servers\.(?:"([^"]+)"|([A-Za-z0-9_-]+))(?:\.[^\]]*)?\]/gm)) {
44186
44550
  const name2 = m[1] ?? m[2];
44187
- if (name2) out3.push(name2);
44551
+ if (name2) out4.push(name2);
44188
44552
  }
44189
- return out3;
44553
+ return out4;
44190
44554
  };
44191
44555
  function mcpServerNames(repoDir) {
44192
44556
  const names = [];
44193
- const claudeJson = readJson2(join22(home(), ".claude.json"));
44557
+ const claudeJson = readJson2(join23(home(), ".claude.json"));
44194
44558
  names.push(...serversOf(claudeJson));
44195
44559
  if (repoDir) {
44196
44560
  const projects = claudeJson?.projects;
44197
44561
  if (projects && typeof projects === "object") names.push(...serversOf(projects[repoDir]));
44198
- names.push(...serversOf(readJson2(join22(repoDir, ".mcp.json"))));
44562
+ names.push(...serversOf(readJson2(join23(repoDir, ".mcp.json"))));
44199
44563
  }
44200
44564
  names.push(...codexServers());
44201
44565
  return [...new Set(names.filter(Boolean))].sort((a, b) => a.localeCompare(b));
@@ -44494,7 +44858,7 @@ async function resolveGithubRepo(projectId) {
44494
44858
 
44495
44859
  // src/services/github-fetch.ts
44496
44860
  init_config2();
44497
- import { execFile as execFile12 } from "node:child_process";
44861
+ import { execFile as execFile14 } from "node:child_process";
44498
44862
  var labelNames = (l) => (l ?? []).map((x) => typeof x === "string" ? x : x.name ?? "").filter(Boolean);
44499
44863
  var norm2 = (s2) => s2.toLowerCase() === "closed" ? "closed" : "open";
44500
44864
  function issueFromGh(raw2) {
@@ -44534,15 +44898,15 @@ function issuesFromRest(raw2) {
44534
44898
  }
44535
44899
  var GH_FIELDS = "number,title,body,author,labels,url,state,createdAt,updatedAt";
44536
44900
  var API2 = "https://api.github.com";
44537
- var defaultRunGh = (args, env) => new Promise((resolve19, reject2) => {
44538
- execFile12(
44901
+ var defaultRunGh = (args, env) => new Promise((resolve21, reject2) => {
44902
+ execFile14(
44539
44903
  args[0],
44540
44904
  args.slice(1),
44541
44905
  { env, maxBuffer: 1 << 26, encoding: "utf8", timeout: 6e4 },
44542
44906
  (err, stdout, stderr) => {
44543
44907
  const e = err;
44544
44908
  if (e && (e.code === "ENOENT" || e.code === "EACCES")) return reject2(e);
44545
- resolve19({ code: err ? Number(err.code ?? 1) : 0, stdout, stderr });
44909
+ resolve21({ code: err ? Number(err.code ?? 1) : 0, stdout, stderr });
44546
44910
  }
44547
44911
  );
44548
44912
  });
@@ -44577,20 +44941,20 @@ async function viaGh(repo, opts, deps) {
44577
44941
  ];
44578
44942
  const env = { ...process.env };
44579
44943
  if (deps.token) env.GH_TOKEN = deps.token;
44580
- let out3;
44944
+ let out4;
44581
44945
  try {
44582
- out3 = await (deps.runGh ?? defaultRunGh)(args, env);
44946
+ out4 = await (deps.runGh ?? defaultRunGh)(args, env);
44583
44947
  } catch {
44584
44948
  return { fallback: true, reason: "gh tak terpasang" };
44585
44949
  }
44586
- if (out3.code !== 0) {
44587
- const kind = classifyGhStderr(out3.stderr);
44950
+ if (out4.code !== 0) {
44951
+ const kind = classifyGhStderr(out4.stderr);
44588
44952
  if (kind === "unauth") return { fallback: true, reason: "gh tak terautentikasi" };
44589
- return { ok: false, kind, error: out3.stderr.trim() || `gh keluar dengan kode ${out3.code}` };
44953
+ return { ok: false, kind, error: out4.stderr.trim() || `gh keluar dengan kode ${out4.code}` };
44590
44954
  }
44591
44955
  let raw2;
44592
44956
  try {
44593
- raw2 = JSON.parse(out3.stdout || "[]");
44957
+ raw2 = JSON.parse(out4.stdout || "[]");
44594
44958
  } catch {
44595
44959
  return { ok: false, kind: "other", error: "keluaran gh bukan JSON" };
44596
44960
  }
@@ -45402,13 +45766,13 @@ async function webhooks_default(app2) {
45402
45766
  sentAt: res.ok ? /* @__PURE__ */ new Date() : null
45403
45767
  }
45404
45768
  });
45405
- const out3 = {
45769
+ const out4 = {
45406
45770
  ok: res.ok,
45407
45771
  httpStatus: res.httpStatus,
45408
45772
  durationMs: res.durationMs,
45409
45773
  error: res.error
45410
45774
  };
45411
- return out3;
45775
+ return out4;
45412
45776
  });
45413
45777
  app2.get("/webhooks/:id/deliveries", async (req, reply) => {
45414
45778
  const { id } = req.params;
@@ -45567,7 +45931,7 @@ init_settings2();
45567
45931
  // src/services/portal-chat/turn.ts
45568
45932
  init_src();
45569
45933
  init_db();
45570
- import { execFile as execFile13 } from "node:child_process";
45934
+ import { execFile as execFile15 } from "node:child_process";
45571
45935
 
45572
45936
  // src/services/portal-chat/guard-input.ts
45573
45937
  import { randomBytes as randomBytes9 } from "node:crypto";
@@ -45681,7 +46045,7 @@ init_src();
45681
46045
  init_db();
45682
46046
  import { mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync2, rmSync as rmSync6, writeFileSync as writeFileSync7 } from "node:fs";
45683
46047
  import { tmpdir as tmpdir5 } from "node:os";
45684
- import { join as join23 } from "node:path";
46048
+ import { join as join24 } from "node:path";
45685
46049
  var STAGE_LABEL = {
45686
46050
  brainstorming: "Dirumuskan",
45687
46051
  objective: "Dirumuskan",
@@ -45736,7 +46100,7 @@ ${c.body}
45736
46100
  ${baris.join("\n")}`;
45737
46101
  }
45738
46102
  async function buildChatWorkspace(projectId) {
45739
- const dir2 = mkdtempSync2(join23(tmpdir5(), "hanoman-portal-chat-"));
46103
+ const dir2 = mkdtempSync2(join24(tmpdir5(), "hanoman-portal-chat-"));
45740
46104
  try {
45741
46105
  const project = await prisma.project.findUnique({
45742
46106
  where: { id: projectId },
@@ -45757,7 +46121,7 @@ async function buildChatWorkspace(projectId) {
45757
46121
  });
45758
46122
  const files = [];
45759
46123
  const tulis = (rel, isi) => {
45760
- writeFileSync7(join23(dir2, rel), isi, { mode: 384 });
46124
+ writeFileSync7(join24(dir2, rel), isi, { mode: 384 });
45761
46125
  files.push(rel);
45762
46126
  };
45763
46127
  tulis("project.md", renderProjectDoc(project));
@@ -45766,11 +46130,11 @@ async function buildChatWorkspace(projectId) {
45766
46130
  tulis("catatan-rilis.md", renderChangelogDoc(changelogs));
45767
46131
  const prds = await listPrds(projectId);
45768
46132
  if (prds.length) {
45769
- mkdirSync10(join23(dir2, "dokumen"), { mode: 448 });
46133
+ mkdirSync10(join24(dir2, "dokumen"), { mode: 448 });
45770
46134
  for (const prd of prds) {
45771
46135
  const isi = await readPrd(projectId, prd.path);
45772
46136
  if (!isi) continue;
45773
- tulis(join23("dokumen", `${prd.slug.replaceAll("/", "-")}.md`), isi);
46137
+ tulis(join24("dokumen", `${prd.slug.replaceAll("/", "-")}.md`), isi);
45774
46138
  }
45775
46139
  }
45776
46140
  return { dir: dir2, files, cleanup: () => rmSync6(dir2, { recursive: true, force: true }) };
@@ -45852,8 +46216,8 @@ function chatFailureReason(err, stdout, stderr, timeoutMs) {
45852
46216
  return `chat portal gagal (${bagaimana}): ${detail}`;
45853
46217
  }
45854
46218
  function runProcess(p, timeoutMs) {
45855
- return new Promise((resolve19, reject2) => {
45856
- const child = execFile13(p.file, p.args, {
46219
+ return new Promise((resolve21, reject2) => {
46220
+ const child = execFile15(p.file, p.args, {
45857
46221
  cwd: p.cwd,
45858
46222
  timeout: timeoutMs,
45859
46223
  maxBuffer: 16 * 1024 * 1024,
@@ -45864,7 +46228,7 @@ function runProcess(p, timeoutMs) {
45864
46228
  stdout,
45865
46229
  stderr,
45866
46230
  timeoutMs
45867
- ))) : resolve19(stdout));
46231
+ ))) : resolve21(stdout));
45868
46232
  child.stdin?.end();
45869
46233
  });
45870
46234
  }
@@ -46282,13 +46646,13 @@ init_db();
46282
46646
  // src/services/bootstrap.ts
46283
46647
  import { createHash as createHash9, randomBytes as randomBytes10, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
46284
46648
  import { chmod, lstat as lstat3, mkdir as mkdir6, readFile as readFile5, unlink as unlink5, writeFile as writeFile5 } from "node:fs/promises";
46285
- import { join as join24 } from "node:path";
46649
+ import { join as join25 } from "node:path";
46286
46650
  var TTL_MS5 = 15 * 6e4;
46287
46651
  var SETUP_TOKEN_FILE = "setup.token";
46288
46652
  var BootstrapError = class extends Error {
46289
46653
  code = "BOOTSTRAP_PROOF";
46290
46654
  };
46291
- var tokenPath = (home3) => join24(home3, SETUP_TOKEN_FILE);
46655
+ var tokenPath = (home3) => join25(home3, SETUP_TOKEN_FILE);
46292
46656
  async function readStored(home3) {
46293
46657
  try {
46294
46658
  const info = await lstat3(tokenPath(home3));
@@ -46503,7 +46867,10 @@ var IDE_SUBS = /* @__PURE__ */ new Set([
46503
46867
  // POST/PATCH/DELETE menuntut ide:write — capability yang sudah memberi hak menimpa isi
46504
46868
  // berkas apa pun lewat PUT /file (hindari kelas bug SPEC-405: prefix tanpa lihat method).
46505
46869
  "entry",
46506
- "upload"
46870
+ "upload",
46871
+ // SPEC-861 · ADR-0132 · daftar & hapus worktree hidup. `rw()` menurunkan read/write DARI METHOD,
46872
+ // jadi POST /worktrees/delete menuntut ide:write (hindari kelas bug SPEC-405).
46873
+ "worktrees"
46507
46874
  ]);
46508
46875
  function capabilityForRoute(method, path2) {
46509
46876
  const read2 = method === "GET" || method === "HEAD";
@@ -46675,16 +47042,16 @@ init_runtime();
46675
47042
 
46676
47043
  // src/services/ingress-policy.ts
46677
47044
  function origins(value, key) {
46678
- const out3 = [];
47045
+ const out4 = [];
46679
47046
  for (const raw2 of value?.split(",") ?? []) {
46680
47047
  const input = raw2.trim();
46681
47048
  if (!input) continue;
46682
47049
  const parsed = new URL(input);
46683
47050
  if (!/^https?:$/.test(parsed.protocol) || parsed.username || parsed.password || parsed.pathname !== "/")
46684
47051
  throw new Error(`${key} harus berisi exact http(s) origin`);
46685
- out3.push({ host: parsed.host.toLowerCase(), base: `${parsed.protocol}//${parsed.host}` });
47052
+ out4.push({ host: parsed.host.toLowerCase(), base: `${parsed.protocol}//${parsed.host}` });
46686
47053
  }
46687
- return out3;
47054
+ return out4;
46688
47055
  }
46689
47056
  function loadIngressPolicy(env) {
46690
47057
  const pub = origins(env.HANOMAN_PUBLIC_ORIGINS, "HANOMAN_PUBLIC_ORIGINS");
@@ -47470,17 +47837,17 @@ function startWebhookEngine() {
47470
47837
  // src/services/auto-merge.ts
47471
47838
  init_src();
47472
47839
  init_db();
47473
- import { execFile as execFile14 } from "node:child_process";
47474
- import { promisify as promisify12 } from "node:util";
47840
+ import { execFile as execFile16 } from "node:child_process";
47841
+ import { promisify as promisify14 } from "node:util";
47475
47842
  init_notifications();
47476
47843
  var AUTO_MERGE_WINDOW_MS = 24 * 60 * 60 * 1e3;
47477
47844
  var AUTO_MERGE_GRACE_MS = 15 * 60 * 1e3;
47478
47845
  var TICK_MS5 = 6e4;
47479
- var exec10 = promisify12(execFile14);
47480
- var GIT8 = { timeout: 3e4, maxBuffer: 1 << 24, encoding: "utf8" };
47846
+ var exec11 = promisify14(execFile16);
47847
+ var GIT9 = { timeout: 3e4, maxBuffer: 1 << 24, encoding: "utf8" };
47481
47848
  var gitOut = async (cwd, args) => {
47482
47849
  try {
47483
- return (await exec10("git", args, { cwd, ...GIT8 })).stdout.trim();
47850
+ return (await exec11("git", args, { cwd, ...GIT9 })).stdout.trim();
47484
47851
  } catch {
47485
47852
  return null;
47486
47853
  }
@@ -47589,13 +47956,13 @@ init_src2();
47589
47956
 
47590
47957
  // src/services/secure-home.ts
47591
47958
  import { chmod as chmod2, lstat as lstat4, mkdir as mkdir7 } from "node:fs/promises";
47592
- import { isAbsolute as isAbsolute6, resolve as resolve18 } from "node:path";
47959
+ import { isAbsolute as isAbsolute6, resolve as resolve20 } from "node:path";
47593
47960
  var HomePermissionError = class extends Error {
47594
47961
  code = "HOME_SYMLINK";
47595
47962
  };
47596
47963
  async function assertNoSymlink(path2, allowMissing) {
47597
47964
  try {
47598
- const info = await lstat4(resolve18(path2));
47965
+ const info = await lstat4(resolve20(path2));
47599
47966
  if (info.isSymbolicLink()) throw new HomePermissionError(`symlink ditolak: ${path2}`);
47600
47967
  } catch (error) {
47601
47968
  if (error.code === "ENOENT" && allowMissing) return;