@christang/keel 5.73.1 → 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/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.73.1",
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.73.1",
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.73.1",
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 at session start, so an updated plugin applies only after "
189
- + `restarting. Updating is ${plugin.remedy}, which Keel names and does `
190
- + "not run.";
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
- function runKeel(cwd, args) {
248
- const cli = (process.env.KEEL_CLI || "keel").trim();
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 version = runKeel(cwd, ["--version"]);
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(cwd, versionMatch[0]);
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));
@@ -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
- // 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.
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: fill in the CHANGELOG entry, then `npm test`, commit,
15
- // tag `vX.Y.Z`, push, and publish a GitHub Release.
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, "package-lock.json");
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 package-lock.json\n");
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
  ]);