cleargate 0.11.1 → 0.11.4

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/dist/cli.js CHANGED
@@ -21,7 +21,7 @@ import { Command } from "commander";
21
21
  // package.json
22
22
  var package_default = {
23
23
  name: "cleargate",
24
- version: "0.11.1",
24
+ version: "0.11.4",
25
25
  private: false,
26
26
  type: "module",
27
27
  description: "Planning framework for Claude Code agents \u2014 sprint/epic/story protocol, five-role agent team (architect/developer/qa/devops/reporter), Karpathy-style awareness wiki.",
@@ -2430,6 +2430,61 @@ function resolveScaffoldRoot(opts) {
2430
2430
  };
2431
2431
  }
2432
2432
 
2433
+ // src/lib/session-load-delta.ts
2434
+ function canonicalize(value) {
2435
+ if (value === null || typeof value !== "object") {
2436
+ return JSON.stringify(value);
2437
+ }
2438
+ if (Array.isArray(value)) {
2439
+ return "[" + value.map(canonicalize).join(",") + "]";
2440
+ }
2441
+ const obj = value;
2442
+ const sortedKeys = Object.keys(obj).sort();
2443
+ const pairs = sortedKeys.map((k) => JSON.stringify(k) + ":" + canonicalize(obj[k]));
2444
+ return "{" + pairs.join(",") + "}";
2445
+ }
2446
+ var HOOK_EVENTS = ["PreToolUse", "PostToolUse", "SessionStart", "SubagentStop"];
2447
+ function extractSettingsHooksBlock(settings) {
2448
+ const hooks = settings["hooks"] ?? {};
2449
+ const extracted = {};
2450
+ for (const event of HOOK_EVENTS) {
2451
+ if (Object.prototype.hasOwnProperty.call(hooks, event)) {
2452
+ extracted[event] = hooks[event];
2453
+ }
2454
+ }
2455
+ return extracted;
2456
+ }
2457
+ function extractMcpCleargateEntry(mcp2) {
2458
+ const servers = mcp2["mcpServers"] ?? {};
2459
+ return servers["cleargate"] ?? null;
2460
+ }
2461
+ function extractSessionLoadDelta(filePath, oldContent, newContent) {
2462
+ const normalized = filePath.replace(/\\/g, "/");
2463
+ if (normalized === ".claude/settings.json") {
2464
+ try {
2465
+ const oldSettings = JSON.parse(oldContent);
2466
+ const newSettings = JSON.parse(newContent);
2467
+ const oldHooks = extractSettingsHooksBlock(oldSettings);
2468
+ const newHooks = extractSettingsHooksBlock(newSettings);
2469
+ return canonicalize(oldHooks) !== canonicalize(newHooks);
2470
+ } catch {
2471
+ return true;
2472
+ }
2473
+ }
2474
+ if (normalized === ".mcp.json") {
2475
+ try {
2476
+ const oldMcp = JSON.parse(oldContent);
2477
+ const newMcp = JSON.parse(newContent);
2478
+ const oldEntry = extractMcpCleargateEntry(oldMcp);
2479
+ const newEntry = extractMcpCleargateEntry(newMcp);
2480
+ return canonicalize(oldEntry) !== canonicalize(newEntry);
2481
+ } catch {
2482
+ return true;
2483
+ }
2484
+ }
2485
+ return true;
2486
+ }
2487
+
2433
2488
  // src/commands/init.ts
2434
2489
  var HOOK_ADDITION = {
2435
2490
  hooks: {
@@ -2605,10 +2660,17 @@ async function initHandler(opts = {}) {
2605
2660
  }
2606
2661
  }
2607
2662
  const mergedSettings = mergeSettings(existingSettings, HOOK_ADDITION);
2663
+ const mergedSettingsContent = JSON.stringify(mergedSettings, null, 2) + "\n";
2664
+ const existingSettingsContent = existingSettings !== null ? JSON.stringify(existingSettings, null, 2) + "\n" : "{}";
2608
2665
  fs15.mkdirSync(path15.dirname(settingsPath), { recursive: true });
2609
- writeAtomic(settingsPath, JSON.stringify(mergedSettings, null, 2) + "\n");
2610
- stdout(`[cleargate init] Updated .claude/settings.json: merged PostToolUse hook
2666
+ writeAtomic(settingsPath, mergedSettingsContent);
2667
+ if (extractSessionLoadDelta(".claude/settings.json", existingSettingsContent, mergedSettingsContent)) {
2668
+ stdout(`[cleargate init] Updated .claude/settings.json: merged PostToolUse hook \u2014 restart Claude Code if already open.
2611
2669
  `);
2670
+ } else {
2671
+ stdout(`[cleargate init] .claude/settings.json unchanged (hooks block already current)
2672
+ `);
2673
+ }
2612
2674
  const claudeMdPath = path15.join(cwd, "CLAUDE.md");
2613
2675
  const claudeMdSrcPath = path15.join(payloadDir, "CLAUDE.md");
2614
2676
  let claudeMdBlock;
@@ -7775,7 +7837,16 @@ function removeClearGateHooks(settings) {
7775
7837
  // src/lib/merge-ui.ts
7776
7838
  import { createPatch } from "diff";
7777
7839
  function renderInlineDiff(ours, theirs, filePath) {
7778
- return createPatch(filePath, ours, theirs, "installed", "upstream");
7840
+ const patch = createPatch(filePath, ours, theirs, "installed", "upstream");
7841
+ const hasHunkLines = patch.split("\n").filter((l) => l.startsWith("+") || l.startsWith("-")).filter((l) => !l.startsWith("+++") && !l.startsWith("---")).length > 0;
7842
+ if (!hasHunkLines) {
7843
+ const ourBytes = Buffer.byteLength(ours, "utf-8");
7844
+ const theirBytes = Buffer.byteLength(theirs, "utf-8");
7845
+ const byteNote = ourBytes !== theirBytes ? `${Math.abs(theirBytes - ourBytes)} bytes changed` : "same byte count";
7846
+ return patch + `(whitespace/EOL-only differences \u2014 ${byteNote})
7847
+ `;
7848
+ }
7849
+ return patch;
7779
7850
  }
7780
7851
  async function promptMergeChoice(opts) {
7781
7852
  const { path: filePath, state: state2, ours, theirs } = opts;
@@ -7845,6 +7916,9 @@ async function writeAtomic2(filePath, content) {
7845
7916
  const tmpPath = filePath + ".tmp." + Date.now();
7846
7917
  await fsp.writeFile(tmpPath, content, "utf-8");
7847
7918
  await fsp.rename(tmpPath, filePath);
7919
+ if (filePath.endsWith(".sh")) {
7920
+ await fsp.chmod(filePath, 493);
7921
+ }
7848
7922
  }
7849
7923
  async function updateSnapshotEntry(projectRoot, filePath, newSha) {
7850
7924
  const snapshotPath = path36.join(projectRoot, ".cleargate", ".install-manifest.json");
@@ -8067,7 +8141,15 @@ async function upgradeHandler(flags, cli) {
8067
8141
  let count = 0;
8068
8142
  for (const item of workItems) {
8069
8143
  const state2 = classify(item.entry.sha256, item.installSha, item.currentSha, item.entry.tier);
8070
- stdout(`[dry-run] ${item.entry.path} action=${item.action} state=${state2}`);
8144
+ const projectedPostSha = item.entry.sha256;
8145
+ const projectedPostState = classify(
8146
+ item.entry.sha256,
8147
+ item.entry.sha256,
8148
+ projectedPostSha,
8149
+ item.entry.tier
8150
+ );
8151
+ const stateLabel = state2 !== projectedPostState ? `state=${state2} \u2192 ${projectedPostState}` : `state=${state2}`;
8152
+ stdout(`[dry-run] ${item.entry.path} action=${item.action} ${stateLabel}`);
8071
8153
  count++;
8072
8154
  }
8073
8155
  stdout(`[dry-run] ${count} files planned. No changes made.`);
@@ -8075,8 +8157,19 @@ async function upgradeHandler(flags, cli) {
8075
8157
  }
8076
8158
  const packageRoot = cli?.packageRoot ?? cwd;
8077
8159
  const driftMap = {};
8160
+ const SESSION_LOAD_PATHS = /* @__PURE__ */ new Set([".claude/settings.json", ".mcp.json"]);
8161
+ const sessionRestartFiles = [];
8078
8162
  for (const item of workItems) {
8079
8163
  const { entry, currentSha, installSha, action } = item;
8164
+ let preMutationContent = null;
8165
+ if (SESSION_LOAD_PATHS.has(entry.path)) {
8166
+ const targetPath = path36.join(cwd, entry.path);
8167
+ try {
8168
+ preMutationContent = await fsp.readFile(targetPath, "utf-8");
8169
+ } catch {
8170
+ preMutationContent = "";
8171
+ }
8172
+ }
8080
8173
  switch (action) {
8081
8174
  case "skip": {
8082
8175
  stdout(`[skip] ${entry.path} policy=${entry.overwrite_policy}`);
@@ -8107,9 +8200,28 @@ async function upgradeHandler(flags, cli) {
8107
8200
  current_sha: postSha,
8108
8201
  package_sha: entry.sha256
8109
8202
  };
8203
+ if (SESSION_LOAD_PATHS.has(entry.path) && preMutationContent !== null) {
8204
+ const targetPath = path36.join(cwd, entry.path);
8205
+ let postMutationContent;
8206
+ try {
8207
+ postMutationContent = await fsp.readFile(targetPath, "utf-8");
8208
+ } catch {
8209
+ postMutationContent = "";
8210
+ }
8211
+ if (extractSessionLoadDelta(entry.path, preMutationContent, postMutationContent)) {
8212
+ sessionRestartFiles.push(entry.path);
8213
+ }
8214
+ }
8110
8215
  }
8111
8216
  await writeDriftState(cwd, driftMap, { lastRefreshed: now.toISOString() });
8112
8217
  stdout("[upgrade] complete.");
8218
+ if (sessionRestartFiles.length > 0) {
8219
+ stdout("");
8220
+ stdout(`\u26A0 Restart Claude Code in this repo to load the new ${sessionRestartFiles.length === 1 ? "config" : "configs"}:`);
8221
+ for (const f of sessionRestartFiles) {
8222
+ stdout(` ${f} (loaded once at session start)`);
8223
+ }
8224
+ }
8113
8225
  }
8114
8226
 
8115
8227
  // src/commands/uninstall.ts