hanoman 0.9.37 → 0.9.39

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.
Files changed (30) hide show
  1. package/dist/build-info.json +3 -3
  2. package/dist/cli.js +80 -10
  3. package/dist/server.js +669 -395
  4. package/docs/agent-integration.md +5 -2
  5. package/package.json +2 -1
  6. package/skills/codex-image/SKILL.md +225 -0
  7. package/skills/codex-image/references/sprite-sheets.md +60 -0
  8. package/skills/codex-image/scripts/codex-image.sh +253 -0
  9. package/skills/codex-image/scripts/normalize-set.py +104 -0
  10. package/web/assets/{DalangHanomanScreen-JE8gkztH.js → DalangHanomanScreen-wpAO0xvb.js} +1 -1
  11. package/web/assets/{DocPreviewModal-B23vI_It.js → DocPreviewModal-CbhkDUJ2.js} +1 -1
  12. package/web/assets/{DocsWorkspace-CbV27H6A.js → DocsWorkspace-CcB0LGFV.js} +1 -1
  13. package/web/assets/{IdeScreen-BY7sm97W.js → IdeScreen-DI4v3ZkU.js} +1 -1
  14. package/web/assets/{LeadScreen-BFwI29zS.js → LeadScreen-BdS_xX_f.js} +1 -1
  15. package/web/assets/{ReviewScreen-pM6zzGZl.js → ReviewScreen-Ddw5U2sM.js} +1 -1
  16. package/web/assets/{SchedulerScreen-BIhqn3X1.js → SchedulerScreen-D6LGVrhM.js} +1 -1
  17. package/web/assets/{SettingsScreen-C_TW3mbs.js → SettingsScreen-CFNcg1T8.js} +1 -1
  18. package/web/assets/{TeamScreen-RT926lDf.js → TeamScreen-BkxPDI5P.js} +1 -1
  19. package/web/assets/TerminalScreen-CEQFK9Rc.js +2 -0
  20. package/web/assets/{TriageScreen-Zp1bdRnl.js → TriageScreen-B-HdZsEt.js} +1 -1
  21. package/web/assets/{VpsScreen-Jnb8C1o2.js → VpsScreen-CWp29NL_.js} +2 -2
  22. package/web/assets/{fields-B_3dIGcG.js → fields-BY7SYerd.js} +1 -1
  23. package/web/assets/{index-C3KwOJxI.css → index-B4b6i2tH.css} +1 -1
  24. package/web/assets/index-C4Py9MJV.js +1032 -0
  25. package/web/assets/{live-CNp86nGW.js → live-CBa_7-hn.js} +1 -1
  26. package/web/assets/{sched-lead-shared-BUcJMfNH.js → sched-lead-shared-CQIoYIzG.js} +1 -1
  27. package/web/assets/{settings-rebase-CYr7W_rB.js → settings-rebase-C3611gs8.js} +1 -1
  28. package/web/index.html +2 -2
  29. package/web/assets/TerminalScreen-fpC5a2jS.js +0 -2
  30. package/web/assets/index-CbX7iwxN.js +0 -1012
package/dist/server.js CHANGED
@@ -4273,12 +4273,13 @@ function stampSpecMethod(payload, methodId) {
4273
4273
  if (typeof payload !== "object" || Array.isArray(payload)) return null;
4274
4274
  return { ...payload, method: methodId };
4275
4275
  }
4276
- var DEFAULT_METHOD, VERIFICATION_GATE, MATT_CLAUSE, AGENT_SKILLS_CLAUSE, METHODS, METHOD_IDS, uniq, PLAN_DIRS, SPEC_DIRS;
4276
+ var DEFAULT_METHOD, VERIFICATION_GATE, MOCKUP_SKILL, MATT_CLAUSE, AGENT_SKILLS_CLAUSE, METHODS, METHOD_IDS, uniq, PLAN_DIRS, SPEC_DIRS;
4277
4277
  var init_method_catalog = __esm({
4278
4278
  "../shared/src/method-catalog.ts"() {
4279
4279
  "use strict";
4280
4280
  DEFAULT_METHOD = "superpowers";
4281
4281
  VERIFICATION_GATE = "superpowers:verification-before-completion";
4282
+ MOCKUP_SKILL = "codex-image";
4282
4283
  MATT_CLAUSE = "Skill mattpocock yang kontraknya mewawancarai manusia (`/grill-me`, `/to-spec`, `/triage`) JANGAN dipakai: pakai primitif model-invoked-nya untuk menguji rancanganmu sendiri, bukan untuk mewawancarai siapa pun. Hal yang masih ambigu dan akan mempengaruhi hasil tetap JANGAN diputuskan sendiri \u2014 bila kamu agen fase, daftarkan di `Keputusan terbuka:` laporanmu; bila tidak, ajukan lewat jalur tanya di klausa otonomi prompt ini. `to-tickets` di fase Plan dipakai HANYA sebagai penghasil berkas plan berkotak `- [ ]` di `docs/matt/plans/`, BUKAN penerbit tiket \u2014 hanoman sendiri adalah issue tracker-nya, jadi jangan menulis ke tracker eksternal mana pun.";
4283
4284
  AGENT_SKILLS_CLAUSE = "Pakai skill Agent Skills langsung sesuai fase, bukan slash-command atau meta-router `using-agent-skills`. Kontrak fase, instruksi project/pengguna, dan jalur keputusan/review hanoman tetap berlaku: `spec-driven-development` hanya menyusun spec pada fase Spec, jangan menjalankan Plan atau Execute dari fase itu. Simpan spec di `docs/agent-skills/specs/` dan plan beserta SELURUH checklist task `- [ ]` di `docs/agent-skills/plans/`, dengan id backlog pada nama berkas; ini menggantikan default `SPEC.md`, `tasks/plan.md`, dan `tasks/todo.md` upstream. Jangan menimpa plan lain yang belum tuntas atau membuat tiket di tracker eksternal: hanoman adalah issue tracker-nya. Centang task di berkas plan yang sama setelah implementasi dan verifikasi. Hal ambigu yang mempengaruhi hasil mengikuti jalur tanya prompt ini; bila kamu agen fase, daftarkan di `Keputusan terbuka:` laporanmu.";
4284
4285
  METHODS = {
@@ -4293,6 +4294,7 @@ var init_method_catalog = __esm({
4293
4294
  phaseSkills: {
4294
4295
  Brainstorm: ["superpowers:brainstorming"],
4295
4296
  Audit: ["superpowers:systematic-debugging"],
4297
+ Mockup: [MOCKUP_SKILL],
4296
4298
  Plan: ["superpowers:writing-plans"],
4297
4299
  Execute: [
4298
4300
  "superpowers:executing-plans",
@@ -4338,6 +4340,7 @@ var init_method_catalog = __esm({
4338
4340
  phaseSkills: {
4339
4341
  Brainstorm: ["mattpocock-skills:grilling"],
4340
4342
  Audit: ["mattpocock-skills:diagnosing-bugs"],
4343
+ Mockup: [MOCKUP_SKILL],
4341
4344
  Plan: ["mattpocock-skills:to-tickets"],
4342
4345
  Execute: [
4343
4346
  "mattpocock-skills:implement",
@@ -4376,6 +4379,7 @@ var init_method_catalog = __esm({
4376
4379
  phaseSkills: {
4377
4380
  Brainstorm: ["agent-skills:idea-refine"],
4378
4381
  Spec: ["agent-skills:spec-driven-development"],
4382
+ Mockup: [MOCKUP_SKILL],
4379
4383
  Audit: [
4380
4384
  "agent-skills:debugging-and-error-recovery",
4381
4385
  "agent-skills:code-review-and-quality"
@@ -4419,7 +4423,7 @@ var init_method_catalog = __esm({
4419
4423
 
4420
4424
  // ../shared/src/method-status.ts
4421
4425
  function methodSkills(m) {
4422
- return [.../* @__PURE__ */ new Set([...Object.values(m.phaseSkills).flat(), ...m.exitSkills])];
4426
+ return [.../* @__PURE__ */ new Set([...Object.values(m.phaseSkills).flat(), ...m.exitSkills])].filter((id2) => !HANOMAN_PROVIDED_SKILLS.has(id2));
4423
4427
  }
4424
4428
  function methodStatus(m, agent, installed2) {
4425
4429
  const haveSkills = new Set(installed2.skills);
@@ -4437,10 +4441,12 @@ function methodStatus(m, agent, installed2) {
4437
4441
  install: [...m.install[agent]]
4438
4442
  };
4439
4443
  }
4440
- var ANTIGRAVITY_METHOD_UNAVAILABLE_REASON;
4444
+ var HANOMAN_PROVIDED_SKILLS, ANTIGRAVITY_METHOD_UNAVAILABLE_REASON;
4441
4445
  var init_method_status = __esm({
4442
4446
  "../shared/src/method-status.ts"() {
4443
4447
  "use strict";
4448
+ init_method_catalog();
4449
+ HANOMAN_PROVIDED_SKILLS = /* @__PURE__ */ new Set([MOCKUP_SKILL]);
4444
4450
  ANTIGRAVITY_METHOD_UNAVAILABLE_REASON = "Installer metode Antigravity belum terverifikasi dari upstream";
4445
4451
  }
4446
4452
  });
@@ -5174,7 +5180,7 @@ var init_logs = __esm({
5174
5180
  function phaseAgentName(phase) {
5175
5181
  return PHASE_AGENT_PREFIX + phase.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
5176
5182
  }
5177
- var ORCHESTRATION_FLOWS, FLOW_PHASES, PHASE_AGENT_PREFIX, isPhaseAgentName, PHASE_EVIDENCE_GRACE_MS, EXECUTE_MODE_FLOWS, REVIEWER_CELL, REVIEWER_FLOWS, REVIEWER_DEFAULTS, UNSKIPPABLE_PHASES;
5183
+ var ORCHESTRATION_FLOWS, FLOW_PHASES, PHASE_AGENT_PREFIX, isPhaseAgentName, PHASE_EVIDENCE_GRACE_MS, EXECUTE_MODE_FLOWS, REVIEWER_CELL, REVIEWER_FLOWS, REVIEWER_DEFAULTS, UNSKIPPABLE_PHASES, MOCKUP_PHASE, OPTIONAL_PHASES;
5178
5184
  var init_orchestration = __esm({
5179
5185
  "../shared/src/orchestration.ts"() {
5180
5186
  "use strict";
@@ -5191,8 +5197,10 @@ var init_orchestration = __esm({
5191
5197
  ];
5192
5198
  FLOW_PHASES = {
5193
5199
  // ADR-0230 · `Review` = fase resmi sesudah Execute (dulu langkah R di dalam Execute, ADR-0170 #4).
5194
- feature: ["Brainstorm", "Objective", "Spec", "Plan", "Execute", "Review"],
5195
- qa: ["Audit", "Spec", "Plan", "Execute", "Review"],
5200
+ // ADR-0233 · `Mockup` = fase opsional sesudah Spec: gambar UI disetujui sebelum Plan, dilewati agen
5201
+ // bila backlog tak menyentuh UI.
5202
+ feature: ["Brainstorm", "Objective", "Spec", "Mockup", "Plan", "Execute", "Review"],
5203
+ qa: ["Audit", "Spec", "Mockup", "Plan", "Execute", "Review"],
5196
5204
  scaffold: ["Brainstorm", "Objective", "Doc index"],
5197
5205
  reverse: ["Scan", "Docs teknis", "Wawancara", "Konvensi & index", "Serah terima"],
5198
5206
  prd: ["Brainstorm", "PRD"],
@@ -5224,6 +5232,8 @@ var init_orchestration = __esm({
5224
5232
  antigravity: { model: "inherit", effort: null }
5225
5233
  };
5226
5234
  UNSKIPPABLE_PHASES = /* @__PURE__ */ new Set([REVIEWER_CELL]);
5235
+ MOCKUP_PHASE = "Mockup";
5236
+ OPTIONAL_PHASES = /* @__PURE__ */ new Set([MOCKUP_PHASE]);
5227
5237
  }
5228
5238
  });
5229
5239
 
@@ -5234,7 +5244,7 @@ var init_runtime_defaults = __esm({
5234
5244
  "use strict";
5235
5245
  init_orchestration();
5236
5246
  init_zod();
5237
- RUNTIME_DEFAULTS_VERSION = "2026-10-25-v1";
5247
+ RUNTIME_DEFAULTS_VERSION = "2026-10-31-v1";
5238
5248
  zRuntimeDefaultState = external_exports.enum(["seeded", "user"]);
5239
5249
  zBuiltinRuntimeDefaults = external_exports.object({
5240
5250
  version: external_exports.string().default(RUNTIME_DEFAULTS_VERSION),
@@ -5290,6 +5300,7 @@ var init_runtime_defaults = __esm({
5290
5300
  Brainstorm: cell("sonnet", "medium"),
5291
5301
  Objective: cell("sonnet", "medium"),
5292
5302
  Spec: cell("opus", "high"),
5303
+ Mockup: cell("sonnet", "medium"),
5293
5304
  Plan: cell("opus", "high"),
5294
5305
  Execute: cell("sonnet", "high"),
5295
5306
  Review: cell("opus", "high")
@@ -5298,6 +5309,7 @@ var init_runtime_defaults = __esm({
5298
5309
  Brainstorm: cell("gpt-5.6-terra", "medium"),
5299
5310
  Objective: cell("gpt-5.6-terra", "medium"),
5300
5311
  Spec: cell("gpt-5.6-sol", "high"),
5312
+ Mockup: cell("gpt-5.6-terra", "medium"),
5301
5313
  Plan: cell("gpt-5.6-sol", "high"),
5302
5314
  Execute: cell("gpt-5.6-terra", "high"),
5303
5315
  Review: cell("gpt-5.6-sol", "high")
@@ -5307,6 +5319,7 @@ var init_runtime_defaults = __esm({
5307
5319
  {
5308
5320
  Audit: cell("opus", "high"),
5309
5321
  Spec: cell("opus", "high"),
5322
+ Mockup: cell("sonnet", "medium"),
5310
5323
  Plan: cell("sonnet", "high"),
5311
5324
  Execute: cell("sonnet", "high"),
5312
5325
  Review: cell("opus", "high")
@@ -5314,6 +5327,7 @@ var init_runtime_defaults = __esm({
5314
5327
  {
5315
5328
  Audit: cell("gpt-5.6-sol", "high"),
5316
5329
  Spec: cell("gpt-5.6-sol", "high"),
5330
+ Mockup: cell("gpt-5.6-terra", "medium"),
5317
5331
  Plan: cell("gpt-5.6-terra", "high"),
5318
5332
  Execute: cell("gpt-5.6-terra", "high"),
5319
5333
  Review: cell("gpt-5.6-sol", "high")
@@ -7699,7 +7713,7 @@ function subKey(topic, params) {
7699
7713
  for (const k of Object.keys(params).sort()) if (params[k] !== void 0) canon[k] = params[k];
7700
7714
  return `${topic}|${JSON.stringify(canon)}`;
7701
7715
  }
7702
- var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zChangeSpecSource, zMarkSpecDone, zMarkSpecReviewed, zIntegrate, zSessionSummary, zProjectView, zSchedulerQueueItem, zSchedulerSourceView, zSchedulerSessionView, zSchedulerQueueCounts, zSchedulerState, zLeadDecisionView, zLeadFlowView, zLeadAnswer, zLeadProjectStatus, zLeadStatusView, zFlow, isGoalShapedFlow, zPrdBrief, zPrdDoc, zBreakdownItem, zBreakdownDoc, zBatchCreateSpec, zEscalationTarget, zEscalationPrefill, zAuditEscalation, zAuditEscalationView, zTerminalSession, zDocFileContent, zDocIndexCat, zDocIndex, HOST_RE, USER_RE, zCreateVps, zPatchVps, USER_ROLES, zUserRole, zLogin, zSignup, zSetup, zSetupApply, zChangePassword, zAdminPasswordReset, zCreateClientAccount, zUpdateClientAccount, zVpsCheck, zMarkNa, zMarkNaBulk, zAttest, zRemediate, PROVISION_PROFILES, COMPONENT_IDS, zProvision, UPDATE_RESTART_EXIT, CONFIG_RESTART_EXIT, MANUAL_RESTART_EXIT, EXIT_INTENT_FILE, zUpdateApplyBody, MAX_SUBS, zSubPage, zSubLimit, zTopicParams, zEventsClientMsg, zTicketView, zTicketAttachmentView, zSpecAttachmentView, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus, zCronExpr, zCreateCron, zPatchCron, zSchedulerCron, zSchedulerCronRun;
7716
+ var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zChangeSpecSource, zMarkSpecDone, zMarkSpecReviewed, zIntegrate, zSessionSummary, zProjectView, zSchedulerQueueItem, zSchedulerSourceView, zSchedulerSessionView, zSchedulerQueueCounts, zSchedulerState, zLeadDecisionView, zLeadFlowView, zLeadAnswer, zLeadProjectStatus, zLeadStatusView, zFlow, isGoalShapedFlow, zPrdBrief, zPrdDoc, zBreakdownItem, zBreakdownDoc, zBatchCreateSpec, zEscalationTarget, zEscalationPrefill, zAuditEscalation, zAuditEscalationView, zTerminalSession, zDocFileContent, zDocIndexCat, zDocIndex, HOST_RE, USER_RE, zCreateVps, zPatchVps, USER_ROLES, zUserRole, zLogin, zSignup, zSetup, zSetupApply, zChangePassword, zAdminPasswordReset, zCreateClientAccount, zUpdateClientAccount, zVpsCheck, zMarkNa, zMarkNaBulk, zAttest, zRemediate, PROVISION_PROFILES, COMPONENT_IDS, zProvision, UPDATE_RESTART_EXIT, CONFIG_RESTART_EXIT, MANUAL_RESTART_EXIT, EXIT_INTENT_FILE, zUpdateApplyBody, MAX_SUBS, zSubPage, zSubLimit, zTopicParams, zEventsClientMsg, zTicketView, zTicketAttachmentView, zSpecAttachmentView, zMockupItem, zSpecMockups, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus, zCronExpr, zCreateCron, zPatchCron, zSchedulerCron, zSchedulerCronRun;
7703
7717
  var init_dto = __esm({
7704
7718
  "../shared/src/dto.ts"() {
7705
7719
  "use strict";
@@ -8323,6 +8337,12 @@ var init_dto = __esm({
8323
8337
  size: external_exports.number().int(),
8324
8338
  createdAt: external_exports.string()
8325
8339
  });
8340
+ zMockupItem = external_exports.object({ name: external_exports.string(), size: external_exports.number().int(), modifiedAt: external_exports.string() });
8341
+ zSpecMockups = external_exports.object({
8342
+ source: external_exports.enum(["worktree", "repo"]).nullable(),
8343
+ dir: external_exports.string().nullable(),
8344
+ items: external_exports.array(zMockupItem)
8345
+ });
8326
8346
  zTicketDetail = zTicketView.extend({
8327
8347
  detail: external_exports.string(),
8328
8348
  attachments: external_exports.array(zTicketAttachmentView),
@@ -8473,6 +8493,9 @@ var init_api = __esm({
8473
8493
  // SPEC-843 · ADR-0124 · lampiran backlog item (multi-berkas per request).
8474
8494
  specAttachments: (id2) => `${API}/specs/${id2}/attachments`,
8475
8495
  specAttachment: (id2, attId) => `${API}/specs/${id2}/attachments/${attId}`,
8496
+ // ADR-0233 · mockup UI backlog (docs/mockups/<spec-id>/).
8497
+ specMockups: (id2) => `${API}/specs/${id2}/mockups`,
8498
+ specMockup: (id2, name2) => `${API}/specs/${id2}/mockups/${encodeURIComponent(name2)}`,
8476
8499
  specReview: (id2) => `${API}/specs/${id2}/review`,
8477
8500
  specReviewFile: (id2, path) => `${API}/specs/${id2}/review/${path}`,
8478
8501
  // ADR-0213 · hapus worktree satu backlog item (cabang & commit tetap).
@@ -11228,6 +11251,19 @@ var init_backlog = __esm({
11228
11251
  build: (a) => ({ method: "GET", path: `/specs/${enc(String(a.spec))}/attachments` }),
11229
11252
  shape: (raw) => raw
11230
11253
  },
11254
+ // ADR-0233 · mockup UI hasil fase Mockup, dibaca dari worktree sesi atau checkout project.
11255
+ {
11256
+ name: "hanoman_backlog_mockups_list",
11257
+ title: "Daftar mockup backlog",
11258
+ description: "Daftar gambar mockup UI sebuah backlog item (hasil fase Mockup, `docs/mockups/<spec-id>/`): `source` (`worktree` sesi atau `repo` checkout project, null bila belum ada), `dir` (path absolut folder di MESIN SERVER hanoman \u2014 baca langsung bila kamu di mesin yang sama), dan `items` {name, size, modifiedAt}. Unduh satu gambar ke disk klien dengan hanoman_backlog_mockup_download.",
11259
+ inputSchema: obj({ properties: { spec: str(ID_HINT) }, required: ["spec"] }),
11260
+ mode: "read",
11261
+ capability: "backlog:read",
11262
+ samplePath: "/specs/SPEC-1/mockups",
11263
+ sampleMethod: "GET",
11264
+ build: (a) => ({ method: "GET", path: `/specs/${enc(String(a.spec))}/mockups` }),
11265
+ shape: (raw) => raw
11266
+ },
11231
11267
  {
11232
11268
  name: "hanoman_backlog_attachment_delete",
11233
11269
  title: "Hapus lampiran backlog",
@@ -14846,6 +14882,26 @@ var init_transfer = __esm({
14846
14882
  build: (a) => ({ method: "GET", path: `${specOne(a)}/${enc(String(a.attachment))}`, download: dlFile(a, "lampiran") }),
14847
14883
  shape: (raw) => raw
14848
14884
  },
14885
+ // ADR-0233 · mockup UI backlog (`docs/mockups/<spec-id>/`), biner → transfer tool (preseden ADR-0183).
14886
+ {
14887
+ name: "hanoman_backlog_mockup_download",
14888
+ title: "Unduh mockup backlog",
14889
+ description: `Mengunduh SATU gambar mockup UI backlog item ke disk lokal klien MCP. Nama berkas dari hanoman_backlog_mockups_list. ${DOWNLOAD_RESULT}`,
14890
+ inputSchema: obj({
14891
+ properties: { spec: SPEC, name: str("Nama berkas mockup, mis. `01-backlog-detail.png`."), savePath: SAVE_PATH, overwrite: OVERWRITE },
14892
+ required: ["spec", "name"]
14893
+ }),
14894
+ mode: "write",
14895
+ capability: "backlog:read",
14896
+ samplePath: "/specs/SPEC-1/mockups/01-x.png",
14897
+ sampleMethod: "GET",
14898
+ build: (a) => ({
14899
+ method: "GET",
14900
+ path: `/specs/${enc(String(a.spec))}/mockups/${enc(String(a.name))}`,
14901
+ download: dlFile(a, String(a.name ?? "mockup.png"))
14902
+ }),
14903
+ shape: (raw) => raw
14904
+ },
14849
14905
  // ───────────────────────── lampiran tiket ─────────────────────────
14850
14906
  {
14851
14907
  name: "hanoman_ticket_attachment_download",
@@ -17395,6 +17451,7 @@ function orchestratorClause(plan, o = {}) {
17395
17451
  const askEscalation = codex2 || antigravity2 ? "tanyakan di terminal ini lalu tunggu jawaban" : ASK_ESCALATION_TOOL;
17396
17452
  const askDecisions = antigravity2 ? "di terminal ini sekaligus dalam satu pesan, tiap pertanyaan bernomor dan diakhiri `?` beserta opsi, rekomendasi dan alasannya, lalu tunggu jawaban" : codex2 ? "di terminal ini sekaligus dalam satu pesan, tiap pertanyaan bernomor dan diakhiri `?` beserta opsinya, lalu tunggu jawaban" : ASK_DECISIONS_TOOL;
17397
17453
  const turnTaking = plan.flow === "reverse" || plan.flow === "scaffold" || plan.flow === "prd" ? " Fase yang bergiliran dengan manusia (Wawancara, Brainstorm prd/scaffold) melapor satu topik per putaran \u2014 relay tiap putaran dengan cara yang sama, bawa konteks & opsi dari laporannya, dan biarkan jawaban bebas." : "";
17454
+ const mockupStep = plan.phases.some((p3) => p3.phase === MOCKUP_PHASE) ? 'M. Fase Mockup: laporan `Status: selesai` dengan `Rekomendasi fase: lewati` \u2192 jalankan `echo "Mockup skipped" >> "$HANOMAN_PHASE_FILE"` (gerbang yang sama seperti langkah 2), BUKAN `Mockup done`. Permintaan persetujuan mockup datang sebagai `Keputusan terbuka:` \u2014 relay lewat langkah 4 apa adanya, termasuk path gambar dan baris `Lihat di hanoman:`; jangan menyetujuinya sendiri.' : "";
17398
17455
  const handoff = plan.phases.some((p3) => p3.phase === "Serah terima") ? "Sesudah fase Serah terima tercatat, tampilkan bagian `Ringkasan serah terima:` dari laporannya APA ADANYA ke manusia di terminal ini (teks biasa \u2014 informasi, bukan pertanyaan) sebelum menutup pekerjaan." : "";
17399
17456
  const runtimeLabel = (entry2) => antigravity2 ? `tier native ${entry2.model} \xB7 effort native unknown` : `${entry2.model} \xB7 ${entry2.effort}`;
17400
17457
  const phaseList = plan.phases.map((p3, i) => `${i + 1}. ${p3.phase} \u2192 \`${p3.agentName}\` \xB7 ${runtimeLabel(p3)}`).join("\n");
@@ -17425,7 +17482,8 @@ ${artefactRule} ${describeRule}`,
17425
17482
  '2. Baca laporannya. `Status: selesai` DENGAN bukti \u2192 SEBELUM hal lain (memanggil fase berikutnya, commit, atau push): jalankan persis `echo "<Nama Fase> done" >> "$HANOMAN_PHASE_FILE"`, lalu verifikasi dengan `tail -1 "$HANOMAN_PHASE_FILE"` bahwa barisnya benar tertulis. Kamu satu-satunya penulis berkas itu.' + (review ? " Khusus Review: marker hanya sesudah `Verdict: lulus` (langkah R)." : ""),
17426
17483
  `3. \`Status: sebagian\`/\`terhalang\`, galat, atau laporan tanpa bukti \u2192 delegasikan ULANG SEKALI ke subagent fase yang sama${retry} dengan laporan gagalnya disertakan (\`Percobaan: 2/2\`). Gagal lagi \u2192 BERHENTI dan ${askEscalation} apa yang harus dilakukan. Aturan ini berlaku walau klausa otonomi di prompt ini menyuruhmu tak bertanya.`,
17427
17484
  `4. \`Status: menunggu-keputusan\` atau \`Keputusan terbuka:\` yang bukan \`-\` \u2192 SEBELUM menulis marker fase atau memanggil fase berikutnya, ajukan SEMUA pertanyaannya ${askDecisions}, lalu LANJUTKAN subagent yang SAMA lewat ${resume} dengan seluruh jawabannya. Ulangi selama subagent masih melaporkan keputusan terbuka \u2014 tak ada batas jumlah pertanyaan maupun putaran, dan giliran relay ini bukan percobaan ulang. JANGAN pernah menjawab sendiri, termasuk di sesi scheduler: yang menjawab ${DECIDER} \u2014 hanoman yang merutekannya, kamu cukup bertanya.${turnTaking}`,
17428
- o.fastPath ? '5. `Rekomendasi fase: jalur-cepat` sesudah Audit \u2192 SEBELUM lanjut ke Execute: jalankan `echo "Spec skipped" >> "$HANOMAN_PHASE_FILE"` lalu `echo "Plan skipped" >> "$HANOMAN_PHASE_FILE"` (gerbang yang sama seperti langkah 2), lalu lanjut ke Execute. `penuh` \u2192 Spec \u2192 Plan \u2192 Execute.' : "",
17485
+ o.fastPath ? '5. `Rekomendasi fase: jalur-cepat` sesudah Audit \u2192 SEBELUM lanjut ke Execute: jalankan `echo "Spec skipped" >> "$HANOMAN_PHASE_FILE"`, `echo "Mockup skipped" >> "$HANOMAN_PHASE_FILE"`, lalu `echo "Plan skipped" >> "$HANOMAN_PHASE_FILE"` (gerbang yang sama seperti langkah 2), lalu lanjut ke Execute. `penuh` \u2192 Spec \u2192 Mockup \u2192 Plan \u2192 Execute.' : "",
17486
+ mockupStep,
17429
17487
  reviewStep,
17430
17488
  handoff,
17431
17489
  "DILARANG mengerjakan isi fase sendiri \u2014 termasuk saat subagent gagal. Menulis `skipped` untuk fase yang dilewati bukan mengerjakannya.",
@@ -17664,7 +17722,7 @@ Stack: ${project.stack || "\u2014"}`,
17664
17722
  ${cron.prompt}`
17665
17723
  ].join("\n\n");
17666
17724
  }
17667
- var PIPELINES, WORK_PHASES, DECIDER, ASK_ROUTE, ASK_ESCALATION_TOOL, ASK_DECISIONS_TOOL, AMBIGUOUS, AUTONOMY_CLAUSE, AUTONOMY_CLAUSE_FULL, autonomyClause, PLAN_SHAPE, phaseInstruction, SELF_REVIEW_CLAUSE, skillInstruction, auditDecisionInstruction, ESCALATION_CONTRACT, auditOnlyInstruction, auditContinuationInstruction, auditContinuationForOrchestrator, writesCode, scopeClause, codeStyleClause, methodClause, attachmentClause, orchestratorAttachmentClause, PROJECT_METHOD, specContext, goalDetail, goalBlock, goalContext, projectContext, scaffoldContext, prdBriefBlock, prdAuditBlock, prdContext, breakdownContext, prdPhaseLines, breakdownPhaseLines, resumeClause, RESUMED_WORKTREE_NOTE, REVERSE_PHASE_GUIDE, SCAFFOLD_PHASE_GUIDE;
17725
+ var PIPELINES, WORK_PHASES, DECIDER, ASK_ROUTE, ASK_ESCALATION_TOOL, ASK_DECISIONS_TOOL, AMBIGUOUS, AUTONOMY_CLAUSE, AUTONOMY_CLAUSE_FULL, autonomyClause, PLAN_SHAPE, phaseInstruction, SELF_REVIEW_CLAUSE, mockupGuide, MOCKUP_FOLLOW_UP, MOCKUP_SINGLE, skillInstruction, auditDecisionInstruction, ESCALATION_CONTRACT, auditOnlyInstruction, auditContinuationInstruction, auditContinuationForOrchestrator, writesCode, scopeClause, codeStyleClause, methodClause, attachmentClause, orchestratorAttachmentClause, PROJECT_METHOD, specContext, goalDetail, goalBlock, goalContext, projectContext, scaffoldContext, prdBriefBlock, prdAuditBlock, prdContext, breakdownContext, prdPhaseLines, breakdownPhaseLines, resumeClause, RESUMED_WORKTREE_NOTE, REVERSE_PHASE_GUIDE, SCAFFOLD_PHASE_GUIDE;
17668
17726
  var init_prompt = __esm({
17669
17727
  "../runner/src/prompt.ts"() {
17670
17728
  "use strict";
@@ -17688,12 +17746,23 @@ var init_prompt = __esm({
17688
17746
  const base3 = `Kerjakan fase berurutan: ${phases2.join(" \u2192 ")}.
17689
17747
  Setiap kali sebuah fase selesai (atau kamu putuskan dilewati), append satu baris ke berkas di $HANOMAN_PHASE_FILE \u2014 persis: \`echo "<Nama Fase> done" >> "$HANOMAN_PHASE_FILE"\`, atau \`skipped\` sebagai ganti \`done\`. Nama fase ditulis apa adanya seperti di atas.`;
17690
17748
  if (!phases2.includes("Plan") || !phases2.includes("Execute")) return base3;
17691
- return base3 + `
17749
+ return base3 + (phases2.includes(MOCKUP_PHASE) ? `
17750
+ ${MOCKUP_SINGLE}
17751
+ ${MOCKUP_FOLLOW_UP.Plan} ${MOCKUP_FOLLOW_UP.Execute}` : "") + `
17692
17752
  Execute BELUM selesai selama plan (\`${method.planDir}/**\`) masih punya task \`- [ ]\`: kerjakan SEMUA PR/task sampai tiap kotak jadi \`- [x]\` sebelum menulis \`Execute done\`. hanoman menahan backlog di \`executing\`, bukan \`done\`, selama masih ada \`- [ ]\`.
17693
17753
  ${PLAN_SHAPE}` + (phases2.includes(REVIEWER_CELL) ? `
17694
17754
  ${SELF_REVIEW_CLAUSE}` : "");
17695
17755
  };
17696
- SELF_REVIEW_CLAUSE = "Fase Review (sesudah `Execute done`): periksa ulang hasil Execute terhadap dokumen spec & plan backlog ini \u2014 baca seluruh perubahan sesi ini (diff dari commit awal worktree) dan jalankan test sesuai scope verifikasi. Tulis tabel `AC \u2192 putusan \u2192 bukti` (setiap acceptance criteria \u2192 perintah & hasil yang kamu baca, atau path:baris) lalu satu baris `Verdict: lulus` atau `Verdict: rework`. `rework` \u2192 perbaiki temuannya, lalu review ulang; maks 2 putaran rework, sisanya jadi keputusan terbuka. Tulis `Review done` HANYA sesudah `Verdict: lulus`; Review TIDAK PERNAH `skipped`.";
17756
+ SELF_REVIEW_CLAUSE = "Fase Review (sesudah `Execute done`): periksa ulang hasil Execute terhadap dokumen spec & plan backlog ini \u2014 baca seluruh perubahan sesi ini (diff dari commit awal worktree) dan jalankan test sesuai scope verifikasi. Tulis tabel `AC \u2192 putusan \u2192 bukti` (setiap acceptance criteria \u2192 perintah & hasil yang kamu baca, atau path:baris) lalu satu baris `Verdict: lulus` atau `Verdict: rework`. `rework` \u2192 perbaiki temuannya, lalu review ulang; maks 2 putaran rework, sisanya jadi keputusan terbuka. Tulis `Review done` HANYA sesudah `Verdict: lulus`; Review TIDAK PERNAH `skipped`. Bila `docs/mockups/<spec-id>/` ada, tabel itu juga memuat kesesuaian hasil dengan mockup yang disetujui.";
17757
+ mockupGuide = (ask, close) => `putuskan dulu dari dokumen spec (qa jalur cepat: dokumen audit) apakah backlog ini mengubah atau membuat UI \u2014 layar, komponen, atau state yang terlihat pengguna. Tidak ada (perbaikan backend, CLI, refactor, docs) \u2192 fase ini dilewati, sebutkan alasannya dalam satu baris; ragu \u2192 anggap ada UI. Ada \u2192 baca design system project bila ada (mis. \`internal/docs/design-system/**\`, \`DESIGN.md\`) dan kode layar yang akan berubah, muat skill \`codex-image\`, lalu hasilkan SATU gambar per layar/state yang berubah, maks 6: desktop \`1536x1024\`, tambah mobile \`1024x1536\` bila layout responsif tersentuh, ke \`docs/mockups/<spec-id>/<NN>-<slug>.png\` (spec-id huruf kecil, NN dua digit urut). Tambahkan bagian \`## Mockup\` di dokumen spec: tiap berkas, layar/state yang ditunjukkannya, dan apa yang berubah. Lalu minta persetujuan ${ask}: sebutkan path absolut setiap gambar dan baris \`Lihat di hanoman: Backlog <SPEC-ID> \u2192 Mockup\`, dengan opsi Setujui \xB7 Revisi (beserta catatan) \xB7 Lewati mockup. Revisi \u2192 buat ulang gambar yang diminta dengan spec gambar LENGKAP ditambah koreksinya (bukan koreksinya saja), timpa berkas yang sama, lalu minta persetujuan lagi. Lewati \u2192 hapus berkas mockup yang belum di-commit. Disetujui \u2192 commit \`docs/mockups/<spec-id>/\` beserta dokumen spec. Bila gambar tak bisa dihasilkan (Codex tak terpasang/belum login, image_gen gagal dua kali), JANGAN membuat pengganti (SVG/CSS/HTML yang dirasterisasi, emoji, atau placeholder bernama sama) \u2014 tanyakan dengan jalur yang sama: tunggu operator menjalankan \`codex login\` lalu lanjut, atau lewati mockup. ${close}`;
17758
+ MOCKUP_FOLLOW_UP = {
17759
+ Plan: "Bila `docs/mockups/<spec-id>/` ada, setiap task UI di plan merujuk berkas mockup yang diwujudkannya.",
17760
+ Execute: "Bila `docs/mockups/<spec-id>/` ada, bangun UI dengan kode sesuai mockup itu \u2014 PNG mockup bukan aset produk."
17761
+ };
17762
+ MOCKUP_SINGLE = "Fase Mockup: " + mockupGuide(
17763
+ "lewat jalur tanya di klausa otonomi prompt ini",
17764
+ "Tulis `Mockup done` sesudah disetujui & di-commit, atau `Mockup skipped` bila dilewati."
17765
+ );
17697
17766
  skillInstruction = (flow2, phases2, method) => {
17698
17767
  const lines2 = phases2.map((p3) => {
17699
17768
  const skills2 = phaseSkillsFor(flow2, p3, method);
@@ -17702,7 +17771,7 @@ ${SELF_REVIEW_CLAUSE}` : "");
17702
17771
  return lines2.length ? `Skills ${method.label} WAJIB: sebelum mengerjakan fase di bawah, muat & ikuti skill-nya dengan mekanisme yang tersedia di agenmu \u2014 bila skill relevan tersedia, pakai.
17703
17772
  ${lines2.join("\n")}` : "";
17704
17773
  };
17705
- auditDecisionInstruction = (flow2) => flow2 !== "qa" ? "" : 'Keputusan pasca-Audit (qa): bila temuan berconfidence tinggi dan perbaikannya bisa dikerjakan langsung (diff kecil, akar masalah jelas), LEWATI Spec dan Plan \u2014 tandai keduanya `skipped` (`echo "Spec skipped" >> "$HANOMAN_PHASE_FILE"` lalu `echo "Plan skipped" >> "$HANOMAN_PHASE_FILE"`) dan langsung ke Execute; dokumen audit menjadi doc-of-record perbaikan itu. Bila temuan luas, berisiko, atau ambigu, jalankan Spec \u2192 Plan \u2192 Execute penuh. Keputusan ini milikmu berdasarkan hasil Audit, bukan default \u2014 jangan bayar perencanaan yang tak perlu untuk perbaikan sepele.';
17774
+ auditDecisionInstruction = (flow2) => flow2 !== "qa" ? "" : 'Keputusan pasca-Audit (qa): bila temuan berconfidence tinggi dan perbaikannya bisa dikerjakan langsung (diff kecil, akar masalah jelas), LEWATI Spec, Mockup, dan Plan \u2014 tandai ketiganya `skipped` (`echo "Spec skipped" >> "$HANOMAN_PHASE_FILE"`, `echo "Mockup skipped" >> "$HANOMAN_PHASE_FILE"`, lalu `echo "Plan skipped" >> "$HANOMAN_PHASE_FILE"`) dan langsung ke Execute; dokumen audit menjadi doc-of-record perbaikan itu. Bila temuan luas, berisiko, atau ambigu, jalankan Spec \u2192 Mockup \u2192 Plan \u2192 Execute penuh. Keputusan ini milikmu berdasarkan hasil Audit, bukan default \u2014 jangan bayar perencanaan yang tak perlu untuk perbaikan sepele.';
17706
17775
  ESCALATION_CONTRACT = [
17707
17776
  "REKOMENDASI ESKALASI (wajib, terbaca mesin). Di bagian akhir dokumen audit, tulis bagian",
17708
17777
  "`## Rekomendasi eskalasi` berisi penjelasan singkat untuk manusia, LALU tepat SATU blok ```json",
@@ -17733,7 +17802,7 @@ ${lines2.join("\n")}` : "";
17733
17802
  const doc = `internal/docs/research/audit-${fromAudit.toLowerCase()}-*.md`;
17734
17803
  if (flow2 === "feature")
17735
17804
  return `Backlog brief ini LANJUTAN dari audit ${fromAudit}. Worktree ini lahir dari branch audit itu, jadi dokumen audit sudah ada di ${doc}. BACA dokumen itu lebih dulu dan pakai sebagai bahan fase Brainstorm & Objective \u2014 temuannya sudah terbukti, jangan menginvestigasi ulang dari nol. Semua fase tetap dijalankan: dokumen audit memuat TEMUAN, bukan bentuk solusi, jadi perancangan fitur tetap pekerjaanmu.`;
17736
- return `Backlog qa ini LANJUTAN dari audit ${fromAudit}. Worktree ini lahir dari branch audit itu, jadi dokumen audit sudah ada di ${doc}. JANGAN mengulang investigasi fase Audit dari nol \u2014 baca dokumen audit itu sebagai temuan, tandai fase Audit dilewati (\`echo "Audit skipped" >> "$HANOMAN_PHASE_FILE"\`), lalu ambil keputusan pasca-Audit: perbaikan jelas & kecil \u2192 langsung Execute (tandai \`Spec skipped\` dan \`Plan skipped\` bila sesuai); selain itu Spec \u2192 Plan \u2192 Execute penuh.`;
17805
+ return `Backlog qa ini LANJUTAN dari audit ${fromAudit}. Worktree ini lahir dari branch audit itu, jadi dokumen audit sudah ada di ${doc}. JANGAN mengulang investigasi fase Audit dari nol \u2014 baca dokumen audit itu sebagai temuan, tandai fase Audit dilewati (\`echo "Audit skipped" >> "$HANOMAN_PHASE_FILE"\`), lalu ambil keputusan pasca-Audit: perbaikan jelas & kecil \u2192 langsung Execute (tandai \`Spec skipped\`, \`Mockup skipped\`, dan \`Plan skipped\` bila sesuai); selain itu Spec \u2192 Mockup \u2192 Plan \u2192 Execute penuh.`;
17737
17806
  };
17738
17807
  auditContinuationForOrchestrator = (flow2, spec) => {
17739
17808
  if (flow2 !== "qa" && flow2 !== "feature") return "";
@@ -17742,7 +17811,7 @@ ${lines2.join("\n")}` : "";
17742
17811
  const doc = `internal/docs/research/audit-${fromAudit.toLowerCase()}-*.md`;
17743
17812
  if (flow2 === "feature")
17744
17813
  return `Backlog brief ini LANJUTAN dari audit ${fromAudit}. Worktree ini lahir dari branch audit itu, jadi dokumen audit sudah ada di ${doc}. Saat memanggil agen fase Brainstorm, cantumkan path itu di baris \`Artefak fase sebelumnya:\` blok serah-terima \u2014 JANGAN membacanya sendiri untuk merancang fitur ini. Saat memanggil agen fase Objective, baris itu biasanya sudah membawa path artefak Brainstorm (dokumen spec yang ditulis fase itu); tambahkan path dokumen audit di samping path itu, bukan menggantikannya. Temuannya sudah terbukti, tapi bentuk solusinya belum; itu tetap dikerjakan agen fasenya masing-masing, bukan olehmu.`;
17745
- return `Backlog qa ini LANJUTAN dari audit ${fromAudit}. Temuannya sudah terbukti di ${doc} \u2014 JANGAN mendelegasikan ulang fase Audit. Sebelum fase lain: jalankan persis \`echo "Audit skipped" >> "$HANOMAN_PHASE_FILE"\`, verifikasi dengan \`tail -1 "$HANOMAN_PHASE_FILE"\` bahwa barisnya benar tertulis (gerbang yang sama seperti langkah 2), lalu cantumkan path dokumen audit itu di baris \`Artefak fase sebelumnya:\` saat memanggil agen fase Spec. Keputusan routing di sini menggantikan langkah 5 di atas \u2014 JANGAN menunggu frasa \`Rekomendasi fase: jalur-cepat\` dari agen Audit karena Audit tidak dijalankan pada kelanjutan ini. Kamu diizinkan mengambil SATU keputusan ROUTING dari isi dokumen audit itu SAJA, dibaca langsung dari kriterianya: temuan berconfidence tinggi dan perbaikan langsung berdiff kecil \u2192 tandai \`Spec skipped\` lalu \`Plan skipped\` (gerbang yang sama) lalu panggil agen fase Execute; selebihnya panggil Spec \u2192 Plan \u2192 Execute penuh lewat agen fasenya masing-masing. Kamu TIDAK menginvestigasi ulang maupun merancang perbaikannya sendiri \u2014 itu tetap pekerjaan agen fase.`;
17814
+ return `Backlog qa ini LANJUTAN dari audit ${fromAudit}. Temuannya sudah terbukti di ${doc} \u2014 JANGAN mendelegasikan ulang fase Audit. Sebelum fase lain: jalankan persis \`echo "Audit skipped" >> "$HANOMAN_PHASE_FILE"\`, verifikasi dengan \`tail -1 "$HANOMAN_PHASE_FILE"\` bahwa barisnya benar tertulis (gerbang yang sama seperti langkah 2), lalu cantumkan path dokumen audit itu di baris \`Artefak fase sebelumnya:\` saat memanggil agen fase Spec. Keputusan routing di sini menggantikan langkah 5 di atas \u2014 JANGAN menunggu frasa \`Rekomendasi fase: jalur-cepat\` dari agen Audit karena Audit tidak dijalankan pada kelanjutan ini. Kamu diizinkan mengambil SATU keputusan ROUTING dari isi dokumen audit itu SAJA, dibaca langsung dari kriterianya: temuan berconfidence tinggi dan perbaikan langsung berdiff kecil \u2192 tandai \`Spec skipped\`, \`Mockup skipped\`, lalu \`Plan skipped\` (gerbang yang sama) lalu panggil agen fase Execute; selebihnya panggil Spec \u2192 Mockup \u2192 Plan \u2192 Execute penuh lewat agen fasenya masing-masing. Kamu TIDAK menginvestigasi ulang maupun merancang perbaikannya sendiri \u2014 itu tetap pekerjaan agen fase.`;
17746
17815
  };
17747
17816
  writesCode = (flow2) => PIPELINES[flow2].some((p3) => WORK_PHASES.includes(p3));
17748
17817
  scopeClause = (flow2, scope) => scope && writesCode(flow2) ? verifyScopeClause(scope) : "";
@@ -18062,7 +18131,7 @@ function defaultGoalCondition({ flow: flow2, specId, branchTo, spec }) {
18062
18131
  const phases2 = PIPELINES[flow2];
18063
18132
  const planGate = phases2.includes("Plan") && phases2.includes("Execute");
18064
18133
  const clauses = [
18065
- `1. output \`cat "$HANOMAN_PHASE_FILE"\` yang memuat satu baris untuk SETIAP fase ${phases2.join(" \u2192 ")}, masing-masing berakhiran \`done\` atau \`skipped\`` + phases2.filter((p3) => UNSKIPPABLE_PHASES.has(p3)).map((p3) => ` (\`${p3}\` wajib \`done\`)`).join("") + ";"
18134
+ `1. output \`cat "$HANOMAN_PHASE_FILE"\` yang memuat satu baris untuk SETIAP fase ${phases2.join(" \u2192 ")}, masing-masing berakhiran \`done\` atau \`skipped\`` + phases2.filter((p3) => UNSKIPPABLE_PHASES.has(p3)).map((p3) => ` (\`${p3}\` wajib \`done\`)`).join("") + phases2.filter((p3) => OPTIONAL_PHASES.has(p3)).map((p3) => ` (\`${p3}\` boleh tanpa baris bila fase sesudahnya sudah tercatat)`).join("") + ";"
18066
18135
  ];
18067
18136
  if (planGate) {
18068
18137
  const dirs = PLAN_DIRS.map((d) => `${d}/`).join(" ");
@@ -18133,12 +18202,13 @@ function codexGoalScript(o) {
18133
18202
  `ST=${shq(o.stateFile)}`,
18134
18203
  "missing=''"
18135
18204
  ];
18136
- for (const p3 of phases2) {
18205
+ phases2.forEach((p3, i) => {
18206
+ const name2 = OPTIONAL_PHASES.has(p3) ? `(${phases2.slice(i).join("|")})` : p3;
18137
18207
  lines2.push(
18138
18208
  // ADR-0230 · fase `UNSKIPPABLE_PHASES` (Review) hanya lolos dengan `done`.
18139
- `grep -qE ${shq(`^${p3} ${UNSKIPPABLE_PHASES.has(p3) ? "done" : "(done|skipped)"}[[:space:]]*$`)} "$PF" 2>/dev/null || missing="$missing\\n- fase ${p3} belum tercatat di \\$HANOMAN_PHASE_FILE"`
18209
+ `grep -qE ${shq(`^${name2} ${UNSKIPPABLE_PHASES.has(p3) ? "done" : "(done|skipped)"}[[:space:]]*$`)} "$PF" 2>/dev/null || missing="$missing\\n- fase ${p3} belum tercatat di \\$HANOMAN_PHASE_FILE"`
18140
18210
  );
18141
- }
18211
+ });
18142
18212
  if (planGate) {
18143
18213
  for (const dir2 of PLAN_DIRS) {
18144
18214
  lines2.push(
@@ -18891,9 +18961,16 @@ function backlogGuide(flow2, phase, ctx) {
18891
18961
  // default skill) lolos hampa. Nama dikunci seperti Brainstorm, dan path persisnya diteruskan
18892
18962
  // orchestrator ke Execute & reviewer — bukan pola glob yang bisa mencocoki plan backlog lain.
18893
18963
  case "Plan":
18894
- return `- Plan: tulis plan implementasi berkotak \`- [ ]\` untuk backlog ini dari dokumen spec-nya (path di serah-terima) ke SATU berkas \`${m.planDir}/<YYYY-MM-DD>-<spec-id>-<slug>.md\` (spec-id backlog ini huruf kecil, mis. \`2026-09-25-spec-123-ekspor-csv.md\` \u2014 nama tanpa spec-id membuat hanoman tak menemukan plan-nya). Laporkan path PERSIS berkas itu di \`Artefak:\`; orchestrator meneruskannya ke Execute. ${PLAN_SHAPE}`;
18964
+ return `- Plan: tulis plan implementasi berkotak \`- [ ]\` untuk backlog ini dari dokumen spec-nya (path di serah-terima) ke SATU berkas \`${m.planDir}/<YYYY-MM-DD>-<spec-id>-<slug>.md\` (spec-id backlog ini huruf kecil, mis. \`2026-09-25-spec-123-ekspor-csv.md\` \u2014 nama tanpa spec-id membuat hanoman tak menemukan plan-nya). Laporkan path PERSIS berkas itu di \`Artefak:\`; orchestrator meneruskannya ke Execute. ${PLAN_SHAPE} ${MOCKUP_FOLLOW_UP.Plan}`;
18895
18965
  case "Execute":
18896
- return `- Execute: kerjakan plan backlog ini \u2014 path persisnya di baris \`Artefak fase sebelumnya:\` serah-terima (bila tak ada: berkas di \`${m.planDir}/\` yang namanya memuat spec-id backlog ini). Execute BELUM selesai selama plan masih punya task \`- [ ]\`: kerjakan SEMUA task sampai tiap kotak jadi \`- [x]\` sebelum melapor \`Status: selesai\`. hanoman menahan backlog di \`executing\`, bukan \`done\`, selama masih ada \`- [ ]\`. Laporan \`Status: selesai\` wajib memuat tabel \`AC \u2192 bukti\`: setiap acceptance criteria dokumen spec (qa jalur-cepat: dokumen audit) \u2192 perintah yang kamu jalankan beserta hasil yang kamu baca, atau path:baris yang membuktikannya. AC tanpa bukti = belum selesai; laporanmu diperiksa ulang sebelum fase ditutup.`;
18966
+ return `- Execute: kerjakan plan backlog ini \u2014 path persisnya di baris \`Artefak fase sebelumnya:\` serah-terima (bila tak ada: berkas di \`${m.planDir}/\` yang namanya memuat spec-id backlog ini). Execute BELUM selesai selama plan masih punya task \`- [ ]\`: kerjakan SEMUA task sampai tiap kotak jadi \`- [x]\` sebelum melapor \`Status: selesai\`. hanoman menahan backlog di \`executing\`, bukan \`done\`, selama masih ada \`- [ ]\`. Laporan \`Status: selesai\` wajib memuat tabel \`AC \u2192 bukti\`: setiap acceptance criteria dokumen spec (qa jalur-cepat: dokumen audit) \u2192 perintah yang kamu jalankan beserta hasil yang kamu baca, atau path:baris yang membuktikannya. AC tanpa bukti = belum selesai; laporanmu diperiksa ulang sebelum fase ditutup. ${MOCKUP_FOLLOW_UP.Execute}`;
18967
+ // ADR-0233 · panduan yang SAMA dengan sesi tunggal (`mockupGuide`); bedanya cuma jalur tanya & penutupan:
18968
+ // agen fase tak bisa bertanya langsung dan tak menulis marker sendiri.
18969
+ case "Mockup":
18970
+ return "- Mockup: " + mockupGuide(
18971
+ "sebagai butir `Keputusan terbuka:` laporanmu (`Status: menunggu-keputusan`) \u2014 orchestrator yang menanyakannya",
18972
+ "Disetujui & di-commit \u2192 `Status: selesai`. Dilewati (tanpa UI, atau dipilih Lewati) \u2192 `Status: selesai` dengan `Rekomendasi fase: lewati` beserta alasannya; orchestrator yang menulis marker-nya."
18973
+ );
18897
18974
  case "Audit":
18898
18975
  return flow2 === "audit" ? "- Audit: ini audit-only \u2014 investigasi SAJA, JANGAN menulis perbaikan kode apa pun. Telusuri akar masalah / log / jawaban dan nilai apakah issue terdefinisi dengan baik. Laporkan temuan beserta buktinya; dokumennya ditulis fase Laporan." : "- Audit: telusuri akar masalah dengan bukti (reproduksi, log, kode) dan tulis dokumen audit ke `internal/docs/research/audit-<spec-id>-<slug>.md`. Lalu putuskan jalurnya dari hasil Audit, bukan default: bila temuan berconfidence tinggi dan perbaikannya bisa dikerjakan langsung (diff kecil, akar masalah jelas), tulis `Rekomendasi fase: jalur-cepat` beserta alasannya \u2014 orchestrator akan melewati Spec & Plan dan dokumen audit menjadi doc-of-record perbaikan itu. Bila temuan luas, berisiko, atau ambigu, tulis `Rekomendasi fase: penuh`. " + FRONT_LOADED_DECISIONS("Spec, Plan, dan Execute") + " Catat jawabannya di dokumen audit.";
18899
18976
  case "Laporan":
@@ -18971,7 +19048,7 @@ function reviewerInstructions(plan, ctx) {
18971
19048
  "Prosedur:",
18972
19049
  [
18973
19050
  `1. Baca dokumen spec & plan dari baris \`Artefak:\` serah-terima (qa jalur-cepat: dokumen audit adalah doc-of-record; bila path tak ada, cari berkas ber-spec-id di \`${m.specDir}/\` & \`${m.planDir}/\`). Plan yang menyimpang dari spec adalah temuan.`,
18974
- "2. Ubah acceptance criteria jadi daftar yang diperiksa SATU PER SATU. Kalimat tak terukur ditandai tak terukur, bukan ditafsirkan.",
19051
+ "2. Ubah acceptance criteria jadi daftar yang diperiksa SATU PER SATU. Kalimat tak terukur ditandai tak terukur, bukan ditafsirkan. Bila `docs/mockups/<spec-id>/` ada, kesesuaian UI hasil dengan mockup yang disetujui ikut jadi kriteria.",
18975
19052
  '3. Baca `git diff "$HANOMAN_BASE_SHA"...HEAD` dan `git status --porcelain` (perubahan belum di-commit adalah temuan). Laporan Execute dan kotak `- [x]` hanyalah klaim.',
18976
19053
  "4. Jalankan test yang relevan dengan berkas yang berubah, sesuai scope verifikasi di bawah, dan baca outputnya sendiri.",
18977
19054
  `5. Per kriteria putuskan salah satu: ${SPEC_AUDIT_VERDICT_LIST}. Kriteria tanpa jangkar di diff bukan otomatis gagal \u2014 cari bukti keadaan akhir; tak bisa dibuktikan \u2192 belum terverifikasi.`
@@ -20971,6 +21048,7 @@ __export(src_exports, {
20971
21048
  GOAL_MAX: () => GOAL_MAX,
20972
21049
  GOAL_MAX_BLOCKS: () => GOAL_MAX_BLOCKS,
20973
21050
  GOAL_TUI_PASTE_LIMIT: () => GOAL_TUI_PASTE_LIMIT,
21051
+ MOCKUP_FOLLOW_UP: () => MOCKUP_FOLLOW_UP,
20974
21052
  PHASE_CONTEXT_HEADER: () => PHASE_CONTEXT_HEADER,
20975
21053
  PIPELINES: () => PIPELINES,
20976
21054
  PLAN_SHAPE: () => PLAN_SHAPE,
@@ -21037,6 +21115,7 @@ __export(src_exports, {
21037
21115
  materializeAntigravityAgents: () => materializeAntigravityAgents,
21038
21116
  materializeCodexAgents: () => materializeCodexAgents,
21039
21117
  methodClause: () => methodClause,
21118
+ mockupGuide: () => mockupGuide,
21040
21119
  orchestratorClause: () => orchestratorClause,
21041
21120
  parseConfigEnv: () => parseConfigEnv,
21042
21121
  phaseAgentInstructions: () => phaseAgentInstructions,
@@ -21509,10 +21588,13 @@ var init_session_phases = __esm({
21509
21588
  decisionFilePath = (repoDir, sessionId2) => `${repoDir}/.worktrees/.decisions/${sessionId2}`;
21510
21589
  phaseFileMemo = /* @__PURE__ */ new Map();
21511
21590
  derivePhases = (seen2, flow2) => {
21591
+ const names = PIPELINES[flow2];
21512
21592
  let activeTaken = false;
21513
- return PIPELINES[flow2].map((name2) => {
21593
+ return names.map((name2, i) => {
21514
21594
  const state = seen2.get(name2);
21515
21595
  if (state) return { name: name2, state };
21596
+ if (OPTIONAL_PHASES.has(name2) && names.slice(i + 1).some((later) => seen2.has(later)))
21597
+ return { name: name2, state: "skipped" };
21516
21598
  if (activeTaken) return { name: name2, state: "pending" };
21517
21599
  activeTaken = true;
21518
21600
  return { name: name2, state: "active" };
@@ -21793,18 +21875,146 @@ var init_antigravity_runtime = __esm({
21793
21875
  }
21794
21876
  });
21795
21877
 
21878
+ // src/runner/deps.ts
21879
+ import { existsSync as existsSync9 } from "node:fs";
21880
+ import { dirname as dirname10, join as join15 } from "node:path";
21881
+ function repoRootFrom(startDir) {
21882
+ let dir2 = startDir;
21883
+ for (let i = 0; i < 8; i++) {
21884
+ if (existsSync9(join15(dir2, "pnpm-workspace.yaml"))) return dir2;
21885
+ const parent = dirname10(dir2);
21886
+ if (parent === dir2) break;
21887
+ dir2 = parent;
21888
+ }
21889
+ return startDir;
21890
+ }
21891
+ var repoRoot;
21892
+ var init_deps = __esm({
21893
+ "src/runner/deps.ts"() {
21894
+ "use strict";
21895
+ repoRoot = (startDir = process.cwd()) => repoRootFrom(startDir);
21896
+ }
21897
+ });
21898
+
21899
+ // src/services/bundled-skills.ts
21900
+ import { createHash as createHash7 } from "node:crypto";
21901
+ import { chmodSync as chmodSync7, cpSync as cpSync3, existsSync as existsSync10, mkdirSync as mkdirSync9, readFileSync as readFileSync11, readdirSync as readdirSync8, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync3, writeFileSync as writeFileSync11 } from "node:fs";
21902
+ import { join as join16, relative as relative5, sep as sep6 } from "node:path";
21903
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
21904
+ function bundledSkillsDir() {
21905
+ const packed = packagedDir();
21906
+ return existsSync10(packed) ? packed : join16(repoRoot(moduleDir()), "server", "skills");
21907
+ }
21908
+ function filesUnder(dir2) {
21909
+ const out4 = [];
21910
+ const walk = (d) => {
21911
+ for (const n2 of readdirSync8(d).sort()) {
21912
+ if (n2 === BUNDLED_MARKER || n2 === ".DS_Store") continue;
21913
+ const full = join16(d, n2);
21914
+ if (statSync3(full).isDirectory()) walk(full);
21915
+ else out4.push(full);
21916
+ }
21917
+ };
21918
+ walk(dir2);
21919
+ return out4;
21920
+ }
21921
+ function contentSha(dir2) {
21922
+ const h2 = createHash7("sha256");
21923
+ for (const f of filesUnder(dir2)) {
21924
+ h2.update(relative5(dir2, f).split(sep6).join("/"));
21925
+ h2.update("\0");
21926
+ h2.update(readFileSync11(f));
21927
+ h2.update("\0");
21928
+ }
21929
+ return h2.digest("hex");
21930
+ }
21931
+ function markerSha(dir2) {
21932
+ try {
21933
+ const parsed = JSON.parse(readFileSync11(join16(dir2, BUNDLED_MARKER), "utf8"));
21934
+ const sha2 = parsed?.sha;
21935
+ return typeof sha2 === "string" ? sha2 : null;
21936
+ } catch {
21937
+ return null;
21938
+ }
21939
+ }
21940
+ function install(src, dest, sha2) {
21941
+ const tmp = `${dest}.tmp-${process.pid}`;
21942
+ rmSync6(tmp, { recursive: true, force: true });
21943
+ cpSync3(src, tmp, { recursive: true });
21944
+ for (const f of filesUnder(tmp))
21945
+ if (relative5(tmp, f).split(sep6)[0] === "scripts") chmodSync7(f, statSync3(f).mode | 493);
21946
+ writeFileSync11(join16(tmp, BUNDLED_MARKER), `${JSON.stringify({ sha: sha2, seededAt: (/* @__PURE__ */ new Date()).toISOString() })}
21947
+ `);
21948
+ rmSync6(dest, { recursive: true, force: true });
21949
+ renameSync4(tmp, dest);
21950
+ }
21951
+ function seedBundledSkills(hanomanHome, o = {}) {
21952
+ const out4 = { seeded: [], updated: [], kept: [] };
21953
+ const source = o.source ?? bundledSkillsDir();
21954
+ const log = o.log ?? ((m) => console.warn(m));
21955
+ const names = skillDirsIn(source);
21956
+ if (!names.length) return out4;
21957
+ const root = hanomanSkillsDir(hanomanHome);
21958
+ for (const name2 of names) {
21959
+ try {
21960
+ const src = join16(source, name2);
21961
+ const dest = join16(root, name2);
21962
+ const bundleSha = contentSha(src);
21963
+ if (!existsSync10(dest)) {
21964
+ mkdirSync9(root, { recursive: true });
21965
+ install(src, dest, bundleSha);
21966
+ out4.seeded.push(name2);
21967
+ continue;
21968
+ }
21969
+ const recorded2 = markerSha(dest);
21970
+ if (recorded2 === bundleSha) continue;
21971
+ if (recorded2 === null || contentSha(dest) !== recorded2) {
21972
+ out4.kept.push(name2);
21973
+ log(`skill bundel ${name2} tak diperbarui: salinan di ${dest} milik operator (tanpa marker atau sudah disunting)`);
21974
+ continue;
21975
+ }
21976
+ install(src, dest, bundleSha);
21977
+ out4.updated.push(name2);
21978
+ } catch (e) {
21979
+ log(`skill bundel ${name2} gagal di-seed: ${e.message}`);
21980
+ }
21981
+ }
21982
+ return out4;
21983
+ }
21984
+ var BUNDLED_MARKER, packagedDir, moduleDir, skillDirsIn, isBundledCopy;
21985
+ var init_bundled_skills = __esm({
21986
+ "src/services/bundled-skills.ts"() {
21987
+ "use strict";
21988
+ init_src2();
21989
+ init_deps();
21990
+ BUNDLED_MARKER = ".hanoman-bundled.json";
21991
+ packagedDir = () => fileURLToPath3(new URL("../skills", import.meta.url));
21992
+ moduleDir = () => fileURLToPath3(new URL(".", import.meta.url));
21993
+ skillDirsIn = (source) => {
21994
+ let names;
21995
+ try {
21996
+ names = readdirSync8(source);
21997
+ } catch {
21998
+ return [];
21999
+ }
22000
+ return names.filter((n2) => !n2.startsWith(".") && existsSync10(join16(source, n2, "SKILL.md"))).sort();
22001
+ };
22002
+ isBundledCopy = (dir2) => existsSync10(join16(dir2, BUNDLED_MARKER));
22003
+ }
22004
+ });
22005
+
21796
22006
  // src/services/skill-inject.ts
21797
22007
  import { execFileSync as execFileSync4 } from "node:child_process";
21798
- import { createHash as createHash7 } from "node:crypto";
21799
- import { appendFileSync as appendFileSync2, existsSync as existsSync9, lstatSync as lstatSync7, mkdirSync as mkdirSync9, readFileSync as readFileSync11, readlinkSync as readlinkSync2, symlinkSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync11 } from "node:fs";
21800
- import { isAbsolute as isAbsolute8, join as join15 } from "node:path";
22008
+ import { createHash as createHash8 } from "node:crypto";
22009
+ import { appendFileSync as appendFileSync2, existsSync as existsSync11, lstatSync as lstatSync7, mkdirSync as mkdirSync10, readFileSync as readFileSync12, readlinkSync as readlinkSync2, symlinkSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync12 } from "node:fs";
22010
+ import { isAbsolute as isAbsolute8, join as join17 } from "node:path";
21801
22011
  function excludeFile(repoDir) {
21802
22012
  const common = execFileSync4("git", ["rev-parse", "--git-common-dir"], { cwd: repoDir, env: agentEnvironment(null, process.env), encoding: "utf8" }).trim();
21803
- return join15(isAbsolute8(common) ? common : join15(repoDir, common), "info", "exclude");
22013
+ return join17(isAbsolute8(common) ? common : join17(repoDir, common), "info", "exclude");
21804
22014
  }
21805
22015
  function ownedSkills(tempDir) {
21806
22016
  try {
21807
- const parsed = JSON.parse(readFileSync11(manifestPath(tempDir), "utf8"));
22017
+ const parsed = JSON.parse(readFileSync12(manifestPath(tempDir), "utf8"));
21808
22018
  return Array.isArray(parsed) ? parsed.filter((row) => row && typeof row.link === "string" && typeof row.target === "string" && typeof row.exclude === "string" && typeof row.pair === "string" && typeof row.ino === "number" && typeof row.dev === "number") : [];
21809
22019
  } catch {
21810
22020
  return [];
@@ -21817,8 +22027,8 @@ function cleanupInjectedSkills(tempDir) {
21817
22027
  if (!st2.isSymbolicLink() || st2.ino !== own.ino || st2.dev !== own.dev || readlinkSync2(own.link) !== own.target) continue;
21818
22028
  unlinkSync2(own.link);
21819
22029
  if (own.pair) {
21820
- const bytes = readFileSync11(own.exclude, "utf8");
21821
- if (bytes.includes(own.pair)) writeFileSync11(own.exclude, bytes.replace(own.pair, ""));
22030
+ const bytes = readFileSync12(own.exclude, "utf8");
22031
+ if (bytes.includes(own.pair)) writeFileSync12(own.exclude, bytes.replace(own.pair, ""));
21822
22032
  }
21823
22033
  } catch {
21824
22034
  }
@@ -21826,18 +22036,20 @@ function cleanupInjectedSkills(tempDir) {
21826
22036
  }
21827
22037
  function injectGlobalSkills(o) {
21828
22038
  const out4 = { injected: [], warnings: [] };
21829
- const opts2 = { hanomanHome: o.hanomanHome, env: process.env };
21830
- const global2 = scanGlobalSkills(opts2).filter((s2) => s2.layer === "hanoman");
22039
+ const opts2 = { hanomanHome: o.hanomanHome, env: process.env, ...o.osHome ? { osHome: o.osHome } : {} };
22040
+ const scanned = scanGlobalSkills(opts2);
22041
+ const global2 = scanned.filter((s2) => s2.layer === "hanoman");
21831
22042
  if (!global2.length) return out4;
21832
22043
  const taken = new Set(scanProjectSkills("session", o.cwd, opts2).map((s2) => s2.name));
21833
- const todo = global2.filter((s2) => !taken.has(s2.name));
22044
+ const userOwned = new Set(scanned.filter((s2) => s2.layer === "user" && s2.loadedBy.includes(o.agent)).map((s2) => s2.name));
22045
+ const todo = global2.filter((s2) => !taken.has(s2.name) && !(userOwned.has(s2.name) && isBundledCopy(s2.dir)));
21834
22046
  if (!todo.length) return out4;
21835
22047
  if (o.agent === "claude") {
21836
- const root = join15(o.tempDir, "skills-root");
22048
+ const root = join17(o.tempDir, "skills-root");
21837
22049
  try {
21838
- mkdirSync9(join15(root, ".claude", "skills"), { recursive: true });
22050
+ mkdirSync10(join17(root, ".claude", "skills"), { recursive: true });
21839
22051
  for (const s2 of todo) {
21840
- const link2 = join15(root, ".claude", "skills", s2.name);
22052
+ const link2 = join17(root, ".claude", "skills", s2.name);
21841
22053
  if (!lstatSync7(link2, { throwIfNoEntry: false })) symlinkSync(s2.dir, link2);
21842
22054
  out4.injected.push(s2.name);
21843
22055
  }
@@ -21858,25 +22070,25 @@ function injectGlobalSkills(o) {
21858
22070
  let ownedLink;
21859
22071
  let ownedPair = "";
21860
22072
  try {
21861
- const link2 = join15(o.cwd, ".agents", "skills", s2.name);
22073
+ const link2 = join17(o.cwd, ".agents", "skills", s2.name);
21862
22074
  if (o.agent === "antigravity") {
21863
- for (const dir2 of [join15(o.cwd, ".agents"), join15(o.cwd, ".agents", "skills")]) {
22075
+ for (const dir2 of [join17(o.cwd, ".agents"), join17(o.cwd, ".agents", "skills")]) {
21864
22076
  const st2 = lstatSync7(dir2, { throwIfNoEntry: false });
21865
22077
  if (st2 && (!st2.isDirectory() || st2.isSymbolicLink())) throw new Error("akar skill bukan direktori aman");
21866
22078
  }
21867
22079
  }
21868
22080
  if (lstatSync7(link2, { throwIfNoEntry: false })) continue;
21869
- mkdirSync9(join15(o.cwd, ".agents", "skills"), { recursive: true });
22081
+ mkdirSync10(join17(o.cwd, ".agents", "skills"), { recursive: true });
21870
22082
  symlinkSync(s2.dir, link2);
21871
22083
  ownedLink = link2;
21872
- mkdirSync9(join15(exclude, ".."), { recursive: true });
21873
- const cur = existsSync9(exclude) ? readFileSync11(exclude, "utf8") : "";
22084
+ mkdirSync10(join17(exclude, ".."), { recursive: true });
22085
+ const cur = existsSync11(exclude) ? readFileSync12(exclude, "utf8") : "";
21874
22086
  const line = excludeLine(s2.name);
21875
22087
  const lines2 = cur.split("\n");
21876
22088
  const hasOperatorLine = lines2.some((entry2, index) => entry2 === line && !lines2[index - 1]?.startsWith(`${INJECT_MARKER}:antigravity:`));
21877
22089
  if (o.agent === "antigravity" ? !hasOperatorLine : !lines2.includes(line)) {
21878
22090
  if (o.agent === "antigravity") {
21879
- const hash4 = createHash7("sha256").update(`${link2}\0${s2.dir}`).digest("hex");
22091
+ const hash4 = createHash8("sha256").update(`${link2}\0${s2.dir}`).digest("hex");
21880
22092
  ownedPair = `${INJECT_MARKER}:antigravity:${hash4}
21881
22093
  ${line}
21882
22094
  `;
@@ -21889,9 +22101,9 @@ ${line}
21889
22101
  }
21890
22102
  }
21891
22103
  if (o.agent === "antigravity") {
21892
- mkdirSync9(o.tempDir, { recursive: true, mode: 448 });
22104
+ mkdirSync10(o.tempDir, { recursive: true, mode: 448 });
21893
22105
  const st2 = lstatSync7(link2);
21894
- writeFileSync11(manifestPath(o.tempDir), JSON.stringify([...ownedSkills(o.tempDir), { link: link2, target: s2.dir, exclude, pair: ownedPair, ino: st2.ino, dev: st2.dev }]), { mode: 384 });
22106
+ writeFileSync12(manifestPath(o.tempDir), JSON.stringify([...ownedSkills(o.tempDir), { link: link2, target: s2.dir, exclude, pair: ownedPair, ino: st2.ino, dev: st2.dev }]), { mode: 384 });
21895
22107
  }
21896
22108
  out4.injected.push(s2.name);
21897
22109
  } catch (e) {
@@ -21901,7 +22113,7 @@ ${line}
21901
22113
  } catch {
21902
22114
  }
21903
22115
  try {
21904
- if (ownedPair) writeFileSync11(exclude, readFileSync11(exclude, "utf8").replace(ownedPair, ""));
22116
+ if (ownedPair) writeFileSync12(exclude, readFileSync12(exclude, "utf8").replace(ownedPair, ""));
21905
22117
  } catch {
21906
22118
  }
21907
22119
  }
@@ -21913,9 +22125,9 @@ ${line}
21913
22125
  function unexcludeInjected(repoDir, name2) {
21914
22126
  try {
21915
22127
  const f = excludeFile(repoDir);
21916
- if (!existsSync9(f)) return;
22128
+ if (!existsSync11(f)) return;
21917
22129
  const line = excludeLine(name2);
21918
- writeFileSync11(f, readFileSync11(f, "utf8").split("\n").filter((l) => l !== line).join("\n"));
22130
+ writeFileSync12(f, readFileSync12(f, "utf8").split("\n").filter((l) => l !== line).join("\n"));
21919
22131
  } catch {
21920
22132
  }
21921
22133
  }
@@ -21924,17 +22136,18 @@ var init_skill_inject = __esm({
21924
22136
  "src/services/skill-inject.ts"() {
21925
22137
  "use strict";
21926
22138
  init_src2();
22139
+ init_bundled_skills();
21927
22140
  INJECT_MARKER = "# hanoman:skill-inject";
21928
22141
  excludeLine = (name2) => `/.agents/skills/${name2}`;
21929
- manifestPath = (tempDir) => join15(tempDir, "antigravity-skills.json");
22142
+ manifestPath = (tempDir) => join17(tempDir, "antigravity-skills.json");
21930
22143
  }
21931
22144
  });
21932
22145
 
21933
22146
  // src/services/antigravity-trust.ts
21934
22147
  import { randomUUID as randomUUID2 } from "node:crypto";
21935
- import { closeSync as closeSync2, lstatSync as lstatSync8, mkdirSync as mkdirSync10, openSync as openSync2, readFileSync as readFileSync12, realpathSync as realpathSync6, renameSync as renameSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync12 } from "node:fs";
22148
+ import { closeSync as closeSync2, lstatSync as lstatSync8, mkdirSync as mkdirSync11, openSync as openSync2, readFileSync as readFileSync13, realpathSync as realpathSync6, renameSync as renameSync5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync13 } from "node:fs";
21936
22149
  import { homedir as homedir4 } from "node:os";
21937
- import { join as join16 } from "node:path";
22150
+ import { join as join18 } from "node:path";
21938
22151
  function safeOwned(path, directory) {
21939
22152
  const st2 = lstatSync8(path);
21940
22153
  if (st2.isSymbolicLink() || (directory ? !st2.isDirectory() : !st2.isFile()) || process.getuid && st2.uid !== process.getuid()) throw new Error("unsafe path");
@@ -21944,18 +22157,18 @@ function ensureAntigravityTrust(cwd, home3 = homedir4()) {
21944
22157
  let fd;
21945
22158
  try {
21946
22159
  const canonical = realpathSync6(cwd);
21947
- mkdirSync10(home3, { recursive: true });
22160
+ mkdirSync11(home3, { recursive: true });
21948
22161
  const root = realpathSync6(home3);
21949
- for (const dir2 of [join16(root, ".gemini"), join16(root, ".gemini", "antigravity-cli")]) {
21950
- if (!lstatSync8(dir2, { throwIfNoEntry: false })) mkdirSync10(dir2, { mode: 448 });
22162
+ for (const dir2 of [join18(root, ".gemini"), join18(root, ".gemini", "antigravity-cli")]) {
22163
+ if (!lstatSync8(dir2, { throwIfNoEntry: false })) mkdirSync11(dir2, { mode: 448 });
21951
22164
  safeOwned(dir2, true);
21952
22165
  }
21953
- const file = join16(root, ".gemini", "antigravity-cli", "settings.json");
22166
+ const file = join18(root, ".gemini", "antigravity-cli", "settings.json");
21954
22167
  const original = lstatSync8(file, { throwIfNoEntry: false });
21955
22168
  let settings = {};
21956
22169
  if (original) {
21957
22170
  safeOwned(file, false);
21958
- const parsed = JSON.parse(readFileSync12(file, "utf8"));
22171
+ const parsed = JSON.parse(readFileSync13(file, "utf8"));
21959
22172
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("invalid settings");
21960
22173
  settings = parsed;
21961
22174
  }
@@ -21967,14 +22180,14 @@ function ensureAntigravityTrust(cwd, home3 = homedir4()) {
21967
22180
  const candidate = `${file}.${randomUUID2()}.tmp`;
21968
22181
  fd = openSync2(candidate, "wx", 384);
21969
22182
  temp = candidate;
21970
- writeFileSync12(fd, `${JSON.stringify({ ...settings, trustedWorkspaces: [...workspaces, canonical] }, null, 2)}
22183
+ writeFileSync13(fd, `${JSON.stringify({ ...settings, trustedWorkspaces: [...workspaces, canonical] }, null, 2)}
21971
22184
  `);
21972
22185
  closeSync2(fd);
21973
22186
  fd = void 0;
21974
22187
  const latest = lstatSync8(file, { throwIfNoEntry: false });
21975
22188
  if (original ? !latest || latest.ino !== original.ino || latest.mtimeMs !== original.mtimeMs : latest)
21976
22189
  throw new Error("settings changed during preparation");
21977
- renameSync4(temp, file);
22190
+ renameSync5(temp, file);
21978
22191
  temp = void 0;
21979
22192
  } catch {
21980
22193
  throw new Error("antigravity-trust-preparation-failed");
@@ -22024,14 +22237,14 @@ var init_local_binding = __esm({
22024
22237
  });
22025
22238
 
22026
22239
  // src/services/skill-library.ts
22027
- import { join as join17 } from "node:path";
22028
- import { existsSync as existsSync10 } from "node:fs";
22240
+ import { join as join19 } from "node:path";
22241
+ import { existsSync as existsSync12 } from "node:fs";
22029
22242
  async function libraryAll() {
22030
22243
  const global2 = scanGlobalSkills(opts());
22031
22244
  const projects = await prisma.project.findMany({ orderBy: { name: "asc" }, select: { id: true, name: true } });
22032
22245
  const groups = await Promise.all(projects.map(async (p3) => {
22033
22246
  const repo = await resolveRepoDir(p3.id);
22034
- if (!repo || !existsSync10(repo)) return { projectId: p3.id, name: p3.name, skills: [], error: "repo tidak ditemukan" };
22247
+ if (!repo || !existsSync12(repo)) return { projectId: p3.id, name: p3.name, skills: [], error: "repo tidak ditemukan" };
22035
22248
  return { projectId: p3.id, name: p3.name, skills: scanProjectSkills(p3.id, repo, opts()) };
22036
22249
  }));
22037
22250
  return { global: global2, projects: groups };
@@ -22054,17 +22267,17 @@ async function targetParent(layer, source, projectId) {
22054
22267
  if (layer === "user") {
22055
22268
  const s2 = source ?? ".claude";
22056
22269
  if (!USER_SOURCES.includes(s2)) throw new SkillError(400, "source user harus .claude/.codex/.agents/.gemini/antigravity-cli");
22057
- if (s2 === ".agents") return join17(agentsSkillHome(process.env), "skills");
22058
- if (s2 === ".gemini/antigravity-cli") return join17(agentSkillHome("antigravity", process.env), "skills");
22059
- return join17(agentSkillHome(s2 === ".codex" ? "codex" : "claude", process.env), "skills");
22270
+ if (s2 === ".agents") return join19(agentsSkillHome(process.env), "skills");
22271
+ if (s2 === ".gemini/antigravity-cli") return join19(agentSkillHome("antigravity", process.env), "skills");
22272
+ return join19(agentSkillHome(s2 === ".codex" ? "codex" : "claude", process.env), "skills");
22060
22273
  }
22061
22274
  if (layer === "project") {
22062
22275
  if (!projectId) throw new SkillError(400, "projectId wajib untuk lapis project");
22063
22276
  const s2 = source ?? ".claude";
22064
22277
  if (!PROJECT_SOURCES.includes(s2)) throw new SkillError(400, "source project harus .claude/.agents/.codex");
22065
22278
  const repo = await resolveRepoDir(projectId);
22066
- if (!repo || !existsSync10(repo)) throw new SkillError(404, "repo project tidak ditemukan");
22067
- return join17(repo, s2, "skills");
22279
+ if (!repo || !existsSync12(repo)) throw new SkillError(404, "repo project tidak ditemukan");
22280
+ return join19(repo, s2, "skills");
22068
22281
  }
22069
22282
  throw new SkillError(400, "layer harus hanoman, user, atau project");
22070
22283
  }
@@ -35867,7 +36080,7 @@ var require_snapshot_utils = __commonJS({
35867
36080
  var require_snapshot_recorder = __commonJS({
35868
36081
  "../node_modules/.pnpm/undici@7.29.1/node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
35869
36082
  "use strict";
35870
- var { writeFile: writeFile9, readFile: readFile11, mkdir: mkdir13 } = __require("node:fs/promises");
36083
+ var { writeFile: writeFile9, readFile: readFile12, mkdir: mkdir13 } = __require("node:fs/promises");
35871
36084
  var { dirname: dirname20, resolve: resolve25 } = __require("node:path");
35872
36085
  var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("node:timers");
35873
36086
  var { InvalidArgumentError, UndiciError } = require_errors();
@@ -36069,7 +36282,7 @@ var require_snapshot_recorder = __commonJS({
36069
36282
  throw new InvalidArgumentError("Snapshot path is required");
36070
36283
  }
36071
36284
  try {
36072
- const data = await readFile11(resolve25(path), "utf8");
36285
+ const data = await readFile12(resolve25(path), "utf8");
36073
36286
  const parsed = JSON.parse(data);
36074
36287
  if (Array.isArray(parsed)) {
36075
36288
  this.#snapshots.clear();
@@ -47920,7 +48133,7 @@ ${captureLines}` : capture.stack;
47920
48133
  module.exports.mockErrors = mockErrors;
47921
48134
  var { EventSource } = require_eventsource();
47922
48135
  module.exports.EventSource = EventSource;
47923
- function install() {
48136
+ function install2() {
47924
48137
  globalThis.fetch = module.exports.fetch;
47925
48138
  globalThis.Headers = module.exports.Headers;
47926
48139
  globalThis.Response = module.exports.Response;
@@ -47932,7 +48145,7 @@ ${captureLines}` : capture.stack;
47932
48145
  globalThis.MessageEvent = module.exports.MessageEvent;
47933
48146
  globalThis.EventSource = module.exports.EventSource;
47934
48147
  }
47935
- module.exports.install = install;
48148
+ module.exports.install = install2;
47936
48149
  }
47937
48150
  });
47938
48151
 
@@ -48178,7 +48391,7 @@ var init_safe_outbound_request = __esm({
48178
48391
  // src/services/uploads.ts
48179
48392
  import { randomUUID as randomUUID3 } from "node:crypto";
48180
48393
  import { mkdir, writeFile, readFile as readFile2, unlink, rm } from "node:fs/promises";
48181
- import { join as join18, resolve as resolve9 } from "node:path";
48394
+ import { join as join20, resolve as resolve9 } from "node:path";
48182
48395
  function extFor(mimeType) {
48183
48396
  return EXT[mimeType] ?? ".bin";
48184
48397
  }
@@ -48189,17 +48402,17 @@ async function saveUpload(buf, mimeType) {
48189
48402
  const dir2 = uploadDir();
48190
48403
  await mkdir(dir2, { recursive: true, mode: 448 });
48191
48404
  const storageKey = `${randomUUID3()}${extFor(mimeType)}`;
48192
- await writeFile(join18(dir2, storageKey), buf, { mode: 384 });
48405
+ await writeFile(join20(dir2, storageKey), buf, { mode: 384 });
48193
48406
  return { storageKey, size: buf.length };
48194
48407
  }
48195
48408
  function sessionUploadDir(sessionId2) {
48196
48409
  if (!SESSION_ID.test(sessionId2)) throw new Error(`sessionId tak sah: ${sessionId2}`);
48197
- return join18(uploadDir(), "terminal", sessionId2);
48410
+ return join20(uploadDir(), "terminal", sessionId2);
48198
48411
  }
48199
48412
  async function saveSessionUpload(sessionId2, buf, mimeType) {
48200
48413
  const dir2 = sessionUploadDir(sessionId2);
48201
48414
  await mkdir(dir2, { recursive: true, mode: 448 });
48202
- const path = join18(dir2, `${randomUUID3()}${extFor(mimeType)}`);
48415
+ const path = join20(dir2, `${randomUUID3()}${extFor(mimeType)}`);
48203
48416
  await writeFile(path, buf, { mode: 384 });
48204
48417
  return { path, size: buf.length };
48205
48418
  }
@@ -48215,11 +48428,11 @@ async function dropSessionUploads(sessionId2) {
48215
48428
  }
48216
48429
  async function readUpload(storageKey) {
48217
48430
  const safe3 = storageKey.replace(/[/\\]/g, "");
48218
- return readFile2(join18(uploadDir(), safe3));
48431
+ return readFile2(join20(uploadDir(), safe3));
48219
48432
  }
48220
48433
  async function readUploadOrFetch(storageKey) {
48221
48434
  const safe3 = storageKey.replace(/[/\\]/g, "");
48222
- const target3 = join18(uploadDir(), safe3);
48435
+ const target3 = join20(uploadDir(), safe3);
48223
48436
  try {
48224
48437
  return await readFile2(target3);
48225
48438
  } catch {
@@ -48246,7 +48459,7 @@ async function readUploadOrFetch(storageKey) {
48246
48459
  }
48247
48460
  async function deleteUpload(storageKey) {
48248
48461
  const safe3 = storageKey.replace(/[/\\]/g, "");
48249
- await unlink(join18(uploadDir(), safe3)).catch(() => {
48462
+ await unlink(join20(uploadDir(), safe3)).catch(() => {
48250
48463
  });
48251
48464
  }
48252
48465
  var EXT, SESSION_ID;
@@ -48689,14 +48902,14 @@ var init_session_sandbox = __esm({
48689
48902
  });
48690
48903
 
48691
48904
  // src/services/session-event-spool.ts
48692
- import { join as join19 } from "node:path";
48905
+ import { join as join21 } from "node:path";
48693
48906
  var sessionEventSpoolRoot, sessionEventDir;
48694
48907
  var init_session_event_spool = __esm({
48695
48908
  "src/services/session-event-spool.ts"() {
48696
48909
  "use strict";
48697
48910
  init_src2();
48698
- sessionEventSpoolRoot = (env = process.env) => join19(resolveHome(env), "session-events");
48699
- sessionEventDir = (sessionId2, env = process.env) => join19(sessionEventSpoolRoot(env), sessionId2);
48911
+ sessionEventSpoolRoot = (env = process.env) => join21(resolveHome(env), "session-events");
48912
+ sessionEventDir = (sessionId2, env = process.env) => join21(sessionEventSpoolRoot(env), sessionId2);
48700
48913
  }
48701
48914
  });
48702
48915
 
@@ -48720,9 +48933,9 @@ import { spawn } from "node-pty";
48720
48933
  import { execFile as execFile2, execFileSync as execFileSync5 } from "node:child_process";
48721
48934
  import { createRequire } from "node:module";
48722
48935
  import { randomUUID as randomUUID4 } from "node:crypto";
48723
- import { chmodSync as chmodSync7, mkdirSync as mkdirSync11, readFileSync as readFileSync13, rmSync as rmSync6, statSync as statSync3, writeFileSync as writeFileSync13 } from "node:fs";
48936
+ import { chmodSync as chmodSync8, mkdirSync as mkdirSync12, readFileSync as readFileSync14, rmSync as rmSync7, statSync as statSync4, writeFileSync as writeFileSync14 } from "node:fs";
48724
48937
  import { stat as stat2 } from "node:fs/promises";
48725
- import { dirname as dirname10, resolve as resolvePath } from "node:path";
48938
+ import { dirname as dirname11, resolve as resolvePath } from "node:path";
48726
48939
  import { tmpdir as tmpdir4 } from "node:os";
48727
48940
  function doneSeenOf(id2) {
48728
48941
  const known2 = phaseDoneSeen.get(id2);
@@ -48783,8 +48996,8 @@ function fitCustomAgentsToArgBudget(customDefs, render, limit = AGENTS_ARG_SAFE_
48783
48996
  }
48784
48997
  function noTtyPromptEnv() {
48785
48998
  const path = askpassDenyPath();
48786
- mkdirSync11(dirname10(path), { recursive: true, mode: 448 });
48787
- writeFileSync13(path, ASKPASS_DENY, { mode: 448 });
48999
+ mkdirSync12(dirname11(path), { recursive: true, mode: 448 });
49000
+ writeFileSync14(path, ASKPASS_DENY, { mode: 448 });
48788
49001
  return { SSH_ASKPASS: path, SSH_ASKPASS_REQUIRE: "force", GIT_TERMINAL_PROMPT: "0" };
48789
49002
  }
48790
49003
  function hanomanDbInheritUnsets(env = process.env) {
@@ -49037,8 +49250,8 @@ function createSession(projectId, cwd, opts2 = {}) {
49037
49250
  phaseDoneSeen.delete(id2);
49038
49251
  const eventDir = opts2.command ? void 0 : sessionEventDir(id2);
49039
49252
  if (eventDir) {
49040
- mkdirSync11(eventDir, { recursive: true, mode: 448 });
49041
- chmodSync7(eventDir, 448);
49253
+ mkdirSync12(eventDir, { recursive: true, mode: 448 });
49254
+ chmodSync8(eventDir, 448);
49042
49255
  }
49043
49256
  const agentForDefs = opts2.agent ?? "claude";
49044
49257
  const sessionEffort = agentForDefs === "codex" && opts2.model && opts2.effort ? coerceCodexEffort(opts2.model, opts2.effort) : opts2.effort;
@@ -49082,7 +49295,7 @@ function createSession(projectId, cwd, opts2 = {}) {
49082
49295
  if (customDefs.length > 0 || requestedPhaseDefs.length > 0) {
49083
49296
  const tempDir = agentTempDir(id2);
49084
49297
  agentConfigDir = tempDir;
49085
- mkdirSync11(tempDir, { recursive: true, mode: 448 });
49298
+ mkdirSync12(tempDir, { recursive: true, mode: 448 });
49086
49299
  let hook;
49087
49300
  const readOnlyHookFor = () => customDefs.some((def2) => def2.workspacePolicy === "read-only") ? hook ??= writeReadOnlyHook(tempDir) : void 0;
49088
49301
  const attempt = (requested) => {
@@ -49097,16 +49310,16 @@ function createSession(projectId, cwd, opts2 = {}) {
49097
49310
  let phaseContextFile;
49098
49311
  if (shared !== void 0) {
49099
49312
  const path = phaseContextFilePath(id2);
49100
- writeFileSync13(path, shared, { mode: 384 });
49101
- chmodSync7(path, 384);
49313
+ writeFileSync14(path, shared, { mode: 384 });
49314
+ chmodSync8(path, 384);
49102
49315
  phaseContextFile = { context: shared, path };
49103
49316
  }
49104
49317
  const agentContractFiles = [];
49105
49318
  for (const policy of new Set(customDefs.map((d) => d.workspacePolicy ?? "inherit"))) {
49106
49319
  const path = agentContractFilePath(id2, policy);
49107
49320
  const content = agentContractOf(policy);
49108
- writeFileSync13(path, content, { mode: 384 });
49109
- chmodSync7(path, 384);
49321
+ writeFileSync14(path, content, { mode: 384 });
49322
+ chmodSync8(path, 384);
49110
49323
  agentContractFiles.push({ policy, content, path });
49111
49324
  }
49112
49325
  const fit = fitCustomAgentsToArgBudget(customDefs, (kept) => renderAgentsJson(
@@ -49128,7 +49341,7 @@ function createSession(projectId, cwd, opts2 = {}) {
49128
49341
  customDefs = fit.kept;
49129
49342
  defs = [...withPhaseDelegation(requested, customDefs, agentForDefs), ...customDefs];
49130
49343
  if (defs.length === 0) return [];
49131
- writeFileSync13(file, fit.json, { mode: 384 });
49344
+ writeFileSync14(file, fit.json, { mode: 384 });
49132
49345
  } catch (error) {
49133
49346
  if (phaseDefs.length === 0) throw error;
49134
49347
  return [error instanceof Error ? error.message : String(error)];
@@ -49208,8 +49421,8 @@ function createSession(projectId, cwd, opts2 = {}) {
49208
49421
  const sessionPrompt = bornPrompt(orchestrated);
49209
49422
  if (!opts2.command && sessionPrompt) {
49210
49423
  promptFile = promptFilePath(id2);
49211
- mkdirSync11(dirname10(promptFile), { recursive: true, mode: 448 });
49212
- writeFileSync13(promptFile, sessionPrompt + rosterBlock, { mode: 384 });
49424
+ mkdirSync12(dirname11(promptFile), { recursive: true, mode: 448 });
49425
+ writeFileSync14(promptFile, sessionPrompt + rosterBlock, { mode: 384 });
49213
49426
  promptArg = `"$(cat ${sq(promptFile)})"`;
49214
49427
  }
49215
49428
  const agent = opts2.agent ?? "claude";
@@ -49220,8 +49433,8 @@ function createSession(projectId, cwd, opts2 = {}) {
49220
49433
  let goalGate;
49221
49434
  if (agent === "codex" && opts2.goal && opts2.flow && opts2.specId) {
49222
49435
  goalGate = goalGatePath(id2);
49223
- mkdirSync11(dirname10(goalGate), { recursive: true, mode: 448 });
49224
- writeFileSync13(goalGate, codexGoalScript({
49436
+ mkdirSync12(dirname11(goalGate), { recursive: true, mode: 448 });
49437
+ writeFileSync14(goalGate, codexGoalScript({
49225
49438
  flow: opts2.flow,
49226
49439
  specId: opts2.specId,
49227
49440
  condition: opts2.goal,
@@ -49268,7 +49481,7 @@ function createSession(projectId, cwd, opts2 = {}) {
49268
49481
  for (const [k, v] of Object.entries(sessionEventEnv(id2))) envPairs.push(`${k}=${sq(v)}`);
49269
49482
  }
49270
49483
  if (opts2.phaseFile) {
49271
- mkdirSync11(dirname10(opts2.phaseFile), { recursive: true });
49484
+ mkdirSync12(dirname11(opts2.phaseFile), { recursive: true });
49272
49485
  envPairs.push(`HANOMAN_PHASE_FILE=${sq(opts2.phaseFile)}`);
49273
49486
  }
49274
49487
  if (!opts2.command) {
@@ -49297,7 +49510,7 @@ function createSession(projectId, cwd, opts2 = {}) {
49297
49510
  });
49298
49511
  cmd = wrapped;
49299
49512
  }
49300
- if (opts2.decisionFile) mkdirSync11(dirname10(opts2.decisionFile), { recursive: true });
49513
+ if (opts2.decisionFile) mkdirSync12(dirname11(opts2.decisionFile), { recursive: true });
49301
49514
  const bootstrap = runtime === "antigravity" ? createAntigravityEnvBootstrap(cmd, agentEnvironment(runtime, { ...process.env, ...opts2.env })) : void 0;
49302
49515
  if (bootstrap) cmd = bootstrap.command;
49303
49516
  try {
@@ -49704,11 +49917,11 @@ function killSession(id2) {
49704
49917
  cleanupAntigravityAgents({ cwd: p3.cwd, sessionId: id2 });
49705
49918
  cleanupInjectedSkills(agentTempDir(id2));
49706
49919
  try {
49707
- rmSync6(agentTempDir(id2), { recursive: true, force: true });
49920
+ rmSync7(agentTempDir(id2), { recursive: true, force: true });
49708
49921
  } catch {
49709
49922
  }
49710
49923
  try {
49711
- rmSync6(sessionEventDir(id2), { recursive: true, force: true });
49924
+ rmSync7(sessionEventDir(id2), { recursive: true, force: true });
49712
49925
  } catch {
49713
49926
  }
49714
49927
  emitDeath({ sessionId: id2, exitCode: p3.exited ? p3.code : null, transcript });
@@ -49767,7 +49980,7 @@ var init_pty = __esm({
49767
49980
  POLL_MS = 500;
49768
49981
  markerFilled = (f) => {
49769
49982
  try {
49770
- return statSync3(f).size > 0;
49983
+ return statSync4(f).size > 0;
49771
49984
  } catch {
49772
49985
  return false;
49773
49986
  }
@@ -49781,7 +49994,7 @@ var init_pty = __esm({
49781
49994
  };
49782
49995
  clearMarker = (f) => {
49783
49996
  try {
49784
- writeFileSync13(f, "");
49997
+ writeFileSync14(f, "");
49785
49998
  } catch {
49786
49999
  }
49787
50000
  };
@@ -49791,7 +50004,7 @@ var init_pty = __esm({
49791
50004
  markerOnset = (f) => {
49792
50005
  let raw;
49793
50006
  try {
49794
- raw = readFileSync13(f, "utf8");
50007
+ raw = readFileSync14(f, "utf8");
49795
50008
  } catch {
49796
50009
  return 0;
49797
50010
  }
@@ -50045,7 +50258,7 @@ var init_redact_known = __esm({
50045
50258
  // src/services/transcript-store.ts
50046
50259
  import { randomUUID as randomUUID5 } from "node:crypto";
50047
50260
  import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile3, unlink as unlink2, readdir as readdir2, stat as stat3 } from "node:fs/promises";
50048
- import { join as join20, resolve as resolve10, basename as basename6 } from "node:path";
50261
+ import { join as join22, resolve as resolve10, basename as basename6 } from "node:path";
50049
50262
  function transcriptDir() {
50050
50263
  return resolve10(effectiveStr("HANOMAN_TRANSCRIPT_DIR")?.trim() || resolveDataDirs().transcripts);
50051
50264
  }
@@ -50065,13 +50278,13 @@ async function saveTranscript(text2) {
50065
50278
  const dir2 = transcriptDir();
50066
50279
  await mkdir2(dir2, { recursive: true, mode: 448 });
50067
50280
  const key = `${randomUUID5()}.log`;
50068
- await writeFile2(join20(dir2, key), body, { encoding: "utf8", mode: 384 });
50281
+ await writeFile2(join22(dir2, key), body, { encoding: "utf8", mode: 384 });
50069
50282
  return { key, bytes: Buffer.byteLength(body, "utf8"), truncated };
50070
50283
  }
50071
50284
  async function readTranscript(key) {
50072
50285
  if (!key) return null;
50073
50286
  try {
50074
- return await readFile3(join20(transcriptDir(), basename6(key)), "utf8");
50287
+ return await readFile3(join22(transcriptDir(), basename6(key)), "utf8");
50075
50288
  } catch {
50076
50289
  return null;
50077
50290
  }
@@ -50079,7 +50292,7 @@ async function readTranscript(key) {
50079
50292
  async function deleteTranscript(key) {
50080
50293
  if (!key) return;
50081
50294
  try {
50082
- await unlink2(join20(transcriptDir(), basename6(key)));
50295
+ await unlink2(join22(transcriptDir(), basename6(key)));
50083
50296
  } catch (e) {
50084
50297
  if (e.code !== "ENOENT") throw e;
50085
50298
  }
@@ -50097,7 +50310,7 @@ async function listTranscripts() {
50097
50310
  for (const name2 of names) {
50098
50311
  if (!name2.endsWith(".log")) continue;
50099
50312
  try {
50100
- rows.push({ key: name2, mtimeMs: (await stat3(join20(dir2, name2))).mtimeMs });
50313
+ rows.push({ key: name2, mtimeMs: (await stat3(join22(dir2, name2))).mtimeMs });
50101
50314
  } catch {
50102
50315
  }
50103
50316
  }
@@ -55390,13 +55603,13 @@ var init_tombstone = __esm({
55390
55603
  });
55391
55604
 
55392
55605
  // src/services/qa-attachment-sync.ts
55393
- import { createHash as createHash8, randomUUID as randomUUID7 } from "node:crypto";
55606
+ import { createHash as createHash9, randomUUID as randomUUID7 } from "node:crypto";
55394
55607
  import { chmod, mkdir as mkdir4, rename as rename2, stat as stat4, writeFile as writeFile4 } from "node:fs/promises";
55395
- import { join as join22 } from "node:path";
55608
+ import { join as join24 } from "node:path";
55396
55609
  async function qaBytesPresent(storageKey) {
55397
55610
  if (!QA_STORAGE_KEY.test(storageKey)) return false;
55398
55611
  try {
55399
- return (await stat4(join22(uploadDir(), storageKey))).isFile();
55612
+ return (await stat4(join24(uploadDir(), storageKey))).isFile();
55400
55613
  } catch {
55401
55614
  return false;
55402
55615
  }
@@ -55411,7 +55624,7 @@ async function settleNewQaAttachment(id2) {
55411
55624
  }
55412
55625
  async function verifyQaBytes(meta, buf) {
55413
55626
  if (buf.length !== meta.size || buf.length === 0 || buf.length > QA_SYNC_MAX_BYTES) return { ok: false, status: 400, error: "size" };
55414
- if (createHash8("sha256").update(buf).digest("hex") !== meta.sha256) return { ok: false, status: 400, error: "sha256" };
55627
+ if (createHash9("sha256").update(buf).digest("hex") !== meta.sha256) return { ok: false, status: 400, error: "sha256" };
55415
55628
  if (SNIFFABLE2.has(meta.mimeType)) {
55416
55629
  if ((await fileTypeFromBuffer(buf))?.mime !== meta.mimeType) return { ok: false, status: 415, error: "type" };
55417
55630
  } else if (!isUtf8Text2(buf)) return { ok: false, status: 415, error: "type" };
@@ -55421,10 +55634,10 @@ async function storeQaBytes(storageKey, buf) {
55421
55634
  if (!QA_STORAGE_KEY.test(storageKey)) throw new Error("storageKey tak sah");
55422
55635
  const dir2 = uploadDir();
55423
55636
  await mkdir4(dir2, { recursive: true, mode: 448 });
55424
- const tmp = join22(dir2, `${storageKey}.tmp-${randomUUID7()}`);
55637
+ const tmp = join24(dir2, `${storageKey}.tmp-${randomUUID7()}`);
55425
55638
  await writeFile4(tmp, buf, { mode: 384 });
55426
- await rename2(tmp, join22(dir2, storageKey));
55427
- await chmod(join22(dir2, storageKey), 384).catch(() => {
55639
+ await rename2(tmp, join24(dir2, storageKey));
55640
+ await chmod(join24(dir2, storageKey), 384).catch(() => {
55428
55641
  });
55429
55642
  }
55430
55643
  var QA_STORAGE_KEY, QA_SYNC_MAX_BYTES, SNIFFABLE2, isUtf8Text2;
@@ -55668,9 +55881,9 @@ async function pull(sinceCursor, limit = 500, maxBytes = PULL_MAX_BYTES, opts2 =
55668
55881
  async function bootstrapSnapshot(after, maxBytes = PULL_MAX_BYTES, opts2 = {}) {
55669
55882
  const tip = await prisma.syncLog.findFirst({ orderBy: { seq: "desc" }, select: { seq: true } });
55670
55883
  const cursor = tip ? String(tip.seq) : "0";
55671
- const sep9 = after ? after.indexOf(":") : -1;
55672
- const afterEntity = sep9 > 0 ? after.slice(0, sep9) : null;
55673
- const afterId = sep9 > 0 ? after.slice(sep9 + 1) : null;
55884
+ const sep11 = after ? after.indexOf(":") : -1;
55885
+ const afterEntity = sep11 > 0 ? after.slice(0, sep11) : null;
55886
+ const afterId = sep11 > 0 ? after.slice(sep11 + 1) : null;
55674
55887
  const startAt = afterEntity ? BOOTSTRAP_ORDER.indexOf(afterEntity) : 0;
55675
55888
  if (startAt < 0) throw new Error("bootstrap cursor tak dikenal");
55676
55889
  const records = [];
@@ -56075,9 +56288,9 @@ var init_sync_notify = __esm({
56075
56288
  // src/services/safe-repo-path.ts
56076
56289
  import { constants as constants3 } from "node:fs";
56077
56290
  import { lstat, mkdir as mkdir5, open as open2, realpath, rename as rename3, unlink as unlink4 } from "node:fs/promises";
56078
- import { isAbsolute as isAbsolute10, join as join23, relative as relative5, resolve as resolve13, sep as sep6 } from "node:path";
56291
+ import { isAbsolute as isAbsolute10, join as join25, relative as relative6, resolve as resolve13, sep as sep7 } from "node:path";
56079
56292
  import { randomUUID as randomUUID8 } from "node:crypto";
56080
- import { closeSync as closeSync3, fstatSync as fstatSync2, lstatSync as lstatSync9, mkdirSync as mkdirSync12, openSync as openSync3, readFileSync as readFileSync14, realpathSync as realpathSync7, renameSync as renameSync5, unlinkSync as unlinkSync4, writeFileSync as writeFileSync14 } from "node:fs";
56293
+ import { closeSync as closeSync3, fstatSync as fstatSync2, lstatSync as lstatSync9, mkdirSync as mkdirSync13, openSync as openSync3, readFileSync as readFileSync15, realpathSync as realpathSync7, renameSync as renameSync6, unlinkSync as unlinkSync4, writeFileSync as writeFileSync15 } from "node:fs";
56081
56294
  function components(rel) {
56082
56295
  if (!rel || isAbsolute10(rel) || rel.includes("\0")) denied("path invalid");
56083
56296
  const parts = rel.replace(/\\/g, "/").split("/");
@@ -56091,15 +56304,15 @@ async function canonicalRoot(root) {
56091
56304
  return out4;
56092
56305
  }
56093
56306
  function beneath(root, candidate) {
56094
- const rel = relative5(root, candidate);
56095
- return rel === "" || !rel.startsWith(`..${sep6}`) && rel !== ".." && !isAbsolute10(rel);
56307
+ const rel = relative6(root, candidate);
56308
+ return rel === "" || !rel.startsWith(`..${sep7}`) && rel !== ".." && !isAbsolute10(rel);
56096
56309
  }
56097
56310
  async function ensureRepoParents(root, rel) {
56098
56311
  const base3 = await canonicalRoot(root);
56099
56312
  const parts = components(rel).slice(0, -1);
56100
56313
  let current = base3;
56101
56314
  for (const part of parts) {
56102
- current = join23(current, part);
56315
+ current = join25(current, part);
56103
56316
  try {
56104
56317
  await mkdir5(current, { mode: 448 });
56105
56318
  } catch (error) {
@@ -56116,9 +56329,9 @@ function ensureRepoParentsSync(root, rel) {
56116
56329
  const parts = components(rel).slice(0, -1);
56117
56330
  let current = base3;
56118
56331
  for (const part of parts) {
56119
- current = join23(current, part);
56332
+ current = join25(current, part);
56120
56333
  try {
56121
- mkdirSync12(current, { mode: 448 });
56334
+ mkdirSync13(current, { mode: 448 });
56122
56335
  } catch (error) {
56123
56336
  if (error.code !== "EEXIST") throw error;
56124
56337
  }
@@ -56132,7 +56345,7 @@ async function resolveRepoEntry(root, rel, opts2 = {}) {
56132
56345
  const parts = components(rel);
56133
56346
  let current = base3;
56134
56347
  for (let i = 0; i < parts.length; i++) {
56135
- current = join23(current, parts[i]);
56348
+ current = join25(current, parts[i]);
56136
56349
  const stat8 = await lstat(current).catch((error) => {
56137
56350
  if (error.code === "ENOENT" && opts2.allowMissingTail) return null;
56138
56351
  if (error.code === "ENOENT" && opts2.allowMissingFinal && i === parts.length - 1) return null;
@@ -56153,7 +56366,7 @@ function assertSafeRepoPathSync(root, rel, allowMissingFinal = false, allowMissi
56153
56366
  const parts = components(rel);
56154
56367
  let current = base3;
56155
56368
  for (let i = 0; i < parts.length; i++) {
56156
- current = join23(current, parts[i]);
56369
+ current = join25(current, parts[i]);
56157
56370
  try {
56158
56371
  const stat8 = lstatSync9(current);
56159
56372
  if (stat8.isSymbolicLink()) denied("symlink");
@@ -56172,7 +56385,7 @@ function readRepoFileSync(root, rel) {
56172
56385
  const fd = openSync3(path, constants3.O_RDONLY | (constants3.O_NOFOLLOW ?? 0));
56173
56386
  try {
56174
56387
  if (!fstatSync2(fd).isFile()) denied("bukan file regular");
56175
- return readFileSync14(fd);
56388
+ return readFileSync15(fd);
56176
56389
  } finally {
56177
56390
  closeSync3(fd);
56178
56391
  }
@@ -56181,12 +56394,12 @@ function writeRepoFileAtomicSync(root, rel, data) {
56181
56394
  ensureRepoParentsSync(root, rel);
56182
56395
  const path = assertSafeRepoPathSync(root, rel, true);
56183
56396
  const parent = resolve13(path, "..");
56184
- const parentRel = relative5(realpathSync7(root), parent);
56397
+ const parentRel = relative6(realpathSync7(root), parent);
56185
56398
  if (parentRel) assertSafeRepoPathSync(root, parentRel);
56186
- const temp = join23(parent, `.hanoman-${randomUUID8()}.tmp`);
56399
+ const temp = join25(parent, `.hanoman-${randomUUID8()}.tmp`);
56187
56400
  const fd = openSync3(temp, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 384);
56188
56401
  try {
56189
- writeFileSync14(fd, data);
56402
+ writeFileSync15(fd, data);
56190
56403
  } finally {
56191
56404
  closeSync3(fd);
56192
56405
  }
@@ -56199,7 +56412,7 @@ function writeRepoFileAtomicSync(root, rel, data) {
56199
56412
  }
56200
56413
  })();
56201
56414
  if (final?.isSymbolicLink() || final && !final.isFile()) denied("target berubah");
56202
- renameSync5(temp, path);
56415
+ renameSync6(temp, path);
56203
56416
  } catch (error) {
56204
56417
  try {
56205
56418
  unlinkSync4(temp);
@@ -56230,7 +56443,7 @@ async function writeRepoFileAtomic(root, rel, data) {
56230
56443
  await mkdir5(entry2.parent, { recursive: false, mode: 448 }).catch((error) => {
56231
56444
  if (error.code !== "EEXIST") throw error;
56232
56445
  });
56233
- const temp = join23(entry2.parent, `.hanoman-${randomUUID8()}.tmp`);
56446
+ const temp = join25(entry2.parent, `.hanoman-${randomUUID8()}.tmp`);
56234
56447
  const handle = await open2(temp, constants3.O_WRONLY | constants3.O_CREAT | constants3.O_EXCL | (constants3.O_NOFOLLOW ?? 0), 384);
56235
56448
  try {
56236
56449
  await handle.writeFile(data);
@@ -56266,8 +56479,8 @@ var init_safe_repo_path = __esm({
56266
56479
  // src/services/integrate.ts
56267
56480
  import { execFile as execFile5 } from "node:child_process";
56268
56481
  import { promisify as promisify4 } from "node:util";
56269
- import { rmSync as rmSync8 } from "node:fs";
56270
- import { join as join24 } from "node:path";
56482
+ import { rmSync as rmSync9 } from "node:fs";
56483
+ import { join as join26 } from "node:path";
56271
56484
  async function worktreeForBranch(repoDir, branch) {
56272
56485
  let path = null;
56273
56486
  for (const line of (await out(repoDir, ["worktree", "list", "--porcelain"])).split("\n")) {
@@ -56292,7 +56505,7 @@ async function resolveTarget(repoDir, target3) {
56292
56505
  async function reclaim(repoDir, wt) {
56293
56506
  await sh(repoDir, ["worktree", "remove", "--force", wt]);
56294
56507
  await sh(repoDir, ["worktree", "prune"]);
56295
- rmSync8(wt, { recursive: true, force: true });
56508
+ rmSync9(wt, { recursive: true, force: true });
56296
56509
  }
56297
56510
  async function integrateBranch(repoDir, src, op, target3) {
56298
56511
  const source = await resolveSource(repoDir, src.branch);
@@ -56300,7 +56513,7 @@ async function integrateBranch(repoDir, src, op, target3) {
56300
56513
  const tgt = await resolveTarget(repoDir, target3);
56301
56514
  if (!tgt) return { status: "error", code: 400, error: `target "${target3}" tidak dikenal` };
56302
56515
  await sh(repoDir, ["fetch", "origin"]);
56303
- const wt = join24(repoDir, ".worktrees", `merge-${sanitize(src.mergeId)}`);
56516
+ const wt = join26(repoDir, ".worktrees", `merge-${sanitize(src.mergeId)}`);
56304
56517
  await reclaim(repoDir, wt);
56305
56518
  const baseRef = op === "merge" ? tgt.ref : source;
56306
56519
  const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `${baseRef}^{commit}`]);
@@ -56361,7 +56574,7 @@ async function mergeIntoCurrent(repoDir, source, opts2 = {}) {
56361
56574
  const src = await resolveGraphSource(repoDir, source);
56362
56575
  if (!src) return { status: "error", code: 400, error: `source "${source}" tak dikenal` };
56363
56576
  await sh(repoDir, ["fetch", "origin"]);
56364
- const wt = join24(repoDir, ".worktrees", `merge-${sanitize(current)}`);
56577
+ const wt = join26(repoDir, ".worktrees", `merge-${sanitize(current)}`);
56365
56578
  await reclaim(repoDir, wt);
56366
56579
  const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `refs/heads/${current}^{commit}`]);
56367
56580
  if (!await ok(repoDir, ["worktree", "add", "--detach", "-q", wt, baseSha]))
@@ -56399,7 +56612,7 @@ async function replayCurrent(repoDir, source, cmd) {
56399
56612
  const current = await out(repoDir, ["rev-parse", "--abbrev-ref", "HEAD"]);
56400
56613
  if (!current || current === "HEAD")
56401
56614
  return { status: "error", code: 409, error: "HEAD detached \u2014 checkout sebuah branch dulu" };
56402
- const wt = join24(repoDir, ".worktrees", `merge-${sanitize(current)}`);
56615
+ const wt = join26(repoDir, ".worktrees", `merge-${sanitize(current)}`);
56403
56616
  await reclaim(repoDir, wt);
56404
56617
  const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `refs/heads/${current}^{commit}`]);
56405
56618
  if (!await ok(repoDir, ["worktree", "add", "--detach", "-q", wt, baseSha]))
@@ -56459,11 +56672,11 @@ import { execFile as execFile6 } from "node:child_process";
56459
56672
  import { promisify as promisify5 } from "node:util";
56460
56673
  import { mkdtemp, copyFile, rm as rm2 } from "node:fs/promises";
56461
56674
  import { tmpdir as tmpdir5 } from "node:os";
56462
- import { join as join25, resolve as resolve15 } from "node:path";
56675
+ import { join as join27, resolve as resolve15 } from "node:path";
56463
56676
  async function withTempIndex(wt, fn) {
56464
56677
  const idx = (await exec4("git", ["rev-parse", "--git-path", "index"], { cwd: wt, ...GIT3 })).stdout.trim();
56465
- const dir2 = await mkdtemp(join25(tmpdir5(), "hanoman-idx-"));
56466
- const tmp = join25(dir2, "index");
56678
+ const dir2 = await mkdtemp(join27(tmpdir5(), "hanoman-idx-"));
56679
+ const tmp = join27(dir2, "index");
56467
56680
  await copyFile(resolve15(wt, idx), tmp);
56468
56681
  const env = { ...process.env, GIT_INDEX_FILE: tmp };
56469
56682
  try {
@@ -56609,7 +56822,7 @@ var init_spec_review2 = __esm({
56609
56822
  GIT3 = { maxBuffer: 1 << 24 };
56610
56823
  MAX = 256 * 1024;
56611
56824
  emptySpecReview = (reason) => ({ base: "", files: [], changed: [], empty: true, reason });
56612
- worktreeDir = (repoDir, specId) => join25(repoDir, ".worktrees", specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
56825
+ worktreeDir = (repoDir, specId) => join27(repoDir, ".worktrees", specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
56613
56826
  splitZ = (s2) => s2.split("\0").filter(Boolean);
56614
56827
  EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
56615
56828
  }
@@ -56917,7 +57130,7 @@ var init_client = __esm({
56917
57130
  });
56918
57131
 
56919
57132
  // src/services/telegram/protocol.ts
56920
- import { createHash as createHash10 } from "node:crypto";
57133
+ import { createHash as createHash11 } from "node:crypto";
56921
57134
  function normalizeTelegramCommand(text2) {
56922
57135
  const clean3 = text2.trim();
56923
57136
  const match = clean3.match(/^\/([a-z0-9_]+)(?:@[a-z0-9_]+)?(?:\s+([\s\S]*))?$/i);
@@ -57006,7 +57219,7 @@ function parseTelegramUpdate(update2, allowlist, maxText = 12e3) {
57006
57219
  };
57007
57220
  }
57008
57221
  function inboundDigest(input) {
57009
- return createHash10("sha256").update(`${input.updateId}\0${input.chatId}\0${input.userId}\0${input.kind}\0${input.text}`, "utf8").digest("hex");
57222
+ return createHash11("sha256").update(`${input.updateId}\0${input.chatId}\0${input.userId}\0${input.kind}\0${input.text}`, "utf8").digest("hex");
57010
57223
  }
57011
57224
  function sanitizeTelegramOutput(text2, exactSecrets = []) {
57012
57225
  let clean3 = text2.replace(ANSI, "").replace(CONTROLS, "");
@@ -57309,7 +57522,7 @@ var init_notifications2 = __esm({
57309
57522
  });
57310
57523
 
57311
57524
  // src/services/codex-trust.ts
57312
- import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync13, readFileSync as readFileSync15, realpathSync as realpathSync9 } from "node:fs";
57525
+ import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync14, readFileSync as readFileSync16, realpathSync as realpathSync9 } from "node:fs";
57313
57526
  import { homedir as homedir5 } from "node:os";
57314
57527
  function ensureCodexTrust(repoDir, home3) {
57315
57528
  const path = codexConfigPath(home3);
@@ -57322,11 +57535,11 @@ function ensureCodexTrust(repoDir, home3) {
57322
57535
  try {
57323
57536
  let existing = "";
57324
57537
  try {
57325
- existing = readFileSync15(path, "utf8");
57538
+ existing = readFileSync16(path, "utf8");
57326
57539
  } catch {
57327
57540
  }
57328
57541
  if (existing.includes(header2)) return;
57329
- mkdirSync13(codexHome(home3), { recursive: true });
57542
+ mkdirSync14(codexHome(home3), { recursive: true });
57330
57543
  const lead = existing === "" || existing.endsWith("\n") ? "" : "\n";
57331
57544
  appendFileSync3(path, `${lead}
57332
57545
  ${header2}
@@ -57466,8 +57679,8 @@ var init_device_sockets = __esm({
57466
57679
 
57467
57680
  // src/services/exit-guard.ts
57468
57681
  import { spawn as spawn3 } from "node:child_process";
57469
- import { writeFileSync as writeFileSync15 } from "node:fs";
57470
- import { join as join29 } from "node:path";
57682
+ import { writeFileSync as writeFileSync16 } from "node:fs";
57683
+ import { join as join31 } from "node:path";
57471
57684
  function watchdogScript(pid, graceMs) {
57472
57685
  const secs = Math.max(1, Math.ceil(graceMs / 1e3));
57473
57686
  return `s=$(ps -o lstart= -p ${pid}); sleep ${secs}; [ -n "$s" ] && [ "$(ps -o lstart= -p ${pid})" = "$s" ] && kill -9 ${pid}`;
@@ -57483,8 +57696,8 @@ function launchWatchdog(script2, env = process.env) {
57483
57696
  function exitGuarded(code, d = {}) {
57484
57697
  const pid = d.pid ?? process.pid;
57485
57698
  try {
57486
- const write2 = d.writeIntent ?? ((p3, t) => writeFileSync15(p3, t, { mode: 384 }));
57487
- write2(join29(d.home ?? resolveHome(), EXIT_INTENT_FILE), formatExitIntent(pid, code));
57699
+ const write2 = d.writeIntent ?? ((p3, t) => writeFileSync16(p3, t, { mode: 384 }));
57700
+ write2(join31(d.home ?? resolveHome(), EXIT_INTENT_FILE), formatExitIntent(pid, code));
57488
57701
  } catch {
57489
57702
  }
57490
57703
  try {
@@ -57504,9 +57717,9 @@ var init_exit_guard = __esm({
57504
57717
  });
57505
57718
 
57506
57719
  // src/services/update.ts
57507
- import { readFileSync as readFileSync16 } from "node:fs";
57508
- import { resolve as resolve20, dirname as dirname13 } from "node:path";
57509
- import { fileURLToPath as fileURLToPath3 } from "node:url";
57720
+ import { readFileSync as readFileSync17 } from "node:fs";
57721
+ import { resolve as resolve20, dirname as dirname14 } from "node:path";
57722
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
57510
57723
  function composeUpdate(x) {
57511
57724
  const available = x.registryStatus === "ok" && x.latestVersion != null && compareSemver(x.latestVersion, x.currentVersion) > 0;
57512
57725
  return {
@@ -57522,10 +57735,10 @@ function composeUpdate(x) {
57522
57735
  };
57523
57736
  }
57524
57737
  function runningVersion() {
57525
- const here = dirname13(fileURLToPath3(import.meta.url));
57738
+ const here = dirname14(fileURLToPath4(import.meta.url));
57526
57739
  for (const p3 of [resolve20(here, "build-info.json"), resolve20(here, "../package.json")]) {
57527
57740
  try {
57528
- const v = JSON.parse(readFileSync16(p3, "utf8")).version;
57741
+ const v = JSON.parse(readFileSync17(p3, "utf8")).version;
57529
57742
  if (typeof v === "string" && v) return v;
57530
57743
  } catch {
57531
57744
  }
@@ -57961,7 +58174,7 @@ var init_remote_session = __esm({
57961
58174
 
57962
58175
  // src/services/spec-attachment-dir.ts
57963
58176
  import { mkdir as mkdir6, readdir as readdir5, readFile as readFile6, rm as rm4, writeFile as writeFile5 } from "node:fs/promises";
57964
- import { join as join30 } from "node:path";
58177
+ import { join as join32 } from "node:path";
57965
58178
  function uniqueName(taken, filename) {
57966
58179
  if (!taken.has(filename)) {
57967
58180
  taken.add(filename);
@@ -58009,19 +58222,19 @@ async function syncSpecAttachmentsDir(specId, projectId) {
58009
58222
  for (const a of rows) {
58010
58223
  let bytes;
58011
58224
  try {
58012
- bytes = await readFile6(join30(uploadDir(), a.storageKey));
58225
+ bytes = await readFile6(join32(uploadDir(), a.storageKey));
58013
58226
  } catch {
58014
58227
  continue;
58015
58228
  }
58016
58229
  const filename = uniqueName(taken, a.filename);
58017
- const path = join30(dir2, filename);
58230
+ const path = join32(dir2, filename);
58018
58231
  await writeFile5(path, bytes, { mode: 384 });
58019
58232
  items.push({ filename, mimeType: a.mimeType, size: a.size, path });
58020
58233
  }
58021
- await writeFile5(join30(dir2, INDEX), renderIndex(specId, items), { mode: 384 });
58234
+ await writeFile5(join32(dir2, INDEX), renderIndex(specId, items), { mode: 384 });
58022
58235
  const keep = /* @__PURE__ */ new Set([INDEX, ...items.map((a) => a.filename)]);
58023
58236
  for (const name2 of await readdir5(dir2)) {
58024
- if (!keep.has(name2)) await rm4(join30(dir2, name2), { recursive: true, force: true }).catch(() => {
58237
+ if (!keep.has(name2)) await rm4(join32(dir2, name2), { recursive: true, force: true }).catch(() => {
58025
58238
  });
58026
58239
  }
58027
58240
  return items;
@@ -58040,7 +58253,7 @@ var init_spec_attachment_dir = __esm({
58040
58253
  init_local_binding();
58041
58254
  init_session_id();
58042
58255
  init_uploads();
58043
- specAttachmentsDir = (repoDir, sessionId2) => join30(repoDir, ".worktrees", ".attachments", sessionId2);
58256
+ specAttachmentsDir = (repoDir, sessionId2) => join32(repoDir, ".worktrees", ".attachments", sessionId2);
58044
58257
  INDEX = "INDEX.md";
58045
58258
  humanSize = (n2) => n2 >= 1024 * 1024 ? `${(n2 / 1024 / 1024).toFixed(1)} MB` : n2 >= 1024 ? `${Math.round(n2 / 1024)} KB` : `${n2} B`;
58046
58259
  }
@@ -58435,7 +58648,7 @@ var init_runtime_defaults2 = __esm({
58435
58648
  });
58436
58649
 
58437
58650
  // src/services/agent-token.ts
58438
- import { randomBytes as randomBytes2, createHash as createHash12, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
58651
+ import { randomBytes as randomBytes2, createHash as createHash13, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
58439
58652
  function toAgentTokenView(t) {
58440
58653
  return {
58441
58654
  id: t.id,
@@ -58522,7 +58735,7 @@ var init_agent_token = __esm({
58522
58735
  "src/services/agent-token.ts"() {
58523
58736
  "use strict";
58524
58737
  init_db();
58525
- hash2 = (token) => createHash12("sha256").update(token).digest("hex");
58738
+ hash2 = (token) => createHash13("sha256").update(token).digest("hex");
58526
58739
  AGENT_CREATOR_PREFIX = "agent:";
58527
58740
  }
58528
58741
  });
@@ -58851,11 +59064,11 @@ var init_project_config = __esm({
58851
59064
 
58852
59065
  // src/services/limits.ts
58853
59066
  import { execFileSync as execFileSync6 } from "node:child_process";
58854
- import { readFileSync as readFileSync17 } from "node:fs";
59067
+ import { readFileSync as readFileSync18 } from "node:fs";
58855
59068
  import { homedir as homedir6, userInfo } from "node:os";
58856
- import { join as join31 } from "node:path";
59069
+ import { join as join34 } from "node:path";
58857
59070
  function credsFile() {
58858
- return join31(effectiveStr("CLAUDE_CONFIG_DIR") ?? join31(homedir6(), ".claude"), ".credentials.json");
59071
+ return join34(effectiveStr("CLAUDE_CONFIG_DIR") ?? join34(homedir6(), ".claude"), ".credentials.json");
58859
59072
  }
58860
59073
  function keychainAccessToken(run6 = securityRun, account = osAccount()) {
58861
59074
  const base3 = ["find-generic-password", "-s", "Claude Code-credentials"];
@@ -58874,7 +59087,7 @@ function readAccessToken() {
58874
59087
  if (tok) return tok;
58875
59088
  }
58876
59089
  try {
58877
- return JSON.parse(readFileSync17(credsFile(), "utf8"))?.claudeAiOauth?.accessToken ?? null;
59090
+ return JSON.parse(readFileSync18(credsFile(), "utf8"))?.claudeAiOauth?.accessToken ?? null;
58878
59091
  } catch {
58879
59092
  return null;
58880
59093
  }
@@ -58958,7 +59171,7 @@ var init_limits = __esm({
58958
59171
  // src/services/codex-limits.ts
58959
59172
  import { open as open3, readdir as readdir6, stat as stat6 } from "node:fs/promises";
58960
59173
  import { homedir as homedir7 } from "node:os";
58961
- import { join as join32 } from "node:path";
59174
+ import { join as join35 } from "node:path";
58962
59175
  function windowLabel(minutes) {
58963
59176
  if (minutes === 300) return "Sesi 5 jam";
58964
59177
  if (minutes === 10080) return "Mingguan";
@@ -58989,7 +59202,7 @@ async function recentRollouts(dir2) {
58989
59202
  return [];
58990
59203
  }
58991
59204
  const stamped = await Promise.all(entries4.map(async (rel) => {
58992
- const full = join32(dir2, rel);
59205
+ const full = join35(dir2, rel);
58993
59206
  try {
58994
59207
  return { full, mtime: (await stat6(full)).mtimeMs };
58995
59208
  } catch {
@@ -59069,7 +59282,7 @@ var init_codex_limits = __esm({
59069
59282
  MAX_FILES = 8;
59070
59283
  cache2 = null;
59071
59284
  freshUntil2 = 0;
59072
- codexSessionsDir = () => join32(process.env.CODEX_HOME ?? join32(homedir7(), ".codex"), "sessions");
59285
+ codexSessionsDir = () => join35(process.env.CODEX_HOME ?? join35(homedir7(), ".codex"), "sessions");
59073
59286
  UNAVAILABLE = { status: "unavailable", windows: [], fetchedAt: null, plan: null };
59074
59287
  severityOf = (pct3) => pct3 >= 90 ? "critical" : pct3 >= 70 ? "warning" : "normal";
59075
59288
  }
@@ -59077,9 +59290,9 @@ var init_codex_limits = __esm({
59077
59290
 
59078
59291
  // src/services/antigravity-limits.ts
59079
59292
  import { execFileSync as execFileSync7 } from "node:child_process";
59080
- import { readFileSync as readFileSync18 } from "node:fs";
59293
+ import { readFileSync as readFileSync19 } from "node:fs";
59081
59294
  import { homedir as homedir8 } from "node:os";
59082
- import { join as join33 } from "node:path";
59295
+ import { join as join36 } from "node:path";
59083
59296
  function readAgyToken(run6 = securityRun2) {
59084
59297
  try {
59085
59298
  let raw = run6(["find-generic-password", "-s", "gemini", "-a", "antigravity", "-w"]).trim();
@@ -59095,7 +59308,7 @@ function readAgyToken(run6 = securityRun2) {
59095
59308
  }
59096
59309
  function defaultProject() {
59097
59310
  try {
59098
- return readFileSync18(join33(agyHome(), "cache", "default_project_id.txt"), "utf8").trim() || null;
59311
+ return readFileSync19(join36(agyHome(), "cache", "default_project_id.txt"), "utf8").trim() || null;
59099
59312
  } catch {
59100
59313
  return null;
59101
59314
  }
@@ -59186,7 +59399,7 @@ var init_antigravity_limits = __esm({
59186
59399
  freshUntil3 = 0;
59187
59400
  UNAVAILABLE2 = { status: "unavailable", windows: [], fetchedAt: null };
59188
59401
  securityRun2 = (args) => execFileSync7("security", args, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
59189
- agyHome = () => join33(homedir8(), ".gemini", "antigravity-cli");
59402
+ agyHome = () => join36(homedir8(), ".gemini", "antigravity-cli");
59190
59403
  severityOf2 = (pct3) => pct3 >= 90 ? "critical" : pct3 >= 70 ? "warning" : "normal";
59191
59404
  prodDeps = {
59192
59405
  platform: process.platform,
@@ -59285,7 +59498,7 @@ var init_budget = __esm({
59285
59498
  });
59286
59499
 
59287
59500
  // src/services/session-dialog.ts
59288
- import { createHash as createHash13 } from "node:crypto";
59501
+ import { createHash as createHash14 } from "node:crypto";
59289
59502
  function readSessionDialog(io) {
59290
59503
  const text2 = io.capture();
59291
59504
  const s2 = answerable(text2);
@@ -59341,7 +59554,7 @@ var init_session_dialog2 = __esm({
59341
59554
  init_tui_dialog();
59342
59555
  init_pty();
59343
59556
  DIALOG_CHUNK_MS = 50;
59344
- screenHashOf = (paneText2) => createHash13("sha256").update(dialogKey(paneText2)).digest("hex").slice(0, 16);
59557
+ screenHashOf = (paneText2) => createHash14("sha256").update(dialogKey(paneText2)).digest("hex").slice(0, 16);
59345
59558
  answerable = (paneText2) => {
59346
59559
  const s2 = readDialogScreen(paneText2);
59347
59560
  if (s2?.kind !== "question") return null;
@@ -59644,8 +59857,8 @@ var init_prompt2 = __esm({
59644
59857
  });
59645
59858
 
59646
59859
  // src/services/lead/verdict.ts
59647
- import { existsSync as existsSync15 } from "node:fs";
59648
- import { isAbsolute as isAbsolute11, resolve as resolvePath2, relative as relative6 } from "node:path";
59860
+ import { existsSync as existsSync18 } from "node:fs";
59861
+ import { isAbsolute as isAbsolute11, resolve as resolvePath2, relative as relative7 } from "node:path";
59649
59862
  function extractJsonBlock(raw) {
59650
59863
  const re = /```json\s*\n([\s\S]*?)```/g;
59651
59864
  let last2 = null;
@@ -59678,9 +59891,9 @@ function keepExistingRefs(refs2, repoDir) {
59678
59891
  if (!repoDir) continue;
59679
59892
  if (isAbsolute11(ref)) continue;
59680
59893
  const full = resolvePath2(repoDir, ref);
59681
- const rel = relative6(repoDir, full);
59894
+ const rel = relative7(repoDir, full);
59682
59895
  if (rel.startsWith("..")) continue;
59683
- if (existsSync15(full)) out4.push(ref);
59896
+ if (existsSync18(full)) out4.push(ref);
59684
59897
  }
59685
59898
  return [...new Set(out4)];
59686
59899
  }
@@ -60870,9 +61083,9 @@ var init_queue = __esm({
60870
61083
  });
60871
61084
 
60872
61085
  // src/services/git-auth.ts
60873
- import { chmodSync as chmodSync8, mkdirSync as mkdirSync14, writeFileSync as writeFileSync16 } from "node:fs";
61086
+ import { chmodSync as chmodSync9, mkdirSync as mkdirSync15, writeFileSync as writeFileSync17 } from "node:fs";
60874
61087
  import { tmpdir as tmpdir6 } from "node:os";
60875
- import { dirname as dirname14 } from "node:path";
61088
+ import { dirname as dirname15 } from "node:path";
60876
61089
  function httpsRemote(remote) {
60877
61090
  const scp = /^[\w.-]+@github\.com:(.+)$/.exec(remote);
60878
61091
  if (scp) return `https://github.com/${scp[1]}`;
@@ -60882,9 +61095,9 @@ function httpsRemote(remote) {
60882
61095
  }
60883
61096
  function gitAuthEnv(token) {
60884
61097
  const path = gitAskpassPath();
60885
- mkdirSync14(dirname14(path), { recursive: true, mode: 448 });
60886
- writeFileSync16(path, ASKPASS_TOKEN, { mode: 448 });
60887
- chmodSync8(path, 448);
61098
+ mkdirSync15(dirname15(path), { recursive: true, mode: 448 });
61099
+ writeFileSync17(path, ASKPASS_TOKEN, { mode: 448 });
61100
+ chmodSync9(path, 448);
60888
61101
  return { GIT_ASKPASS: path, GIT_TERMINAL_PROMPT: "0", HANOMAN_GIT_TOKEN: token };
60889
61102
  }
60890
61103
  var gitAskpassPath, ASKPASS_TOKEN, isGithubHttps;
@@ -63884,7 +64097,7 @@ var require_websocket2 = __commonJS({
63884
64097
  var http = __require("http");
63885
64098
  var net = __require("net");
63886
64099
  var tls = __require("tls");
63887
- var { randomBytes: randomBytes12, createHash: createHash23 } = __require("crypto");
64100
+ var { randomBytes: randomBytes12, createHash: createHash24 } = __require("crypto");
63888
64101
  var { Duplex, Readable } = __require("stream");
63889
64102
  var { URL: URL2 } = __require("url");
63890
64103
  var PerMessageDeflate2 = require_permessage_deflate2();
@@ -64552,7 +64765,7 @@ var require_websocket2 = __commonJS({
64552
64765
  abortHandshake(websocket2, socket2, "Invalid Upgrade header");
64553
64766
  return;
64554
64767
  }
64555
- const digest3 = createHash23("sha1").update(key + GUID).digest("base64");
64768
+ const digest3 = createHash24("sha1").update(key + GUID).digest("base64");
64556
64769
  if (res.headers["sec-websocket-accept"] !== digest3) {
64557
64770
  abortHandshake(websocket2, socket2, "Invalid Sec-WebSocket-Accept header");
64558
64771
  return;
@@ -64921,7 +65134,7 @@ var require_websocket_server = __commonJS({
64921
65134
  var EventEmitter = __require("events");
64922
65135
  var http = __require("http");
64923
65136
  var { Duplex } = __require("stream");
64924
- var { createHash: createHash23 } = __require("crypto");
65137
+ var { createHash: createHash24 } = __require("crypto");
64925
65138
  var extension2 = require_extension();
64926
65139
  var PerMessageDeflate2 = require_permessage_deflate2();
64927
65140
  var subprotocol2 = require_subprotocol();
@@ -65228,7 +65441,7 @@ var require_websocket_server = __commonJS({
65228
65441
  );
65229
65442
  }
65230
65443
  if (this._state > RUNNING) return abortHandshake(socket2, 503);
65231
- const digest3 = createHash23("sha1").update(key + GUID).digest("base64");
65444
+ const digest3 = createHash24("sha1").update(key + GUID).digest("base64");
65232
65445
  const headers = [
65233
65446
  "HTTP/1.1 101 Switching Protocols",
65234
65447
  "Upgrade: websocket",
@@ -65524,16 +65737,16 @@ var init_client2 = __esm({
65524
65737
  });
65525
65738
 
65526
65739
  // src/services/logs/spool.ts
65527
- import { appendFile, mkdir as mkdir7, readFile as readFile7, readdir as readdir7, rm as rm5, stat as stat7 } from "node:fs/promises";
65528
- import { join as join34 } from "node:path";
65740
+ import { appendFile, mkdir as mkdir7, readFile as readFile8, readdir as readdir7, rm as rm5, stat as stat7 } from "node:fs/promises";
65741
+ import { join as join37 } from "node:path";
65529
65742
  function spoolDir(lane) {
65530
- return join34(resolveDataDirs().home, "log-spool", lane);
65743
+ return join37(resolveDataDirs().home, "log-spool", lane);
65531
65744
  }
65532
65745
  async function currentSegment(lane) {
65533
65746
  const dir2 = spoolDir(lane);
65534
65747
  await mkdir7(dir2, { recursive: true, mode: 448 });
65535
65748
  if (currentFile && currentBytes < LOG_SPOOL_SEGMENT_BYTES) return currentFile;
65536
- currentFile = join34(dir2, `${Date.now()}-${++segCounter}.ndjson`);
65749
+ currentFile = join37(dir2, `${Date.now()}-${++segCounter}.ndjson`);
65537
65750
  currentBytes = 0;
65538
65751
  return currentFile;
65539
65752
  }
@@ -65555,8 +65768,8 @@ async function readSpoolSegments(lane) {
65555
65768
  const files2 = names.filter((n2) => n2.endsWith(".ndjson")).sort();
65556
65769
  const out4 = [];
65557
65770
  for (const name2 of files2) {
65558
- const file = join34(dir2, name2);
65559
- const raw = await readFile7(file, "utf8").catch(() => "");
65771
+ const file = join37(dir2, name2);
65772
+ const raw = await readFile8(file, "utf8").catch(() => "");
65560
65773
  const entries4 = raw.split("\n").filter(Boolean).map((l) => JSON.parse(l));
65561
65774
  out4.push({ file, entries: entries4 });
65562
65775
  }
@@ -66220,8 +66433,8 @@ var init_git_token = __esm({
66220
66433
 
66221
66434
  // src/services/project-checkout.ts
66222
66435
  import { execFile as execFile14, spawnSync as spawnSync4 } from "node:child_process";
66223
- import { existsSync as existsSync16, readdirSync as readdirSync8, rmSync as rmSync9 } from "node:fs";
66224
- import { join as join35 } from "node:path";
66436
+ import { existsSync as existsSync19, readdirSync as readdirSync10, rmSync as rmSync10 } from "node:fs";
66437
+ import { join as join38 } from "node:path";
66225
66438
  import { promisify as promisify13 } from "node:util";
66226
66439
  async function runGit(args, o) {
66227
66440
  const full = o.token ? ["-c", "credential.helper=", ...args] : args;
@@ -66243,8 +66456,8 @@ async function doClone(projectId, opts2) {
66243
66456
  if (!project) throw new CheckoutError(`project "${projectId}" tidak ada`, "not-found");
66244
66457
  if (!project.gitRemote) throw new CheckoutError(`project "${projectId}" tidak punya gitRemote untuk di-clone`, "no-remote");
66245
66458
  const remote = project.gitRemote;
66246
- const dest = opts2.dir ?? join35(checkoutRoot(), slug(project.id));
66247
- if (existsSync16(dest) && !isEmptyDir(dest)) {
66459
+ const dest = opts2.dir ?? join38(checkoutRoot(), slug(project.id));
66460
+ if (existsSync19(dest) && !isEmptyDir(dest)) {
66248
66461
  const origin = originOf2(dest);
66249
66462
  if (origin && sameRemote(origin, remote)) {
66250
66463
  await setBinding(projectId, dest);
@@ -66260,7 +66473,7 @@ async function doClone(projectId, opts2) {
66260
66473
  timeout: CLONE_TIMEOUT_MS
66261
66474
  });
66262
66475
  if (!res.ok) {
66263
- rmSync9(dest, { recursive: true, force: true });
66476
+ rmSync10(dest, { recursive: true, force: true });
66264
66477
  const auth = AUTH_RE.test(res.stderr);
66265
66478
  throw new CheckoutError(
66266
66479
  auth ? useAuth ? `token GitHub ditolak atau tanpa akses ke ${remote}` : `akses ke ${remote} ditolak: isi GITHUB_TOKEN di Settings atau hubungkan instance ini ke hub` : `git clone ${remote} gagal`,
@@ -66307,7 +66520,7 @@ var init_project_checkout = __esm({
66307
66520
  CLONE_TIMEOUT_MS = 10 * 6e4;
66308
66521
  FETCH_TIMEOUT_MS = 2 * 6e4;
66309
66522
  AUTH_RE = /Authentication failed|could not read (Username|Password)|Invalid username or password|HTTP 40[13]|returned error: 40[13]|Repository not found|Permission denied \(publickey\)/i;
66310
- checkoutRoot = () => join35(resolveHome(), "projects");
66523
+ checkoutRoot = () => join38(resolveHome(), "projects");
66311
66524
  slug = (id2) => id2.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "project";
66312
66525
  sameRemote = (a, b) => {
66313
66526
  const norm5 = (u) => httpsRemote(u).replace(/\.git$/, "").replace(/\/+$/, "").toLowerCase();
@@ -66316,7 +66529,7 @@ var init_project_checkout = __esm({
66316
66529
  redact = (text2, token) => token ? text2.split(token).join("***") : text2;
66317
66530
  isEmptyDir = (dir2) => {
66318
66531
  try {
66319
- return readdirSync8(dir2).length === 0;
66532
+ return readdirSync10(dir2).length === 0;
66320
66533
  } catch {
66321
66534
  return false;
66322
66535
  }
@@ -67085,7 +67298,7 @@ var init_approvals = __esm({
67085
67298
  });
67086
67299
 
67087
67300
  // src/services/telegram/gateway.ts
67088
- import { createHash as createHash14 } from "node:crypto";
67301
+ import { createHash as createHash15 } from "node:crypto";
67089
67302
  var digestRaw, TelegramGateway;
67090
67303
  var init_gateway = __esm({
67091
67304
  "src/services/telegram/gateway.ts"() {
@@ -67094,7 +67307,7 @@ var init_gateway = __esm({
67094
67307
  init_protocol();
67095
67308
  init_runtime();
67096
67309
  init_typing();
67097
- digestRaw = (update2) => createHash14("sha256").update(JSON.stringify(update2)).digest("hex");
67310
+ digestRaw = (update2) => createHash15("sha256").update(JSON.stringify(update2)).digest("hex");
67098
67311
  TelegramGateway = class {
67099
67312
  constructor(deps) {
67100
67313
  this.deps = deps;
@@ -67418,14 +67631,14 @@ var init_engine_command = __esm({
67418
67631
  });
67419
67632
 
67420
67633
  // src/services/telegram/session.ts
67421
- import { createHash as createHash15 } from "node:crypto";
67634
+ import { createHash as createHash16 } from "node:crypto";
67422
67635
  var chatHash, telegramOperatorSessionId, formatTelegramTurn, TelegramSessionCoordinator;
67423
67636
  var init_session = __esm({
67424
67637
  "src/services/telegram/session.ts"() {
67425
67638
  "use strict";
67426
67639
  init_src2();
67427
67640
  init_engine_command();
67428
- chatHash = (chatId) => createHash15("sha256").update(chatId).digest("hex").slice(0, 16);
67641
+ chatHash = (chatId) => createHash16("sha256").update(chatId).digest("hex").slice(0, 16);
67429
67642
  telegramOperatorSessionId = (chatId) => `telegram-${chatHash(chatId)}`;
67430
67643
  formatTelegramTurn = (input) => `[Telegram update ${input.updateId} \xB7 chat ${input.chatId} \xB7 kind ${input.kind}]
67431
67644
  ${input.text}`;
@@ -67822,7 +68035,7 @@ __export(bootstrap_exports, {
67822
68035
  reloadTelegramGateway: () => reloadTelegramGateway,
67823
68036
  telegramSessionDeps: () => telegramSessionDeps
67824
68037
  });
67825
- import { mkdirSync as mkdirSync15 } from "node:fs";
68038
+ import { mkdirSync as mkdirSync16 } from "node:fs";
67826
68039
  function parseTelegramAllowedUserIds(raw) {
67827
68040
  const ids = raw.split(/[\s,]+/).filter(Boolean);
67828
68041
  if (!ids.length) throw new Error("Telegram allowlist wajib berisi numeric user id");
@@ -67847,7 +68060,7 @@ function telegramSessionDeps(input) {
67847
68060
  home: resolveHome(),
67848
68061
  apiBase: input.apiBase,
67849
68062
  agentToken: input.agentToken,
67850
- ensureDir: (path) => mkdirSync15(path, { recursive: true })
68063
+ ensureDir: (path) => mkdirSync16(path, { recursive: true })
67851
68064
  };
67852
68065
  }
67853
68066
  async function productionFactory(input) {
@@ -68069,17 +68282,17 @@ var init_codex_version = __esm({
68069
68282
  });
68070
68283
 
68071
68284
  // src/services/agent-tool-catalog.ts
68072
- import { readFileSync as readFileSync26 } from "node:fs";
68285
+ import { readFileSync as readFileSync27 } from "node:fs";
68073
68286
  import { homedir as homedir13 } from "node:os";
68074
- import { join as join49 } from "node:path";
68287
+ import { join as join51 } from "node:path";
68075
68288
  function mcpServerNames(repoDir) {
68076
68289
  const names = [];
68077
- const claudeJson = readJson2(join49(home(), ".claude.json"));
68290
+ const claudeJson = readJson2(join51(home(), ".claude.json"));
68078
68291
  names.push(...serversOf(claudeJson));
68079
68292
  if (repoDir) {
68080
68293
  const projects = claudeJson?.projects;
68081
68294
  if (projects && typeof projects === "object") names.push(...serversOf(projects[repoDir]));
68082
- names.push(...serversOf(readJson2(join49(repoDir, ".mcp.json"))));
68295
+ names.push(...serversOf(readJson2(join51(repoDir, ".mcp.json"))));
68083
68296
  }
68084
68297
  names.push(...codexServers());
68085
68298
  return [...new Set(names.filter(Boolean))].sort((a, b) => a.localeCompare(b));
@@ -68095,7 +68308,7 @@ var init_agent_tool_catalog = __esm({
68095
68308
  home = () => process.env.HOME || homedir13();
68096
68309
  readJson2 = (path) => {
68097
68310
  try {
68098
- return JSON.parse(readFileSync26(path, "utf8"));
68311
+ return JSON.parse(readFileSync27(path, "utf8"));
68099
68312
  } catch {
68100
68313
  return null;
68101
68314
  }
@@ -68108,7 +68321,7 @@ var init_agent_tool_catalog = __esm({
68108
68321
  codexServers = () => {
68109
68322
  let text2;
68110
68323
  try {
68111
- text2 = readFileSync26(join49(home(), ".codex", "config.toml"), "utf8");
68324
+ text2 = readFileSync27(join51(home(), ".codex", "config.toml"), "utf8");
68112
68325
  } catch {
68113
68326
  return [];
68114
68327
  }
@@ -68124,7 +68337,7 @@ var init_agent_tool_catalog = __esm({
68124
68337
  });
68125
68338
 
68126
68339
  // src/services/builtin-agents.ts
68127
- import { createHash as createHash19 } from "node:crypto";
68340
+ import { createHash as createHash20 } from "node:crypto";
68128
68341
  async function seedBuiltinAgents() {
68129
68342
  try {
68130
68343
  const setting = await getSetting();
@@ -68226,7 +68439,7 @@ var init_builtin_agents2 = __esm({
68226
68439
  init_settings3();
68227
68440
  init_tombstone();
68228
68441
  init_sync_notify();
68229
- digest2 = (parts) => createHash19("sha256").update(parts.join(" ")).digest("hex").slice(0, 16);
68442
+ digest2 = (parts) => createHash20("sha256").update(parts.join(" ")).digest("hex").slice(0, 16);
68230
68443
  legacyFingerprint = (name2, description, instructions, tools) => digest2([name2, description, instructions, [...tools].join(",")]);
68231
68444
  mentionPart = (v) => {
68232
68445
  const m = mentionsOf(v);
@@ -71726,9 +71939,9 @@ import Fastify from "fastify";
71726
71939
  import fastifyStatic from "@fastify/static";
71727
71940
  import websocket from "@fastify/websocket";
71728
71941
  import cookie from "@fastify/cookie";
71729
- import { fileURLToPath as fileURLToPath5 } from "node:url";
71942
+ import { fileURLToPath as fileURLToPath6 } from "node:url";
71730
71943
  import { dirname as dirname19 } from "node:path";
71731
- import { existsSync as existsSync23 } from "node:fs";
71944
+ import { existsSync as existsSync25 } from "node:fs";
71732
71945
 
71733
71946
  // src/web-dir.ts
71734
71947
  import { resolve as resolve11 } from "node:path";
@@ -71782,7 +71995,7 @@ async function agent_doc_default(app3, opts2) {
71782
71995
  init_config2();
71783
71996
  init_db();
71784
71997
  init_uploads();
71785
- import { createHash as createHash9 } from "node:crypto";
71998
+ import { createHash as createHash10 } from "node:crypto";
71786
71999
 
71787
72000
  // src/services/upload-pipeline.ts
71788
72001
  init_file_type();
@@ -71792,7 +72005,7 @@ init_uploads();
71792
72005
  import { randomUUID as randomUUID6 } from "node:crypto";
71793
72006
  import { spawn as spawn2 } from "node:child_process";
71794
72007
  import { mkdir as mkdir3, rename, unlink as unlink3, writeFile as writeFile3 } from "node:fs/promises";
71795
- import { basename as basename7, isAbsolute as isAbsolute9, join as join21 } from "node:path";
72008
+ import { basename as basename7, isAbsolute as isAbsolute9, join as join23 } from "node:path";
71796
72009
  import sharp from "sharp";
71797
72010
  var UPLOAD_LIMITS = {
71798
72011
  fileBytes: 5 * 1024 * 1024,
@@ -71866,16 +72079,16 @@ function safeFilename2(input, extension2) {
71866
72079
  }
71867
72080
  async function commitToStorage(buffer, extension2, deps, beforePromote) {
71868
72081
  const storageDir = deps.storageDir ?? uploadDir();
71869
- const quarantineDir = join21(storageDir, ".quarantine");
72082
+ const quarantineDir = join23(storageDir, ".quarantine");
71870
72083
  await mkdir3(quarantineDir, { recursive: true, mode: 448 });
71871
72084
  await mkdir3(storageDir, { recursive: true, mode: 448 });
71872
- const quarantine = join21(quarantineDir, `${randomUUID6()}.upload`);
72085
+ const quarantine = join23(quarantineDir, `${randomUUID6()}.upload`);
71873
72086
  const storageKey = `${randomUUID6()}${extension2}`;
71874
72087
  await writeFile3(quarantine, buffer, { mode: 384, flag: "wx" });
71875
72088
  try {
71876
72089
  await timeout((deps.scanner ?? scannerFromEnv)(quarantine), UPLOAD_LIMITS.scanMs, "UPLOAD_SCAN");
71877
72090
  await beforePromote?.();
71878
- await rename(quarantine, join21(storageDir, storageKey));
72091
+ await rename(quarantine, join23(storageDir, storageKey));
71879
72092
  } catch (error) {
71880
72093
  await unlink3(quarantine).catch(() => {
71881
72094
  });
@@ -72277,7 +72490,7 @@ async function addQaAttachments(report, owner, files2) {
72277
72490
  continue;
72278
72491
  }
72279
72492
  try {
72280
- const sha256 = createHash9("sha256").update(await readUpload(safe3.storageKey)).digest("hex");
72493
+ const sha256 = createHash10("sha256").update(await readUpload(safe3.storageKey)).digest("hex");
72281
72494
  const row = await prisma.qaAttachment.create({ data: {
72282
72495
  reportId: report.id,
72283
72496
  projectId: report.projectId,
@@ -72331,7 +72544,7 @@ init_src();
72331
72544
  init_safe_repo_path();
72332
72545
  import { execFile as execFile3 } from "node:child_process";
72333
72546
  import { promisify as promisify2 } from "node:util";
72334
- import { existsSync as existsSync11, rmSync as rmSync7 } from "node:fs";
72547
+ import { existsSync as existsSync13, rmSync as rmSync8 } from "node:fs";
72335
72548
  import { resolve as resolve14 } from "node:path";
72336
72549
  var exec = promisify2(execFile3);
72337
72550
  async function listRepoDocs(repoDir) {
@@ -72356,12 +72569,12 @@ function docsDirOf(repoDir) {
72356
72569
  }
72357
72570
  function resolveIndex(repoDir, docsDir) {
72358
72571
  const rel = `${docsDir}/README.md`;
72359
- return existsSync11(resolve14(repoDir, rel)) ? rel : "";
72572
+ return existsSync13(resolve14(repoDir, rel)) ? rel : "";
72360
72573
  }
72361
72574
  var catOf = (rel) => rel.includes("/") ? rel.slice(0, rel.lastIndexOf("/")) : ".";
72362
72575
  var nameOf = (rel) => rel.includes("/") ? rel.slice(rel.lastIndexOf("/") + 1) : rel;
72363
72576
  async function scanRepoDocs(repoDir) {
72364
- if (!repoDir || !existsSync11(repoDir)) return { coverage: 0, tree: [] };
72577
+ if (!repoDir || !existsSync13(repoDir)) return { coverage: 0, tree: [] };
72365
72578
  const files2 = await listRepoDocs(repoDir);
72366
72579
  const docsDir = docsDirOf(repoDir);
72367
72580
  const index = resolveIndex(repoDir, docsDir);
@@ -72409,8 +72622,8 @@ function writeDocFile(repoDir, rel, content) {
72409
72622
  }
72410
72623
  function deleteDocFile(repoDir, rel) {
72411
72624
  const abs = docAbsPath(repoDir, rel);
72412
- if (!existsSync11(abs)) return false;
72413
- rmSync7(abs);
72625
+ if (!existsSync13(abs)) return false;
72626
+ rmSync8(abs);
72414
72627
  return true;
72415
72628
  }
72416
72629
 
@@ -72728,7 +72941,7 @@ async function projects_default(app3) {
72728
72941
  init_src();
72729
72942
  init_src2();
72730
72943
  init_integrate();
72731
- import { existsSync as existsSync14 } from "node:fs";
72944
+ import { existsSync as existsSync17 } from "node:fs";
72732
72945
  import { basename as basename9 } from "node:path";
72733
72946
 
72734
72947
  // src/services/worktree-delete.ts
@@ -72747,7 +72960,7 @@ init_spec_review2();
72747
72960
  init_safe_repo_path();
72748
72961
  import { execFile as execFile7 } from "node:child_process";
72749
72962
  import { promisify as promisify6 } from "node:util";
72750
- import { existsSync as existsSync12 } from "node:fs";
72963
+ import { existsSync as existsSync14 } from "node:fs";
72751
72964
  import { readdir as readdir3 } from "node:fs/promises";
72752
72965
  import { resolve as resolve16 } from "node:path";
72753
72966
  var exec5 = promisify6(execFile7);
@@ -72760,7 +72973,7 @@ function repoAbsPath(repoDir, rel, allowMissingFinal = false) {
72760
72973
  return resolve16(repoDir, rel);
72761
72974
  }
72762
72975
  async function listRepoTree(repoDir, ref = "") {
72763
- if (!repoDir || !existsSync12(repoDir)) return [];
72976
+ if (!repoDir || !existsSync14(repoDir)) return [];
72764
72977
  try {
72765
72978
  const { stdout } = ref ? await exec5("git", ["ls-tree", "-r", "--name-only", "-z", ref], { cwd: repoDir, ...GIT4 }) : await exec5("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], { cwd: repoDir, ...GIT4 });
72766
72979
  return [...new Set(splitZ2(stdout))].sort();
@@ -72770,7 +72983,7 @@ async function listRepoTree(repoDir, ref = "") {
72770
72983
  }
72771
72984
  async function listIgnoredEntries(repoDir) {
72772
72985
  const empty = { files: [], dirs: [] };
72773
- if (!repoDir || !existsSync12(repoDir)) return empty;
72986
+ if (!repoDir || !existsSync14(repoDir)) return empty;
72774
72987
  try {
72775
72988
  const { stdout } = await exec5(
72776
72989
  "git",
@@ -72823,7 +73036,7 @@ async function currentBranch(repoDir) {
72823
73036
  }
72824
73037
  async function repoStatus(repoDir) {
72825
73038
  const empty = { branch: "", ahead: 0, behind: 0, staged: [], unstaged: [], untracked: [], clean: true };
72826
- if (!repoDir || !existsSync12(repoDir)) return empty;
73039
+ if (!repoDir || !existsSync14(repoDir)) return empty;
72827
73040
  try {
72828
73041
  const { stdout } = await exec5("git", ["status", "--porcelain=v1", "-z", "-b"], { cwd: repoDir, ...GIT4 });
72829
73042
  const parts = stdout.split("\0");
@@ -72856,7 +73069,7 @@ async function repoStatus(repoDir) {
72856
73069
  }
72857
73070
  }
72858
73071
  async function listStashes(repoDir) {
72859
- if (!repoDir || !existsSync12(repoDir)) return [];
73072
+ if (!repoDir || !existsSync14(repoDir)) return [];
72860
73073
  try {
72861
73074
  const { stdout } = await exec5("git", ["stash", "list", `--format=%gd${US}%s${US}%aI`], { cwd: repoDir, ...GIT4 });
72862
73075
  return stdout.split("\n").filter(Boolean).map((line) => {
@@ -72868,7 +73081,7 @@ async function listStashes(repoDir) {
72868
73081
  }
72869
73082
  }
72870
73083
  async function listGraph(repoDir, limit = 200, opts2 = {}) {
72871
- if (!repoDir || !existsSync12(repoDir)) return { commits: [], current: "", total: 0 };
73084
+ if (!repoDir || !existsSync14(repoDir)) return { commits: [], current: "", total: 0 };
72872
73085
  try {
72873
73086
  const fmt = ["%H", "%P", "%an", "%aI", "%s", "%D"].join(US);
72874
73087
  const refArgs = opts2.branches?.length ? ["--end-of-options", ...opts2.branches] : [
@@ -72938,7 +73151,7 @@ async function changedOf(repoDir, sha2) {
72938
73151
  }
72939
73152
  async function compareCommits(repoDir, from, to) {
72940
73153
  const empty = { from, to, changed: [] };
72941
- if (!repoDir || !existsSync12(repoDir)) return empty;
73154
+ if (!repoDir || !existsSync14(repoDir)) return empty;
72942
73155
  try {
72943
73156
  const [num, name2] = await Promise.all([
72944
73157
  exec5("git", ["diff", "--numstat", "-z", "--no-renames", "--end-of-options", from, to], { cwd: repoDir, ...GIT4 }),
@@ -72950,7 +73163,7 @@ async function compareCommits(repoDir, from, to) {
72950
73163
  }
72951
73164
  }
72952
73165
  async function searchCommits(repoDir, q2, by = "all") {
72953
- if (!repoDir || !existsSync12(repoDir) || !q2) return [];
73166
+ if (!repoDir || !existsSync14(repoDir) || !q2) return [];
72954
73167
  const run6 = async (extra) => {
72955
73168
  try {
72956
73169
  return (await exec5("git", ["log", "--all", "--format=%H", ...extra], { cwd: repoDir, ...GIT4 })).stdout.split("\n").filter(Boolean);
@@ -73197,7 +73410,7 @@ async function runGitOp(repoDir, op) {
73197
73410
  }
73198
73411
  }
73199
73412
  async function workingStatus(repoDir) {
73200
- if (!repoDir || !existsSync12(repoDir)) return { branch: "", staged: [], unstaged: [] };
73413
+ if (!repoDir || !existsSync14(repoDir)) return { branch: "", staged: [], unstaged: [] };
73201
73414
  const [branch, staged, unstaged] = await Promise.all([
73202
73415
  currentBranch(repoDir),
73203
73416
  changedFiles(repoDir, ["--cached"]),
@@ -73206,7 +73419,7 @@ async function workingStatus(repoDir) {
73206
73419
  return { branch, staged, unstaged };
73207
73420
  }
73208
73421
  async function workingFileDiff(repoDir, path, staged) {
73209
- if (!repoDir || !existsSync12(repoDir)) return null;
73422
+ if (!repoDir || !existsSync14(repoDir)) return null;
73210
73423
  repoAbsPath(repoDir, path, true);
73211
73424
  const changed = staged ? await changedFiles(repoDir, ["--cached"]) : await withTempIndex(repoDir, (env) => changedFiles(repoDir, [], env));
73212
73425
  const cf = changed.find((c) => c.path === path);
@@ -73415,7 +73628,7 @@ async function deleteBranches(repoDir, names, opts2) {
73415
73628
  import { execFile as execFile9 } from "node:child_process";
73416
73629
  import { realpathSync as realpathSync8 } from "node:fs";
73417
73630
  import { stat as stat5 } from "node:fs/promises";
73418
- import { basename as basename8, dirname as dirname12, join as join26, resolve as resolve17, sep as sep7 } from "node:path";
73631
+ import { basename as basename8, dirname as dirname13, join as join28, resolve as resolve17, sep as sep8 } from "node:path";
73419
73632
  import { promisify as promisify8 } from "node:util";
73420
73633
  var exec7 = promisify8(execFile9);
73421
73634
  var GIT6 = { timeout: 6e4, maxBuffer: 1 << 24, encoding: "utf8" };
@@ -73456,20 +73669,20 @@ var real = (p3) => {
73456
73669
  return realpathSync8(p3);
73457
73670
  } catch {
73458
73671
  const path = resolve17(p3);
73459
- const parent = dirname12(path);
73460
- return parent === path ? path : join26(real(parent), basename8(path));
73672
+ const parent = dirname13(path);
73673
+ return parent === path ? path : join28(real(parent), basename8(path));
73461
73674
  }
73462
73675
  };
73463
73676
  function hasWorktreeSession(path, sessionId2, sessions) {
73464
73677
  const target3 = real(path);
73465
73678
  return sessions.some((s2) => {
73466
73679
  const current = real(s2.cwd);
73467
- return s2.id === sessionId2 || current === target3 || current.startsWith(target3 + sep7);
73680
+ return s2.id === sessionId2 || current === target3 || current.startsWith(target3 + sep8);
73468
73681
  });
73469
73682
  }
73470
73683
  async function bornAt(path) {
73471
73684
  try {
73472
- const st2 = await stat5(join26(path, ".git"));
73685
+ const st2 = await stat5(join28(path, ".git"));
73473
73686
  const ms = st2.birthtimeMs > 0 ? st2.birthtimeMs : st2.mtimeMs;
73474
73687
  return new Date(ms).toISOString();
73475
73688
  } catch {
@@ -73494,7 +73707,7 @@ async function listWorktrees(repoDir, inputs) {
73494
73707
  const rows = [];
73495
73708
  for (const w of raw) {
73496
73709
  const path = resolve17(w.path);
73497
- if (path === trash || path.startsWith(trash + sep7)) continue;
73710
+ if (path === trash || path.startsWith(trash + sep8)) continue;
73498
73711
  const name2 = basename8(path);
73499
73712
  const deletable = path !== baseReal && path !== mainPath;
73500
73713
  const spec = inputs.specs.get(name2);
@@ -73681,12 +73894,12 @@ async function recordHeadSha(specId, worktree, read2 = readHead) {
73681
73894
  init_local_binding();
73682
73895
 
73683
73896
  // src/services/session-worktree.ts
73684
- import { resolve as resolve18, sep as sep8 } from "node:path";
73897
+ import { resolve as resolve18, sep as sep9 } from "node:path";
73685
73898
  function ownsWorktree(repoDir, cwd) {
73686
73899
  const base3 = resolve18(repoDir);
73687
73900
  const dir2 = resolve18(cwd);
73688
73901
  const container = resolve18(base3, ".worktrees");
73689
- return dir2 !== base3 && dir2 !== container && dir2.startsWith(container + sep8);
73902
+ return dir2 !== base3 && dir2 !== container && dir2.startsWith(container + sep9);
73690
73903
  }
73691
73904
 
73692
73905
  // src/services/worktree-reaper.ts
@@ -73697,7 +73910,7 @@ init_notifications2();
73697
73910
  import { execFile as execFile11 } from "node:child_process";
73698
73911
  import { readdir as readdir4, rm as rm3 } from "node:fs/promises";
73699
73912
  import { promisify as promisify10 } from "node:util";
73700
- import { join as join27, resolve as resolve19 } from "node:path";
73913
+ import { join as join29, resolve as resolve19 } from "node:path";
73701
73914
  var TICK_MS = 6e4;
73702
73915
  var trashDirOf = (repoDir) => resolve19(repoDir, ".worktrees", ".trash");
73703
73916
  var exec8 = promisify10(execFile11);
@@ -73758,7 +73971,7 @@ async function sweepRepo(repoDir, projectId, deps = prodReaperDeps) {
73758
73971
  }
73759
73972
  let removed = 0;
73760
73973
  for (const entry2 of entries4) {
73761
- const path = join27(dir2, entry2);
73974
+ const path = join29(dir2, entry2);
73762
73975
  const known2 = pending.get(path);
73763
73976
  const row = known2 ?? { path, repoDir, projectId, entry: entry2, sessionId: sessionIdOf(entry2), since: Date.now() };
73764
73977
  pending.set(path, row);
@@ -74222,8 +74435,8 @@ function appendSourceHistory(current, entry2) {
74222
74435
 
74223
74436
  // src/services/spec-reset.ts
74224
74437
  import { execFile as execFile13 } from "node:child_process";
74225
- import { existsSync as existsSync13 } from "node:fs";
74226
- import { join as join28 } from "node:path";
74438
+ import { existsSync as existsSync15 } from "node:fs";
74439
+ import { join as join30 } from "node:path";
74227
74440
  import { promisify as promisify12 } from "node:util";
74228
74441
 
74229
74442
  // src/services/stage-artifacts.ts
@@ -74278,14 +74491,14 @@ async function planSpecReset(spec) {
74278
74491
  const repoDir = await resolveRepoDir(spec.projectId);
74279
74492
  if (!repoDir) return EMPTY3;
74280
74493
  const sid = sessionIdForSpec(spec.id);
74281
- const wt = join28(repoDir, ".worktrees", sid);
74494
+ const wt = join30(repoDir, ".worktrees", sid);
74282
74495
  const branch = `hanoman/${sid}`;
74283
74496
  const hasBranch = await shaResolvable(repoDir, `refs/heads/${branch}`);
74284
74497
  return {
74285
74498
  // Berkas fase seluruh stage yang ditinggalkan — rentang yang sama persis dengan revert stage
74286
74499
  // SPEC-167, karena tujuan akhirnya memang sama: item berdiri lagi di `brainstorming`.
74287
74500
  wouldDelete: await artifactsToRemove(spec.projectId, spec.id, "brainstorming", spec.stage),
74288
- worktree: existsSync13(wt) ? wt : null,
74501
+ worktree: existsSync15(wt) ? wt : null,
74289
74502
  branch: hasBranch ? branch : null
74290
74503
  };
74291
74504
  }
@@ -74362,6 +74575,61 @@ init_remote_session();
74362
74575
  init_registry();
74363
74576
  init_spec_attachment_dir();
74364
74577
  init_uploads();
74578
+
74579
+ // src/services/spec-mockups.ts
74580
+ init_local_binding();
74581
+ init_spec_review2();
74582
+ import { existsSync as existsSync16, readdirSync as readdirSync9, realpathSync as realpathSync10, statSync as statSync5 } from "node:fs";
74583
+ import { readFile as readFile7 } from "node:fs/promises";
74584
+ import { extname as extname2, join as join33, sep as sep10 } from "node:path";
74585
+ var MOCKUP_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*\.(png|jpe?g|webp)$/i;
74586
+ var MIME = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".webp": "image/webp" };
74587
+ var isDir = (p3) => {
74588
+ try {
74589
+ return statSync5(p3).isDirectory();
74590
+ } catch {
74591
+ return false;
74592
+ }
74593
+ };
74594
+ async function resolveMockupDir(spec) {
74595
+ const repoDir = await resolveRepoDir(spec.projectId);
74596
+ if (!repoDir) return null;
74597
+ const rel = join33("docs", "mockups", spec.id.toLowerCase());
74598
+ const worktree = join33(worktreeDir(repoDir, spec.id), rel);
74599
+ if (isDir(worktree)) return { source: "worktree", dir: worktree };
74600
+ const repo = join33(repoDir, rel);
74601
+ return isDir(repo) ? { source: "repo", dir: repo } : null;
74602
+ }
74603
+ async function listMockups(spec) {
74604
+ const found = await resolveMockupDir(spec);
74605
+ if (!found) return { source: null, dir: null, items: [] };
74606
+ const items = readdirSync9(found.dir).filter((n2) => MOCKUP_NAME.test(n2)).sort().flatMap((name2) => {
74607
+ try {
74608
+ const st2 = statSync5(join33(found.dir, name2));
74609
+ return st2.isFile() ? [{ name: name2, size: st2.size, modifiedAt: st2.mtime.toISOString() }] : [];
74610
+ } catch {
74611
+ return [];
74612
+ }
74613
+ });
74614
+ return { source: found.source, dir: found.dir, items };
74615
+ }
74616
+ async function readMockup(spec, name2) {
74617
+ if (!MOCKUP_NAME.test(name2)) return null;
74618
+ const found = await resolveMockupDir(spec);
74619
+ if (!found) return null;
74620
+ const file = join33(found.dir, name2);
74621
+ if (!existsSync16(file)) return null;
74622
+ try {
74623
+ const root = realpathSync10(found.dir);
74624
+ const real2 = realpathSync10(file);
74625
+ if (!real2.startsWith(root + sep10) || !statSync5(real2).isFile()) return null;
74626
+ return { buf: await readFile7(real2), mime: MIME[extname2(real2).toLowerCase()] ?? "application/octet-stream" };
74627
+ } catch {
74628
+ return null;
74629
+ }
74630
+ }
74631
+
74632
+ // src/routes/specs.ts
74365
74633
  init_stage_machine();
74366
74634
 
74367
74635
  // src/services/spec-docs.ts
@@ -76843,7 +77111,7 @@ init_stage_machine();
76843
77111
  init_notifications2();
76844
77112
  init_sync_notify();
76845
77113
  init_spec_deps();
76846
- import { createHash as createHash11 } from "node:crypto";
77114
+ import { createHash as createHash12 } from "node:crypto";
76847
77115
  var specNum = (id2) => Number.parseInt(id2.match(/\d+/)?.[0] ?? "0", 10);
76848
77116
  var selectOf = (shape) => Object.fromEntries(
76849
77117
  Object.keys(shape).filter((k) => k !== "blockedBy").map((k) => [k, true])
@@ -76935,7 +77203,7 @@ async function liveSignature() {
76935
77203
  async function specsDigest() {
76936
77204
  const agg = await prisma.spec.aggregate({ _count: { _all: true }, _max: { updatedAt: true }, _sum: { version: true } });
76937
77205
  const db = `${agg._count._all}:${agg._max.updatedAt?.getTime() ?? 0}:${agg._sum.version ?? 0}`;
76938
- return createHash11("sha1").update(`${db}|${await liveSignature()}`).digest("hex");
77206
+ return createHash12("sha1").update(`${db}|${await liveSignature()}`).digest("hex");
76939
77207
  }
76940
77208
  async function liveSpecs(filter = {}) {
76941
77209
  await liveOverlayTick();
@@ -77263,6 +77531,24 @@ ${item.context}` : item.context;
77263
77531
  await syncSpecAttachmentsDir(spec.id, spec.projectId);
77264
77532
  return reply.code(201).send(result);
77265
77533
  });
77534
+ app3.get("/specs/:id/mockups", async (req, reply) => {
77535
+ const { id: id2 } = req.params;
77536
+ const spec = await prisma.spec.findUnique({ where: { id: id2 }, select: { id: true, projectId: true } });
77537
+ if (!spec) return reply.code(404).send({ error: "not found" });
77538
+ return listMockups(spec);
77539
+ });
77540
+ app3.get("/specs/:id/mockups/:name", async (req, reply) => {
77541
+ const { id: id2, name: name2 } = req.params;
77542
+ const spec = await prisma.spec.findUnique({ where: { id: id2 }, select: { id: true, projectId: true } });
77543
+ const file = spec ? await readMockup(spec, name2) : null;
77544
+ if (!file) return reply.code(404).send({ error: "not found" });
77545
+ reply.header("content-type", file.mime);
77546
+ reply.header("content-disposition", `inline; filename="${name2}"`);
77547
+ reply.header("x-content-type-options", "nosniff");
77548
+ reply.header("content-security-policy", "sandbox; default-src 'none'");
77549
+ reply.header("cache-control", "no-cache");
77550
+ return reply.send(file.buf);
77551
+ });
77266
77552
  app3.get("/specs/:id/attachments/:attId", async (req, reply) => {
77267
77553
  const { id: id2, attId } = req.params;
77268
77554
  const a = await prisma.specAttachment.findUnique({ where: { id: attId } });
@@ -77358,7 +77644,7 @@ ${item.context}` : item.context;
77358
77644
  });
77359
77645
  const specWithProject = (id2) => prisma.spec.findUnique({ where: { id: id2 }, include: { project: true } });
77360
77646
  const resolveReview = async (repoDir, spec) => {
77361
- if (existsSync14(worktreeDir(repoDir, spec.id))) return { wt: true };
77647
+ if (existsSync17(worktreeDir(repoDir, spec.id))) return { wt: true };
77362
77648
  if (spec.baseSha && spec.headSha && await shaResolvable(repoDir, spec.baseSha) && await shaResolvable(repoDir, spec.headSha))
77363
77649
  return { wt: false, base: spec.baseSha, head: spec.headSha };
77364
77650
  const r = await specCommitRange(repoDir, spec.id);
@@ -77654,7 +77940,7 @@ import { spawn as spawn5 } from "node:child_process";
77654
77940
  // src/services/git-remotes.ts
77655
77941
  import { execFile as execFile16 } from "node:child_process";
77656
77942
  import { promisify as promisify15 } from "node:util";
77657
- import { existsSync as existsSync17 } from "node:fs";
77943
+ import { existsSync as existsSync20 } from "node:fs";
77658
77944
  var exec10 = promisify15(execFile16);
77659
77945
  var GIT7 = { maxBuffer: 1 << 24, encoding: "utf8" };
77660
77946
  function parseRemote(url2) {
@@ -77673,7 +77959,7 @@ function prUrl(remoteUrl, branch, base3 = "main") {
77673
77959
  return null;
77674
77960
  }
77675
77961
  async function listRemotes(repoDir) {
77676
- if (!repoDir || !existsSync17(repoDir)) return [];
77962
+ if (!repoDir || !existsSync20(repoDir)) return [];
77677
77963
  try {
77678
77964
  const { stdout } = await exec10("git", ["remote", "-v"], { cwd: repoDir, ...GIT7 });
77679
77965
  const map = /* @__PURE__ */ new Map();
@@ -77721,7 +78007,7 @@ init_integrate();
77721
78007
  init_safe_repo_path();
77722
78008
  import { lstat as lstat2, rename as rename4, rm as rm6 } from "node:fs/promises";
77723
78009
  import { createWriteStream } from "node:fs";
77724
- import { join as join36 } from "node:path";
78010
+ import { join as join39 } from "node:path";
77725
78011
  import { randomUUID as randomUUID11 } from "node:crypto";
77726
78012
  import { pipeline } from "node:stream/promises";
77727
78013
  var EntryExistsError = class extends Error {
@@ -77782,7 +78068,7 @@ async function saveUpload2(repoDir, rel, source, opts2 = {}) {
77782
78068
  if (current && !opts2.overwrite) return { status: "exists" };
77783
78069
  if (current && !current.isFile())
77784
78070
  throw new PathContainmentError("repository path ditolak: target bukan file regular");
77785
- const temp = join36(entry2.parent, `.hanoman-${randomUUID11()}.tmp`);
78071
+ const temp = join39(entry2.parent, `.hanoman-${randomUUID11()}.tmp`);
77786
78072
  try {
77787
78073
  await pipeline(source, createWriteStream(temp, { flags: "wx", mode: 384 }));
77788
78074
  if (opts2.isTruncated?.()) {
@@ -78273,15 +78559,15 @@ async function finishGraphOp(reply, id2, repoDir, r, verb) {
78273
78559
  // src/routes/fs.ts
78274
78560
  import { readdir as readdir8 } from "node:fs/promises";
78275
78561
  import { homedir as homedir9 } from "node:os";
78276
- import { resolve as resolve22, dirname as dirname15, join as join37 } from "node:path";
78562
+ import { resolve as resolve22, dirname as dirname16, join as join40 } from "node:path";
78277
78563
  async function fs_default(app3) {
78278
78564
  app3.get("/fs/browse", async (req, reply) => {
78279
78565
  const q2 = req.query.path;
78280
78566
  const dir2 = q2 && q2.trim() ? resolve22(q2.trim()) : homedir9();
78281
78567
  try {
78282
78568
  const ents = await readdir8(dir2, { withFileTypes: true });
78283
- const entries4 = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: join37(dir2, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
78284
- const parent = dirname15(dir2);
78569
+ const entries4 = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: join40(dir2, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
78570
+ const parent = dirname16(dir2);
78285
78571
  return { path: dir2, parent: parent === dir2 ? null : parent, entries: entries4 };
78286
78572
  } catch {
78287
78573
  return reply.code(400).send({ error: `tak bisa membaca folder "${dir2}"` });
@@ -78295,7 +78581,7 @@ init_src();
78295
78581
  init_src2();
78296
78582
  init_session_phases();
78297
78583
  init_spec_review2();
78298
- import { existsSync as existsSync18 } from "node:fs";
78584
+ import { existsSync as existsSync21 } from "node:fs";
78299
78585
  import { execFileSync as execFileSync9 } from "node:child_process";
78300
78586
  import { randomUUID as randomUUID13 } from "node:crypto";
78301
78587
  init_integrate();
@@ -78360,7 +78646,7 @@ function setBounded(map, key, value, maxKeys) {
78360
78646
  // src/services/auth.ts
78361
78647
  init_src2();
78362
78648
  init_db();
78363
- import { randomBytes as randomBytes4, scrypt as scryptCb, timingSafeEqual as timingSafeEqual4, createHash as createHash16 } from "node:crypto";
78649
+ import { randomBytes as randomBytes4, scrypt as scryptCb, timingSafeEqual as timingSafeEqual4, createHash as createHash17 } from "node:crypto";
78364
78650
  import { promisify as promisify16 } from "node:util";
78365
78651
  var scrypt = promisify16(scryptCb);
78366
78652
  var COOKIE_NAME = "hn_session";
@@ -78378,7 +78664,7 @@ async function verifyPassword(pw, stored) {
78378
78664
  return key.length === want.length && timingSafeEqual4(key, want);
78379
78665
  }
78380
78666
  var newSessionToken = () => randomBytes4(32).toString("base64url");
78381
- var sessionId = (token) => createHash16("sha256").update(token).digest("hex");
78667
+ var sessionId = (token) => createHash17("sha256").update(token).digest("hex");
78382
78668
  async function createSession2(userId) {
78383
78669
  const token = newSessionToken();
78384
78670
  await prisma.session.create({
@@ -78439,9 +78725,9 @@ function cookieOpts(req) {
78439
78725
 
78440
78726
  // src/services/device-token.ts
78441
78727
  init_db();
78442
- import { randomBytes as randomBytes5, createHash as createHash17 } from "node:crypto";
78728
+ import { randomBytes as randomBytes5, createHash as createHash18 } from "node:crypto";
78443
78729
  var newDeviceToken = () => randomBytes5(32).toString("base64url");
78444
- var tokenHash = (token) => createHash17("sha256").update(token).digest("hex");
78730
+ var tokenHash = (token) => createHash18("sha256").update(token).digest("hex");
78445
78731
  async function issueDeviceToken(userId, name2) {
78446
78732
  const token = newDeviceToken();
78447
78733
  const row = await prisma.deviceToken.create({ data: { userId, name: name2, tokenHash: tokenHash(token) } });
@@ -78654,14 +78940,14 @@ function installCommand(m, agent, shell = shellBin()) {
78654
78940
  init_local_binding();
78655
78941
 
78656
78942
  // src/services/terminal-diag.ts
78657
- import { appendFileSync as appendFileSync4, mkdirSync as mkdirSync16, statSync as statSync4, rmSync as rmSync10 } from "node:fs";
78658
- import { join as join38 } from "node:path";
78943
+ import { appendFileSync as appendFileSync4, mkdirSync as mkdirSync17, statSync as statSync6, rmSync as rmSync11 } from "node:fs";
78944
+ import { join as join41 } from "node:path";
78659
78945
  var DIAG_MAX_BYTES = 2 * 1024 * 1024;
78660
78946
  var KINDS = /* @__PURE__ */ new Set(["key", "comp", "data", "ack", "pred"]);
78661
78947
  var ID = /^[A-Za-z0-9_-]{1,64}$/;
78662
78948
  function diagFile(home3, sessionId2) {
78663
78949
  if (!ID.test(sessionId2)) throw new Error(`id sesi tak sah untuk diag: ${sessionId2}`);
78664
- return join38(home3, "diag", `${sessionId2}.jsonl`);
78950
+ return join41(home3, "diag", `${sessionId2}.jsonl`);
78665
78951
  }
78666
78952
  function usable(ev) {
78667
78953
  if (!ev || typeof ev !== "object") return false;
@@ -78672,9 +78958,9 @@ function appendDiag(home3, sessionId2, events) {
78672
78958
  const file = diagFile(home3, sessionId2);
78673
78959
  const rows = events.filter(usable);
78674
78960
  if (!rows.length) return;
78675
- mkdirSync16(join38(home3, "diag"), { recursive: true });
78961
+ mkdirSync17(join41(home3, "diag"), { recursive: true });
78676
78962
  try {
78677
- if (statSync4(file).size > DIAG_MAX_BYTES) rmSync10(file);
78963
+ if (statSync6(file).size > DIAG_MAX_BYTES) rmSync11(file);
78678
78964
  } catch {
78679
78965
  }
78680
78966
  appendFileSync4(file, rows.map((r) => JSON.stringify(r)).join("\n") + "\n");
@@ -78754,11 +79040,11 @@ init_uploads();
78754
79040
  // src/services/agent-invocations.ts
78755
79041
  init_src();
78756
79042
  init_db();
78757
- import { createHash as createHash18, randomUUID as randomUUID12 } from "node:crypto";
79043
+ import { createHash as createHash19, randomUUID as randomUUID12 } from "node:crypto";
78758
79044
  import { execFileSync as execFileSync8 } from "node:child_process";
78759
79045
  import { homedir as homedir10 } from "node:os";
78760
- import { readFileSync as readFileSync19, realpathSync as realpathSync10, statSync as statSync5 } from "node:fs";
78761
- import { isAbsolute as isAbsolute12, relative as relative7, resolve as resolve23 } from "node:path";
79046
+ import { readFileSync as readFileSync20, realpathSync as realpathSync11, statSync as statSync7 } from "node:fs";
79047
+ import { isAbsolute as isAbsolute12, relative as relative8, resolve as resolve23 } from "node:path";
78762
79048
 
78763
79049
  // src/services/session-event-relay.ts
78764
79050
  init_session_event_token();
@@ -78766,7 +79052,7 @@ init_session_event_spool();
78766
79052
  init_ingress_policy();
78767
79053
  import { constants as constants4 } from "node:fs";
78768
79054
  import { mkdir as mkdir8, open as open4, readdir as readdir9, rm as rm7 } from "node:fs/promises";
78769
- import { join as join39 } from "node:path";
79055
+ import { join as join42 } from "node:path";
78770
79056
  var MAX_EVENT_BYTES = 1e6;
78771
79057
  var MAX_FILES_PER_DRAIN = 1e3;
78772
79058
  var SESSION_ID_RE = /^[a-z0-9_-]+$/;
@@ -78817,7 +79103,7 @@ async function drainSpool(app3, root, observation, host2) {
78817
79103
  let readBuffer;
78818
79104
  for (const session of await readdir9(root, { withFileTypes: true })) {
78819
79105
  if (!session.isDirectory() || !SESSION_ID_RE.test(session.name)) continue;
78820
- const dir2 = join39(root, session.name);
79106
+ const dir2 = join42(root, session.name);
78821
79107
  let entries4;
78822
79108
  try {
78823
79109
  entries4 = await readdir9(dir2, { withFileTypes: true });
@@ -78829,7 +79115,7 @@ async function drainSpool(app3, root, observation, host2) {
78829
79115
  if (!entry2.isFile() || !entry2.name.endsWith(".json")) continue;
78830
79116
  const eventAt = /^(\d{1,15})-/.exec(entry2.name)?.[1];
78831
79117
  if (++examined > MAX_FILES_PER_DRAIN) return delivered;
78832
- const path = join39(dir2, entry2.name);
79118
+ const path = join42(dir2, entry2.name);
78833
79119
  let payload;
78834
79120
  let handle;
78835
79121
  try {
@@ -78916,7 +79202,7 @@ var MAX_TRANSCRIPT_BYTES2 = 10 * 1024 * 1024;
78916
79202
  var ANSI2 = /[\u001b\u009b](?:\][^\u0007]*(?:\u0007|\u001b\\)|\[[0-?]*[ -/]*[@-~]|[@-_])/g;
78917
79203
  var snapshotHashes = /* @__PURE__ */ new Map();
78918
79204
  var keyOf = (x) => `${x.sessionId}\0${x.runtimeInvocationId}`;
78919
- var hash3 = (value) => createHash18("sha256").update(value).digest("hex");
79205
+ var hash3 = (value) => createHash19("sha256").update(value).digest("hex");
78920
79206
  var defaultGitStatus = (cwd) => {
78921
79207
  try {
78922
79208
  return execFileSync8("git", ["-C", cwd, "status", "--porcelain=v1", "-z"], {
@@ -78951,7 +79237,7 @@ var transcriptRoots = () => [
78951
79237
  resolve23(process.env.CODEX_HOME ?? `${homedir10()}/.codex`)
78952
79238
  ];
78953
79239
  var inside = (path, root) => {
78954
- const rel = relative7(root, path);
79240
+ const rel = relative8(root, path);
78955
79241
  return rel === "" || !rel.startsWith("..") && !isAbsolute12(rel);
78956
79242
  };
78957
79243
  var EMPTY_USAGE = { inputTokens: null, outputTokens: null, cachedTokens: null };
@@ -79024,12 +79310,12 @@ function usageFromText(text2) {
79024
79310
  function transcriptUsage(path, roots = transcriptRoots()) {
79025
79311
  if (!path) return EMPTY_USAGE;
79026
79312
  try {
79027
- const real2 = realpathSync10(path);
79028
- const safeRoots = roots.map((root) => realpathSync10(root));
79313
+ const real2 = realpathSync11(path);
79314
+ const safeRoots = roots.map((root) => realpathSync11(root));
79029
79315
  if (!safeRoots.some((root) => inside(real2, root))) return EMPTY_USAGE;
79030
- const info = statSync5(real2);
79316
+ const info = statSync7(real2);
79031
79317
  if (!info.isFile() || info.size > MAX_TRANSCRIPT_BYTES2) return EMPTY_USAGE;
79032
- return usageFromText(readFileSync19(real2, "utf8"));
79318
+ return usageFromText(readFileSync20(real2, "utf8"));
79033
79319
  } catch {
79034
79320
  return EMPTY_USAGE;
79035
79321
  }
@@ -79314,25 +79600,25 @@ init_ask();
79314
79600
  // src/services/decision-guard.ts
79315
79601
  init_src2();
79316
79602
  init_db();
79317
- import { mkdirSync as mkdirSync17, readFileSync as readFileSync20, readdirSync as readdirSync9, rmSync as rmSync11, statSync as statSync6, writeFileSync as writeFileSync17 } from "node:fs";
79318
- import { join as join40 } from "node:path";
79603
+ import { mkdirSync as mkdirSync18, readFileSync as readFileSync21, readdirSync as readdirSync11, rmSync as rmSync12, statSync as statSync8, writeFileSync as writeFileSync18 } from "node:fs";
79604
+ import { join as join43 } from "node:path";
79319
79605
  var SESSION_HEADER = "x-hanoman-session";
79320
79606
  var SAFE_ID = /^[A-Za-z0-9._-]{1,200}$/;
79321
79607
  var launchers = /* @__PURE__ */ new Map();
79322
79608
  var STALE_MS = 30 * 24 * 60 * 6e4;
79323
- var launcherDir = (env = process.env) => join40(resolveHome(env), "session-launchers");
79609
+ var launcherDir = (env = process.env) => join43(resolveHome(env), "session-launchers");
79324
79610
  function recordLauncher(sessionId2, agentId, env = process.env) {
79325
79611
  if (!SAFE_ID.test(sessionId2)) return;
79326
79612
  const known2 = launchersFor(sessionId2, env);
79327
79613
  known2.add(agentId);
79328
79614
  try {
79329
79615
  const dir2 = launcherDir(env);
79330
- mkdirSync17(dir2, { recursive: true, mode: 448 });
79331
- writeFileSync17(join40(dir2, sessionId2), [...known2].join("\n"), { mode: 384 });
79616
+ mkdirSync18(dir2, { recursive: true, mode: 448 });
79617
+ writeFileSync18(join43(dir2, sessionId2), [...known2].join("\n"), { mode: 384 });
79332
79618
  const now = Date.now();
79333
- for (const f of readdirSync9(dir2)) {
79619
+ for (const f of readdirSync11(dir2)) {
79334
79620
  try {
79335
- if (now - statSync6(join40(dir2, f)).mtimeMs > STALE_MS) rmSync11(join40(dir2, f), { force: true });
79621
+ if (now - statSync8(join43(dir2, f)).mtimeMs > STALE_MS) rmSync12(join43(dir2, f), { force: true });
79336
79622
  } catch {
79337
79623
  }
79338
79624
  }
@@ -79344,7 +79630,7 @@ function launchersFor(sessionId2, env) {
79344
79630
  if (hot) return hot;
79345
79631
  const ids = /* @__PURE__ */ new Set();
79346
79632
  try {
79347
- for (const l of readFileSync20(join40(launcherDir(env), sessionId2), "utf8").split("\n")) if (l.trim()) ids.add(l.trim());
79633
+ for (const l of readFileSync21(join43(launcherDir(env), sessionId2), "utf8").split("\n")) if (l.trim()) ids.add(l.trim());
79348
79634
  } catch {
79349
79635
  }
79350
79636
  launchers.set(sessionId2, ids);
@@ -79796,7 +80082,7 @@ async function terminal_default(app3, opts2) {
79796
80082
  if (!repoDir) return { ok: false, code: 409, msg: "project belum punya repoDir" };
79797
80083
  if (!s2.cwd.includes("/.worktrees/"))
79798
80084
  return { ok: false, code: 409, msg: "belum ada worktree untuk di-review \u2014 jalankan/lanjutkan sesi dulu", empty: "no-worktree" };
79799
- if (!existsSync18(s2.cwd))
80085
+ if (!existsSync21(s2.cwd))
79800
80086
  return { ok: false, code: 409, msg: "belum ada worktree untuk di-review \u2014 jalankan/lanjutkan sesi dulu", empty: "no-commit" };
79801
80087
  return { ok: true, id: s2.id, repoDir };
79802
80088
  };
@@ -80059,7 +80345,7 @@ async function terminalWorkspaceRoutes(app3) {
80059
80345
  // src/routes/vps.ts
80060
80346
  init_db();
80061
80347
  init_src();
80062
- import { readFileSync as readFileSync25, existsSync as existsSync22 } from "node:fs";
80348
+ import { readFileSync as readFileSync26, existsSync as existsSync24 } from "node:fs";
80063
80349
  import { homedir as homedir12 } from "node:os";
80064
80350
 
80065
80351
  // src/vps/catalog/catalog.data.ts
@@ -80267,14 +80553,14 @@ function resolveComponents(ids, profile) {
80267
80553
  }
80268
80554
 
80269
80555
  // src/services/vps-provision.ts
80270
- import { readFileSync as readFileSync22 } from "node:fs";
80556
+ import { readFileSync as readFileSync23 } from "node:fs";
80271
80557
 
80272
80558
  // src/services/vps-ssh.ts
80273
80559
  init_config2();
80274
80560
  import { spawn as spawn6 } from "node:child_process";
80275
- import { mkdtempSync as mkdtempSync4, rmSync as rmSync12, writeFileSync as writeFileSync18 } from "node:fs";
80561
+ import { mkdtempSync as mkdtempSync4, rmSync as rmSync13, writeFileSync as writeFileSync19 } from "node:fs";
80276
80562
  import { tmpdir as tmpdir7 } from "node:os";
80277
- import { dirname as dirname16, join as join41 } from "node:path";
80563
+ import { dirname as dirname17, join as join44 } from "node:path";
80278
80564
  var sshBin = () => effectiveStr("HANOMAN_SSH_BIN") ?? "ssh";
80279
80565
  function consoleArgv(t) {
80280
80566
  return [
@@ -80289,9 +80575,9 @@ function consoleArgv(t) {
80289
80575
  ];
80290
80576
  }
80291
80577
  function askpassScript() {
80292
- const dir2 = mkdtempSync4(join41(tmpdir7(), "hanoman-askpass-"));
80293
- const path = join41(dir2, "askpass.sh");
80294
- writeFileSync18(path, `#!/bin/sh
80578
+ const dir2 = mkdtempSync4(join44(tmpdir7(), "hanoman-askpass-"));
80579
+ const path = join44(dir2, "askpass.sh");
80580
+ writeFileSync19(path, `#!/bin/sh
80295
80581
  printf '%s' "$HANOMAN_SSH_PASSWORD"
80296
80582
  `, { mode: 448 });
80297
80583
  return path;
@@ -80330,7 +80616,7 @@ function sshExec(t, remoteCmd, opts2 = {}) {
80330
80616
  const timer11 = setTimeout(() => p3.kill("SIGKILL"), opts2.timeoutMs ?? 6e4);
80331
80617
  const done = (r) => {
80332
80618
  clearTimeout(timer11);
80333
- if (askpass) rmSync12(dirname16(askpass), { recursive: true, force: true });
80619
+ if (askpass) rmSync13(dirname17(askpass), { recursive: true, force: true });
80334
80620
  resolve25(r);
80335
80621
  };
80336
80622
  p3.stdout.on("data", (d) => {
@@ -80350,26 +80636,10 @@ function sshExec(t, remoteCmd, opts2 = {}) {
80350
80636
 
80351
80637
  // src/services/vps-audit.ts
80352
80638
  init_db();
80353
- import { existsSync as existsSync20, readFileSync as readFileSync21 } from "node:fs";
80354
- import { join as join43 } from "node:path";
80355
- import { fileURLToPath as fileURLToPath4 } from "node:url";
80356
-
80357
- // src/runner/deps.ts
80358
- import { existsSync as existsSync19 } from "node:fs";
80359
- import { dirname as dirname17, join as join42 } from "node:path";
80360
- function repoRootFrom(startDir) {
80361
- let dir2 = startDir;
80362
- for (let i = 0; i < 8; i++) {
80363
- if (existsSync19(join42(dir2, "pnpm-workspace.yaml"))) return dir2;
80364
- const parent = dirname17(dir2);
80365
- if (parent === dir2) break;
80366
- dir2 = parent;
80367
- }
80368
- return startDir;
80369
- }
80370
- var repoRoot = (startDir = process.cwd()) => repoRootFrom(startDir);
80371
-
80372
- // src/services/vps-audit.ts
80639
+ init_deps();
80640
+ import { existsSync as existsSync22, readFileSync as readFileSync22 } from "node:fs";
80641
+ import { join as join45 } from "node:path";
80642
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
80373
80643
  init_sync_notify();
80374
80644
 
80375
80645
  // src/vps/scoring.ts
@@ -80469,11 +80739,11 @@ function parseHealth(out4) {
80469
80739
  return { uptime: h2.uptime ?? "", disk: h2.disk ?? "", mem: h2.mem ?? "", load: h2.load ?? "" };
80470
80740
  }
80471
80741
  var target2 = (v) => ({ host: v.host, port: v.port, user: v.user, keyPath: v.keyPath });
80472
- var packagedScript = (f) => fileURLToPath4(new URL(`../scripts/vps/${f}`, import.meta.url));
80473
- var moduleDir = () => fileURLToPath4(new URL(".", import.meta.url));
80742
+ var packagedScript = (f) => fileURLToPath5(new URL(`../scripts/vps/${f}`, import.meta.url));
80743
+ var moduleDir2 = () => fileURLToPath5(new URL(".", import.meta.url));
80474
80744
  var scriptPath = (f) => {
80475
80745
  const packed = packagedScript(f);
80476
- return existsSync20(packed) ? packed : join43(repoRoot(moduleDir()), "server", "scripts", "vps", f);
80746
+ return existsSync22(packed) ? packed : join45(repoRoot(moduleDir2()), "server", "scripts", "vps", f);
80477
80747
  };
80478
80748
  async function itemStatesOf(vpsId) {
80479
80749
  const rows = await prisma.vpsItemState.findMany({ where: { vpsId } });
@@ -80483,7 +80753,7 @@ async function runAudit(v) {
80483
80753
  const r = await sshExec(
80484
80754
  target2(v),
80485
80755
  "sudo -n bash -s",
80486
- { stdin: readFileSync21(scriptPath("audit.sh"), "utf8"), timeoutMs: 6e4 }
80756
+ { stdin: readFileSync22(scriptPath("audit.sh"), "utf8"), timeoutMs: 6e4 }
80487
80757
  );
80488
80758
  const audit = parseAudit(r.out);
80489
80759
  if (r.code !== 0 || audit.length === 0) return { ok: false, out: r.out };
@@ -80563,7 +80833,7 @@ function parseProvisionSteps(out4) {
80563
80833
  return m ? [{ item: m[1], status: m[2], detail: (m[3] ?? "").trim() }] : [];
80564
80834
  });
80565
80835
  }
80566
- var script = () => readFileSync22(scriptPath("provision.sh"), "utf8");
80836
+ var script = () => readFileSync23(scriptPath("provision.sh"), "utf8");
80567
80837
  async function probeComponents(v) {
80568
80838
  const r = await sshExec(v, "env MODE=probe bash -s", { stdin: script(), timeoutMs: PROBE_TIMEOUT_MS });
80569
80839
  const components2 = parseComponents(r.out);
@@ -80590,7 +80860,7 @@ async function readSetupToken(v, home3 = "/var/lib/hanoman") {
80590
80860
  }
80591
80861
 
80592
80862
  // src/services/vps-remediate.ts
80593
- import { readFileSync as readFileSync23 } from "node:fs";
80863
+ import { readFileSync as readFileSync24 } from "node:fs";
80594
80864
  function parseSteps2(out4) {
80595
80865
  return out4.split("\n").flatMap((line) => {
80596
80866
  const m = line.match(/^STEP (\S+) (would|ok|fail)(?: (.*))?$/);
@@ -80601,7 +80871,7 @@ async function remediate(v, items, dryRun) {
80601
80871
  const r = await sshExec(
80602
80872
  v,
80603
80873
  `sudo -n env ITEMS=${items.join(",")} SSH_PORT=${v.port} DRY_RUN=${dryRun ? "1" : ""} bash -s`,
80604
- { stdin: readFileSync23(scriptPath("remediate.sh"), "utf8"), timeoutMs: 3e5 }
80874
+ { stdin: readFileSync24(scriptPath("remediate.sh"), "utf8"), timeoutMs: 3e5 }
80605
80875
  );
80606
80876
  return { ok: r.code === 0, steps: parseSteps2(r.out), out: r.out };
80607
80877
  }
@@ -80718,47 +80988,47 @@ async function listVpsWithScore() {
80718
80988
  init_src2();
80719
80989
  init_config2();
80720
80990
  import { execFileSync as execFileSync10 } from "node:child_process";
80721
- import { chmodSync as chmodSync9, copyFileSync, existsSync as existsSync21, mkdirSync as mkdirSync18, readFileSync as readFileSync24, unlinkSync as unlinkSync5 } from "node:fs";
80991
+ import { chmodSync as chmodSync10, copyFileSync, existsSync as existsSync23, mkdirSync as mkdirSync19, readFileSync as readFileSync25, unlinkSync as unlinkSync5 } from "node:fs";
80722
80992
  import { homedir as homedir11 } from "node:os";
80723
- import { join as join44 } from "node:path";
80993
+ import { join as join46 } from "node:path";
80724
80994
  var keyDir = () => effectiveStr("HANOMAN_SSH_KEY_DIR") ?? resolveDataDirs().sshKeys;
80725
80995
  var KEY_FILES = ["id_ed25519", "id_ed25519.pub"];
80726
80996
  function adoptLegacyKey(dir2) {
80727
80997
  if (effectiveStr("HANOMAN_SSH_KEY_DIR")) return;
80728
- const legacy = join44(homedir11(), ".hanoman");
80998
+ const legacy = join46(homedir11(), ".hanoman");
80729
80999
  if (legacy === dir2) return;
80730
- if (!KEY_FILES.every((f) => existsSync21(join44(legacy, f)))) return;
80731
- mkdirSync18(dir2, { recursive: true, mode: 448 });
81000
+ if (!KEY_FILES.every((f) => existsSync23(join46(legacy, f)))) return;
81001
+ mkdirSync19(dir2, { recursive: true, mode: 448 });
80732
81002
  for (const f of KEY_FILES) {
80733
- copyFileSync(join44(legacy, f), join44(dir2, f));
80734
- chmodSync9(join44(dir2, f), 384);
81003
+ copyFileSync(join46(legacy, f), join46(dir2, f));
81004
+ chmodSync10(join46(dir2, f), 384);
80735
81005
  }
80736
- for (const f of KEY_FILES) unlinkSync5(join44(legacy, f));
81006
+ for (const f of KEY_FILES) unlinkSync5(join46(legacy, f));
80737
81007
  console.log(`vps: key SSH dipindah dari ${legacy} ke ${dir2} (SPEC-846 \u2014 satu batas backup)`);
80738
81008
  }
80739
81009
  function ensureHanomanKey() {
80740
81010
  const dir2 = keyDir();
80741
- const privPath = join44(dir2, KEY_FILES[0]);
81011
+ const privPath = join46(dir2, KEY_FILES[0]);
80742
81012
  const pubPath = `${privPath}.pub`;
80743
- if (!existsSync21(privPath)) adoptLegacyKey(dir2);
80744
- if (!existsSync21(privPath)) {
80745
- mkdirSync18(dir2, { recursive: true, mode: 448 });
81013
+ if (!existsSync23(privPath)) adoptLegacyKey(dir2);
81014
+ if (!existsSync23(privPath)) {
81015
+ mkdirSync19(dir2, { recursive: true, mode: 448 });
80746
81016
  execFileSync10(
80747
81017
  "ssh-keygen",
80748
81018
  ["-t", "ed25519", "-N", "", "-C", "hanoman", "-f", privPath],
80749
81019
  { stdio: ["ignore", "ignore", "pipe"] }
80750
81020
  );
80751
81021
  }
80752
- return { privPath, pubPath, pub: readFileSync24(pubPath, "utf8").trim() };
81022
+ return { privPath, pubPath, pub: readFileSync25(pubPath, "utf8").trim() };
80753
81023
  }
80754
81024
 
80755
81025
  // src/services/vps-bootstrap.ts
80756
81026
  var INSTALL_CMD = `mkdir -p ~/.ssh && chmod 700 ~/.ssh && touch ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys && k=$(cat) && { grep -qxF "$k" ~/.ssh/authorized_keys || printf '%s\\n' "$k" >> ~/.ssh/authorized_keys; }`;
80757
81027
  async function bootstrapKey(t, password) {
80758
81028
  const { privPath, pub } = ensureHanomanKey();
80759
- const install = await sshExec(t, INSTALL_CMD, { stdin: `${pub}
81029
+ const install2 = await sshExec(t, INSTALL_CMD, { stdin: `${pub}
80760
81030
  `, password, timeoutMs: 6e4 });
80761
- if (install.code !== 0) return { ok: false, out: install.out };
81031
+ if (install2.code !== 0) return { ok: false, out: install2.out };
80762
81032
  const verify = await sshExec({ ...t, keyPath: privPath }, "true", { timeoutMs: 3e4 });
80763
81033
  if (verify.code !== 0) return { ok: false, out: verify.out };
80764
81034
  return { ok: true, keyPath: privPath };
@@ -80769,7 +81039,7 @@ init_session_launch_gate();
80769
81039
  init_settings3();
80770
81040
  init_sync_notify();
80771
81041
  function keyMissing(v) {
80772
- return !!v.keyPath && !existsSync22(v.keyPath);
81042
+ return !!v.keyPath && !existsSync24(v.keyPath);
80773
81043
  }
80774
81044
  async function vps_default(app3) {
80775
81045
  app3.get("/vps", async () => listVpsWithScore());
@@ -80997,7 +81267,7 @@ async function vps_default(app3) {
80997
81267
  const r = await sshExec(
80998
81268
  v,
80999
81269
  `sudo -n env SSH_PORT=${v.port} SSH_USER=${v.user} bash -s`,
81000
- { stdin: readFileSync25(scriptPath("harden.sh"), "utf8"), timeoutMs: 3e5 }
81270
+ { stdin: readFileSync26(scriptPath("harden.sh"), "utf8"), timeoutMs: 3e5 }
81001
81271
  );
81002
81272
  if (r.code !== 0) return reply.code(502).send({ error: "harden gagal lewat ssh", transcript: r.out });
81003
81273
  const verify = await sshExec(v, "true", { timeoutMs: 3e4 });
@@ -81146,8 +81416,8 @@ async function antigravity(app3) {
81146
81416
  init_src2();
81147
81417
  init_src();
81148
81418
  import { randomUUID as randomUUID14 } from "node:crypto";
81149
- import { readFile as readFile8, writeFile as writeFile6, rename as rename5, mkdir as mkdir9, rm as rm9 } from "node:fs/promises";
81150
- import { dirname as dirname18, join as join46 } from "node:path";
81419
+ import { readFile as readFile9, writeFile as writeFile6, rename as rename5, mkdir as mkdir9, rm as rm9 } from "node:fs/promises";
81420
+ import { dirname as dirname18, join as join48 } from "node:path";
81151
81421
 
81152
81422
  // src/services/model-catalog-parser.ts
81153
81423
  init_zod();
@@ -81254,7 +81524,7 @@ init_session_sandbox();
81254
81524
  import { spawn as spawn7 } from "node:child_process";
81255
81525
  import { mkdtemp as mkdtemp2, rm as rm8 } from "node:fs/promises";
81256
81526
  import { tmpdir as tmpdir8 } from "node:os";
81257
- import { join as join45 } from "node:path";
81527
+ import { join as join47 } from "node:path";
81258
81528
  var MAX_BYTES = 4 * 1024 * 1024;
81259
81529
  var TIMEOUT_MS = 2e4;
81260
81530
  var quote3 = (s2) => "'" + s2.replaceAll("'", "'\\''") + "'";
@@ -81353,7 +81623,7 @@ function runCatalogCommand(command, agent, cwd, env, timeoutMs = TIMEOUT_MS) {
81353
81623
  }
81354
81624
  async function probeModelCatalog(agent) {
81355
81625
  assertAntigravityLaunch(agent);
81356
- const cwd = await mkdtemp2(join45(tmpdir8(), "hanoman-models-"));
81626
+ const cwd = await mkdtemp2(join47(tmpdir8(), "hanoman-models-"));
81357
81627
  const env = agentEnvironment(agent, process.env);
81358
81628
  for (const key of ["HANOMAN_CLAUDE_BIN", "HANOMAN_CODEX_BIN", "HANOMAN_PODMAN_BIN", "HANOMAN_ANTIGRAVITY_BIN"]) {
81359
81629
  const value = effectiveStr(key);
@@ -81464,12 +81734,12 @@ async function writeModelCatalogCache(file, catalog, current = () => true) {
81464
81734
  await rm9(temp, { force: true });
81465
81735
  }
81466
81736
  }
81467
- var cacheFile = () => join46(resolveHome(), "model-catalog.json");
81737
+ var cacheFile = () => join48(resolveHome(), "model-catalog.json");
81468
81738
  var modelCatalogService = createModelCatalogService({
81469
81739
  probe: probeModelCatalog,
81470
81740
  key: catalogProbeKey,
81471
81741
  read: async () => {
81472
- const content = await readFile8(cacheFile(), "utf8");
81742
+ const content = await readFile9(cacheFile(), "utf8");
81473
81743
  if (Buffer.byteLength(content) > 4 * 1024 * 1024) throw new Error("oversized cache");
81474
81744
  return JSON.parse(content);
81475
81745
  },
@@ -82653,8 +82923,8 @@ init_db();
82653
82923
  init_pty();
82654
82924
  init_config5();
82655
82925
  init_inbox();
82656
- import { mkdirSync as mkdirSync19, writeFileSync as writeFileSync19 } from "node:fs";
82657
- import { join as join47 } from "node:path";
82926
+ import { mkdirSync as mkdirSync20, writeFileSync as writeFileSync20 } from "node:fs";
82927
+ import { join as join49 } from "node:path";
82658
82928
  init_human_input();
82659
82929
  var idle = /* @__PURE__ */ new Set();
82660
82930
  var sending = /* @__PURE__ */ new Set();
@@ -82665,7 +82935,7 @@ function isLeadIdle(id2) {
82665
82935
  return idle.has(id2);
82666
82936
  }
82667
82937
  var HUMAN_QUIET_MS = 12e4;
82668
- var batchDir = (projectId) => join47(resolveHome(), "lead-batches", projectId);
82938
+ var batchDir = (projectId) => join49(resolveHome(), "lead-batches", projectId);
82669
82939
  var briefFn = async () => "(situasi tak tersedia \u2014 panggil /brief)";
82670
82940
  function setBriefBuilder(fn) {
82671
82941
  briefFn = fn;
@@ -82685,9 +82955,9 @@ var prodCourierDeps = {
82685
82955
  brief: (p3) => briefFn(p3).catch(() => "(situasi tak tersedia \u2014 panggil /brief)"),
82686
82956
  write: (projectId, text2) => {
82687
82957
  const dir2 = batchDir(projectId);
82688
- mkdirSync19(dir2, { recursive: true });
82689
- const file = join47(dir2, `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.md`);
82690
- writeFileSync19(file, text2);
82958
+ mkdirSync20(dir2, { recursive: true });
82959
+ const file = join49(dir2, `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.md`);
82960
+ writeFileSync20(file, text2);
82691
82961
  return file;
82692
82962
  },
82693
82963
  send: (id2, line) => sendToPane(id2, line),
@@ -83485,7 +83755,7 @@ init_src();
83485
83755
  init_db();
83486
83756
  import { gunzipSync, gzipSync } from "node:zlib";
83487
83757
  import { mkdir as mkdir10, rename as rename6, writeFile as writeFile7 } from "node:fs/promises";
83488
- import { join as join48 } from "node:path";
83758
+ import { join as join50 } from "node:path";
83489
83759
 
83490
83760
  // src/services/device-auth.ts
83491
83761
  function bearerToken2(req) {
@@ -83743,9 +84013,9 @@ async function sync_default(app3) {
83743
84013
  const deviceId = req.device.id;
83744
84014
  const withTranscriptKey = await Promise.all(parsed.data.entries.map(async (e) => {
83745
84015
  if (!e.transcript) return e;
83746
- const dir2 = join48(resolveDataDirs().home, "remote-transcripts", deviceId);
84016
+ const dir2 = join50(resolveDataDirs().home, "remote-transcripts", deviceId);
83747
84017
  await mkdir10(dir2, { recursive: true, mode: 448 });
83748
- const final = join48(dir2, `${e.seq}.txt`);
84018
+ const final = join50(dir2, `${e.seq}.txt`);
83749
84019
  const tmp = `${final}.tmp`;
83750
84020
  await writeFile7(tmp, e.transcript, { encoding: "utf8", mode: 384 });
83751
84021
  await rename6(tmp, final);
@@ -84300,9 +84570,9 @@ init_db();
84300
84570
  init_db();
84301
84571
  init_uploads();
84302
84572
  init_src();
84303
- import { createHash as createHash20, randomBytes as randomBytes7 } from "node:crypto";
84573
+ import { createHash as createHash21, randomBytes as randomBytes7 } from "node:crypto";
84304
84574
  function hashAccessKey(key) {
84305
- return createHash20("sha256").update(key).digest("hex");
84575
+ return createHash21("sha256").update(key).digest("hex");
84306
84576
  }
84307
84577
  function generateAccessKey() {
84308
84578
  const key = "hnm_tkt_" + randomBytes7(24).toString("hex");
@@ -87148,9 +87418,9 @@ init_codex_trust();
87148
87418
  init_qa_attachment_transfer();
87149
87419
  init_uploads();
87150
87420
  init_ask_via2();
87151
- import { createHash as createHash21 } from "node:crypto";
87421
+ import { createHash as createHash22 } from "node:crypto";
87152
87422
  import { execFileSync as execFileSync11 } from "node:child_process";
87153
- import { join as join50 } from "node:path";
87423
+ import { join as join52 } from "node:path";
87154
87424
  var QaSessionError = class extends Error {
87155
87425
  constructor(code, message, needsBind = false) {
87156
87426
  super(message);
@@ -87158,7 +87428,7 @@ var QaSessionError = class extends Error {
87158
87428
  this.needsBind = needsBind;
87159
87429
  }
87160
87430
  };
87161
- var qaSessionId = (findingId) => `qa-${createHash21("sha256").update(findingId).digest("hex").slice(0, 24)}`;
87431
+ var qaSessionId = (findingId) => `qa-${createHash22("sha256").update(findingId).digest("hex").slice(0, 24)}`;
87162
87432
  async function startQaFindingSession(d, fid) {
87163
87433
  const f = d.findings.find((x) => x.id === fid);
87164
87434
  if (!f) throw new QaSessionError(404, "temuan tidak ditemukan");
@@ -87177,7 +87447,7 @@ async function startQaFindingSession(d, fid) {
87177
87447
  const saved = await saveSessionUpload(id2, bytes, a.mimeType);
87178
87448
  files2.push(`${JSON.stringify(a.filename)} (${a.mimeType}): ${saved.path}`);
87179
87449
  }
87180
- const wt = join50(repoDir, ".worktrees", id2);
87450
+ const wt = join52(repoDir, ".worktrees", id2);
87181
87451
  const branch = `qa/${id2.slice(3)}`;
87182
87452
  const reused = realGit.worktreeAlive(wt);
87183
87453
  try {
@@ -88720,9 +88990,9 @@ ${baru}`;
88720
88990
  // src/services/portal-chat/workspace.ts
88721
88991
  init_src();
88722
88992
  init_db();
88723
- import { mkdirSync as mkdirSync20, mkdtempSync as mkdtempSync5, rmSync as rmSync13, writeFileSync as writeFileSync20 } from "node:fs";
88993
+ import { mkdirSync as mkdirSync21, mkdtempSync as mkdtempSync5, rmSync as rmSync14, writeFileSync as writeFileSync21 } from "node:fs";
88724
88994
  import { tmpdir as tmpdir9 } from "node:os";
88725
- import { join as join51 } from "node:path";
88995
+ import { join as join53 } from "node:path";
88726
88996
  var STAGE_LABEL = {
88727
88997
  brainstorming: "Dirumuskan",
88728
88998
  objective: "Dirumuskan",
@@ -88765,7 +89035,7 @@ function renderTicketDoc(tickets2) {
88765
89035
  ${baris.join("\n")}`;
88766
89036
  }
88767
89037
  async function buildChatWorkspace(projectId) {
88768
- const dir2 = mkdtempSync5(join51(tmpdir9(), "hanoman-portal-chat-"));
89038
+ const dir2 = mkdtempSync5(join53(tmpdir9(), "hanoman-portal-chat-"));
88769
89039
  try {
88770
89040
  const project = await prisma.project.findUnique({
88771
89041
  where: { id: projectId },
@@ -88780,7 +89050,7 @@ async function buildChatWorkspace(projectId) {
88780
89050
  });
88781
89051
  const files2 = [];
88782
89052
  const tulis = (rel, isi) => {
88783
- writeFileSync20(join51(dir2, rel), isi, { mode: 384 });
89053
+ writeFileSync21(join53(dir2, rel), isi, { mode: 384 });
88784
89054
  files2.push(rel);
88785
89055
  };
88786
89056
  tulis("project.md", renderProjectDoc(project));
@@ -88788,16 +89058,16 @@ async function buildChatWorkspace(projectId) {
88788
89058
  tulis("laporan.md", renderTicketDoc(tickets2.map((t) => toPortalTicket(t, null))));
88789
89059
  const prds = await listPrds(projectId);
88790
89060
  if (prds.length) {
88791
- mkdirSync20(join51(dir2, "dokumen"), { mode: 448 });
89061
+ mkdirSync21(join53(dir2, "dokumen"), { mode: 448 });
88792
89062
  for (const prd of prds) {
88793
89063
  const isi = await readPrd(projectId, prd.path);
88794
89064
  if (!isi) continue;
88795
- tulis(join51("dokumen", `${prd.slug.replaceAll("/", "-")}.md`), isi);
89065
+ tulis(join53("dokumen", `${prd.slug.replaceAll("/", "-")}.md`), isi);
88796
89066
  }
88797
89067
  }
88798
- return { dir: dir2, files: files2, cleanup: () => rmSync13(dir2, { recursive: true, force: true }) };
89068
+ return { dir: dir2, files: files2, cleanup: () => rmSync14(dir2, { recursive: true, force: true }) };
88799
89069
  } catch (error) {
88800
- rmSync13(dir2, { recursive: true, force: true });
89070
+ rmSync14(dir2, { recursive: true, force: true });
88801
89071
  throw error;
88802
89072
  }
88803
89073
  }
@@ -89317,20 +89587,20 @@ init_src();
89317
89587
  init_db();
89318
89588
 
89319
89589
  // src/services/bootstrap.ts
89320
- import { createHash as createHash22, randomBytes as randomBytes11, timingSafeEqual as timingSafeEqual5 } from "node:crypto";
89321
- import { chmod as chmod2, lstat as lstat3, mkdir as mkdir11, readFile as readFile9, unlink as unlink5, writeFile as writeFile8 } from "node:fs/promises";
89322
- import { join as join52 } from "node:path";
89590
+ import { createHash as createHash23, randomBytes as randomBytes11, timingSafeEqual as timingSafeEqual5 } from "node:crypto";
89591
+ import { chmod as chmod2, lstat as lstat3, mkdir as mkdir11, readFile as readFile10, unlink as unlink5, writeFile as writeFile8 } from "node:fs/promises";
89592
+ import { join as join54 } from "node:path";
89323
89593
  var TTL_MS7 = 15 * 6e4;
89324
89594
  var SETUP_TOKEN_FILE = "setup.token";
89325
89595
  var BootstrapError = class extends Error {
89326
89596
  code = "BOOTSTRAP_PROOF";
89327
89597
  };
89328
- var tokenPath = (home3) => join52(home3, SETUP_TOKEN_FILE);
89598
+ var tokenPath = (home3) => join54(home3, SETUP_TOKEN_FILE);
89329
89599
  async function readStored(home3) {
89330
89600
  try {
89331
89601
  const info = await lstat3(tokenPath(home3));
89332
89602
  if (info.isSymbolicLink() || !info.isFile()) throw new BootstrapError("invalid setup proof");
89333
- const [token, expires] = (await readFile9(tokenPath(home3), "utf8")).trim().split("\n");
89603
+ const [token, expires] = (await readFile10(tokenPath(home3), "utf8")).trim().split("\n");
89334
89604
  const expiresAt = Date.parse(expires ?? "");
89335
89605
  if (!token || !Number.isFinite(expiresAt)) throw new BootstrapError("invalid setup proof");
89336
89606
  return { token, expiresAt };
@@ -89370,8 +89640,8 @@ ${new Date(expiresAt).toISOString()}
89370
89640
  }
89371
89641
  async function verifySetupToken(candidate, home3, now = Date.now()) {
89372
89642
  const stored = await readStored(home3);
89373
- const got = createHash22("sha256").update(candidate).digest();
89374
- const want = createHash22("sha256").update(stored.token).digest();
89643
+ const got = createHash23("sha256").update(candidate).digest();
89644
+ const want = createHash23("sha256").update(stored.token).digest();
89375
89645
  if (stored.expiresAt <= now || !timingSafeEqual5(got, want)) throw new BootstrapError("invalid setup proof");
89376
89646
  }
89377
89647
  async function consumeSetupToken(home3) {
@@ -90162,8 +90432,8 @@ init_db();
90162
90432
  init_event_log();
90163
90433
  init_transcript_store();
90164
90434
  init_src2();
90165
- import { readFile as readFile10 } from "node:fs/promises";
90166
- import { join as join53 } from "node:path";
90435
+ import { readFile as readFile11 } from "node:fs/promises";
90436
+ import { join as join55 } from "node:path";
90167
90437
  function decodeCursor(c) {
90168
90438
  if (!c) return null;
90169
90439
  try {
@@ -90201,9 +90471,9 @@ async function readRemoteTranscript(id2) {
90201
90471
  const row = await prisma.logEntry.findUnique({ where: { id: id2 } });
90202
90472
  if (!row || row.lane !== "transcript" || !row.transcriptKey) return null;
90203
90473
  if (row.deviceId === "local") return readTranscript(row.transcriptKey);
90204
- const path = join53(resolveDataDirs().home, "remote-transcripts", row.transcriptKey);
90474
+ const path = join55(resolveDataDirs().home, "remote-transcripts", row.transcriptKey);
90205
90475
  try {
90206
- return await readFile10(path, "utf8");
90476
+ return await readFile11(path, "utf8");
90207
90477
  } catch {
90208
90478
  return null;
90209
90479
  }
@@ -90398,7 +90668,7 @@ var SETUP_PUBLIC = /* @__PURE__ */ new Set([
90398
90668
  "POST /api/setup"
90399
90669
  ]);
90400
90670
  function buildApp({ requireAuth = true, agentDocFile, env = process.env } = {}) {
90401
- const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname19(fileURLToPath5(import.meta.url)), env, existsSync23);
90671
+ const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname19(fileURLToPath6(import.meta.url)), env, existsSync25);
90402
90672
  const ingress = loadIngressPolicy(env);
90403
90673
  const app3 = Fastify({ logger: false, trustProxy: trustProxyFromEnv(env) });
90404
90674
  app3.setErrorHandler((error, _req, reply) => {
@@ -90547,7 +90817,7 @@ function buildApp({ requireAuth = true, agentDocFile, env = process.env } = {})
90547
90817
  await api.register(qaBacklog);
90548
90818
  }, { prefix: "/api" });
90549
90819
  if (shouldServeWeb(env)) {
90550
- const dist = pickWebDir(dirname19(fileURLToPath5(import.meta.url)), env, existsSync23);
90820
+ const dist = pickWebDir(dirname19(fileURLToPath6(import.meta.url)), env, existsSync25);
90551
90821
  if (dist) {
90552
90822
  app3.register(fastifyStatic, { root: dist });
90553
90823
  app3.setNotFoundHandler((req, reply) => req.url.startsWith("/api") ? reply.code(404).send({ error: "not found" }) : req.url.startsWith("/assets/") ? reply.code(404).type("text/plain").send("not found") : reply.sendFile("index.html"));
@@ -90992,6 +91262,7 @@ async function resumeReconciledSessions(cutoff, deps = prodDeps4) {
90992
91262
  init_event_log();
90993
91263
  init_settings3();
90994
91264
  init_custom_agents2();
91265
+ init_bundled_skills();
90995
91266
  init_pty();
90996
91267
  init_bootstrap();
90997
91268
 
@@ -91483,9 +91754,9 @@ async function pruneLogs(now, retention, opts2 = {}) {
91483
91754
  }
91484
91755
  async function reconcileRemoteTranscripts() {
91485
91756
  const { readdir: readdir10, unlink: unlink6, stat: stat8 } = await import("node:fs/promises");
91486
- const { join: join54 } = await import("node:path");
91757
+ const { join: join56 } = await import("node:path");
91487
91758
  const { resolveDataDirs: resolveDataDirs2 } = await Promise.resolve().then(() => (init_src2(), src_exports));
91488
- const root = join54(resolveDataDirs2().home, "remote-transcripts");
91759
+ const root = join56(resolveDataDirs2().home, "remote-transcripts");
91489
91760
  let deviceDirs;
91490
91761
  try {
91491
91762
  deviceDirs = await readdir10(root);
@@ -91499,16 +91770,16 @@ async function reconcileRemoteTranscripts() {
91499
91770
  for (const dev of deviceDirs) {
91500
91771
  let files2;
91501
91772
  try {
91502
- files2 = await readdir10(join54(root, dev));
91773
+ files2 = await readdir10(join56(root, dev));
91503
91774
  } catch {
91504
91775
  continue;
91505
91776
  }
91506
91777
  for (const f of files2) {
91507
91778
  const key = `${dev}/${f}`;
91508
91779
  if (referenced.has(key)) continue;
91509
- const st2 = await stat8(join54(root, dev, f)).catch(() => null);
91780
+ const st2 = await stat8(join56(root, dev, f)).catch(() => null);
91510
91781
  if (!st2 || Date.now() - st2.mtimeMs < gracePeriodMs) continue;
91511
- await unlink6(join54(root, dev, f)).catch(() => {
91782
+ await unlink6(join56(root, dev, f)).catch(() => {
91512
91783
  });
91513
91784
  orphans++;
91514
91785
  }
@@ -91743,6 +92014,9 @@ bootstrapReady.then(async () => {
91743
92014
  console.error("config runtime gagal dimuat \u2014 memakai env/default:", e);
91744
92015
  }
91745
92016
  await installCustomAgents();
92017
+ const skills2 = seedBundledSkills(home2);
92018
+ if (skills2.seeded.length || skills2.updated.length)
92019
+ console.log(`skill bundel: di-seed ${skills2.seeded.join(", ") || "-"} \xB7 diperbarui ${skills2.updated.join(", ") || "-"}`);
91746
92020
  return app2.listen({ port, host });
91747
92021
  }).then(async () => {
91748
92022
  console.log(`hanoman api ${host}:${port}`);