jioogen 0.2.3 → 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 +1417 -134
- 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,19 +8752,32 @@ 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
|
|
8769
|
-
import {
|
|
8759
|
+
import { createHash } from "node:crypto";
|
|
8760
|
+
import { cp, mkdir as mkdir3, readdir as readdir2, readFile as readFile4, rm as rm3, stat as stat2, writeFile as writeFile3 } from "node:fs/promises";
|
|
8761
|
+
import { homedir as homedir2 } from "node:os";
|
|
8770
8762
|
import { dirname as dirname3, join as join4, resolve as resolve2 } from "node:path";
|
|
8763
|
+
import { fileURLToPath } from "node:url";
|
|
8771
8764
|
var EXECUTION_SKILL_NAME = "jioogen-execution";
|
|
8772
8765
|
var AGENT_SKILL_DIRS = [
|
|
8773
8766
|
".claude/skills",
|
|
8767
|
+
".opencode/skills",
|
|
8774
8768
|
".agent/skills",
|
|
8775
8769
|
".codex/skills",
|
|
8776
8770
|
".cursor/skills"
|
|
8777
8771
|
];
|
|
8772
|
+
var SKILL_PACK_NAMES = ["baseline", "ui-quality", "backend-rules"];
|
|
8773
|
+
var SKILL_PACKS = {
|
|
8774
|
+
baseline: ["karpathy-guidelines"],
|
|
8775
|
+
"ui-quality": ["ui-ux-pro-max", "impeccable"],
|
|
8776
|
+
"backend-rules": []
|
|
8777
|
+
};
|
|
8778
|
+
var HARNESS_MANIFEST_PATH = ".jioogen/harness.json";
|
|
8779
|
+
var HARNESS_SCHEMA_VERSION = 1;
|
|
8780
|
+
var FRONTEND_MOCK_LAYER_RE = /^[ \t]*(?:[-*][ \t]+)?layer[ \t]*[:=][ \t]*[`"']?frontend_mock\b/im;
|
|
8778
8781
|
var MARKER_PREFIX = "<!-- jioogen-execution-skill:";
|
|
8779
8782
|
var MARKER = `${MARKER_PREFIX} v1 -->`;
|
|
8780
8783
|
var JIOOGEN_AGENTS_MD = `${MARKER}
|
|
@@ -8824,12 +8827,15 @@ var JIOOGEN_AGENTS_MD = `${MARKER}
|
|
|
8824
8827
|
|
|
8825
8828
|
## 5. Checkpoint
|
|
8826
8829
|
Berhenti dan minta konfirmasi user bila task berikutnya berpindah layer, nomor
|
|
8827
|
-
\`phase\` naik,
|
|
8828
|
-
|
|
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\`.
|
|
8829
8835
|
|
|
8830
8836
|
## 6. Skill eksekusi
|
|
8831
8837
|
Baca skill \`${EXECUTION_SKILL_NAME}\` di awal prompt
|
|
8832
|
-
(\`.claude/skills\`, \`.agent/skills\`, \`.codex/skills\`, atau \`.cursor/skills\`).
|
|
8838
|
+
(\`.claude/skills\`, \`.opencode/skills\`, \`.agent/skills\`, \`.codex/skills\`, atau \`.cursor/skills\`).
|
|
8833
8839
|
Skill itu memuat kontrak lengkap; file ini adalah ringkasan yang mengikat.
|
|
8834
8840
|
`;
|
|
8835
8841
|
var JIOOGEN_EXECUTION_SKILL = `---
|
|
@@ -8903,10 +8909,13 @@ atas ide, refactor, perbaikan, atau fitur lain.
|
|
|
8903
8909
|
Berhenti dan minta konfirmasi user bila:
|
|
8904
8910
|
- task berikutnya berpindah \`layer\` (mis. \`frontend_mock\` \u2192 \`backend_api\`/\`database\`),
|
|
8905
8911
|
- nomor \`phase\` naik,
|
|
8906
|
-
- task bertanda \`checkpoint: true
|
|
8912
|
+
- task bertanda \`checkpoint: true\`,
|
|
8913
|
+
- task tergolong berisiko (migrasi/schema/auth/permission/payment/webhook/secret/deploy/infra).
|
|
8907
8914
|
|
|
8908
|
-
Jangan lanjut sebelum user
|
|
8909
|
-
|
|
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\`.
|
|
8910
8919
|
|
|
8911
8920
|
## 6. Larangan ringkas
|
|
8912
8921
|
|
|
@@ -8929,10 +8938,247 @@ async function readFileSafe(path) {
|
|
|
8929
8938
|
return void 0;
|
|
8930
8939
|
}
|
|
8931
8940
|
}
|
|
8932
|
-
|
|
8941
|
+
function isRecord2(value) {
|
|
8942
|
+
return typeof value === "object" && value !== null;
|
|
8943
|
+
}
|
|
8944
|
+
function isMissingFile3(error) {
|
|
8945
|
+
return isRecord2(error) && error.code === "ENOENT";
|
|
8946
|
+
}
|
|
8947
|
+
async function isFile(path) {
|
|
8948
|
+
try {
|
|
8949
|
+
return (await stat2(path)).isFile();
|
|
8950
|
+
} catch (error) {
|
|
8951
|
+
if (isMissingFile3(error)) return false;
|
|
8952
|
+
throw error;
|
|
8953
|
+
}
|
|
8954
|
+
}
|
|
8955
|
+
async function isDirectory(path) {
|
|
8956
|
+
try {
|
|
8957
|
+
return (await stat2(path)).isDirectory();
|
|
8958
|
+
} catch (error) {
|
|
8959
|
+
if (isMissingFile3(error)) return false;
|
|
8960
|
+
throw error;
|
|
8961
|
+
}
|
|
8962
|
+
}
|
|
8963
|
+
function isSkillPackName(value) {
|
|
8964
|
+
return typeof value === "string" && SKILL_PACK_NAMES.includes(value);
|
|
8965
|
+
}
|
|
8966
|
+
function isProductKind(value) {
|
|
8967
|
+
return value === "ui" || value === "non-ui";
|
|
8968
|
+
}
|
|
8969
|
+
function parseSkillEntry(value) {
|
|
8970
|
+
if (!isRecord2(value)) return null;
|
|
8971
|
+
const { name, source, sha256 } = value;
|
|
8972
|
+
if (typeof name !== "string" || name.length === 0) return null;
|
|
8973
|
+
if (typeof source !== "string" || source.length === 0) return null;
|
|
8974
|
+
if (typeof sha256 !== "string" || sha256.length === 0) return null;
|
|
8975
|
+
return { name, source, sha256 };
|
|
8976
|
+
}
|
|
8977
|
+
function parsePack(value) {
|
|
8978
|
+
if (!isRecord2(value)) return null;
|
|
8979
|
+
const { name, skills, missing } = value;
|
|
8980
|
+
if (!isSkillPackName(name)) return null;
|
|
8981
|
+
if (!Array.isArray(skills) || !Array.isArray(missing)) return null;
|
|
8982
|
+
const parsedSkills = [];
|
|
8983
|
+
for (const entry of skills) {
|
|
8984
|
+
const parsed = parseSkillEntry(entry);
|
|
8985
|
+
if (parsed === null) return null;
|
|
8986
|
+
parsedSkills.push(parsed);
|
|
8987
|
+
}
|
|
8988
|
+
const parsedMissing = [];
|
|
8989
|
+
for (const entry of missing) {
|
|
8990
|
+
if (typeof entry !== "string") return null;
|
|
8991
|
+
parsedMissing.push(entry);
|
|
8992
|
+
}
|
|
8993
|
+
return { name, skills: parsedSkills, missing: parsedMissing };
|
|
8994
|
+
}
|
|
8995
|
+
function parseHarnessManifest(value) {
|
|
8996
|
+
if (!isRecord2(value)) return null;
|
|
8997
|
+
const { schemaVersion, productKind, packs, updatedAt } = value;
|
|
8998
|
+
if (schemaVersion !== HARNESS_SCHEMA_VERSION) return null;
|
|
8999
|
+
if (!isProductKind(productKind)) return null;
|
|
9000
|
+
if (!Array.isArray(packs)) return null;
|
|
9001
|
+
const parsedPacks = [];
|
|
9002
|
+
for (const entry of packs) {
|
|
9003
|
+
const parsed = parsePack(entry);
|
|
9004
|
+
if (parsed === null) return null;
|
|
9005
|
+
parsedPacks.push(parsed);
|
|
9006
|
+
}
|
|
9007
|
+
if (typeof updatedAt !== "string" || updatedAt.length === 0) return null;
|
|
9008
|
+
return { schemaVersion: HARNESS_SCHEMA_VERSION, productKind, packs: parsedPacks, updatedAt };
|
|
9009
|
+
}
|
|
9010
|
+
async function readHarnessManifest(bundleDir) {
|
|
9011
|
+
const path = join4(resolve2(bundleDir), HARNESS_MANIFEST_PATH);
|
|
9012
|
+
let raw;
|
|
9013
|
+
try {
|
|
9014
|
+
raw = await readFile4(path, "utf8");
|
|
9015
|
+
} catch (error) {
|
|
9016
|
+
if (isMissingFile3(error)) return null;
|
|
9017
|
+
throw error;
|
|
9018
|
+
}
|
|
9019
|
+
try {
|
|
9020
|
+
return parseHarnessManifest(JSON.parse(raw));
|
|
9021
|
+
} catch {
|
|
9022
|
+
return null;
|
|
9023
|
+
}
|
|
9024
|
+
}
|
|
9025
|
+
async function writeHarnessManifest(bundleDir, manifest) {
|
|
9026
|
+
const path = join4(resolve2(bundleDir), HARNESS_MANIFEST_PATH);
|
|
9027
|
+
await mkdir3(dirname3(path), { recursive: true });
|
|
9028
|
+
await writeFile3(path, `${JSON.stringify(manifest, null, 2)}
|
|
9029
|
+
`, "utf8");
|
|
9030
|
+
return path;
|
|
9031
|
+
}
|
|
9032
|
+
async function detectProductKind(bundleDir) {
|
|
9033
|
+
const tasksDir = join4(resolve2(bundleDir), "tasks");
|
|
9034
|
+
let entries;
|
|
9035
|
+
try {
|
|
9036
|
+
entries = await readdir2(tasksDir, { withFileTypes: true });
|
|
9037
|
+
} catch (error) {
|
|
9038
|
+
if (isMissingFile3(error)) return "non-ui";
|
|
9039
|
+
throw error;
|
|
9040
|
+
}
|
|
9041
|
+
for (const entry of entries) {
|
|
9042
|
+
if (!entry.isFile() || !entry.name.toLowerCase().endsWith(".md")) continue;
|
|
9043
|
+
const content = await readFileSafe(join4(tasksDir, entry.name));
|
|
9044
|
+
if (content !== void 0 && FRONTEND_MOCK_LAYER_RE.test(content)) return "ui";
|
|
9045
|
+
}
|
|
9046
|
+
return "non-ui";
|
|
9047
|
+
}
|
|
9048
|
+
function packsForProduct(productKind) {
|
|
9049
|
+
return productKind === "ui" ? ["baseline", "ui-quality", "backend-rules"] : ["baseline", "backend-rules"];
|
|
9050
|
+
}
|
|
9051
|
+
function cliPackageRoot() {
|
|
9052
|
+
return resolve2(dirname3(fileURLToPath(import.meta.url)), "..");
|
|
9053
|
+
}
|
|
9054
|
+
function skillSourceCandidates(pack, name) {
|
|
9055
|
+
const candidates = [];
|
|
9056
|
+
const override = process.env.JIOOGEN_SKILLS_DIR?.trim();
|
|
9057
|
+
if (override !== void 0 && override.length > 0) candidates.push(join4(override, name));
|
|
9058
|
+
candidates.push(join4(configDir(), "skill-cache", name));
|
|
9059
|
+
candidates.push(join4(cliPackageRoot(), "skills-pack", pack, name));
|
|
9060
|
+
const home = homedir2();
|
|
9061
|
+
candidates.push(join4(home, ".agents", "skills", name));
|
|
9062
|
+
candidates.push(join4(home, ".config", "opencode", "skills", name));
|
|
9063
|
+
candidates.push(join4(home, ".claude", "skills", name));
|
|
9064
|
+
return candidates;
|
|
9065
|
+
}
|
|
9066
|
+
async function resolveSkillSource(pack, name) {
|
|
9067
|
+
for (const candidate of skillSourceCandidates(pack, name)) {
|
|
9068
|
+
if (await isDirectory(candidate) && await isFile(join4(candidate, "SKILL.md"))) {
|
|
9069
|
+
return candidate;
|
|
9070
|
+
}
|
|
9071
|
+
}
|
|
9072
|
+
return null;
|
|
9073
|
+
}
|
|
9074
|
+
async function collectRelativeFiles(root, prefix) {
|
|
9075
|
+
const entries = await readdir2(join4(root, prefix), { withFileTypes: true });
|
|
9076
|
+
const files = [];
|
|
9077
|
+
for (const entry of entries) {
|
|
9078
|
+
const relative2 = prefix.length === 0 ? entry.name : `${prefix}/${entry.name}`;
|
|
9079
|
+
if (entry.isDirectory()) {
|
|
9080
|
+
files.push(...await collectRelativeFiles(root, relative2));
|
|
9081
|
+
} else if (entry.isFile()) {
|
|
9082
|
+
files.push(relative2);
|
|
9083
|
+
}
|
|
9084
|
+
}
|
|
9085
|
+
return files;
|
|
9086
|
+
}
|
|
9087
|
+
async function hashSkillDirectory(directory) {
|
|
9088
|
+
const root = resolve2(directory);
|
|
9089
|
+
const files = (await collectRelativeFiles(root, "")).sort();
|
|
9090
|
+
const hash = createHash("sha256");
|
|
9091
|
+
for (const relative2 of files) {
|
|
9092
|
+
hash.update(relative2);
|
|
9093
|
+
hash.update("\0");
|
|
9094
|
+
hash.update(await readFile4(join4(root, ...relative2.split("/"))));
|
|
9095
|
+
hash.update("\0");
|
|
9096
|
+
}
|
|
9097
|
+
return hash.digest("hex");
|
|
9098
|
+
}
|
|
9099
|
+
async function replaceDirectory(source, target) {
|
|
9100
|
+
await rm3(target, { recursive: true, force: true });
|
|
9101
|
+
await mkdir3(dirname3(target), { recursive: true });
|
|
9102
|
+
await cp(source, target, { recursive: true });
|
|
9103
|
+
}
|
|
9104
|
+
function packNamesInCanonicalOrder(names) {
|
|
9105
|
+
const wanted = new Set(names);
|
|
9106
|
+
return SKILL_PACK_NAMES.filter((name) => wanted.has(name));
|
|
9107
|
+
}
|
|
9108
|
+
async function provisionPack(root, pack, prior, written, preserved) {
|
|
9109
|
+
const skills = [];
|
|
9110
|
+
const missing = [];
|
|
9111
|
+
for (const name of SKILL_PACKS[pack]) {
|
|
9112
|
+
const source = await resolveSkillSource(pack, name);
|
|
9113
|
+
if (source === null) {
|
|
9114
|
+
missing.push(name);
|
|
9115
|
+
continue;
|
|
9116
|
+
}
|
|
9117
|
+
const sha256 = await hashSkillDirectory(source);
|
|
9118
|
+
const previous = prior?.skills.find((entry) => entry.name === name);
|
|
9119
|
+
let owned = previous !== void 0;
|
|
9120
|
+
for (const dir of AGENT_SKILL_DIRS) {
|
|
9121
|
+
const targetDir = join4(root, dir, name);
|
|
9122
|
+
const skillMd = join4(targetDir, "SKILL.md");
|
|
9123
|
+
if (await isFile(skillMd)) {
|
|
9124
|
+
const targetHash = await hashSkillDirectory(targetDir);
|
|
9125
|
+
if (previous === void 0) {
|
|
9126
|
+
preserved.push(`${dir}/${name}/SKILL.md`);
|
|
9127
|
+
continue;
|
|
9128
|
+
}
|
|
9129
|
+
if (targetHash === sha256) {
|
|
9130
|
+
owned = true;
|
|
9131
|
+
continue;
|
|
9132
|
+
}
|
|
9133
|
+
await replaceDirectory(source, targetDir);
|
|
9134
|
+
written.push(`${dir}/${name}/SKILL.md`);
|
|
9135
|
+
owned = true;
|
|
9136
|
+
continue;
|
|
9137
|
+
}
|
|
9138
|
+
await replaceDirectory(source, targetDir);
|
|
9139
|
+
written.push(`${dir}/${name}/SKILL.md`);
|
|
9140
|
+
owned = true;
|
|
9141
|
+
}
|
|
9142
|
+
if (owned) skills.push({ name, source, sha256 });
|
|
9143
|
+
}
|
|
9144
|
+
return { name: pack, skills, missing };
|
|
9145
|
+
}
|
|
9146
|
+
function sameManifestBody(manifest, productKind, packs) {
|
|
9147
|
+
return manifest.productKind === productKind && JSON.stringify(manifest.packs) === JSON.stringify(packs);
|
|
9148
|
+
}
|
|
9149
|
+
async function provisionSkillPacks(bundleDir, packNames, productKind) {
|
|
9150
|
+
const root = resolve2(bundleDir);
|
|
9151
|
+
const existing = await readHarnessManifest(root);
|
|
9152
|
+
const written = [];
|
|
9153
|
+
const preserved = [];
|
|
9154
|
+
const packs = [];
|
|
9155
|
+
for (const pack of packNamesInCanonicalOrder(packNames)) {
|
|
9156
|
+
const prior = existing?.packs.find((entry) => entry.name === pack);
|
|
9157
|
+
packs.push(await provisionPack(root, pack, prior, written, preserved));
|
|
9158
|
+
}
|
|
9159
|
+
const changed = existing === null || !sameManifestBody(existing, productKind, packs);
|
|
9160
|
+
const manifest = {
|
|
9161
|
+
schemaVersion: HARNESS_SCHEMA_VERSION,
|
|
9162
|
+
productKind,
|
|
9163
|
+
packs,
|
|
9164
|
+
updatedAt: changed ? (/* @__PURE__ */ new Date()).toISOString() : existing?.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
9165
|
+
};
|
|
9166
|
+
const harnessPath = await writeHarnessManifest(root, manifest);
|
|
9167
|
+
return {
|
|
9168
|
+
written,
|
|
9169
|
+
preserved,
|
|
9170
|
+
productKind,
|
|
9171
|
+
packs,
|
|
9172
|
+
missing: packs.flatMap((pack) => pack.missing),
|
|
9173
|
+
harnessPath
|
|
9174
|
+
};
|
|
9175
|
+
}
|
|
9176
|
+
async function installAgentSkills(bundleDir, options = {}) {
|
|
8933
9177
|
const root = resolve2(bundleDir);
|
|
8934
9178
|
const written = [];
|
|
8935
9179
|
const preserved = [];
|
|
9180
|
+
const productKind = options.productKind ?? await detectProductKind(root);
|
|
9181
|
+
const harnessPath = join4(root, HARNESS_MANIFEST_PATH);
|
|
8936
9182
|
const agentsPath = join4(root, "AGENTS.md");
|
|
8937
9183
|
const existing = await readFileSafe(agentsPath);
|
|
8938
9184
|
if (existing === void 0 || existing.includes(MARKER_PREFIX)) {
|
|
@@ -8948,12 +9194,591 @@ async function installAgentSkills(bundleDir) {
|
|
|
8948
9194
|
await writeFile3(target, JIOOGEN_EXECUTION_SKILL, "utf8");
|
|
8949
9195
|
written.push(relative2);
|
|
8950
9196
|
}
|
|
8951
|
-
|
|
9197
|
+
if (options.noSkills === true) {
|
|
9198
|
+
return { written, preserved, productKind, packs: [], missing: [], harnessPath };
|
|
9199
|
+
}
|
|
9200
|
+
const current = await readHarnessManifest(root);
|
|
9201
|
+
const requested = options.packs ?? packNamesInCanonicalOrder([
|
|
9202
|
+
...packsForProduct(productKind),
|
|
9203
|
+
...current?.packs.map((pack) => pack.name) ?? []
|
|
9204
|
+
]);
|
|
9205
|
+
const provision = await provisionSkillPacks(root, requested, productKind);
|
|
9206
|
+
return {
|
|
9207
|
+
written: [...written, ...provision.written],
|
|
9208
|
+
preserved: [...preserved, ...provision.preserved],
|
|
9209
|
+
productKind,
|
|
9210
|
+
packs: provision.packs,
|
|
9211
|
+
missing: provision.missing,
|
|
9212
|
+
harnessPath: provision.harnessPath
|
|
9213
|
+
};
|
|
9214
|
+
}
|
|
9215
|
+
function parseSkillPackArgument(value) {
|
|
9216
|
+
if (value === "all") return [...SKILL_PACK_NAMES];
|
|
9217
|
+
return isSkillPackName(value) ? [value] : null;
|
|
9218
|
+
}
|
|
9219
|
+
async function addSkillPacks(bundleDir, packNames, options = {}) {
|
|
9220
|
+
const root = resolve2(bundleDir);
|
|
9221
|
+
const existing = await readHarnessManifest(root);
|
|
9222
|
+
const productKind = options.productKind ?? existing?.productKind ?? await detectProductKind(root);
|
|
9223
|
+
const requested = packNamesInCanonicalOrder([
|
|
9224
|
+
...existing?.packs.map((pack) => pack.name) ?? [],
|
|
9225
|
+
...packNames
|
|
9226
|
+
]);
|
|
9227
|
+
const provision = await provisionSkillPacks(root, requested, productKind);
|
|
9228
|
+
return { ...provision, added: packNamesInCanonicalOrder(packNames) };
|
|
9229
|
+
}
|
|
9230
|
+
async function updateSkillPacks(bundleDir) {
|
|
9231
|
+
const root = resolve2(bundleDir);
|
|
9232
|
+
const existing = await readHarnessManifest(root);
|
|
9233
|
+
if (existing === null) return null;
|
|
9234
|
+
return provisionSkillPacks(
|
|
9235
|
+
root,
|
|
9236
|
+
existing.packs.map((pack) => pack.name),
|
|
9237
|
+
existing.productKind
|
|
9238
|
+
);
|
|
9239
|
+
}
|
|
9240
|
+
async function removeSkillPack(bundleDir, pack) {
|
|
9241
|
+
const root = resolve2(bundleDir);
|
|
9242
|
+
const existing = await readHarnessManifest(root);
|
|
9243
|
+
const removed = [];
|
|
9244
|
+
const preserved = [];
|
|
9245
|
+
if (existing === null) return { removed, preserved, packs: [] };
|
|
9246
|
+
const entry = existing.packs.find((candidate) => candidate.name === pack);
|
|
9247
|
+
if (entry !== void 0) {
|
|
9248
|
+
for (const skill of entry.skills) {
|
|
9249
|
+
for (const dir of AGENT_SKILL_DIRS) {
|
|
9250
|
+
const targetDir = join4(root, dir, skill.name);
|
|
9251
|
+
const skillMd = join4(targetDir, "SKILL.md");
|
|
9252
|
+
if (!await isFile(skillMd)) continue;
|
|
9253
|
+
const targetHash = await hashSkillDirectory(targetDir);
|
|
9254
|
+
if (targetHash !== skill.sha256) {
|
|
9255
|
+
preserved.push(`${dir}/${skill.name}/SKILL.md`);
|
|
9256
|
+
continue;
|
|
9257
|
+
}
|
|
9258
|
+
await rm3(targetDir, { recursive: true, force: true });
|
|
9259
|
+
removed.push(`${dir}/${skill.name}/SKILL.md`);
|
|
9260
|
+
}
|
|
9261
|
+
}
|
|
9262
|
+
}
|
|
9263
|
+
const packs = existing.packs.filter((candidate) => candidate.name !== pack);
|
|
9264
|
+
await writeHarnessManifest(root, {
|
|
9265
|
+
...existing,
|
|
9266
|
+
packs,
|
|
9267
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9268
|
+
});
|
|
9269
|
+
return { removed, preserved, packs };
|
|
9270
|
+
}
|
|
9271
|
+
function skillsDisabled(options) {
|
|
9272
|
+
if (options.noSkills === true) return true;
|
|
9273
|
+
const value = process.env.JIOOGEN_NO_SKILLS?.trim().toLowerCase();
|
|
9274
|
+
return value === "1" || value === "true" || value === "yes";
|
|
9275
|
+
}
|
|
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");
|
|
8952
9773
|
}
|
|
8953
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
|
+
|
|
8954
9779
|
// src/tasks.ts
|
|
8955
|
-
import { readdir as
|
|
8956
|
-
import { join as
|
|
9780
|
+
import { readdir as readdir4, readFile as readFile6 } from "node:fs/promises";
|
|
9781
|
+
import { join as join6 } from "node:path";
|
|
8957
9782
|
var TASK_ID_RE2 = /\b(?:TASK|T)-\d{3,}\b/i;
|
|
8958
9783
|
var HEADING_RE = /^#{1,4}\s+(.+)$/m;
|
|
8959
9784
|
var LAYER_RANK = {
|
|
@@ -8964,13 +9789,51 @@ var LAYER_RANK = {
|
|
|
8964
9789
|
verification: 4
|
|
8965
9790
|
};
|
|
8966
9791
|
var IN_PROGRESS = ["in_progress", "verifying"];
|
|
9792
|
+
var QUALITY_HEADING = "## KUALITAS WAJIB (ringkas; detail di skill bila tersedia)";
|
|
9793
|
+
var MAX_CHECKLIST_LINES = 12;
|
|
9794
|
+
var UI_SKILLS = ["ui-ux-pro-max", "impeccable"];
|
|
9795
|
+
var LAYER_CHECKLISTS = {
|
|
9796
|
+
frontend_mock: [
|
|
9797
|
+
"- Reuse token desain yang ada; jangan hardcode warna/spacing bila token tersedia.",
|
|
9798
|
+
"- Tangani semua state UI: loading, empty, error, success.",
|
|
9799
|
+
"- Responsive untuk mobile dan desktop.",
|
|
9800
|
+
"- Aksesibilitas dasar: label, aria, focus, keyboard.",
|
|
9801
|
+
"- Microcopy jelas dan konsisten dengan konteks."
|
|
9802
|
+
],
|
|
9803
|
+
database: [
|
|
9804
|
+
"- Migrasi eksplisit dan aman (reversible, tanpa mengubah migrasi lama).",
|
|
9805
|
+
"- Constraint dan index sesuai pola query.",
|
|
9806
|
+
"- Relasi dan isolation sesuai specs (tenant/owner).",
|
|
9807
|
+
"- Seed idempotent (bisa dijalankan ulang tanpa duplikasi).",
|
|
9808
|
+
"- Tanpa query lintas pemilik."
|
|
9809
|
+
],
|
|
9810
|
+
backend_api: [
|
|
9811
|
+
"- Validasi input di boundary.",
|
|
9812
|
+
"- Error contract konsisten (status + bentuk response).",
|
|
9813
|
+
"- Auth/permission dicek sebelum mutasi.",
|
|
9814
|
+
"- Idempotensi untuk operasi tulis/retry.",
|
|
9815
|
+
"- Logging tanpa secret/PII."
|
|
9816
|
+
],
|
|
9817
|
+
integration: [
|
|
9818
|
+
"- Mock dihapus dari jalur utama.",
|
|
9819
|
+
"- Kontrak CONTRACT-XXX sama dengan implementasi.",
|
|
9820
|
+
"- E2E memakai data nyata.",
|
|
9821
|
+
"- Error path diuji."
|
|
9822
|
+
],
|
|
9823
|
+
verification: [
|
|
9824
|
+
"- Jalankan semua verificationCommand sampai exit 0.",
|
|
9825
|
+
"- Smoke route/navigasi utama.",
|
|
9826
|
+
"- UI states dan responsive dicek.",
|
|
9827
|
+
"- Bukti Acceptance Criteria \xA711."
|
|
9828
|
+
]
|
|
9829
|
+
};
|
|
8967
9830
|
async function loadTasks(bundleDir) {
|
|
8968
|
-
const tasksDir =
|
|
9831
|
+
const tasksDir = join6(bundleDir, "tasks");
|
|
8969
9832
|
let entries;
|
|
8970
9833
|
try {
|
|
8971
|
-
entries = await
|
|
9834
|
+
entries = await readdir4(tasksDir, { withFileTypes: true });
|
|
8972
9835
|
} catch (error) {
|
|
8973
|
-
if (
|
|
9836
|
+
if (isMissingFile5(error)) throw new Error(`Direktori tasks/ tidak ditemukan di ${bundleDir}`);
|
|
8974
9837
|
throw error;
|
|
8975
9838
|
}
|
|
8976
9839
|
const names = entries.filter(
|
|
@@ -8979,16 +9842,16 @@ async function loadTasks(bundleDir) {
|
|
|
8979
9842
|
const tasks = [];
|
|
8980
9843
|
if (names.length > 0) {
|
|
8981
9844
|
for (const name of names) {
|
|
8982
|
-
const content = await
|
|
9845
|
+
const content = await readFile6(join6(tasksDir, name), "utf8");
|
|
8983
9846
|
tasks.push(parseSingleTask(name, content));
|
|
8984
9847
|
}
|
|
8985
9848
|
} else {
|
|
8986
|
-
const singlePath =
|
|
9849
|
+
const singlePath = join6(tasksDir, "TASKS.md");
|
|
8987
9850
|
let content;
|
|
8988
9851
|
try {
|
|
8989
|
-
content = await
|
|
9852
|
+
content = await readFile6(singlePath, "utf8");
|
|
8990
9853
|
} catch (error) {
|
|
8991
|
-
if (
|
|
9854
|
+
if (isMissingFile5(error)) throw new Error(`Tidak ada berkas task di ${tasksDir}`);
|
|
8992
9855
|
throw error;
|
|
8993
9856
|
}
|
|
8994
9857
|
tasks.push(...parseSingleFile(content));
|
|
@@ -9041,7 +9904,7 @@ function buildTask(id, heading, file, content) {
|
|
|
9041
9904
|
dependsOn: context.dependsOn
|
|
9042
9905
|
};
|
|
9043
9906
|
}
|
|
9044
|
-
function renderActiveTask(task) {
|
|
9907
|
+
function renderActiveTask(task, context) {
|
|
9045
9908
|
const lines = [
|
|
9046
9909
|
"## TASK AKTIF \u2014 PRIORITASKAN INI (satu task per giliran)",
|
|
9047
9910
|
`ref: ${task.id}`,
|
|
@@ -9058,14 +9921,23 @@ function renderActiveTask(task) {
|
|
|
9058
9921
|
"- Jangan mengarang fitur/endpoint/kolom di luar PRD.md/specs/.",
|
|
9059
9922
|
"- Detail implementasi (pola, struktur file dalam scope, library yang sudah ada) milikmu; ikuti konvensi proyek.",
|
|
9060
9923
|
"- Jangan mengubah PRD.md/specs/kontrak. Butuh perubahan kontrak? STOP dan laporkan.",
|
|
9061
|
-
"- Selesai: `npx jioogen task complete <ref>`, lalu `npx jioogen sync`."
|
|
9062
|
-
"",
|
|
9063
|
-
"--- ISI TASK (otoritas scope) ---",
|
|
9064
|
-
task.content,
|
|
9065
|
-
"--- AKHIR ISI TASK ---"
|
|
9924
|
+
"- Selesai: `npx jioogen task complete <ref>`, lalu `npx jioogen sync`."
|
|
9066
9925
|
);
|
|
9926
|
+
const checklist = renderQualityChecklist(task.layer, context);
|
|
9927
|
+
if (checklist.length > 0) lines.push("", ...checklist);
|
|
9928
|
+
lines.push("", "--- ISI TASK (otoritas scope) ---", task.content, "--- AKHIR ISI TASK ---");
|
|
9067
9929
|
return lines.join("\n");
|
|
9068
9930
|
}
|
|
9931
|
+
function renderQualityChecklist(layer, context) {
|
|
9932
|
+
if (context === void 0 || layer === void 0) return [];
|
|
9933
|
+
const items = [...LAYER_CHECKLISTS[layer] ?? []];
|
|
9934
|
+
if (layer === "frontend_mock") {
|
|
9935
|
+
const references = UI_SKILLS.filter((skill) => context.skills?.includes(skill) === true);
|
|
9936
|
+
if (references.length > 0) items.push(`- Referensi skill: ${references.join(", ")}`);
|
|
9937
|
+
}
|
|
9938
|
+
if (items.length === 0) return [];
|
|
9939
|
+
return [QUALITY_HEADING, ...items.slice(0, MAX_CHECKLIST_LINES)];
|
|
9940
|
+
}
|
|
9069
9941
|
function resolveLayer(value) {
|
|
9070
9942
|
if (value === void 0) return void 0;
|
|
9071
9943
|
const token = value.trim().replace(/[`*]/g, "").split(/[\s,(]/)[0];
|
|
@@ -9149,16 +10021,194 @@ function isBoundary(previous, next) {
|
|
|
9149
10021
|
}
|
|
9150
10022
|
return previous.phase !== void 0 && next.phase !== previous.phase;
|
|
9151
10023
|
}
|
|
9152
|
-
|
|
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) {
|
|
9153
10203
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
9154
10204
|
}
|
|
9155
10205
|
|
|
9156
10206
|
// src/zip.ts
|
|
9157
10207
|
var import_adm_zip = __toESM(require_adm_zip(), 1);
|
|
9158
10208
|
import { mkdir as mkdir4, writeFile as writeFile4 } from "node:fs/promises";
|
|
9159
|
-
import { dirname as dirname4, resolve as
|
|
10209
|
+
import { dirname as dirname4, resolve as resolve4, sep } from "node:path";
|
|
9160
10210
|
async function extractBundle(bytes, targetDir) {
|
|
9161
|
-
const root =
|
|
10211
|
+
const root = resolve4(targetDir);
|
|
9162
10212
|
const zip = new import_adm_zip.default(Buffer.from(bytes));
|
|
9163
10213
|
const written = [];
|
|
9164
10214
|
for (const entry of zip.getEntries()) {
|
|
@@ -9168,7 +10218,7 @@ async function extractBundle(bytes, targetDir) {
|
|
|
9168
10218
|
if (segments.some((segment) => segment.length === 0 || segment === "." || segment === "..")) {
|
|
9169
10219
|
throw new Error(`Path bundle tidak aman: ${entry.entryName}`);
|
|
9170
10220
|
}
|
|
9171
|
-
const destination =
|
|
10221
|
+
const destination = resolve4(root, relative2);
|
|
9172
10222
|
if (!destination.startsWith(`${root}${sep}`)) {
|
|
9173
10223
|
throw new Error(`Path bundle keluar dari direktori target: ${entry.entryName}`);
|
|
9174
10224
|
}
|
|
@@ -9185,10 +10235,10 @@ function isValidProjectId(projectId) {
|
|
|
9185
10235
|
return PROJECT_ID_RE.test(projectId.trim());
|
|
9186
10236
|
}
|
|
9187
10237
|
async function loginCommand(options) {
|
|
9188
|
-
const token = options.token ??
|
|
10238
|
+
const token = options.token ?? envFirst2("JIOOGEN_TOKEN", "JIOOCODE_TOKEN");
|
|
9189
10239
|
if (token !== void 0 && token.length > 0) {
|
|
9190
10240
|
const apiUrl = normalizeBaseUrl(
|
|
9191
|
-
options.api ??
|
|
10241
|
+
options.api ?? envFirst2("JIOOGEN_API", "JIOOCODE_API") ?? DEFAULT_API_URL
|
|
9192
10242
|
);
|
|
9193
10243
|
const client = new ApiClient(apiUrl, `${SESSION_COOKIE}=${token}`);
|
|
9194
10244
|
const body = await client.getJson(ENDPOINTS.me);
|
|
@@ -9203,17 +10253,17 @@ async function loginCommand(options) {
|
|
|
9203
10253
|
console.log(`Credential disimpan di ${credentialsPath()} (mode 0600).`);
|
|
9204
10254
|
return;
|
|
9205
10255
|
}
|
|
9206
|
-
const email = options.email ??
|
|
10256
|
+
const email = options.email ?? envFirst2("JIOOGEN_EMAIL", "JIOOCODE_EMAIL");
|
|
9207
10257
|
if (email === void 0 || email.length === 0) {
|
|
9208
10258
|
throw new Error("--email wajib diisi (atau set JIOOGEN_EMAIL).");
|
|
9209
10259
|
}
|
|
9210
|
-
let password = options.password ??
|
|
10260
|
+
let password = options.password ?? envFirst2("JIOOGEN_PASSWORD", "JIOOCODE_PASSWORD");
|
|
9211
10261
|
if (options.passwordStdin === true) password = (await readStdin()).trim();
|
|
9212
10262
|
if (password === void 0 || password.length === 0) {
|
|
9213
10263
|
throw new Error("--password atau --password-stdin wajib diisi.");
|
|
9214
10264
|
}
|
|
9215
10265
|
const result = await apiLogin(
|
|
9216
|
-
options.api ??
|
|
10266
|
+
options.api ?? envFirst2("JIOOGEN_API", "JIOOCODE_API") ?? DEFAULT_API_URL,
|
|
9217
10267
|
email,
|
|
9218
10268
|
password
|
|
9219
10269
|
);
|
|
@@ -9271,11 +10321,11 @@ async function pullCommand(projectId, options) {
|
|
|
9271
10321
|
}
|
|
9272
10322
|
await assertPullDefaultDir(process.cwd(), options.dir);
|
|
9273
10323
|
const { apiUrl, client } = await resolveApi(options);
|
|
9274
|
-
const target =
|
|
10324
|
+
const target = resolve5(options.dir ?? projectId);
|
|
9275
10325
|
await assertPullTarget(target, projectId, options.force === true);
|
|
9276
10326
|
const bytes = await client.getBytes(projectPath(projectId, "/bundle"));
|
|
9277
10327
|
const written = await extractBundle(bytes, target);
|
|
9278
|
-
const skills = await installAgentSkills(target);
|
|
10328
|
+
const skills = await installAgentSkills(target, { noSkills: skillsDisabled(options) });
|
|
9279
10329
|
await writeProjectLink(target, { projectId, apiUrl });
|
|
9280
10330
|
if (options.json === true) {
|
|
9281
10331
|
console.log(JSON.stringify({ projectId, target, files: written, skills }, null, 2));
|
|
@@ -9283,36 +10333,29 @@ async function pullCommand(projectId, options) {
|
|
|
9283
10333
|
}
|
|
9284
10334
|
console.log(`Pulled ${written.length} file ke ${target}`);
|
|
9285
10335
|
console.log("Project ter-bind lewat .jioogen/project.json.");
|
|
9286
|
-
console.log(
|
|
9287
|
-
|
|
9288
|
-
);
|
|
10336
|
+
console.log(`Skills eksekusi terpasang: ${skills.written.length} file.`);
|
|
10337
|
+
printSkillPacks(skills.packs, skills.missing);
|
|
9289
10338
|
}
|
|
9290
10339
|
async function linkCommand(projectId, options) {
|
|
9291
10340
|
const { apiUrl, client } = await resolveApi(options);
|
|
9292
|
-
const target =
|
|
10341
|
+
const target = resolve5(options.dir ?? process.cwd());
|
|
9293
10342
|
await assertLinkTarget(target, projectId);
|
|
9294
10343
|
await verifyProjectAccess(client, projectId);
|
|
9295
10344
|
await writeProjectLink(target, { projectId, apiUrl });
|
|
9296
|
-
const skills = await installAgentSkills(target);
|
|
9297
|
-
const linkedFile =
|
|
10345
|
+
const skills = await installAgentSkills(target, { noSkills: skillsDisabled(options) });
|
|
10346
|
+
const linkedFile = join7(target, ".jioogen", "project.json");
|
|
9298
10347
|
if (options.json === true) {
|
|
9299
|
-
console.log(
|
|
9300
|
-
JSON.stringify(
|
|
9301
|
-
{ projectId, target, apiUrl, linkedFile, skills: skills.written.length },
|
|
9302
|
-
null,
|
|
9303
|
-
2
|
|
9304
|
-
)
|
|
9305
|
-
);
|
|
10348
|
+
console.log(JSON.stringify({ projectId, target, apiUrl, linkedFile, skills }, null, 2));
|
|
9306
10349
|
return;
|
|
9307
10350
|
}
|
|
9308
10351
|
console.log(`Project ${projectId} ter-bind ke ${target}.`);
|
|
9309
10352
|
console.log(`File link: ${linkedFile} (API ${apiUrl}).`);
|
|
9310
10353
|
console.log(`Skills eksekusi terpasang: ${skills.written.length} file.`);
|
|
10354
|
+
printSkillPacks(skills.packs, skills.missing);
|
|
9311
10355
|
}
|
|
9312
10356
|
async function skillsInstallCommand(options) {
|
|
9313
|
-
const
|
|
9314
|
-
const
|
|
9315
|
-
const skills = await installAgentSkills(bundleDir);
|
|
10357
|
+
const bundleDir = await resolveSkillsBundleDir(options);
|
|
10358
|
+
const skills = await installAgentSkills(bundleDir, { noSkills: skillsDisabled(options) });
|
|
9316
10359
|
if (options.json === true) {
|
|
9317
10360
|
console.log(JSON.stringify({ bundleDir, ...skills }, null, 2));
|
|
9318
10361
|
return;
|
|
@@ -9320,10 +10363,94 @@ async function skillsInstallCommand(options) {
|
|
|
9320
10363
|
console.log(`Skills eksekusi terpasang di ${bundleDir}:`);
|
|
9321
10364
|
for (const file of skills.written) console.log(`- ${file}`);
|
|
9322
10365
|
for (const file of skills.preserved) console.log(`- ${file} (dilewati: milik user)`);
|
|
10366
|
+
printSkillPacks(skills.packs, skills.missing);
|
|
10367
|
+
}
|
|
10368
|
+
async function skillsListCommand(options) {
|
|
10369
|
+
const bundleDir = await resolveSkillsBundleDir(options);
|
|
10370
|
+
const manifest = await readHarnessManifest(bundleDir);
|
|
10371
|
+
if (options.json === true) {
|
|
10372
|
+
console.log(JSON.stringify({ bundleDir, harness: manifest }, null, 2));
|
|
10373
|
+
return;
|
|
10374
|
+
}
|
|
10375
|
+
if (manifest === null) {
|
|
10376
|
+
console.log(`Belum ada skill pack terpasang di ${bundleDir}.`);
|
|
10377
|
+
return;
|
|
10378
|
+
}
|
|
10379
|
+
console.log(`Bundle: ${bundleDir}`);
|
|
10380
|
+
console.log(`Product: ${manifest.productKind} | updated: ${manifest.updatedAt}`);
|
|
10381
|
+
for (const pack of manifest.packs) {
|
|
10382
|
+
console.log(`${pack.name}:`);
|
|
10383
|
+
for (const skill of pack.skills) {
|
|
10384
|
+
console.log(`- ${skill.name} (installed, ${skill.sha256.slice(0, 12)})`);
|
|
10385
|
+
}
|
|
10386
|
+
for (const name of pack.missing) console.log(`- ${name} (missing)`);
|
|
10387
|
+
if (pack.skills.length === 0 && pack.missing.length === 0) console.log("- (tidak ada skill)");
|
|
10388
|
+
}
|
|
10389
|
+
}
|
|
10390
|
+
async function skillsAddCommand(packArgument, options) {
|
|
10391
|
+
const packNames = parseSkillPackArgument(packArgument);
|
|
10392
|
+
if (packNames === null) {
|
|
10393
|
+
throw new UsageError("Pack tidak dikenal. Pilih: baseline | ui-quality | backend-rules | all.");
|
|
10394
|
+
}
|
|
10395
|
+
const bundleDir = await resolveSkillsBundleDir(options);
|
|
10396
|
+
const result = await addSkillPacks(bundleDir, packNames);
|
|
10397
|
+
if (options.json === true) {
|
|
10398
|
+
console.log(JSON.stringify({ bundleDir, ...result }, null, 2));
|
|
10399
|
+
return;
|
|
10400
|
+
}
|
|
10401
|
+
console.log(`Skill pack ditambahkan ke ${bundleDir}: ${result.added.join(", ")}`);
|
|
10402
|
+
printSkillPacks(result.packs, result.missing);
|
|
10403
|
+
}
|
|
10404
|
+
async function skillsUpdateCommand(options) {
|
|
10405
|
+
const bundleDir = await resolveSkillsBundleDir(options);
|
|
10406
|
+
const result = await updateSkillPacks(bundleDir);
|
|
10407
|
+
if (result === null) {
|
|
10408
|
+
if (options.json === true) {
|
|
10409
|
+
console.log(JSON.stringify({ bundleDir, harness: null }, null, 2));
|
|
10410
|
+
return;
|
|
10411
|
+
}
|
|
10412
|
+
console.log(`Belum ada skill pack tercatat di ${bundleDir}.`);
|
|
10413
|
+
return;
|
|
10414
|
+
}
|
|
10415
|
+
if (options.json === true) {
|
|
10416
|
+
console.log(JSON.stringify({ bundleDir, ...result }, null, 2));
|
|
10417
|
+
return;
|
|
10418
|
+
}
|
|
10419
|
+
console.log(`Skill pack diperbarui di ${bundleDir}.`);
|
|
10420
|
+
printSkillPacks(result.packs, result.missing);
|
|
10421
|
+
}
|
|
10422
|
+
async function skillsRemoveCommand(packArgument, options) {
|
|
10423
|
+
const packNames = parseSkillPackArgument(packArgument);
|
|
10424
|
+
if (packNames === null || packNames.length !== 1) {
|
|
10425
|
+
throw new UsageError("Pack tidak dikenal. Pilih: baseline | ui-quality | backend-rules.");
|
|
10426
|
+
}
|
|
10427
|
+
const pack = packNames[0];
|
|
10428
|
+
const bundleDir = await resolveSkillsBundleDir(options);
|
|
10429
|
+
const result = await removeSkillPack(bundleDir, pack);
|
|
10430
|
+
if (options.json === true) {
|
|
10431
|
+
console.log(JSON.stringify({ bundleDir, pack, ...result }, null, 2));
|
|
10432
|
+
return;
|
|
10433
|
+
}
|
|
10434
|
+
console.log(`Skill pack ${pack} dihapus dari ${bundleDir}: ${result.removed.length} file.`);
|
|
10435
|
+
for (const file of result.preserved) {
|
|
10436
|
+
console.log(`- ${file} (dilewati: milik user / sudah berubah)`);
|
|
10437
|
+
}
|
|
10438
|
+
}
|
|
10439
|
+
async function resolveSkillsBundleDir(options) {
|
|
10440
|
+
const link = await findProjectLink(options.dir ?? process.cwd());
|
|
10441
|
+
return resolve5(link?.bundleDir ?? options.dir ?? process.cwd());
|
|
10442
|
+
}
|
|
10443
|
+
function printSkillPacks(packs, missing) {
|
|
10444
|
+
if (packs.length === 0 && missing.length === 0) return;
|
|
10445
|
+
const installed = packs.filter((pack) => pack.skills.length > 0).map((pack) => `${pack.name} (${pack.skills.map((skill) => skill.name).join(", ")})`);
|
|
10446
|
+
if (installed.length > 0) console.log(`Skill pack: ${installed.join("; ")}.`);
|
|
10447
|
+
if (missing.length > 0) {
|
|
10448
|
+
console.warn(`Peringatan: skill tidak ditemukan (dilewati): ${missing.join(", ")}.`);
|
|
10449
|
+
}
|
|
9323
10450
|
}
|
|
9324
10451
|
async function checkCommand(options) {
|
|
9325
10452
|
const link = await findProjectLink(options.dir ?? process.cwd());
|
|
9326
|
-
const bundleDir =
|
|
10453
|
+
const bundleDir = resolve5(link?.bundleDir ?? options.dir ?? process.cwd());
|
|
9327
10454
|
const result = await inspectBundle(bundleDir);
|
|
9328
10455
|
if (options.json === true) {
|
|
9329
10456
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -9332,6 +10459,90 @@ async function checkCommand(options) {
|
|
|
9332
10459
|
}
|
|
9333
10460
|
if (!result.ok) process.exitCode = 1;
|
|
9334
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
|
+
}
|
|
9335
10546
|
async function handoffCommand(options) {
|
|
9336
10547
|
const { client, projectId } = await resolveBundle(options);
|
|
9337
10548
|
const payload = await client.postJson(
|
|
@@ -9348,15 +10559,19 @@ async function handoffCommand(options) {
|
|
|
9348
10559
|
}
|
|
9349
10560
|
}
|
|
9350
10561
|
async function taskNextCommand(options) {
|
|
9351
|
-
const { projectId, definitions, written, next } = await withLocalState(
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
|
|
9359
|
-
|
|
10562
|
+
const { projectId, bundleDir, definitions, written, next } = await withLocalState(
|
|
10563
|
+
options,
|
|
10564
|
+
async (local) => {
|
|
10565
|
+
const written2 = await writeState(local.bundleDir, local.state);
|
|
10566
|
+
return {
|
|
10567
|
+
projectId: local.projectId,
|
|
10568
|
+
bundleDir: local.bundleDir,
|
|
10569
|
+
definitions: local.definitions,
|
|
10570
|
+
written: written2,
|
|
10571
|
+
next: selectNext(local.definitions, written2.tasks)
|
|
10572
|
+
};
|
|
10573
|
+
}
|
|
10574
|
+
);
|
|
9360
10575
|
if (next === null) {
|
|
9361
10576
|
if (options.json === true) {
|
|
9362
10577
|
console.log(JSON.stringify({ done: true, projectId }, null, 2));
|
|
@@ -9373,7 +10588,12 @@ async function taskNextCommand(options) {
|
|
|
9373
10588
|
return;
|
|
9374
10589
|
}
|
|
9375
10590
|
const awaitingCheckpoint = written.current?.awaitingCheckpoint === true;
|
|
9376
|
-
const
|
|
10591
|
+
const manifest = await readHarnessManifest(bundleDir);
|
|
10592
|
+
const context = manifest === null ? void 0 : {
|
|
10593
|
+
productKind: manifest.productKind,
|
|
10594
|
+
skills: manifest.packs.flatMap((pack) => pack.skills.map((skill) => skill.name))
|
|
10595
|
+
};
|
|
10596
|
+
const instruction = renderActiveTask(next.task, context);
|
|
9377
10597
|
if (options.json === true) {
|
|
9378
10598
|
console.log(
|
|
9379
10599
|
JSON.stringify(
|
|
@@ -9427,11 +10647,17 @@ async function taskCompleteCommand(ref, options) {
|
|
|
9427
10647
|
(entry) => entry.ref.toUpperCase() === ref.toUpperCase()
|
|
9428
10648
|
);
|
|
9429
10649
|
const next2 = selectNext(local.definitions, completed.tasks);
|
|
9430
|
-
|
|
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;
|
|
9431
10657
|
const withCheckpoint = setCurrent(completed, {
|
|
9432
|
-
ref:
|
|
9433
|
-
|
|
9434
|
-
phase:
|
|
10658
|
+
ref: target?.id ?? completedTask2.ref,
|
|
10659
|
+
layer: target?.layer ?? completedTask2.layer,
|
|
10660
|
+
phase: target?.phase ?? completedTask2.phase ?? 1,
|
|
9435
10661
|
awaitingCheckpoint: true
|
|
9436
10662
|
});
|
|
9437
10663
|
await writeState(local.bundleDir, withCheckpoint);
|
|
@@ -9441,11 +10667,17 @@ async function taskCompleteCommand(ref, options) {
|
|
|
9441
10667
|
return { completedTask: completedTask2, next: next2, checkpoint: false };
|
|
9442
10668
|
});
|
|
9443
10669
|
console.log(`Done ${completedTask?.ref ?? ref}.`);
|
|
9444
|
-
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
|
+
}
|
|
9445
10678
|
console.log(
|
|
9446
|
-
|
|
10679
|
+
'Berhenti. Setelah user memutuskan: `jioogen review --approve`, `--request-changes "<teks>"`, atau `--stop`; lalu `jioogen sync`.'
|
|
9447
10680
|
);
|
|
9448
|
-
console.log("Berhenti dan minta konfirmasi user sebelum task start berikutnya.");
|
|
9449
10681
|
return;
|
|
9450
10682
|
}
|
|
9451
10683
|
if (next !== null) console.log(`Berikutnya: ${next.task.id} \u2014 ${next.task.title}`);
|
|
@@ -9547,7 +10779,7 @@ async function syncCommand(options) {
|
|
|
9547
10779
|
assertBundleRelative(relative2);
|
|
9548
10780
|
files.push({
|
|
9549
10781
|
path: relative2,
|
|
9550
|
-
content: await
|
|
10782
|
+
content: await readFile7(join7(bundleDir, relative2), "utf8")
|
|
9551
10783
|
});
|
|
9552
10784
|
}
|
|
9553
10785
|
}
|
|
@@ -9569,9 +10801,9 @@ async function syncCommand(options) {
|
|
|
9569
10801
|
async function resolveApi(options) {
|
|
9570
10802
|
const stored = await readCredentials();
|
|
9571
10803
|
const apiUrl = normalizeBaseUrl(
|
|
9572
|
-
options.api ??
|
|
10804
|
+
options.api ?? envFirst2("JIOOGEN_API", "JIOOCODE_API") ?? stored?.apiUrl ?? DEFAULT_API_URL
|
|
9573
10805
|
);
|
|
9574
|
-
const explicitCookie =
|
|
10806
|
+
const explicitCookie = envFirst2("JIOOGEN_COOKIE", "JIOOCODE_COOKIE");
|
|
9575
10807
|
const storedCookie = stored !== null && normalizeBaseUrl(stored.apiUrl) === apiUrl ? stored.cookie : void 0;
|
|
9576
10808
|
const cookie = explicitCookie || storedCookie;
|
|
9577
10809
|
if (cookie === void 0 || cookie.length === 0) {
|
|
@@ -9586,7 +10818,7 @@ async function resolveApi(options) {
|
|
|
9586
10818
|
}
|
|
9587
10819
|
async function resolveLocalBundle(options) {
|
|
9588
10820
|
const link = await findProjectLink(options.dir ?? process.cwd());
|
|
9589
|
-
const projectId = options.project ??
|
|
10821
|
+
const projectId = options.project ?? envFirst2("JIOOGEN_PROJECT", "JIOOCODE_PROJECT") ?? link?.projectId;
|
|
9590
10822
|
if (projectId === void 0 || projectId.length === 0) {
|
|
9591
10823
|
throw new Error(
|
|
9592
10824
|
"Project belum di-bind. Jalankan `jioogen pull <projectId>` atau pakai --project."
|
|
@@ -9594,7 +10826,7 @@ async function resolveLocalBundle(options) {
|
|
|
9594
10826
|
}
|
|
9595
10827
|
return {
|
|
9596
10828
|
projectId,
|
|
9597
|
-
bundleDir:
|
|
10829
|
+
bundleDir: resolve5(link?.bundleDir ?? options.dir ?? process.cwd()),
|
|
9598
10830
|
linkedApiUrl: link?.apiUrl
|
|
9599
10831
|
};
|
|
9600
10832
|
}
|
|
@@ -9647,9 +10879,9 @@ async function assertPullDefaultDir(cwd, dirOption) {
|
|
|
9647
10879
|
async function assertPullTarget(target, projectId, force = false) {
|
|
9648
10880
|
let entries;
|
|
9649
10881
|
try {
|
|
9650
|
-
entries = await
|
|
10882
|
+
entries = await readdir5(target);
|
|
9651
10883
|
} catch (error) {
|
|
9652
|
-
if (
|
|
10884
|
+
if (isMissingFile7(error)) return;
|
|
9653
10885
|
throw error;
|
|
9654
10886
|
}
|
|
9655
10887
|
if (entries.length === 0) return;
|
|
@@ -9681,19 +10913,19 @@ async function assertLinkTarget(target, projectId) {
|
|
|
9681
10913
|
}
|
|
9682
10914
|
async function hasBundleMarkers(target) {
|
|
9683
10915
|
try {
|
|
9684
|
-
await
|
|
10916
|
+
await readFile7(join7(target, "PRD.md"));
|
|
9685
10917
|
return true;
|
|
9686
10918
|
} catch (error) {
|
|
9687
|
-
if (!
|
|
10919
|
+
if (!isMissingFile7(error)) throw error;
|
|
9688
10920
|
}
|
|
9689
|
-
return hasTaskMarkdown(
|
|
10921
|
+
return hasTaskMarkdown(join7(target, "tasks"));
|
|
9690
10922
|
}
|
|
9691
10923
|
async function hasTaskMarkdown(directory) {
|
|
9692
10924
|
let names;
|
|
9693
10925
|
try {
|
|
9694
|
-
names = await
|
|
10926
|
+
names = await readdir5(directory);
|
|
9695
10927
|
} catch (error) {
|
|
9696
|
-
if (
|
|
10928
|
+
if (isMissingFile7(error)) return false;
|
|
9697
10929
|
throw error;
|
|
9698
10930
|
}
|
|
9699
10931
|
return names.some((name) => name.toLowerCase().endsWith(".md"));
|
|
@@ -9749,18 +10981,18 @@ async function readStdin() {
|
|
|
9749
10981
|
for await (const chunk of process.stdin) chunks.push(Buffer.from(chunk));
|
|
9750
10982
|
return Buffer.concat(chunks).toString("utf8");
|
|
9751
10983
|
}
|
|
9752
|
-
function
|
|
10984
|
+
function envFirst2(primary, legacy) {
|
|
9753
10985
|
const value = process.env[primary]?.trim();
|
|
9754
10986
|
if (value !== void 0 && value.length > 0) return value;
|
|
9755
10987
|
const fallback = process.env[legacy]?.trim();
|
|
9756
10988
|
return fallback !== void 0 && fallback.length > 0 ? fallback : void 0;
|
|
9757
10989
|
}
|
|
9758
|
-
function
|
|
10990
|
+
function isMissingFile7(error) {
|
|
9759
10991
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
9760
10992
|
}
|
|
9761
10993
|
|
|
9762
10994
|
// src/index.ts
|
|
9763
|
-
var VERSION = "0.2.
|
|
10995
|
+
var VERSION = "0.2.5";
|
|
9764
10996
|
var USAGE = `jioogen \u2014 CLI JiooGen (task selection lokal, server read-only)
|
|
9765
10997
|
|
|
9766
10998
|
Usage:
|
|
@@ -9769,10 +11001,17 @@ Usage:
|
|
|
9769
11001
|
jioogen logout
|
|
9770
11002
|
jioogen whoami
|
|
9771
11003
|
jioogen projects
|
|
9772
|
-
jioogen pull <projectId> [--dir <dir>] [--force]
|
|
9773
|
-
jioogen link <projectId> [--dir <dir>]
|
|
9774
|
-
jioogen skills install [--dir <dir>]
|
|
11004
|
+
jioogen pull <projectId> [--dir <dir>] [--force] [--no-skills]
|
|
11005
|
+
jioogen link <projectId> [--dir <dir>] [--no-skills]
|
|
11006
|
+
jioogen skills install [--dir <dir>] [--no-skills]
|
|
11007
|
+
jioogen skills list [--dir <dir>] [--json]
|
|
11008
|
+
jioogen skills add <baseline|ui-quality|backend-rules|all> [--dir <dir>]
|
|
11009
|
+
jioogen skills update [--dir <dir>]
|
|
11010
|
+
jioogen skills remove <baseline|ui-quality|backend-rules> [--dir <dir>]
|
|
9775
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]
|
|
9776
11015
|
jioogen handoff [--project <id>] [--json]
|
|
9777
11016
|
jioogen task next [--json]
|
|
9778
11017
|
jioogen task start <ref>
|
|
@@ -9789,6 +11028,7 @@ Global options:
|
|
|
9789
11028
|
--api <url> Base URL JiooGen (env JIOOGEN_API / JIOOCODE_API / tersimpan)
|
|
9790
11029
|
--project <id> Override binding project
|
|
9791
11030
|
--dir <dir> Override direktori bundle
|
|
11031
|
+
--no-skills Jangan pasang skill pack (atau env JIOOGEN_NO_SKILLS=1)
|
|
9792
11032
|
-h, --help Tampilkan bantuan
|
|
9793
11033
|
-v, --version Tampilkan versi
|
|
9794
11034
|
`;
|
|
@@ -9809,8 +11049,14 @@ async function main() {
|
|
|
9809
11049
|
dir: { type: "string" },
|
|
9810
11050
|
remote: { type: "boolean" },
|
|
9811
11051
|
force: { type: "boolean" },
|
|
11052
|
+
"no-skills": { type: "boolean" },
|
|
9812
11053
|
files: { type: "string" },
|
|
9813
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" },
|
|
9814
11060
|
json: { type: "boolean" },
|
|
9815
11061
|
help: { type: "boolean", short: "h" },
|
|
9816
11062
|
version: { type: "boolean", short: "v" }
|
|
@@ -9835,8 +11081,14 @@ async function main() {
|
|
|
9835
11081
|
dir: text(raw.dir),
|
|
9836
11082
|
remote: flag(raw.remote),
|
|
9837
11083
|
force: flag(raw.force),
|
|
11084
|
+
noSkills: flag(raw["no-skills"]),
|
|
9838
11085
|
files: text(raw.files),
|
|
9839
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),
|
|
9840
11092
|
json: flag(raw.json)
|
|
9841
11093
|
};
|
|
9842
11094
|
if (flag(raw.version)) {
|
|
@@ -9899,6 +11151,15 @@ async function dispatch(command, rest, options) {
|
|
|
9899
11151
|
case "check":
|
|
9900
11152
|
await checkCommand(options);
|
|
9901
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;
|
|
9902
11163
|
case "handoff":
|
|
9903
11164
|
await handoffCommand(options);
|
|
9904
11165
|
return;
|
|
@@ -9925,8 +11186,30 @@ async function dispatchSkills(rest, options) {
|
|
|
9925
11186
|
case "install":
|
|
9926
11187
|
await skillsInstallCommand(options);
|
|
9927
11188
|
return;
|
|
11189
|
+
case "list":
|
|
11190
|
+
await skillsListCommand(options);
|
|
11191
|
+
return;
|
|
11192
|
+
case "add": {
|
|
11193
|
+
const pack = rest[1];
|
|
11194
|
+
if (pack === void 0) {
|
|
11195
|
+
throw new UsageError("`jioogen skills add` butuh <baseline|ui-quality|backend-rules|all>.");
|
|
11196
|
+
}
|
|
11197
|
+
await skillsAddCommand(pack, options);
|
|
11198
|
+
return;
|
|
11199
|
+
}
|
|
11200
|
+
case "update":
|
|
11201
|
+
await skillsUpdateCommand(options);
|
|
11202
|
+
return;
|
|
11203
|
+
case "remove": {
|
|
11204
|
+
const pack = rest[1];
|
|
11205
|
+
if (pack === void 0) {
|
|
11206
|
+
throw new UsageError("`jioogen skills remove` butuh <baseline|ui-quality|backend-rules>.");
|
|
11207
|
+
}
|
|
11208
|
+
await skillsRemoveCommand(pack, options);
|
|
11209
|
+
return;
|
|
11210
|
+
}
|
|
9928
11211
|
default:
|
|
9929
|
-
throw new UsageError("Subcommand skills: install");
|
|
11212
|
+
throw new UsageError("Subcommand skills: install | list | add | update | remove");
|
|
9930
11213
|
}
|
|
9931
11214
|
}
|
|
9932
11215
|
async function dispatchTask(rest, options) {
|