hanoman 0.1.20 → 0.1.22
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 +52 -6
- package/dist/server.js +308 -102
- package/package.json +1 -1
- package/prisma/migrations/20260806000000_spec_source_history/migration.sql +10 -0
- package/prisma/schema.prisma +7 -0
- package/web/assets/hnm-ill-diagram-technical-kit-modular-master-v01-DMmzOITA.webp +0 -0
- package/web/assets/hnm-ill-hero-workflow-16x9-master-v01-BngPlVl5.webp +0 -0
- package/web/assets/hnm-ill-lakon-anoman-duta-4x3-master-v01-0hEUEZ6d.webp +0 -0
- package/web/assets/hnm-ill-lakon-anoman-obong-4x3-master-v01-D841uTR5.webp +0 -0
- package/web/assets/hnm-ill-lakon-chiranjivi-4x3-master-v01-DtbnhQ7C.webp +0 -0
- package/web/assets/hnm-ill-lakon-gunung-dronagiri-4x3-master-v01-C6nCvUhL.webp +0 -0
- package/web/assets/hnm-ill-mascot-pose-ask-1x1-master-v01-S3NAOxD0.webp +0 -0
- package/web/assets/hnm-ill-mascot-pose-carry-knowledge-1x1-master-v01-BnkGXS8s.webp +0 -0
- package/web/assets/hnm-ill-mascot-pose-celebrate-1x1-master-v01-B-OkKay_.webp +0 -0
- package/web/assets/hnm-ill-mascot-pose-neutral-1x1-master-v01-CnvdPWgA.webp +0 -0
- package/web/assets/hnm-ill-mascot-pose-observe-1x1-master-v01-CG_jUBwM.webp +0 -0
- package/web/assets/hnm-ill-mascot-pose-warn-1x1-master-v01-Cm_KnXCE.webp +0 -0
- package/web/assets/hnm-ill-mascot-pose-welcome-1x1-master-v01-CAaHl0C1.webp +0 -0
- package/web/assets/hnm-ill-mascot-pose-work-1x1-master-v01-CFT1_F13.webp +0 -0
- package/web/assets/hnm-ill-model-character-sheet-master-v01-CIs0yaMj.webp +0 -0
- package/web/assets/hnm-ill-model-mascot-sheet-master-v01-BNJl7gCC.webp +0 -0
- package/web/assets/hnm-ill-motif-lakon-buntut-tile-strip-master-v01-DUZOJrQs.webp +0 -0
- package/web/assets/hnm-ill-product-state-awaiting-decision-4x3-master-v01-BwYfsI4Y.webp +0 -0
- package/web/assets/hnm-ill-product-state-empty-backlog-4x3-master-v01-CpttzxOY.webp +0 -0
- package/web/assets/hnm-ill-product-state-onboarding-4x3-master-v01-BEqlvDgy.webp +0 -0
- package/web/assets/hnm-ill-product-state-recoverable-error-4x3-master-v01-BcVo2BbN.webp +0 -0
- package/web/assets/hnm-ill-product-state-session-active-4x3-master-v01-BErsEZ5E.webp +0 -0
- package/web/assets/hnm-ill-product-state-success-4x3-master-v01-tJsjz-0L.webp +0 -0
- package/web/assets/hnm-ill-social-landscape-16x9-master-v01-DCmpP1dP.webp +0 -0
- package/web/assets/hnm-ill-social-portrait-4x5-master-v01-DAMYmb8y.webp +0 -0
- package/web/assets/hnm-ill-social-square-1x1-master-v01-CA5ntGVr.webp +0 -0
- package/web/assets/hnm-ill-social-story-9x16-master-v01-DCZ-dWfx.webp +0 -0
- package/web/assets/hnm-ill-spot-context-1x1-master-v01-C-_dXTKT.webp +0 -0
- package/web/assets/hnm-ill-spot-durable-knowledge-1x1-master-v01-_CVUcBE4.webp +0 -0
- package/web/assets/hnm-ill-spot-human-control-1x1-master-v01-BRV52OwQ.webp +0 -0
- package/web/assets/hnm-ill-spot-isolation-1x1-master-v01-C6wtR8uB.webp +0 -0
- package/web/assets/hnm-ill-spot-parallel-work-1x1-master-v01-Ch4Wl1Du.webp +0 -0
- package/web/assets/hnm-ill-spot-visibility-1x1-master-v01-DkgTZ2MF.webp +0 -0
- package/web/assets/hnm-ill-sticker-blocked-1x1-master-v01-suto6sZ9.webp +0 -0
- package/web/assets/hnm-ill-sticker-docs-updated-1x1-master-v01-CV0BaOI1.webp +0 -0
- package/web/assets/hnm-ill-sticker-ready-1x1-master-v01-Cetao79T.webp +0 -0
- package/web/assets/hnm-ill-sticker-review-1x1-master-v01-txPj5Zvx.webp +0 -0
- package/web/assets/hnm-ill-sticker-shipped-1x1-master-v01-r05qU9m-.webp +0 -0
- package/web/assets/hnm-ill-sticker-thanks-1x1-master-v01-BlHuF8Nt.webp +0 -0
- package/web/assets/hnm-ill-sticker-waiting-1x1-master-v01-Dh-UnLNA.webp +0 -0
- package/web/assets/hnm-ill-sticker-working-1x1-master-v01-TQU0v2gf.webp +0 -0
- package/web/assets/{index-BJTj--gG.js → index-Di1yjym7.js} +1547 -1547
- package/web/index.html +1 -1
package/dist/build-info.json
CHANGED
package/dist/cli.js
CHANGED
|
@@ -4301,6 +4301,24 @@ var init_enums = __esm({
|
|
|
4301
4301
|
}
|
|
4302
4302
|
});
|
|
4303
4303
|
|
|
4304
|
+
// ../shared/src/spec-source.ts
|
|
4305
|
+
function payloadShapeFor(source) {
|
|
4306
|
+
return source === "qa" ? "qa" : source === "goal" ? "goal" : "brief";
|
|
4307
|
+
}
|
|
4308
|
+
function shapeOfPayload(payload) {
|
|
4309
|
+
const p = payload ?? {};
|
|
4310
|
+
return "severity" in p ? "qa" : "goal" in p ? "goal" : "brief";
|
|
4311
|
+
}
|
|
4312
|
+
function payloadMatchesSource(source, payload) {
|
|
4313
|
+
return shapeOfPayload(payload) === payloadShapeFor(source);
|
|
4314
|
+
}
|
|
4315
|
+
var init_spec_source = __esm({
|
|
4316
|
+
"../shared/src/spec-source.ts"() {
|
|
4317
|
+
"use strict";
|
|
4318
|
+
init_enums();
|
|
4319
|
+
}
|
|
4320
|
+
});
|
|
4321
|
+
|
|
4304
4322
|
// ../shared/src/agent-engine.ts
|
|
4305
4323
|
var zAgentEngine;
|
|
4306
4324
|
var init_agent_engine = __esm({
|
|
@@ -4438,7 +4456,7 @@ var init_auto_merge = __esm({
|
|
|
4438
4456
|
});
|
|
4439
4457
|
|
|
4440
4458
|
// ../shared/src/entities.ts
|
|
4441
|
-
var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSpec, NOTIFY_SOUNDS, zCodex, CODEX_DEFAULTS, zSourceCommon, zScheduler, SCHEDULER_DEFAULTS, zGoal, GOAL_DEFAULTS, zConflict, CONFLICT_DEFAULTS, CHANGELOG_ENGINE_DEFAULTS, zLeadEngine, zLead, LEAD_DEFAULTS, zSetting, zNotification, zDocFile, zDeviceTokenView;
|
|
4459
|
+
var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSourceChange, zSpec, NOTIFY_SOUNDS, zCodex, CODEX_DEFAULTS, zSourceCommon, zScheduler, SCHEDULER_DEFAULTS, zGoal, GOAL_DEFAULTS, zConflict, CONFLICT_DEFAULTS, CHANGELOG_ENGINE_DEFAULTS, zLeadEngine, zLead, LEAD_DEFAULTS, zSetting, zNotification, zDocFile, zDeviceTokenView;
|
|
4442
4460
|
var init_entities = __esm({
|
|
4443
4461
|
"../shared/src/entities.ts"() {
|
|
4444
4462
|
"use strict";
|
|
@@ -4488,6 +4506,13 @@ var init_entities = __esm({
|
|
|
4488
4506
|
id: external_exports.string(),
|
|
4489
4507
|
reason: external_exports.enum(["missing", "unfinished", "unmerged"])
|
|
4490
4508
|
});
|
|
4509
|
+
zSourceChange = external_exports.object({
|
|
4510
|
+
at: external_exports.string(),
|
|
4511
|
+
from: external_exports.string(),
|
|
4512
|
+
to: external_exports.string(),
|
|
4513
|
+
by: external_exports.string(),
|
|
4514
|
+
payload: external_exports.unknown().optional()
|
|
4515
|
+
});
|
|
4491
4516
|
zSpec = external_exports.object({
|
|
4492
4517
|
id: external_exports.string(),
|
|
4493
4518
|
projectId: external_exports.string(),
|
|
@@ -4515,7 +4540,11 @@ var init_entities = __esm({
|
|
|
4515
4540
|
blockedBy: external_exports.array(zSpecBlocker).default([]),
|
|
4516
4541
|
// SPEC-486 · ADR-0103 · override kebijakan auto-merge item ini; null = warisi project.
|
|
4517
4542
|
// `.nullable().default(null)` menjaga respons/klien versi lama tetap parse.
|
|
4518
|
-
autoMerge: zAutoMerge.nullable().default(null)
|
|
4543
|
+
autoMerge: zAutoMerge.nullable().default(null),
|
|
4544
|
+
// SPEC-546 · ADR-0109 · jejak konversi type. `.default([])` menjaga respons/klien versi lama
|
|
4545
|
+
// tetap parse; kolom DB-nya `Json?` sehingga baris yang belum pernah dikonversi mengirim
|
|
4546
|
+
// `null` — pemakai UI menulis `spec.sourceHistory ?? []`, cermin `blockedBy`.
|
|
4547
|
+
sourceHistory: external_exports.array(zSourceChange).default([])
|
|
4519
4548
|
});
|
|
4520
4549
|
NOTIFY_SOUNDS = [
|
|
4521
4550
|
"off",
|
|
@@ -4957,7 +4986,7 @@ var init_prd_status = __esm({
|
|
|
4957
4986
|
});
|
|
4958
4987
|
|
|
4959
4988
|
// ../shared/src/dto.ts
|
|
4960
|
-
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;
|
|
4989
|
+
var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zChangeSpecSource, 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;
|
|
4961
4990
|
var init_dto = __esm({
|
|
4962
4991
|
"../shared/src/dto.ts"() {
|
|
4963
4992
|
"use strict";
|
|
@@ -4967,6 +4996,7 @@ var init_dto = __esm({
|
|
|
4967
4996
|
init_auto_merge();
|
|
4968
4997
|
init_prd_status();
|
|
4969
4998
|
init_enums();
|
|
4999
|
+
init_spec_source();
|
|
4970
5000
|
zIssueDeviceToken = external_exports.object({ name: external_exports.string().min(1) });
|
|
4971
5001
|
zSessionResult = external_exports.object({
|
|
4972
5002
|
id: external_exports.string(),
|
|
@@ -5034,9 +5064,7 @@ var init_dto = __esm({
|
|
|
5034
5064
|
// SPEC-447 · ADR-0093 · divalidasi server (id ada / satu project / bukan diri sendiri / non-siklus).
|
|
5035
5065
|
dependsOn: external_exports.array(external_exports.string()).optional()
|
|
5036
5066
|
}).superRefine((o, ctx) => {
|
|
5037
|
-
|
|
5038
|
-
const want = o.source === "qa" ? "qa" : o.source === "goal" ? "goal" : "brief";
|
|
5039
|
-
if (shape !== want)
|
|
5067
|
+
if (!payloadMatchesSource(o.source, o.payload))
|
|
5040
5068
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["payload"], message: "bentuk payload tak cocok dengan source" });
|
|
5041
5069
|
});
|
|
5042
5070
|
zPatchSpec = external_exports.object({
|
|
@@ -5057,6 +5085,13 @@ var init_dto = __esm({
|
|
|
5057
5085
|
// apa yang terjadi SESUDAH kerja, bukan konten yang sedang dikerjakan sesi hidup.
|
|
5058
5086
|
autoMerge: zAutoMerge.nullable().optional()
|
|
5059
5087
|
});
|
|
5088
|
+
zChangeSpecSource = external_exports.object({
|
|
5089
|
+
source: zSpecSource,
|
|
5090
|
+
payload: external_exports.union([zBriefPayload, zQaPayload, zGoalPayload]).optional()
|
|
5091
|
+
}).superRefine((o, ctx) => {
|
|
5092
|
+
if (o.payload !== void 0 && !payloadMatchesSource(o.source, o.payload))
|
|
5093
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["payload"], message: "bentuk payload tak cocok dengan source" });
|
|
5094
|
+
});
|
|
5060
5095
|
zIntegrate = external_exports.object({
|
|
5061
5096
|
op: external_exports.enum(["merge", "rebase"]),
|
|
5062
5097
|
target: external_exports.string().regex(/^(local|origin):.+/)
|
|
@@ -5454,6 +5489,8 @@ var init_api = __esm({
|
|
|
5454
5489
|
// SPEC-340 · ADR-0076 · rekomendasi tindak lanjut audit (turunan blok json dokumen audit).
|
|
5455
5490
|
specEscalation: (id) => `${API}/specs/${id}/escalation`,
|
|
5456
5491
|
specIntegrate: (id) => `${API}/specs/${id}/integrate`,
|
|
5492
|
+
// SPEC-546 · ADR-0109 · ubah type/source item in-place (operasi khusus, bukan field PATCH).
|
|
5493
|
+
specSource: (id) => `${API}/specs/${id}/source`,
|
|
5457
5494
|
specReview: (id) => `${API}/specs/${id}/review`,
|
|
5458
5495
|
specReviewFile: (id, path) => `${API}/specs/${id}/review/${path}`,
|
|
5459
5496
|
settings: `${API}/settings`,
|
|
@@ -6009,6 +6046,14 @@ var init_webhook = __esm({
|
|
|
6009
6046
|
label: "Stage backlog berpindah",
|
|
6010
6047
|
changed: ["stage"],
|
|
6011
6048
|
when: "Stage berpindah \u2014 baik oleh fase sesi yang tercatat (otomatis) maupun revert manual operator. Menggantikan spec.updated untuk perubahan itu."
|
|
6049
|
+
}, {
|
|
6050
|
+
// SPEC-546 · ADR-0109 · konversi type item. Pola yang sama dengan stage_changed: peristiwa
|
|
6051
|
+
// turunan MENGGANTIKAN spec.updated, supaya penerima bisa bereaksi pada "type berpindah"
|
|
6052
|
+
// tanpa mendiff dua amplop.
|
|
6053
|
+
type: "spec.source_changed",
|
|
6054
|
+
label: "Type backlog berpindah",
|
|
6055
|
+
changed: ["source"],
|
|
6056
|
+
when: "Type/source item backlog dikonversi lewat POST /specs/:id/source (mis. brief \u2192 qa). Menggantikan spec.updated untuk perubahan itu."
|
|
6012
6057
|
}],
|
|
6013
6058
|
sample: {
|
|
6014
6059
|
id: "SPEC-481",
|
|
@@ -6965,6 +7010,7 @@ var init_src = __esm({
|
|
|
6965
7010
|
"../shared/src/index.ts"() {
|
|
6966
7011
|
"use strict";
|
|
6967
7012
|
init_enums();
|
|
7013
|
+
init_spec_source();
|
|
6968
7014
|
init_agent_engine();
|
|
6969
7015
|
init_entities();
|
|
6970
7016
|
init_agent();
|
package/dist/server.js
CHANGED
|
@@ -4164,6 +4164,98 @@ var init_enums = __esm({
|
|
|
4164
4164
|
}
|
|
4165
4165
|
});
|
|
4166
4166
|
|
|
4167
|
+
// ../shared/src/spec-source.ts
|
|
4168
|
+
function payloadShapeFor(source) {
|
|
4169
|
+
return source === "qa" ? "qa" : source === "goal" ? "goal" : "brief";
|
|
4170
|
+
}
|
|
4171
|
+
function shapeOfPayload(payload) {
|
|
4172
|
+
const p = payload ?? {};
|
|
4173
|
+
return "severity" in p ? "qa" : "goal" in p ? "goal" : "brief";
|
|
4174
|
+
}
|
|
4175
|
+
function payloadMatchesSource(source, payload) {
|
|
4176
|
+
return shapeOfPayload(payload) === payloadShapeFor(source);
|
|
4177
|
+
}
|
|
4178
|
+
function priorityFromSeverity(severity) {
|
|
4179
|
+
return severity === "minor" ? "sedang" : "tinggi";
|
|
4180
|
+
}
|
|
4181
|
+
function severityFromPriority(priority) {
|
|
4182
|
+
return priority === "tinggi" ? "major" : "minor";
|
|
4183
|
+
}
|
|
4184
|
+
function convertPayload(to, payload) {
|
|
4185
|
+
const p = payload ?? {};
|
|
4186
|
+
const str3 = (k) => typeof p[k] === "string" ? p[k] : "";
|
|
4187
|
+
const prio = () => p.priority === "tinggi" || p.priority === "rendah" ? p.priority : "sedang";
|
|
4188
|
+
const nonEmpty = (fields) => fields.filter((f) => str3(f) !== "");
|
|
4189
|
+
const fromShape = shapeOfPayload(payload);
|
|
4190
|
+
const toShape = payloadShapeFor(to);
|
|
4191
|
+
const fromAudit = str3("fromAudit");
|
|
4192
|
+
const done = (out3, dropped) => ({
|
|
4193
|
+
payload: out3,
|
|
4194
|
+
dropped,
|
|
4195
|
+
missing: SHAPE_REQUIRED[toShape].filter((f) => typeof out3[f] !== "string" || out3[f] === "")
|
|
4196
|
+
});
|
|
4197
|
+
if (fromShape === toShape) return { payload: { ...p }, dropped: [], missing: [] };
|
|
4198
|
+
if (toShape === "qa") {
|
|
4199
|
+
if (fromShape === "brief")
|
|
4200
|
+
return done({
|
|
4201
|
+
severity: severityFromPriority(prio()),
|
|
4202
|
+
steps: "",
|
|
4203
|
+
expected: str3("outcome"),
|
|
4204
|
+
actual: str3("context"),
|
|
4205
|
+
env: "",
|
|
4206
|
+
...fromAudit ? { fromAudit } : {}
|
|
4207
|
+
}, nonEmpty(["constraints"]));
|
|
4208
|
+
return done({
|
|
4209
|
+
severity: severityFromPriority(prio()),
|
|
4210
|
+
steps: "",
|
|
4211
|
+
expected: str3("goal"),
|
|
4212
|
+
actual: "",
|
|
4213
|
+
env: ""
|
|
4214
|
+
}, nonEmpty(["done", "constraints"]));
|
|
4215
|
+
}
|
|
4216
|
+
if (toShape === "goal") {
|
|
4217
|
+
if (fromShape === "brief") {
|
|
4218
|
+
const goal = str3("outcome") || str3("context");
|
|
4219
|
+
return done(
|
|
4220
|
+
{ goal, done: "", constraints: str3("constraints"), priority: prio() },
|
|
4221
|
+
nonEmpty([...str3("outcome") ? ["context"] : [], "fromAudit"])
|
|
4222
|
+
);
|
|
4223
|
+
}
|
|
4224
|
+
return done({
|
|
4225
|
+
goal: str3("expected"),
|
|
4226
|
+
done: "",
|
|
4227
|
+
constraints: "",
|
|
4228
|
+
priority: priorityFromSeverity(p.severity)
|
|
4229
|
+
}, nonEmpty(["steps", "actual", "env", "fromAudit"]));
|
|
4230
|
+
}
|
|
4231
|
+
if (fromShape === "qa")
|
|
4232
|
+
return done({
|
|
4233
|
+
context: str3("actual"),
|
|
4234
|
+
outcome: str3("expected"),
|
|
4235
|
+
constraints: "",
|
|
4236
|
+
priority: priorityFromSeverity(p.severity),
|
|
4237
|
+
...fromAudit ? { fromAudit } : {}
|
|
4238
|
+
}, nonEmpty(["steps", "env"]));
|
|
4239
|
+
return done({
|
|
4240
|
+
context: "",
|
|
4241
|
+
outcome: str3("goal"),
|
|
4242
|
+
constraints: str3("constraints"),
|
|
4243
|
+
priority: prio()
|
|
4244
|
+
}, nonEmpty(["done"]));
|
|
4245
|
+
}
|
|
4246
|
+
var SHAPE_REQUIRED;
|
|
4247
|
+
var init_spec_source = __esm({
|
|
4248
|
+
"../shared/src/spec-source.ts"() {
|
|
4249
|
+
"use strict";
|
|
4250
|
+
init_enums();
|
|
4251
|
+
SHAPE_REQUIRED = {
|
|
4252
|
+
brief: ["context", "outcome"],
|
|
4253
|
+
qa: ["steps", "expected", "actual", "env"],
|
|
4254
|
+
goal: ["goal", "done"]
|
|
4255
|
+
};
|
|
4256
|
+
}
|
|
4257
|
+
});
|
|
4258
|
+
|
|
4167
4259
|
// ../shared/src/agent-engine.ts
|
|
4168
4260
|
var zAgentEngine;
|
|
4169
4261
|
var init_agent_engine = __esm({
|
|
@@ -4341,7 +4433,7 @@ function cmpVersion(a, b) {
|
|
|
4341
4433
|
}
|
|
4342
4434
|
return 0;
|
|
4343
4435
|
}
|
|
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;
|
|
4436
|
+
var zProject, zBriefPayload, zQaPayload, zGoalPayload, zSpecBlocker, zSourceChange, 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
4437
|
var init_entities = __esm({
|
|
4346
4438
|
"../shared/src/entities.ts"() {
|
|
4347
4439
|
"use strict";
|
|
@@ -4391,6 +4483,13 @@ var init_entities = __esm({
|
|
|
4391
4483
|
id: external_exports.string(),
|
|
4392
4484
|
reason: external_exports.enum(["missing", "unfinished", "unmerged"])
|
|
4393
4485
|
});
|
|
4486
|
+
zSourceChange = external_exports.object({
|
|
4487
|
+
at: external_exports.string(),
|
|
4488
|
+
from: external_exports.string(),
|
|
4489
|
+
to: external_exports.string(),
|
|
4490
|
+
by: external_exports.string(),
|
|
4491
|
+
payload: external_exports.unknown().optional()
|
|
4492
|
+
});
|
|
4394
4493
|
zSpec = external_exports.object({
|
|
4395
4494
|
id: external_exports.string(),
|
|
4396
4495
|
projectId: external_exports.string(),
|
|
@@ -4418,7 +4517,11 @@ var init_entities = __esm({
|
|
|
4418
4517
|
blockedBy: external_exports.array(zSpecBlocker).default([]),
|
|
4419
4518
|
// SPEC-486 · ADR-0103 · override kebijakan auto-merge item ini; null = warisi project.
|
|
4420
4519
|
// `.nullable().default(null)` menjaga respons/klien versi lama tetap parse.
|
|
4421
|
-
autoMerge: zAutoMerge.nullable().default(null)
|
|
4520
|
+
autoMerge: zAutoMerge.nullable().default(null),
|
|
4521
|
+
// SPEC-546 · ADR-0109 · jejak konversi type. `.default([])` menjaga respons/klien versi lama
|
|
4522
|
+
// tetap parse; kolom DB-nya `Json?` sehingga baris yang belum pernah dikonversi mengirim
|
|
4523
|
+
// `null` — pemakai UI menulis `spec.sourceHistory ?? []`, cermin `blockedBy`.
|
|
4524
|
+
sourceHistory: external_exports.array(zSourceChange).default([])
|
|
4422
4525
|
});
|
|
4423
4526
|
NOTIFY_SOUNDS = [
|
|
4424
4527
|
"off",
|
|
@@ -5134,7 +5237,7 @@ var init_prd_status = __esm({
|
|
|
5134
5237
|
function flowForSource(source) {
|
|
5135
5238
|
return source === "qa" ? "qa" : source === "audit" ? "audit" : source === "goal" ? "goal" : "feature";
|
|
5136
5239
|
}
|
|
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;
|
|
5240
|
+
var zIssueDeviceToken, zSessionResult, zSessionHistory, zCreateProject, zProjectId, zRenameProject, zUpdateProject, zCreateSpec, zPatchSpec, zChangeSpecSource, 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;
|
|
5138
5241
|
var init_dto = __esm({
|
|
5139
5242
|
"../shared/src/dto.ts"() {
|
|
5140
5243
|
"use strict";
|
|
@@ -5144,6 +5247,7 @@ var init_dto = __esm({
|
|
|
5144
5247
|
init_auto_merge();
|
|
5145
5248
|
init_prd_status();
|
|
5146
5249
|
init_enums();
|
|
5250
|
+
init_spec_source();
|
|
5147
5251
|
zIssueDeviceToken = external_exports.object({ name: external_exports.string().min(1) });
|
|
5148
5252
|
zSessionResult = external_exports.object({
|
|
5149
5253
|
id: external_exports.string(),
|
|
@@ -5211,9 +5315,7 @@ var init_dto = __esm({
|
|
|
5211
5315
|
// SPEC-447 · ADR-0093 · divalidasi server (id ada / satu project / bukan diri sendiri / non-siklus).
|
|
5212
5316
|
dependsOn: external_exports.array(external_exports.string()).optional()
|
|
5213
5317
|
}).superRefine((o, ctx) => {
|
|
5214
|
-
|
|
5215
|
-
const want = o.source === "qa" ? "qa" : o.source === "goal" ? "goal" : "brief";
|
|
5216
|
-
if (shape !== want)
|
|
5318
|
+
if (!payloadMatchesSource(o.source, o.payload))
|
|
5217
5319
|
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["payload"], message: "bentuk payload tak cocok dengan source" });
|
|
5218
5320
|
});
|
|
5219
5321
|
zPatchSpec = external_exports.object({
|
|
@@ -5234,6 +5336,13 @@ var init_dto = __esm({
|
|
|
5234
5336
|
// apa yang terjadi SESUDAH kerja, bukan konten yang sedang dikerjakan sesi hidup.
|
|
5235
5337
|
autoMerge: zAutoMerge.nullable().optional()
|
|
5236
5338
|
});
|
|
5339
|
+
zChangeSpecSource = external_exports.object({
|
|
5340
|
+
source: zSpecSource,
|
|
5341
|
+
payload: external_exports.union([zBriefPayload, zQaPayload, zGoalPayload]).optional()
|
|
5342
|
+
}).superRefine((o, ctx) => {
|
|
5343
|
+
if (o.payload !== void 0 && !payloadMatchesSource(o.source, o.payload))
|
|
5344
|
+
ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: ["payload"], message: "bentuk payload tak cocok dengan source" });
|
|
5345
|
+
});
|
|
5237
5346
|
zIntegrate = external_exports.object({
|
|
5238
5347
|
op: external_exports.enum(["merge", "rebase"]),
|
|
5239
5348
|
target: external_exports.string().regex(/^(local|origin):.+/)
|
|
@@ -5631,6 +5740,8 @@ var init_api = __esm({
|
|
|
5631
5740
|
// SPEC-340 · ADR-0076 · rekomendasi tindak lanjut audit (turunan blok json dokumen audit).
|
|
5632
5741
|
specEscalation: (id) => `${API}/specs/${id}/escalation`,
|
|
5633
5742
|
specIntegrate: (id) => `${API}/specs/${id}/integrate`,
|
|
5743
|
+
// SPEC-546 · ADR-0109 · ubah type/source item in-place (operasi khusus, bukan field PATCH).
|
|
5744
|
+
specSource: (id) => `${API}/specs/${id}/source`,
|
|
5634
5745
|
specReview: (id) => `${API}/specs/${id}/review`,
|
|
5635
5746
|
specReviewFile: (id, path) => `${API}/specs/${id}/review/${path}`,
|
|
5636
5747
|
settings: `${API}/settings`,
|
|
@@ -6311,6 +6422,14 @@ var init_webhook = __esm({
|
|
|
6311
6422
|
label: "Stage backlog berpindah",
|
|
6312
6423
|
changed: ["stage"],
|
|
6313
6424
|
when: "Stage berpindah \u2014 baik oleh fase sesi yang tercatat (otomatis) maupun revert manual operator. Menggantikan spec.updated untuk perubahan itu."
|
|
6425
|
+
}, {
|
|
6426
|
+
// SPEC-546 · ADR-0109 · konversi type item. Pola yang sama dengan stage_changed: peristiwa
|
|
6427
|
+
// turunan MENGGANTIKAN spec.updated, supaya penerima bisa bereaksi pada "type berpindah"
|
|
6428
|
+
// tanpa mendiff dua amplop.
|
|
6429
|
+
type: "spec.source_changed",
|
|
6430
|
+
label: "Type backlog berpindah",
|
|
6431
|
+
changed: ["source"],
|
|
6432
|
+
when: "Type/source item backlog dikonversi lewat POST /specs/:id/source (mis. brief \u2192 qa). Menggantikan spec.updated untuk perubahan itu."
|
|
6314
6433
|
}],
|
|
6315
6434
|
sample: {
|
|
6316
6435
|
id: "SPEC-481",
|
|
@@ -7250,6 +7369,7 @@ var init_src = __esm({
|
|
|
7250
7369
|
"../shared/src/index.ts"() {
|
|
7251
7370
|
"use strict";
|
|
7252
7371
|
init_enums();
|
|
7372
|
+
init_spec_source();
|
|
7253
7373
|
init_agent_engine();
|
|
7254
7374
|
init_entities();
|
|
7255
7375
|
init_agent();
|
|
@@ -9749,7 +9869,12 @@ var init_sync = __esm({
|
|
|
9749
9869
|
// SPEC-516 · ADR-0105 · doneAt ikut menyeberang — cermin createdAt/startedAt. Tanpa ini spec
|
|
9750
9870
|
// asal-hub mendarat di tiap client dengan doneAt null tanpa satu pun error, dan changelog
|
|
9751
9871
|
// mode backlog di client itu selamanya kosong.
|
|
9752
|
-
|
|
9872
|
+
// SPEC-546 · ADR-0109 · sourceHistory ikut menyeberang: jejak konversi type adalah bagian
|
|
9873
|
+
// keadaan yang harus dilihat sama oleh semua mesin. `upsert` yang tak menyebut sebuah kolom
|
|
9874
|
+
// TETAP berhasil, jadi kolom yang terlewat di sini mendarat sebagai null palsu di tiap client
|
|
9875
|
+
// tanpa satu pun error (kelas gagal-senyap ADR-0090/0093/0094/0105). BUKAN DATE_FIELDS —
|
|
9876
|
+
// `at` hidup di dalam JSON-nya, kolomnya sendiri bukan DateTime.
|
|
9877
|
+
spec: ["projectId", "title", "source", "stage", "priority", "author", "objective", "payload", "branchFrom", "baseSha", "headSha", "dependsOn", "sourceHistory", "createdAt", "startedAt", "doneAt", "updatedAt"],
|
|
9753
9878
|
vps: ["name", "host", "port", "user", "health", "audit", "hardened", "lastSeenAt", "lastAuditAt", "updatedAt"],
|
|
9754
9879
|
sessionResult: ["projectId", "specId", "oldStage", "newStage", "commitSha", "branch", "prUrl", "status", "deviceId", "author", "createdAt", "updatedAt"],
|
|
9755
9880
|
// SPEC-268 · ADR-0066 · metadata tiket (lampiran biner tak disync). accessKeyHash wajib
|
|
@@ -19321,6 +19446,148 @@ async function nextSpecId(repoDir) {
|
|
|
19321
19446
|
return `SPEC-${maxNum(ids, floor) + 1}`;
|
|
19322
19447
|
}
|
|
19323
19448
|
|
|
19449
|
+
// src/services/spec-fields.ts
|
|
19450
|
+
init_src();
|
|
19451
|
+
function deriveSpecFields(source, payload, manualPriority) {
|
|
19452
|
+
if (source === "goal") {
|
|
19453
|
+
const pick2 = (v) => typeof v === "string" ? v.trim() : "";
|
|
19454
|
+
return {
|
|
19455
|
+
priority: manualPriority,
|
|
19456
|
+
objective: pick2(payload?.goal) || pick2(payload?.done) || "\u2014 goal belum diisi."
|
|
19457
|
+
};
|
|
19458
|
+
}
|
|
19459
|
+
const isQa = source === "qa";
|
|
19460
|
+
const priority = isQa && payload && "severity" in payload ? priorityFromSeverity(payload.severity) : manualPriority;
|
|
19461
|
+
const objective = isQa && payload && "actual" in payload ? payload.actual || payload.steps || "\u2014 audit untuk menelusuri akar masalah." : payload && "outcome" in payload ? payload.outcome || payload.context || "\u2014 brainstorm untuk memperjelas objective." : "";
|
|
19462
|
+
return { priority, objective };
|
|
19463
|
+
}
|
|
19464
|
+
|
|
19465
|
+
// src/services/spec-source.ts
|
|
19466
|
+
init_src();
|
|
19467
|
+
function checkSourceChange(spec, to, payload) {
|
|
19468
|
+
const started2 = spec.stage !== "brainstorming" || spec.baseSha !== null;
|
|
19469
|
+
if (started2) {
|
|
19470
|
+
if (flowForSource(spec.source) !== flowForSource(to))
|
|
19471
|
+
return {
|
|
19472
|
+
ok: false,
|
|
19473
|
+
code: 409,
|
|
19474
|
+
error: "backlog item sudah dimulai \u2014 type hanya bisa pindah ke source dengan flow yang sama"
|
|
19475
|
+
};
|
|
19476
|
+
if (payload !== void 0)
|
|
19477
|
+
return { ok: false, code: 409, error: "backlog item sudah dimulai \u2014 isinya tak bisa diubah" };
|
|
19478
|
+
return { ok: true, payload: spec.payload ?? {}, dropped: [] };
|
|
19479
|
+
}
|
|
19480
|
+
if (payload !== void 0) {
|
|
19481
|
+
if (!payloadMatchesSource(to, payload))
|
|
19482
|
+
return { ok: false, code: 400, error: "bentuk payload tak cocok dengan source" };
|
|
19483
|
+
return { ok: true, payload, dropped: [] };
|
|
19484
|
+
}
|
|
19485
|
+
const c = convertPayload(to, spec.payload);
|
|
19486
|
+
return { ok: true, payload: c.payload, dropped: c.dropped };
|
|
19487
|
+
}
|
|
19488
|
+
function sourceChangeEntry(spec, to, by, at) {
|
|
19489
|
+
return { at: at.toISOString(), from: spec.source, to, by, payload: spec.payload ?? null };
|
|
19490
|
+
}
|
|
19491
|
+
function appendSourceHistory(current, entry) {
|
|
19492
|
+
return [...Array.isArray(current) ? current : [], entry];
|
|
19493
|
+
}
|
|
19494
|
+
|
|
19495
|
+
// src/services/notifications.ts
|
|
19496
|
+
init_db();
|
|
19497
|
+
init_pty();
|
|
19498
|
+
async function recordDrift(vpsId, vpsName, drift, snapshotId) {
|
|
19499
|
+
if (drift.length === 0) return;
|
|
19500
|
+
const ids = drift.map((d) => d.itemId);
|
|
19501
|
+
const shown = ids.slice(0, 5).join(", ") + (ids.length > 5 ? `, +${ids.length - 5} lagi` : "");
|
|
19502
|
+
const title = `Drift di "${vpsName}": ${drift.length} item regresi (${shown})`;
|
|
19503
|
+
await prisma.notification.create({
|
|
19504
|
+
data: { type: "drift", key: `drift:${vpsId}:${snapshotId}`, title, projectId: null }
|
|
19505
|
+
}).catch(() => {
|
|
19506
|
+
});
|
|
19507
|
+
}
|
|
19508
|
+
async function recordCompletion(specId, title, projectId) {
|
|
19509
|
+
await prisma.spec.updateMany({ where: { id: specId, doneAt: null }, data: { doneAt: /* @__PURE__ */ new Date() } }).catch(() => {
|
|
19510
|
+
});
|
|
19511
|
+
const sessionId2 = specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
19512
|
+
await prisma.notification.create({
|
|
19513
|
+
data: { type: "done", key: `done:${specId}`, specId, sessionId: sessionId2, title, projectId }
|
|
19514
|
+
}).catch(() => {
|
|
19515
|
+
});
|
|
19516
|
+
}
|
|
19517
|
+
async function recordFailure(specId, title, projectId, reason) {
|
|
19518
|
+
const sessionId2 = specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
19519
|
+
await prisma.notification.create({
|
|
19520
|
+
data: { type: "fail", key: `fail:${specId}`, specId, sessionId: sessionId2, title: `Gagal: ${title} \u2014 ${reason}`, projectId }
|
|
19521
|
+
}).catch(() => {
|
|
19522
|
+
});
|
|
19523
|
+
}
|
|
19524
|
+
async function recordAutoMerge(specId, projectId, title) {
|
|
19525
|
+
const sessionId2 = specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
19526
|
+
await prisma.notification.create({
|
|
19527
|
+
data: { type: "automerge", key: `automerge:${specId}`, specId, sessionId: sessionId2, title, projectId }
|
|
19528
|
+
}).catch(() => {
|
|
19529
|
+
});
|
|
19530
|
+
}
|
|
19531
|
+
async function recordSourceChange(specId, projectId, title, from, to, seq) {
|
|
19532
|
+
const sessionId2 = specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
19533
|
+
await prisma.notification.create({
|
|
19534
|
+
data: {
|
|
19535
|
+
type: "spec-source",
|
|
19536
|
+
key: `source:${specId}:${seq}`,
|
|
19537
|
+
specId,
|
|
19538
|
+
sessionId: sessionId2,
|
|
19539
|
+
title: `${specId} \xB7 type ${from} \u2192 ${to} \u2014 ${title}`,
|
|
19540
|
+
projectId
|
|
19541
|
+
}
|
|
19542
|
+
}).catch(() => {
|
|
19543
|
+
});
|
|
19544
|
+
}
|
|
19545
|
+
async function recordNewTicket(ticketId, projectId, projectName, category, title) {
|
|
19546
|
+
const short = title.length > 80 ? title.slice(0, 77) + "\u2026" : title;
|
|
19547
|
+
const t = `Keluhan baru di "${projectName}": ${category}: ${short}`;
|
|
19548
|
+
await prisma.notification.create({
|
|
19549
|
+
data: { type: "ticket", key: `ticket:${ticketId}`, projectId, title: t }
|
|
19550
|
+
}).catch(() => {
|
|
19551
|
+
});
|
|
19552
|
+
}
|
|
19553
|
+
async function recordLeadDecision(decisionId, title, projectId, specId, sessionId2) {
|
|
19554
|
+
await prisma.notification.create({
|
|
19555
|
+
data: { type: "lead", key: `lead:${decisionId}`, specId, sessionId: sessionId2, projectId, title }
|
|
19556
|
+
}).catch(() => {
|
|
19557
|
+
});
|
|
19558
|
+
}
|
|
19559
|
+
var awaiting = /* @__PURE__ */ new Set();
|
|
19560
|
+
async function scanDecisions(read = liveDecisions) {
|
|
19561
|
+
const next = /* @__PURE__ */ new Set();
|
|
19562
|
+
const fresh = [];
|
|
19563
|
+
for (const s2 of read()) {
|
|
19564
|
+
if (!markerFilled(s2.decisionFile)) continue;
|
|
19565
|
+
next.add(s2.id);
|
|
19566
|
+
if (!awaiting.has(s2.id)) fresh.push(s2);
|
|
19567
|
+
}
|
|
19568
|
+
awaiting = next;
|
|
19569
|
+
for (const s2 of fresh) {
|
|
19570
|
+
const title = s2.specId ? (await prisma.spec.findUnique({ where: { id: s2.specId }, select: { title: true } }))?.title ?? s2.specId : s2.id;
|
|
19571
|
+
await prisma.notification.create({
|
|
19572
|
+
data: { type: "decision", specId: s2.specId ?? null, sessionId: s2.id, projectId: s2.projectId || null, title }
|
|
19573
|
+
});
|
|
19574
|
+
}
|
|
19575
|
+
}
|
|
19576
|
+
var DEFAULT_FEED_TAKE = 50;
|
|
19577
|
+
async function notificationsFeed(p = {}) {
|
|
19578
|
+
await scanDecisions();
|
|
19579
|
+
const pageSize = p.limit ? Math.max(1, Math.floor(+p.limit) || 1) : DEFAULT_FEED_TAKE;
|
|
19580
|
+
const page = p.page ? Math.max(1, Math.floor(+p.page) || 1) : 1;
|
|
19581
|
+
const total = await prisma.notification.count();
|
|
19582
|
+
const items = await prisma.notification.findMany({
|
|
19583
|
+
orderBy: { createdAt: "desc" },
|
|
19584
|
+
skip: (page - 1) * pageSize,
|
|
19585
|
+
take: pageSize
|
|
19586
|
+
});
|
|
19587
|
+
const unread = await prisma.notification.count({ where: { readAt: null } });
|
|
19588
|
+
return { items, unread, total, page, pageSize };
|
|
19589
|
+
}
|
|
19590
|
+
|
|
19324
19591
|
// src/routes/specs.ts
|
|
19325
19592
|
init_stage_machine();
|
|
19326
19593
|
|
|
@@ -21978,88 +22245,6 @@ init_pty();
|
|
|
21978
22245
|
init_session_phases();
|
|
21979
22246
|
init_stage_machine();
|
|
21980
22247
|
|
|
21981
|
-
// src/services/notifications.ts
|
|
21982
|
-
init_db();
|
|
21983
|
-
init_pty();
|
|
21984
|
-
async function recordDrift(vpsId, vpsName, drift, snapshotId) {
|
|
21985
|
-
if (drift.length === 0) return;
|
|
21986
|
-
const ids = drift.map((d) => d.itemId);
|
|
21987
|
-
const shown = ids.slice(0, 5).join(", ") + (ids.length > 5 ? `, +${ids.length - 5} lagi` : "");
|
|
21988
|
-
const title = `Drift di "${vpsName}": ${drift.length} item regresi (${shown})`;
|
|
21989
|
-
await prisma.notification.create({
|
|
21990
|
-
data: { type: "drift", key: `drift:${vpsId}:${snapshotId}`, title, projectId: null }
|
|
21991
|
-
}).catch(() => {
|
|
21992
|
-
});
|
|
21993
|
-
}
|
|
21994
|
-
async function recordCompletion(specId, title, projectId) {
|
|
21995
|
-
await prisma.spec.updateMany({ where: { id: specId, doneAt: null }, data: { doneAt: /* @__PURE__ */ new Date() } }).catch(() => {
|
|
21996
|
-
});
|
|
21997
|
-
const sessionId2 = specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
21998
|
-
await prisma.notification.create({
|
|
21999
|
-
data: { type: "done", key: `done:${specId}`, specId, sessionId: sessionId2, title, projectId }
|
|
22000
|
-
}).catch(() => {
|
|
22001
|
-
});
|
|
22002
|
-
}
|
|
22003
|
-
async function recordFailure(specId, title, projectId, reason) {
|
|
22004
|
-
const sessionId2 = specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
22005
|
-
await prisma.notification.create({
|
|
22006
|
-
data: { type: "fail", key: `fail:${specId}`, specId, sessionId: sessionId2, title: `Gagal: ${title} \u2014 ${reason}`, projectId }
|
|
22007
|
-
}).catch(() => {
|
|
22008
|
-
});
|
|
22009
|
-
}
|
|
22010
|
-
async function recordAutoMerge(specId, projectId, title) {
|
|
22011
|
-
const sessionId2 = specId.toLowerCase().replace(/[^a-z0-9_-]/g, "_");
|
|
22012
|
-
await prisma.notification.create({
|
|
22013
|
-
data: { type: "automerge", key: `automerge:${specId}`, specId, sessionId: sessionId2, title, projectId }
|
|
22014
|
-
}).catch(() => {
|
|
22015
|
-
});
|
|
22016
|
-
}
|
|
22017
|
-
async function recordNewTicket(ticketId, projectId, projectName, category, title) {
|
|
22018
|
-
const short = title.length > 80 ? title.slice(0, 77) + "\u2026" : title;
|
|
22019
|
-
const t = `Keluhan baru di "${projectName}": ${category}: ${short}`;
|
|
22020
|
-
await prisma.notification.create({
|
|
22021
|
-
data: { type: "ticket", key: `ticket:${ticketId}`, projectId, title: t }
|
|
22022
|
-
}).catch(() => {
|
|
22023
|
-
});
|
|
22024
|
-
}
|
|
22025
|
-
async function recordLeadDecision(decisionId, title, projectId, specId, sessionId2) {
|
|
22026
|
-
await prisma.notification.create({
|
|
22027
|
-
data: { type: "lead", key: `lead:${decisionId}`, specId, sessionId: sessionId2, projectId, title }
|
|
22028
|
-
}).catch(() => {
|
|
22029
|
-
});
|
|
22030
|
-
}
|
|
22031
|
-
var awaiting = /* @__PURE__ */ new Set();
|
|
22032
|
-
async function scanDecisions(read = liveDecisions) {
|
|
22033
|
-
const next = /* @__PURE__ */ new Set();
|
|
22034
|
-
const fresh = [];
|
|
22035
|
-
for (const s2 of read()) {
|
|
22036
|
-
if (!markerFilled(s2.decisionFile)) continue;
|
|
22037
|
-
next.add(s2.id);
|
|
22038
|
-
if (!awaiting.has(s2.id)) fresh.push(s2);
|
|
22039
|
-
}
|
|
22040
|
-
awaiting = next;
|
|
22041
|
-
for (const s2 of fresh) {
|
|
22042
|
-
const title = s2.specId ? (await prisma.spec.findUnique({ where: { id: s2.specId }, select: { title: true } }))?.title ?? s2.specId : s2.id;
|
|
22043
|
-
await prisma.notification.create({
|
|
22044
|
-
data: { type: "decision", specId: s2.specId ?? null, sessionId: s2.id, projectId: s2.projectId || null, title }
|
|
22045
|
-
});
|
|
22046
|
-
}
|
|
22047
|
-
}
|
|
22048
|
-
var DEFAULT_FEED_TAKE = 50;
|
|
22049
|
-
async function notificationsFeed(p = {}) {
|
|
22050
|
-
await scanDecisions();
|
|
22051
|
-
const pageSize = p.limit ? Math.max(1, Math.floor(+p.limit) || 1) : DEFAULT_FEED_TAKE;
|
|
22052
|
-
const page = p.page ? Math.max(1, Math.floor(+p.page) || 1) : 1;
|
|
22053
|
-
const total = await prisma.notification.count();
|
|
22054
|
-
const items = await prisma.notification.findMany({
|
|
22055
|
-
orderBy: { createdAt: "desc" },
|
|
22056
|
-
skip: (page - 1) * pageSize,
|
|
22057
|
-
take: pageSize
|
|
22058
|
-
});
|
|
22059
|
-
const unread = await prisma.notification.count({ where: { readAt: null } });
|
|
22060
|
-
return { items, unread, total, page, pageSize };
|
|
22061
|
-
}
|
|
22062
|
-
|
|
22063
22248
|
// src/services/spec-head.ts
|
|
22064
22249
|
init_db();
|
|
22065
22250
|
init_src2();
|
|
@@ -22110,19 +22295,6 @@ async function liveSpecs(filter = {}) {
|
|
|
22110
22295
|
|
|
22111
22296
|
// src/routes/specs.ts
|
|
22112
22297
|
var branchUnknown = async (repoDir, branch) => !(await branchFromCandidates(repoDir)).includes(branch);
|
|
22113
|
-
function deriveSpecFields(source, payload, manualPriority) {
|
|
22114
|
-
if (source === "goal") {
|
|
22115
|
-
const pick2 = (v) => typeof v === "string" ? v.trim() : "";
|
|
22116
|
-
return {
|
|
22117
|
-
priority: manualPriority,
|
|
22118
|
-
objective: pick2(payload?.goal) || pick2(payload?.done) || "\u2014 goal belum diisi."
|
|
22119
|
-
};
|
|
22120
|
-
}
|
|
22121
|
-
const isQa = source === "qa";
|
|
22122
|
-
const priority = isQa && payload && "severity" in payload ? payload.severity === "minor" ? "sedang" : "tinggi" : manualPriority;
|
|
22123
|
-
const objective = isQa && payload && "actual" in payload ? payload.actual || payload.steps || "\u2014 audit untuk menelusuri akar masalah." : payload && "outcome" in payload ? payload.outcome || payload.context || "\u2014 brainstorm untuk memperjelas objective." : "";
|
|
22124
|
-
return { priority, objective };
|
|
22125
|
-
}
|
|
22126
22298
|
function filterSpecs(specs, f) {
|
|
22127
22299
|
const needle = (f.q ?? "").trim().toLowerCase();
|
|
22128
22300
|
const from = dayStart(f.from);
|
|
@@ -22275,6 +22447,40 @@ ${item.context}` : item.context;
|
|
|
22275
22447
|
await notifySynced("spec", id);
|
|
22276
22448
|
return updated;
|
|
22277
22449
|
});
|
|
22450
|
+
app2.post("/specs/:id/source", async (req, reply) => {
|
|
22451
|
+
const { id } = req.params;
|
|
22452
|
+
const parsed = zChangeSpecSource.safeParse(req.body);
|
|
22453
|
+
if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
|
|
22454
|
+
const spec = await prisma.spec.findUnique({ where: { id } });
|
|
22455
|
+
if (!spec) return reply.code(404).send({ error: "not found" });
|
|
22456
|
+
const to = parsed.data.source;
|
|
22457
|
+
if (to === spec.source) return reply.code(400).send({ error: "source tak berubah" });
|
|
22458
|
+
const gate = checkSourceChange(spec, to, parsed.data.payload);
|
|
22459
|
+
if (!gate.ok) return reply.code(gate.code).send({ error: gate.error });
|
|
22460
|
+
const by = req.user?.email ?? "system";
|
|
22461
|
+
const history = appendSourceHistory(
|
|
22462
|
+
spec.sourceHistory,
|
|
22463
|
+
sourceChangeEntry(spec, to, by, /* @__PURE__ */ new Date())
|
|
22464
|
+
);
|
|
22465
|
+
const { priority, objective } = deriveSpecFields(
|
|
22466
|
+
to,
|
|
22467
|
+
gate.payload,
|
|
22468
|
+
gate.payload.priority ?? spec.priority
|
|
22469
|
+
);
|
|
22470
|
+
const updated = await prisma.spec.update({
|
|
22471
|
+
where: { id },
|
|
22472
|
+
data: {
|
|
22473
|
+
source: to,
|
|
22474
|
+
payload: gate.payload,
|
|
22475
|
+
priority,
|
|
22476
|
+
objective,
|
|
22477
|
+
sourceHistory: history
|
|
22478
|
+
}
|
|
22479
|
+
});
|
|
22480
|
+
await recordSourceChange(spec.id, spec.projectId, spec.title, spec.source, to, history.length);
|
|
22481
|
+
await notifySynced("spec", id);
|
|
22482
|
+
return updated;
|
|
22483
|
+
});
|
|
22278
22484
|
app2.get("/specs/:id/docs", async (req) => ({ files: await listSpecDocs(req.params.id) }));
|
|
22279
22485
|
app2.get("/specs/:id/docs/*", async (req, reply) => {
|
|
22280
22486
|
const { id } = req.params;
|