@christang/keel 5.73.0 → 5.74.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/README.md +16 -7
- package/README.zh-CN.md +12 -7
- package/assets/bootstrap/AGENTS.md +1 -1
- package/bin/keel +9 -0
- package/bin/keel.js +14 -7
- package/npm-shrinkwrap.json +1137 -0
- package/package.json +2 -1
- package/plugins/keel/.claude-plugin/plugin.json +1 -1
- package/plugins/keel/.codex-plugin/plugin.json +1 -1
- package/plugins/keel/scripts/session-start.js +68 -9
- package/scripts/bump_version.js +36 -11
- package/scripts/validate_plugin.py +473 -22
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@christang/keel",
|
|
3
3
|
"displayName": "Keel",
|
|
4
4
|
"description": "Keel OpenSpec execution discipline CLI for Claude Code, Codex, and OpenCode.",
|
|
5
|
-
"version": "5.
|
|
5
|
+
"version": "5.74.0",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"src/core/",
|
|
18
18
|
"assets/",
|
|
19
19
|
"README.md",
|
|
20
|
+
"npm-shrinkwrap.json",
|
|
20
21
|
"plugins/",
|
|
21
22
|
"!**/__pycache__",
|
|
22
23
|
"!**/*.pyc"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "keel",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.74.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.74.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",
|
|
@@ -160,13 +160,16 @@ function protocolVersion(cwd) {
|
|
|
160
160
|
// stamped into its managed block. Keel reports the disagreement and stops
|
|
161
161
|
// there — installing and updating are the host's, which already has commands
|
|
162
162
|
// for both.
|
|
163
|
-
function versionReport(cwd, cli) {
|
|
163
|
+
function versionReport(cwd, cli, pathCli = null) {
|
|
164
164
|
const plugin = pluginManifest();
|
|
165
165
|
const found = [
|
|
166
166
|
["plugin", plugin.version],
|
|
167
167
|
["CLI", cli],
|
|
168
168
|
["protocol", protocolVersion(cwd)],
|
|
169
169
|
];
|
|
170
|
+
// A PATH copy is compared only when it exists. Its absence is the
|
|
171
|
+
// plugin-only install, which is not drift and not an unread version.
|
|
172
|
+
if (pathCli) found.push(["PATH keel", pathCli]);
|
|
170
173
|
// Missing is not mismatched. A version nobody can discover never produces a
|
|
171
174
|
// line on its own, or a repository with no managed block would be warned at
|
|
172
175
|
// every session until its reader stopped looking — and fewer than two
|
|
@@ -184,10 +187,31 @@ function versionReport(cwd, cli) {
|
|
|
184
187
|
.map(([name]) => name)
|
|
185
188
|
.join(" and ");
|
|
186
189
|
const missing = unread ? ` (${unread} undiscovered, not compared)` : "";
|
|
190
|
+
// Naming the reload rather than a restart (#164): the host's
|
|
191
|
+
// `/reload-plugins` swaps hooks in the running session, so "restart" sent
|
|
192
|
+
// the reader to the expensive remedy for a cheap one.
|
|
193
|
+
// The host puts the user's PATH ahead of plugin `bin/` directories, so a
|
|
194
|
+
// global install is what the agent's own `keel` commands run even though
|
|
195
|
+
// this hook ran the plugin's. Both remedies are named; neither is run.
|
|
196
|
+
const shadow = pathCli && pathCli !== cli
|
|
197
|
+
? ` The \`keel\` on PATH (${pathCli}) shadows this plugin's CLI for the `
|
|
198
|
+
+ "agent's commands, because the host puts your PATH first: remove it "
|
|
199
|
+
+ "with `npm rm -g @christang/keel`, since the plugin carries its own, "
|
|
200
|
+
+ `or align it with \`npm i -g @christang/keel@${cli}\`.`
|
|
201
|
+
: "";
|
|
187
202
|
return `runtime versions disagree: ${named}${missing}. A session's hooks are `
|
|
188
|
-
+ "fixed
|
|
189
|
-
+ `
|
|
190
|
-
+
|
|
203
|
+
+ "fixed when it loads the plugin, so an updated plugin applies after "
|
|
204
|
+
+ "`/reload-plugins` or at the next session start. Updating is "
|
|
205
|
+
+ `${plugin.remedy}, which Keel names and does not run.${shadow}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// The `keel` a bare command resolves, asked only when this hook ran its own
|
|
209
|
+
// CLI; with no such command there is nothing to compare.
|
|
210
|
+
function pathCliVersion(cwd) {
|
|
211
|
+
const probe = runKeel(cwd, ["--version"], "keel");
|
|
212
|
+
if (probe.error || probe.status !== 0) return null;
|
|
213
|
+
const match = String(probe.stdout || "").match(/\d+\.\d+\.\d+/);
|
|
214
|
+
return match ? match[0] : null;
|
|
191
215
|
}
|
|
192
216
|
|
|
193
217
|
// The Keel mark. A keel is the carina, the ridge on a bird's sternum, so the
|
|
@@ -244,8 +268,38 @@ function panel(lines) {
|
|
|
244
268
|
].join("\n");
|
|
245
269
|
}
|
|
246
270
|
|
|
247
|
-
|
|
248
|
-
|
|
271
|
+
// On Claude the plugin is the published package (#164), so the CLI it shipped
|
|
272
|
+
// with sits three levels above this script. It is recognized by the package's
|
|
273
|
+
// name and not by the path alone: a Codex cache holds only `plugins/keel`, and
|
|
274
|
+
// whatever lies above that is not this plugin's to run.
|
|
275
|
+
function packagedCli() {
|
|
276
|
+
const root = path.join(__dirname, "..", "..", "..");
|
|
277
|
+
try {
|
|
278
|
+
const pkg = JSON.parse(
|
|
279
|
+
fs.readFileSync(path.join(root, "package.json"), "utf8")
|
|
280
|
+
);
|
|
281
|
+
const cli = path.join(root, "bin", "keel.js");
|
|
282
|
+
if (pkg.name === "@christang/keel" && fs.existsSync(cli)) return cli;
|
|
283
|
+
} catch {
|
|
284
|
+
// No package around this script: the plugin was copied on its own.
|
|
285
|
+
}
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// An explicit KEEL_CLI first, then the CLI this plugin shipped with, then
|
|
290
|
+
// whatever `keel` is on PATH. The packaged CLI runs under the node running
|
|
291
|
+
// this hook, so it needs neither PATH nor an executable mode.
|
|
292
|
+
function keelCommand() {
|
|
293
|
+
const explicit = (process.env.KEEL_CLI || "").trim();
|
|
294
|
+
if (explicit) return { command: explicit, packaged: false };
|
|
295
|
+
const own = packagedCli();
|
|
296
|
+
if (own) {
|
|
297
|
+
return { command: `"${process.execPath}" "${own}"`, packaged: true };
|
|
298
|
+
}
|
|
299
|
+
return { command: "keel", packaged: false };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function runKeel(cwd, args, cli) {
|
|
249
303
|
return spawnSync(`${cli} ${args.join(" ")}`, {
|
|
250
304
|
cwd,
|
|
251
305
|
shell: true,
|
|
@@ -283,7 +337,8 @@ function main() {
|
|
|
283
337
|
return 0;
|
|
284
338
|
}
|
|
285
339
|
|
|
286
|
-
const
|
|
340
|
+
const cli = keelCommand();
|
|
341
|
+
const version = runKeel(cwd, ["--version"], cli.command);
|
|
287
342
|
const versionMatch = String(version.stdout || "").match(/(\d+)\.\d+\.\d+/);
|
|
288
343
|
if (
|
|
289
344
|
version.error
|
|
@@ -298,7 +353,7 @@ function main() {
|
|
|
298
353
|
return 0;
|
|
299
354
|
}
|
|
300
355
|
|
|
301
|
-
const result = runKeel(cwd, ["context", "--json"]);
|
|
356
|
+
const result = runKeel(cwd, ["context", "--json"], cli.command);
|
|
302
357
|
if (result.error || result.status !== 0 || !String(result.stdout || "").trim()) {
|
|
303
358
|
fallback("`keel context --json` failed or timed out.");
|
|
304
359
|
return 0;
|
|
@@ -375,7 +430,11 @@ function main() {
|
|
|
375
430
|
+ "does not guess among candidates."
|
|
376
431
|
);
|
|
377
432
|
}
|
|
378
|
-
const drift = versionReport(
|
|
433
|
+
const drift = versionReport(
|
|
434
|
+
cwd,
|
|
435
|
+
versionMatch[0],
|
|
436
|
+
cli.packaged ? pathCliVersion(cwd) : null
|
|
437
|
+
);
|
|
379
438
|
if (drift) {
|
|
380
439
|
lines.push(`- ${drift}`);
|
|
381
440
|
human.splice(human.length - 1, 0, drift[0].toUpperCase() + drift.slice(1));
|
package/scripts/bump_version.js
CHANGED
|
@@ -4,22 +4,23 @@
|
|
|
4
4
|
// One-shot version bump across every place Keel pins its version.
|
|
5
5
|
//
|
|
6
6
|
// The Keel validation suite requires the same version in package.json,
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
7
|
+
// npm-shrinkwrap.json, both native plugin manifests, the Claude marketplace
|
|
8
|
+
// entry and the package release it pins, the validator constants, the
|
|
9
|
+
// protocol docs, and the changelog. This script updates all of them together
|
|
10
|
+
// so a release never ships half-aligned.
|
|
10
11
|
//
|
|
11
12
|
// Usage:
|
|
12
13
|
// node scripts/bump_version.js <patch|minor|major|explicit-version>
|
|
13
14
|
//
|
|
14
|
-
// After running
|
|
15
|
-
//
|
|
15
|
+
// After running, follow the steps it prints: the release lands through a pull
|
|
16
|
+
// request, and nothing is tagged by hand (#162).
|
|
16
17
|
|
|
17
18
|
const fs = require("fs");
|
|
18
19
|
const path = require("path");
|
|
19
20
|
|
|
20
21
|
const ROOT = path.resolve(__dirname, "..");
|
|
21
22
|
const PKG_PATH = path.join(ROOT, "package.json");
|
|
22
|
-
const LOCK_PATH = path.join(ROOT, "
|
|
23
|
+
const LOCK_PATH = path.join(ROOT, "npm-shrinkwrap.json");
|
|
23
24
|
const CHANGELOG_PATH = path.join(ROOT, "keel", "CHANGELOG.md");
|
|
24
25
|
const CHANGELOG_HEADER = "# Keel Changelog\n\n";
|
|
25
26
|
const SEMVER_RE = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
@@ -58,7 +59,24 @@ function bumpPackageFiles(newVersion) {
|
|
|
58
59
|
lock.packages[""].version = newVersion;
|
|
59
60
|
}
|
|
60
61
|
writeJson(LOCK_PATH, lock);
|
|
61
|
-
process.stdout.write(" updated
|
|
62
|
+
process.stdout.write(" updated npm-shrinkwrap.json\n");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// On Claude the plugin is the published package (#164): the marketplace entry
|
|
66
|
+
// is its manifest, labeled with the release and pinned to that same release of
|
|
67
|
+
// `@christang/keel`. Both numbers move, or a refresh installs the old package.
|
|
68
|
+
function bumpClaudeMarketplace(newVersion) {
|
|
69
|
+
const relPath = ".claude-plugin/marketplace.json";
|
|
70
|
+
const filePath = path.join(ROOT, relPath);
|
|
71
|
+
const market = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
72
|
+
const entry = (market.plugins || []).find((plugin) => plugin.name === "keel");
|
|
73
|
+
if (!entry || !entry.source || typeof entry.source !== "object") {
|
|
74
|
+
fail(`expected an npm-sourced keel entry in ${relPath}`);
|
|
75
|
+
}
|
|
76
|
+
entry.version = newVersion;
|
|
77
|
+
entry.source.version = newVersion;
|
|
78
|
+
writeJson(filePath, market);
|
|
79
|
+
process.stdout.write(` updated ${relPath}\n`);
|
|
62
80
|
}
|
|
63
81
|
|
|
64
82
|
function replaceInFile(relPath, replacements) {
|
|
@@ -145,6 +163,7 @@ function main() {
|
|
|
145
163
|
process.stdout.write(`Bumping ${oldVersion} -> ${newVersion}\n`);
|
|
146
164
|
|
|
147
165
|
bumpPackageFiles(newVersion);
|
|
166
|
+
bumpClaudeMarketplace(newVersion);
|
|
148
167
|
replaceInFile("plugins/keel/.claude-plugin/plugin.json", [
|
|
149
168
|
[`"version": "${oldVersion}"`, `"version": "${newVersion}"`],
|
|
150
169
|
]);
|
|
@@ -165,13 +184,19 @@ function main() {
|
|
|
165
184
|
|
|
166
185
|
prependChangelogEntry(newVersion);
|
|
167
186
|
|
|
187
|
+
// A version is released by landing its pull request: publish.yml's `land`
|
|
188
|
+
// job merges it once full-gate passes, then publishes, tags, and releases in
|
|
189
|
+
// the same run. It skips a version whose tag already exists, so a tag pushed
|
|
190
|
+
// by hand with no release means nothing publishes it at all (#162).
|
|
168
191
|
process.stdout.write(
|
|
169
192
|
`\nDone. Next:\n` +
|
|
170
|
-
` 1.
|
|
193
|
+
` 1. Write the ${newVersion} entry into its stub in keel/CHANGELOG.md.\n` +
|
|
171
194
|
` 2. npm test\n` +
|
|
172
|
-
` 3. git
|
|
173
|
-
`
|
|
174
|
-
`
|
|
195
|
+
` 3. Commit on a branch (git add -A, so an archived change is included),\n` +
|
|
196
|
+
` push it, and open a pull request into main.\n` +
|
|
197
|
+
` When full-gate passes, the land job merges it, publishes ${newVersion} to npm,\n` +
|
|
198
|
+
` and creates tag v${newVersion} with its release. Do not tag by hand: the job\n` +
|
|
199
|
+
` skips a version whose tag exists, and a pushed tag alone publishes nothing.\n`
|
|
175
200
|
);
|
|
176
201
|
}
|
|
177
202
|
|