hanoman 0.1.16 → 0.1.18
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 +44 -0
- package/dist/server.js +413 -40
- package/docs/agent-integration.md +40 -0
- 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 +30 -0
- package/web/assets/{index-GQaOZTKC.js → index-BrlNu5Qe.js} +1536 -1536
- package/web/index.html +1 -1
package/dist/server.js
CHANGED
|
@@ -5732,6 +5732,10 @@ var init_api = __esm({
|
|
|
5732
5732
|
// SPEC-471 · ADR-0095 · tarik & triase issue GitHub (di belakang gate cookie, capability `support`).
|
|
5733
5733
|
// hanoman TIDAK PERNAH menulis ke GitHub — tak ada path komentar/close di sini, dan itu disengaja.
|
|
5734
5734
|
githubPull: (id) => `${API}/projects/${encodeURIComponent(id)}/github/pull`,
|
|
5735
|
+
// SPEC-516 · ADR-0105 · changelog per project
|
|
5736
|
+
changelog: (id) => `${API}/projects/${encodeURIComponent(id)}/changelog`,
|
|
5737
|
+
changelogSources: (id) => `${API}/projects/${encodeURIComponent(id)}/changelog/sources`,
|
|
5738
|
+
changelogItem: (id, cid) => `${API}/projects/${encodeURIComponent(id)}/changelog/${encodeURIComponent(cid)}`,
|
|
5735
5739
|
githubIssues: (id) => `${API}/projects/${encodeURIComponent(id)}/github/issues`,
|
|
5736
5740
|
githubIssuesAccept: `${API}/github-issues/accept`,
|
|
5737
5741
|
githubIssueAccept: (id) => `${API}/github-issues/${encodeURIComponent(id)}/accept`,
|
|
@@ -7118,6 +7122,51 @@ var init_mcp = __esm({
|
|
|
7118
7122
|
}
|
|
7119
7123
|
});
|
|
7120
7124
|
|
|
7125
|
+
// ../shared/src/changelog.ts
|
|
7126
|
+
function dayString(d) {
|
|
7127
|
+
const p = (n2) => String(n2).padStart(2, "0");
|
|
7128
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
|
|
7129
|
+
}
|
|
7130
|
+
function defaultRange(today) {
|
|
7131
|
+
const from = new Date(today.getFullYear(), today.getMonth(), today.getDate() - (DEFAULT_RANGE_DAYS - 1));
|
|
7132
|
+
return { from: dayString(from), to: dayString(today) };
|
|
7133
|
+
}
|
|
7134
|
+
var CHANGELOG_MODES, zChangelogMode, zDay, zChangelogRequest, zChangelogView, DEFAULT_RANGE_DAYS;
|
|
7135
|
+
var init_changelog = __esm({
|
|
7136
|
+
"../shared/src/changelog.ts"() {
|
|
7137
|
+
"use strict";
|
|
7138
|
+
init_zod();
|
|
7139
|
+
CHANGELOG_MODES = ["backlog", "commit", "version"];
|
|
7140
|
+
zChangelogMode = external_exports.enum(CHANGELOG_MODES);
|
|
7141
|
+
zDay = external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/, "format tanggal harus YYYY-MM-DD");
|
|
7142
|
+
zChangelogRequest = external_exports.discriminatedUnion("mode", [
|
|
7143
|
+
external_exports.object({ mode: external_exports.literal("backlog"), from: zDay.optional(), to: zDay.optional() }),
|
|
7144
|
+
external_exports.object({ mode: external_exports.literal("commit"), fromSha: external_exports.string().trim().min(4), toSha: external_exports.string().trim().min(4) }),
|
|
7145
|
+
external_exports.object({ mode: external_exports.literal("version"), fromTag: external_exports.string().trim().min(1).optional(), toTag: external_exports.string().trim().min(1) })
|
|
7146
|
+
]).superRefine((v, ctx) => {
|
|
7147
|
+
if (v.mode === "backlog" && v.from && v.to && v.from > v.to)
|
|
7148
|
+
ctx.addIssue({
|
|
7149
|
+
code: external_exports.ZodIssueCode.custom,
|
|
7150
|
+
path: ["from"],
|
|
7151
|
+
message: "rentang tanggal terbalik \u2014 `from` harus lebih awal atau sama dengan `to`"
|
|
7152
|
+
});
|
|
7153
|
+
});
|
|
7154
|
+
zChangelogView = external_exports.object({
|
|
7155
|
+
id: external_exports.string(),
|
|
7156
|
+
projectId: external_exports.string(),
|
|
7157
|
+
mode: zChangelogMode,
|
|
7158
|
+
title: external_exports.string(),
|
|
7159
|
+
params: external_exports.unknown(),
|
|
7160
|
+
body: external_exports.string(),
|
|
7161
|
+
generator: external_exports.enum(["agent", "fallback"]),
|
|
7162
|
+
warning: external_exports.string().nullable(),
|
|
7163
|
+
itemCount: external_exports.number().int(),
|
|
7164
|
+
createdAt: external_exports.string()
|
|
7165
|
+
});
|
|
7166
|
+
DEFAULT_RANGE_DAYS = 30;
|
|
7167
|
+
}
|
|
7168
|
+
});
|
|
7169
|
+
|
|
7121
7170
|
// ../shared/src/index.ts
|
|
7122
7171
|
var init_src = __esm({
|
|
7123
7172
|
"../shared/src/index.ts"() {
|
|
@@ -7142,6 +7191,7 @@ var init_src = __esm({
|
|
|
7142
7191
|
init_webhook();
|
|
7143
7192
|
init_mcp();
|
|
7144
7193
|
init_auto_merge();
|
|
7194
|
+
init_changelog();
|
|
7145
7195
|
}
|
|
7146
7196
|
});
|
|
7147
7197
|
|
|
@@ -9584,7 +9634,10 @@ var init_sync = __esm({
|
|
|
9584
9634
|
// ini spec asal-hub mendapat createdAt lokal palsu di tiap client (kolom NOT NULL ber-default).
|
|
9585
9635
|
// SPEC-447 · ADR-0093 · dependsOn ikut juga: tanpa itu client tak tahu urutannya dan akan
|
|
9586
9636
|
// meluncurkan pekerjaan yang di hub terblokir. Bukan DATE_FIELDS — nilainya array string.
|
|
9587
|
-
|
|
9637
|
+
// SPEC-516 · ADR-0105 · doneAt ikut menyeberang — cermin createdAt/startedAt. Tanpa ini spec
|
|
9638
|
+
// asal-hub mendarat di tiap client dengan doneAt null tanpa satu pun error, dan changelog
|
|
9639
|
+
// mode backlog di client itu selamanya kosong.
|
|
9640
|
+
spec: ["projectId", "title", "source", "stage", "priority", "author", "objective", "payload", "branchFrom", "baseSha", "headSha", "dependsOn", "createdAt", "startedAt", "doneAt", "updatedAt"],
|
|
9588
9641
|
vps: ["name", "host", "port", "user", "health", "audit", "hardened", "lastSeenAt", "lastAuditAt", "updatedAt"],
|
|
9589
9642
|
sessionResult: ["projectId", "specId", "oldStage", "newStage", "commitSha", "branch", "prUrl", "status", "deviceId", "author", "createdAt", "updatedAt"],
|
|
9590
9643
|
// SPEC-268 · ADR-0066 · metadata tiket (lampiran biner tak disync). accessKeyHash wajib
|
|
@@ -9624,7 +9677,7 @@ var init_sync = __esm({
|
|
|
9624
9677
|
};
|
|
9625
9678
|
DATE_FIELDS = {
|
|
9626
9679
|
project: ["updatedAt"],
|
|
9627
|
-
spec: ["createdAt", "startedAt", "updatedAt"],
|
|
9680
|
+
spec: ["createdAt", "startedAt", "doneAt", "updatedAt"],
|
|
9628
9681
|
vps: ["lastSeenAt", "lastAuditAt", "updatedAt"],
|
|
9629
9682
|
sessionResult: ["createdAt", "updatedAt"],
|
|
9630
9683
|
ticket: ["createdAt", "updatedAt"],
|
|
@@ -9940,8 +9993,8 @@ async function telegramEngineContext() {
|
|
|
9940
9993
|
};
|
|
9941
9994
|
}
|
|
9942
9995
|
function telegramReloadNeeded(before, after) {
|
|
9943
|
-
const
|
|
9944
|
-
return JSON.stringify(
|
|
9996
|
+
const strip2 = ({ engine: _engine, ...rest }) => rest;
|
|
9997
|
+
return JSON.stringify(strip2(before)) !== JSON.stringify(strip2(after));
|
|
9945
9998
|
}
|
|
9946
9999
|
var init_config3 = __esm({
|
|
9947
10000
|
"src/services/telegram/config.ts"() {
|
|
@@ -11311,7 +11364,7 @@ var require_permessage_deflate = __commonJS({
|
|
|
11311
11364
|
var Limiter = require_limiter();
|
|
11312
11365
|
var { kStatusCode } = require_constants();
|
|
11313
11366
|
var FastBuffer = Buffer[Symbol.species];
|
|
11314
|
-
var
|
|
11367
|
+
var TRAILER2 = Buffer.from([0, 0, 255, 255]);
|
|
11315
11368
|
var kPerMessageDeflate = Symbol("permessage-deflate");
|
|
11316
11369
|
var kTotalLength = Symbol("total-length");
|
|
11317
11370
|
var kCallback = Symbol("callback");
|
|
@@ -11586,7 +11639,7 @@ var require_permessage_deflate = __commonJS({
|
|
|
11586
11639
|
}
|
|
11587
11640
|
this._inflate[kCallback] = callback;
|
|
11588
11641
|
this._inflate.write(data);
|
|
11589
|
-
if (fin) this._inflate.write(
|
|
11642
|
+
if (fin) this._inflate.write(TRAILER2);
|
|
11590
11643
|
this._inflate.flush(() => {
|
|
11591
11644
|
const err = this._inflate[kError];
|
|
11592
11645
|
if (err) {
|
|
@@ -16697,7 +16750,7 @@ var require_Decoder = __commonJS({
|
|
|
16697
16750
|
"../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/lib/utils/Decoder.js"(exports, module) {
|
|
16698
16751
|
"use strict";
|
|
16699
16752
|
var RE_PLUS = /\+/g;
|
|
16700
|
-
var
|
|
16753
|
+
var HEX2 = [
|
|
16701
16754
|
0,
|
|
16702
16755
|
0,
|
|
16703
16756
|
0,
|
|
@@ -16838,7 +16891,7 @@ var require_Decoder = __commonJS({
|
|
|
16838
16891
|
const len = str3.length;
|
|
16839
16892
|
for (; i < len; ++i) {
|
|
16840
16893
|
if (this.buffer !== void 0) {
|
|
16841
|
-
if (!
|
|
16894
|
+
if (!HEX2[str3.charCodeAt(i)]) {
|
|
16842
16895
|
res += "%" + this.buffer;
|
|
16843
16896
|
this.buffer = void 0;
|
|
16844
16897
|
--i;
|
|
@@ -17260,7 +17313,7 @@ var require_plugin = __commonJS({
|
|
|
17260
17313
|
var require_generateId = __commonJS({
|
|
17261
17314
|
"../node_modules/.pnpm/@fastify+multipart@8.3.1/node_modules/@fastify/multipart/lib/generateId.js"(exports, module) {
|
|
17262
17315
|
"use strict";
|
|
17263
|
-
var
|
|
17316
|
+
var HEX2 = [
|
|
17264
17317
|
"00",
|
|
17265
17318
|
"01",
|
|
17266
17319
|
"02",
|
|
@@ -17520,13 +17573,13 @@ var require_generateId = __commonJS({
|
|
|
17520
17573
|
];
|
|
17521
17574
|
var random = Math.random;
|
|
17522
17575
|
function seed() {
|
|
17523
|
-
return
|
|
17576
|
+
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
17577
|
}
|
|
17525
17578
|
module.exports.generateId = function generateIdFn() {
|
|
17526
17579
|
let num = 0;
|
|
17527
17580
|
let str3 = seed();
|
|
17528
17581
|
return function generateId() {
|
|
17529
|
-
return num === 255 ? (str3 = seed()) +
|
|
17582
|
+
return num === 255 ? (str3 = seed()) + HEX2[num = 0] : str3 + HEX2[++num];
|
|
17530
17583
|
};
|
|
17531
17584
|
}();
|
|
17532
17585
|
}
|
|
@@ -18359,7 +18412,7 @@ import websocket from "@fastify/websocket";
|
|
|
18359
18412
|
import cookie from "@fastify/cookie";
|
|
18360
18413
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
18361
18414
|
import { dirname as dirname13 } from "node:path";
|
|
18362
|
-
import { existsSync as
|
|
18415
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
18363
18416
|
|
|
18364
18417
|
// src/web-dir.ts
|
|
18365
18418
|
import { resolve } from "node:path";
|
|
@@ -21817,6 +21870,8 @@ async function recordDrift(vpsId, vpsName, drift, snapshotId) {
|
|
|
21817
21870
|
});
|
|
21818
21871
|
}
|
|
21819
21872
|
async function recordCompletion(specId, title, projectId) {
|
|
21873
|
+
await prisma.spec.updateMany({ where: { id: specId, doneAt: null }, data: { doneAt: /* @__PURE__ */ new Date() } }).catch(() => {
|
|
21874
|
+
});
|
|
21820
21875
|
const sessionId2 = specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
21821
21876
|
await prisma.notification.create({
|
|
21822
21877
|
data: { type: "done", key: `done:${specId}`, specId, sessionId: sessionId2, title, projectId }
|
|
@@ -27763,6 +27818,323 @@ async function lead_default(app2) {
|
|
|
27763
27818
|
});
|
|
27764
27819
|
}
|
|
27765
27820
|
|
|
27821
|
+
// src/routes/changelog.ts
|
|
27822
|
+
init_src();
|
|
27823
|
+
init_db();
|
|
27824
|
+
|
|
27825
|
+
// src/services/changelog/collect.ts
|
|
27826
|
+
init_db();
|
|
27827
|
+
import { execFile as execFile10 } from "node:child_process";
|
|
27828
|
+
import { promisify as promisify9 } from "node:util";
|
|
27829
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
27830
|
+
|
|
27831
|
+
// src/services/changelog/scrub.ts
|
|
27832
|
+
var MERGE = /^merge\s+(branch|pull request|remote-tracking branch|tag)\b/i;
|
|
27833
|
+
var CONVENTIONAL = /^(feat|fix|chore|docs|refactor|test|perf|build|ci|style|revert)(\([^)]*\))?!?:\s*/i;
|
|
27834
|
+
var PATH_LIKE = /(?:[\w.@-]+\/)+[\w.-]+\.\w{1,6}/g;
|
|
27835
|
+
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;
|
|
27836
|
+
var INTERNAL_REF = /\b(?:SPEC|ADR|PR|ISSUE)[-\s]?\d+\b/gi;
|
|
27837
|
+
var CAMEL = /\b[a-z]{2,}(?:[A-Z][a-z]{2,})+\b/g;
|
|
27838
|
+
var SNAKE = /\b[a-z][a-z0-9]*(?:_[a-z0-9]+)+\b/gi;
|
|
27839
|
+
var CALL = /\b[A-Za-z_][\w.]*\(\s*\)/g;
|
|
27840
|
+
var INLINE_CODE = /`[^`\n]*`/g;
|
|
27841
|
+
var FENCE = /^```[\s\S]*?^```$/gm;
|
|
27842
|
+
var TRAILER = /^[A-Za-z-]+:\s.*$/gm;
|
|
27843
|
+
var tidy = (s2) => s2.replace(/\(\s*\)/g, " ").replace(/[ \t]{2,}/g, " ").replace(/\s+([,.;:!?])/g, "$1").replace(/^[\s:;,·—–-]+/, "").replace(/[\s:;,·—–-]+$/, "").trim();
|
|
27844
|
+
function strip(s2) {
|
|
27845
|
+
return s2.replace(INLINE_CODE, " ").replace(PATH_LIKE, " ").replace(CALL, " ").replace(HEX, " ").replace(INTERNAL_REF, " ").replace(CAMEL, " ").replace(SNAKE, " ");
|
|
27846
|
+
}
|
|
27847
|
+
function scrubSubject(s2) {
|
|
27848
|
+
const one = (s2 ?? "").replace(/\s+/g, " ").trim();
|
|
27849
|
+
if (!one || MERGE.test(one)) return "";
|
|
27850
|
+
return tidy(strip(one.replace(CONVENTIONAL, "")));
|
|
27851
|
+
}
|
|
27852
|
+
function scrubBody(s2) {
|
|
27853
|
+
const first = (s2 ?? "").replace(TRAILER, "").split(/\n\s*\n/)[0] ?? "";
|
|
27854
|
+
return tidy(strip(first.replace(/\s+/g, " ").trim()));
|
|
27855
|
+
}
|
|
27856
|
+
function scrubOutput(md) {
|
|
27857
|
+
const noFence = (md ?? "").replace(FENCE, "");
|
|
27858
|
+
const lines2 = noFence.split("\n").map((line) => {
|
|
27859
|
+
const m = /^(\s*(?:[-*+]|\d+\.)\s+|\s*#{1,6}\s+|\s*>\s*)?(.*)$/.exec(line);
|
|
27860
|
+
const lead = m?.[1] ?? "";
|
|
27861
|
+
const rest = m?.[2] ?? line;
|
|
27862
|
+
const cleaned = tidy(strip(rest));
|
|
27863
|
+
return cleaned ? `${lead}${cleaned}` : "";
|
|
27864
|
+
});
|
|
27865
|
+
return lines2.join("\n").replace(/\n{3,}/g, "\n\n").trim() + "\n";
|
|
27866
|
+
}
|
|
27867
|
+
|
|
27868
|
+
// src/services/changelog/collect.ts
|
|
27869
|
+
var toItem = (label, detail) => {
|
|
27870
|
+
const l = scrubSubject(label);
|
|
27871
|
+
return l ? { label: l, detail: scrubBody(detail) } : null;
|
|
27872
|
+
};
|
|
27873
|
+
async function collectBacklog(projectId, from, to) {
|
|
27874
|
+
const f = dayStart(from), t = dayEnd(to);
|
|
27875
|
+
const rows = await prisma.spec.findMany({
|
|
27876
|
+
where: { projectId, stage: "done" },
|
|
27877
|
+
select: { title: true, objective: true, doneAt: true },
|
|
27878
|
+
orderBy: [{ doneAt: "asc" }, { id: "asc" }]
|
|
27879
|
+
});
|
|
27880
|
+
const stampless = rows.filter((r) => r.doneAt === null).length;
|
|
27881
|
+
const hit = rows.filter((r) => inDayRange(r.doneAt, f, t));
|
|
27882
|
+
const items = hit.map((r) => toItem(r.title, r.objective ?? "")).filter((x) => x !== null);
|
|
27883
|
+
if (items.length === 0)
|
|
27884
|
+
return { ok: false, reason: `tak ada backlog yang selesai antara ${from} dan ${to}` };
|
|
27885
|
+
const notes = [];
|
|
27886
|
+
if (stampless > 0)
|
|
27887
|
+
notes.push(`${stampless} item selesai tanpa stempel waktu (selesai sebelum stempel ini ada) dan tak ikut dihitung.`);
|
|
27888
|
+
return { ok: true, input: { mode: "backlog", title: `${from} \u2013 ${to}`, items, notes } };
|
|
27889
|
+
}
|
|
27890
|
+
var exec8 = promisify9(execFile10);
|
|
27891
|
+
var GIT7 = { maxBuffer: 1 << 24, encoding: "utf8" };
|
|
27892
|
+
var US2 = "";
|
|
27893
|
+
var RS = "";
|
|
27894
|
+
var NO_REPO = "project ini belum ditautkan ke repo di mesin ini";
|
|
27895
|
+
var NO_TAG = "repo project ini belum punya tag rilis";
|
|
27896
|
+
var usable = (repoDir) => !!repoDir && existsSync12(repoDir);
|
|
27897
|
+
async function listTags(repoDir) {
|
|
27898
|
+
if (!usable(repoDir)) return { tags: [], head: null, reason: NO_REPO };
|
|
27899
|
+
try {
|
|
27900
|
+
const { stdout } = await exec8("git", ["tag", "--list", "--sort=-creatordate"], { cwd: repoDir, ...GIT7 });
|
|
27901
|
+
const tags = stdout.split("\n").map((s2) => s2.trim()).filter(Boolean);
|
|
27902
|
+
const head = await exec8("git", ["rev-parse", "--short", "HEAD"], { cwd: repoDir, ...GIT7 }).then((r) => r.stdout.trim()).catch(() => null);
|
|
27903
|
+
return { tags, head, reason: tags.length ? null : NO_TAG };
|
|
27904
|
+
} catch {
|
|
27905
|
+
return { tags: [], head: null, reason: NO_TAG };
|
|
27906
|
+
}
|
|
27907
|
+
}
|
|
27908
|
+
async function previousTag(repoDir, tag2) {
|
|
27909
|
+
return exec8("git", ["describe", "--tags", "--abbrev=0", "--end-of-options", `${tag2}^`], { cwd: repoDir, ...GIT7 }).then((r) => r.stdout.trim() || null).catch(() => null);
|
|
27910
|
+
}
|
|
27911
|
+
async function revExists(repoDir, rev) {
|
|
27912
|
+
return exec8("git", ["rev-parse", "--verify", "--quiet", "--end-of-options", `${rev}^{commit}`], { cwd: repoDir, ...GIT7 }).then(() => true).catch(() => false);
|
|
27913
|
+
}
|
|
27914
|
+
async function logRange(repoDir, range) {
|
|
27915
|
+
const { stdout } = await exec8(
|
|
27916
|
+
"git",
|
|
27917
|
+
["log", "--no-merges", `--pretty=format:%s${US2}%b${RS}`, ...range],
|
|
27918
|
+
{ cwd: repoDir, ...GIT7 }
|
|
27919
|
+
);
|
|
27920
|
+
return stdout.split(RS).map((rec) => rec.replace(/^\n+/, "")).filter((rec) => rec.trim()).map((rec) => {
|
|
27921
|
+
const [subject, body] = rec.split(US2);
|
|
27922
|
+
return toItem(subject ?? "", body ?? "");
|
|
27923
|
+
}).filter((x) => x !== null);
|
|
27924
|
+
}
|
|
27925
|
+
async function collectCommits(repoDir, fromSha, toSha) {
|
|
27926
|
+
if (!usable(repoDir)) return { ok: false, reason: NO_REPO };
|
|
27927
|
+
for (const rev of [fromSha, toSha])
|
|
27928
|
+
if (!await revExists(repoDir, rev)) return { ok: false, reason: `revisi "${rev}" tak dikenal di repo project` };
|
|
27929
|
+
try {
|
|
27930
|
+
const items = await logRange(repoDir, ["--end-of-options", `${fromSha}..${toSha}`]);
|
|
27931
|
+
if (!items.length) return { ok: false, reason: `tak ada perubahan antara "${fromSha}" dan "${toSha}"` };
|
|
27932
|
+
const short = (s2) => s2.slice(0, 7);
|
|
27933
|
+
return { ok: true, input: { mode: "commit", title: `${short(fromSha)} \u2192 ${short(toSha)}`, items, notes: [] } };
|
|
27934
|
+
} catch (e) {
|
|
27935
|
+
return { ok: false, reason: `git menolak rentang itu: ${e.message.split("\n")[0]}` };
|
|
27936
|
+
}
|
|
27937
|
+
}
|
|
27938
|
+
async function collectVersions(repoDir, fromTag, toTag) {
|
|
27939
|
+
const { tags, reason } = await listTags(repoDir);
|
|
27940
|
+
if (reason) return { ok: false, reason };
|
|
27941
|
+
if (!usable(repoDir)) return { ok: false, reason: NO_REPO };
|
|
27942
|
+
if (!tags.includes(toTag)) return { ok: false, reason: `tag "${toTag}" tak ada di repo project` };
|
|
27943
|
+
if (fromTag && !tags.includes(fromTag)) return { ok: false, reason: `tag "${fromTag}" tak ada di repo project` };
|
|
27944
|
+
const prev = fromTag ?? await previousTag(repoDir, toTag);
|
|
27945
|
+
try {
|
|
27946
|
+
const items = await logRange(repoDir, ["--end-of-options", prev ? `${prev}..${toTag}` : toTag]);
|
|
27947
|
+
if (!items.length) return { ok: false, reason: `tak ada perubahan yang masuk ke "${toTag}"` };
|
|
27948
|
+
const title = fromTag ? `${fromTag} \u2192 ${toTag}` : toTag;
|
|
27949
|
+
return { ok: true, input: { mode: "version", title, items, notes: [] } };
|
|
27950
|
+
} catch (e) {
|
|
27951
|
+
return { ok: false, reason: `git menolak rentang itu: ${e.message.split("\n")[0]}` };
|
|
27952
|
+
}
|
|
27953
|
+
}
|
|
27954
|
+
|
|
27955
|
+
// src/services/changelog/generate.ts
|
|
27956
|
+
init_src();
|
|
27957
|
+
init_db();
|
|
27958
|
+
init_settings2();
|
|
27959
|
+
|
|
27960
|
+
// src/services/changelog/render.ts
|
|
27961
|
+
var MODE_LABEL = {
|
|
27962
|
+
backlog: "backlog yang selesai dalam rentang tanggal",
|
|
27963
|
+
commit: "riwayat perubahan repo dalam rentang yang dipilih",
|
|
27964
|
+
version: "perubahan yang masuk ke versi/rilis itu"
|
|
27965
|
+
};
|
|
27966
|
+
var DETAIL_MAX = 240;
|
|
27967
|
+
var clip2 = (s2) => s2.length > DETAIL_MAX ? `${s2.slice(0, DETAIL_MAX - 1).trimEnd()}\u2026` : s2;
|
|
27968
|
+
function fallbackMarkdown(input) {
|
|
27969
|
+
const lines2 = [`# Changelog \u2014 ${input.title}`, ""];
|
|
27970
|
+
if (input.items.length === 0) {
|
|
27971
|
+
lines2.push("_Tak ada perubahan yang bisa diringkas untuk rentang ini._", "");
|
|
27972
|
+
} else {
|
|
27973
|
+
lines2.push(`Ringkasan ${input.items.length} perubahan untuk pemakai.`, "");
|
|
27974
|
+
for (const it of input.items) {
|
|
27975
|
+
const d = clip2(it.detail.trim());
|
|
27976
|
+
lines2.push(d ? `- **${it.label}** \u2014 ${d}` : `- **${it.label}**`);
|
|
27977
|
+
}
|
|
27978
|
+
lines2.push("");
|
|
27979
|
+
}
|
|
27980
|
+
if (input.notes.length) {
|
|
27981
|
+
lines2.push("---", "");
|
|
27982
|
+
for (const n2 of input.notes) lines2.push(`> ${n2}`);
|
|
27983
|
+
lines2.push("");
|
|
27984
|
+
}
|
|
27985
|
+
return lines2.join("\n");
|
|
27986
|
+
}
|
|
27987
|
+
function changelogPrompt(input, budgetMs) {
|
|
27988
|
+
const budget = Math.max(1, Math.round(budgetMs / 1e3));
|
|
27989
|
+
const items = input.items.map((it, i) => `${i + 1}. ${it.label}${it.detail ? ` \u2014 ${it.detail}` : ""}`).join("\n");
|
|
27990
|
+
return [
|
|
27991
|
+
"Kamu menulis changelog untuk PEMAKAI sebuah produk, bukan untuk developer.",
|
|
27992
|
+
"",
|
|
27993
|
+
`Anggaran waktumu ${budget} detik. Jawab langsung \u2014 jangan membaca berkas, jangan memakai tool,`,
|
|
27994
|
+
"jangan menyelidiki apa pun. Seluruh bahan sudah ada di bawah.",
|
|
27995
|
+
"",
|
|
27996
|
+
`Judul rentang: ${input.title}`,
|
|
27997
|
+
`Bahan berasal dari ${MODE_LABEL[input.mode]}.`,
|
|
27998
|
+
"",
|
|
27999
|
+
"Bahan:",
|
|
28000
|
+
items || "(tidak ada)",
|
|
28001
|
+
"",
|
|
28002
|
+
"Aturan:",
|
|
28003
|
+
"- Bahasa Indonesia, gaya editorial: tenang, ringkas, kalimat penuh.",
|
|
28004
|
+
"- Tulis apa yang berubah BAGI PEMAKAI, bukan apa yang disentuh di dalam kode.",
|
|
28005
|
+
"- DILARANG menyebut nama berkas, nama fungsi, nama variabel, hash commit, nomor SPEC/ADR,",
|
|
28006
|
+
" nama branch, atau istilah internal apa pun.",
|
|
28007
|
+
"- Gabungkan bahan yang bicara hal yang sama jadi satu butir. 3\u201310 butir; kurangi bila memang sedikit.",
|
|
28008
|
+
"- Jangan mengarang perubahan yang tak ada di bahan.",
|
|
28009
|
+
"",
|
|
28010
|
+
"Bentuk keluaran \u2014 HANYA markdown ini, tanpa kalimat pembuka atau penutup di luarnya,",
|
|
28011
|
+
"tanpa blok kode:",
|
|
28012
|
+
"",
|
|
28013
|
+
`# Changelog \u2014 ${input.title}`,
|
|
28014
|
+
"",
|
|
28015
|
+
"<satu paragraf pembuka, maksimal dua kalimat>",
|
|
28016
|
+
"",
|
|
28017
|
+
"- **<judul singkat perubahan>** \u2014 <satu kalimat manfaatnya bagi pemakai>"
|
|
28018
|
+
].join("\n");
|
|
28019
|
+
}
|
|
28020
|
+
|
|
28021
|
+
// src/services/changelog/generate.ts
|
|
28022
|
+
var CHANGELOG_TIMEOUT_MS = 18e4;
|
|
28023
|
+
async function collect(projectId, req) {
|
|
28024
|
+
if (req.mode === "backlog") {
|
|
28025
|
+
const d = defaultRange(/* @__PURE__ */ new Date());
|
|
28026
|
+
return collectBacklog(projectId, req.from ?? d.from, req.to ?? d.to);
|
|
28027
|
+
}
|
|
28028
|
+
const repoDir = await resolveRepoDir(projectId);
|
|
28029
|
+
return req.mode === "commit" ? collectCommits(repoDir, req.fromSha, req.toSha) : collectVersions(repoDir, req.fromTag, req.toTag);
|
|
28030
|
+
}
|
|
28031
|
+
async function generateChangelog(projectId, req, deps = {}) {
|
|
28032
|
+
const got = await collect(projectId, req);
|
|
28033
|
+
if (!got.ok) return got;
|
|
28034
|
+
const input = got.input;
|
|
28035
|
+
const prompt = changelogPrompt(input, CHANGELOG_TIMEOUT_MS);
|
|
28036
|
+
const { agent, model, effort } = await sessionAgentDefaults();
|
|
28037
|
+
const run2 = deps.think ?? think;
|
|
28038
|
+
let body = "";
|
|
28039
|
+
let generator = "agent";
|
|
28040
|
+
const warnings = [...input.notes];
|
|
28041
|
+
try {
|
|
28042
|
+
const raw = await run2(prompt, { agent, model, effort, timeoutMs: CHANGELOG_TIMEOUT_MS });
|
|
28043
|
+
body = scrubOutput(raw ?? "");
|
|
28044
|
+
if (!body.trim()) throw new Error("agen tak memulangkan teks apa pun");
|
|
28045
|
+
} catch (e) {
|
|
28046
|
+
generator = "fallback";
|
|
28047
|
+
body = fallbackMarkdown(input);
|
|
28048
|
+
warnings.push(`Narasi otomatis tak tersedia \u2014 ${e.message}. Yang tampil adalah draf ringkas.`);
|
|
28049
|
+
}
|
|
28050
|
+
const row = await prisma.changelog.create({
|
|
28051
|
+
data: {
|
|
28052
|
+
projectId,
|
|
28053
|
+
mode: input.mode,
|
|
28054
|
+
title: input.title,
|
|
28055
|
+
params: req,
|
|
28056
|
+
body,
|
|
28057
|
+
generator,
|
|
28058
|
+
itemCount: input.items.length,
|
|
28059
|
+
warning: warnings.length ? warnings.join(" ") : null
|
|
28060
|
+
}
|
|
28061
|
+
});
|
|
28062
|
+
return { ok: true, row };
|
|
28063
|
+
}
|
|
28064
|
+
|
|
28065
|
+
// src/routes/changelog.ts
|
|
28066
|
+
var view6 = (c) => ({
|
|
28067
|
+
id: c.id,
|
|
28068
|
+
projectId: c.projectId,
|
|
28069
|
+
mode: c.mode,
|
|
28070
|
+
title: c.title,
|
|
28071
|
+
params: c.params,
|
|
28072
|
+
body: c.body,
|
|
28073
|
+
generator: c.generator,
|
|
28074
|
+
warning: c.warning,
|
|
28075
|
+
itemCount: c.itemCount,
|
|
28076
|
+
createdAt: c.createdAt.toISOString()
|
|
28077
|
+
});
|
|
28078
|
+
async function changelog_default(app2) {
|
|
28079
|
+
app2.get("/projects/:id/changelog/sources", async (req, reply) => {
|
|
28080
|
+
const { id } = req.params;
|
|
28081
|
+
if (!await prisma.project.findUnique({ where: { id } })) return reply.code(404).send({ error: "not found" });
|
|
28082
|
+
const { tags, head, reason } = await listTags(await resolveRepoDir(id));
|
|
28083
|
+
const done = await prisma.spec.findMany({
|
|
28084
|
+
where: { projectId: id, stage: "done", doneAt: { not: null } },
|
|
28085
|
+
select: { doneAt: true },
|
|
28086
|
+
orderBy: { doneAt: "asc" }
|
|
28087
|
+
});
|
|
28088
|
+
return {
|
|
28089
|
+
hasRepo: reason === null || tags.length > 0,
|
|
28090
|
+
tags,
|
|
28091
|
+
head,
|
|
28092
|
+
reason,
|
|
28093
|
+
backlog: {
|
|
28094
|
+
doneCount: done.length,
|
|
28095
|
+
earliest: done[0]?.doneAt?.toISOString() ?? null,
|
|
28096
|
+
latest: done[done.length - 1]?.doneAt?.toISOString() ?? null
|
|
28097
|
+
},
|
|
28098
|
+
defaultRange: defaultRange(/* @__PURE__ */ new Date())
|
|
28099
|
+
};
|
|
28100
|
+
});
|
|
28101
|
+
app2.get("/projects/:id/changelog", async (req, reply) => {
|
|
28102
|
+
const { id } = req.params;
|
|
28103
|
+
if (!await prisma.project.findUnique({ where: { id } })) return reply.code(404).send({ error: "not found" });
|
|
28104
|
+
const { page, limit } = req.query;
|
|
28105
|
+
const rows = await prisma.changelog.findMany({ where: { projectId: id }, orderBy: { createdAt: "desc" } });
|
|
28106
|
+
return paginate(rows.map(view6), page, limit);
|
|
28107
|
+
});
|
|
28108
|
+
app2.get("/projects/:id/changelog/:cid", async (req, reply) => {
|
|
28109
|
+
const { id, cid } = req.params;
|
|
28110
|
+
const row = await prisma.changelog.findFirst({ where: { id: cid, projectId: id } });
|
|
28111
|
+
if (!row) return reply.code(404).send({ error: "not found" });
|
|
28112
|
+
const fmt = downloadFormat(req.query);
|
|
28113
|
+
if (fmt) return sendDocDownload(reply, fmt, {
|
|
28114
|
+
content: row.body,
|
|
28115
|
+
name: `${row.title}.md`,
|
|
28116
|
+
prefix: `${id}-changelog`,
|
|
28117
|
+
eyebrow: `hanoman \xB7 ${id} \xB7 changelog`,
|
|
28118
|
+
path: row.title
|
|
28119
|
+
});
|
|
28120
|
+
return view6(row);
|
|
28121
|
+
});
|
|
28122
|
+
app2.post("/projects/:id/changelog", async (req, reply) => {
|
|
28123
|
+
const { id } = req.params;
|
|
28124
|
+
if (!await prisma.project.findUnique({ where: { id } })) return reply.code(404).send({ error: "not found" });
|
|
28125
|
+
const parsed = zChangelogRequest.safeParse(req.body);
|
|
28126
|
+
if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
|
|
28127
|
+
const r = await generateChangelog(id, parsed.data);
|
|
28128
|
+
if (!r.ok) return reply.code(422).send({ error: r.reason });
|
|
28129
|
+
return reply.code(201).send(view6(r.row));
|
|
28130
|
+
});
|
|
28131
|
+
app2.delete("/projects/:id/changelog/:cid", async (req, reply) => {
|
|
28132
|
+
const { id, cid } = req.params;
|
|
28133
|
+
const { count } = await prisma.changelog.deleteMany({ where: { id: cid, projectId: id } });
|
|
28134
|
+
return count ? reply.code(204).send() : reply.code(404).send({ error: "not found" });
|
|
28135
|
+
});
|
|
28136
|
+
}
|
|
28137
|
+
|
|
27766
28138
|
// src/routes/custom-agents.ts
|
|
27767
28139
|
init_src();
|
|
27768
28140
|
init_db();
|
|
@@ -27893,7 +28265,7 @@ async function installCustomAgents() {
|
|
|
27893
28265
|
|
|
27894
28266
|
// src/routes/custom-agents.ts
|
|
27895
28267
|
var rowsOf = async () => await prisma.customAgent.findMany();
|
|
27896
|
-
var
|
|
28268
|
+
var view7 = (r, projectId) => ({
|
|
27897
28269
|
id: r.id,
|
|
27898
28270
|
projectId: r.projectId,
|
|
27899
28271
|
name: r.name,
|
|
@@ -27938,7 +28310,7 @@ async function custom_agents_default(app2) {
|
|
|
27938
28310
|
const byName = /* @__PURE__ */ new Map();
|
|
27939
28311
|
for (const r of rows) if (r.projectId === null) byName.set(r.name, r);
|
|
27940
28312
|
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) =>
|
|
28313
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name)).map((r) => view7(r, projectId));
|
|
27942
28314
|
});
|
|
27943
28315
|
app2.post("/custom-agents", async (req, reply) => {
|
|
27944
28316
|
const parsed = zCreateCustomAgent.safeParse(req.body);
|
|
@@ -27985,7 +28357,7 @@ async function custom_agents_default(app2) {
|
|
|
27985
28357
|
} });
|
|
27986
28358
|
await loadCustomAgents();
|
|
27987
28359
|
await notifySynced("customAgent", id);
|
|
27988
|
-
return reply.code(201).send(
|
|
28360
|
+
return reply.code(201).send(view7(row));
|
|
27989
28361
|
});
|
|
27990
28362
|
app2.patch("/custom-agents/:id", async (req, reply) => {
|
|
27991
28363
|
const { id } = req.params;
|
|
@@ -28032,7 +28404,7 @@ async function custom_agents_default(app2) {
|
|
|
28032
28404
|
} });
|
|
28033
28405
|
await loadCustomAgents();
|
|
28034
28406
|
await notifySynced("customAgent", id);
|
|
28035
|
-
return
|
|
28407
|
+
return view7(row);
|
|
28036
28408
|
});
|
|
28037
28409
|
app2.delete("/custom-agents/:id", async (req, reply) => {
|
|
28038
28410
|
const { id } = req.params;
|
|
@@ -28105,7 +28477,7 @@ async function resolveGithubRepo(projectId) {
|
|
|
28105
28477
|
|
|
28106
28478
|
// src/services/github-fetch.ts
|
|
28107
28479
|
init_config2();
|
|
28108
|
-
import { execFile as
|
|
28480
|
+
import { execFile as execFile11 } from "node:child_process";
|
|
28109
28481
|
var labelNames = (l) => (l ?? []).map((x) => typeof x === "string" ? x : x.name ?? "").filter(Boolean);
|
|
28110
28482
|
var norm2 = (s2) => s2.toLowerCase() === "closed" ? "closed" : "open";
|
|
28111
28483
|
function issueFromGh(raw) {
|
|
@@ -28146,7 +28518,7 @@ function issuesFromRest(raw) {
|
|
|
28146
28518
|
var GH_FIELDS = "number,title,body,author,labels,url,state,createdAt,updatedAt";
|
|
28147
28519
|
var API2 = "https://api.github.com";
|
|
28148
28520
|
var defaultRunGh = (args, env) => new Promise((resolve15, reject2) => {
|
|
28149
|
-
|
|
28521
|
+
execFile11(
|
|
28150
28522
|
args[0],
|
|
28151
28523
|
args.slice(1),
|
|
28152
28524
|
{ env, maxBuffer: 1 << 26, encoding: "utf8", timeout: 6e4 },
|
|
@@ -28373,7 +28745,7 @@ var STATUS = {
|
|
|
28373
28745
|
unauthorized: 401,
|
|
28374
28746
|
other: 502
|
|
28375
28747
|
};
|
|
28376
|
-
var
|
|
28748
|
+
var view8 = (i) => ({
|
|
28377
28749
|
id: i.id,
|
|
28378
28750
|
projectId: i.projectId,
|
|
28379
28751
|
repoSlug: i.repoSlug,
|
|
@@ -28408,7 +28780,7 @@ async function githubIssues(app2) {
|
|
|
28408
28780
|
where: { projectId: id, ...status ? { status } : {} },
|
|
28409
28781
|
orderBy: [{ number: "desc" }]
|
|
28410
28782
|
});
|
|
28411
|
-
return reply.send({ items: items.map(
|
|
28783
|
+
return reply.send({ items: items.map(view8) });
|
|
28412
28784
|
});
|
|
28413
28785
|
app2.post("/github-issues/accept", async (req, reply) => {
|
|
28414
28786
|
const parsed = zAcceptMany.safeParse(req.body ?? {});
|
|
@@ -29120,8 +29492,8 @@ init_db();
|
|
|
29120
29492
|
// src/services/auth.ts
|
|
29121
29493
|
init_db();
|
|
29122
29494
|
import { randomBytes as randomBytes7, scrypt as scryptCb, timingSafeEqual as timingSafeEqual2, createHash as createHash7 } from "node:crypto";
|
|
29123
|
-
import { promisify as
|
|
29124
|
-
var scrypt =
|
|
29495
|
+
import { promisify as promisify10 } from "node:util";
|
|
29496
|
+
var scrypt = promisify10(scryptCb);
|
|
29125
29497
|
var COOKIE_NAME = "hn_session";
|
|
29126
29498
|
var SESSION_TTL_MS = 7 * 24 * 3600 * 1e3;
|
|
29127
29499
|
async function hashPassword(pw) {
|
|
@@ -29191,7 +29563,7 @@ function cookieOpts() {
|
|
|
29191
29563
|
}
|
|
29192
29564
|
|
|
29193
29565
|
// src/routes/auth.ts
|
|
29194
|
-
var
|
|
29566
|
+
var view9 = (u) => ({ id: u.id, email: u.email, createdAt: u.createdAt.toISOString() });
|
|
29195
29567
|
async function issue(reply, userId) {
|
|
29196
29568
|
const token = await createSession2(userId);
|
|
29197
29569
|
reply.setCookie(COOKIE_NAME, token, cookieOpts());
|
|
@@ -29209,7 +29581,7 @@ async function auth_default(app2) {
|
|
|
29209
29581
|
data: { email: p.data.email, passwordHash: await hashPassword(p.data.password) }
|
|
29210
29582
|
});
|
|
29211
29583
|
await issue(reply, user.id);
|
|
29212
|
-
return { user:
|
|
29584
|
+
return { user: view9(user) };
|
|
29213
29585
|
});
|
|
29214
29586
|
app2.post("/auth/login", async (req, reply) => {
|
|
29215
29587
|
if (loginThrottle(req.ip).blocked) return reply.code(429).send({ error: "too many attempts" });
|
|
@@ -29222,7 +29594,7 @@ async function auth_default(app2) {
|
|
|
29222
29594
|
}
|
|
29223
29595
|
clearLoginFails(req.ip);
|
|
29224
29596
|
await issue(reply, user.id);
|
|
29225
|
-
return { user:
|
|
29597
|
+
return { user: view9(user) };
|
|
29226
29598
|
});
|
|
29227
29599
|
app2.post("/auth/logout", async (req, reply) => {
|
|
29228
29600
|
const token = req.cookies?.[COOKIE_NAME];
|
|
@@ -29230,7 +29602,7 @@ async function auth_default(app2) {
|
|
|
29230
29602
|
reply.clearCookie(COOKIE_NAME, { path: "/" });
|
|
29231
29603
|
return reply.code(204).send();
|
|
29232
29604
|
});
|
|
29233
|
-
app2.get("/auth/users", async () => (await prisma.user.findMany({ orderBy: { createdAt: "asc" } })).map(
|
|
29605
|
+
app2.get("/auth/users", async () => (await prisma.user.findMany({ orderBy: { createdAt: "asc" } })).map(view9));
|
|
29234
29606
|
app2.post("/auth/users", async (req, reply) => {
|
|
29235
29607
|
const p = zSignup.safeParse(req.body);
|
|
29236
29608
|
if (!p.success) return reply.code(400).send({ error: p.error.flatten() });
|
|
@@ -29239,7 +29611,7 @@ async function auth_default(app2) {
|
|
|
29239
29611
|
const user = await prisma.user.create({
|
|
29240
29612
|
data: { email: p.data.email, passwordHash: await hashPassword(p.data.password) }
|
|
29241
29613
|
});
|
|
29242
|
-
return
|
|
29614
|
+
return view9(user);
|
|
29243
29615
|
});
|
|
29244
29616
|
app2.delete("/auth/users/:id", async (req, reply) => {
|
|
29245
29617
|
if (await prisma.user.count() <= 1) return reply.code(400).send({ error: "tak bisa hapus user terakhir" });
|
|
@@ -29259,7 +29631,7 @@ async function auth_default(app2) {
|
|
|
29259
29631
|
});
|
|
29260
29632
|
await deleteUserSessions(user.id);
|
|
29261
29633
|
await issue(reply, user.id);
|
|
29262
|
-
return { user:
|
|
29634
|
+
return { user: view9(user) };
|
|
29263
29635
|
});
|
|
29264
29636
|
}
|
|
29265
29637
|
|
|
@@ -29272,15 +29644,15 @@ async function agent_tokens_default(app2) {
|
|
|
29272
29644
|
app2.post("/agent-tokens", async (req, reply) => {
|
|
29273
29645
|
const p = zAgentTokenCreate.safeParse(req.body);
|
|
29274
29646
|
if (!p.success) return reply.code(400).send({ error: p.error.flatten() });
|
|
29275
|
-
const { view:
|
|
29276
|
-
return reply.code(201).send({ ...
|
|
29647
|
+
const { view: view10, token } = await issueAgentToken({ ...p.data, createdBy: req.user?.id });
|
|
29648
|
+
return reply.code(201).send({ ...view10, token });
|
|
29277
29649
|
});
|
|
29278
29650
|
app2.patch("/agent-tokens/:id", async (req, reply) => {
|
|
29279
29651
|
const p = zAgentTokenPatch.safeParse(req.body);
|
|
29280
29652
|
if (!p.success) return reply.code(400).send({ error: p.error.flatten() });
|
|
29281
29653
|
const { id } = req.params;
|
|
29282
|
-
const
|
|
29283
|
-
return
|
|
29654
|
+
const view10 = await patchAgentToken(id, p.data);
|
|
29655
|
+
return view10 ? reply.send(view10) : reply.code(404).send({ error: "not found" });
|
|
29284
29656
|
});
|
|
29285
29657
|
app2.delete("/agent-tokens/:id", async (req, reply) => {
|
|
29286
29658
|
const { id } = req.params;
|
|
@@ -29350,7 +29722,7 @@ function capabilityForRoute(method, path) {
|
|
|
29350
29722
|
}
|
|
29351
29723
|
if (top === "projects") {
|
|
29352
29724
|
const sub = seg[2];
|
|
29353
|
-
if (sub === "docs" || sub === "prds") return rw("docs");
|
|
29725
|
+
if (sub === "docs" || sub === "prds" || sub === "changelog") return rw("docs");
|
|
29354
29726
|
if (sub === "github") return rw("support");
|
|
29355
29727
|
if (sub && IDE_SUBS.has(sub)) return rw("ide");
|
|
29356
29728
|
return rw("projects");
|
|
@@ -29495,7 +29867,7 @@ var PUBLIC = /* @__PURE__ */ new Set([
|
|
|
29495
29867
|
"GET /api/agent-integration.md"
|
|
29496
29868
|
]);
|
|
29497
29869
|
function buildApp({ requireAuth = true, agentDocFile } = {}) {
|
|
29498
|
-
const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname13(fileURLToPath4(import.meta.url)), process.env,
|
|
29870
|
+
const docFile = agentDocFile !== void 0 ? agentDocFile : pickGuideFile(dirname13(fileURLToPath4(import.meta.url)), process.env, existsSync13);
|
|
29499
29871
|
const app2 = Fastify({ logger: false, trustProxy: true });
|
|
29500
29872
|
app2.addContentTypeParser("application/json", { parseAs: "string" }, (_req, body, done) => {
|
|
29501
29873
|
if (!body) return done(null, void 0);
|
|
@@ -29578,9 +29950,10 @@ function buildApp({ requireAuth = true, agentDocFile } = {}) {
|
|
|
29578
29950
|
await api.register(githubIssues);
|
|
29579
29951
|
await api.register(telegramRoutes);
|
|
29580
29952
|
await api.register(webhooks_default);
|
|
29953
|
+
await api.register(changelog_default);
|
|
29581
29954
|
}, { prefix: "/api" });
|
|
29582
29955
|
if (process.env.NODE_ENV === "production") {
|
|
29583
|
-
const dist = pickWebDir(dirname13(fileURLToPath4(import.meta.url)), process.env,
|
|
29956
|
+
const dist = pickWebDir(dirname13(fileURLToPath4(import.meta.url)), process.env, existsSync13);
|
|
29584
29957
|
if (dist) {
|
|
29585
29958
|
app2.register(fastifyStatic, { root: dist });
|
|
29586
29959
|
app2.setNotFoundHandler((req, reply) => req.url.startsWith("/api") ? reply.code(404).send({ error: "not found" }) : reply.sendFile("index.html"));
|
|
@@ -30116,16 +30489,16 @@ function startWebhookEngine() {
|
|
|
30116
30489
|
// src/services/auto-merge.ts
|
|
30117
30490
|
init_src();
|
|
30118
30491
|
init_db();
|
|
30119
|
-
import { execFile as
|
|
30120
|
-
import { promisify as
|
|
30492
|
+
import { execFile as execFile12 } from "node:child_process";
|
|
30493
|
+
import { promisify as promisify11 } from "node:util";
|
|
30121
30494
|
var AUTO_MERGE_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
30122
30495
|
var AUTO_MERGE_GRACE_MS = 15 * 60 * 1e3;
|
|
30123
30496
|
var TICK_MS4 = 6e4;
|
|
30124
|
-
var
|
|
30125
|
-
var
|
|
30497
|
+
var exec9 = promisify11(execFile12);
|
|
30498
|
+
var GIT8 = { timeout: 3e4, maxBuffer: 1 << 24, encoding: "utf8" };
|
|
30126
30499
|
var gitOut = async (cwd, args) => {
|
|
30127
30500
|
try {
|
|
30128
|
-
return (await
|
|
30501
|
+
return (await exec9("git", args, { cwd, ...GIT8 })).stdout.trim();
|
|
30129
30502
|
} catch {
|
|
30130
30503
|
return null;
|
|
30131
30504
|
}
|