hanoman 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -4151,7 +4151,7 @@ var init_enums = __esm({
4151
4151
  "use strict";
4152
4152
  init_zod();
4153
4153
  zStage = external_exports.enum(["brainstorming", "objective", "spec-ready", "planned", "executing", "done"]);
4154
- zSpecSource = external_exports.enum(["brief", "qa", "audit", "cross-audit", "help"]);
4154
+ zSpecSource = external_exports.enum(["brief", "qa", "audit", "cross-audit", "help", "goal"]);
4155
4155
  zDocStatus = external_exports.enum(["ok", "drift", "broken"]);
4156
4156
  zPriority = external_exports.enum(["tinggi", "sedang", "rendah"]);
4157
4157
  zProjectKind = external_exports.enum(["from-scratch", "existing"]);
@@ -4184,7 +4184,7 @@ function cmpVersion(a, b) {
4184
4184
  }
4185
4185
  return 0;
4186
4186
  }
4187
- var zProject, zBriefPayload, zQaPayload, zSpec, NOTIFY_SOUNDS, E_5_6, E_LUNA, E_BASE, CODEX_MODELS, RETIRED_CODEX_MODELS, zCodex, CODEX_DEFAULTS, zSourceCommon, zScheduler, SCHEDULER_DEFAULTS, zGoal, GOAL_DEFAULTS, zConflict, CONFLICT_DEFAULTS, zSetting, zNotification, zDocFile, zDeviceTokenView;
4187
+ var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpec, NOTIFY_SOUNDS, E_5_6, E_LUNA, E_BASE, CODEX_MODELS, RETIRED_CODEX_MODELS, zCodex, CODEX_DEFAULTS, zSourceCommon, zScheduler, SCHEDULER_DEFAULTS, zGoal, GOAL_DEFAULTS, zConflict, CONFLICT_DEFAULTS, zSetting, zNotification, zDocFile, zDeviceTokenView;
4188
4188
  var init_entities = __esm({
4189
4189
  "../shared/src/entities.ts"() {
4190
4190
  "use strict";
@@ -4223,6 +4223,12 @@ var init_entities = __esm({
4223
4223
  // SPEC-244 · qa dinaikkan dari audit → sinyal skip fase Audit (ADR-0059)
4224
4224
  fromErrorGroup: external_exports.string().optional()
4225
4225
  });
4226
+ zGoalPayload = external_exports.object({
4227
+ goal: external_exports.string(),
4228
+ done: external_exports.string(),
4229
+ constraints: external_exports.string(),
4230
+ priority: zPriority
4231
+ });
4226
4232
  zSpec = external_exports.object({
4227
4233
  id: external_exports.string(),
4228
4234
  projectId: external_exports.string(),
@@ -4232,7 +4238,8 @@ var init_entities = __esm({
4232
4238
  priority: zPriority,
4233
4239
  author: external_exports.string(),
4234
4240
  objective: external_exports.string(),
4235
- payload: external_exports.union([zBriefPayload, zQaPayload]).nullable(),
4241
+ payload: external_exports.union([zBriefPayload, zQaPayload, zGoalPayload]).nullable(),
4242
+ // SPEC-407 · +goal
4236
4243
  branchFrom: external_exports.string().nullable(),
4237
4244
  // SPEC-143 · null = default project (main)
4238
4245
  baseSha: external_exports.string().nullable()
@@ -4446,9 +4453,9 @@ var init_agent = __esm({
4446
4453
 
4447
4454
  // ../shared/src/dto.ts
4448
4455
  function flowForSource(source) {
4449
- return source === "qa" ? "qa" : source === "audit" ? "audit" : source === "cross-audit" ? "cross-audit" : "feature";
4456
+ return source === "qa" ? "qa" : source === "audit" ? "audit" : source === "cross-audit" ? "cross-audit" : source === "goal" ? "goal" : "feature";
4450
4457
  }
4451
- var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zCreateLink, zUpdateProject, zCreateSpec, zPatchSpec, zIntegrate, zSessionSummary, zProjectView, zSchedulerQueueItem, zSchedulerSourceView, zSchedulerSessionView, zSchedulerState, zFlow, zPrdBrief, zPrdDoc, zBreakdownItem, zBreakdownDoc, zBatchCreateSpec, zEscalationTarget, zEscalationPrefill, zAuditEscalation, zAuditEscalationView, zTerminalSession, zDocFileContent, zDocIndexCat, zDocIndex, HOST_RE, USER_RE, zCreateVps, zPatchVps, zLogin, zSignup, zChangePassword, zVpsCheck, zMarkNa, zMarkNaBulk, zAttest, zRemediate, zStackFrame, zSymbolicatedFrame, zSourceMapUpload, zIngestPayload, zErrorGroupView, zErrorEventView, zErrorGroupDetail, zIngestKeyView, zTicketView, zTicketAttachmentView, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus;
4458
+ var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zCreateLink, zUpdateProject, zCreateSpec, zPatchSpec, zIntegrate, zSessionSummary, zProjectView, zSchedulerQueueItem, zSchedulerSourceView, zSchedulerSessionView, zSchedulerState, zFlow, zPrdBrief, zPrdDoc, zBreakdownItem, zBreakdownDoc, zBatchCreateSpec, zEscalationTarget, zEscalationPrefill, zAuditEscalation, zAuditEscalationView, zTerminalSession, zDocFileContent, zDocIndexCat, zDocIndex, HOST_RE, USER_RE, zCreateVps, zPatchVps, zLogin, zSignup, zChangePassword, zVpsCheck, zMarkNa, zMarkNaBulk, zAttest, zRemediate, UPDATE_RESTART_EXIT, zUpdateApplyBody, zStackFrame, zSymbolicatedFrame, zSourceMapUpload, zIngestPayload, zErrorGroupView, zErrorEventView, zErrorGroupDetail, zIngestKeyView, zTicketView, zTicketAttachmentView, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus;
4452
4459
  var init_dto = __esm({
4453
4460
  "../shared/src/dto.ts"() {
4454
4461
  "use strict";
@@ -4517,11 +4524,12 @@ var init_dto = __esm({
4517
4524
  source: zSpecSource,
4518
4525
  title: external_exports.string().min(1),
4519
4526
  priority: zPriority,
4520
- payload: external_exports.union([zBriefPayload, zQaPayload]),
4527
+ payload: external_exports.union([zBriefPayload, zQaPayload, zGoalPayload]),
4521
4528
  branchFrom: external_exports.string().min(1).optional()
4522
4529
  }).superRefine((o, ctx) => {
4523
- const isQa = "severity" in o.payload;
4524
- if (o.source === "qa" !== isQa)
4530
+ const shape = "severity" in o.payload ? "qa" : "goal" in o.payload ? "goal" : "brief";
4531
+ const want = o.source === "qa" ? "qa" : o.source === "goal" ? "goal" : "brief";
4532
+ if (shape !== want)
4525
4533
  ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["payload"], message: "bentuk payload tak cocok dengan source" });
4526
4534
  });
4527
4535
  zPatchSpec = external_exports.object({
@@ -4531,7 +4539,8 @@ var init_dto = __esm({
4531
4539
  // SPEC-186 · edit konten selagi item belum dimulai. Ditolak server bila sudah mulai.
4532
4540
  title: external_exports.string().min(1).optional(),
4533
4541
  priority: zPriority.optional(),
4534
- payload: external_exports.union([zBriefPayload, zQaPayload]).optional()
4542
+ payload: external_exports.union([zBriefPayload, zQaPayload, zGoalPayload]).optional()
4543
+ // SPEC-407 · +goal
4535
4544
  });
4536
4545
  zIntegrate = external_exports.object({
4537
4546
  op: external_exports.enum(["merge", "rebase"]),
@@ -4596,7 +4605,7 @@ var init_dto = __esm({
4596
4605
  queue: external_exports.array(zSchedulerQueueItem),
4597
4606
  sessions: external_exports.array(zSchedulerSessionView)
4598
4607
  });
4599
- zFlow = external_exports.enum(["feature", "qa", "scaffold", "reverse", "prd", "audit", "breakdown", "cross-audit"]);
4608
+ zFlow = external_exports.enum(["feature", "qa", "scaffold", "reverse", "prd", "audit", "breakdown", "cross-audit", "goal"]);
4600
4609
  zPrdBrief = external_exports.object({
4601
4610
  title: external_exports.string().min(1),
4602
4611
  context: external_exports.string(),
@@ -4742,6 +4751,8 @@ var init_dto = __esm({
4742
4751
  });
4743
4752
  zAttest = external_exports.object({ note: external_exports.string().max(500).optional() });
4744
4753
  zRemediate = external_exports.object({ items: external_exports.array(external_exports.string()).min(1).max(64) });
4754
+ UPDATE_RESTART_EXIT = 75;
4755
+ zUpdateApplyBody = external_exports.object({ confirm: external_exports.boolean().optional() });
4745
4756
  zStackFrame = external_exports.object({
4746
4757
  function: external_exports.string().max(500).optional(),
4747
4758
  filename: external_exports.string().max(2e3).optional(),
@@ -5520,6 +5531,22 @@ var init_verify_scope = __esm({
5520
5531
  }
5521
5532
  });
5522
5533
 
5534
+ // ../runner/src/goal-spec.ts
5535
+ function readGoalPayload(payload) {
5536
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return null;
5537
+ const p = payload;
5538
+ const goal = str(p.goal);
5539
+ if (!goal) return null;
5540
+ return { goal, done: str(p.done), constraints: str(p.constraints) };
5541
+ }
5542
+ var str;
5543
+ var init_goal_spec = __esm({
5544
+ "../runner/src/goal-spec.ts"() {
5545
+ "use strict";
5546
+ str = (v) => typeof v === "string" ? v.trim() : "";
5547
+ }
5548
+ });
5549
+
5523
5550
  // ../runner/src/prompt.ts
5524
5551
  function startPrompt(flow, spec, branchTo, autonomy, verifyScope) {
5525
5552
  const detail = spec.payload ? `
@@ -5560,7 +5587,7 @@ Detail: ${JSON.stringify(spec.payload)}` : "";
5560
5587
  const auditDecided = resume.recorded.some((line) => line.startsWith("Audit "));
5561
5588
  return [
5562
5589
  `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.`,
5563
- resumeClause(resume, branchTo),
5590
+ resumeClause(resume, branchTo, PIPELINES[flow].includes("Plan")),
5564
5591
  phaseInstruction(PIPELINES[flow]),
5565
5592
  auditDecided ? "" : auditDecisionInstruction(flow),
5566
5593
  autonomyClause(autonomy),
@@ -5572,6 +5599,27 @@ Judul: ${spec.title}
5572
5599
  Objective: ${spec.objective}${detail}`
5573
5600
  ].filter(Boolean).join("\n\n");
5574
5601
  }
5602
+ function startGoalPrompt(spec, branchTo, opts = {}) {
5603
+ const g = readGoalPayload(spec.payload);
5604
+ const detail = [
5605
+ `Goal: ${g?.goal ?? spec.objective}`,
5606
+ g?.done ? `Selesai bila: ${g.done}` : "",
5607
+ g?.constraints ? `Batasan: ${g.constraints}` : ""
5608
+ ].filter(Boolean).join("\n");
5609
+ return [
5610
+ "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.",
5611
+ opts.resume ? resumeClause(opts.resume, branchTo, false) : "",
5612
+ detail,
5613
+ phaseInstruction(PIPELINES.goal),
5614
+ "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.",
5615
+ autonomyClause(opts.autonomy),
5616
+ scopeClause("goal", opts.verifyScope),
5617
+ skillInstruction(PIPELINES.goal),
5618
+ `Setelah fase terakhir: commit, lalu \`git push origin HEAD:refs/heads/${branchTo}\`. Worktree ini detached HEAD \u2014 itu memang disengaja.`,
5619
+ `Backlog item ${spec.id} \xB7 sumber ${spec.source} \xB7 prioritas ${spec.priority}
5620
+ Judul: ${spec.title}`
5621
+ ].filter(Boolean).join("\n\n");
5622
+ }
5575
5623
  function startProjectPrompt(flow, project, branchTo) {
5576
5624
  return [
5577
5625
  `hanoman ${flow}. Susun Source of Truth repo ini dari kodenya di internal/docs/**, mengikuti STANDAR DOCS di bagian bawah prompt ini.`,
@@ -5676,12 +5724,13 @@ Judul: ${spec.title}
5676
5724
  Objective: ${spec.objective}${detail}`
5677
5725
  ].filter(Boolean).join("\n\n");
5678
5726
  }
5679
- var PIPELINES, AUTONOMY_CLAUSE, AUTONOMY_CLAUSE_FULL, autonomyClause, phaseInstruction, PHASE_SKILLS, skillInstruction, auditDecisionInstruction, ESCALATION_CONTRACT, auditOnlyInstruction, auditContinuationInstruction, scopeClause, resumeClause, RESUMED_WORKTREE_NOTE, REVERSE_PHASE_GUIDE, SCAFFOLD_PHASE_GUIDE, projectLine, crossAuditLogGuide, CROSS_AUDIT_FOCUS;
5727
+ var PIPELINES, AUTONOMY_CLAUSE, AUTONOMY_CLAUSE_FULL, autonomyClause, phaseInstruction, PHASE_SKILLS, skillInstruction, auditDecisionInstruction, ESCALATION_CONTRACT, auditOnlyInstruction, auditContinuationInstruction, writesCode, scopeClause, resumeClause, RESUMED_WORKTREE_NOTE, REVERSE_PHASE_GUIDE, SCAFFOLD_PHASE_GUIDE, projectLine, crossAuditLogGuide, CROSS_AUDIT_FOCUS;
5680
5728
  var init_prompt = __esm({
5681
5729
  "../runner/src/prompt.ts"() {
5682
5730
  "use strict";
5683
5731
  init_reverse_standard();
5684
5732
  init_verify_scope();
5733
+ init_goal_spec();
5685
5734
  PIPELINES = {
5686
5735
  feature: ["Brainstorm", "Objective", "Spec", "Plan", "Execute"],
5687
5736
  qa: ["Audit", "Spec", "Plan", "Execute"],
@@ -5692,7 +5741,11 @@ var init_prompt = __esm({
5692
5741
  breakdown: ["Analisis", "Breakdown"],
5693
5742
  // SPEC-337 · ADR-0075 · audit lintas project: fase & stage-map identik audit-only, scope-nya
5694
5743
  // yang berbeda (project utama + tetangga ProjectLink).
5695
- "cross-audit": ["Audit", "Laporan"]
5744
+ "cross-audit": ["Audit", "Laporan"],
5745
+ // SPEC-407 · ADR-0089 · backlog goal: tak ada fase perencanaan sama sekali. `Goal` = kerjakan,
5746
+ // `Verifikasi` = buktikan. Kedua nama unik lintas PIPELINES — syarat peta REACHED di server,
5747
+ // yang berkunci nama fase saja.
5748
+ goal: ["Goal", "Verifikasi"]
5696
5749
  };
5697
5750
  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.';
5698
5751
  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.";
@@ -5712,7 +5765,10 @@ Execute BELUM selesai selama plan (\`docs/superpowers/plans/**\`) masih punya ta
5712
5765
  "superpowers:executing-plans",
5713
5766
  "superpowers:test-driven-development",
5714
5767
  "superpowers:verification-before-completion"
5715
- ]
5768
+ ],
5769
+ // SPEC-407 · fase `Goal` sengaja TANPA skill: seluruh inti flow ini adalah membebaskan sesi
5770
+ // dari proses kaku. Yang tetap dijaga cuma pintu keluarnya.
5771
+ Verifikasi: ["superpowers:verification-before-completion"]
5716
5772
  };
5717
5773
  skillInstruction = (phases) => {
5718
5774
  const lines2 = phases.filter((p) => PHASE_SKILLS[p]).map((p) => `- ${p}: ${PHASE_SKILLS[p].join(", ")}`);
@@ -5752,17 +5808,19 @@ ${lines2.join("\n")}` : "";
5752
5808
  return `Backlog brief ini LANJUTAN dari audit ${fromAudit}. Worktree ini lahir dari branch audit itu, jadi dokumen audit sudah ada di ${doc}. BACA dokumen itu lebih dulu dan pakai sebagai bahan fase Brainstorm & Objective \u2014 temuannya sudah terbukti, jangan menginvestigasi ulang dari nol. Semua fase tetap dijalankan: dokumen audit memuat TEMUAN, bukan bentuk solusi, jadi perancangan fitur tetap pekerjaanmu.`;
5753
5809
  return `Backlog qa ini LANJUTAN dari audit ${fromAudit}. Worktree ini lahir dari branch audit itu, jadi dokumen audit sudah ada di ${doc}. JANGAN mengulang investigasi fase Audit dari nol \u2014 baca dokumen audit itu sebagai temuan, tandai fase Audit dilewati (\`echo "Audit skipped" >> "$HANOMAN_PHASE_FILE"\`), lalu ambil keputusan pasca-Audit: perbaikan jelas & kecil \u2192 langsung Execute (tandai \`Spec skipped\` dan \`Plan skipped\` bila sesuai); selain itu Spec \u2192 Plan \u2192 Execute penuh.`;
5754
5810
  };
5755
- scopeClause = (flow, scope) => scope && PIPELINES[flow].includes("Execute") ? verifyScopeClause(scope) : "";
5756
- resumeClause = (r, branchTo) => {
5811
+ writesCode = (flow) => PIPELINES[flow].includes("Execute") || PIPELINES[flow].includes("Goal");
5812
+ scopeClause = (flow, scope) => scope && writesCode(flow) ? verifyScopeClause(scope) : "";
5813
+ resumeClause = (r, branchTo, hasPlan = true) => {
5757
5814
  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.";
5758
- const lanjut = r.next ? `Lanjutkan dari fase: ${r.next}.` : "Semua fase sudah tercatat. Periksa apakah plan di `docs/superpowers/plans/**` masih menyisakan task `- [ ]` dan selesaikan sisanya; bila sudah bersih, tinggal commit & push.";
5815
+ const lanjut = r.next ? `Lanjutkan dari fase: ${r.next}.` : hasPlan ? "Semua fase sudah tercatat. Periksa apakah plan di `docs/superpowers/plans/**` masih menyisakan task `- [ ]` dan selesaikan sisanya; bila sudah bersih, tinggal commit & push." : "Semua fase sudah tercatat. Buktikan sekali lagi goal-nya benar-benar tercapai, lalu commit & push.";
5759
5816
  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.`;
5817
+ const baca = hasPlan ? "Sebelum menulis apa pun: baca `git log --oneline` dan `git status`, lalu plan di `docs/superpowers/plans/**` untuk backlog item ini (`- [x]` sudah selesai, `- [ ]` belum). Jangan menulis ulang yang sudah ada." : "Sebelum menulis apa pun: baca `git log --oneline` dan `git status` untuk melihat apa yang sudah dikerjakan. Jangan menulis ulang yang sudah ada.";
5760
5818
  return [
5761
5819
  "Sesi ini MELANJUTKAN pekerjaan sesi sebelumnya untuk backlog item yang sama \u2014 bukan memulai dari nol.",
5762
5820
  fase,
5763
5821
  lanjut,
5764
5822
  worktree,
5765
- "Sebelum menulis apa pun: baca `git log --oneline` dan `git status`, lalu plan di `docs/superpowers/plans/**` untuk backlog item ini (`- [x]` sudah selesai, `- [ ]` belum). Jangan menulis ulang yang sudah ada."
5823
+ baca
5766
5824
  ].join(" ");
5767
5825
  };
5768
5826
  RESUMED_WORKTREE_NOTE = "Catatan: worktree ini BUKAN kosong \u2014 ia berisi pekerjaan sesi sebelumnya untuk tugas yang sama (sesi itu berhenti sebelum selesai). Baca `git log --oneline` dan `git status` lebih dulu, lalu lanjutkan dari sana alih-alih menulis ulang semuanya dari awal.";
@@ -5930,7 +5988,21 @@ var init_settings = __esm({
5930
5988
  });
5931
5989
 
5932
5990
  // ../runner/src/goal.ts
5933
- function defaultGoalCondition({ flow, specId, branchTo }) {
5991
+ function goalFlowCondition(specId, branchTo, spec) {
5992
+ const g = readGoalPayload(spec?.payload);
5993
+ const goal = g?.goal || (spec?.objective ?? "").trim() || "(goal tak tercatat di backlog item)";
5994
+ const bukti = g?.done || goal;
5995
+ return [
5996
+ `Sesi goal hanoman ${specId}. GOAL: ${goal}`,
5997
+ "Sesi ini hanya boleh berhenti bila transkrip TERBARU memuat bukti langsung semua hal berikut:",
5998
+ `1. goal tercapai \u2014 ${bukti};`,
5999
+ `2. output \`cat "$HANOMAN_PHASE_FILE"\` yang memuat satu baris untuk SETIAP fase ${PIPELINES.goal.join(" \u2192 ")}, masing-masing berakhiran \`done\` atau \`skipped\`;`,
6000
+ `3. output \`git push origin HEAD:refs/heads/${branchTo}\` yang SUKSES sesudah commit terakhir.`,
6001
+ "Bila salah satu bukti tak ada di transkrip terbaru, kondisi BELUM terpenuhi: jalankan perintah verifikasinya, tuntaskan yang masih kurang, lalu lanjutkan \u2014 jangan berhenti."
6002
+ ].join("\n");
6003
+ }
6004
+ function defaultGoalCondition({ flow, specId, branchTo, spec }) {
6005
+ if (flow === "goal") return goalFlowCondition(specId, branchTo, spec);
5934
6006
  const phases = PIPELINES[flow];
5935
6007
  const planGate = phases.includes("Plan") && phases.includes("Execute");
5936
6008
  const clauses = [
@@ -5964,6 +6036,7 @@ var init_goal = __esm({
5964
6036
  "../runner/src/goal.ts"() {
5965
6037
  "use strict";
5966
6038
  init_prompt();
6039
+ init_goal_spec();
5967
6040
  GOAL_MAX = 4e3;
5968
6041
  goalOneLine = (cond) => cond.replace(/\s+/g, " ").trim();
5969
6042
  GOAL_CHUNK = 500;
@@ -6128,6 +6201,7 @@ var init_src2 = __esm({
6128
6201
  init_git();
6129
6202
  init_settings();
6130
6203
  init_goal();
6204
+ init_goal_spec();
6131
6205
  init_codex_settings();
6132
6206
  init_agent_cli();
6133
6207
  init_verify_scope();
@@ -6199,7 +6273,8 @@ function readPhases(file, flow) {
6199
6273
  function stageFor(phases) {
6200
6274
  let best = -1;
6201
6275
  for (const p of phases) {
6202
- if (p.name === "Execute" && p.state === "active") best = Math.max(best, STAGES.indexOf("executing"));
6276
+ if ((p.name === "Execute" || p.name === "Goal") && p.state === "active")
6277
+ best = Math.max(best, STAGES.indexOf("executing"));
6203
6278
  if (p.state !== "done" && p.state !== "skipped") continue;
6204
6279
  const s = REACHED[p.name];
6205
6280
  if (s) best = Math.max(best, STAGES.indexOf(s));
@@ -6244,7 +6319,11 @@ var init_session_phases = __esm({
6244
6319
  Spec: "spec-ready",
6245
6320
  Plan: "planned",
6246
6321
  Laporan: "done",
6247
- Execute: "done"
6322
+ Execute: "done",
6323
+ // SPEC-407 · ADR-0089 · flow goal (Goal → Verifikasi): fase kerja mencapai `executing`, fase
6324
+ // verifikasi yang mencapai `done`. Kedua nama unik lintas PIPELINES — peta ini berkunci nama.
6325
+ Goal: "executing",
6326
+ Verifikasi: "done"
6248
6327
  };
6249
6328
  }
6250
6329
  });
@@ -12269,7 +12348,7 @@ var require_parseParams = __commonJS({
12269
12348
  var STATE_VALUE = 1;
12270
12349
  var STATE_CHARSET = 2;
12271
12350
  var STATE_LANG = 3;
12272
- function parseParams(str) {
12351
+ function parseParams(str2) {
12273
12352
  const res = [];
12274
12353
  let state = STATE_KEY;
12275
12354
  let charset = "";
@@ -12277,9 +12356,9 @@ var require_parseParams = __commonJS({
12277
12356
  let escaping = false;
12278
12357
  let p = 0;
12279
12358
  let tmp = "";
12280
- const len = str.length;
12359
+ const len = str2.length;
12281
12360
  for (var i = 0; i < len; ++i) {
12282
- const char = str[i];
12361
+ const char = str2[i];
12283
12362
  if (char === "\\" && inquote) {
12284
12363
  if (escaping) {
12285
12364
  escaping = false;
@@ -12292,7 +12371,7 @@ var require_parseParams = __commonJS({
12292
12371
  if (inquote) {
12293
12372
  inquote = false;
12294
12373
  state = STATE_KEY;
12295
- while (i + 1 < len && str[i + 1] !== ";") {
12374
+ while (i + 1 < len && str2[i + 1] !== ";") {
12296
12375
  ++i;
12297
12376
  }
12298
12377
  } else {
@@ -12811,29 +12890,29 @@ var require_Decoder = __commonJS({
12811
12890
  function Decoder() {
12812
12891
  this.buffer = void 0;
12813
12892
  }
12814
- Decoder.prototype.write = function(str) {
12815
- str = str.replace(RE_PLUS, " ");
12893
+ Decoder.prototype.write = function(str2) {
12894
+ str2 = str2.replace(RE_PLUS, " ");
12816
12895
  let res = "";
12817
12896
  let i = 0;
12818
12897
  let p = 0;
12819
- const len = str.length;
12898
+ const len = str2.length;
12820
12899
  for (; i < len; ++i) {
12821
12900
  if (this.buffer !== void 0) {
12822
- if (!HEX[str.charCodeAt(i)]) {
12901
+ if (!HEX[str2.charCodeAt(i)]) {
12823
12902
  res += "%" + this.buffer;
12824
12903
  this.buffer = void 0;
12825
12904
  --i;
12826
12905
  } else {
12827
- this.buffer += str[i];
12906
+ this.buffer += str2[i];
12828
12907
  ++p;
12829
12908
  if (this.buffer.length === 2) {
12830
12909
  res += String.fromCharCode(parseInt(this.buffer, 16));
12831
12910
  this.buffer = void 0;
12832
12911
  }
12833
12912
  }
12834
- } else if (str[i] === "%") {
12913
+ } else if (str2[i] === "%") {
12835
12914
  if (i > p) {
12836
- res += str.substring(p, i);
12915
+ res += str2.substring(p, i);
12837
12916
  p = i;
12838
12917
  }
12839
12918
  this.buffer = "";
@@ -12841,7 +12920,7 @@ var require_Decoder = __commonJS({
12841
12920
  }
12842
12921
  }
12843
12922
  if (p < len && this.buffer === void 0) {
12844
- res += str.substring(p);
12923
+ res += str2.substring(p);
12845
12924
  }
12846
12925
  return res;
12847
12926
  };
@@ -13505,9 +13584,9 @@ var require_generateId = __commonJS({
13505
13584
  }
13506
13585
  module.exports.generateId = function generateIdFn() {
13507
13586
  let num = 0;
13508
- let str = seed();
13587
+ let str2 = seed();
13509
13588
  return function generateId() {
13510
- return num === 255 ? (str = seed()) + HEX[num = 0] : str + HEX[++num];
13589
+ return num === 255 ? (str2 = seed()) + HEX[num = 0] : str2 + HEX[++num];
13511
13590
  };
13512
13591
  }();
13513
13592
  }
@@ -15450,10 +15529,10 @@ function cleanUrl(href) {
15450
15529
  }
15451
15530
  var noopTest = { exec: () => null };
15452
15531
  function splitCells(tableRow, count) {
15453
- const row = tableRow.replace(/\|/g, (match, offset, str) => {
15532
+ const row = tableRow.replace(/\|/g, (match, offset, str2) => {
15454
15533
  let escaped = false;
15455
15534
  let curr = offset;
15456
- while (--curr >= 0 && str[curr] === "\\")
15535
+ while (--curr >= 0 && str2[curr] === "\\")
15457
15536
  escaped = !escaped;
15458
15537
  if (escaped) {
15459
15538
  return "|";
@@ -15481,14 +15560,14 @@ function splitCells(tableRow, count) {
15481
15560
  }
15482
15561
  return cells;
15483
15562
  }
15484
- function rtrim(str, c, invert) {
15485
- const l = str.length;
15563
+ function rtrim(str2, c, invert) {
15564
+ const l = str2.length;
15486
15565
  if (l === 0) {
15487
15566
  return "";
15488
15567
  }
15489
15568
  let suffLen = 0;
15490
15569
  while (suffLen < l) {
15491
- const currChar = str.charAt(l - suffLen - 1);
15570
+ const currChar = str2.charAt(l - suffLen - 1);
15492
15571
  if (currChar === c && !invert) {
15493
15572
  suffLen++;
15494
15573
  } else if (currChar !== c && invert) {
@@ -15497,19 +15576,19 @@ function rtrim(str, c, invert) {
15497
15576
  break;
15498
15577
  }
15499
15578
  }
15500
- return str.slice(0, l - suffLen);
15579
+ return str2.slice(0, l - suffLen);
15501
15580
  }
15502
- function findClosingBracket(str, b) {
15503
- if (str.indexOf(b[1]) === -1) {
15581
+ function findClosingBracket(str2, b) {
15582
+ if (str2.indexOf(b[1]) === -1) {
15504
15583
  return -1;
15505
15584
  }
15506
15585
  let level = 0;
15507
- for (let i = 0; i < str.length; i++) {
15508
- if (str[i] === "\\") {
15586
+ for (let i = 0; i < str2.length; i++) {
15587
+ if (str2[i] === "\\") {
15509
15588
  i++;
15510
- } else if (str[i] === b[0]) {
15589
+ } else if (str2[i] === b[0]) {
15511
15590
  level++;
15512
- } else if (str[i] === b[1]) {
15591
+ } else if (str2[i] === b[1]) {
15513
15592
  level--;
15514
15593
  if (level < 0) {
15515
15594
  return i;
@@ -17839,6 +17918,13 @@ async function liveSpecs(filter = {}) {
17839
17918
  // src/routes/specs.ts
17840
17919
  var branchUnknown = async (repoDir, branch) => !(await branchFromCandidates(repoDir)).includes(branch);
17841
17920
  function deriveSpecFields(source, payload, manualPriority) {
17921
+ if (source === "goal") {
17922
+ const pick = (v) => typeof v === "string" ? v.trim() : "";
17923
+ return {
17924
+ priority: manualPriority,
17925
+ objective: pick(payload?.goal) || pick(payload?.done) || "\u2014 goal belum diisi."
17926
+ };
17927
+ }
17842
17928
  const isQa = source === "qa";
17843
17929
  const priority = isQa && payload && "severity" in payload ? payload.severity === "minor" ? "sedang" : "tinggi" : manualPriority;
17844
17930
  const objective = isQa && payload && "actual" in payload ? payload.actual || payload.steps || "\u2014 audit untuk menelusuri akar masalah." : payload && "outcome" in payload ? payload.outcome || payload.context || "\u2014 brainstorm untuk memperjelas objective." : "";
@@ -17878,7 +17964,7 @@ async function specs_default(app2) {
17878
17964
  source: b.source,
17879
17965
  stage: "brainstorming",
17880
17966
  priority,
17881
- author: isQa ? `QA \xB7 ${author}` : b.source === "audit" ? `Audit \xB7 ${author}` : b.source === "cross-audit" ? `Audit lintas \xB7 ${author}` : author,
17967
+ author: isQa ? `QA \xB7 ${author}` : b.source === "audit" ? `Audit \xB7 ${author}` : b.source === "cross-audit" ? `Audit lintas \xB7 ${author}` : b.source === "goal" ? `Goal \xB7 ${author}` : author,
17882
17968
  objective,
17883
17969
  payload: b.payload,
17884
17970
  branchFrom: b.branchFrom ?? null
@@ -19299,6 +19385,14 @@ function resumeState(repoDir, worktree, branchTo, headSha) {
19299
19385
  const base = realGit.revParse(repoDir, `origin/${branchTo}`) ?? realGit.revParse(repoDir, branchTo) ?? (headSha ? realGit.revParse(repoDir, headSha) : null);
19300
19386
  return base ? { worktreeKept: false, base } : null;
19301
19387
  }
19388
+ function buildResumeCtx(repoDir, id, flow, worktreeKept) {
19389
+ const phases = readPhases(phaseFilePath(repoDir, id), flow);
19390
+ return {
19391
+ recorded: phases.filter((p) => p.state === "done" || p.state === "skipped").map((p) => `${p.name} ${p.state}`),
19392
+ next: phases.find((p) => p.state === "active")?.name,
19393
+ worktreeKept
19394
+ };
19395
+ }
19302
19396
  async function startSpecSession(spec, opts) {
19303
19397
  const repoDir = await resolveRepoDir(spec.projectId);
19304
19398
  if (!repoDir) throw new LaunchError(`project "${spec.projectId}" belum di-bind ke checkout lokal`, "needs-bind");
@@ -19315,11 +19409,14 @@ async function startSpecSession(spec, opts) {
19315
19409
  const worktree = `${repoDir}/.worktrees/${id}`;
19316
19410
  const branchTo = `hanoman/${id}`;
19317
19411
  const resume = !isContinue && spec.baseSha ? resumeState(repoDir, worktree, branchTo, spec.headSha) : null;
19318
- const goal = opts.goal ?? setting.goal.enabled ? resolveGoalCondition(
19319
- { flow: opts.flow, specId: spec.id, branchTo },
19320
- opts.goalCondition,
19321
- setting.goal.condition
19322
- ) : void 0;
19412
+ const isGoalFlow = opts.flow === "goal";
19413
+ const goalArgs = {
19414
+ flow: opts.flow,
19415
+ specId: spec.id,
19416
+ branchTo,
19417
+ spec: { payload: spec.payload ?? void 0, objective: spec.objective }
19418
+ };
19419
+ const goal = isGoalFlow || (opts.goal ?? setting.goal.enabled) ? resolveGoalCondition(goalArgs, opts.goalCondition, isGoalFlow ? null : setting.goal.condition) : void 0;
19323
19420
  const verifyScope = opts.verifyScope ?? setting.verifyScope;
19324
19421
  if (agent === "codex") ensureCodexTrust(repoDir);
19325
19422
  let baseSha;
@@ -19342,17 +19439,18 @@ async function startSpecSession(spec, opts) {
19342
19439
  objective: spec.objective,
19343
19440
  payload: spec.payload ?? void 0
19344
19441
  };
19442
+ const resumeCtx = resume ? buildResumeCtx(repoDir, id, opts.flow, resume.worktreeKept) : void 0;
19345
19443
  let prompt;
19346
- if (isContinue) {
19444
+ if (isGoalFlow) {
19445
+ prompt = startGoalPrompt(brief, branchTo, {
19446
+ autonomy: opts.autonomy,
19447
+ verifyScope,
19448
+ resume: resumeCtx
19449
+ });
19450
+ } else if (isContinue) {
19347
19451
  prompt = continuePrompt(opts.flow, brief, branchTo, opts.autonomy, verifyScope);
19348
- } else if (resume) {
19349
- const phases = readPhases(phaseFilePath(repoDir, id), opts.flow);
19350
- const ctx = {
19351
- recorded: phases.filter((p) => p.state === "done" || p.state === "skipped").map((p) => `${p.name} ${p.state}`),
19352
- next: phases.find((p) => p.state === "active")?.name,
19353
- worktreeKept: resume.worktreeKept
19354
- };
19355
- prompt = resumePrompt(opts.flow, brief, branchTo, ctx, opts.autonomy, verifyScope);
19452
+ } else if (resumeCtx) {
19453
+ prompt = resumePrompt(opts.flow, brief, branchTo, resumeCtx, opts.autonomy, verifyScope);
19356
19454
  } else {
19357
19455
  prompt = startPrompt(opts.flow, brief, branchTo, opts.autonomy, verifyScope);
19358
19456
  }
@@ -20681,6 +20779,9 @@ async function codex(app2) {
20681
20779
  app2.get("/codex/version", async () => codexVersionInfo());
20682
20780
  }
20683
20781
 
20782
+ // src/routes/update.ts
20783
+ init_src();
20784
+
20684
20785
  // src/services/update.ts
20685
20786
  init_src();
20686
20787
  init_config2();
@@ -20695,7 +20796,11 @@ function composeUpdate(x) {
20695
20796
  latestVersion: x.latestVersion,
20696
20797
  registry: { status: x.registryStatus, checkedAt: x.checkedAt },
20697
20798
  updateAvailable: available,
20698
- command: available ? UPDATE_COMMAND : ""
20799
+ command: available ? UPDATE_COMMAND : "",
20800
+ // Diwariskan apa adanya. Ia fakta tentang cara proses ini dilahirkan, bukan kesimpulan
20801
+ // tentang ada-tidaknya update — menurunkannya dari `available` akan menyembunyikan
20802
+ // instalasi tak-tersupervisi tepat saat tombolnya paling ingin ditekan.
20803
+ canApply: x.canApply
20699
20804
  };
20700
20805
  }
20701
20806
  var RESULT_TTL_MS = 15e3;
@@ -20716,6 +20821,15 @@ function runningVersion() {
20716
20821
  }
20717
20822
  return "0.0.0";
20718
20823
  }
20824
+ function supervised() {
20825
+ const v = process.env.HANOMAN_SUPERVISOR;
20826
+ return v === "1" || v === "true";
20827
+ }
20828
+ var exiter = null;
20829
+ function requestRestartForUpdate() {
20830
+ const delay = effectiveInt("HANOMAN_UPDATE_RESTART_DELAY_MS") ?? 250;
20831
+ setTimeout(() => (exiter ?? ((c) => process.exit(c)))(UPDATE_RESTART_EXIT), delay);
20832
+ }
20719
20833
  async function maybeFetch() {
20720
20834
  if (!effectiveBool("HANOMAN_UPDATE_FETCH")) return;
20721
20835
  if (lastFetchAt && Date.now() - lastFetchAt < FETCH_TTL_MS) return;
@@ -20748,15 +20862,30 @@ async function getUpdateStatus() {
20748
20862
  currentVersion: runningVersion(),
20749
20863
  latestVersion: lastLatest,
20750
20864
  registryStatus: lastStatus,
20751
- checkedAt: lastFetchAt ? new Date(lastFetchAt).toISOString() : null
20865
+ checkedAt: lastFetchAt ? new Date(lastFetchAt).toISOString() : null,
20866
+ canApply: supervised()
20752
20867
  });
20753
20868
  cached = { at: Date.now(), value };
20754
20869
  return value;
20755
20870
  }
20756
20871
 
20757
20872
  // src/routes/update.ts
20873
+ init_pty();
20758
20874
  async function update(app2) {
20759
20875
  app2.get("/update", async () => getUpdateStatus());
20876
+ app2.post("/update/apply", async (req, reply) => {
20877
+ const parsed = zUpdateApplyBody.safeParse(req.body ?? {});
20878
+ if (!parsed.success) return reply.code(400).send({ error: "bad-body" });
20879
+ const u = await getUpdateStatus();
20880
+ if (!u.canApply) return reply.code(409).send({ error: "unsupervised" });
20881
+ if (!u.updateAvailable) return reply.code(409).send({ error: "up-to-date", current: u.currentVersion });
20882
+ const liveSessions = listSessions().filter((s) => !s.exited).length;
20883
+ const from = u.currentVersion;
20884
+ const to = u.latestVersion;
20885
+ if (!parsed.data.confirm) return reply.code(409).send({ error: "confirm-required", liveSessions, from, to });
20886
+ requestRestartForUpdate();
20887
+ return reply.code(202).send({ accepted: true, from, to, liveSessions });
20888
+ });
20760
20889
  }
20761
20890
 
20762
20891
  // src/services/events.ts
@@ -23197,7 +23326,8 @@ function capabilityForRoute(method, path) {
23197
23326
  const seg = path.replace(/^\/api\/?/, "").replace(/\/+$/, "").split("/").filter(Boolean);
23198
23327
  const top = seg[0] ?? "";
23199
23328
  if (top === "auth" || top === "agent-tokens" || top === "device-tokens" || top === "sync") return "COOKIE_ONLY";
23200
- if (top === "limits" || top === "update" || top === "events" || top === "fs" || top === "health") return "GLOBAL_READ";
23329
+ if (top === "limits" || top === "update" || top === "events" || top === "fs" || top === "health")
23330
+ return read ? "GLOBAL_READ" : "COOKIE_ONLY";
23201
23331
  if (top === "scheduler") return rw("settings");
23202
23332
  if (top === "settings" || top === "config") return rw("settings");
23203
23333
  if (top === "specs") return rw("backlog");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hanoman",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Orchestrator + dashboard workflow docs-driven untuk sesi Claude Code / Codex",
5
5
  "type": "module",
6
6
  "repository": {
@@ -11,7 +11,8 @@
11
11
  "hanoman": "bin/hanoman.mjs"
12
12
  },
13
13
  "scripts": {
14
- "postinstall": "prisma generate --schema prisma/schema.prisma || true"
14
+ "postinstall": "prisma generate --schema prisma/schema.prisma || true",
15
+ "prepublishOnly": "node dist/cli.js __verify"
15
16
  },
16
17
  "engines": {
17
18
  "node": ">=20"
@@ -25,17 +26,15 @@
25
26
  "LICENSE"
26
27
  ],
27
28
  "dependencies": {
28
- "@fastify/cookie": "^9.4.0",
29
+ "fastify": "^4.28.0",
29
30
  "@fastify/static": "^7.0.0",
30
31
  "@fastify/websocket": "^10.0.1",
31
- "@prisma/client": "^6.19.3",
32
- "fastify": "^4.28.0",
32
+ "@fastify/cookie": "^9.4.0",
33
+ "@prisma/client": "^6.19.0",
33
34
  "node-pty": "^1.1.0",
34
35
  "pdfkit": "^0.19.1",
36
+ "prisma": "^6.19.0",
35
37
  "pg": "^8.13.1"
36
38
  },
37
- "license": "MIT",
38
- "devDependencies": {
39
- "prisma": "^6.19.3"
40
- }
39
+ "license": "MIT"
41
40
  }