hanoman 0.1.46 → 0.1.50

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.
@@ -169,6 +169,10 @@ dan tak memakai agent token.
169
169
  | `POST /api/specs` | `backlog:write` | buat backlog item — bentuk payload di §7. |
170
170
  | `PATCH /api/specs/:id` | `backlog:write` | ubah item; konten hanya selagi belum dimulai. |
171
171
  | `POST /api/specs/:id/done` | `backlog:write` | tandai item **selesai** tanpa menjalankan sesi — untuk pekerjaan yang beres di luar hanoman. Body `{ reason?: string (≤280), confirm?: boolean }`, keduanya opsional; balasannya `Spec` yang sudah `stage:"done"`. Tak menjalankan maupun menghentikan sesi apa pun. |
172
+ | `GET /api/specs/:id/attachments` | `backlog:read` | lampiran backlog item — gambar & dokumen yang dilampirkan manusia sebagai konteks kerja. |
173
+ | `POST /api/specs/:id/attachments` | `backlog:write` | unggah lampiran (`multipart/form-data`, beberapa berkas per request). Berkas yang ditolak **tak** menggagalkan yang lain — periksa `rejected[]`. |
174
+ | `GET /api/specs/:id/attachments/:attId` | `backlog:read` | byte satu lampiran. |
175
+ | `DELETE /api/specs/:id/attachments/:attId` | `backlog:write` | hapus satu lampiran. |
172
176
  | `GET /api/specs/:id/docs` | `backlog:read` | dokumen yang ditulis sesi item itu. |
173
177
  | `GET /api/specs/:id/review` | `backlog:read` | diff hasil kerja sesi. |
174
178
  | `GET /api/projects/:id/docs` | `docs:read` | index Source of Truth project. |
@@ -299,6 +303,7 @@ memanggilnya; jangan lakukan tanpa manusia.
299
303
  | `branchFrom` yang tak ada di repo project → **400**, bukan diterima lalu gagal di tengah sesi | ambil kandidatnya dari `GET /api/projects/:id/branches` |
300
304
  | **401 telanjang** tak memisahkan "host salah" dari "token salah" dari "master switch mati" | probe `GET /api/health` sekali: 200 = host benar → masalahnya token atau master switch |
301
305
  | **403** bukan kegagalan permanen | bacalah field `need`, sampaikan ke manusia, minta capability itu ditambahkan |
306
+ | **Lampiran backlog tak menyeberang sync** (`SpecAttachment` LOCAL-only, ADR-0124) | item yang sama di instance lain tampil **tanpa** lampiran. Kalau lampiran yang disebut manusia tak kamu temukan, kemungkinan besar kamu bicara ke instance yang bukan tempat lampiran itu diunggah — tanyakan, jangan simpulkan lampirannya tak ada |
302
307
 
303
308
  ## 10. Contoh alur end-to-end
304
309
 
@@ -461,6 +466,11 @@ dan seluruh `/api/vps*` (remote exec) **tidak ikut**, begitu pula merge/rebase (
461
466
  penghapusan backlog, dan perubahan `stage`. Batasan ini ada di katalog toolnya, bukan di token:
462
467
  token yang punya `sessions:write` sekalipun tak akan menemukan tool untuk memakainya. Lihat §8.
463
468
 
469
+ **Lampiran backlog** (`/api/specs/:id/attachments*`, SPEC-843 · ADR-0124) juga sengaja **tak punya
470
+ tool**: berkasnya lahir dari disk manusia, bukan dari model, dan tool MCP berbentuk JSON sehingga
471
+ byte biner tak punya representasi di sana. REST-nya tetap terjangkau agent token
472
+ ber-`backlog:read`/`backlog:write` — yang tak dipajang adalah **tool**-nya.
473
+
464
474
  ### Opsi
465
475
 
466
476
  | Variabel / flag | Arti |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hanoman",
3
- "version": "0.1.46",
3
+ "version": "0.1.50",
4
4
  "description": "Orchestrator + dashboard workflow docs-driven untuk sesi Claude Code / Codex",
5
5
  "type": "module",
6
6
  "repository": {
@@ -0,0 +1,19 @@
1
+ -- SPEC-843 · ADR-0124 · lampiran per backlog item.
2
+ --
3
+ -- Ditulis tangan (bukan `migrate dev`): worktree tetangga membuat `migrate dev` me-reset DB saat
4
+ -- ada drift. ADITIF murni — satu tabel baru, tak ada tabel yang diredefinisi.
5
+ --
6
+ -- TANPA kolom `version`: entitas ini LOCAL-only dan tak pernah masuk changefeed sync.
7
+ CREATE TABLE "SpecAttachment" (
8
+ "id" TEXT NOT NULL PRIMARY KEY,
9
+ "specId" TEXT NOT NULL,
10
+ "projectId" TEXT NOT NULL,
11
+ "filename" TEXT NOT NULL,
12
+ "mimeType" TEXT NOT NULL,
13
+ "size" INTEGER NOT NULL,
14
+ "storageKey" TEXT NOT NULL,
15
+ "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
16
+ CONSTRAINT "SpecAttachment_specId_fkey" FOREIGN KEY ("specId") REFERENCES "Spec" ("id") ON DELETE CASCADE ON UPDATE CASCADE
17
+ );
18
+
19
+ CREATE INDEX "SpecAttachment_specId_idx" ON "SpecAttachment"("specId");
@@ -0,0 +1,27 @@
1
+ -- SPEC-844 · ADR-0125 · riwayat sesi mencatat BAGAIMANA sebuah baris ditutup.
2
+ --
3
+ -- Ditulis tangan (bukan `migrate dev`): worktree tetangga membuat `migrate dev` me-reset DB saat
4
+ -- ada drift. ADITIF murni — dua kolom NULLABLE tanpa default, tak ada tabel diredefinisi, jadi
5
+ -- jebakan `ADD COLUMN … DEFAULT CURRENT_TIMESTAMP` (ADR-0090) tak berlaku di sini.
6
+ ALTER TABLE "SessionHistory" ADD COLUMN "endedReason" TEXT;
7
+ ALTER TABLE "SessionHistory" ADD COLUMN "reconciledAt" DATETIME;
8
+
9
+ -- Backfill SEKALI JALAN. Baris lama tak bisa ditanyai, tapi jejaknya masih ada: `reconcileHistory`
10
+ -- membaca `updatedAt` SEBELUM update-nya sendiri (jadi `updatedAt` melompat ke waktu boot dan
11
+ -- meninggalkan `endedAt` di belakang), sementara `finishSession` menulis `endedAt = new Date()` DI
12
+ -- DALAM update yang sama (jadi jaraknya nol). Terukur pada DB hidup 806 baris: tutup normal
13
+ -- 0–39 ms (n=777) vs rekonsiliasi 275 966–82 224 277 ms (n=20) — empat orde besaran tanpa satu pun
14
+ -- baris di antaranya; ambang 60 000 ms duduk di tengah celah itu.
15
+ --
16
+ -- Ini backfill, BUKAN aturan render: menjadikannya kontrak akan mengunci detail penyimpanan Prisma
17
+ -- sebagai semantik produk. Prisma menyimpan DateTime SQLite sebagai INTEGER milidetik, jadi
18
+ -- selisihnya aritmetika biasa; `CAST` dipasang supaya representasi lain (teks ISO) menghasilkan
19
+ -- selisih 0 → nol baris cocok → seluruh tabel jatuh ke 'closed', yaitu perilaku sebelum spec ini.
20
+ UPDATE "SessionHistory"
21
+ SET "endedReason" = 'reconciled', "reconciledAt" = "updatedAt"
22
+ WHERE "endedAt" IS NOT NULL
23
+ AND CAST("updatedAt" AS INTEGER) - CAST("endedAt" AS INTEGER) > 60000;
24
+
25
+ UPDATE "SessionHistory"
26
+ SET "endedReason" = 'closed'
27
+ WHERE "endedAt" IS NOT NULL AND "endedReason" IS NULL;
@@ -87,6 +87,30 @@ model Spec {
87
87
  sourceHistory Json?
88
88
  updatedAt DateTime @updatedAt
89
89
  project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
90
+ attachments SpecAttachment[]
91
+ }
92
+
93
+ // SPEC-843 · ADR-0124 · lampiran per backlog item — konteks yang dipegang manusia saat memfilekan
94
+ // item, dibaca agen di dalam sesi. Byte hidup di HANOMAN_UPLOAD_DIR (server-local, di luar repoDir);
95
+ // `storageKey` opaque, `filename` nama asli tersanitasi untuk tampilan saja.
96
+ //
97
+ // LOCAL-only: TANPA `version`, jadi ia tak pernah masuk changefeed sync (cermin LeadFlow,
98
+ // WebhookEndpoint, Changelog). Byte memang tak menyeberang (ADR-0062), dan metadata yang menyeberang
99
+ // TANPA byte-nya hanya menghasilkan lampiran yang tak bisa dibuka di mesin lain — keadaan terburuk
100
+ // dari ketiga pilihan. Sengaja BUKAN TicketAttachment: tiket dan backlog dua domain dengan aturan
101
+ // sync dan tingkat kepercayaan yang berbeda (ADR-0124 §1/§4).
102
+ model SpecAttachment {
103
+ id String @id @default(cuid())
104
+ specId String
105
+ projectId String // denormal — kuota & isolasi per project, cermin TicketAttachment
106
+ filename String
107
+ mimeType String
108
+ size Int
109
+ storageKey String
110
+ createdAt DateTime @default(now())
111
+ spec Spec @relation(fields: [specId], references: [id], onDelete: Cascade)
112
+
113
+ @@index([specId])
90
114
  }
91
115
 
92
116
  // SPEC-516 · ADR-0105 · changelog naratif per project yang sudah dibangkitkan.
@@ -368,6 +392,13 @@ model SessionHistory {
368
392
  startedAt DateTime @default(now())
369
393
  endedAt DateTime?
370
394
  exitCode Int?
395
+ // SPEC-844 · ADR-0125 · CARA baris ini ditutup: "closed" (killSession) atau "reconciled" (pane
396
+ // sudah lenyap saat boot — hasilnya tak diketahui). null = baris lahir sebelum kolom ini ada.
397
+ // Kelas hasil (selesai/gagal) sengaja TIDAK disimpan: ia turunan exitCode (sessionOutcome).
398
+ endedReason String?
399
+ // Kapan boot menemukan panenya lenyap. `endedAt` baris rekonsiliasi adalah batas BAWAH
400
+ // ("terakhir diketahui hidup"); kolom ini batas atasnya.
401
+ reconciledAt DateTime?
371
402
  transcriptKey String?
372
403
  transcriptBytes Int?
373
404
  createdAt DateTime @default(now())