hanoman 0.1.26 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-info.json +3 -3
- package/dist/cli.js +612 -272
- package/dist/server.js +617 -233
- package/package.json +1 -1
- package/web/assets/{index-BfOACraN.js → index-CJNgL7_5.js} +1552 -1552
- 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,186 @@ var init_paths = __esm({
|
|
|
8500
8655
|
}
|
|
8501
8656
|
});
|
|
8502
8657
|
|
|
8658
|
+
// ../runner/src/skills.ts
|
|
8659
|
+
import { homedir as homedir2 } from "node:os";
|
|
8660
|
+
import { basename, dirname as dirname3, join as join2, resolve as resolve6 } from "node:path";
|
|
8661
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
8662
|
+
function agentSkillHome(agent, env = process.env, osHome = homedir2()) {
|
|
8663
|
+
const own = (agent === "codex" ? env.HANOMAN_CODEX_HOME : env.HANOMAN_CLAUDE_HOME)?.trim();
|
|
8664
|
+
if (own) return own;
|
|
8665
|
+
if (agent === "codex") {
|
|
8666
|
+
const cx = env.CODEX_HOME?.trim();
|
|
8667
|
+
if (cx) return cx;
|
|
8668
|
+
}
|
|
8669
|
+
return join2(osHome, agent === "codex" ? ".codex" : ".claude");
|
|
8670
|
+
}
|
|
8671
|
+
function agentsSkillHome(env = process.env, osHome = homedir2()) {
|
|
8672
|
+
return env.HANOMAN_AGENTS_HOME?.trim() || join2(osHome, ".agents");
|
|
8673
|
+
}
|
|
8674
|
+
function dirsIn(dir2) {
|
|
8675
|
+
try {
|
|
8676
|
+
return readdirSync(dir2, { withFileTypes: true }).filter((e) => (e.isDirectory() || e.isSymbolicLink()) && !e.name.startsWith(".")).map((e) => e.name);
|
|
8677
|
+
} catch {
|
|
8678
|
+
return [];
|
|
8679
|
+
}
|
|
8680
|
+
}
|
|
8681
|
+
function readJson(file) {
|
|
8682
|
+
try {
|
|
8683
|
+
return JSON.parse(readFileSync2(file, "utf8"));
|
|
8684
|
+
} catch {
|
|
8685
|
+
return null;
|
|
8686
|
+
}
|
|
8687
|
+
}
|
|
8688
|
+
function isSkillDir(dir2) {
|
|
8689
|
+
return existsSync2(join2(dir2, "SKILL.md"));
|
|
8690
|
+
}
|
|
8691
|
+
function asSkill(dir2, pkg, name2 = basename(dir2)) {
|
|
8692
|
+
return { id: pkg ? `${pkg}:${name2}` : name2, name: name2, pkg, dir: dir2 };
|
|
8693
|
+
}
|
|
8694
|
+
function skillsUnder(dir2, pkg, depth = 2) {
|
|
8695
|
+
const out3 = [];
|
|
8696
|
+
for (const name2 of dirsIn(dir2)) {
|
|
8697
|
+
const sub = join2(dir2, name2);
|
|
8698
|
+
if (isSkillDir(sub)) out3.push(asSkill(sub, pkg, name2));
|
|
8699
|
+
else if (depth > 1) out3.push(...skillsUnder(sub, pkg, depth - 1));
|
|
8700
|
+
}
|
|
8701
|
+
return out3;
|
|
8702
|
+
}
|
|
8703
|
+
function manifestSkills(installPath, pkg) {
|
|
8704
|
+
for (const marker of [".claude-plugin", ".codex-plugin"]) {
|
|
8705
|
+
const j = readJson(join2(installPath, marker, "plugin.json"));
|
|
8706
|
+
const declared = j?.skills;
|
|
8707
|
+
if (!Array.isArray(declared)) continue;
|
|
8708
|
+
const out3 = [];
|
|
8709
|
+
for (const rel of declared) {
|
|
8710
|
+
if (typeof rel !== "string" || !rel) continue;
|
|
8711
|
+
const p = resolve6(installPath, rel);
|
|
8712
|
+
const dir2 = basename(p) === "SKILL.md" ? dirname3(p) : p;
|
|
8713
|
+
if (isSkillDir(dir2)) out3.push(asSkill(dir2, pkg));
|
|
8714
|
+
}
|
|
8715
|
+
if (out3.length) return out3;
|
|
8716
|
+
}
|
|
8717
|
+
return [];
|
|
8718
|
+
}
|
|
8719
|
+
function pluginSkills(installPath, pkg) {
|
|
8720
|
+
const declared = manifestSkills(installPath, pkg);
|
|
8721
|
+
return declared.length ? declared : skillsUnder(join2(installPath, "skills"), pkg);
|
|
8722
|
+
}
|
|
8723
|
+
function lockPluginNames(agentsHome) {
|
|
8724
|
+
const out3 = /* @__PURE__ */ new Map();
|
|
8725
|
+
const j = readJson(join2(agentsHome, ".skill-lock.json"));
|
|
8726
|
+
const skills = j?.skills;
|
|
8727
|
+
if (!skills || typeof skills !== "object") return out3;
|
|
8728
|
+
for (const [name2, entry] of Object.entries(skills)) {
|
|
8729
|
+
const pkg = entry?.pluginName;
|
|
8730
|
+
if (typeof pkg === "string" && pkg.trim()) out3.set(name2, pkg.trim());
|
|
8731
|
+
}
|
|
8732
|
+
return out3;
|
|
8733
|
+
}
|
|
8734
|
+
function splitPluginKey(key) {
|
|
8735
|
+
const at = key.lastIndexOf("@");
|
|
8736
|
+
return at > 0 ? { pkg: key.slice(0, at), marketplace: key.slice(at + 1) } : { pkg: key, marketplace: "" };
|
|
8737
|
+
}
|
|
8738
|
+
function manifestRoots(home2) {
|
|
8739
|
+
const j = readJson(join2(home2, "plugins", "installed_plugins.json"));
|
|
8740
|
+
const plugins = j?.plugins;
|
|
8741
|
+
if (!plugins || typeof plugins !== "object") return [];
|
|
8742
|
+
const out3 = [];
|
|
8743
|
+
for (const [key, entries] of Object.entries(plugins)) {
|
|
8744
|
+
const { pkg, marketplace } = splitPluginKey(key);
|
|
8745
|
+
for (const e of Array.isArray(entries) ? entries : []) {
|
|
8746
|
+
const p = e?.installPath;
|
|
8747
|
+
if (typeof p === "string" && p) out3.push({ pkg, marketplace, dir: p });
|
|
8748
|
+
}
|
|
8749
|
+
}
|
|
8750
|
+
return out3;
|
|
8751
|
+
}
|
|
8752
|
+
function cacheRoots(home2) {
|
|
8753
|
+
const cache6 = join2(home2, "plugins", "cache");
|
|
8754
|
+
const out3 = [];
|
|
8755
|
+
for (const marketplace of dirsIn(cache6))
|
|
8756
|
+
for (const pkg of dirsIn(join2(cache6, marketplace)))
|
|
8757
|
+
for (const version of dirsIn(join2(cache6, marketplace, pkg)))
|
|
8758
|
+
out3.push({ pkg, marketplace, dir: join2(cache6, marketplace, pkg, version) });
|
|
8759
|
+
return out3;
|
|
8760
|
+
}
|
|
8761
|
+
function disabledPlugins(agent, home2) {
|
|
8762
|
+
const out3 = /* @__PURE__ */ new Set();
|
|
8763
|
+
if (agent === "claude") {
|
|
8764
|
+
const en = readJson(join2(home2, "settings.json"))?.enabledPlugins;
|
|
8765
|
+
if (en && typeof en === "object") {
|
|
8766
|
+
for (const [k, v] of Object.entries(en)) if (v === false) out3.add(k);
|
|
8767
|
+
}
|
|
8768
|
+
return out3;
|
|
8769
|
+
}
|
|
8770
|
+
let toml;
|
|
8771
|
+
try {
|
|
8772
|
+
toml = readFileSync2(join2(home2, "config.toml"), "utf8");
|
|
8773
|
+
} catch {
|
|
8774
|
+
return out3;
|
|
8775
|
+
}
|
|
8776
|
+
let current = null;
|
|
8777
|
+
for (const raw of toml.split("\n")) {
|
|
8778
|
+
const line = raw.trim();
|
|
8779
|
+
const head = /^\[([^\]]*)\]$/.exec(line);
|
|
8780
|
+
if (head) {
|
|
8781
|
+
const m = /^plugins\."(.+)"$/.exec(head[1] ?? "");
|
|
8782
|
+
current = m ? m[1] : null;
|
|
8783
|
+
continue;
|
|
8784
|
+
}
|
|
8785
|
+
if (current && /^enabled\s*=\s*false\b/.test(line)) out3.add(current);
|
|
8786
|
+
}
|
|
8787
|
+
return out3;
|
|
8788
|
+
}
|
|
8789
|
+
function scanAgentSkills(agent, env = process.env, osHome = homedir2()) {
|
|
8790
|
+
const home2 = agentSkillHome(agent, env, osHome);
|
|
8791
|
+
const disabled = disabledPlugins(agent, home2);
|
|
8792
|
+
const roots = [];
|
|
8793
|
+
const skills = [];
|
|
8794
|
+
const packages = /* @__PURE__ */ new Set();
|
|
8795
|
+
const seenSkill = /* @__PURE__ */ new Set();
|
|
8796
|
+
const seenRoot = /* @__PURE__ */ new Set();
|
|
8797
|
+
const add = (dir2, found) => {
|
|
8798
|
+
if (!found.length) return;
|
|
8799
|
+
if (!seenRoot.has(dir2)) {
|
|
8800
|
+
seenRoot.add(dir2);
|
|
8801
|
+
roots.push(dir2);
|
|
8802
|
+
}
|
|
8803
|
+
for (const s2 of found) if (!seenSkill.has(s2.id)) {
|
|
8804
|
+
seenSkill.add(s2.id);
|
|
8805
|
+
skills.push(s2);
|
|
8806
|
+
}
|
|
8807
|
+
};
|
|
8808
|
+
const userDir = join2(home2, "skills");
|
|
8809
|
+
add(userDir, skillsUnder(userDir, null));
|
|
8810
|
+
for (const r of [...manifestRoots(home2), ...cacheRoots(home2)]) {
|
|
8811
|
+
if (disabled.has(`${r.pkg}@${r.marketplace}`)) continue;
|
|
8812
|
+
const found = pluginSkills(r.dir, r.pkg);
|
|
8813
|
+
if (!found.length) continue;
|
|
8814
|
+
packages.add(r.pkg);
|
|
8815
|
+
add(join2(r.dir, "skills"), found);
|
|
8816
|
+
}
|
|
8817
|
+
if (agent === "codex") {
|
|
8818
|
+
const agentsHome = agentsSkillHome(env, osHome);
|
|
8819
|
+
const dir2 = join2(agentsHome, "skills");
|
|
8820
|
+
const names = lockPluginNames(agentsHome);
|
|
8821
|
+
const flat = skillsUnder(dir2, null);
|
|
8822
|
+
const withPkg = flat.flatMap((s2) => {
|
|
8823
|
+
const pkg = names.get(s2.name);
|
|
8824
|
+
if (!pkg) return [s2];
|
|
8825
|
+
packages.add(pkg);
|
|
8826
|
+
return [asSkill(s2.dir, pkg, s2.name), s2];
|
|
8827
|
+
});
|
|
8828
|
+
add(dir2, withPkg);
|
|
8829
|
+
}
|
|
8830
|
+
return { agent, home: home2, roots, skills, packages: [...packages] };
|
|
8831
|
+
}
|
|
8832
|
+
var init_skills = __esm({
|
|
8833
|
+
"../runner/src/skills.ts"() {
|
|
8834
|
+
"use strict";
|
|
8835
|
+
}
|
|
8836
|
+
});
|
|
8837
|
+
|
|
8503
8838
|
// ../runner/src/telegram-operator.ts
|
|
8504
8839
|
function buildTelegramOperatorPrompt(input) {
|
|
8505
8840
|
const personality = input.personality ? [
|
|
@@ -8588,6 +8923,7 @@ var init_src2 = __esm({
|
|
|
8588
8923
|
init_verify_scope();
|
|
8589
8924
|
init_code_style();
|
|
8590
8925
|
init_paths();
|
|
8926
|
+
init_skills();
|
|
8591
8927
|
init_telegram_operator();
|
|
8592
8928
|
}
|
|
8593
8929
|
});
|
|
@@ -8724,7 +9060,7 @@ var init_tap = __esm({
|
|
|
8724
9060
|
|
|
8725
9061
|
// src/db.ts
|
|
8726
9062
|
import { mkdirSync as mkdirSync2 } from "node:fs";
|
|
8727
|
-
import { dirname as
|
|
9063
|
+
import { dirname as dirname4, resolve as resolve7 } from "node:path";
|
|
8728
9064
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
8729
9065
|
import { PrismaClient } from "@prisma/client";
|
|
8730
9066
|
var schemaDir, notice, url, base, prisma;
|
|
@@ -8734,12 +9070,12 @@ var init_db = __esm({
|
|
|
8734
9070
|
init_env();
|
|
8735
9071
|
init_src2();
|
|
8736
9072
|
init_tap();
|
|
8737
|
-
schemaDir =
|
|
9073
|
+
schemaDir = resolve7(dirname4(fileURLToPath2(import.meta.url)), "../prisma");
|
|
8738
9074
|
notice = dbUrlNotice(process.env);
|
|
8739
9075
|
if (notice) console.warn(notice);
|
|
8740
9076
|
url = resolveDbUrl(process.env, schemaDir);
|
|
8741
9077
|
process.env.DATABASE_URL = url;
|
|
8742
|
-
mkdirSync2(
|
|
9078
|
+
mkdirSync2(dirname4(dbFilePath(url)), { recursive: true });
|
|
8743
9079
|
base = new PrismaClient();
|
|
8744
9080
|
prisma = base.$extends(webhookTap(base));
|
|
8745
9081
|
}
|
|
@@ -8755,12 +9091,12 @@ var init_stage_machine = __esm({
|
|
|
8755
9091
|
});
|
|
8756
9092
|
|
|
8757
9093
|
// src/services/session-phases.ts
|
|
8758
|
-
import { readFileSync as
|
|
9094
|
+
import { readFileSync as readFileSync3, readdirSync as readdirSync2 } from "node:fs";
|
|
8759
9095
|
function recorded(file) {
|
|
8760
9096
|
const out3 = /* @__PURE__ */ new Map();
|
|
8761
9097
|
let raw;
|
|
8762
9098
|
try {
|
|
8763
|
-
raw =
|
|
9099
|
+
raw = readFileSync3(file, "utf8");
|
|
8764
9100
|
} catch {
|
|
8765
9101
|
return out3;
|
|
8766
9102
|
}
|
|
@@ -8798,19 +9134,21 @@ function stageFor(phases) {
|
|
|
8798
9134
|
return best < 0 ? null : STAGES[best];
|
|
8799
9135
|
}
|
|
8800
9136
|
function planComplete(worktree, specId) {
|
|
8801
|
-
const dir2 = `${worktree}/docs/superpowers/plans`;
|
|
8802
9137
|
const re = new RegExp(`(^|[^a-z0-9])${specId.toLowerCase()}([^0-9]|$)`);
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
names
|
|
8806
|
-
} catch {
|
|
8807
|
-
return true;
|
|
8808
|
-
}
|
|
8809
|
-
for (const n2 of names) {
|
|
8810
|
-
if (!re.test(n2.toLowerCase())) continue;
|
|
9138
|
+
for (const rel of PLAN_DIRS) {
|
|
9139
|
+
const dir2 = `${worktree}/${rel}`;
|
|
9140
|
+
let names;
|
|
8811
9141
|
try {
|
|
8812
|
-
|
|
9142
|
+
names = readdirSync2(dir2);
|
|
8813
9143
|
} catch {
|
|
9144
|
+
continue;
|
|
9145
|
+
}
|
|
9146
|
+
for (const n2 of names) {
|
|
9147
|
+
if (!re.test(n2.toLowerCase())) continue;
|
|
9148
|
+
try {
|
|
9149
|
+
if (/^[ \t]*- \[ \]/m.test(readFileSync3(`${dir2}/${n2}`, "utf8"))) return false;
|
|
9150
|
+
} catch {
|
|
9151
|
+
}
|
|
8814
9152
|
}
|
|
8815
9153
|
}
|
|
8816
9154
|
return true;
|
|
@@ -8829,6 +9167,7 @@ var init_session_phases = __esm({
|
|
|
8829
9167
|
"src/services/session-phases.ts"() {
|
|
8830
9168
|
"use strict";
|
|
8831
9169
|
init_src2();
|
|
9170
|
+
init_src();
|
|
8832
9171
|
init_stage_machine();
|
|
8833
9172
|
phaseFilePath = (repoDir, sessionId2) => `${repoDir}/.worktrees/.phases/${sessionId2}`;
|
|
8834
9173
|
decisionFilePath = (repoDir, sessionId2) => `${repoDir}/.worktrees/.decisions/${sessionId2}`;
|
|
@@ -9090,9 +9429,9 @@ var init_tui_dialog = __esm({
|
|
|
9090
9429
|
});
|
|
9091
9430
|
|
|
9092
9431
|
// src/services/secret-box.ts
|
|
9093
|
-
import { chmodSync, existsSync as
|
|
9432
|
+
import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync } from "node:fs";
|
|
9094
9433
|
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
9095
|
-
import { dirname as
|
|
9434
|
+
import { dirname as dirname5, join as join3 } from "node:path";
|
|
9096
9435
|
function isEncrypted(value) {
|
|
9097
9436
|
return value.startsWith(ENC_PREFIX);
|
|
9098
9437
|
}
|
|
@@ -9124,7 +9463,7 @@ function fromEnv(raw) {
|
|
|
9124
9463
|
return null;
|
|
9125
9464
|
}
|
|
9126
9465
|
function secretKeyPath() {
|
|
9127
|
-
return
|
|
9466
|
+
return join3(resolveHome(), "secret.key");
|
|
9128
9467
|
}
|
|
9129
9468
|
function secretKey() {
|
|
9130
9469
|
if (cached) return cached;
|
|
@@ -9136,14 +9475,14 @@ function secretKey() {
|
|
|
9136
9475
|
return key2;
|
|
9137
9476
|
}
|
|
9138
9477
|
const path = secretKeyPath();
|
|
9139
|
-
if (
|
|
9140
|
-
const key2 = Buffer.from(
|
|
9478
|
+
if (existsSync3(path)) {
|
|
9479
|
+
const key2 = Buffer.from(readFileSync4(path, "utf8").trim(), "base64url");
|
|
9141
9480
|
if (key2.length !== KEY_BYTES) throw new Error(`kunci di ${path} rusak \u2014 panjangnya bukan 32 byte`);
|
|
9142
9481
|
cached = key2;
|
|
9143
9482
|
return key2;
|
|
9144
9483
|
}
|
|
9145
9484
|
const key = randomBytes(KEY_BYTES);
|
|
9146
|
-
mkdirSync3(
|
|
9485
|
+
mkdirSync3(dirname5(path), { recursive: true });
|
|
9147
9486
|
writeFileSync(path, key.toString("base64url"), { mode: 384 });
|
|
9148
9487
|
chmodSync(path, 384);
|
|
9149
9488
|
cached = key;
|
|
@@ -9242,7 +9581,7 @@ import { spawn } from "node-pty";
|
|
|
9242
9581
|
import { execFileSync } from "node:child_process";
|
|
9243
9582
|
import { randomUUID } from "node:crypto";
|
|
9244
9583
|
import { mkdirSync as mkdirSync4, statSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
9245
|
-
import { dirname as
|
|
9584
|
+
import { dirname as dirname6 } from "node:path";
|
|
9246
9585
|
import { tmpdir } from "node:os";
|
|
9247
9586
|
function tmux(...args) {
|
|
9248
9587
|
try {
|
|
@@ -9324,7 +9663,7 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
9324
9663
|
let promptArg = "";
|
|
9325
9664
|
if (!opts.command && opts.prompt) {
|
|
9326
9665
|
const promptFile = promptFilePath(id);
|
|
9327
|
-
mkdirSync4(
|
|
9666
|
+
mkdirSync4(dirname6(promptFile), { recursive: true });
|
|
9328
9667
|
writeFileSync2(promptFile, opts.prompt + rosterBlock);
|
|
9329
9668
|
promptArg = `"$(cat ${sq(promptFile)})"`;
|
|
9330
9669
|
}
|
|
@@ -9336,7 +9675,7 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
9336
9675
|
let goalGate;
|
|
9337
9676
|
if (agent === "codex" && opts.goal && opts.flow && opts.specId) {
|
|
9338
9677
|
goalGate = goalGatePath(id);
|
|
9339
|
-
mkdirSync4(
|
|
9678
|
+
mkdirSync4(dirname6(goalGate), { recursive: true });
|
|
9340
9679
|
writeFileSync2(goalGate, codexGoalScript({
|
|
9341
9680
|
flow: opts.flow,
|
|
9342
9681
|
specId: opts.specId,
|
|
@@ -9352,7 +9691,7 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
9352
9691
|
const json = renderAgentsJson(customDefs);
|
|
9353
9692
|
if (json) {
|
|
9354
9693
|
agentsFile = agentsFilePath(id);
|
|
9355
|
-
mkdirSync4(
|
|
9694
|
+
mkdirSync4(dirname6(agentsFile), { recursive: true });
|
|
9356
9695
|
writeFileSync2(agentsFile, json);
|
|
9357
9696
|
}
|
|
9358
9697
|
}
|
|
@@ -9372,12 +9711,12 @@ function createSession(projectId, cwd, opts = {}) {
|
|
|
9372
9711
|
for (const [k, v] of Object.entries(rootBypassEnv())) envPairs.push(`${k}=${sq(v)}`);
|
|
9373
9712
|
}
|
|
9374
9713
|
if (opts.phaseFile) {
|
|
9375
|
-
mkdirSync4(
|
|
9714
|
+
mkdirSync4(dirname6(opts.phaseFile), { recursive: true });
|
|
9376
9715
|
envPairs.push(`HANOMAN_PHASE_FILE=${sq(opts.phaseFile)}`);
|
|
9377
9716
|
}
|
|
9378
9717
|
for (const [k, v] of Object.entries(opts.env ?? {})) envPairs.push(`${k}=${sq(v)}`);
|
|
9379
9718
|
const cmd = envPairs.length ? `${envPairs.join(" ")} ${argv}` : argv;
|
|
9380
|
-
if (opts.decisionFile) mkdirSync4(
|
|
9719
|
+
if (opts.decisionFile) mkdirSync4(dirname6(opts.decisionFile), { recursive: true });
|
|
9381
9720
|
tmux(
|
|
9382
9721
|
"set-option",
|
|
9383
9722
|
"-g",
|
|
@@ -10199,6 +10538,8 @@ var init_settings2 = __esm({
|
|
|
10199
10538
|
// SPEC-338 · ADR-0074 · model/effort codex
|
|
10200
10539
|
verifyScope: "changed",
|
|
10201
10540
|
// SPEC-376 · ADR-0080 · uji hanya yang berubah
|
|
10541
|
+
method: DEFAULT_METHOD,
|
|
10542
|
+
// SPEC-734 · ADR-0113 · metode workflow default
|
|
10202
10543
|
conflict: CONFLICT_DEFAULTS,
|
|
10203
10544
|
// SPEC-383 · ADR-0081 · default sesi konflik (opt-in, mati)
|
|
10204
10545
|
lead: LEAD_DEFAULTS,
|
|
@@ -10213,8 +10554,8 @@ var init_settings2 = __esm({
|
|
|
10213
10554
|
});
|
|
10214
10555
|
|
|
10215
10556
|
// src/services/codex-trust.ts
|
|
10216
|
-
import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as
|
|
10217
|
-
import { homedir as
|
|
10557
|
+
import { appendFileSync, mkdirSync as mkdirSync6, readFileSync as readFileSync6, realpathSync as realpathSync2 } from "node:fs";
|
|
10558
|
+
import { homedir as homedir3 } from "node:os";
|
|
10218
10559
|
function ensureCodexTrust(repoDir, home2) {
|
|
10219
10560
|
const path = codexConfigPath(home2);
|
|
10220
10561
|
let canonical = repoDir;
|
|
@@ -10226,7 +10567,7 @@ function ensureCodexTrust(repoDir, home2) {
|
|
|
10226
10567
|
try {
|
|
10227
10568
|
let existing = "";
|
|
10228
10569
|
try {
|
|
10229
|
-
existing =
|
|
10570
|
+
existing = readFileSync6(path, "utf8");
|
|
10230
10571
|
} catch {
|
|
10231
10572
|
}
|
|
10232
10573
|
if (existing.includes(header2)) return;
|
|
@@ -10243,7 +10584,7 @@ var codexHome, codexConfigPath;
|
|
|
10243
10584
|
var init_codex_trust = __esm({
|
|
10244
10585
|
"src/services/codex-trust.ts"() {
|
|
10245
10586
|
"use strict";
|
|
10246
|
-
codexHome = (home2) => home2 ?? process.env.CODEX_HOME ?? `${
|
|
10587
|
+
codexHome = (home2) => home2 ?? process.env.CODEX_HOME ?? `${homedir3()}/.codex`;
|
|
10247
10588
|
codexConfigPath = (home2) => `${codexHome(home2)}/config.toml`;
|
|
10248
10589
|
}
|
|
10249
10590
|
});
|
|
@@ -10886,7 +11227,7 @@ var init_gateway = __esm({
|
|
|
10886
11227
|
const message = sanitizeTelegramOutput(error.message, this.deps.exactSecrets).slice(0, 500);
|
|
10887
11228
|
updateTelegramRuntimeStatus({ running: false, readiness: "error", lastError: message });
|
|
10888
11229
|
if (error instanceof TelegramApiError && error.code === 409) return;
|
|
10889
|
-
await new Promise((
|
|
11230
|
+
await new Promise((resolve16) => setTimeout(resolve16, 1e3));
|
|
10890
11231
|
}
|
|
10891
11232
|
}
|
|
10892
11233
|
}
|
|
@@ -16896,7 +17237,7 @@ var require_parseParams = __commonJS({
|
|
|
16896
17237
|
var require_basename = __commonJS({
|
|
16897
17238
|
"../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/lib/utils/basename.js"(exports, module) {
|
|
16898
17239
|
"use strict";
|
|
16899
|
-
module.exports = function
|
|
17240
|
+
module.exports = function basename4(path) {
|
|
16900
17241
|
if (typeof path !== "string") {
|
|
16901
17242
|
return "";
|
|
16902
17243
|
}
|
|
@@ -16923,7 +17264,7 @@ var require_multipart = __commonJS({
|
|
|
16923
17264
|
var Dicer = require_Dicer();
|
|
16924
17265
|
var parseParams = require_parseParams();
|
|
16925
17266
|
var decodeText = require_decodeText();
|
|
16926
|
-
var
|
|
17267
|
+
var basename4 = require_basename();
|
|
16927
17268
|
var getLimit = require_getLimit();
|
|
16928
17269
|
var RE_BOUNDARY = /^boundary$/i;
|
|
16929
17270
|
var RE_FIELD = /^form-data$/i;
|
|
@@ -17040,7 +17381,7 @@ var require_multipart = __commonJS({
|
|
|
17040
17381
|
} else if (RE_FILENAME.test(parsed[i][0])) {
|
|
17041
17382
|
filename = parsed[i][1];
|
|
17042
17383
|
if (!preservePath) {
|
|
17043
|
-
filename =
|
|
17384
|
+
filename = basename4(filename);
|
|
17044
17385
|
}
|
|
17045
17386
|
}
|
|
17046
17387
|
}
|
|
@@ -18124,17 +18465,17 @@ var require_stream_wormhole = __commonJS({
|
|
|
18124
18465
|
"../node_modules/.pnpm/stream-wormhole@1.1.0/node_modules/stream-wormhole/index.js"(exports, module) {
|
|
18125
18466
|
"use strict";
|
|
18126
18467
|
module.exports = (stream, throwError) => {
|
|
18127
|
-
return new Promise((
|
|
18468
|
+
return new Promise((resolve16, reject2) => {
|
|
18128
18469
|
if (typeof stream.resume !== "function") {
|
|
18129
|
-
return
|
|
18470
|
+
return resolve16();
|
|
18130
18471
|
}
|
|
18131
18472
|
stream.unpipe && stream.unpipe();
|
|
18132
18473
|
stream.resume();
|
|
18133
18474
|
if (stream._readableState && stream._readableState.ended) {
|
|
18134
|
-
return
|
|
18475
|
+
return resolve16();
|
|
18135
18476
|
}
|
|
18136
18477
|
if (!stream.readable || stream.destroyed) {
|
|
18137
|
-
return
|
|
18478
|
+
return resolve16();
|
|
18138
18479
|
}
|
|
18139
18480
|
function cleanup() {
|
|
18140
18481
|
stream.removeListener("end", onEnd);
|
|
@@ -18143,14 +18484,14 @@ var require_stream_wormhole = __commonJS({
|
|
|
18143
18484
|
}
|
|
18144
18485
|
function onEnd() {
|
|
18145
18486
|
cleanup();
|
|
18146
|
-
|
|
18487
|
+
resolve16();
|
|
18147
18488
|
}
|
|
18148
18489
|
function onError(err) {
|
|
18149
18490
|
cleanup();
|
|
18150
18491
|
if (throwError) {
|
|
18151
18492
|
reject2(err);
|
|
18152
18493
|
} else {
|
|
18153
|
-
|
|
18494
|
+
resolve16();
|
|
18154
18495
|
}
|
|
18155
18496
|
}
|
|
18156
18497
|
stream.on("end", onEnd);
|
|
@@ -18585,16 +18926,16 @@ var require_multipart2 = __commonJS({
|
|
|
18585
18926
|
}
|
|
18586
18927
|
};
|
|
18587
18928
|
const parts = () => {
|
|
18588
|
-
return new Promise((
|
|
18929
|
+
return new Promise((resolve16, reject2) => {
|
|
18589
18930
|
handle((val) => {
|
|
18590
18931
|
if (val instanceof Error) {
|
|
18591
18932
|
if (val.message === "Unexpected end of multipart data") {
|
|
18592
|
-
|
|
18933
|
+
resolve16(null);
|
|
18593
18934
|
} else {
|
|
18594
18935
|
reject2(val);
|
|
18595
18936
|
}
|
|
18596
18937
|
} else {
|
|
18597
|
-
|
|
18938
|
+
resolve16(val);
|
|
18598
18939
|
}
|
|
18599
18940
|
});
|
|
18600
18941
|
});
|
|
@@ -18860,8 +19201,8 @@ import fastifyStatic from "@fastify/static";
|
|
|
18860
19201
|
import websocket from "@fastify/websocket";
|
|
18861
19202
|
import cookie from "@fastify/cookie";
|
|
18862
19203
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
18863
|
-
import { dirname as
|
|
18864
|
-
import { existsSync as
|
|
19204
|
+
import { dirname as dirname14 } from "node:path";
|
|
19205
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
18865
19206
|
|
|
18866
19207
|
// src/web-dir.ts
|
|
18867
19208
|
import { resolve } from "node:path";
|
|
@@ -18923,8 +19264,8 @@ init_stage_machine();
|
|
|
18923
19264
|
init_src();
|
|
18924
19265
|
import { execFile } from "node:child_process";
|
|
18925
19266
|
import { promisify } from "node:util";
|
|
18926
|
-
import { readFileSync as
|
|
18927
|
-
import { resolve as
|
|
19267
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync4, rmSync as rmSync2, mkdirSync as mkdirSync5 } from "node:fs";
|
|
19268
|
+
import { resolve as resolve8, dirname as dirname7, sep } from "node:path";
|
|
18928
19269
|
var exec = promisify(execFile);
|
|
18929
19270
|
async function listRepoDocs(repoDir) {
|
|
18930
19271
|
try {
|
|
@@ -18940,7 +19281,7 @@ async function listRepoDocs(repoDir) {
|
|
|
18940
19281
|
}
|
|
18941
19282
|
function docsDirOf(repoDir) {
|
|
18942
19283
|
try {
|
|
18943
|
-
const raw =
|
|
19284
|
+
const raw = readFileSync5(resolve8(repoDir, "hanoman.config.json"), "utf8");
|
|
18944
19285
|
return zHanomanConfig.parse(JSON.parse(raw)).docsDir;
|
|
18945
19286
|
} catch {
|
|
18946
19287
|
return zHanomanConfig.parse({}).docsDir;
|
|
@@ -18948,18 +19289,18 @@ function docsDirOf(repoDir) {
|
|
|
18948
19289
|
}
|
|
18949
19290
|
function resolveIndex(repoDir, docsDir) {
|
|
18950
19291
|
const rel = `${docsDir}/README.md`;
|
|
18951
|
-
return
|
|
19292
|
+
return existsSync4(resolve8(repoDir, rel)) ? rel : "";
|
|
18952
19293
|
}
|
|
18953
19294
|
var catOf = (rel) => rel.includes("/") ? rel.slice(0, rel.lastIndexOf("/")) : ".";
|
|
18954
19295
|
var nameOf = (rel) => rel.includes("/") ? rel.slice(rel.lastIndexOf("/") + 1) : rel;
|
|
18955
19296
|
async function scanRepoDocs(repoDir) {
|
|
18956
|
-
if (!repoDir || !
|
|
19297
|
+
if (!repoDir || !existsSync4(repoDir)) return { coverage: 0, tree: [] };
|
|
18957
19298
|
const files = await listRepoDocs(repoDir);
|
|
18958
19299
|
const docsDir = docsDirOf(repoDir);
|
|
18959
19300
|
const index = resolveIndex(repoDir, docsDir);
|
|
18960
19301
|
const read = (rel) => {
|
|
18961
19302
|
try {
|
|
18962
|
-
return
|
|
19303
|
+
return readFileSync5(resolve8(repoDir, rel), "utf8");
|
|
18963
19304
|
} catch {
|
|
18964
19305
|
return null;
|
|
18965
19306
|
}
|
|
@@ -18982,25 +19323,25 @@ async function scanRepoDocs(repoDir) {
|
|
|
18982
19323
|
function docAbsPath(repoDir, rel) {
|
|
18983
19324
|
if (!rel.endsWith(".md")) throw new Error("hanya file .md yang diizinkan");
|
|
18984
19325
|
if (rel.split("/").includes(".git")) throw new Error("tidak boleh menyentuh .git");
|
|
18985
|
-
const abs =
|
|
19326
|
+
const abs = resolve8(repoDir, rel);
|
|
18986
19327
|
if (abs !== repoDir && !abs.startsWith(repoDir + sep)) throw new Error("path keluar dari repo");
|
|
18987
19328
|
return abs;
|
|
18988
19329
|
}
|
|
18989
19330
|
function readDocFile(repoDir, rel) {
|
|
18990
19331
|
try {
|
|
18991
|
-
return
|
|
19332
|
+
return readFileSync5(docAbsPath(repoDir, rel), "utf8");
|
|
18992
19333
|
} catch {
|
|
18993
19334
|
return null;
|
|
18994
19335
|
}
|
|
18995
19336
|
}
|
|
18996
19337
|
function writeDocFile(repoDir, rel, content) {
|
|
18997
19338
|
const abs = docAbsPath(repoDir, rel);
|
|
18998
|
-
mkdirSync5(
|
|
19339
|
+
mkdirSync5(dirname7(abs), { recursive: true });
|
|
18999
19340
|
writeFileSync3(abs, content);
|
|
19000
19341
|
}
|
|
19001
19342
|
function deleteDocFile(repoDir, rel) {
|
|
19002
19343
|
const abs = docAbsPath(repoDir, rel);
|
|
19003
|
-
if (!
|
|
19344
|
+
if (!existsSync4(abs)) return false;
|
|
19004
19345
|
rmSync2(abs);
|
|
19005
19346
|
return true;
|
|
19006
19347
|
}
|
|
@@ -19287,13 +19628,13 @@ async function projects_default(app2) {
|
|
|
19287
19628
|
// src/routes/specs.ts
|
|
19288
19629
|
init_src();
|
|
19289
19630
|
init_src2();
|
|
19290
|
-
import { existsSync as
|
|
19631
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
19291
19632
|
|
|
19292
19633
|
// src/services/integrate.ts
|
|
19293
19634
|
import { execFile as execFile3 } from "node:child_process";
|
|
19294
19635
|
import { promisify as promisify3 } from "node:util";
|
|
19295
19636
|
import { rmSync as rmSync3 } from "node:fs";
|
|
19296
|
-
import { join as
|
|
19637
|
+
import { join as join4 } from "node:path";
|
|
19297
19638
|
var sanitize = (id) => id.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
19298
19639
|
var sourceBranch = (specId) => `hanoman/${sanitize(specId)}`;
|
|
19299
19640
|
var exec3 = promisify3(execFile3);
|
|
@@ -19337,7 +19678,7 @@ async function integrateBranch(repoDir, src, op, target2) {
|
|
|
19337
19678
|
const tgt = await resolveTarget(repoDir, target2);
|
|
19338
19679
|
if (!tgt) return { status: "error", code: 400, error: `target "${target2}" tidak dikenal` };
|
|
19339
19680
|
await sh(repoDir, ["fetch", "origin"]);
|
|
19340
|
-
const wt =
|
|
19681
|
+
const wt = join4(repoDir, ".worktrees", `merge-${sanitize(src.mergeId)}`);
|
|
19341
19682
|
await reclaim(repoDir, wt);
|
|
19342
19683
|
const baseRef = op === "merge" ? tgt.ref : source;
|
|
19343
19684
|
const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `${baseRef}^{commit}`]);
|
|
@@ -19398,7 +19739,7 @@ async function mergeIntoCurrent(repoDir, source, opts = {}) {
|
|
|
19398
19739
|
const src = await resolveGraphSource(repoDir, source);
|
|
19399
19740
|
if (!src) return { status: "error", code: 400, error: `source "${source}" tak dikenal` };
|
|
19400
19741
|
await sh(repoDir, ["fetch", "origin"]);
|
|
19401
|
-
const wt =
|
|
19742
|
+
const wt = join4(repoDir, ".worktrees", `merge-${sanitize(current)}`);
|
|
19402
19743
|
await reclaim(repoDir, wt);
|
|
19403
19744
|
const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `refs/heads/${current}^{commit}`]);
|
|
19404
19745
|
if (!await ok(repoDir, ["worktree", "add", "--detach", "-q", wt, baseSha]))
|
|
@@ -19436,7 +19777,7 @@ async function replayCurrent(repoDir, source, cmd) {
|
|
|
19436
19777
|
const current = await out(repoDir, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
19437
19778
|
if (!current || current === "HEAD")
|
|
19438
19779
|
return { status: "error", code: 409, error: "HEAD detached \u2014 checkout sebuah branch dulu" };
|
|
19439
|
-
const wt =
|
|
19780
|
+
const wt = join4(repoDir, ".worktrees", `merge-${sanitize(current)}`);
|
|
19440
19781
|
await reclaim(repoDir, wt);
|
|
19441
19782
|
const baseSha = await out(repoDir, ["rev-parse", "--verify", "--end-of-options", `refs/heads/${current}^{commit}`]);
|
|
19442
19783
|
if (!await ok(repoDir, ["worktree", "add", "--detach", "-q", wt, baseSha]))
|
|
@@ -19485,16 +19826,16 @@ import { execFile as execFile4 } from "node:child_process";
|
|
|
19485
19826
|
import { promisify as promisify4 } from "node:util";
|
|
19486
19827
|
import { readFile as readFile2, mkdtemp, copyFile, rm } from "node:fs/promises";
|
|
19487
19828
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
19488
|
-
import { join as
|
|
19829
|
+
import { join as join5, resolve as resolve9 } from "node:path";
|
|
19489
19830
|
var exec4 = promisify4(execFile4);
|
|
19490
19831
|
var GIT3 = { maxBuffer: 1 << 24 };
|
|
19491
19832
|
var MAX = 256 * 1024;
|
|
19492
|
-
var worktreeDir = (repoDir, specId) =>
|
|
19833
|
+
var worktreeDir = (repoDir, specId) => join5(repoDir, ".worktrees", specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_"));
|
|
19493
19834
|
async function withTempIndex(wt, fn) {
|
|
19494
19835
|
const idx = (await exec4("git", ["rev-parse", "--git-path", "index"], { cwd: wt, ...GIT3 })).stdout.trim();
|
|
19495
|
-
const dir2 = await mkdtemp(
|
|
19496
|
-
const tmp =
|
|
19497
|
-
await copyFile(
|
|
19836
|
+
const dir2 = await mkdtemp(join5(tmpdir2(), "hanoman-idx-"));
|
|
19837
|
+
const tmp = join5(dir2, "index");
|
|
19838
|
+
await copyFile(resolve9(wt, idx), tmp);
|
|
19498
19839
|
const env = { ...process.env, GIT_INDEX_FILE: tmp };
|
|
19499
19840
|
try {
|
|
19500
19841
|
await exec4("git", ["add", "-A", "-N"], { cwd: wt, env, ...GIT3 });
|
|
@@ -19618,7 +19959,7 @@ async function reviewFile(repoDir, specId, baseSha, branchFrom, path) {
|
|
|
19618
19959
|
let contentRaw = null;
|
|
19619
19960
|
if (status !== "D") {
|
|
19620
19961
|
try {
|
|
19621
|
-
contentRaw = await readFile2(
|
|
19962
|
+
contentRaw = await readFile2(join5(wt, path), "utf8");
|
|
19622
19963
|
} catch {
|
|
19623
19964
|
contentRaw = null;
|
|
19624
19965
|
}
|
|
@@ -19803,14 +20144,15 @@ async function notificationsFeed(p = {}) {
|
|
|
19803
20144
|
init_stage_machine();
|
|
19804
20145
|
|
|
19805
20146
|
// src/services/stage-artifacts.ts
|
|
20147
|
+
init_src();
|
|
19806
20148
|
init_stage_machine();
|
|
19807
20149
|
var ARTIFACT_DIR = {
|
|
19808
|
-
"spec-ready":
|
|
19809
|
-
planned:
|
|
20150
|
+
"spec-ready": SPEC_DIRS.map((d) => `${d}/`),
|
|
20151
|
+
planned: PLAN_DIRS.map((d) => `${d}/`)
|
|
19810
20152
|
};
|
|
19811
20153
|
async function artifactsToRemove(projectId, specId, target2, current) {
|
|
19812
20154
|
const ti = STAGES.indexOf(target2), ci = STAGES.indexOf(current);
|
|
19813
|
-
const dirs = STAGES.filter((_, i) => i > ti && i <= ci).
|
|
20155
|
+
const dirs = STAGES.filter((_, i) => i > ti && i <= ci).flatMap((s2) => ARTIFACT_DIR[s2] ?? []);
|
|
19814
20156
|
if (!dirs.length) return [];
|
|
19815
20157
|
const repoDir = await resolveRepoDir(projectId);
|
|
19816
20158
|
if (!repoDir) return [];
|
|
@@ -19842,6 +20184,7 @@ async function deleteDoc(projectId, path) {
|
|
|
19842
20184
|
}
|
|
19843
20185
|
|
|
19844
20186
|
// src/services/spec-docs.ts
|
|
20187
|
+
init_src();
|
|
19845
20188
|
init_db();
|
|
19846
20189
|
init_pty();
|
|
19847
20190
|
var ORDER = ["audit", "spec", "plan", "objective", "brainstorm", "other"];
|
|
@@ -19850,8 +20193,8 @@ function kindOf(path) {
|
|
|
19850
20193
|
if (p.endsWith("-audit.md") || p.includes("/research/audit-")) return "audit";
|
|
19851
20194
|
if (p.endsWith("-objective.md")) return "objective";
|
|
19852
20195
|
if (p.endsWith("-brainstorm.md")) return "brainstorm";
|
|
19853
|
-
if (p.endsWith("-design.md") || p.endsWith("-spec.md") || p.startsWith(
|
|
19854
|
-
if (p.startsWith(
|
|
20196
|
+
if (p.endsWith("-design.md") || p.endsWith("-spec.md") || SPEC_DIRS.some((d) => p.startsWith(`${d}/`))) return "spec";
|
|
20197
|
+
if (PLAN_DIRS.some((d) => p.startsWith(`${d}/`)) || p.endsWith("-plan.md")) return "plan";
|
|
19855
20198
|
return "other";
|
|
19856
20199
|
}
|
|
19857
20200
|
async function resolveDir(specId, sessions = listSessions()) {
|
|
@@ -22381,7 +22724,7 @@ function renderDocPdf(text, name2, meta) {
|
|
|
22381
22724
|
});
|
|
22382
22725
|
const chunks = [];
|
|
22383
22726
|
doc.on("data", (c) => chunks.push(c));
|
|
22384
|
-
const done = new Promise((
|
|
22727
|
+
const done = new Promise((resolve16) => doc.on("end", () => resolve16(Buffer.concat(chunks))));
|
|
22385
22728
|
doc.font("Helvetica-Bold").fontSize(8).fillColor(BRASS).text(toWinAnsi(meta.eyebrow.toUpperCase()), { characterSpacing: 0.8 });
|
|
22386
22729
|
doc.moveDown(0.25);
|
|
22387
22730
|
doc.font("Helvetica-Bold").fontSize(17).fillColor(STRONG).text(toWinAnsi(name2.split("/").pop() ?? name2));
|
|
@@ -22763,7 +23106,7 @@ ${item.context}` : item.context;
|
|
|
22763
23106
|
});
|
|
22764
23107
|
const specWithProject = (id) => prisma.spec.findUnique({ where: { id }, include: { project: true } });
|
|
22765
23108
|
const resolveReview = async (repoDir, spec) => {
|
|
22766
|
-
if (
|
|
23109
|
+
if (existsSync5(worktreeDir(repoDir, spec.id))) return { wt: true };
|
|
22767
23110
|
if (spec.baseSha && spec.headSha && await shaResolvable(repoDir, spec.baseSha) && await shaResolvable(repoDir, spec.headSha))
|
|
22768
23111
|
return { wt: false, base: spec.baseSha, head: spec.headSha };
|
|
22769
23112
|
const r = await specCommitRange(repoDir, spec.id);
|
|
@@ -22998,13 +23341,13 @@ async function docs_default(app2) {
|
|
|
22998
23341
|
|
|
22999
23342
|
// src/routes/ide.ts
|
|
23000
23343
|
init_src2();
|
|
23001
|
-
import { basename } from "node:path";
|
|
23344
|
+
import { basename as basename2 } from "node:path";
|
|
23002
23345
|
import { spawn as spawn2 } from "node:child_process";
|
|
23003
23346
|
|
|
23004
23347
|
// src/services/git-remotes.ts
|
|
23005
23348
|
import { execFile as execFile5 } from "node:child_process";
|
|
23006
23349
|
import { promisify as promisify5 } from "node:util";
|
|
23007
|
-
import { existsSync as
|
|
23350
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
23008
23351
|
var exec5 = promisify5(execFile5);
|
|
23009
23352
|
var GIT4 = { maxBuffer: 1 << 24, encoding: "utf8" };
|
|
23010
23353
|
function parseRemote(url2) {
|
|
@@ -23023,7 +23366,7 @@ function prUrl(remoteUrl, branch, base2 = "main") {
|
|
|
23023
23366
|
return null;
|
|
23024
23367
|
}
|
|
23025
23368
|
async function listRemotes(repoDir) {
|
|
23026
|
-
if (!repoDir || !
|
|
23369
|
+
if (!repoDir || !existsSync6(repoDir)) return [];
|
|
23027
23370
|
try {
|
|
23028
23371
|
const { stdout } = await exec5("git", ["remote", "-v"], { cwd: repoDir, ...GIT4 });
|
|
23029
23372
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -23065,20 +23408,20 @@ import { promisify as promisify7 } from "node:util";
|
|
|
23065
23408
|
import { execFile as execFile6 } from "node:child_process";
|
|
23066
23409
|
import { promisify as promisify6 } from "node:util";
|
|
23067
23410
|
import { readFile as readFile3, writeFile, mkdir } from "node:fs/promises";
|
|
23068
|
-
import { existsSync as
|
|
23069
|
-
import { resolve as
|
|
23411
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
23412
|
+
import { resolve as resolve10, sep as sep2, dirname as dirname9 } from "node:path";
|
|
23070
23413
|
var exec6 = promisify6(execFile6);
|
|
23071
23414
|
var GIT5 = { maxBuffer: 1 << 24 };
|
|
23072
23415
|
var MAX2 = 256 * 1024;
|
|
23073
23416
|
var splitZ2 = (s2) => s2.split("\0").filter(Boolean);
|
|
23074
23417
|
function repoAbsPath(repoDir, rel) {
|
|
23075
23418
|
if (rel.split(/[\\/]/).includes(".git")) throw new Error("tidak boleh menyentuh .git");
|
|
23076
|
-
const abs =
|
|
23419
|
+
const abs = resolve10(repoDir, rel);
|
|
23077
23420
|
if (abs !== repoDir && !abs.startsWith(repoDir + sep2)) throw new Error("path keluar dari repo");
|
|
23078
23421
|
return abs;
|
|
23079
23422
|
}
|
|
23080
23423
|
async function listRepoTree(repoDir, ref = "") {
|
|
23081
|
-
if (!repoDir || !
|
|
23424
|
+
if (!repoDir || !existsSync7(repoDir)) return [];
|
|
23082
23425
|
try {
|
|
23083
23426
|
const { stdout } = ref ? await exec6("git", ["ls-tree", "-r", "--name-only", "-z", ref], { cwd: repoDir, ...GIT5 }) : await exec6("git", ["ls-files", "--cached", "--others", "--exclude-standard", "-z"], { cwd: repoDir, ...GIT5 });
|
|
23084
23427
|
return [...new Set(splitZ2(stdout))].sort();
|
|
@@ -23104,7 +23447,7 @@ async function currentBranch(repoDir) {
|
|
|
23104
23447
|
}
|
|
23105
23448
|
async function repoStatus(repoDir) {
|
|
23106
23449
|
const empty = { branch: "", ahead: 0, behind: 0, staged: [], unstaged: [], untracked: [], clean: true };
|
|
23107
|
-
if (!repoDir || !
|
|
23450
|
+
if (!repoDir || !existsSync7(repoDir)) return empty;
|
|
23108
23451
|
try {
|
|
23109
23452
|
const { stdout } = await exec6("git", ["status", "--porcelain=v1", "-z", "-b"], { cwd: repoDir, ...GIT5 });
|
|
23110
23453
|
const parts = stdout.split("\0");
|
|
@@ -23137,7 +23480,7 @@ async function repoStatus(repoDir) {
|
|
|
23137
23480
|
}
|
|
23138
23481
|
}
|
|
23139
23482
|
async function listStashes(repoDir) {
|
|
23140
|
-
if (!repoDir || !
|
|
23483
|
+
if (!repoDir || !existsSync7(repoDir)) return [];
|
|
23141
23484
|
try {
|
|
23142
23485
|
const { stdout } = await exec6("git", ["stash", "list", `--format=%gd${US}%s${US}%aI`], { cwd: repoDir, ...GIT5 });
|
|
23143
23486
|
return stdout.split("\n").filter(Boolean).map((line) => {
|
|
@@ -23149,7 +23492,7 @@ async function listStashes(repoDir) {
|
|
|
23149
23492
|
}
|
|
23150
23493
|
}
|
|
23151
23494
|
async function listGraph(repoDir, limit = 200, opts = {}) {
|
|
23152
|
-
if (!repoDir || !
|
|
23495
|
+
if (!repoDir || !existsSync7(repoDir)) return { commits: [], current: "", total: 0 };
|
|
23153
23496
|
try {
|
|
23154
23497
|
const fmt = ["%H", "%P", "%an", "%aI", "%s", "%D"].join(US);
|
|
23155
23498
|
const refArgs = opts.branches?.length ? ["--end-of-options", ...opts.branches] : [
|
|
@@ -23219,7 +23562,7 @@ async function changedOf(repoDir, sha) {
|
|
|
23219
23562
|
}
|
|
23220
23563
|
async function compareCommits(repoDir, from, to) {
|
|
23221
23564
|
const empty = { from, to, changed: [] };
|
|
23222
|
-
if (!repoDir || !
|
|
23565
|
+
if (!repoDir || !existsSync7(repoDir)) return empty;
|
|
23223
23566
|
try {
|
|
23224
23567
|
const [num, name2] = await Promise.all([
|
|
23225
23568
|
exec6("git", ["diff", "--numstat", "-z", "--no-renames", "--end-of-options", from, to], { cwd: repoDir, ...GIT5 }),
|
|
@@ -23231,7 +23574,7 @@ async function compareCommits(repoDir, from, to) {
|
|
|
23231
23574
|
}
|
|
23232
23575
|
}
|
|
23233
23576
|
async function searchCommits(repoDir, q, by = "all") {
|
|
23234
|
-
if (!repoDir || !
|
|
23577
|
+
if (!repoDir || !existsSync7(repoDir) || !q) return [];
|
|
23235
23578
|
const run2 = async (extra) => {
|
|
23236
23579
|
try {
|
|
23237
23580
|
return (await exec6("git", ["log", "--all", "--format=%H", ...extra], { cwd: repoDir, ...GIT5 })).stdout.split("\n").filter(Boolean);
|
|
@@ -23312,7 +23655,7 @@ async function commitFileDiff(repoDir, sha, rel) {
|
|
|
23312
23655
|
async function writeRepoFile(repoDir, rel, content) {
|
|
23313
23656
|
if (!repoDir) throw new Error("project tidak punya repoDir");
|
|
23314
23657
|
const abs = repoAbsPath(repoDir, rel);
|
|
23315
|
-
await mkdir(
|
|
23658
|
+
await mkdir(dirname9(abs), { recursive: true });
|
|
23316
23659
|
await writeFile(abs, content);
|
|
23317
23660
|
}
|
|
23318
23661
|
function validateGitOp(op) {
|
|
@@ -23480,7 +23823,7 @@ async function runGitOp(repoDir, op) {
|
|
|
23480
23823
|
}
|
|
23481
23824
|
}
|
|
23482
23825
|
async function workingStatus(repoDir) {
|
|
23483
|
-
if (!repoDir || !
|
|
23826
|
+
if (!repoDir || !existsSync7(repoDir)) return { branch: "", staged: [], unstaged: [] };
|
|
23484
23827
|
const [branch, staged, unstaged] = await Promise.all([
|
|
23485
23828
|
currentBranch(repoDir),
|
|
23486
23829
|
changedFiles(repoDir, ["--cached"]),
|
|
@@ -23489,7 +23832,7 @@ async function workingStatus(repoDir) {
|
|
|
23489
23832
|
return { branch, staged, unstaged };
|
|
23490
23833
|
}
|
|
23491
23834
|
async function workingFileDiff(repoDir, path, staged) {
|
|
23492
|
-
if (!repoDir || !
|
|
23835
|
+
if (!repoDir || !existsSync7(repoDir)) return null;
|
|
23493
23836
|
repoAbsPath(repoDir, path);
|
|
23494
23837
|
const changed = staged ? await changedFiles(repoDir, ["--cached"]) : await withTempIndex(repoDir, (env) => changedFiles(repoDir, [], env));
|
|
23495
23838
|
const cf = changed.find((c) => c.path === path);
|
|
@@ -23808,7 +24151,7 @@ async function ide_default(app2) {
|
|
|
23808
24151
|
const fmt = q.format === "tar" ? "tar" : "zip";
|
|
23809
24152
|
const child = spawn2("git", ["archive", `--format=${fmt}`, "--end-of-options", ref], { cwd: repoDir });
|
|
23810
24153
|
reply.header("content-type", fmt === "zip" ? "application/zip" : "application/x-tar");
|
|
23811
|
-
reply.header("content-disposition", `attachment; filename="${
|
|
24154
|
+
reply.header("content-disposition", `attachment; filename="${basename2(repoDir)}-${ref.replace(/[^\w.-]/g, "_")}.${fmt}"`);
|
|
23812
24155
|
return reply.send(child.stdout);
|
|
23813
24156
|
});
|
|
23814
24157
|
app2.get("/projects/:id/commit/:sha", async (req, reply) => {
|
|
@@ -23960,22 +24303,22 @@ async function finishGraphOp(reply, id, repoDir, r, verb) {
|
|
|
23960
24303
|
CODE_STYLE_CLAUSE,
|
|
23961
24304
|
`${verb} via git graph project ${id}.`
|
|
23962
24305
|
].join("\n\n");
|
|
23963
|
-
const s2 = createSession(id, r.worktree, { id:
|
|
24306
|
+
const s2 = createSession(id, r.worktree, { id: basename2(r.worktree), model, effort, agent, prompt });
|
|
23964
24307
|
return { status: "conflict", sessionId: s2.id };
|
|
23965
24308
|
}
|
|
23966
24309
|
|
|
23967
24310
|
// src/routes/fs.ts
|
|
23968
24311
|
import { readdir } from "node:fs/promises";
|
|
23969
|
-
import { homedir as
|
|
23970
|
-
import { resolve as
|
|
24312
|
+
import { homedir as homedir4 } from "node:os";
|
|
24313
|
+
import { resolve as resolve11, dirname as dirname10, join as join6 } from "node:path";
|
|
23971
24314
|
async function fs_default(app2) {
|
|
23972
24315
|
app2.get("/fs/browse", async (req, reply) => {
|
|
23973
24316
|
const q = req.query.path;
|
|
23974
|
-
const dir2 = q && q.trim() ?
|
|
24317
|
+
const dir2 = q && q.trim() ? resolve11(q.trim()) : homedir4();
|
|
23975
24318
|
try {
|
|
23976
24319
|
const ents = await readdir(dir2, { withFileTypes: true });
|
|
23977
|
-
const entries = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path:
|
|
23978
|
-
const parent =
|
|
24320
|
+
const entries = ents.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => ({ name: e.name, path: join6(dir2, e.name) })).sort((a, b) => a.name.localeCompare(b.name));
|
|
24321
|
+
const parent = dirname10(dir2);
|
|
23979
24322
|
return { path: dir2, parent: parent === dir2 ? null : parent, entries };
|
|
23980
24323
|
} catch {
|
|
23981
24324
|
return reply.code(400).send({ error: `tak bisa membaca folder "${dir2}"` });
|
|
@@ -23988,13 +24331,14 @@ init_db();
|
|
|
23988
24331
|
init_src();
|
|
23989
24332
|
init_src2();
|
|
23990
24333
|
init_session_phases();
|
|
23991
|
-
import { existsSync as
|
|
24334
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
23992
24335
|
init_settings2();
|
|
23993
24336
|
init_codex_trust();
|
|
23994
24337
|
|
|
23995
24338
|
// src/services/session-launch.ts
|
|
23996
24339
|
init_db();
|
|
23997
24340
|
init_src2();
|
|
24341
|
+
init_src();
|
|
23998
24342
|
init_settings2();
|
|
23999
24343
|
init_codex_trust();
|
|
24000
24344
|
init_pty();
|
|
@@ -24051,6 +24395,8 @@ async function startSpecSession(spec, opts) {
|
|
|
24051
24395
|
};
|
|
24052
24396
|
const goal = isGoalFlow || (opts.goal ?? setting.goal.enabled) ? resolveGoalCondition(goalArgs, opts.goalCondition, isGoalFlow ? null : setting.goal.condition) : void 0;
|
|
24053
24397
|
const verifyScope = opts.verifyScope ?? setting.verifyScope;
|
|
24398
|
+
const recordedMethod = readSpecMethod(spec.payload);
|
|
24399
|
+
const method = resolveMethod(opts.method ?? recordedMethod ?? setting.method);
|
|
24054
24400
|
if (agent === "codex") ensureCodexTrust(repoDir);
|
|
24055
24401
|
let baseSha;
|
|
24056
24402
|
if (resume?.worktreeKept) {
|
|
@@ -24067,6 +24413,10 @@ async function startSpecSession(spec, opts) {
|
|
|
24067
24413
|
data: { baseSha, headSha: null, startedAt: /* @__PURE__ */ new Date() }
|
|
24068
24414
|
});
|
|
24069
24415
|
}
|
|
24416
|
+
if (!recordedMethod) {
|
|
24417
|
+
const stamped = stampSpecMethod(spec.payload, method.id);
|
|
24418
|
+
if (stamped) await prisma.spec.update({ where: { id: spec.id }, data: { payload: stamped } });
|
|
24419
|
+
}
|
|
24070
24420
|
const brief = {
|
|
24071
24421
|
id: spec.id,
|
|
24072
24422
|
title: spec.title,
|
|
@@ -24081,14 +24431,15 @@ async function startSpecSession(spec, opts) {
|
|
|
24081
24431
|
prompt = startGoalPrompt(brief, branchTo, {
|
|
24082
24432
|
autonomy: opts.autonomy,
|
|
24083
24433
|
verifyScope,
|
|
24084
|
-
resume: resumeCtx
|
|
24434
|
+
resume: resumeCtx,
|
|
24435
|
+
method: method.id
|
|
24085
24436
|
});
|
|
24086
24437
|
} else if (isContinue) {
|
|
24087
|
-
prompt = continuePrompt(opts.flow, brief, branchTo, opts.autonomy, verifyScope);
|
|
24438
|
+
prompt = continuePrompt(opts.flow, brief, branchTo, opts.autonomy, verifyScope, method.id);
|
|
24088
24439
|
} else if (resumeCtx) {
|
|
24089
|
-
prompt = resumePrompt(opts.flow, brief, branchTo, resumeCtx, opts.autonomy, verifyScope);
|
|
24440
|
+
prompt = resumePrompt(opts.flow, brief, branchTo, resumeCtx, opts.autonomy, verifyScope, method.id);
|
|
24090
24441
|
} else {
|
|
24091
|
-
prompt = startPrompt(opts.flow, brief, branchTo, opts.autonomy, verifyScope);
|
|
24442
|
+
prompt = startPrompt(opts.flow, brief, branchTo, opts.autonomy, verifyScope, method.id);
|
|
24092
24443
|
}
|
|
24093
24444
|
const scopeEnv = { HANOMAN_BASE_SHA: baseSha, HANOMAN_VERIFY_SCOPE: verifyScope };
|
|
24094
24445
|
const s2 = createSession(spec.projectId, worktree, {
|
|
@@ -24106,12 +24457,30 @@ async function startSpecSession(spec, opts) {
|
|
|
24106
24457
|
return resume ? { id: s2.id, resumed: true } : { id: s2.id };
|
|
24107
24458
|
}
|
|
24108
24459
|
|
|
24460
|
+
// src/services/method-status.ts
|
|
24461
|
+
init_src();
|
|
24462
|
+
init_src2();
|
|
24463
|
+
init_pty();
|
|
24464
|
+
function methodStatusReport(env = process.env) {
|
|
24465
|
+
const scans = zAgent.options.map((a) => scanAgentSkills(a, env));
|
|
24466
|
+
return {
|
|
24467
|
+
agents: scans.map((s2) => ({ agent: s2.agent, home: s2.home, roots: s2.roots, skills: s2.skills.length })),
|
|
24468
|
+
methods: scans.flatMap((s2) => {
|
|
24469
|
+
const installed = { skills: s2.skills.map((k) => k.id), packages: s2.packages };
|
|
24470
|
+
return METHOD_IDS.map((id) => methodStatus(METHODS[id], s2.agent, installed));
|
|
24471
|
+
})
|
|
24472
|
+
};
|
|
24473
|
+
}
|
|
24474
|
+
function installCommand(m, agent, shell = shellBin()) {
|
|
24475
|
+
return [shell, "-lc", `${m.install[agent].join(" && ")}; exec '${shell}' -l`];
|
|
24476
|
+
}
|
|
24477
|
+
|
|
24109
24478
|
// src/services/session-worktree.ts
|
|
24110
|
-
import { resolve as
|
|
24479
|
+
import { resolve as resolve12, sep as sep3 } from "node:path";
|
|
24111
24480
|
function ownsWorktree(repoDir, cwd) {
|
|
24112
|
-
const base2 =
|
|
24113
|
-
const dir2 =
|
|
24114
|
-
const container =
|
|
24481
|
+
const base2 = resolve12(repoDir);
|
|
24482
|
+
const dir2 = resolve12(cwd);
|
|
24483
|
+
const container = resolve12(base2, ".worktrees");
|
|
24115
24484
|
return dir2 !== base2 && dir2 !== container && dir2.startsWith(container + sep3);
|
|
24116
24485
|
}
|
|
24117
24486
|
|
|
@@ -24196,6 +24565,8 @@ async function terminal_default(app2) {
|
|
|
24196
24565
|
// SPEC-338 · ADR-0074
|
|
24197
24566
|
verifyScope: parsed.data.verifyScope,
|
|
24198
24567
|
// SPEC-376 · ADR-0080
|
|
24568
|
+
method: parsed.data.method,
|
|
24569
|
+
// SPEC-734 · ADR-0113
|
|
24199
24570
|
force: parsed.data.force
|
|
24200
24571
|
// SPEC-447 · ADR-0093
|
|
24201
24572
|
});
|
|
@@ -24214,6 +24585,13 @@ async function terminal_default(app2) {
|
|
|
24214
24585
|
if ("shell" in parsed.data) {
|
|
24215
24586
|
const repoDir2 = await resolveRepoDir(project.id);
|
|
24216
24587
|
if (!repoDir2) return reply.code(400).send({ error: `project "${project.id}" belum di-bind ke checkout lokal`, needsBind: true });
|
|
24588
|
+
const inst = parsed.data.install;
|
|
24589
|
+
if (inst) {
|
|
24590
|
+
const m = METHODS[inst.method];
|
|
24591
|
+
if (!m) return reply.code(400).send({ error: `metode "${inst.method}" tak dikenal` });
|
|
24592
|
+
const s4 = createSession(project.id, repoDir2, { command: installCommand(m, inst.agent) });
|
|
24593
|
+
return reply.code(201).send({ id: s4.id });
|
|
24594
|
+
}
|
|
24217
24595
|
const s3 = createSession(project.id, repoDir2, { command: [shellBin()] });
|
|
24218
24596
|
return reply.code(201).send({ id: s3.id });
|
|
24219
24597
|
}
|
|
@@ -24382,7 +24760,7 @@ async function terminal_default(app2) {
|
|
|
24382
24760
|
if (!s2) return { ok: false, code: 404, msg: "not found" };
|
|
24383
24761
|
const repoDir = await resolveRepoDir(s2.projectId);
|
|
24384
24762
|
if (!repoDir) return { ok: false, code: 409, msg: "project belum punya repoDir" };
|
|
24385
|
-
if (!s2.cwd.includes("/.worktrees/") || !
|
|
24763
|
+
if (!s2.cwd.includes("/.worktrees/") || !existsSync8(s2.cwd))
|
|
24386
24764
|
return { ok: false, code: 409, msg: "belum ada worktree untuk di-review \u2014 jalankan/lanjutkan sesi dulu" };
|
|
24387
24765
|
return { ok: true, id: s2.id, repoDir };
|
|
24388
24766
|
};
|
|
@@ -24474,8 +24852,8 @@ async function terminal_default(app2) {
|
|
|
24474
24852
|
// src/routes/vps.ts
|
|
24475
24853
|
init_db();
|
|
24476
24854
|
init_src();
|
|
24477
|
-
import { readFileSync as
|
|
24478
|
-
import { homedir as
|
|
24855
|
+
import { readFileSync as readFileSync10, existsSync as existsSync11 } from "node:fs";
|
|
24856
|
+
import { homedir as homedir6 } from "node:os";
|
|
24479
24857
|
|
|
24480
24858
|
// src/vps/catalog/catalog.data.ts
|
|
24481
24859
|
var RAW_SECTIONS = [{ "id": "ssh", "title": "SSH Hardening", "icon": "\u{1F511}", "count": 14 }, { "id": "firewall", "title": "Firewall & Network", "icon": "\u{1F525}", "count": 14 }, { "id": "aapanel", "title": "aaPanel Security", "icon": "\u{1F5A5}\uFE0F", "count": 14 }, { "id": "user", "title": "User & Permission Management", "icon": "\u{1F464}", "count": 14 }, { "id": "system", "title": "System Hardening (OS Level)", "icon": "\u2699\uFE0F", "count": 16 }, { "id": "webserver", "title": "Web Server Hardening", "icon": "\u{1F310}", "count": 14 }, { "id": "database", "title": "Database Security", "icon": "\u{1F5C4}\uFE0F", "count": 15 }, { "id": "ssl", "title": "SSL/TLS & Enkripsi", "icon": "\u{1F512}", "count": 13 }, { "id": "ids", "title": "Intrusion Detection & Prevention", "icon": "\u{1F6A8}", "count": 9 }, { "id": "logging", "title": "Logging & Monitoring", "icon": "\u{1F4CA}", "count": 13 }, { "id": "backup", "title": "Backup & Disaster Recovery", "icon": "\u{1F4BE}", "count": 12 }, { "id": "ddos", "title": "DDoS Protection", "icon": "\u{1F30A}", "count": 11 }, { "id": "kernel", "title": "Kernel Hardening", "icon": "\u{1F9E0}", "count": 12 }, { "id": "malware", "title": "File Integrity & Malware Detection", "icon": "\u{1F9A0}", "count": 7 }, { "id": "enterprise", "title": "Advanced Enterprise & Government Level", "icon": "\u{1F3E2}", "count": 31 }, { "id": "checklist", "title": "Checklist Rutin", "icon": "\u2705", "count": 23 }];
|
|
@@ -24557,14 +24935,14 @@ var _map = new Map(CATALOG.map((c) => [c.id, c]));
|
|
|
24557
24935
|
var byId = (id) => _map.get(id);
|
|
24558
24936
|
|
|
24559
24937
|
// src/services/vps-remediate.ts
|
|
24560
|
-
import { readFileSync as
|
|
24938
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
24561
24939
|
|
|
24562
24940
|
// src/services/vps-ssh.ts
|
|
24563
24941
|
init_config2();
|
|
24564
24942
|
import { spawn as spawn3 } from "node:child_process";
|
|
24565
24943
|
import { mkdtempSync, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
24566
24944
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
24567
|
-
import { dirname as
|
|
24945
|
+
import { dirname as dirname11, join as join7 } from "node:path";
|
|
24568
24946
|
var sshBin = () => effectiveStr("HANOMAN_SSH_BIN") ?? "ssh";
|
|
24569
24947
|
function consoleArgv(t) {
|
|
24570
24948
|
return [
|
|
@@ -24579,8 +24957,8 @@ function consoleArgv(t) {
|
|
|
24579
24957
|
];
|
|
24580
24958
|
}
|
|
24581
24959
|
function askpassScript() {
|
|
24582
|
-
const dir2 = mkdtempSync(
|
|
24583
|
-
const path =
|
|
24960
|
+
const dir2 = mkdtempSync(join7(tmpdir3(), "hanoman-askpass-"));
|
|
24961
|
+
const path = join7(dir2, "askpass.sh");
|
|
24584
24962
|
writeFileSync4(path, `#!/bin/sh
|
|
24585
24963
|
printf '%s' "$HANOMAN_SSH_PASSWORD"
|
|
24586
24964
|
`, { mode: 448 });
|
|
@@ -24614,14 +24992,14 @@ function sshExec(t, remoteCmd, opts = {}) {
|
|
|
24614
24992
|
SSH_ASKPASS_REQUIRE: "force",
|
|
24615
24993
|
HANOMAN_SSH_PASSWORD: opts.password
|
|
24616
24994
|
} : process.env;
|
|
24617
|
-
return new Promise((
|
|
24995
|
+
return new Promise((resolve16) => {
|
|
24618
24996
|
const p = spawn3(sshBin(), args, { stdio: ["pipe", "pipe", "pipe"], env });
|
|
24619
24997
|
let out3 = "";
|
|
24620
24998
|
const timer7 = setTimeout(() => p.kill("SIGKILL"), opts.timeoutMs ?? 6e4);
|
|
24621
24999
|
const done = (r) => {
|
|
24622
25000
|
clearTimeout(timer7);
|
|
24623
|
-
if (askpass) rmSync4(
|
|
24624
|
-
|
|
25001
|
+
if (askpass) rmSync4(dirname11(askpass), { recursive: true, force: true });
|
|
25002
|
+
resolve16(r);
|
|
24625
25003
|
};
|
|
24626
25004
|
p.stdout.on("data", (d) => {
|
|
24627
25005
|
out3 += d;
|
|
@@ -24640,17 +25018,17 @@ function sshExec(t, remoteCmd, opts = {}) {
|
|
|
24640
25018
|
|
|
24641
25019
|
// src/services/vps-audit.ts
|
|
24642
25020
|
init_db();
|
|
24643
|
-
import { readFileSync as
|
|
24644
|
-
import { join as
|
|
25021
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
25022
|
+
import { join as join9 } from "node:path";
|
|
24645
25023
|
|
|
24646
25024
|
// src/runner/deps.ts
|
|
24647
|
-
import { existsSync as
|
|
24648
|
-
import { dirname as
|
|
25025
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
25026
|
+
import { dirname as dirname12, join as join8 } from "node:path";
|
|
24649
25027
|
function repoRootFrom(startDir) {
|
|
24650
25028
|
let dir2 = startDir;
|
|
24651
25029
|
for (let i = 0; i < 8; i++) {
|
|
24652
|
-
if (
|
|
24653
|
-
const parent =
|
|
25030
|
+
if (existsSync9(join8(dir2, "pnpm-workspace.yaml"))) return dir2;
|
|
25031
|
+
const parent = dirname12(dir2);
|
|
24654
25032
|
if (parent === dir2) break;
|
|
24655
25033
|
dir2 = parent;
|
|
24656
25034
|
}
|
|
@@ -24754,7 +25132,7 @@ function parseHealth(out3) {
|
|
|
24754
25132
|
return { uptime: h.uptime ?? "", disk: h.disk ?? "", mem: h.mem ?? "", load: h.load ?? "" };
|
|
24755
25133
|
}
|
|
24756
25134
|
var target = (v) => ({ host: v.host, port: v.port, user: v.user, keyPath: v.keyPath });
|
|
24757
|
-
var scriptPath = (f) =>
|
|
25135
|
+
var scriptPath = (f) => join9(repoRoot(), "server", "scripts", "vps", f);
|
|
24758
25136
|
async function itemStatesOf(vpsId) {
|
|
24759
25137
|
const rows = await prisma.vpsItemState.findMany({ where: { vpsId } });
|
|
24760
25138
|
return Object.fromEntries(rows.map((s2) => [s2.itemId, { na: s2.na, attested: s2.attested }]));
|
|
@@ -24763,7 +25141,7 @@ async function runAudit(v) {
|
|
|
24763
25141
|
const r = await sshExec(
|
|
24764
25142
|
target(v),
|
|
24765
25143
|
"sudo -n bash -s",
|
|
24766
|
-
{ stdin:
|
|
25144
|
+
{ stdin: readFileSync7(scriptPath("audit.sh"), "utf8"), timeoutMs: 6e4 }
|
|
24767
25145
|
);
|
|
24768
25146
|
const audit = parseAudit(r.out);
|
|
24769
25147
|
if (r.code !== 0 || audit.length === 0) return { ok: false, out: r.out };
|
|
@@ -24822,7 +25200,7 @@ async function remediate(v, items, dryRun) {
|
|
|
24822
25200
|
const r = await sshExec(
|
|
24823
25201
|
v,
|
|
24824
25202
|
`sudo -n env ITEMS=${items.join(",")} SSH_PORT=${v.port} DRY_RUN=${dryRun ? "1" : ""} bash -s`,
|
|
24825
|
-
{ stdin:
|
|
25203
|
+
{ stdin: readFileSync8(scriptPath("remediate.sh"), "utf8"), timeoutMs: 3e5 }
|
|
24826
25204
|
);
|
|
24827
25205
|
return { ok: r.code === 0, steps: parseSteps(r.out), out: r.out };
|
|
24828
25206
|
}
|
|
@@ -24901,15 +25279,15 @@ async function buildChecklist(vpsId) {
|
|
|
24901
25279
|
// src/services/vps-key.ts
|
|
24902
25280
|
init_config2();
|
|
24903
25281
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
24904
|
-
import { existsSync as
|
|
24905
|
-
import { homedir as
|
|
24906
|
-
import { join as
|
|
24907
|
-
var keyDir = () => effectiveStr("HANOMAN_SSH_KEY_DIR") ??
|
|
25282
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync9 } from "node:fs";
|
|
25283
|
+
import { homedir as homedir5 } from "node:os";
|
|
25284
|
+
import { join as join10 } from "node:path";
|
|
25285
|
+
var keyDir = () => effectiveStr("HANOMAN_SSH_KEY_DIR") ?? join10(homedir5(), ".hanoman");
|
|
24908
25286
|
function ensureHanomanKey() {
|
|
24909
25287
|
const dir2 = keyDir();
|
|
24910
|
-
const privPath =
|
|
25288
|
+
const privPath = join10(dir2, "id_ed25519");
|
|
24911
25289
|
const pubPath = `${privPath}.pub`;
|
|
24912
|
-
if (!
|
|
25290
|
+
if (!existsSync10(privPath)) {
|
|
24913
25291
|
mkdirSync8(dir2, { recursive: true, mode: 448 });
|
|
24914
25292
|
execFileSync2(
|
|
24915
25293
|
"ssh-keygen",
|
|
@@ -24917,7 +25295,7 @@ function ensureHanomanKey() {
|
|
|
24917
25295
|
{ stdio: ["ignore", "ignore", "pipe"] }
|
|
24918
25296
|
);
|
|
24919
25297
|
}
|
|
24920
|
-
return { privPath, pubPath, pub:
|
|
25298
|
+
return { privPath, pubPath, pub: readFileSync9(pubPath, "utf8").trim() };
|
|
24921
25299
|
}
|
|
24922
25300
|
|
|
24923
25301
|
// src/services/vps-bootstrap.ts
|
|
@@ -24936,7 +25314,7 @@ async function bootstrapKey(t, password) {
|
|
|
24936
25314
|
init_pty();
|
|
24937
25315
|
init_settings2();
|
|
24938
25316
|
function keyMissing(v) {
|
|
24939
|
-
return !!v.keyPath && !
|
|
25317
|
+
return !!v.keyPath && !existsSync11(v.keyPath);
|
|
24940
25318
|
}
|
|
24941
25319
|
async function vps_default(app2) {
|
|
24942
25320
|
app2.get("/vps", async () => prisma.vps.findMany({ orderBy: { createdAt: "asc" } }));
|
|
@@ -25094,7 +25472,7 @@ async function vps_default(app2) {
|
|
|
25094
25472
|
const r = await sshExec(
|
|
25095
25473
|
v,
|
|
25096
25474
|
`sudo -n env SSH_PORT=${v.port} SSH_USER=${v.user} bash -s`,
|
|
25097
|
-
{ stdin:
|
|
25475
|
+
{ stdin: readFileSync10(scriptPath("harden.sh"), "utf8"), timeoutMs: 3e5 }
|
|
25098
25476
|
);
|
|
25099
25477
|
if (r.code !== 0) return reply.code(502).send({ error: "harden gagal lewat ssh", transcript: r.out });
|
|
25100
25478
|
const verify = await sshExec(v, "true", { timeoutMs: 3e4 });
|
|
@@ -25119,7 +25497,7 @@ async function vps_default(app2) {
|
|
|
25119
25497
|
const v = await prisma.vps.findUnique({ where: { id: req.params.id } });
|
|
25120
25498
|
if (!v) return reply.code(404).send({ error: "not found" });
|
|
25121
25499
|
if (keyMissing(v)) return reply.code(409).send({ error: "key VPS tidak ada di mesin ini", keyMissing: true });
|
|
25122
|
-
const s2 = createSession(`vps-console:${v.id}`,
|
|
25500
|
+
const s2 = createSession(`vps-console:${v.id}`, homedir6(), { id: `vpsc-${v.id}`, command: consoleArgv(v) });
|
|
25123
25501
|
return reply.code(201).send({ id: s2.id });
|
|
25124
25502
|
});
|
|
25125
25503
|
app2.post("/vps/:id/session", async (req, reply) => {
|
|
@@ -25128,7 +25506,7 @@ async function vps_default(app2) {
|
|
|
25128
25506
|
if (keyMissing(v)) return reply.code(409).send({ error: "key VPS tidak ada di mesin ini", keyMissing: true });
|
|
25129
25507
|
const checks = v.audit ?? [];
|
|
25130
25508
|
const { model, effort } = await sessionModel();
|
|
25131
|
-
const s2 = createSession(`vps:${v.id}`,
|
|
25509
|
+
const s2 = createSession(`vps:${v.id}`, homedir6(), {
|
|
25132
25510
|
model,
|
|
25133
25511
|
effort,
|
|
25134
25512
|
prompt: [
|
|
@@ -25146,9 +25524,9 @@ async function vps_default(app2) {
|
|
|
25146
25524
|
// src/services/limits.ts
|
|
25147
25525
|
init_config2();
|
|
25148
25526
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
25149
|
-
import { readFileSync as
|
|
25150
|
-
import { homedir as
|
|
25151
|
-
import { join as
|
|
25527
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
25528
|
+
import { homedir as homedir7 } from "node:os";
|
|
25529
|
+
import { join as join11 } from "node:path";
|
|
25152
25530
|
var USAGE_URL = "https://api.anthropic.com/api/oauth/usage";
|
|
25153
25531
|
var TTL_MS2 = 3e4;
|
|
25154
25532
|
var lastOk = null;
|
|
@@ -25161,7 +25539,7 @@ var LABELS = {
|
|
|
25161
25539
|
var humanize = (k) => k.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
25162
25540
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
25163
25541
|
function credsFile() {
|
|
25164
|
-
return
|
|
25542
|
+
return join11(effectiveStr("CLAUDE_CONFIG_DIR") ?? join11(homedir7(), ".claude"), ".credentials.json");
|
|
25165
25543
|
}
|
|
25166
25544
|
function readAccessToken() {
|
|
25167
25545
|
if (process.platform === "darwin" && !effectiveStr("CLAUDE_CONFIG_DIR")) {
|
|
@@ -25177,7 +25555,7 @@ function readAccessToken() {
|
|
|
25177
25555
|
}
|
|
25178
25556
|
}
|
|
25179
25557
|
try {
|
|
25180
|
-
return JSON.parse(
|
|
25558
|
+
return JSON.parse(readFileSync11(credsFile(), "utf8"))?.claudeAiOauth?.accessToken ?? null;
|
|
25181
25559
|
} catch {
|
|
25182
25560
|
return null;
|
|
25183
25561
|
}
|
|
@@ -25234,15 +25612,15 @@ async function getLimits() {
|
|
|
25234
25612
|
|
|
25235
25613
|
// src/services/codex-limits.ts
|
|
25236
25614
|
import { open as open2, readdir as readdir2, stat } from "node:fs/promises";
|
|
25237
|
-
import { homedir as
|
|
25238
|
-
import { join as
|
|
25615
|
+
import { homedir as homedir8 } from "node:os";
|
|
25616
|
+
import { join as join12 } from "node:path";
|
|
25239
25617
|
var TTL_MS3 = 3e4;
|
|
25240
25618
|
var STALE_AFTER_MS = 12 * 36e5;
|
|
25241
25619
|
var TAIL_BYTES = 512 * 1024;
|
|
25242
25620
|
var MAX_FILES = 8;
|
|
25243
25621
|
var cache2 = null;
|
|
25244
25622
|
var freshUntil2 = 0;
|
|
25245
|
-
var codexSessionsDir = () =>
|
|
25623
|
+
var codexSessionsDir = () => join12(process.env.CODEX_HOME ?? join12(homedir8(), ".codex"), "sessions");
|
|
25246
25624
|
var UNAVAILABLE = { status: "unavailable", windows: [], fetchedAt: null, plan: null };
|
|
25247
25625
|
function windowLabel(minutes) {
|
|
25248
25626
|
if (minutes === 300) return "Sesi 5 jam";
|
|
@@ -25275,7 +25653,7 @@ async function recentRollouts(dir2) {
|
|
|
25275
25653
|
return [];
|
|
25276
25654
|
}
|
|
25277
25655
|
const stamped = await Promise.all(entries.map(async (rel) => {
|
|
25278
|
-
const full =
|
|
25656
|
+
const full = join12(dir2, rel);
|
|
25279
25657
|
try {
|
|
25280
25658
|
return { full, mtime: (await stat(full)).mtimeMs };
|
|
25281
25659
|
} catch {
|
|
@@ -25391,14 +25769,19 @@ async function codex(app2) {
|
|
|
25391
25769
|
app2.get("/codex/version", async () => codexVersionInfo());
|
|
25392
25770
|
}
|
|
25393
25771
|
|
|
25772
|
+
// src/routes/methods.ts
|
|
25773
|
+
async function methods(app2) {
|
|
25774
|
+
app2.get("/methods/status", async () => methodStatusReport());
|
|
25775
|
+
}
|
|
25776
|
+
|
|
25394
25777
|
// src/routes/update.ts
|
|
25395
25778
|
init_src();
|
|
25396
25779
|
|
|
25397
25780
|
// src/services/update.ts
|
|
25398
25781
|
init_src();
|
|
25399
25782
|
init_config2();
|
|
25400
|
-
import { readFileSync as
|
|
25401
|
-
import { resolve as
|
|
25783
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
25784
|
+
import { resolve as resolve13, dirname as dirname13 } from "node:path";
|
|
25402
25785
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
25403
25786
|
var UPDATE_COMMAND = "npm i -g hanoman@latest";
|
|
25404
25787
|
function composeUpdate(x) {
|
|
@@ -25423,10 +25806,10 @@ var lastFetchAt = 0;
|
|
|
25423
25806
|
var lastLatest = null;
|
|
25424
25807
|
var lastStatus = "unavailable";
|
|
25425
25808
|
function runningVersion() {
|
|
25426
|
-
const here =
|
|
25427
|
-
for (const p of [
|
|
25809
|
+
const here = dirname13(fileURLToPath3(import.meta.url));
|
|
25810
|
+
for (const p of [resolve13(here, "build-info.json"), resolve13(here, "../package.json")]) {
|
|
25428
25811
|
try {
|
|
25429
|
-
const v = JSON.parse(
|
|
25812
|
+
const v = JSON.parse(readFileSync12(p, "utf8")).version;
|
|
25430
25813
|
if (typeof v === "string" && v) return v;
|
|
25431
25814
|
} catch {
|
|
25432
25815
|
}
|
|
@@ -25774,7 +26157,7 @@ init_conflicts();
|
|
|
25774
26157
|
init_config2();
|
|
25775
26158
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
25776
26159
|
import { mkdir as mkdir2, writeFile as writeFile2, readFile as readFile4, unlink } from "node:fs/promises";
|
|
25777
|
-
import { join as
|
|
26160
|
+
import { join as join13, resolve as resolve14 } from "node:path";
|
|
25778
26161
|
var EXT = {
|
|
25779
26162
|
"image/png": ".png",
|
|
25780
26163
|
"image/jpeg": ".jpg",
|
|
@@ -25784,22 +26167,22 @@ function extFor(mimeType) {
|
|
|
25784
26167
|
return EXT[mimeType] ?? ".bin";
|
|
25785
26168
|
}
|
|
25786
26169
|
function uploadDir() {
|
|
25787
|
-
return
|
|
26170
|
+
return resolve14(effectiveStr("HANOMAN_UPLOAD_DIR") ?? join13(process.cwd(), "data", "uploads"));
|
|
25788
26171
|
}
|
|
25789
26172
|
async function saveUpload(buf, mimeType) {
|
|
25790
26173
|
const dir2 = uploadDir();
|
|
25791
26174
|
await mkdir2(dir2, { recursive: true });
|
|
25792
26175
|
const storageKey = `${randomUUID3()}${extFor(mimeType)}`;
|
|
25793
|
-
await writeFile2(
|
|
26176
|
+
await writeFile2(join13(dir2, storageKey), buf);
|
|
25794
26177
|
return { storageKey, size: buf.length };
|
|
25795
26178
|
}
|
|
25796
26179
|
async function readUpload(storageKey) {
|
|
25797
26180
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
25798
|
-
return readFile4(
|
|
26181
|
+
return readFile4(join13(uploadDir(), safe));
|
|
25799
26182
|
}
|
|
25800
26183
|
async function readUploadOrFetch(storageKey) {
|
|
25801
26184
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
25802
|
-
const target2 =
|
|
26185
|
+
const target2 = join13(uploadDir(), safe);
|
|
25803
26186
|
try {
|
|
25804
26187
|
return await readFile4(target2);
|
|
25805
26188
|
} catch {
|
|
@@ -25818,7 +26201,7 @@ async function readUploadOrFetch(storageKey) {
|
|
|
25818
26201
|
}
|
|
25819
26202
|
async function deleteUpload(storageKey) {
|
|
25820
26203
|
const safe = storageKey.replace(/[/\\]/g, "");
|
|
25821
|
-
await unlink(
|
|
26204
|
+
await unlink(join13(uploadDir(), safe)).catch(() => {
|
|
25822
26205
|
});
|
|
25823
26206
|
}
|
|
25824
26207
|
|
|
@@ -25949,10 +26332,10 @@ import { randomUUID as randomUUID5 } from "node:crypto";
|
|
|
25949
26332
|
init_config2();
|
|
25950
26333
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
25951
26334
|
import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile5, unlink as unlink2 } from "node:fs/promises";
|
|
25952
|
-
import { join as
|
|
26335
|
+
import { join as join14, resolve as resolve15, basename as basename3 } from "node:path";
|
|
25953
26336
|
var MAX_TRANSCRIPT_BYTES = 1024 * 1024;
|
|
25954
26337
|
function transcriptDir() {
|
|
25955
|
-
return
|
|
26338
|
+
return resolve15(effectiveStr("HANOMAN_TRANSCRIPT_DIR") ?? join14(process.cwd(), "data", "transcripts"));
|
|
25956
26339
|
}
|
|
25957
26340
|
function clamp(text) {
|
|
25958
26341
|
const buf = Buffer.from(text, "utf8");
|
|
@@ -25970,20 +26353,20 @@ async function saveTranscript(text) {
|
|
|
25970
26353
|
const dir2 = transcriptDir();
|
|
25971
26354
|
await mkdir3(dir2, { recursive: true });
|
|
25972
26355
|
const key = `${randomUUID4()}.log`;
|
|
25973
|
-
await writeFile3(
|
|
26356
|
+
await writeFile3(join14(dir2, key), body, "utf8");
|
|
25974
26357
|
return { key, bytes: Buffer.byteLength(body, "utf8"), truncated };
|
|
25975
26358
|
}
|
|
25976
26359
|
async function readTranscript(key) {
|
|
25977
26360
|
if (!key) return null;
|
|
25978
26361
|
try {
|
|
25979
|
-
return await readFile5(
|
|
26362
|
+
return await readFile5(join14(transcriptDir(), basename3(key)), "utf8");
|
|
25980
26363
|
} catch {
|
|
25981
26364
|
return null;
|
|
25982
26365
|
}
|
|
25983
26366
|
}
|
|
25984
26367
|
async function deleteTranscript(key) {
|
|
25985
26368
|
if (!key) return;
|
|
25986
|
-
await unlink2(
|
|
26369
|
+
await unlink2(join14(transcriptDir(), basename3(key))).catch(() => {
|
|
25987
26370
|
});
|
|
25988
26371
|
}
|
|
25989
26372
|
|
|
@@ -26406,7 +26789,7 @@ init_db();
|
|
|
26406
26789
|
|
|
26407
26790
|
// src/services/ticket-accept.ts
|
|
26408
26791
|
init_db();
|
|
26409
|
-
import { join as
|
|
26792
|
+
import { join as join15 } from "node:path";
|
|
26410
26793
|
async function materializeAttachments(atts) {
|
|
26411
26794
|
const ready = /* @__PURE__ */ new Set();
|
|
26412
26795
|
for (const a of atts) {
|
|
@@ -26421,7 +26804,7 @@ async function materializeAttachments(atts) {
|
|
|
26421
26804
|
var attachmentInstruction = (t, atts, ready) => {
|
|
26422
26805
|
if (atts.length === 0) return "Tanpa lampiran.";
|
|
26423
26806
|
const list2 = atts.map(
|
|
26424
|
-
(a) => ready.has(a.id) ? `- ${a.filename} (${a.mimeType}) \u2192 ${
|
|
26807
|
+
(a) => ready.has(a.id) ? `- ${a.filename} (${a.mimeType}) \u2192 ${join15(uploadDir(), a.storageKey)}` : `- ${a.filename} (${a.mimeType}) \u2192 BELUM TERUNDUH, ambil via GET /api/tickets/${t.id}/attachments/${a.id}`
|
|
26425
26808
|
).join("\n");
|
|
26426
26809
|
return `LAMPIRAN (${atts.length}) dari pelapor \u2014 biasanya screenshot yang menunjukkan masalah. PERIKSA setiap lampiran untuk memahami konteks keluhan sebelum bekerja; jangan berasumsi dari teks saja. Berkas ada di direktori upload server (baca langsung dengan tool Read):
|
|
26427
26810
|
${list2}
|
|
@@ -27112,7 +27495,7 @@ function leadPrompt(q, c) {
|
|
|
27112
27495
|
lines2.push(`Anggarkan begini: pakai paling banyak separuh waktu untuk mengumpulkan bukti, lalu **berhenti membaca** dan tulis jawabannya dengan apa yang sudah kamu punya. Kalau buktinya jadi tipis karena itu, turunkan \`confidence\`-nya \u2014 jangan menambah waktu baca.`);
|
|
27113
27496
|
lines2.push("");
|
|
27114
27497
|
lines2.push("## Cara kerja");
|
|
27115
|
-
lines2.push(
|
|
27498
|
+
lines2.push(`1. KUMPULKAN BUKTI DULU sebelum memutuskan, DI DALAM anggaran waktu di atas: \`internal/docs/**\` (Source of Truth) dan index-nya, ADR yang relevan, plan ${PLAN_DIRS.map((d) => `\`${d}/**\``).join(" / ")}, kode yang bersangkutan, dan riwayat git. Baca, jangan mengingat \u2014 tapi baca seperlunya, bukan sehabisnya.`);
|
|
27116
27499
|
lines2.push('2. Putuskan. Kalau setelah membaca kamu masih ragu, TETAP putuskan: pilih opsi yang PALING MUDAH DIBATALKAN, lalu tandai `confidence: "ragu"`. "Tidak tahu" bukan jawaban, dan meminta manusia memutuskan adalah persis keadaan yang kamu ada untuk menghapusnya. SATU pengecualian: bila jawabannya menuntut fakta konkret yang memang TIDAK ADA di repo maupun di konteks ini, isi `missing` dengan daftar pendek hal yang kurang \u2014 hal yang bisa disediakan seseorang, bukan keluhan \u2014 dan tetap tulis `decision` sebagai langkah paling aman sementara. `missing` yang terisi memaksa `confidence` jadi `ragu` dan memanggil operator; jangan memakainya untuk bukti yang cuma tipis.');
|
|
27117
27500
|
lines2.push("3. Rujuk bukti yang BENAR-BENAR kamu baca. Rujukan berupa path berkas relatif terhadap checkout, nomor ADR (`ADR-0091`), atau sha commit. Rujukan yang tak ada di repo akan dibuang server dan membuat jawabanmu tampak tanpa dasar.");
|
|
27118
27501
|
lines2.push("4. JANGAN membaca atau mengutip kredensial (isi `.env*`, token, kunci privat). Jejak keputusan disimpan di basis data; rahasia tak boleh mendarat di sana.");
|
|
@@ -27147,7 +27530,7 @@ function leadPrompt(q, c) {
|
|
|
27147
27530
|
|
|
27148
27531
|
// src/services/lead/verdict.ts
|
|
27149
27532
|
init_src();
|
|
27150
|
-
import { existsSync as
|
|
27533
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
27151
27534
|
import { isAbsolute as isAbsolute3, resolve as resolvePath, relative } from "node:path";
|
|
27152
27535
|
function extractJsonBlock(raw) {
|
|
27153
27536
|
const re = /```json\s*\n([\s\S]*?)```/g;
|
|
@@ -27185,7 +27568,7 @@ function keepExistingRefs(refs2, repoDir) {
|
|
|
27185
27568
|
const full = resolvePath(repoDir, ref);
|
|
27186
27569
|
const rel = relative(repoDir, full);
|
|
27187
27570
|
if (rel.startsWith("..")) continue;
|
|
27188
|
-
if (
|
|
27571
|
+
if (existsSync12(full)) out3.push(ref);
|
|
27189
27572
|
}
|
|
27190
27573
|
return [...new Set(out3)];
|
|
27191
27574
|
}
|
|
@@ -27371,7 +27754,7 @@ function leadFailureReason(agent, timeoutMs, err, stdout, stderr) {
|
|
|
27371
27754
|
function think(prompt, o) {
|
|
27372
27755
|
const bin = binFor(o.agent);
|
|
27373
27756
|
const args = leadArgv({ agent: o.agent, model: o.model, effort: o.effort, prompt });
|
|
27374
|
-
return new Promise((
|
|
27757
|
+
return new Promise((resolve16, reject2) => {
|
|
27375
27758
|
const child = execFile9(bin, args, {
|
|
27376
27759
|
cwd: o.cwd,
|
|
27377
27760
|
timeout: o.timeoutMs,
|
|
@@ -27384,7 +27767,7 @@ function think(prompt, o) {
|
|
|
27384
27767
|
reject2(new Error(leadFailureReason(o.agent, o.timeoutMs, err, stdout, stderr)));
|
|
27385
27768
|
return;
|
|
27386
27769
|
}
|
|
27387
|
-
|
|
27770
|
+
resolve16(stdout);
|
|
27388
27771
|
});
|
|
27389
27772
|
child.stdin?.end();
|
|
27390
27773
|
});
|
|
@@ -27421,9 +27804,9 @@ function acquire(cap, waitMs) {
|
|
|
27421
27804
|
return Promise.resolve();
|
|
27422
27805
|
}
|
|
27423
27806
|
const startedAt = Date.now();
|
|
27424
|
-
return new Promise((
|
|
27807
|
+
return new Promise((resolve16, reject2) => {
|
|
27425
27808
|
const w = {
|
|
27426
|
-
grant:
|
|
27809
|
+
grant: resolve16,
|
|
27427
27810
|
deny: reject2,
|
|
27428
27811
|
timer: setTimeout(() => {
|
|
27429
27812
|
const i = queue.indexOf(w);
|
|
@@ -28783,7 +29166,7 @@ init_db();
|
|
|
28783
29166
|
init_db();
|
|
28784
29167
|
import { execFile as execFile10 } from "node:child_process";
|
|
28785
29168
|
import { promisify as promisify9 } from "node:util";
|
|
28786
|
-
import { existsSync as
|
|
29169
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
28787
29170
|
|
|
28788
29171
|
// src/services/changelog/scrub.ts
|
|
28789
29172
|
var MERGE = /^merge\s+(branch|pull request|remote-tracking branch|tag)\b/i;
|
|
@@ -28850,7 +29233,7 @@ var US2 = "";
|
|
|
28850
29233
|
var RS = "";
|
|
28851
29234
|
var NO_REPO = "project ini belum ditautkan ke repo di mesin ini";
|
|
28852
29235
|
var NO_TAG = "repo project ini belum punya tag rilis";
|
|
28853
|
-
var usable = (repoDir) => !!repoDir &&
|
|
29236
|
+
var usable = (repoDir) => !!repoDir && existsSync13(repoDir);
|
|
28854
29237
|
async function listTags(repoDir) {
|
|
28855
29238
|
if (!usable(repoDir)) return { tags: [], head: null, reason: NO_REPO };
|
|
28856
29239
|
try {
|
|
@@ -29111,13 +29494,13 @@ init_db();
|
|
|
29111
29494
|
|
|
29112
29495
|
// src/services/agent-tool-catalog.ts
|
|
29113
29496
|
init_src();
|
|
29114
|
-
import { readFileSync as
|
|
29115
|
-
import { homedir as
|
|
29116
|
-
import { join as
|
|
29117
|
-
var home = () => process.env.HOME ||
|
|
29118
|
-
var
|
|
29497
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
29498
|
+
import { homedir as homedir9 } from "node:os";
|
|
29499
|
+
import { join as join16 } from "node:path";
|
|
29500
|
+
var home = () => process.env.HOME || homedir9();
|
|
29501
|
+
var readJson2 = (path) => {
|
|
29119
29502
|
try {
|
|
29120
|
-
return JSON.parse(
|
|
29503
|
+
return JSON.parse(readFileSync13(path, "utf8"));
|
|
29121
29504
|
} catch {
|
|
29122
29505
|
return null;
|
|
29123
29506
|
}
|
|
@@ -29130,7 +29513,7 @@ var serversOf = (node) => {
|
|
|
29130
29513
|
var codexServers = () => {
|
|
29131
29514
|
let text;
|
|
29132
29515
|
try {
|
|
29133
|
-
text =
|
|
29516
|
+
text = readFileSync13(join16(home(), ".codex", "config.toml"), "utf8");
|
|
29134
29517
|
} catch {
|
|
29135
29518
|
return [];
|
|
29136
29519
|
}
|
|
@@ -29143,12 +29526,12 @@ var codexServers = () => {
|
|
|
29143
29526
|
};
|
|
29144
29527
|
function mcpServerNames(repoDir) {
|
|
29145
29528
|
const names = [];
|
|
29146
|
-
const claudeJson =
|
|
29529
|
+
const claudeJson = readJson2(join16(home(), ".claude.json"));
|
|
29147
29530
|
names.push(...serversOf(claudeJson));
|
|
29148
29531
|
if (repoDir) {
|
|
29149
29532
|
const projects = claudeJson?.projects;
|
|
29150
29533
|
if (projects && typeof projects === "object") names.push(...serversOf(projects[repoDir]));
|
|
29151
|
-
names.push(...serversOf(
|
|
29534
|
+
names.push(...serversOf(readJson2(join16(repoDir, ".mcp.json"))));
|
|
29152
29535
|
}
|
|
29153
29536
|
names.push(...codexServers());
|
|
29154
29537
|
return [...new Set(names.filter(Boolean))].sort((a, b) => a.localeCompare(b));
|
|
@@ -29487,7 +29870,7 @@ function issuesFromRest(raw) {
|
|
|
29487
29870
|
}
|
|
29488
29871
|
var GH_FIELDS = "number,title,body,author,labels,url,state,createdAt,updatedAt";
|
|
29489
29872
|
var API2 = "https://api.github.com";
|
|
29490
|
-
var defaultRunGh = (args, env) => new Promise((
|
|
29873
|
+
var defaultRunGh = (args, env) => new Promise((resolve16, reject2) => {
|
|
29491
29874
|
execFile11(
|
|
29492
29875
|
args[0],
|
|
29493
29876
|
args.slice(1),
|
|
@@ -29495,7 +29878,7 @@ var defaultRunGh = (args, env) => new Promise((resolve15, reject2) => {
|
|
|
29495
29878
|
(err, stdout, stderr) => {
|
|
29496
29879
|
const e = err;
|
|
29497
29880
|
if (e && (e.code === "ENOENT" || e.code === "EACCES")) return reject2(e);
|
|
29498
|
-
|
|
29881
|
+
resolve16({ code: err ? Number(err.code ?? 1) : 0, stdout, stderr });
|
|
29499
29882
|
}
|
|
29500
29883
|
);
|
|
29501
29884
|
});
|
|
@@ -31034,7 +31417,7 @@ var PUBLIC = /* @__PURE__ */ new Set([
|
|
|
31034
31417
|
"GET /api/agent-integration.md"
|
|
31035
31418
|
]);
|
|
31036
31419
|
function buildApp({ requireAuth = true, agentDocFile } = {}) {
|
|
31037
|
-
const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(
|
|
31420
|
+
const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname14(fileURLToPath4(import.meta.url)), process.env, existsSync14);
|
|
31038
31421
|
const app2 = Fastify({ logger: false, trustProxy: true });
|
|
31039
31422
|
app2.addContentTypeParser("application/json", { parseAs: "string" }, (_req, body, done) => {
|
|
31040
31423
|
if (!body) return done(null, void 0);
|
|
@@ -31114,6 +31497,7 @@ function buildApp({ requireAuth = true, agentDocFile } = {}) {
|
|
|
31114
31497
|
await api.register(tickets_default);
|
|
31115
31498
|
await api.register(scheduler_default);
|
|
31116
31499
|
await api.register(codex);
|
|
31500
|
+
await api.register(methods);
|
|
31117
31501
|
await api.register(lead_default);
|
|
31118
31502
|
await api.register(custom_agents_default);
|
|
31119
31503
|
await api.register(githubIssues);
|
|
@@ -31124,7 +31508,7 @@ function buildApp({ requireAuth = true, agentDocFile } = {}) {
|
|
|
31124
31508
|
await api.register(client_accounts_default);
|
|
31125
31509
|
}, { prefix: "/api" });
|
|
31126
31510
|
if (process.env.NODE_ENV === "production") {
|
|
31127
|
-
const dist = pickWebDir(
|
|
31511
|
+
const dist = pickWebDir(dirname14(fileURLToPath4(import.meta.url)), process.env, existsSync14);
|
|
31128
31512
|
if (dist) {
|
|
31129
31513
|
app2.register(fastifyStatic, { root: dist });
|
|
31130
31514
|
app2.setNotFoundHandler((req, reply) => req.url.startsWith("/api") ? reply.code(404).send({ error: "not found" }) : reply.sendFile("index.html"));
|