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 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 stat2;
366
+ var stat5;
367
367
  try {
368
- stat2 = self.fs.statSync(resolvedPath);
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 (stat2 && stat2.isFile()) throw Errors.FILE_IN_THE_WAY(`"${resolvedPath}"`);
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 stat2 = self.fs.statSync(path);
386
- if (stat2.isDirectory()) {
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, stat2) {
420
- if (exist && stat2 && stat2.isDirectory()) {
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 stat2;
472
+ let stat5;
473
473
  try {
474
- stat2 = self.fs.lstatSync(cur);
474
+ stat5 = self.fs.lstatSync(cur);
475
475
  } catch (e) {
476
476
  break;
477
477
  }
478
- if (stat2.isSymbolicLink()) throw Errors.FILE_IN_THE_WAY(`"${cur}"`);
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 stat2 = self.fs.statSync(path2);
505
+ const stat5 = self.fs.statSync(path2);
506
506
  if (!pattern || pattern.test(path2)) {
507
- files.push(pth.normalize(path2) + (stat2.isDirectory() ? self.sep : ""));
507
+ files.push(pth.normalize(path2) + (stat5.isDirectory() ? self.sep : ""));
508
508
  }
509
- if (stat2.isDirectory() && recursive) {
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, stat2) {
556
+ self.fs.stat(file, function(err3, stat5) {
557
557
  if (err3) return done(err3);
558
- if (!stat2) {
558
+ if (!stat5) {
559
559
  if (!--pending) done();
560
560
  return;
561
561
  }
562
- results.push(pth.normalize(file) + (stat2.isDirectory() ? self.sep : ""));
563
- if (!stat2.isDirectory()) {
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: join7, normalize, sep: sep2 } = pth.posix;
2128
- return join7(pth.isAbsolute(zipPath) ? "/" : ".", normalize(sep2 + zipPath.split("\\").join(sep2) + sep2));
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((resolve5, reject) => {
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) resolve5(this);
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((resolve5, reject) => {
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
- resolve5(this);
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((resolve5, reject) => {
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 ? resolve5(done) : reject("ADM-ZIP: Wasn't able to write zip file");
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((resolve5, reject) => {
2853
- _zip.toAsyncBuffer(resolve5, reject);
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 readdir3, readFile as readFile6 } from "node:fs/promises";
2982
- import { join as join6, resolve as resolve4 } from "node:path";
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 errorMessage = "";
3475
+ let errorMessage2 = "";
3476
3476
  const maps = errorMaps.filter((m) => !!m).slice().reverse();
3477
3477
  for (const map of maps) {
3478
- errorMessage = map(fullIssue, { data, defaultError: errorMessage }).message;
3478
+ errorMessage2 = map(fullIssue, { data, defaultError: errorMessage2 }).message;
3479
3479
  }
3480
3480
  return {
3481
3481
  ...issueData,
3482
3482
  path: fullPath,
3483
- message: errorMessage
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/errors.ts
8566
- var UsageError = class extends Error {
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((resolve5) => setTimeout(resolve5, ms));
8755
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
8766
8756
  }
8767
8757
 
8768
8758
  // src/skills.ts
8769
- import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
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, atau task bertanda \`checkpoint: true\`. Jangan lanjut sebelum user
8828
- mengetik "lanjut".
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 mengetik "lanjut". Set \`current.awaitingCheckpoint = true\`
8909
- di sidecar saat berhenti.
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
- async function installAgentSkills(bundleDir) {
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
- return { written, preserved };
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 readdir2, readFile as readFile5 } from "node:fs/promises";
8956
- import { join as join5 } from "node:path";
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 = join5(bundleDir, "tasks");
9831
+ const tasksDir = join6(bundleDir, "tasks");
8969
9832
  let entries;
8970
9833
  try {
8971
- entries = await readdir2(tasksDir, { withFileTypes: true });
9834
+ entries = await readdir4(tasksDir, { withFileTypes: true });
8972
9835
  } catch (error) {
8973
- if (isMissingFile3(error)) throw new Error(`Direktori tasks/ tidak ditemukan di ${bundleDir}`);
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 readFile5(join5(tasksDir, name), "utf8");
9845
+ const content = await readFile6(join6(tasksDir, name), "utf8");
8983
9846
  tasks.push(parseSingleTask(name, content));
8984
9847
  }
8985
9848
  } else {
8986
- const singlePath = join5(tasksDir, "TASKS.md");
9849
+ const singlePath = join6(tasksDir, "TASKS.md");
8987
9850
  let content;
8988
9851
  try {
8989
- content = await readFile5(singlePath, "utf8");
9852
+ content = await readFile6(singlePath, "utf8");
8990
9853
  } catch (error) {
8991
- if (isMissingFile3(error)) throw new Error(`Tidak ada berkas task di ${tasksDir}`);
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
- function isMissingFile3(error) {
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 resolve3, sep } from "node:path";
10209
+ import { dirname as dirname4, resolve as resolve4, sep } from "node:path";
9160
10210
  async function extractBundle(bytes, targetDir) {
9161
- const root = resolve3(targetDir);
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 = resolve3(root, relative2);
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 ?? envFirst("JIOOGEN_TOKEN", "JIOOCODE_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 ?? envFirst("JIOOGEN_API", "JIOOCODE_API") ?? DEFAULT_API_URL
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 ?? envFirst("JIOOGEN_EMAIL", "JIOOCODE_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 ?? envFirst("JIOOGEN_PASSWORD", "JIOOCODE_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 ?? envFirst("JIOOGEN_API", "JIOOCODE_API") ?? DEFAULT_API_URL,
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 = resolve4(options.dir ?? projectId);
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
- `Skills eksekusi terpasang: ${skills.written.length} file (AGENTS.md + 4 harness skill).`
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 = resolve4(options.dir ?? process.cwd());
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 = join6(target, ".jioogen", "project.json");
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 link = await findProjectLink(options.dir ?? process.cwd());
9314
- const bundleDir = resolve4(link?.bundleDir ?? options.dir ?? process.cwd());
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 = resolve4(link?.bundleDir ?? options.dir ?? process.cwd());
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(options, async (local) => {
9352
- const written2 = await writeState(local.bundleDir, local.state);
9353
- return {
9354
- projectId: local.projectId,
9355
- definitions: local.definitions,
9356
- written: written2,
9357
- next: selectNext(local.definitions, written2.tasks)
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 instruction = renderActiveTask(next.task);
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
- if (completedTask2 !== void 0 && next2 !== null && isBoundary(completedTask2, next2.task)) {
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: next2.task.id,
9433
- ...next2.task.layer !== void 0 ? { layer: next2.task.layer } : {},
9434
- phase: next2.task.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 && next !== null) {
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
- `Checkpoint berikutnya: ${next.task.id} (${next.task.layer ?? "-"} / phase ${next.task.phase}).`
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 readFile6(join6(bundleDir, relative2), "utf8")
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 ?? envFirst("JIOOGEN_API", "JIOOCODE_API") ?? stored?.apiUrl ?? DEFAULT_API_URL
10804
+ options.api ?? envFirst2("JIOOGEN_API", "JIOOCODE_API") ?? stored?.apiUrl ?? DEFAULT_API_URL
9573
10805
  );
9574
- const explicitCookie = envFirst("JIOOGEN_COOKIE", "JIOOCODE_COOKIE");
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 ?? envFirst("JIOOGEN_PROJECT", "JIOOCODE_PROJECT") ?? link?.projectId;
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: resolve4(link?.bundleDir ?? options.dir ?? process.cwd()),
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 readdir3(target);
10882
+ entries = await readdir5(target);
9651
10883
  } catch (error) {
9652
- if (isMissingFile4(error)) return;
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 readFile6(join6(target, "PRD.md"));
10916
+ await readFile7(join7(target, "PRD.md"));
9685
10917
  return true;
9686
10918
  } catch (error) {
9687
- if (!isMissingFile4(error)) throw error;
10919
+ if (!isMissingFile7(error)) throw error;
9688
10920
  }
9689
- return hasTaskMarkdown(join6(target, "tasks"));
10921
+ return hasTaskMarkdown(join7(target, "tasks"));
9690
10922
  }
9691
10923
  async function hasTaskMarkdown(directory) {
9692
10924
  let names;
9693
10925
  try {
9694
- names = await readdir3(directory);
10926
+ names = await readdir5(directory);
9695
10927
  } catch (error) {
9696
- if (isMissingFile4(error)) return false;
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 envFirst(primary, legacy) {
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 isMissingFile4(error) {
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.3";
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) {