From 17f6f9509031fa1c9ce6724c3c0ca6b1d8c90ad0 Mon Sep 17 00:00:00 2001 From: Sreedhara GS Date: Fri, 27 Mar 2026 16:44:11 +0900 Subject: [PATCH] fix(ck): address Greptile + CodeRabbit review bugs - Fix read-after-write in session-start.mjs: read prevSession BEFORE overwriting current-session.json so unsaved-session detection fires - Fix shell injection in resume.mjs: replace execSync shell string with fs.existsSync for directory existence check - Fix shell injection in shared.mjs gitSummary: replace nested \$(git ...) subshell with a separate runGit() call to get rev count - Fix displayName never shown: render functions now use ctx.displayName ?? ctx.name so user-supplied names show instead of the slug - Fix renderListTable: uses context.displayName ?? entry.name - Fix init.mjs: use path.basename() instead of cwd.split('/').pop() - Fix save.mjs confirmation: show original name, not contextDir slug Co-Authored-By: Claude Sonnet 4.6 --- skills/ck/commands/init.mjs | 4 ++-- skills/ck/commands/resume.mjs | 17 ++++++----------- skills/ck/commands/save.mjs | 2 +- skills/ck/commands/shared.mjs | 15 ++++++++------- skills/ck/hooks/session-start.mjs | 9 ++++++--- 5 files changed, 23 insertions(+), 24 deletions(-) diff --git a/skills/ck/commands/init.mjs b/skills/ck/commands/init.mjs index ef8e647b..fd25bf2d 100644 --- a/skills/ck/commands/init.mjs +++ b/skills/ck/commands/init.mjs @@ -9,7 +9,7 @@ */ import { readFileSync, existsSync } from 'fs'; -import { resolve } from 'path'; +import { resolve, basename } from 'path'; import { readProjects } from './shared.mjs'; const cwd = process.env.PWD || process.cwd(); @@ -137,7 +137,7 @@ if (readme && !output.description) { // ── Name fallback: directory name ───────────────────────────────────────────── if (!output.name) { - output.name = cwd.split('/').pop().toLowerCase().replace(/\s+/g, '-'); + output.name = basename(cwd).toLowerCase().replace(/\s+/g, '-'); } console.log(JSON.stringify(output, null, 2)); diff --git a/skills/ck/commands/resume.mjs b/skills/ck/commands/resume.mjs index b384fcd8..ccb5e313 100644 --- a/skills/ck/commands/resume.mjs +++ b/skills/ck/commands/resume.mjs @@ -8,8 +8,8 @@ * exit 0: success exit 1: not found */ +import { existsSync } from 'fs'; import { resolveContext, renderBriefingBox } from './shared.mjs'; -import { execSync } from 'child_process'; const arg = process.argv[2]; const cwd = process.env.PWD || process.cwd(); @@ -25,16 +25,11 @@ const { context, projectPath } = resolved; // Attempt to cd to the project path if (projectPath && projectPath !== cwd) { - try { - const exists = execSync(`test -d "${projectPath}" && echo yes || echo no`, { - stdio: 'pipe', encoding: 'utf8', timeout: 2000, - }).trim(); - if (exists === 'yes') { - console.log(`→ cd ${projectPath}`); - } else { - console.log(`⚠ Path not found: ${projectPath}`); - } - } catch { /* non-fatal */ } + if (existsSync(projectPath)) { + console.log(`→ cd ${projectPath}`); + } else { + console.log(`⚠ Path not found: ${projectPath}`); + } } console.log(''); diff --git a/skills/ck/commands/save.mjs b/skills/ck/commands/save.mjs index 522e7848..dc60efc4 100644 --- a/skills/ck/commands/save.mjs +++ b/skills/ck/commands/save.mjs @@ -85,7 +85,7 @@ if (isInit) { }; writeProjects(projects); - console.log(`✓ Project '${contextDir}' registered.`); + console.log(`✓ Project '${name}' registered.`); console.log(` Use /ck:save to save session state and /ck:resume to reload it next time.`); process.exit(0); } diff --git a/skills/ck/commands/shared.mjs b/skills/ck/commands/shared.mjs index 248b3cad..73ae6521 100644 --- a/skills/ck/commands/shared.mjs +++ b/skills/ck/commands/shared.mjs @@ -165,9 +165,10 @@ export function gitSummary(projectPath, sinceDate) { const commits = log.split('\n').filter(Boolean).length; if (commits === 0) return null; - // Count unique files changed across those commits - const diff = runGit(`diff --shortstat HEAD@{$(git -C "${projectPath}" rev-list --count HEAD --since="${sinceDate}")}..HEAD 2>/dev/null`, projectPath) - || runGit(`diff --shortstat HEAD~${Math.min(commits, 50)}..HEAD`, projectPath); + // Count unique files changed: use a separate runGit call to avoid nested shell substitution + const countStr = runGit(`rev-list --count HEAD --since="${sinceDate}"`, projectPath); + const revCount = countStr ? parseInt(countStr, 10) : commits; + const diff = runGit(`diff --shortstat HEAD~${Math.min(revCount, 50)}..HEAD`, projectPath); if (diff) { const filesMatch = diff.match(/(\d+) file/); @@ -196,7 +197,7 @@ export function renderContextMd(ctx) { const latest = ctx.sessions?.[ctx.sessions.length - 1] || null; const lines = [ ``, - `# Project: ${ctx.name}`, + `# Project: ${ctx.displayName ?? ctx.name}`, `> Path: ${ctx.path}`, ]; if (ctx.repo) lines.push(`> Repo: ${ctx.repo}`); @@ -282,7 +283,7 @@ export function renderBriefingBox(ctx, meta = {}) { const lines = [ `┌${'─'.repeat(W)}┐`, - `│ RESUMING: ${pad(ctx.name, W - 12)}│`, + `│ RESUMING: ${pad(ctx.displayName ?? ctx.name, W - 12)}│`, `│ Last session: ${pad(`${when} | Sessions: ${sessions}`, W - 16)}│`, ]; if (shortSessId) lines.push(`│ Session ID: ${pad(shortSessId, W - 14)}│`); @@ -318,7 +319,7 @@ export function renderInfoBlock(ctx) { const latest = ctx.sessions?.[ctx.sessions.length - 1] || {}; const sep = '─'.repeat(44); const lines = [ - `ck: ${ctx.name}`, + `ck: ${ctx.displayName ?? ctx.name}`, sep, ]; lines.push(`PATH ${ctx.path}`); @@ -352,7 +353,7 @@ export function renderListTable(entries, cwd, todayStr) { const statusLabel = icon === '●' ? '● Active' : icon === '◐' ? '◐ Warm' : '○ Stale'; const sessId = latest.id ? latest.id.slice(0, 8) : '—'; const summary = (latest.summary || '—').slice(0, 34); - const displayName = (e.name + (isHere ? ' <-' : '')).slice(0, 18); + const displayName = ((e.context?.displayName ?? e.name) + (isHere ? ' <-' : '')).slice(0, 18); return { num: String(i + 1), name: displayName, diff --git a/skills/ck/hooks/session-start.mjs b/skills/ck/hooks/session-start.mjs index e1743d65..10f87f23 100644 --- a/skills/ck/hooks/session-start.mjs +++ b/skills/ck/hooks/session-start.mjs @@ -86,6 +86,9 @@ function main() { const projects = readJson(PROJECTS_FILE) || {}; const entry = projects[cwd]; + // Read previous session BEFORE overwriting current-session.json + const prevSession = readJson(CURRENT_SESSION); + // Write current-session.json try { writeFileSync(CURRENT_SESSION, JSON.stringify({ @@ -108,17 +111,17 @@ function main() { const latest = context.sessions?.[context.sessions.length - 1] || {}; const sessionDate = latest.date || context.createdAt; const sessionCount = context.sessions?.length || 0; + const displayName = context.displayName ?? context.name; // ── Compact summary block (~100 tokens) ────────────────────────────── const summaryLines = [ - `ck: ${context.name} | ${daysAgo(sessionDate)} | ${sessionCount} session${sessionCount !== 1 ? 's' : ''}`, + `ck: ${displayName} | ${daysAgo(sessionDate)} | ${sessionCount} session${sessionCount !== 1 ? 's' : ''}`, `Goal: ${context.goal || '—'}`, latest.leftOff ? `Left off: ${latest.leftOff.split('\n')[0]}` : null, latest.nextSteps?.length ? `Next: ${latest.nextSteps.slice(0, 2).join(' · ')}` : null, ].filter(Boolean); // ── Unsaved session detection ───────────────────────────────────────── - const prevSession = readJson(CURRENT_SESSION); if (prevSession?.sessionId && prevSession.sessionId !== sessionId) { // Check if previous session ID exists in sessions array const alreadySaved = context.sessions?.some(s => s.id === prevSession.sessionId); @@ -141,7 +144,7 @@ function main() { parts.push([ `---`, - `## ck: ${context.name}`, + `## ck: ${displayName}`, ``, summaryLines.join('\n'), ].join('\n'));