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:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -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/
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
|
||||
14
package.json
14
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user