skissue 0.1.21 → 0.1.23
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/README.md +10 -10
- package/dist/entry.js +136 -97
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -324,7 +324,7 @@ npm run check:all
|
|
|
324
324
|
|
|
325
325
|
This repo uses a three-workflow CI/CD pipeline:
|
|
326
326
|
|
|
327
|
-
1. **PR & push** — [`ci.yml`](.github/workflows/ci.yml) runs `npm run verify` (TypeScript, ESLint, Prettier, tests, harness, harness score, build)
|
|
327
|
+
1. **PR & push** — [`ci.yml`](.github/workflows/ci.yml) runs `npm run verify` (TypeScript, ESLint, Prettier, tests with coverage thresholds, harness, harness score, build)
|
|
328
328
|
2. **Green main** — [`version-and-release.yml`](.github/workflows/version-and-release.yml) bumps the patch version, tags, and creates a GitHub Release
|
|
329
329
|
3. **Tag push** — [`publish.yml`](.github/workflows/publish.yml) runs `npm publish --access public`
|
|
330
330
|
|
|
@@ -371,17 +371,17 @@ skillsRoot: .agents/skills
|
|
|
371
371
|
git clone https://github.com/midyan/skissue.git
|
|
372
372
|
cd skissue
|
|
373
373
|
npm install # also enables Husky git hooks via prepare
|
|
374
|
-
npm run verify # typecheck + lint + format + test + harness + harness score + build
|
|
374
|
+
npm run verify # typecheck + lint + format + test:coverage + harness + harness score + build
|
|
375
375
|
```
|
|
376
376
|
|
|
377
|
-
| Script | What it does
|
|
378
|
-
| ----------------------- |
|
|
379
|
-
| `npm run dev -- <args>` | Run CLI from source via tsx
|
|
380
|
-
| `npm run verify` | Full pipeline: tsc, eslint, prettier, vitest, check:all, harness score, build |
|
|
381
|
-
| `npm run build` | Production build via esbuild
|
|
382
|
-
| `npm test` | Run vitest
|
|
383
|
-
| `npm run check:all` | Harness runner (hard skill checks)
|
|
384
|
-
| `npm run repo-verify` | Same as verify, with explicit skill discovery output
|
|
377
|
+
| Script | What it does |
|
|
378
|
+
| ----------------------- | -------------------------------------------------------------------------------------- |
|
|
379
|
+
| `npm run dev -- <args>` | Run CLI from source via tsx |
|
|
380
|
+
| `npm run verify` | Full pipeline: tsc, eslint, prettier, vitest+coverage, check:all, harness score, build |
|
|
381
|
+
| `npm run build` | Production build via esbuild |
|
|
382
|
+
| `npm test` | Run vitest |
|
|
383
|
+
| `npm run check:all` | Harness runner (hard skill checks) |
|
|
384
|
+
| `npm run repo-verify` | Same as verify, with explicit skill discovery output |
|
|
385
385
|
|
|
386
386
|
### Project structure
|
|
387
387
|
|
package/dist/entry.js
CHANGED
|
@@ -864,9 +864,9 @@ async function runInit(cwd) {
|
|
|
864
864
|
}
|
|
865
865
|
|
|
866
866
|
// src/commands/install.ts
|
|
867
|
-
import
|
|
868
|
-
import
|
|
869
|
-
import { join as
|
|
867
|
+
import chalk4 from "chalk";
|
|
868
|
+
import ora2 from "ora";
|
|
869
|
+
import { join as join7 } from "node:path";
|
|
870
870
|
|
|
871
871
|
// src/lockfile.ts
|
|
872
872
|
import { readFile as readFile2, writeFile as writeFile3, mkdir as mkdir5 } from "node:fs/promises";
|
|
@@ -915,44 +915,85 @@ function removeSkillLock(lock, skillId) {
|
|
|
915
915
|
return { ...lock, skills };
|
|
916
916
|
}
|
|
917
917
|
|
|
918
|
-
// src/registry/
|
|
919
|
-
import { readFile as readFile3 } from "node:fs/promises";
|
|
918
|
+
// src/registry/catalog.ts
|
|
919
|
+
import { access as access2, readFile as readFile3, readdir } from "node:fs/promises";
|
|
920
|
+
import { constants } from "node:fs";
|
|
920
921
|
import { join as join4 } from "node:path";
|
|
921
922
|
import { z as z3 } from "zod";
|
|
922
923
|
var RegistryJsonSchema = z3.object({
|
|
923
924
|
skills: z3.record(z3.string(), z3.string()).optional()
|
|
924
925
|
}).passthrough();
|
|
925
|
-
async function
|
|
926
|
+
async function listRegistrySkillIds(registryRepoRoot) {
|
|
927
|
+
const ids = /* @__PURE__ */ new Set();
|
|
926
928
|
const registryFile = join4(registryRepoRoot, "registry.json");
|
|
929
|
+
try {
|
|
930
|
+
const raw = await readFile3(registryFile, "utf8");
|
|
931
|
+
const parsed = JSON.parse(raw);
|
|
932
|
+
const reg = RegistryJsonSchema.safeParse(parsed);
|
|
933
|
+
if (reg.success && reg.data.skills) {
|
|
934
|
+
for (const id of Object.keys(reg.data.skills)) {
|
|
935
|
+
if (id.trim()) ids.add(id);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
} catch {
|
|
939
|
+
}
|
|
940
|
+
const registryDir = join4(registryRepoRoot, "registry");
|
|
941
|
+
try {
|
|
942
|
+
const entries = await readdir(registryDir, { withFileTypes: true });
|
|
943
|
+
for (const e of entries) {
|
|
944
|
+
if (!e.isDirectory()) continue;
|
|
945
|
+
const id = e.name;
|
|
946
|
+
if (!id.trim()) continue;
|
|
947
|
+
const skillRoot = join4(registryDir, id);
|
|
948
|
+
try {
|
|
949
|
+
await access2(join4(skillRoot, "SKILL.md"), constants.R_OK);
|
|
950
|
+
ids.add(id);
|
|
951
|
+
} catch {
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
} catch {
|
|
955
|
+
}
|
|
956
|
+
return [...ids].sort((a, b) => a.localeCompare(b));
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
// src/registry/resolve.ts
|
|
960
|
+
import { readFile as readFile4 } from "node:fs/promises";
|
|
961
|
+
import { join as join5 } from "node:path";
|
|
962
|
+
import { z as z4 } from "zod";
|
|
963
|
+
var RegistryJsonSchema2 = z4.object({
|
|
964
|
+
skills: z4.record(z4.string(), z4.string()).optional()
|
|
965
|
+
}).passthrough();
|
|
966
|
+
async function resolveSkillPath(registryRepoRoot, skillId) {
|
|
967
|
+
const registryFile = join5(registryRepoRoot, "registry.json");
|
|
927
968
|
let raw;
|
|
928
969
|
try {
|
|
929
|
-
raw = await
|
|
970
|
+
raw = await readFile4(registryFile, "utf8");
|
|
930
971
|
} catch {
|
|
931
|
-
return { skillPath:
|
|
972
|
+
return { skillPath: join5("registry", skillId).replace(/\\/g, "/"), source: "convention" };
|
|
932
973
|
}
|
|
933
974
|
const parsed = JSON.parse(raw);
|
|
934
|
-
const reg =
|
|
975
|
+
const reg = RegistryJsonSchema2.safeParse(parsed);
|
|
935
976
|
if (!reg.success || !reg.data.skills) {
|
|
936
|
-
return { skillPath:
|
|
977
|
+
return { skillPath: join5("registry", skillId).replace(/\\/g, "/"), source: "convention" };
|
|
937
978
|
}
|
|
938
979
|
const mapped = reg.data.skills[skillId];
|
|
939
980
|
if (mapped !== void 0 && mapped.length > 0) {
|
|
940
981
|
return { skillPath: normalizeRelPath(mapped), source: "registry.json" };
|
|
941
982
|
}
|
|
942
|
-
return { skillPath:
|
|
983
|
+
return { skillPath: join5("registry", skillId).replace(/\\/g, "/"), source: "convention" };
|
|
943
984
|
}
|
|
944
985
|
function normalizeRelPath(p4) {
|
|
945
986
|
return p4.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
946
987
|
}
|
|
947
988
|
|
|
948
989
|
// src/io.ts
|
|
949
|
-
import { access as
|
|
950
|
-
import { constants } from "node:fs";
|
|
951
|
-
import { join as
|
|
990
|
+
import { access as access3, cp, rm as rm2 } from "node:fs/promises";
|
|
991
|
+
import { constants as constants2 } from "node:fs";
|
|
992
|
+
import { join as join6 } from "node:path";
|
|
952
993
|
async function assertSkillMdPresent(skillSourceDir) {
|
|
953
|
-
const p4 =
|
|
994
|
+
const p4 = join6(skillSourceDir, "SKILL.md");
|
|
954
995
|
try {
|
|
955
|
-
await
|
|
996
|
+
await access3(p4, constants2.R_OK);
|
|
956
997
|
} catch {
|
|
957
998
|
throw new Error(`Expected SKILL.md in skill path: ${skillSourceDir}`);
|
|
958
999
|
}
|
|
@@ -962,10 +1003,50 @@ async function copySkillTree(fromDir, toDir) {
|
|
|
962
1003
|
await cp(fromDir, toDir, { recursive: true, force: true });
|
|
963
1004
|
}
|
|
964
1005
|
|
|
1006
|
+
// src/commands/uninstall.ts
|
|
1007
|
+
import chalk3 from "chalk";
|
|
1008
|
+
import ora from "ora";
|
|
1009
|
+
import { rm as rm3 } from "node:fs/promises";
|
|
1010
|
+
async function uninstallSkillQuiet(cwd, skillId) {
|
|
1011
|
+
const config = await loadConfig(cwd);
|
|
1012
|
+
const dest = skillInstallPath(cwd, config.skillsRoot, skillId);
|
|
1013
|
+
await rm3(dest, { recursive: true, force: true });
|
|
1014
|
+
const lock = await readLockOrEmpty(cwd);
|
|
1015
|
+
if (lock.skills[skillId]) {
|
|
1016
|
+
await writeLock(cwd, removeSkillLock(lock, skillId));
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
async function runUninstall(cwd, skillId) {
|
|
1020
|
+
const spin = ora(`Removing ${skillId}`).start();
|
|
1021
|
+
try {
|
|
1022
|
+
const lockBefore = await readLockOrEmpty(cwd);
|
|
1023
|
+
await uninstallSkillQuiet(cwd, skillId);
|
|
1024
|
+
if (!lockBefore.skills[skillId]) {
|
|
1025
|
+
spin.stopAndPersist({
|
|
1026
|
+
symbol: chalk3.yellow("\u26A0"),
|
|
1027
|
+
text: chalk3.yellow(`No lock entry for ${skillId}; removed directory if present.`)
|
|
1028
|
+
});
|
|
1029
|
+
} else {
|
|
1030
|
+
spin.succeed(chalk3.green(`Uninstalled ${skillId}`));
|
|
1031
|
+
}
|
|
1032
|
+
} catch (err) {
|
|
1033
|
+
spin.fail(chalk3.red(err instanceof Error ? err.message : String(err)));
|
|
1034
|
+
throw err;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
965
1038
|
// src/commands/install.ts
|
|
966
|
-
async function installSkillFromCheckout(cwd, config, repoPath, head, skillId) {
|
|
1039
|
+
async function installSkillFromCheckout(cwd, config, repoPath, head, skillId, catalogIds) {
|
|
1040
|
+
if (!catalogIds.has(skillId)) {
|
|
1041
|
+
const lock2 = await readLockOrEmpty(cwd);
|
|
1042
|
+
if (lock2.skills[skillId]) {
|
|
1043
|
+
await uninstallSkillQuiet(cwd, skillId);
|
|
1044
|
+
return { outcome: "removed", skillId };
|
|
1045
|
+
}
|
|
1046
|
+
throw new Error(`Skill "${skillId}" is not in the registry.`);
|
|
1047
|
+
}
|
|
967
1048
|
const { skillPath } = await resolveSkillPath(repoPath, skillId);
|
|
968
|
-
const src =
|
|
1049
|
+
const src = join7(repoPath, skillPath);
|
|
969
1050
|
await assertSkillMdPresent(src);
|
|
970
1051
|
const dest = skillInstallPath(cwd, config.skillsRoot, skillId);
|
|
971
1052
|
await copySkillTree(src, dest);
|
|
@@ -977,17 +1058,26 @@ async function installSkillFromCheckout(cwd, config, repoPath, head, skillId) {
|
|
|
977
1058
|
ref
|
|
978
1059
|
});
|
|
979
1060
|
await writeLock(cwd, next);
|
|
980
|
-
return dest;
|
|
1061
|
+
return { outcome: "installed", dest };
|
|
981
1062
|
}
|
|
982
1063
|
async function runInstall(cwd, skillId) {
|
|
983
1064
|
const config = await loadConfig(cwd);
|
|
984
|
-
const spin =
|
|
1065
|
+
const spin = ora2(`Resolving registry and installing ${skillId}`).start();
|
|
985
1066
|
try {
|
|
986
1067
|
const { path: repoPath, head } = await ensureRegistryCheckout(cwd, config);
|
|
987
|
-
const
|
|
988
|
-
|
|
1068
|
+
const catalogIds = new Set(await listRegistrySkillIds(repoPath));
|
|
1069
|
+
const result = await installSkillFromCheckout(cwd, config, repoPath, head, skillId, catalogIds);
|
|
1070
|
+
if (result.outcome === "removed") {
|
|
1071
|
+
spin.succeed(
|
|
1072
|
+
chalk4.yellow(
|
|
1073
|
+
`Removed ${skillId} \u2014 no longer in the registry (was still installed locally).`
|
|
1074
|
+
)
|
|
1075
|
+
);
|
|
1076
|
+
} else {
|
|
1077
|
+
spin.succeed(chalk4.green(`Installed ${skillId} \u2192 ${result.dest}`));
|
|
1078
|
+
}
|
|
989
1079
|
} catch (err) {
|
|
990
|
-
spin.fail(
|
|
1080
|
+
spin.fail(chalk4.red(err instanceof Error ? err.message : String(err)));
|
|
991
1081
|
throw err;
|
|
992
1082
|
}
|
|
993
1083
|
}
|
|
@@ -998,55 +1088,45 @@ async function runInstallMany(cwd, skillIds, options) {
|
|
|
998
1088
|
if (options?.checkout) {
|
|
999
1089
|
({ path: repoPath, head } = options.checkout);
|
|
1000
1090
|
} else {
|
|
1001
|
-
const prep =
|
|
1091
|
+
const prep = ora2("Preparing registry\u2026").start();
|
|
1002
1092
|
try {
|
|
1003
1093
|
const c = await ensureRegistryCheckout(cwd, config);
|
|
1004
1094
|
repoPath = c.path;
|
|
1005
1095
|
head = c.head;
|
|
1006
|
-
prep.succeed(
|
|
1096
|
+
prep.succeed(chalk4.green("Registry ready."));
|
|
1007
1097
|
} catch (err) {
|
|
1008
|
-
prep.fail(
|
|
1098
|
+
prep.fail(chalk4.red(err instanceof Error ? err.message : String(err)));
|
|
1009
1099
|
throw err;
|
|
1010
1100
|
}
|
|
1011
1101
|
}
|
|
1102
|
+
const catalogIds = new Set(await listRegistrySkillIds(repoPath));
|
|
1012
1103
|
for (const skillId of skillIds) {
|
|
1013
|
-
const spin =
|
|
1104
|
+
const spin = ora2(`Installing ${skillId}\u2026`).start();
|
|
1014
1105
|
try {
|
|
1015
|
-
const
|
|
1016
|
-
|
|
1106
|
+
const result = await installSkillFromCheckout(
|
|
1107
|
+
cwd,
|
|
1108
|
+
config,
|
|
1109
|
+
repoPath,
|
|
1110
|
+
head,
|
|
1111
|
+
skillId,
|
|
1112
|
+
catalogIds
|
|
1113
|
+
);
|
|
1114
|
+
if (result.outcome === "removed") {
|
|
1115
|
+
spin.succeed(
|
|
1116
|
+
chalk4.yellow(
|
|
1117
|
+
`Removed ${skillId} \u2014 no longer in the registry (was still installed locally).`
|
|
1118
|
+
)
|
|
1119
|
+
);
|
|
1120
|
+
} else {
|
|
1121
|
+
spin.succeed(chalk4.green(`Installed ${skillId} \u2192 ${result.dest}`));
|
|
1122
|
+
}
|
|
1017
1123
|
} catch (err) {
|
|
1018
|
-
spin.fail(
|
|
1124
|
+
spin.fail(chalk4.red(err instanceof Error ? err.message : String(err)));
|
|
1019
1125
|
throw err;
|
|
1020
1126
|
}
|
|
1021
1127
|
}
|
|
1022
1128
|
}
|
|
1023
1129
|
|
|
1024
|
-
// src/commands/uninstall.ts
|
|
1025
|
-
import chalk4 from "chalk";
|
|
1026
|
-
import ora2 from "ora";
|
|
1027
|
-
import { rm as rm3 } from "node:fs/promises";
|
|
1028
|
-
async function runUninstall(cwd, skillId) {
|
|
1029
|
-
const config = await loadConfig(cwd);
|
|
1030
|
-
const spin = ora2(`Removing ${skillId}`).start();
|
|
1031
|
-
try {
|
|
1032
|
-
const dest = skillInstallPath(cwd, config.skillsRoot, skillId);
|
|
1033
|
-
await rm3(dest, { recursive: true, force: true });
|
|
1034
|
-
const lock = await readLockOrEmpty(cwd);
|
|
1035
|
-
if (!lock.skills[skillId]) {
|
|
1036
|
-
spin.stopAndPersist({
|
|
1037
|
-
symbol: chalk4.yellow("\u26A0"),
|
|
1038
|
-
text: chalk4.yellow(`No lock entry for ${skillId}; removed directory if present.`)
|
|
1039
|
-
});
|
|
1040
|
-
} else {
|
|
1041
|
-
await writeLock(cwd, removeSkillLock(lock, skillId));
|
|
1042
|
-
spin.succeed(chalk4.green(`Uninstalled ${skillId}`));
|
|
1043
|
-
}
|
|
1044
|
-
} catch (err) {
|
|
1045
|
-
spin.fail(chalk4.red(err instanceof Error ? err.message : String(err)));
|
|
1046
|
-
throw err;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
1130
|
// src/commands/list.ts
|
|
1051
1131
|
import chalk5 from "chalk";
|
|
1052
1132
|
async function runList(cwd) {
|
|
@@ -1186,47 +1266,6 @@ function printSkillIssueBanner(version) {
|
|
|
1186
1266
|
console.log("");
|
|
1187
1267
|
}
|
|
1188
1268
|
|
|
1189
|
-
// src/registry/catalog.ts
|
|
1190
|
-
import { access as access3, readFile as readFile4, readdir } from "node:fs/promises";
|
|
1191
|
-
import { constants as constants2 } from "node:fs";
|
|
1192
|
-
import { join as join7 } from "node:path";
|
|
1193
|
-
import { z as z4 } from "zod";
|
|
1194
|
-
var RegistryJsonSchema2 = z4.object({
|
|
1195
|
-
skills: z4.record(z4.string(), z4.string()).optional()
|
|
1196
|
-
}).passthrough();
|
|
1197
|
-
async function listRegistrySkillIds(registryRepoRoot) {
|
|
1198
|
-
const ids = /* @__PURE__ */ new Set();
|
|
1199
|
-
const registryFile = join7(registryRepoRoot, "registry.json");
|
|
1200
|
-
try {
|
|
1201
|
-
const raw = await readFile4(registryFile, "utf8");
|
|
1202
|
-
const parsed = JSON.parse(raw);
|
|
1203
|
-
const reg = RegistryJsonSchema2.safeParse(parsed);
|
|
1204
|
-
if (reg.success && reg.data.skills) {
|
|
1205
|
-
for (const id of Object.keys(reg.data.skills)) {
|
|
1206
|
-
if (id.trim()) ids.add(id);
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
} catch {
|
|
1210
|
-
}
|
|
1211
|
-
const registryDir = join7(registryRepoRoot, "registry");
|
|
1212
|
-
try {
|
|
1213
|
-
const entries = await readdir(registryDir, { withFileTypes: true });
|
|
1214
|
-
for (const e of entries) {
|
|
1215
|
-
if (!e.isDirectory()) continue;
|
|
1216
|
-
const id = e.name;
|
|
1217
|
-
if (!id.trim()) continue;
|
|
1218
|
-
const skillRoot = join7(registryDir, id);
|
|
1219
|
-
try {
|
|
1220
|
-
await access3(join7(skillRoot, "SKILL.md"), constants2.R_OK);
|
|
1221
|
-
ids.add(id);
|
|
1222
|
-
} catch {
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
} catch {
|
|
1226
|
-
}
|
|
1227
|
-
return [...ids].sort((a, b) => a.localeCompare(b));
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
1269
|
// src/commands/manage.ts
|
|
1231
1270
|
var MAIN_MENU_DOUBLE_ESCAPE_MS = 1200;
|
|
1232
1271
|
async function promptMainMenuSelect(availableCount, installedCount) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skissue",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"check:registry": "tsx harness/validate-registry/hard/index.ts",
|
|
42
42
|
"check:all": "tsx harness/runner.ts",
|
|
43
43
|
"check:harness-score": "tsx harness/report-harness-score/hard/index.ts",
|
|
44
|
-
"verify": "tsc --noEmit && npm run lint && npm run format:check && npm test && npm run check:all && npm run check:harness-score && npm run build",
|
|
44
|
+
"verify": "tsc --noEmit && npm run lint && npm run format:check && npm run test:coverage && npm run check:all && npm run check:harness-score && npm run build",
|
|
45
45
|
"repo-verify": "tsx harness/repo-verify/hard/index.ts",
|
|
46
46
|
"prepublishOnly": "npm run build",
|
|
47
47
|
"prepare": "husky || node -e \"process.exit(0)\""
|