jioogen 0.2.3 → 0.2.4

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 stat3;
367
367
  try {
368
- stat2 = self.fs.statSync(resolvedPath);
368
+ stat3 = 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 (stat3 && stat3.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 stat3 = self.fs.statSync(path);
386
+ if (stat3.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, stat3) {
420
+ if (exist && stat3 && stat3.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 stat3;
473
473
  try {
474
- stat2 = self.fs.lstatSync(cur);
474
+ stat3 = 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 (stat3.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 stat3 = 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) + (stat3.isDirectory() ? self.sep : ""));
508
508
  }
509
- if (stat2.isDirectory() && recursive) {
509
+ if (stat3.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, stat3) {
557
557
  if (err3) return done(err3);
558
- if (!stat2) {
558
+ if (!stat3) {
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) + (stat3.isDirectory() ? self.sep : ""));
563
+ if (!stat3.isDirectory()) {
564
564
  if (!--pending) done();
565
565
  return;
566
566
  }
@@ -2978,7 +2978,7 @@ var ApiClient = class {
2978
2978
  };
2979
2979
 
2980
2980
  // src/commands.ts
2981
- import { readdir as readdir3, readFile as readFile6 } from "node:fs/promises";
2981
+ import { readdir as readdir4, readFile as readFile6 } from "node:fs/promises";
2982
2982
  import { join as join6, resolve as resolve4 } from "node:path";
2983
2983
 
2984
2984
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
@@ -8766,15 +8766,28 @@ function delay(ms) {
8766
8766
  }
8767
8767
 
8768
8768
  // src/skills.ts
8769
- import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
8769
+ import { createHash } from "node:crypto";
8770
+ import { cp, mkdir as mkdir3, readdir as readdir2, readFile as readFile4, rm as rm3, stat as stat2, writeFile as writeFile3 } from "node:fs/promises";
8771
+ import { homedir as homedir2 } from "node:os";
8770
8772
  import { dirname as dirname3, join as join4, resolve as resolve2 } from "node:path";
8773
+ import { fileURLToPath } from "node:url";
8771
8774
  var EXECUTION_SKILL_NAME = "jioogen-execution";
8772
8775
  var AGENT_SKILL_DIRS = [
8773
8776
  ".claude/skills",
8777
+ ".opencode/skills",
8774
8778
  ".agent/skills",
8775
8779
  ".codex/skills",
8776
8780
  ".cursor/skills"
8777
8781
  ];
8782
+ var SKILL_PACK_NAMES = ["baseline", "ui-quality", "backend-rules"];
8783
+ var SKILL_PACKS = {
8784
+ baseline: ["karpathy-guidelines"],
8785
+ "ui-quality": ["ui-ux-pro-max", "impeccable"],
8786
+ "backend-rules": []
8787
+ };
8788
+ var HARNESS_MANIFEST_PATH = ".jioogen/harness.json";
8789
+ var HARNESS_SCHEMA_VERSION = 1;
8790
+ var FRONTEND_MOCK_LAYER_RE = /^[ \t]*(?:[-*][ \t]+)?layer[ \t]*[:=][ \t]*[`"']?frontend_mock\b/im;
8778
8791
  var MARKER_PREFIX = "<!-- jioogen-execution-skill:";
8779
8792
  var MARKER = `${MARKER_PREFIX} v1 -->`;
8780
8793
  var JIOOGEN_AGENTS_MD = `${MARKER}
@@ -8829,7 +8842,7 @@ mengetik "lanjut".
8829
8842
 
8830
8843
  ## 6. Skill eksekusi
8831
8844
  Baca skill \`${EXECUTION_SKILL_NAME}\` di awal prompt
8832
- (\`.claude/skills\`, \`.agent/skills\`, \`.codex/skills\`, atau \`.cursor/skills\`).
8845
+ (\`.claude/skills\`, \`.opencode/skills\`, \`.agent/skills\`, \`.codex/skills\`, atau \`.cursor/skills\`).
8833
8846
  Skill itu memuat kontrak lengkap; file ini adalah ringkasan yang mengikat.
8834
8847
  `;
8835
8848
  var JIOOGEN_EXECUTION_SKILL = `---
@@ -8929,10 +8942,247 @@ async function readFileSafe(path) {
8929
8942
  return void 0;
8930
8943
  }
8931
8944
  }
8932
- async function installAgentSkills(bundleDir) {
8945
+ function isRecord2(value) {
8946
+ return typeof value === "object" && value !== null;
8947
+ }
8948
+ function isMissingFile3(error) {
8949
+ return isRecord2(error) && error.code === "ENOENT";
8950
+ }
8951
+ async function isFile(path) {
8952
+ try {
8953
+ return (await stat2(path)).isFile();
8954
+ } catch (error) {
8955
+ if (isMissingFile3(error)) return false;
8956
+ throw error;
8957
+ }
8958
+ }
8959
+ async function isDirectory(path) {
8960
+ try {
8961
+ return (await stat2(path)).isDirectory();
8962
+ } catch (error) {
8963
+ if (isMissingFile3(error)) return false;
8964
+ throw error;
8965
+ }
8966
+ }
8967
+ function isSkillPackName(value) {
8968
+ return typeof value === "string" && SKILL_PACK_NAMES.includes(value);
8969
+ }
8970
+ function isProductKind(value) {
8971
+ return value === "ui" || value === "non-ui";
8972
+ }
8973
+ function parseSkillEntry(value) {
8974
+ if (!isRecord2(value)) return null;
8975
+ const { name, source, sha256 } = value;
8976
+ if (typeof name !== "string" || name.length === 0) return null;
8977
+ if (typeof source !== "string" || source.length === 0) return null;
8978
+ if (typeof sha256 !== "string" || sha256.length === 0) return null;
8979
+ return { name, source, sha256 };
8980
+ }
8981
+ function parsePack(value) {
8982
+ if (!isRecord2(value)) return null;
8983
+ const { name, skills, missing } = value;
8984
+ if (!isSkillPackName(name)) return null;
8985
+ if (!Array.isArray(skills) || !Array.isArray(missing)) return null;
8986
+ const parsedSkills = [];
8987
+ for (const entry of skills) {
8988
+ const parsed = parseSkillEntry(entry);
8989
+ if (parsed === null) return null;
8990
+ parsedSkills.push(parsed);
8991
+ }
8992
+ const parsedMissing = [];
8993
+ for (const entry of missing) {
8994
+ if (typeof entry !== "string") return null;
8995
+ parsedMissing.push(entry);
8996
+ }
8997
+ return { name, skills: parsedSkills, missing: parsedMissing };
8998
+ }
8999
+ function parseHarnessManifest(value) {
9000
+ if (!isRecord2(value)) return null;
9001
+ const { schemaVersion, productKind, packs, updatedAt } = value;
9002
+ if (schemaVersion !== HARNESS_SCHEMA_VERSION) return null;
9003
+ if (!isProductKind(productKind)) return null;
9004
+ if (!Array.isArray(packs)) return null;
9005
+ const parsedPacks = [];
9006
+ for (const entry of packs) {
9007
+ const parsed = parsePack(entry);
9008
+ if (parsed === null) return null;
9009
+ parsedPacks.push(parsed);
9010
+ }
9011
+ if (typeof updatedAt !== "string" || updatedAt.length === 0) return null;
9012
+ return { schemaVersion: HARNESS_SCHEMA_VERSION, productKind, packs: parsedPacks, updatedAt };
9013
+ }
9014
+ async function readHarnessManifest(bundleDir) {
9015
+ const path = join4(resolve2(bundleDir), HARNESS_MANIFEST_PATH);
9016
+ let raw;
9017
+ try {
9018
+ raw = await readFile4(path, "utf8");
9019
+ } catch (error) {
9020
+ if (isMissingFile3(error)) return null;
9021
+ throw error;
9022
+ }
9023
+ try {
9024
+ return parseHarnessManifest(JSON.parse(raw));
9025
+ } catch {
9026
+ return null;
9027
+ }
9028
+ }
9029
+ async function writeHarnessManifest(bundleDir, manifest) {
9030
+ const path = join4(resolve2(bundleDir), HARNESS_MANIFEST_PATH);
9031
+ await mkdir3(dirname3(path), { recursive: true });
9032
+ await writeFile3(path, `${JSON.stringify(manifest, null, 2)}
9033
+ `, "utf8");
9034
+ return path;
9035
+ }
9036
+ async function detectProductKind(bundleDir) {
9037
+ const tasksDir = join4(resolve2(bundleDir), "tasks");
9038
+ let entries;
9039
+ try {
9040
+ entries = await readdir2(tasksDir, { withFileTypes: true });
9041
+ } catch (error) {
9042
+ if (isMissingFile3(error)) return "non-ui";
9043
+ throw error;
9044
+ }
9045
+ for (const entry of entries) {
9046
+ if (!entry.isFile() || !entry.name.toLowerCase().endsWith(".md")) continue;
9047
+ const content = await readFileSafe(join4(tasksDir, entry.name));
9048
+ if (content !== void 0 && FRONTEND_MOCK_LAYER_RE.test(content)) return "ui";
9049
+ }
9050
+ return "non-ui";
9051
+ }
9052
+ function packsForProduct(productKind) {
9053
+ return productKind === "ui" ? ["baseline", "ui-quality", "backend-rules"] : ["baseline", "backend-rules"];
9054
+ }
9055
+ function cliPackageRoot() {
9056
+ return resolve2(dirname3(fileURLToPath(import.meta.url)), "..");
9057
+ }
9058
+ function skillSourceCandidates(pack, name) {
9059
+ const candidates = [];
9060
+ const override = process.env.JIOOGEN_SKILLS_DIR?.trim();
9061
+ if (override !== void 0 && override.length > 0) candidates.push(join4(override, name));
9062
+ candidates.push(join4(configDir(), "skill-cache", name));
9063
+ candidates.push(join4(cliPackageRoot(), "skills-pack", pack, name));
9064
+ const home = homedir2();
9065
+ candidates.push(join4(home, ".agents", "skills", name));
9066
+ candidates.push(join4(home, ".config", "opencode", "skills", name));
9067
+ candidates.push(join4(home, ".claude", "skills", name));
9068
+ return candidates;
9069
+ }
9070
+ async function resolveSkillSource(pack, name) {
9071
+ for (const candidate of skillSourceCandidates(pack, name)) {
9072
+ if (await isDirectory(candidate) && await isFile(join4(candidate, "SKILL.md"))) {
9073
+ return candidate;
9074
+ }
9075
+ }
9076
+ return null;
9077
+ }
9078
+ async function collectRelativeFiles(root, prefix) {
9079
+ const entries = await readdir2(join4(root, prefix), { withFileTypes: true });
9080
+ const files = [];
9081
+ for (const entry of entries) {
9082
+ const relative2 = prefix.length === 0 ? entry.name : `${prefix}/${entry.name}`;
9083
+ if (entry.isDirectory()) {
9084
+ files.push(...await collectRelativeFiles(root, relative2));
9085
+ } else if (entry.isFile()) {
9086
+ files.push(relative2);
9087
+ }
9088
+ }
9089
+ return files;
9090
+ }
9091
+ async function hashSkillDirectory(directory) {
9092
+ const root = resolve2(directory);
9093
+ const files = (await collectRelativeFiles(root, "")).sort();
9094
+ const hash = createHash("sha256");
9095
+ for (const relative2 of files) {
9096
+ hash.update(relative2);
9097
+ hash.update("\0");
9098
+ hash.update(await readFile4(join4(root, ...relative2.split("/"))));
9099
+ hash.update("\0");
9100
+ }
9101
+ return hash.digest("hex");
9102
+ }
9103
+ async function replaceDirectory(source, target) {
9104
+ await rm3(target, { recursive: true, force: true });
9105
+ await mkdir3(dirname3(target), { recursive: true });
9106
+ await cp(source, target, { recursive: true });
9107
+ }
9108
+ function packNamesInCanonicalOrder(names) {
9109
+ const wanted = new Set(names);
9110
+ return SKILL_PACK_NAMES.filter((name) => wanted.has(name));
9111
+ }
9112
+ async function provisionPack(root, pack, prior, written, preserved) {
9113
+ const skills = [];
9114
+ const missing = [];
9115
+ for (const name of SKILL_PACKS[pack]) {
9116
+ const source = await resolveSkillSource(pack, name);
9117
+ if (source === null) {
9118
+ missing.push(name);
9119
+ continue;
9120
+ }
9121
+ const sha256 = await hashSkillDirectory(source);
9122
+ const previous = prior?.skills.find((entry) => entry.name === name);
9123
+ let owned = previous !== void 0;
9124
+ for (const dir of AGENT_SKILL_DIRS) {
9125
+ const targetDir = join4(root, dir, name);
9126
+ const skillMd = join4(targetDir, "SKILL.md");
9127
+ if (await isFile(skillMd)) {
9128
+ const targetHash = await hashSkillDirectory(targetDir);
9129
+ if (previous === void 0) {
9130
+ preserved.push(`${dir}/${name}/SKILL.md`);
9131
+ continue;
9132
+ }
9133
+ if (targetHash === sha256) {
9134
+ owned = true;
9135
+ continue;
9136
+ }
9137
+ await replaceDirectory(source, targetDir);
9138
+ written.push(`${dir}/${name}/SKILL.md`);
9139
+ owned = true;
9140
+ continue;
9141
+ }
9142
+ await replaceDirectory(source, targetDir);
9143
+ written.push(`${dir}/${name}/SKILL.md`);
9144
+ owned = true;
9145
+ }
9146
+ if (owned) skills.push({ name, source, sha256 });
9147
+ }
9148
+ return { name: pack, skills, missing };
9149
+ }
9150
+ function sameManifestBody(manifest, productKind, packs) {
9151
+ return manifest.productKind === productKind && JSON.stringify(manifest.packs) === JSON.stringify(packs);
9152
+ }
9153
+ async function provisionSkillPacks(bundleDir, packNames, productKind) {
9154
+ const root = resolve2(bundleDir);
9155
+ const existing = await readHarnessManifest(root);
9156
+ const written = [];
9157
+ const preserved = [];
9158
+ const packs = [];
9159
+ for (const pack of packNamesInCanonicalOrder(packNames)) {
9160
+ const prior = existing?.packs.find((entry) => entry.name === pack);
9161
+ packs.push(await provisionPack(root, pack, prior, written, preserved));
9162
+ }
9163
+ const changed = existing === null || !sameManifestBody(existing, productKind, packs);
9164
+ const manifest = {
9165
+ schemaVersion: HARNESS_SCHEMA_VERSION,
9166
+ productKind,
9167
+ packs,
9168
+ updatedAt: changed ? (/* @__PURE__ */ new Date()).toISOString() : existing?.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString()
9169
+ };
9170
+ const harnessPath = await writeHarnessManifest(root, manifest);
9171
+ return {
9172
+ written,
9173
+ preserved,
9174
+ productKind,
9175
+ packs,
9176
+ missing: packs.flatMap((pack) => pack.missing),
9177
+ harnessPath
9178
+ };
9179
+ }
9180
+ async function installAgentSkills(bundleDir, options = {}) {
8933
9181
  const root = resolve2(bundleDir);
8934
9182
  const written = [];
8935
9183
  const preserved = [];
9184
+ const productKind = options.productKind ?? await detectProductKind(root);
9185
+ const harnessPath = join4(root, HARNESS_MANIFEST_PATH);
8936
9186
  const agentsPath = join4(root, "AGENTS.md");
8937
9187
  const existing = await readFileSafe(agentsPath);
8938
9188
  if (existing === void 0 || existing.includes(MARKER_PREFIX)) {
@@ -8948,11 +9198,88 @@ async function installAgentSkills(bundleDir) {
8948
9198
  await writeFile3(target, JIOOGEN_EXECUTION_SKILL, "utf8");
8949
9199
  written.push(relative2);
8950
9200
  }
8951
- return { written, preserved };
9201
+ if (options.noSkills === true) {
9202
+ return { written, preserved, productKind, packs: [], missing: [], harnessPath };
9203
+ }
9204
+ const current = await readHarnessManifest(root);
9205
+ const requested = options.packs ?? packNamesInCanonicalOrder([
9206
+ ...packsForProduct(productKind),
9207
+ ...current?.packs.map((pack) => pack.name) ?? []
9208
+ ]);
9209
+ const provision = await provisionSkillPacks(root, requested, productKind);
9210
+ return {
9211
+ written: [...written, ...provision.written],
9212
+ preserved: [...preserved, ...provision.preserved],
9213
+ productKind,
9214
+ packs: provision.packs,
9215
+ missing: provision.missing,
9216
+ harnessPath: provision.harnessPath
9217
+ };
9218
+ }
9219
+ function parseSkillPackArgument(value) {
9220
+ if (value === "all") return [...SKILL_PACK_NAMES];
9221
+ return isSkillPackName(value) ? [value] : null;
9222
+ }
9223
+ async function addSkillPacks(bundleDir, packNames, options = {}) {
9224
+ const root = resolve2(bundleDir);
9225
+ const existing = await readHarnessManifest(root);
9226
+ const productKind = options.productKind ?? existing?.productKind ?? await detectProductKind(root);
9227
+ const requested = packNamesInCanonicalOrder([
9228
+ ...existing?.packs.map((pack) => pack.name) ?? [],
9229
+ ...packNames
9230
+ ]);
9231
+ const provision = await provisionSkillPacks(root, requested, productKind);
9232
+ return { ...provision, added: packNamesInCanonicalOrder(packNames) };
9233
+ }
9234
+ async function updateSkillPacks(bundleDir) {
9235
+ const root = resolve2(bundleDir);
9236
+ const existing = await readHarnessManifest(root);
9237
+ if (existing === null) return null;
9238
+ return provisionSkillPacks(
9239
+ root,
9240
+ existing.packs.map((pack) => pack.name),
9241
+ existing.productKind
9242
+ );
9243
+ }
9244
+ async function removeSkillPack(bundleDir, pack) {
9245
+ const root = resolve2(bundleDir);
9246
+ const existing = await readHarnessManifest(root);
9247
+ const removed = [];
9248
+ const preserved = [];
9249
+ if (existing === null) return { removed, preserved, packs: [] };
9250
+ const entry = existing.packs.find((candidate) => candidate.name === pack);
9251
+ if (entry !== void 0) {
9252
+ for (const skill of entry.skills) {
9253
+ for (const dir of AGENT_SKILL_DIRS) {
9254
+ const targetDir = join4(root, dir, skill.name);
9255
+ const skillMd = join4(targetDir, "SKILL.md");
9256
+ if (!await isFile(skillMd)) continue;
9257
+ const targetHash = await hashSkillDirectory(targetDir);
9258
+ if (targetHash !== skill.sha256) {
9259
+ preserved.push(`${dir}/${skill.name}/SKILL.md`);
9260
+ continue;
9261
+ }
9262
+ await rm3(targetDir, { recursive: true, force: true });
9263
+ removed.push(`${dir}/${skill.name}/SKILL.md`);
9264
+ }
9265
+ }
9266
+ }
9267
+ const packs = existing.packs.filter((candidate) => candidate.name !== pack);
9268
+ await writeHarnessManifest(root, {
9269
+ ...existing,
9270
+ packs,
9271
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
9272
+ });
9273
+ return { removed, preserved, packs };
9274
+ }
9275
+ function skillsDisabled(options) {
9276
+ if (options.noSkills === true) return true;
9277
+ const value = process.env.JIOOGEN_NO_SKILLS?.trim().toLowerCase();
9278
+ return value === "1" || value === "true" || value === "yes";
8952
9279
  }
8953
9280
 
8954
9281
  // src/tasks.ts
8955
- import { readdir as readdir2, readFile as readFile5 } from "node:fs/promises";
9282
+ import { readdir as readdir3, readFile as readFile5 } from "node:fs/promises";
8956
9283
  import { join as join5 } from "node:path";
8957
9284
  var TASK_ID_RE2 = /\b(?:TASK|T)-\d{3,}\b/i;
8958
9285
  var HEADING_RE = /^#{1,4}\s+(.+)$/m;
@@ -8964,13 +9291,51 @@ var LAYER_RANK = {
8964
9291
  verification: 4
8965
9292
  };
8966
9293
  var IN_PROGRESS = ["in_progress", "verifying"];
9294
+ var QUALITY_HEADING = "## KUALITAS WAJIB (ringkas; detail di skill bila tersedia)";
9295
+ var MAX_CHECKLIST_LINES = 12;
9296
+ var UI_SKILLS = ["ui-ux-pro-max", "impeccable"];
9297
+ var LAYER_CHECKLISTS = {
9298
+ frontend_mock: [
9299
+ "- Reuse token desain yang ada; jangan hardcode warna/spacing bila token tersedia.",
9300
+ "- Tangani semua state UI: loading, empty, error, success.",
9301
+ "- Responsive untuk mobile dan desktop.",
9302
+ "- Aksesibilitas dasar: label, aria, focus, keyboard.",
9303
+ "- Microcopy jelas dan konsisten dengan konteks."
9304
+ ],
9305
+ database: [
9306
+ "- Migrasi eksplisit dan aman (reversible, tanpa mengubah migrasi lama).",
9307
+ "- Constraint dan index sesuai pola query.",
9308
+ "- Relasi dan isolation sesuai specs (tenant/owner).",
9309
+ "- Seed idempotent (bisa dijalankan ulang tanpa duplikasi).",
9310
+ "- Tanpa query lintas pemilik."
9311
+ ],
9312
+ backend_api: [
9313
+ "- Validasi input di boundary.",
9314
+ "- Error contract konsisten (status + bentuk response).",
9315
+ "- Auth/permission dicek sebelum mutasi.",
9316
+ "- Idempotensi untuk operasi tulis/retry.",
9317
+ "- Logging tanpa secret/PII."
9318
+ ],
9319
+ integration: [
9320
+ "- Mock dihapus dari jalur utama.",
9321
+ "- Kontrak CONTRACT-XXX sama dengan implementasi.",
9322
+ "- E2E memakai data nyata.",
9323
+ "- Error path diuji."
9324
+ ],
9325
+ verification: [
9326
+ "- Jalankan semua verificationCommand sampai exit 0.",
9327
+ "- Smoke route/navigasi utama.",
9328
+ "- UI states dan responsive dicek.",
9329
+ "- Bukti Acceptance Criteria \xA711."
9330
+ ]
9331
+ };
8967
9332
  async function loadTasks(bundleDir) {
8968
9333
  const tasksDir = join5(bundleDir, "tasks");
8969
9334
  let entries;
8970
9335
  try {
8971
- entries = await readdir2(tasksDir, { withFileTypes: true });
9336
+ entries = await readdir3(tasksDir, { withFileTypes: true });
8972
9337
  } catch (error) {
8973
- if (isMissingFile3(error)) throw new Error(`Direktori tasks/ tidak ditemukan di ${bundleDir}`);
9338
+ if (isMissingFile4(error)) throw new Error(`Direktori tasks/ tidak ditemukan di ${bundleDir}`);
8974
9339
  throw error;
8975
9340
  }
8976
9341
  const names = entries.filter(
@@ -8988,7 +9353,7 @@ async function loadTasks(bundleDir) {
8988
9353
  try {
8989
9354
  content = await readFile5(singlePath, "utf8");
8990
9355
  } catch (error) {
8991
- if (isMissingFile3(error)) throw new Error(`Tidak ada berkas task di ${tasksDir}`);
9356
+ if (isMissingFile4(error)) throw new Error(`Tidak ada berkas task di ${tasksDir}`);
8992
9357
  throw error;
8993
9358
  }
8994
9359
  tasks.push(...parseSingleFile(content));
@@ -9041,7 +9406,7 @@ function buildTask(id, heading, file, content) {
9041
9406
  dependsOn: context.dependsOn
9042
9407
  };
9043
9408
  }
9044
- function renderActiveTask(task) {
9409
+ function renderActiveTask(task, context) {
9045
9410
  const lines = [
9046
9411
  "## TASK AKTIF \u2014 PRIORITASKAN INI (satu task per giliran)",
9047
9412
  `ref: ${task.id}`,
@@ -9058,14 +9423,23 @@ function renderActiveTask(task) {
9058
9423
  "- Jangan mengarang fitur/endpoint/kolom di luar PRD.md/specs/.",
9059
9424
  "- Detail implementasi (pola, struktur file dalam scope, library yang sudah ada) milikmu; ikuti konvensi proyek.",
9060
9425
  "- 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 ---"
9426
+ "- Selesai: `npx jioogen task complete <ref>`, lalu `npx jioogen sync`."
9066
9427
  );
9428
+ const checklist = renderQualityChecklist(task.layer, context);
9429
+ if (checklist.length > 0) lines.push("", ...checklist);
9430
+ lines.push("", "--- ISI TASK (otoritas scope) ---", task.content, "--- AKHIR ISI TASK ---");
9067
9431
  return lines.join("\n");
9068
9432
  }
9433
+ function renderQualityChecklist(layer, context) {
9434
+ if (context === void 0 || layer === void 0) return [];
9435
+ const items = [...LAYER_CHECKLISTS[layer] ?? []];
9436
+ if (layer === "frontend_mock") {
9437
+ const references = UI_SKILLS.filter((skill) => context.skills?.includes(skill) === true);
9438
+ if (references.length > 0) items.push(`- Referensi skill: ${references.join(", ")}`);
9439
+ }
9440
+ if (items.length === 0) return [];
9441
+ return [QUALITY_HEADING, ...items.slice(0, MAX_CHECKLIST_LINES)];
9442
+ }
9069
9443
  function resolveLayer(value) {
9070
9444
  if (value === void 0) return void 0;
9071
9445
  const token = value.trim().replace(/[`*]/g, "").split(/[\s,(]/)[0];
@@ -9149,7 +9523,7 @@ function isBoundary(previous, next) {
9149
9523
  }
9150
9524
  return previous.phase !== void 0 && next.phase !== previous.phase;
9151
9525
  }
9152
- function isMissingFile3(error) {
9526
+ function isMissingFile4(error) {
9153
9527
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
9154
9528
  }
9155
9529
 
@@ -9275,7 +9649,7 @@ async function pullCommand(projectId, options) {
9275
9649
  await assertPullTarget(target, projectId, options.force === true);
9276
9650
  const bytes = await client.getBytes(projectPath(projectId, "/bundle"));
9277
9651
  const written = await extractBundle(bytes, target);
9278
- const skills = await installAgentSkills(target);
9652
+ const skills = await installAgentSkills(target, { noSkills: skillsDisabled(options) });
9279
9653
  await writeProjectLink(target, { projectId, apiUrl });
9280
9654
  if (options.json === true) {
9281
9655
  console.log(JSON.stringify({ projectId, target, files: written, skills }, null, 2));
@@ -9283,9 +9657,8 @@ async function pullCommand(projectId, options) {
9283
9657
  }
9284
9658
  console.log(`Pulled ${written.length} file ke ${target}`);
9285
9659
  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
- );
9660
+ console.log(`Skills eksekusi terpasang: ${skills.written.length} file.`);
9661
+ printSkillPacks(skills.packs, skills.missing);
9289
9662
  }
9290
9663
  async function linkCommand(projectId, options) {
9291
9664
  const { apiUrl, client } = await resolveApi(options);
@@ -9293,26 +9666,20 @@ async function linkCommand(projectId, options) {
9293
9666
  await assertLinkTarget(target, projectId);
9294
9667
  await verifyProjectAccess(client, projectId);
9295
9668
  await writeProjectLink(target, { projectId, apiUrl });
9296
- const skills = await installAgentSkills(target);
9669
+ const skills = await installAgentSkills(target, { noSkills: skillsDisabled(options) });
9297
9670
  const linkedFile = join6(target, ".jioogen", "project.json");
9298
9671
  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
- );
9672
+ console.log(JSON.stringify({ projectId, target, apiUrl, linkedFile, skills }, null, 2));
9306
9673
  return;
9307
9674
  }
9308
9675
  console.log(`Project ${projectId} ter-bind ke ${target}.`);
9309
9676
  console.log(`File link: ${linkedFile} (API ${apiUrl}).`);
9310
9677
  console.log(`Skills eksekusi terpasang: ${skills.written.length} file.`);
9678
+ printSkillPacks(skills.packs, skills.missing);
9311
9679
  }
9312
9680
  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);
9681
+ const bundleDir = await resolveSkillsBundleDir(options);
9682
+ const skills = await installAgentSkills(bundleDir, { noSkills: skillsDisabled(options) });
9316
9683
  if (options.json === true) {
9317
9684
  console.log(JSON.stringify({ bundleDir, ...skills }, null, 2));
9318
9685
  return;
@@ -9320,6 +9687,90 @@ async function skillsInstallCommand(options) {
9320
9687
  console.log(`Skills eksekusi terpasang di ${bundleDir}:`);
9321
9688
  for (const file of skills.written) console.log(`- ${file}`);
9322
9689
  for (const file of skills.preserved) console.log(`- ${file} (dilewati: milik user)`);
9690
+ printSkillPacks(skills.packs, skills.missing);
9691
+ }
9692
+ async function skillsListCommand(options) {
9693
+ const bundleDir = await resolveSkillsBundleDir(options);
9694
+ const manifest = await readHarnessManifest(bundleDir);
9695
+ if (options.json === true) {
9696
+ console.log(JSON.stringify({ bundleDir, harness: manifest }, null, 2));
9697
+ return;
9698
+ }
9699
+ if (manifest === null) {
9700
+ console.log(`Belum ada skill pack terpasang di ${bundleDir}.`);
9701
+ return;
9702
+ }
9703
+ console.log(`Bundle: ${bundleDir}`);
9704
+ console.log(`Product: ${manifest.productKind} | updated: ${manifest.updatedAt}`);
9705
+ for (const pack of manifest.packs) {
9706
+ console.log(`${pack.name}:`);
9707
+ for (const skill of pack.skills) {
9708
+ console.log(`- ${skill.name} (installed, ${skill.sha256.slice(0, 12)})`);
9709
+ }
9710
+ for (const name of pack.missing) console.log(`- ${name} (missing)`);
9711
+ if (pack.skills.length === 0 && pack.missing.length === 0) console.log("- (tidak ada skill)");
9712
+ }
9713
+ }
9714
+ async function skillsAddCommand(packArgument, options) {
9715
+ const packNames = parseSkillPackArgument(packArgument);
9716
+ if (packNames === null) {
9717
+ throw new UsageError("Pack tidak dikenal. Pilih: baseline | ui-quality | backend-rules | all.");
9718
+ }
9719
+ const bundleDir = await resolveSkillsBundleDir(options);
9720
+ const result = await addSkillPacks(bundleDir, packNames);
9721
+ if (options.json === true) {
9722
+ console.log(JSON.stringify({ bundleDir, ...result }, null, 2));
9723
+ return;
9724
+ }
9725
+ console.log(`Skill pack ditambahkan ke ${bundleDir}: ${result.added.join(", ")}`);
9726
+ printSkillPacks(result.packs, result.missing);
9727
+ }
9728
+ async function skillsUpdateCommand(options) {
9729
+ const bundleDir = await resolveSkillsBundleDir(options);
9730
+ const result = await updateSkillPacks(bundleDir);
9731
+ if (result === null) {
9732
+ if (options.json === true) {
9733
+ console.log(JSON.stringify({ bundleDir, harness: null }, null, 2));
9734
+ return;
9735
+ }
9736
+ console.log(`Belum ada skill pack tercatat di ${bundleDir}.`);
9737
+ return;
9738
+ }
9739
+ if (options.json === true) {
9740
+ console.log(JSON.stringify({ bundleDir, ...result }, null, 2));
9741
+ return;
9742
+ }
9743
+ console.log(`Skill pack diperbarui di ${bundleDir}.`);
9744
+ printSkillPacks(result.packs, result.missing);
9745
+ }
9746
+ async function skillsRemoveCommand(packArgument, options) {
9747
+ const packNames = parseSkillPackArgument(packArgument);
9748
+ if (packNames === null || packNames.length !== 1) {
9749
+ throw new UsageError("Pack tidak dikenal. Pilih: baseline | ui-quality | backend-rules.");
9750
+ }
9751
+ const pack = packNames[0];
9752
+ const bundleDir = await resolveSkillsBundleDir(options);
9753
+ const result = await removeSkillPack(bundleDir, pack);
9754
+ if (options.json === true) {
9755
+ console.log(JSON.stringify({ bundleDir, pack, ...result }, null, 2));
9756
+ return;
9757
+ }
9758
+ console.log(`Skill pack ${pack} dihapus dari ${bundleDir}: ${result.removed.length} file.`);
9759
+ for (const file of result.preserved) {
9760
+ console.log(`- ${file} (dilewati: milik user / sudah berubah)`);
9761
+ }
9762
+ }
9763
+ async function resolveSkillsBundleDir(options) {
9764
+ const link = await findProjectLink(options.dir ?? process.cwd());
9765
+ return resolve4(link?.bundleDir ?? options.dir ?? process.cwd());
9766
+ }
9767
+ function printSkillPacks(packs, missing) {
9768
+ if (packs.length === 0 && missing.length === 0) return;
9769
+ const installed = packs.filter((pack) => pack.skills.length > 0).map((pack) => `${pack.name} (${pack.skills.map((skill) => skill.name).join(", ")})`);
9770
+ if (installed.length > 0) console.log(`Skill pack: ${installed.join("; ")}.`);
9771
+ if (missing.length > 0) {
9772
+ console.warn(`Peringatan: skill tidak ditemukan (dilewati): ${missing.join(", ")}.`);
9773
+ }
9323
9774
  }
9324
9775
  async function checkCommand(options) {
9325
9776
  const link = await findProjectLink(options.dir ?? process.cwd());
@@ -9348,15 +9799,19 @@ async function handoffCommand(options) {
9348
9799
  }
9349
9800
  }
9350
9801
  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
- });
9802
+ const { projectId, bundleDir, definitions, written, next } = await withLocalState(
9803
+ options,
9804
+ async (local) => {
9805
+ const written2 = await writeState(local.bundleDir, local.state);
9806
+ return {
9807
+ projectId: local.projectId,
9808
+ bundleDir: local.bundleDir,
9809
+ definitions: local.definitions,
9810
+ written: written2,
9811
+ next: selectNext(local.definitions, written2.tasks)
9812
+ };
9813
+ }
9814
+ );
9360
9815
  if (next === null) {
9361
9816
  if (options.json === true) {
9362
9817
  console.log(JSON.stringify({ done: true, projectId }, null, 2));
@@ -9373,7 +9828,12 @@ async function taskNextCommand(options) {
9373
9828
  return;
9374
9829
  }
9375
9830
  const awaitingCheckpoint = written.current?.awaitingCheckpoint === true;
9376
- const instruction = renderActiveTask(next.task);
9831
+ const manifest = await readHarnessManifest(bundleDir);
9832
+ const context = manifest === null ? void 0 : {
9833
+ productKind: manifest.productKind,
9834
+ skills: manifest.packs.flatMap((pack) => pack.skills.map((skill) => skill.name))
9835
+ };
9836
+ const instruction = renderActiveTask(next.task, context);
9377
9837
  if (options.json === true) {
9378
9838
  console.log(
9379
9839
  JSON.stringify(
@@ -9647,9 +10107,9 @@ async function assertPullDefaultDir(cwd, dirOption) {
9647
10107
  async function assertPullTarget(target, projectId, force = false) {
9648
10108
  let entries;
9649
10109
  try {
9650
- entries = await readdir3(target);
10110
+ entries = await readdir4(target);
9651
10111
  } catch (error) {
9652
- if (isMissingFile4(error)) return;
10112
+ if (isMissingFile5(error)) return;
9653
10113
  throw error;
9654
10114
  }
9655
10115
  if (entries.length === 0) return;
@@ -9684,16 +10144,16 @@ async function hasBundleMarkers(target) {
9684
10144
  await readFile6(join6(target, "PRD.md"));
9685
10145
  return true;
9686
10146
  } catch (error) {
9687
- if (!isMissingFile4(error)) throw error;
10147
+ if (!isMissingFile5(error)) throw error;
9688
10148
  }
9689
10149
  return hasTaskMarkdown(join6(target, "tasks"));
9690
10150
  }
9691
10151
  async function hasTaskMarkdown(directory) {
9692
10152
  let names;
9693
10153
  try {
9694
- names = await readdir3(directory);
10154
+ names = await readdir4(directory);
9695
10155
  } catch (error) {
9696
- if (isMissingFile4(error)) return false;
10156
+ if (isMissingFile5(error)) return false;
9697
10157
  throw error;
9698
10158
  }
9699
10159
  return names.some((name) => name.toLowerCase().endsWith(".md"));
@@ -9755,12 +10215,12 @@ function envFirst(primary, legacy) {
9755
10215
  const fallback = process.env[legacy]?.trim();
9756
10216
  return fallback !== void 0 && fallback.length > 0 ? fallback : void 0;
9757
10217
  }
9758
- function isMissingFile4(error) {
10218
+ function isMissingFile5(error) {
9759
10219
  return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
9760
10220
  }
9761
10221
 
9762
10222
  // src/index.ts
9763
- var VERSION = "0.2.3";
10223
+ var VERSION = "0.2.4";
9764
10224
  var USAGE = `jioogen \u2014 CLI JiooGen (task selection lokal, server read-only)
9765
10225
 
9766
10226
  Usage:
@@ -9769,9 +10229,13 @@ Usage:
9769
10229
  jioogen logout
9770
10230
  jioogen whoami
9771
10231
  jioogen projects
9772
- jioogen pull <projectId> [--dir <dir>] [--force]
9773
- jioogen link <projectId> [--dir <dir>]
9774
- jioogen skills install [--dir <dir>]
10232
+ jioogen pull <projectId> [--dir <dir>] [--force] [--no-skills]
10233
+ jioogen link <projectId> [--dir <dir>] [--no-skills]
10234
+ jioogen skills install [--dir <dir>] [--no-skills]
10235
+ jioogen skills list [--dir <dir>] [--json]
10236
+ jioogen skills add <baseline|ui-quality|backend-rules|all> [--dir <dir>]
10237
+ jioogen skills update [--dir <dir>]
10238
+ jioogen skills remove <baseline|ui-quality|backend-rules> [--dir <dir>]
9775
10239
  jioogen check [--dir <dir>] [--json]
9776
10240
  jioogen handoff [--project <id>] [--json]
9777
10241
  jioogen task next [--json]
@@ -9789,6 +10253,7 @@ Global options:
9789
10253
  --api <url> Base URL JiooGen (env JIOOGEN_API / JIOOCODE_API / tersimpan)
9790
10254
  --project <id> Override binding project
9791
10255
  --dir <dir> Override direktori bundle
10256
+ --no-skills Jangan pasang skill pack (atau env JIOOGEN_NO_SKILLS=1)
9792
10257
  -h, --help Tampilkan bantuan
9793
10258
  -v, --version Tampilkan versi
9794
10259
  `;
@@ -9809,6 +10274,7 @@ async function main() {
9809
10274
  dir: { type: "string" },
9810
10275
  remote: { type: "boolean" },
9811
10276
  force: { type: "boolean" },
10277
+ "no-skills": { type: "boolean" },
9812
10278
  files: { type: "string" },
9813
10279
  note: { type: "string" },
9814
10280
  json: { type: "boolean" },
@@ -9835,6 +10301,7 @@ async function main() {
9835
10301
  dir: text(raw.dir),
9836
10302
  remote: flag(raw.remote),
9837
10303
  force: flag(raw.force),
10304
+ noSkills: flag(raw["no-skills"]),
9838
10305
  files: text(raw.files),
9839
10306
  note: text(raw.note),
9840
10307
  json: flag(raw.json)
@@ -9925,8 +10392,30 @@ async function dispatchSkills(rest, options) {
9925
10392
  case "install":
9926
10393
  await skillsInstallCommand(options);
9927
10394
  return;
10395
+ case "list":
10396
+ await skillsListCommand(options);
10397
+ return;
10398
+ case "add": {
10399
+ const pack = rest[1];
10400
+ if (pack === void 0) {
10401
+ throw new UsageError("`jioogen skills add` butuh <baseline|ui-quality|backend-rules|all>.");
10402
+ }
10403
+ await skillsAddCommand(pack, options);
10404
+ return;
10405
+ }
10406
+ case "update":
10407
+ await skillsUpdateCommand(options);
10408
+ return;
10409
+ case "remove": {
10410
+ const pack = rest[1];
10411
+ if (pack === void 0) {
10412
+ throw new UsageError("`jioogen skills remove` butuh <baseline|ui-quality|backend-rules>.");
10413
+ }
10414
+ await skillsRemoveCommand(pack, options);
10415
+ return;
10416
+ }
9928
10417
  default:
9929
- throw new UsageError("Subcommand skills: install");
10418
+ throw new UsageError("Subcommand skills: install | list | add | update | remove");
9930
10419
  }
9931
10420
  }
9932
10421
  async function dispatchTask(rest, options) {