cleargate 0.11.3 → 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 +15 -0
- package/dist/MANIFEST.json +5 -5
- package/dist/cli.cjs +105 -7
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +104 -7
- 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,21 @@
|
|
|
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
|
+
|
|
6
21
|
## [0.11.3] — 2026-05-05
|
|
7
22
|
|
|
8
23
|
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.
|
|
3416
|
+
`);
|
|
3417
|
+
} else {
|
|
3418
|
+
stdout(`[cleargate init] .claude/settings.json unchanged (hooks block already current)
|
|
3357
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;
|
|
@@ -9159,7 +9231,15 @@ async function upgradeHandler(flags, cli) {
|
|
|
9159
9231
|
let count = 0;
|
|
9160
9232
|
for (const item of workItems) {
|
|
9161
9233
|
const state2 = classify(item.entry.sha256, item.installSha, item.currentSha, item.entry.tier);
|
|
9162
|
-
|
|
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}`);
|
|
9163
9243
|
count++;
|
|
9164
9244
|
}
|
|
9165
9245
|
stdout(`[dry-run] ${count} files planned. No changes made.`);
|
|
@@ -9171,6 +9251,15 @@ async function upgradeHandler(flags, cli) {
|
|
|
9171
9251
|
const sessionRestartFiles = [];
|
|
9172
9252
|
for (const item of workItems) {
|
|
9173
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
|
+
}
|
|
9174
9263
|
switch (action) {
|
|
9175
9264
|
case "skip": {
|
|
9176
9265
|
stdout(`[skip] ${entry.path} policy=${entry.overwrite_policy}`);
|
|
@@ -9201,8 +9290,17 @@ async function upgradeHandler(flags, cli) {
|
|
|
9201
9290
|
current_sha: postSha,
|
|
9202
9291
|
package_sha: entry.sha256
|
|
9203
9292
|
};
|
|
9204
|
-
if (SESSION_LOAD_PATHS.has(entry.path) &&
|
|
9205
|
-
|
|
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
|
+
}
|
|
9206
9304
|
}
|
|
9207
9305
|
}
|
|
9208
9306
|
await writeDriftState(cwd, driftMap, { lastRefreshed: now.toISOString() });
|