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/build-info.json +3 -3
- package/dist/cli.js +524 -242
- package/dist/server.js +506 -180
- package/package.json +1 -1
- package/web/assets/{index-BfOACraN.js → index-D6bmQt7Y.js} +1549 -1549
- package/web/index.html +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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 (
|
|
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
|
|
8018
|
-
|
|
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
|
-
|
|
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 ?
|
|
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 ?
|
|
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 -- "- \\[ \\]"
|
|
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
|
-
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
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,128 @@ 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 { join as join2 } 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 dirsIn(dir2) {
|
|
8672
|
+
try {
|
|
8673
|
+
return readdirSync(dir2, { withFileTypes: true }).filter((e) => (e.isDirectory() || e.isSymbolicLink()) && !e.name.startsWith(".")).map((e) => e.name);
|
|
8674
|
+
} catch {
|
|
8675
|
+
return [];
|
|
8676
|
+
}
|
|
8677
|
+
}
|
|
8678
|
+
function readJson(file) {
|
|
8679
|
+
try {
|
|
8680
|
+
return JSON.parse(readFileSync2(file, "utf8"));
|
|
8681
|
+
} catch {
|
|
8682
|
+
return null;
|
|
8683
|
+
}
|
|
8684
|
+
}
|
|
8685
|
+
function skillsUnder(dir2, pkg) {
|
|
8686
|
+
return dirsIn(dir2).filter((name2) => existsSync2(join2(dir2, name2, "SKILL.md"))).map((name2) => ({ id: pkg ? `${pkg}:${name2}` : name2, name: name2, pkg, dir: join2(dir2, name2) }));
|
|
8687
|
+
}
|
|
8688
|
+
function splitPluginKey(key) {
|
|
8689
|
+
const at = key.lastIndexOf("@");
|
|
8690
|
+
return at > 0 ? { pkg: key.slice(0, at), marketplace: key.slice(at + 1) } : { pkg: key, marketplace: "" };
|
|
8691
|
+
}
|
|
8692
|
+
function manifestRoots(home2) {
|
|
8693
|
+
const j = readJson(join2(home2, "plugins", "installed_plugins.json"));
|
|
8694
|
+
const plugins = j?.plugins;
|
|
8695
|
+
if (!plugins || typeof plugins !== "object") return [];
|
|
8696
|
+
const out3 = [];
|
|
8697
|
+
for (const [key, entries] of Object.entries(plugins)) {
|
|
8698
|
+
const { pkg, marketplace } = splitPluginKey(key);
|
|
8699
|
+
for (const e of Array.isArray(entries) ? entries : []) {
|
|
8700
|
+
const p = e?.installPath;
|
|
8701
|
+
if (typeof p === "string" && p) out3.push({ pkg, marketplace, dir: p });
|
|
8702
|
+
}
|
|
8703
|
+
}
|
|
8704
|
+
return out3;
|
|
8705
|
+
}
|
|
8706
|
+
function cacheRoots(home2) {
|
|
8707
|
+
const cache6 = join2(home2, "plugins", "cache");
|
|
8708
|
+
const out3 = [];
|
|
8709
|
+
for (const marketplace of dirsIn(cache6))
|
|
8710
|
+
for (const pkg of dirsIn(join2(cache6, marketplace)))
|
|
8711
|
+
for (const version of dirsIn(join2(cache6, marketplace, pkg)))
|
|
8712
|
+
out3.push({ pkg, marketplace, dir: join2(cache6, marketplace, pkg, version) });
|
|
8713
|
+
return out3;
|
|
8714
|
+
}
|
|
8715
|
+
function disabledPlugins(agent, home2) {
|
|
8716
|
+
const out3 = /* @__PURE__ */ new Set();
|
|
8717
|
+
if (agent === "claude") {
|
|
8718
|
+
const en = readJson(join2(home2, "settings.json"))?.enabledPlugins;
|
|
8719
|
+
if (en && typeof en === "object") {
|
|
8720
|
+
for (const [k, v] of Object.entries(en)) if (v === false) out3.add(k);
|
|
8721
|
+
}
|
|
8722
|
+
return out3;
|
|
8723
|
+
}
|
|
8724
|
+
let toml;
|
|
8725
|
+
try {
|
|
8726
|
+
toml = readFileSync2(join2(home2, "config.toml"), "utf8");
|
|
8727
|
+
} catch {
|
|
8728
|
+
return out3;
|
|
8729
|
+
}
|
|
8730
|
+
let current = null;
|
|
8731
|
+
for (const raw of toml.split("\n")) {
|
|
8732
|
+
const line = raw.trim();
|
|
8733
|
+
const head = /^\[([^\]]*)\]$/.exec(line);
|
|
8734
|
+
if (head) {
|
|
8735
|
+
const m = /^plugins\."(.+)"$/.exec(head[1] ?? "");
|
|
8736
|
+
current = m ? m[1] : null;
|
|
8737
|
+
continue;
|
|
8738
|
+
}
|
|
8739
|
+
if (current && /^enabled\s*=\s*false\b/.test(line)) out3.add(current);
|
|
8740
|
+
}
|
|
8741
|
+
return out3;
|
|
8742
|
+
}
|
|
8743
|
+
function scanAgentSkills(agent, env = process.env, osHome = homedir2()) {
|
|
8744
|
+
const home2 = agentSkillHome(agent, env, osHome);
|
|
8745
|
+
const disabled = disabledPlugins(agent, home2);
|
|
8746
|
+
const roots = [];
|
|
8747
|
+
const skills = [];
|
|
8748
|
+
const packages = /* @__PURE__ */ new Set();
|
|
8749
|
+
const seenSkill = /* @__PURE__ */ new Set();
|
|
8750
|
+
const seenRoot = /* @__PURE__ */ new Set();
|
|
8751
|
+
const add = (dir2, found) => {
|
|
8752
|
+
if (!found.length) return;
|
|
8753
|
+
if (!seenRoot.has(dir2)) {
|
|
8754
|
+
seenRoot.add(dir2);
|
|
8755
|
+
roots.push(dir2);
|
|
8756
|
+
}
|
|
8757
|
+
for (const s2 of found) if (!seenSkill.has(s2.id)) {
|
|
8758
|
+
seenSkill.add(s2.id);
|
|
8759
|
+
skills.push(s2);
|
|
8760
|
+
}
|
|
8761
|
+
};
|
|
8762
|
+
const userDir = join2(home2, "skills");
|
|
8763
|
+
add(userDir, skillsUnder(userDir, null));
|
|
8764
|
+
for (const r of [...manifestRoots(home2), ...cacheRoots(home2)]) {
|
|
8765
|
+
if (disabled.has(`${r.pkg}@${r.marketplace}`)) continue;
|
|
8766
|
+
const dir2 = join2(r.dir, "skills");
|
|
8767
|
+
const found = skillsUnder(dir2, r.pkg);
|
|
8768
|
+
if (!found.length) continue;
|
|
8769
|
+
packages.add(r.pkg);
|
|
8770
|
+
add(dir2, found);
|
|
8771
|
+
}
|
|
8772
|
+
return { agent, home: home2, roots, skills, packages: [...packages] };
|
|
8773
|
+
}
|
|
8774
|
+
var init_skills = __esm({
|
|
8775
|
+
"../runner/src/skills.ts"() {
|
|
8776
|
+
"use strict";
|
|
8777
|
+
}
|
|
8778
|
+
});
|
|
8779
|
+
|
|
8503
8780
|
// ../runner/src/telegram-operator.ts
|
|
8504
8781
|
function buildTelegramOperatorPrompt(input) {
|
|
8505
8782
|
const personality = input.personality ? [
|
|
@@ -8588,6 +8865,7 @@ var init_src2 = __esm({
|
|
|
8588
8865
|
init_verify_scope();
|
|
8589
8866
|
init_code_style();
|
|
8590
8867
|
init_paths();
|
|
8868
|
+
init_skills();
|
|
8591
8869
|
init_telegram_operator();
|
|
8592
8870
|
}
|
|
8593
8871
|
});
|
|
@@ -8755,12 +9033,12 @@ var init_stage_machine = __esm({
|
|
|
8755
9033
|
});
|
|
8756
9034
|
|
|
8757
9035
|
// src/services/session-phases.ts
|
|
8758
|
-
import { readFileSync as
|
|
9036
|
+
import { readFileSync as readFileSync3, readdirSync as readdirSync2 } from "node:fs";
|
|
8759
9037
|
function recorded(file) {
|
|
8760
9038
|
const out3 = /* @__PURE__ */ new Map();
|
|
8761
9039
|
let raw;
|
|
8762
9040
|
try {
|
|
8763
|
-
raw =
|
|
9041
|
+
raw = readFileSync3(file, "utf8");
|
|
8764
9042
|
} catch {
|
|
8765
9043
|
return out3;
|
|
8766
9044
|
}
|
|
@@ -8798,19 +9076,21 @@ function stageFor(phases) {
|
|
|
8798
9076
|
return best < 0 ? null : STAGES[best];
|
|
8799
9077
|
}
|
|
8800
9078
|
function planComplete(worktree, specId) {
|
|
8801
|
-
const dir2 = `${worktree}/docs/superpowers/plans`;
|
|
8802
9079
|
const re = new RegExp(`(^|[^a-z0-9])${specId.toLowerCase()}([^0-9]|$)`);
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
names
|
|
8806
|
-
} catch {
|
|
8807
|
-
return true;
|
|
8808
|
-
}
|
|
8809
|
-
for (const n2 of names) {
|
|
8810
|
-
if (!re.test(n2.toLowerCase())) continue;
|
|
9080
|
+
for (const rel of PLAN_DIRS) {
|
|
9081
|
+
const dir2 = `${worktree}/${rel}`;
|
|
9082
|
+
let names;
|
|
8811
9083
|
try {
|
|
8812
|
-
|
|
9084
|
+
names = readdirSync2(dir2);
|
|
8813
9085
|
} catch {
|
|
9086
|
+
continue;
|
|
9087
|
+
}
|
|
9088
|
+
for (const n2 of names) {
|
|
9089
|
+
if (!re.test(n2.toLowerCase())) continue;
|
|
9090
|
+
try {
|
|
9091
|
+
if (/^[ \t]*- \[ \]/m.test(readFileSync3(`${dir2}/${n2}`, "utf8"))) return false;
|
|
9092
|
+
} catch {
|
|
9093
|
+
}
|
|
8814
9094
|
}
|
|
8815
9095
|
}
|
|
8816
9096
|
return true;
|
|
@@ -8829,6 +9109,7 @@ var init_session_phases = __esm({
|
|
|
8829
9109
|
"src/services/session-phases.ts"() {
|
|
8830
9110
|
"use strict";
|
|
8831
9111
|
init_src2();
|
|
9112
|
+
init_src();
|
|
8832
9113
|
init_stage_machine();
|
|
8833
9114
|
phaseFilePath = (repoDir, sessionId2) => `${repoDir}/.worktrees/.phases/${sessionId2}`;
|
|
8834
9115
|
decisionFilePath = (repoDir, sessionId2) => `${repoDir}/.worktrees/.decisions/${sessionId2}`;
|
|
@@ -9090,9 +9371,9 @@ var init_tui_dialog = __esm({
|
|
|
9090
9371
|
});
|
|
9091
9372
|
|
|
9092
9373
|
// src/services/secret-box.ts
|
|
9093
|
-
import { chmodSync, existsSync as
|
|
9374
|
+
import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync } from "node:fs";
|
|
9094
9375
|
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
9095
|
-
import { dirname as dirname4, join as
|
|
9376
|
+
import { dirname as dirname4, join as join3 } from "node:path";
|
|
9096
9377
|
function isEncrypted(value) {
|
|
9097
9378
|
return value.startsWith(ENC_PREFIX);
|
|
9098
9379
|
}
|
|
@@ -9124,7 +9405,7 @@ function fromEnv(raw) {
|
|
|
9124
9405
|
return null;
|
|
9125
9406
|
}
|
|
9126
9407
|
function secretKeyPath() {
|
|
9127
|
-
return
|
|
9408
|
+
return join3(resolveHome(), "secret.key");
|
|
9128
9409
|
}
|
|
9129
9410
|
function secretKey() {
|
|
9130
9411
|
if (cached) return cached;
|
|
@@ -9136,8 +9417,8 @@ function secretKey() {
|
|
|
9136
9417
|
return key2;
|
|
9137
9418
|
}
|
|
9138
9419
|
const path = secretKeyPath();
|
|
9139
|
-
if (
|
|
9140
|
-
const key2 = Buffer.from(
|
|
9420
|
+
if (existsSync3(path)) {
|
|
9421
|
+
const key2 = Buffer.from(readFileSync4(path, "utf8").trim(), "base64url");
|
|
9141
9422
|
if (key2.length !== KEY_BYTES) throw new Error(`kunci di ${path} rusak \u2014 panjangnya bukan 32 byte`);
|
|
9142
9423
|
cached = key2;
|
|
9143
9424
|
return key2;
|
|
@@ -10199,6 +10480,8 @@ var init_settings2 = __esm({
|
|
|
10199
10480
|
// SPEC-338 · ADR-0074 · model/effort codex
|
|
10200
10481
|
verifyScope: "changed",
|
|
10201
10482
|
// SPEC-376 · ADR-0080 · uji hanya yang berubah
|
|
10483
|
+
method: DEFAULT_METHOD,
|
|
10484
|
+
// SPEC-734 · ADR-0113 · metode workflow default
|
|
10202
10485
|
conflict: CONFLICT_DEFAULTS,
|
|
10203
10486
|
// SPEC-383 · ADR-0081 · default sesi konflik (opt-in, mati)
|
|
10204
10487
|
lead: LEAD_DEFAULTS,
|
|
@@ -10213,8 +10496,8 @@ var init_settings2 = __esm({
|
|
|
10213
10496
|
});
|
|
10214
10497
|
|
|
10215
10498
|
// src/services/codex-trust.ts
|
|
10216
|
-
import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as
|
|
10217
|
-
import { homedir as
|
|
10499
|
+
import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync6, realpathSync as realpathSync2 } from "node:fs";
|
|
10500
|
+
import { homedir as homedir3 } from "node:os";
|
|
10218
10501
|
function ensureCodexTrust(repoDir, home2) {
|
|
10219
10502
|
const path = codexConfigPath(home2);
|
|
10220
10503
|
let canonical = repoDir;
|
|
@@ -10226,7 +10509,7 @@ function ensureCodexTrust(repoDir, home2) {
|
|
|
10226
10509
|
try {
|
|
10227
10510
|
let existing = "";
|
|
10228
10511
|
try {
|
|
10229
|
-
existing =
|
|
10512
|
+
existing = readFileSync6(path, "utf8");
|
|
10230
10513
|
} catch {
|
|
10231
10514
|
}
|
|
10232
10515
|
if (existing.includes(header2)) return;
|
|
@@ -10243,7 +10526,7 @@ var codexHome, codexConfigPath;
|
|
|
10243
10526
|
var init_codex_trust = __esm({
|
|
10244
10527
|
"src/services/codex-trust.ts"() {
|
|
10245
10528
|
"use strict";
|
|
10246
|
-
codexHome = (home2) => home2 ?? process.env.CODEX_HOME ?? `${
|
|
10529
|
+
codexHome = (home2) => home2 ?? process.env.CODEX_HOME ?? `${homedir3()}/.codex`;
|
|
10247
10530
|
codexConfigPath = (home2) => `${codexHome(home2)}/config.toml`;
|
|
10248
10531
|
}
|
|
10249
10532
|
});
|
|
@@ -18861,7 +19144,7 @@ import websocket from "@fastify/websocket";
|
|
|
18861
19144
|
import cookie from "@fastify/cookie";
|
|
18862
19145
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
18863
19146
|
import { dirname as dirname13 } from "node:path";
|
|
18864
|
-
import { existsSync as
|
|
19147
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
18865
19148
|
|
|
18866
19149
|
// src/web-dir.ts
|
|
18867
19150
|
import { resolve } from "node:path";
|
|
@@ -18923,7 +19206,7 @@ init_stage_machine();
|
|
|
18923
19206
|
init_src();
|
|
18924
19207
|
import { execFile } from "node:child_process";
|
|
18925
19208
|
import { promisify } from "node:util";
|
|
18926
|
-
import { readFileSync as
|
|
19209
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync4, rmSync as rmSync2, mkdirSync as mkdirSync5 } from "node:fs";
|
|
18927
19210
|
import { resolve as resolve7, dirname as dirname6, sep } from "node:path";
|
|
18928
19211
|
var exec = promisify(execFile);
|
|
18929
19212
|
async function listRepoDocs(repoDir) {
|
|
@@ -18940,7 +19223,7 @@ async function listRepoDocs(repoDir) {
|
|
|
18940
19223
|
}
|
|
18941
19224
|
function docsDirOf(repoDir) {
|
|
18942
19225
|
try {
|
|
18943
|
-
const raw =
|
|
19226
|
+
const raw = readFileSync5(resolve7(repoDir, "hanoman.config.json"), "utf8");
|
|
18944
19227
|
return zHanomanConfig.parse(JSON.parse(raw)).docsDir;
|
|
18945
19228
|
} catch {
|
|
18946
19229
|
return zHanomanConfig.parse({}).docsDir;
|
|
@@ -18948,18 +19231,18 @@ function docsDirOf(repoDir) {
|
|
|
18948
19231
|
}
|
|
18949
19232
|
function resolveIndex(repoDir, docsDir) {
|
|
18950
19233
|
const rel = `${docsDir}/README.md`;
|
|
18951
|
-
return
|
|
19234
|
+
return existsSync4(resolve7(repoDir, rel)) ? rel : "";
|
|
18952
19235
|
}
|
|
18953
19236
|
var catOf = (rel) => rel.includes("/") ? rel.slice(0, rel.lastIndexOf("/")) : ".";
|
|
18954
19237
|
var nameOf = (rel) => rel.includes("/") ? rel.slice(rel.lastIndexOf("/") + 1) : rel;
|
|
18955
19238
|
async function scanRepoDocs(repoDir) {
|
|
18956
|
-
if (!repoDir || !
|
|
19239
|
+
if (!repoDir || !existsSync4(repoDir)) return { coverage: 0, tree: [] };
|
|
18957
19240
|
const files = await listRepoDocs(repoDir);
|
|
18958
19241
|
const docsDir = docsDirOf(repoDir);
|
|
18959
19242
|
const index = resolveIndex(repoDir, docsDir);
|
|
18960
19243
|
const read = (rel) => {
|
|
18961
19244
|
try {
|
|
18962
|
-
return
|
|
19245
|
+
return readFileSync5(resolve7(repoDir, rel), "utf8");
|
|
18963
19246
|
} catch {
|
|
18964
19247
|
return null;
|
|
18965
19248
|
}
|
|
@@ -18988,7 +19271,7 @@ function docAbsPath(repoDir, rel) {
|
|
|
18988
19271
|
}
|
|
18989
19272
|
function readDocFile(repoDir, rel) {
|
|
18990
19273
|
try {
|
|
18991
|
-
return
|
|
19274
|
+
return readFileSync5(docAbsPath(repoDir, rel), "utf8");
|
|
18992
19275
|
} catch {
|
|
18993
19276
|
return null;
|
|
18994
19277
|
}
|
|
@@ -19000,7 +19283,7 @@ function writeDocFile(repoDir, rel, content) {
|
|
|
19000
19283
|
}
|
|
19001
19284
|
function deleteDocFile(repoDir, rel) {
|
|
19002
19285
|
const abs = docAbsPath(repoDir, rel);
|
|
19003
|
-
if (!
|
|
19286
|
+
if (!existsSync4(abs)) return false;
|
|
19004
19287
|
rmSync2(abs);
|
|
19005
19288
|
return true;
|
|
19006
19289
|
}
|
|
@@ -19287,13 +19570,13 @@ async function projects_default(app2) {
|
|
|
19287
19570
|
// src/routes/specs.ts
|
|
19288
19571
|
init_src();
|
|
19289
19572
|
init_src2();
|
|
19290
|
-
import { existsSync as
|
|
19573
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
19291
19574
|
|
|
19292
19575
|
// src/services/integrate.ts
|
|
19293
19576
|
import { execFile as execFile3 } from "node:child_process";
|
|
19294
19577
|
import { promisify as promisify3 } from "node:util";
|
|
19295
19578
|
import { rmSync as rmSync3 } from "node:fs";
|
|
19296
|
-
import { join as
|
|
19579
|
+
import { join as join4 } from "node:path";
|
|
19297
19580
|
var sanitize = (id) => id.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
19298
19581
|
var sourceBranch = (specId) => `hanoman/${sanitize(specId)}`;
|
|
19299
19582
|
var exec3 = promisify3(execFile3);
|
|
@@ -19337,7 +19620,7 @@ async function integrateBranch(repoDir, src, op, target2) {
|
|
|
19337
19620
|
const tgt = await resolveTarget(repoDir, target2);
|
|
19338
19621
|
if (!tgt) return { status: "error", code: 400, error: `target "${target2}" tidak dikenal` };
|
|
19339
19622
|
await sh(repoDir, ["fetch", "origin"]);
|
|
19340
|
-
const wt =
|
|
19623
|
+
const wt = join4(repoDir, ".worktrees", `merge-${sanitize(src.mergeId)}`);
|
|
19341
19624
|
await reclaim(repoDir, wt);
|
|
19342
19625
|
const baseRef = op === "merge" ? tgt.ref : source;
|
|
19343
19626
|
const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `${baseRef}^{commit}`]);
|
|
@@ -19398,7 +19681,7 @@ async function mergeIntoCurrent(repoDir, source, opts = {}) {
|
|
|
19398
19681
|
const src = await resolveGraphSource(repoDir, source);
|
|
19399
19682
|
if (!src) return { status: "error", code: 400, error: `source "${source}" tak dikenal` };
|
|
19400
19683
|
await sh(repoDir, ["fetch", "origin"]);
|
|
19401
|
-
const wt =
|
|
19684
|
+
const wt = join4(repoDir, ".worktrees", `merge-${sanitize(current)}`);
|
|
19402
19685
|
await reclaim(repoDir, wt);
|
|
19403
19686
|
const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `refs/heads/${current}^{commit}`]);
|
|
19404
19687
|
if (!await ok(repoDir, ["worktree", "add", "--detach", "-q", wt, baseSha]))
|
|
@@ -19436,7 +19719,7 @@ async function replayCurrent(repoDir, source, cmd) {
|
|
|
19436
19719
|
const current = await out(repoDir, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
19437
19720
|
if (!current || current === "HEAD")
|
|
19438
19721
|
return { status: "error", code: 409, error: "HEAD detached \u2014 checkout sebuah branch dulu" };
|
|
19439
|
-
const wt =
|
|
19722
|
+
const wt = join4(repoDir, ".worktrees", `merge-${sanitize(current)}`);
|
|
19440
19723
|
await reclaim(repoDir, wt);
|
|
19441
19724
|
const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `refs/heads/${current}^{commit}`]);
|
|
19442
19725
|
if (!await ok(repoDir, ["worktree", "add", "--detach", "-q", wt, baseSha]))
|
|
@@ -19485,15 +19768,15 @@ import { execFile as execFile4 } from "node:child_process";
|
|
|
19485
19768
|
import { promisify as promisify4 } from "node:util";
|
|
19486
19769
|
import { readFile as readFile2, mkdtemp, copyFile, rm } from "node:fs/promises";
|
|
19487
19770
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
19488
|
-
import { join as
|
|
19771
|
+
import { join as join5, resolve as resolve8 } from "node:path";
|
|
19489
19772
|
var exec4 = promisify4(execFile4);
|
|
19490
19773
|
var GIT3 = { maxBuffer: 1 << 24 };
|
|
19491
19774
|
var MAX = 256 * 1024;
|
|
19492
|
-
var worktreeDir = (repoDir, specId) =>
|
|
19775
|
+
var worktreeDir = (repoDir, specId) => join5(repoDir, ".worktrees", specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
|
|
19493
19776
|
async function withTempIndex(wt, fn) {
|
|
19494
19777
|
const idx = (await exec4("git", ["rev-parse", "--git-path", "index"], { cwd: wt, ...GIT3 })).stdout.trim();
|
|
19495
|
-
const dir2 = await mkdtemp(
|
|
19496
|
-
const tmp =
|
|
19778
|
+
const dir2 = await mkdtemp(join5(tmpdir2(), "hanoman-idx-"));
|
|
19779
|
+
const tmp = join5(dir2, "index");
|
|
19497
19780
|
await copyFile(resolve8(wt, idx), tmp);
|
|
19498
19781
|
const env = { ...process.env, GIT_INDEX_FILE: tmp };
|
|
19499
19782
|
try {
|
|
@@ -19618,7 +19901,7 @@ async function reviewFile(repoDir, specId, baseSha, branchFrom, path) {
|
|
|
19618
19901
|
let contentRaw = null;
|
|
19619
19902
|
if (status !== "D") {
|
|
19620
19903
|
try {
|
|
19621
|
-
contentRaw = await readFile2(
|
|
19904
|
+
contentRaw = await readFile2(join5(wt, path), "utf8");
|
|
19622
19905
|
} catch {
|
|
19623
19906
|
contentRaw = null;
|
|
19624
19907
|
}
|
|
@@ -19803,14 +20086,15 @@ async function notificationsFeed(p = {}) {
|
|
|
19803
20086
|
init_stage_machine();
|
|
19804
20087
|
|
|
19805
20088
|
// src/services/stage-artifacts.ts
|
|
20089
|
+
init_src();
|
|
19806
20090
|
init_stage_machine();
|
|
19807
20091
|
var ARTIFACT_DIR = {
|
|
19808
|
-
"spec-ready":
|
|
19809
|
-
planned:
|
|
20092
|
+
"spec-ready": SPEC_DIRS.map((d) => `${d}/`),
|
|
20093
|
+
planned: PLAN_DIRS.map((d) => `${d}/`)
|
|
19810
20094
|
};
|
|
19811
20095
|
async function artifactsToRemove(projectId, specId, target2, current) {
|
|
19812
20096
|
const ti = STAGES.indexOf(target2), ci = STAGES.indexOf(current);
|
|
19813
|
-
const dirs = STAGES.filter((_, i) => i > ti && i <= ci).
|
|
20097
|
+
const dirs = STAGES.filter((_, i) => i > ti && i <= ci).flatMap((s2) => ARTIFACT_DIR[s2] ?? []);
|
|
19814
20098
|
if (!dirs.length) return [];
|
|
19815
20099
|
const repoDir = await resolveRepoDir(projectId);
|
|
19816
20100
|
if (!repoDir) return [];
|
|
@@ -19842,6 +20126,7 @@ async function deleteDoc(projectId, path) {
|
|
|
19842
20126
|
}
|
|
19843
20127
|
|
|
19844
20128
|
// src/services/spec-docs.ts
|
|
20129
|
+
init_src();
|
|
19845
20130
|
init_db();
|
|
19846
20131
|
init_pty();
|
|
19847
20132
|
var ORDER = ["audit", "spec", "plan", "objective", "brainstorm", "other"];
|
|
@@ -19850,8 +20135,8 @@ function kindOf(path) {
|
|
|
19850
20135
|
if (p.endsWith("-audit.md") || p.includes("/research/audit-")) return "audit";
|
|
19851
20136
|
if (p.endsWith("-objective.md")) return "objective";
|
|
19852
20137
|
if (p.endsWith("-brainstorm.md")) return "brainstorm";
|
|
19853
|
-
if (p.endsWith("-design.md") || p.endsWith("-spec.md") || p.startsWith(
|
|
19854
|
-
if (p.startsWith(
|
|
20138
|
+
if (p.endsWith("-design.md") || p.endsWith("-spec.md") || SPEC_DIRS.some((d) => p.startsWith(`${d}/`))) return "spec";
|
|
20139
|
+
if (PLAN_DIRS.some((d) => p.startsWith(`${d}/`)) || p.endsWith("-plan.md")) return "plan";
|
|
19855
20140
|
return "other";
|
|
19856
20141
|
}
|
|
19857
20142
|
async function resolveDir(specId, sessions = listSessions()) {
|
|
@@ -22763,7 +23048,7 @@ ${item.context}` : item.context;
|
|
|
22763
23048
|
});
|
|
22764
23049
|
const specWithProject = (id) => prisma.spec.findUnique({ where: { id }, include: { project: true } });
|
|
22765
23050
|
const resolveReview = async (repoDir, spec) => {
|
|
22766
|
-
if (
|
|
23051
|
+
if (existsSync5(worktreeDir(repoDir, spec.id))) return { wt: true };
|
|
22767
23052
|
if (spec.baseSha && spec.headSha && await shaResolvable(repoDir, spec.baseSha) && await shaResolvable(repoDir, spec.headSha))
|
|
22768
23053
|
return { wt: false, base: spec.baseSha, head: spec.headSha };
|
|
22769
23054
|
const r = await specCommitRange(repoDir, spec.id);
|
|
@@ -23004,7 +23289,7 @@ import { spawn as spawn2 } from "node:child_process";
|
|
|
23004
23289
|
// src/services/git-remotes.ts
|
|
23005
23290
|
import { execFile as execFile5 } from "node:child_process";
|
|
23006
23291
|
import { promisify as promisify5 } from "node:util";
|
|
23007
|
-
import { existsSync as
|
|
23292
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
23008
23293
|
var exec5 = promisify5(execFile5);
|
|
23009
23294
|
var GIT4 = { maxBuffer: 1 << 24, encoding: "utf8" };
|
|
23010
23295
|
function parseRemote(url2) {
|
|
@@ -23023,7 +23308,7 @@ function prUrl(remoteUrl, branch, base2 = "main") {
|
|
|
23023
23308
|
return null;
|
|
23024
23309
|
}
|
|
23025
23310
|
async function listRemotes(repoDir) {
|
|
23026
|
-
if (!repoDir || !
|
|
23311
|
+
if (!repoDir || !existsSync6(repoDir)) return [];
|
|
23027
23312
|
try {
|
|
23028
23313
|
const { stdout } = await exec5("git", ["remote", "-v"], { cwd: repoDir, ...GIT4 });
|
|
23029
23314
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -23065,7 +23350,7 @@ import { promisify as promisify7 } from "node:util";
|
|
|
23065
23350
|
import { execFile as execFile6 } from "node:child_process";
|
|
23066
23351
|
import { promisify as promisify6 } from "node:util";
|
|
23067
23352
|
import { readFile as readFile3, writeFile, mkdir } from "node:fs/promises";
|
|
23068
|
-
import { existsSync as
|
|
23353
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
23069
23354
|
import { resolve as resolve9, sep as sep2, dirname as dirname8 } from "node:path";
|
|
23070
23355
|
var exec6 = promisify6(execFile6);
|
|
23071
23356
|
var GIT5 = { maxBuffer: 1 << 24 };
|
|
@@ -23078,7 +23363,7 @@ function repoAbsPath(repoDir, rel) {
|
|
|
23078
23363
|
return abs;
|
|
23079
23364
|
}
|
|
23080
23365
|
async function listRepoTree(repoDir, ref = "") {
|
|
23081
|
-
if (!repoDir || !
|
|
23366
|
+
if (!repoDir || !existsSync7(repoDir)) return [];
|
|
23082
23367
|
try {
|
|
23083
23368
|
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
23369
|
return [...new Set(splitZ2(stdout))].sort();
|
|
@@ -23104,7 +23389,7 @@ async function currentBranch(repoDir) {
|
|
|
23104
23389
|
}
|
|
23105
23390
|
async function repoStatus(repoDir) {
|
|
23106
23391
|
const empty = { branch: "", ahead: 0, behind: 0, staged: [], unstaged: [], untracked: [], clean: true };
|
|
23107
|
-
if (!repoDir || !
|
|
23392
|
+
if (!repoDir || !existsSync7(repoDir)) return empty;
|
|
23108
23393
|
try {
|
|
23109
23394
|
const { stdout } = await exec6("git", ["status", "--porcelain=v1", "-z", "-b"], { cwd: repoDir, ...GIT5 });
|
|
23110
23395
|
const parts = stdout.split("\0");
|
|
@@ -23137,7 +23422,7 @@ async function repoStatus(repoDir) {
|
|
|
23137
23422
|
}
|
|
23138
23423
|
}
|
|
23139
23424
|
async function listStashes(repoDir) {
|
|
23140
|
-
if (!repoDir || !
|
|
23425
|
+
if (!repoDir || !existsSync7(repoDir)) return [];
|
|
23141
23426
|
try {
|
|
23142
23427
|
const { stdout } = await exec6("git", ["stash", "list", `--format=%gd${US}%s${US}%aI`], { cwd: repoDir, ...GIT5 });
|
|
23143
23428
|
return stdout.split("\n").filter(Boolean).map((line) => {
|
|
@@ -23149,7 +23434,7 @@ async function listStashes(repoDir) {
|
|
|
23149
23434
|
}
|
|
23150
23435
|
}
|
|
23151
23436
|
async function listGraph(repoDir, limit = 200, opts = {}) {
|
|
23152
|
-
if (!repoDir || !
|
|
23437
|
+
if (!repoDir || !existsSync7(repoDir)) return { commits: [], current: "", total: 0 };
|
|
23153
23438
|
try {
|
|
23154
23439
|
const fmt = ["%H", "%P", "%an", "%aI", "%s", "%D"].join(US);
|
|
23155
23440
|
const refArgs = opts.branches?.length ? ["--end-of-options", ...opts.branches] : [
|
|
@@ -23219,7 +23504,7 @@ async function changedOf(repoDir, sha) {
|
|
|
23219
23504
|
}
|
|
23220
23505
|
async function compareCommits(repoDir, from, to) {
|
|
23221
23506
|
const empty = { from, to, changed: [] };
|
|
23222
|
-
if (!repoDir || !
|
|
23507
|
+
if (!repoDir || !existsSync7(repoDir)) return empty;
|
|
23223
23508
|
try {
|
|
23224
23509
|
const [num, name2] = await Promise.all([
|
|
23225
23510
|
exec6("git", ["diff", "--numstat", "-z", "--no-renames", "--end-of-options", from, to], { cwd: repoDir, ...GIT5 }),
|
|
@@ -23231,7 +23516,7 @@ async function compareCommits(repoDir, from, to) {
|
|
|
23231
23516
|
}
|
|
23232
23517
|
}
|
|
23233
23518
|
async function searchCommits(repoDir, q, by = "all") {
|
|
23234
|
-
if (!repoDir || !
|
|
23519
|
+
if (!repoDir || !existsSync7(repoDir) || !q) return [];
|
|
23235
23520
|
const run2 = async (extra) => {
|
|
23236
23521
|
try {
|
|
23237
23522
|
return (await exec6("git", ["log", "--all", "--format=%H", ...extra], { cwd: repoDir, ...GIT5 })).stdout.split("\n").filter(Boolean);
|
|
@@ -23480,7 +23765,7 @@ async function runGitOp(repoDir, op) {
|
|
|
23480
23765
|
}
|
|
23481
23766
|
}
|
|
23482
23767
|
async function workingStatus(repoDir) {
|
|
23483
|
-
if (!repoDir || !
|
|
23768
|
+
if (!repoDir || !existsSync7(repoDir)) return { branch: "", staged: [], unstaged: [] };
|
|
23484
23769
|
const [branch, staged, unstaged] = await Promise.all([
|
|
23485
23770
|
currentBranch(repoDir),
|
|
23486
23771
|
changedFiles(repoDir, ["--cached"]),
|
|
@@ -23489,7 +23774,7 @@ async function workingStatus(repoDir) {
|
|
|
23489
23774
|
return { branch, staged, unstaged };
|
|
23490
23775
|
}
|
|
23491
23776
|
async function workingFileDiff(repoDir, path, staged) {
|
|
23492
|
-
if (!repoDir || !
|
|
23777
|
+
if (!repoDir || !existsSync7(repoDir)) return null;
|
|
23493
23778
|
repoAbsPath(repoDir, path);
|
|
23494
23779
|
const changed = staged ? await changedFiles(repoDir, ["--cached"]) : await withTempIndex(repoDir, (env) => changedFiles(repoDir, [], env));
|
|
23495
23780
|
const cf = changed.find((c) => c.path === path);
|
|
@@ -23966,15 +24251,15 @@ async function finishGraphOp(reply, id, repoDir, r, verb) {
|
|
|
23966
24251
|
|
|
23967
24252
|
// src/routes/fs.ts
|
|
23968
24253
|
import { readdir } from "node:fs/promises";
|
|
23969
|
-
import { homedir as
|
|
23970
|
-
import { resolve as resolve10, dirname as dirname9, join as
|
|
24254
|
+
import { homedir as homedir4 } from "node:os";
|
|
24255
|
+
import { resolve as resolve10, dirname as dirname9, join as join6 } from "node:path";
|
|
23971
24256
|
async function fs_default(app2) {
|
|
23972
24257
|
app2.get("/fs/browse", async (req, reply) => {
|
|
23973
24258
|
const q = req.query.path;
|
|
23974
|
-
const dir2 = q && q.trim() ? resolve10(q.trim()) :
|
|
24259
|
+
const dir2 = q && q.trim() ? resolve10(q.trim()) : homedir4();
|
|
23975
24260
|
try {
|
|
23976
24261
|
const ents = await readdir(dir2, { withFileTypes: true });
|
|
23977
|
-
const entries = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path:
|
|
24262
|
+
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));
|
|
23978
24263
|
const parent = dirname9(dir2);
|
|
23979
24264
|
return { path: dir2, parent: parent === dir2 ? null : parent, entries };
|
|
23980
24265
|
} catch {
|
|
@@ -23988,13 +24273,14 @@ init_db();
|
|
|
23988
24273
|
init_src();
|
|
23989
24274
|
init_src2();
|
|
23990
24275
|
init_session_phases();
|
|
23991
|
-
import { existsSync as
|
|
24276
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
23992
24277
|
init_settings2();
|
|
23993
24278
|
init_codex_trust();
|
|
23994
24279
|
|
|
23995
24280
|
// src/services/session-launch.ts
|
|
23996
24281
|
init_db();
|
|
23997
24282
|
init_src2();
|
|
24283
|
+
init_src();
|
|
23998
24284
|
init_settings2();
|
|
23999
24285
|
init_codex_trust();
|
|
24000
24286
|
init_pty();
|
|
@@ -24051,6 +24337,8 @@ async function startSpecSession(spec, opts) {
|
|
|
24051
24337
|
};
|
|
24052
24338
|
const goal = isGoalFlow || (opts.goal ?? setting.goal.enabled) ? resolveGoalCondition(goalArgs, opts.goalCondition, isGoalFlow ? null : setting.goal.condition) : void 0;
|
|
24053
24339
|
const verifyScope = opts.verifyScope ?? setting.verifyScope;
|
|
24340
|
+
const recordedMethod = readSpecMethod(spec.payload);
|
|
24341
|
+
const method = resolveMethod(opts.method ?? recordedMethod ?? setting.method);
|
|
24054
24342
|
if (agent === "codex") ensureCodexTrust(repoDir);
|
|
24055
24343
|
let baseSha;
|
|
24056
24344
|
if (resume?.worktreeKept) {
|
|
@@ -24067,6 +24355,10 @@ async function startSpecSession(spec, opts) {
|
|
|
24067
24355
|
data: { baseSha, headSha: null, startedAt: /* @__PURE__ */ new Date() }
|
|
24068
24356
|
});
|
|
24069
24357
|
}
|
|
24358
|
+
if (!recordedMethod) {
|
|
24359
|
+
const stamped = stampSpecMethod(spec.payload, method.id);
|
|
24360
|
+
if (stamped) await prisma.spec.update({ where: { id: spec.id }, data: { payload: stamped } });
|
|
24361
|
+
}
|
|
24070
24362
|
const brief = {
|
|
24071
24363
|
id: spec.id,
|
|
24072
24364
|
title: spec.title,
|
|
@@ -24081,14 +24373,15 @@ async function startSpecSession(spec, opts) {
|
|
|
24081
24373
|
prompt = startGoalPrompt(brief, branchTo, {
|
|
24082
24374
|
autonomy: opts.autonomy,
|
|
24083
24375
|
verifyScope,
|
|
24084
|
-
resume: resumeCtx
|
|
24376
|
+
resume: resumeCtx,
|
|
24377
|
+
method: method.id
|
|
24085
24378
|
});
|
|
24086
24379
|
} else if (isContinue) {
|
|
24087
|
-
prompt = continuePrompt(opts.flow, brief, branchTo, opts.autonomy, verifyScope);
|
|
24380
|
+
prompt = continuePrompt(opts.flow, brief, branchTo, opts.autonomy, verifyScope, method.id);
|
|
24088
24381
|
} else if (resumeCtx) {
|
|
24089
|
-
prompt = resumePrompt(opts.flow, brief, branchTo, resumeCtx, opts.autonomy, verifyScope);
|
|
24382
|
+
prompt = resumePrompt(opts.flow, brief, branchTo, resumeCtx, opts.autonomy, verifyScope, method.id);
|
|
24090
24383
|
} else {
|
|
24091
|
-
prompt = startPrompt(opts.flow, brief, branchTo, opts.autonomy, verifyScope);
|
|
24384
|
+
prompt = startPrompt(opts.flow, brief, branchTo, opts.autonomy, verifyScope, method.id);
|
|
24092
24385
|
}
|
|
24093
24386
|
const scopeEnv = { HANOMAN_BASE_SHA: baseSha, HANOMAN_VERIFY_SCOPE: verifyScope };
|
|
24094
24387
|
const s2 = createSession(spec.projectId, worktree, {
|
|
@@ -24106,6 +24399,24 @@ async function startSpecSession(spec, opts) {
|
|
|
24106
24399
|
return resume ? { id: s2.id, resumed: true } : { id: s2.id };
|
|
24107
24400
|
}
|
|
24108
24401
|
|
|
24402
|
+
// src/services/method-status.ts
|
|
24403
|
+
init_src();
|
|
24404
|
+
init_src2();
|
|
24405
|
+
init_pty();
|
|
24406
|
+
function methodStatusReport(env = process.env) {
|
|
24407
|
+
const scans = zAgent.options.map((a) => scanAgentSkills(a, env));
|
|
24408
|
+
return {
|
|
24409
|
+
agents: scans.map((s2) => ({ agent: s2.agent, home: s2.home, roots: s2.roots, skills: s2.skills.length })),
|
|
24410
|
+
methods: scans.flatMap((s2) => {
|
|
24411
|
+
const installed = { skills: s2.skills.map((k) => k.id), packages: s2.packages };
|
|
24412
|
+
return METHOD_IDS.map((id) => methodStatus(METHODS[id], s2.agent, installed));
|
|
24413
|
+
})
|
|
24414
|
+
};
|
|
24415
|
+
}
|
|
24416
|
+
function installCommand(m, agent, shell = shellBin()) {
|
|
24417
|
+
return [shell, "-lc", `${m.install[agent].join(" && ")}; exec '${shell}' -l`];
|
|
24418
|
+
}
|
|
24419
|
+
|
|
24109
24420
|
// src/services/session-worktree.ts
|
|
24110
24421
|
import { resolve as resolve11, sep as sep3 } from "node:path";
|
|
24111
24422
|
function ownsWorktree(repoDir, cwd) {
|
|
@@ -24196,6 +24507,8 @@ async function terminal_default(app2) {
|
|
|
24196
24507
|
// SPEC-338 · ADR-0074
|
|
24197
24508
|
verifyScope: parsed.data.verifyScope,
|
|
24198
24509
|
// SPEC-376 · ADR-0080
|
|
24510
|
+
method: parsed.data.method,
|
|
24511
|
+
// SPEC-734 · ADR-0113
|
|
24199
24512
|
force: parsed.data.force
|
|
24200
24513
|
// SPEC-447 · ADR-0093
|
|
24201
24514
|
});
|
|
@@ -24214,6 +24527,13 @@ async function terminal_default(app2) {
|
|
|
24214
24527
|
if ("shell" in parsed.data) {
|
|
24215
24528
|
const repoDir2 = await resolveRepoDir(project.id);
|
|
24216
24529
|
if (!repoDir2) return reply.code(400).send({ error: `project "${project.id}" belum di-bind ke checkout lokal`, needsBind: true });
|
|
24530
|
+
const inst = parsed.data.install;
|
|
24531
|
+
if (inst) {
|
|
24532
|
+
const m = METHODS[inst.method];
|
|
24533
|
+
if (!m) return reply.code(400).send({ error: `metode "${inst.method}" tak dikenal` });
|
|
24534
|
+
const s4 = createSession(project.id, repoDir2, { command: installCommand(m, inst.agent) });
|
|
24535
|
+
return reply.code(201).send({ id: s4.id });
|
|
24536
|
+
}
|
|
24217
24537
|
const s3 = createSession(project.id, repoDir2, { command: [shellBin()] });
|
|
24218
24538
|
return reply.code(201).send({ id: s3.id });
|
|
24219
24539
|
}
|
|
@@ -24382,7 +24702,7 @@ async function terminal_default(app2) {
|
|
|
24382
24702
|
if (!s2) return { ok: false, code: 404, msg: "not found" };
|
|
24383
24703
|
const repoDir = await resolveRepoDir(s2.projectId);
|
|
24384
24704
|
if (!repoDir) return { ok: false, code: 409, msg: "project belum punya repoDir" };
|
|
24385
|
-
if (!s2.cwd.includes("/.worktrees/") || !
|
|
24705
|
+
if (!s2.cwd.includes("/.worktrees/") || !existsSync8(s2.cwd))
|
|
24386
24706
|
return { ok: false, code: 409, msg: "belum ada worktree untuk di-review \u2014 jalankan/lanjutkan sesi dulu" };
|
|
24387
24707
|
return { ok: true, id: s2.id, repoDir };
|
|
24388
24708
|
};
|
|
@@ -24474,8 +24794,8 @@ async function terminal_default(app2) {
|
|
|
24474
24794
|
// src/routes/vps.ts
|
|
24475
24795
|
init_db();
|
|
24476
24796
|
init_src();
|
|
24477
|
-
import { readFileSync as
|
|
24478
|
-
import { homedir as
|
|
24797
|
+
import { readFileSync as readFileSync10, existsSync as existsSync11 } from "node:fs";
|
|
24798
|
+
import { homedir as homedir6 } from "node:os";
|
|
24479
24799
|
|
|
24480
24800
|
// src/vps/catalog/catalog.data.ts
|
|
24481
24801
|
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 +24877,14 @@ var _map = new Map(CATALOG.map((c) => [c.id, c]));
|
|
|
24557
24877
|
var byId = (id) => _map.get(id);
|
|
24558
24878
|
|
|
24559
24879
|
// src/services/vps-remediate.ts
|
|
24560
|
-
import { readFileSync as
|
|
24880
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
24561
24881
|
|
|
24562
24882
|
// src/services/vps-ssh.ts
|
|
24563
24883
|
init_config2();
|
|
24564
24884
|
import { spawn as spawn3 } from "node:child_process";
|
|
24565
24885
|
import { mkdtempSync, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
24566
24886
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
24567
|
-
import { dirname as dirname10, join as
|
|
24887
|
+
import { dirname as dirname10, join as join7 } from "node:path";
|
|
24568
24888
|
var sshBin = () => effectiveStr("HANOMAN_SSH_BIN") ?? "ssh";
|
|
24569
24889
|
function consoleArgv(t) {
|
|
24570
24890
|
return [
|
|
@@ -24579,8 +24899,8 @@ function consoleArgv(t) {
|
|
|
24579
24899
|
];
|
|
24580
24900
|
}
|
|
24581
24901
|
function askpassScript() {
|
|
24582
|
-
const dir2 = mkdtempSync(
|
|
24583
|
-
const path =
|
|
24902
|
+
const dir2 = mkdtempSync(join7(tmpdir3(), "hanoman-askpass-"));
|
|
24903
|
+
const path = join7(dir2, "askpass.sh");
|
|
24584
24904
|
writeFileSync4(path, `#!/bin/sh
|
|
24585
24905
|
printf '%s' "$HANOMAN_SSH_PASSWORD"
|
|
24586
24906
|
`, { mode: 448 });
|
|
@@ -24640,16 +24960,16 @@ function sshExec(t, remoteCmd, opts = {}) {
|
|
|
24640
24960
|
|
|
24641
24961
|
// src/services/vps-audit.ts
|
|
24642
24962
|
init_db();
|
|
24643
|
-
import { readFileSync as
|
|
24644
|
-
import { join as
|
|
24963
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
24964
|
+
import { join as join9 } from "node:path";
|
|
24645
24965
|
|
|
24646
24966
|
// src/runner/deps.ts
|
|
24647
|
-
import { existsSync as
|
|
24648
|
-
import { dirname as dirname11, join as
|
|
24967
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
24968
|
+
import { dirname as dirname11, join as join8 } from "node:path";
|
|
24649
24969
|
function repoRootFrom(startDir) {
|
|
24650
24970
|
let dir2 = startDir;
|
|
24651
24971
|
for (let i = 0; i < 8; i++) {
|
|
24652
|
-
if (
|
|
24972
|
+
if (existsSync9(join8(dir2, "pnpm-workspace.yaml"))) return dir2;
|
|
24653
24973
|
const parent = dirname11(dir2);
|
|
24654
24974
|
if (parent === dir2) break;
|
|
24655
24975
|
dir2 = parent;
|
|
@@ -24754,7 +25074,7 @@ function parseHealth(out3) {
|
|
|
24754
25074
|
return { uptime: h.uptime ?? "", disk: h.disk ?? "", mem: h.mem ?? "", load: h.load ?? "" };
|
|
24755
25075
|
}
|
|
24756
25076
|
var target = (v) => ({ host: v.host, port: v.port, user: v.user, keyPath: v.keyPath });
|
|
24757
|
-
var scriptPath = (f) =>
|
|
25077
|
+
var scriptPath = (f) => join9(repoRoot(), "server", "scripts", "vps", f);
|
|
24758
25078
|
async function itemStatesOf(vpsId) {
|
|
24759
25079
|
const rows = await prisma.vpsItemState.findMany({ where: { vpsId } });
|
|
24760
25080
|
return Object.fromEntries(rows.map((s2) => [s2.itemId, { na: s2.na, attested: s2.attested }]));
|
|
@@ -24763,7 +25083,7 @@ async function runAudit(v) {
|
|
|
24763
25083
|
const r = await sshExec(
|
|
24764
25084
|
target(v),
|
|
24765
25085
|
"sudo -n bash -s",
|
|
24766
|
-
{ stdin:
|
|
25086
|
+
{ stdin: readFileSync7(scriptPath("audit.sh"), "utf8"), timeoutMs: 6e4 }
|
|
24767
25087
|
);
|
|
24768
25088
|
const audit = parseAudit(r.out);
|
|
24769
25089
|
if (r.code !== 0 || audit.length === 0) return { ok: false, out: r.out };
|
|
@@ -24822,7 +25142,7 @@ async function remediate(v, items, dryRun) {
|
|
|
24822
25142
|
const r = await sshExec(
|
|
24823
25143
|
v,
|
|
24824
25144
|
`sudo -n env ITEMS=${items.join(",")} SSH_PORT=${v.port} DRY_RUN=${dryRun ? "1" : ""} bash -s`,
|
|
24825
|
-
{ stdin:
|
|
25145
|
+
{ stdin: readFileSync8(scriptPath("remediate.sh"), "utf8"), timeoutMs: 3e5 }
|
|
24826
25146
|
);
|
|
24827
25147
|
return { ok: r.code === 0, steps: parseSteps(r.out), out: r.out };
|
|
24828
25148
|
}
|
|
@@ -24901,15 +25221,15 @@ async function buildChecklist(vpsId) {
|
|
|
24901
25221
|
// src/services/vps-key.ts
|
|
24902
25222
|
init_config2();
|
|
24903
25223
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
24904
|
-
import { existsSync as
|
|
24905
|
-
import { homedir as
|
|
24906
|
-
import { join as
|
|
24907
|
-
var keyDir = () => effectiveStr("HANOMAN_SSH_KEY_DIR") ??
|
|
25224
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync9 } from "node:fs";
|
|
25225
|
+
import { homedir as homedir5 } from "node:os";
|
|
25226
|
+
import { join as join10 } from "node:path";
|
|
25227
|
+
var keyDir = () => effectiveStr("HANOMAN_SSH_KEY_DIR") ?? join10(homedir5(), ".hanoman");
|
|
24908
25228
|
function ensureHanomanKey() {
|
|
24909
25229
|
const dir2 = keyDir();
|
|
24910
|
-
const privPath =
|
|
25230
|
+
const privPath = join10(dir2, "id_ed25519");
|
|
24911
25231
|
const pubPath = `${privPath}.pub`;
|
|
24912
|
-
if (!
|
|
25232
|
+
if (!existsSync10(privPath)) {
|
|
24913
25233
|
mkdirSync8(dir2, { recursive: true, mode: 448 });
|
|
24914
25234
|
execFileSync2(
|
|
24915
25235
|
"ssh-keygen",
|
|
@@ -24917,7 +25237,7 @@ function ensureHanomanKey() {
|
|
|
24917
25237
|
{ stdio: ["ignore", "ignore", "pipe"] }
|
|
24918
25238
|
);
|
|
24919
25239
|
}
|
|
24920
|
-
return { privPath, pubPath, pub:
|
|
25240
|
+
return { privPath, pubPath, pub: readFileSync9(pubPath, "utf8").trim() };
|
|
24921
25241
|
}
|
|
24922
25242
|
|
|
24923
25243
|
// src/services/vps-bootstrap.ts
|
|
@@ -24936,7 +25256,7 @@ async function bootstrapKey(t, password) {
|
|
|
24936
25256
|
init_pty();
|
|
24937
25257
|
init_settings2();
|
|
24938
25258
|
function keyMissing(v) {
|
|
24939
|
-
return !!v.keyPath && !
|
|
25259
|
+
return !!v.keyPath && !existsSync11(v.keyPath);
|
|
24940
25260
|
}
|
|
24941
25261
|
async function vps_default(app2) {
|
|
24942
25262
|
app2.get("/vps", async () => prisma.vps.findMany({ orderBy: { createdAt: "asc" } }));
|
|
@@ -25094,7 +25414,7 @@ async function vps_default(app2) {
|
|
|
25094
25414
|
const r = await sshExec(
|
|
25095
25415
|
v,
|
|
25096
25416
|
`sudo -n env SSH_PORT=${v.port} SSH_USER=${v.user} bash -s`,
|
|
25097
|
-
{ stdin:
|
|
25417
|
+
{ stdin: readFileSync10(scriptPath("harden.sh"), "utf8"), timeoutMs: 3e5 }
|
|
25098
25418
|
);
|
|
25099
25419
|
if (r.code !== 0) return reply.code(502).send({ error: "harden gagal lewat ssh", transcript: r.out });
|
|
25100
25420
|
const verify = await sshExec(v, "true", { timeoutMs: 3e4 });
|
|
@@ -25119,7 +25439,7 @@ async function vps_default(app2) {
|
|
|
25119
25439
|
const v = await prisma.vps.findUnique({ where: { id: req.params.id } });
|
|
25120
25440
|
if (!v) return reply.code(404).send({ error: "not found" });
|
|
25121
25441
|
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}`,
|
|
25442
|
+
const s2 = createSession(`vps-console:${v.id}`, homedir6(), { id: `vpsc-${v.id}`, command: consoleArgv(v) });
|
|
25123
25443
|
return reply.code(201).send({ id: s2.id });
|
|
25124
25444
|
});
|
|
25125
25445
|
app2.post("/vps/:id/session", async (req, reply) => {
|
|
@@ -25128,7 +25448,7 @@ async function vps_default(app2) {
|
|
|
25128
25448
|
if (keyMissing(v)) return reply.code(409).send({ error: "key VPS tidak ada di mesin ini", keyMissing: true });
|
|
25129
25449
|
const checks = v.audit ?? [];
|
|
25130
25450
|
const { model, effort } = await sessionModel();
|
|
25131
|
-
const s2 = createSession(`vps:${v.id}`,
|
|
25451
|
+
const s2 = createSession(`vps:${v.id}`, homedir6(), {
|
|
25132
25452
|
model,
|
|
25133
25453
|
effort,
|
|
25134
25454
|
prompt: [
|
|
@@ -25146,9 +25466,9 @@ async function vps_default(app2) {
|
|
|
25146
25466
|
// src/services/limits.ts
|
|
25147
25467
|
init_config2();
|
|
25148
25468
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
25149
|
-
import { readFileSync as
|
|
25150
|
-
import { homedir as
|
|
25151
|
-
import { join as
|
|
25469
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
25470
|
+
import { homedir as homedir7 } from "node:os";
|
|
25471
|
+
import { join as join11 } from "node:path";
|
|
25152
25472
|
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
25153
25473
|
var TTL_MS2 = 3e4;
|
|
25154
25474
|
var lastOk = null;
|
|
@@ -25161,7 +25481,7 @@ var LABELS = {
|
|
|
25161
25481
|
var humanize = (k) => k.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
25162
25482
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
25163
25483
|
function credsFile() {
|
|
25164
|
-
return
|
|
25484
|
+
return join11(effectiveStr("CLAUDE_CONFIG_DIR") ?? join11(homedir7(), ".claude"), ".credentials.json");
|
|
25165
25485
|
}
|
|
25166
25486
|
function readAccessToken() {
|
|
25167
25487
|
if (process.platform === "darwin" && !effectiveStr("CLAUDE_CONFIG_DIR")) {
|
|
@@ -25177,7 +25497,7 @@ function readAccessToken() {
|
|
|
25177
25497
|
}
|
|
25178
25498
|
}
|
|
25179
25499
|
try {
|
|
25180
|
-
return JSON.parse(
|
|
25500
|
+
return JSON.parse(readFileSync11(credsFile(), "utf8"))?.claudeAiOauth?.accessToken ?? null;
|
|
25181
25501
|
} catch {
|
|
25182
25502
|
return null;
|
|
25183
25503
|
}
|
|
@@ -25234,15 +25554,15 @@ async function getLimits() {
|
|
|
25234
25554
|
|
|
25235
25555
|
// src/services/codex-limits.ts
|
|
25236
25556
|
import { open as open2, readdir as readdir2, stat } from "node:fs/promises";
|
|
25237
|
-
import { homedir as
|
|
25238
|
-
import { join as
|
|
25557
|
+
import { homedir as homedir8 } from "node:os";
|
|
25558
|
+
import { join as join12 } from "node:path";
|
|
25239
25559
|
var TTL_MS3 = 3e4;
|
|
25240
25560
|
var STALE_AFTER_MS = 12 * 36e5;
|
|
25241
25561
|
var TAIL_BYTES = 512 * 1024;
|
|
25242
25562
|
var MAX_FILES = 8;
|
|
25243
25563
|
var cache2 = null;
|
|
25244
25564
|
var freshUntil2 = 0;
|
|
25245
|
-
var codexSessionsDir = () =>
|
|
25565
|
+
var codexSessionsDir = () => join12(process.env.CODEX_HOME ?? join12(homedir8(), ".codex"), "sessions");
|
|
25246
25566
|
var UNAVAILABLE = { status: "unavailable", windows: [], fetchedAt: null, plan: null };
|
|
25247
25567
|
function windowLabel(minutes) {
|
|
25248
25568
|
if (minutes === 300) return "Sesi 5 jam";
|
|
@@ -25275,7 +25595,7 @@ async function recentRollouts(dir2) {
|
|
|
25275
25595
|
return [];
|
|
25276
25596
|
}
|
|
25277
25597
|
const stamped = await Promise.all(entries.map(async (rel) => {
|
|
25278
|
-
const full =
|
|
25598
|
+
const full = join12(dir2, rel);
|
|
25279
25599
|
try {
|
|
25280
25600
|
return { full, mtime: (await stat(full)).mtimeMs };
|
|
25281
25601
|
} catch {
|
|
@@ -25391,13 +25711,18 @@ async function codex(app2) {
|
|
|
25391
25711
|
app2.get("/codex/version", async () => codexVersionInfo());
|
|
25392
25712
|
}
|
|
25393
25713
|
|
|
25714
|
+
// src/routes/methods.ts
|
|
25715
|
+
async function methods(app2) {
|
|
25716
|
+
app2.get("/methods/status", async () => methodStatusReport());
|
|
25717
|
+
}
|
|
25718
|
+
|
|
25394
25719
|
// src/routes/update.ts
|
|
25395
25720
|
init_src();
|
|
25396
25721
|
|
|
25397
25722
|
// src/services/update.ts
|
|
25398
25723
|
init_src();
|
|
25399
25724
|
init_config2();
|
|
25400
|
-
import { readFileSync as
|
|
25725
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
25401
25726
|
import { resolve as resolve12, dirname as dirname12 } from "node:path";
|
|
25402
25727
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
25403
25728
|
var UPDATE_COMMAND = "npm i -g hanoman@latest";
|
|
@@ -25426,7 +25751,7 @@ function runningVersion() {
|
|
|
25426
25751
|
const here = dirname12(fileURLToPath3(import.meta.url));
|
|
25427
25752
|
for (const p of [resolve12(here, "build-info.json"), resolve12(here, "../package.json")]) {
|
|
25428
25753
|
try {
|
|
25429
|
-
const v = JSON.parse(
|
|
25754
|
+
const v = JSON.parse(readFileSync12(p, "utf8")).version;
|
|
25430
25755
|
if (typeof v === "string" && v) return v;
|
|
25431
25756
|
} catch {
|
|
25432
25757
|
}
|
|
@@ -25774,7 +26099,7 @@ init_conflicts();
|
|
|
25774
26099
|
init_config2();
|
|
25775
26100
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
25776
26101
|
import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile4, unlink } from "node:fs/promises";
|
|
25777
|
-
import { join as
|
|
26102
|
+
import { join as join13, resolve as resolve13 } from "node:path";
|
|
25778
26103
|
var EXT = {
|
|
25779
26104
|
"image/png": ".png",
|
|
25780
26105
|
"image/jpeg": ".jpg",
|
|
@@ -25784,22 +26109,22 @@ function extFor(mimeType) {
|
|
|
25784
26109
|
return EXT[mimeType] ?? ".bin";
|
|
25785
26110
|
}
|
|
25786
26111
|
function uploadDir() {
|
|
25787
|
-
return resolve13(effectiveStr("HANOMAN_UPLOAD_DIR") ??
|
|
26112
|
+
return resolve13(effectiveStr("HANOMAN_UPLOAD_DIR") ?? join13(process.cwd(), "data", "uploads"));
|
|
25788
26113
|
}
|
|
25789
26114
|
async function saveUpload(buf, mimeType) {
|
|
25790
26115
|
const dir2 = uploadDir();
|
|
25791
26116
|
await mkdir2(dir2, { recursive: true });
|
|
25792
26117
|
const storageKey = `${randomUUID3()}${extFor(mimeType)}`;
|
|
25793
|
-
await writeFile2(
|
|
26118
|
+
await writeFile2(join13(dir2, storageKey), buf);
|
|
25794
26119
|
return { storageKey, size: buf.length };
|
|
25795
26120
|
}
|
|
25796
26121
|
async function readUpload(storageKey) {
|
|
25797
26122
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
25798
|
-
return readFile4(
|
|
26123
|
+
return readFile4(join13(uploadDir(), safe));
|
|
25799
26124
|
}
|
|
25800
26125
|
async function readUploadOrFetch(storageKey) {
|
|
25801
26126
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
25802
|
-
const target2 =
|
|
26127
|
+
const target2 = join13(uploadDir(), safe);
|
|
25803
26128
|
try {
|
|
25804
26129
|
return await readFile4(target2);
|
|
25805
26130
|
} catch {
|
|
@@ -25818,7 +26143,7 @@ async function readUploadOrFetch(storageKey) {
|
|
|
25818
26143
|
}
|
|
25819
26144
|
async function deleteUpload(storageKey) {
|
|
25820
26145
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
25821
|
-
await unlink(
|
|
26146
|
+
await unlink(join13(uploadDir(), safe)).catch(() => {
|
|
25822
26147
|
});
|
|
25823
26148
|
}
|
|
25824
26149
|
|
|
@@ -25949,10 +26274,10 @@ import { randomUUID as randomUUID5 } from "node:crypto";
|
|
|
25949
26274
|
init_config2();
|
|
25950
26275
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
25951
26276
|
import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile5, unlink as unlink2 } from "node:fs/promises";
|
|
25952
|
-
import { join as
|
|
26277
|
+
import { join as join14, resolve as resolve14, basename as basename2 } from "node:path";
|
|
25953
26278
|
var MAX_TRANSCRIPT_BYTES = 1024 * 1024;
|
|
25954
26279
|
function transcriptDir() {
|
|
25955
|
-
return resolve14(effectiveStr("HANOMAN_TRANSCRIPT_DIR") ??
|
|
26280
|
+
return resolve14(effectiveStr("HANOMAN_TRANSCRIPT_DIR") ?? join14(process.cwd(), "data", "transcripts"));
|
|
25956
26281
|
}
|
|
25957
26282
|
function clamp(text) {
|
|
25958
26283
|
const buf = Buffer.from(text, "utf8");
|
|
@@ -25970,20 +26295,20 @@ async function saveTranscript(text) {
|
|
|
25970
26295
|
const dir2 = transcriptDir();
|
|
25971
26296
|
await mkdir3(dir2, { recursive: true });
|
|
25972
26297
|
const key = `${randomUUID4()}.log`;
|
|
25973
|
-
await writeFile3(
|
|
26298
|
+
await writeFile3(join14(dir2, key), body, "utf8");
|
|
25974
26299
|
return { key, bytes: Buffer.byteLength(body, "utf8"), truncated };
|
|
25975
26300
|
}
|
|
25976
26301
|
async function readTranscript(key) {
|
|
25977
26302
|
if (!key) return null;
|
|
25978
26303
|
try {
|
|
25979
|
-
return await readFile5(
|
|
26304
|
+
return await readFile5(join14(transcriptDir(), basename2(key)), "utf8");
|
|
25980
26305
|
} catch {
|
|
25981
26306
|
return null;
|
|
25982
26307
|
}
|
|
25983
26308
|
}
|
|
25984
26309
|
async function deleteTranscript(key) {
|
|
25985
26310
|
if (!key) return;
|
|
25986
|
-
await unlink2(
|
|
26311
|
+
await unlink2(join14(transcriptDir(), basename2(key))).catch(() => {
|
|
25987
26312
|
});
|
|
25988
26313
|
}
|
|
25989
26314
|
|
|
@@ -26406,7 +26731,7 @@ init_db();
|
|
|
26406
26731
|
|
|
26407
26732
|
// src/services/ticket-accept.ts
|
|
26408
26733
|
init_db();
|
|
26409
|
-
import { join as
|
|
26734
|
+
import { join as join15 } from "node:path";
|
|
26410
26735
|
async function materializeAttachments(atts) {
|
|
26411
26736
|
const ready = /* @__PURE__ */ new Set();
|
|
26412
26737
|
for (const a of atts) {
|
|
@@ -26421,7 +26746,7 @@ async function materializeAttachments(atts) {
|
|
|
26421
26746
|
var attachmentInstruction = (t, atts, ready) => {
|
|
26422
26747
|
if (atts.length === 0) return "Tanpa lampiran.";
|
|
26423
26748
|
const list2 = atts.map(
|
|
26424
|
-
(a) => ready.has(a.id) ? `- ${a.filename} (${a.mimeType}) \u2192 ${
|
|
26749
|
+
(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
26750
|
).join("\n");
|
|
26426
26751
|
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
26752
|
${list2}
|
|
@@ -27112,7 +27437,7 @@ function leadPrompt(q, c) {
|
|
|
27112
27437
|
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
27438
|
lines2.push("");
|
|
27114
27439
|
lines2.push("## Cara kerja");
|
|
27115
|
-
lines2.push(
|
|
27440
|
+
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
27441
|
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
27442
|
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
27443
|
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 +27472,7 @@ function leadPrompt(q, c) {
|
|
|
27147
27472
|
|
|
27148
27473
|
// src/services/lead/verdict.ts
|
|
27149
27474
|
init_src();
|
|
27150
|
-
import { existsSync as
|
|
27475
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
27151
27476
|
import { isAbsolute as isAbsolute3, resolve as resolvePath, relative } from "node:path";
|
|
27152
27477
|
function extractJsonBlock(raw) {
|
|
27153
27478
|
const re = /```json\s*\n([\s\S]*?)```/g;
|
|
@@ -27185,7 +27510,7 @@ function keepExistingRefs(refs2, repoDir) {
|
|
|
27185
27510
|
const full = resolvePath(repoDir, ref);
|
|
27186
27511
|
const rel = relative(repoDir, full);
|
|
27187
27512
|
if (rel.startsWith("..")) continue;
|
|
27188
|
-
if (
|
|
27513
|
+
if (existsSync12(full)) out3.push(ref);
|
|
27189
27514
|
}
|
|
27190
27515
|
return [...new Set(out3)];
|
|
27191
27516
|
}
|
|
@@ -28783,7 +29108,7 @@ init_db();
|
|
|
28783
29108
|
init_db();
|
|
28784
29109
|
import { execFile as execFile10 } from "node:child_process";
|
|
28785
29110
|
import { promisify as promisify9 } from "node:util";
|
|
28786
|
-
import { existsSync as
|
|
29111
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
28787
29112
|
|
|
28788
29113
|
// src/services/changelog/scrub.ts
|
|
28789
29114
|
var MERGE = /^merge\s+(branch|pull request|remote-tracking branch|tag)\b/i;
|
|
@@ -28850,7 +29175,7 @@ var US2 = "";
|
|
|
28850
29175
|
var RS = "";
|
|
28851
29176
|
var NO_REPO = "project ini belum ditautkan ke repo di mesin ini";
|
|
28852
29177
|
var NO_TAG = "repo project ini belum punya tag rilis";
|
|
28853
|
-
var usable = (repoDir) => !!repoDir &&
|
|
29178
|
+
var usable = (repoDir) => !!repoDir && existsSync13(repoDir);
|
|
28854
29179
|
async function listTags(repoDir) {
|
|
28855
29180
|
if (!usable(repoDir)) return { tags: [], head: null, reason: NO_REPO };
|
|
28856
29181
|
try {
|
|
@@ -29111,13 +29436,13 @@ init_db();
|
|
|
29111
29436
|
|
|
29112
29437
|
// src/services/agent-tool-catalog.ts
|
|
29113
29438
|
init_src();
|
|
29114
|
-
import { readFileSync as
|
|
29115
|
-
import { homedir as
|
|
29116
|
-
import { join as
|
|
29117
|
-
var home = () => process.env.HOME ||
|
|
29118
|
-
var
|
|
29439
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
29440
|
+
import { homedir as homedir9 } from "node:os";
|
|
29441
|
+
import { join as join16 } from "node:path";
|
|
29442
|
+
var home = () => process.env.HOME || homedir9();
|
|
29443
|
+
var readJson2 = (path) => {
|
|
29119
29444
|
try {
|
|
29120
|
-
return JSON.parse(
|
|
29445
|
+
return JSON.parse(readFileSync13(path, "utf8"));
|
|
29121
29446
|
} catch {
|
|
29122
29447
|
return null;
|
|
29123
29448
|
}
|
|
@@ -29130,7 +29455,7 @@ var serversOf = (node) => {
|
|
|
29130
29455
|
var codexServers = () => {
|
|
29131
29456
|
let text;
|
|
29132
29457
|
try {
|
|
29133
|
-
text =
|
|
29458
|
+
text = readFileSync13(join16(home(), ".codex", "config.toml"), "utf8");
|
|
29134
29459
|
} catch {
|
|
29135
29460
|
return [];
|
|
29136
29461
|
}
|
|
@@ -29143,12 +29468,12 @@ var codexServers = () => {
|
|
|
29143
29468
|
};
|
|
29144
29469
|
function mcpServerNames(repoDir) {
|
|
29145
29470
|
const names = [];
|
|
29146
|
-
const claudeJson =
|
|
29471
|
+
const claudeJson = readJson2(join16(home(), ".claude.json"));
|
|
29147
29472
|
names.push(...serversOf(claudeJson));
|
|
29148
29473
|
if (repoDir) {
|
|
29149
29474
|
const projects = claudeJson?.projects;
|
|
29150
29475
|
if (projects && typeof projects === "object") names.push(...serversOf(projects[repoDir]));
|
|
29151
|
-
names.push(...serversOf(
|
|
29476
|
+
names.push(...serversOf(readJson2(join16(repoDir, ".mcp.json"))));
|
|
29152
29477
|
}
|
|
29153
29478
|
names.push(...codexServers());
|
|
29154
29479
|
return [...new Set(names.filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
@@ -31034,7 +31359,7 @@ var PUBLIC = /* @__PURE__ */ new Set([
|
|
|
31034
31359
|
"GET /api/agent-integration.md"
|
|
31035
31360
|
]);
|
|
31036
31361
|
function buildApp({ requireAuth = true, agentDocFile } = {}) {
|
|
31037
|
-
const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname13(fileURLToPath4(import.meta.url)), process.env,
|
|
31362
|
+
const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname13(fileURLToPath4(import.meta.url)), process.env, existsSync14);
|
|
31038
31363
|
const app2 = Fastify({ logger: false, trustProxy: true });
|
|
31039
31364
|
app2.addContentTypeParser("application/json", { parseAs: "string" }, (_req, body, done) => {
|
|
31040
31365
|
if (!body) return done(null, void 0);
|
|
@@ -31114,6 +31439,7 @@ function buildApp({ requireAuth = true, agentDocFile } = {}) {
|
|
|
31114
31439
|
await api.register(tickets_default);
|
|
31115
31440
|
await api.register(scheduler_default);
|
|
31116
31441
|
await api.register(codex);
|
|
31442
|
+
await api.register(methods);
|
|
31117
31443
|
await api.register(lead_default);
|
|
31118
31444
|
await api.register(custom_agents_default);
|
|
31119
31445
|
await api.register(githubIssues);
|
|
@@ -31124,7 +31450,7 @@ function buildApp({ requireAuth = true, agentDocFile } = {}) {
|
|
|
31124
31450
|
await api.register(client_accounts_default);
|
|
31125
31451
|
}, { prefix: "/api" });
|
|
31126
31452
|
if (process.env.NODE_ENV === "production") {
|
|
31127
|
-
const dist = pickWebDir(dirname13(fileURLToPath4(import.meta.url)), process.env,
|
|
31453
|
+
const dist = pickWebDir(dirname13(fileURLToPath4(import.meta.url)), process.env, existsSync14);
|
|
31128
31454
|
if (dist) {
|
|
31129
31455
|
app2.register(fastifyStatic, { root: dist });
|
|
31130
31456
|
app2.setNotFoundHandler((req, reply) => req.url.startsWith("/api") ? reply.code(404).send({ error: "not found" }) : reply.sendFile("index.html"));
|