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/CHANGELOG.md +29 -0
- package/dist/MANIFEST.json +5 -5
- package/dist/cli.cjs +118 -5
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +117 -5
- package/dist/cli.js.map +1 -1
- package/dist/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +6 -1
- package/dist/templates/cleargate-planning/.claude/hooks/token-ledger.sh +162 -45
- package/dist/templates/cleargate-planning/.cleargate/scripts/write_dispatch.sh +7 -1
- package/dist/templates/cleargate-planning/MANIFEST.json +5 -5
- package/package.json +1 -1
- package/templates/cleargate-planning/.claude/hooks/pending-task-sentinel.sh +6 -1
- package/templates/cleargate-planning/.claude/hooks/token-ledger.sh +162 -45
- package/templates/cleargate-planning/.cleargate/scripts/write_dispatch.sh +7 -1
- package/templates/cleargate-planning/MANIFEST.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,35 @@
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
Format: [Common Changelog](https://common-changelog.org/) — most-recent version first.
|
|
5
5
|
|
|
6
|
+
## [0.11.4] — 2026-05-05
|
|
7
|
+
|
|
8
|
+
SPRINT-26 Dogfood Hardening — Issues Surfaced by Live Use. Five items shipped (3 bugs + 2 CRs); the SDLC Hardening arc closed in SPRINT-25, this is the first sprint addressing dogfood-surfaced framework gaps from real-use testing on `markdown_file_renderer`.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Token-ledger `work_item` fallback grep no longer misattributes to first lexical EPIC-NNN** (BUG-027) — `cleargate-planning/.claude/hooks/token-ledger.sh` resolver chain now tries (1) prior ledger row's `work_item_id` then (2) most-recent `dispatch-marker:` log line BEFORE falling back to transcript grep. Regression of [[BUG-024]] (closed in SPRINT-19); 12 misattribution instances observed during the SPRINT-02 dogfood test on `markdown_file_renderer` (~25% of session output tokens tagged to wrong epic). New authoritative snapshot `cleargate-cli/test/snapshots/hooks/token-ledger.bug-027.sh` supersedes cr-044's byte-equality assertion (cr-044 demoted to existence-only).
|
|
12
|
+
- **`cleargate upgrade --dry-run` and live run now report identical `state=` for every file** (BUG-028) — `cleargate-cli/src/commands/upgrade.ts` dry-run path now computes a hypothetical `postSha` by reading the upstream payload and emits a two-state line `state=<pre> → <post>` for upstream-changed files. `cleargate-cli/src/lib/merge-ui.ts` `renderInlineDiff()` appends a `(whitespace/EOL-only differences — N bytes changed)` annotation when `createPatch()` produces an empty body, so the user always sees a signal in the merge prompt. Discovered when the same `.claude/hooks/session-start.sh` file reported `state=clean` in dry-run and `state=upstream-changed` in the immediately-following real run.
|
|
13
|
+
- **Parallel-eligible Task dispatches no longer silently serialize** (BUG-029) — `cleargate-planning/.claude/hooks/write_dispatch.sh` and `cleargate-planning/.claude/hooks/pending-task-sentinel.sh` now uniquify marker filenames with `${ts}-${PID}-${RANDOM}` suffixes (was: single `.dispatch-${SESSION_ID}.json` and `.pending-task-${TURN_INDEX}.json` per orchestrator session, which collided when two Agent calls fired in one turn). `cleargate-planning/.claude/hooks/token-ledger.sh` SubagentStop handler now matches completion to dispatch by `(work_item_id, agent_type)` tuple from the transcript instead of the prior `ls -t | head -1` newest-file lookup. Discovered during the SPRINT-02 dogfood test where two parallel-eligible stories dispatched at the same timestamp but only one produced a ledger row; the second was silently dropped and re-dispatched 18 minutes later. New authoritative snapshot `cleargate-cli/test/snapshots/hooks/token-ledger.bug-029.sh`; `bug-027.sh` rolls forward to the new canonical state and is demoted to existence-only.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- **Smarter session-load restart warning — suppresses no-op rewrites** (CR-059) — new helper `cleargate-cli/src/lib/session-load-delta.ts` exporting `extractSessionLoadDelta(filePath, oldContent, newContent)` returns `true` only when schema-meaningful keys actually changed: `hooks.{PreToolUse,PostToolUse,SessionStart,SubagentStop}.*` for `.claude/settings.json`, `mcpServers.cleargate` for `.mcp.json`. Both `upgrade.ts` and `init.ts` now consult the helper before emitting the v0.11.2 "Restart Claude Code" warning, so cosmetic re-formats (key order, whitespace, trailing newline) don't trigger warning fatigue. Conservative on parse failure: when in doubt, warn.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
- **`CLAUDE.md` "Dogfood split" section clarified** (CR-060) — explicit one-paragraph note that target repos do **not** receive a `cleargate-planning/` directory; only the *contents* of `.claude/` and `.cleargate/` are copied. Top-level `CLAUDE.md` is bounded-block-injected; top-level `MANIFEST.json` is skipped per `cleargate-cli/src/init/copy-payload.ts:54` SKIP_FILES; install snapshot lands at `.cleargate/.install-manifest.json`. Pure doc, no behavior change.
|
|
20
|
+
|
|
21
|
+
## [0.11.3] — 2026-05-05
|
|
22
|
+
|
|
23
|
+
Hotfix.
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- **`cleargate upgrade` no longer strips the executable bit from `.sh` hook scripts** — `cleargate-cli/src/commands/upgrade.ts` `writeAtomic()` now `chmod 0o755` after writing any `.sh` target. `fs.writeFile` defaults to 0o644; init solved this in `copy-payload.ts` (BUG-018) but upgrade's write path bypassed that fix. Result: every Claude Code hook in target repos failed with `Permission denied` after upgrade until manually re-chmod'd. Observed today on `markdown_file_renderer` after the SPRINT-02 PostToolUse:Edit hook fired.
|
|
27
|
+
|
|
28
|
+
## [0.11.2] — 2026-05-05
|
|
29
|
+
|
|
30
|
+
Hotfix.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
- **`cleargate upgrade` and `cleargate init` now warn when session-loaded configs change** — `cleargate-cli/src/commands/upgrade.ts` tracks modifications to `.claude/settings.json` and `.mcp.json` during the run and prints a `⚠ Restart Claude Code in this repo` block at the end if either changed. `init.ts` adds the same "restart Claude Code if already open" suffix to its `Updated .claude/settings.json` log line (parallels the existing `.mcp.json` message). Without this, hook wiring + MCP-server changes silently fail to load until the user happens to restart their session — observed today when SPRINT-02 drafts in `markdown_file_renderer` did not trigger `stamp-and-gate.sh` after upgrade because the running session held a pre-upgrade settings snapshot.
|
|
34
|
+
|
|
6
35
|
## [0.11.1] — 2026-05-05
|
|
7
36
|
|
|
8
37
|
Hotfix.
|
package/dist/MANIFEST.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"cleargate_version": "0.11.
|
|
3
|
-
"generated_at": "2026-05-
|
|
2
|
+
"cleargate_version": "0.11.4",
|
|
3
|
+
"generated_at": "2026-05-05T17:48:13.929Z",
|
|
4
4
|
"files": [
|
|
5
5
|
{
|
|
6
6
|
"path": ".claude/agents/architect.md",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
},
|
|
68
68
|
{
|
|
69
69
|
"path": ".claude/hooks/pending-task-sentinel.sh",
|
|
70
|
-
"sha256": "
|
|
70
|
+
"sha256": "a3c2dc71a803c37527afd059b81e2adad2104d4887ae125846a2416f2f719b71",
|
|
71
71
|
"tier": "hook",
|
|
72
72
|
"overwrite_policy": "always",
|
|
73
73
|
"preserve_on_uninstall": false
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"path": ".claude/hooks/token-ledger.sh",
|
|
126
|
-
"sha256": "
|
|
126
|
+
"sha256": "37a5bc311ca36f016ed72f1ded92d70d2b0acdd8993d634667b05d3407de0f22",
|
|
127
127
|
"tier": "hook",
|
|
128
128
|
"overwrite_policy": "always",
|
|
129
129
|
"preserve_on_uninstall": false
|
|
@@ -389,7 +389,7 @@
|
|
|
389
389
|
},
|
|
390
390
|
{
|
|
391
391
|
"path": ".cleargate/scripts/write_dispatch.sh",
|
|
392
|
-
"sha256": "
|
|
392
|
+
"sha256": "2d4ebbd8a6f0e833c86b534c3106377018d54f6e24b3ff1a171b18d807103748",
|
|
393
393
|
"tier": "script",
|
|
394
394
|
"overwrite_policy": "always",
|
|
395
395
|
"preserve_on_uninstall": false
|
package/dist/cli.cjs
CHANGED
|
@@ -696,7 +696,7 @@ var import_commander = require("commander");
|
|
|
696
696
|
// package.json
|
|
697
697
|
var package_default = {
|
|
698
698
|
name: "cleargate",
|
|
699
|
-
version: "0.11.
|
|
699
|
+
version: "0.11.4",
|
|
700
700
|
private: false,
|
|
701
701
|
type: "module",
|
|
702
702
|
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.",
|
|
@@ -3176,6 +3176,62 @@ function resolveScaffoldRoot(opts) {
|
|
|
3176
3176
|
};
|
|
3177
3177
|
}
|
|
3178
3178
|
|
|
3179
|
+
// src/lib/session-load-delta.ts
|
|
3180
|
+
init_cjs_shims();
|
|
3181
|
+
function canonicalize(value) {
|
|
3182
|
+
if (value === null || typeof value !== "object") {
|
|
3183
|
+
return JSON.stringify(value);
|
|
3184
|
+
}
|
|
3185
|
+
if (Array.isArray(value)) {
|
|
3186
|
+
return "[" + value.map(canonicalize).join(",") + "]";
|
|
3187
|
+
}
|
|
3188
|
+
const obj = value;
|
|
3189
|
+
const sortedKeys = Object.keys(obj).sort();
|
|
3190
|
+
const pairs = sortedKeys.map((k) => JSON.stringify(k) + ":" + canonicalize(obj[k]));
|
|
3191
|
+
return "{" + pairs.join(",") + "}";
|
|
3192
|
+
}
|
|
3193
|
+
var HOOK_EVENTS = ["PreToolUse", "PostToolUse", "SessionStart", "SubagentStop"];
|
|
3194
|
+
function extractSettingsHooksBlock(settings) {
|
|
3195
|
+
const hooks = settings["hooks"] ?? {};
|
|
3196
|
+
const extracted = {};
|
|
3197
|
+
for (const event of HOOK_EVENTS) {
|
|
3198
|
+
if (Object.prototype.hasOwnProperty.call(hooks, event)) {
|
|
3199
|
+
extracted[event] = hooks[event];
|
|
3200
|
+
}
|
|
3201
|
+
}
|
|
3202
|
+
return extracted;
|
|
3203
|
+
}
|
|
3204
|
+
function extractMcpCleargateEntry(mcp2) {
|
|
3205
|
+
const servers = mcp2["mcpServers"] ?? {};
|
|
3206
|
+
return servers["cleargate"] ?? null;
|
|
3207
|
+
}
|
|
3208
|
+
function extractSessionLoadDelta(filePath, oldContent, newContent) {
|
|
3209
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
3210
|
+
if (normalized === ".claude/settings.json") {
|
|
3211
|
+
try {
|
|
3212
|
+
const oldSettings = JSON.parse(oldContent);
|
|
3213
|
+
const newSettings = JSON.parse(newContent);
|
|
3214
|
+
const oldHooks = extractSettingsHooksBlock(oldSettings);
|
|
3215
|
+
const newHooks = extractSettingsHooksBlock(newSettings);
|
|
3216
|
+
return canonicalize(oldHooks) !== canonicalize(newHooks);
|
|
3217
|
+
} catch {
|
|
3218
|
+
return true;
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
if (normalized === ".mcp.json") {
|
|
3222
|
+
try {
|
|
3223
|
+
const oldMcp = JSON.parse(oldContent);
|
|
3224
|
+
const newMcp = JSON.parse(newContent);
|
|
3225
|
+
const oldEntry = extractMcpCleargateEntry(oldMcp);
|
|
3226
|
+
const newEntry = extractMcpCleargateEntry(newMcp);
|
|
3227
|
+
return canonicalize(oldEntry) !== canonicalize(newEntry);
|
|
3228
|
+
} catch {
|
|
3229
|
+
return true;
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
return true;
|
|
3233
|
+
}
|
|
3234
|
+
|
|
3179
3235
|
// src/commands/init.ts
|
|
3180
3236
|
var HOOK_ADDITION = {
|
|
3181
3237
|
hooks: {
|
|
@@ -3351,10 +3407,17 @@ async function initHandler(opts = {}) {
|
|
|
3351
3407
|
}
|
|
3352
3408
|
}
|
|
3353
3409
|
const mergedSettings = mergeSettings(existingSettings, HOOK_ADDITION);
|
|
3410
|
+
const mergedSettingsContent = JSON.stringify(mergedSettings, null, 2) + "\n";
|
|
3411
|
+
const existingSettingsContent = existingSettings !== null ? JSON.stringify(existingSettings, null, 2) + "\n" : "{}";
|
|
3354
3412
|
fs17.mkdirSync(path18.dirname(settingsPath), { recursive: true });
|
|
3355
|
-
writeAtomic(settingsPath,
|
|
3356
|
-
|
|
3413
|
+
writeAtomic(settingsPath, mergedSettingsContent);
|
|
3414
|
+
if (extractSessionLoadDelta(".claude/settings.json", existingSettingsContent, mergedSettingsContent)) {
|
|
3415
|
+
stdout(`[cleargate init] Updated .claude/settings.json: merged PostToolUse hook \u2014 restart Claude Code if already open.
|
|
3357
3416
|
`);
|
|
3417
|
+
} else {
|
|
3418
|
+
stdout(`[cleargate init] .claude/settings.json unchanged (hooks block already current)
|
|
3419
|
+
`);
|
|
3420
|
+
}
|
|
3358
3421
|
const claudeMdPath = path18.join(cwd, "CLAUDE.md");
|
|
3359
3422
|
const claudeMdSrcPath = path18.join(payloadDir, "CLAUDE.md");
|
|
3360
3423
|
let claudeMdBlock;
|
|
@@ -8863,7 +8926,16 @@ function removeClearGateHooks(settings) {
|
|
|
8863
8926
|
init_cjs_shims();
|
|
8864
8927
|
var import_diff = require("diff");
|
|
8865
8928
|
function renderInlineDiff(ours, theirs, filePath) {
|
|
8866
|
-
|
|
8929
|
+
const patch = (0, import_diff.createPatch)(filePath, ours, theirs, "installed", "upstream");
|
|
8930
|
+
const hasHunkLines = patch.split("\n").filter((l) => l.startsWith("+") || l.startsWith("-")).filter((l) => !l.startsWith("+++") && !l.startsWith("---")).length > 0;
|
|
8931
|
+
if (!hasHunkLines) {
|
|
8932
|
+
const ourBytes = Buffer.byteLength(ours, "utf-8");
|
|
8933
|
+
const theirBytes = Buffer.byteLength(theirs, "utf-8");
|
|
8934
|
+
const byteNote = ourBytes !== theirBytes ? `${Math.abs(theirBytes - ourBytes)} bytes changed` : "same byte count";
|
|
8935
|
+
return patch + `(whitespace/EOL-only differences \u2014 ${byteNote})
|
|
8936
|
+
`;
|
|
8937
|
+
}
|
|
8938
|
+
return patch;
|
|
8867
8939
|
}
|
|
8868
8940
|
async function promptMergeChoice(opts) {
|
|
8869
8941
|
const { path: filePath, state: state2, ours, theirs } = opts;
|
|
@@ -8934,6 +9006,9 @@ async function writeAtomic2(filePath, content) {
|
|
|
8934
9006
|
const tmpPath = filePath + ".tmp." + Date.now();
|
|
8935
9007
|
await fsp.writeFile(tmpPath, content, "utf-8");
|
|
8936
9008
|
await fsp.rename(tmpPath, filePath);
|
|
9009
|
+
if (filePath.endsWith(".sh")) {
|
|
9010
|
+
await fsp.chmod(filePath, 493);
|
|
9011
|
+
}
|
|
8937
9012
|
}
|
|
8938
9013
|
async function updateSnapshotEntry(projectRoot, filePath, newSha) {
|
|
8939
9014
|
const snapshotPath = path41.join(projectRoot, ".cleargate", ".install-manifest.json");
|
|
@@ -9156,7 +9231,15 @@ async function upgradeHandler(flags, cli) {
|
|
|
9156
9231
|
let count = 0;
|
|
9157
9232
|
for (const item of workItems) {
|
|
9158
9233
|
const state2 = classify(item.entry.sha256, item.installSha, item.currentSha, item.entry.tier);
|
|
9159
|
-
|
|
9234
|
+
const projectedPostSha = item.entry.sha256;
|
|
9235
|
+
const projectedPostState = classify(
|
|
9236
|
+
item.entry.sha256,
|
|
9237
|
+
item.entry.sha256,
|
|
9238
|
+
projectedPostSha,
|
|
9239
|
+
item.entry.tier
|
|
9240
|
+
);
|
|
9241
|
+
const stateLabel = state2 !== projectedPostState ? `state=${state2} \u2192 ${projectedPostState}` : `state=${state2}`;
|
|
9242
|
+
stdout(`[dry-run] ${item.entry.path} action=${item.action} ${stateLabel}`);
|
|
9160
9243
|
count++;
|
|
9161
9244
|
}
|
|
9162
9245
|
stdout(`[dry-run] ${count} files planned. No changes made.`);
|
|
@@ -9164,8 +9247,19 @@ async function upgradeHandler(flags, cli) {
|
|
|
9164
9247
|
}
|
|
9165
9248
|
const packageRoot = cli?.packageRoot ?? cwd;
|
|
9166
9249
|
const driftMap = {};
|
|
9250
|
+
const SESSION_LOAD_PATHS = /* @__PURE__ */ new Set([".claude/settings.json", ".mcp.json"]);
|
|
9251
|
+
const sessionRestartFiles = [];
|
|
9167
9252
|
for (const item of workItems) {
|
|
9168
9253
|
const { entry, currentSha, installSha, action } = item;
|
|
9254
|
+
let preMutationContent = null;
|
|
9255
|
+
if (SESSION_LOAD_PATHS.has(entry.path)) {
|
|
9256
|
+
const targetPath = path41.join(cwd, entry.path);
|
|
9257
|
+
try {
|
|
9258
|
+
preMutationContent = await fsp.readFile(targetPath, "utf-8");
|
|
9259
|
+
} catch {
|
|
9260
|
+
preMutationContent = "";
|
|
9261
|
+
}
|
|
9262
|
+
}
|
|
9169
9263
|
switch (action) {
|
|
9170
9264
|
case "skip": {
|
|
9171
9265
|
stdout(`[skip] ${entry.path} policy=${entry.overwrite_policy}`);
|
|
@@ -9196,9 +9290,28 @@ async function upgradeHandler(flags, cli) {
|
|
|
9196
9290
|
current_sha: postSha,
|
|
9197
9291
|
package_sha: entry.sha256
|
|
9198
9292
|
};
|
|
9293
|
+
if (SESSION_LOAD_PATHS.has(entry.path) && preMutationContent !== null) {
|
|
9294
|
+
const targetPath = path41.join(cwd, entry.path);
|
|
9295
|
+
let postMutationContent;
|
|
9296
|
+
try {
|
|
9297
|
+
postMutationContent = await fsp.readFile(targetPath, "utf-8");
|
|
9298
|
+
} catch {
|
|
9299
|
+
postMutationContent = "";
|
|
9300
|
+
}
|
|
9301
|
+
if (extractSessionLoadDelta(entry.path, preMutationContent, postMutationContent)) {
|
|
9302
|
+
sessionRestartFiles.push(entry.path);
|
|
9303
|
+
}
|
|
9304
|
+
}
|
|
9199
9305
|
}
|
|
9200
9306
|
await writeDriftState(cwd, driftMap, { lastRefreshed: now.toISOString() });
|
|
9201
9307
|
stdout("[upgrade] complete.");
|
|
9308
|
+
if (sessionRestartFiles.length > 0) {
|
|
9309
|
+
stdout("");
|
|
9310
|
+
stdout(`\u26A0 Restart Claude Code in this repo to load the new ${sessionRestartFiles.length === 1 ? "config" : "configs"}:`);
|
|
9311
|
+
for (const f of sessionRestartFiles) {
|
|
9312
|
+
stdout(` ${f} (loaded once at session start)`);
|
|
9313
|
+
}
|
|
9314
|
+
}
|
|
9202
9315
|
}
|
|
9203
9316
|
|
|
9204
9317
|
// src/commands/uninstall.ts
|