fix: rename statusline.js to gsd-statusline.js for consistent hook naming

All GSD hooks now use gsd- prefix. Installer updated to clean up old
statusline.js file and hook registration from previous installs.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Lex Christopherson
2026-01-20 11:55:59 -06:00
parent 9a3a394f79
commit 900fc95efc
6 changed files with 177 additions and 14 deletions

4
.gitignore vendored
View File

@@ -3,9 +3,11 @@ package-lock.json
.DS_Store
TO-DOS.md
CLAUDE.md
.planning
/research.claude/
commands.html
# Local test installs
.claude/
# Build artifacts (committed to npm, not git)
hooks/dist/

View File

@@ -28,6 +28,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Fixed
- Orchestrators now inline file contents in Task prompts (fixes context issues with @ references)
- Tech debt from milestone audit addressed
- All hooks now use `gsd-` prefix for consistency (statusline.js → gsd-statusline.js)
## [1.8.0] - 2026-01-19

View File

@@ -162,6 +162,7 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix) {
function cleanupOrphanedFiles(claudeDir) {
const orphanedFiles = [
'hooks/gsd-notify.sh', // Removed in v1.6.x
'hooks/statusline.js', // Renamed to gsd-statusline.js in v1.9.0
];
for (const relPath of orphanedFiles) {
@@ -179,6 +180,7 @@ function cleanupOrphanedFiles(claudeDir) {
function cleanupOrphanedHooks(settings) {
const orphanedHookPatterns = [
'gsd-notify.sh', // Removed in v1.6.x
'hooks/statusline.js', // Renamed to gsd-statusline.js in v1.9.0
];
let cleaned = false;
@@ -353,19 +355,22 @@ function install(isGlobal) {
failures.push('VERSION');
}
// Copy hooks
const hooksSrc = path.join(src, 'hooks');
// Copy hooks from dist/ (bundled with dependencies)
const hooksSrc = path.join(src, 'hooks', 'dist');
if (fs.existsSync(hooksSrc)) {
const hooksDest = path.join(claudeDir, 'hooks');
fs.mkdirSync(hooksDest, { recursive: true });
const hookEntries = fs.readdirSync(hooksSrc);
for (const entry of hookEntries) {
const srcFile = path.join(hooksSrc, entry);
const destFile = path.join(hooksDest, entry);
fs.copyFileSync(srcFile, destFile);
// Only copy files, not directories
if (fs.statSync(srcFile).isFile()) {
const destFile = path.join(hooksDest, entry);
fs.copyFileSync(srcFile, destFile);
}
}
if (verifyInstalled(hooksDest, 'hooks')) {
console.log(` ${green}✓${reset} Installed hooks`);
console.log(` ${green}✓${reset} Installed hooks (bundled)`);
} else {
failures.push('hooks');
}
@@ -382,8 +387,8 @@ function install(isGlobal) {
const settingsPath = path.join(claudeDir, 'settings.json');
const settings = cleanupOrphanedHooks(readSettings(settingsPath));
const statuslineCommand = isGlobal
? 'node "$HOME/.claude/hooks/statusline.js"'
: 'node .claude/hooks/statusline.js';
? 'node "$HOME/.claude/hooks/gsd-statusline.js"'
: 'node .claude/hooks/gsd-statusline.js';
const updateCheckCommand = isGlobal
? 'node "$HOME/.claude/hooks/gsd-check-update.js"'
: 'node .claude/hooks/gsd-check-update.js';

View File

@@ -25,7 +25,9 @@
const fs = require('fs');
const path = require('path');
const initSqlJs = require('sql.js');
const { execSync } = require('child_process');
// Use ASM.js version for bundling (no WASM file dependency)
const initSqlJs = require('sql.js/dist/sql-asm.js');
// Graph database schema (simple-graph pattern)
const GRAPH_SCHEMA = `
@@ -609,6 +611,121 @@ function extractPurpose(content) {
return null;
}
/**
* Generate entity slug from file path
* e.g., 'src/lib/db.ts' -> 'src-lib-db'
*/
function generateSlug(filePath) {
return filePath
.replace(/^\/+/, '') // Remove leading slashes
.replace(/\.[^.]+$/, '') // Remove extension
.replace(/[\/\\]/g, '-') // Replace path separators with hyphens
.replace(/[^a-zA-Z0-9-]/g, '-') // Replace non-alphanumeric with hyphens
.replace(/-+/g, '-') // Collapse multiple hyphens
.replace(/^-|-$/g, '') // Remove leading/trailing hyphens
.toLowerCase();
}
/**
* Check if signature (exports/imports) changed
* Returns true if entity should be regenerated
*/
function signatureChanged(prevEntry, exports, imports) {
if (!prevEntry) return true; // New file
const prevExports = JSON.stringify(prevEntry.exports || []);
const prevImports = JSON.stringify(prevEntry.imports || []);
const newExports = JSON.stringify(exports);
const newImports = JSON.stringify(imports);
return prevExports !== newExports || prevImports !== newImports;
}
/**
* Generate semantic entity file using Claude
* Spawns `claude -p` to analyze file and generate entity markdown
*
* @param {string} filePath - Path to the code file
* @param {string} content - File content
* @param {Array} exports - Extracted exports
* @param {Array} imports - Extracted imports
*/
async function generateEntity(filePath, content, exports, imports) {
const intelDir = path.join(process.cwd(), '.planning', 'intel');
const entitiesDir = path.join(intelDir, 'entities');
// Ensure entities directory exists
if (!fs.existsSync(entitiesDir)) {
fs.mkdirSync(entitiesDir, { recursive: true });
}
const slug = generateSlug(filePath);
const entityPath = path.join(entitiesDir, `${slug}.md`);
const today = new Date().toISOString().split('T')[0];
// Build prompt for Claude
const prompt = `Analyze this code file and generate ONLY the entity markdown (no explanation, no code fences).
Path: ${filePath}
Exports: ${exports.join(', ') || 'none'}
Imports: ${imports.join(', ') || 'none'}
Content:
${content.slice(0, 3000)}${content.length > 3000 ? '\n... (truncated)' : ''}
Output this EXACT format (fill in the brackets):
---
path: ${filePath}
type: [module|component|util|config|api|hook|service|model|test]
updated: ${today}
status: active
---
# ${path.basename(filePath)}
## Purpose
[1-2 sentences: What does this file do? Why does it exist?]
## Exports
[List each export with brief description, or "None" if no exports]
## Dependencies
[List internal deps as [[slug]] wiki-links, external as plain text, or "None"]
## Used By
TBD
## Notes
[Optional: any important patterns or gotchas, or remove this section]`;
try {
// Spawn claude -p with timeout
const cmd = `claude -p "${prompt.replace(/"/g, '\\"').replace(/\n/g, '\\n')}"`;
const result = execSync(cmd, {
encoding: 'utf8',
timeout: 30000, // 30 second timeout
maxBuffer: 1024 * 1024 // 1MB buffer
});
// Write entity file
fs.writeFileSync(entityPath, result.trim());
// Sync to graph
await syncEntityToGraph(entityPath);
return entityPath;
} catch (e) {
// Silent failure - don't block on entity generation errors
return null;
}
}
/**
* Generate semantic summary from graph database
* Target: < 500 tokens for context injection
@@ -1057,9 +1174,41 @@ process.stdin.on('end', () => {
const exports = extractExports(content);
const imports = extractImports(content);
// Update index
// Check if signature changed (triggers entity regeneration)
const intelDir = path.join(process.cwd(), '.planning', 'intel');
const indexPath = path.join(intelDir, 'index.json');
let prevEntry = null;
if (fs.existsSync(indexPath)) {
try {
const indexContent = fs.readFileSync(indexPath, 'utf8');
const index = JSON.parse(indexContent);
const normalizedPath = path.resolve(filePath);
prevEntry = index.files?.[normalizedPath];
} catch (e) {
// Ignore read errors
}
}
// Update index first (always)
updateIndex(filePath, exports, imports);
// Generate entity if signature changed
if (fs.existsSync(intelDir) && signatureChanged(prevEntry, exports, imports)) {
generateEntity(filePath, content, exports, imports)
.then(async (entityPath) => {
if (entityPath) {
// Regenerate summary after new entity
await regenerateEntitySummary();
}
process.exit(0);
})
.catch(() => {
process.exit(0);
});
return; // Wait for async
}
process.exit(0);
} catch (error) {
// Silent failure - never block Claude

View File

@@ -10,7 +10,8 @@
"commands",
"get-shit-done",
"agents",
"hooks"
"hooks/dist",
"scripts"
],
"keywords": [
"claude",
@@ -33,8 +34,13 @@
"engines": {
"node": ">=16.7.0"
},
"dependencies": {
"sql.js": "^1.12.0",
"@anthropic-ai/sdk": "^0.52.0"
"dependencies": {},
"devDependencies": {
"esbuild": "^0.24.0",
"sql.js": "^1.12.0"
},
"scripts": {
"build:hooks": "node scripts/build-hooks.js",
"prepublishOnly": "npm run build:hooks"
}
}