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
|
.DS_Store
|
||||||
TO-DOS.md
|
TO-DOS.md
|
||||||
CLAUDE.md
|
CLAUDE.md
|
||||||
.planning
|
|
||||||
/research.claude/
|
/research.claude/
|
||||||
commands.html
|
commands.html
|
||||||
|
|
||||||
# Local test installs
|
# Local test installs
|
||||||
.claude/
|
.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
|
### Fixed
|
||||||
- Orchestrators now inline file contents in Task prompts (fixes context issues with @ references)
|
- Orchestrators now inline file contents in Task prompts (fixes context issues with @ references)
|
||||||
- Tech debt from milestone audit addressed
|
- 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
|
## [1.8.0] - 2026-01-19
|
||||||
|
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ function copyWithPathReplacement(srcDir, destDir, pathPrefix) {
|
|||||||
function cleanupOrphanedFiles(claudeDir) {
|
function cleanupOrphanedFiles(claudeDir) {
|
||||||
const orphanedFiles = [
|
const orphanedFiles = [
|
||||||
'hooks/gsd-notify.sh', // Removed in v1.6.x
|
'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) {
|
for (const relPath of orphanedFiles) {
|
||||||
@@ -179,6 +180,7 @@ function cleanupOrphanedFiles(claudeDir) {
|
|||||||
function cleanupOrphanedHooks(settings) {
|
function cleanupOrphanedHooks(settings) {
|
||||||
const orphanedHookPatterns = [
|
const orphanedHookPatterns = [
|
||||||
'gsd-notify.sh', // Removed in v1.6.x
|
'gsd-notify.sh', // Removed in v1.6.x
|
||||||
|
'hooks/statusline.js', // Renamed to gsd-statusline.js in v1.9.0
|
||||||
];
|
];
|
||||||
|
|
||||||
let cleaned = false;
|
let cleaned = false;
|
||||||
@@ -353,19 +355,22 @@ function install(isGlobal) {
|
|||||||
failures.push('VERSION');
|
failures.push('VERSION');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy hooks
|
// Copy hooks from dist/ (bundled with dependencies)
|
||||||
const hooksSrc = path.join(src, 'hooks');
|
const hooksSrc = path.join(src, 'hooks', 'dist');
|
||||||
if (fs.existsSync(hooksSrc)) {
|
if (fs.existsSync(hooksSrc)) {
|
||||||
const hooksDest = path.join(claudeDir, 'hooks');
|
const hooksDest = path.join(claudeDir, 'hooks');
|
||||||
fs.mkdirSync(hooksDest, { recursive: true });
|
fs.mkdirSync(hooksDest, { recursive: true });
|
||||||
const hookEntries = fs.readdirSync(hooksSrc);
|
const hookEntries = fs.readdirSync(hooksSrc);
|
||||||
for (const entry of hookEntries) {
|
for (const entry of hookEntries) {
|
||||||
const srcFile = path.join(hooksSrc, entry);
|
const srcFile = path.join(hooksSrc, entry);
|
||||||
const destFile = path.join(hooksDest, entry);
|
// Only copy files, not directories
|
||||||
fs.copyFileSync(srcFile, destFile);
|
if (fs.statSync(srcFile).isFile()) {
|
||||||
|
const destFile = path.join(hooksDest, entry);
|
||||||
|
fs.copyFileSync(srcFile, destFile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (verifyInstalled(hooksDest, 'hooks')) {
|
if (verifyInstalled(hooksDest, 'hooks')) {
|
||||||
console.log(` ${green}✓${reset} Installed hooks`);
|
console.log(` ${green}✓${reset} Installed hooks (bundled)`);
|
||||||
} else {
|
} else {
|
||||||
failures.push('hooks');
|
failures.push('hooks');
|
||||||
}
|
}
|
||||||
@@ -382,8 +387,8 @@ function install(isGlobal) {
|
|||||||
const settingsPath = path.join(claudeDir, 'settings.json');
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
||||||
const settings = cleanupOrphanedHooks(readSettings(settingsPath));
|
const settings = cleanupOrphanedHooks(readSettings(settingsPath));
|
||||||
const statuslineCommand = isGlobal
|
const statuslineCommand = isGlobal
|
||||||
? 'node "$HOME/.claude/hooks/statusline.js"'
|
? 'node "$HOME/.claude/hooks/gsd-statusline.js"'
|
||||||
: 'node .claude/hooks/statusline.js';
|
: 'node .claude/hooks/gsd-statusline.js';
|
||||||
const updateCheckCommand = isGlobal
|
const updateCheckCommand = isGlobal
|
||||||
? 'node "$HOME/.claude/hooks/gsd-check-update.js"'
|
? 'node "$HOME/.claude/hooks/gsd-check-update.js"'
|
||||||
: 'node .claude/hooks/gsd-check-update.js';
|
: 'node .claude/hooks/gsd-check-update.js';
|
||||||
|
|||||||
@@ -25,7 +25,9 @@
|
|||||||
|
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
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)
|
// Graph database schema (simple-graph pattern)
|
||||||
const GRAPH_SCHEMA = `
|
const GRAPH_SCHEMA = `
|
||||||
@@ -609,6 +611,121 @@ function extractPurpose(content) {
|
|||||||
return null;
|
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
|
* Generate semantic summary from graph database
|
||||||
* Target: < 500 tokens for context injection
|
* Target: < 500 tokens for context injection
|
||||||
@@ -1057,9 +1174,41 @@ process.stdin.on('end', () => {
|
|||||||
const exports = extractExports(content);
|
const exports = extractExports(content);
|
||||||
const imports = extractImports(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);
|
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);
|
process.exit(0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Silent failure - never block Claude
|
// Silent failure - never block Claude
|
||||||
|
|||||||
14
package.json
14
package.json
@@ -10,7 +10,8 @@
|
|||||||
"commands",
|
"commands",
|
||||||
"get-shit-done",
|
"get-shit-done",
|
||||||
"agents",
|
"agents",
|
||||||
"hooks"
|
"hooks/dist",
|
||||||
|
"scripts"
|
||||||
],
|
],
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"claude",
|
"claude",
|
||||||
@@ -33,8 +34,13 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.7.0"
|
"node": ">=16.7.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {},
|
||||||
"sql.js": "^1.12.0",
|
"devDependencies": {
|
||||||
"@anthropic-ai/sdk": "^0.52.0"
|
"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