hanoman 0.1.26 → 0.1.27

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/cli.js CHANGED
@@ -16,165 +16,6 @@ var init_types = __esm({
16
16
  }
17
17
  });
18
18
 
19
- // ../runner/src/reverse-standard.ts
20
- var init_reverse_standard = __esm({
21
- "../runner/src/reverse-standard.ts"() {
22
- "use strict";
23
- }
24
- });
25
-
26
- // ../runner/src/verify-scope.ts
27
- var CHANGED;
28
- var init_verify_scope = __esm({
29
- "../runner/src/verify-scope.ts"() {
30
- "use strict";
31
- CHANGED = [
32
- "Scope verifikasi: HANYA yang berubah. Mesin ini menjalankan beberapa sesi sekaligus \u2014",
33
- "memverifikasi seluruh project menghabiskan RAM & CPU yang sedang dipakai sesi lain.",
34
- "",
35
- "Berkas yang berubah di worktree ini:",
36
- '`git diff --name-only "$HANOMAN_BASE_SHA"...HEAD` (yang sudah di-commit) dan',
37
- "`git status --porcelain` (yang belum).",
38
- "",
39
- "- Test: jalankan HANYA test yang berkaitan dengan berkas itu. Repo vitest:",
40
- ' `pnpm vitest --run --changed "$HANOMAN_BASE_SHA"` (vitest sendiri menurunkan berkas yang',
41
- " berubah dari git, termasuk yang belum di-commit), atau `pnpm vitest related --run <berkas\u2026>`,",
42
- " atau sebut path berkas test-nya langsung. Stack lain: `pytest <path>`, `go test ./paket/...`.",
43
- " JANGAN `pnpm test` atau `vitest run` polos \u2014 itu seluruh suite.",
44
- " Jebakan: `--changed` menyalakan `passWithNoTests`, jadi nol test TERLIHAT hijau. Pastikan",
45
- ' test-nya memang berjalan, jangan menerima "no test files" sebagai bukti.',
46
- "- Typecheck: hanya paket yang tersentuh (mis. `pnpm --filter ./server typecheck`).",
47
- " JANGAN `pnpm -r typecheck` \u2014 itu menyalakan satu proses tsc per paket sekaligus.",
48
- "- Lint: hanya berkas yang berubah, bukan seluruh repo.",
49
- "- Build penuh: hanya bila yang kamu ubah memang soal build/bundling.",
50
- "- Boot server + curl / smoke end-to-end: hanya bila task ini menyentuh endpoint atau perilaku",
51
- " runtime-nya, sekali di akhir \u2014 bukan rutin tiap task.",
52
- "- Membereskan proses nyangkut: JANGAN `pkill -f <pola>` maupun `killall`. Prompt setiap sesi",
53
- " hidup di ARGV proses agennya, jadi pola seperti `vitest`/`tsc`/`node` ikut mencocoki agen sesi",
54
- " LAIN di mesin ini dan mematikannya di tengah kerja \u2014 dan `pkill` mengecualikan leluhurnya",
55
- " sendiri, jadi yang mati selalu sesi tetangga, bukan sesimu. Bunuh per-PID: `ps`/`lsof -ti:<port>`",
56
- " \u2192 `kill <pid>`; atau sempitkan polanya sampai memuat path worktree-mu sendiri.",
57
- "",
58
- "Suite penuh, lint penuh, dan build penuh adalah tugas MANUSIA sebelum merge, bukan tugas sesi.",
59
- "Pengecualian yang kamu putuskan sendiri: bila perubahanmu memang berdampak luas (mengubah",
60
- "tipe/kontrak bersama, skema, atau berkas yang diimpor banyak modul), perluas scope seperlunya",
61
- "dan katakan alasannya. Ini panduan biaya, bukan larangan."
62
- ].join("\n");
63
- }
64
- });
65
-
66
- // ../runner/src/code-style.ts
67
- var CODE_STYLE_CLAUSE;
68
- var init_code_style = __esm({
69
- "../runner/src/code-style.ts"() {
70
- "use strict";
71
- CODE_STYLE_CLAUSE = [
72
- "Gaya kode \u2014 berlaku setiap kali kamu menulis atau mengubah kode:",
73
- "- Tulis kode yang rapi dan mengikuti idiom, penamaan, serta struktur kode di sekitarnya.",
74
- " Kodemu harus terbaca seperti kode yang sudah ada di berkas itu, bukan seperti tempelan.",
75
- "- Jangan menulis komentar yang cuma mengulang apa yang sudah dinyatakan kode.",
76
- "- Komentar hanya untuk hal yang TIDAK terbaca dari kode: alasan/why sebuah keputusan,",
77
- " trade-off yang diambil, workaround beserta rujukan SPEC/ADR-nya, atau invariant yang tak",
78
- " kelihatan. Komentar semacam itu justru berharga \u2014 jangan ikut dibuang.",
79
- "- Jangan menambahkan komentar pembatas seksi, header berhiasan, atau narasi langkah demi langkah.",
80
- "- Jangan meninggalkan kode mati atau kode yang dikomentari. Hapus saja; riwayat git yang menyimpannya."
81
- ].join("\n");
82
- }
83
- });
84
-
85
- // ../runner/src/goal-spec.ts
86
- var init_goal_spec = __esm({
87
- "../runner/src/goal-spec.ts"() {
88
- "use strict";
89
- }
90
- });
91
-
92
- // ../runner/src/prompt.ts
93
- var ESCALATION_CONTRACT, REVERSE_PHASE_GUIDE, SCAFFOLD_PHASE_GUIDE;
94
- var init_prompt = __esm({
95
- "../runner/src/prompt.ts"() {
96
- "use strict";
97
- init_reverse_standard();
98
- init_verify_scope();
99
- init_code_style();
100
- init_goal_spec();
101
- ESCALATION_CONTRACT = [
102
- "REKOMENDASI ESKALASI (wajib, terbaca mesin). Di bagian akhir dokumen audit, tulis bagian",
103
- "`## Rekomendasi eskalasi` berisi penjelasan singkat untuk manusia, LALU tepat SATU blok ```json",
104
- "berbentuk persis seperti ini (satu-satunya blok json di dokumen itu):",
105
- "",
106
- "```json",
107
- '{ "escalation": { "target": "none|qa|brief|prd", "reason": "<alasan singkat>",',
108
- ' "alternatives": ["<target lain yang masuk akal>"],',
109
- ' "prefill": { "title": "", "context": "", "outcome": "", "constraints": "", "severity": "", "steps": "" } } }',
110
- "```",
111
- "",
112
- "Pilih `target` dari hasil auditmu, bukan default:",
113
- '- "qa" \u2014 bug / regresi / perilaku salah yang perlu diperbaiki. Isi `prefill.severity`',
114
- " (critical|major|minor) dan `prefill.steps` (langkah reproduksi).",
115
- '- "brief" \u2014 kebutuhan/fitur yang bentuknya sudah jelas dan cakupannya satu backlog.',
116
- " Isi `prefill.title`, `prefill.context`, `prefill.outcome`.",
117
- '- "prd" \u2014 kebutuhan produk yang besar, ambigu, atau lintas modul sehingga perlu dokumen PRD',
118
- " lebih dulu. Isi `prefill.title`, `prefill.context`, `prefill.outcome`.",
119
- '- "none" \u2014 pertanyaannya sudah terjawab; tak perlu perbaikan maupun fitur baru.',
120
- "",
121
- "`alternatives` boleh array kosong. Jangan menulis blok json lain di dokumen itu."
122
- ].join("\n");
123
- REVERSE_PHASE_GUIDE = [
124
- "- Scan: baca source code \u2014 stack, arsitektur, data model, API surface, perilaku domain. Belum menulis docs.",
125
- "- Docs teknis: tulis kategori yang bisa diturunkan dari kode (architecture, requirements + EARS dari perilaku nyata, adr ber-Status accepted (reverse-engineered), operations, security, design-system/frontend bila relevan). Isi lengkap dan spesifik, bukan kerangka.",
126
- "- Wawancara: untuk product, business, brand, research, entrypoints \u2014 ajukan SATU pertanyaan per giliran ke manusia di terminal ini, tunggu jawabannya, isi docs dari jawaban. Jangan mengarang. Topik tanpa jawaban tandai: Status: draft \u2014 menunggu input.",
127
- "- Konvensi & index: tulis internal/docs/README.md (index bernomor lengkap), CLAUDE.md, AGENTS.md, .claude/settings.json + .claude/hooks/ensure-docs-updated.py persis seperti STANDAR DOCS.",
128
- "- Serah terima: pastikan setiap berkas docs terdaftar di index, lalu tulis ringkasan hasil + daftar pertanyaan yang belum terjawab ke terminal."
129
- ].join("\n");
130
- SCAFFOLD_PHASE_GUIDE = [
131
- "- Brainstorm: perdalam IDE project (di bawah) jadi masalah, pengguna, scope, dan metrik sukses. Ajukan SATU pertanyaan per giliran ke manusia di terminal ini, tunggu jawabannya. Jangan mengarang; topik yang belum dijawab tandai sebagai open question.",
132
- "- Objective: kunci SATU MVP objective yang terukur dari hasil brainstorm, tulis ringkas di docs.",
133
- "- Doc index: tulis SELURUH internal/docs/** dari ide+objective+jawaban, mengikuti STANDAR DOCS di bawah \u2014 entrypoints, product, business, requirements (+EARS dari perilaku yang diinginkan), research, architecture (stack/data-model/api-contract/nfr), adr awal (Status accepted), design-system/frontend bila ada UI, operations, security, plus README index bernomor + CLAUDE.md + AGENTS.md + Stop hook. Lengkap dan spesifik terhadap ide ini, BUKAN kerangka."
134
- ].join("\n");
135
- }
136
- });
137
-
138
- // ../runner/src/git.ts
139
- var init_git = __esm({
140
- "../runner/src/git.ts"() {
141
- "use strict";
142
- }
143
- });
144
-
145
- // ../runner/src/settings.ts
146
- var init_settings = __esm({
147
- "../runner/src/settings.ts"() {
148
- "use strict";
149
- }
150
- });
151
-
152
- // ../runner/src/goal.ts
153
- var init_goal = __esm({
154
- "../runner/src/goal.ts"() {
155
- "use strict";
156
- init_prompt();
157
- init_goal_spec();
158
- }
159
- });
160
-
161
- // ../runner/src/codex-settings.ts
162
- var init_codex_settings = __esm({
163
- "../runner/src/codex-settings.ts"() {
164
- "use strict";
165
- init_prompt();
166
- }
167
- });
168
-
169
- // ../runner/src/agent-cli.ts
170
- var init_agent_cli = __esm({
171
- "../runner/src/agent-cli.ts"() {
172
- "use strict";
173
- init_settings();
174
- init_codex_settings();
175
- }
176
- });
177
-
178
19
  // ../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js
179
20
  var util, objectUtil, ZodParsedType, getParsedType;
180
21
  var init_util = __esm({
@@ -4319,6 +4160,120 @@ var init_spec_source = __esm({
4319
4160
  }
4320
4161
  });
4321
4162
 
4163
+ // ../shared/src/method-catalog.ts
4164
+ function resolveMethod(id) {
4165
+ return (id ? METHODS[id] : void 0) ?? METHODS[DEFAULT_METHOD];
4166
+ }
4167
+ var DEFAULT_METHOD, VERIFICATION_GATE, MATT_CLAUSE, METHODS, METHOD_IDS, uniq, PLAN_DIRS, SPEC_DIRS;
4168
+ var init_method_catalog = __esm({
4169
+ "../shared/src/method-catalog.ts"() {
4170
+ "use strict";
4171
+ DEFAULT_METHOD = "superpowers";
4172
+ VERIFICATION_GATE = "superpowers:verification-before-completion";
4173
+ 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.";
4174
+ METHODS = {
4175
+ // Isi `PHASE_SKILLS` (SPEC-166) dipindah APA ADANYA. Objective & Spec adalah keluaran skill
4176
+ // brainstorming yang di-invoke di fase Brainstorm — sengaja tak punya entri sendiri. Fase reverse
4177
+ // dipandu standar docs di prompt-nya, bukan skill.
4178
+ superpowers: {
4179
+ id: "superpowers",
4180
+ label: "superpowers",
4181
+ planDir: "docs/superpowers/plans",
4182
+ specDir: "docs/superpowers/specs",
4183
+ phaseSkills: {
4184
+ Brainstorm: ["superpowers:brainstorming"],
4185
+ Audit: ["superpowers:systematic-debugging"],
4186
+ Plan: ["superpowers:writing-plans"],
4187
+ Execute: [
4188
+ "superpowers:executing-plans",
4189
+ "superpowers:test-driven-development",
4190
+ VERIFICATION_GATE
4191
+ ],
4192
+ // SPEC-407 · fase `Goal` sengaja TANPA skill: seluruh inti flow itu membebaskan sesi dari
4193
+ // proses kaku. Yang tetap dijaga cuma pintu keluarnya.
4194
+ Verifikasi: [VERIFICATION_GATE]
4195
+ },
4196
+ exitSkills: [VERIFICATION_GATE],
4197
+ requires: ["superpowers"],
4198
+ install: {
4199
+ claude: [
4200
+ "claude plugin marketplace add obra/superpowers-marketplace",
4201
+ "claude plugin install superpowers@superpowers-marketplace"
4202
+ ],
4203
+ codex: ["codex plugin add superpowers@openai-curated"]
4204
+ }
4205
+ },
4206
+ // Plugin `mattpocock-skills` (`/plugin install mattpocock-skills`); skill plugin di Claude Code
4207
+ // beralamat `plugin:skill`. `superpowers` tetap ikut di `requires` karena gerbang verifikasinya
4208
+ // dipinjam dari sana — mattpocock tak punya padanannya.
4209
+ matt: {
4210
+ id: "matt",
4211
+ label: "mattpocock",
4212
+ planDir: "docs/matt/plans",
4213
+ specDir: "docs/matt/specs",
4214
+ phaseSkills: {
4215
+ Brainstorm: ["mattpocock-skills:grilling"],
4216
+ Audit: ["mattpocock-skills:diagnosing-bugs"],
4217
+ Plan: ["mattpocock-skills:to-tickets"],
4218
+ Execute: [
4219
+ "mattpocock-skills:implement",
4220
+ "mattpocock-skills:tdd",
4221
+ "mattpocock-skills:code-review"
4222
+ ],
4223
+ Verifikasi: [VERIFICATION_GATE]
4224
+ },
4225
+ exitSkills: [VERIFICATION_GATE],
4226
+ extraClause: MATT_CLAUSE,
4227
+ requires: ["mattpocock-skills", "superpowers"],
4228
+ // `mattpocock-skills` ada di marketplace resmi Claude Code → tanpa `marketplace add`.
4229
+ // superpowers ikut dipasang karena `requires` menyebutnya: gerbang verifikasinya dipinjam
4230
+ // dari sana (INVARIAN 2). codex belum punya plugin native mattpocock — jalur resminya
4231
+ // `npx skills`, dan ia INTERAKTIF; itu justru alasan pemasangan hidup di pane yang ditonton.
4232
+ install: {
4233
+ claude: [
4234
+ "claude plugin install mattpocock-skills",
4235
+ "claude plugin marketplace add obra/superpowers-marketplace",
4236
+ "claude plugin install superpowers@superpowers-marketplace"
4237
+ ],
4238
+ codex: [
4239
+ "npx skills@latest add mattpocock/skills",
4240
+ "codex plugin add superpowers@openai-curated"
4241
+ ]
4242
+ }
4243
+ }
4244
+ };
4245
+ METHOD_IDS = Object.keys(METHODS);
4246
+ uniq = (xs) => [...new Set(xs)];
4247
+ PLAN_DIRS = uniq(METHOD_IDS.map((id) => METHODS[id].planDir));
4248
+ SPEC_DIRS = uniq(METHOD_IDS.map((id) => METHODS[id].specDir));
4249
+ }
4250
+ });
4251
+
4252
+ // ../shared/src/method-status.ts
4253
+ function methodSkills(m) {
4254
+ return [.../* @__PURE__ */ new Set([...Object.values(m.phaseSkills).flat(), ...m.exitSkills])];
4255
+ }
4256
+ function methodStatus(m, agent, installed) {
4257
+ const haveSkills = new Set(installed.skills);
4258
+ const havePackages = new Set(installed.packages);
4259
+ const missingPackages = m.requires.filter((p) => !havePackages.has(p));
4260
+ const missingSkills = methodSkills(m).filter((id) => !haveSkills.has(id));
4261
+ return {
4262
+ method: m.id,
4263
+ label: m.label,
4264
+ agent,
4265
+ ready: missingPackages.length === 0 && missingSkills.length === 0,
4266
+ missingPackages,
4267
+ missingSkills,
4268
+ install: [...m.install[agent]]
4269
+ };
4270
+ }
4271
+ var init_method_status = __esm({
4272
+ "../shared/src/method-status.ts"() {
4273
+ "use strict";
4274
+ }
4275
+ });
4276
+
4322
4277
  // ../shared/src/agent-engine.ts
4323
4278
  var zAgentEngine;
4324
4279
  var init_agent_engine = __esm({
@@ -4465,6 +4420,7 @@ var init_entities = __esm({
4465
4420
  init_telegram();
4466
4421
  init_agent_engine();
4467
4422
  init_auto_merge();
4423
+ init_method_catalog();
4468
4424
  zProject = external_exports.object({
4469
4425
  id: external_exports.string(),
4470
4426
  name: external_exports.string(),
@@ -4664,6 +4620,12 @@ var init_entities = __esm({
4664
4620
  // SPEC-338 · ADR-0074 · model/effort codex
4665
4621
  verifyScope: zVerifyScope.default("changed"),
4666
4622
  // SPEC-376 · ADR-0080 · scope verifikasi sesi
4623
+ // SPEC-734 · ADR-0113 · metode workflow default. LENIENT (`z.string()`, bukan `z.enum`):
4624
+ // instance yang di-sync dari hub bisa membawa id metode yang belum ada di build ini, dan itu
4625
+ // harus jadi fallback diam di titik pakai (`resolveMethod`) — bukan baris Setting yang gagal
4626
+ // parse lalu mengosongkan layar Settings. Alasan yang sama dengan model/effort di sini.
4627
+ method: external_exports.string().default(DEFAULT_METHOD),
4628
+ // SPEC-734 · ADR-0113 · metode workflow default
4667
4629
  conflict: zConflict.default(CONFLICT_DEFAULTS),
4668
4630
  // SPEC-383 · ADR-0081 · default sesi konflik rebase/merge
4669
4631
  lead: zLead.default(LEAD_DEFAULTS),
@@ -5378,7 +5340,15 @@ var init_dto = __esm({
5378
5340
  // SPEC-236 · terminal biasa NON-claude: shell mentah di repoDir project. Tanpa flow (bukan
5379
5341
  // pipeline claude). DIDAHULUKAN: z.object non-strict membuang key asing, jadi bila varian
5380
5342
  // longgar {project,flow?} lebih dulu, {project,shell:true} akan lolos sbg plain (shell dibuang).
5381
- external_exports.object({ project: external_exports.string(), shell: external_exports.literal(true) }),
5343
+ // SPEC-739 · ADR-0114 · pemasangan skill metode: klien mengirim METODE + AGEN, bukan teks
5344
+ // perintah — perintahnya diturunkan server dari katalog `METHODS`. Dengan begitu "perintah
5345
+ // instalasi adalah data katalog" berlaku ujung ke ujung, dan endpoint ini tak pernah menjadi
5346
+ // "jalankan shell arbitrer". Tanpa field ini, shell mentah persis seperti SPEC-236.
5347
+ external_exports.object({
5348
+ project: external_exports.string(),
5349
+ shell: external_exports.literal(true),
5350
+ install: external_exports.object({ method: external_exports.string(), agent: zAgent }).optional()
5351
+ }),
5382
5352
  // SPEC-166 · "reverse" = sesi project-level di worktree-nya sendiri, menyusun Source of Truth
5383
5353
  // dari kode. TANPA override runtime: sesi project-level mengikuti Setting.agent (ADR-0074).
5384
5354
  // Terminal biasa (tanpa flow) kini punya variannya SENDIRI di bawah — lihat SPEC-517.
@@ -5425,6 +5395,10 @@ var init_dto = __esm({
5425
5395
  goalCondition: external_exports.string().max(4e3).optional(),
5426
5396
  agent: zAgent.optional(),
5427
5397
  verifyScope: zVerifyScope.optional(),
5398
+ // SPEC-734 · ADR-0113 — metode workflow per SESI: undefined → Spec.payload.method →
5399
+ // Setting.method → "superpowers". LENIENT (`z.string()`, bukan `z.enum`): id yang tak dikenal
5400
+ // jatuh ke default di `resolveMethod`, bukan ditolak 400 — alasan yang sama dengan zSetting.
5401
+ method: external_exports.string().optional(),
5428
5402
  // SPEC-447 · ADR-0093 — lewati gerbang dependency. Hanya jalur manusia; UI hanya
5429
5403
  // mengirimkannya sesudah operator melihat daftar pemblokirnya.
5430
5404
  force: external_exports.boolean().optional()
@@ -5618,6 +5592,8 @@ var init_api = __esm({
5618
5592
  limits: `${API}/limits`,
5619
5593
  // SPEC-339 · versi codex CLI (peringatan lunak model GPT-5.6).
5620
5594
  codexVersion: `${API}/codex/version`,
5595
+ // SPEC-739 · ADR-0114 · kesiapan skill metode per agen. LOCAL-only, diturunkan live dari disk.
5596
+ methodStatus: `${API}/methods/status`,
5621
5597
  // SPEC-489 · panduan AI agent — markdown MENTAH & PUBLIC (tanpa auth). Satu definisi URL untuk
5622
5598
  // klien web dan untuk tautan yang disalin operator ke agennya.
5623
5599
  agentDoc: `${API}/agent-integration.md`,
@@ -7146,6 +7122,8 @@ var init_src = __esm({
7146
7122
  "use strict";
7147
7123
  init_enums();
7148
7124
  init_spec_source();
7125
+ init_method_catalog();
7126
+ init_method_status();
7149
7127
  init_agent_engine();
7150
7128
  init_entities();
7151
7129
  init_agent();
@@ -7172,6 +7150,169 @@ var init_src = __esm({
7172
7150
  }
7173
7151
  });
7174
7152
 
7153
+ // ../runner/src/reverse-standard.ts
7154
+ var init_reverse_standard = __esm({
7155
+ "../runner/src/reverse-standard.ts"() {
7156
+ "use strict";
7157
+ }
7158
+ });
7159
+
7160
+ // ../runner/src/verify-scope.ts
7161
+ var CHANGED;
7162
+ var init_verify_scope = __esm({
7163
+ "../runner/src/verify-scope.ts"() {
7164
+ "use strict";
7165
+ CHANGED = [
7166
+ "Scope verifikasi: HANYA yang berubah. Mesin ini menjalankan beberapa sesi sekaligus \u2014",
7167
+ "memverifikasi seluruh project menghabiskan RAM & CPU yang sedang dipakai sesi lain.",
7168
+ "",
7169
+ "Berkas yang berubah di worktree ini:",
7170
+ '`git diff --name-only "$HANOMAN_BASE_SHA"...HEAD` (yang sudah di-commit) dan',
7171
+ "`git status --porcelain` (yang belum).",
7172
+ "",
7173
+ "- Test: jalankan HANYA test yang berkaitan dengan berkas itu. Repo vitest:",
7174
+ ' `pnpm vitest --run --changed "$HANOMAN_BASE_SHA"` (vitest sendiri menurunkan berkas yang',
7175
+ " berubah dari git, termasuk yang belum di-commit), atau `pnpm vitest related --run <berkas\u2026>`,",
7176
+ " atau sebut path berkas test-nya langsung. Stack lain: `pytest <path>`, `go test ./paket/...`.",
7177
+ " JANGAN `pnpm test` atau `vitest run` polos \u2014 itu seluruh suite.",
7178
+ " Jebakan: `--changed` menyalakan `passWithNoTests`, jadi nol test TERLIHAT hijau. Pastikan",
7179
+ ' test-nya memang berjalan, jangan menerima "no test files" sebagai bukti.',
7180
+ "- Typecheck: hanya paket yang tersentuh (mis. `pnpm --filter ./server typecheck`).",
7181
+ " JANGAN `pnpm -r typecheck` \u2014 itu menyalakan satu proses tsc per paket sekaligus.",
7182
+ "- Lint: hanya berkas yang berubah, bukan seluruh repo.",
7183
+ "- Build penuh: hanya bila yang kamu ubah memang soal build/bundling.",
7184
+ "- Boot server + curl / smoke end-to-end: hanya bila task ini menyentuh endpoint atau perilaku",
7185
+ " runtime-nya, sekali di akhir \u2014 bukan rutin tiap task.",
7186
+ "- Membereskan proses nyangkut: JANGAN `pkill -f <pola>` maupun `killall`. Prompt setiap sesi",
7187
+ " hidup di ARGV proses agennya, jadi pola seperti `vitest`/`tsc`/`node` ikut mencocoki agen sesi",
7188
+ " LAIN di mesin ini dan mematikannya di tengah kerja \u2014 dan `pkill` mengecualikan leluhurnya",
7189
+ " sendiri, jadi yang mati selalu sesi tetangga, bukan sesimu. Bunuh per-PID: `ps`/`lsof -ti:<port>`",
7190
+ " \u2192 `kill <pid>`; atau sempitkan polanya sampai memuat path worktree-mu sendiri.",
7191
+ "",
7192
+ "Suite penuh, lint penuh, dan build penuh adalah tugas MANUSIA sebelum merge, bukan tugas sesi.",
7193
+ "Pengecualian yang kamu putuskan sendiri: bila perubahanmu memang berdampak luas (mengubah",
7194
+ "tipe/kontrak bersama, skema, atau berkas yang diimpor banyak modul), perluas scope seperlunya",
7195
+ "dan katakan alasannya. Ini panduan biaya, bukan larangan."
7196
+ ].join("\n");
7197
+ }
7198
+ });
7199
+
7200
+ // ../runner/src/code-style.ts
7201
+ var CODE_STYLE_CLAUSE;
7202
+ var init_code_style = __esm({
7203
+ "../runner/src/code-style.ts"() {
7204
+ "use strict";
7205
+ CODE_STYLE_CLAUSE = [
7206
+ "Gaya kode \u2014 berlaku setiap kali kamu menulis atau mengubah kode:",
7207
+ "- Tulis kode yang rapi dan mengikuti idiom, penamaan, serta struktur kode di sekitarnya.",
7208
+ " Kodemu harus terbaca seperti kode yang sudah ada di berkas itu, bukan seperti tempelan.",
7209
+ "- Jangan menulis komentar yang cuma mengulang apa yang sudah dinyatakan kode.",
7210
+ "- Komentar hanya untuk hal yang TIDAK terbaca dari kode: alasan/why sebuah keputusan,",
7211
+ " trade-off yang diambil, workaround beserta rujukan SPEC/ADR-nya, atau invariant yang tak",
7212
+ " kelihatan. Komentar semacam itu justru berharga \u2014 jangan ikut dibuang.",
7213
+ "- Jangan menambahkan komentar pembatas seksi, header berhiasan, atau narasi langkah demi langkah.",
7214
+ "- Jangan meninggalkan kode mati atau kode yang dikomentari. Hapus saja; riwayat git yang menyimpannya."
7215
+ ].join("\n");
7216
+ }
7217
+ });
7218
+
7219
+ // ../runner/src/goal-spec.ts
7220
+ var init_goal_spec = __esm({
7221
+ "../runner/src/goal-spec.ts"() {
7222
+ "use strict";
7223
+ }
7224
+ });
7225
+
7226
+ // ../runner/src/prompt.ts
7227
+ var ESCALATION_CONTRACT, PROJECT_METHOD, REVERSE_PHASE_GUIDE, SCAFFOLD_PHASE_GUIDE;
7228
+ var init_prompt = __esm({
7229
+ "../runner/src/prompt.ts"() {
7230
+ "use strict";
7231
+ init_src();
7232
+ init_reverse_standard();
7233
+ init_verify_scope();
7234
+ init_code_style();
7235
+ init_goal_spec();
7236
+ ESCALATION_CONTRACT = [
7237
+ "REKOMENDASI ESKALASI (wajib, terbaca mesin). Di bagian akhir dokumen audit, tulis bagian",
7238
+ "`## Rekomendasi eskalasi` berisi penjelasan singkat untuk manusia, LALU tepat SATU blok ```json",
7239
+ "berbentuk persis seperti ini (satu-satunya blok json di dokumen itu):",
7240
+ "",
7241
+ "```json",
7242
+ '{ "escalation": { "target": "none|qa|brief|prd", "reason": "<alasan singkat>",',
7243
+ ' "alternatives": ["<target lain yang masuk akal>"],',
7244
+ ' "prefill": { "title": "", "context": "", "outcome": "", "constraints": "", "severity": "", "steps": "" } } }',
7245
+ "```",
7246
+ "",
7247
+ "Pilih `target` dari hasil auditmu, bukan default:",
7248
+ '- "qa" \u2014 bug / regresi / perilaku salah yang perlu diperbaiki. Isi `prefill.severity`',
7249
+ " (critical|major|minor) dan `prefill.steps` (langkah reproduksi).",
7250
+ '- "brief" \u2014 kebutuhan/fitur yang bentuknya sudah jelas dan cakupannya satu backlog.',
7251
+ " Isi `prefill.title`, `prefill.context`, `prefill.outcome`.",
7252
+ '- "prd" \u2014 kebutuhan produk yang besar, ambigu, atau lintas modul sehingga perlu dokumen PRD',
7253
+ " lebih dulu. Isi `prefill.title`, `prefill.context`, `prefill.outcome`.",
7254
+ '- "none" \u2014 pertanyaannya sudah terjawab; tak perlu perbaikan maupun fitur baru.',
7255
+ "",
7256
+ "`alternatives` boleh array kosong. Jangan menulis blok json lain di dokumen itu."
7257
+ ].join("\n");
7258
+ PROJECT_METHOD = resolveMethod();
7259
+ REVERSE_PHASE_GUIDE = [
7260
+ "- Scan: baca source code \u2014 stack, arsitektur, data model, API surface, perilaku domain. Belum menulis docs.",
7261
+ "- Docs teknis: tulis kategori yang bisa diturunkan dari kode (architecture, requirements + EARS dari perilaku nyata, adr ber-Status accepted (reverse-engineered), operations, security, design-system/frontend bila relevan). Isi lengkap dan spesifik, bukan kerangka.",
7262
+ "- Wawancara: untuk product, business, brand, research, entrypoints \u2014 ajukan SATU pertanyaan per giliran ke manusia di terminal ini, tunggu jawabannya, isi docs dari jawaban. Jangan mengarang. Topik tanpa jawaban tandai: Status: draft \u2014 menunggu input.",
7263
+ "- Konvensi & index: tulis internal/docs/README.md (index bernomor lengkap), CLAUDE.md, AGENTS.md, .claude/settings.json + .claude/hooks/ensure-docs-updated.py persis seperti STANDAR DOCS.",
7264
+ "- Serah terima: pastikan setiap berkas docs terdaftar di index, lalu tulis ringkasan hasil + daftar pertanyaan yang belum terjawab ke terminal."
7265
+ ].join("\n");
7266
+ SCAFFOLD_PHASE_GUIDE = [
7267
+ "- Brainstorm: perdalam IDE project (di bawah) jadi masalah, pengguna, scope, dan metrik sukses. Ajukan SATU pertanyaan per giliran ke manusia di terminal ini, tunggu jawabannya. Jangan mengarang; topik yang belum dijawab tandai sebagai open question.",
7268
+ "- Objective: kunci SATU MVP objective yang terukur dari hasil brainstorm, tulis ringkas di docs.",
7269
+ "- Doc index: tulis SELURUH internal/docs/** dari ide+objective+jawaban, mengikuti STANDAR DOCS di bawah \u2014 entrypoints, product, business, requirements (+EARS dari perilaku yang diinginkan), research, architecture (stack/data-model/api-contract/nfr), adr awal (Status accepted), design-system/frontend bila ada UI, operations, security, plus README index bernomor + CLAUDE.md + AGENTS.md + Stop hook. Lengkap dan spesifik terhadap ide ini, BUKAN kerangka."
7270
+ ].join("\n");
7271
+ }
7272
+ });
7273
+
7274
+ // ../runner/src/git.ts
7275
+ var init_git = __esm({
7276
+ "../runner/src/git.ts"() {
7277
+ "use strict";
7278
+ }
7279
+ });
7280
+
7281
+ // ../runner/src/settings.ts
7282
+ var init_settings = __esm({
7283
+ "../runner/src/settings.ts"() {
7284
+ "use strict";
7285
+ }
7286
+ });
7287
+
7288
+ // ../runner/src/goal.ts
7289
+ var init_goal = __esm({
7290
+ "../runner/src/goal.ts"() {
7291
+ "use strict";
7292
+ init_src();
7293
+ init_prompt();
7294
+ init_goal_spec();
7295
+ }
7296
+ });
7297
+
7298
+ // ../runner/src/codex-settings.ts
7299
+ var init_codex_settings = __esm({
7300
+ "../runner/src/codex-settings.ts"() {
7301
+ "use strict";
7302
+ init_src();
7303
+ init_prompt();
7304
+ }
7305
+ });
7306
+
7307
+ // ../runner/src/agent-cli.ts
7308
+ var init_agent_cli = __esm({
7309
+ "../runner/src/agent-cli.ts"() {
7310
+ "use strict";
7311
+ init_settings();
7312
+ init_codex_settings();
7313
+ }
7314
+ });
7315
+
7175
7316
  // ../runner/src/custom-agents.ts
7176
7317
  var init_custom_agents = __esm({
7177
7318
  "../runner/src/custom-agents.ts"() {
@@ -7238,6 +7379,128 @@ var init_paths = __esm({
7238
7379
  }
7239
7380
  });
7240
7381
 
7382
+ // ../runner/src/skills.ts
7383
+ import { homedir as homedir2 } from "node:os";
7384
+ import { join as join2 } from "node:path";
7385
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
7386
+ function agentSkillHome(agent, env = process.env, osHome = homedir2()) {
7387
+ const own = (agent === "codex" ? env.HANOMAN_CODEX_HOME : env.HANOMAN_CLAUDE_HOME)?.trim();
7388
+ if (own) return own;
7389
+ if (agent === "codex") {
7390
+ const cx = env.CODEX_HOME?.trim();
7391
+ if (cx) return cx;
7392
+ }
7393
+ return join2(osHome, agent === "codex" ? ".codex" : ".claude");
7394
+ }
7395
+ function dirsIn(dir) {
7396
+ try {
7397
+ return readdirSync(dir, { withFileTypes: true }).filter((e) => (e.isDirectory() || e.isSymbolicLink()) && !e.name.startsWith(".")).map((e) => e.name);
7398
+ } catch {
7399
+ return [];
7400
+ }
7401
+ }
7402
+ function readJson(file) {
7403
+ try {
7404
+ return JSON.parse(readFileSync(file, "utf8"));
7405
+ } catch {
7406
+ return null;
7407
+ }
7408
+ }
7409
+ function skillsUnder(dir, pkg) {
7410
+ return dirsIn(dir).filter((name) => existsSync(join2(dir, name, "SKILL.md"))).map((name) => ({ id: pkg ? `${pkg}:${name}` : name, name, pkg, dir: join2(dir, name) }));
7411
+ }
7412
+ function splitPluginKey(key) {
7413
+ const at = key.lastIndexOf("@");
7414
+ return at > 0 ? { pkg: key.slice(0, at), marketplace: key.slice(at + 1) } : { pkg: key, marketplace: "" };
7415
+ }
7416
+ function manifestRoots(home) {
7417
+ const j = readJson(join2(home, "plugins", "installed_plugins.json"));
7418
+ const plugins = j?.plugins;
7419
+ if (!plugins || typeof plugins !== "object") return [];
7420
+ const out = [];
7421
+ for (const [key, entries] of Object.entries(plugins)) {
7422
+ const { pkg, marketplace } = splitPluginKey(key);
7423
+ for (const e of Array.isArray(entries) ? entries : []) {
7424
+ const p = e?.installPath;
7425
+ if (typeof p === "string" && p) out.push({ pkg, marketplace, dir: p });
7426
+ }
7427
+ }
7428
+ return out;
7429
+ }
7430
+ function cacheRoots(home) {
7431
+ const cache = join2(home, "plugins", "cache");
7432
+ const out = [];
7433
+ for (const marketplace of dirsIn(cache))
7434
+ for (const pkg of dirsIn(join2(cache, marketplace)))
7435
+ for (const version3 of dirsIn(join2(cache, marketplace, pkg)))
7436
+ out.push({ pkg, marketplace, dir: join2(cache, marketplace, pkg, version3) });
7437
+ return out;
7438
+ }
7439
+ function disabledPlugins(agent, home) {
7440
+ const out = /* @__PURE__ */ new Set();
7441
+ if (agent === "claude") {
7442
+ const en = readJson(join2(home, "settings.json"))?.enabledPlugins;
7443
+ if (en && typeof en === "object") {
7444
+ for (const [k, v] of Object.entries(en)) if (v === false) out.add(k);
7445
+ }
7446
+ return out;
7447
+ }
7448
+ let toml;
7449
+ try {
7450
+ toml = readFileSync(join2(home, "config.toml"), "utf8");
7451
+ } catch {
7452
+ return out;
7453
+ }
7454
+ let current = null;
7455
+ for (const raw of toml.split("\n")) {
7456
+ const line = raw.trim();
7457
+ const head = /^\[([^\]]*)\]$/.exec(line);
7458
+ if (head) {
7459
+ const m = /^plugins\."(.+)"$/.exec(head[1] ?? "");
7460
+ current = m ? m[1] : null;
7461
+ continue;
7462
+ }
7463
+ if (current && /^enabled\s*=\s*false\b/.test(line)) out.add(current);
7464
+ }
7465
+ return out;
7466
+ }
7467
+ function scanAgentSkills(agent, env = process.env, osHome = homedir2()) {
7468
+ const home = agentSkillHome(agent, env, osHome);
7469
+ const disabled = disabledPlugins(agent, home);
7470
+ const roots = [];
7471
+ const skills = [];
7472
+ const packages = /* @__PURE__ */ new Set();
7473
+ const seenSkill = /* @__PURE__ */ new Set();
7474
+ const seenRoot = /* @__PURE__ */ new Set();
7475
+ const add = (dir, found) => {
7476
+ if (!found.length) return;
7477
+ if (!seenRoot.has(dir)) {
7478
+ seenRoot.add(dir);
7479
+ roots.push(dir);
7480
+ }
7481
+ for (const s2 of found) if (!seenSkill.has(s2.id)) {
7482
+ seenSkill.add(s2.id);
7483
+ skills.push(s2);
7484
+ }
7485
+ };
7486
+ const userDir = join2(home, "skills");
7487
+ add(userDir, skillsUnder(userDir, null));
7488
+ for (const r of [...manifestRoots(home), ...cacheRoots(home)]) {
7489
+ if (disabled.has(`${r.pkg}@${r.marketplace}`)) continue;
7490
+ const dir = join2(r.dir, "skills");
7491
+ const found = skillsUnder(dir, r.pkg);
7492
+ if (!found.length) continue;
7493
+ packages.add(r.pkg);
7494
+ add(dir, found);
7495
+ }
7496
+ return { agent, home, roots, skills, packages: [...packages] };
7497
+ }
7498
+ var init_skills = __esm({
7499
+ "../runner/src/skills.ts"() {
7500
+ "use strict";
7501
+ }
7502
+ });
7503
+
7241
7504
  // ../runner/src/telegram-operator.ts
7242
7505
  var init_telegram_operator = __esm({
7243
7506
  "../runner/src/telegram-operator.ts"() {
@@ -7262,29 +7525,30 @@ var init_src2 = __esm({
7262
7525
  init_verify_scope();
7263
7526
  init_code_style();
7264
7527
  init_paths();
7528
+ init_skills();
7265
7529
  init_telegram_operator();
7266
7530
  }
7267
7531
  });
7268
7532
 
7269
7533
  // src/layout.ts
7270
- import { join as join2, resolve as resolve2 } from "node:path";
7534
+ import { join as join3, resolve as resolve2 } from "node:path";
7271
7535
  function resolveLayout(distDir2, exists) {
7272
7536
  const pkg = resolve2(distDir2, "..");
7273
- if (exists(join2(pkg, "prisma", "schema.prisma"))) {
7537
+ if (exists(join3(pkg, "prisma", "schema.prisma"))) {
7274
7538
  return {
7275
7539
  root: pkg,
7276
- schema: join2(pkg, "prisma", "schema.prisma"),
7277
- server: join2(pkg, "dist", "server.js"),
7278
- web: exists(join2(pkg, "web")) ? join2(pkg, "web") : null
7540
+ schema: join3(pkg, "prisma", "schema.prisma"),
7541
+ server: join3(pkg, "dist", "server.js"),
7542
+ web: exists(join3(pkg, "web")) ? join3(pkg, "web") : null
7279
7543
  };
7280
7544
  }
7281
7545
  const repo = resolve2(distDir2, "../..");
7282
- if (exists(join2(repo, "server", "prisma", "schema.prisma"))) {
7546
+ if (exists(join3(repo, "server", "prisma", "schema.prisma"))) {
7283
7547
  return {
7284
7548
  root: repo,
7285
- schema: join2(repo, "server", "prisma", "schema.prisma"),
7286
- server: join2(repo, "server", "dist", "server.js"),
7287
- web: exists(join2(repo, "src", "dist")) ? join2(repo, "src", "dist") : null
7549
+ schema: join3(repo, "server", "prisma", "schema.prisma"),
7550
+ server: join3(repo, "server", "dist", "server.js"),
7551
+ web: exists(join3(repo, "src", "dist")) ? join3(repo, "src", "dist") : null
7288
7552
  };
7289
7553
  }
7290
7554
  throw new Error(`hanoman: prisma/schema.prisma tak ditemukan dari ${distDir2} \u2014 instalasi rusak?`);
@@ -7382,8 +7646,8 @@ __export(start_exports, {
7382
7646
  });
7383
7647
  import { spawn, execFileSync as execFileSync2 } from "node:child_process";
7384
7648
  import { createRequire } from "node:module";
7385
- import { existsSync, mkdirSync, readdirSync, statSync, chmodSync } from "node:fs";
7386
- import { dirname as dirname2, join as join3, resolve as resolvePath } from "node:path";
7649
+ import { existsSync as existsSync2, mkdirSync, readdirSync as readdirSync2, statSync, chmodSync } from "node:fs";
7650
+ import { dirname as dirname2, join as join4, resolve as resolvePath } from "node:path";
7387
7651
  import { fileURLToPath } from "node:url";
7388
7652
  function parseStartArgs(argv) {
7389
7653
  const out = { port: null, host: null, db: null, migrate: true };
@@ -7476,9 +7740,9 @@ async function ensurePrismaClient(schema, dbUrl, ctx) {
7476
7740
  }
7477
7741
  }
7478
7742
  function spawnHelperPaths(ptyDir, listDir, exists) {
7479
- const dirs = [join3(ptyDir, "build", "Release")];
7480
- for (const name of listDir(join3(ptyDir, "prebuilds"))) dirs.push(join3(ptyDir, "prebuilds", name));
7481
- return dirs.map((d) => join3(d, "spawn-helper")).filter(exists);
7743
+ const dirs = [join4(ptyDir, "build", "Release")];
7744
+ for (const name of listDir(join4(ptyDir, "prebuilds"))) dirs.push(join4(ptyDir, "prebuilds", name));
7745
+ return dirs.map((d) => join4(d, "spawn-helper")).filter(exists);
7482
7746
  }
7483
7747
  function ensureSpawnHelpersExecutable(paths2, ops) {
7484
7748
  const fixed = [];
@@ -7502,11 +7766,11 @@ function repairSpawnHelper(ctx) {
7502
7766
  }
7503
7767
  const paths2 = spawnHelperPaths(ptyDir, (d) => {
7504
7768
  try {
7505
- return readdirSync(d);
7769
+ return readdirSync2(d);
7506
7770
  } catch {
7507
7771
  return [];
7508
7772
  }
7509
- }, existsSync);
7773
+ }, existsSync2);
7510
7774
  const fixed = ensureSpawnHelpersExecutable(paths2, {
7511
7775
  mode: (p) => statSync(p).mode,
7512
7776
  chmod: (p, m) => chmodSync(p, m)
@@ -7558,7 +7822,7 @@ async function start(argv, ctx) {
7558
7822
  let layout;
7559
7823
  let dbUrl;
7560
7824
  try {
7561
- layout = resolveLayout(distDir(), existsSync);
7825
+ layout = resolveLayout(distDir(), existsSync2);
7562
7826
  dbUrl = opts.db ? `file:${resolvePath(opts.db)}` : resolveDbUrl(ctx.env, dirname2(layout.schema));
7563
7827
  } catch (e) {
7564
7828
  ctx.stderr(`${e.message}
@@ -7571,7 +7835,7 @@ async function start(argv, ctx) {
7571
7835
  const home = resolveHome(ctx.env);
7572
7836
  mkdirSync(home, { recursive: true });
7573
7837
  mkdirSync(dirname2(dbFilePath(dbUrl)), { recursive: true });
7574
- if (!existsSync(layout.server)) {
7838
+ if (!existsSync2(layout.server)) {
7575
7839
  ctx.stderr(`hanoman: bundle server tak ada di ${layout.server} \u2014 jalankan \`pnpm build\` dulu
7576
7840
  `);
7577
7841
  return 1;
@@ -7655,7 +7919,7 @@ __export(doctor_exports, {
7655
7919
  doctorReport: () => doctorReport
7656
7920
  });
7657
7921
  import { execFileSync as execFileSync3 } from "node:child_process";
7658
- import { accessSync, constants, existsSync as existsSync2 } from "node:fs";
7922
+ import { accessSync, constants, existsSync as existsSync3 } from "node:fs";
7659
7923
  import { dirname as dirname3 } from "node:path";
7660
7924
  function doctorReport(p) {
7661
7925
  const major = Number(/^v?(\d+)/.exec(p.node)?.[1] ?? 0);
@@ -7675,6 +7939,15 @@ function doctorReport(p) {
7675
7939
  { mark: p.web ? "\u2713" : "!", text: p.web ? "aset dashboard ada" : "aset dashboard tak ada \u2014 API jalan, dashboard tidak", fatal: false },
7676
7940
  { mark: "\xB7", text: `db ${p.db}`, fatal: false }
7677
7941
  ];
7942
+ for (const m of p.methods) {
7943
+ const agent = m.agent === "codex" ? "Codex CLI" : "Claude Code";
7944
+ rows.push({
7945
+ mark: m.ready ? "\u2713" : "!",
7946
+ text: m.ready ? `metode ${m.method} \xB7 ${agent} \u2014 siap` : `metode ${m.method} \xB7 ${agent} \u2014 belum siap: ` + [...m.missingPackages, ...m.missingSkills].join(", ") + m.install.map((c) => `
7947
+ ${c}`).join(""),
7948
+ fatal: false
7949
+ });
7950
+ }
7678
7951
  if (!p.claude && !p.codex) {
7679
7952
  rows.push({ mark: "\u2717", text: "tak ada CLI agen (claude ATAU codex wajib ada)", fatal: true });
7680
7953
  }
@@ -7691,7 +7964,7 @@ async function doctor(_argv, ctx) {
7691
7964
  let layout;
7692
7965
  let db;
7693
7966
  try {
7694
- layout = resolveLayout(distDir(), existsSync2);
7967
+ layout = resolveLayout(distDir(), existsSync3);
7695
7968
  db = dbFilePath(resolveDbUrl(ctx.env, dirname3(layout.schema)));
7696
7969
  } catch (e) {
7697
7970
  ctx.stderr(`${e.message}
@@ -7701,20 +7974,28 @@ async function doctor(_argv, ctx) {
7701
7974
  const home = resolveHome(ctx.env);
7702
7975
  let homeWritable = false;
7703
7976
  try {
7704
- accessSync(existsSync2(home) ? home : dirname3(home), constants.W_OK);
7977
+ accessSync(existsSync3(home) ? home : dirname3(home), constants.W_OK);
7705
7978
  homeWritable = true;
7706
7979
  } catch {
7707
7980
  }
7981
+ const claude = version(ctx.env.HANOMAN_CLAUDE_BIN ?? "claude", ["--version"]);
7982
+ const codex = version(ctx.env.HANOMAN_CODEX_BIN ?? "codex", ["--version"]);
7983
+ const method = METHODS[DEFAULT_METHOD];
7984
+ const methods = zAgent.options.filter((a) => (a === "codex" ? codex : claude) !== null).map((a) => {
7985
+ const s2 = scanAgentSkills(a, ctx.env);
7986
+ return methodStatus(method, a, { skills: s2.skills.map((k) => k.id), packages: s2.packages });
7987
+ });
7708
7988
  const r = doctorReport({
7709
7989
  node: process.version,
7710
7990
  git: version("git", ["--version"]),
7711
7991
  tmux: version("tmux", ["-V"]),
7712
- claude: version(ctx.env.HANOMAN_CLAUDE_BIN ?? "claude", ["--version"]),
7713
- codex: version(ctx.env.HANOMAN_CODEX_BIN ?? "codex", ["--version"]),
7992
+ claude,
7993
+ codex,
7714
7994
  gh: version(ctx.env.HANOMAN_GH_BIN ?? "gh", ["--version"]),
7715
7995
  homeWritable,
7716
7996
  web: layout.web !== null,
7717
- db
7997
+ db,
7998
+ methods
7718
7999
  });
7719
8000
  ctx.stdout(`hanoman doctor
7720
8001
  ${r.lines.join("\n")}
@@ -7730,6 +8011,7 @@ var init_doctor = __esm({
7730
8011
  "src/commands/doctor.ts"() {
7731
8012
  "use strict";
7732
8013
  init_src2();
8014
+ init_src();
7733
8015
  init_layout();
7734
8016
  init_start();
7735
8017
  }
@@ -28519,9 +28801,9 @@ var mcp_exports = {};
28519
28801
  __export(mcp_exports, {
28520
28802
  default: () => mcp
28521
28803
  });
28522
- import { readFileSync } from "node:fs";
28523
- import { homedir as homedir2 } from "node:os";
28524
- import { join as join4 } from "node:path";
28804
+ import { readFileSync as readFileSync2 } from "node:fs";
28805
+ import { homedir as homedir3 } from "node:os";
28806
+ import { join as join5 } from "node:path";
28525
28807
  async function mcp(argv, ctx) {
28526
28808
  const cfg = resolveMcpConfig(argv, ctx.env, () => tokenFile(ctx.env));
28527
28809
  const say = (s2) => ctx.stderr(redactToken(s2, cfg.token) + "\n");
@@ -28552,9 +28834,9 @@ var init_mcp2 = __esm({
28552
28834
  init_server();
28553
28835
  init_redact();
28554
28836
  tokenFile = (env) => {
28555
- const home = env.HANOMAN_HOME ?? join4(homedir2(), ".hanoman");
28837
+ const home = env.HANOMAN_HOME ?? join5(homedir3(), ".hanoman");
28556
28838
  try {
28557
- return readFileSync(join4(home, "agent-token"), "utf8");
28839
+ return readFileSync2(join5(home, "agent-token"), "utf8");
28558
28840
  } catch {
28559
28841
  return null;
28560
28842
  }
@@ -28574,7 +28856,7 @@ __export(migrate_pg_exports, {
28574
28856
  migrationSteps: () => migrationSteps,
28575
28857
  parseMigrateArgs: () => parseMigrateArgs
28576
28858
  });
28577
- import { existsSync as existsSync3 } from "node:fs";
28859
+ import { existsSync as existsSync4 } from "node:fs";
28578
28860
  import { dirname as dirname4, resolve as resolvePath2 } from "node:path";
28579
28861
  function coerceInt8(rows, fields, model) {
28580
28862
  const cols = fields.filter((f) => f.dataTypeID === INT8_OID).map((f) => f.name);
@@ -28642,7 +28924,7 @@ async function migratePg(argv, ctx) {
28642
28924
  let layout;
28643
28925
  let dbUrl;
28644
28926
  try {
28645
- layout = resolveLayout(distDir(), existsSync3);
28927
+ layout = resolveLayout(distDir(), existsSync4);
28646
28928
  dbUrl = opts.to ? `file:${resolvePath2(opts.to)}` : resolveDbUrl(ctx.env, dirname4(layout.schema));
28647
28929
  } catch (e) {
28648
28930
  ctx.stderr(`${e.message}
@@ -28781,11 +29063,11 @@ var init_migrate_pg = __esm({
28781
29063
  });
28782
29064
 
28783
29065
  // src/config.ts
28784
- import { readFileSync as readFileSync2, existsSync as existsSync4 } from "node:fs";
28785
- import { join as join5 } from "node:path";
29066
+ import { readFileSync as readFileSync3, existsSync as existsSync5 } from "node:fs";
29067
+ import { join as join6 } from "node:path";
28786
29068
  function loadConfig(repoRoot) {
28787
- const p = join5(repoRoot, "hanoman.config.json");
28788
- const raw = existsSync4(p) ? JSON.parse(readFileSync2(p, "utf8")) : {};
29069
+ const p = join6(repoRoot, "hanoman.config.json");
29070
+ const raw = existsSync5(p) ? JSON.parse(readFileSync3(p, "utf8")) : {};
28789
29071
  return zHanomanConfig.parse(raw);
28790
29072
  }
28791
29073
  var init_config3 = __esm({
@@ -28797,12 +29079,12 @@ var init_config3 = __esm({
28797
29079
 
28798
29080
  // src/repo.ts
28799
29081
  import { spawnSync } from "node:child_process";
28800
- import { join as join6 } from "node:path";
29082
+ import { join as join7 } from "node:path";
28801
29083
  function resolveRepo(cwd) {
28802
29084
  const r = spawnSync("git", ["rev-parse", "--show-toplevel"], { cwd, encoding: "utf8" });
28803
29085
  const root = r.status === 0 ? r.stdout.trim() : cwd;
28804
29086
  const { docsDir } = loadConfig(root);
28805
- return { root, docsDir, indexPath: join6(root, docsDir, "README.md") };
29087
+ return { root, docsDir, indexPath: join7(root, docsDir, "README.md") };
28806
29088
  }
28807
29089
  var init_repo = __esm({
28808
29090
  "src/repo.ts"() {
@@ -28812,10 +29094,10 @@ var init_repo = __esm({
28812
29094
  });
28813
29095
 
28814
29096
  // src/docs-model.ts
28815
- import { readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
28816
- import { join as join7, relative } from "node:path";
29097
+ import { readFileSync as readFileSync4, readdirSync as readdirSync3, statSync as statSync2 } from "node:fs";
29098
+ import { join as join8, relative } from "node:path";
28817
29099
  function parseIndex(indexPath) {
28818
- const md = readFileSync3(indexPath, "utf8");
29100
+ const md = readFileSync4(indexPath, "utf8");
28819
29101
  const out = /* @__PURE__ */ new Set();
28820
29102
  for (const m of md.matchAll(LINK_RE2)) {
28821
29103
  let t = m[1].trim();
@@ -28828,9 +29110,9 @@ function parseIndex(indexPath) {
28828
29110
  function walkDocs(docsRoot) {
28829
29111
  const out = [];
28830
29112
  const walk = (dir) => {
28831
- for (const name of readdirSync2(dir)) {
29113
+ for (const name of readdirSync3(dir)) {
28832
29114
  if (name.startsWith(".")) continue;
28833
- const abs = join7(dir, name);
29115
+ const abs = join8(dir, name);
28834
29116
  if (statSync2(abs).isDirectory()) walk(abs);
28835
29117
  else out.push(relative(docsRoot, abs).split("\\").join("/"));
28836
29118
  }
@@ -28862,17 +29144,17 @@ var init_docs_model = __esm({
28862
29144
  });
28863
29145
 
28864
29146
  // src/verify.ts
28865
- import { join as join8 } from "node:path";
28866
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
29147
+ import { join as join9 } from "node:path";
29148
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
28867
29149
  function scanCoverage(cwd) {
28868
29150
  const { root, docsDir, indexPath } = resolveRepo(cwd);
28869
- const docsRoot = join8(root, docsDir);
28870
- if (!existsSync5(docsRoot)) return { coverage: 100, cats: [] };
28871
- if (!existsSync5(indexPath)) throw new Error(`index Source of Truth tidak ada: ${indexPath}`);
29151
+ const docsRoot = join9(root, docsDir);
29152
+ if (!existsSync6(docsRoot)) return { coverage: 100, cats: [] };
29153
+ if (!existsSync6(indexPath)) throw new Error(`index Source of Truth tidak ada: ${indexPath}`);
28872
29154
  const corpus = walkDocs(docsRoot);
28873
29155
  const read = (rel) => {
28874
29156
  try {
28875
- return readFileSync4(join8(docsRoot, rel), "utf8");
29157
+ return readFileSync5(join9(docsRoot, rel), "utf8");
28876
29158
  } catch {
28877
29159
  return null;
28878
29160
  }
@@ -28915,9 +29197,9 @@ var init_docs_scan = __esm({
28915
29197
  });
28916
29198
 
28917
29199
  // src/index-edit.ts
28918
- import { readFileSync as readFileSync5, writeFileSync } from "node:fs";
29200
+ import { readFileSync as readFileSync6, writeFileSync } from "node:fs";
28919
29201
  function addLink(indexPath, relPath, category) {
28920
- let md = readFileSync5(indexPath, "utf8");
29202
+ let md = readFileSync6(indexPath, "utf8");
28921
29203
  if (md.includes(`(${relPath})`)) return;
28922
29204
  const base = relPath.split("/").pop().replace(/\.md$/, "");
28923
29205
  const line = `- [${base}](${relPath})`;
@@ -28946,23 +29228,23 @@ __export(docs_index_exports, {
28946
29228
  default: () => docs_index_default
28947
29229
  });
28948
29230
  import { parseArgs as parseArgs2 } from "node:util";
28949
- import { join as join9 } from "node:path";
28950
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
29231
+ import { join as join10 } from "node:path";
29232
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "node:fs";
28951
29233
  async function docs_index_default(args, ctx) {
28952
29234
  const { values } = parseArgs2({ args, options: { check: { type: "boolean" }, fix: { type: "boolean" } }, allowPositionals: true });
28953
29235
  const { root, docsDir, indexPath } = resolveRepo(ctx.cwd);
28954
- const docsRoot = join9(root, docsDir);
29236
+ const docsRoot = join10(root, docsDir);
28955
29237
  const corpus = walkDocs(docsRoot);
28956
29238
  const read = (rel) => {
28957
29239
  try {
28958
- return readFileSync6(join9(docsRoot, rel), "utf8");
29240
+ return readFileSync7(join10(docsRoot, rel), "utf8");
28959
29241
  } catch {
28960
29242
  return null;
28961
29243
  }
28962
29244
  };
28963
29245
  const linked = linkedSetFrom(INDEX_NAME, corpus, read);
28964
29246
  const unlinked = corpus.filter((f) => f !== INDEX_NAME && !linked.has(f));
28965
- const dangling = [...parseIndex(indexPath)].filter((p) => !existsSync6(join9(docsRoot, p)));
29247
+ const dangling = [...parseIndex(indexPath)].filter((p) => !existsSync7(join10(docsRoot, p)));
28966
29248
  if (values.fix) {
28967
29249
  for (const f of unlinked) addLink(indexPath, f, f.split("/")[0]);
28968
29250
  ctx.stdout(`linked ${unlinked.length} doc(s)
@@ -29015,7 +29297,7 @@ var init_docs_link = __esm({
29015
29297
  });
29016
29298
 
29017
29299
  // src/release/pack.ts
29018
- import { join as join10 } from "node:path";
29300
+ import { join as join11 } from "node:path";
29019
29301
  function packageJsonFor(version3, deps) {
29020
29302
  return {
29021
29303
  name: PKG_NAME,
@@ -29053,17 +29335,17 @@ function verifyPackedDeps(pkg) {
29053
29335
  }
29054
29336
  function copyPlan(repo) {
29055
29337
  return [
29056
- { from: join10(repo, "server/dist/server.js"), to: "dist/server.js" },
29057
- { from: join10(repo, "server/dist/build-info.json"), to: "dist/build-info.json" },
29058
- { from: join10(repo, "cli/dist/hanoman.js"), to: "dist/cli.js" },
29059
- { from: join10(repo, "src/dist"), to: "web", dir: true },
29060
- { from: join10(repo, "server/prisma/schema.prisma"), to: "prisma/schema.prisma" },
29061
- { from: join10(repo, "server/prisma/migrations"), to: "prisma/migrations", dir: true },
29062
- { from: join10(repo, "internal/docs/operations/npm-readme.md"), to: "README.md" },
29338
+ { from: join11(repo, "server/dist/server.js"), to: "dist/server.js" },
29339
+ { from: join11(repo, "server/dist/build-info.json"), to: "dist/build-info.json" },
29340
+ { from: join11(repo, "cli/dist/hanoman.js"), to: "dist/cli.js" },
29341
+ { from: join11(repo, "src/dist"), to: "web", dir: true },
29342
+ { from: join11(repo, "server/prisma/schema.prisma"), to: "prisma/schema.prisma" },
29343
+ { from: join11(repo, "server/prisma/migrations"), to: "prisma/migrations", dir: true },
29344
+ { from: join11(repo, "internal/docs/operations/npm-readme.md"), to: "README.md" },
29063
29345
  // SPEC-489 · dibaca runtime oleh pickGuideFile (kandidat `<pkg>/docs/…`). Bukan README:
29064
29346
  // README paket adalah npm-readme.md (berhadapan-MANUSIA), ini naskah berhadapan-AGEN.
29065
- { from: join10(repo, "docs/agent-integration.md"), to: "docs/agent-integration.md" },
29066
- { from: join10(repo, "LICENSE"), to: "LICENSE" }
29347
+ { from: join11(repo, "docs/agent-integration.md"), to: "docs/agent-integration.md" },
29348
+ { from: join11(repo, "LICENSE"), to: "LICENSE" }
29067
29349
  ];
29068
29350
  }
29069
29351
  var PKG_NAME, REPO_URL, RUNTIME_DEPS, REQUIRED_ARTIFACTS, BIN_SHIM;
@@ -29109,10 +29391,10 @@ var pack_exports = {};
29109
29391
  __export(pack_exports, {
29110
29392
  default: () => pack
29111
29393
  });
29112
- import { cpSync, existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
29113
- import { dirname as dirname5, join as join11 } from "node:path";
29394
+ import { cpSync, existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync8, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
29395
+ import { dirname as dirname5, join as join12 } from "node:path";
29114
29396
  function depVersions(repo) {
29115
- const read = (p) => JSON.parse(readFileSync7(join11(repo, p), "utf8"));
29397
+ const read = (p) => JSON.parse(readFileSync8(join12(repo, p), "utf8"));
29116
29398
  const server = read("server/package.json"), cli = read("cli/package.json");
29117
29399
  const pools = [server.dependencies, server.devDependencies, cli.dependencies, cli.devDependencies];
29118
29400
  const out = {};
@@ -29126,19 +29408,19 @@ function depVersions(repo) {
29126
29408
  async function pack(argv, ctx) {
29127
29409
  let repo;
29128
29410
  try {
29129
- repo = resolveLayout(distDir(), existsSync7).root;
29411
+ repo = resolveLayout(distDir(), existsSync8).root;
29130
29412
  } catch (e) {
29131
29413
  ctx.stderr(`${e.message}
29132
29414
  `);
29133
29415
  return 1;
29134
29416
  }
29135
- if (!existsSync7(join11(repo, "server/prisma/schema.prisma"))) {
29417
+ if (!existsSync8(join12(repo, "server/prisma/schema.prisma"))) {
29136
29418
  ctx.stderr("pack: hanya bisa dijalankan dari checkout repo hanoman\n");
29137
29419
  return 1;
29138
29420
  }
29139
29421
  const outIdx = argv.indexOf("--out");
29140
- const out = outIdx === -1 ? join11(repo, "dist-npm") : join11(repo, argv[outIdx + 1] ?? "dist-npm");
29141
- const version3 = JSON.parse(readFileSync7(join11(repo, "package.json"), "utf8")).version;
29422
+ const out = outIdx === -1 ? join12(repo, "dist-npm") : join12(repo, argv[outIdx + 1] ?? "dist-npm");
29423
+ const version3 = JSON.parse(readFileSync8(join12(repo, "package.json"), "utf8")).version;
29142
29424
  if (!version3) {
29143
29425
  ctx.stderr("pack: root package.json tanpa field version\n");
29144
29426
  return 1;
@@ -29152,20 +29434,20 @@ async function pack(argv, ctx) {
29152
29434
  return 1;
29153
29435
  }
29154
29436
  rmSync(out, { recursive: true, force: true });
29155
- mkdirSync2(join11(out, "bin"), { recursive: true });
29437
+ mkdirSync2(join12(out, "bin"), { recursive: true });
29156
29438
  for (const item of copyPlan(repo)) {
29157
- if (!existsSync7(item.from)) {
29439
+ if (!existsSync8(item.from)) {
29158
29440
  ctx.stderr(`pack: artefak hilang \u2014 ${item.from} (sudah \`pnpm build\`?)
29159
29441
  `);
29160
29442
  return 1;
29161
29443
  }
29162
- const dest = join11(out, item.to);
29444
+ const dest = join12(out, item.to);
29163
29445
  mkdirSync2(dirname5(dest), { recursive: true });
29164
29446
  cpSync(item.from, dest, item.dir ? { recursive: true } : {});
29165
29447
  }
29166
- writeFileSync2(join11(out, "bin/hanoman.mjs"), BIN_SHIM);
29167
- writeFileSync2(join11(out, "package.json"), JSON.stringify(packageJsonFor(version3, deps), null, 2) + "\n");
29168
- const missing = REQUIRED_ARTIFACTS.filter((a) => !existsSync7(join11(out, a)));
29448
+ writeFileSync2(join12(out, "bin/hanoman.mjs"), BIN_SHIM);
29449
+ writeFileSync2(join12(out, "package.json"), JSON.stringify(packageJsonFor(version3, deps), null, 2) + "\n");
29450
+ const missing = REQUIRED_ARTIFACTS.filter((a) => !existsSync8(join12(out, a)));
29169
29451
  if (missing.length) {
29170
29452
  ctx.stderr(`pack: artefak wajib hilang: ${missing.join(", ")}
29171
29453
  `);
@@ -29190,13 +29472,13 @@ var verify_packed_exports = {};
29190
29472
  __export(verify_packed_exports, {
29191
29473
  default: () => verifyPacked
29192
29474
  });
29193
- import { readFileSync as readFileSync8 } from "node:fs";
29194
- import { join as join12 } from "node:path";
29475
+ import { readFileSync as readFileSync9 } from "node:fs";
29476
+ import { join as join13 } from "node:path";
29195
29477
  function verifyPacked(_argv, ctx) {
29196
- const path = join12(process.cwd(), "package.json");
29478
+ const path = join13(process.cwd(), "package.json");
29197
29479
  let pkg;
29198
29480
  try {
29199
- pkg = JSON.parse(readFileSync8(path, "utf8"));
29481
+ pkg = JSON.parse(readFileSync9(path, "utf8"));
29200
29482
  } catch {
29201
29483
  ctx.stderr(`verify: tak bisa membaca ${path}
29202
29484
  `);