@christang/keel 5.1.2 → 5.2.1
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 +114 -158
- package/README.zh-CN.md +118 -197
- package/assets/bootstrap/AGENTS.md +1 -1
- package/{plugins/keel/skills/keel-align-expectations/references → assets/lenses}/hardware-dsl.md +4 -2
- package/{plugins/keel/skills/keel-align-expectations/references → assets/lenses}/hardware.md +4 -2
- package/{plugins/keel/skills/keel-align-expectations/references → assets/lenses}/web.md +4 -2
- package/assets/openspec/schemas/keel-spec-driven/schema.yaml +11 -5
- package/assets/openspec/schemas/keel-spec-driven/templates/design.md +1 -1
- package/assets/openspec/schemas/keel-spec-driven/templates/tasks.md +13 -4
- package/bin/keel.js +221 -18
- package/package.json +1 -1
- package/plugins/keel/.claude-plugin/plugin.json +1 -1
- package/plugins/keel/.codex-plugin/plugin.json +1 -1
- package/plugins/keel/skills/keel-align-expectations/SKILL.md +2 -6
- package/plugins/keel/skills/keel-debug-failure/SKILL.md +2 -2
- package/plugins/keel/skills/keel-review-checklist/SKILL.md +2 -2
- package/plugins/keel/skills/keel-tdd-or-test-first/SKILL.md +2 -2
- package/scripts/bump_version.js +140 -0
- package/scripts/install_to_repo.py +3 -73
- package/scripts/validate_plugin.py +558 -107
- package/src/core/context.js +10 -3
- package/src/core/gates.js +40 -10
- package/src/core/task-contract.js +21 -0
package/bin/keel.js
CHANGED
|
@@ -47,7 +47,7 @@ const {
|
|
|
47
47
|
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
48
48
|
const PACKAGE_JSON = require(path.join(PACKAGE_ROOT, "package.json"));
|
|
49
49
|
const INSTALL_SCRIPT = path.join(PACKAGE_ROOT, "scripts", "install_to_repo.py");
|
|
50
|
-
const DEFAULT_UPDATE_SOURCE = "
|
|
50
|
+
const DEFAULT_UPDATE_SOURCE = "@christang/keel";
|
|
51
51
|
const VALID_TARGETS = new Set(["claude", "codex", "opencode", "both"]);
|
|
52
52
|
const KEEL_SKILLS = [
|
|
53
53
|
"keel-align-expectations",
|
|
@@ -56,11 +56,6 @@ const KEEL_SKILLS = [
|
|
|
56
56
|
"keel-review-checklist",
|
|
57
57
|
"keel-tdd-or-test-first",
|
|
58
58
|
];
|
|
59
|
-
const ALIGNMENT_REFERENCES = [
|
|
60
|
-
"references/web.md",
|
|
61
|
-
"references/hardware.md",
|
|
62
|
-
"references/hardware-dsl.md",
|
|
63
|
-
];
|
|
64
59
|
const OPENSPEC_COMMAND_IDS = ["propose", "explore", "apply", "sync", "archive"];
|
|
65
60
|
const OPENSPEC_SKILLS = [
|
|
66
61
|
"openspec-propose",
|
|
@@ -86,6 +81,8 @@ Usage:
|
|
|
86
81
|
keel project tasks [repo] --target claude [--change name] [--json]
|
|
87
82
|
keel gate task-start|task-complete|change-close [repo] [--change name] [--task id] [--action sync|archive] [--base git-ref] [--no-guard] [--record] [--json]
|
|
88
83
|
keel guard start|status|clear [repo] [--change name] [--task id] [--force] [--json]
|
|
84
|
+
keel lenses list|add [name] [repo] [--force]
|
|
85
|
+
keel openspec [args...]
|
|
89
86
|
keel --init [repo] [--target claude|codex|opencode] [--dry-run] [--force-template-update]
|
|
90
87
|
keel --install [repo] [--target claude|codex|opencode] [--dry-run] [--force-template-update]
|
|
91
88
|
keel --clear [repo] [--target claude|codex|opencode] [--dry-run]
|
|
@@ -105,9 +102,7 @@ Defaults:
|
|
|
105
102
|
Project layout:
|
|
106
103
|
continuity is recomputed from OpenSpec on every invocation.
|
|
107
104
|
keel/HANDOFF.md is an optional keel-handoff/v1 pointer override.
|
|
108
|
-
|
|
109
|
-
Codex skills are installed under .agents/skills/keel-*.
|
|
110
|
-
OpenCode skills are installed under .opencode/skills/keel-*.
|
|
105
|
+
keel-* behavioral skills are delivered by the installed Keel plugin, not by the CLI.
|
|
111
106
|
repeat keel --install to refresh project protocol files.
|
|
112
107
|
|
|
113
108
|
Examples:
|
|
@@ -121,6 +116,9 @@ Examples:
|
|
|
121
116
|
keel guard start --change my-change --task 1.1 --json
|
|
122
117
|
keel guard status --json
|
|
123
118
|
keel guard clear --json
|
|
119
|
+
keel lenses list
|
|
120
|
+
keel lenses add web
|
|
121
|
+
keel lenses add web --force
|
|
124
122
|
keel --init
|
|
125
123
|
keel --install
|
|
126
124
|
keel --install --target codex
|
|
@@ -169,6 +167,9 @@ function parseArgs(argv) {
|
|
|
169
167
|
noGuard: false,
|
|
170
168
|
record: false,
|
|
171
169
|
guardSubcommand: null,
|
|
170
|
+
lensesSubcommand: null,
|
|
171
|
+
lensName: null,
|
|
172
|
+
openspecArgs: [],
|
|
172
173
|
force: false,
|
|
173
174
|
projectionEvent: null,
|
|
174
175
|
authorizations: [],
|
|
@@ -207,6 +208,15 @@ function parseArgs(argv) {
|
|
|
207
208
|
parsed.action = "guard";
|
|
208
209
|
continue;
|
|
209
210
|
}
|
|
211
|
+
if (arg === "lenses" && parsed.action === null && parsed.repo === null) {
|
|
212
|
+
parsed.action = "lenses";
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (arg === "openspec" && parsed.action === null && parsed.repo === null) {
|
|
216
|
+
parsed.action = "openspec";
|
|
217
|
+
parsed.openspecArgs = argv.slice(index + 1);
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
210
220
|
if (arg === "--force") {
|
|
211
221
|
parsed.force = true;
|
|
212
222
|
continue;
|
|
@@ -368,8 +378,8 @@ function parseArgs(argv) {
|
|
|
368
378
|
if (arg === "--profile" || arg.startsWith("--profile=")) {
|
|
369
379
|
fail(
|
|
370
380
|
"--profile is no longer supported: web, hardware, and hardware-dsl "
|
|
371
|
-
+ "guidance is
|
|
372
|
-
+ "
|
|
381
|
+
+ "guidance is now user-authored lenses in keel/lenses/*.md "
|
|
382
|
+
+ "(scaffold with `keel lenses add`)"
|
|
373
383
|
);
|
|
374
384
|
}
|
|
375
385
|
if (arg === "--repo") {
|
|
@@ -423,6 +433,18 @@ function parseArgs(argv) {
|
|
|
423
433
|
parsed.projectSubcommand = arg;
|
|
424
434
|
continue;
|
|
425
435
|
}
|
|
436
|
+
if (parsed.action === "lenses" && parsed.lensesSubcommand === null) {
|
|
437
|
+
parsed.lensesSubcommand = arg;
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (
|
|
441
|
+
parsed.action === "lenses"
|
|
442
|
+
&& parsed.lensesSubcommand === "add"
|
|
443
|
+
&& parsed.lensName === null
|
|
444
|
+
) {
|
|
445
|
+
parsed.lensName = arg;
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
426
448
|
if (parsed.repo !== null) {
|
|
427
449
|
fail("repo path was provided more than once");
|
|
428
450
|
}
|
|
@@ -486,8 +508,25 @@ function parseArgs(argv) {
|
|
|
486
508
|
if (parsed.action !== "guard" && parsed.guardSubcommand !== null) {
|
|
487
509
|
fail("guard subcommands apply only to keel guard");
|
|
488
510
|
}
|
|
489
|
-
if (
|
|
490
|
-
|
|
511
|
+
if (
|
|
512
|
+
parsed.force
|
|
513
|
+
&& parsed.action !== "guard"
|
|
514
|
+
&& !(parsed.action === "lenses" && parsed.lensesSubcommand === "add")
|
|
515
|
+
) {
|
|
516
|
+
fail("--force applies only to keel guard start or keel lenses add");
|
|
517
|
+
}
|
|
518
|
+
if (parsed.action === "lenses") {
|
|
519
|
+
if (!["list", "add"].includes(parsed.lensesSubcommand || "")) {
|
|
520
|
+
fail("lenses requires list or add");
|
|
521
|
+
}
|
|
522
|
+
if (parsed.lensesSubcommand === "add" && !parsed.lensName) {
|
|
523
|
+
fail("keel lenses add requires a lens name");
|
|
524
|
+
}
|
|
525
|
+
if (parsed.lensesSubcommand === "list" && parsed.lensName) {
|
|
526
|
+
fail("keel lenses list does not take a lens name");
|
|
527
|
+
}
|
|
528
|
+
} else if (parsed.lensesSubcommand !== null || parsed.lensName !== null) {
|
|
529
|
+
fail("lens subcommands apply only to keel lenses");
|
|
491
530
|
}
|
|
492
531
|
if (parsed.noGuard && parsed.action !== "gate") {
|
|
493
532
|
fail("--no-guard applies only to keel gate task-start");
|
|
@@ -983,6 +1022,7 @@ function keelOpenSpecOverlay(action) {
|
|
|
983
1022
|
"- The current agent reviews all subagent output, command evidence, and diffs before marking any task complete.",
|
|
984
1023
|
"- When implementation exposes a material expectation, acceptance boundary, or user-owned decision absent from durable authority, stop before implementing that choice, rerun `keel-align-expectations`, and reauthor the affected proposal/design/spec/task authority first.",
|
|
985
1024
|
"- A discovered repository fact that does not change accepted behavior or scope may be recorded and execution continues inside the existing task boundary without a product interview.",
|
|
1025
|
+
"- Invoke OpenSpec through `keel openspec` (for example `keel openspec validate`); a bare `openspec` command may not be on PATH.",
|
|
986
1026
|
]
|
|
987
1027
|
: [
|
|
988
1028
|
"- The current agent owns final sync/archive decisions and must verify task evidence, follow-up ownership, and completion gates before proceeding.",
|
|
@@ -990,6 +1030,9 @@ function keelOpenSpecOverlay(action) {
|
|
|
990
1030
|
"- Target-native subagents may help with bounded assessment or evidence production only; they cannot archive, sync, change acceptance, or bypass completion gates.",
|
|
991
1031
|
"- The current agent reviews any subagent report before running `openspec-sync-specs`, `/opsx:sync`, or `/opsx:archive`.",
|
|
992
1032
|
"- Do not treat generic OpenSpec archive delegation language as authority to transfer Keel ownership.",
|
|
1033
|
+
"- Invoke OpenSpec through `keel openspec` (for example `keel openspec validate`); a bare `openspec` command may not be on PATH.",
|
|
1034
|
+
"- When `/opsx:sync` has already promoted the change's spec delta, run the archive with `--skip-specs` so the promoted delta is not re-applied; archive is not idempotent over an already-synced delta.",
|
|
1035
|
+
"- After archiving, run `keel guard clear` to drop the change's guard manifest; the read-only gate never clears it for you.",
|
|
993
1036
|
];
|
|
994
1037
|
|
|
995
1038
|
const lines = [
|
|
@@ -1174,6 +1217,12 @@ function printTargetSurface(repo, target) {
|
|
|
1174
1217
|
formatCount(openspecSkillCounts, openspecSkillRoot)
|
|
1175
1218
|
);
|
|
1176
1219
|
|
|
1220
|
+
printDoctorLine(
|
|
1221
|
+
"Keel behavioral skills",
|
|
1222
|
+
"plugin",
|
|
1223
|
+
"keel-* skills are delivered by the installed Keel plugin (see native plugin status above); install the plugin if it is missing"
|
|
1224
|
+
);
|
|
1225
|
+
|
|
1177
1226
|
const commands = commandSurfaceForTarget(target, repo);
|
|
1178
1227
|
const commandCounts = countExisting(commands.paths);
|
|
1179
1228
|
const commandDetail =
|
|
@@ -1226,11 +1275,27 @@ function runDoctor(options) {
|
|
|
1226
1275
|
);
|
|
1227
1276
|
|
|
1228
1277
|
const openspec = findOpenSpecCommand();
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1278
|
+
if (!openspec) {
|
|
1279
|
+
printDoctorLine(
|
|
1280
|
+
"openspec",
|
|
1281
|
+
"missing",
|
|
1282
|
+
"reinstall keel so npm installs its OpenSpec dependency"
|
|
1283
|
+
);
|
|
1284
|
+
} else {
|
|
1285
|
+
const bareOpenSpecOnPath =
|
|
1286
|
+
!path.isAbsolute(openspec)
|
|
1287
|
+
|| runCommand("openspec", ["--version"], {
|
|
1288
|
+
stdio: "ignore",
|
|
1289
|
+
silentNotFound: true,
|
|
1290
|
+
}) === 0;
|
|
1291
|
+
printDoctorLine(
|
|
1292
|
+
"openspec",
|
|
1293
|
+
bareOpenSpecOnPath ? "ok" : "warning",
|
|
1294
|
+
bareOpenSpecOnPath
|
|
1295
|
+
? openspec
|
|
1296
|
+
: `${openspec} is keel-resolvable but bare \`openspec\` is not on PATH — use \`keel openspec\``
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1234
1299
|
|
|
1235
1300
|
process.stdout.write("\nProject status:\n");
|
|
1236
1301
|
const checkStatus = runPython(
|
|
@@ -1257,15 +1322,142 @@ function runDoctor(options) {
|
|
|
1257
1322
|
}
|
|
1258
1323
|
|
|
1259
1324
|
printTargetSurface(repo, options.target);
|
|
1325
|
+
printLensSurface(repo, options.target);
|
|
1260
1326
|
|
|
1261
1327
|
return checkStatus;
|
|
1262
1328
|
}
|
|
1263
1329
|
|
|
1330
|
+
const SHIPPED_LENS_DIR = path.join(PACKAGE_ROOT, "assets", "lenses");
|
|
1331
|
+
const EXPECTED_LENS_TEMPLATES = ["web", "hardware", "hardware-dsl"];
|
|
1332
|
+
|
|
1333
|
+
function targetSkillsDir(repo, target) {
|
|
1334
|
+
if (target === "codex") return path.join(repo, ".agents", "skills");
|
|
1335
|
+
if (target === "opencode") return path.join(repo, ".opencode", "skills");
|
|
1336
|
+
return path.join(repo, ".claude", "skills");
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
function legacyProfileSkills(repo, target) {
|
|
1340
|
+
try {
|
|
1341
|
+
return fs
|
|
1342
|
+
.readdirSync(targetSkillsDir(repo, target), { withFileTypes: true })
|
|
1343
|
+
.filter(
|
|
1344
|
+
(entry) => entry.isDirectory() && /^keel-profile-/.test(entry.name)
|
|
1345
|
+
)
|
|
1346
|
+
.map((entry) => entry.name)
|
|
1347
|
+
.sort();
|
|
1348
|
+
} catch (error) {
|
|
1349
|
+
return [];
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
function printLensSurface(repo, target) {
|
|
1354
|
+
process.stdout.write("\nDomain lens surface:\n");
|
|
1355
|
+
const shipped = lensNames(SHIPPED_LENS_DIR);
|
|
1356
|
+
const missing = EXPECTED_LENS_TEMPLATES.filter(
|
|
1357
|
+
(name) => !shipped.includes(name)
|
|
1358
|
+
);
|
|
1359
|
+
printDoctorLine(
|
|
1360
|
+
"lens templates",
|
|
1361
|
+
missing.length === 0 ? "ok" : "incomplete",
|
|
1362
|
+
missing.length === 0
|
|
1363
|
+
? `shipped: ${EXPECTED_LENS_TEMPLATES.join(", ")}; scaffold with keel lenses add`
|
|
1364
|
+
: `missing template(s): ${missing.join(", ")}`
|
|
1365
|
+
);
|
|
1366
|
+
const installed = lensNames(path.join(repo, "keel", "lenses"));
|
|
1367
|
+
printDoctorLine(
|
|
1368
|
+
"installed lenses",
|
|
1369
|
+
installed.length > 0 ? "ok" : "none",
|
|
1370
|
+
installed.length > 0
|
|
1371
|
+
? `keel/lenses/: ${installed.join(", ")}`
|
|
1372
|
+
: "no user lenses yet; keel lenses add scaffolds one"
|
|
1373
|
+
);
|
|
1374
|
+
const legacy = legacyProfileSkills(repo, target);
|
|
1375
|
+
if (legacy.length > 0) {
|
|
1376
|
+
printDoctorLine(
|
|
1377
|
+
"legacy profiles",
|
|
1378
|
+
"migrate",
|
|
1379
|
+
`found ${legacy.join(", ")}; v3 keel-profile-* skills are replaced by `
|
|
1380
|
+
+ "pluggable lenses (keel lenses add). Left untouched; not active state."
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
function lensNames(dir) {
|
|
1386
|
+
try {
|
|
1387
|
+
return fs
|
|
1388
|
+
.readdirSync(dir)
|
|
1389
|
+
.filter((name) => name.endsWith(".md"))
|
|
1390
|
+
.map((name) => name.slice(0, -3))
|
|
1391
|
+
.sort();
|
|
1392
|
+
} catch (error) {
|
|
1393
|
+
return [];
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
function runLensesList(repo) {
|
|
1398
|
+
const shipped = lensNames(SHIPPED_LENS_DIR);
|
|
1399
|
+
const installed = lensNames(path.join(repo, "keel", "lenses"));
|
|
1400
|
+
process.stdout.write("Shipped lens templates (assets/lenses/):\n");
|
|
1401
|
+
if (shipped.length === 0) {
|
|
1402
|
+
process.stdout.write(" (none)\n");
|
|
1403
|
+
} else {
|
|
1404
|
+
for (const name of shipped) {
|
|
1405
|
+
const mark = installed.includes(name) ? " (installed)" : "";
|
|
1406
|
+
process.stdout.write(` ${name}${mark}\n`);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
process.stdout.write("\nInstalled lenses (keel/lenses/):\n");
|
|
1410
|
+
if (installed.length === 0) {
|
|
1411
|
+
process.stdout.write(" (none) — run keel lenses add <name>\n");
|
|
1412
|
+
} else {
|
|
1413
|
+
for (const name of installed) {
|
|
1414
|
+
const mark = shipped.includes(name) ? "" : " (custom)";
|
|
1415
|
+
process.stdout.write(` ${name}${mark}\n`);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
return 0;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
function runLensesAdd(repo, name, force) {
|
|
1422
|
+
const source = path.join(SHIPPED_LENS_DIR, `${name}.md`);
|
|
1423
|
+
if (!fs.existsSync(source)) {
|
|
1424
|
+
const available = lensNames(SHIPPED_LENS_DIR).join(", ") || "(none)";
|
|
1425
|
+
fail(`unknown lens template: ${name}; shipped templates: ${available}`);
|
|
1426
|
+
}
|
|
1427
|
+
const destDir = path.join(repo, "keel", "lenses");
|
|
1428
|
+
const dest = path.join(destDir, `${name}.md`);
|
|
1429
|
+
if (fs.existsSync(dest) && !force) {
|
|
1430
|
+
process.stderr.write(
|
|
1431
|
+
`keel: keel/lenses/${name}.md already exists; pass --force to overwrite\n`
|
|
1432
|
+
);
|
|
1433
|
+
return 3;
|
|
1434
|
+
}
|
|
1435
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
1436
|
+
fs.copyFileSync(source, dest);
|
|
1437
|
+
process.stdout.write(
|
|
1438
|
+
`keel: wrote keel/lenses/${name}.md from the ${name} template; `
|
|
1439
|
+
+ "edit it to fit this repository\n"
|
|
1440
|
+
);
|
|
1441
|
+
return 0;
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1264
1444
|
function runAction(options) {
|
|
1265
1445
|
if (options.updateSource !== null && options.action !== "update") {
|
|
1266
1446
|
fail("--source only applies to --update");
|
|
1267
1447
|
}
|
|
1268
1448
|
|
|
1449
|
+
if (options.action === "openspec") {
|
|
1450
|
+
const openspec = findOpenSpecCommand();
|
|
1451
|
+
if (!openspec) {
|
|
1452
|
+
process.stderr.write(
|
|
1453
|
+
"keel: openspec is not resolvable; reinstall keel so npm installs "
|
|
1454
|
+
+ "its OpenSpec dependency\n"
|
|
1455
|
+
);
|
|
1456
|
+
return 1;
|
|
1457
|
+
}
|
|
1458
|
+
return runCommand(openspec, options.openspecArgs, { stdio: "inherit" });
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1269
1461
|
if (options.action === "context") {
|
|
1270
1462
|
if (options.dryRun || options.forceTemplateUpdate || options.updateSource) {
|
|
1271
1463
|
fail("context does not accept install or update options");
|
|
@@ -1351,6 +1543,17 @@ function runAction(options) {
|
|
|
1351
1543
|
: 3;
|
|
1352
1544
|
}
|
|
1353
1545
|
|
|
1546
|
+
if (options.action === "lenses") {
|
|
1547
|
+
if (options.dryRun || options.forceTemplateUpdate || options.updateSource) {
|
|
1548
|
+
fail("lenses does not accept install or update options");
|
|
1549
|
+
}
|
|
1550
|
+
const repo = path.resolve(options.repo || process.cwd());
|
|
1551
|
+
if (options.lensesSubcommand === "list") {
|
|
1552
|
+
return runLensesList(repo);
|
|
1553
|
+
}
|
|
1554
|
+
return runLensesAdd(repo, options.lensName, options.force);
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1354
1557
|
if (options.action === "capabilities") {
|
|
1355
1558
|
if (options.dryRun || options.forceTemplateUpdate || options.updateSource) {
|
|
1356
1559
|
fail("capabilities does not accept install or update options");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "keel",
|
|
3
|
-
"version": "5.1
|
|
3
|
+
"version": "5.2.1",
|
|
4
4
|
"description": "Keel OpenSpec execution discipline: stateless continuity, task capsules, deterministic gates, and expectation alignment for Codex and Claude Code.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TanglmChris",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "keel",
|
|
3
|
-
"version": "5.1
|
|
3
|
+
"version": "5.2.1",
|
|
4
4
|
"description": "Keel OpenSpec execution discipline: stateless continuity, task capsules, deterministic gates, and expectation alignment for Codex and Claude Code.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TanglmChris",
|
|
@@ -40,13 +40,9 @@ Accepted alignment routes to existing OpenSpec owners; create no separate alignm
|
|
|
40
40
|
- specs own observable requirements and positive/negative/edge/failure scenarios.
|
|
41
41
|
- tasks.md owns Covers, verification strategy and checks, scope, and stop boundaries that reference the accepted authority instead of duplicating chat prose.
|
|
42
42
|
|
|
43
|
-
## Domain
|
|
43
|
+
## Domain lenses
|
|
44
44
|
|
|
45
|
-
When the change
|
|
46
|
-
|
|
47
|
-
- references/web.md for UI, API, routing, auth/session, persistence, or backend integration work.
|
|
48
|
-
- references/hardware.md for Verilog/SystemVerilog interface, protocol, reset, or verification work.
|
|
49
|
-
- references/hardware-dsl.md for hardware modeling DSL, generated RTL, or golden/equivalence work.
|
|
45
|
+
When the change signals a specific domain, look in `keel/lenses/` for a lens whose `Applies when:` header matches, and read only that lens before asking domain questions; do not load unrelated lenses. When no lens matches, or the repo defines none, proceed on the domain-agnostic path. Lenses are user-authored; scaffold the bundled starting points with `keel lenses add` (web, hardware, hardware-dsl).
|
|
50
46
|
|
|
51
47
|
## Boundaries
|
|
52
48
|
|
|
@@ -12,9 +12,9 @@ Use this skill when a command or validation path fails. Keep the investigation t
|
|
|
12
12
|
|
|
13
13
|
Read the selected OpenSpec task's Commands, Acceptance, Coupling, Candidate Boundary, Autonomy boundary, Stop Rules, Evidence, Stop if, Read, Touch, and Mode fields. When `Coupling: required`, also read design.md's Coupled Iteration Contract. Read the failed command output and any repository files needed to reproduce or explain the failure.
|
|
14
14
|
|
|
15
|
-
## Domain
|
|
15
|
+
## Domain lenses
|
|
16
16
|
|
|
17
|
-
When the change's artifacts or the failing surface signal a
|
|
17
|
+
When the change's artifacts or the failing surface signal a domain, consult the matching lens's `Execution and review checks` section from `keel/lenses/` — the lens whose `Applies when:` header matches — before locking a root-cause hypothesis, and load only that one. When no lens matches, load nothing.
|
|
18
18
|
|
|
19
19
|
## Fuse
|
|
20
20
|
|
|
@@ -35,9 +35,9 @@ Record the current agent's judgment inside the selected task Evidence:
|
|
|
35
35
|
|
|
36
36
|
The Review remains in tasks.md. A user-facing Report summarizes delivery but is not hidden gate state. Do not let Core or this checklist write evidence automatically.
|
|
37
37
|
|
|
38
|
-
## Domain
|
|
38
|
+
## Domain lenses
|
|
39
39
|
|
|
40
|
-
When the change's artifacts or Touch extensions signal a
|
|
40
|
+
When the change's artifacts or Touch extensions signal a domain, consult the matching lens's `Execution and review checks` section from `keel/lenses/` — the lens whose `Applies when:` header matches — before concluding the review, and load only that one. When no lens matches, load nothing.
|
|
41
41
|
|
|
42
42
|
## Expectation and follow-up ownership
|
|
43
43
|
|
|
@@ -24,9 +24,9 @@ Read the selected task's compiled capsule: resolved Acceptance, Verify strategy
|
|
|
24
24
|
|
|
25
25
|
Red-green strategies (`vertical-tdd`, `regression-first`) must record concrete per-label `.red` and `.green` Evidence entries for the same check; `keel gate task-complete` rejects absent or pending entries.
|
|
26
26
|
|
|
27
|
-
## Domain
|
|
27
|
+
## Domain lenses
|
|
28
28
|
|
|
29
|
-
When the change's proposal/design/specs or the task's Touch extensions signal a
|
|
29
|
+
When the change's proposal/design/specs or the task's Touch extensions signal a domain, consult the matching lens's `Execution and review checks` section from `keel/lenses/` — the lens whose `Applies when:` header matches — before finalizing the strategy and the first check, and load only that one. When no lens matches, load nothing.
|
|
30
30
|
|
|
31
31
|
## Coupled-task preflight
|
|
32
32
|
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// One-shot version bump across every place Keel pins its version.
|
|
5
|
+
//
|
|
6
|
+
// The Keel validation suite requires the same version in package.json,
|
|
7
|
+
// package-lock.json, both native plugin manifests, the validator constants,
|
|
8
|
+
// the protocol docs, and the changelog. This script updates all of them
|
|
9
|
+
// together so a release never ships half-aligned.
|
|
10
|
+
//
|
|
11
|
+
// Usage:
|
|
12
|
+
// node scripts/bump_version.js <patch|minor|major|explicit-version>
|
|
13
|
+
//
|
|
14
|
+
// After running: fill in the CHANGELOG entry, then `npm test`, commit,
|
|
15
|
+
// tag `vX.Y.Z`, push, and publish a GitHub Release.
|
|
16
|
+
|
|
17
|
+
const fs = require("fs");
|
|
18
|
+
const path = require("path");
|
|
19
|
+
|
|
20
|
+
const ROOT = path.resolve(__dirname, "..");
|
|
21
|
+
const PKG_PATH = path.join(ROOT, "package.json");
|
|
22
|
+
const LOCK_PATH = path.join(ROOT, "package-lock.json");
|
|
23
|
+
const CHANGELOG_PATH = path.join(ROOT, "keel", "CHANGELOG.md");
|
|
24
|
+
const CHANGELOG_HEADER = "# Keel Changelog\n\n";
|
|
25
|
+
const SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
26
|
+
|
|
27
|
+
function fail(message) {
|
|
28
|
+
process.stderr.write(`bump-version: ${message}\n`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveNewVersion(current, target) {
|
|
33
|
+
if (target === "patch" || target === "minor" || target === "major") {
|
|
34
|
+
const [, major, minor, patch] = current.match(SEMVER_RE).map(Number);
|
|
35
|
+
if (target === "major") return `${major + 1}.0.0`;
|
|
36
|
+
if (target === "minor") return `${major}.${minor + 1}.0`;
|
|
37
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
38
|
+
}
|
|
39
|
+
if (!SEMVER_RE.test(target)) {
|
|
40
|
+
fail(`not a patch|minor|major keyword or an X.Y.Z version: ${target}`);
|
|
41
|
+
}
|
|
42
|
+
return target;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function writeJson(filePath, data) {
|
|
46
|
+
fs.writeFileSync(filePath, `${JSON.stringify(data, null, 2)}\n`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function bumpPackageFiles(newVersion) {
|
|
50
|
+
const pkg = JSON.parse(fs.readFileSync(PKG_PATH, "utf8"));
|
|
51
|
+
pkg.version = newVersion;
|
|
52
|
+
writeJson(PKG_PATH, pkg);
|
|
53
|
+
process.stdout.write(" updated package.json\n");
|
|
54
|
+
|
|
55
|
+
const lock = JSON.parse(fs.readFileSync(LOCK_PATH, "utf8"));
|
|
56
|
+
lock.version = newVersion;
|
|
57
|
+
if (lock.packages && lock.packages[""]) {
|
|
58
|
+
lock.packages[""].version = newVersion;
|
|
59
|
+
}
|
|
60
|
+
writeJson(LOCK_PATH, lock);
|
|
61
|
+
process.stdout.write(" updated package-lock.json\n");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function replaceInFile(relPath, replacements) {
|
|
65
|
+
const filePath = path.join(ROOT, relPath);
|
|
66
|
+
let content = fs.readFileSync(filePath, "utf8");
|
|
67
|
+
for (const [from, to] of replacements) {
|
|
68
|
+
if (!content.includes(from)) {
|
|
69
|
+
fail(`expected to find ${JSON.stringify(from)} in ${relPath}`);
|
|
70
|
+
}
|
|
71
|
+
content = content.split(from).join(to);
|
|
72
|
+
}
|
|
73
|
+
fs.writeFileSync(filePath, content);
|
|
74
|
+
process.stdout.write(` updated ${relPath}\n`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function prependChangelogEntry(newVersion) {
|
|
78
|
+
let content = fs.readFileSync(CHANGELOG_PATH, "utf8");
|
|
79
|
+
if (content.includes(`## ${newVersion} `) || content.includes(`## ${newVersion}\n`)) {
|
|
80
|
+
process.stdout.write(` keel/CHANGELOG.md already has a ${newVersion} entry\n`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (!content.startsWith(CHANGELOG_HEADER)) {
|
|
84
|
+
fail("keel/CHANGELOG.md does not start with the expected header");
|
|
85
|
+
}
|
|
86
|
+
const entry =
|
|
87
|
+
`## ${newVersion} - TODO: summarize this release\n\n` +
|
|
88
|
+
"- TODO: describe the change.\n" +
|
|
89
|
+
"- Version alignment: the npm package, both native plugin manifests, " +
|
|
90
|
+
`protocol docs, and this changelog share Keel ${newVersion}; the OpenSpec ` +
|
|
91
|
+
"dependency pin stays `^1.4.1`.\n\n";
|
|
92
|
+
content = CHANGELOG_HEADER + entry + content.slice(CHANGELOG_HEADER.length);
|
|
93
|
+
fs.writeFileSync(CHANGELOG_PATH, content);
|
|
94
|
+
process.stdout.write(" updated keel/CHANGELOG.md (fill in the TODO lines)\n");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function main() {
|
|
98
|
+
const target = process.argv[2];
|
|
99
|
+
if (!target) {
|
|
100
|
+
fail("usage: node scripts/bump_version.js <patch|minor|major|explicit-version>");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const oldVersion = JSON.parse(fs.readFileSync(PKG_PATH, "utf8")).version;
|
|
104
|
+
if (!SEMVER_RE.test(oldVersion)) {
|
|
105
|
+
fail(`current package.json version is not X.Y.Z: ${oldVersion}`);
|
|
106
|
+
}
|
|
107
|
+
const newVersion = resolveNewVersion(oldVersion, target);
|
|
108
|
+
process.stdout.write(`Bumping ${oldVersion} -> ${newVersion}\n`);
|
|
109
|
+
|
|
110
|
+
bumpPackageFiles(newVersion);
|
|
111
|
+
replaceInFile("plugins/keel/.claude-plugin/plugin.json", [
|
|
112
|
+
[`"version": "${oldVersion}"`, `"version": "${newVersion}"`],
|
|
113
|
+
]);
|
|
114
|
+
replaceInFile("plugins/keel/.codex-plugin/plugin.json", [
|
|
115
|
+
[`"version": "${oldVersion}"`, `"version": "${newVersion}"`],
|
|
116
|
+
]);
|
|
117
|
+
replaceInFile("scripts/validate_plugin.py", [
|
|
118
|
+
[`PACKAGE_VERSION = "${oldVersion}"`, `PACKAGE_VERSION = "${newVersion}"`],
|
|
119
|
+
[`PROTOCOL_VERSION = "${oldVersion}"`, `PROTOCOL_VERSION = "${newVersion}"`],
|
|
120
|
+
]);
|
|
121
|
+
replaceInFile("AGENTS.md", [
|
|
122
|
+
[`v${oldVersion}`, `v${newVersion}`],
|
|
123
|
+
[`version=${oldVersion}`, `version=${newVersion}`],
|
|
124
|
+
]);
|
|
125
|
+
replaceInFile("assets/bootstrap/AGENTS.md", [
|
|
126
|
+
[`version=${oldVersion}`, `version=${newVersion}`],
|
|
127
|
+
]);
|
|
128
|
+
prependChangelogEntry(newVersion);
|
|
129
|
+
|
|
130
|
+
process.stdout.write(
|
|
131
|
+
`\nDone. Next:\n` +
|
|
132
|
+
` 1. Edit keel/CHANGELOG.md ${newVersion} entry.\n` +
|
|
133
|
+
` 2. npm test\n` +
|
|
134
|
+
` 3. git commit -am "${newVersion}"\n` +
|
|
135
|
+
` 4. git tag v${newVersion} && git push --follow-tags && git push origin v${newVersion}\n` +
|
|
136
|
+
` 5. gh release create v${newVersion} --title v${newVersion} --notes "..."\n`
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
main();
|