@stackmemoryai/stackmemory 0.5.66 → 0.6.0

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.
Files changed (58) hide show
  1. package/README.md +139 -45
  2. package/bin/codex-sm +6 -0
  3. package/bin/opencode-sm +1 -1
  4. package/dist/src/cli/claude-sm.js +162 -25
  5. package/dist/src/cli/claude-sm.js.map +2 -2
  6. package/dist/src/cli/commands/ping.js +14 -0
  7. package/dist/src/cli/commands/ping.js.map +7 -0
  8. package/dist/src/cli/commands/ralph.js +103 -1
  9. package/dist/src/cli/commands/ralph.js.map +2 -2
  10. package/dist/src/cli/commands/retrieval.js +1 -1
  11. package/dist/src/cli/commands/retrieval.js.map +2 -2
  12. package/dist/src/cli/commands/skills.js +201 -6
  13. package/dist/src/cli/commands/skills.js.map +2 -2
  14. package/dist/src/cli/index.js +66 -27
  15. package/dist/src/cli/index.js.map +2 -2
  16. package/dist/src/core/digest/types.js +1 -1
  17. package/dist/src/core/digest/types.js.map +1 -1
  18. package/dist/src/core/extensions/provider-adapter.js +2 -5
  19. package/dist/src/core/extensions/provider-adapter.js.map +2 -2
  20. package/dist/src/core/retrieval/llm-provider.js +2 -2
  21. package/dist/src/core/retrieval/llm-provider.js.map +1 -1
  22. package/dist/src/core/retrieval/types.js +1 -1
  23. package/dist/src/core/retrieval/types.js.map +1 -1
  24. package/dist/src/features/sweep/pty-wrapper.js +15 -5
  25. package/dist/src/features/sweep/pty-wrapper.js.map +2 -2
  26. package/dist/src/features/workers/tmux-manager.js +71 -0
  27. package/dist/src/features/workers/tmux-manager.js.map +7 -0
  28. package/dist/src/features/workers/worker-registry.js +52 -0
  29. package/dist/src/features/workers/worker-registry.js.map +7 -0
  30. package/dist/src/integrations/linear/webhook-handler.js +82 -0
  31. package/dist/src/integrations/linear/webhook-handler.js.map +2 -2
  32. package/dist/src/integrations/mcp/server.js +16 -10
  33. package/dist/src/integrations/mcp/server.js.map +2 -2
  34. package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js +2 -2
  35. package/dist/src/integrations/ralph/patterns/oracle-worker-pattern.js.map +2 -2
  36. package/dist/src/orchestrators/multimodal/constants.js +1 -1
  37. package/dist/src/orchestrators/multimodal/constants.js.map +1 -1
  38. package/dist/src/orchestrators/multimodal/harness.js +28 -29
  39. package/dist/src/orchestrators/multimodal/harness.js.map +2 -2
  40. package/dist/src/orchestrators/multimodal/providers.js +35 -22
  41. package/dist/src/orchestrators/multimodal/providers.js.map +2 -2
  42. package/dist/src/skills/claude-skills.js +116 -1
  43. package/dist/src/skills/claude-skills.js.map +2 -2
  44. package/dist/src/skills/linear-task-runner.js +262 -0
  45. package/dist/src/skills/linear-task-runner.js.map +7 -0
  46. package/dist/src/skills/recursive-agent-orchestrator.js +114 -85
  47. package/dist/src/skills/recursive-agent-orchestrator.js.map +2 -2
  48. package/dist/src/skills/spec-generator-skill.js +441 -0
  49. package/dist/src/skills/spec-generator-skill.js.map +7 -0
  50. package/package.json +12 -5
  51. package/scripts/install-claude-hooks-auto.js +23 -9
  52. package/scripts/install-claude-hooks.sh +2 -2
  53. package/templates/claude-hooks/hooks.json +4 -2
  54. package/templates/claude-hooks/on-task-complete.js +91 -0
  55. package/templates/claude-hooks/post-edit-sweep.js +7 -10
  56. package/templates/claude-hooks/skill-eval.cjs +411 -0
  57. package/templates/claude-hooks/skill-eval.sh +31 -0
  58. package/templates/claude-hooks/skill-rules.json +274 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/skills/spec-generator-skill.ts"],
4
+ "sourcesContent": ["/**\n * Spec Generator Skill\n * Generates iterative spec documents: ONE_PAGER \u2192 DEV_SPEC \u2192 PROMPT_PLAN \u2192 AGENTS.md\n * Progressive context \u2014 later docs read earlier ones from disk.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { SkillContext, SkillResult } from './claude-skills.js';\nimport { logger } from '../core/monitoring/logger.js';\n\nexport type SpecType = 'one-pager' | 'dev-spec' | 'prompt-plan' | 'agents';\n\ninterface SpecConfig {\n filename: string;\n title: string;\n sections: string[];\n inputs: SpecType[]; // earlier docs this type reads\n}\n\nconst SPEC_DIR = 'docs/specs';\n\nconst SPEC_CONFIGS: Record<SpecType, SpecConfig> = {\n 'one-pager': {\n filename: 'ONE_PAGER.md',\n title: 'One-Pager',\n sections: [\n 'Problem',\n 'Audience',\n 'Platform',\n 'Core Flow',\n 'MVP Features',\n 'Non-Goals',\n 'Metrics',\n ],\n inputs: [],\n },\n 'dev-spec': {\n filename: 'DEV_SPEC.md',\n title: 'Development Specification',\n sections: [\n 'Architecture',\n 'Tech Stack',\n 'API Contracts',\n 'Data Models',\n 'Auth',\n 'Error Handling',\n 'Deployment',\n ],\n inputs: ['one-pager'],\n },\n 'prompt-plan': {\n filename: 'PROMPT_PLAN.md',\n title: 'Prompt Plan',\n sections: [\n 'Stage A: Project Setup',\n 'Stage B: Core Data Models',\n 'Stage C: API Layer',\n 'Stage D: Business Logic',\n 'Stage E: Frontend / UI',\n 'Stage F: Integration & Testing',\n 'Stage G: Deploy & Polish',\n ],\n inputs: ['one-pager', 'dev-spec'],\n },\n agents: {\n filename: 'AGENTS.md',\n title: 'AGENTS.md',\n sections: [\n 'Repo Files',\n 'Responsibilities',\n 'Guardrails',\n 'Testing',\n 'When to Ask',\n ],\n inputs: ['one-pager', 'dev-spec', 'prompt-plan'],\n },\n};\n\n// --- Templates ---\n\nfunction onePagerTemplate(title: string): string {\n return `# ${title} \u2014 One-Pager\n\n## Problem\n<!-- What problem does this solve? Who has this problem? -->\n\n## Audience\n<!-- Primary users and their context -->\n\n## Platform\n<!-- Web / Mobile / CLI / API \u2014 and why -->\n\n## Core Flow\n<!-- Happy-path user journey in 3-5 steps -->\n1.\n2.\n3.\n\n## MVP Features\n<!-- Minimum set of features for first release -->\n- [ ]\n- [ ]\n- [ ]\n\n## Non-Goals\n<!-- Explicitly out of scope for MVP -->\n-\n\n## Metrics\n<!-- How will you measure success? -->\n-\n`;\n}\n\nfunction devSpecTemplate(title: string, onePagerContent: string): string {\n return `# ${title} \u2014 Development Specification\n\n> Generated from ONE_PAGER.md\n\n<details><summary>Source: ONE_PAGER.md</summary>\n\n${onePagerContent}\n\n</details>\n\n## Architecture\n<!-- High-level system diagram / component breakdown -->\n\n## Tech Stack\n<!-- Languages, frameworks, databases, infra -->\n| Layer | Choice | Rationale |\n|-------|--------|-----------|\n| Frontend | | |\n| Backend | | |\n| Database | | |\n| Hosting | | |\n\n## API Contracts\n<!-- Key endpoints with request/response shapes -->\n\n## Data Models\n<!-- Core entities and relationships -->\n\n## Auth\n<!-- Authentication and authorization strategy -->\n\n## Error Handling\n<!-- Error codes, retry strategies, user-facing messages -->\n\n## Deployment\n<!-- CI/CD, environments, rollback strategy -->\n`;\n}\n\nfunction promptPlanTemplate(\n title: string,\n onePagerContent: string,\n devSpecContent: string\n): string {\n return `# ${title} \u2014 Prompt Plan\n\n> Generated from ONE_PAGER.md and DEV_SPEC.md\n> Each stage has TDD checkboxes \u2014 check off as tasks complete.\n\n<details><summary>Source: ONE_PAGER.md</summary>\n\n${onePagerContent}\n\n</details>\n\n<details><summary>Source: DEV_SPEC.md</summary>\n\n${devSpecContent}\n\n</details>\n\n## Stage A: Project Setup\n- [ ] Initialize repository and tooling\n- [ ] Configure CI/CD pipeline\n- [ ] Set up development environment\n\n## Stage B: Core Data Models\n- [ ] Define database schema\n- [ ] Create model layer\n- [ ] Write model tests\n\n## Stage C: API Layer\n- [ ] Implement API endpoints\n- [ ] Add input validation\n- [ ] Write API tests\n\n## Stage D: Business Logic\n- [ ] Implement core business rules\n- [ ] Add edge case handling\n- [ ] Write integration tests\n\n## Stage E: Frontend / UI\n- [ ] Build core UI components\n- [ ] Implement user flows\n- [ ] Write UI tests\n\n## Stage F: Integration & Testing\n- [ ] End-to-end test suite\n- [ ] Performance testing\n- [ ] Security audit\n\n## Stage G: Deploy & Polish\n- [ ] Production deployment\n- [ ] Monitoring and alerting\n- [ ] Documentation\n`;\n}\n\nfunction agentsTemplate(title: string, inputs: Record<string, string>): string {\n const sourceBlocks = Object.entries(inputs)\n .map(\n ([name, content]) =>\n `<details><summary>Source: ${name}</summary>\\n\\n${content}\\n\\n</details>`\n )\n .join('\\n\\n');\n\n return `# ${title} \u2014 AGENTS.md\n\n> Auto-generated agent configuration for Claude Code / Cursor / Windsurf.\n\n${sourceBlocks}\n\n## Repo Files\n<!-- Key files and their purpose -->\n| File | Purpose |\n|------|---------|\n| | |\n\n## Responsibilities\n<!-- What this agent should and shouldn't do -->\n### DO\n-\n\n### DON'T\n-\n\n## Guardrails\n<!-- Safety constraints and limits -->\n- Never commit secrets or credentials\n- Always run tests before committing\n- Keep changes focused and atomic\n\n## Testing\n<!-- How to validate changes -->\n\\`\\`\\`bash\nnpm test\nnpm run lint\nnpm run build\n\\`\\`\\`\n\n## When to Ask\n<!-- Situations where the agent should ask for human input -->\n- Architectural changes affecting multiple systems\n- Security-sensitive modifications\n- Breaking API changes\n- Ambiguous requirements\n`;\n}\n\n// --- Skill Class ---\n\nexport class SpecGeneratorSkill {\n private baseDir: string;\n\n constructor(private context: SkillContext) {\n this.baseDir = process.cwd();\n }\n\n /** Generate a spec document by type */\n async generate(\n type: SpecType,\n title: string,\n opts?: { force?: boolean }\n ): Promise<SkillResult> {\n const config = SPEC_CONFIGS[type];\n if (!config) {\n return { success: false, message: `Unknown spec type: ${type}` };\n }\n\n const specDir = path.join(this.baseDir, SPEC_DIR);\n const outputPath = path.join(specDir, config.filename);\n\n // Check if file already exists\n if (fs.existsSync(outputPath) && !opts?.force) {\n return {\n success: false,\n message: `${config.filename} already exists. Use --force to overwrite.`,\n data: { path: outputPath },\n };\n }\n\n // Load input documents (progressive context)\n const inputContents: Record<string, string> = {};\n for (const inputType of config.inputs) {\n const inputConfig = SPEC_CONFIGS[inputType];\n const inputPath = path.join(specDir, inputConfig.filename);\n if (fs.existsSync(inputPath)) {\n inputContents[inputConfig.filename] = fs.readFileSync(\n inputPath,\n 'utf-8'\n );\n }\n }\n\n // Generate content from template\n const content = this.renderTemplate(type, title, inputContents);\n\n // Write file\n fs.mkdirSync(specDir, { recursive: true });\n fs.writeFileSync(outputPath, content, 'utf-8');\n\n logger.info(`Generated spec: ${config.filename}`, { type, title });\n\n return {\n success: true,\n message: `Created ${config.filename}`,\n data: {\n path: outputPath,\n type,\n sections: config.sections,\n inputsUsed: Object.keys(inputContents),\n },\n action: `Generated ${SPEC_DIR}/${config.filename}`,\n };\n }\n\n /** List existing spec documents */\n async list(): Promise<SkillResult> {\n const specDir = path.join(this.baseDir, SPEC_DIR);\n const specs: Array<{\n type: SpecType;\n filename: string;\n exists: boolean;\n path: string;\n }> = [];\n\n for (const [type, config] of Object.entries(SPEC_CONFIGS)) {\n const filePath = path.join(specDir, config.filename);\n specs.push({\n type: type as SpecType,\n filename: config.filename,\n exists: fs.existsSync(filePath),\n path: filePath,\n });\n }\n\n const existing = specs.filter((s) => s.exists);\n const missing = specs.filter((s) => !s.exists);\n\n return {\n success: true,\n message: `${existing.length}/${specs.length} specs exist`,\n data: { specs, existing, missing },\n };\n }\n\n /** Update a spec \u2014 primarily for checking off PROMPT_PLAN items */\n async update(filePath: string, changes: string): Promise<SkillResult> {\n const resolvedPath = path.isAbsolute(filePath)\n ? filePath\n : path.join(this.baseDir, filePath);\n\n if (!fs.existsSync(resolvedPath)) {\n return { success: false, message: `File not found: ${resolvedPath}` };\n }\n\n let content = fs.readFileSync(resolvedPath, 'utf-8');\n\n // Parse checkbox updates: \"Stage A:1\" means check item 1 in Stage A\n const checkboxPattern = /^(Stage [A-G])(?::(\\d+))?$/;\n const match = changes.match(checkboxPattern);\n\n if (match) {\n const [, stageName, itemNum] = match;\n content = this.checkItem(\n content,\n stageName,\n itemNum ? parseInt(itemNum) : undefined\n );\n } else {\n // Treat as direct checkbox text match: find \"- [ ] <changes>\" and check it\n const escaped = changes.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const re = new RegExp(`- \\\\[ \\\\] ${escaped}`, 'i');\n if (re.test(content)) {\n content = content.replace(re, `- [x] ${changes}`);\n } else {\n return {\n success: false,\n message: `No unchecked item matching \"${changes}\" found`,\n };\n }\n }\n\n // Append changelog entry\n const timestamp = new Date().toISOString().slice(0, 19).replace('T', ' ');\n const changelog = `\\n<!-- Updated: ${timestamp} | ${changes} -->\\n`;\n content += changelog;\n\n fs.writeFileSync(resolvedPath, content, 'utf-8');\n\n logger.info('Updated spec', { path: resolvedPath, changes });\n\n return {\n success: true,\n message: `Updated: ${changes}`,\n data: { path: resolvedPath, changes },\n action: `Checked off item in ${path.basename(resolvedPath)}`,\n };\n }\n\n /** Validate completeness of a spec */\n async validate(filePath: string): Promise<SkillResult> {\n const resolvedPath = path.isAbsolute(filePath)\n ? filePath\n : path.join(this.baseDir, filePath);\n\n if (!fs.existsSync(resolvedPath)) {\n return { success: false, message: `File not found: ${resolvedPath}` };\n }\n\n const content = fs.readFileSync(resolvedPath, 'utf-8');\n\n // Count checkboxes\n const unchecked = (content.match(/- \\[ \\]/g) || []).length;\n const checked = (content.match(/- \\[x\\]/gi) || []).length;\n const total = checked + unchecked;\n\n // Check for empty sections (## header followed by only whitespace/comments)\n const emptySections: string[] = [];\n const sectionRegex = /^## (.+)$/gm;\n let sectionMatch;\n\n while ((sectionMatch = sectionRegex.exec(content)) !== null) {\n const sectionName = sectionMatch[1];\n const sectionStart = content.indexOf(sectionMatch[0]);\n const nextSection = content.indexOf('\\n## ', sectionStart + 1);\n const sectionContent =\n nextSection === -1\n ? content.slice(sectionStart + sectionMatch[0].length)\n : content.slice(sectionStart + sectionMatch[0].length, nextSection);\n\n // Strip comments and whitespace\n const stripped = sectionContent\n .replace(/<!--.*?-->/gs, '')\n .replace(/\\s+/g, '')\n .trim();\n\n if (stripped.length === 0 || stripped === '||') {\n emptySections.push(sectionName);\n }\n }\n\n const isComplete = unchecked === 0 && emptySections.length === 0;\n\n return {\n success: true,\n message: isComplete\n ? 'Spec is complete'\n : `Spec incomplete: ${unchecked} unchecked items, ${emptySections.length} empty sections`,\n data: {\n path: resolvedPath,\n checkboxes: { checked, unchecked, total },\n emptySections,\n isComplete,\n completionPercent:\n total > 0 ? Math.round((checked / total) * 100) : 100,\n },\n };\n }\n\n // --- Private helpers ---\n\n private renderTemplate(\n type: SpecType,\n title: string,\n inputs: Record<string, string>\n ): string {\n switch (type) {\n case 'one-pager':\n return onePagerTemplate(title);\n\n case 'dev-spec':\n return devSpecTemplate(\n title,\n inputs['ONE_PAGER.md'] ||\n '*ONE_PAGER.md not found \u2014 generate it first.*'\n );\n\n case 'prompt-plan':\n return promptPlanTemplate(\n title,\n inputs['ONE_PAGER.md'] || '*ONE_PAGER.md not found*',\n inputs['DEV_SPEC.md'] ||\n '*DEV_SPEC.md not found \u2014 generate it first.*'\n );\n\n case 'agents':\n return agentsTemplate(title, inputs);\n\n default:\n return `# ${title}\\n\\nUnknown spec type: ${type}\\n`;\n }\n }\n\n /** Check off a specific checkbox item in a stage */\n private checkItem(\n content: string,\n stageName: string,\n itemIndex?: number\n ): string {\n const lines = content.split('\\n');\n let inStage = false;\n let itemCount = 0;\n\n for (let i = 0; i < lines.length; i++) {\n // Detect stage header\n if (lines[i].startsWith('## ') && lines[i].includes(stageName)) {\n inStage = true;\n itemCount = 0;\n continue;\n }\n\n // Exit stage on next header\n if (inStage && lines[i].startsWith('## ')) {\n break;\n }\n\n // Process checkbox\n if (inStage && lines[i].match(/^- \\[ \\]/)) {\n itemCount++;\n if (itemIndex === undefined || itemCount === itemIndex) {\n lines[i] = lines[i].replace('- [ ]', '- [x]');\n if (itemIndex !== undefined) break;\n }\n }\n }\n\n return lines.join('\\n');\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stackmemoryai/stackmemory",
3
- "version": "0.5.66",
4
- "description": "Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, and smart retrieval.",
3
+ "version": "0.6.0",
4
+ "description": "Project-scoped memory for AI coding tools. Durable context across sessions with MCP integration, frames, smart retrieval, Claude Code skills, and automatic hooks.",
5
5
  "engines": {
6
6
  "node": ">=20.0.0",
7
7
  "npm": ">=10.0.0"
@@ -18,6 +18,7 @@
18
18
  },
19
19
  "types": "dist/src/index.d.ts",
20
20
  "files": [
21
+ "bin",
21
22
  "dist",
22
23
  "scripts",
23
24
  "templates",
@@ -41,12 +42,17 @@
41
42
  "llm",
42
43
  "mcp",
43
44
  "claude",
45
+ "claude-code",
44
46
  "coding",
45
47
  "persistence",
46
- "code-review",
47
- "predictive-edit",
48
+ "skills",
49
+ "hooks",
50
+ "spec-generator",
51
+ "linear",
52
+ "task-runner",
48
53
  "prompt-optimization",
49
- "model-routing"
54
+ "model-routing",
55
+ "agent-orchestration"
50
56
  ],
51
57
  "scripts": {
52
58
  "start": "node dist/src/integrations/mcp/server.js",
@@ -118,6 +124,7 @@
118
124
  "inquirer": "^9.3.8",
119
125
  "msgpackr": "^1.10.1",
120
126
  "ngrok": "^5.0.0-beta.2",
127
+ "node-pty": "^1.1.0",
121
128
  "open": "^11.0.0",
122
129
  "ora": "^9.0.0",
123
130
  "pg": "^8.17.1",
@@ -58,6 +58,8 @@ async function askForConsent() {
58
58
  console.log(' - Track tool usage for better context');
59
59
  console.log(' - Enable session persistence across restarts');
60
60
  console.log(' - Sync context with Linear (optional)');
61
+ console.log(' - Auto-update PROMPT_PLAN on task completion');
62
+ console.log(' - Recommend relevant skills based on your prompts');
61
63
  console.log('\nThis will modify files in ~/.claude/\n');
62
64
 
63
65
  return new Promise((resolve) => {
@@ -89,11 +91,19 @@ async function installClaudeHooks() {
89
91
  console.log('Created ~/.claude/hooks directory');
90
92
  }
91
93
 
92
- // Copy hook files
93
- const hookFiles = ['tool-use-trace.js', 'on-startup.js', 'on-clear.js'];
94
+ // Copy hook files (scripts + data files)
95
+ const hookFiles = [
96
+ 'tool-use-trace.js',
97
+ 'on-startup.js',
98
+ 'on-clear.js',
99
+ 'on-task-complete.js',
100
+ 'skill-eval.sh',
101
+ 'skill-eval.cjs',
102
+ ];
103
+ const dataFiles = ['skill-rules.json'];
94
104
  let installed = 0;
95
105
 
96
- for (const hookFile of hookFiles) {
106
+ for (const hookFile of [...hookFiles, ...dataFiles]) {
97
107
  const srcPath = join(templatesDir, hookFile);
98
108
  const destPath = join(claudeHooksDir, hookFile);
99
109
 
@@ -107,12 +117,14 @@ async function installClaudeHooks() {
107
117
 
108
118
  copyFileSync(srcPath, destPath);
109
119
 
110
- // Make executable
111
- try {
112
- const { execSync } = await import('child_process');
113
- execSync(`chmod +x "${destPath}"`, { stdio: 'ignore' });
114
- } catch {
115
- // Silent fail on chmod
120
+ // Make executable (scripts only, not data files)
121
+ if (!dataFiles.includes(hookFile)) {
122
+ try {
123
+ const { execSync } = await import('child_process');
124
+ execSync(`chmod +x "${destPath}"`, { stdio: 'ignore' });
125
+ } catch {
126
+ // Silent fail on chmod
127
+ }
116
128
  }
117
129
 
118
130
  installed++;
@@ -136,6 +148,8 @@ async function installClaudeHooks() {
136
148
  'tool-use-approval': join(claudeHooksDir, 'tool-use-trace.js'),
137
149
  'on-startup': join(claudeHooksDir, 'on-startup.js'),
138
150
  'on-clear': join(claudeHooksDir, 'on-clear.js'),
151
+ 'on-task-complete': join(claudeHooksDir, 'on-task-complete.js'),
152
+ 'user-prompt-submit': join(claudeHooksDir, 'skill-eval.sh'),
139
153
  };
140
154
 
141
155
  writeFileSync(claudeConfigFile, JSON.stringify(newHooksConfig, null, 2));
@@ -99,7 +99,7 @@ MAX_FILE_SIZE=100000 # Max size per file (100KB)
99
99
  MAX_TOTAL_SIZE=500000 # Max total size (500KB)
100
100
 
101
101
  # Claude CLI settings
102
- CLAUDE_MODEL=claude-3-opus # Model to use
102
+ CLAUDE_MODEL=claude-sonnet-4-20250514 # Model to use
103
103
  CLAUDE_MAX_TOKENS=4000 # Max tokens per request
104
104
 
105
105
  # File patterns to check (regex)
@@ -130,4 +130,4 @@ echo ""
130
130
  echo "Environment variables you can set:"
131
131
  echo " CLAUDE_AUTO_FIX=true # Auto-apply suggested fixes"
132
132
  echo " CLAUDE_REVIEW_ENABLED=false # Skip code review"
133
- echo " CLAUDE_TEST_ENABLED=false # Skip test generation"
133
+ echo " CLAUDE_TEST_ENABLED=false # Skip test generation"
@@ -1,5 +1,7 @@
1
1
  {
2
2
  "tool-use-approval": "~/.claude/hooks/tool-use-trace.js",
3
3
  "on-startup": "~/.claude/hooks/on-startup.js",
4
- "on-clear": "~/.claude/hooks/on-clear.js"
5
- }
4
+ "on-clear": "~/.claude/hooks/on-clear.js",
5
+ "on-task-complete": "~/.claude/hooks/on-task-complete.js",
6
+ "user-prompt-submit": "~/.claude/hooks/skill-eval.sh"
7
+ }
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * On-task-complete hook for StackMemory
5
+ * Triggers when a task is marked as done in Claude Code.
6
+ *
7
+ * Actions:
8
+ * 1. Auto-updates PROMPT_PLAN.md checkboxes (fuzzy match on task keywords)
9
+ * 2. Syncs Linear tasks (if STA-* identifier present)
10
+ * 3. Logs to ~/.stackmemory/logs/hook-errors.log on failure (non-blocking)
11
+ */
12
+
13
+ import fs from 'fs';
14
+ import path from 'path';
15
+
16
+ async function onTaskComplete() {
17
+ try {
18
+ const input = JSON.parse(fs.readFileSync(0, 'utf-8'));
19
+
20
+ // Sync Linear if STA task
21
+ if (input.task && input.task.includes('STA-')) {
22
+ try {
23
+ const smBin = path.join(process.env.HOME || '', '.stackmemory', 'bin');
24
+ const syncScript = path.join(
25
+ process.cwd(),
26
+ 'scripts',
27
+ 'sync-linear-graphql.js'
28
+ );
29
+ if (fs.existsSync(syncScript)) {
30
+ const { execSync } = await import('child_process');
31
+ execSync(`node "${syncScript}"`, {
32
+ stdio: 'ignore',
33
+ timeout: 10000,
34
+ });
35
+ }
36
+ } catch (_e) {
37
+ // Non-blocking
38
+ }
39
+ }
40
+
41
+ // Auto-update PROMPT_PLAN checkboxes if spec exists
42
+ const promptPlanPath = path.join(
43
+ process.cwd(),
44
+ 'docs',
45
+ 'specs',
46
+ 'PROMPT_PLAN.md'
47
+ );
48
+ if (fs.existsSync(promptPlanPath) && input.task) {
49
+ try {
50
+ const content = fs.readFileSync(promptPlanPath, 'utf-8');
51
+ const taskWords = input.task.split(/\s+/).filter((w) => w.length > 3);
52
+ const lines = content.split('\n');
53
+ let updated = false;
54
+ for (let i = 0; i < lines.length; i++) {
55
+ if (
56
+ lines[i].includes('- [ ]') &&
57
+ taskWords.some((w) =>
58
+ lines[i].toLowerCase().includes(w.toLowerCase())
59
+ )
60
+ ) {
61
+ lines[i] = lines[i].replace('- [ ]', '- [x]');
62
+ updated = true;
63
+ break;
64
+ }
65
+ }
66
+ if (updated) {
67
+ fs.writeFileSync(promptPlanPath, lines.join('\n'));
68
+ }
69
+ } catch (_e) {
70
+ // Silently fail
71
+ }
72
+ }
73
+ } catch (error) {
74
+ const logDir = path.join(
75
+ process.env.HOME || '/tmp',
76
+ '.stackmemory',
77
+ 'logs'
78
+ );
79
+ try {
80
+ fs.mkdirSync(logDir, { recursive: true });
81
+ fs.appendFileSync(
82
+ path.join(logDir, 'hook-errors.log'),
83
+ `[${new Date().toISOString()}] on-task-complete: ${error.message}\n`
84
+ );
85
+ } catch (_e) {
86
+ // Last resort: silent
87
+ }
88
+ }
89
+ }
90
+
91
+ onTaskComplete();
@@ -15,6 +15,10 @@ import { fileURLToPath } from 'url';
15
15
  const __filename = fileURLToPath(import.meta.url);
16
16
  const __dirname = path.dirname(__filename);
17
17
 
18
+ const SWEEP_STATE_DIR =
19
+ process.env.SWEEP_STATE_DIR ||
20
+ path.join(process.env.HOME || '/tmp', '.stackmemory');
21
+
18
22
  const CONFIG = {
19
23
  enabled: process.env.SWEEP_ENABLED !== 'false',
20
24
  maxRecentDiffs: 5,
@@ -46,16 +50,9 @@ const CONFIG = {
46
50
  '.svelte',
47
51
  '.astro',
48
52
  ],
49
- stateFile: path.join(
50
- process.env.HOME || '/tmp',
51
- '.stackmemory',
52
- 'sweep-state.json'
53
- ),
54
- logFile: path.join(
55
- process.env.HOME || '/tmp',
56
- '.stackmemory',
57
- 'sweep-predictions.log'
58
- ),
53
+ stateFile: path.join(SWEEP_STATE_DIR, 'sweep-state.json'),
54
+ logFile: path.join(SWEEP_STATE_DIR, 'sweep-predictions.log'),
55
+ // Python script stays at shared location (shared server)
59
56
  pythonScript: path.join(
60
57
  process.env.HOME || '/tmp',
61
58
  '.stackmemory',