hanoman 0.1.26 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -4256,6 +4256,130 @@ var init_spec_source = __esm({
4256
4256
  }
4257
4257
  });
4258
4258
 
4259
+ // ../shared/src/method-catalog.ts
4260
+ function resolveMethod(id) {
4261
+ return (id ? METHODS[id] : void 0) ?? METHODS[DEFAULT_METHOD];
4262
+ }
4263
+ function readSpecMethod(payload) {
4264
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
4265
+ const m = payload.method;
4266
+ return typeof m === "string" && m.trim() !== "" ? m.trim() : null;
4267
+ }
4268
+ function stampSpecMethod(payload, methodId) {
4269
+ if (payload === null || payload === void 0) return { method: methodId };
4270
+ if (typeof payload !== "object" || Array.isArray(payload)) return null;
4271
+ return { ...payload, method: methodId };
4272
+ }
4273
+ var DEFAULT_METHOD, VERIFICATION_GATE, MATT_CLAUSE, METHODS, METHOD_IDS, uniq, PLAN_DIRS, SPEC_DIRS;
4274
+ var init_method_catalog = __esm({
4275
+ "../shared/src/method-catalog.ts"() {
4276
+ "use strict";
4277
+ DEFAULT_METHOD = "superpowers";
4278
+ VERIFICATION_GATE = "superpowers:verification-before-completion";
4279
+ MATT_CLAUSE = "Sesi ini TAK BERPENUNGGU \u2014 tak ada manusia yang menonton terminal untuk menjawab. Skill mattpocock yang kontraknya mewawancarai manusia (`/grill-me`, `/to-spec`, `/triage`) JANGAN dipakai: pakai primitif model-invoked-nya dan putuskan sendiri. `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.";
4280
+ METHODS = {
4281
+ // Isi `PHASE_SKILLS` (SPEC-166) dipindah APA ADANYA. Objective & Spec adalah keluaran skill
4282
+ // brainstorming yang di-invoke di fase Brainstorm — sengaja tak punya entri sendiri. Fase reverse
4283
+ // dipandu standar docs di prompt-nya, bukan skill.
4284
+ superpowers: {
4285
+ id: "superpowers",
4286
+ label: "superpowers",
4287
+ planDir: "docs/superpowers/plans",
4288
+ specDir: "docs/superpowers/specs",
4289
+ phaseSkills: {
4290
+ Brainstorm: ["superpowers:brainstorming"],
4291
+ Audit: ["superpowers:systematic-debugging"],
4292
+ Plan: ["superpowers:writing-plans"],
4293
+ Execute: [
4294
+ "superpowers:executing-plans",
4295
+ "superpowers:test-driven-development",
4296
+ VERIFICATION_GATE
4297
+ ],
4298
+ // SPEC-407 · fase `Goal` sengaja TANPA skill: seluruh inti flow itu membebaskan sesi dari
4299
+ // proses kaku. Yang tetap dijaga cuma pintu keluarnya.
4300
+ Verifikasi: [VERIFICATION_GATE]
4301
+ },
4302
+ exitSkills: [VERIFICATION_GATE],
4303
+ requires: ["superpowers"],
4304
+ install: {
4305
+ claude: [
4306
+ "claude plugin marketplace add obra/superpowers-marketplace",
4307
+ "claude plugin install superpowers@superpowers-marketplace"
4308
+ ],
4309
+ codex: ["codex plugin add superpowers@openai-curated"]
4310
+ }
4311
+ },
4312
+ // Plugin `mattpocock-skills` (`/plugin install mattpocock-skills`); skill plugin di Claude Code
4313
+ // beralamat `plugin:skill`. `superpowers` tetap ikut di `requires` karena gerbang verifikasinya
4314
+ // dipinjam dari sana — mattpocock tak punya padanannya.
4315
+ matt: {
4316
+ id: "matt",
4317
+ label: "mattpocock",
4318
+ planDir: "docs/matt/plans",
4319
+ specDir: "docs/matt/specs",
4320
+ phaseSkills: {
4321
+ Brainstorm: ["mattpocock-skills:grilling"],
4322
+ Audit: ["mattpocock-skills:diagnosing-bugs"],
4323
+ Plan: ["mattpocock-skills:to-tickets"],
4324
+ Execute: [
4325
+ "mattpocock-skills:implement",
4326
+ "mattpocock-skills:tdd",
4327
+ "mattpocock-skills:code-review"
4328
+ ],
4329
+ Verifikasi: [VERIFICATION_GATE]
4330
+ },
4331
+ exitSkills: [VERIFICATION_GATE],
4332
+ extraClause: MATT_CLAUSE,
4333
+ requires: ["mattpocock-skills", "superpowers"],
4334
+ // `mattpocock-skills` ada di marketplace resmi Claude Code → tanpa `marketplace add`.
4335
+ // superpowers ikut dipasang karena `requires` menyebutnya: gerbang verifikasinya dipinjam
4336
+ // dari sana (INVARIAN 2). codex belum punya plugin native mattpocock — jalur resminya
4337
+ // `npx skills`, dan ia INTERAKTIF; itu justru alasan pemasangan hidup di pane yang ditonton.
4338
+ install: {
4339
+ claude: [
4340
+ "claude plugin install mattpocock-skills",
4341
+ "claude plugin marketplace add obra/superpowers-marketplace",
4342
+ "claude plugin install superpowers@superpowers-marketplace"
4343
+ ],
4344
+ codex: [
4345
+ "npx skills@latest add mattpocock/skills",
4346
+ "codex plugin add superpowers@openai-curated"
4347
+ ]
4348
+ }
4349
+ }
4350
+ };
4351
+ METHOD_IDS = Object.keys(METHODS);
4352
+ uniq = (xs) => [...new Set(xs)];
4353
+ PLAN_DIRS = uniq(METHOD_IDS.map((id) => METHODS[id].planDir));
4354
+ SPEC_DIRS = uniq(METHOD_IDS.map((id) => METHODS[id].specDir));
4355
+ }
4356
+ });
4357
+
4358
+ // ../shared/src/method-status.ts
4359
+ function methodSkills(m) {
4360
+ return [.../* @__PURE__ */ new Set([...Object.values(m.phaseSkills).flat(), ...m.exitSkills])];
4361
+ }
4362
+ function methodStatus(m, agent, installed) {
4363
+ const haveSkills = new Set(installed.skills);
4364
+ const havePackages = new Set(installed.packages);
4365
+ const missingPackages = m.requires.filter((p) => !havePackages.has(p));
4366
+ const missingSkills = methodSkills(m).filter((id) => !haveSkills.has(id));
4367
+ return {
4368
+ method: m.id,
4369
+ label: m.label,
4370
+ agent,
4371
+ ready: missingPackages.length === 0 && missingSkills.length === 0,
4372
+ missingPackages,
4373
+ missingSkills,
4374
+ install: [...m.install[agent]]
4375
+ };
4376
+ }
4377
+ var init_method_status = __esm({
4378
+ "../shared/src/method-status.ts"() {
4379
+ "use strict";
4380
+ }
4381
+ });
4382
+
4259
4383
  // ../shared/src/agent-engine.ts
4260
4384
  var zAgentEngine;
4261
4385
  var init_agent_engine = __esm({
@@ -4442,6 +4566,7 @@ var init_entities = __esm({
4442
4566
  init_telegram();
4443
4567
  init_agent_engine();
4444
4568
  init_auto_merge();
4569
+ init_method_catalog();
4445
4570
  zProject = external_exports.object({
4446
4571
  id: external_exports.string(),
4447
4572
  name: external_exports.string(),
@@ -4662,6 +4787,12 @@ var init_entities = __esm({
4662
4787
  // SPEC-338 · ADR-0074 · model/effort codex
4663
4788
  verifyScope: zVerifyScope.default("changed"),
4664
4789
  // SPEC-376 · ADR-0080 · scope verifikasi sesi
4790
+ // SPEC-734 · ADR-0113 · metode workflow default. LENIENT (`z.string()`, bukan `z.enum`):
4791
+ // instance yang di-sync dari hub bisa membawa id metode yang belum ada di build ini, dan itu
4792
+ // harus jadi fallback diam di titik pakai (`resolveMethod`) — bukan baris Setting yang gagal
4793
+ // parse lalu mengosongkan layar Settings. Alasan yang sama dengan model/effort di sini.
4794
+ method: external_exports.string().default(DEFAULT_METHOD),
4795
+ // SPEC-734 · ADR-0113 · metode workflow default
4665
4796
  conflict: zConflict.default(CONFLICT_DEFAULTS),
4666
4797
  // SPEC-383 · ADR-0081 · default sesi konflik rebase/merge
4667
4798
  lead: zLead.default(LEAD_DEFAULTS),
@@ -5651,7 +5782,15 @@ var init_dto = __esm({
5651
5782
  // SPEC-236 · terminal biasa NON-claude: shell mentah di repoDir project. Tanpa flow (bukan
5652
5783
  // pipeline claude). DIDAHULUKAN: z.object non-strict membuang key asing, jadi bila varian
5653
5784
  // longgar {project,flow?} lebih dulu, {project,shell:true} akan lolos sbg plain (shell dibuang).
5654
- external_exports.object({ project: external_exports.string(), shell: external_exports.literal(true) }),
5785
+ // SPEC-739 · ADR-0114 · pemasangan skill metode: klien mengirim METODE + AGEN, bukan teks
5786
+ // perintah — perintahnya diturunkan server dari katalog `METHODS`. Dengan begitu "perintah
5787
+ // instalasi adalah data katalog" berlaku ujung ke ujung, dan endpoint ini tak pernah menjadi
5788
+ // "jalankan shell arbitrer". Tanpa field ini, shell mentah persis seperti SPEC-236.
5789
+ external_exports.object({
5790
+ project: external_exports.string(),
5791
+ shell: external_exports.literal(true),
5792
+ install: external_exports.object({ method: external_exports.string(), agent: zAgent }).optional()
5793
+ }),
5655
5794
  // SPEC-166 · "reverse" = sesi project-level di worktree-nya sendiri, menyusun Source of Truth
5656
5795
  // dari kode. TANPA override runtime: sesi project-level mengikuti Setting.agent (ADR-0074).
5657
5796
  // Terminal biasa (tanpa flow) kini punya variannya SENDIRI di bawah — lihat SPEC-517.
@@ -5698,6 +5837,10 @@ var init_dto = __esm({
5698
5837
  goalCondition: external_exports.string().max(4e3).optional(),
5699
5838
  agent: zAgent.optional(),
5700
5839
  verifyScope: zVerifyScope.optional(),
5840
+ // SPEC-734 · ADR-0113 — metode workflow per SESI: undefined → Spec.payload.method →
5841
+ // Setting.method → "superpowers". LENIENT (`z.string()`, bukan `z.enum`): id yang tak dikenal
5842
+ // jatuh ke default di `resolveMethod`, bukan ditolak 400 — alasan yang sama dengan zSetting.
5843
+ method: external_exports.string().optional(),
5701
5844
  // SPEC-447 · ADR-0093 — lewati gerbang dependency. Hanya jalur manusia; UI hanya
5702
5845
  // mengirimkannya sesudah operator melihat daftar pemblokirnya.
5703
5846
  force: external_exports.boolean().optional()
@@ -5891,6 +6034,8 @@ var init_api = __esm({
5891
6034
  limits: `${API}/limits`,
5892
6035
  // SPEC-339 · versi codex CLI (peringatan lunak model GPT-5.6).
5893
6036
  codexVersion: `${API}/codex/version`,
6037
+ // SPEC-739 · ADR-0114 · kesiapan skill metode per agen. LOCAL-only, diturunkan live dari disk.
6038
+ methodStatus: `${API}/methods/status`,
5894
6039
  // SPEC-489 · panduan AI agent — markdown MENTAH & PUBLIC (tanpa auth). Satu definisi URL untuk
5895
6040
  // klien web dan untuk tautan yang disalin operator ke agennya.
5896
6041
  agentDoc: `${API}/agent-integration.md`,
@@ -7557,6 +7702,8 @@ var init_src = __esm({
7557
7702
  "use strict";
7558
7703
  init_enums();
7559
7704
  init_spec_source();
7705
+ init_method_catalog();
7706
+ init_method_status();
7560
7707
  init_agent_engine();
7561
7708
  init_entities();
7562
7709
  init_agent();
@@ -7811,54 +7958,60 @@ var init_goal_spec = __esm({
7811
7958
  });
7812
7959
 
7813
7960
  // ../runner/src/prompt.ts
7814
- function startPrompt(flow, spec, branchTo, autonomy, verifyScope) {
7961
+ function startPrompt(flow, spec, branchTo, autonomy, verifyScope, method) {
7962
+ const m = resolveMethod(method);
7815
7963
  const detail = spec.payload ? `
7816
7964
  Detail: ${JSON.stringify(spec.payload)}` : "";
7817
7965
  return [
7818
7966
  `hanoman ${flow}. Ikuti internal/docs sebagai Source of Truth; perbarui docs yang tersentuh dan link-nya di index, dalam commit yang sama.`,
7819
- phaseInstruction(PIPELINES[flow]),
7967
+ phaseInstruction(PIPELINES[flow], m),
7820
7968
  auditDecisionInstruction(flow),
7821
7969
  auditContinuationInstruction(flow, spec),
7822
7970
  auditOnlyInstruction(flow),
7823
7971
  autonomyClause(autonomy),
7824
7972
  scopeClause(flow, verifyScope),
7825
7973
  codeStyleClause(flow),
7826
- skillInstruction(PIPELINES[flow]),
7974
+ methodClause(m),
7975
+ skillInstruction(PIPELINES[flow], m, writesCode(flow)),
7827
7976
  `Setelah fase terakhir: commit, lalu \`git push origin HEAD:refs/heads/${branchTo}\`. Worktree ini detached HEAD \u2014 itu memang disengaja.`,
7828
7977
  `Backlog item ${spec.id} \xB7 sumber ${spec.source} \xB7 prioritas ${spec.priority}
7829
7978
  Judul: ${spec.title}
7830
7979
  Objective: ${spec.objective}${detail}`
7831
7980
  ].filter(Boolean).join("\n\n");
7832
7981
  }
7833
- function continuePrompt(flow, spec, branchTo, autonomy, verifyScope) {
7982
+ function continuePrompt(flow, spec, branchTo, autonomy, verifyScope, method) {
7983
+ const m = resolveMethod(method);
7834
7984
  const detail = spec.payload ? `
7835
7985
  Detail: ${JSON.stringify(spec.payload)}` : "";
7836
7986
  return [
7837
7987
  `hanoman ${flow} \u2014 MELANJUTKAN backlog item yang sebelumnya ditandai selesai padahal pekerjaannya belum tuntas. Ikuti internal/docs sebagai Source of Truth; perbarui docs yang tersentuh dan link-nya di index, dalam commit yang sama.`,
7838
- `JANGAN mengulang fase awal \u2014 spec & plan sudah ada. Lanjut di fase Execute: baca plan di docs/superpowers/plans/** untuk backlog item ini, periksa task yang sudah \`[x]\` dan selesaikan yang masih \`[ ]\`. Verifikasi nyata sebelum klaim selesai.`,
7988
+ `JANGAN mengulang fase awal \u2014 spec & plan sudah ada. Lanjut di fase Execute: baca plan di ${m.planDir}/** untuk backlog item ini, periksa task yang sudah \`[x]\` dan selesaikan yang masih \`[ ]\`. Verifikasi nyata sebelum klaim selesai.`,
7839
7989
  autonomyClause(autonomy),
7840
7990
  scopeClause(flow, verifyScope),
7841
7991
  codeStyleClause(flow),
7842
- skillInstruction(["Execute"]),
7992
+ methodClause(m),
7993
+ skillInstruction(["Execute"], m, writesCode(flow)),
7843
7994
  `Setelah selesai: commit, lalu \`git push origin HEAD:refs/heads/${branchTo}\`. Worktree ini detached HEAD \u2014 itu memang disengaja.`,
7844
7995
  `Backlog item ${spec.id} \xB7 sumber ${spec.source} \xB7 prioritas ${spec.priority}
7845
7996
  Judul: ${spec.title}
7846
7997
  Objective: ${spec.objective}${detail}`
7847
7998
  ].filter(Boolean).join("\n\n");
7848
7999
  }
7849
- function resumePrompt(flow, spec, branchTo, resume, autonomy, verifyScope) {
8000
+ function resumePrompt(flow, spec, branchTo, resume, autonomy, verifyScope, method) {
8001
+ const m = resolveMethod(method);
7850
8002
  const detail = spec.payload ? `
7851
8003
  Detail: ${JSON.stringify(spec.payload)}` : "";
7852
8004
  const auditDecided = resume.recorded.some((line) => line.startsWith("Audit "));
7853
8005
  return [
7854
8006
  `hanoman ${flow} \u2014 MELANJUTKAN sesi backlog yang sudah berjalan. Ikuti internal/docs sebagai Source of Truth; perbarui docs yang tersentuh dan link-nya di index, dalam commit yang sama.`,
7855
- resumeClause(resume, branchTo, PIPELINES[flow].includes("Plan")),
7856
- phaseInstruction(PIPELINES[flow]),
8007
+ resumeClause(resume, branchTo, m.planDir, PIPELINES[flow].includes("Plan")),
8008
+ phaseInstruction(PIPELINES[flow], m),
7857
8009
  auditDecided ? "" : auditDecisionInstruction(flow),
7858
8010
  autonomyClause(autonomy),
7859
8011
  scopeClause(flow, verifyScope),
7860
8012
  codeStyleClause(flow),
7861
- skillInstruction(PIPELINES[flow]),
8013
+ methodClause(m),
8014
+ skillInstruction(PIPELINES[flow], m, writesCode(flow)),
7862
8015
  `Setelah fase terakhir: commit, lalu \`git push origin HEAD:refs/heads/${branchTo}\`. Worktree ini detached HEAD \u2014 itu memang disengaja.`,
7863
8016
  `Backlog item ${spec.id} \xB7 sumber ${spec.source} \xB7 prioritas ${spec.priority}
7864
8017
  Judul: ${spec.title}
@@ -7866,6 +8019,7 @@ Objective: ${spec.objective}${detail}`
7866
8019
  ].filter(Boolean).join("\n\n");
7867
8020
  }
7868
8021
  function startGoalPrompt(spec, branchTo, opts = {}) {
8022
+ const m = resolveMethod(opts.method);
7869
8023
  const g = readGoalPayload(spec.payload);
7870
8024
  const detail = [
7871
8025
  `Goal: ${g?.goal ?? spec.objective}`,
@@ -7874,14 +8028,15 @@ function startGoalPrompt(spec, branchTo, opts = {}) {
7874
8028
  ].filter(Boolean).join("\n");
7875
8029
  return [
7876
8030
  "hanoman goal \u2014 sesi ini mengejar SATU goal sampai tercapai. TIDAK ada fase Brainstorm, Objective, Spec, maupun Plan: jangan menulis design doc, jangan menulis plan berkotak, jangan memecah pekerjaan ini jadi backlog baru. Langsung kerjakan goal-nya. Tetap ikuti internal/docs sebagai Source of Truth; perbarui docs yang tersentuh dan link-nya di index, dalam commit yang sama.",
7877
- opts.resume ? resumeClause(opts.resume, branchTo, false) : "",
8031
+ opts.resume ? resumeClause(opts.resume, branchTo, m.planDir, false) : "",
7878
8032
  detail,
7879
- phaseInstruction(PIPELINES.goal),
8033
+ phaseInstruction(PIPELINES.goal, m),
7880
8034
  "Fase Verifikasi bukan formalitas: jalankan perintah yang membuktikan goal-nya tercapai (test/typecheck/benchmark/perintah yang relevan) dan baca outputnya. Klaim tanpa output bukan bukti.",
7881
8035
  autonomyClause(opts.autonomy),
7882
8036
  scopeClause("goal", opts.verifyScope),
7883
8037
  codeStyleClause("goal"),
7884
- skillInstruction(PIPELINES.goal),
8038
+ methodClause(m),
8039
+ skillInstruction(PIPELINES.goal, m, writesCode("goal")),
7885
8040
  `Setelah fase terakhir: commit, lalu \`git push origin HEAD:refs/heads/${branchTo}\`. Worktree ini detached HEAD \u2014 itu memang disengaja.`,
7886
8041
  `Backlog item ${spec.id} \xB7 sumber ${spec.source} \xB7 prioritas ${spec.priority}
7887
8042
  Judul: ${spec.title}`
@@ -7890,7 +8045,7 @@ Judul: ${spec.title}`
7890
8045
  function startProjectPrompt(flow, project, branchTo) {
7891
8046
  return [
7892
8047
  `hanoman ${flow}. Susun Source of Truth repo ini dari kodenya di internal/docs/**, mengikuti STANDAR DOCS di bagian bawah prompt ini.`,
7893
- phaseInstruction(PIPELINES[flow]),
8048
+ phaseInstruction(PIPELINES[flow], PROJECT_METHOD),
7894
8049
  REVERSE_PHASE_GUIDE,
7895
8050
  `Setiap fase selesai: commit hasilnya, lalu \`git push origin HEAD:refs/heads/${branchTo}\` \u2014 push per fase, supaya pekerjaan tak hilang bila worktree lenyap. Bila remote origin tidak ada, lewati push dan catat itu di laporan akhir \u2014 jangan gagal diam-diam. Worktree ini detached HEAD \u2014 memang disengaja. Manusia yang me-review dan merge branch ${branchTo}.`,
7896
8051
  `Project ${project.id} \xB7 ${project.name}
@@ -7908,10 +8063,10 @@ PRD ini adalah TINDAK LANJUT audit di bawah. Pakai temuannya sebagai bahan brain
7908
8063
  ${audit.content}` : "";
7909
8064
  return [
7910
8065
  `hanoman prd. Kamu memandu PM/PO menyusun SATU dokumen PRD untuk project ini dari brief + brainstorm. Keluaranmu HANYA dokumen PRD \u2014 JANGAN menulis kode fitur.`,
7911
- phaseInstruction(PIPELINES.prd),
8066
+ phaseInstruction(PIPELINES.prd, PROJECT_METHOD),
7912
8067
  `- Brainstorm: pandu PM secara interaktif. Ajukan SATU pertanyaan per giliran ke manusia di terminal ini, tunggu jawabannya, perdalam brief sampai jelas (masalah, pengguna, scope, metrik sukses). Jangan mengarang; topik yang PM belum jawab tandai sebagai open question.`,
7913
8068
  `- PRD: tulis dokumen ke \`docs/prd/${slug}.md\`. Awali dengan heading \`# <judul PRD>\`, lalu bagian: Ringkasan \xB7 Masalah & konteks \xB7 Persona/pengguna \xB7 Goals & non-goals \xB7 Scope (in/out) \xB7 User stories \xB7 Acceptance criteria (gaya EARS) \xB7 Metrik sukses \xB7 Open questions. Isi lengkap dan spesifik dari hasil brainstorm, bukan kerangka kosong.`,
7914
- skillInstruction(PIPELINES.prd),
8069
+ skillInstruction(PIPELINES.prd, PROJECT_METHOD, false),
7915
8070
  `Setelah PRD ditulis: commit, lalu \`git push origin HEAD:refs/heads/${branchTo}\`. Bila remote origin tidak ada, lewati push dan catat itu di terminal \u2014 jangan gagal diam-diam. Worktree ini detached HEAD \u2014 memang disengaja. Manusia yang me-review lalu merge branch ${branchTo}.`,
7916
8071
  `Project ${project.id} \xB7 ${project.name}
7917
8072
  Brief \u2014 Judul: ${brief.title}
@@ -7925,7 +8080,7 @@ function startBreakdownPrompt(project, prd, branchTo) {
7925
8080
  const slug = branchTo.slice(branchTo.lastIndexOf("/") + 1);
7926
8081
  return [
7927
8082
  `hanoman breakdown. Kamu memecah SATU PRD kompleks menjadi BEBERAPA backlog kecil yang bisa dikerjakan PARALEL tanpa saling bergantung. Keluaranmu HANYA dokumen manifest \u2014 JANGAN menulis kode fitur.`,
7928
- phaseInstruction(PIPELINES.breakdown),
8083
+ phaseInstruction(PIPELINES.breakdown, PROJECT_METHOD),
7929
8084
  `- Analisis: baca PRD (di bawah) sampai paham SELURUH scope in-PRD. Petakan pekerjaan menjadi unit-unit yang: (a) kecil & terukur \u2014 tiap unit tuntas dalam satu sesi; (b) non-overlapping \u2014 cakupan tak tumpang tindih; (c) TANPA cross-dependency \u2014 urutan bebas, bisa jalan bersamaan; (d) gabungannya MENUTUP seluruh scope PRD. Bila dua unit terpaksa berurutan, gabung jadi satu.`,
7930
8085
  `- Breakdown: tulis manifest ke \`docs/prd/${slug}.breakdown.md\`. Awali heading \`# Breakdown: ${prd.title}\`, lalu prosa: ringkasan + untuk TIAP backlog satu paragraf (judul, cakupan, dan SATU kalimat kenapa aman-paralel / tak bergantung yang lain). Di AKHIR dokumen sertakan TEPAT SATU blok kode berpagar json berisi kontrak mesin PERSIS bentuk ini (tanpa komentar, priority \u2208 tinggi|sedang|rendah):
7931
8086
  \`\`\`json
@@ -7942,10 +8097,10 @@ ${prd.content}`
7942
8097
  function startScaffoldPrompt(project, branchTo) {
7943
8098
  return [
7944
8099
  `hanoman scaffold. Susun Source of Truth LENGKAP untuk project from-scratch ini di internal/docs/** DARI IDE-nya, mengikuti STANDAR DOCS di bagian bawah prompt ini. Belum ada kode \u2014 docs dulu.`,
7945
- phaseInstruction(PIPELINES.scaffold),
8100
+ phaseInstruction(PIPELINES.scaffold, PROJECT_METHOD),
7946
8101
  SCAFFOLD_PHASE_GUIDE,
7947
8102
  `Setiap fase selesai: commit hasilnya, lalu \`git push origin HEAD:refs/heads/${branchTo}\` \u2014 push per fase, supaya pekerjaan tak hilang bila worktree lenyap. Bila remote origin tidak ada, lewati push dan catat itu di laporan akhir \u2014 jangan gagal diam-diam. Worktree ini detached HEAD \u2014 memang disengaja. Manusia yang me-review dan merge branch ${branchTo}.`,
7948
- skillInstruction(PIPELINES.scaffold),
8103
+ skillInstruction(PIPELINES.scaffold, PROJECT_METHOD, false),
7949
8104
  `Project ${project.id} \xB7 ${project.name}
7950
8105
  Ide awal: ${project.desc || "\u2014"}
7951
8106
  Stack: ${project.stack || "\u2014"}`,
@@ -7967,10 +8122,11 @@ Stack: ${project.stack || "\u2014"}`,
7967
8122
  ${cron.prompt}`
7968
8123
  ].join("\n\n");
7969
8124
  }
7970
- var PIPELINES, AUTONOMY_CLAUSE, AUTONOMY_CLAUSE_FULL, autonomyClause, phaseInstruction, PHASE_SKILLS, skillInstruction, auditDecisionInstruction, ESCALATION_CONTRACT, auditOnlyInstruction, auditContinuationInstruction, writesCode, scopeClause, codeStyleClause, resumeClause, RESUMED_WORKTREE_NOTE, REVERSE_PHASE_GUIDE, SCAFFOLD_PHASE_GUIDE;
8125
+ var PIPELINES, AUTONOMY_CLAUSE, AUTONOMY_CLAUSE_FULL, autonomyClause, phaseInstruction, skillInstruction, auditDecisionInstruction, ESCALATION_CONTRACT, auditOnlyInstruction, auditContinuationInstruction, writesCode, scopeClause, codeStyleClause, methodClause, PROJECT_METHOD, resumeClause, RESUMED_WORKTREE_NOTE, REVERSE_PHASE_GUIDE, SCAFFOLD_PHASE_GUIDE;
7971
8126
  var init_prompt = __esm({
7972
8127
  "../runner/src/prompt.ts"() {
7973
8128
  "use strict";
8129
+ init_src();
7974
8130
  init_reverse_standard();
7975
8131
  init_verify_scope();
7976
8132
  init_code_style();
@@ -7993,29 +8149,21 @@ var init_prompt = __esm({
7993
8149
  AUTONOMY_CLAUSE = 'Jalankan seluruh pipeline sampai tuntas tanpa berhenti di batas antar-fase. Checkpoint "review"/"approval"/"need review" milik skill superpowers BUKAN titik berhenti di sini \u2014 lanjut saja ke fase berikutnya. Berhenti HANYA saat butuh keputusan manusia sejati (percabangan yang mengubah bentuk kerja: data model, kontrak API, scope); saat itu tanyakan di terminal ini dan tunggu jawabannya. Selain itu, terus lanjut.';
7994
8150
  AUTONOMY_CLAUSE_FULL = "Kamu berjalan TANPA pengawas \u2014 tak ada manusia yang menonton terminal ini untuk menjawab. Putuskan sendiri di SETIAP percabangan (termasuk yang mengubah bentuk kerja: data model, kontrak API, scope) berdasarkan Source of Truth dan penilaian terbaikmu; JANGAN berhenti bertanya. Tembus seluruh pipeline sampai stage `done`, lalu commit & push. Jangan menunggu review/persetujuan siapa pun \u2014 catat asumsi & keputusan penting di pesan commit agar bisa di-review pasca-fakta. Merge ke branch utama tetap dilakukan manusia, bukan kamu.";
7995
8151
  autonomyClause = (mode) => mode === "full-control" ? AUTONOMY_CLAUSE_FULL : AUTONOMY_CLAUSE;
7996
- phaseInstruction = (phases) => {
8152
+ phaseInstruction = (phases, method) => {
7997
8153
  const base2 = `Kerjakan fase berurutan: ${phases.join(" \u2192 ")}.
7998
8154
  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.`;
7999
8155
  if (!phases.includes("Plan") || !phases.includes("Execute")) return base2;
8000
8156
  return base2 + `
8001
- Execute BELUM selesai selama plan (\`docs/superpowers/plans/**\`) 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 \`- [ ]\`.`;
8002
- };
8003
- PHASE_SKILLS = {
8004
- Brainstorm: ["superpowers:brainstorming"],
8005
- Audit: ["superpowers:systematic-debugging"],
8006
- Plan: ["superpowers:writing-plans"],
8007
- Execute: [
8008
- "superpowers:executing-plans",
8009
- "superpowers:test-driven-development",
8010
- "superpowers:verification-before-completion"
8011
- ],
8012
- // SPEC-407 · fase `Goal` sengaja TANPA skill: seluruh inti flow ini adalah membebaskan sesi
8013
- // dari proses kaku. Yang tetap dijaga cuma pintu keluarnya.
8014
- Verifikasi: ["superpowers:verification-before-completion"]
8157
+ 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 \`- [ ]\`.`;
8015
8158
  };
8016
- skillInstruction = (phases) => {
8017
- const lines2 = phases.filter((p) => PHASE_SKILLS[p]).map((p) => `- ${p}: ${PHASE_SKILLS[p].join(", ")}`);
8018
- return lines2.length ? `Skills superpowers WAJIB: sebelum mengerjakan fase di bawah, muat & ikuti skill-nya dengan mekanisme yang tersedia di agenmu \u2014 bila skill relevan tersedia, pakai.
8159
+ skillInstruction = (phases, method, withExit) => {
8160
+ const last = phases[phases.length - 1];
8161
+ const lines2 = phases.map((p) => {
8162
+ const own = method.phaseSkills[p] ?? [];
8163
+ const skills = withExit && p === last ? [.../* @__PURE__ */ new Set([...own, ...method.exitSkills])] : own;
8164
+ return skills.length ? `- ${p}: ${skills.join(", ")}` : "";
8165
+ }).filter(Boolean);
8166
+ 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.
8019
8167
  ${lines2.join("\n")}` : "";
8020
8168
  };
8021
8169
  auditDecisionInstruction = (flow) => flow !== "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.';
@@ -8054,11 +8202,13 @@ ${lines2.join("\n")}` : "";
8054
8202
  writesCode = (flow) => PIPELINES[flow].includes("Execute") || PIPELINES[flow].includes("Goal");
8055
8203
  scopeClause = (flow, scope) => scope && writesCode(flow) ? verifyScopeClause(scope) : "";
8056
8204
  codeStyleClause = (flow) => writesCode(flow) ? CODE_STYLE_CLAUSE : "";
8057
- resumeClause = (r, branchTo, hasPlan = true) => {
8205
+ methodClause = (method) => method.extraClause ?? "";
8206
+ PROJECT_METHOD = resolveMethod();
8207
+ resumeClause = (r, branchTo, planDir, hasPlan = true) => {
8058
8208
  const fase = r.recorded.length ? `Fase yang SUDAH tercatat di $HANOMAN_PHASE_FILE: ${r.recorded.join(" \xB7 ")}. JANGAN mengulang fase itu dan JANGAN menulis ulang barisnya.` : "Belum ada fase yang tercatat di $HANOMAN_PHASE_FILE \u2014 worktree ini sendiri yang jadi alasan melanjutkan.";
8059
- const lanjut = r.next ? `Lanjutkan dari fase: ${r.next}.` : hasPlan ? "Semua fase sudah tercatat. Periksa apakah plan di `docs/superpowers/plans/**` masih menyisakan task `- [ ]` dan selesaikan sisanya; bila sudah bersih, tinggal commit & push." : "Semua fase sudah tercatat. Buktikan sekali lagi goal-nya benar-benar tercapai, lalu commit & push.";
8209
+ const lanjut = r.next ? `Lanjutkan dari fase: ${r.next}.` : hasPlan ? `Semua fase sudah tercatat. Periksa apakah plan di \`${planDir}/**\` masih menyisakan task \`- [ ]\` dan selesaikan sisanya; bila sudah bersih, tinggal commit & push.` : "Semua fase sudah tercatat. Buktikan sekali lagi goal-nya benar-benar tercapai, lalu commit & push.";
8060
8210
  const worktree = r.worktreeKept ? "Worktree ini adalah worktree sesi sebelumnya apa adanya \u2014 termasuk perubahan yang belum di-commit." : `Worktree ini DIBANGUN ULANG dari tip branch sesi \`${branchTo}\`: commit sesi sebelumnya ada, tetapi perubahan yang belum sempat di-commit TIDAK ada.`;
8061
- const baca = hasPlan ? "Sebelum menulis apa pun: baca `git log --oneline` dan `git status`, lalu plan di `docs/superpowers/plans/**` untuk backlog item ini (`- [x]` sudah selesai, `- [ ]` belum). Jangan menulis ulang yang sudah ada." : "Sebelum menulis apa pun: baca `git log --oneline` dan `git status` untuk melihat apa yang sudah dikerjakan. Jangan menulis ulang yang sudah ada.";
8211
+ const baca = hasPlan ? `Sebelum menulis apa pun: baca \`git log --oneline\` dan \`git status\`, lalu plan di \`${planDir}/**\` untuk backlog item ini (\`- [x]\` sudah selesai, \`- [ ]\` belum). Jangan menulis ulang yang sudah ada.` : "Sebelum menulis apa pun: baca `git log --oneline` dan `git status` untuk melihat apa yang sudah dikerjakan. Jangan menulis ulang yang sudah ada.";
8062
8212
  return [
8063
8213
  "Sesi ini MELANJUTKAN pekerjaan sesi sebelumnya untuk backlog item yang sama \u2014 bukan memulai dari nol.",
8064
8214
  fase,
@@ -8243,8 +8393,9 @@ function defaultGoalCondition({ flow, specId, branchTo, spec }) {
8243
8393
  `1. output \`cat "$HANOMAN_PHASE_FILE"\` yang memuat satu baris untuk SETIAP fase ${phases.join(" \u2192 ")}, masing-masing berakhiran \`done\` atau \`skipped\`;`
8244
8394
  ];
8245
8395
  if (planGate) {
8396
+ const dirs = PLAN_DIRS.map((d) => `${d}/`).join(" ");
8246
8397
  clauses.push(
8247
- `2. output \`grep -rn -- "- \\[ \\]" docs/superpowers/plans/\` yang KOSONG untuk plan backlog ini \u2014 tak ada task yang masih \`- [ ]\` (atau bukti bahwa backlog ini memang tak berplan);`
8398
+ `2. output \`grep -rn -- "- \\[ \\]" ${dirs}\` yang KOSONG untuk plan backlog ini \u2014 tak ada task yang masih \`- [ ]\` (atau bukti bahwa backlog ini memang tak berplan);`
8248
8399
  );
8249
8400
  }
8250
8401
  clauses.push(
@@ -8269,6 +8420,7 @@ var GOAL_MAX, goalOneLine, GOAL_CHUNK;
8269
8420
  var init_goal = __esm({
8270
8421
  "../runner/src/goal.ts"() {
8271
8422
  "use strict";
8423
+ init_src();
8272
8424
  init_prompt();
8273
8425
  init_goal_spec();
8274
8426
  GOAL_MAX = 4e3;
@@ -8309,12 +8461,14 @@ function codexGoalScript(o) {
8309
8461
  );
8310
8462
  }
8311
8463
  if (planGate) {
8312
- lines2.push(
8313
- `for f in ${shq(o.worktree)}/docs/superpowers/plans/*${o.specId.toLowerCase()}*; do`,
8314
- ` [ -f "$f" ] || continue`,
8315
- ` grep -qE '^[ ]*- \\[ \\]' "$f" && missing="$missing\\n- plan $f masih punya task - [ ] yang belum selesai"`,
8316
- "done"
8317
- );
8464
+ for (const dir2 of PLAN_DIRS) {
8465
+ lines2.push(
8466
+ `for f in ${shq(`${o.worktree}/${dir2}`)}/*${o.specId.toLowerCase()}*; do`,
8467
+ ` [ -f "$f" ] || continue`,
8468
+ ` grep -qE '^[ ]*- \\[ \\]' "$f" && missing="$missing\\n- plan $f masih punya task - [ ] yang belum selesai"`,
8469
+ "done"
8470
+ );
8471
+ }
8318
8472
  }
8319
8473
  lines2.push(
8320
8474
  'if [ -z "$missing" ]; then exit 0; fi',
@@ -8339,6 +8493,7 @@ var tq, shq, group, GOAL_MAX_BLOCKS;
8339
8493
  var init_codex_settings = __esm({
8340
8494
  "../runner/src/codex-settings.ts"() {
8341
8495
  "use strict";
8496
+ init_src();
8342
8497
  init_prompt();
8343
8498
  tq = (s2) => `"${s2.split("\\").join("\\\\").split('"').join('\\"')}"`;
8344
8499
  shq = (s2) => `'${s2.split("'").join("'\\''")}'`;
@@ -8500,6 +8655,186 @@ var init_paths = __esm({
8500
8655
  }
8501
8656
  });
8502
8657
 
8658
+ // ../runner/src/skills.ts
8659
+ import { homedir as homedir2 } from "node:os";
8660
+ import { basename, dirname as dirname3, join as join2, resolve as resolve6 } from "node:path";
8661
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
8662
+ function agentSkillHome(agent, env = process.env, osHome = homedir2()) {
8663
+ const own = (agent === "codex" ? env.HANOMAN_CODEX_HOME : env.HANOMAN_CLAUDE_HOME)?.trim();
8664
+ if (own) return own;
8665
+ if (agent === "codex") {
8666
+ const cx = env.CODEX_HOME?.trim();
8667
+ if (cx) return cx;
8668
+ }
8669
+ return join2(osHome, agent === "codex" ? ".codex" : ".claude");
8670
+ }
8671
+ function agentsSkillHome(env = process.env, osHome = homedir2()) {
8672
+ return env.HANOMAN_AGENTS_HOME?.trim() || join2(osHome, ".agents");
8673
+ }
8674
+ function dirsIn(dir2) {
8675
+ try {
8676
+ return readdirSync(dir2, { withFileTypes: true }).filter((e) => (e.isDirectory() || e.isSymbolicLink()) && !e.name.startsWith(".")).map((e) => e.name);
8677
+ } catch {
8678
+ return [];
8679
+ }
8680
+ }
8681
+ function readJson(file) {
8682
+ try {
8683
+ return JSON.parse(readFileSync2(file, "utf8"));
8684
+ } catch {
8685
+ return null;
8686
+ }
8687
+ }
8688
+ function isSkillDir(dir2) {
8689
+ return existsSync2(join2(dir2, "SKILL.md"));
8690
+ }
8691
+ function asSkill(dir2, pkg, name2 = basename(dir2)) {
8692
+ return { id: pkg ? `${pkg}:${name2}` : name2, name: name2, pkg, dir: dir2 };
8693
+ }
8694
+ function skillsUnder(dir2, pkg, depth = 2) {
8695
+ const out3 = [];
8696
+ for (const name2 of dirsIn(dir2)) {
8697
+ const sub = join2(dir2, name2);
8698
+ if (isSkillDir(sub)) out3.push(asSkill(sub, pkg, name2));
8699
+ else if (depth > 1) out3.push(...skillsUnder(sub, pkg, depth - 1));
8700
+ }
8701
+ return out3;
8702
+ }
8703
+ function manifestSkills(installPath, pkg) {
8704
+ for (const marker of [".claude-plugin", ".codex-plugin"]) {
8705
+ const j = readJson(join2(installPath, marker, "plugin.json"));
8706
+ const declared = j?.skills;
8707
+ if (!Array.isArray(declared)) continue;
8708
+ const out3 = [];
8709
+ for (const rel of declared) {
8710
+ if (typeof rel !== "string" || !rel) continue;
8711
+ const p = resolve6(installPath, rel);
8712
+ const dir2 = basename(p) === "SKILL.md" ? dirname3(p) : p;
8713
+ if (isSkillDir(dir2)) out3.push(asSkill(dir2, pkg));
8714
+ }
8715
+ if (out3.length) return out3;
8716
+ }
8717
+ return [];
8718
+ }
8719
+ function pluginSkills(installPath, pkg) {
8720
+ const declared = manifestSkills(installPath, pkg);
8721
+ return declared.length ? declared : skillsUnder(join2(installPath, "skills"), pkg);
8722
+ }
8723
+ function lockPluginNames(agentsHome) {
8724
+ const out3 = /* @__PURE__ */ new Map();
8725
+ const j = readJson(join2(agentsHome, ".skill-lock.json"));
8726
+ const skills = j?.skills;
8727
+ if (!skills || typeof skills !== "object") return out3;
8728
+ for (const [name2, entry] of Object.entries(skills)) {
8729
+ const pkg = entry?.pluginName;
8730
+ if (typeof pkg === "string" && pkg.trim()) out3.set(name2, pkg.trim());
8731
+ }
8732
+ return out3;
8733
+ }
8734
+ function splitPluginKey(key) {
8735
+ const at = key.lastIndexOf("@");
8736
+ return at > 0 ? { pkg: key.slice(0, at), marketplace: key.slice(at + 1) } : { pkg: key, marketplace: "" };
8737
+ }
8738
+ function manifestRoots(home2) {
8739
+ const j = readJson(join2(home2, "plugins", "installed_plugins.json"));
8740
+ const plugins = j?.plugins;
8741
+ if (!plugins || typeof plugins !== "object") return [];
8742
+ const out3 = [];
8743
+ for (const [key, entries] of Object.entries(plugins)) {
8744
+ const { pkg, marketplace } = splitPluginKey(key);
8745
+ for (const e of Array.isArray(entries) ? entries : []) {
8746
+ const p = e?.installPath;
8747
+ if (typeof p === "string" && p) out3.push({ pkg, marketplace, dir: p });
8748
+ }
8749
+ }
8750
+ return out3;
8751
+ }
8752
+ function cacheRoots(home2) {
8753
+ const cache6 = join2(home2, "plugins", "cache");
8754
+ const out3 = [];
8755
+ for (const marketplace of dirsIn(cache6))
8756
+ for (const pkg of dirsIn(join2(cache6, marketplace)))
8757
+ for (const version of dirsIn(join2(cache6, marketplace, pkg)))
8758
+ out3.push({ pkg, marketplace, dir: join2(cache6, marketplace, pkg, version) });
8759
+ return out3;
8760
+ }
8761
+ function disabledPlugins(agent, home2) {
8762
+ const out3 = /* @__PURE__ */ new Set();
8763
+ if (agent === "claude") {
8764
+ const en = readJson(join2(home2, "settings.json"))?.enabledPlugins;
8765
+ if (en && typeof en === "object") {
8766
+ for (const [k, v] of Object.entries(en)) if (v === false) out3.add(k);
8767
+ }
8768
+ return out3;
8769
+ }
8770
+ let toml;
8771
+ try {
8772
+ toml = readFileSync2(join2(home2, "config.toml"), "utf8");
8773
+ } catch {
8774
+ return out3;
8775
+ }
8776
+ let current = null;
8777
+ for (const raw of toml.split("\n")) {
8778
+ const line = raw.trim();
8779
+ const head = /^\[([^\]]*)\]$/.exec(line);
8780
+ if (head) {
8781
+ const m = /^plugins\."(.+)"$/.exec(head[1] ?? "");
8782
+ current = m ? m[1] : null;
8783
+ continue;
8784
+ }
8785
+ if (current && /^enabled\s*=\s*false\b/.test(line)) out3.add(current);
8786
+ }
8787
+ return out3;
8788
+ }
8789
+ function scanAgentSkills(agent, env = process.env, osHome = homedir2()) {
8790
+ const home2 = agentSkillHome(agent, env, osHome);
8791
+ const disabled = disabledPlugins(agent, home2);
8792
+ const roots = [];
8793
+ const skills = [];
8794
+ const packages = /* @__PURE__ */ new Set();
8795
+ const seenSkill = /* @__PURE__ */ new Set();
8796
+ const seenRoot = /* @__PURE__ */ new Set();
8797
+ const add = (dir2, found) => {
8798
+ if (!found.length) return;
8799
+ if (!seenRoot.has(dir2)) {
8800
+ seenRoot.add(dir2);
8801
+ roots.push(dir2);
8802
+ }
8803
+ for (const s2 of found) if (!seenSkill.has(s2.id)) {
8804
+ seenSkill.add(s2.id);
8805
+ skills.push(s2);
8806
+ }
8807
+ };
8808
+ const userDir = join2(home2, "skills");
8809
+ add(userDir, skillsUnder(userDir, null));
8810
+ for (const r of [...manifestRoots(home2), ...cacheRoots(home2)]) {
8811
+ if (disabled.has(`${r.pkg}@${r.marketplace}`)) continue;
8812
+ const found = pluginSkills(r.dir, r.pkg);
8813
+ if (!found.length) continue;
8814
+ packages.add(r.pkg);
8815
+ add(join2(r.dir, "skills"), found);
8816
+ }
8817
+ if (agent === "codex") {
8818
+ const agentsHome = agentsSkillHome(env, osHome);
8819
+ const dir2 = join2(agentsHome, "skills");
8820
+ const names = lockPluginNames(agentsHome);
8821
+ const flat = skillsUnder(dir2, null);
8822
+ const withPkg = flat.flatMap((s2) => {
8823
+ const pkg = names.get(s2.name);
8824
+ if (!pkg) return [s2];
8825
+ packages.add(pkg);
8826
+ return [asSkill(s2.dir, pkg, s2.name), s2];
8827
+ });
8828
+ add(dir2, withPkg);
8829
+ }
8830
+ return { agent, home: home2, roots, skills, packages: [...packages] };
8831
+ }
8832
+ var init_skills = __esm({
8833
+ "../runner/src/skills.ts"() {
8834
+ "use strict";
8835
+ }
8836
+ });
8837
+
8503
8838
  // ../runner/src/telegram-operator.ts
8504
8839
  function buildTelegramOperatorPrompt(input) {
8505
8840
  const personality = input.personality ? [
@@ -8588,6 +8923,7 @@ var init_src2 = __esm({
8588
8923
  init_verify_scope();
8589
8924
  init_code_style();
8590
8925
  init_paths();
8926
+ init_skills();
8591
8927
  init_telegram_operator();
8592
8928
  }
8593
8929
  });
@@ -8724,7 +9060,7 @@ var init_tap = __esm({
8724
9060
 
8725
9061
  // src/db.ts
8726
9062
  import { mkdirSync as mkdirSync2 } from "node:fs";
8727
- import { dirname as dirname3, resolve as resolve6 } from "node:path";
9063
+ import { dirname as dirname4, resolve as resolve7 } from "node:path";
8728
9064
  import { fileURLToPath as fileURLToPath2 } from "node:url";
8729
9065
  import { PrismaClient } from "@prisma/client";
8730
9066
  var schemaDir, notice, url, base, prisma;
@@ -8734,12 +9070,12 @@ var init_db = __esm({
8734
9070
  init_env();
8735
9071
  init_src2();
8736
9072
  init_tap();
8737
- schemaDir = resolve6(dirname3(fileURLToPath2(import.meta.url)), "../prisma");
9073
+ schemaDir = resolve7(dirname4(fileURLToPath2(import.meta.url)), "../prisma");
8738
9074
  notice = dbUrlNotice(process.env);
8739
9075
  if (notice) console.warn(notice);
8740
9076
  url = resolveDbUrl(process.env, schemaDir);
8741
9077
  process.env.DATABASE_URL = url;
8742
- mkdirSync2(dirname3(dbFilePath(url)), { recursive: true });
9078
+ mkdirSync2(dirname4(dbFilePath(url)), { recursive: true });
8743
9079
  base = new PrismaClient();
8744
9080
  prisma = base.$extends(webhookTap(base));
8745
9081
  }
@@ -8755,12 +9091,12 @@ var init_stage_machine = __esm({
8755
9091
  });
8756
9092
 
8757
9093
  // src/services/session-phases.ts
8758
- import { readFileSync as readFileSync2, readdirSync } from "node:fs";
9094
+ import { readFileSync as readFileSync3, readdirSync as readdirSync2 } from "node:fs";
8759
9095
  function recorded(file) {
8760
9096
  const out3 = /* @__PURE__ */ new Map();
8761
9097
  let raw;
8762
9098
  try {
8763
- raw = readFileSync2(file, "utf8");
9099
+ raw = readFileSync3(file, "utf8");
8764
9100
  } catch {
8765
9101
  return out3;
8766
9102
  }
@@ -8798,19 +9134,21 @@ function stageFor(phases) {
8798
9134
  return best < 0 ? null : STAGES[best];
8799
9135
  }
8800
9136
  function planComplete(worktree, specId) {
8801
- const dir2 = `${worktree}/docs/superpowers/plans`;
8802
9137
  const re = new RegExp(`(^|[^a-z0-9])${specId.toLowerCase()}([^0-9]|$)`);
8803
- let names;
8804
- try {
8805
- names = readdirSync(dir2);
8806
- } catch {
8807
- return true;
8808
- }
8809
- for (const n2 of names) {
8810
- if (!re.test(n2.toLowerCase())) continue;
9138
+ for (const rel of PLAN_DIRS) {
9139
+ const dir2 = `${worktree}/${rel}`;
9140
+ let names;
8811
9141
  try {
8812
- if (/^[ \t]*- \[ \]/m.test(readFileSync2(`${dir2}/${n2}`, "utf8"))) return false;
9142
+ names = readdirSync2(dir2);
8813
9143
  } catch {
9144
+ continue;
9145
+ }
9146
+ for (const n2 of names) {
9147
+ if (!re.test(n2.toLowerCase())) continue;
9148
+ try {
9149
+ if (/^[ \t]*- \[ \]/m.test(readFileSync3(`${dir2}/${n2}`, "utf8"))) return false;
9150
+ } catch {
9151
+ }
8814
9152
  }
8815
9153
  }
8816
9154
  return true;
@@ -8829,6 +9167,7 @@ var init_session_phases = __esm({
8829
9167
  "src/services/session-phases.ts"() {
8830
9168
  "use strict";
8831
9169
  init_src2();
9170
+ init_src();
8832
9171
  init_stage_machine();
8833
9172
  phaseFilePath = (repoDir, sessionId2) => `${repoDir}/.worktrees/.phases/${sessionId2}`;
8834
9173
  decisionFilePath = (repoDir, sessionId2) => `${repoDir}/.worktrees/.decisions/${sessionId2}`;
@@ -9090,9 +9429,9 @@ var init_tui_dialog = __esm({
9090
9429
  });
9091
9430
 
9092
9431
  // src/services/secret-box.ts
9093
- import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync } from "node:fs";
9432
+ import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync } from "node:fs";
9094
9433
  import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
9095
- import { dirname as dirname4, join as join2 } from "node:path";
9434
+ import { dirname as dirname5, join as join3 } from "node:path";
9096
9435
  function isEncrypted(value) {
9097
9436
  return value.startsWith(ENC_PREFIX);
9098
9437
  }
@@ -9124,7 +9463,7 @@ function fromEnv(raw) {
9124
9463
  return null;
9125
9464
  }
9126
9465
  function secretKeyPath() {
9127
- return join2(resolveHome(), "secret.key");
9466
+ return join3(resolveHome(), "secret.key");
9128
9467
  }
9129
9468
  function secretKey() {
9130
9469
  if (cached) return cached;
@@ -9136,14 +9475,14 @@ function secretKey() {
9136
9475
  return key2;
9137
9476
  }
9138
9477
  const path = secretKeyPath();
9139
- if (existsSync2(path)) {
9140
- const key2 = Buffer.from(readFileSync3(path, "utf8").trim(), "base64url");
9478
+ if (existsSync3(path)) {
9479
+ const key2 = Buffer.from(readFileSync4(path, "utf8").trim(), "base64url");
9141
9480
  if (key2.length !== KEY_BYTES) throw new Error(`kunci di ${path} rusak \u2014 panjangnya bukan 32 byte`);
9142
9481
  cached = key2;
9143
9482
  return key2;
9144
9483
  }
9145
9484
  const key = randomBytes(KEY_BYTES);
9146
- mkdirSync3(dirname4(path), { recursive: true });
9485
+ mkdirSync3(dirname5(path), { recursive: true });
9147
9486
  writeFileSync(path, key.toString("base64url"), { mode: 384 });
9148
9487
  chmodSync(path, 384);
9149
9488
  cached = key;
@@ -9242,7 +9581,7 @@ import { spawn } from "node-pty";
9242
9581
  import { execFileSync } from "node:child_process";
9243
9582
  import { randomUUID } from "node:crypto";
9244
9583
  import { mkdirSync as mkdirSync4, statSync, writeFileSync as writeFileSync2 } from "node:fs";
9245
- import { dirname as dirname5 } from "node:path";
9584
+ import { dirname as dirname6 } from "node:path";
9246
9585
  import { tmpdir } from "node:os";
9247
9586
  function tmux(...args) {
9248
9587
  try {
@@ -9324,7 +9663,7 @@ function createSession(projectId, cwd, opts = {}) {
9324
9663
  let promptArg = "";
9325
9664
  if (!opts.command && opts.prompt) {
9326
9665
  const promptFile = promptFilePath(id);
9327
- mkdirSync4(dirname5(promptFile), { recursive: true });
9666
+ mkdirSync4(dirname6(promptFile), { recursive: true });
9328
9667
  writeFileSync2(promptFile, opts.prompt + rosterBlock);
9329
9668
  promptArg = `"$(cat ${sq(promptFile)})"`;
9330
9669
  }
@@ -9336,7 +9675,7 @@ function createSession(projectId, cwd, opts = {}) {
9336
9675
  let goalGate;
9337
9676
  if (agent === "codex" && opts.goal && opts.flow && opts.specId) {
9338
9677
  goalGate = goalGatePath(id);
9339
- mkdirSync4(dirname5(goalGate), { recursive: true });
9678
+ mkdirSync4(dirname6(goalGate), { recursive: true });
9340
9679
  writeFileSync2(goalGate, codexGoalScript({
9341
9680
  flow: opts.flow,
9342
9681
  specId: opts.specId,
@@ -9352,7 +9691,7 @@ function createSession(projectId, cwd, opts = {}) {
9352
9691
  const json = renderAgentsJson(customDefs);
9353
9692
  if (json) {
9354
9693
  agentsFile = agentsFilePath(id);
9355
- mkdirSync4(dirname5(agentsFile), { recursive: true });
9694
+ mkdirSync4(dirname6(agentsFile), { recursive: true });
9356
9695
  writeFileSync2(agentsFile, json);
9357
9696
  }
9358
9697
  }
@@ -9372,12 +9711,12 @@ function createSession(projectId, cwd, opts = {}) {
9372
9711
  for (const [k, v] of Object.entries(rootBypassEnv())) envPairs.push(`${k}=${sq(v)}`);
9373
9712
  }
9374
9713
  if (opts.phaseFile) {
9375
- mkdirSync4(dirname5(opts.phaseFile), { recursive: true });
9714
+ mkdirSync4(dirname6(opts.phaseFile), { recursive: true });
9376
9715
  envPairs.push(`HANOMAN_PHASE_FILE=${sq(opts.phaseFile)}`);
9377
9716
  }
9378
9717
  for (const [k, v] of Object.entries(opts.env ?? {})) envPairs.push(`${k}=${sq(v)}`);
9379
9718
  const cmd = envPairs.length ? `${envPairs.join(" ")} ${argv}` : argv;
9380
- if (opts.decisionFile) mkdirSync4(dirname5(opts.decisionFile), { recursive: true });
9719
+ if (opts.decisionFile) mkdirSync4(dirname6(opts.decisionFile), { recursive: true });
9381
9720
  tmux(
9382
9721
  "set-option",
9383
9722
  "-g",
@@ -10199,6 +10538,8 @@ var init_settings2 = __esm({
10199
10538
  // SPEC-338 · ADR-0074 · model/effort codex
10200
10539
  verifyScope: "changed",
10201
10540
  // SPEC-376 · ADR-0080 · uji hanya yang berubah
10541
+ method: DEFAULT_METHOD,
10542
+ // SPEC-734 · ADR-0113 · metode workflow default
10202
10543
  conflict: CONFLICT_DEFAULTS,
10203
10544
  // SPEC-383 · ADR-0081 · default sesi konflik (opt-in, mati)
10204
10545
  lead: LEAD_DEFAULTS,
@@ -10213,8 +10554,8 @@ var init_settings2 = __esm({
10213
10554
  });
10214
10555
 
10215
10556
  // src/services/codex-trust.ts
10216
- import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync5, realpathSync as realpathSync2 } from "node:fs";
10217
- import { homedir as homedir2 } from "node:os";
10557
+ import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync6, realpathSync as realpathSync2 } from "node:fs";
10558
+ import { homedir as homedir3 } from "node:os";
10218
10559
  function ensureCodexTrust(repoDir, home2) {
10219
10560
  const path = codexConfigPath(home2);
10220
10561
  let canonical = repoDir;
@@ -10226,7 +10567,7 @@ function ensureCodexTrust(repoDir, home2) {
10226
10567
  try {
10227
10568
  let existing = "";
10228
10569
  try {
10229
- existing = readFileSync5(path, "utf8");
10570
+ existing = readFileSync6(path, "utf8");
10230
10571
  } catch {
10231
10572
  }
10232
10573
  if (existing.includes(header2)) return;
@@ -10243,7 +10584,7 @@ var codexHome, codexConfigPath;
10243
10584
  var init_codex_trust = __esm({
10244
10585
  "src/services/codex-trust.ts"() {
10245
10586
  "use strict";
10246
- codexHome = (home2) => home2 ?? process.env.CODEX_HOME ?? `${homedir2()}/.codex`;
10587
+ codexHome = (home2) => home2 ?? process.env.CODEX_HOME ?? `${homedir3()}/.codex`;
10247
10588
  codexConfigPath = (home2) => `${codexHome(home2)}/config.toml`;
10248
10589
  }
10249
10590
  });
@@ -10886,7 +11227,7 @@ var init_gateway = __esm({
10886
11227
  const message = sanitizeTelegramOutput(error.message, this.deps.exactSecrets).slice(0, 500);
10887
11228
  updateTelegramRuntimeStatus({ running: false, readiness: "error", lastError: message });
10888
11229
  if (error instanceof TelegramApiError && error.code === 409) return;
10889
- await new Promise((resolve15) => setTimeout(resolve15, 1e3));
11230
+ await new Promise((resolve16) => setTimeout(resolve16, 1e3));
10890
11231
  }
10891
11232
  }
10892
11233
  }
@@ -16896,7 +17237,7 @@ var require_parseParams = __commonJS({
16896
17237
  var require_basename = __commonJS({
16897
17238
  "../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/lib/utils/basename.js"(exports, module) {
16898
17239
  "use strict";
16899
- module.exports = function basename3(path) {
17240
+ module.exports = function basename4(path) {
16900
17241
  if (typeof path !== "string") {
16901
17242
  return "";
16902
17243
  }
@@ -16923,7 +17264,7 @@ var require_multipart = __commonJS({
16923
17264
  var Dicer = require_Dicer();
16924
17265
  var parseParams = require_parseParams();
16925
17266
  var decodeText = require_decodeText();
16926
- var basename3 = require_basename();
17267
+ var basename4 = require_basename();
16927
17268
  var getLimit = require_getLimit();
16928
17269
  var RE_BOUNDARY = /^boundary$/i;
16929
17270
  var RE_FIELD = /^form-data$/i;
@@ -17040,7 +17381,7 @@ var require_multipart = __commonJS({
17040
17381
  } else if (RE_FILENAME.test(parsed[i][0])) {
17041
17382
  filename = parsed[i][1];
17042
17383
  if (!preservePath) {
17043
- filename = basename3(filename);
17384
+ filename = basename4(filename);
17044
17385
  }
17045
17386
  }
17046
17387
  }
@@ -18124,17 +18465,17 @@ var require_stream_wormhole = __commonJS({
18124
18465
  "../node_modules/.pnpm/stream-wormhole@1.1.0/node_modules/stream-wormhole/index.js"(exports, module) {
18125
18466
  "use strict";
18126
18467
  module.exports = (stream, throwError) => {
18127
- return new Promise((resolve15, reject2) => {
18468
+ return new Promise((resolve16, reject2) => {
18128
18469
  if (typeof stream.resume !== "function") {
18129
- return resolve15();
18470
+ return resolve16();
18130
18471
  }
18131
18472
  stream.unpipe && stream.unpipe();
18132
18473
  stream.resume();
18133
18474
  if (stream._readableState && stream._readableState.ended) {
18134
- return resolve15();
18475
+ return resolve16();
18135
18476
  }
18136
18477
  if (!stream.readable || stream.destroyed) {
18137
- return resolve15();
18478
+ return resolve16();
18138
18479
  }
18139
18480
  function cleanup() {
18140
18481
  stream.removeListener("end", onEnd);
@@ -18143,14 +18484,14 @@ var require_stream_wormhole = __commonJS({
18143
18484
  }
18144
18485
  function onEnd() {
18145
18486
  cleanup();
18146
- resolve15();
18487
+ resolve16();
18147
18488
  }
18148
18489
  function onError(err) {
18149
18490
  cleanup();
18150
18491
  if (throwError) {
18151
18492
  reject2(err);
18152
18493
  } else {
18153
- resolve15();
18494
+ resolve16();
18154
18495
  }
18155
18496
  }
18156
18497
  stream.on("end", onEnd);
@@ -18585,16 +18926,16 @@ var require_multipart2 = __commonJS({
18585
18926
  }
18586
18927
  };
18587
18928
  const parts = () => {
18588
- return new Promise((resolve15, reject2) => {
18929
+ return new Promise((resolve16, reject2) => {
18589
18930
  handle((val) => {
18590
18931
  if (val instanceof Error) {
18591
18932
  if (val.message === "Unexpected end of multipart data") {
18592
- resolve15(null);
18933
+ resolve16(null);
18593
18934
  } else {
18594
18935
  reject2(val);
18595
18936
  }
18596
18937
  } else {
18597
- resolve15(val);
18938
+ resolve16(val);
18598
18939
  }
18599
18940
  });
18600
18941
  });
@@ -18860,8 +19201,8 @@ import fastifyStatic from "@fastify/static";
18860
19201
  import websocket from "@fastify/websocket";
18861
19202
  import cookie from "@fastify/cookie";
18862
19203
  import { fileURLToPath as fileURLToPath4 } from "node:url";
18863
- import { dirname as dirname13 } from "node:path";
18864
- import { existsSync as existsSync13 } from "node:fs";
19204
+ import { dirname as dirname14 } from "node:path";
19205
+ import { existsSync as existsSync14 } from "node:fs";
18865
19206
 
18866
19207
  // src/web-dir.ts
18867
19208
  import { resolve } from "node:path";
@@ -18923,8 +19264,8 @@ init_stage_machine();
18923
19264
  init_src();
18924
19265
  import { execFile } from "node:child_process";
18925
19266
  import { promisify } from "node:util";
18926
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync3, rmSync as rmSync2, mkdirSync as mkdirSync5 } from "node:fs";
18927
- import { resolve as resolve7, dirname as dirname6, sep } from "node:path";
19267
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync4, rmSync as rmSync2, mkdirSync as mkdirSync5 } from "node:fs";
19268
+ import { resolve as resolve8, dirname as dirname7, sep } from "node:path";
18928
19269
  var exec = promisify(execFile);
18929
19270
  async function listRepoDocs(repoDir) {
18930
19271
  try {
@@ -18940,7 +19281,7 @@ async function listRepoDocs(repoDir) {
18940
19281
  }
18941
19282
  function docsDirOf(repoDir) {
18942
19283
  try {
18943
- const raw = readFileSync4(resolve7(repoDir, "hanoman.config.json"), "utf8");
19284
+ const raw = readFileSync5(resolve8(repoDir, "hanoman.config.json"), "utf8");
18944
19285
  return zHanomanConfig.parse(JSON.parse(raw)).docsDir;
18945
19286
  } catch {
18946
19287
  return zHanomanConfig.parse({}).docsDir;
@@ -18948,18 +19289,18 @@ function docsDirOf(repoDir) {
18948
19289
  }
18949
19290
  function resolveIndex(repoDir, docsDir) {
18950
19291
  const rel = `${docsDir}/README.md`;
18951
- return existsSync3(resolve7(repoDir, rel)) ? rel : "";
19292
+ return existsSync4(resolve8(repoDir, rel)) ? rel : "";
18952
19293
  }
18953
19294
  var catOf = (rel) => rel.includes("/") ? rel.slice(0, rel.lastIndexOf("/")) : ".";
18954
19295
  var nameOf = (rel) => rel.includes("/") ? rel.slice(rel.lastIndexOf("/") + 1) : rel;
18955
19296
  async function scanRepoDocs(repoDir) {
18956
- if (!repoDir || !existsSync3(repoDir)) return { coverage: 0, tree: [] };
19297
+ if (!repoDir || !existsSync4(repoDir)) return { coverage: 0, tree: [] };
18957
19298
  const files = await listRepoDocs(repoDir);
18958
19299
  const docsDir = docsDirOf(repoDir);
18959
19300
  const index = resolveIndex(repoDir, docsDir);
18960
19301
  const read = (rel) => {
18961
19302
  try {
18962
- return readFileSync4(resolve7(repoDir, rel), "utf8");
19303
+ return readFileSync5(resolve8(repoDir, rel), "utf8");
18963
19304
  } catch {
18964
19305
  return null;
18965
19306
  }
@@ -18982,25 +19323,25 @@ async function scanRepoDocs(repoDir) {
18982
19323
  function docAbsPath(repoDir, rel) {
18983
19324
  if (!rel.endsWith(".md")) throw new Error("hanya file .md yang diizinkan");
18984
19325
  if (rel.split("/").includes(".git")) throw new Error("tidak boleh menyentuh .git");
18985
- const abs = resolve7(repoDir, rel);
19326
+ const abs = resolve8(repoDir, rel);
18986
19327
  if (abs !== repoDir && !abs.startsWith(repoDir + sep)) throw new Error("path keluar dari repo");
18987
19328
  return abs;
18988
19329
  }
18989
19330
  function readDocFile(repoDir, rel) {
18990
19331
  try {
18991
- return readFileSync4(docAbsPath(repoDir, rel), "utf8");
19332
+ return readFileSync5(docAbsPath(repoDir, rel), "utf8");
18992
19333
  } catch {
18993
19334
  return null;
18994
19335
  }
18995
19336
  }
18996
19337
  function writeDocFile(repoDir, rel, content) {
18997
19338
  const abs = docAbsPath(repoDir, rel);
18998
- mkdirSync5(dirname6(abs), { recursive: true });
19339
+ mkdirSync5(dirname7(abs), { recursive: true });
18999
19340
  writeFileSync3(abs, content);
19000
19341
  }
19001
19342
  function deleteDocFile(repoDir, rel) {
19002
19343
  const abs = docAbsPath(repoDir, rel);
19003
- if (!existsSync3(abs)) return false;
19344
+ if (!existsSync4(abs)) return false;
19004
19345
  rmSync2(abs);
19005
19346
  return true;
19006
19347
  }
@@ -19287,13 +19628,13 @@ async function projects_default(app2) {
19287
19628
  // src/routes/specs.ts
19288
19629
  init_src();
19289
19630
  init_src2();
19290
- import { existsSync as existsSync4 } from "node:fs";
19631
+ import { existsSync as existsSync5 } from "node:fs";
19291
19632
 
19292
19633
  // src/services/integrate.ts
19293
19634
  import { execFile as execFile3 } from "node:child_process";
19294
19635
  import { promisify as promisify3 } from "node:util";
19295
19636
  import { rmSync as rmSync3 } from "node:fs";
19296
- import { join as join3 } from "node:path";
19637
+ import { join as join4 } from "node:path";
19297
19638
  var sanitize = (id) => id.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
19298
19639
  var sourceBranch = (specId) => `hanoman/${sanitize(specId)}`;
19299
19640
  var exec3 = promisify3(execFile3);
@@ -19337,7 +19678,7 @@ async function integrateBranch(repoDir, src, op, target2) {
19337
19678
  const tgt = await resolveTarget(repoDir, target2);
19338
19679
  if (!tgt) return { status: "error", code: 400, error: `target "${target2}" tidak dikenal` };
19339
19680
  await sh(repoDir, ["fetch", "origin"]);
19340
- const wt = join3(repoDir, ".worktrees", `merge-${sanitize(src.mergeId)}`);
19681
+ const wt = join4(repoDir, ".worktrees", `merge-${sanitize(src.mergeId)}`);
19341
19682
  await reclaim(repoDir, wt);
19342
19683
  const baseRef = op === "merge" ? tgt.ref : source;
19343
19684
  const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `${baseRef}^{commit}`]);
@@ -19398,7 +19739,7 @@ async function mergeIntoCurrent(repoDir, source, opts = {}) {
19398
19739
  const src = await resolveGraphSource(repoDir, source);
19399
19740
  if (!src) return { status: "error", code: 400, error: `source "${source}" tak dikenal` };
19400
19741
  await sh(repoDir, ["fetch", "origin"]);
19401
- const wt = join3(repoDir, ".worktrees", `merge-${sanitize(current)}`);
19742
+ const wt = join4(repoDir, ".worktrees", `merge-${sanitize(current)}`);
19402
19743
  await reclaim(repoDir, wt);
19403
19744
  const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `refs/heads/${current}^{commit}`]);
19404
19745
  if (!await ok(repoDir, ["worktree", "add", "--detach", "-q", wt, baseSha]))
@@ -19436,7 +19777,7 @@ async function replayCurrent(repoDir, source, cmd) {
19436
19777
  const current = await out(repoDir, ["rev-parse", "--abbrev-ref", "HEAD"]);
19437
19778
  if (!current || current === "HEAD")
19438
19779
  return { status: "error", code: 409, error: "HEAD detached \u2014 checkout sebuah branch dulu" };
19439
- const wt = join3(repoDir, ".worktrees", `merge-${sanitize(current)}`);
19780
+ const wt = join4(repoDir, ".worktrees", `merge-${sanitize(current)}`);
19440
19781
  await reclaim(repoDir, wt);
19441
19782
  const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `refs/heads/${current}^{commit}`]);
19442
19783
  if (!await ok(repoDir, ["worktree", "add", "--detach", "-q", wt, baseSha]))
@@ -19485,16 +19826,16 @@ import { execFile as execFile4 } from "node:child_process";
19485
19826
  import { promisify as promisify4 } from "node:util";
19486
19827
  import { readFile as readFile2, mkdtemp, copyFile, rm } from "node:fs/promises";
19487
19828
  import { tmpdir as tmpdir2 } from "node:os";
19488
- import { join as join4, resolve as resolve8 } from "node:path";
19829
+ import { join as join5, resolve as resolve9 } from "node:path";
19489
19830
  var exec4 = promisify4(execFile4);
19490
19831
  var GIT3 = { maxBuffer: 1 << 24 };
19491
19832
  var MAX = 256 * 1024;
19492
- var worktreeDir = (repoDir, specId) => join4(repoDir, ".worktrees", specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
19833
+ var worktreeDir = (repoDir, specId) => join5(repoDir, ".worktrees", specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
19493
19834
  async function withTempIndex(wt, fn) {
19494
19835
  const idx = (await exec4("git", ["rev-parse", "--git-path", "index"], { cwd: wt, ...GIT3 })).stdout.trim();
19495
- const dir2 = await mkdtemp(join4(tmpdir2(), "hanoman-idx-"));
19496
- const tmp = join4(dir2, "index");
19497
- await copyFile(resolve8(wt, idx), tmp);
19836
+ const dir2 = await mkdtemp(join5(tmpdir2(), "hanoman-idx-"));
19837
+ const tmp = join5(dir2, "index");
19838
+ await copyFile(resolve9(wt, idx), tmp);
19498
19839
  const env = { ...process.env, GIT_INDEX_FILE: tmp };
19499
19840
  try {
19500
19841
  await exec4("git", ["add", "-A", "-N"], { cwd: wt, env, ...GIT3 });
@@ -19618,7 +19959,7 @@ async function reviewFile(repoDir, specId, baseSha, branchFrom, path) {
19618
19959
  let contentRaw = null;
19619
19960
  if (status !== "D") {
19620
19961
  try {
19621
- contentRaw = await readFile2(join4(wt, path), "utf8");
19962
+ contentRaw = await readFile2(join5(wt, path), "utf8");
19622
19963
  } catch {
19623
19964
  contentRaw = null;
19624
19965
  }
@@ -19803,14 +20144,15 @@ async function notificationsFeed(p = {}) {
19803
20144
  init_stage_machine();
19804
20145
 
19805
20146
  // src/services/stage-artifacts.ts
20147
+ init_src();
19806
20148
  init_stage_machine();
19807
20149
  var ARTIFACT_DIR = {
19808
- "spec-ready": "docs/superpowers/specs/",
19809
- planned: "docs/superpowers/plans/"
20150
+ "spec-ready": SPEC_DIRS.map((d) => `${d}/`),
20151
+ planned: PLAN_DIRS.map((d) => `${d}/`)
19810
20152
  };
19811
20153
  async function artifactsToRemove(projectId, specId, target2, current) {
19812
20154
  const ti = STAGES.indexOf(target2), ci = STAGES.indexOf(current);
19813
- const dirs = STAGES.filter((_, i) => i > ti && i <= ci).map((s2) => ARTIFACT_DIR[s2]).filter((d) => !!d);
20155
+ const dirs = STAGES.filter((_, i) => i > ti && i <= ci).flatMap((s2) => ARTIFACT_DIR[s2] ?? []);
19814
20156
  if (!dirs.length) return [];
19815
20157
  const repoDir = await resolveRepoDir(projectId);
19816
20158
  if (!repoDir) return [];
@@ -19842,6 +20184,7 @@ async function deleteDoc(projectId, path) {
19842
20184
  }
19843
20185
 
19844
20186
  // src/services/spec-docs.ts
20187
+ init_src();
19845
20188
  init_db();
19846
20189
  init_pty();
19847
20190
  var ORDER = ["audit", "spec", "plan", "objective", "brainstorm", "other"];
@@ -19850,8 +20193,8 @@ function kindOf(path) {
19850
20193
  if (p.endsWith("-audit.md") || p.includes("/research/audit-")) return "audit";
19851
20194
  if (p.endsWith("-objective.md")) return "objective";
19852
20195
  if (p.endsWith("-brainstorm.md")) return "brainstorm";
19853
- if (p.endsWith("-design.md") || p.endsWith("-spec.md") || p.startsWith("docs/superpowers/specs/")) return "spec";
19854
- if (p.startsWith("docs/superpowers/plans/") || p.endsWith("-plan.md")) return "plan";
20196
+ if (p.endsWith("-design.md") || p.endsWith("-spec.md") || SPEC_DIRS.some((d) => p.startsWith(`${d}/`))) return "spec";
20197
+ if (PLAN_DIRS.some((d) => p.startsWith(`${d}/`)) || p.endsWith("-plan.md")) return "plan";
19855
20198
  return "other";
19856
20199
  }
19857
20200
  async function resolveDir(specId, sessions = listSessions()) {
@@ -22381,7 +22724,7 @@ function renderDocPdf(text, name2, meta) {
22381
22724
  });
22382
22725
  const chunks = [];
22383
22726
  doc.on("data", (c) => chunks.push(c));
22384
- const done = new Promise((resolve15) => doc.on("end", () => resolve15(Buffer.concat(chunks))));
22727
+ const done = new Promise((resolve16) => doc.on("end", () => resolve16(Buffer.concat(chunks))));
22385
22728
  doc.font("Helvetica-Bold").fontSize(8).fillColor(BRASS).text(toWinAnsi(meta.eyebrow.toUpperCase()), { characterSpacing: 0.8 });
22386
22729
  doc.moveDown(0.25);
22387
22730
  doc.font("Helvetica-Bold").fontSize(17).fillColor(STRONG).text(toWinAnsi(name2.split("/").pop() ?? name2));
@@ -22763,7 +23106,7 @@ ${item.context}` : item.context;
22763
23106
  });
22764
23107
  const specWithProject = (id) => prisma.spec.findUnique({ where: { id }, include: { project: true } });
22765
23108
  const resolveReview = async (repoDir, spec) => {
22766
- if (existsSync4(worktreeDir(repoDir, spec.id))) return { wt: true };
23109
+ if (existsSync5(worktreeDir(repoDir, spec.id))) return { wt: true };
22767
23110
  if (spec.baseSha && spec.headSha && await shaResolvable(repoDir, spec.baseSha) && await shaResolvable(repoDir, spec.headSha))
22768
23111
  return { wt: false, base: spec.baseSha, head: spec.headSha };
22769
23112
  const r = await specCommitRange(repoDir, spec.id);
@@ -22998,13 +23341,13 @@ async function docs_default(app2) {
22998
23341
 
22999
23342
  // src/routes/ide.ts
23000
23343
  init_src2();
23001
- import { basename } from "node:path";
23344
+ import { basename as basename2 } from "node:path";
23002
23345
  import { spawn as spawn2 } from "node:child_process";
23003
23346
 
23004
23347
  // src/services/git-remotes.ts
23005
23348
  import { execFile as execFile5 } from "node:child_process";
23006
23349
  import { promisify as promisify5 } from "node:util";
23007
- import { existsSync as existsSync5 } from "node:fs";
23350
+ import { existsSync as existsSync6 } from "node:fs";
23008
23351
  var exec5 = promisify5(execFile5);
23009
23352
  var GIT4 = { maxBuffer: 1 << 24, encoding: "utf8" };
23010
23353
  function parseRemote(url2) {
@@ -23023,7 +23366,7 @@ function prUrl(remoteUrl, branch, base2 = "main") {
23023
23366
  return null;
23024
23367
  }
23025
23368
  async function listRemotes(repoDir) {
23026
- if (!repoDir || !existsSync5(repoDir)) return [];
23369
+ if (!repoDir || !existsSync6(repoDir)) return [];
23027
23370
  try {
23028
23371
  const { stdout } = await exec5("git", ["remote", "-v"], { cwd: repoDir, ...GIT4 });
23029
23372
  const map = /* @__PURE__ */ new Map();
@@ -23065,20 +23408,20 @@ import { promisify as promisify7 } from "node:util";
23065
23408
  import { execFile as execFile6 } from "node:child_process";
23066
23409
  import { promisify as promisify6 } from "node:util";
23067
23410
  import { readFile as readFile3, writeFile, mkdir } from "node:fs/promises";
23068
- import { existsSync as existsSync6 } from "node:fs";
23069
- import { resolve as resolve9, sep as sep2, dirname as dirname8 } from "node:path";
23411
+ import { existsSync as existsSync7 } from "node:fs";
23412
+ import { resolve as resolve10, sep as sep2, dirname as dirname9 } from "node:path";
23070
23413
  var exec6 = promisify6(execFile6);
23071
23414
  var GIT5 = { maxBuffer: 1 << 24 };
23072
23415
  var MAX2 = 256 * 1024;
23073
23416
  var splitZ2 = (s2) => s2.split("\0").filter(Boolean);
23074
23417
  function repoAbsPath(repoDir, rel) {
23075
23418
  if (rel.split(/[\\/]/).includes(".git")) throw new Error("tidak boleh menyentuh .git");
23076
- const abs = resolve9(repoDir, rel);
23419
+ const abs = resolve10(repoDir, rel);
23077
23420
  if (abs !== repoDir && !abs.startsWith(repoDir + sep2)) throw new Error("path keluar dari repo");
23078
23421
  return abs;
23079
23422
  }
23080
23423
  async function listRepoTree(repoDir, ref = "") {
23081
- if (!repoDir || !existsSync6(repoDir)) return [];
23424
+ if (!repoDir || !existsSync7(repoDir)) return [];
23082
23425
  try {
23083
23426
  const { stdout } = ref ? await exec6("git", ["ls-tree", "-r", "--name-only", "-z", ref], { cwd: repoDir, ...GIT5 }) : await exec6("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], { cwd: repoDir, ...GIT5 });
23084
23427
  return [...new Set(splitZ2(stdout))].sort();
@@ -23104,7 +23447,7 @@ async function currentBranch(repoDir) {
23104
23447
  }
23105
23448
  async function repoStatus(repoDir) {
23106
23449
  const empty = { branch: "", ahead: 0, behind: 0, staged: [], unstaged: [], untracked: [], clean: true };
23107
- if (!repoDir || !existsSync6(repoDir)) return empty;
23450
+ if (!repoDir || !existsSync7(repoDir)) return empty;
23108
23451
  try {
23109
23452
  const { stdout } = await exec6("git", ["status", "--porcelain=v1", "-z", "-b"], { cwd: repoDir, ...GIT5 });
23110
23453
  const parts = stdout.split("\0");
@@ -23137,7 +23480,7 @@ async function repoStatus(repoDir) {
23137
23480
  }
23138
23481
  }
23139
23482
  async function listStashes(repoDir) {
23140
- if (!repoDir || !existsSync6(repoDir)) return [];
23483
+ if (!repoDir || !existsSync7(repoDir)) return [];
23141
23484
  try {
23142
23485
  const { stdout } = await exec6("git", ["stash", "list", `--format=%gd${US}%s${US}%aI`], { cwd: repoDir, ...GIT5 });
23143
23486
  return stdout.split("\n").filter(Boolean).map((line) => {
@@ -23149,7 +23492,7 @@ async function listStashes(repoDir) {
23149
23492
  }
23150
23493
  }
23151
23494
  async function listGraph(repoDir, limit = 200, opts = {}) {
23152
- if (!repoDir || !existsSync6(repoDir)) return { commits: [], current: "", total: 0 };
23495
+ if (!repoDir || !existsSync7(repoDir)) return { commits: [], current: "", total: 0 };
23153
23496
  try {
23154
23497
  const fmt = ["%H", "%P", "%an", "%aI", "%s", "%D"].join(US);
23155
23498
  const refArgs = opts.branches?.length ? ["--end-of-options", ...opts.branches] : [
@@ -23219,7 +23562,7 @@ async function changedOf(repoDir, sha) {
23219
23562
  }
23220
23563
  async function compareCommits(repoDir, from, to) {
23221
23564
  const empty = { from, to, changed: [] };
23222
- if (!repoDir || !existsSync6(repoDir)) return empty;
23565
+ if (!repoDir || !existsSync7(repoDir)) return empty;
23223
23566
  try {
23224
23567
  const [num, name2] = await Promise.all([
23225
23568
  exec6("git", ["diff", "--numstat", "-z", "--no-renames", "--end-of-options", from, to], { cwd: repoDir, ...GIT5 }),
@@ -23231,7 +23574,7 @@ async function compareCommits(repoDir, from, to) {
23231
23574
  }
23232
23575
  }
23233
23576
  async function searchCommits(repoDir, q, by = "all") {
23234
- if (!repoDir || !existsSync6(repoDir) || !q) return [];
23577
+ if (!repoDir || !existsSync7(repoDir) || !q) return [];
23235
23578
  const run2 = async (extra) => {
23236
23579
  try {
23237
23580
  return (await exec6("git", ["log", "--all", "--format=%H", ...extra], { cwd: repoDir, ...GIT5 })).stdout.split("\n").filter(Boolean);
@@ -23312,7 +23655,7 @@ async function commitFileDiff(repoDir, sha, rel) {
23312
23655
  async function writeRepoFile(repoDir, rel, content) {
23313
23656
  if (!repoDir) throw new Error("project tidak punya repoDir");
23314
23657
  const abs = repoAbsPath(repoDir, rel);
23315
- await mkdir(dirname8(abs), { recursive: true });
23658
+ await mkdir(dirname9(abs), { recursive: true });
23316
23659
  await writeFile(abs, content);
23317
23660
  }
23318
23661
  function validateGitOp(op) {
@@ -23480,7 +23823,7 @@ async function runGitOp(repoDir, op) {
23480
23823
  }
23481
23824
  }
23482
23825
  async function workingStatus(repoDir) {
23483
- if (!repoDir || !existsSync6(repoDir)) return { branch: "", staged: [], unstaged: [] };
23826
+ if (!repoDir || !existsSync7(repoDir)) return { branch: "", staged: [], unstaged: [] };
23484
23827
  const [branch, staged, unstaged] = await Promise.all([
23485
23828
  currentBranch(repoDir),
23486
23829
  changedFiles(repoDir, ["--cached"]),
@@ -23489,7 +23832,7 @@ async function workingStatus(repoDir) {
23489
23832
  return { branch, staged, unstaged };
23490
23833
  }
23491
23834
  async function workingFileDiff(repoDir, path, staged) {
23492
- if (!repoDir || !existsSync6(repoDir)) return null;
23835
+ if (!repoDir || !existsSync7(repoDir)) return null;
23493
23836
  repoAbsPath(repoDir, path);
23494
23837
  const changed = staged ? await changedFiles(repoDir, ["--cached"]) : await withTempIndex(repoDir, (env) => changedFiles(repoDir, [], env));
23495
23838
  const cf = changed.find((c) => c.path === path);
@@ -23808,7 +24151,7 @@ async function ide_default(app2) {
23808
24151
  const fmt = q.format === "tar" ? "tar" : "zip";
23809
24152
  const child = spawn2("git", ["archive", `--format=${fmt}`, "--end-of-options", ref], { cwd: repoDir });
23810
24153
  reply.header("content-type", fmt === "zip" ? "application/zip" : "application/x-tar");
23811
- reply.header("content-disposition", `attachment; filename="${basename(repoDir)}-${ref.replace(/[^\w.-]/g, "_")}.${fmt}"`);
24154
+ reply.header("content-disposition", `attachment; filename="${basename2(repoDir)}-${ref.replace(/[^\w.-]/g, "_")}.${fmt}"`);
23812
24155
  return reply.send(child.stdout);
23813
24156
  });
23814
24157
  app2.get("/projects/:id/commit/:sha", async (req, reply) => {
@@ -23960,22 +24303,22 @@ async function finishGraphOp(reply, id, repoDir, r, verb) {
23960
24303
  CODE_STYLE_CLAUSE,
23961
24304
  `${verb} via git graph project ${id}.`
23962
24305
  ].join("\n\n");
23963
- const s2 = createSession(id, r.worktree, { id: basename(r.worktree), model, effort, agent, prompt });
24306
+ const s2 = createSession(id, r.worktree, { id: basename2(r.worktree), model, effort, agent, prompt });
23964
24307
  return { status: "conflict", sessionId: s2.id };
23965
24308
  }
23966
24309
 
23967
24310
  // src/routes/fs.ts
23968
24311
  import { readdir } from "node:fs/promises";
23969
- import { homedir as homedir3 } from "node:os";
23970
- import { resolve as resolve10, dirname as dirname9, join as join5 } from "node:path";
24312
+ import { homedir as homedir4 } from "node:os";
24313
+ import { resolve as resolve11, dirname as dirname10, join as join6 } from "node:path";
23971
24314
  async function fs_default(app2) {
23972
24315
  app2.get("/fs/browse", async (req, reply) => {
23973
24316
  const q = req.query.path;
23974
- const dir2 = q && q.trim() ? resolve10(q.trim()) : homedir3();
24317
+ const dir2 = q && q.trim() ? resolve11(q.trim()) : homedir4();
23975
24318
  try {
23976
24319
  const ents = await readdir(dir2, { withFileTypes: true });
23977
- const entries = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: join5(dir2, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
23978
- const parent = dirname9(dir2);
24320
+ const entries = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: join6(dir2, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
24321
+ const parent = dirname10(dir2);
23979
24322
  return { path: dir2, parent: parent === dir2 ? null : parent, entries };
23980
24323
  } catch {
23981
24324
  return reply.code(400).send({ error: `tak bisa membaca folder "${dir2}"` });
@@ -23988,13 +24331,14 @@ init_db();
23988
24331
  init_src();
23989
24332
  init_src2();
23990
24333
  init_session_phases();
23991
- import { existsSync as existsSync7 } from "node:fs";
24334
+ import { existsSync as existsSync8 } from "node:fs";
23992
24335
  init_settings2();
23993
24336
  init_codex_trust();
23994
24337
 
23995
24338
  // src/services/session-launch.ts
23996
24339
  init_db();
23997
24340
  init_src2();
24341
+ init_src();
23998
24342
  init_settings2();
23999
24343
  init_codex_trust();
24000
24344
  init_pty();
@@ -24051,6 +24395,8 @@ async function startSpecSession(spec, opts) {
24051
24395
  };
24052
24396
  const goal = isGoalFlow || (opts.goal ?? setting.goal.enabled) ? resolveGoalCondition(goalArgs, opts.goalCondition, isGoalFlow ? null : setting.goal.condition) : void 0;
24053
24397
  const verifyScope = opts.verifyScope ?? setting.verifyScope;
24398
+ const recordedMethod = readSpecMethod(spec.payload);
24399
+ const method = resolveMethod(opts.method ?? recordedMethod ?? setting.method);
24054
24400
  if (agent === "codex") ensureCodexTrust(repoDir);
24055
24401
  let baseSha;
24056
24402
  if (resume?.worktreeKept) {
@@ -24067,6 +24413,10 @@ async function startSpecSession(spec, opts) {
24067
24413
  data: { baseSha, headSha: null, startedAt: /* @__PURE__ */ new Date() }
24068
24414
  });
24069
24415
  }
24416
+ if (!recordedMethod) {
24417
+ const stamped = stampSpecMethod(spec.payload, method.id);
24418
+ if (stamped) await prisma.spec.update({ where: { id: spec.id }, data: { payload: stamped } });
24419
+ }
24070
24420
  const brief = {
24071
24421
  id: spec.id,
24072
24422
  title: spec.title,
@@ -24081,14 +24431,15 @@ async function startSpecSession(spec, opts) {
24081
24431
  prompt = startGoalPrompt(brief, branchTo, {
24082
24432
  autonomy: opts.autonomy,
24083
24433
  verifyScope,
24084
- resume: resumeCtx
24434
+ resume: resumeCtx,
24435
+ method: method.id
24085
24436
  });
24086
24437
  } else if (isContinue) {
24087
- prompt = continuePrompt(opts.flow, brief, branchTo, opts.autonomy, verifyScope);
24438
+ prompt = continuePrompt(opts.flow, brief, branchTo, opts.autonomy, verifyScope, method.id);
24088
24439
  } else if (resumeCtx) {
24089
- prompt = resumePrompt(opts.flow, brief, branchTo, resumeCtx, opts.autonomy, verifyScope);
24440
+ prompt = resumePrompt(opts.flow, brief, branchTo, resumeCtx, opts.autonomy, verifyScope, method.id);
24090
24441
  } else {
24091
- prompt = startPrompt(opts.flow, brief, branchTo, opts.autonomy, verifyScope);
24442
+ prompt = startPrompt(opts.flow, brief, branchTo, opts.autonomy, verifyScope, method.id);
24092
24443
  }
24093
24444
  const scopeEnv = { HANOMAN_BASE_SHA: baseSha, HANOMAN_VERIFY_SCOPE: verifyScope };
24094
24445
  const s2 = createSession(spec.projectId, worktree, {
@@ -24106,12 +24457,30 @@ async function startSpecSession(spec, opts) {
24106
24457
  return resume ? { id: s2.id, resumed: true } : { id: s2.id };
24107
24458
  }
24108
24459
 
24460
+ // src/services/method-status.ts
24461
+ init_src();
24462
+ init_src2();
24463
+ init_pty();
24464
+ function methodStatusReport(env = process.env) {
24465
+ const scans = zAgent.options.map((a) => scanAgentSkills(a, env));
24466
+ return {
24467
+ agents: scans.map((s2) => ({ agent: s2.agent, home: s2.home, roots: s2.roots, skills: s2.skills.length })),
24468
+ methods: scans.flatMap((s2) => {
24469
+ const installed = { skills: s2.skills.map((k) => k.id), packages: s2.packages };
24470
+ return METHOD_IDS.map((id) => methodStatus(METHODS[id], s2.agent, installed));
24471
+ })
24472
+ };
24473
+ }
24474
+ function installCommand(m, agent, shell = shellBin()) {
24475
+ return [shell, "-lc", `${m.install[agent].join(" && ")}; exec '${shell}' -l`];
24476
+ }
24477
+
24109
24478
  // src/services/session-worktree.ts
24110
- import { resolve as resolve11, sep as sep3 } from "node:path";
24479
+ import { resolve as resolve12, sep as sep3 } from "node:path";
24111
24480
  function ownsWorktree(repoDir, cwd) {
24112
- const base2 = resolve11(repoDir);
24113
- const dir2 = resolve11(cwd);
24114
- const container = resolve11(base2, ".worktrees");
24481
+ const base2 = resolve12(repoDir);
24482
+ const dir2 = resolve12(cwd);
24483
+ const container = resolve12(base2, ".worktrees");
24115
24484
  return dir2 !== base2 && dir2 !== container && dir2.startsWith(container + sep3);
24116
24485
  }
24117
24486
 
@@ -24196,6 +24565,8 @@ async function terminal_default(app2) {
24196
24565
  // SPEC-338 · ADR-0074
24197
24566
  verifyScope: parsed.data.verifyScope,
24198
24567
  // SPEC-376 · ADR-0080
24568
+ method: parsed.data.method,
24569
+ // SPEC-734 · ADR-0113
24199
24570
  force: parsed.data.force
24200
24571
  // SPEC-447 · ADR-0093
24201
24572
  });
@@ -24214,6 +24585,13 @@ async function terminal_default(app2) {
24214
24585
  if ("shell" in parsed.data) {
24215
24586
  const repoDir2 = await resolveRepoDir(project.id);
24216
24587
  if (!repoDir2) return reply.code(400).send({ error: `project "${project.id}" belum di-bind ke checkout lokal`, needsBind: true });
24588
+ const inst = parsed.data.install;
24589
+ if (inst) {
24590
+ const m = METHODS[inst.method];
24591
+ if (!m) return reply.code(400).send({ error: `metode "${inst.method}" tak dikenal` });
24592
+ const s4 = createSession(project.id, repoDir2, { command: installCommand(m, inst.agent) });
24593
+ return reply.code(201).send({ id: s4.id });
24594
+ }
24217
24595
  const s3 = createSession(project.id, repoDir2, { command: [shellBin()] });
24218
24596
  return reply.code(201).send({ id: s3.id });
24219
24597
  }
@@ -24382,7 +24760,7 @@ async function terminal_default(app2) {
24382
24760
  if (!s2) return { ok: false, code: 404, msg: "not found" };
24383
24761
  const repoDir = await resolveRepoDir(s2.projectId);
24384
24762
  if (!repoDir) return { ok: false, code: 409, msg: "project belum punya repoDir" };
24385
- if (!s2.cwd.includes("/.worktrees/") || !existsSync7(s2.cwd))
24763
+ if (!s2.cwd.includes("/.worktrees/") || !existsSync8(s2.cwd))
24386
24764
  return { ok: false, code: 409, msg: "belum ada worktree untuk di-review \u2014 jalankan/lanjutkan sesi dulu" };
24387
24765
  return { ok: true, id: s2.id, repoDir };
24388
24766
  };
@@ -24474,8 +24852,8 @@ async function terminal_default(app2) {
24474
24852
  // src/routes/vps.ts
24475
24853
  init_db();
24476
24854
  init_src();
24477
- import { readFileSync as readFileSync9, existsSync as existsSync10 } from "node:fs";
24478
- import { homedir as homedir5 } from "node:os";
24855
+ import { readFileSync as readFileSync10, existsSync as existsSync11 } from "node:fs";
24856
+ import { homedir as homedir6 } from "node:os";
24479
24857
 
24480
24858
  // src/vps/catalog/catalog.data.ts
24481
24859
  var RAW_SECTIONS = [{ "id": "ssh", "title": "SSH Hardening", "icon": "\u{1F511}", "count": 14 }, { "id": "firewall", "title": "Firewall & Network", "icon": "\u{1F525}", "count": 14 }, { "id": "aapanel", "title": "aaPanel Security", "icon": "\u{1F5A5}\uFE0F", "count": 14 }, { "id": "user", "title": "User & Permission Management", "icon": "\u{1F464}", "count": 14 }, { "id": "system", "title": "System Hardening (OS Level)", "icon": "\u2699\uFE0F", "count": 16 }, { "id": "webserver", "title": "Web Server Hardening", "icon": "\u{1F310}", "count": 14 }, { "id": "database", "title": "Database Security", "icon": "\u{1F5C4}\uFE0F", "count": 15 }, { "id": "ssl", "title": "SSL/TLS & Enkripsi", "icon": "\u{1F512}", "count": 13 }, { "id": "ids", "title": "Intrusion Detection & Prevention", "icon": "\u{1F6A8}", "count": 9 }, { "id": "logging", "title": "Logging & Monitoring", "icon": "\u{1F4CA}", "count": 13 }, { "id": "backup", "title": "Backup & Disaster Recovery", "icon": "\u{1F4BE}", "count": 12 }, { "id": "ddos", "title": "DDoS Protection", "icon": "\u{1F30A}", "count": 11 }, { "id": "kernel", "title": "Kernel Hardening", "icon": "\u{1F9E0}", "count": 12 }, { "id": "malware", "title": "File Integrity & Malware Detection", "icon": "\u{1F9A0}", "count": 7 }, { "id": "enterprise", "title": "Advanced Enterprise & Government Level", "icon": "\u{1F3E2}", "count": 31 }, { "id": "checklist", "title": "Checklist Rutin", "icon": "\u2705", "count": 23 }];
@@ -24557,14 +24935,14 @@ var _map = new Map(CATALOG.map((c) => [c.id, c]));
24557
24935
  var byId = (id) => _map.get(id);
24558
24936
 
24559
24937
  // src/services/vps-remediate.ts
24560
- import { readFileSync as readFileSync7 } from "node:fs";
24938
+ import { readFileSync as readFileSync8 } from "node:fs";
24561
24939
 
24562
24940
  // src/services/vps-ssh.ts
24563
24941
  init_config2();
24564
24942
  import { spawn as spawn3 } from "node:child_process";
24565
24943
  import { mkdtempSync, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "node:fs";
24566
24944
  import { tmpdir as tmpdir3 } from "node:os";
24567
- import { dirname as dirname10, join as join6 } from "node:path";
24945
+ import { dirname as dirname11, join as join7 } from "node:path";
24568
24946
  var sshBin = () => effectiveStr("HANOMAN_SSH_BIN") ?? "ssh";
24569
24947
  function consoleArgv(t) {
24570
24948
  return [
@@ -24579,8 +24957,8 @@ function consoleArgv(t) {
24579
24957
  ];
24580
24958
  }
24581
24959
  function askpassScript() {
24582
- const dir2 = mkdtempSync(join6(tmpdir3(), "hanoman-askpass-"));
24583
- const path = join6(dir2, "askpass.sh");
24960
+ const dir2 = mkdtempSync(join7(tmpdir3(), "hanoman-askpass-"));
24961
+ const path = join7(dir2, "askpass.sh");
24584
24962
  writeFileSync4(path, `#!/bin/sh
24585
24963
  printf '%s' "$HANOMAN_SSH_PASSWORD"
24586
24964
  `, { mode: 448 });
@@ -24614,14 +24992,14 @@ function sshExec(t, remoteCmd, opts = {}) {
24614
24992
  SSH_ASKPASS_REQUIRE: "force",
24615
24993
  HANOMAN_SSH_PASSWORD: opts.password
24616
24994
  } : process.env;
24617
- return new Promise((resolve15) => {
24995
+ return new Promise((resolve16) => {
24618
24996
  const p = spawn3(sshBin(), args, { stdio: ["pipe", "pipe", "pipe"], env });
24619
24997
  let out3 = "";
24620
24998
  const timer7 = setTimeout(() => p.kill("SIGKILL"), opts.timeoutMs ?? 6e4);
24621
24999
  const done = (r) => {
24622
25000
  clearTimeout(timer7);
24623
- if (askpass) rmSync4(dirname10(askpass), { recursive: true, force: true });
24624
- resolve15(r);
25001
+ if (askpass) rmSync4(dirname11(askpass), { recursive: true, force: true });
25002
+ resolve16(r);
24625
25003
  };
24626
25004
  p.stdout.on("data", (d) => {
24627
25005
  out3 += d;
@@ -24640,17 +25018,17 @@ function sshExec(t, remoteCmd, opts = {}) {
24640
25018
 
24641
25019
  // src/services/vps-audit.ts
24642
25020
  init_db();
24643
- import { readFileSync as readFileSync6 } from "node:fs";
24644
- import { join as join8 } from "node:path";
25021
+ import { readFileSync as readFileSync7 } from "node:fs";
25022
+ import { join as join9 } from "node:path";
24645
25023
 
24646
25024
  // src/runner/deps.ts
24647
- import { existsSync as existsSync8 } from "node:fs";
24648
- import { dirname as dirname11, join as join7 } from "node:path";
25025
+ import { existsSync as existsSync9 } from "node:fs";
25026
+ import { dirname as dirname12, join as join8 } from "node:path";
24649
25027
  function repoRootFrom(startDir) {
24650
25028
  let dir2 = startDir;
24651
25029
  for (let i = 0; i < 8; i++) {
24652
- if (existsSync8(join7(dir2, "pnpm-workspace.yaml"))) return dir2;
24653
- const parent = dirname11(dir2);
25030
+ if (existsSync9(join8(dir2, "pnpm-workspace.yaml"))) return dir2;
25031
+ const parent = dirname12(dir2);
24654
25032
  if (parent === dir2) break;
24655
25033
  dir2 = parent;
24656
25034
  }
@@ -24754,7 +25132,7 @@ function parseHealth(out3) {
24754
25132
  return { uptime: h.uptime ?? "", disk: h.disk ?? "", mem: h.mem ?? "", load: h.load ?? "" };
24755
25133
  }
24756
25134
  var target = (v) => ({ host: v.host, port: v.port, user: v.user, keyPath: v.keyPath });
24757
- var scriptPath = (f) => join8(repoRoot(), "server", "scripts", "vps", f);
25135
+ var scriptPath = (f) => join9(repoRoot(), "server", "scripts", "vps", f);
24758
25136
  async function itemStatesOf(vpsId) {
24759
25137
  const rows = await prisma.vpsItemState.findMany({ where: { vpsId } });
24760
25138
  return Object.fromEntries(rows.map((s2) => [s2.itemId, { na: s2.na, attested: s2.attested }]));
@@ -24763,7 +25141,7 @@ async function runAudit(v) {
24763
25141
  const r = await sshExec(
24764
25142
  target(v),
24765
25143
  "sudo -n bash -s",
24766
- { stdin: readFileSync6(scriptPath("audit.sh"), "utf8"), timeoutMs: 6e4 }
25144
+ { stdin: readFileSync7(scriptPath("audit.sh"), "utf8"), timeoutMs: 6e4 }
24767
25145
  );
24768
25146
  const audit = parseAudit(r.out);
24769
25147
  if (r.code !== 0 || audit.length === 0) return { ok: false, out: r.out };
@@ -24822,7 +25200,7 @@ async function remediate(v, items, dryRun) {
24822
25200
  const r = await sshExec(
24823
25201
  v,
24824
25202
  `sudo -n env ITEMS=${items.join(",")} SSH_PORT=${v.port} DRY_RUN=${dryRun ? "1" : ""} bash -s`,
24825
- { stdin: readFileSync7(scriptPath("remediate.sh"), "utf8"), timeoutMs: 3e5 }
25203
+ { stdin: readFileSync8(scriptPath("remediate.sh"), "utf8"), timeoutMs: 3e5 }
24826
25204
  );
24827
25205
  return { ok: r.code === 0, steps: parseSteps(r.out), out: r.out };
24828
25206
  }
@@ -24901,15 +25279,15 @@ async function buildChecklist(vpsId) {
24901
25279
  // src/services/vps-key.ts
24902
25280
  init_config2();
24903
25281
  import { execFileSync as execFileSync2 } from "node:child_process";
24904
- import { existsSync as existsSync9, mkdirSync as mkdirSync8, readFileSync as readFileSync8 } from "node:fs";
24905
- import { homedir as homedir4 } from "node:os";
24906
- import { join as join9 } from "node:path";
24907
- var keyDir = () => effectiveStr("HANOMAN_SSH_KEY_DIR") ?? join9(homedir4(), ".hanoman");
25282
+ import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync9 } from "node:fs";
25283
+ import { homedir as homedir5 } from "node:os";
25284
+ import { join as join10 } from "node:path";
25285
+ var keyDir = () => effectiveStr("HANOMAN_SSH_KEY_DIR") ?? join10(homedir5(), ".hanoman");
24908
25286
  function ensureHanomanKey() {
24909
25287
  const dir2 = keyDir();
24910
- const privPath = join9(dir2, "id_ed25519");
25288
+ const privPath = join10(dir2, "id_ed25519");
24911
25289
  const pubPath = `${privPath}.pub`;
24912
- if (!existsSync9(privPath)) {
25290
+ if (!existsSync10(privPath)) {
24913
25291
  mkdirSync8(dir2, { recursive: true, mode: 448 });
24914
25292
  execFileSync2(
24915
25293
  "ssh-keygen",
@@ -24917,7 +25295,7 @@ function ensureHanomanKey() {
24917
25295
  { stdio: ["ignore", "ignore", "pipe"] }
24918
25296
  );
24919
25297
  }
24920
- return { privPath, pubPath, pub: readFileSync8(pubPath, "utf8").trim() };
25298
+ return { privPath, pubPath, pub: readFileSync9(pubPath, "utf8").trim() };
24921
25299
  }
24922
25300
 
24923
25301
  // src/services/vps-bootstrap.ts
@@ -24936,7 +25314,7 @@ async function bootstrapKey(t, password) {
24936
25314
  init_pty();
24937
25315
  init_settings2();
24938
25316
  function keyMissing(v) {
24939
- return !!v.keyPath && !existsSync10(v.keyPath);
25317
+ return !!v.keyPath && !existsSync11(v.keyPath);
24940
25318
  }
24941
25319
  async function vps_default(app2) {
24942
25320
  app2.get("/vps", async () => prisma.vps.findMany({ orderBy: { createdAt: "asc" } }));
@@ -25094,7 +25472,7 @@ async function vps_default(app2) {
25094
25472
  const r = await sshExec(
25095
25473
  v,
25096
25474
  `sudo -n env SSH_PORT=${v.port} SSH_USER=${v.user} bash -s`,
25097
- { stdin: readFileSync9(scriptPath("harden.sh"), "utf8"), timeoutMs: 3e5 }
25475
+ { stdin: readFileSync10(scriptPath("harden.sh"), "utf8"), timeoutMs: 3e5 }
25098
25476
  );
25099
25477
  if (r.code !== 0) return reply.code(502).send({ error: "harden gagal lewat ssh", transcript: r.out });
25100
25478
  const verify = await sshExec(v, "true", { timeoutMs: 3e4 });
@@ -25119,7 +25497,7 @@ async function vps_default(app2) {
25119
25497
  const v = await prisma.vps.findUnique({ where: { id: req.params.id } });
25120
25498
  if (!v) return reply.code(404).send({ error: "not found" });
25121
25499
  if (keyMissing(v)) return reply.code(409).send({ error: "key VPS tidak ada di mesin ini", keyMissing: true });
25122
- const s2 = createSession(`vps-console:${v.id}`, homedir5(), { id: `vpsc-${v.id}`, command: consoleArgv(v) });
25500
+ const s2 = createSession(`vps-console:${v.id}`, homedir6(), { id: `vpsc-${v.id}`, command: consoleArgv(v) });
25123
25501
  return reply.code(201).send({ id: s2.id });
25124
25502
  });
25125
25503
  app2.post("/vps/:id/session", async (req, reply) => {
@@ -25128,7 +25506,7 @@ async function vps_default(app2) {
25128
25506
  if (keyMissing(v)) return reply.code(409).send({ error: "key VPS tidak ada di mesin ini", keyMissing: true });
25129
25507
  const checks = v.audit ?? [];
25130
25508
  const { model, effort } = await sessionModel();
25131
- const s2 = createSession(`vps:${v.id}`, homedir5(), {
25509
+ const s2 = createSession(`vps:${v.id}`, homedir6(), {
25132
25510
  model,
25133
25511
  effort,
25134
25512
  prompt: [
@@ -25146,9 +25524,9 @@ async function vps_default(app2) {
25146
25524
  // src/services/limits.ts
25147
25525
  init_config2();
25148
25526
  import { execFileSync as execFileSync3 } from "node:child_process";
25149
- import { readFileSync as readFileSync10 } from "node:fs";
25150
- import { homedir as homedir6 } from "node:os";
25151
- import { join as join10 } from "node:path";
25527
+ import { readFileSync as readFileSync11 } from "node:fs";
25528
+ import { homedir as homedir7 } from "node:os";
25529
+ import { join as join11 } from "node:path";
25152
25530
  var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
25153
25531
  var TTL_MS2 = 3e4;
25154
25532
  var lastOk = null;
@@ -25161,7 +25539,7 @@ var LABELS = {
25161
25539
  var humanize = (k) => k.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
25162
25540
  var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
25163
25541
  function credsFile() {
25164
- return join10(effectiveStr("CLAUDE_CONFIG_DIR") ?? join10(homedir6(), ".claude"), ".credentials.json");
25542
+ return join11(effectiveStr("CLAUDE_CONFIG_DIR") ?? join11(homedir7(), ".claude"), ".credentials.json");
25165
25543
  }
25166
25544
  function readAccessToken() {
25167
25545
  if (process.platform === "darwin" && !effectiveStr("CLAUDE_CONFIG_DIR")) {
@@ -25177,7 +25555,7 @@ function readAccessToken() {
25177
25555
  }
25178
25556
  }
25179
25557
  try {
25180
- return JSON.parse(readFileSync10(credsFile(), "utf8"))?.claudeAiOauth?.accessToken ?? null;
25558
+ return JSON.parse(readFileSync11(credsFile(), "utf8"))?.claudeAiOauth?.accessToken ?? null;
25181
25559
  } catch {
25182
25560
  return null;
25183
25561
  }
@@ -25234,15 +25612,15 @@ async function getLimits() {
25234
25612
 
25235
25613
  // src/services/codex-limits.ts
25236
25614
  import { open as open2, readdir as readdir2, stat } from "node:fs/promises";
25237
- import { homedir as homedir7 } from "node:os";
25238
- import { join as join11 } from "node:path";
25615
+ import { homedir as homedir8 } from "node:os";
25616
+ import { join as join12 } from "node:path";
25239
25617
  var TTL_MS3 = 3e4;
25240
25618
  var STALE_AFTER_MS = 12 * 36e5;
25241
25619
  var TAIL_BYTES = 512 * 1024;
25242
25620
  var MAX_FILES = 8;
25243
25621
  var cache2 = null;
25244
25622
  var freshUntil2 = 0;
25245
- var codexSessionsDir = () => join11(process.env.CODEX_HOME ?? join11(homedir7(), ".codex"), "sessions");
25623
+ var codexSessionsDir = () => join12(process.env.CODEX_HOME ?? join12(homedir8(), ".codex"), "sessions");
25246
25624
  var UNAVAILABLE = { status: "unavailable", windows: [], fetchedAt: null, plan: null };
25247
25625
  function windowLabel(minutes) {
25248
25626
  if (minutes === 300) return "Sesi 5 jam";
@@ -25275,7 +25653,7 @@ async function recentRollouts(dir2) {
25275
25653
  return [];
25276
25654
  }
25277
25655
  const stamped = await Promise.all(entries.map(async (rel) => {
25278
- const full = join11(dir2, rel);
25656
+ const full = join12(dir2, rel);
25279
25657
  try {
25280
25658
  return { full, mtime: (await stat(full)).mtimeMs };
25281
25659
  } catch {
@@ -25391,14 +25769,19 @@ async function codex(app2) {
25391
25769
  app2.get("/codex/version", async () => codexVersionInfo());
25392
25770
  }
25393
25771
 
25772
+ // src/routes/methods.ts
25773
+ async function methods(app2) {
25774
+ app2.get("/methods/status", async () => methodStatusReport());
25775
+ }
25776
+
25394
25777
  // src/routes/update.ts
25395
25778
  init_src();
25396
25779
 
25397
25780
  // src/services/update.ts
25398
25781
  init_src();
25399
25782
  init_config2();
25400
- import { readFileSync as readFileSync11 } from "node:fs";
25401
- import { resolve as resolve12, dirname as dirname12 } from "node:path";
25783
+ import { readFileSync as readFileSync12 } from "node:fs";
25784
+ import { resolve as resolve13, dirname as dirname13 } from "node:path";
25402
25785
  import { fileURLToPath as fileURLToPath3 } from "node:url";
25403
25786
  var UPDATE_COMMAND = "npm i -g hanoman@latest";
25404
25787
  function composeUpdate(x) {
@@ -25423,10 +25806,10 @@ var lastFetchAt = 0;
25423
25806
  var lastLatest = null;
25424
25807
  var lastStatus = "unavailable";
25425
25808
  function runningVersion() {
25426
- const here = dirname12(fileURLToPath3(import.meta.url));
25427
- for (const p of [resolve12(here, "build-info.json"), resolve12(here, "../package.json")]) {
25809
+ const here = dirname13(fileURLToPath3(import.meta.url));
25810
+ for (const p of [resolve13(here, "build-info.json"), resolve13(here, "../package.json")]) {
25428
25811
  try {
25429
- const v = JSON.parse(readFileSync11(p, "utf8")).version;
25812
+ const v = JSON.parse(readFileSync12(p, "utf8")).version;
25430
25813
  if (typeof v === "string" && v) return v;
25431
25814
  } catch {
25432
25815
  }
@@ -25774,7 +26157,7 @@ init_conflicts();
25774
26157
  init_config2();
25775
26158
  import { randomUUID as randomUUID3 } from "node:crypto";
25776
26159
  import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile4, unlink } from "node:fs/promises";
25777
- import { join as join12, resolve as resolve13 } from "node:path";
26160
+ import { join as join13, resolve as resolve14 } from "node:path";
25778
26161
  var EXT = {
25779
26162
  "image/png": ".png",
25780
26163
  "image/jpeg": ".jpg",
@@ -25784,22 +26167,22 @@ function extFor(mimeType) {
25784
26167
  return EXT[mimeType] ?? ".bin";
25785
26168
  }
25786
26169
  function uploadDir() {
25787
- return resolve13(effectiveStr("HANOMAN_UPLOAD_DIR") ?? join12(process.cwd(), "data", "uploads"));
26170
+ return resolve14(effectiveStr("HANOMAN_UPLOAD_DIR") ?? join13(process.cwd(), "data", "uploads"));
25788
26171
  }
25789
26172
  async function saveUpload(buf, mimeType) {
25790
26173
  const dir2 = uploadDir();
25791
26174
  await mkdir2(dir2, { recursive: true });
25792
26175
  const storageKey = `${randomUUID3()}${extFor(mimeType)}`;
25793
- await writeFile2(join12(dir2, storageKey), buf);
26176
+ await writeFile2(join13(dir2, storageKey), buf);
25794
26177
  return { storageKey, size: buf.length };
25795
26178
  }
25796
26179
  async function readUpload(storageKey) {
25797
26180
  const safe = storageKey.replace(/[/\\]/g, "");
25798
- return readFile4(join12(uploadDir(), safe));
26181
+ return readFile4(join13(uploadDir(), safe));
25799
26182
  }
25800
26183
  async function readUploadOrFetch(storageKey) {
25801
26184
  const safe = storageKey.replace(/[/\\]/g, "");
25802
- const target2 = join12(uploadDir(), safe);
26185
+ const target2 = join13(uploadDir(), safe);
25803
26186
  try {
25804
26187
  return await readFile4(target2);
25805
26188
  } catch {
@@ -25818,7 +26201,7 @@ async function readUploadOrFetch(storageKey) {
25818
26201
  }
25819
26202
  async function deleteUpload(storageKey) {
25820
26203
  const safe = storageKey.replace(/[/\\]/g, "");
25821
- await unlink(join12(uploadDir(), safe)).catch(() => {
26204
+ await unlink(join13(uploadDir(), safe)).catch(() => {
25822
26205
  });
25823
26206
  }
25824
26207
 
@@ -25949,10 +26332,10 @@ import { randomUUID as randomUUID5 } from "node:crypto";
25949
26332
  init_config2();
25950
26333
  import { randomUUID as randomUUID4 } from "node:crypto";
25951
26334
  import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile5, unlink as unlink2 } from "node:fs/promises";
25952
- import { join as join13, resolve as resolve14, basename as basename2 } from "node:path";
26335
+ import { join as join14, resolve as resolve15, basename as basename3 } from "node:path";
25953
26336
  var MAX_TRANSCRIPT_BYTES = 1024 * 1024;
25954
26337
  function transcriptDir() {
25955
- return resolve14(effectiveStr("HANOMAN_TRANSCRIPT_DIR") ?? join13(process.cwd(), "data", "transcripts"));
26338
+ return resolve15(effectiveStr("HANOMAN_TRANSCRIPT_DIR") ?? join14(process.cwd(), "data", "transcripts"));
25956
26339
  }
25957
26340
  function clamp(text) {
25958
26341
  const buf = Buffer.from(text, "utf8");
@@ -25970,20 +26353,20 @@ async function saveTranscript(text) {
25970
26353
  const dir2 = transcriptDir();
25971
26354
  await mkdir3(dir2, { recursive: true });
25972
26355
  const key = `${randomUUID4()}.log`;
25973
- await writeFile3(join13(dir2, key), body, "utf8");
26356
+ await writeFile3(join14(dir2, key), body, "utf8");
25974
26357
  return { key, bytes: Buffer.byteLength(body, "utf8"), truncated };
25975
26358
  }
25976
26359
  async function readTranscript(key) {
25977
26360
  if (!key) return null;
25978
26361
  try {
25979
- return await readFile5(join13(transcriptDir(), basename2(key)), "utf8");
26362
+ return await readFile5(join14(transcriptDir(), basename3(key)), "utf8");
25980
26363
  } catch {
25981
26364
  return null;
25982
26365
  }
25983
26366
  }
25984
26367
  async function deleteTranscript(key) {
25985
26368
  if (!key) return;
25986
- await unlink2(join13(transcriptDir(), basename2(key))).catch(() => {
26369
+ await unlink2(join14(transcriptDir(), basename3(key))).catch(() => {
25987
26370
  });
25988
26371
  }
25989
26372
 
@@ -26406,7 +26789,7 @@ init_db();
26406
26789
 
26407
26790
  // src/services/ticket-accept.ts
26408
26791
  init_db();
26409
- import { join as join14 } from "node:path";
26792
+ import { join as join15 } from "node:path";
26410
26793
  async function materializeAttachments(atts) {
26411
26794
  const ready = /* @__PURE__ */ new Set();
26412
26795
  for (const a of atts) {
@@ -26421,7 +26804,7 @@ async function materializeAttachments(atts) {
26421
26804
  var attachmentInstruction = (t, atts, ready) => {
26422
26805
  if (atts.length === 0) return "Tanpa lampiran.";
26423
26806
  const list2 = atts.map(
26424
- (a) => ready.has(a.id) ? `- ${a.filename} (${a.mimeType}) \u2192 ${join14(uploadDir(), a.storageKey)}` : `- ${a.filename} (${a.mimeType}) \u2192 BELUM TERUNDUH, ambil via GET /api/tickets/${t.id}/attachments/${a.id}`
26807
+ (a) => ready.has(a.id) ? `- ${a.filename} (${a.mimeType}) \u2192 ${join15(uploadDir(), a.storageKey)}` : `- ${a.filename} (${a.mimeType}) \u2192 BELUM TERUNDUH, ambil via GET /api/tickets/${t.id}/attachments/${a.id}`
26425
26808
  ).join("\n");
26426
26809
  return `LAMPIRAN (${atts.length}) dari pelapor \u2014 biasanya screenshot yang menunjukkan masalah. PERIKSA setiap lampiran untuk memahami konteks keluhan sebelum bekerja; jangan berasumsi dari teks saja. Berkas ada di direktori upload server (baca langsung dengan tool Read):
26427
26810
  ${list2}
@@ -27112,7 +27495,7 @@ function leadPrompt(q, c) {
27112
27495
  lines2.push(`Anggarkan begini: pakai paling banyak separuh waktu untuk mengumpulkan bukti, lalu **berhenti membaca** dan tulis jawabannya dengan apa yang sudah kamu punya. Kalau buktinya jadi tipis karena itu, turunkan \`confidence\`-nya \u2014 jangan menambah waktu baca.`);
27113
27496
  lines2.push("");
27114
27497
  lines2.push("## Cara kerja");
27115
- lines2.push("1. KUMPULKAN BUKTI DULU sebelum memutuskan, DI DALAM anggaran waktu di atas: `internal/docs/**` (Source of Truth) dan index-nya, ADR yang relevan, plan `docs/superpowers/plans/**`, kode yang bersangkutan, dan riwayat git. Baca, jangan mengingat \u2014 tapi baca seperlunya, bukan sehabisnya.");
27498
+ lines2.push(`1. KUMPULKAN BUKTI DULU sebelum memutuskan, DI DALAM anggaran waktu di atas: \`internal/docs/**\` (Source of Truth) dan index-nya, ADR yang relevan, plan ${PLAN_DIRS.map((d) => `\`${d}/**\``).join(" / ")}, kode yang bersangkutan, dan riwayat git. Baca, jangan mengingat \u2014 tapi baca seperlunya, bukan sehabisnya.`);
27116
27499
  lines2.push('2. Putuskan. Kalau setelah membaca kamu masih ragu, TETAP putuskan: pilih opsi yang PALING MUDAH DIBATALKAN, lalu tandai `confidence: "ragu"`. "Tidak tahu" bukan jawaban, dan meminta manusia memutuskan adalah persis keadaan yang kamu ada untuk menghapusnya. SATU pengecualian: bila jawabannya menuntut fakta konkret yang memang TIDAK ADA di repo maupun di konteks ini, isi `missing` dengan daftar pendek hal yang kurang \u2014 hal yang bisa disediakan seseorang, bukan keluhan \u2014 dan tetap tulis `decision` sebagai langkah paling aman sementara. `missing` yang terisi memaksa `confidence` jadi `ragu` dan memanggil operator; jangan memakainya untuk bukti yang cuma tipis.');
27117
27500
  lines2.push("3. Rujuk bukti yang BENAR-BENAR kamu baca. Rujukan berupa path berkas relatif terhadap checkout, nomor ADR (`ADR-0091`), atau sha commit. Rujukan yang tak ada di repo akan dibuang server dan membuat jawabanmu tampak tanpa dasar.");
27118
27501
  lines2.push("4. JANGAN membaca atau mengutip kredensial (isi `.env*`, token, kunci privat). Jejak keputusan disimpan di basis data; rahasia tak boleh mendarat di sana.");
@@ -27147,7 +27530,7 @@ function leadPrompt(q, c) {
27147
27530
 
27148
27531
  // src/services/lead/verdict.ts
27149
27532
  init_src();
27150
- import { existsSync as existsSync11 } from "node:fs";
27533
+ import { existsSync as existsSync12 } from "node:fs";
27151
27534
  import { isAbsolute as isAbsolute3, resolve as resolvePath, relative } from "node:path";
27152
27535
  function extractJsonBlock(raw) {
27153
27536
  const re = /```json\s*\n([\s\S]*?)```/g;
@@ -27185,7 +27568,7 @@ function keepExistingRefs(refs2, repoDir) {
27185
27568
  const full = resolvePath(repoDir, ref);
27186
27569
  const rel = relative(repoDir, full);
27187
27570
  if (rel.startsWith("..")) continue;
27188
- if (existsSync11(full)) out3.push(ref);
27571
+ if (existsSync12(full)) out3.push(ref);
27189
27572
  }
27190
27573
  return [...new Set(out3)];
27191
27574
  }
@@ -27371,7 +27754,7 @@ function leadFailureReason(agent, timeoutMs, err, stdout, stderr) {
27371
27754
  function think(prompt, o) {
27372
27755
  const bin = binFor(o.agent);
27373
27756
  const args = leadArgv({ agent: o.agent, model: o.model, effort: o.effort, prompt });
27374
- return new Promise((resolve15, reject2) => {
27757
+ return new Promise((resolve16, reject2) => {
27375
27758
  const child = execFile9(bin, args, {
27376
27759
  cwd: o.cwd,
27377
27760
  timeout: o.timeoutMs,
@@ -27384,7 +27767,7 @@ function think(prompt, o) {
27384
27767
  reject2(new Error(leadFailureReason(o.agent, o.timeoutMs, err, stdout, stderr)));
27385
27768
  return;
27386
27769
  }
27387
- resolve15(stdout);
27770
+ resolve16(stdout);
27388
27771
  });
27389
27772
  child.stdin?.end();
27390
27773
  });
@@ -27421,9 +27804,9 @@ function acquire(cap, waitMs) {
27421
27804
  return Promise.resolve();
27422
27805
  }
27423
27806
  const startedAt = Date.now();
27424
- return new Promise((resolve15, reject2) => {
27807
+ return new Promise((resolve16, reject2) => {
27425
27808
  const w = {
27426
- grant: resolve15,
27809
+ grant: resolve16,
27427
27810
  deny: reject2,
27428
27811
  timer: setTimeout(() => {
27429
27812
  const i = queue.indexOf(w);
@@ -28783,7 +29166,7 @@ init_db();
28783
29166
  init_db();
28784
29167
  import { execFile as execFile10 } from "node:child_process";
28785
29168
  import { promisify as promisify9 } from "node:util";
28786
- import { existsSync as existsSync12 } from "node:fs";
29169
+ import { existsSync as existsSync13 } from "node:fs";
28787
29170
 
28788
29171
  // src/services/changelog/scrub.ts
28789
29172
  var MERGE = /^merge\s+(branch|pull request|remote-tracking branch|tag)\b/i;
@@ -28850,7 +29233,7 @@ var US2 = "";
28850
29233
  var RS = "";
28851
29234
  var NO_REPO = "project ini belum ditautkan ke repo di mesin ini";
28852
29235
  var NO_TAG = "repo project ini belum punya tag rilis";
28853
- var usable = (repoDir) => !!repoDir && existsSync12(repoDir);
29236
+ var usable = (repoDir) => !!repoDir && existsSync13(repoDir);
28854
29237
  async function listTags(repoDir) {
28855
29238
  if (!usable(repoDir)) return { tags: [], head: null, reason: NO_REPO };
28856
29239
  try {
@@ -29111,13 +29494,13 @@ init_db();
29111
29494
 
29112
29495
  // src/services/agent-tool-catalog.ts
29113
29496
  init_src();
29114
- import { readFileSync as readFileSync12 } from "node:fs";
29115
- import { homedir as homedir8 } from "node:os";
29116
- import { join as join15 } from "node:path";
29117
- var home = () => process.env.HOME || homedir8();
29118
- var readJson = (path) => {
29497
+ import { readFileSync as readFileSync13 } from "node:fs";
29498
+ import { homedir as homedir9 } from "node:os";
29499
+ import { join as join16 } from "node:path";
29500
+ var home = () => process.env.HOME || homedir9();
29501
+ var readJson2 = (path) => {
29119
29502
  try {
29120
- return JSON.parse(readFileSync12(path, "utf8"));
29503
+ return JSON.parse(readFileSync13(path, "utf8"));
29121
29504
  } catch {
29122
29505
  return null;
29123
29506
  }
@@ -29130,7 +29513,7 @@ var serversOf = (node) => {
29130
29513
  var codexServers = () => {
29131
29514
  let text;
29132
29515
  try {
29133
- text = readFileSync12(join15(home(), ".codex", "config.toml"), "utf8");
29516
+ text = readFileSync13(join16(home(), ".codex", "config.toml"), "utf8");
29134
29517
  } catch {
29135
29518
  return [];
29136
29519
  }
@@ -29143,12 +29526,12 @@ var codexServers = () => {
29143
29526
  };
29144
29527
  function mcpServerNames(repoDir) {
29145
29528
  const names = [];
29146
- const claudeJson = readJson(join15(home(), ".claude.json"));
29529
+ const claudeJson = readJson2(join16(home(), ".claude.json"));
29147
29530
  names.push(...serversOf(claudeJson));
29148
29531
  if (repoDir) {
29149
29532
  const projects = claudeJson?.projects;
29150
29533
  if (projects && typeof projects === "object") names.push(...serversOf(projects[repoDir]));
29151
- names.push(...serversOf(readJson(join15(repoDir, ".mcp.json"))));
29534
+ names.push(...serversOf(readJson2(join16(repoDir, ".mcp.json"))));
29152
29535
  }
29153
29536
  names.push(...codexServers());
29154
29537
  return [...new Set(names.filter(Boolean))].sort((a, b) => a.localeCompare(b));
@@ -29487,7 +29870,7 @@ function issuesFromRest(raw) {
29487
29870
  }
29488
29871
  var GH_FIELDS = "number,title,body,author,labels,url,state,createdAt,updatedAt";
29489
29872
  var API2 = "https://api.github.com";
29490
- var defaultRunGh = (args, env) => new Promise((resolve15, reject2) => {
29873
+ var defaultRunGh = (args, env) => new Promise((resolve16, reject2) => {
29491
29874
  execFile11(
29492
29875
  args[0],
29493
29876
  args.slice(1),
@@ -29495,7 +29878,7 @@ var defaultRunGh = (args, env) => new Promise((resolve15, reject2) => {
29495
29878
  (err, stdout, stderr) => {
29496
29879
  const e = err;
29497
29880
  if (e && (e.code === "ENOENT" || e.code === "EACCES")) return reject2(e);
29498
- resolve15({ code: err ? Number(err.code ?? 1) : 0, stdout, stderr });
29881
+ resolve16({ code: err ? Number(err.code ?? 1) : 0, stdout, stderr });
29499
29882
  }
29500
29883
  );
29501
29884
  });
@@ -31034,7 +31417,7 @@ var PUBLIC = /* @__PURE__ */ new Set([
31034
31417
  "GET /api/agent-integration.md"
31035
31418
  ]);
31036
31419
  function buildApp({ requireAuth = true, agentDocFile } = {}) {
31037
- const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname13(fileURLToPath4(import.meta.url)), process.env, existsSync13);
31420
+ const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname14(fileURLToPath4(import.meta.url)), process.env, existsSync14);
31038
31421
  const app2 = Fastify({ logger: false, trustProxy: true });
31039
31422
  app2.addContentTypeParser("application/json", { parseAs: "string" }, (_req, body, done) => {
31040
31423
  if (!body) return done(null, void 0);
@@ -31114,6 +31497,7 @@ function buildApp({ requireAuth = true, agentDocFile } = {}) {
31114
31497
  await api.register(tickets_default);
31115
31498
  await api.register(scheduler_default);
31116
31499
  await api.register(codex);
31500
+ await api.register(methods);
31117
31501
  await api.register(lead_default);
31118
31502
  await api.register(custom_agents_default);
31119
31503
  await api.register(githubIssues);
@@ -31124,7 +31508,7 @@ function buildApp({ requireAuth = true, agentDocFile } = {}) {
31124
31508
  await api.register(client_accounts_default);
31125
31509
  }, { prefix: "/api" });
31126
31510
  if (process.env.NODE_ENV === "production") {
31127
- const dist = pickWebDir(dirname13(fileURLToPath4(import.meta.url)), process.env, existsSync13);
31511
+ const dist = pickWebDir(dirname14(fileURLToPath4(import.meta.url)), process.env, existsSync14);
31128
31512
  if (dist) {
31129
31513
  app2.register(fastifyStatic, { root: dist });
31130
31514
  app2.setNotFoundHandler((req, reply) => req.url.startsWith("/api") ? reply.code(404).send({ error: "not found" }) : reply.sendFile("index.html"));