jioogen 0.2.2 → 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 +567 -69
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -363,9 +363,9 @@ var require_utils = __commonJS({
|
|
|
363
363
|
fpath.split(self.sep).forEach(function(name) {
|
|
364
364
|
if (!name || name.substr(-1, 1) === ":") return;
|
|
365
365
|
resolvedPath += self.sep + name;
|
|
366
|
-
var
|
|
366
|
+
var stat3;
|
|
367
367
|
try {
|
|
368
|
-
|
|
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 (
|
|
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
|
|
386
|
-
if (
|
|
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,
|
|
420
|
-
if (exist &&
|
|
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
|
|
472
|
+
let stat3;
|
|
473
473
|
try {
|
|
474
|
-
|
|
474
|
+
stat3 = self.fs.lstatSync(cur);
|
|
475
475
|
} catch (e) {
|
|
476
476
|
break;
|
|
477
477
|
}
|
|
478
|
-
if (
|
|
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
|
|
505
|
+
const stat3 = self.fs.statSync(path2);
|
|
506
506
|
if (!pattern || pattern.test(path2)) {
|
|
507
|
-
files.push(pth.normalize(path2) + (
|
|
507
|
+
files.push(pth.normalize(path2) + (stat3.isDirectory() ? self.sep : ""));
|
|
508
508
|
}
|
|
509
|
-
if (
|
|
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,
|
|
556
|
+
self.fs.stat(file, function(err3, stat3) {
|
|
557
557
|
if (err3) return done(err3);
|
|
558
|
-
if (!
|
|
558
|
+
if (!stat3) {
|
|
559
559
|
if (!--pending) done();
|
|
560
560
|
return;
|
|
561
561
|
}
|
|
562
|
-
results.push(pth.normalize(file) + (
|
|
563
|
-
if (!
|
|
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
|
|
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 {
|
|
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
|
-
|
|
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) {
|
|
8933
9154
|
const root = resolve2(bundleDir);
|
|
9155
|
+
const existing = await readHarnessManifest(root);
|
|
8934
9156
|
const written = [];
|
|
8935
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 = {}) {
|
|
9181
|
+
const root = resolve2(bundleDir);
|
|
9182
|
+
const written = [];
|
|
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
|
-
|
|
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
|
|
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
|
|
9336
|
+
entries = await readdir3(tasksDir, { withFileTypes: true });
|
|
8972
9337
|
} catch (error) {
|
|
8973
|
-
if (
|
|
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 (
|
|
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
|
|
9526
|
+
function isMissingFile4(error) {
|
|
9153
9527
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
9154
9528
|
}
|
|
9155
9529
|
|
|
@@ -9269,12 +9643,13 @@ async function pullCommand(projectId, options) {
|
|
|
9269
9643
|
`Project id tidak valid: ${projectId}. Gunakan UUID dari \`jioogen projects\`.`
|
|
9270
9644
|
);
|
|
9271
9645
|
}
|
|
9646
|
+
await assertPullDefaultDir(process.cwd(), options.dir);
|
|
9272
9647
|
const { apiUrl, client } = await resolveApi(options);
|
|
9273
9648
|
const target = resolve4(options.dir ?? projectId);
|
|
9274
9649
|
await assertPullTarget(target, projectId, options.force === true);
|
|
9275
9650
|
const bytes = await client.getBytes(projectPath(projectId, "/bundle"));
|
|
9276
9651
|
const written = await extractBundle(bytes, target);
|
|
9277
|
-
const skills = await installAgentSkills(target);
|
|
9652
|
+
const skills = await installAgentSkills(target, { noSkills: skillsDisabled(options) });
|
|
9278
9653
|
await writeProjectLink(target, { projectId, apiUrl });
|
|
9279
9654
|
if (options.json === true) {
|
|
9280
9655
|
console.log(JSON.stringify({ projectId, target, files: written, skills }, null, 2));
|
|
@@ -9282,9 +9657,8 @@ async function pullCommand(projectId, options) {
|
|
|
9282
9657
|
}
|
|
9283
9658
|
console.log(`Pulled ${written.length} file ke ${target}`);
|
|
9284
9659
|
console.log("Project ter-bind lewat .jioogen/project.json.");
|
|
9285
|
-
console.log(
|
|
9286
|
-
|
|
9287
|
-
);
|
|
9660
|
+
console.log(`Skills eksekusi terpasang: ${skills.written.length} file.`);
|
|
9661
|
+
printSkillPacks(skills.packs, skills.missing);
|
|
9288
9662
|
}
|
|
9289
9663
|
async function linkCommand(projectId, options) {
|
|
9290
9664
|
const { apiUrl, client } = await resolveApi(options);
|
|
@@ -9292,26 +9666,20 @@ async function linkCommand(projectId, options) {
|
|
|
9292
9666
|
await assertLinkTarget(target, projectId);
|
|
9293
9667
|
await verifyProjectAccess(client, projectId);
|
|
9294
9668
|
await writeProjectLink(target, { projectId, apiUrl });
|
|
9295
|
-
const skills = await installAgentSkills(target);
|
|
9669
|
+
const skills = await installAgentSkills(target, { noSkills: skillsDisabled(options) });
|
|
9296
9670
|
const linkedFile = join6(target, ".jioogen", "project.json");
|
|
9297
9671
|
if (options.json === true) {
|
|
9298
|
-
console.log(
|
|
9299
|
-
JSON.stringify(
|
|
9300
|
-
{ projectId, target, apiUrl, linkedFile, skills: skills.written.length },
|
|
9301
|
-
null,
|
|
9302
|
-
2
|
|
9303
|
-
)
|
|
9304
|
-
);
|
|
9672
|
+
console.log(JSON.stringify({ projectId, target, apiUrl, linkedFile, skills }, null, 2));
|
|
9305
9673
|
return;
|
|
9306
9674
|
}
|
|
9307
9675
|
console.log(`Project ${projectId} ter-bind ke ${target}.`);
|
|
9308
9676
|
console.log(`File link: ${linkedFile} (API ${apiUrl}).`);
|
|
9309
9677
|
console.log(`Skills eksekusi terpasang: ${skills.written.length} file.`);
|
|
9678
|
+
printSkillPacks(skills.packs, skills.missing);
|
|
9310
9679
|
}
|
|
9311
9680
|
async function skillsInstallCommand(options) {
|
|
9312
|
-
const
|
|
9313
|
-
const
|
|
9314
|
-
const skills = await installAgentSkills(bundleDir);
|
|
9681
|
+
const bundleDir = await resolveSkillsBundleDir(options);
|
|
9682
|
+
const skills = await installAgentSkills(bundleDir, { noSkills: skillsDisabled(options) });
|
|
9315
9683
|
if (options.json === true) {
|
|
9316
9684
|
console.log(JSON.stringify({ bundleDir, ...skills }, null, 2));
|
|
9317
9685
|
return;
|
|
@@ -9319,6 +9687,90 @@ async function skillsInstallCommand(options) {
|
|
|
9319
9687
|
console.log(`Skills eksekusi terpasang di ${bundleDir}:`);
|
|
9320
9688
|
for (const file of skills.written) console.log(`- ${file}`);
|
|
9321
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
|
+
}
|
|
9322
9774
|
}
|
|
9323
9775
|
async function checkCommand(options) {
|
|
9324
9776
|
const link = await findProjectLink(options.dir ?? process.cwd());
|
|
@@ -9347,15 +9799,19 @@ async function handoffCommand(options) {
|
|
|
9347
9799
|
}
|
|
9348
9800
|
}
|
|
9349
9801
|
async function taskNextCommand(options) {
|
|
9350
|
-
const { projectId, definitions, written, next } = await withLocalState(
|
|
9351
|
-
|
|
9352
|
-
|
|
9353
|
-
|
|
9354
|
-
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
|
|
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
|
+
);
|
|
9359
9815
|
if (next === null) {
|
|
9360
9816
|
if (options.json === true) {
|
|
9361
9817
|
console.log(JSON.stringify({ done: true, projectId }, null, 2));
|
|
@@ -9372,7 +9828,12 @@ async function taskNextCommand(options) {
|
|
|
9372
9828
|
return;
|
|
9373
9829
|
}
|
|
9374
9830
|
const awaitingCheckpoint = written.current?.awaitingCheckpoint === true;
|
|
9375
|
-
const
|
|
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);
|
|
9376
9837
|
if (options.json === true) {
|
|
9377
9838
|
console.log(
|
|
9378
9839
|
JSON.stringify(
|
|
@@ -9635,12 +10096,20 @@ function assertCompleteDependencies(ref, definitions, states, force) {
|
|
|
9635
10096
|
`Tidak bisa menyelesaikan ${ref}: dependency belum selesai (${missing.join(", ")}). Selesaikan dependency itu lebih dulu, atau tambahkan --force bila memang disengaja.`
|
|
9636
10097
|
);
|
|
9637
10098
|
}
|
|
10099
|
+
async function assertPullDefaultDir(cwd, dirOption) {
|
|
10100
|
+
if (dirOption) return;
|
|
10101
|
+
if (await hasBundleMarkers(cwd)) {
|
|
10102
|
+
throw new Error(
|
|
10103
|
+
`Direktori ${cwd} adalah bundle JiooGen. Gunakan \`jioogen link <projectId>\` untuk mengikat folder ini, atau \`--dir <dir>\` eksplisit untuk mengunduh ke folder lain.`
|
|
10104
|
+
);
|
|
10105
|
+
}
|
|
10106
|
+
}
|
|
9638
10107
|
async function assertPullTarget(target, projectId, force = false) {
|
|
9639
10108
|
let entries;
|
|
9640
10109
|
try {
|
|
9641
|
-
entries = await
|
|
10110
|
+
entries = await readdir4(target);
|
|
9642
10111
|
} catch (error) {
|
|
9643
|
-
if (
|
|
10112
|
+
if (isMissingFile5(error)) return;
|
|
9644
10113
|
throw error;
|
|
9645
10114
|
}
|
|
9646
10115
|
if (entries.length === 0) return;
|
|
@@ -9675,16 +10144,16 @@ async function hasBundleMarkers(target) {
|
|
|
9675
10144
|
await readFile6(join6(target, "PRD.md"));
|
|
9676
10145
|
return true;
|
|
9677
10146
|
} catch (error) {
|
|
9678
|
-
if (!
|
|
10147
|
+
if (!isMissingFile5(error)) throw error;
|
|
9679
10148
|
}
|
|
9680
10149
|
return hasTaskMarkdown(join6(target, "tasks"));
|
|
9681
10150
|
}
|
|
9682
10151
|
async function hasTaskMarkdown(directory) {
|
|
9683
10152
|
let names;
|
|
9684
10153
|
try {
|
|
9685
|
-
names = await
|
|
10154
|
+
names = await readdir4(directory);
|
|
9686
10155
|
} catch (error) {
|
|
9687
|
-
if (
|
|
10156
|
+
if (isMissingFile5(error)) return false;
|
|
9688
10157
|
throw error;
|
|
9689
10158
|
}
|
|
9690
10159
|
return names.some((name) => name.toLowerCase().endsWith(".md"));
|
|
@@ -9746,12 +10215,12 @@ function envFirst(primary, legacy) {
|
|
|
9746
10215
|
const fallback = process.env[legacy]?.trim();
|
|
9747
10216
|
return fallback !== void 0 && fallback.length > 0 ? fallback : void 0;
|
|
9748
10217
|
}
|
|
9749
|
-
function
|
|
10218
|
+
function isMissingFile5(error) {
|
|
9750
10219
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
9751
10220
|
}
|
|
9752
10221
|
|
|
9753
10222
|
// src/index.ts
|
|
9754
|
-
var VERSION = "0.2.
|
|
10223
|
+
var VERSION = "0.2.4";
|
|
9755
10224
|
var USAGE = `jioogen \u2014 CLI JiooGen (task selection lokal, server read-only)
|
|
9756
10225
|
|
|
9757
10226
|
Usage:
|
|
@@ -9760,9 +10229,13 @@ Usage:
|
|
|
9760
10229
|
jioogen logout
|
|
9761
10230
|
jioogen whoami
|
|
9762
10231
|
jioogen projects
|
|
9763
|
-
jioogen pull <projectId> [--dir <dir>] [--force]
|
|
9764
|
-
jioogen link <projectId> [--dir <dir>]
|
|
9765
|
-
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>]
|
|
9766
10239
|
jioogen check [--dir <dir>] [--json]
|
|
9767
10240
|
jioogen handoff [--project <id>] [--json]
|
|
9768
10241
|
jioogen task next [--json]
|
|
@@ -9780,6 +10253,7 @@ Global options:
|
|
|
9780
10253
|
--api <url> Base URL JiooGen (env JIOOGEN_API / JIOOCODE_API / tersimpan)
|
|
9781
10254
|
--project <id> Override binding project
|
|
9782
10255
|
--dir <dir> Override direktori bundle
|
|
10256
|
+
--no-skills Jangan pasang skill pack (atau env JIOOGEN_NO_SKILLS=1)
|
|
9783
10257
|
-h, --help Tampilkan bantuan
|
|
9784
10258
|
-v, --version Tampilkan versi
|
|
9785
10259
|
`;
|
|
@@ -9800,6 +10274,7 @@ async function main() {
|
|
|
9800
10274
|
dir: { type: "string" },
|
|
9801
10275
|
remote: { type: "boolean" },
|
|
9802
10276
|
force: { type: "boolean" },
|
|
10277
|
+
"no-skills": { type: "boolean" },
|
|
9803
10278
|
files: { type: "string" },
|
|
9804
10279
|
note: { type: "string" },
|
|
9805
10280
|
json: { type: "boolean" },
|
|
@@ -9826,6 +10301,7 @@ async function main() {
|
|
|
9826
10301
|
dir: text(raw.dir),
|
|
9827
10302
|
remote: flag(raw.remote),
|
|
9828
10303
|
force: flag(raw.force),
|
|
10304
|
+
noSkills: flag(raw["no-skills"]),
|
|
9829
10305
|
files: text(raw.files),
|
|
9830
10306
|
note: text(raw.note),
|
|
9831
10307
|
json: flag(raw.json)
|
|
@@ -9916,8 +10392,30 @@ async function dispatchSkills(rest, options) {
|
|
|
9916
10392
|
case "install":
|
|
9917
10393
|
await skillsInstallCommand(options);
|
|
9918
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
|
+
}
|
|
9919
10417
|
default:
|
|
9920
|
-
throw new UsageError("Subcommand skills: install");
|
|
10418
|
+
throw new UsageError("Subcommand skills: install | list | add | update | remove");
|
|
9921
10419
|
}
|
|
9922
10420
|
}
|
|
9923
10421
|
async function dispatchTask(rest, options) {
|