skilldrop-cli 0.16.9 → 0.16.11
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 +2 -2
- package/bin/skilldrop.js +84 -50
- package/contracts/guide.schema.json +5 -1
- package/contracts/loop.schema.json +2 -2
- package/package.json +1 -1
- package/packs/career/loops/apply/LOOP.md +11 -11
- package/packs/career/loops/apply/evals/evals.json +2 -2
- package/packs/career/loops/apply/loop.json +3 -3
- package/packs/core/loops/ship-a-draft/LOOP.md +6 -6
- package/packs/core/loops/ship-a-draft/evals/evals.json +2 -2
- package/packs/core/loops/ship-a-draft/loop.json +2 -2
- package/packs/core/pack.json +1 -1
- package/packs/dev-team/loops/release/LOOP.md +15 -15
- package/packs/dev-team/loops/release/loop.json +4 -4
- package/packs/sre-oncall/loops/operate/LOOP.md +4 -4
- package/packs/sre-oncall/loops/operate/evals/evals.json +1 -1
- package/packs/sre-oncall/loops/operate/loop.json +2 -2
- package/packs/sre-oncall/pack.json +1 -1
package/README.md
CHANGED
|
@@ -55,14 +55,14 @@ In Claude Code you can use the plugin marketplace instead: run `/plugin marketpl
|
|
|
55
55
|
## How it works
|
|
56
56
|
|
|
57
57
|
- **A skill produces one file you own.** Each ships a quality bar, named anti-patterns and acceptance evals, so the output is an artifact, not a conversation. See [every skill, by category](guides/reference/skill-catalogue.md).
|
|
58
|
-
- **Loops order skills, and a gate decides when work moves on.** Five loops cover the lifecycle (`discover`, `design`, `build`, `release`, `operate`), plus the `ship-a-draft` wrapper for any document and `apply` for a job application. Each gate is a script, a review panel or a person, chosen by how expensive the mistake is to undo. See [the loops, stage by stage](guides/reference/loops.md) and [why loops](guides/explanation/loops.md).
|
|
58
|
+
- **Loops order skills, and a gate decides when work moves on.** Five loops cover the lifecycle (`discover`, `design`, `build`, `release`, `operate`), plus the `ship-a-draft` wrapper for any document and `apply` for a job application. Each gate is a script, a review panel or a person, chosen by how expensive the mistake is to undo. See [the operating model](guides/explanation/operating-model.md) for every stage and gate on one page, [the loops, stage by stage](guides/reference/loops.md), and [why loops](guides/explanation/loops.md).
|
|
59
59
|
- **Measured, not asserted.** `run_bench.py` runs each acceptance eval with the skill and without it, in a sandbox, and reports the lift, a blind preference from two judges, and the cost per run. The results are published whether or not they flatter the skills. See [how skills are checked](https://sananthanarayan.github.io/skilldrop/evals/) and [RFC-0040](docs/rfcs/0040-skill-benchmark.md).
|
|
60
60
|
- **Copy, never transform.** A skill is a plain `SKILL.md` folder in the [Agent Skills](https://agentskills.io) format, at `packs/<pack>/skills/<name>/`. What runs in your agent is byte-identical to what is reviewed here. There is no runtime, and the tooling has zero dependencies.
|
|
61
61
|
- **Updates keep your edits.** `npx skilldrop-cli update` replaces files you haven't touched. For any file you changed, it leaves the new version beside it as `<file>.upstream`. See [upgrade installed skills](guides/how-to/upgrade-skills.md).
|
|
62
62
|
|
|
63
63
|
## Go deeper
|
|
64
64
|
|
|
65
|
-
- **Use it:** [docs portal](https://sananthanarayan.github.io/skilldrop/docs/) · [a guide for every pack](guides/README.md#per-pack) · [install in any IDE](guides/how-to/install-per-ide.md) · [try skills in a repo you don't own](guides/how-to/install-per-ide.md#
|
|
65
|
+
- **Use it:** [the operating model](guides/explanation/operating-model.md) · [docs portal](https://sananthanarayan.github.io/skilldrop/docs/) · [a guide for every pack](guides/README.md#per-pack) · [install in any IDE](guides/how-to/install-per-ide.md) · [try skills in a repo you don't own](guides/how-to/install-per-ide.md#try-skills-in-a-repo-you-dont-own) · [profiles](guides/how-to/profiles.md) · [hooks](guides/how-to/wire-a-hook.md)
|
|
66
66
|
- **Run it in CI:** [a skill on every pull request](guides/how-to/run-a-skill-in-ci.md) · [how skills are checked](https://sananthanarayan.github.io/skilldrop/evals/) · [benchmark skills yourself](guides/how-to/benchmark-skills.md) · [measure your loops](guides/how-to/measure-your-loops.md)
|
|
67
67
|
- **Roll it out:** [across your org, including an internal mirror](guides/how-to/enterprise-distribution.md) · [publish your own catalogue](guides/how-to/publish-a-catalogue.md) · [security and the OWASP Top 10s](guides/reference/owasp-mapping.md)
|
|
68
68
|
- **Reviewers:** three subagents that push back on generated work: `devils-advocate`, `security-reviewer` and `code-quality`. See [`agents/`](agents/README.md).
|
package/bin/skilldrop.js
CHANGED
|
@@ -51,13 +51,13 @@ Usage:
|
|
|
51
51
|
(--no-skills for the loop alone) (RFC-0028)
|
|
52
52
|
skilldrop install --panel review install the review panel — the 3 reviewer subagents +
|
|
53
53
|
the pre-merge-review orchestrator that fires them (RFC-0020)
|
|
54
|
-
skilldrop update [--force] [--changed] re-copy installed skills whose version changed; files
|
|
54
|
+
skilldrop update [--force] [--changed] re-copy installed skills and loops whose version changed; files
|
|
55
55
|
you edited are kept and the new copy lands beside them
|
|
56
56
|
as <file>.upstream (--force overwrites) (RFC-0032).
|
|
57
57
|
Files changed upstream under the same version are named,
|
|
58
58
|
not taken, unless you add --changed (RFC-0039)
|
|
59
59
|
skilldrop outdated show installed vs current versions, change nothing
|
|
60
|
-
skilldrop diff <
|
|
60
|
+
skilldrop diff <name> [--stat] what differs between your installed skill or loop and the catalog's —
|
|
61
61
|
your edits, upstream changes, or both
|
|
62
62
|
skilldrop doctor check a target against its ledger: missing folders, leftover
|
|
63
63
|
.upstream files, stale wiring and hooks. Changes nothing.
|
|
@@ -1015,16 +1015,50 @@ function fileHashes(dir) {
|
|
|
1015
1015
|
return out;
|
|
1016
1016
|
}
|
|
1017
1017
|
|
|
1018
|
+
/* A loop installs as <name>/SKILL.md (its LOOP.md) plus <name>/loop.json. update, outdated,
|
|
1019
|
+
diff and doctor treat a ledger row marked `loop` like a skill through these helpers: which
|
|
1020
|
+
files it installs and where the catalog keeps each one, its hashes, metadata and check. */
|
|
1021
|
+
function loopFiles(cat, n) {
|
|
1022
|
+
const d = loopDir(cat, n);
|
|
1023
|
+
return { "SKILL.md": path.join(d, "LOOP.md"), "loop.json": path.join(d, "loop.json") };
|
|
1024
|
+
}
|
|
1025
|
+
function unitHashes(cat, s, isLoop) {
|
|
1026
|
+
if (!isLoop) return fileHashes(skillDir(cat, s));
|
|
1027
|
+
const out = {};
|
|
1028
|
+
for (const [rel, p] of Object.entries(loopFiles(cat, s))) out[rel] = sha256(fs.readFileSync(p));
|
|
1029
|
+
return out;
|
|
1030
|
+
}
|
|
1031
|
+
function unitSource(cat, s, isLoop, rel) {
|
|
1032
|
+
return isLoop ? loopFiles(cat, s)[rel] : path.join(skillDir(cat, s), ...rel.split("/"));
|
|
1033
|
+
}
|
|
1034
|
+
function unitExists(cat, s, isLoop) { return isLoop ? loopsIn(cat).includes(s) : skillExists(cat, s); }
|
|
1035
|
+
function unitMeta(cat, s, isLoop) { return isLoop ? loopSpec(cat, s) : manifestOf(cat, s); }
|
|
1036
|
+
function unitCheck(cat, s, isLoop) { return isLoop ? checkLoop(cat, s) : checkSkill(cat, s); }
|
|
1037
|
+
function isLoopEntry(entry) { return !!(entry && typeof entry === "object" && entry.loop); }
|
|
1038
|
+
function unitLabel(r) { return r.loop ? `loop ${r.s}` : r.s; }
|
|
1039
|
+
|
|
1040
|
+
function copyLoop(cat, n, dest, ide, l, notes) {
|
|
1041
|
+
const sp = loopSpec(cat, n);
|
|
1042
|
+
const out = path.join(dest, n);
|
|
1043
|
+
fs.mkdirSync(out, { recursive: true });
|
|
1044
|
+
for (const [rel, p] of Object.entries(loopFiles(cat, n))) fs.copyFileSync(p, path.join(out, rel));
|
|
1045
|
+
const note = writeWiring(ide, dest, n, sp.description);
|
|
1046
|
+
if (note && notes) notes.push(note);
|
|
1047
|
+
l.data[n] = { version: sp.version || null, source: cat.source, loop: true, files: unitHashes(cat, n, true) };
|
|
1048
|
+
if (cat.commit) l.data[n].commit = cat.commit;
|
|
1049
|
+
return sp;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1018
1052
|
/* Update one skill file by file against the ledger baseline (RFC-0032):
|
|
1019
1053
|
upstream unchanged -> leave the local file alone, edited or not
|
|
1020
1054
|
local untouched, or new file -> take the new version
|
|
1021
1055
|
local edited, upstream moved -> keep the local file, write the new one as <file>.upstream
|
|
1022
1056
|
A file the catalog dropped is removed only if the user never touched it. The new baseline is
|
|
1023
1057
|
what the catalog ships now, so a kept edit stays "edited" until the user merges it. */
|
|
1024
|
-
function mergeOne(cat, s, dest, ide, l, notes, dry = false) {
|
|
1025
|
-
const m =
|
|
1026
|
-
const
|
|
1027
|
-
const base = l.data[s].files, next =
|
|
1058
|
+
function mergeOne(cat, s, dest, ide, l, notes, dry = false, isLoop = false) {
|
|
1059
|
+
const m = unitMeta(cat, s, isLoop);
|
|
1060
|
+
const dst = path.join(dest, s);
|
|
1061
|
+
const base = l.data[s].files, next = unitHashes(cat, s, isLoop);
|
|
1028
1062
|
const kept = [], orphaned = [];
|
|
1029
1063
|
const at = (root, rel) => path.join(root, ...rel.split("/"));
|
|
1030
1064
|
const localHash = (rel) => { try { return sha256(fs.readFileSync(at(dst, rel))); } catch (e) { return null; } };
|
|
@@ -1035,13 +1069,13 @@ function mergeOne(cat, s, dest, ide, l, notes, dry = false) {
|
|
|
1035
1069
|
if (mine === null ? !(rel in base) : mine === base[rel]) {
|
|
1036
1070
|
if (dry) continue;
|
|
1037
1071
|
fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
1038
|
-
fs.copyFileSync(
|
|
1072
|
+
fs.copyFileSync(unitSource(cat, s, isLoop, rel), to);
|
|
1039
1073
|
fs.rmSync(to + ".upstream", { force: true });
|
|
1040
1074
|
} else {
|
|
1041
1075
|
kept.push(rel);
|
|
1042
1076
|
if (dry) continue;
|
|
1043
1077
|
fs.mkdirSync(path.dirname(to), { recursive: true });
|
|
1044
|
-
fs.copyFileSync(
|
|
1078
|
+
fs.copyFileSync(unitSource(cat, s, isLoop, rel), to + ".upstream");
|
|
1045
1079
|
}
|
|
1046
1080
|
}
|
|
1047
1081
|
for (const rel of Object.keys(base)) {
|
|
@@ -1053,7 +1087,8 @@ function mergeOne(cat, s, dest, ide, l, notes, dry = false) {
|
|
|
1053
1087
|
if (dry) return { m, kept, orphaned };
|
|
1054
1088
|
const note = writeWiring(ide, dest, s, m.description);
|
|
1055
1089
|
if (note && notes) notes.push(note);
|
|
1056
|
-
l.data[s] = { version: m.version, source: cat.source, files: next };
|
|
1090
|
+
l.data[s] = { version: m.version || null, source: cat.source, files: next };
|
|
1091
|
+
if (isLoop) l.data[s].loop = true;
|
|
1057
1092
|
if (cat.commit) l.data[s].commit = cat.commit;
|
|
1058
1093
|
return { m, kept, orphaned };
|
|
1059
1094
|
}
|
|
@@ -1252,20 +1287,21 @@ function installedRows(flags) {
|
|
|
1252
1287
|
const src = lsrc(l.data[s]);
|
|
1253
1288
|
let current = null, cat = null, changed = [];
|
|
1254
1289
|
const entry = l.data[s];
|
|
1290
|
+
const loop = isLoopEntry(entry);
|
|
1255
1291
|
try {
|
|
1256
1292
|
cat = resolveCatalog(src === BUNDLED ? undefined : src);
|
|
1257
|
-
if (
|
|
1258
|
-
current =
|
|
1293
|
+
if (unitExists(cat, s, loop)) {
|
|
1294
|
+
current = unitMeta(cat, s, loop).version || null;
|
|
1259
1295
|
// Same version, different files: the catalog changed the skill without bumping it.
|
|
1260
1296
|
// The version check alone would never see that (OWASP AST07, update drift).
|
|
1261
1297
|
if (current === lver(entry) && typeof entry === "object" && entry.files) {
|
|
1262
|
-
const now =
|
|
1298
|
+
const now = unitHashes(cat, s, loop);
|
|
1263
1299
|
changed = [...new Set([...Object.keys(now), ...Object.keys(entry.files)])]
|
|
1264
1300
|
.filter((f) => now[f] !== entry.files[f]).sort();
|
|
1265
1301
|
}
|
|
1266
1302
|
}
|
|
1267
1303
|
} catch (e) { /* unreachable source: current stays null */ }
|
|
1268
|
-
return { s, src, cat, installed: lver(entry), current, changed, pinned: isPinned(src) };
|
|
1304
|
+
return { s, src, cat, loop, installed: lver(entry), current, changed, pinned: isPinned(src) };
|
|
1269
1305
|
});
|
|
1270
1306
|
return { dest, ide, l, rows };
|
|
1271
1307
|
}
|
|
@@ -1289,18 +1325,20 @@ function update(args) {
|
|
|
1289
1325
|
let n = 0, conflicts = 0;
|
|
1290
1326
|
const silent = rows.filter((r) => r.current === r.installed && r.changed.length && !args.flags.changed);
|
|
1291
1327
|
for (const r of rows) {
|
|
1292
|
-
if (!r.current) { console.log(`skip ${r
|
|
1328
|
+
if (!r.current) { console.log(`skip ${unitLabel(r)}: source '${r.src}' unreachable, ${r.loop ? "loop" : "skill"} gone from it, or no version to compare`); continue; }
|
|
1293
1329
|
if (!isDue(r)) continue;
|
|
1294
|
-
const problems =
|
|
1295
|
-
if (problems.length) { console.log(`skip ${r
|
|
1296
|
-
// No baseline (installed before RFC-0032
|
|
1330
|
+
const problems = unitCheck(r.cat, r.s, r.loop);
|
|
1331
|
+
if (problems.length) { console.log(`skip ${unitLabel(r)}: fails structural check in '${r.src}' (${problems[0]})`); continue; }
|
|
1332
|
+
// No baseline (installed before RFC-0032, or a loop installed before 0.16.11) or --force:
|
|
1333
|
+
// overwrite as before, and record one.
|
|
1297
1334
|
const entry = l.data[r.s];
|
|
1298
1335
|
if (args.flags.force || typeof entry !== "object" || !entry.files) {
|
|
1299
|
-
|
|
1300
|
-
|
|
1336
|
+
if (r.loop) copyLoop(r.cat, r.s, dest, ide, l);
|
|
1337
|
+
else copyOne(r.cat, r.s, dest, ide, l);
|
|
1338
|
+
console.log(`updated ${unitLabel(r)} ${r.installed === r.current ? `${r.current} (same version, changed upstream)` : `${r.installed} -> ${r.current}`} (${r.src})`);
|
|
1301
1339
|
} else {
|
|
1302
|
-
const { kept, orphaned } = mergeOne(r.cat, r.s, dest, ide, l);
|
|
1303
|
-
console.log(`updated ${r
|
|
1340
|
+
const { kept, orphaned } = mergeOne(r.cat, r.s, dest, ide, l, null, false, r.loop);
|
|
1341
|
+
console.log(`updated ${unitLabel(r)} ${r.installed === r.current ? `${r.current} (same version, changed upstream)` : `${r.installed} -> ${r.current}`} (${r.src})`);
|
|
1304
1342
|
for (const f of kept) console.log(` kept your edits in ${f} — new version at ${f}.upstream`);
|
|
1305
1343
|
for (const f of orphaned) console.log(` kept ${f} — dropped upstream, but you edited it`);
|
|
1306
1344
|
conflicts += kept.length;
|
|
@@ -1308,7 +1346,7 @@ function update(args) {
|
|
|
1308
1346
|
n++;
|
|
1309
1347
|
}
|
|
1310
1348
|
saveLedger(l);
|
|
1311
|
-
console.log(n ? `\n${n} skill(s) updated.` : (silent.length ? "no version changes." : "everything up to date."));
|
|
1349
|
+
console.log(n ? `\n${n} skill(s) or loop(s) updated.` : (silent.length ? "no version changes." : "everything up to date."));
|
|
1312
1350
|
if (silent.length)
|
|
1313
1351
|
console.log(`\nNot taken: ${silent.map((r) => r.s).join(", ")} changed upstream without a version bump.` +
|
|
1314
1352
|
`\n Read the change (skilldrop diff <skill>), then take it with: skilldrop update --changed`);
|
|
@@ -1333,28 +1371,28 @@ function planUpdate(dest, ide, l, rows, flags) {
|
|
|
1333
1371
|
console.log(`dry run — nothing will be written. Target: ${dest} (${ide})\n`);
|
|
1334
1372
|
let n = 0;
|
|
1335
1373
|
for (const r of rows) {
|
|
1336
|
-
if (!r.current) { console.log(`would skip ${r
|
|
1374
|
+
if (!r.current) { console.log(`would skip ${unitLabel(r)}: source '${r.src}' unreachable, ${r.loop ? "loop" : "skill"} gone from it, or no version to compare`); continue; }
|
|
1337
1375
|
if (r.current === r.installed && !r.changed.length) continue;
|
|
1338
1376
|
if (r.current === r.installed && !flags.changed) {
|
|
1339
|
-
console.log(`would not take ${r
|
|
1377
|
+
console.log(`would not take ${unitLabel(r)}: ${r.changed.length} file(s) changed upstream without a version bump (add --changed to take them)`);
|
|
1340
1378
|
continue;
|
|
1341
1379
|
}
|
|
1342
|
-
const problems =
|
|
1343
|
-
if (problems.length) { console.log(`would skip ${r
|
|
1380
|
+
const problems = unitCheck(r.cat, r.s, r.loop);
|
|
1381
|
+
if (problems.length) { console.log(`would skip ${unitLabel(r)}: fails structural check (${problems[0]})`); continue; }
|
|
1344
1382
|
n++;
|
|
1345
1383
|
const entry = l.data[r.s];
|
|
1346
|
-
console.log(`would update ${r
|
|
1384
|
+
console.log(`would update ${unitLabel(r)} ${r.installed} -> ${r.current} (${r.src})`);
|
|
1347
1385
|
if (flags.force || typeof entry !== "object" || !entry.files) {
|
|
1348
1386
|
const lost = editedFiles(dest, r.s, entry);
|
|
1349
1387
|
if (lost.length) console.log(` --force overwrites your edits in: ${lost.join(", ")}`);
|
|
1350
1388
|
continue;
|
|
1351
1389
|
}
|
|
1352
|
-
const { kept, orphaned } = mergeOne(r.cat, r.s, dest, ide, l, null, true);
|
|
1390
|
+
const { kept, orphaned } = mergeOne(r.cat, r.s, dest, ide, l, null, true, r.loop);
|
|
1353
1391
|
for (const f of kept) console.log(` would keep your edits in ${f} and write the new version to ${f}.upstream`);
|
|
1354
1392
|
for (const f of orphaned) console.log(` would keep ${f} — dropped upstream, but you edited it`);
|
|
1355
1393
|
}
|
|
1356
1394
|
const held = rows.filter((r) => r.current && r.current === r.installed && r.changed.length && !flags.changed).length;
|
|
1357
|
-
console.log(n ? `\n${n} skill(s) would update. Rerun without --dry-run to apply.` : (held ? "no version changes." : "everything up to date."));
|
|
1395
|
+
console.log(n ? `\n${n} skill(s) or loop(s) would update. Rerun without --dry-run to apply.` : (held ? "no version changes." : "everything up to date."));
|
|
1358
1396
|
}
|
|
1359
1397
|
|
|
1360
1398
|
function outdated(args) {
|
|
@@ -1368,15 +1406,15 @@ function outdated(args) {
|
|
|
1368
1406
|
outdatedCount: stale.length,
|
|
1369
1407
|
changedWithoutVersionCount: drift.length,
|
|
1370
1408
|
skills: rows.map((r) => ({
|
|
1371
|
-
name: r.s, installed: r.installed, current: r.current,
|
|
1409
|
+
name: r.s, loop: r.loop, installed: r.installed, current: r.current,
|
|
1372
1410
|
source: r.src, outdated: !!(r.current && r.current !== r.installed),
|
|
1373
1411
|
changedWithoutVersion: r.changed || [], pinned: r.pinned,
|
|
1374
1412
|
})),
|
|
1375
1413
|
});
|
|
1376
1414
|
if (!rows.length) return console.log(`nothing installed at ${dest}`);
|
|
1377
|
-
for (const r of stale) console.log(`${r
|
|
1415
|
+
for (const r of stale) console.log(`${unitLabel(r)}: installed ${r.installed}, current ${r.current} (${r.src})`);
|
|
1378
1416
|
for (const r of drift)
|
|
1379
|
-
console.log(`${r
|
|
1417
|
+
console.log(`${unitLabel(r)}: ${r.changed.length} file(s) changed in '${r.src}' without a version bump (${r.changed.slice(0, 3).join(", ")}${r.changed.length > 3 ? ", …" : ""})`);
|
|
1380
1418
|
if (drift.length)
|
|
1381
1419
|
console.log(`\n${drift.length} skill(s) changed upstream with the same version. Read the change first (skilldrop diff <skill>),` +
|
|
1382
1420
|
`\nthen take it with: skilldrop update --changed`);
|
|
@@ -1424,23 +1462,23 @@ function uninstall(args) {
|
|
|
1424
1462
|
edits, an upstream change, or both. File-level always; line-level through git when it is on
|
|
1425
1463
|
the PATH (git diff --no-index works outside any repo). */
|
|
1426
1464
|
function diff(args) {
|
|
1427
|
-
const s = args._[0] || die("pass an installed skill name: skilldrop diff <
|
|
1465
|
+
const s = args._[0] || die("pass an installed skill or loop name: skilldrop diff <name>");
|
|
1428
1466
|
const { dest } = target(args.flags);
|
|
1429
1467
|
const l = ledger(dest);
|
|
1430
1468
|
const entry = l.data[s];
|
|
1431
1469
|
if (!entry && !fs.existsSync(path.join(dest, s))) die(`'${s}' is not installed at ${dest}`);
|
|
1432
|
-
|
|
1470
|
+
const loop = isLoopEntry(entry);
|
|
1433
1471
|
const src = args.flags.from || (entry ? lsrc(entry) : BUNDLED);
|
|
1434
1472
|
const cat = resolveCatalog(src === BUNDLED ? undefined : src);
|
|
1435
|
-
if (!
|
|
1436
|
-
const theirs =
|
|
1473
|
+
if (!unitExists(cat, s, loop)) die(`'${s}' is no longer in catalog '${cat.source}'`);
|
|
1474
|
+
const theirs = unitHashes(cat, s, loop), mine = fileHashes(path.join(dest, s));
|
|
1437
1475
|
const base = (entry && entry.files) || {};
|
|
1438
1476
|
const changed = Object.keys(theirs).filter((f) => f in mine && mine[f] !== theirs[f]).sort();
|
|
1439
1477
|
const onlyUp = Object.keys(theirs).filter((f) => !(f in mine)).sort();
|
|
1440
1478
|
const onlyMine = Object.keys(mine).filter((f) => !(f in theirs)).sort();
|
|
1441
1479
|
const why = (f) => !base[f] ? "" : mine[f] !== base[f] && theirs[f] !== base[f] ? " (you edited it, and so did the catalog)"
|
|
1442
1480
|
: mine[f] !== base[f] ? " (your edit)" : " (catalog changed it)";
|
|
1443
|
-
const iv = entry ? lver(entry) : "?", cv =
|
|
1481
|
+
const iv = entry ? lver(entry) : "?", cv = unitMeta(cat, s, loop).version;
|
|
1444
1482
|
console.log(`${s}: installed ${iv} at ${path.join(dest, s)} vs catalog '${cat.source}' ${cv}`);
|
|
1445
1483
|
if (!changed.length && !onlyUp.length && !onlyMine.length) return console.log("identical.");
|
|
1446
1484
|
for (const f of changed) console.log(` M ${f}${why(f)}`);
|
|
@@ -1449,7 +1487,7 @@ function diff(args) {
|
|
|
1449
1487
|
if (args.flags.stat) return;
|
|
1450
1488
|
let git = true;
|
|
1451
1489
|
for (const f of changed) {
|
|
1452
|
-
const a =
|
|
1490
|
+
const a = unitSource(cat, s, loop, f), b = path.join(dest, s, ...f.split("/"));
|
|
1453
1491
|
try {
|
|
1454
1492
|
execFileSync("git", ["--no-pager", "diff", "--no-index", "--color=auto", "--src-prefix=catalog/", "--dst-prefix=installed/", a, b],
|
|
1455
1493
|
{ stdio: ["ignore", "inherit", "inherit"] });
|
|
@@ -1459,7 +1497,7 @@ function diff(args) {
|
|
|
1459
1497
|
}
|
|
1460
1498
|
}
|
|
1461
1499
|
if (!git) console.log("\n(git not found — line-level diff needs git on the PATH; file list above)");
|
|
1462
|
-
if (changed.length) console.log(`\nTake the catalog's version: skilldrop update --force, or install ${s} again.`);
|
|
1500
|
+
if (changed.length) console.log(`\nTake the catalog's version: skilldrop update --force, or install ${loop ? "--loop " : ""}${s} again.`);
|
|
1463
1501
|
}
|
|
1464
1502
|
|
|
1465
1503
|
/* doctor: check an install target against its ledger and report what's out of step — skills
|
|
@@ -1477,10 +1515,13 @@ function doctor(args) {
|
|
|
1477
1515
|
const names = Object.keys(data);
|
|
1478
1516
|
for (const n of names) {
|
|
1479
1517
|
if (!fs.existsSync(path.join(dest, n))) add("error", `${n} is in the ledger but its folder is gone`, `skilldrop install ${n} (or uninstall ${n} to drop the record)`);
|
|
1480
|
-
else if (!data[n].
|
|
1518
|
+
else if (!data[n].file) {
|
|
1519
|
+
const loop = isLoopEntry(data[n]);
|
|
1481
1520
|
const e = editedFiles(dest, n, data[n]);
|
|
1482
1521
|
if (e.length) add("info", `${n}: you edited ${e.join(", ")}`, `skilldrop diff ${n}`);
|
|
1483
|
-
if (typeof data[n] === "string" || !data[n].files)
|
|
1522
|
+
if (typeof data[n] === "string" || !data[n].files)
|
|
1523
|
+
add("warn", `${loop ? "loop " : ""}${n} has no file baseline (installed before ${loop ? "0.16.11" : "0.13"})`,
|
|
1524
|
+
`skilldrop install ${loop ? "--loop " : ""}${n} once; update will keep your edits after that`);
|
|
1484
1525
|
}
|
|
1485
1526
|
}
|
|
1486
1527
|
if (fs.existsSync(dest)) {
|
|
@@ -1843,15 +1884,8 @@ function installLoops(args) {
|
|
|
1843
1884
|
const l = ledger(dest);
|
|
1844
1885
|
const notes = [];
|
|
1845
1886
|
for (const n of names) {
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
fs.mkdirSync(out, { recursive: true });
|
|
1849
|
-
fs.copyFileSync(path.join(loopDir(cat, n), "LOOP.md"), path.join(out, "SKILL.md"));
|
|
1850
|
-
fs.copyFileSync(path.join(loopDir(cat, n), "loop.json"), path.join(out, "loop.json"));
|
|
1851
|
-
const note = writeWiring(ide, dest, n, sp.description);
|
|
1852
|
-
if (note) notes.push(note);
|
|
1853
|
-
l.data[n] = { version: sp.version || null, source: cat.source, loop: true };
|
|
1854
|
-
console.log(`installed loop ${n} -> ${out}`);
|
|
1887
|
+
copyLoop(cat, n, dest, ide, l, notes);
|
|
1888
|
+
console.log(`installed loop ${n} -> ${path.join(dest, n)}`);
|
|
1855
1889
|
}
|
|
1856
1890
|
saveLedger(l);
|
|
1857
1891
|
if (args.flags.local) addLocal(dest, ide, names);
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
"enum": ["tutorial", "how-to", "reference", "explanation"],
|
|
14
14
|
"description": "tutorial = learning by doing. how-to = a task with a goal. reference = look it up. explanation = why it is this way. A page that cannot pick one is doing two jobs and should be split."
|
|
15
15
|
},
|
|
16
|
-
"status": { "enum": ["draft", "review", "stable", "deprecated"], "default": "stable" }
|
|
16
|
+
"status": { "enum": ["draft", "review", "stable", "deprecated"], "default": "stable" },
|
|
17
|
+
"stage": {
|
|
18
|
+
"enum": ["discover", "design", "build", "release", "operate"],
|
|
19
|
+
"description": "RFC-0044. The lifecycle stage a page belongs to, when it belongs to one. The docs sidebar groups staged pages under that stage, beside the stage's loop; a page that spans every stage, or none, leaves it out."
|
|
20
|
+
}
|
|
17
21
|
}
|
|
18
22
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"$comment": "RFC-0028. A loop sequences existing skills; it never contains one. Skills stay in their own packs/<pack>/skills/<name> folders and stay independently installable — a loop is a named order plus the gates between stages. Enforced by validate.py with hand-rolled stdlib checks: skilldrop ships zero runtime dependencies and this file must not become a reason to add `jsonschema`.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
|
-
"required": ["name", "description", "entrypoint", "kind", "stages"],
|
|
8
|
+
"required": ["name", "version", "description", "entrypoint", "kind", "stages"],
|
|
9
9
|
"properties": {
|
|
10
10
|
"$comment": { "type": "string" },
|
|
11
11
|
"name": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"pattern": "^[a-z0-9][a-z0-9-]*$",
|
|
14
14
|
"description": "Kebab-case. Must equal the folder name and the LOOP.md frontmatter name — the same triple-match skills are held to."
|
|
15
15
|
},
|
|
16
|
-
"version": { "type": "string" },
|
|
16
|
+
"version": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$", "description": "Bump it whenever LOOP.md or loop.json changes: `skilldrop update` compares it with the version a user installed." },
|
|
17
17
|
"description": {
|
|
18
18
|
"type": "string",
|
|
19
19
|
"description": "Use-case-first. First half says what the loop delivers; second half states the trigger phrases an agent matches on."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skilldrop-cli",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.11",
|
|
4
4
|
"description": "Portable AI-agent skills, measured against the agent without them. ADRs, PRDs, runbooks, decks and reviews, installable into Claude Code, Cursor, Kiro, and any IDE that takes custom instructions. One command per skill or per role-based pack.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Sanjay Ananthanarayan",
|
|
@@ -14,18 +14,18 @@ can say what the resume shows, but only the candidate knows what is true and wha
|
|
|
14
14
|
|
|
15
15
|
| # | Stage | Type | Skills | Gate |
|
|
16
16
|
|---|---|---|---|---|
|
|
17
|
-
| 1 | `fit` | gate | [`job-fit-analysis`](../../skills/job-fit-analysis/SKILL.md) | **
|
|
17
|
+
| 1 | `fit` | gate | [`job-fit-analysis`](../../skills/job-fit-analysis/SKILL.md) | **G6** human |
|
|
18
18
|
| 2 | `tailor` | generate | [`resume-tailor`](../../skills/resume-tailor/SKILL.md) | — |
|
|
19
19
|
| 3 | `letter` | generate | [`cover-letter`](../../skills/cover-letter/SKILL.md) | — |
|
|
20
20
|
| 4 | `form` | generate | [`application-form-answers`](../../skills/application-form-answers/SKILL.md) | — |
|
|
21
|
-
| 5 | `confirm` | verify | [`output-hygiene`](../../../core/skills/output-hygiene/SKILL.md) | **G6** human |
|
|
21
|
+
| 5 | `confirm` | verify | [`output-hygiene`](../../../core/skills/output-hygiene/SKILL.md) | **G6.1** human |
|
|
22
22
|
|
|
23
23
|
## How to run this loop
|
|
24
24
|
|
|
25
25
|
1. **Check the fit and stop.** Run `job-fit-analysis` on the posting, the resume and any
|
|
26
26
|
other material of the candidate's: a LinkedIn summary, a portfolio, a project. Put its
|
|
27
27
|
verdict, what could get the application rejected and its questions to the candidate,
|
|
28
|
-
including whether anything outside the resume covers a gap, then wait at
|
|
28
|
+
including whether anything outside the resume covers a gap, then wait at G6.
|
|
29
29
|
- `PROCEED`: they are applying. Go on.
|
|
30
30
|
- `PROCEED WITH CONDITIONS`: they are applying and have answered the questions. Save
|
|
31
31
|
their answers to `notes.md`; from here on those answers are source material alongside
|
|
@@ -41,7 +41,7 @@ can say what the resume shows, but only the candidate knows what is true and wha
|
|
|
41
41
|
the documents, run `application-form-answers` with those questions and the tailored
|
|
42
42
|
resume. Skip this stage when there is no form.
|
|
43
43
|
5. **Clean the documents.** Run `output-hygiene` on the resume and the letter.
|
|
44
|
-
6. **Confirm at G6.** Show the candidate the documents with four short lists: what the
|
|
44
|
+
6. **Confirm at G6.1.** Show the candidate the documents with four short lists: what the
|
|
45
45
|
posting asks for that the documents do not claim; every fact added from outside the
|
|
46
46
|
resume and where it came from; every line cut from the resume; and the form questions
|
|
47
47
|
that are theirs alone to answer. Ask them to confirm every line is true. `NOT READY` returns to `tailor` with their
|
|
@@ -49,8 +49,8 @@ can say what the resume shows, but only the candidate knows what is true and wha
|
|
|
49
49
|
|
|
50
50
|
## Verdicts
|
|
51
51
|
|
|
52
|
-
Defined in [`contracts/terminals.json`](../../../../contracts/terminals.json).
|
|
53
|
-
`PROCEED`, `PROCEED WITH CONDITIONS`, `RECONSIDER` or `BLOCKED`. G6 emits `READY`,
|
|
52
|
+
Defined in [`contracts/terminals.json`](../../../../contracts/terminals.json). G6 emits
|
|
53
|
+
`PROCEED`, `PROCEED WITH CONDITIONS`, `RECONSIDER` or `BLOCKED`. G6.1 emits `READY`,
|
|
54
54
|
`NOT READY` or `BLOCKED`. `RECONSIDER` leaves the loop: the candidate's time goes to a
|
|
55
55
|
better posting.
|
|
56
56
|
|
|
@@ -64,7 +64,7 @@ produce documents carrying their name that they have not confirmed.
|
|
|
64
64
|
|
|
65
65
|
Name the missing skill and skilldrop as its source, do the minimal inline version of that
|
|
66
66
|
stage under the same rule (nothing the candidate didn't supply), and record that it ran
|
|
67
|
-
degraded. Never skip G6.
|
|
67
|
+
degraded. Never skip G6.1.
|
|
68
68
|
|
|
69
69
|
## Run log (opt-in)
|
|
70
70
|
|
|
@@ -73,7 +73,7 @@ gate's verdict, append one JSON line to that file. Nothing else is recorded, and
|
|
|
73
73
|
sent anywhere. When the variable isn't set, or you can't read it, skip this section.
|
|
74
74
|
|
|
75
75
|
```bash
|
|
76
|
-
printf '%s\n' '{"ts":"2026-10-05T14:03:00Z","loop":"apply","stage":"fit","gate":"
|
|
76
|
+
printf '%s\n' '{"ts":"2026-10-05T14:03:00Z","loop":"apply","stage":"fit","gate":"G6","verdict":"PROCEED","round":1}' >> "$SKILLDROP_LOOP_LOG"
|
|
77
77
|
```
|
|
78
78
|
|
|
79
79
|
Fields: `ts` (UTC), `loop`, `stage`, `gate` (`null` for a stage without one), `verdict` (the
|
|
@@ -83,9 +83,9 @@ The log holds verdicts only: never the posting, the resume or the candidate's na
|
|
|
83
83
|
## Quality bar
|
|
84
84
|
|
|
85
85
|
- **The candidate decides to apply before anything is written.** A tailored resume for a job they would have skipped is wasted work.
|
|
86
|
-
- **Every claim in both documents traces to the resume or to something the candidate said.** Answers given at
|
|
86
|
+
- **Every claim in both documents traces to the resume or to something the candidate said.** Answers given at G6 are written to `notes.md`, so the trace exists.
|
|
87
87
|
- **The resume, the letter and the form agree.** Same titles, dates and numbers in all three.
|
|
88
|
-
- **The candidate confirms and sends.** G6 is their signature; the loop never submits an application.
|
|
88
|
+
- **The candidate confirms and sends.** G6.1 is their signature; the loop never submits an application.
|
|
89
89
|
|
|
90
90
|
## Anti-patterns to avoid
|
|
91
91
|
|
|
@@ -93,5 +93,5 @@ The log holds verdicts only: never the posting, the resume or the candidate's na
|
|
|
93
93
|
- ❌ **Treating a `Not shown` item as true** because the candidate proceeded. Only what they answered goes in.
|
|
94
94
|
- ❌ **Writing the letter from the original resume.** The letter follows the tailored one, or the two will disagree.
|
|
95
95
|
- ❌ **Answering a declaration on the form** for the candidate, or leaving out the list of what was added and cut.
|
|
96
|
-
- ❌ **Self-confirming at G6.** A clean claim check says the words are in the source; it cannot say they are true.
|
|
96
|
+
- ❌ **Self-confirming at G6.1.** A clean claim check says the words are in the source; it cannot say they are true.
|
|
97
97
|
- ❌ **Submitting the application.** Sending is the candidate's act.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"assertions": [
|
|
8
8
|
"Starts with job-fit-analysis and delivers its verdict before any resume or letter is written",
|
|
9
9
|
"Names the unmet bachelor's degree as a screening requirement",
|
|
10
|
-
"Stops at
|
|
10
|
+
"Stops at G6 and asks the candidate whether to apply; does not tailor the resume in the same turn",
|
|
11
11
|
"Does not advise claiming the degree or adding any keyword the resume does not support"
|
|
12
12
|
]
|
|
13
13
|
},
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"The tailored resume includes Spanish and does not claim SAP",
|
|
20
20
|
"The cover letter is written after the tailored resume and uses the same titles, dates and numbers",
|
|
21
21
|
"Runs output-hygiene on both documents before the final gate",
|
|
22
|
-
"Stops at G6 and asks the candidate to confirm every line is true; does not submit or send anything",
|
|
22
|
+
"Stops at G6.1 and asks the candidate to confirm every line is true; does not submit or send anything",
|
|
23
23
|
"Lists what the posting asks for that neither document claims"
|
|
24
24
|
]
|
|
25
25
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "RFC-0042. One job application, start to send. The first gate exists because the cheapest application is the one not written; the last because the candidate, not the agent, signs what is sent.",
|
|
3
3
|
"name": "apply",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "Take one job posting and one resume to an application ready to send — an honest fit check, a tailored resume, a cover letter and the answers to the application form — with the candidate deciding whether to apply before any writing, and confirming every claim is true before anything is sent. Use when the user wants to apply for a job, says \"help me apply for this\", or wants a resume and cover letter prepared for one posting.",
|
|
6
6
|
"entrypoint": "LOOP.md",
|
|
7
7
|
"kind": "loop",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"intent": "Say honestly where the candidate stands against the posting, then let them decide whether this application is worth writing.",
|
|
14
14
|
"skills": ["job-fit-analysis"],
|
|
15
15
|
"gate": {
|
|
16
|
-
"id": "
|
|
16
|
+
"id": "G6",
|
|
17
17
|
"kind": "human",
|
|
18
18
|
"verdicts": ["PROCEED", "PROCEED WITH CONDITIONS", "RECONSIDER", "BLOCKED"]
|
|
19
19
|
}
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"intent": "Strip what the machine left behind, then have the candidate confirm that every line in the documents is true and theirs, and that anything added from outside the resume is right.",
|
|
43
43
|
"skills": ["output-hygiene"],
|
|
44
44
|
"gate": {
|
|
45
|
-
"id": "G6",
|
|
45
|
+
"id": "G6.1",
|
|
46
46
|
"kind": "human",
|
|
47
47
|
"verdicts": ["READY", "NOT READY", "BLOCKED"],
|
|
48
48
|
"revise_to": "tailor"
|
|
@@ -16,7 +16,7 @@ check the argument and the surface after.
|
|
|
16
16
|
|---|---|---|---|---|
|
|
17
17
|
| 1 | `intake` | generate | [`brief-intake`](../../skills/brief-intake/SKILL.md) | — |
|
|
18
18
|
| 2 | `draft` | generate | any generator (`*`) | — |
|
|
19
|
-
| 3 | `critique` | verify | [`doc-critique`](../../skills/doc-critique/SKILL.md) | **
|
|
19
|
+
| 3 | `critique` | verify | [`doc-critique`](../../skills/doc-critique/SKILL.md) | **G5** review |
|
|
20
20
|
| 4 | `polish` | verify | [`output-hygiene`](../../skills/output-hygiene/SKILL.md) | — |
|
|
21
21
|
|
|
22
22
|
## How to run this wrapper
|
|
@@ -29,7 +29,7 @@ check the argument and the surface after.
|
|
|
29
29
|
chosen generator will refuse to start without. Resolve those before drafting.
|
|
30
30
|
3. **Draft.** Run the chosen generator on the brief. It is a normal skilldrop skill with its
|
|
31
31
|
own quality bar — this wrapper does not override it.
|
|
32
|
-
4. **Critique at
|
|
32
|
+
4. **Critique at G5.** Run `doc-critique`, which dispatches to the same rubric the generator
|
|
33
33
|
was held to. `MAJOR REWRITE` returns to `intake` (not to `draft` — a rewrite usually means
|
|
34
34
|
the input was wrong, not the prose). `WRONG ARTIFACT` leaves the wrapper entirely.
|
|
35
35
|
5. **Polish last.** Run `output-hygiene` only once the argument is settled. It is a surface
|
|
@@ -37,14 +37,14 @@ check the argument and the surface after.
|
|
|
37
37
|
|
|
38
38
|
## Verdicts
|
|
39
39
|
|
|
40
|
-
Defined in [`contracts/terminals.json`](../../../../contracts/terminals.json).
|
|
40
|
+
Defined in [`contracts/terminals.json`](../../../../contracts/terminals.json). G5 emits the critique
|
|
41
41
|
vocabulary. `SHIP WITH CHANGES` carries its changes forward to `polish`; it does not mean the
|
|
42
42
|
changes are optional.
|
|
43
43
|
|
|
44
44
|
## When a stage's skill is not installed
|
|
45
45
|
|
|
46
46
|
Name the missing skill and skilldrop as its source, do the minimal inline version of that
|
|
47
|
-
stage, and say the stage ran degraded. Do not skip
|
|
47
|
+
stage, and say the stage ran degraded. Do not skip G5 — an unreviewed artifact is the failure
|
|
48
48
|
mode this wrapper exists to prevent.
|
|
49
49
|
|
|
50
50
|
## Run log (opt-in)
|
|
@@ -54,7 +54,7 @@ gate's verdict, append one JSON line to that file. Nothing else is recorded, and
|
|
|
54
54
|
sent anywhere. When the variable isn't set, or you can't read it, skip this section.
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
|
-
printf '%s\n' '{"ts":"2026-10-01T14:03:00Z","loop":"ship-a-draft","stage":"critique","gate":"
|
|
57
|
+
printf '%s\n' '{"ts":"2026-10-01T14:03:00Z","loop":"ship-a-draft","stage":"critique","gate":"G5","verdict":"SHIP IT","round":1}' >> "$SKILLDROP_LOOP_LOG"
|
|
58
58
|
```
|
|
59
59
|
|
|
60
60
|
Fields: `ts` (UTC), `loop`, `stage`, `gate` (`null` for a stage without one), `verdict` (the
|
|
@@ -71,7 +71,7 @@ which end in `BLOCKED`.
|
|
|
71
71
|
|
|
72
72
|
## Anti-patterns to avoid
|
|
73
73
|
|
|
74
|
-
- ❌ **Running `polish` as the whole review.** Clean prose with a broken argument still fails; that is what
|
|
74
|
+
- ❌ **Running `polish` as the whole review.** Clean prose with a broken argument still fails; that is what G5 is for.
|
|
75
75
|
- ❌ **Skipping `intake` because the input "is already structured."** A Slack thread that looks organized is still untagged — nobody downstream can tell what was inferred.
|
|
76
76
|
- ❌ **Looping `critique` → `draft` instead of `critique` → `intake`.** Re-drafting from the same incomplete brief reproduces the same gaps with new words.
|
|
77
77
|
- ❌ **Treating `WRONG ARTIFACT` as a severe `MAJOR REWRITE`.** It is a redirect: the user wanted a different document, and revising this one cannot get there.
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
"assertions": [
|
|
8
8
|
"Chooses the target generator (exec-summary) BEFORE running intake, because the brief is shaped for a target",
|
|
9
9
|
"Runs brief-intake and tags every field explicit / implied / inferred / missing",
|
|
10
|
-
"Runs doc-critique at
|
|
10
|
+
"Runs doc-critique at G5 against the same rubric the generator was held to",
|
|
11
11
|
"Runs output-hygiene last, only once the argument is settled",
|
|
12
12
|
"Reports what output-hygiene did not touch"
|
|
13
13
|
]
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"id": "major-rewrite-returns-to-intake",
|
|
17
17
|
"prompt": "The critique on my draft came back MAJOR REWRITE. Continue the ship-a-draft wrapper.",
|
|
18
18
|
"assertions": [
|
|
19
|
-
"Routes back to `intake`, not to `draft`, because
|
|
19
|
+
"Routes back to `intake`, not to `draft`, because G5's revise_to names intake",
|
|
20
20
|
"Explains that re-drafting from the same incomplete brief reproduces the same gaps",
|
|
21
21
|
"Does not run output-hygiene on a draft that is about to be rewritten"
|
|
22
22
|
]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "RFC-0028. This wrapper is docs/knowledge-work-pipeline.mmd promoted from a hand-maintained diagram into the machine-readable source that diagram is now generated from. It is a wrapper, not a loop: the generator at its centre is chosen at run time from whichever artifact the user actually wants.",
|
|
3
3
|
"name": "ship-a-draft",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "Take raw mess to a stakeholder-ready artifact by wrapping any skilldrop generator in the two passes either side of it — structured intake before, critique and machine-residue scrub after. Use when the user has notes, a transcript, or a ticket and wants a finished document rather than a first draft.",
|
|
6
6
|
"entrypoint": "LOOP.md",
|
|
7
7
|
"kind": "wrapper",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"intent": "Review the draft against the same rubric the generator was held to.",
|
|
26
26
|
"skills": ["doc-critique"],
|
|
27
27
|
"gate": {
|
|
28
|
-
"id": "
|
|
28
|
+
"id": "G5",
|
|
29
29
|
"kind": "review",
|
|
30
30
|
"verdicts": ["SHIP IT", "SHIP WITH CHANGES", "MAJOR REWRITE", "WRONG ARTIFACT", "BLOCKED"],
|
|
31
31
|
"revise_to": "intake"
|
package/packs/core/pack.json
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"skill": "doc-critique",
|
|
38
38
|
"you_do": "Hand over the draft you wrote from the brief, by hand or with any generator skill.",
|
|
39
39
|
"you_get": "A critique that opens with a verdict and lists findings as blocker, major, minor or nit, each pointing to the section and suggesting a fix.",
|
|
40
|
-
"decide": "Gate
|
|
40
|
+
"decide": "Gate G5: SHIP IT or SHIP WITH CHANGES moves on; MAJOR REWRITE sends you back to the brief; WRONG ARTIFACT means you need a different kind of document."
|
|
41
41
|
},
|
|
42
42
|
{
|
|
43
43
|
"skill": "output-hygiene",
|
|
@@ -15,9 +15,9 @@ being folded into either neighbour.
|
|
|
15
15
|
| # | Stage | Type | Skills | Gate |
|
|
16
16
|
|---|---|---|---|---|
|
|
17
17
|
| 1 | `plan` | generate | [`migration-plan`](../../skills/migration-plan/SKILL.md) | — |
|
|
18
|
-
| 2 | `ready` | verify | [`launch-readiness`](../../skills/launch-readiness/SKILL.md) | **
|
|
18
|
+
| 2 | `ready` | verify | [`launch-readiness`](../../skills/launch-readiness/SKILL.md) | **G3** review |
|
|
19
19
|
| 3 | `announce` | generate | [`release-notes`](../../skills/release-notes/SKILL.md) | — |
|
|
20
|
-
| 4 | `go` | gate | [`council-review`](../../../core/skills/council-review/SKILL.md) | **
|
|
20
|
+
| 4 | `go` | gate | [`council-review`](../../../core/skills/council-review/SKILL.md) | **G3.1** human |
|
|
21
21
|
|
|
22
22
|
## How to run this loop
|
|
23
23
|
|
|
@@ -25,34 +25,34 @@ being folded into either neighbour.
|
|
|
25
25
|
rollback for each one, and at most one named point of no return. For a code-only change
|
|
26
26
|
behind a flag, the plan can be short: the flag, the stages and how to turn it off. It still
|
|
27
27
|
has to exist.
|
|
28
|
-
2. **Judge readiness at
|
|
28
|
+
2. **Judge readiness at G3.** `launch-readiness` lists the change's own failure modes, then
|
|
29
29
|
checks rollback, detection, response, measured targets, data and privacy, comms and
|
|
30
30
|
exposure, each with evidence. `REVISE` returns to `plan`, and the report names which gap to
|
|
31
31
|
close and which skill closes it. A missing rollback is `BLOCKED` on its own.
|
|
32
32
|
3. **Announce only once it is ready.** `release-notes` drafts the customer-facing and
|
|
33
|
-
internal notes from the git range. Writing them before
|
|
33
|
+
internal notes from the git range. Writing them before G3 risks announcing a launch that
|
|
34
34
|
then does not happen.
|
|
35
|
-
4. **A human decides at
|
|
35
|
+
4. **A human decides at G3.1.** The release owner decides whether to ship *now*, given
|
|
36
36
|
timing, freeze windows and on-call coverage. When that decision is contested, use
|
|
37
37
|
`council-review`. `RECONSIDER` means it is ready but this is the wrong time, and the work
|
|
38
38
|
waits instead of going back to `plan`.
|
|
39
39
|
|
|
40
|
-
After `PROCEED`, the change is live and the work moves to `operate`. Gaps that
|
|
40
|
+
After `PROCEED`, the change is live and the work moves to `operate`. Gaps that G3 sent to
|
|
41
41
|
`observability-plan` or `runbook-generator` are the first things `operate` picks up.
|
|
42
42
|
|
|
43
43
|
## Verdicts
|
|
44
44
|
|
|
45
|
-
Defined in [`contracts/terminals.json`](../../../../contracts/terminals.json).
|
|
46
|
-
`PROCEED WITH CONDITIONS`, `REVISE` or `BLOCKED`.
|
|
47
|
-
CONDITIONS` at
|
|
45
|
+
Defined in [`contracts/terminals.json`](../../../../contracts/terminals.json). G3 emits `PROCEED`,
|
|
46
|
+
`PROCEED WITH CONDITIONS`, `REVISE` or `BLOCKED`. G3.1 adds `RECONSIDER`. `PROCEED WITH
|
|
47
|
+
CONDITIONS` at G3 is allowed only when the open rows are comms or exposure. A detection or
|
|
48
48
|
response gap is always `REVISE`.
|
|
49
49
|
|
|
50
50
|
## Non-interactive runs
|
|
51
51
|
|
|
52
52
|
Never invent evidence such as rehearsal logs, load-test numbers, dashboards or sign-offs.
|
|
53
53
|
When the change being released cannot be identified, emit `BLOCKED: need <the PR, tag, or
|
|
54
|
-
feature name>`.
|
|
55
|
-
|
|
54
|
+
feature name>`. G3.1 is a human gate. A headless run stops after `announce` and reports the
|
|
55
|
+
G3 verdict for a person to act on.
|
|
56
56
|
|
|
57
57
|
## When a stage's skill is not installed
|
|
58
58
|
|
|
@@ -68,7 +68,7 @@ gate's verdict, append one JSON line to that file. Nothing else is recorded, and
|
|
|
68
68
|
sent anywhere. When the variable isn't set, or you can't read it, skip this section.
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
|
-
printf '%s\n' '{"ts":"2026-10-01T14:03:00Z","loop":"release","stage":"ready","gate":"
|
|
71
|
+
printf '%s\n' '{"ts":"2026-10-01T14:03:00Z","loop":"release","stage":"ready","gate":"G3","verdict":"PROCEED","round":1}' >> "$SKILLDROP_LOOP_LOG"
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
Fields: `ts` (UTC), `loop`, `stage`, `gate` (`null` for a stage without one), `verdict` (the
|
|
@@ -80,12 +80,12 @@ which end in `BLOCKED`.
|
|
|
80
80
|
|
|
81
81
|
- **Rollback exists before readiness is judged.** A plan with no way back is the one thing that cannot be fixed after launch.
|
|
82
82
|
- **Readiness rests on evidence.** Every check links to something; "we're good" is `not met`.
|
|
83
|
-
- **Can and should are separate gates.**
|
|
83
|
+
- **Can and should are separate gates.** G3 says whether it can ship; G3.1 says whether it should ship now.
|
|
84
84
|
- **Detection gaps leave this loop as hand-offs.** `operate` picks them up first, rather than discovering them during the first incident.
|
|
85
85
|
|
|
86
86
|
## Anti-patterns to avoid
|
|
87
87
|
|
|
88
88
|
- ❌ **Jumping from merge to "instrument a running service".** That skips this loop entirely, which is the gap it exists to close.
|
|
89
|
-
- ❌ **Announcing before
|
|
89
|
+
- ❌ **Announcing before G3.** Customers hear about a launch that is then pulled.
|
|
90
90
|
- ❌ **Treating flag-off as a rollback without a data story.** If dual-write or a backfill has run, turning the flag off does not undo the data.
|
|
91
|
-
- ❌ **Letting
|
|
91
|
+
- ❌ **Letting G3.1 overrule a G3 `BLOCKED`.** A human can delay a ready launch; they cannot make an unready one ready by deciding to go.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$comment": "RFC-0031. Covers the step between build (ends at merge, G2.1) and operate (starts at instrument).
|
|
2
|
+
"$comment": "RFC-0031. Covers the step between build (ends at merge, G2.1) and operate (starts at instrument). Its gates are G3 and G3.1: the whole number places the loop in the lifecycle (discover G0 through operate G4), and a decimal is a second gate inside the same loop (RFC-0044).",
|
|
3
3
|
"name": "release",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "Take merged code to live users through a planned, reversible rollout — plan the phases and the rollback, judge readiness on evidence, draft the release notes, then a human makes the go/no-go call. Use when the user has merged a change and asks how to roll it out, whether they are ready to launch, or for a go/no-go decision.",
|
|
6
6
|
"entrypoint": "LOOP.md",
|
|
7
7
|
"kind": "loop",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"intent": "Judge on evidence whether the change can go live: rollback, detection, response, measured targets, data, comms, exposure.",
|
|
20
20
|
"skills": ["launch-readiness"],
|
|
21
21
|
"gate": {
|
|
22
|
-
"id": "
|
|
22
|
+
"id": "G3",
|
|
23
23
|
"kind": "review",
|
|
24
24
|
"verdicts": ["PROCEED", "PROCEED WITH CONDITIONS", "REVISE", "BLOCKED"],
|
|
25
25
|
"revise_to": "plan"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"intent": "A human decides whether it should ship now. Readiness says it can; this decides timing, freeze windows, and who is on call.",
|
|
38
38
|
"skills": ["council-review"],
|
|
39
39
|
"gate": {
|
|
40
|
-
"id": "
|
|
40
|
+
"id": "G3.1",
|
|
41
41
|
"kind": "human",
|
|
42
42
|
"verdicts": ["PROCEED", "PROCEED WITH CONDITIONS", "REVISE", "RECONSIDER", "BLOCKED"],
|
|
43
43
|
"revise_to": "plan"
|
|
@@ -17,7 +17,7 @@ the cost of time; this one runs while real users are affected. Its feedback edge
|
|
|
17
17
|
| 1 | `instrument` | generate | [`observability-plan`](../../skills/observability-plan/SKILL.md) | — |
|
|
18
18
|
| 2 | `document` | generate | [`runbook-generator`](../../skills/runbook-generator/SKILL.md) | — |
|
|
19
19
|
| 3 | `respond` | generate | [`incident-comms`](../../skills/incident-comms/SKILL.md) | — |
|
|
20
|
-
| 4 | `learn` | verify | [`postmortem-generator`](../../skills/postmortem-generator/SKILL.md) | **
|
|
20
|
+
| 4 | `learn` | verify | [`postmortem-generator`](../../skills/postmortem-generator/SKILL.md) | **G4** human |
|
|
21
21
|
|
|
22
22
|
## How to run this loop
|
|
23
23
|
|
|
@@ -29,7 +29,7 @@ the cost of time; this one runs while real users are affected. Its feedback edge
|
|
|
29
29
|
3. **Respond to the audience the incident actually has.** `incident-comms` is staged —
|
|
30
30
|
detect, mitigate, resolve — and the stage determines the message. Writing a resolution
|
|
31
31
|
update during mitigation is the most common error here.
|
|
32
|
-
4. **Learn at
|
|
32
|
+
4. **Learn at G4.** `postmortem-generator` analyses blamelessly and ends with runbook deltas.
|
|
33
33
|
A human signs off that the incident is closed. `REVISE` returns to `document`: the deltas
|
|
34
34
|
are the loop's output, and applying them is what closes it.
|
|
35
35
|
|
|
@@ -38,7 +38,7 @@ starts at `instrument`. Do not replay earlier stages for their own sake.
|
|
|
38
38
|
|
|
39
39
|
## Verdicts
|
|
40
40
|
|
|
41
|
-
Defined in [`contracts/terminals.json`](../../../../contracts/terminals.json).
|
|
41
|
+
Defined in [`contracts/terminals.json`](../../../../contracts/terminals.json). G4 emits `PROCEED`,
|
|
42
42
|
`PROCEED WITH CONDITIONS`, `REVISE`, or `BLOCKED`. `PROCEED WITH CONDITIONS` means the
|
|
43
43
|
incident is closed but named follow-ups are outstanding — they travel with the postmortem.
|
|
44
44
|
|
|
@@ -61,7 +61,7 @@ gate's verdict, append one JSON line to that file. Nothing else is recorded, and
|
|
|
61
61
|
sent anywhere. When the variable isn't set, or you can't read it, skip this section.
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
|
-
printf '%s\n' '{"ts":"2026-10-01T14:03:00Z","loop":"operate","stage":"learn","gate":"
|
|
64
|
+
printf '%s\n' '{"ts":"2026-10-01T14:03:00Z","loop":"operate","stage":"learn","gate":"G4","verdict":"PROCEED","round":1}' >> "$SKILLDROP_LOOP_LOG"
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
Fields: `ts` (UTC), `loop`, `stage`, `gate` (`null` for a stage without one), `verdict` (the
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"Stages the communication (detect / mitigate / resolve) and matches the message to the stage the incident is in",
|
|
10
10
|
"Produces a blameless postmortem that names missing guardrails, never people",
|
|
11
11
|
"Ends in concrete runbook deltas phrased as before/after edits",
|
|
12
|
-
"Routes those deltas back to `document` via
|
|
12
|
+
"Routes those deltas back to `document` via G4's revise edge rather than treating the postmortem as terminal"
|
|
13
13
|
]
|
|
14
14
|
},
|
|
15
15
|
{
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$comment": "RFC-0028. The only loop whose failures are irreversible and live. Its feedback edge is real and already documented in the repo: postmortem-generator emits runbook deltas phrased as edits for runbook-generator.",
|
|
3
3
|
"name": "operate",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "Take a shipped service through detection, response, and learning — instrument it, write the runbook, communicate the incident, then feed the postmortem's deltas back into the runbook. Use when the user is preparing to run a service, is in an incident, or is writing a postmortem.",
|
|
6
6
|
"entrypoint": "LOOP.md",
|
|
7
7
|
"kind": "loop",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"intent": "Analyse blamelessly and emit runbook deltas. An incident that changes no procedure has not been learned from.",
|
|
32
32
|
"skills": ["postmortem-generator"],
|
|
33
33
|
"gate": {
|
|
34
|
-
"id": "
|
|
34
|
+
"id": "G4",
|
|
35
35
|
"kind": "human",
|
|
36
36
|
"verdicts": ["PROCEED", "PROCEED WITH CONDITIONS", "REVISE", "BLOCKED"],
|
|
37
37
|
"revise_to": "document"
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"skill": "postmortem-generator",
|
|
47
47
|
"you_do": "Paste the incident thread, pager timeline and notes once it is over.",
|
|
48
48
|
"you_get": "A blameless postmortem with quantified impact, a UTC timeline showing detection and mitigation gaps, contributing factors, up to eight action items, and runbook deltas phrased as edits.",
|
|
49
|
-
"decide": "Gate
|
|
49
|
+
"decide": "Gate G4, a human call: PROCEED closes it, PROCEED WITH CONDITIONS closes it with named follow-ups, REVISE sends the deltas back to the runbook."
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
"skill": "delivery-metrics-report",
|