hanoman 0.1.16 → 0.1.19
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 +102 -8
- package/dist/server.js +719 -96
- package/docs/agent-integration.md +45 -2
- package/package.json +1 -1
- package/prisma/migrations/20260803000000_spec_done_at/migration.sql +16 -0
- package/prisma/migrations/20260803001000_changelog/migration.sql +21 -0
- package/prisma/schema.prisma +31 -1
- package/web/assets/{index-GQaOZTKC.js → index-BJTj--gG.js} +1546 -1546
- package/web/index.html +1 -1
package/dist/server.js
CHANGED
|
@@ -4341,7 +4341,7 @@ function cmpVersion(a, b) {
|
|
|
4341
4341
|
}
|
|
4342
4342
|
return 0;
|
|
4343
4343
|
}
|
|
4344
|
-
var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSpec, NOTIFY_SOUNDS, MODELS, EFFORTS, 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, zLeadEngine, zLead, LEAD_DEFAULTS, zSetting, zNotification, zDocFile, zDeviceTokenView;
|
|
4344
|
+
var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSpec, NOTIFY_SOUNDS, MODELS, EFFORTS, 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, CHANGELOG_ENGINE_DEFAULTS, zLeadEngine, zLead, LEAD_DEFAULTS, zSetting, zNotification, zDocFile, zDeviceTokenView;
|
|
4345
4345
|
var init_entities = __esm({
|
|
4346
4346
|
"../shared/src/entities.ts"() {
|
|
4347
4347
|
"use strict";
|
|
@@ -4491,6 +4491,7 @@ var init_entities = __esm({
|
|
|
4491
4491
|
effort: external_exports.string().default("xhigh")
|
|
4492
4492
|
});
|
|
4493
4493
|
CONFLICT_DEFAULTS = zConflict.parse({});
|
|
4494
|
+
CHANGELOG_ENGINE_DEFAULTS = zAgentEngine.parse({});
|
|
4494
4495
|
zLeadEngine = zAgentEngine;
|
|
4495
4496
|
zLead = external_exports.object({
|
|
4496
4497
|
enabled: external_exports.boolean().default(false),
|
|
@@ -4562,8 +4563,10 @@ var init_entities = __esm({
|
|
|
4562
4563
|
// SPEC-383 · ADR-0081 · default sesi konflik rebase/merge
|
|
4563
4564
|
lead: zLead.default(LEAD_DEFAULTS),
|
|
4564
4565
|
// SPEC-409 · ADR-0091 · hanoman-lead (default mati)
|
|
4565
|
-
telegram: zTelegramSettings.default(TELEGRAM_DEFAULTS)
|
|
4566
|
+
telegram: zTelegramSettings.default(TELEGRAM_DEFAULTS),
|
|
4566
4567
|
// SPEC-476 · ADR-0096 · gateway Telegram (default mati)
|
|
4568
|
+
changelog: zAgentEngine.default(CHANGELOG_ENGINE_DEFAULTS)
|
|
4569
|
+
// SPEC-518 · agen pembuat changelog (opt-in, mati)
|
|
4567
4570
|
});
|
|
4568
4571
|
zNotification = external_exports.object({
|
|
4569
4572
|
id: external_exports.string(),
|
|
@@ -5092,11 +5095,46 @@ var init_lead = __esm({
|
|
|
5092
5095
|
}
|
|
5093
5096
|
});
|
|
5094
5097
|
|
|
5098
|
+
// ../shared/src/prd-status.ts
|
|
5099
|
+
function prdBranchFor(prdPath) {
|
|
5100
|
+
if (!prdPath.startsWith(PRD_DIR) || !prdPath.endsWith(".md")) return null;
|
|
5101
|
+
const slug = prdPath.slice(PRD_DIR.length, -3);
|
|
5102
|
+
return slug ? `prd/${slug}` : null;
|
|
5103
|
+
}
|
|
5104
|
+
function payloadText(payload) {
|
|
5105
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return "";
|
|
5106
|
+
const p = payload;
|
|
5107
|
+
return [p.context, p.goal].filter((v) => typeof v === "string").join("\n");
|
|
5108
|
+
}
|
|
5109
|
+
function specDerivesFromPrd(spec, prdPath) {
|
|
5110
|
+
const branch = prdBranchFor(prdPath);
|
|
5111
|
+
if (!branch) return false;
|
|
5112
|
+
if (spec.branchFrom === branch) return true;
|
|
5113
|
+
return payloadText(spec.payload).includes(prdPath);
|
|
5114
|
+
}
|
|
5115
|
+
function prdStatusOf(prdPath, specs) {
|
|
5116
|
+
const derived = specs.filter((s2) => specDerivesFromPrd(s2, prdPath));
|
|
5117
|
+
const specCount = derived.length;
|
|
5118
|
+
const doneCount = derived.filter((s2) => s2.stage === "done").length;
|
|
5119
|
+
const status = specCount === 0 ? "draft" : doneCount === specCount ? "terwujud" : "dieskalasi";
|
|
5120
|
+
return { status, specCount, doneCount };
|
|
5121
|
+
}
|
|
5122
|
+
var PRD_STATUSES, zPrdStatus, PRD_DIR;
|
|
5123
|
+
var init_prd_status = __esm({
|
|
5124
|
+
"../shared/src/prd-status.ts"() {
|
|
5125
|
+
"use strict";
|
|
5126
|
+
init_zod();
|
|
5127
|
+
PRD_STATUSES = ["draft", "dieskalasi", "terwujud"];
|
|
5128
|
+
zPrdStatus = external_exports.enum(PRD_STATUSES);
|
|
5129
|
+
PRD_DIR = "docs/prd/";
|
|
5130
|
+
}
|
|
5131
|
+
});
|
|
5132
|
+
|
|
5095
5133
|
// ../shared/src/dto.ts
|
|
5096
5134
|
function flowForSource(source) {
|
|
5097
5135
|
return source === "qa" ? "qa" : source === "audit" ? "audit" : source === "goal" ? "goal" : "feature";
|
|
5098
5136
|
}
|
|
5099
|
-
var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zIntegrate, zSessionSummary, zProjectView, zSchedulerQueueItem, zSchedulerSourceView, zSchedulerSessionView, zSchedulerState, zLeadDecisionView, zLeadFlowView, zLeadAnswer, zLeadProjectStatus, zLeadStatusView, 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, zTicketView, zTicketAttachmentView, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus;
|
|
5137
|
+
var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zIntegrate, zSessionSummary, zProjectView, zSchedulerQueueItem, zSchedulerSourceView, zSchedulerSessionView, zSchedulerQueueCounts, zSchedulerState, zLeadDecisionView, zLeadFlowView, zLeadAnswer, zLeadProjectStatus, zLeadStatusView, 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, zTicketView, zTicketAttachmentView, zTicketDetail, zTicketEditInput, zHelpInfo, zPublicTicketStatus;
|
|
5100
5138
|
var init_dto = __esm({
|
|
5101
5139
|
"../shared/src/dto.ts"() {
|
|
5102
5140
|
"use strict";
|
|
@@ -5104,6 +5142,7 @@ var init_dto = __esm({
|
|
|
5104
5142
|
init_entities();
|
|
5105
5143
|
init_lead();
|
|
5106
5144
|
init_auto_merge();
|
|
5145
|
+
init_prd_status();
|
|
5107
5146
|
init_enums();
|
|
5108
5147
|
zIssueDeviceToken = external_exports.object({ name: external_exports.string().min(1) });
|
|
5109
5148
|
zSessionResult = external_exports.object({
|
|
@@ -5248,12 +5287,21 @@ var init_dto = __esm({
|
|
|
5248
5287
|
decision: external_exports.boolean(),
|
|
5249
5288
|
exited: external_exports.boolean()
|
|
5250
5289
|
});
|
|
5290
|
+
zSchedulerQueueCounts = external_exports.object({
|
|
5291
|
+
queued: external_exports.number().int(),
|
|
5292
|
+
launched: external_exports.number().int(),
|
|
5293
|
+
done: external_exports.number().int(),
|
|
5294
|
+
failed: external_exports.number().int(),
|
|
5295
|
+
// SPEC-522 · `canceled` adalah nilai kelima `status`; hitungannya ikut supaya panel bisa
|
|
5296
|
+
// menampilkan seksi "Dibatalkan" tanpa memuat daftarnya.
|
|
5297
|
+
canceled: external_exports.number().int()
|
|
5298
|
+
});
|
|
5251
5299
|
zSchedulerState = external_exports.object({
|
|
5252
5300
|
config: zScheduler,
|
|
5253
5301
|
cap: external_exports.number(),
|
|
5254
5302
|
liveCount: external_exports.number(),
|
|
5255
5303
|
sources: external_exports.array(zSchedulerSourceView),
|
|
5256
|
-
|
|
5304
|
+
queueCounts: zSchedulerQueueCounts,
|
|
5257
5305
|
sessions: external_exports.array(zSchedulerSessionView)
|
|
5258
5306
|
});
|
|
5259
5307
|
zLeadDecisionView = external_exports.object({
|
|
@@ -5361,7 +5409,13 @@ var init_dto = __esm({
|
|
|
5361
5409
|
title: external_exports.string(),
|
|
5362
5410
|
live: external_exports.boolean(),
|
|
5363
5411
|
projectId: external_exports.string(),
|
|
5364
|
-
projectName: external_exports.string()
|
|
5412
|
+
projectName: external_exports.string(),
|
|
5413
|
+
// SPEC-520 · status TURUNAN dari backlog yang lahir dari PRD ini (ADR-0018/0019) — bukan
|
|
5414
|
+
// kolom, bukan prosa di dalam dokumennya. `live` di atas menjawab pertanyaan LAIN
|
|
5415
|
+
// (freshest-wins dari worktree sesi prd hidup) dan sengaja tetap ortogonal.
|
|
5416
|
+
status: zPrdStatus,
|
|
5417
|
+
specCount: external_exports.number().int().nonnegative(),
|
|
5418
|
+
doneCount: external_exports.number().int().nonnegative()
|
|
5365
5419
|
});
|
|
5366
5420
|
zBreakdownItem = external_exports.object({
|
|
5367
5421
|
title: external_exports.string().min(1),
|
|
@@ -5406,9 +5460,10 @@ var init_dto = __esm({
|
|
|
5406
5460
|
// pipeline claude). DIDAHULUKAN: z.object non-strict membuang key asing, jadi bila varian
|
|
5407
5461
|
// longgar {project,flow?} lebih dulu, {project,shell:true} akan lolos sbg plain (shell dibuang).
|
|
5408
5462
|
external_exports.object({ project: external_exports.string(), shell: external_exports.literal(true) }),
|
|
5409
|
-
//
|
|
5410
|
-
//
|
|
5411
|
-
|
|
5463
|
+
// SPEC-166 · "reverse" = sesi project-level di worktree-nya sendiri, menyusun Source of Truth
|
|
5464
|
+
// dari kode. TANPA override runtime: sesi project-level mengikuti Setting.agent (ADR-0074).
|
|
5465
|
+
// Terminal biasa (tanpa flow) kini punya variannya SENDIRI di bawah — lihat SPEC-517.
|
|
5466
|
+
external_exports.object({ project: external_exports.string(), flow: external_exports.literal("reverse") }),
|
|
5412
5467
|
// SPEC-210 · sesi prd project-level di worktree sendiri; menghasilkan dokumen PRD dari brief.
|
|
5413
5468
|
// SPEC-340 · ADR-0076 · eskalasi audit → PRD: branchFrom = branch audit (worktree lahir dari sana,
|
|
5414
5469
|
// resolveCommit + fallback origin/<rev>), fromAudit = id spec audit (isi dokumennya disematkan ke
|
|
@@ -5425,6 +5480,18 @@ var init_dto = __esm({
|
|
|
5425
5480
|
// SPEC-222 · scaffold: sesi project-level from-scratch, menyusun SoT dari ide. Tanpa brief
|
|
5426
5481
|
// (diseed dari Project.desc), tanpa Spec — cermin reverse.
|
|
5427
5482
|
external_exports.object({ project: external_exports.string(), flow: external_exports.literal("scaffold") }),
|
|
5483
|
+
// SPEC-517 · terminal agen biasa: agen (claude|codex) + model + effort boleh dipilih PER SESI,
|
|
5484
|
+
// seperti picker Start backlog (ADR-0061/0074). Kosong → default global (Setting).
|
|
5485
|
+
// `flow: z.undefined()` BUKAN hiasan: varian ini permisif dan diletakkan SESUDAH semua varian
|
|
5486
|
+
// ber-flow, jadi tanpa gerbang itu body flow yang CACAT ({project, flow:"prd"} tanpa brief)
|
|
5487
|
+
// akan lolos ke sini dan melahirkan terminal biasa secara senyap alih-alih dijawab 400.
|
|
5488
|
+
external_exports.object({
|
|
5489
|
+
project: external_exports.string(),
|
|
5490
|
+
flow: external_exports.undefined(),
|
|
5491
|
+
agent: zAgent.optional(),
|
|
5492
|
+
model: external_exports.string().optional(),
|
|
5493
|
+
effort: external_exports.string().optional()
|
|
5494
|
+
}),
|
|
5428
5495
|
// SPEC-252 · ADR-0061 — model & effort per SESI: override opsional saat Start; kosong → global.
|
|
5429
5496
|
// SPEC-332 · ADR-0073 — mode goal per SESI: `goal` undefined → ikut Setting.goal.enabled,
|
|
5430
5497
|
// false → mati walau global nyala; `goalCondition` kosong → template global → default bawaan.
|
|
@@ -5674,6 +5741,12 @@ var init_api = __esm({
|
|
|
5674
5741
|
// SPEC-299 · panel scheduler (daun #6) — konsumen read-only fondasi SPEC-294/ADR-0072.
|
|
5675
5742
|
schedulerConfig: `${API}/scheduler/config`,
|
|
5676
5743
|
schedulerState: `${API}/scheduler/state`,
|
|
5744
|
+
// SPEC-522 · ADR-0106 · batalkan / antre lagi SATU baris antrean. Di bawah prefix `scheduler`
|
|
5745
|
+
// supaya capability-nya turunan peta yang sudah ada (settings, MENURUT METHOD) — tanpa baris peta baru.
|
|
5746
|
+
schedulerQueueCancel: (id) => `${API}/scheduler/queue/${encodeURIComponent(id)}/cancel`,
|
|
5747
|
+
schedulerQueueRequeue: (id) => `${API}/scheduler/queue/${encodeURIComponent(id)}/requeue`,
|
|
5748
|
+
// SPEC-523 · antrean sebagai daftar berhalaman (page/limit + status), lepas dari `state`.
|
|
5749
|
+
schedulerQueue: `${API}/scheduler/queue`,
|
|
5677
5750
|
// SPEC-409 · ADR-0091 · hanoman-lead. Semua HTTP (polling) — tak ada kanal WS baru (AC-26).
|
|
5678
5751
|
leadConfig: `${API}/lead/config`,
|
|
5679
5752
|
leadStatus: `${API}/lead/status`,
|
|
@@ -5732,6 +5805,10 @@ var init_api = __esm({
|
|
|
5732
5805
|
// SPEC-471 · ADR-0095 · tarik & triase issue GitHub (di belakang gate cookie, capability `support`).
|
|
5733
5806
|
// hanoman TIDAK PERNAH menulis ke GitHub — tak ada path komentar/close di sini, dan itu disengaja.
|
|
5734
5807
|
githubPull: (id) => `${API}/projects/${encodeURIComponent(id)}/github/pull`,
|
|
5808
|
+
// SPEC-516 · ADR-0105 · changelog per project
|
|
5809
|
+
changelog: (id) => `${API}/projects/${encodeURIComponent(id)}/changelog`,
|
|
5810
|
+
changelogSources: (id) => `${API}/projects/${encodeURIComponent(id)}/changelog/sources`,
|
|
5811
|
+
changelogItem: (id, cid) => `${API}/projects/${encodeURIComponent(id)}/changelog/${encodeURIComponent(cid)}`,
|
|
5735
5812
|
githubIssues: (id) => `${API}/projects/${encodeURIComponent(id)}/github/issues`,
|
|
5736
5813
|
githubIssuesAccept: `${API}/github-issues/accept`,
|
|
5737
5814
|
githubIssueAccept: (id) => `${API}/github-issues/${encodeURIComponent(id)}/accept`,
|
|
@@ -7118,6 +7195,56 @@ var init_mcp = __esm({
|
|
|
7118
7195
|
}
|
|
7119
7196
|
});
|
|
7120
7197
|
|
|
7198
|
+
// ../shared/src/changelog.ts
|
|
7199
|
+
function dayString(d) {
|
|
7200
|
+
const p = (n2) => String(n2).padStart(2, "0");
|
|
7201
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
7202
|
+
}
|
|
7203
|
+
function defaultRange(today) {
|
|
7204
|
+
const from = new Date(today.getFullYear(), today.getMonth(), today.getDate() - (DEFAULT_RANGE_DAYS - 1));
|
|
7205
|
+
return { from: dayString(from), to: dayString(today) };
|
|
7206
|
+
}
|
|
7207
|
+
function changelogMatches(row, q) {
|
|
7208
|
+
const needle = (q ?? "").trim().toLowerCase();
|
|
7209
|
+
if (!needle) return true;
|
|
7210
|
+
return row.title.toLowerCase().includes(needle) || row.body.toLowerCase().includes(needle) || row.mode.toLowerCase().includes(needle);
|
|
7211
|
+
}
|
|
7212
|
+
var CHANGELOG_MODES, zChangelogMode, zDay, zChangelogRequest, zChangelogView, DEFAULT_RANGE_DAYS;
|
|
7213
|
+
var init_changelog = __esm({
|
|
7214
|
+
"../shared/src/changelog.ts"() {
|
|
7215
|
+
"use strict";
|
|
7216
|
+
init_zod();
|
|
7217
|
+
CHANGELOG_MODES = ["backlog", "commit", "version"];
|
|
7218
|
+
zChangelogMode = external_exports.enum(CHANGELOG_MODES);
|
|
7219
|
+
zDay = external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/, "format tanggal harus YYYY-MM-DD");
|
|
7220
|
+
zChangelogRequest = external_exports.discriminatedUnion("mode", [
|
|
7221
|
+
external_exports.object({ mode: external_exports.literal("backlog"), from: zDay.optional(), to: zDay.optional() }),
|
|
7222
|
+
external_exports.object({ mode: external_exports.literal("commit"), fromSha: external_exports.string().trim().min(4), toSha: external_exports.string().trim().min(4) }),
|
|
7223
|
+
external_exports.object({ mode: external_exports.literal("version"), fromTag: external_exports.string().trim().min(1).optional(), toTag: external_exports.string().trim().min(1) })
|
|
7224
|
+
]).superRefine((v, ctx) => {
|
|
7225
|
+
if (v.mode === "backlog" && v.from && v.to && v.from > v.to)
|
|
7226
|
+
ctx.addIssue({
|
|
7227
|
+
code: external_exports.ZodIssueCode.custom,
|
|
7228
|
+
path: ["from"],
|
|
7229
|
+
message: "rentang tanggal terbalik \u2014 `from` harus lebih awal atau sama dengan `to`"
|
|
7230
|
+
});
|
|
7231
|
+
});
|
|
7232
|
+
zChangelogView = external_exports.object({
|
|
7233
|
+
id: external_exports.string(),
|
|
7234
|
+
projectId: external_exports.string(),
|
|
7235
|
+
mode: zChangelogMode,
|
|
7236
|
+
title: external_exports.string(),
|
|
7237
|
+
params: external_exports.unknown(),
|
|
7238
|
+
body: external_exports.string(),
|
|
7239
|
+
generator: external_exports.enum(["agent", "fallback"]),
|
|
7240
|
+
warning: external_exports.string().nullable(),
|
|
7241
|
+
itemCount: external_exports.number().int(),
|
|
7242
|
+
createdAt: external_exports.string()
|
|
7243
|
+
});
|
|
7244
|
+
DEFAULT_RANGE_DAYS = 30;
|
|
7245
|
+
}
|
|
7246
|
+
});
|
|
7247
|
+
|
|
7121
7248
|
// ../shared/src/index.ts
|
|
7122
7249
|
var init_src = __esm({
|
|
7123
7250
|
"../shared/src/index.ts"() {
|
|
@@ -7134,6 +7261,7 @@ var init_src = __esm({
|
|
|
7134
7261
|
init_api();
|
|
7135
7262
|
init_coverage();
|
|
7136
7263
|
init_ticket_status();
|
|
7264
|
+
init_prd_status();
|
|
7137
7265
|
init_session_kind();
|
|
7138
7266
|
init_config();
|
|
7139
7267
|
init_config_registry();
|
|
@@ -7142,6 +7270,7 @@ var init_src = __esm({
|
|
|
7142
7270
|
init_webhook();
|
|
7143
7271
|
init_mcp();
|
|
7144
7272
|
init_auto_merge();
|
|
7273
|
+
init_changelog();
|
|
7145
7274
|
}
|
|
7146
7275
|
});
|
|
7147
7276
|
|
|
@@ -9584,7 +9713,10 @@ var init_sync = __esm({
|
|
|
9584
9713
|
// ini spec asal-hub mendapat createdAt lokal palsu di tiap client (kolom NOT NULL ber-default).
|
|
9585
9714
|
// SPEC-447 · ADR-0093 · dependsOn ikut juga: tanpa itu client tak tahu urutannya dan akan
|
|
9586
9715
|
// meluncurkan pekerjaan yang di hub terblokir. Bukan DATE_FIELDS — nilainya array string.
|
|
9587
|
-
|
|
9716
|
+
// SPEC-516 · ADR-0105 · doneAt ikut menyeberang — cermin createdAt/startedAt. Tanpa ini spec
|
|
9717
|
+
// asal-hub mendarat di tiap client dengan doneAt null tanpa satu pun error, dan changelog
|
|
9718
|
+
// mode backlog di client itu selamanya kosong.
|
|
9719
|
+
spec: ["projectId", "title", "source", "stage", "priority", "author", "objective", "payload", "branchFrom", "baseSha", "headSha", "dependsOn", "createdAt", "startedAt", "doneAt", "updatedAt"],
|
|
9588
9720
|
vps: ["name", "host", "port", "user", "health", "audit", "hardened", "lastSeenAt", "lastAuditAt", "updatedAt"],
|
|
9589
9721
|
sessionResult: ["projectId", "specId", "oldStage", "newStage", "commitSha", "branch", "prUrl", "status", "deviceId", "author", "createdAt", "updatedAt"],
|
|
9590
9722
|
// SPEC-268 · ADR-0066 · metadata tiket (lampiran biner tak disync). accessKeyHash wajib
|
|
@@ -9624,7 +9756,7 @@ var init_sync = __esm({
|
|
|
9624
9756
|
};
|
|
9625
9757
|
DATE_FIELDS = {
|
|
9626
9758
|
project: ["updatedAt"],
|
|
9627
|
-
spec: ["createdAt", "startedAt", "updatedAt"],
|
|
9759
|
+
spec: ["createdAt", "startedAt", "doneAt", "updatedAt"],
|
|
9628
9760
|
vps: ["lastSeenAt", "lastAuditAt", "updatedAt"],
|
|
9629
9761
|
sessionResult: ["createdAt", "updatedAt"],
|
|
9630
9762
|
ticket: ["createdAt", "updatedAt"],
|
|
@@ -9671,6 +9803,13 @@ async function conflictSessionDefaults() {
|
|
|
9671
9803
|
if (!c.enabled) return agentDefaultsOf(s2);
|
|
9672
9804
|
return c.agent === "codex" ? { agent: "codex", model: c.model, effort: coerceCodexEffort(c.model, c.effort) } : { agent: "claude", model: c.model, effort: c.effort };
|
|
9673
9805
|
}
|
|
9806
|
+
async function terminalAgentDefaults(o) {
|
|
9807
|
+
const s2 = await getSetting();
|
|
9808
|
+
const base2 = o.agent ? o.agent === "codex" ? { agent: "codex", model: s2.codex.model, effort: s2.codex.effort } : { agent: "claude", model: s2.model, effort: s2.effort } : agentDefaultsOf(s2);
|
|
9809
|
+
const model = o.model ?? base2.model;
|
|
9810
|
+
const effort = o.effort ?? base2.effort;
|
|
9811
|
+
return base2.agent === "codex" ? { agent: "codex", model, effort: coerceCodexEffort(model, effort) } : { agent: "claude", model, effort };
|
|
9812
|
+
}
|
|
9674
9813
|
var STEP, DEFAULT_SETTING, RETIRED_MODELS;
|
|
9675
9814
|
var init_settings2 = __esm({
|
|
9676
9815
|
"src/services/settings.ts"() {
|
|
@@ -9703,8 +9842,10 @@ var init_settings2 = __esm({
|
|
|
9703
9842
|
// SPEC-383 · ADR-0081 · default sesi konflik (opt-in, mati)
|
|
9704
9843
|
lead: LEAD_DEFAULTS,
|
|
9705
9844
|
// SPEC-409 · ADR-0091 · hanoman-lead (master switch mati)
|
|
9706
|
-
telegram: TELEGRAM_DEFAULTS
|
|
9845
|
+
telegram: TELEGRAM_DEFAULTS,
|
|
9707
9846
|
// SPEC-476 · ADR-0096 · gateway Telegram opt-in
|
|
9847
|
+
changelog: CHANGELOG_ENGINE_DEFAULTS
|
|
9848
|
+
// SPEC-518 · agen pembuat changelog (opt-in, mati)
|
|
9708
9849
|
};
|
|
9709
9850
|
RETIRED_MODELS = { "claude-opus-4-8": "claude-opus-5" };
|
|
9710
9851
|
}
|
|
@@ -9940,8 +10081,8 @@ async function telegramEngineContext() {
|
|
|
9940
10081
|
};
|
|
9941
10082
|
}
|
|
9942
10083
|
function telegramReloadNeeded(before, after) {
|
|
9943
|
-
const
|
|
9944
|
-
return JSON.stringify(
|
|
10084
|
+
const strip2 = ({ engine: _engine, ...rest }) => rest;
|
|
10085
|
+
return JSON.stringify(strip2(before)) !== JSON.stringify(strip2(after));
|
|
9945
10086
|
}
|
|
9946
10087
|
var init_config3 = __esm({
|
|
9947
10088
|
"src/services/telegram/config.ts"() {
|
|
@@ -11311,7 +11452,7 @@ var require_permessage_deflate = __commonJS({
|
|
|
11311
11452
|
var Limiter = require_limiter();
|
|
11312
11453
|
var { kStatusCode } = require_constants();
|
|
11313
11454
|
var FastBuffer = Buffer[Symbol.species];
|
|
11314
|
-
var
|
|
11455
|
+
var TRAILER2 = Buffer.from([0, 0, 255, 255]);
|
|
11315
11456
|
var kPerMessageDeflate = Symbol("permessage-deflate");
|
|
11316
11457
|
var kTotalLength = Symbol("total-length");
|
|
11317
11458
|
var kCallback = Symbol("callback");
|
|
@@ -11586,7 +11727,7 @@ var require_permessage_deflate = __commonJS({
|
|
|
11586
11727
|
}
|
|
11587
11728
|
this._inflate[kCallback] = callback;
|
|
11588
11729
|
this._inflate.write(data);
|
|
11589
|
-
if (fin) this._inflate.write(
|
|
11730
|
+
if (fin) this._inflate.write(TRAILER2);
|
|
11590
11731
|
this._inflate.flush(() => {
|
|
11591
11732
|
const err = this._inflate[kError];
|
|
11592
11733
|
if (err) {
|
|
@@ -16697,7 +16838,7 @@ var require_Decoder = __commonJS({
|
|
|
16697
16838
|
"../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/lib/utils/Decoder.js"(exports, module) {
|
|
16698
16839
|
"use strict";
|
|
16699
16840
|
var RE_PLUS = /\+/g;
|
|
16700
|
-
var
|
|
16841
|
+
var HEX2 = [
|
|
16701
16842
|
0,
|
|
16702
16843
|
0,
|
|
16703
16844
|
0,
|
|
@@ -16838,7 +16979,7 @@ var require_Decoder = __commonJS({
|
|
|
16838
16979
|
const len = str3.length;
|
|
16839
16980
|
for (; i < len; ++i) {
|
|
16840
16981
|
if (this.buffer !== void 0) {
|
|
16841
|
-
if (!
|
|
16982
|
+
if (!HEX2[str3.charCodeAt(i)]) {
|
|
16842
16983
|
res += "%" + this.buffer;
|
|
16843
16984
|
this.buffer = void 0;
|
|
16844
16985
|
--i;
|
|
@@ -17260,7 +17401,7 @@ var require_plugin = __commonJS({
|
|
|
17260
17401
|
var require_generateId = __commonJS({
|
|
17261
17402
|
"../node_modules/.pnpm/@fastify+multipart@8.3.1/node_modules/@fastify/multipart/lib/generateId.js"(exports, module) {
|
|
17262
17403
|
"use strict";
|
|
17263
|
-
var
|
|
17404
|
+
var HEX2 = [
|
|
17264
17405
|
"00",
|
|
17265
17406
|
"01",
|
|
17266
17407
|
"02",
|
|
@@ -17520,13 +17661,13 @@ var require_generateId = __commonJS({
|
|
|
17520
17661
|
];
|
|
17521
17662
|
var random = Math.random;
|
|
17522
17663
|
function seed() {
|
|
17523
|
-
return
|
|
17664
|
+
return HEX2[255 * random() | 0] + HEX2[255 * random() | 0] + HEX2[255 * random() | 0] + HEX2[255 * random() | 0] + HEX2[255 * random() | 0] + HEX2[255 * random() | 0] + HEX2[255 * random() | 0];
|
|
17524
17665
|
}
|
|
17525
17666
|
module.exports.generateId = function generateIdFn() {
|
|
17526
17667
|
let num = 0;
|
|
17527
17668
|
let str3 = seed();
|
|
17528
17669
|
return function generateId() {
|
|
17529
|
-
return num === 255 ? (str3 = seed()) +
|
|
17670
|
+
return num === 255 ? (str3 = seed()) + HEX2[num = 0] : str3 + HEX2[++num];
|
|
17530
17671
|
};
|
|
17531
17672
|
}();
|
|
17532
17673
|
}
|
|
@@ -18359,7 +18500,7 @@ import websocket from "@fastify/websocket";
|
|
|
18359
18500
|
import cookie from "@fastify/cookie";
|
|
18360
18501
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
18361
18502
|
import { dirname as dirname13 } from "node:path";
|
|
18362
|
-
import { existsSync as
|
|
18503
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
18363
18504
|
|
|
18364
18505
|
// src/web-dir.ts
|
|
18365
18506
|
import { resolve } from "node:path";
|
|
@@ -21817,6 +21958,8 @@ async function recordDrift(vpsId, vpsName, drift, snapshotId) {
|
|
|
21817
21958
|
});
|
|
21818
21959
|
}
|
|
21819
21960
|
async function recordCompletion(specId, title, projectId) {
|
|
21961
|
+
await prisma.spec.updateMany({ where: { id: specId, doneAt: null }, data: { doneAt: /* @__PURE__ */ new Date() } }).catch(() => {
|
|
21962
|
+
});
|
|
21820
21963
|
const sessionId2 = specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
21821
21964
|
await prisma.notification.create({
|
|
21822
21965
|
data: { type: "done", key: `done:${specId}`, specId, sessionId: sessionId2, title, projectId }
|
|
@@ -21868,11 +22011,19 @@ async function scanDecisions(read = liveDecisions) {
|
|
|
21868
22011
|
});
|
|
21869
22012
|
}
|
|
21870
22013
|
}
|
|
21871
|
-
|
|
22014
|
+
var DEFAULT_FEED_TAKE = 50;
|
|
22015
|
+
async function notificationsFeed(p = {}) {
|
|
21872
22016
|
await scanDecisions();
|
|
21873
|
-
const
|
|
22017
|
+
const pageSize = p.limit ? Math.max(1, Math.floor(+p.limit) || 1) : DEFAULT_FEED_TAKE;
|
|
22018
|
+
const page = p.page ? Math.max(1, Math.floor(+p.page) || 1) : 1;
|
|
22019
|
+
const total = await prisma.notification.count();
|
|
22020
|
+
const items = await prisma.notification.findMany({
|
|
22021
|
+
orderBy: { createdAt: "desc" },
|
|
22022
|
+
skip: (page - 1) * pageSize,
|
|
22023
|
+
take: pageSize
|
|
22024
|
+
});
|
|
21874
22025
|
const unread = await prisma.notification.count({ where: { readAt: null } });
|
|
21875
|
-
return { items, unread };
|
|
22026
|
+
return { items, unread, total, page, pageSize };
|
|
21876
22027
|
}
|
|
21877
22028
|
|
|
21878
22029
|
// src/services/spec-head.ts
|
|
@@ -22194,7 +22345,7 @@ ${item.context}` : item.context;
|
|
|
22194
22345
|
// src/routes/notifications.ts
|
|
22195
22346
|
init_db();
|
|
22196
22347
|
async function notifications_default(app2) {
|
|
22197
|
-
app2.get("/notifications", async () => notificationsFeed());
|
|
22348
|
+
app2.get("/notifications", async (req) => notificationsFeed(req.query));
|
|
22198
22349
|
app2.post("/notifications/read", async (_req, reply) => {
|
|
22199
22350
|
await prisma.notification.updateMany({ where: { readAt: null }, data: { readAt: /* @__PURE__ */ new Date() } });
|
|
22200
22351
|
return reply.code(204).send();
|
|
@@ -22233,11 +22384,12 @@ async function settings_default(app2) {
|
|
|
22233
22384
|
init_src();
|
|
22234
22385
|
|
|
22235
22386
|
// src/services/project-prds.ts
|
|
22387
|
+
init_src();
|
|
22236
22388
|
init_db();
|
|
22237
22389
|
init_pty();
|
|
22238
|
-
var
|
|
22239
|
-
var isPrd = (rel) => rel.startsWith(
|
|
22240
|
-
var slugOf = (rel) => rel.slice(
|
|
22390
|
+
var PRD_DIR2 = "docs/prd/";
|
|
22391
|
+
var isPrd = (rel) => rel.startsWith(PRD_DIR2) && rel.endsWith(".md") && !rel.endsWith(".breakdown.md");
|
|
22392
|
+
var slugOf = (rel) => rel.slice(PRD_DIR2.length, -3);
|
|
22241
22393
|
var titleOf = (content, slug) => {
|
|
22242
22394
|
const m = content?.match(/^#\s+(.+)$/m);
|
|
22243
22395
|
return m ? m[1].trim() : slug;
|
|
@@ -22247,13 +22399,26 @@ async function resolveDir2(projectId, sessions = listSessions()) {
|
|
|
22247
22399
|
if (live) return { dir: live.cwd, live: true };
|
|
22248
22400
|
return { dir: await resolveRepoDir(projectId), live: false };
|
|
22249
22401
|
}
|
|
22250
|
-
async function
|
|
22402
|
+
async function loadTraces(projectIds) {
|
|
22403
|
+
const map = new Map(projectIds.map((id) => [id, []]));
|
|
22404
|
+
if (!projectIds.length) return map;
|
|
22405
|
+
const rows = await prisma.spec.findMany({
|
|
22406
|
+
where: { projectId: { in: projectIds } },
|
|
22407
|
+
select: { projectId: true, stage: true, payload: true, branchFrom: true }
|
|
22408
|
+
});
|
|
22409
|
+
for (const r of rows)
|
|
22410
|
+
map.get(r.projectId)?.push({ stage: r.stage, payload: r.payload, branchFrom: r.branchFrom });
|
|
22411
|
+
return map;
|
|
22412
|
+
}
|
|
22413
|
+
async function listPrds(projectId, sessions = listSessions(), traces) {
|
|
22251
22414
|
const { dir: dir2, live } = await resolveDir2(projectId, sessions);
|
|
22252
22415
|
if (!dir2) return [];
|
|
22253
22416
|
const project = await prisma.project.findUnique({ where: { id: projectId }, select: { name: true } });
|
|
22254
22417
|
const projectName = project?.name ?? projectId;
|
|
22418
|
+
const specs = traces ?? (await loadTraces([projectId])).get(projectId) ?? [];
|
|
22255
22419
|
return (await listRepoDocs(dir2)).filter(isPrd).map((rel) => {
|
|
22256
22420
|
const slug = slugOf(rel);
|
|
22421
|
+
const { status, specCount, doneCount } = prdStatusOf(rel, specs);
|
|
22257
22422
|
return {
|
|
22258
22423
|
slug,
|
|
22259
22424
|
name: rel.slice(rel.lastIndexOf("/") + 1),
|
|
@@ -22261,7 +22426,10 @@ async function listPrds(projectId, sessions = listSessions()) {
|
|
|
22261
22426
|
title: titleOf(readDocFile(dir2, rel), slug),
|
|
22262
22427
|
live,
|
|
22263
22428
|
projectId,
|
|
22264
|
-
projectName
|
|
22429
|
+
projectName,
|
|
22430
|
+
status,
|
|
22431
|
+
specCount,
|
|
22432
|
+
doneCount
|
|
22265
22433
|
};
|
|
22266
22434
|
}).sort((a, b) => a.slug.localeCompare(b.slug));
|
|
22267
22435
|
}
|
|
@@ -22270,7 +22438,10 @@ async function listAllPrds(sessions = listSessions()) {
|
|
|
22270
22438
|
orderBy: { createdAt: "desc" },
|
|
22271
22439
|
select: { id: true }
|
|
22272
22440
|
});
|
|
22273
|
-
const
|
|
22441
|
+
const traces = await loadTraces(projects.map((p) => p.id));
|
|
22442
|
+
const nested = await Promise.all(
|
|
22443
|
+
projects.map((p) => listPrds(p.id, sessions, traces.get(p.id) ?? []))
|
|
22444
|
+
);
|
|
22274
22445
|
return nested.flat();
|
|
22275
22446
|
}
|
|
22276
22447
|
async function readPrd(projectId, path, sessions = listSessions()) {
|
|
@@ -22282,8 +22453,8 @@ async function readPrd(projectId, path, sessions = listSessions()) {
|
|
|
22282
22453
|
// src/services/project-breakdowns.ts
|
|
22283
22454
|
init_src();
|
|
22284
22455
|
init_pty();
|
|
22285
|
-
var
|
|
22286
|
-
var isPrd2 = (rel) => rel.startsWith(
|
|
22456
|
+
var PRD_DIR3 = "docs/prd/";
|
|
22457
|
+
var isPrd2 = (rel) => rel.startsWith(PRD_DIR3) && rel.endsWith(".md");
|
|
22287
22458
|
function breakdownPathFor(prdPath) {
|
|
22288
22459
|
if (!isPrd2(prdPath) || prdPath.endsWith(".breakdown.md")) return null;
|
|
22289
22460
|
return prdPath.slice(0, -3) + ".breakdown.md";
|
|
@@ -22523,7 +22694,7 @@ async function listStashes(repoDir) {
|
|
|
22523
22694
|
}
|
|
22524
22695
|
}
|
|
22525
22696
|
async function listGraph(repoDir, limit = 200, opts = {}) {
|
|
22526
|
-
if (!repoDir || !existsSync6(repoDir)) return { commits: [], current: "" };
|
|
22697
|
+
if (!repoDir || !existsSync6(repoDir)) return { commits: [], current: "", total: 0 };
|
|
22527
22698
|
try {
|
|
22528
22699
|
const fmt = ["%H", "%P", "%an", "%aI", "%s", "%D"].join(US);
|
|
22529
22700
|
const refArgs = opts.branches?.length ? ["--end-of-options", ...opts.branches] : [
|
|
@@ -22555,9 +22726,16 @@ async function listGraph(repoDir, limit = 200, opts = {}) {
|
|
|
22555
22726
|
tags
|
|
22556
22727
|
};
|
|
22557
22728
|
});
|
|
22558
|
-
|
|
22729
|
+
let total = commits.length;
|
|
22730
|
+
try {
|
|
22731
|
+
const c = await exec6("git", ["rev-list", "--count", ...refArgs], { cwd: repoDir, ...GIT5 });
|
|
22732
|
+
const n2 = Number(c.stdout.trim());
|
|
22733
|
+
if (Number.isFinite(n2)) total = n2;
|
|
22734
|
+
} catch {
|
|
22735
|
+
}
|
|
22736
|
+
return { commits, current: await currentBranch(repoDir), total };
|
|
22559
22737
|
} catch {
|
|
22560
|
-
return { commits: [], current: "" };
|
|
22738
|
+
return { commits: [], current: "", total: 0 };
|
|
22561
22739
|
}
|
|
22562
22740
|
}
|
|
22563
22741
|
function mergeNumName(numOut, nameOut) {
|
|
@@ -23718,7 +23896,7 @@ async function terminal_default(app2) {
|
|
|
23718
23896
|
});
|
|
23719
23897
|
return reply.code(201).send({ id: s3.id });
|
|
23720
23898
|
}
|
|
23721
|
-
const { agent, model, effort } = await
|
|
23899
|
+
const { agent, model, effort } = await terminalAgentDefaults(parsed.data);
|
|
23722
23900
|
if (agent === "codex") ensureCodexTrust(repoDir);
|
|
23723
23901
|
const s2 = createSession(project.id, repoDir, { agent, model, effort });
|
|
23724
23902
|
return reply.code(201).send({ id: s2.id });
|
|
@@ -25929,7 +26107,9 @@ async function tickets_default(app2) {
|
|
|
25929
26107
|
}
|
|
25930
26108
|
|
|
25931
26109
|
// src/routes/scheduler.ts
|
|
26110
|
+
init_zod();
|
|
25932
26111
|
init_src();
|
|
26112
|
+
init_db();
|
|
25933
26113
|
|
|
25934
26114
|
// src/services/scheduler/config.ts
|
|
25935
26115
|
init_db();
|
|
@@ -25958,12 +26138,39 @@ async function enqueue(i) {
|
|
|
25958
26138
|
function listQueue(status) {
|
|
25959
26139
|
return prisma.schedulerQueueItem.findMany({ where: status ? { status } : void 0 });
|
|
25960
26140
|
}
|
|
26141
|
+
async function listQueuePage(f = {}) {
|
|
26142
|
+
const where = f.status ? { status: f.status } : void 0;
|
|
26143
|
+
const total = await prisma.schedulerQueueItem.count({ where });
|
|
26144
|
+
const pageSize = f.limit ? Math.max(1, Math.floor(+f.limit) || 1) : total || 1;
|
|
26145
|
+
const page = f.page ? Math.max(1, Math.floor(+f.page) || 1) : 1;
|
|
26146
|
+
const items = await prisma.schedulerQueueItem.findMany({
|
|
26147
|
+
where,
|
|
26148
|
+
orderBy: { enqueuedAt: "desc" },
|
|
26149
|
+
skip: (page - 1) * pageSize,
|
|
26150
|
+
take: pageSize
|
|
26151
|
+
});
|
|
26152
|
+
return { items, total, page, pageSize };
|
|
26153
|
+
}
|
|
26154
|
+
async function queueCounts() {
|
|
26155
|
+
const [queued3, launched, done, failed, canceled] = await Promise.all([
|
|
26156
|
+
prisma.schedulerQueueItem.count({ where: { status: "queued" } }),
|
|
26157
|
+
prisma.schedulerQueueItem.count({ where: { status: "launched" } }),
|
|
26158
|
+
prisma.schedulerQueueItem.count({ where: { status: "done" } }),
|
|
26159
|
+
prisma.schedulerQueueItem.count({ where: { status: "failed" } }),
|
|
26160
|
+
prisma.schedulerQueueItem.count({ where: { status: "canceled" } })
|
|
26161
|
+
]);
|
|
26162
|
+
return { queued: queued3, launched, done, failed, canceled };
|
|
26163
|
+
}
|
|
25961
26164
|
async function queued2() {
|
|
25962
26165
|
const items = await prisma.schedulerQueueItem.findMany({ where: { status: "queued" } });
|
|
25963
26166
|
return items.sort((a, b) => (RANK[a.priority] ?? 1) - (RANK[b.priority] ?? 1) || a.enqueuedAt.getTime() - b.enqueuedAt.getTime());
|
|
25964
26167
|
}
|
|
25965
26168
|
async function markLaunched(id, sessionId2) {
|
|
25966
|
-
|
|
26169
|
+
const { count } = await prisma.schedulerQueueItem.updateMany({
|
|
26170
|
+
where: { id, status: "queued" },
|
|
26171
|
+
data: { status: "launched", sessionId: sessionId2, launchedAt: /* @__PURE__ */ new Date() }
|
|
26172
|
+
});
|
|
26173
|
+
return count > 0;
|
|
25967
26174
|
}
|
|
25968
26175
|
async function markFailed(id, note) {
|
|
25969
26176
|
await prisma.schedulerQueueItem.update({ where: { id }, data: { status: "failed", note: note ?? null } });
|
|
@@ -25974,11 +26181,29 @@ async function markDone(id, note) {
|
|
|
25974
26181
|
data: { status: "done", ...note ? { note } : {} }
|
|
25975
26182
|
});
|
|
25976
26183
|
}
|
|
25977
|
-
async function
|
|
26184
|
+
async function noteRow(id, note) {
|
|
25978
26185
|
const row = await prisma.schedulerQueueItem.findUnique({ where: { id }, select: { note: true } });
|
|
25979
26186
|
if (row?.note === note) return;
|
|
25980
26187
|
await prisma.schedulerQueueItem.update({ where: { id }, data: { note } });
|
|
25981
26188
|
}
|
|
26189
|
+
async function markCanceled(id, note) {
|
|
26190
|
+
const { count } = await prisma.schedulerQueueItem.updateMany({
|
|
26191
|
+
where: { id, status: "queued" },
|
|
26192
|
+
data: { status: "canceled", note }
|
|
26193
|
+
});
|
|
26194
|
+
return count > 0;
|
|
26195
|
+
}
|
|
26196
|
+
async function markRequeued(id) {
|
|
26197
|
+
const { count } = await prisma.schedulerQueueItem.updateMany({
|
|
26198
|
+
where: { id, status: "canceled" },
|
|
26199
|
+
data: { status: "queued", note: null }
|
|
26200
|
+
});
|
|
26201
|
+
return count > 0;
|
|
26202
|
+
}
|
|
26203
|
+
async function isQueued(id) {
|
|
26204
|
+
const row = await prisma.schedulerQueueItem.findUnique({ where: { id }, select: { status: true } });
|
|
26205
|
+
return row?.status === "queued";
|
|
26206
|
+
}
|
|
25982
26207
|
|
|
25983
26208
|
// src/services/scheduler/registry.ts
|
|
25984
26209
|
var sources = /* @__PURE__ */ new Map();
|
|
@@ -26002,6 +26227,14 @@ function isDue(id, everyMin, now) {
|
|
|
26002
26227
|
|
|
26003
26228
|
// src/routes/scheduler.ts
|
|
26004
26229
|
init_pty();
|
|
26230
|
+
var CANCEL_NOTE = "dibatalkan operator";
|
|
26231
|
+
var zCancelBody = external_exports.object({ reason: external_exports.string().trim().max(200).optional() });
|
|
26232
|
+
async function refuse(reply, id, verb) {
|
|
26233
|
+
const row = await prisma.schedulerQueueItem.findUnique({ where: { id }, select: { status: true } });
|
|
26234
|
+
if (!row) return reply.code(404).send({ error: "item antrean tak ada" });
|
|
26235
|
+
const why = row.status === "launched" ? "sesinya sudah berjalan \u2014 tutup dari Terminal bila memang tak diperlukan" : `statusnya sudah ${row.status}`;
|
|
26236
|
+
return reply.code(409).send({ error: `tak bisa ${verb}: ${why}`, status: row.status });
|
|
26237
|
+
}
|
|
26005
26238
|
async function scheduler_default(app2) {
|
|
26006
26239
|
app2.get("/scheduler/config", async () => getScheduler());
|
|
26007
26240
|
app2.put("/scheduler/config", async (req, reply) => {
|
|
@@ -26009,10 +26242,22 @@ async function scheduler_default(app2) {
|
|
|
26009
26242
|
if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
|
|
26010
26243
|
return setScheduler(parsed.data);
|
|
26011
26244
|
});
|
|
26245
|
+
app2.post("/scheduler/queue/:id/cancel", async (req, reply) => {
|
|
26246
|
+
const { id } = req.params;
|
|
26247
|
+
const parsed = zCancelBody.safeParse(req.body ?? {});
|
|
26248
|
+
if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
|
|
26249
|
+
const note = parsed.data.reason ? `${CANCEL_NOTE}: ${parsed.data.reason}` : CANCEL_NOTE;
|
|
26250
|
+
if (!await markCanceled(id, note)) return refuse(reply, id, "membatalkan");
|
|
26251
|
+
return prisma.schedulerQueueItem.findUnique({ where: { id } });
|
|
26252
|
+
});
|
|
26253
|
+
app2.post("/scheduler/queue/:id/requeue", async (req, reply) => {
|
|
26254
|
+
const { id } = req.params;
|
|
26255
|
+
if (!await markRequeued(id)) return refuse(reply, id, "mengembalikan ke antrean");
|
|
26256
|
+
return prisma.schedulerQueueItem.findUnique({ where: { id } });
|
|
26257
|
+
});
|
|
26012
26258
|
app2.get("/scheduler/state", async () => {
|
|
26013
26259
|
const cfg = await getScheduler();
|
|
26014
26260
|
const live = listSessions().filter((s2) => !s2.exited);
|
|
26015
|
-
const queue2 = await listQueue();
|
|
26016
26261
|
const srcView = (id, sc, minCount) => {
|
|
26017
26262
|
const last = getLastRun(id);
|
|
26018
26263
|
return {
|
|
@@ -26028,9 +26273,31 @@ async function scheduler_default(app2) {
|
|
|
26028
26273
|
srcView("backlog", cfg.sources.backlog),
|
|
26029
26274
|
srcView("triase", cfg.sources.triase)
|
|
26030
26275
|
];
|
|
26031
|
-
const
|
|
26276
|
+
const counts = await queueCounts();
|
|
26277
|
+
const launchedSpecs = new Set((await listQueue("launched")).map((q) => q.specId));
|
|
26032
26278
|
const sessions = live.filter((s2) => s2.specId && launchedSpecs.has(s2.specId));
|
|
26033
|
-
return { config: cfg, cap: cfg.maxConcurrent, liveCount: live.length, sources: sources2,
|
|
26279
|
+
return { config: cfg, cap: cfg.maxConcurrent, liveCount: live.length, sources: sources2, queueCounts: counts, sessions };
|
|
26280
|
+
});
|
|
26281
|
+
app2.get("/scheduler/queue", async (req) => {
|
|
26282
|
+
const { status, page, limit } = req.query;
|
|
26283
|
+
const r = await listQueuePage({ status, page, limit });
|
|
26284
|
+
return {
|
|
26285
|
+
items: r.items.map((q) => ({
|
|
26286
|
+
id: q.id,
|
|
26287
|
+
specId: q.specId,
|
|
26288
|
+
projectId: q.projectId,
|
|
26289
|
+
source: q.source,
|
|
26290
|
+
priority: q.priority,
|
|
26291
|
+
status: q.status,
|
|
26292
|
+
sessionId: q.sessionId,
|
|
26293
|
+
note: q.note,
|
|
26294
|
+
enqueuedAt: q.enqueuedAt.toISOString(),
|
|
26295
|
+
launchedAt: q.launchedAt ? q.launchedAt.toISOString() : null
|
|
26296
|
+
})),
|
|
26297
|
+
total: r.total,
|
|
26298
|
+
page: r.page,
|
|
26299
|
+
pageSize: r.pageSize
|
|
26300
|
+
};
|
|
26034
26301
|
});
|
|
26035
26302
|
}
|
|
26036
26303
|
|
|
@@ -26209,6 +26476,22 @@ function keepExistingRefs(refs2, repoDir) {
|
|
|
26209
26476
|
// src/services/lead/trail.ts
|
|
26210
26477
|
init_db();
|
|
26211
26478
|
init_src();
|
|
26479
|
+
|
|
26480
|
+
// src/services/lead/page.ts
|
|
26481
|
+
var LEAD_MAX_TAKE = 200;
|
|
26482
|
+
var LEAD_DEFAULT_TAKE = 50;
|
|
26483
|
+
function leadWindow(f) {
|
|
26484
|
+
if (f.limit !== void 0 || f.page !== void 0) {
|
|
26485
|
+
const pageSize = Math.min(Math.max(1, Math.floor(f.limit ?? LEAD_DEFAULT_TAKE) || 1), LEAD_MAX_TAKE);
|
|
26486
|
+
const page = Math.max(1, Math.floor(f.page ?? 1) || 1);
|
|
26487
|
+
return { skip: (page - 1) * pageSize, take: pageSize, page, pageSize };
|
|
26488
|
+
}
|
|
26489
|
+
const take2 = Math.min(f.take ?? LEAD_DEFAULT_TAKE, LEAD_MAX_TAKE);
|
|
26490
|
+
const skip = f.skip ?? 0;
|
|
26491
|
+
return { skip, take: take2, page: Math.floor(skip / Math.max(1, take2)) + 1, pageSize: take2 };
|
|
26492
|
+
}
|
|
26493
|
+
|
|
26494
|
+
// src/services/lead/trail.ts
|
|
26212
26495
|
async function recordDecision(i) {
|
|
26213
26496
|
return prisma.leadDecision.create({
|
|
26214
26497
|
data: {
|
|
@@ -26243,20 +26526,24 @@ async function recordDecision(i) {
|
|
|
26243
26526
|
});
|
|
26244
26527
|
}
|
|
26245
26528
|
async function listDecisions(f = {}) {
|
|
26246
|
-
|
|
26247
|
-
|
|
26248
|
-
|
|
26249
|
-
|
|
26250
|
-
|
|
26251
|
-
|
|
26252
|
-
|
|
26253
|
-
|
|
26529
|
+
const where = {
|
|
26530
|
+
...f.projectId ? { projectId: f.projectId } : {},
|
|
26531
|
+
...f.specId ? { specId: f.specId } : {},
|
|
26532
|
+
...f.sessionId ? { sessionId: f.sessionId } : {},
|
|
26533
|
+
...f.flowId ? { flowId: f.flowId } : {},
|
|
26534
|
+
...f.status ? { status: f.status } : {}
|
|
26535
|
+
};
|
|
26536
|
+
const w = leadWindow(f);
|
|
26537
|
+
const total = await prisma.leadDecision.count({ where });
|
|
26538
|
+
const rows = await prisma.leadDecision.findMany({
|
|
26539
|
+
where,
|
|
26254
26540
|
// SPEC-485 · satu RANTAI dibaca dari awal: urutan pertanyaannya adalah isi jejaknya. Daftar
|
|
26255
26541
|
// umum tetap terbaru-dulu (AC-24) — dua pertanyaan yang berbeda, dua urutan yang berbeda.
|
|
26256
26542
|
orderBy: { createdAt: f.flowId ? "asc" : "desc" },
|
|
26257
|
-
take:
|
|
26258
|
-
skip:
|
|
26543
|
+
take: w.take,
|
|
26544
|
+
skip: w.skip
|
|
26259
26545
|
});
|
|
26546
|
+
return { rows, total, page: w.page, pageSize: w.pageSize };
|
|
26260
26547
|
}
|
|
26261
26548
|
async function overrideDecision(id, answer, reason, rawChoices = []) {
|
|
26262
26549
|
const old = await prisma.leadDecision.findUnique({ where: { id } });
|
|
@@ -26500,15 +26787,19 @@ async function closeFlow(id, reason) {
|
|
|
26500
26787
|
});
|
|
26501
26788
|
}
|
|
26502
26789
|
async function listFlows(f = {}) {
|
|
26503
|
-
|
|
26504
|
-
|
|
26505
|
-
|
|
26506
|
-
|
|
26507
|
-
|
|
26790
|
+
const where = {
|
|
26791
|
+
...f.projectId ? { projectId: f.projectId } : {},
|
|
26792
|
+
...f.status ? { status: f.status } : {}
|
|
26793
|
+
};
|
|
26794
|
+
const w = leadWindow(f);
|
|
26795
|
+
const total = await prisma.leadFlow.count({ where });
|
|
26796
|
+
const rows = await prisma.leadFlow.findMany({
|
|
26797
|
+
where,
|
|
26508
26798
|
orderBy: { createdAt: "desc" },
|
|
26509
|
-
take:
|
|
26510
|
-
skip:
|
|
26799
|
+
take: w.take,
|
|
26800
|
+
skip: w.skip
|
|
26511
26801
|
});
|
|
26802
|
+
return { rows, total, page: w.page, pageSize: w.pageSize };
|
|
26512
26803
|
}
|
|
26513
26804
|
async function expireFlows(now) {
|
|
26514
26805
|
const due = await prisma.leadFlow.findMany({
|
|
@@ -27636,7 +27927,7 @@ async function lead_default(app2) {
|
|
|
27636
27927
|
});
|
|
27637
27928
|
app2.get("/lead/decisions", async (req) => {
|
|
27638
27929
|
const q = req.query;
|
|
27639
|
-
const
|
|
27930
|
+
const r = await listDecisions({
|
|
27640
27931
|
projectId: q.projectId,
|
|
27641
27932
|
specId: q.specId,
|
|
27642
27933
|
sessionId: q.sessionId,
|
|
@@ -27644,19 +27935,23 @@ async function lead_default(app2) {
|
|
|
27644
27935
|
// SPEC-485 · satu rantai dibaca lewat filter ini, urut NAIK (lihat `listDecisions`).
|
|
27645
27936
|
flowId: q.flowId,
|
|
27646
27937
|
take: q.take ? Number(q.take) : void 0,
|
|
27647
|
-
skip: q.skip ? Number(q.skip) : void 0
|
|
27938
|
+
skip: q.skip ? Number(q.skip) : void 0,
|
|
27939
|
+
page: q.page ? Number(q.page) : void 0,
|
|
27940
|
+
limit: q.limit ? Number(q.limit) : void 0
|
|
27648
27941
|
});
|
|
27649
|
-
return { items: rows.map(toDecisionView) };
|
|
27942
|
+
return { items: r.rows.map(toDecisionView), total: r.total, page: r.page, pageSize: r.pageSize };
|
|
27650
27943
|
});
|
|
27651
27944
|
app2.get("/lead/flows", async (req) => {
|
|
27652
27945
|
const q = req.query;
|
|
27653
|
-
const
|
|
27946
|
+
const r = await listFlows({
|
|
27654
27947
|
projectId: q.projectId,
|
|
27655
27948
|
status: q.status,
|
|
27656
27949
|
take: q.take ? Number(q.take) : void 0,
|
|
27657
|
-
skip: q.skip ? Number(q.skip) : void 0
|
|
27950
|
+
skip: q.skip ? Number(q.skip) : void 0,
|
|
27951
|
+
page: q.page ? Number(q.page) : void 0,
|
|
27952
|
+
limit: q.limit ? Number(q.limit) : void 0
|
|
27658
27953
|
});
|
|
27659
|
-
return { items: rows.map(toFlowView) };
|
|
27954
|
+
return { items: r.rows.map(toFlowView), total: r.total, page: r.page, pageSize: r.pageSize };
|
|
27660
27955
|
});
|
|
27661
27956
|
app2.post("/lead/flows/:id/submit", async (req, reply) => {
|
|
27662
27957
|
const { id } = req.params;
|
|
@@ -27763,6 +28058,331 @@ async function lead_default(app2) {
|
|
|
27763
28058
|
});
|
|
27764
28059
|
}
|
|
27765
28060
|
|
|
28061
|
+
// src/routes/changelog.ts
|
|
28062
|
+
init_src();
|
|
28063
|
+
init_db();
|
|
28064
|
+
|
|
28065
|
+
// src/services/changelog/collect.ts
|
|
28066
|
+
init_db();
|
|
28067
|
+
import { execFile as execFile10 } from "node:child_process";
|
|
28068
|
+
import { promisify as promisify9 } from "node:util";
|
|
28069
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
28070
|
+
|
|
28071
|
+
// src/services/changelog/scrub.ts
|
|
28072
|
+
var MERGE = /^merge\s+(branch|pull request|remote-tracking branch|tag)\b/i;
|
|
28073
|
+
var CONVENTIONAL = /^(feat|fix|chore|docs|refactor|test|perf|build|ci|style|revert)(\([^)]*\))?!?:\s*/i;
|
|
28074
|
+
var PATH_LIKE = /(?:[\w.@-]+\/)+[\w.-]+\.\w{1,6}/g;
|
|
28075
|
+
var HEX = /\b(?=[0-9a-f]{7,40}\b)(?=[0-9a-f]*\d)(?=[0-9a-f]*[a-f])[0-9a-f]{7,40}\b/gi;
|
|
28076
|
+
var INTERNAL_REF = /\b(?:SPEC|ADR|PR|ISSUE)[-\s]?\d+\b/gi;
|
|
28077
|
+
var CAMEL = /\b[a-z]{2,}(?:[A-Z][a-z]{2,})+\b/g;
|
|
28078
|
+
var SNAKE = /\b[a-z][a-z0-9]*(?:_[a-z0-9]+)+\b/gi;
|
|
28079
|
+
var CALL = /\b[A-Za-z_][\w.]*\(\s*\)/g;
|
|
28080
|
+
var INLINE_CODE = /`[^`\n]*`/g;
|
|
28081
|
+
var FENCE = /^```[\s\S]*?^```$/gm;
|
|
28082
|
+
var TRAILER = /^[A-Za-z-]+:\s.*$/gm;
|
|
28083
|
+
var tidy = (s2) => s2.replace(/\(\s*\)/g, " ").replace(/[ \t]{2,}/g, " ").replace(/\s+([,.;:!?])/g, "$1").replace(/^[\s:;,·—–-]+/, "").replace(/[\s:;,·—–-]+$/, "").trim();
|
|
28084
|
+
function strip(s2) {
|
|
28085
|
+
return s2.replace(INLINE_CODE, " ").replace(PATH_LIKE, " ").replace(CALL, " ").replace(HEX, " ").replace(INTERNAL_REF, " ").replace(CAMEL, " ").replace(SNAKE, " ");
|
|
28086
|
+
}
|
|
28087
|
+
function scrubSubject(s2) {
|
|
28088
|
+
const one = (s2 ?? "").replace(/\s+/g, " ").trim();
|
|
28089
|
+
if (!one || MERGE.test(one)) return "";
|
|
28090
|
+
return tidy(strip(one.replace(CONVENTIONAL, "")));
|
|
28091
|
+
}
|
|
28092
|
+
function scrubBody(s2) {
|
|
28093
|
+
const first = (s2 ?? "").replace(TRAILER, "").split(/\n\s*\n/)[0] ?? "";
|
|
28094
|
+
return tidy(strip(first.replace(/\s+/g, " ").trim()));
|
|
28095
|
+
}
|
|
28096
|
+
function scrubOutput(md) {
|
|
28097
|
+
const noFence = (md ?? "").replace(FENCE, "");
|
|
28098
|
+
const lines2 = noFence.split("\n").map((line) => {
|
|
28099
|
+
const m = /^(\s*(?:[-*+]|\d+\.)\s+|\s*#{1,6}\s+|\s*>\s*)?(.*)$/.exec(line);
|
|
28100
|
+
const lead = m?.[1] ?? "";
|
|
28101
|
+
const rest = m?.[2] ?? line;
|
|
28102
|
+
const cleaned = tidy(strip(rest));
|
|
28103
|
+
return cleaned ? `${lead}${cleaned}` : "";
|
|
28104
|
+
});
|
|
28105
|
+
return lines2.join("\n").replace(/\n{3,}/g, "\n\n").trim() + "\n";
|
|
28106
|
+
}
|
|
28107
|
+
|
|
28108
|
+
// src/services/changelog/collect.ts
|
|
28109
|
+
var toItem = (label, detail) => {
|
|
28110
|
+
const l = scrubSubject(label);
|
|
28111
|
+
return l ? { label: l, detail: scrubBody(detail) } : null;
|
|
28112
|
+
};
|
|
28113
|
+
async function collectBacklog(projectId, from, to) {
|
|
28114
|
+
const f = dayStart(from), t = dayEnd(to);
|
|
28115
|
+
const rows = await prisma.spec.findMany({
|
|
28116
|
+
where: { projectId, stage: "done" },
|
|
28117
|
+
select: { title: true, objective: true, doneAt: true },
|
|
28118
|
+
orderBy: [{ doneAt: "asc" }, { id: "asc" }]
|
|
28119
|
+
});
|
|
28120
|
+
const stampless = rows.filter((r) => r.doneAt === null).length;
|
|
28121
|
+
const hit = rows.filter((r) => inDayRange(r.doneAt, f, t));
|
|
28122
|
+
const items = hit.map((r) => toItem(r.title, r.objective ?? "")).filter((x) => x !== null);
|
|
28123
|
+
if (items.length === 0)
|
|
28124
|
+
return { ok: false, reason: `tak ada backlog yang selesai antara ${from} dan ${to}` };
|
|
28125
|
+
const notes = [];
|
|
28126
|
+
if (stampless > 0)
|
|
28127
|
+
notes.push(`${stampless} item selesai tanpa stempel waktu (selesai sebelum stempel ini ada) dan tak ikut dihitung.`);
|
|
28128
|
+
return { ok: true, input: { mode: "backlog", title: `${from} \u2013 ${to}`, items, notes } };
|
|
28129
|
+
}
|
|
28130
|
+
var exec8 = promisify9(execFile10);
|
|
28131
|
+
var GIT7 = { maxBuffer: 1 << 24, encoding: "utf8" };
|
|
28132
|
+
var US2 = "";
|
|
28133
|
+
var RS = "";
|
|
28134
|
+
var NO_REPO = "project ini belum ditautkan ke repo di mesin ini";
|
|
28135
|
+
var NO_TAG = "repo project ini belum punya tag rilis";
|
|
28136
|
+
var usable = (repoDir) => !!repoDir && existsSync12(repoDir);
|
|
28137
|
+
async function listTags(repoDir) {
|
|
28138
|
+
if (!usable(repoDir)) return { tags: [], head: null, reason: NO_REPO };
|
|
28139
|
+
try {
|
|
28140
|
+
const { stdout } = await exec8("git", ["tag", "--list", "--sort=-creatordate"], { cwd: repoDir, ...GIT7 });
|
|
28141
|
+
const tags = stdout.split("\n").map((s2) => s2.trim()).filter(Boolean);
|
|
28142
|
+
const head = await exec8("git", ["rev-parse", "--short", "HEAD"], { cwd: repoDir, ...GIT7 }).then((r) => r.stdout.trim()).catch(() => null);
|
|
28143
|
+
return { tags, head, reason: tags.length ? null : NO_TAG };
|
|
28144
|
+
} catch {
|
|
28145
|
+
return { tags: [], head: null, reason: NO_TAG };
|
|
28146
|
+
}
|
|
28147
|
+
}
|
|
28148
|
+
async function previousTag(repoDir, tag2) {
|
|
28149
|
+
return exec8("git", ["describe", "--tags", "--abbrev=0", "--end-of-options", `${tag2}^`], { cwd: repoDir, ...GIT7 }).then((r) => r.stdout.trim() || null).catch(() => null);
|
|
28150
|
+
}
|
|
28151
|
+
async function revExists(repoDir, rev) {
|
|
28152
|
+
return exec8("git", ["rev-parse", "--verify", "--quiet", "--end-of-options", `${rev}^{commit}`], { cwd: repoDir, ...GIT7 }).then(() => true).catch(() => false);
|
|
28153
|
+
}
|
|
28154
|
+
async function logRange(repoDir, range) {
|
|
28155
|
+
const { stdout } = await exec8(
|
|
28156
|
+
"git",
|
|
28157
|
+
["log", "--no-merges", `--pretty=format:%s${US2}%b${RS}`, ...range],
|
|
28158
|
+
{ cwd: repoDir, ...GIT7 }
|
|
28159
|
+
);
|
|
28160
|
+
return stdout.split(RS).map((rec) => rec.replace(/^\n+/, "")).filter((rec) => rec.trim()).map((rec) => {
|
|
28161
|
+
const [subject, body] = rec.split(US2);
|
|
28162
|
+
return toItem(subject ?? "", body ?? "");
|
|
28163
|
+
}).filter((x) => x !== null);
|
|
28164
|
+
}
|
|
28165
|
+
async function collectCommits(repoDir, fromSha, toSha) {
|
|
28166
|
+
if (!usable(repoDir)) return { ok: false, reason: NO_REPO };
|
|
28167
|
+
for (const rev of [fromSha, toSha])
|
|
28168
|
+
if (!await revExists(repoDir, rev)) return { ok: false, reason: `revisi "${rev}" tak dikenal di repo project` };
|
|
28169
|
+
try {
|
|
28170
|
+
const items = await logRange(repoDir, ["--end-of-options", `${fromSha}..${toSha}`]);
|
|
28171
|
+
if (!items.length) return { ok: false, reason: `tak ada perubahan antara "${fromSha}" dan "${toSha}"` };
|
|
28172
|
+
const short = (s2) => s2.slice(0, 7);
|
|
28173
|
+
return { ok: true, input: { mode: "commit", title: `${short(fromSha)} \u2192 ${short(toSha)}`, items, notes: [] } };
|
|
28174
|
+
} catch (e) {
|
|
28175
|
+
return { ok: false, reason: `git menolak rentang itu: ${e.message.split("\n")[0]}` };
|
|
28176
|
+
}
|
|
28177
|
+
}
|
|
28178
|
+
async function collectVersions(repoDir, fromTag, toTag) {
|
|
28179
|
+
const { tags, reason } = await listTags(repoDir);
|
|
28180
|
+
if (reason) return { ok: false, reason };
|
|
28181
|
+
if (!usable(repoDir)) return { ok: false, reason: NO_REPO };
|
|
28182
|
+
if (!tags.includes(toTag)) return { ok: false, reason: `tag "${toTag}" tak ada di repo project` };
|
|
28183
|
+
if (fromTag && !tags.includes(fromTag)) return { ok: false, reason: `tag "${fromTag}" tak ada di repo project` };
|
|
28184
|
+
const prev = fromTag ?? await previousTag(repoDir, toTag);
|
|
28185
|
+
try {
|
|
28186
|
+
const items = await logRange(repoDir, ["--end-of-options", prev ? `${prev}..${toTag}` : toTag]);
|
|
28187
|
+
if (!items.length) return { ok: false, reason: `tak ada perubahan yang masuk ke "${toTag}"` };
|
|
28188
|
+
const title = fromTag ? `${fromTag} \u2192 ${toTag}` : toTag;
|
|
28189
|
+
return { ok: true, input: { mode: "version", title, items, notes: [] } };
|
|
28190
|
+
} catch (e) {
|
|
28191
|
+
return { ok: false, reason: `git menolak rentang itu: ${e.message.split("\n")[0]}` };
|
|
28192
|
+
}
|
|
28193
|
+
}
|
|
28194
|
+
|
|
28195
|
+
// src/services/changelog/generate.ts
|
|
28196
|
+
init_src();
|
|
28197
|
+
init_db();
|
|
28198
|
+
|
|
28199
|
+
// src/services/changelog/config.ts
|
|
28200
|
+
init_src();
|
|
28201
|
+
init_settings2();
|
|
28202
|
+
async function changelogAgentDefaults() {
|
|
28203
|
+
const e = (await getSetting()).changelog;
|
|
28204
|
+
if (!e.enabled) return sessionAgentDefaults();
|
|
28205
|
+
return e.agent === "codex" ? { agent: "codex", model: e.model, effort: coerceCodexEffort(e.model, e.effort) } : { agent: "claude", model: e.model, effort: e.effort };
|
|
28206
|
+
}
|
|
28207
|
+
|
|
28208
|
+
// src/services/changelog/render.ts
|
|
28209
|
+
var MODE_LABEL = {
|
|
28210
|
+
backlog: "backlog yang selesai dalam rentang tanggal",
|
|
28211
|
+
commit: "riwayat perubahan repo dalam rentang yang dipilih",
|
|
28212
|
+
version: "perubahan yang masuk ke versi/rilis itu"
|
|
28213
|
+
};
|
|
28214
|
+
var DETAIL_MAX = 240;
|
|
28215
|
+
var clip2 = (s2) => s2.length > DETAIL_MAX ? `${s2.slice(0, DETAIL_MAX - 1).trimEnd()}\u2026` : s2;
|
|
28216
|
+
function fallbackMarkdown(input) {
|
|
28217
|
+
const lines2 = [`# Changelog \u2014 ${input.title}`, ""];
|
|
28218
|
+
if (input.items.length === 0) {
|
|
28219
|
+
lines2.push("_Tak ada perubahan yang bisa diringkas untuk rentang ini._", "");
|
|
28220
|
+
} else {
|
|
28221
|
+
lines2.push(`Ringkasan ${input.items.length} perubahan untuk pemakai.`, "");
|
|
28222
|
+
for (const it of input.items) {
|
|
28223
|
+
const d = clip2(it.detail.trim());
|
|
28224
|
+
lines2.push(d ? `- **${it.label}** \u2014 ${d}` : `- **${it.label}**`);
|
|
28225
|
+
}
|
|
28226
|
+
lines2.push("");
|
|
28227
|
+
}
|
|
28228
|
+
if (input.notes.length) {
|
|
28229
|
+
lines2.push("---", "");
|
|
28230
|
+
for (const n2 of input.notes) lines2.push(`> ${n2}`);
|
|
28231
|
+
lines2.push("");
|
|
28232
|
+
}
|
|
28233
|
+
return lines2.join("\n");
|
|
28234
|
+
}
|
|
28235
|
+
function changelogPrompt(input, budgetMs) {
|
|
28236
|
+
const budget = Math.max(1, Math.round(budgetMs / 1e3));
|
|
28237
|
+
const items = input.items.map((it, i) => `${i + 1}. ${it.label}${it.detail ? ` \u2014 ${it.detail}` : ""}`).join("\n");
|
|
28238
|
+
return [
|
|
28239
|
+
"Kamu menulis changelog untuk PEMAKAI sebuah produk, bukan untuk developer.",
|
|
28240
|
+
"",
|
|
28241
|
+
`Anggaran waktumu ${budget} detik. Jawab langsung \u2014 jangan membaca berkas, jangan memakai tool,`,
|
|
28242
|
+
"jangan menyelidiki apa pun. Seluruh bahan sudah ada di bawah.",
|
|
28243
|
+
"",
|
|
28244
|
+
`Judul rentang: ${input.title}`,
|
|
28245
|
+
`Bahan berasal dari ${MODE_LABEL[input.mode]}.`,
|
|
28246
|
+
"",
|
|
28247
|
+
"Bahan:",
|
|
28248
|
+
items || "(tidak ada)",
|
|
28249
|
+
"",
|
|
28250
|
+
"Aturan:",
|
|
28251
|
+
"- Bahasa Indonesia, gaya editorial: tenang, ringkas, kalimat penuh.",
|
|
28252
|
+
"- Tulis apa yang berubah BAGI PEMAKAI, bukan apa yang disentuh di dalam kode.",
|
|
28253
|
+
"- DILARANG menyebut nama berkas, nama fungsi, nama variabel, hash commit, nomor SPEC/ADR,",
|
|
28254
|
+
" nama branch, atau istilah internal apa pun.",
|
|
28255
|
+
"- Gabungkan bahan yang bicara hal yang sama jadi satu butir. 3\u201310 butir; kurangi bila memang sedikit.",
|
|
28256
|
+
"- Jangan mengarang perubahan yang tak ada di bahan.",
|
|
28257
|
+
"",
|
|
28258
|
+
"Bentuk keluaran \u2014 HANYA markdown ini, tanpa kalimat pembuka atau penutup di luarnya,",
|
|
28259
|
+
"tanpa blok kode:",
|
|
28260
|
+
"",
|
|
28261
|
+
`# Changelog \u2014 ${input.title}`,
|
|
28262
|
+
"",
|
|
28263
|
+
"<satu paragraf pembuka, maksimal dua kalimat>",
|
|
28264
|
+
"",
|
|
28265
|
+
"- **<judul singkat perubahan>** \u2014 <satu kalimat manfaatnya bagi pemakai>"
|
|
28266
|
+
].join("\n");
|
|
28267
|
+
}
|
|
28268
|
+
|
|
28269
|
+
// src/services/changelog/generate.ts
|
|
28270
|
+
var CHANGELOG_TIMEOUT_MS = 18e4;
|
|
28271
|
+
async function collect(projectId, req) {
|
|
28272
|
+
if (req.mode === "backlog") {
|
|
28273
|
+
const d = defaultRange(/* @__PURE__ */ new Date());
|
|
28274
|
+
return collectBacklog(projectId, req.from ?? d.from, req.to ?? d.to);
|
|
28275
|
+
}
|
|
28276
|
+
const repoDir = await resolveRepoDir(projectId);
|
|
28277
|
+
return req.mode === "commit" ? collectCommits(repoDir, req.fromSha, req.toSha) : collectVersions(repoDir, req.fromTag, req.toTag);
|
|
28278
|
+
}
|
|
28279
|
+
async function generateChangelog(projectId, req, deps = {}) {
|
|
28280
|
+
const got = await collect(projectId, req);
|
|
28281
|
+
if (!got.ok) return got;
|
|
28282
|
+
const input = got.input;
|
|
28283
|
+
const prompt = changelogPrompt(input, CHANGELOG_TIMEOUT_MS);
|
|
28284
|
+
const { agent, model, effort } = await changelogAgentDefaults();
|
|
28285
|
+
const run2 = deps.think ?? think;
|
|
28286
|
+
let body = "";
|
|
28287
|
+
let generator = "agent";
|
|
28288
|
+
const warnings = [...input.notes];
|
|
28289
|
+
try {
|
|
28290
|
+
const raw = await run2(prompt, { agent, model, effort, timeoutMs: CHANGELOG_TIMEOUT_MS });
|
|
28291
|
+
body = scrubOutput(raw ?? "");
|
|
28292
|
+
if (!body.trim()) throw new Error("agen tak memulangkan teks apa pun");
|
|
28293
|
+
} catch (e) {
|
|
28294
|
+
generator = "fallback";
|
|
28295
|
+
body = fallbackMarkdown(input);
|
|
28296
|
+
warnings.push(`Narasi otomatis tak tersedia \u2014 ${e.message}. Yang tampil adalah draf ringkas.`);
|
|
28297
|
+
}
|
|
28298
|
+
const row = await prisma.changelog.create({
|
|
28299
|
+
data: {
|
|
28300
|
+
projectId,
|
|
28301
|
+
mode: input.mode,
|
|
28302
|
+
title: input.title,
|
|
28303
|
+
params: req,
|
|
28304
|
+
body,
|
|
28305
|
+
generator,
|
|
28306
|
+
itemCount: input.items.length,
|
|
28307
|
+
warning: warnings.length ? warnings.join(" ") : null
|
|
28308
|
+
}
|
|
28309
|
+
});
|
|
28310
|
+
return { ok: true, row };
|
|
28311
|
+
}
|
|
28312
|
+
|
|
28313
|
+
// src/routes/changelog.ts
|
|
28314
|
+
var view6 = (c) => ({
|
|
28315
|
+
id: c.id,
|
|
28316
|
+
projectId: c.projectId,
|
|
28317
|
+
mode: c.mode,
|
|
28318
|
+
title: c.title,
|
|
28319
|
+
params: c.params,
|
|
28320
|
+
body: c.body,
|
|
28321
|
+
generator: c.generator,
|
|
28322
|
+
warning: c.warning,
|
|
28323
|
+
itemCount: c.itemCount,
|
|
28324
|
+
createdAt: c.createdAt.toISOString()
|
|
28325
|
+
});
|
|
28326
|
+
async function changelog_default(app2) {
|
|
28327
|
+
app2.get("/projects/:id/changelog/sources", async (req, reply) => {
|
|
28328
|
+
const { id } = req.params;
|
|
28329
|
+
if (!await prisma.project.findUnique({ where: { id } })) return reply.code(404).send({ error: "not found" });
|
|
28330
|
+
const { tags, head, reason } = await listTags(await resolveRepoDir(id));
|
|
28331
|
+
const done = await prisma.spec.findMany({
|
|
28332
|
+
where: { projectId: id, stage: "done", doneAt: { not: null } },
|
|
28333
|
+
select: { doneAt: true },
|
|
28334
|
+
orderBy: { doneAt: "asc" }
|
|
28335
|
+
});
|
|
28336
|
+
return {
|
|
28337
|
+
hasRepo: reason === null || tags.length > 0,
|
|
28338
|
+
tags,
|
|
28339
|
+
head,
|
|
28340
|
+
reason,
|
|
28341
|
+
backlog: {
|
|
28342
|
+
doneCount: done.length,
|
|
28343
|
+
earliest: done[0]?.doneAt?.toISOString() ?? null,
|
|
28344
|
+
latest: done[done.length - 1]?.doneAt?.toISOString() ?? null
|
|
28345
|
+
},
|
|
28346
|
+
defaultRange: defaultRange(/* @__PURE__ */ new Date())
|
|
28347
|
+
};
|
|
28348
|
+
});
|
|
28349
|
+
app2.get("/projects/:id/changelog", async (req, reply) => {
|
|
28350
|
+
const { id } = req.params;
|
|
28351
|
+
if (!await prisma.project.findUnique({ where: { id } })) return reply.code(404).send({ error: "not found" });
|
|
28352
|
+
const { page, limit, q } = req.query;
|
|
28353
|
+
const rows = await prisma.changelog.findMany({ where: { projectId: id }, orderBy: { createdAt: "desc" } });
|
|
28354
|
+
return paginate(rows.filter((r) => changelogMatches(r, q ?? "")).map(view6), page, limit);
|
|
28355
|
+
});
|
|
28356
|
+
app2.get("/projects/:id/changelog/:cid", async (req, reply) => {
|
|
28357
|
+
const { id, cid } = req.params;
|
|
28358
|
+
const row = await prisma.changelog.findFirst({ where: { id: cid, projectId: id } });
|
|
28359
|
+
if (!row) return reply.code(404).send({ error: "not found" });
|
|
28360
|
+
const fmt = downloadFormat(req.query);
|
|
28361
|
+
if (fmt) return sendDocDownload(reply, fmt, {
|
|
28362
|
+
content: row.body,
|
|
28363
|
+
name: `${row.title}.md`,
|
|
28364
|
+
prefix: `${id}-changelog`,
|
|
28365
|
+
eyebrow: `hanoman \xB7 ${id} \xB7 changelog`,
|
|
28366
|
+
path: row.title
|
|
28367
|
+
});
|
|
28368
|
+
return view6(row);
|
|
28369
|
+
});
|
|
28370
|
+
app2.post("/projects/:id/changelog", async (req, reply) => {
|
|
28371
|
+
const { id } = req.params;
|
|
28372
|
+
if (!await prisma.project.findUnique({ where: { id } })) return reply.code(404).send({ error: "not found" });
|
|
28373
|
+
const parsed = zChangelogRequest.safeParse(req.body);
|
|
28374
|
+
if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
|
|
28375
|
+
const r = await generateChangelog(id, parsed.data);
|
|
28376
|
+
if (!r.ok) return reply.code(422).send({ error: r.reason });
|
|
28377
|
+
return reply.code(201).send(view6(r.row));
|
|
28378
|
+
});
|
|
28379
|
+
app2.delete("/projects/:id/changelog/:cid", async (req, reply) => {
|
|
28380
|
+
const { id, cid } = req.params;
|
|
28381
|
+
const { count } = await prisma.changelog.deleteMany({ where: { id: cid, projectId: id } });
|
|
28382
|
+
return count ? reply.code(204).send() : reply.code(404).send({ error: "not found" });
|
|
28383
|
+
});
|
|
28384
|
+
}
|
|
28385
|
+
|
|
27766
28386
|
// src/routes/custom-agents.ts
|
|
27767
28387
|
init_src();
|
|
27768
28388
|
init_db();
|
|
@@ -27893,7 +28513,7 @@ async function installCustomAgents() {
|
|
|
27893
28513
|
|
|
27894
28514
|
// src/routes/custom-agents.ts
|
|
27895
28515
|
var rowsOf = async () => await prisma.customAgent.findMany();
|
|
27896
|
-
var
|
|
28516
|
+
var view7 = (r, projectId) => ({
|
|
27897
28517
|
id: r.id,
|
|
27898
28518
|
projectId: r.projectId,
|
|
27899
28519
|
name: r.name,
|
|
@@ -27938,7 +28558,7 @@ async function custom_agents_default(app2) {
|
|
|
27938
28558
|
const byName = /* @__PURE__ */ new Map();
|
|
27939
28559
|
for (const r of rows) if (r.projectId === null) byName.set(r.name, r);
|
|
27940
28560
|
for (const r of rows) if (r.projectId !== null) byName.set(r.name, r);
|
|
27941
|
-
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name)).map((r) =>
|
|
28561
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name)).map((r) => view7(r, projectId));
|
|
27942
28562
|
});
|
|
27943
28563
|
app2.post("/custom-agents", async (req, reply) => {
|
|
27944
28564
|
const parsed = zCreateCustomAgent.safeParse(req.body);
|
|
@@ -27985,7 +28605,7 @@ async function custom_agents_default(app2) {
|
|
|
27985
28605
|
} });
|
|
27986
28606
|
await loadCustomAgents();
|
|
27987
28607
|
await notifySynced("customAgent", id);
|
|
27988
|
-
return reply.code(201).send(
|
|
28608
|
+
return reply.code(201).send(view7(row));
|
|
27989
28609
|
});
|
|
27990
28610
|
app2.patch("/custom-agents/:id", async (req, reply) => {
|
|
27991
28611
|
const { id } = req.params;
|
|
@@ -28032,7 +28652,7 @@ async function custom_agents_default(app2) {
|
|
|
28032
28652
|
} });
|
|
28033
28653
|
await loadCustomAgents();
|
|
28034
28654
|
await notifySynced("customAgent", id);
|
|
28035
|
-
return
|
|
28655
|
+
return view7(row);
|
|
28036
28656
|
});
|
|
28037
28657
|
app2.delete("/custom-agents/:id", async (req, reply) => {
|
|
28038
28658
|
const { id } = req.params;
|
|
@@ -28105,7 +28725,7 @@ async function resolveGithubRepo(projectId) {
|
|
|
28105
28725
|
|
|
28106
28726
|
// src/services/github-fetch.ts
|
|
28107
28727
|
init_config2();
|
|
28108
|
-
import { execFile as
|
|
28728
|
+
import { execFile as execFile11 } from "node:child_process";
|
|
28109
28729
|
var labelNames = (l) => (l ?? []).map((x) => typeof x === "string" ? x : x.name ?? "").filter(Boolean);
|
|
28110
28730
|
var norm2 = (s2) => s2.toLowerCase() === "closed" ? "closed" : "open";
|
|
28111
28731
|
function issueFromGh(raw) {
|
|
@@ -28146,7 +28766,7 @@ function issuesFromRest(raw) {
|
|
|
28146
28766
|
var GH_FIELDS = "number,title,body,author,labels,url,state,createdAt,updatedAt";
|
|
28147
28767
|
var API2 = "https://api.github.com";
|
|
28148
28768
|
var defaultRunGh = (args, env) => new Promise((resolve15, reject2) => {
|
|
28149
|
-
|
|
28769
|
+
execFile11(
|
|
28150
28770
|
args[0],
|
|
28151
28771
|
args.slice(1),
|
|
28152
28772
|
{ env, maxBuffer: 1 << 26, encoding: "utf8", timeout: 6e4 },
|
|
@@ -28373,7 +28993,7 @@ var STATUS = {
|
|
|
28373
28993
|
unauthorized: 401,
|
|
28374
28994
|
other: 502
|
|
28375
28995
|
};
|
|
28376
|
-
var
|
|
28996
|
+
var view8 = (i) => ({
|
|
28377
28997
|
id: i.id,
|
|
28378
28998
|
projectId: i.projectId,
|
|
28379
28999
|
repoSlug: i.repoSlug,
|
|
@@ -28401,14 +29021,14 @@ async function githubIssues(app2) {
|
|
|
28401
29021
|
});
|
|
28402
29022
|
app2.get("/projects/:id/github/issues", async (req, reply) => {
|
|
28403
29023
|
const { id } = req.params;
|
|
28404
|
-
const { status } = req.query;
|
|
29024
|
+
const { status, page, limit } = req.query;
|
|
28405
29025
|
const project = await prisma.project.findUnique({ where: { id }, select: { id: true } });
|
|
28406
29026
|
if (!project) return reply.code(404).send({ error: "not found" });
|
|
28407
29027
|
const items = await prisma.githubIssue.findMany({
|
|
28408
29028
|
where: { projectId: id, ...status ? { status } : {} },
|
|
28409
29029
|
orderBy: [{ number: "desc" }]
|
|
28410
29030
|
});
|
|
28411
|
-
return reply.send(
|
|
29031
|
+
return reply.send(paginate(items.map(view8), page, limit));
|
|
28412
29032
|
});
|
|
28413
29033
|
app2.post("/github-issues/accept", async (req, reply) => {
|
|
28414
29034
|
const parsed = zAcceptMany.safeParse(req.body ?? {});
|
|
@@ -29120,8 +29740,8 @@ init_db();
|
|
|
29120
29740
|
// src/services/auth.ts
|
|
29121
29741
|
init_db();
|
|
29122
29742
|
import { randomBytes as randomBytes7, scrypt as scryptCb, timingSafeEqual as timingSafeEqual2, createHash as createHash7 } from "node:crypto";
|
|
29123
|
-
import { promisify as
|
|
29124
|
-
var scrypt =
|
|
29743
|
+
import { promisify as promisify10 } from "node:util";
|
|
29744
|
+
var scrypt = promisify10(scryptCb);
|
|
29125
29745
|
var COOKIE_NAME = "hn_session";
|
|
29126
29746
|
var SESSION_TTL_MS = 7 * 24 * 3600 * 1e3;
|
|
29127
29747
|
async function hashPassword(pw) {
|
|
@@ -29191,7 +29811,7 @@ function cookieOpts() {
|
|
|
29191
29811
|
}
|
|
29192
29812
|
|
|
29193
29813
|
// src/routes/auth.ts
|
|
29194
|
-
var
|
|
29814
|
+
var view9 = (u) => ({ id: u.id, email: u.email, createdAt: u.createdAt.toISOString() });
|
|
29195
29815
|
async function issue(reply, userId) {
|
|
29196
29816
|
const token = await createSession2(userId);
|
|
29197
29817
|
reply.setCookie(COOKIE_NAME, token, cookieOpts());
|
|
@@ -29209,7 +29829,7 @@ async function auth_default(app2) {
|
|
|
29209
29829
|
data: { email: p.data.email, passwordHash: await hashPassword(p.data.password) }
|
|
29210
29830
|
});
|
|
29211
29831
|
await issue(reply, user.id);
|
|
29212
|
-
return { user:
|
|
29832
|
+
return { user: view9(user) };
|
|
29213
29833
|
});
|
|
29214
29834
|
app2.post("/auth/login", async (req, reply) => {
|
|
29215
29835
|
if (loginThrottle(req.ip).blocked) return reply.code(429).send({ error: "too many attempts" });
|
|
@@ -29222,7 +29842,7 @@ async function auth_default(app2) {
|
|
|
29222
29842
|
}
|
|
29223
29843
|
clearLoginFails(req.ip);
|
|
29224
29844
|
await issue(reply, user.id);
|
|
29225
|
-
return { user:
|
|
29845
|
+
return { user: view9(user) };
|
|
29226
29846
|
});
|
|
29227
29847
|
app2.post("/auth/logout", async (req, reply) => {
|
|
29228
29848
|
const token = req.cookies?.[COOKIE_NAME];
|
|
@@ -29230,7 +29850,7 @@ async function auth_default(app2) {
|
|
|
29230
29850
|
reply.clearCookie(COOKIE_NAME, { path: "/" });
|
|
29231
29851
|
return reply.code(204).send();
|
|
29232
29852
|
});
|
|
29233
|
-
app2.get("/auth/users", async () => (await prisma.user.findMany({ orderBy: { createdAt: "asc" } })).map(
|
|
29853
|
+
app2.get("/auth/users", async () => (await prisma.user.findMany({ orderBy: { createdAt: "asc" } })).map(view9));
|
|
29234
29854
|
app2.post("/auth/users", async (req, reply) => {
|
|
29235
29855
|
const p = zSignup.safeParse(req.body);
|
|
29236
29856
|
if (!p.success) return reply.code(400).send({ error: p.error.flatten() });
|
|
@@ -29239,7 +29859,7 @@ async function auth_default(app2) {
|
|
|
29239
29859
|
const user = await prisma.user.create({
|
|
29240
29860
|
data: { email: p.data.email, passwordHash: await hashPassword(p.data.password) }
|
|
29241
29861
|
});
|
|
29242
|
-
return
|
|
29862
|
+
return view9(user);
|
|
29243
29863
|
});
|
|
29244
29864
|
app2.delete("/auth/users/:id", async (req, reply) => {
|
|
29245
29865
|
if (await prisma.user.count() <= 1) return reply.code(400).send({ error: "tak bisa hapus user terakhir" });
|
|
@@ -29259,7 +29879,7 @@ async function auth_default(app2) {
|
|
|
29259
29879
|
});
|
|
29260
29880
|
await deleteUserSessions(user.id);
|
|
29261
29881
|
await issue(reply, user.id);
|
|
29262
|
-
return { user:
|
|
29882
|
+
return { user: view9(user) };
|
|
29263
29883
|
});
|
|
29264
29884
|
}
|
|
29265
29885
|
|
|
@@ -29272,15 +29892,15 @@ async function agent_tokens_default(app2) {
|
|
|
29272
29892
|
app2.post("/agent-tokens", async (req, reply) => {
|
|
29273
29893
|
const p = zAgentTokenCreate.safeParse(req.body);
|
|
29274
29894
|
if (!p.success) return reply.code(400).send({ error: p.error.flatten() });
|
|
29275
|
-
const { view:
|
|
29276
|
-
return reply.code(201).send({ ...
|
|
29895
|
+
const { view: view10, token } = await issueAgentToken({ ...p.data, createdBy: req.user?.id });
|
|
29896
|
+
return reply.code(201).send({ ...view10, token });
|
|
29277
29897
|
});
|
|
29278
29898
|
app2.patch("/agent-tokens/:id", async (req, reply) => {
|
|
29279
29899
|
const p = zAgentTokenPatch.safeParse(req.body);
|
|
29280
29900
|
if (!p.success) return reply.code(400).send({ error: p.error.flatten() });
|
|
29281
29901
|
const { id } = req.params;
|
|
29282
|
-
const
|
|
29283
|
-
return
|
|
29902
|
+
const view10 = await patchAgentToken(id, p.data);
|
|
29903
|
+
return view10 ? reply.send(view10) : reply.code(404).send({ error: "not found" });
|
|
29284
29904
|
});
|
|
29285
29905
|
app2.delete("/agent-tokens/:id", async (req, reply) => {
|
|
29286
29906
|
const { id } = req.params;
|
|
@@ -29350,7 +29970,7 @@ function capabilityForRoute(method, path) {
|
|
|
29350
29970
|
}
|
|
29351
29971
|
if (top === "projects") {
|
|
29352
29972
|
const sub = seg[2];
|
|
29353
|
-
if (sub === "docs" || sub === "prds") return rw("docs");
|
|
29973
|
+
if (sub === "docs" || sub === "prds" || sub === "changelog") return rw("docs");
|
|
29354
29974
|
if (sub === "github") return rw("support");
|
|
29355
29975
|
if (sub && IDE_SUBS.has(sub)) return rw("ide");
|
|
29356
29976
|
return rw("projects");
|
|
@@ -29495,7 +30115,7 @@ var PUBLIC = /* @__PURE__ */ new Set([
|
|
|
29495
30115
|
"GET /api/agent-integration.md"
|
|
29496
30116
|
]);
|
|
29497
30117
|
function buildApp({ requireAuth = true, agentDocFile } = {}) {
|
|
29498
|
-
const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname13(fileURLToPath4(import.meta.url)), process.env,
|
|
30118
|
+
const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname13(fileURLToPath4(import.meta.url)), process.env, existsSync13);
|
|
29499
30119
|
const app2 = Fastify({ logger: false, trustProxy: true });
|
|
29500
30120
|
app2.addContentTypeParser("application/json", { parseAs: "string" }, (_req, body, done) => {
|
|
29501
30121
|
if (!body) return done(null, void 0);
|
|
@@ -29578,9 +30198,10 @@ function buildApp({ requireAuth = true, agentDocFile } = {}) {
|
|
|
29578
30198
|
await api.register(githubIssues);
|
|
29579
30199
|
await api.register(telegramRoutes);
|
|
29580
30200
|
await api.register(webhooks_default);
|
|
30201
|
+
await api.register(changelog_default);
|
|
29581
30202
|
}, { prefix: "/api" });
|
|
29582
30203
|
if (process.env.NODE_ENV === "production") {
|
|
29583
|
-
const dist = pickWebDir(dirname13(fileURLToPath4(import.meta.url)), process.env,
|
|
30204
|
+
const dist = pickWebDir(dirname13(fileURLToPath4(import.meta.url)), process.env, existsSync13);
|
|
29584
30205
|
if (dist) {
|
|
29585
30206
|
app2.register(fastifyStatic, { root: dist });
|
|
29586
30207
|
app2.setNotFoundHandler((req, reply) => req.url.startsWith("/api") ? reply.code(404).send({ error: "not found" }) : reply.sendFile("index.html"));
|
|
@@ -29631,6 +30252,7 @@ init_src();
|
|
|
29631
30252
|
|
|
29632
30253
|
// src/services/scheduler/governor.ts
|
|
29633
30254
|
var ALREADY_DONE_NOTE = "spec sudah selesai \u2014 tak diluncurkan";
|
|
30255
|
+
var canceledRaceNote = (sessionId2) => `dibatalkan saat sesi ${sessionId2} sudah terlanjur lahir \u2014 sesi dibiarkan hidup, tutup dari Terminal bila tak diperlukan`;
|
|
29634
30256
|
var draining = false;
|
|
29635
30257
|
async function drain(cfg, deps) {
|
|
29636
30258
|
if (draining) return;
|
|
@@ -29640,13 +30262,14 @@ async function drain(cfg, deps) {
|
|
|
29640
30262
|
if (slots <= 0) return;
|
|
29641
30263
|
for (const item of await queued2()) {
|
|
29642
30264
|
if (slots <= 0) break;
|
|
30265
|
+
if (!await isQueued(item.id)) continue;
|
|
29643
30266
|
if (await deps.isDone(item.specId)) {
|
|
29644
30267
|
await markDone(item.id, ALREADY_DONE_NOTE);
|
|
29645
30268
|
continue;
|
|
29646
30269
|
}
|
|
29647
30270
|
const blocked = await deps.blockers(item.specId);
|
|
29648
30271
|
if (blocked.length) {
|
|
29649
|
-
await
|
|
30272
|
+
await noteRow(item.id, blockedNote(blocked));
|
|
29650
30273
|
continue;
|
|
29651
30274
|
}
|
|
29652
30275
|
const liveId = deps.isLive(item.specId);
|
|
@@ -29656,7 +30279,7 @@ async function drain(cfg, deps) {
|
|
|
29656
30279
|
}
|
|
29657
30280
|
try {
|
|
29658
30281
|
const sessionId2 = await deps.launch(item, cfg.autonomy);
|
|
29659
|
-
await markLaunched(item.id, sessionId2);
|
|
30282
|
+
if (!await markLaunched(item.id, sessionId2)) await noteRow(item.id, canceledRaceNote(sessionId2));
|
|
29660
30283
|
slots--;
|
|
29661
30284
|
} catch (e) {
|
|
29662
30285
|
await markFailed(item.id, e.message);
|
|
@@ -30116,16 +30739,16 @@ function startWebhookEngine() {
|
|
|
30116
30739
|
// src/services/auto-merge.ts
|
|
30117
30740
|
init_src();
|
|
30118
30741
|
init_db();
|
|
30119
|
-
import { execFile as
|
|
30120
|
-
import { promisify as
|
|
30742
|
+
import { execFile as execFile12 } from "node:child_process";
|
|
30743
|
+
import { promisify as promisify11 } from "node:util";
|
|
30121
30744
|
var AUTO_MERGE_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
30122
30745
|
var AUTO_MERGE_GRACE_MS = 15 * 60 * 1e3;
|
|
30123
30746
|
var TICK_MS4 = 6e4;
|
|
30124
|
-
var
|
|
30125
|
-
var
|
|
30747
|
+
var exec9 = promisify11(execFile12);
|
|
30748
|
+
var GIT8 = { timeout: 3e4, maxBuffer: 1 << 24, encoding: "utf8" };
|
|
30126
30749
|
var gitOut = async (cwd, args) => {
|
|
30127
30750
|
try {
|
|
30128
|
-
return (await
|
|
30751
|
+
return (await exec9("git", args, { cwd, ...GIT8 })).stdout.trim();
|
|
30129
30752
|
} catch {
|
|
30130
30753
|
return null;
|
|
30131
30754
|
}
|