@christang/keel 5.1.1 → 5.2.0
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/LICENSE +21 -21
- 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 +172 -166
- package/assets/openspec/schemas/keel-spec-driven/templates/tasks.md +13 -4
- package/bin/keel.js +218 -15
- 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/hooks/hooks.json +30 -30
- package/plugins/keel/scripts/pretooluse-guard.js +156 -156
- package/plugins/keel/scripts/session-start.js +182 -182
- 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 +0 -70
- package/scripts/run_python.js +63 -63
- package/scripts/validate_plugin.py +408 -96
- package/src/core/capabilities.js +291 -291
- package/src/core/context.js +521 -514
- package/src/core/gates.js +664 -643
- package/src/core/goal.js +230 -230
- package/src/core/guard.js +295 -295
- package/src/core/helper.js +319 -319
- package/src/core/projection.js +195 -195
- package/src/core/task-contract.js +757 -736
- package/src/core/tasksview.js +123 -123
package/bin/keel.js
CHANGED
|
@@ -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;
|
|
@@ -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.
|
|
3
|
+
"version": "5.2.0",
|
|
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.
|
|
3
|
+
"version": "5.2.0",
|
|
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,30 +1,30 @@
|
|
|
1
|
-
{
|
|
2
|
-
"description": "Keel plugin hooks: non-blocking SessionStart projection and the manifest-gated PreToolUse write guard, both backed by the shared keel CLI contract.",
|
|
3
|
-
"hooks": {
|
|
4
|
-
"SessionStart": [
|
|
5
|
-
{
|
|
6
|
-
"hooks": [
|
|
7
|
-
{
|
|
8
|
-
"type": "command",
|
|
9
|
-
"command": "node",
|
|
10
|
-
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/session-start.js"],
|
|
11
|
-
"timeout": 15
|
|
12
|
-
}
|
|
13
|
-
]
|
|
14
|
-
}
|
|
15
|
-
],
|
|
16
|
-
"PreToolUse": [
|
|
17
|
-
{
|
|
18
|
-
"matcher": "Edit|Write|NotebookEdit",
|
|
19
|
-
"hooks": [
|
|
20
|
-
{
|
|
21
|
-
"type": "command",
|
|
22
|
-
"command": "node",
|
|
23
|
-
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/pretooluse-guard.js"],
|
|
24
|
-
"timeout": 10
|
|
25
|
-
}
|
|
26
|
-
]
|
|
27
|
-
}
|
|
28
|
-
]
|
|
29
|
-
}
|
|
30
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"description": "Keel plugin hooks: non-blocking SessionStart projection and the manifest-gated PreToolUse write guard, both backed by the shared keel CLI contract.",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionStart": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node",
|
|
10
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/session-start.js"],
|
|
11
|
+
"timeout": 15
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"PreToolUse": [
|
|
17
|
+
{
|
|
18
|
+
"matcher": "Edit|Write|NotebookEdit",
|
|
19
|
+
"hooks": [
|
|
20
|
+
{
|
|
21
|
+
"type": "command",
|
|
22
|
+
"command": "node",
|
|
23
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/pretooluse-guard.js"],
|
|
24
|
+
"timeout": 10
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|