jioogen 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +890 -96
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -363,9 +363,9 @@ var require_utils = __commonJS({
|
|
|
363
363
|
fpath.split(self.sep).forEach(function(name) {
|
|
364
364
|
if (!name || name.substr(-1, 1) === ":") return;
|
|
365
365
|
resolvedPath += self.sep + name;
|
|
366
|
-
var
|
|
366
|
+
var stat5;
|
|
367
367
|
try {
|
|
368
|
-
|
|
368
|
+
stat5 = self.fs.statSync(resolvedPath);
|
|
369
369
|
} catch (e) {
|
|
370
370
|
if (e.message && e.message.startsWith("ENOENT")) {
|
|
371
371
|
self.fs.mkdirSync(resolvedPath);
|
|
@@ -373,7 +373,7 @@ var require_utils = __commonJS({
|
|
|
373
373
|
throw e;
|
|
374
374
|
}
|
|
375
375
|
}
|
|
376
|
-
if (
|
|
376
|
+
if (stat5 && stat5.isFile()) throw Errors.FILE_IN_THE_WAY(`"${resolvedPath}"`);
|
|
377
377
|
});
|
|
378
378
|
}
|
|
379
379
|
mkdirSync(folder);
|
|
@@ -382,8 +382,8 @@ var require_utils = __commonJS({
|
|
|
382
382
|
const self = this;
|
|
383
383
|
if (self.fs.existsSync(path)) {
|
|
384
384
|
if (!overwrite) return false;
|
|
385
|
-
var
|
|
386
|
-
if (
|
|
385
|
+
var stat5 = self.fs.statSync(path);
|
|
386
|
+
if (stat5.isDirectory()) {
|
|
387
387
|
return false;
|
|
388
388
|
}
|
|
389
389
|
}
|
|
@@ -416,8 +416,8 @@ var require_utils = __commonJS({
|
|
|
416
416
|
const self = this;
|
|
417
417
|
self.fs.exists(path, function(exist) {
|
|
418
418
|
if (exist && !overwrite) return callback(false);
|
|
419
|
-
self.fs.stat(path, function(err,
|
|
420
|
-
if (exist &&
|
|
419
|
+
self.fs.stat(path, function(err, stat5) {
|
|
420
|
+
if (exist && stat5 && stat5.isDirectory()) {
|
|
421
421
|
return callback(false);
|
|
422
422
|
}
|
|
423
423
|
var folder = pth.dirname(path);
|
|
@@ -469,13 +469,13 @@ var require_utils = __commonJS({
|
|
|
469
469
|
for (const part of rel.split(pth.sep)) {
|
|
470
470
|
if (!part || part === ".") continue;
|
|
471
471
|
cur = pth.join(cur, part);
|
|
472
|
-
let
|
|
472
|
+
let stat5;
|
|
473
473
|
try {
|
|
474
|
-
|
|
474
|
+
stat5 = self.fs.lstatSync(cur);
|
|
475
475
|
} catch (e) {
|
|
476
476
|
break;
|
|
477
477
|
}
|
|
478
|
-
if (
|
|
478
|
+
if (stat5.isSymbolicLink()) throw Errors.FILE_IN_THE_WAY(`"${cur}"`);
|
|
479
479
|
}
|
|
480
480
|
};
|
|
481
481
|
Utils.prototype.findFiles = function(path) {
|
|
@@ -502,11 +502,11 @@ var require_utils = __commonJS({
|
|
|
502
502
|
self.fs.readdirSync(dir).forEach(function(file) {
|
|
503
503
|
const path2 = pth.join(dir, file);
|
|
504
504
|
if (escapesRoot(path2)) return;
|
|
505
|
-
const
|
|
505
|
+
const stat5 = self.fs.statSync(path2);
|
|
506
506
|
if (!pattern || pattern.test(path2)) {
|
|
507
|
-
files.push(pth.normalize(path2) + (
|
|
507
|
+
files.push(pth.normalize(path2) + (stat5.isDirectory() ? self.sep : ""));
|
|
508
508
|
}
|
|
509
|
-
if (
|
|
509
|
+
if (stat5.isDirectory() && recursive) {
|
|
510
510
|
const realDir = self.fs.realpathSync(path2);
|
|
511
511
|
if (!visited.has(realDir)) {
|
|
512
512
|
visited.add(realDir);
|
|
@@ -553,14 +553,14 @@ var require_utils = __commonJS({
|
|
|
553
553
|
if (!--pending) done();
|
|
554
554
|
return;
|
|
555
555
|
}
|
|
556
|
-
self.fs.stat(file, function(err3,
|
|
556
|
+
self.fs.stat(file, function(err3, stat5) {
|
|
557
557
|
if (err3) return done(err3);
|
|
558
|
-
if (!
|
|
558
|
+
if (!stat5) {
|
|
559
559
|
if (!--pending) done();
|
|
560
560
|
return;
|
|
561
561
|
}
|
|
562
|
-
results.push(pth.normalize(file) + (
|
|
563
|
-
if (!
|
|
562
|
+
results.push(pth.normalize(file) + (stat5.isDirectory() ? self.sep : ""));
|
|
563
|
+
if (!stat5.isDirectory()) {
|
|
564
564
|
if (!--pending) done();
|
|
565
565
|
return;
|
|
566
566
|
}
|
|
@@ -2124,8 +2124,8 @@ var require_adm_zip = __commonJS({
|
|
|
2124
2124
|
return null;
|
|
2125
2125
|
}
|
|
2126
2126
|
function fixPath(zipPath) {
|
|
2127
|
-
const { join:
|
|
2128
|
-
return
|
|
2127
|
+
const { join: join8, normalize, sep: sep2 } = pth.posix;
|
|
2128
|
+
return join8(pth.isAbsolute(zipPath) ? "/" : ".", normalize(sep2 + zipPath.split("\\").join(sep2) + sep2));
|
|
2129
2129
|
}
|
|
2130
2130
|
function filenameFilter(filterfn) {
|
|
2131
2131
|
if (filterfn instanceof RegExp) {
|
|
@@ -2521,10 +2521,10 @@ var require_adm_zip = __commonJS({
|
|
|
2521
2521
|
* @param {function|string} [props.namefix] - optional function to help fix filename
|
|
2522
2522
|
*/
|
|
2523
2523
|
addLocalFolderPromise: function(localPath, props) {
|
|
2524
|
-
return new Promise((
|
|
2524
|
+
return new Promise((resolve6, reject) => {
|
|
2525
2525
|
this.addLocalFolderAsync2(Object.assign({ localPath }, props), (err, done) => {
|
|
2526
2526
|
if (err) return reject(err);
|
|
2527
|
-
if (done)
|
|
2527
|
+
if (done) resolve6(this);
|
|
2528
2528
|
});
|
|
2529
2529
|
});
|
|
2530
2530
|
},
|
|
@@ -2716,12 +2716,12 @@ var require_adm_zip = __commonJS({
|
|
|
2716
2716
|
keepOriginalPermission = get_Bool(false, keepOriginalPermission);
|
|
2717
2717
|
overwrite = get_Bool(false, overwrite);
|
|
2718
2718
|
if (!callback) {
|
|
2719
|
-
return new Promise((
|
|
2719
|
+
return new Promise((resolve6, reject) => {
|
|
2720
2720
|
this.extractAllToAsync(targetPath, overwrite, keepOriginalPermission, function(err) {
|
|
2721
2721
|
if (err) {
|
|
2722
2722
|
reject(err);
|
|
2723
2723
|
} else {
|
|
2724
|
-
|
|
2724
|
+
resolve6(this);
|
|
2725
2725
|
}
|
|
2726
2726
|
});
|
|
2727
2727
|
});
|
|
@@ -2836,11 +2836,11 @@ var require_adm_zip = __commonJS({
|
|
|
2836
2836
|
*/
|
|
2837
2837
|
writeZipPromise: function(targetFileName, props) {
|
|
2838
2838
|
const { overwrite, perm } = Object.assign({ overwrite: true }, props);
|
|
2839
|
-
return new Promise((
|
|
2839
|
+
return new Promise((resolve6, reject) => {
|
|
2840
2840
|
if (!targetFileName && opts.filename) targetFileName = opts.filename;
|
|
2841
2841
|
if (!targetFileName) reject("ADM-ZIP: ZIP File Name Missing");
|
|
2842
2842
|
this.toBufferPromise().then((zipData) => {
|
|
2843
|
-
const ret = (done) => done ?
|
|
2843
|
+
const ret = (done) => done ? resolve6(done) : reject("ADM-ZIP: Wasn't able to write zip file");
|
|
2844
2844
|
filetools.writeFileToAsync(targetFileName, zipData, overwrite, perm, ret);
|
|
2845
2845
|
}, reject);
|
|
2846
2846
|
});
|
|
@@ -2849,8 +2849,8 @@ var require_adm_zip = __commonJS({
|
|
|
2849
2849
|
* @returns {Promise<Buffer>} A promise to the Buffer.
|
|
2850
2850
|
*/
|
|
2851
2851
|
toBufferPromise: function() {
|
|
2852
|
-
return new Promise((
|
|
2853
|
-
_zip.toAsyncBuffer(
|
|
2852
|
+
return new Promise((resolve6, reject) => {
|
|
2853
|
+
_zip.toAsyncBuffer(resolve6, reject);
|
|
2854
2854
|
});
|
|
2855
2855
|
},
|
|
2856
2856
|
/**
|
|
@@ -2978,8 +2978,8 @@ var ApiClient = class {
|
|
|
2978
2978
|
};
|
|
2979
2979
|
|
|
2980
2980
|
// src/commands.ts
|
|
2981
|
-
import { readdir as
|
|
2982
|
-
import { join as
|
|
2981
|
+
import { readdir as readdir5, readFile as readFile7 } from "node:fs/promises";
|
|
2982
|
+
import { join as join7, resolve as resolve5 } from "node:path";
|
|
2983
2983
|
|
|
2984
2984
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
|
|
2985
2985
|
var external_exports = {};
|
|
@@ -3472,15 +3472,15 @@ var makeIssue = (params) => {
|
|
|
3472
3472
|
message: issueData.message
|
|
3473
3473
|
};
|
|
3474
3474
|
}
|
|
3475
|
-
let
|
|
3475
|
+
let errorMessage2 = "";
|
|
3476
3476
|
const maps = errorMaps.filter((m) => !!m).slice().reverse();
|
|
3477
3477
|
for (const map of maps) {
|
|
3478
|
-
|
|
3478
|
+
errorMessage2 = map(fullIssue, { data, defaultError: errorMessage2 }).message;
|
|
3479
3479
|
}
|
|
3480
3480
|
return {
|
|
3481
3481
|
...issueData,
|
|
3482
3482
|
path: fullPath,
|
|
3483
|
-
message:
|
|
3483
|
+
message: errorMessage2
|
|
3484
3484
|
};
|
|
3485
3485
|
};
|
|
3486
3486
|
var EMPTY_PATH = [];
|
|
@@ -8562,19 +8562,9 @@ function isMissingFile(error) {
|
|
|
8562
8562
|
return isRecord(error) && error.code === "ENOENT";
|
|
8563
8563
|
}
|
|
8564
8564
|
|
|
8565
|
-
// src/
|
|
8566
|
-
|
|
8567
|
-
};
|
|
8568
|
-
|
|
8569
|
-
// src/output.ts
|
|
8570
|
-
function formatTable(headers, rows) {
|
|
8571
|
-
const widths = headers.map(
|
|
8572
|
-
(header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length))
|
|
8573
|
-
);
|
|
8574
|
-
const render = (cells) => cells.map((cell, index) => cell.padEnd(widths[index] ?? 0)).join(" ").trimEnd();
|
|
8575
|
-
const divider = widths.map((width) => "-".repeat(width)).join(" ");
|
|
8576
|
-
return [render(headers), divider, ...rows.map(render)].join("\n");
|
|
8577
|
-
}
|
|
8565
|
+
// src/doctor.ts
|
|
8566
|
+
import { readdir as readdir3, readFile as readFile5, rm as rm4, stat as stat3 } from "node:fs/promises";
|
|
8567
|
+
import { join as join5 } from "node:path";
|
|
8578
8568
|
|
|
8579
8569
|
// src/sidecar.ts
|
|
8580
8570
|
import { randomUUID } from "node:crypto";
|
|
@@ -8762,7 +8752,7 @@ async function removeStaleLock(target) {
|
|
|
8762
8752
|
}
|
|
8763
8753
|
}
|
|
8764
8754
|
function delay(ms) {
|
|
8765
|
-
return new Promise((
|
|
8755
|
+
return new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
8766
8756
|
}
|
|
8767
8757
|
|
|
8768
8758
|
// src/skills.ts
|
|
@@ -8837,8 +8827,11 @@ var JIOOGEN_AGENTS_MD = `${MARKER}
|
|
|
8837
8827
|
|
|
8838
8828
|
## 5. Checkpoint
|
|
8839
8829
|
Berhenti dan minta konfirmasi user bila task berikutnya berpindah layer, nomor
|
|
8840
|
-
\`phase\` naik,
|
|
8841
|
-
|
|
8830
|
+
\`phase\` naik, task bertanda \`checkpoint: true\`, atau task tergolong berisiko
|
|
8831
|
+
(migrasi/schema/auth/permission/payment/webhook/secret/deploy/infra). Jangan lanjut
|
|
8832
|
+
sebelum user memutuskan. Setelah user memutuskan, jalankan salah satu:
|
|
8833
|
+
\`npx jioogen review --approve\`, \`npx jioogen review --request-changes "<teks>"\`,
|
|
8834
|
+
atau \`npx jioogen review --stop\`, lalu \`npx jioogen sync\`.
|
|
8842
8835
|
|
|
8843
8836
|
## 6. Skill eksekusi
|
|
8844
8837
|
Baca skill \`${EXECUTION_SKILL_NAME}\` di awal prompt
|
|
@@ -8916,10 +8909,13 @@ atas ide, refactor, perbaikan, atau fitur lain.
|
|
|
8916
8909
|
Berhenti dan minta konfirmasi user bila:
|
|
8917
8910
|
- task berikutnya berpindah \`layer\` (mis. \`frontend_mock\` \u2192 \`backend_api\`/\`database\`),
|
|
8918
8911
|
- nomor \`phase\` naik,
|
|
8919
|
-
- task bertanda \`checkpoint: true
|
|
8912
|
+
- task bertanda \`checkpoint: true\`,
|
|
8913
|
+
- task tergolong berisiko (migrasi/schema/auth/permission/payment/webhook/secret/deploy/infra).
|
|
8920
8914
|
|
|
8921
|
-
Jangan lanjut sebelum user
|
|
8922
|
-
|
|
8915
|
+
Jangan lanjut sebelum user memutuskan. Set \`current.awaitingCheckpoint = true\` di
|
|
8916
|
+
sidecar saat berhenti, lalu setelah user memutuskan jalankan
|
|
8917
|
+
\`npx jioogen review --approve\` / \`--request-changes "<teks>"\` / \`--stop\`,
|
|
8918
|
+
diikuti \`npx jioogen sync\`.
|
|
8923
8919
|
|
|
8924
8920
|
## 6. Larangan ringkas
|
|
8925
8921
|
|
|
@@ -9278,9 +9274,511 @@ function skillsDisabled(options) {
|
|
|
9278
9274
|
return value === "1" || value === "true" || value === "yes";
|
|
9279
9275
|
}
|
|
9280
9276
|
|
|
9277
|
+
// src/doctor.ts
|
|
9278
|
+
var REQUIRED_NODE_MAJOR = 22;
|
|
9279
|
+
var LOCK_STALE_MS2 = 1e4;
|
|
9280
|
+
var AGENTS_MARKER = "<!-- jioogen-execution-skill:";
|
|
9281
|
+
var BUNDLE_SCAFFOLD_CHECKS = /* @__PURE__ */ new Set(["agents", "execution-skill", "skill-packs"]);
|
|
9282
|
+
function makeCheck(id, status, detail, fixable) {
|
|
9283
|
+
return fixable === true ? { id, status, detail, fixable: true } : { id, status, detail };
|
|
9284
|
+
}
|
|
9285
|
+
function errorMessage(error) {
|
|
9286
|
+
return error instanceof Error ? error.message : String(error);
|
|
9287
|
+
}
|
|
9288
|
+
function isMissingFile4(error) {
|
|
9289
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
9290
|
+
}
|
|
9291
|
+
function resolveDeps(deps) {
|
|
9292
|
+
return {
|
|
9293
|
+
readCredentials: deps?.readCredentials ?? readCredentials,
|
|
9294
|
+
fetchMe: deps?.fetchMe ?? (async (apiUrl, cookie) => {
|
|
9295
|
+
const client = new ApiClient(apiUrl, cookie);
|
|
9296
|
+
return client.getJson(ENDPOINTS.me);
|
|
9297
|
+
}),
|
|
9298
|
+
fetchExecution: deps?.fetchExecution ?? (async (apiUrl, cookie, projectId) => {
|
|
9299
|
+
const client = new ApiClient(apiUrl, cookie);
|
|
9300
|
+
return client.getJson(projectPath(projectId, "/execution"));
|
|
9301
|
+
}),
|
|
9302
|
+
installSkills: deps?.installSkills ?? ((bundleDir) => installAgentSkills(bundleDir))
|
|
9303
|
+
};
|
|
9304
|
+
}
|
|
9305
|
+
function checkNode() {
|
|
9306
|
+
const raw = process.versions.node;
|
|
9307
|
+
const major = Number.parseInt(raw.split(".")[0] ?? "0", 10);
|
|
9308
|
+
if (Number.isFinite(major) && major >= REQUIRED_NODE_MAJOR) {
|
|
9309
|
+
return makeCheck("node", "pass", `Node.js v${raw} memenuhi syarat >=${REQUIRED_NODE_MAJOR}.`);
|
|
9310
|
+
}
|
|
9311
|
+
return makeCheck(
|
|
9312
|
+
"node",
|
|
9313
|
+
"fail",
|
|
9314
|
+
`Node.js v${raw} terlalu lama; butuh >=${REQUIRED_NODE_MAJOR}. Perbarui Node.js lalu ulangi.`
|
|
9315
|
+
);
|
|
9316
|
+
}
|
|
9317
|
+
function checkCredentials(credentials, error) {
|
|
9318
|
+
if (error !== null) {
|
|
9319
|
+
return makeCheck(
|
|
9320
|
+
"credentials",
|
|
9321
|
+
"fail",
|
|
9322
|
+
`Credential tidak bisa dibaca: ${error}. Periksa izin file credential.`
|
|
9323
|
+
);
|
|
9324
|
+
}
|
|
9325
|
+
if (credentials === null) {
|
|
9326
|
+
return makeCheck(
|
|
9327
|
+
"credentials",
|
|
9328
|
+
"fail",
|
|
9329
|
+
"Belum ada credential. Jalankan `jioogen login --email ... --password-stdin`."
|
|
9330
|
+
);
|
|
9331
|
+
}
|
|
9332
|
+
return makeCheck("credentials", "pass", `Credential tersimpan untuk ${credentials.apiUrl}.`);
|
|
9333
|
+
}
|
|
9334
|
+
function checkApi(credentials, email, error) {
|
|
9335
|
+
if (credentials === null) {
|
|
9336
|
+
return makeCheck(
|
|
9337
|
+
"api",
|
|
9338
|
+
"warn",
|
|
9339
|
+
"Dilewati: credential belum ada. Jalankan `jioogen login` lalu ulangi."
|
|
9340
|
+
);
|
|
9341
|
+
}
|
|
9342
|
+
if (error !== null) {
|
|
9343
|
+
const message = errorMessage(error);
|
|
9344
|
+
if (error instanceof ApiError && (error.status === 401 || error.status === 403)) {
|
|
9345
|
+
return makeCheck(
|
|
9346
|
+
"api",
|
|
9347
|
+
"fail",
|
|
9348
|
+
`Sesi ditolak (${error.status}). Jalankan \`jioogen login\` lagi.`
|
|
9349
|
+
);
|
|
9350
|
+
}
|
|
9351
|
+
return makeCheck(
|
|
9352
|
+
"api",
|
|
9353
|
+
"warn",
|
|
9354
|
+
`Tidak dapat menghubungi ${credentials.apiUrl}: ${message}. Cek koneksi lalu ulangi.`
|
|
9355
|
+
);
|
|
9356
|
+
}
|
|
9357
|
+
return makeCheck(
|
|
9358
|
+
"api",
|
|
9359
|
+
"pass",
|
|
9360
|
+
`API ${credentials.apiUrl} terjangkau; login sebagai ${email ?? "-"}.`
|
|
9361
|
+
);
|
|
9362
|
+
}
|
|
9363
|
+
function checkBinding(requestedProjectId, link, error) {
|
|
9364
|
+
if (error !== null) {
|
|
9365
|
+
return makeCheck(
|
|
9366
|
+
"binding",
|
|
9367
|
+
"fail",
|
|
9368
|
+
`Link project tidak valid: ${error}. Perbaiki atau hapus .jioogen/project.json.`
|
|
9369
|
+
);
|
|
9370
|
+
}
|
|
9371
|
+
if (link === null) {
|
|
9372
|
+
return makeCheck(
|
|
9373
|
+
"binding",
|
|
9374
|
+
"fail",
|
|
9375
|
+
"Belum terikat ke project. Jalankan `jioogen pull <projectId>` di folder bundle."
|
|
9376
|
+
);
|
|
9377
|
+
}
|
|
9378
|
+
if (requestedProjectId !== void 0 && link.projectId !== requestedProjectId) {
|
|
9379
|
+
return makeCheck(
|
|
9380
|
+
"binding",
|
|
9381
|
+
"fail",
|
|
9382
|
+
`Bundle terikat ke project ${link.projectId}, bukan ${requestedProjectId}. Jalankan \`jioogen pull ${requestedProjectId}\`.`
|
|
9383
|
+
);
|
|
9384
|
+
}
|
|
9385
|
+
return makeCheck("binding", "pass", `Bundle terikat ke project ${link.projectId}.`);
|
|
9386
|
+
}
|
|
9387
|
+
async function checkProject(deps, credentials, projectId) {
|
|
9388
|
+
if (credentials === null) {
|
|
9389
|
+
return makeCheck("project", "warn", "Dilewati: credential belum ada.");
|
|
9390
|
+
}
|
|
9391
|
+
if (projectId === void 0 || projectId.length === 0) {
|
|
9392
|
+
return makeCheck(
|
|
9393
|
+
"project",
|
|
9394
|
+
"warn",
|
|
9395
|
+
"Dilewati: project belum diketahui. Jalankan `jioogen pull <projectId>`."
|
|
9396
|
+
);
|
|
9397
|
+
}
|
|
9398
|
+
try {
|
|
9399
|
+
await deps.fetchExecution(credentials.apiUrl, credentials.cookie, projectId);
|
|
9400
|
+
return makeCheck("project", "pass", `Project ${projectId} dapat diakses akun ini.`);
|
|
9401
|
+
} catch (error) {
|
|
9402
|
+
if (error instanceof ApiError) {
|
|
9403
|
+
if (error.status === 404) {
|
|
9404
|
+
return makeCheck(
|
|
9405
|
+
"project",
|
|
9406
|
+
"fail",
|
|
9407
|
+
`Project ${projectId} tidak ditemukan atau bukan milik akun ini.`
|
|
9408
|
+
);
|
|
9409
|
+
}
|
|
9410
|
+
if (error.status === 401 || error.status === 403) {
|
|
9411
|
+
return makeCheck(
|
|
9412
|
+
"project",
|
|
9413
|
+
"fail",
|
|
9414
|
+
`Akses project ditolak (${error.status}). Jalankan \`jioogen login\` lagi.`
|
|
9415
|
+
);
|
|
9416
|
+
}
|
|
9417
|
+
}
|
|
9418
|
+
return makeCheck(
|
|
9419
|
+
"project",
|
|
9420
|
+
"warn",
|
|
9421
|
+
`Gagal memeriksa project ${projectId}: ${errorMessage(error)}. Coba lagi saat API stabil.`
|
|
9422
|
+
);
|
|
9423
|
+
}
|
|
9424
|
+
}
|
|
9425
|
+
async function isFile2(path) {
|
|
9426
|
+
try {
|
|
9427
|
+
return (await stat3(path)).isFile();
|
|
9428
|
+
} catch {
|
|
9429
|
+
return false;
|
|
9430
|
+
}
|
|
9431
|
+
}
|
|
9432
|
+
async function isDirectory2(path) {
|
|
9433
|
+
try {
|
|
9434
|
+
return (await stat3(path)).isDirectory();
|
|
9435
|
+
} catch {
|
|
9436
|
+
return false;
|
|
9437
|
+
}
|
|
9438
|
+
}
|
|
9439
|
+
async function listMarkdownFiles(directory) {
|
|
9440
|
+
try {
|
|
9441
|
+
const entries = await readdir3(directory, { withFileTypes: true });
|
|
9442
|
+
return entries.filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith(".md")).map((entry) => entry.name);
|
|
9443
|
+
} catch {
|
|
9444
|
+
return [];
|
|
9445
|
+
}
|
|
9446
|
+
}
|
|
9447
|
+
async function checkBundle(bundleDir) {
|
|
9448
|
+
const missing = [];
|
|
9449
|
+
if (!await isFile2(join5(bundleDir, "PRD.md"))) missing.push("PRD.md");
|
|
9450
|
+
const specsDir = join5(bundleDir, "specs");
|
|
9451
|
+
if (!await isDirectory2(specsDir)) {
|
|
9452
|
+
missing.push("specs/");
|
|
9453
|
+
} else {
|
|
9454
|
+
const content = (await listMarkdownFiles(specsDir)).filter(
|
|
9455
|
+
(name) => name.toLowerCase() !== "readme.md"
|
|
9456
|
+
);
|
|
9457
|
+
if (content.length === 0) missing.push("specs/*.md (konten)");
|
|
9458
|
+
}
|
|
9459
|
+
const tasksDir = join5(bundleDir, "tasks");
|
|
9460
|
+
if (!await isDirectory2(tasksDir)) {
|
|
9461
|
+
missing.push("tasks/");
|
|
9462
|
+
} else {
|
|
9463
|
+
const content = (await listMarkdownFiles(tasksDir)).filter(
|
|
9464
|
+
(name) => name.toLowerCase() !== "readme.md"
|
|
9465
|
+
);
|
|
9466
|
+
if (content.length === 0) missing.push("tasks/*.md (task)");
|
|
9467
|
+
}
|
|
9468
|
+
if (missing.length > 0) {
|
|
9469
|
+
return makeCheck(
|
|
9470
|
+
"bundle",
|
|
9471
|
+
"fail",
|
|
9472
|
+
`Bundle tidak lengkap: ${missing.join(", ")}. Jalankan \`jioogen pull <projectId>\`.`
|
|
9473
|
+
);
|
|
9474
|
+
}
|
|
9475
|
+
try {
|
|
9476
|
+
const report = await inspectBundle(bundleDir);
|
|
9477
|
+
if (!report.ok) {
|
|
9478
|
+
const codes = Object.entries(report.issueCounts).map(([code, count]) => `${code}\xD7${count}`).join(", ");
|
|
9479
|
+
return makeCheck(
|
|
9480
|
+
"bundle",
|
|
9481
|
+
"fail",
|
|
9482
|
+
`Bundle gagal gate (${report.issueCount} isu: ${codes}). Perbaiki PRD/specs/tasks.`
|
|
9483
|
+
);
|
|
9484
|
+
}
|
|
9485
|
+
return makeCheck("bundle", "pass", `Bundle lengkap dan lulus gate (${report.files} file).`);
|
|
9486
|
+
} catch (error) {
|
|
9487
|
+
return makeCheck("bundle", "fail", `Gagal memeriksa bundle: ${errorMessage(error)}.`);
|
|
9488
|
+
}
|
|
9489
|
+
}
|
|
9490
|
+
async function checkSidecar(bundleDir) {
|
|
9491
|
+
try {
|
|
9492
|
+
const state = await readState(bundleDir);
|
|
9493
|
+
if (state === null) {
|
|
9494
|
+
return makeCheck("sidecar", "warn", "Sidecar belum ada. Jalankan `jioogen task next`.");
|
|
9495
|
+
}
|
|
9496
|
+
return makeCheck("sidecar", "pass", `Sidecar valid dengan ${state.tasks.length} task.`);
|
|
9497
|
+
} catch (error) {
|
|
9498
|
+
return makeCheck(
|
|
9499
|
+
"sidecar",
|
|
9500
|
+
"fail",
|
|
9501
|
+
`Sidecar tidak valid: ${errorMessage(error)}. Hapus tasks/.execution/state.json lalu jalankan \`jioogen task next\`.`
|
|
9502
|
+
);
|
|
9503
|
+
}
|
|
9504
|
+
}
|
|
9505
|
+
async function checkLock(bundleDir) {
|
|
9506
|
+
const target = lockPath(bundleDir);
|
|
9507
|
+
let info;
|
|
9508
|
+
try {
|
|
9509
|
+
info = await stat3(target);
|
|
9510
|
+
} catch (error) {
|
|
9511
|
+
if (isMissingFile4(error)) {
|
|
9512
|
+
return makeCheck("lock", "pass", "Tidak ada lock; sidecar bebas ditulis.");
|
|
9513
|
+
}
|
|
9514
|
+
return makeCheck("lock", "warn", `Tidak bisa memeriksa lock: ${errorMessage(error)}.`);
|
|
9515
|
+
}
|
|
9516
|
+
const ageMs = Date.now() - info.mtimeMs;
|
|
9517
|
+
const ageSeconds = Math.max(0, Math.round(ageMs / 1e3));
|
|
9518
|
+
if (ageMs > LOCK_STALE_MS2) {
|
|
9519
|
+
return makeCheck(
|
|
9520
|
+
"lock",
|
|
9521
|
+
"warn",
|
|
9522
|
+
`Lock basi (${ageSeconds} detik) tertinggal. Jalankan \`jioogen doctor --fix\` untuk menghapus.`,
|
|
9523
|
+
true
|
|
9524
|
+
);
|
|
9525
|
+
}
|
|
9526
|
+
return makeCheck(
|
|
9527
|
+
"lock",
|
|
9528
|
+
"warn",
|
|
9529
|
+
`Lock masih aktif (${ageSeconds} detik); proses lain mungkin menulis. Tunggu atau hapus bila proses sudah mati.`
|
|
9530
|
+
);
|
|
9531
|
+
}
|
|
9532
|
+
async function checkAgents(bundleDir) {
|
|
9533
|
+
let content;
|
|
9534
|
+
try {
|
|
9535
|
+
content = await readFile5(join5(bundleDir, "AGENTS.md"), "utf8");
|
|
9536
|
+
} catch (error) {
|
|
9537
|
+
if (isMissingFile4(error)) {
|
|
9538
|
+
return makeCheck(
|
|
9539
|
+
"agents",
|
|
9540
|
+
"fail",
|
|
9541
|
+
"AGENTS.md belum ada. Jalankan `jioogen doctor --fix` untuk memasang kontrak eksekusi.",
|
|
9542
|
+
true
|
|
9543
|
+
);
|
|
9544
|
+
}
|
|
9545
|
+
return makeCheck(
|
|
9546
|
+
"agents",
|
|
9547
|
+
"fail",
|
|
9548
|
+
`AGENTS.md tidak bisa dibaca: ${errorMessage(error)}.`,
|
|
9549
|
+
true
|
|
9550
|
+
);
|
|
9551
|
+
}
|
|
9552
|
+
if (!content.includes(AGENTS_MARKER)) {
|
|
9553
|
+
return makeCheck(
|
|
9554
|
+
"agents",
|
|
9555
|
+
"fail",
|
|
9556
|
+
"AGENTS.md ada tetapi tanpa marker JiooGen. Jalankan `jioogen doctor --fix` untuk memasang ulang.",
|
|
9557
|
+
true
|
|
9558
|
+
);
|
|
9559
|
+
}
|
|
9560
|
+
return makeCheck("agents", "pass", "AGENTS.md terpasang dengan marker eksekusi JiooGen.");
|
|
9561
|
+
}
|
|
9562
|
+
async function checkExecutionSkill(bundleDir) {
|
|
9563
|
+
const missing = [];
|
|
9564
|
+
for (const dir of AGENT_SKILL_DIRS) {
|
|
9565
|
+
const target = join5(bundleDir, dir, EXECUTION_SKILL_NAME, "SKILL.md");
|
|
9566
|
+
if (!await isFile2(target)) missing.push(dir);
|
|
9567
|
+
}
|
|
9568
|
+
if (missing.length > 0) {
|
|
9569
|
+
return makeCheck(
|
|
9570
|
+
"execution-skill",
|
|
9571
|
+
"fail",
|
|
9572
|
+
`Skill ${EXECUTION_SKILL_NAME} hilang di: ${missing.join(", ")}. Jalankan \`jioogen doctor --fix\`.`,
|
|
9573
|
+
true
|
|
9574
|
+
);
|
|
9575
|
+
}
|
|
9576
|
+
return makeCheck(
|
|
9577
|
+
"execution-skill",
|
|
9578
|
+
"pass",
|
|
9579
|
+
`Skill ${EXECUTION_SKILL_NAME} ada di ${AGENT_SKILL_DIRS.length} direktori harness.`
|
|
9580
|
+
);
|
|
9581
|
+
}
|
|
9582
|
+
async function checkSkillPacks(bundleDir) {
|
|
9583
|
+
let manifest;
|
|
9584
|
+
try {
|
|
9585
|
+
manifest = await readHarnessManifest(bundleDir);
|
|
9586
|
+
} catch (error) {
|
|
9587
|
+
return makeCheck(
|
|
9588
|
+
"skill-packs",
|
|
9589
|
+
"warn",
|
|
9590
|
+
`harness.json tidak bisa dibaca: ${errorMessage(error)}. Jalankan \`jioogen doctor --fix\`.`,
|
|
9591
|
+
true
|
|
9592
|
+
);
|
|
9593
|
+
}
|
|
9594
|
+
if (manifest === null) {
|
|
9595
|
+
return makeCheck(
|
|
9596
|
+
"skill-packs",
|
|
9597
|
+
"warn",
|
|
9598
|
+
".jioogen/harness.json belum ada atau tidak valid. Jalankan `jioogen doctor --fix` untuk menulis ulang.",
|
|
9599
|
+
true
|
|
9600
|
+
);
|
|
9601
|
+
}
|
|
9602
|
+
if (manifest.packs.length === 0) {
|
|
9603
|
+
return makeCheck("skill-packs", "pass", "Tidak ada skill pack yang dikelola.");
|
|
9604
|
+
}
|
|
9605
|
+
const issues = [];
|
|
9606
|
+
for (const pack of manifest.packs) {
|
|
9607
|
+
for (const name of pack.missing) {
|
|
9608
|
+
issues.push(`${pack.name}: ${name} tidak tersedia saat instalasi`);
|
|
9609
|
+
}
|
|
9610
|
+
for (const skill of pack.skills) {
|
|
9611
|
+
let matched = false;
|
|
9612
|
+
for (const dir of AGENT_SKILL_DIRS) {
|
|
9613
|
+
const target = join5(bundleDir, dir, skill.name);
|
|
9614
|
+
if (!await isFile2(join5(target, "SKILL.md"))) continue;
|
|
9615
|
+
try {
|
|
9616
|
+
if (await hashSkillDirectory(target) === skill.sha256) {
|
|
9617
|
+
matched = true;
|
|
9618
|
+
break;
|
|
9619
|
+
}
|
|
9620
|
+
} catch {
|
|
9621
|
+
}
|
|
9622
|
+
}
|
|
9623
|
+
if (!matched) issues.push(`${pack.name}: ${skill.name} hilang atau hash tidak cocok`);
|
|
9624
|
+
}
|
|
9625
|
+
}
|
|
9626
|
+
if (issues.length > 0) {
|
|
9627
|
+
return makeCheck(
|
|
9628
|
+
"skill-packs",
|
|
9629
|
+
"warn",
|
|
9630
|
+
`Skill pack bermasalah: ${issues.join("; ")}. Jalankan \`jioogen doctor --fix\`.`,
|
|
9631
|
+
true
|
|
9632
|
+
);
|
|
9633
|
+
}
|
|
9634
|
+
return makeCheck(
|
|
9635
|
+
"skill-packs",
|
|
9636
|
+
"pass",
|
|
9637
|
+
`Semua skill pack cocok (${manifest.packs.length} pack).`
|
|
9638
|
+
);
|
|
9639
|
+
}
|
|
9640
|
+
async function checkDuplicates(bundleDir) {
|
|
9641
|
+
let manifest = null;
|
|
9642
|
+
try {
|
|
9643
|
+
manifest = await readHarnessManifest(bundleDir);
|
|
9644
|
+
} catch {
|
|
9645
|
+
}
|
|
9646
|
+
const managed = /* @__PURE__ */ new Set([EXECUTION_SKILL_NAME]);
|
|
9647
|
+
for (const pack of manifest?.packs ?? []) {
|
|
9648
|
+
for (const skill of pack.skills) managed.add(skill.name);
|
|
9649
|
+
}
|
|
9650
|
+
const dirsByName = /* @__PURE__ */ new Map();
|
|
9651
|
+
for (const dir of AGENT_SKILL_DIRS) {
|
|
9652
|
+
const root = join5(bundleDir, dir);
|
|
9653
|
+
if (!await isDirectory2(root)) continue;
|
|
9654
|
+
let entries;
|
|
9655
|
+
try {
|
|
9656
|
+
entries = await readdir3(root, { withFileTypes: true });
|
|
9657
|
+
} catch {
|
|
9658
|
+
continue;
|
|
9659
|
+
}
|
|
9660
|
+
for (const entry of entries) {
|
|
9661
|
+
if (!entry.isDirectory()) continue;
|
|
9662
|
+
if (managed.has(entry.name)) continue;
|
|
9663
|
+
if (!await isFile2(join5(root, entry.name, "SKILL.md"))) continue;
|
|
9664
|
+
const owners = dirsByName.get(entry.name) ?? [];
|
|
9665
|
+
owners.push(dir);
|
|
9666
|
+
dirsByName.set(entry.name, owners);
|
|
9667
|
+
}
|
|
9668
|
+
}
|
|
9669
|
+
const duplicates = [...dirsByName.entries()].filter(([, dirs]) => dirs.length > 1);
|
|
9670
|
+
if (duplicates.length === 0) {
|
|
9671
|
+
return makeCheck("duplicates", "pass", "Tidak ada skill duplikat antar-harness.");
|
|
9672
|
+
}
|
|
9673
|
+
const detail = duplicates.map(([name, dirs]) => `${name} (${dirs.join(", ")})`).join("; ");
|
|
9674
|
+
return makeCheck(
|
|
9675
|
+
"duplicates",
|
|
9676
|
+
"warn",
|
|
9677
|
+
`Skill yang sama ada di beberapa harness: ${detail}. Pastikan itu disengaja.`
|
|
9678
|
+
);
|
|
9679
|
+
}
|
|
9680
|
+
async function checkHarness(bundleDir) {
|
|
9681
|
+
const detected = [];
|
|
9682
|
+
for (const dir of AGENT_SKILL_DIRS) {
|
|
9683
|
+
if (await isDirectory2(join5(bundleDir, dir))) detected.push(dir);
|
|
9684
|
+
}
|
|
9685
|
+
if (detected.length === 0) {
|
|
9686
|
+
return makeCheck("harness", "pass", "Tidak ada direktori harness terdeteksi.");
|
|
9687
|
+
}
|
|
9688
|
+
return makeCheck("harness", "pass", `Harness terdeteksi: ${detected.join(", ")}.`);
|
|
9689
|
+
}
|
|
9690
|
+
async function runChecks(options, deps) {
|
|
9691
|
+
const checks = [checkNode()];
|
|
9692
|
+
let credentials = null;
|
|
9693
|
+
let credentialsError = null;
|
|
9694
|
+
try {
|
|
9695
|
+
credentials = await deps.readCredentials();
|
|
9696
|
+
} catch (error) {
|
|
9697
|
+
credentialsError = errorMessage(error);
|
|
9698
|
+
}
|
|
9699
|
+
checks.push(checkCredentials(credentials, credentialsError));
|
|
9700
|
+
let email = null;
|
|
9701
|
+
let apiError2 = null;
|
|
9702
|
+
if (credentials !== null) {
|
|
9703
|
+
try {
|
|
9704
|
+
email = (await deps.fetchMe(credentials.apiUrl, credentials.cookie)).email;
|
|
9705
|
+
} catch (error) {
|
|
9706
|
+
apiError2 = error;
|
|
9707
|
+
}
|
|
9708
|
+
}
|
|
9709
|
+
checks.push(checkApi(credentials, email, apiError2));
|
|
9710
|
+
let link = null;
|
|
9711
|
+
let linkError = null;
|
|
9712
|
+
try {
|
|
9713
|
+
link = await findProjectLink(options.bundleDir);
|
|
9714
|
+
} catch (error) {
|
|
9715
|
+
linkError = errorMessage(error);
|
|
9716
|
+
}
|
|
9717
|
+
checks.push(checkBinding(options.projectId, link, linkError));
|
|
9718
|
+
const effectiveProjectId = options.projectId ?? link?.projectId;
|
|
9719
|
+
checks.push(await checkProject(deps, credentials, effectiveProjectId));
|
|
9720
|
+
checks.push(await checkBundle(options.bundleDir));
|
|
9721
|
+
checks.push(await checkSidecar(options.bundleDir));
|
|
9722
|
+
checks.push(await checkLock(options.bundleDir));
|
|
9723
|
+
checks.push(await checkAgents(options.bundleDir));
|
|
9724
|
+
checks.push(await checkExecutionSkill(options.bundleDir));
|
|
9725
|
+
checks.push(await checkSkillPacks(options.bundleDir));
|
|
9726
|
+
checks.push(await checkDuplicates(options.bundleDir));
|
|
9727
|
+
checks.push(await checkHarness(options.bundleDir));
|
|
9728
|
+
return checks;
|
|
9729
|
+
}
|
|
9730
|
+
async function applyFixes(options, deps, checks) {
|
|
9731
|
+
let changed = false;
|
|
9732
|
+
if (checks.some((check) => check.id === "lock" && check.fixable === true)) {
|
|
9733
|
+
try {
|
|
9734
|
+
await rm4(lockPath(options.bundleDir), { force: true });
|
|
9735
|
+
changed = true;
|
|
9736
|
+
} catch {
|
|
9737
|
+
}
|
|
9738
|
+
}
|
|
9739
|
+
const needsScaffold = checks.some(
|
|
9740
|
+
(check) => check.fixable === true && BUNDLE_SCAFFOLD_CHECKS.has(check.id)
|
|
9741
|
+
);
|
|
9742
|
+
if (needsScaffold) {
|
|
9743
|
+
try {
|
|
9744
|
+
await deps.installSkills(options.bundleDir);
|
|
9745
|
+
changed = true;
|
|
9746
|
+
} catch {
|
|
9747
|
+
}
|
|
9748
|
+
}
|
|
9749
|
+
return changed;
|
|
9750
|
+
}
|
|
9751
|
+
async function runDoctor(options) {
|
|
9752
|
+
const deps = resolveDeps(options.deps);
|
|
9753
|
+
let checks = await runChecks(options, deps);
|
|
9754
|
+
if (options.fix) {
|
|
9755
|
+
const changed = await applyFixes(options, deps, checks);
|
|
9756
|
+
if (changed) checks = await runChecks(options, deps);
|
|
9757
|
+
}
|
|
9758
|
+
return { ok: !checks.some((check) => check.status === "fail"), checks };
|
|
9759
|
+
}
|
|
9760
|
+
|
|
9761
|
+
// src/errors.ts
|
|
9762
|
+
var UsageError = class extends Error {
|
|
9763
|
+
};
|
|
9764
|
+
|
|
9765
|
+
// src/output.ts
|
|
9766
|
+
function formatTable(headers, rows) {
|
|
9767
|
+
const widths = headers.map(
|
|
9768
|
+
(header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length))
|
|
9769
|
+
);
|
|
9770
|
+
const render = (cells) => cells.map((cell, index) => cell.padEnd(widths[index] ?? 0)).join(" ").trimEnd();
|
|
9771
|
+
const divider = widths.map((width) => "-".repeat(width)).join(" ");
|
|
9772
|
+
return [render(headers), divider, ...rows.map(render)].join("\n");
|
|
9773
|
+
}
|
|
9774
|
+
|
|
9775
|
+
// src/session.ts
|
|
9776
|
+
import { rm as rm5, stat as stat4 } from "node:fs/promises";
|
|
9777
|
+
import { resolve as resolve3 } from "node:path";
|
|
9778
|
+
|
|
9281
9779
|
// src/tasks.ts
|
|
9282
|
-
import { readdir as
|
|
9283
|
-
import { join as
|
|
9780
|
+
import { readdir as readdir4, readFile as readFile6 } from "node:fs/promises";
|
|
9781
|
+
import { join as join6 } from "node:path";
|
|
9284
9782
|
var TASK_ID_RE2 = /\b(?:TASK|T)-\d{3,}\b/i;
|
|
9285
9783
|
var HEADING_RE = /^#{1,4}\s+(.+)$/m;
|
|
9286
9784
|
var LAYER_RANK = {
|
|
@@ -9330,12 +9828,12 @@ var LAYER_CHECKLISTS = {
|
|
|
9330
9828
|
]
|
|
9331
9829
|
};
|
|
9332
9830
|
async function loadTasks(bundleDir) {
|
|
9333
|
-
const tasksDir =
|
|
9831
|
+
const tasksDir = join6(bundleDir, "tasks");
|
|
9334
9832
|
let entries;
|
|
9335
9833
|
try {
|
|
9336
|
-
entries = await
|
|
9834
|
+
entries = await readdir4(tasksDir, { withFileTypes: true });
|
|
9337
9835
|
} catch (error) {
|
|
9338
|
-
if (
|
|
9836
|
+
if (isMissingFile5(error)) throw new Error(`Direktori tasks/ tidak ditemukan di ${bundleDir}`);
|
|
9339
9837
|
throw error;
|
|
9340
9838
|
}
|
|
9341
9839
|
const names = entries.filter(
|
|
@@ -9344,16 +9842,16 @@ async function loadTasks(bundleDir) {
|
|
|
9344
9842
|
const tasks = [];
|
|
9345
9843
|
if (names.length > 0) {
|
|
9346
9844
|
for (const name of names) {
|
|
9347
|
-
const content = await
|
|
9845
|
+
const content = await readFile6(join6(tasksDir, name), "utf8");
|
|
9348
9846
|
tasks.push(parseSingleTask(name, content));
|
|
9349
9847
|
}
|
|
9350
9848
|
} else {
|
|
9351
|
-
const singlePath =
|
|
9849
|
+
const singlePath = join6(tasksDir, "TASKS.md");
|
|
9352
9850
|
let content;
|
|
9353
9851
|
try {
|
|
9354
|
-
content = await
|
|
9852
|
+
content = await readFile6(singlePath, "utf8");
|
|
9355
9853
|
} catch (error) {
|
|
9356
|
-
if (
|
|
9854
|
+
if (isMissingFile5(error)) throw new Error(`Tidak ada berkas task di ${tasksDir}`);
|
|
9357
9855
|
throw error;
|
|
9358
9856
|
}
|
|
9359
9857
|
tasks.push(...parseSingleFile(content));
|
|
@@ -9523,16 +10021,194 @@ function isBoundary(previous, next) {
|
|
|
9523
10021
|
}
|
|
9524
10022
|
return previous.phase !== void 0 && next.phase !== previous.phase;
|
|
9525
10023
|
}
|
|
9526
|
-
|
|
10024
|
+
var RISKY_TASK_RE = /\b(migrat(?:e|ion|ions)?|migrasi|prisma|schema|rls|auth|permission|payment|checkout|webhook|secret|deploy|drop)\b/i;
|
|
10025
|
+
var TARGET_FILES_LABEL_RE = /^\s*(?:[-*]\s*)?\*{0,2}targetFiles\*{0,2}\s*:\s*(.*)$/i;
|
|
10026
|
+
function isRiskyTask(task) {
|
|
10027
|
+
const targetFiles = task.targetFiles ?? targetFilesFromContent(task.content);
|
|
10028
|
+
return RISKY_TASK_RE.test([task.id, task.title, ...targetFiles].join("\n"));
|
|
10029
|
+
}
|
|
10030
|
+
function targetFilesFromContent(content) {
|
|
10031
|
+
const lines = content.split(/\r?\n/);
|
|
10032
|
+
const index = lines.findIndex((line) => TARGET_FILES_LABEL_RE.test(line));
|
|
10033
|
+
if (index === -1) return [];
|
|
10034
|
+
const inline = (lines[index]?.match(TARGET_FILES_LABEL_RE)?.[1] ?? "").trim();
|
|
10035
|
+
if (inline.length > 0) return splitTargetFiles(inline);
|
|
10036
|
+
const items = [];
|
|
10037
|
+
for (let cursor = index + 1; cursor < lines.length; cursor += 1) {
|
|
10038
|
+
const line = lines[cursor] ?? "";
|
|
10039
|
+
if (line.trim().length === 0) continue;
|
|
10040
|
+
const item = /^\s*-\s+(.+?)\s*$/.exec(line);
|
|
10041
|
+
if (item === null) break;
|
|
10042
|
+
const cleaned = cleanTargetFile(item[1] ?? "");
|
|
10043
|
+
if (cleaned.length > 0) items.push(cleaned);
|
|
10044
|
+
}
|
|
10045
|
+
return items;
|
|
10046
|
+
}
|
|
10047
|
+
function splitTargetFiles(value) {
|
|
10048
|
+
return value.replace(/[[\]`]/g, "").split(",").map(cleanTargetFile).filter((part) => part.length > 0 && !/^(?:\(?none\)?|n\/a|-+)$/i.test(part));
|
|
10049
|
+
}
|
|
10050
|
+
function cleanTargetFile(value) {
|
|
10051
|
+
return value.replace(/^[-*\s]+/, "").trim();
|
|
10052
|
+
}
|
|
10053
|
+
function isMissingFile5(error) {
|
|
10054
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
10055
|
+
}
|
|
10056
|
+
|
|
10057
|
+
// src/session.ts
|
|
10058
|
+
var LOCK_STALE_MS3 = 1e4;
|
|
10059
|
+
var CHANGE_REQUEST_PREFIX = "Perubahan diminta:";
|
|
10060
|
+
async function resumeSession(options) {
|
|
10061
|
+
const context = await resolveBundleContext(options);
|
|
10062
|
+
await removeStaleLock2(context.bundleDir);
|
|
10063
|
+
const initial = await readState(context.bundleDir);
|
|
10064
|
+
if (initial === null) {
|
|
10065
|
+
return {
|
|
10066
|
+
projectId: context.projectId ?? "",
|
|
10067
|
+
bundleDir: context.bundleDir,
|
|
10068
|
+
hasState: false,
|
|
10069
|
+
current: null,
|
|
10070
|
+
nextRef: null,
|
|
10071
|
+
awaitingCheckpoint: false
|
|
10072
|
+
};
|
|
10073
|
+
}
|
|
10074
|
+
return withStateLock(context.bundleDir, async () => {
|
|
10075
|
+
const state = await readState(context.bundleDir) ?? initial;
|
|
10076
|
+
const definitions = await loadTasks(context.bundleDir);
|
|
10077
|
+
const merged = mergeTasks(state, definitions);
|
|
10078
|
+
const written = await writeState(context.bundleDir, merged);
|
|
10079
|
+
const current = written.current ?? null;
|
|
10080
|
+
const next = selectNext(definitions, written.tasks);
|
|
10081
|
+
return {
|
|
10082
|
+
projectId: written.projectId ?? context.projectId ?? "",
|
|
10083
|
+
bundleDir: context.bundleDir,
|
|
10084
|
+
hasState: true,
|
|
10085
|
+
current,
|
|
10086
|
+
nextRef: next?.task.id ?? null,
|
|
10087
|
+
awaitingCheckpoint: current?.awaitingCheckpoint === true
|
|
10088
|
+
};
|
|
10089
|
+
});
|
|
10090
|
+
}
|
|
10091
|
+
async function reviewSession(options, review) {
|
|
10092
|
+
const context = await resolveBundleContext(options);
|
|
10093
|
+
return withStateLock(context.bundleDir, async () => {
|
|
10094
|
+
const state = await readState(context.bundleDir);
|
|
10095
|
+
if (state === null) return unchanged(review.action, null);
|
|
10096
|
+
const current = state.current ?? null;
|
|
10097
|
+
if (review.action === "stop") {
|
|
10098
|
+
return stopReview(context.bundleDir, state, current, review.note);
|
|
10099
|
+
}
|
|
10100
|
+
if (current === null || current.awaitingCheckpoint !== true) {
|
|
10101
|
+
return unchanged(review.action, current?.ref ?? null);
|
|
10102
|
+
}
|
|
10103
|
+
if (review.action === "approve") {
|
|
10104
|
+
const cleared = { ...current, awaitingCheckpoint: false };
|
|
10105
|
+
const written2 = await writeState(context.bundleDir, setCurrent(state, cleared));
|
|
10106
|
+
const ref2 = written2.current?.ref ?? current.ref ?? null;
|
|
10107
|
+
return { action: review.action, ref: ref2, clearedCheckpoint: true, nextRef: ref2 };
|
|
10108
|
+
}
|
|
10109
|
+
const ref = review.ref ?? latestDoneRef(state);
|
|
10110
|
+
if (ref === null) {
|
|
10111
|
+
return {
|
|
10112
|
+
action: review.action,
|
|
10113
|
+
ref: null,
|
|
10114
|
+
clearedCheckpoint: false,
|
|
10115
|
+
nextRef: current.ref ?? null
|
|
10116
|
+
};
|
|
10117
|
+
}
|
|
10118
|
+
const target = findTask(state, ref);
|
|
10119
|
+
if (target === void 0) {
|
|
10120
|
+
throw new Error(
|
|
10121
|
+
`Task ${ref} tidak ada di sidecar. Jalankan \`jioogen task next\` lebih dulu.`
|
|
10122
|
+
);
|
|
10123
|
+
}
|
|
10124
|
+
const note = `${CHANGE_REQUEST_PREFIX} ${review.note ?? ""}`.trim();
|
|
10125
|
+
const reopened = withStatus(state, target.ref, "in_progress", { note, awaitingCheckpoint: false });
|
|
10126
|
+
const reopenedTarget = findTask(reopened, target.ref);
|
|
10127
|
+
const nextCurrent = {
|
|
10128
|
+
ref: reopenedTarget?.ref ?? target.ref,
|
|
10129
|
+
...reopenedTarget?.layer !== void 0 ? { layer: reopenedTarget.layer } : {},
|
|
10130
|
+
...reopenedTarget?.phase !== void 0 ? { phase: reopenedTarget.phase } : {},
|
|
10131
|
+
awaitingCheckpoint: false
|
|
10132
|
+
};
|
|
10133
|
+
const written = await writeState(context.bundleDir, setCurrent(reopened, nextCurrent));
|
|
10134
|
+
const finalRef = written.current?.ref ?? nextCurrent.ref ?? null;
|
|
10135
|
+
return { action: review.action, ref: finalRef, clearedCheckpoint: true, nextRef: finalRef };
|
|
10136
|
+
});
|
|
10137
|
+
}
|
|
10138
|
+
async function stopReview(bundleDir, state, current, note) {
|
|
10139
|
+
const ref = current?.ref ?? null;
|
|
10140
|
+
if (note === void 0 || note.length === 0 || current?.ref === void 0) {
|
|
10141
|
+
return { action: "stop", ref, clearedCheckpoint: false, nextRef: ref };
|
|
10142
|
+
}
|
|
10143
|
+
const key = current.ref.toUpperCase();
|
|
10144
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10145
|
+
const tasks = state.tasks.map(
|
|
10146
|
+
(task) => task.ref.toUpperCase() === key ? { ...task, note, updatedAt: now } : task
|
|
10147
|
+
);
|
|
10148
|
+
const written = await writeState(bundleDir, { ...state, tasks });
|
|
10149
|
+
return {
|
|
10150
|
+
action: "stop",
|
|
10151
|
+
ref,
|
|
10152
|
+
clearedCheckpoint: false,
|
|
10153
|
+
nextRef: written.current?.ref ?? ref
|
|
10154
|
+
};
|
|
10155
|
+
}
|
|
10156
|
+
function unchanged(action, ref) {
|
|
10157
|
+
return { action, ref, clearedCheckpoint: false, nextRef: ref };
|
|
10158
|
+
}
|
|
10159
|
+
function findTask(state, ref) {
|
|
10160
|
+
const key = ref.toUpperCase();
|
|
10161
|
+
return state.tasks.find((task) => task.ref.toUpperCase() === key);
|
|
10162
|
+
}
|
|
10163
|
+
function latestDoneRef(state) {
|
|
10164
|
+
let latest = null;
|
|
10165
|
+
for (const task of state.tasks) {
|
|
10166
|
+
if (task.status !== "done") continue;
|
|
10167
|
+
if (latest === null || updatedAtMs(task) >= updatedAtMs(latest)) latest = task;
|
|
10168
|
+
}
|
|
10169
|
+
return latest?.ref ?? null;
|
|
10170
|
+
}
|
|
10171
|
+
function updatedAtMs(task) {
|
|
10172
|
+
if (task.updatedAt === void 0) return Number.NEGATIVE_INFINITY;
|
|
10173
|
+
const value = Date.parse(task.updatedAt);
|
|
10174
|
+
return Number.isNaN(value) ? Number.NEGATIVE_INFINITY : value;
|
|
10175
|
+
}
|
|
10176
|
+
async function resolveBundleContext(options) {
|
|
10177
|
+
const link = await findProjectLink(options.dir ?? process.cwd());
|
|
10178
|
+
const projectId = options.project ?? envFirst("JIOOGEN_PROJECT", "JIOOCODE_PROJECT") ?? link?.projectId;
|
|
10179
|
+
return {
|
|
10180
|
+
projectId,
|
|
10181
|
+
bundleDir: resolve3(link?.bundleDir ?? options.dir ?? process.cwd())
|
|
10182
|
+
};
|
|
10183
|
+
}
|
|
10184
|
+
async function removeStaleLock2(bundleDir) {
|
|
10185
|
+
const target = lockPath(bundleDir);
|
|
10186
|
+
let info;
|
|
10187
|
+
try {
|
|
10188
|
+
info = await stat4(target);
|
|
10189
|
+
} catch (error) {
|
|
10190
|
+
if (isMissingFile6(error)) return;
|
|
10191
|
+
throw error;
|
|
10192
|
+
}
|
|
10193
|
+
if (Date.now() - info.mtimeMs <= LOCK_STALE_MS3) return;
|
|
10194
|
+
await rm5(target, { force: true });
|
|
10195
|
+
}
|
|
10196
|
+
function envFirst(primary, legacy) {
|
|
10197
|
+
const value = process.env[primary]?.trim();
|
|
10198
|
+
if (value !== void 0 && value.length > 0) return value;
|
|
10199
|
+
const fallback = process.env[legacy]?.trim();
|
|
10200
|
+
return fallback !== void 0 && fallback.length > 0 ? fallback : void 0;
|
|
10201
|
+
}
|
|
10202
|
+
function isMissingFile6(error) {
|
|
9527
10203
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
9528
10204
|
}
|
|
9529
10205
|
|
|
9530
10206
|
// src/zip.ts
|
|
9531
10207
|
var import_adm_zip = __toESM(require_adm_zip(), 1);
|
|
9532
10208
|
import { mkdir as mkdir4, writeFile as writeFile4 } from "node:fs/promises";
|
|
9533
|
-
import { dirname as dirname4, resolve as
|
|
10209
|
+
import { dirname as dirname4, resolve as resolve4, sep } from "node:path";
|
|
9534
10210
|
async function extractBundle(bytes, targetDir) {
|
|
9535
|
-
const root =
|
|
10211
|
+
const root = resolve4(targetDir);
|
|
9536
10212
|
const zip = new import_adm_zip.default(Buffer.from(bytes));
|
|
9537
10213
|
const written = [];
|
|
9538
10214
|
for (const entry of zip.getEntries()) {
|
|
@@ -9542,7 +10218,7 @@ async function extractBundle(bytes, targetDir) {
|
|
|
9542
10218
|
if (segments.some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
|
|
9543
10219
|
throw new Error(`Path bundle tidak aman: ${entry.entryName}`);
|
|
9544
10220
|
}
|
|
9545
|
-
const destination =
|
|
10221
|
+
const destination = resolve4(root, relative2);
|
|
9546
10222
|
if (!destination.startsWith(`${root}${sep}`)) {
|
|
9547
10223
|
throw new Error(`Path bundle keluar dari direktori target: ${entry.entryName}`);
|
|
9548
10224
|
}
|
|
@@ -9559,10 +10235,10 @@ function isValidProjectId(projectId) {
|
|
|
9559
10235
|
return PROJECT_ID_RE.test(projectId.trim());
|
|
9560
10236
|
}
|
|
9561
10237
|
async function loginCommand(options) {
|
|
9562
|
-
const token = options.token ??
|
|
10238
|
+
const token = options.token ?? envFirst2("JIOOGEN_TOKEN", "JIOOCODE_TOKEN");
|
|
9563
10239
|
if (token !== void 0 && token.length > 0) {
|
|
9564
10240
|
const apiUrl = normalizeBaseUrl(
|
|
9565
|
-
options.api ??
|
|
10241
|
+
options.api ?? envFirst2("JIOOGEN_API", "JIOOCODE_API") ?? DEFAULT_API_URL
|
|
9566
10242
|
);
|
|
9567
10243
|
const client = new ApiClient(apiUrl, `${SESSION_COOKIE}=${token}`);
|
|
9568
10244
|
const body = await client.getJson(ENDPOINTS.me);
|
|
@@ -9577,17 +10253,17 @@ async function loginCommand(options) {
|
|
|
9577
10253
|
console.log(`Credential disimpan di ${credentialsPath()} (mode 0600).`);
|
|
9578
10254
|
return;
|
|
9579
10255
|
}
|
|
9580
|
-
const email = options.email ??
|
|
10256
|
+
const email = options.email ?? envFirst2("JIOOGEN_EMAIL", "JIOOCODE_EMAIL");
|
|
9581
10257
|
if (email === void 0 || email.length === 0) {
|
|
9582
10258
|
throw new Error("--email wajib diisi (atau set JIOOGEN_EMAIL).");
|
|
9583
10259
|
}
|
|
9584
|
-
let password = options.password ??
|
|
10260
|
+
let password = options.password ?? envFirst2("JIOOGEN_PASSWORD", "JIOOCODE_PASSWORD");
|
|
9585
10261
|
if (options.passwordStdin === true) password = (await readStdin()).trim();
|
|
9586
10262
|
if (password === void 0 || password.length === 0) {
|
|
9587
10263
|
throw new Error("--password atau --password-stdin wajib diisi.");
|
|
9588
10264
|
}
|
|
9589
10265
|
const result = await apiLogin(
|
|
9590
|
-
options.api ??
|
|
10266
|
+
options.api ?? envFirst2("JIOOGEN_API", "JIOOCODE_API") ?? DEFAULT_API_URL,
|
|
9591
10267
|
email,
|
|
9592
10268
|
password
|
|
9593
10269
|
);
|
|
@@ -9645,7 +10321,7 @@ async function pullCommand(projectId, options) {
|
|
|
9645
10321
|
}
|
|
9646
10322
|
await assertPullDefaultDir(process.cwd(), options.dir);
|
|
9647
10323
|
const { apiUrl, client } = await resolveApi(options);
|
|
9648
|
-
const target =
|
|
10324
|
+
const target = resolve5(options.dir ?? projectId);
|
|
9649
10325
|
await assertPullTarget(target, projectId, options.force === true);
|
|
9650
10326
|
const bytes = await client.getBytes(projectPath(projectId, "/bundle"));
|
|
9651
10327
|
const written = await extractBundle(bytes, target);
|
|
@@ -9662,12 +10338,12 @@ async function pullCommand(projectId, options) {
|
|
|
9662
10338
|
}
|
|
9663
10339
|
async function linkCommand(projectId, options) {
|
|
9664
10340
|
const { apiUrl, client } = await resolveApi(options);
|
|
9665
|
-
const target =
|
|
10341
|
+
const target = resolve5(options.dir ?? process.cwd());
|
|
9666
10342
|
await assertLinkTarget(target, projectId);
|
|
9667
10343
|
await verifyProjectAccess(client, projectId);
|
|
9668
10344
|
await writeProjectLink(target, { projectId, apiUrl });
|
|
9669
10345
|
const skills = await installAgentSkills(target, { noSkills: skillsDisabled(options) });
|
|
9670
|
-
const linkedFile =
|
|
10346
|
+
const linkedFile = join7(target, ".jioogen", "project.json");
|
|
9671
10347
|
if (options.json === true) {
|
|
9672
10348
|
console.log(JSON.stringify({ projectId, target, apiUrl, linkedFile, skills }, null, 2));
|
|
9673
10349
|
return;
|
|
@@ -9762,7 +10438,7 @@ async function skillsRemoveCommand(packArgument, options) {
|
|
|
9762
10438
|
}
|
|
9763
10439
|
async function resolveSkillsBundleDir(options) {
|
|
9764
10440
|
const link = await findProjectLink(options.dir ?? process.cwd());
|
|
9765
|
-
return
|
|
10441
|
+
return resolve5(link?.bundleDir ?? options.dir ?? process.cwd());
|
|
9766
10442
|
}
|
|
9767
10443
|
function printSkillPacks(packs, missing) {
|
|
9768
10444
|
if (packs.length === 0 && missing.length === 0) return;
|
|
@@ -9774,7 +10450,7 @@ function printSkillPacks(packs, missing) {
|
|
|
9774
10450
|
}
|
|
9775
10451
|
async function checkCommand(options) {
|
|
9776
10452
|
const link = await findProjectLink(options.dir ?? process.cwd());
|
|
9777
|
-
const bundleDir =
|
|
10453
|
+
const bundleDir = resolve5(link?.bundleDir ?? options.dir ?? process.cwd());
|
|
9778
10454
|
const result = await inspectBundle(bundleDir);
|
|
9779
10455
|
if (options.json === true) {
|
|
9780
10456
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -9783,6 +10459,90 @@ async function checkCommand(options) {
|
|
|
9783
10459
|
}
|
|
9784
10460
|
if (!result.ok) process.exitCode = 1;
|
|
9785
10461
|
}
|
|
10462
|
+
async function doctorCommand(options) {
|
|
10463
|
+
const link = await findProjectLink(options.dir ?? process.cwd());
|
|
10464
|
+
const bundleDir = resolve5(link?.bundleDir ?? options.dir ?? process.cwd());
|
|
10465
|
+
const projectId = options.project ?? link?.projectId;
|
|
10466
|
+
const report = await runDoctor({
|
|
10467
|
+
bundleDir,
|
|
10468
|
+
...projectId === void 0 ? {} : { projectId },
|
|
10469
|
+
fix: options.fix === true
|
|
10470
|
+
});
|
|
10471
|
+
if (options.json === true) {
|
|
10472
|
+
console.log(JSON.stringify(report, null, 2));
|
|
10473
|
+
} else {
|
|
10474
|
+
for (const check of report.checks) {
|
|
10475
|
+
console.log(`${check.status.toUpperCase().padEnd(4)} ${check.id} \u2014 ${check.detail}`);
|
|
10476
|
+
}
|
|
10477
|
+
const fails = report.checks.filter((check) => check.status === "fail").length;
|
|
10478
|
+
const warns = report.checks.filter((check) => check.status === "warn").length;
|
|
10479
|
+
console.log(
|
|
10480
|
+
`
|
|
10481
|
+
${report.ok ? "Doctor OK" : "Doctor GAGAL"} (${fails} fail, ${warns} warn).` + (options.fix === true ? "" : " Jalankan `jioogen doctor --fix` untuk perbaikan wiring.")
|
|
10482
|
+
);
|
|
10483
|
+
}
|
|
10484
|
+
if (!report.ok) process.exitCode = 1;
|
|
10485
|
+
}
|
|
10486
|
+
async function resumeCommand(options) {
|
|
10487
|
+
const result = await resumeSession(options);
|
|
10488
|
+
if (options.json === true) {
|
|
10489
|
+
console.log(JSON.stringify(result, null, 2));
|
|
10490
|
+
return;
|
|
10491
|
+
}
|
|
10492
|
+
if (!result.hasState) {
|
|
10493
|
+
console.log("Belum ada eksekusi tersimpan. Mulai dengan `jioogen task next`.");
|
|
10494
|
+
return;
|
|
10495
|
+
}
|
|
10496
|
+
const current = result.current?.ref ?? "-";
|
|
10497
|
+
console.log(`Resume project ${result.projectId} di ${result.bundleDir}.`);
|
|
10498
|
+
console.log(`Current: ${current}${result.awaitingCheckpoint ? " (checkpoint)" : ""}`);
|
|
10499
|
+
console.log(`Task berikutnya: ${result.nextRef ?? "selesai"}.`);
|
|
10500
|
+
if (result.awaitingCheckpoint) {
|
|
10501
|
+
console.log(
|
|
10502
|
+
'Putuskan: `jioogen review --approve`, `--request-changes "<teks>"`, atau `--stop`.'
|
|
10503
|
+
);
|
|
10504
|
+
}
|
|
10505
|
+
}
|
|
10506
|
+
async function reviewCommand(options) {
|
|
10507
|
+
const actions = [
|
|
10508
|
+
options.approve === true ? "approve" : void 0,
|
|
10509
|
+
options.requestChanges !== void 0 ? "request-changes" : void 0,
|
|
10510
|
+
options.stop === true ? "stop" : void 0
|
|
10511
|
+
].filter((action2) => action2 !== void 0);
|
|
10512
|
+
if (actions.length !== 1) {
|
|
10513
|
+
throw new UsageError(
|
|
10514
|
+
'`jioogen review` butuh tepat satu: --approve, --request-changes "<teks>", atau --stop.'
|
|
10515
|
+
);
|
|
10516
|
+
}
|
|
10517
|
+
const action = actions[0];
|
|
10518
|
+
if (action === void 0) {
|
|
10519
|
+
throw new UsageError("`jioogen review` butuh tepat satu aksi.");
|
|
10520
|
+
}
|
|
10521
|
+
const result = await reviewSession(options, {
|
|
10522
|
+
action,
|
|
10523
|
+
...options.requestChanges === void 0 ? {} : { note: options.requestChanges },
|
|
10524
|
+
...options.ref === void 0 ? {} : { ref: options.ref }
|
|
10525
|
+
});
|
|
10526
|
+
if (options.json === true) {
|
|
10527
|
+
console.log(JSON.stringify(result, null, 2));
|
|
10528
|
+
return;
|
|
10529
|
+
}
|
|
10530
|
+
if (action === "stop") {
|
|
10531
|
+
console.log("Review: stop. Checkpoint dipertahankan; lanjutkan kapan saja dengan `resume`.");
|
|
10532
|
+
return;
|
|
10533
|
+
}
|
|
10534
|
+
if (!result.clearedCheckpoint) {
|
|
10535
|
+
console.log("Tidak ada checkpoint aktif. Jalankan `jioogen resume` untuk status.");
|
|
10536
|
+
return;
|
|
10537
|
+
}
|
|
10538
|
+
if (action === "approve") {
|
|
10539
|
+
console.log("Review: disetujui. Checkpoint dibuka.");
|
|
10540
|
+
} else {
|
|
10541
|
+
console.log(`Review: perubahan diminta untuk ${result.ref ?? "task terakhir"}.`);
|
|
10542
|
+
}
|
|
10543
|
+
if (result.nextRef !== null) console.log(`Task berikutnya: ${result.nextRef}.`);
|
|
10544
|
+
console.log("Jalankan `jioogen sync` agar panel monitoring ikut diperbarui.");
|
|
10545
|
+
}
|
|
9786
10546
|
async function handoffCommand(options) {
|
|
9787
10547
|
const { client, projectId } = await resolveBundle(options);
|
|
9788
10548
|
const payload = await client.postJson(
|
|
@@ -9887,11 +10647,17 @@ async function taskCompleteCommand(ref, options) {
|
|
|
9887
10647
|
(entry) => entry.ref.toUpperCase() === ref.toUpperCase()
|
|
9888
10648
|
);
|
|
9889
10649
|
const next2 = selectNext(local.definitions, completed.tasks);
|
|
9890
|
-
|
|
10650
|
+
const definition = local.definitions.find(
|
|
10651
|
+
(entry) => entry.id.toUpperCase() === ref.toUpperCase()
|
|
10652
|
+
);
|
|
10653
|
+
const risky = definition !== void 0 && isRiskyTask(definition);
|
|
10654
|
+
const boundary = completedTask2 !== void 0 && (next2 !== null && isBoundary(completedTask2, next2.task) || risky);
|
|
10655
|
+
if (boundary && completedTask2 !== void 0) {
|
|
10656
|
+
const target = next2?.task;
|
|
9891
10657
|
const withCheckpoint = setCurrent(completed, {
|
|
9892
|
-
ref:
|
|
9893
|
-
|
|
9894
|
-
phase:
|
|
10658
|
+
ref: target?.id ?? completedTask2.ref,
|
|
10659
|
+
layer: target?.layer ?? completedTask2.layer,
|
|
10660
|
+
phase: target?.phase ?? completedTask2.phase ?? 1,
|
|
9895
10661
|
awaitingCheckpoint: true
|
|
9896
10662
|
});
|
|
9897
10663
|
await writeState(local.bundleDir, withCheckpoint);
|
|
@@ -9901,11 +10667,17 @@ async function taskCompleteCommand(ref, options) {
|
|
|
9901
10667
|
return { completedTask: completedTask2, next: next2, checkpoint: false };
|
|
9902
10668
|
});
|
|
9903
10669
|
console.log(`Done ${completedTask?.ref ?? ref}.`);
|
|
9904
|
-
if (checkpoint
|
|
10670
|
+
if (checkpoint) {
|
|
10671
|
+
if (next === null) {
|
|
10672
|
+
console.log("Semua task selesai; menunggu review terakhir.");
|
|
10673
|
+
} else {
|
|
10674
|
+
console.log(
|
|
10675
|
+
`Checkpoint berikutnya: ${next.task.id} (${next.task.layer ?? "-"} / phase ${next.task.phase}).`
|
|
10676
|
+
);
|
|
10677
|
+
}
|
|
9905
10678
|
console.log(
|
|
9906
|
-
|
|
10679
|
+
'Berhenti. Setelah user memutuskan: `jioogen review --approve`, `--request-changes "<teks>"`, atau `--stop`; lalu `jioogen sync`.'
|
|
9907
10680
|
);
|
|
9908
|
-
console.log("Berhenti dan minta konfirmasi user sebelum task start berikutnya.");
|
|
9909
10681
|
return;
|
|
9910
10682
|
}
|
|
9911
10683
|
if (next !== null) console.log(`Berikutnya: ${next.task.id} \u2014 ${next.task.title}`);
|
|
@@ -10007,7 +10779,7 @@ async function syncCommand(options) {
|
|
|
10007
10779
|
assertBundleRelative(relative2);
|
|
10008
10780
|
files.push({
|
|
10009
10781
|
path: relative2,
|
|
10010
|
-
content: await
|
|
10782
|
+
content: await readFile7(join7(bundleDir, relative2), "utf8")
|
|
10011
10783
|
});
|
|
10012
10784
|
}
|
|
10013
10785
|
}
|
|
@@ -10029,9 +10801,9 @@ async function syncCommand(options) {
|
|
|
10029
10801
|
async function resolveApi(options) {
|
|
10030
10802
|
const stored = await readCredentials();
|
|
10031
10803
|
const apiUrl = normalizeBaseUrl(
|
|
10032
|
-
options.api ??
|
|
10804
|
+
options.api ?? envFirst2("JIOOGEN_API", "JIOOCODE_API") ?? stored?.apiUrl ?? DEFAULT_API_URL
|
|
10033
10805
|
);
|
|
10034
|
-
const explicitCookie =
|
|
10806
|
+
const explicitCookie = envFirst2("JIOOGEN_COOKIE", "JIOOCODE_COOKIE");
|
|
10035
10807
|
const storedCookie = stored !== null && normalizeBaseUrl(stored.apiUrl) === apiUrl ? stored.cookie : void 0;
|
|
10036
10808
|
const cookie = explicitCookie || storedCookie;
|
|
10037
10809
|
if (cookie === void 0 || cookie.length === 0) {
|
|
@@ -10046,7 +10818,7 @@ async function resolveApi(options) {
|
|
|
10046
10818
|
}
|
|
10047
10819
|
async function resolveLocalBundle(options) {
|
|
10048
10820
|
const link = await findProjectLink(options.dir ?? process.cwd());
|
|
10049
|
-
const projectId = options.project ??
|
|
10821
|
+
const projectId = options.project ?? envFirst2("JIOOGEN_PROJECT", "JIOOCODE_PROJECT") ?? link?.projectId;
|
|
10050
10822
|
if (projectId === void 0 || projectId.length === 0) {
|
|
10051
10823
|
throw new Error(
|
|
10052
10824
|
"Project belum di-bind. Jalankan `jioogen pull <projectId>` atau pakai --project."
|
|
@@ -10054,7 +10826,7 @@ async function resolveLocalBundle(options) {
|
|
|
10054
10826
|
}
|
|
10055
10827
|
return {
|
|
10056
10828
|
projectId,
|
|
10057
|
-
bundleDir:
|
|
10829
|
+
bundleDir: resolve5(link?.bundleDir ?? options.dir ?? process.cwd()),
|
|
10058
10830
|
linkedApiUrl: link?.apiUrl
|
|
10059
10831
|
};
|
|
10060
10832
|
}
|
|
@@ -10107,9 +10879,9 @@ async function assertPullDefaultDir(cwd, dirOption) {
|
|
|
10107
10879
|
async function assertPullTarget(target, projectId, force = false) {
|
|
10108
10880
|
let entries;
|
|
10109
10881
|
try {
|
|
10110
|
-
entries = await
|
|
10882
|
+
entries = await readdir5(target);
|
|
10111
10883
|
} catch (error) {
|
|
10112
|
-
if (
|
|
10884
|
+
if (isMissingFile7(error)) return;
|
|
10113
10885
|
throw error;
|
|
10114
10886
|
}
|
|
10115
10887
|
if (entries.length === 0) return;
|
|
@@ -10141,19 +10913,19 @@ async function assertLinkTarget(target, projectId) {
|
|
|
10141
10913
|
}
|
|
10142
10914
|
async function hasBundleMarkers(target) {
|
|
10143
10915
|
try {
|
|
10144
|
-
await
|
|
10916
|
+
await readFile7(join7(target, "PRD.md"));
|
|
10145
10917
|
return true;
|
|
10146
10918
|
} catch (error) {
|
|
10147
|
-
if (!
|
|
10919
|
+
if (!isMissingFile7(error)) throw error;
|
|
10148
10920
|
}
|
|
10149
|
-
return hasTaskMarkdown(
|
|
10921
|
+
return hasTaskMarkdown(join7(target, "tasks"));
|
|
10150
10922
|
}
|
|
10151
10923
|
async function hasTaskMarkdown(directory) {
|
|
10152
10924
|
let names;
|
|
10153
10925
|
try {
|
|
10154
|
-
names = await
|
|
10926
|
+
names = await readdir5(directory);
|
|
10155
10927
|
} catch (error) {
|
|
10156
|
-
if (
|
|
10928
|
+
if (isMissingFile7(error)) return false;
|
|
10157
10929
|
throw error;
|
|
10158
10930
|
}
|
|
10159
10931
|
return names.some((name) => name.toLowerCase().endsWith(".md"));
|
|
@@ -10209,18 +10981,18 @@ async function readStdin() {
|
|
|
10209
10981
|
for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
|
|
10210
10982
|
return Buffer.concat(chunks).toString("utf8");
|
|
10211
10983
|
}
|
|
10212
|
-
function
|
|
10984
|
+
function envFirst2(primary, legacy) {
|
|
10213
10985
|
const value = process.env[primary]?.trim();
|
|
10214
10986
|
if (value !== void 0 && value.length > 0) return value;
|
|
10215
10987
|
const fallback = process.env[legacy]?.trim();
|
|
10216
10988
|
return fallback !== void 0 && fallback.length > 0 ? fallback : void 0;
|
|
10217
10989
|
}
|
|
10218
|
-
function
|
|
10990
|
+
function isMissingFile7(error) {
|
|
10219
10991
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
10220
10992
|
}
|
|
10221
10993
|
|
|
10222
10994
|
// src/index.ts
|
|
10223
|
-
var VERSION = "0.2.
|
|
10995
|
+
var VERSION = "0.2.5";
|
|
10224
10996
|
var USAGE = `jioogen \u2014 CLI JiooGen (task selection lokal, server read-only)
|
|
10225
10997
|
|
|
10226
10998
|
Usage:
|
|
@@ -10237,6 +11009,9 @@ Usage:
|
|
|
10237
11009
|
jioogen skills update [--dir <dir>]
|
|
10238
11010
|
jioogen skills remove <baseline|ui-quality|backend-rules> [--dir <dir>]
|
|
10239
11011
|
jioogen check [--dir <dir>] [--json]
|
|
11012
|
+
jioogen doctor [--fix] [--dir <dir>] [--json]
|
|
11013
|
+
jioogen resume [--dir <dir>] [--json]
|
|
11014
|
+
jioogen review (--approve | --request-changes <teks> | --stop) [--ref <TASK-XXX>] [--json]
|
|
10240
11015
|
jioogen handoff [--project <id>] [--json]
|
|
10241
11016
|
jioogen task next [--json]
|
|
10242
11017
|
jioogen task start <ref>
|
|
@@ -10277,6 +11052,11 @@ async function main() {
|
|
|
10277
11052
|
"no-skills": { type: "boolean" },
|
|
10278
11053
|
files: { type: "string" },
|
|
10279
11054
|
note: { type: "string" },
|
|
11055
|
+
fix: { type: "boolean" },
|
|
11056
|
+
approve: { type: "boolean" },
|
|
11057
|
+
stop: { type: "boolean" },
|
|
11058
|
+
"request-changes": { type: "string" },
|
|
11059
|
+
ref: { type: "string" },
|
|
10280
11060
|
json: { type: "boolean" },
|
|
10281
11061
|
help: { type: "boolean", short: "h" },
|
|
10282
11062
|
version: { type: "boolean", short: "v" }
|
|
@@ -10304,6 +11084,11 @@ async function main() {
|
|
|
10304
11084
|
noSkills: flag(raw["no-skills"]),
|
|
10305
11085
|
files: text(raw.files),
|
|
10306
11086
|
note: text(raw.note),
|
|
11087
|
+
fix: flag(raw.fix),
|
|
11088
|
+
approve: flag(raw.approve),
|
|
11089
|
+
stop: flag(raw.stop),
|
|
11090
|
+
requestChanges: text(raw["request-changes"]),
|
|
11091
|
+
ref: text(raw.ref),
|
|
10307
11092
|
json: flag(raw.json)
|
|
10308
11093
|
};
|
|
10309
11094
|
if (flag(raw.version)) {
|
|
@@ -10366,6 +11151,15 @@ async function dispatch(command, rest, options) {
|
|
|
10366
11151
|
case "check":
|
|
10367
11152
|
await checkCommand(options);
|
|
10368
11153
|
return;
|
|
11154
|
+
case "doctor":
|
|
11155
|
+
await doctorCommand(options);
|
|
11156
|
+
return;
|
|
11157
|
+
case "resume":
|
|
11158
|
+
await resumeCommand(options);
|
|
11159
|
+
return;
|
|
11160
|
+
case "review":
|
|
11161
|
+
await reviewCommand(options);
|
|
11162
|
+
return;
|
|
10369
11163
|
case "handoff":
|
|
10370
11164
|
await handoffCommand(options);
|
|
10371
11165
|
return;
|