Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e822d34342 | |||
| 5590a02dc5 | |||
| 601596f5cb | |||
| 817c4348b1 | |||
| 5963850775 |
+101
-66
@@ -3,101 +3,136 @@
|
||||
|
||||
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||
|
||||
### Dec 28, 2025
|
||||
### Oct 25, 2025
|
||||
|
||||
**marketplace.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #33520 | 10:46 PM | 🔵 | Marketplace configuration lists claude-mem plugin metadata | ~240 |
|
||||
| #33486 | 10:35 PM | ✅ | Committed Version Bump to 8.2.6 | ~212 |
|
||||
| #33484 | " | ✅ | Verified Version Synchronization at 8.2.6 | ~173 |
|
||||
| #33482 | 10:34 PM | ✅ | Updated Marketplace Plugin Version to 8.2.6 | ~159 |
|
||||
| #33479 | " | 🔵 | Marketplace Configuration for Plugin Distribution | ~242 |
|
||||
| #33477 | " | 🔵 | Current Version Across Package Files is 8.2.5 | ~185 |
|
||||
| #33311 | 3:09 PM | ✅ | Version 8.2.3 Release Deployed with Worker Stability Improvements | ~434 |
|
||||
| #33300 | 3:08 PM | ✅ | Version 8.2.4 Released with Full Automation Pipeline | ~357 |
|
||||
| #33281 | 3:07 PM | ✅ | Released v8.2.1 with Worker Lifecycle Hardening | ~332 |
|
||||
| #2374 | 2:55 PM | ✅ | Marketplace metadata version synchronized to 4.2.11 | ~157 |
|
||||
|
||||
### Dec 29, 2025
|
||||
### Oct 27, 2025
|
||||
|
||||
**marketplace.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #34368 | 11:30 PM | ✅ | Bumped version to 8.5.1 for patch release | ~218 |
|
||||
| #34351 | 11:13 PM | ✅ | Version 8.5.0 Release Committed to Git | ~402 |
|
||||
| #34349 | 11:12 PM | ✅ | Version 8.5.0 Synchronized Across All Files | ~220 |
|
||||
| #33997 | 7:10 PM | ✅ | Version Bumped to 8.2.10 | ~179 |
|
||||
| #33994 | " | ✅ | Version bump to 8.2.10 for claude-mem plugin | ~186 |
|
||||
| #33992 | " | 🔵 | Version Consistency Verified Across Package Files | ~133 |
|
||||
| #33956 | 6:45 PM | 🔵 | Version 8.2.9 Release Verification Complete | ~227 |
|
||||
| #33948 | 6:42 PM | 🔵 | Current Version State Across Project Files | ~195 |
|
||||
| #33840 | 4:24 PM | ✅ | Version 8.2.8 Changes Committed to Git | ~233 |
|
||||
| #33838 | 4:23 PM | ✅ | Patch Release Version Bump to 8.2.8 Completed | ~216 |
|
||||
| #33836 | " | 🔵 | Current Version State Before Patch Release | ~125 |
|
||||
| #2757 | 1:23 AM | 🟣 | Released v4.3.3 with Configurable Session Display and First-Time Setup UX | ~391 |
|
||||
|
||||
### Dec 31, 2025
|
||||
### Nov 4, 2025
|
||||
|
||||
**marketplace.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #34723 | 4:51 PM | ✅ | Version 8.5.2 Release Committed and Tagged | ~198 |
|
||||
| #34721 | " | ✅ | Version 8.5.2 Synchronized Across All Configuration Files | ~186 |
|
||||
| #34719 | " | ✅ | Marketplace Version Bumped to 8.5.2 | ~169 |
|
||||
| #34716 | 4:50 PM | 🔵 | Current Version Before Patch Release | ~193 |
|
||||
| #34715 | " | 🔵 | Version 8.5.1 confirmed across all package configuration files | ~181 |
|
||||
| #3706 | 9:47 PM | ✅ | Marketplace Plugin Version Synchronized to 5.0.2 | ~162 |
|
||||
| #3655 | 3:43 PM | ✅ | Version bumped to 5.0.1 across project | ~354 |
|
||||
|
||||
### Jan 1, 2026
|
||||
### Nov 5, 2025
|
||||
|
||||
**marketplace.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #35680 | 11:43 PM | 🟣 | Automated version 8.5.3 release workflow completed | ~470 |
|
||||
| #35674 | 11:41 PM | 🔵 | Release staged files identified for version 8.5.3 | ~287 |
|
||||
| #35673 | " | ✅ | Staged version files for commit | ~155 |
|
||||
| #35671 | " | ✅ | Bumped claude-mem plugin version to 8.5.3 | ~137 |
|
||||
| #35669 | 11:40 PM | 🔵 | Claude plugin marketplace configuration discovered | ~235 |
|
||||
| #4068 | 10:58 PM | ✅ | Committed v5.1.0 release with comprehensive release notes | ~486 |
|
||||
| #4066 | 10:57 PM | ✅ | Updated marketplace.json version to 5.1.0 | ~192 |
|
||||
| #3739 | 2:24 PM | ✅ | Updated version to 5.0.3 across project manifests | ~322 |
|
||||
|
||||
### Jan 2, 2026
|
||||
### Nov 6, 2025
|
||||
|
||||
**marketplace.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #35944 | 2:57 PM | 🔵 | Current Project Version 8.5.4 Verified | ~200 |
|
||||
| #35926 | 2:53 PM | ✅ | Committed Version Bump to 8.5.4 | ~210 |
|
||||
| #35924 | " | ✅ | Verified Version Consistency Across All Files | ~186 |
|
||||
| #35922 | 2:52 PM | ✅ | Updated Marketplace Version to 8.5.4 | ~150 |
|
||||
| #35918 | " | 🔵 | Marketplace Configuration Structure | ~200 |
|
||||
| #35917 | " | 🔵 | Current Version Identified as 8.5.3 | ~170 |
|
||||
| #35905 | 2:49 PM | 🔵 | Current Version is 8.5.3 Across All Package Files | ~193 |
|
||||
| #4099 | 1:13 PM | 🟣 | Theme Toggle for Light/Dark Mode | ~253 |
|
||||
| #4096 | " | ✅ | Marketplace Metadata Version Sync | ~179 |
|
||||
| #4092 | 1:12 PM | 🔵 | Marketplace Configuration for Claude-Mem Plugin | ~194 |
|
||||
| #4078 | 12:50 PM | 🔴 | Fixed PM2 ENOENT error on Windows systems | ~286 |
|
||||
| #4075 | 12:49 PM | ✅ | Marketplace plugin version synchronized to 5.1.1 | ~189 |
|
||||
|
||||
### Jan 3, 2026
|
||||
### Nov 7, 2025
|
||||
|
||||
**marketplace.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #36669 | 11:37 PM | ✅ | Merge conflicts resolved automatically - only 5 metadata files modified | ~345 |
|
||||
| #4612 | 6:33 PM | ✅ | Version Bumped to 5.2.0 Across All Package Metadata | ~359 |
|
||||
| #4598 | 6:31 PM | ✅ | PR #69 Merged: cleanup/worker Branch Integration | ~469 |
|
||||
| #4298 | 11:54 AM | 🔴 | Fixed PostToolUse Hook Schema Compliance | ~310 |
|
||||
| #4295 | 11:53 AM | ✅ | Synchronized Plugin Marketplace Version to 5.1.4 | ~188 |
|
||||
|
||||
### Jan 4, 2026
|
||||
### Nov 8, 2025
|
||||
|
||||
**marketplace.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #36924 | 2:25 AM | ✅ | Merged fix/pr-538-followups branch into main with comprehensive updates | ~481 |
|
||||
| #36922 | " | ✅ | Version Bump Committed to Git | ~261 |
|
||||
| #36920 | " | 🔵 | Version 8.5.8 already set across all configuration files | ~203 |
|
||||
| #36918 | " | ✅ | Version bumped to 8.5.8 in marketplace.json | ~216 |
|
||||
| #36916 | 2:24 AM | 🔵 | Current version identified in marketplace configuration | ~224 |
|
||||
| #36912 | " | 🔵 | Current version identified as 8.5.7 across all package files | ~190 |
|
||||
| #36700 | 12:00 AM | ✅ | Committed Version 8.5.7 Across All Package Files | ~260 |
|
||||
| #36699 | " | ✅ | Version Bumped to 8.5.7 Across All Package Files | ~271 |
|
||||
| #36698 | " | ✅ | Marketplace Plugin Version Updated to 8.5.7 | ~257 |
|
||||
| #5150 | 7:37 PM | 🟣 | Troubleshooting Skill Added to Claude-Mem Plugin | ~427 |
|
||||
| #5133 | 7:29 PM | ✅ | Version 5.2.3 Released with Build Process | ~487 |
|
||||
|
||||
### Jan 5, 2026
|
||||
### Nov 9, 2025
|
||||
|
||||
**marketplace.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #38093 | 10:44 PM | ✅ | Committed version bump to 9.0.0 | ~217 |
|
||||
| #38090 | " | ✅ | Version bumped to 9.0.0 in marketplace.json | ~157 |
|
||||
| #38087 | 10:43 PM | 🔵 | Current version identified as 8.5.10 across all version files | ~200 |
|
||||
| #5941 | 7:14 PM | ✅ | Marketplace Version Updated to 5.4.0 | ~157 |
|
||||
|
||||
### Nov 10, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #6341 | 1:49 PM | ✅ | Version Bumped to 5.4.1 | ~239 |
|
||||
|
||||
### Nov 11, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #6602 | 1:51 PM | ✅ | Version 5.4.5 Released to GitHub | ~279 |
|
||||
| #6601 | " | ✅ | Version Patch Bump 5.4.4 to 5.4.5 | ~233 |
|
||||
|
||||
### Nov 14, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #8212 | 3:06 PM | 🔵 | Version Consistency Verification Across Multiple Configuration Files | ~238 |
|
||||
|
||||
### Nov 25, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #14882 | 1:32 PM | 🔵 | Marketplace Configuration Defines Plugin Version and Source Directory | ~366 |
|
||||
|
||||
### Nov 30, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #18064 | 10:52 PM | ✅ | Bumped version to 6.3.7 in marketplace.json | ~179 |
|
||||
| #18060 | 10:51 PM | 🔵 | Read marketplace.json plugin manifest | ~190 |
|
||||
|
||||
### Dec 1, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #18428 | 3:33 PM | 🔵 | Version Conflict in Marketplace Configuration | ~191 |
|
||||
|
||||
### Dec 4, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #20049 | 3:23 PM | ✅ | Updated marketplace.json version to 6.5.2 | ~203 |
|
||||
|
||||
### Dec 9, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #22559 | 1:08 AM | ✅ | Version 7.0.3 committed to repository | ~261 |
|
||||
| #22551 | 1:07 AM | ✅ | Marketplace metadata updated to version 7.0.3 | ~179 |
|
||||
|
||||
### Dec 10, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #23440 | 2:25 PM | ✅ | Marketplace Configuration Updated to 7.0.8 | ~188 |
|
||||
|
||||
### Dec 14, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #26799 | 11:39 PM | ✅ | Marketplace Manifest Version Updated to 7.2.3 | ~248 |
|
||||
| #26796 | " | ✅ | Version Bumped to 7.2.3 in marketplace.json | ~259 |
|
||||
| #26792 | 11:38 PM | 🔵 | Current Version Confirmed as 7.2.2 Across All Configuration Files | ~291 |
|
||||
|
||||
### Dec 16, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #28306 | 10:08 PM | 🔵 | Marketplace Configuration Also Shows Version 7.3.3 | ~220 |
|
||||
| #27555 | 4:48 PM | ✅ | Version bump committed to main branch | ~242 |
|
||||
| #27553 | " | ✅ | Version consistency verified across all configuration files | ~195 |
|
||||
| #27551 | 4:47 PM | ✅ | Marketplace.json version updated to 7.3.1 | ~207 |
|
||||
</claude-mem-context>
|
||||
@@ -10,7 +10,7 @@
|
||||
"plugins": [
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "9.0.1",
|
||||
"version": "9.0.2",
|
||||
"source": "./plugin",
|
||||
"description": "Persistent memory system for Claude Code - context compression across sessions"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
# Plan: PR #628 Polish Items
|
||||
|
||||
**PR**: #628 - Windows Terminal Tab Accumulation & Windows 11 Compatibility
|
||||
**Status**: APPROVED by 3 reviewers with minor suggestions
|
||||
**Branch**: `feature/no-more-hook-files`
|
||||
|
||||
---
|
||||
|
||||
## Phase 0: Documentation Discovery (Completed by Orchestrator)
|
||||
|
||||
### Allowed APIs and Patterns
|
||||
|
||||
**Exit Code Constants** - `src/shared/hook-constants.ts:18-23`:
|
||||
```typescript
|
||||
export const HOOK_EXIT_CODES = {
|
||||
SUCCESS: 0,
|
||||
FAILURE: 1,
|
||||
BLOCKING_ERROR: 2,
|
||||
} as const;
|
||||
```
|
||||
|
||||
**Timeout Constants** - `src/shared/hook-constants.ts:1-8`:
|
||||
```typescript
|
||||
export const HOOK_TIMEOUTS = {
|
||||
DEFAULT: 300000,
|
||||
HEALTH_CHECK: 30000,
|
||||
WORKER_STARTUP_WAIT: 1000,
|
||||
WORKER_STARTUP_RETRIES: 300,
|
||||
PRE_RESTART_SETTLE_DELAY: 2000,
|
||||
WINDOWS_MULTIPLIER: 1.5
|
||||
} as const;
|
||||
```
|
||||
|
||||
**Platform Timeout Function** - `src/services/infrastructure/ProcessManager.ts:70-73`:
|
||||
```typescript
|
||||
export function getPlatformTimeout(baseMs: number): number {
|
||||
const WINDOWS_MULTIPLIER = 2.0;
|
||||
return process.platform === 'win32' ? Math.round(baseMs * WINDOWS_MULTIPLIER) : baseMs;
|
||||
}
|
||||
```
|
||||
|
||||
**Migration Guide Pattern** - `docs/public/architecture/pm2-to-bun-migration.mdx`:
|
||||
- Uses MDX format with frontmatter
|
||||
- Starts with `<Note>` for historical context
|
||||
- Uses `<AccordionGroup>` for before/after comparisons
|
||||
- Includes executive summary, key benefits, migration impact sections
|
||||
|
||||
**Exit Code Documentation** - `private/context/claude-code/exit-codes.md`:
|
||||
- Defines exit code 0, 2, and other behaviors
|
||||
- Per-hook event behavior table
|
||||
|
||||
### Files to Modify
|
||||
|
||||
| File | Change | Lines |
|
||||
|------|--------|-------|
|
||||
| `src/services/infrastructure/ProcessManager.ts` | Add POWERSHELL_TIMEOUT constant, reduce from 60000 to 10000 | 93, 123, 175, 241 |
|
||||
| `src/shared/hook-constants.ts` | Add POWERSHELL_TIMEOUT constant | After line 8 |
|
||||
| `CLAUDE.md` | Document exit code strategy | Architecture section |
|
||||
|
||||
### Anti-Patterns to Avoid
|
||||
|
||||
- DO NOT invent new exit code values (only 0, 1, 2 exist)
|
||||
- DO NOT change Windows multiplier (1.5x in hooks, 2.0x in ProcessManager - they serve different purposes)
|
||||
- DO NOT add upper bound PID validation (not in existing pattern, reviewers marked as "nice to have")
|
||||
- DO NOT create migration guide for Cursor (shell scripts still exist in cursor-hooks/, not removed)
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Extract PowerShell Timeout Constant
|
||||
|
||||
### What to Implement
|
||||
|
||||
Add a `POWERSHELL_TIMEOUT` constant to centralize the magic number `60000` and reduce to `10000` (10 seconds) as recommended by reviewers.
|
||||
|
||||
### Documentation References
|
||||
|
||||
1. Copy constant pattern from `src/shared/hook-constants.ts:1-8`
|
||||
2. Copy usage pattern from `src/services/infrastructure/ProcessManager.ts:93`
|
||||
|
||||
### Implementation Steps
|
||||
|
||||
1. **Add constant to hook-constants.ts** after line 8:
|
||||
```typescript
|
||||
POWERSHELL_COMMAND: 10000, // PowerShell process enumeration (10s - typically completes in <1s)
|
||||
```
|
||||
|
||||
2. **Import and use in ProcessManager.ts**:
|
||||
- Import `HOOK_TIMEOUTS` from `../../shared/hook-constants.js`
|
||||
- Replace `{ timeout: 60000 }` with `{ timeout: HOOK_TIMEOUTS.POWERSHELL_COMMAND }` at lines 93, 123, 175, 241
|
||||
|
||||
### Verification Checklist
|
||||
|
||||
- [ ] `grep -n "60000" src/services/infrastructure/ProcessManager.ts` returns 0 matches
|
||||
- [ ] `grep -n "POWERSHELL_COMMAND" src/services/infrastructure/ProcessManager.ts` returns 4 matches
|
||||
- [ ] `npm run build` succeeds
|
||||
- [ ] `npm test` passes (22/22 PowerShell tests still pass)
|
||||
|
||||
### Anti-Pattern Guards
|
||||
|
||||
- DO NOT use `getPlatformTimeout()` for PowerShell commands (they already run only on Windows)
|
||||
- DO NOT change timeout values in other files (only ProcessManager.ts uses PowerShell)
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Document Exit Code Strategy in CLAUDE.md
|
||||
|
||||
### What to Implement
|
||||
|
||||
Add an "Exit Code Strategy" section to the main CLAUDE.md to explain the graceful exit philosophy adopted in this PR.
|
||||
|
||||
### Documentation References
|
||||
|
||||
1. Copy exit code definitions from `private/context/claude-code/exit-codes.md`
|
||||
2. Follow format of existing CLAUDE.md sections
|
||||
|
||||
### Implementation Steps
|
||||
|
||||
1. **Add section after "File Locations"** in `/Users/alexnewman/Scripts/claude-mem/CLAUDE.md`:
|
||||
|
||||
```markdown
|
||||
## Exit Code Strategy
|
||||
|
||||
Claude-mem hooks use specific exit codes per Claude Code's hook contract:
|
||||
|
||||
- **Exit 0**: Success or graceful shutdown (Windows Terminal closes tabs)
|
||||
- **Exit 1**: Non-blocking error (stderr shown to user, continues)
|
||||
- **Exit 2**: Blocking error (stderr fed to Claude for processing)
|
||||
|
||||
**Philosophy**: Worker/hook errors exit with code 0 to prevent Windows Terminal tab accumulation. The wrapper/plugin layer handles restart logic. ERROR-level logging is maintained for diagnostics.
|
||||
|
||||
See `private/context/claude-code/exit-codes.md` for full hook behavior matrix.
|
||||
```
|
||||
|
||||
### Verification Checklist
|
||||
|
||||
- [ ] `grep -n "Exit Code Strategy" CLAUDE.md` returns 1 match
|
||||
- [ ] Section appears after "File Locations" section
|
||||
- [ ] No duplicate sections added
|
||||
|
||||
### Anti-Pattern Guards
|
||||
|
||||
- DO NOT copy the full exit-codes.md table (keep it brief, reference the source)
|
||||
- DO NOT change actual exit code behavior in code files
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Update Tests for New Timeout Constant
|
||||
|
||||
### What to Implement
|
||||
|
||||
Add test coverage for the new `POWERSHELL_COMMAND` timeout constant.
|
||||
|
||||
### Documentation References
|
||||
|
||||
1. Copy test pattern from `tests/hook-constants.test.ts:26-48`
|
||||
|
||||
### Implementation Steps
|
||||
|
||||
1. **Add test to hook-constants.test.ts** after line 42:
|
||||
```typescript
|
||||
test('POWERSHELL_COMMAND timeout is 10000ms', () => {
|
||||
expect(HOOK_TIMEOUTS.POWERSHELL_COMMAND).toBe(10000);
|
||||
});
|
||||
```
|
||||
|
||||
### Verification Checklist
|
||||
|
||||
- [ ] `npm test -- tests/hook-constants.test.ts` passes
|
||||
- [ ] New test appears in test output
|
||||
- [ ] All 22 PowerShell parsing tests still pass
|
||||
|
||||
### Anti-Pattern Guards
|
||||
|
||||
- DO NOT modify PowerShell parsing tests (they test parsing, not timeouts)
|
||||
- DO NOT add integration tests for actual PowerShell execution (out of scope)
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Final Verification
|
||||
|
||||
### Verification Checklist
|
||||
|
||||
1. **Build passes**: `npm run build`
|
||||
2. **All tests pass**: `npm test`
|
||||
3. **No magic numbers remain**: `grep -rn "60000" src/services/infrastructure/ProcessManager.ts` returns 0
|
||||
4. **Exit code documentation exists**: `grep -n "Exit Code Strategy" CLAUDE.md` returns 1
|
||||
5. **Constant is used**: `grep -rn "POWERSHELL_COMMAND" src/` returns multiple matches
|
||||
|
||||
### Anti-Pattern Grep Checks
|
||||
|
||||
- [ ] `grep -rn "timeout: 60000" src/` returns 0 matches (no hardcoded 60s timeouts in ProcessManager)
|
||||
- [ ] `grep -rn "process.exit(3)" src/` returns 0 matches (exit code 3 not used)
|
||||
|
||||
### Commit Message Template
|
||||
|
||||
```
|
||||
polish: extract PowerShell timeout constant and document exit code strategy
|
||||
|
||||
- Extract magic number 60000ms to HOOK_TIMEOUTS.POWERSHELL_COMMAND (10000ms)
|
||||
- Reduce PowerShell timeout from 60s to 10s per review feedback
|
||||
- Document exit code strategy in CLAUDE.md
|
||||
- Add test coverage for new constant
|
||||
|
||||
Addresses review feedback from PR #628
|
||||
|
||||
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Phase | Description | Files Changed | Verification |
|
||||
|-------|-------------|---------------|--------------|
|
||||
| 0 | Documentation Discovery | N/A | Patterns identified |
|
||||
| 1 | Extract PowerShell timeout | hook-constants.ts, ProcessManager.ts | grep + build + test |
|
||||
| 2 | Document exit strategy | CLAUDE.md | grep |
|
||||
| 3 | Add test coverage | hook-constants.test.ts | npm test |
|
||||
| 4 | Final verification | N/A | All checks pass |
|
||||
|
||||
**Estimated Changes**: ~20 lines added/modified across 4 files
|
||||
**Risk Level**: Low (constants extraction, documentation only)
|
||||
**Breaking Changes**: None
|
||||
@@ -0,0 +1,7 @@
|
||||
<claude-mem-context>
|
||||
# Recent Activity
|
||||
|
||||
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||
|
||||
*No recent activity*
|
||||
</claude-mem-context>
|
||||
+35
-10
@@ -2,6 +2,41 @@
|
||||
|
||||
All notable changes to claude-mem.
|
||||
|
||||
## [v9.0.1] - 2026-01-08
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
### Claude Code 2.1.1 Compatibility
|
||||
- Fixed hook architecture for compatibility with Claude Code 2.1.0/2.1.1
|
||||
- Context is now injected silently via SessionStart hook
|
||||
- Removed deprecated `user-message-hook` (no longer used in CC 2.1.0+)
|
||||
|
||||
### Path Validation for CLAUDE.md Distribution
|
||||
- Added `isValidPathForClaudeMd()` to reject malformed paths:
|
||||
- Tilde paths (`~`) that Node.js doesn't expand
|
||||
- URLs (`http://`, `https://`)
|
||||
- Paths with spaces (likely command text or PR references)
|
||||
- Paths with `#` (GitHub issue/PR references)
|
||||
- Relative paths that escape project boundary
|
||||
- Cleaned up 12 invalid CLAUDE.md files created by bug artifacts
|
||||
- Updated `.gitignore` to prevent future accidents
|
||||
|
||||
### Log-Level Audit
|
||||
- Promoted 38+ WARN messages to ERROR level for improved debugging:
|
||||
- Parser: observation type errors, data contamination
|
||||
- SDK/Agents: empty init responses (Gemini, OpenRouter)
|
||||
- Worker/Queue: session recovery, auto-recovery failures
|
||||
- Chroma: sync failures, search failures
|
||||
- SQLite: search failures
|
||||
- Session/Generator: failures, missing context
|
||||
- Infrastructure: shutdown, process management failures
|
||||
|
||||
## Internal Changes
|
||||
- Removed hardcoded fake token counts from context injection
|
||||
- Standardized Claude Code 2.1.0 note wording across documentation
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v9.0.0...v9.0.1
|
||||
|
||||
## [v9.0.0] - 2026-01-06
|
||||
|
||||
## 🚀 Live Context System
|
||||
@@ -1254,13 +1289,3 @@ Fixed unbounded database growth in the `pending_messages` table by implementing
|
||||
---
|
||||
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||||
|
||||
## [v7.2.2] - 2025-12-15
|
||||
|
||||
## Changes
|
||||
|
||||
- **Refactor:** Consolidate mem-search skill, remove desktop-skill duplication
|
||||
- Delete separate `desktop-skill/` directory (was outdated)
|
||||
- Generate `mem-search.zip` during build from `plugin/skills/mem-search/`
|
||||
- Update docs with correct MCP tool list and new download path
|
||||
- Single source of truth for Claude Desktop skill
|
||||
|
||||
|
||||
@@ -41,6 +41,18 @@ Settings are managed in `~/.claude-mem/settings.json`. The file is auto-created
|
||||
- **Database**: `~/.claude-mem/claude-mem.db`
|
||||
- **Chroma**: `~/.claude-mem/chroma/`
|
||||
|
||||
## Exit Code Strategy
|
||||
|
||||
Claude-mem hooks use specific exit codes per Claude Code's hook contract:
|
||||
|
||||
- **Exit 0**: Success or graceful shutdown (Windows Terminal closes tabs)
|
||||
- **Exit 1**: Non-blocking error (stderr shown to user, continues)
|
||||
- **Exit 2**: Blocking error (stderr fed to Claude for processing)
|
||||
|
||||
**Philosophy**: Worker/hook errors exit with code 0 to prevent Windows Terminal tab accumulation. The wrapper/plugin layer handles restart logic. ERROR-level logging is maintained for diagnostics.
|
||||
|
||||
See `private/context/claude-code/exit-codes.md` for full hook behavior matrix.
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Bun** (all platforms - auto-installed if missing)
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
# Common utility functions for Cursor hooks (PowerShell)
|
||||
# Dot-source this file in hook scripts: . "$PSScriptRoot\common.ps1"
|
||||
# Note: ErrorActionPreference should be set in each script, not globally here
|
||||
|
||||
# Get worker port from settings with validation
|
||||
function Get-WorkerPort {
|
||||
$settingsPath = Join-Path $env:USERPROFILE ".claude-mem\settings.json"
|
||||
$port = 37777
|
||||
|
||||
if (Test-Path $settingsPath) {
|
||||
try {
|
||||
$settings = Get-Content $settingsPath -Raw | ConvertFrom-Json
|
||||
if ($settings.CLAUDE_MEM_WORKER_PORT) {
|
||||
$parsedPort = [int]$settings.CLAUDE_MEM_WORKER_PORT
|
||||
if ($parsedPort -ge 1 -and $parsedPort -le 65535) {
|
||||
$port = $parsedPort
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
# Ignore parse errors, use default
|
||||
}
|
||||
}
|
||||
|
||||
return $port
|
||||
}
|
||||
|
||||
# Ensure worker is running with retries
|
||||
function Test-WorkerReady {
|
||||
param(
|
||||
[int]$Port = 37777,
|
||||
[int]$MaxRetries = 75
|
||||
)
|
||||
|
||||
for ($i = 0; $i -lt $MaxRetries; $i++) {
|
||||
try {
|
||||
$response = Invoke-RestMethod -Uri "http://127.0.0.1:$Port/api/readiness" -Method Get -TimeoutSec 1 -ErrorAction Stop
|
||||
return $true
|
||||
} catch {
|
||||
Start-Sleep -Milliseconds 200
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
# Get project name from workspace root
|
||||
function Get-ProjectName {
|
||||
param([string]$WorkspaceRoot)
|
||||
|
||||
if ([string]::IsNullOrEmpty($WorkspaceRoot)) {
|
||||
return "unknown-project"
|
||||
}
|
||||
|
||||
# Handle Windows drive root (e.g., "C:\")
|
||||
if ($WorkspaceRoot -match '^([A-Za-z]):\\?$') {
|
||||
return "drive-$($Matches[1].ToUpper())"
|
||||
}
|
||||
|
||||
$projectName = Split-Path $WorkspaceRoot -Leaf
|
||||
if ([string]::IsNullOrEmpty($projectName)) {
|
||||
return "unknown-project"
|
||||
}
|
||||
|
||||
return $projectName
|
||||
}
|
||||
|
||||
# URL encode a string
|
||||
function Get-UrlEncodedString {
|
||||
param([string]$String)
|
||||
|
||||
if ([string]::IsNullOrEmpty($String)) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return [System.Uri]::EscapeDataString($String)
|
||||
}
|
||||
|
||||
# Check if string is empty or null
|
||||
function Test-IsEmpty {
|
||||
param([string]$String)
|
||||
|
||||
return [string]::IsNullOrEmpty($String) -or $String -eq "null" -or $String -eq "empty"
|
||||
}
|
||||
|
||||
# Safely read JSON from stdin with error handling
|
||||
function Read-JsonInput {
|
||||
try {
|
||||
$input = [Console]::In.ReadToEnd()
|
||||
if ([string]::IsNullOrEmpty($input)) {
|
||||
return @{}
|
||||
}
|
||||
return $input | ConvertFrom-Json -ErrorAction Stop
|
||||
} catch {
|
||||
return @{}
|
||||
}
|
||||
}
|
||||
|
||||
# Safely get JSON field with fallback
|
||||
function Get-JsonField {
|
||||
param(
|
||||
[PSObject]$Json,
|
||||
[string]$Field,
|
||||
[string]$Fallback = ""
|
||||
)
|
||||
|
||||
if ($null -eq $Json) {
|
||||
return $Fallback
|
||||
}
|
||||
|
||||
# Handle array access syntax (e.g., "workspace_roots[0]")
|
||||
if ($Field -match '^(.+)\[(\d+)\]$') {
|
||||
$arrayField = $Matches[1]
|
||||
$index = [int]$Matches[2]
|
||||
|
||||
if ($Json.PSObject.Properties.Name -contains $arrayField) {
|
||||
$array = $Json.$arrayField
|
||||
if ($null -ne $array -and $array.Count -gt $index) {
|
||||
$value = $array[$index]
|
||||
if (-not (Test-IsEmpty $value)) {
|
||||
return $value
|
||||
}
|
||||
}
|
||||
}
|
||||
return $Fallback
|
||||
}
|
||||
|
||||
# Simple field access
|
||||
if ($Json.PSObject.Properties.Name -contains $Field) {
|
||||
$value = $Json.$Field
|
||||
if (-not (Test-IsEmpty $value)) {
|
||||
return $value
|
||||
}
|
||||
}
|
||||
|
||||
return $Fallback
|
||||
}
|
||||
|
||||
# Convert object to JSON string (compact)
|
||||
function ConvertTo-JsonCompact {
|
||||
param([object]$Object)
|
||||
|
||||
return $Object | ConvertTo-Json -Compress -Depth 10
|
||||
}
|
||||
|
||||
# Send HTTP POST request (fire-and-forget style)
|
||||
function Send-HttpPostAsync {
|
||||
param(
|
||||
[string]$Uri,
|
||||
[object]$Body
|
||||
)
|
||||
|
||||
try {
|
||||
$bodyJson = ConvertTo-JsonCompact $Body
|
||||
Start-Job -ScriptBlock {
|
||||
param($u, $b)
|
||||
try {
|
||||
Invoke-RestMethod -Uri $u -Method Post -Body $b -ContentType "application/json" -TimeoutSec 5 -ErrorAction SilentlyContinue | Out-Null
|
||||
} catch {}
|
||||
} -ArgumentList $Uri, $bodyJson | Out-Null
|
||||
} catch {
|
||||
# Ignore errors - fire and forget
|
||||
}
|
||||
}
|
||||
|
||||
# Send HTTP POST request (synchronous)
|
||||
function Send-HttpPost {
|
||||
param(
|
||||
[string]$Uri,
|
||||
[object]$Body
|
||||
)
|
||||
|
||||
try {
|
||||
$bodyJson = ConvertTo-JsonCompact $Body
|
||||
Invoke-RestMethod -Uri $u -Method Post -Body $bodyJson -ContentType "application/json" -TimeoutSec 5 -ErrorAction SilentlyContinue | Out-Null
|
||||
} catch {
|
||||
# Ignore errors
|
||||
}
|
||||
}
|
||||
|
||||
# Get HTTP response
|
||||
function Get-HttpResponse {
|
||||
param(
|
||||
[string]$Uri,
|
||||
[int]$TimeoutSec = 5
|
||||
)
|
||||
|
||||
try {
|
||||
return Invoke-RestMethod -Uri $Uri -Method Get -TimeoutSec $TimeoutSec -ErrorAction Stop
|
||||
} catch {
|
||||
return $null
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Common utility functions for Cursor hooks
|
||||
# Source this file in hook scripts: source "$(dirname "$0")/common.sh"
|
||||
|
||||
# Check if required commands exist
|
||||
check_dependencies() {
|
||||
local missing=()
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
missing+=("jq")
|
||||
fi
|
||||
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
missing+=("curl")
|
||||
fi
|
||||
|
||||
if [ ${#missing[@]} -gt 0 ]; then
|
||||
echo "Error: Missing required dependencies: ${missing[*]}" >&2
|
||||
echo "Please install: ${missing[*]}" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Safely read JSON from stdin with error handling
|
||||
read_json_input() {
|
||||
local input
|
||||
input=$(cat 2>/dev/null || echo "{}")
|
||||
|
||||
# Validate JSON
|
||||
if ! echo "$input" | jq empty 2>/dev/null; then
|
||||
# Invalid JSON - return empty object
|
||||
echo "{}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$input"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Get worker port from settings with validation
|
||||
get_worker_port() {
|
||||
local data_dir="${HOME}/.claude-mem"
|
||||
local settings_file="${data_dir}/settings.json"
|
||||
local port="37777"
|
||||
|
||||
if [ -f "$settings_file" ]; then
|
||||
local parsed_port
|
||||
parsed_port=$(jq -r '.CLAUDE_MEM_WORKER_PORT // "37777"' "$settings_file" 2>/dev/null || echo "37777")
|
||||
|
||||
# Validate port is a number between 1-65535
|
||||
if [[ "$parsed_port" =~ ^[0-9]+$ ]] && [ "$parsed_port" -ge 1 ] && [ "$parsed_port" -le 65535 ]; then
|
||||
port="$parsed_port"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "$port"
|
||||
}
|
||||
|
||||
# Ensure worker is running with retries
|
||||
ensure_worker_running() {
|
||||
local port="${1:-37777}"
|
||||
local max_retries="${2:-75}" # 15 seconds total (75 * 0.2s)
|
||||
local retry_count=0
|
||||
|
||||
while [ $retry_count -lt $max_retries ]; do
|
||||
if curl -s -f "http://127.0.0.1:${port}/api/readiness" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
sleep 0.2
|
||||
retry_count=$((retry_count + 1))
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# URL encode a string (basic implementation)
|
||||
url_encode() {
|
||||
local string="$1"
|
||||
# Use printf to URL encode
|
||||
printf '%s' "$string" | jq -sRr @uri
|
||||
}
|
||||
|
||||
# Get project name from workspace root
|
||||
get_project_name() {
|
||||
local workspace_root="$1"
|
||||
|
||||
if [ -z "$workspace_root" ]; then
|
||||
echo "unknown-project"
|
||||
return
|
||||
fi
|
||||
|
||||
# Use basename, fallback to unknown-project
|
||||
local project_name
|
||||
project_name=$(basename "$workspace_root" 2>/dev/null || echo "unknown-project")
|
||||
|
||||
# Handle edge case: empty basename (root directory)
|
||||
if [ -z "$project_name" ]; then
|
||||
# Check if it's a Windows drive root
|
||||
if [[ "$workspace_root" =~ ^[A-Za-z]:\\?$ ]]; then
|
||||
local drive_letter
|
||||
drive_letter=$(echo "$workspace_root" | grep -oE '^[A-Za-z]' | tr '[:lower:]' '[:upper:]')
|
||||
echo "drive-${drive_letter}"
|
||||
else
|
||||
echo "unknown-project"
|
||||
fi
|
||||
else
|
||||
echo "$project_name"
|
||||
fi
|
||||
}
|
||||
|
||||
# Safely extract JSON field with fallback
|
||||
# Supports both simple fields (e.g., "conversation_id") and array access (e.g., "workspace_roots[0]")
|
||||
json_get() {
|
||||
local json="$1"
|
||||
local field="$2"
|
||||
local fallback="${3:-}"
|
||||
|
||||
local value
|
||||
|
||||
# Handle array access syntax (e.g., "workspace_roots[0]")
|
||||
if [[ "$field" =~ ^(.+)\[([0-9]+)\]$ ]]; then
|
||||
local array_field="${BASH_REMATCH[1]}"
|
||||
local index="${BASH_REMATCH[2]}"
|
||||
value=$(echo "$json" | jq -r --arg f "$array_field" --arg i "$index" --arg fb "$fallback" '.[$f] // [] | .[$i | tonumber] // $fb' 2>/dev/null || echo "$fallback")
|
||||
else
|
||||
# Simple field access
|
||||
value=$(echo "$json" | jq -r --arg f "$field" --arg fb "$fallback" '.[$f] // $fb' 2>/dev/null || echo "$fallback")
|
||||
fi
|
||||
|
||||
echo "$value"
|
||||
}
|
||||
|
||||
# Check if string is empty or null
|
||||
is_empty() {
|
||||
local str="$1"
|
||||
[ -z "$str" ] || [ "$str" = "null" ] || [ "$str" = "empty" ]
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
# Context Hook for Cursor (beforeSubmitPrompt) - PowerShell
|
||||
# Ensures worker is running and refreshes context before prompt submission
|
||||
#
|
||||
# Context is updated in BOTH places:
|
||||
# - Here (beforeSubmitPrompt): Fresh context at session start
|
||||
# - stop hook (session-summary.ps1): Updated context after observations are made
|
||||
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
|
||||
# Source common utilities
|
||||
$commonPath = Join-Path $PSScriptRoot "common.ps1"
|
||||
if (Test-Path $commonPath) {
|
||||
. $commonPath
|
||||
} else {
|
||||
Write-Output '{"continue": true}'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Read JSON input from stdin
|
||||
$input = Read-JsonInput
|
||||
|
||||
# Extract workspace root
|
||||
$workspaceRoot = Get-JsonField $input "workspace_roots[0]" ""
|
||||
if (Test-IsEmpty $workspaceRoot) {
|
||||
$workspaceRoot = Get-Location
|
||||
}
|
||||
|
||||
# Get project name
|
||||
$projectName = Get-ProjectName $workspaceRoot
|
||||
|
||||
# Get worker port from settings
|
||||
$workerPort = Get-WorkerPort
|
||||
|
||||
# Ensure worker is running (with retries)
|
||||
# This primes the worker before the session starts
|
||||
if (Test-WorkerReady -Port $workerPort) {
|
||||
# Refresh context file with latest observations
|
||||
$projectEncoded = Get-UrlEncodedString $projectName
|
||||
$contextUri = "http://127.0.0.1:$workerPort/api/context/inject?project=$projectEncoded"
|
||||
$context = Get-HttpResponse -Uri $contextUri
|
||||
|
||||
if (-not [string]::IsNullOrEmpty($context)) {
|
||||
$rulesDir = Join-Path $workspaceRoot ".cursor\rules"
|
||||
$rulesFile = Join-Path $rulesDir "claude-mem-context.mdc"
|
||||
|
||||
# Create rules directory if it doesn't exist
|
||||
if (-not (Test-Path $rulesDir)) {
|
||||
New-Item -ItemType Directory -Path $rulesDir -Force | Out-Null
|
||||
}
|
||||
|
||||
# Write context as a Cursor rule with alwaysApply: true
|
||||
$ruleContent = @"
|
||||
---
|
||||
alwaysApply: true
|
||||
description: "Claude-mem context from past sessions (auto-updated)"
|
||||
---
|
||||
|
||||
# Memory Context from Past Sessions
|
||||
|
||||
The following context is from claude-mem, a persistent memory system that tracks your coding sessions.
|
||||
|
||||
$context
|
||||
|
||||
---
|
||||
*Updated after last session. Use claude-mem's MCP search tools for more detailed queries.*
|
||||
"@
|
||||
|
||||
Set-Content -Path $rulesFile -Value $ruleContent -Encoding UTF8 -Force
|
||||
}
|
||||
}
|
||||
|
||||
# Allow prompt to continue
|
||||
Write-Output '{"continue": true}'
|
||||
exit 0
|
||||
@@ -1,70 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Context Hook for Cursor (beforeSubmitPrompt)
|
||||
# Ensures worker is running and refreshes context before prompt submission
|
||||
#
|
||||
# Context is updated in BOTH places:
|
||||
# - Here (beforeSubmitPrompt): Fresh context at session start
|
||||
# - stop hook (session-summary.sh): Updated context after observations are made
|
||||
|
||||
# Source common utilities
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/common.sh" 2>/dev/null || {
|
||||
echo '{"continue": true}'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Check dependencies (non-blocking)
|
||||
check_dependencies >/dev/null 2>&1 || true
|
||||
|
||||
# Read JSON input from stdin
|
||||
input=$(read_json_input)
|
||||
|
||||
# Extract workspace root
|
||||
workspace_root=$(json_get "$input" "workspace_roots[0]" "")
|
||||
if is_empty "$workspace_root"; then
|
||||
workspace_root=$(pwd)
|
||||
fi
|
||||
|
||||
# Get project name
|
||||
project_name=$(get_project_name "$workspace_root")
|
||||
|
||||
# Get worker port from settings
|
||||
worker_port=$(get_worker_port)
|
||||
|
||||
# Ensure worker is running (with retries)
|
||||
# This primes the worker before the session starts
|
||||
if ensure_worker_running "$worker_port" >/dev/null 2>&1; then
|
||||
# Refresh context file with latest observations
|
||||
project_encoded=$(url_encode "$project_name")
|
||||
context=$(curl -s -f "http://127.0.0.1:${worker_port}/api/context/inject?project=${project_encoded}" 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$context" ]; then
|
||||
rules_dir="${workspace_root}/.cursor/rules"
|
||||
rules_file="${rules_dir}/claude-mem-context.mdc"
|
||||
|
||||
# Create rules directory if it doesn't exist
|
||||
mkdir -p "$rules_dir" 2>/dev/null || true
|
||||
|
||||
# Write context as a Cursor rule with alwaysApply: true
|
||||
cat > "$rules_file" 2>/dev/null << EOF
|
||||
---
|
||||
alwaysApply: true
|
||||
description: "Claude-mem context from past sessions (auto-updated)"
|
||||
---
|
||||
|
||||
# Memory Context from Past Sessions
|
||||
|
||||
The following context is from claude-mem, a persistent memory system that tracks your coding sessions.
|
||||
|
||||
${context}
|
||||
|
||||
---
|
||||
*Updated after last session. Use claude-mem's MCP search tools for more detailed queries.*
|
||||
EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
# Allow prompt to continue
|
||||
echo '{"continue": true}'
|
||||
exit 0
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Installation script for claude-mem Cursor hooks
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
INSTALL_TYPE="${1:-user}" # 'user' (recommended) or 'project'
|
||||
|
||||
echo "Installing claude-mem Cursor hooks (${INSTALL_TYPE} level)..."
|
||||
|
||||
case "$INSTALL_TYPE" in
|
||||
"project")
|
||||
if [ ! -d ".cursor" ]; then
|
||||
mkdir -p .cursor
|
||||
fi
|
||||
TARGET_DIR=".cursor"
|
||||
HOOKS_DIR=".cursor/hooks"
|
||||
;;
|
||||
"user")
|
||||
TARGET_DIR="${HOME}/.cursor"
|
||||
HOOKS_DIR="${HOME}/.cursor/hooks"
|
||||
;;
|
||||
*)
|
||||
echo "Invalid install type: $INSTALL_TYPE"
|
||||
echo "Usage: $0 [user (recommended)|project]"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Create hooks directory
|
||||
mkdir -p "$HOOKS_DIR"
|
||||
|
||||
# Copy hook scripts
|
||||
echo "Copying hook scripts..."
|
||||
cp "$SCRIPT_DIR"/*.sh "$HOOKS_DIR/"
|
||||
chmod +x "$HOOKS_DIR"/*.sh
|
||||
|
||||
# Copy hooks.json
|
||||
echo "Copying hooks.json..."
|
||||
cp "$SCRIPT_DIR/hooks.json" "$TARGET_DIR/hooks.json"
|
||||
|
||||
# Update paths in hooks.json if needed
|
||||
# Use portable sed approach that works on both BSD (macOS) and GNU (Linux) sed
|
||||
if [ "$INSTALL_TYPE" = "project" ]; then
|
||||
# For project-level, paths should be relative
|
||||
# Create temp file, modify, then move (portable across sed variants)
|
||||
tmp_file=$(mktemp)
|
||||
sed 's|\./cursor-hooks/|\./\.cursor/hooks/|g' "$TARGET_DIR/hooks.json" > "$tmp_file"
|
||||
mv "$tmp_file" "$TARGET_DIR/hooks.json"
|
||||
else
|
||||
# For user-level, use absolute paths
|
||||
tmp_file=$(mktemp)
|
||||
sed "s|\./cursor-hooks/|${HOOKS_DIR}/|g" "$TARGET_DIR/hooks.json" > "$tmp_file"
|
||||
mv "$tmp_file" "$TARGET_DIR/hooks.json"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✓ Installation complete!"
|
||||
echo ""
|
||||
echo "Hooks installed to: $TARGET_DIR/hooks.json"
|
||||
echo "Scripts installed to: $HOOKS_DIR"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. Ensure claude-mem worker is running:"
|
||||
echo " cd ~/.claude/plugins/marketplaces/thedotmack && npm run worker:start"
|
||||
echo ""
|
||||
echo "2. Restart Cursor to load the hooks"
|
||||
echo ""
|
||||
echo "3. Check Cursor Settings → Hooks tab to verify hooks are active"
|
||||
echo ""
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
# Save File Edit Hook for Cursor (PowerShell)
|
||||
# Captures file edits made by the agent
|
||||
# Maps file edits to claude-mem observations
|
||||
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
|
||||
# Source common utilities
|
||||
$commonPath = Join-Path $PSScriptRoot "common.ps1"
|
||||
if (Test-Path $commonPath) {
|
||||
. $commonPath
|
||||
} else {
|
||||
Write-Warning "common.ps1 not found, using fallback functions"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Read JSON input from stdin with error handling
|
||||
$input = Read-JsonInput
|
||||
|
||||
# Extract common fields with safe fallbacks
|
||||
$conversationId = Get-JsonField $input "conversation_id" ""
|
||||
$generationId = Get-JsonField $input "generation_id" ""
|
||||
$filePath = Get-JsonField $input "file_path" ""
|
||||
$workspaceRoot = Get-JsonField $input "workspace_roots[0]" ""
|
||||
|
||||
# Fallback to current directory if no workspace root
|
||||
if (Test-IsEmpty $workspaceRoot) {
|
||||
$workspaceRoot = Get-Location
|
||||
}
|
||||
|
||||
# Exit if no file_path
|
||||
if (Test-IsEmpty $filePath) {
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Use conversation_id as session_id, fallback to generation_id
|
||||
$sessionId = $conversationId
|
||||
if (Test-IsEmpty $sessionId) {
|
||||
$sessionId = $generationId
|
||||
}
|
||||
|
||||
# Exit if no session_id available
|
||||
if (Test-IsEmpty $sessionId) {
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Get worker port from settings with validation
|
||||
$workerPort = Get-WorkerPort
|
||||
|
||||
# Extract edits array, defaulting to [] if invalid
|
||||
$edits = @()
|
||||
if ($input.PSObject.Properties.Name -contains "edits") {
|
||||
$edits = $input.edits
|
||||
if ($null -eq $edits -or -not ($edits -is [array])) {
|
||||
$edits = @()
|
||||
}
|
||||
}
|
||||
|
||||
# Exit if no edits
|
||||
if ($edits.Count -eq 0) {
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Create a summary of the edits for the observation
|
||||
$editSummaries = @()
|
||||
foreach ($edit in $edits) {
|
||||
$oldStr = ""
|
||||
$newStr = ""
|
||||
|
||||
if ($edit.PSObject.Properties.Name -contains "old_string") {
|
||||
$oldStr = $edit.old_string
|
||||
if ($oldStr.Length -gt 50) {
|
||||
$oldStr = $oldStr.Substring(0, 50) + "..."
|
||||
}
|
||||
}
|
||||
|
||||
if ($edit.PSObject.Properties.Name -contains "new_string") {
|
||||
$newStr = $edit.new_string
|
||||
if ($newStr.Length -gt 50) {
|
||||
$newStr = $newStr.Substring(0, 50) + "..."
|
||||
}
|
||||
}
|
||||
|
||||
$editSummaries += "$oldStr → $newStr"
|
||||
}
|
||||
|
||||
$editSummary = $editSummaries -join "; "
|
||||
if ([string]::IsNullOrEmpty($editSummary)) {
|
||||
$editSummary = "File edited"
|
||||
}
|
||||
|
||||
# Treat file edits as a "write_file" tool usage
|
||||
$toolInput = @{
|
||||
file_path = $filePath
|
||||
edits = $edits
|
||||
}
|
||||
|
||||
$toolResponse = @{
|
||||
success = $true
|
||||
summary = $editSummary
|
||||
}
|
||||
|
||||
$payload = @{
|
||||
contentSessionId = $sessionId
|
||||
tool_name = "write_file"
|
||||
tool_input = $toolInput
|
||||
tool_response = $toolResponse
|
||||
cwd = $workspaceRoot
|
||||
}
|
||||
|
||||
# Ensure worker is running (with retries like claude-mem hooks)
|
||||
if (-not (Test-WorkerReady -Port $workerPort)) {
|
||||
# Worker not ready - exit gracefully (don't block Cursor)
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Send observation to claude-mem worker (fire-and-forget)
|
||||
$uri = "http://127.0.0.1:$workerPort/api/sessions/observations"
|
||||
|
||||
try {
|
||||
$bodyJson = ConvertTo-JsonCompact $payload
|
||||
Invoke-RestMethod -Uri $uri -Method Post -Body $bodyJson -ContentType "application/json" -TimeoutSec 5 -ErrorAction SilentlyContinue | Out-Null
|
||||
} catch {
|
||||
# Ignore errors - don't block Cursor
|
||||
}
|
||||
|
||||
exit 0
|
||||
@@ -1,112 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Save File Edit Hook for Cursor
|
||||
# Captures file edits made by the agent
|
||||
# Maps file edits to claude-mem observations
|
||||
|
||||
# Source common utilities
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/common.sh" 2>/dev/null || {
|
||||
echo "Warning: common.sh not found, using fallback functions" >&2
|
||||
}
|
||||
|
||||
# Check dependencies (non-blocking)
|
||||
check_dependencies >/dev/null 2>&1 || true
|
||||
|
||||
# Read JSON input from stdin with error handling
|
||||
input=$(read_json_input)
|
||||
|
||||
# Extract common fields with safe fallbacks
|
||||
conversation_id=$(json_get "$input" "conversation_id" "")
|
||||
generation_id=$(json_get "$input" "generation_id" "")
|
||||
file_path=$(json_get "$input" "file_path" "")
|
||||
workspace_root=$(json_get "$input" "workspace_roots[0]" "")
|
||||
|
||||
# Fallback to current directory if no workspace root
|
||||
if is_empty "$workspace_root"; then
|
||||
workspace_root=$(pwd)
|
||||
fi
|
||||
|
||||
# Exit if no file_path
|
||||
if is_empty "$file_path"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Use conversation_id as session_id, fallback to generation_id
|
||||
session_id="$conversation_id"
|
||||
if is_empty "$session_id"; then
|
||||
session_id="$generation_id"
|
||||
fi
|
||||
|
||||
# Exit if no session_id available
|
||||
if is_empty "$session_id"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get worker port from settings with validation
|
||||
worker_port=$(get_worker_port)
|
||||
|
||||
# Extract edits array, defaulting to [] if invalid
|
||||
edits=$(echo "$input" | jq -c '.edits // []' 2>/dev/null || echo "[]")
|
||||
|
||||
# Validate edits is a valid JSON array
|
||||
if ! echo "$edits" | jq 'type == "array"' 2>/dev/null | grep -q true; then
|
||||
edits="[]"
|
||||
fi
|
||||
|
||||
# Exit if no edits
|
||||
if [ "$edits" = "[]" ] || is_empty "$edits"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create a summary of the edits for the observation (with error handling)
|
||||
edit_summary=$(echo "$edits" | jq -r '[.[] | "\(.old_string[0:50] // "")... → \(.new_string[0:50] // "")..."] | join("; ")' 2>/dev/null || echo "File edited")
|
||||
|
||||
# Treat file edits as a "write_file" tool usage
|
||||
tool_input=$(jq -n \
|
||||
--arg path "$file_path" \
|
||||
--argjson edits "$edits" \
|
||||
'{
|
||||
file_path: $path,
|
||||
edits: $edits
|
||||
}' 2>/dev/null || echo '{}')
|
||||
|
||||
tool_response=$(jq -n \
|
||||
--arg summary "$edit_summary" \
|
||||
'{
|
||||
success: true,
|
||||
summary: $summary
|
||||
}' 2>/dev/null || echo '{}')
|
||||
|
||||
payload=$(jq -n \
|
||||
--arg sessionId "$session_id" \
|
||||
--arg cwd "$workspace_root" \
|
||||
--argjson toolInput "$tool_input" \
|
||||
--argjson toolResponse "$tool_response" \
|
||||
'{
|
||||
contentSessionId: $sessionId,
|
||||
tool_name: "write_file",
|
||||
tool_input: $toolInput,
|
||||
tool_response: $toolResponse,
|
||||
cwd: $cwd
|
||||
}' 2>/dev/null)
|
||||
|
||||
# Exit if payload creation failed
|
||||
if [ -z "$payload" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Ensure worker is running (with retries like claude-mem hooks)
|
||||
if ! ensure_worker_running "$worker_port"; then
|
||||
# Worker not ready - exit gracefully (don't block Cursor)
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Send observation to claude-mem worker (fire-and-forget)
|
||||
curl -s -X POST \
|
||||
"http://127.0.0.1:${worker_port}/api/sessions/observations" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$payload" \
|
||||
>/dev/null 2>&1 || true
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
# Save Observation Hook for Cursor (PowerShell)
|
||||
# Captures MCP tool usage and shell command execution
|
||||
# Maps to claude-mem's save-hook functionality
|
||||
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
|
||||
# Source common utilities
|
||||
$commonPath = Join-Path $PSScriptRoot "common.ps1"
|
||||
if (Test-Path $commonPath) {
|
||||
. $commonPath
|
||||
} else {
|
||||
Write-Warning "common.ps1 not found, using fallback functions"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Read JSON input from stdin with error handling
|
||||
$input = Read-JsonInput
|
||||
|
||||
# Extract common fields with safe fallbacks
|
||||
$conversationId = Get-JsonField $input "conversation_id" ""
|
||||
$generationId = Get-JsonField $input "generation_id" ""
|
||||
$workspaceRoot = Get-JsonField $input "workspace_roots[0]" ""
|
||||
|
||||
# Fallback to current directory if no workspace root
|
||||
if (Test-IsEmpty $workspaceRoot) {
|
||||
$workspaceRoot = Get-Location
|
||||
}
|
||||
|
||||
# Use conversation_id as session_id (stable across turns), fallback to generation_id
|
||||
$sessionId = $conversationId
|
||||
if (Test-IsEmpty $sessionId) {
|
||||
$sessionId = $generationId
|
||||
}
|
||||
|
||||
# Exit if no session_id available
|
||||
if (Test-IsEmpty $sessionId) {
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Get worker port from settings with validation
|
||||
$workerPort = Get-WorkerPort
|
||||
|
||||
# Determine hook type and extract relevant data
|
||||
$hookEvent = Get-JsonField $input "hook_event_name" ""
|
||||
|
||||
$payload = $null
|
||||
|
||||
if ($hookEvent -eq "afterMCPExecution") {
|
||||
# MCP tool execution
|
||||
$toolName = Get-JsonField $input "tool_name" ""
|
||||
|
||||
if (Test-IsEmpty $toolName) {
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Extract tool_input and tool_response, defaulting to {} if invalid
|
||||
$toolInput = @{}
|
||||
$toolResponse = @{}
|
||||
|
||||
if ($input.PSObject.Properties.Name -contains "tool_input") {
|
||||
$toolInput = $input.tool_input
|
||||
if ($null -eq $toolInput) { $toolInput = @{} }
|
||||
}
|
||||
|
||||
if ($input.PSObject.Properties.Name -contains "result_json") {
|
||||
$toolResponse = $input.result_json
|
||||
if ($null -eq $toolResponse) { $toolResponse = @{} }
|
||||
}
|
||||
|
||||
# Prepare observation payload
|
||||
$payload = @{
|
||||
contentSessionId = $sessionId
|
||||
tool_name = $toolName
|
||||
tool_input = $toolInput
|
||||
tool_response = $toolResponse
|
||||
cwd = $workspaceRoot
|
||||
}
|
||||
|
||||
} elseif ($hookEvent -eq "afterShellExecution") {
|
||||
# Shell command execution
|
||||
$command = Get-JsonField $input "command" ""
|
||||
|
||||
if (Test-IsEmpty $command) {
|
||||
exit 0
|
||||
}
|
||||
|
||||
$output = Get-JsonField $input "output" ""
|
||||
|
||||
# Treat shell commands as "Bash" tool usage
|
||||
$toolInput = @{ command = $command }
|
||||
$toolResponse = @{ output = $output }
|
||||
|
||||
$payload = @{
|
||||
contentSessionId = $sessionId
|
||||
tool_name = "Bash"
|
||||
tool_input = $toolInput
|
||||
tool_response = $toolResponse
|
||||
cwd = $workspaceRoot
|
||||
}
|
||||
|
||||
} else {
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Exit if payload creation failed
|
||||
if ($null -eq $payload) {
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Ensure worker is running (with retries like claude-mem hooks)
|
||||
if (-not (Test-WorkerReady -Port $workerPort)) {
|
||||
# Worker not ready - exit gracefully (don't block Cursor)
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Send observation to claude-mem worker (fire-and-forget)
|
||||
$uri = "http://127.0.0.1:$workerPort/api/sessions/observations"
|
||||
|
||||
try {
|
||||
$bodyJson = ConvertTo-JsonCompact $payload
|
||||
Invoke-RestMethod -Uri $uri -Method Post -Body $bodyJson -ContentType "application/json" -TimeoutSec 5 -ErrorAction SilentlyContinue | Out-Null
|
||||
} catch {
|
||||
# Ignore errors - don't block Cursor
|
||||
}
|
||||
|
||||
exit 0
|
||||
@@ -1,129 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Save Observation Hook for Cursor
|
||||
# Captures MCP tool usage and shell command execution
|
||||
# Maps to claude-mem's save-hook functionality
|
||||
|
||||
# Source common utilities
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/common.sh" 2>/dev/null || {
|
||||
echo "Warning: common.sh not found, using fallback functions" >&2
|
||||
}
|
||||
|
||||
# Check dependencies (non-blocking)
|
||||
check_dependencies >/dev/null 2>&1 || true
|
||||
|
||||
# Read JSON input from stdin with error handling
|
||||
input=$(read_json_input)
|
||||
|
||||
# Extract common fields with safe fallbacks
|
||||
conversation_id=$(json_get "$input" "conversation_id" "")
|
||||
generation_id=$(json_get "$input" "generation_id" "")
|
||||
workspace_root=$(json_get "$input" "workspace_roots[0]" "")
|
||||
|
||||
# Fallback to current directory if no workspace root
|
||||
if is_empty "$workspace_root"; then
|
||||
workspace_root=$(pwd)
|
||||
fi
|
||||
|
||||
# Use conversation_id as session_id (stable across turns), fallback to generation_id
|
||||
session_id="$conversation_id"
|
||||
if is_empty "$session_id"; then
|
||||
session_id="$generation_id"
|
||||
fi
|
||||
|
||||
# Exit if no session_id available
|
||||
if is_empty "$session_id"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get worker port from settings with validation
|
||||
worker_port=$(get_worker_port)
|
||||
|
||||
# Determine hook type and extract relevant data
|
||||
hook_event=$(json_get "$input" "hook_event_name" "")
|
||||
|
||||
if [ "$hook_event" = "afterMCPExecution" ]; then
|
||||
# MCP tool execution
|
||||
tool_name=$(json_get "$input" "tool_name" "")
|
||||
|
||||
if is_empty "$tool_name"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Extract tool_input and tool_response, defaulting to {} if invalid
|
||||
tool_input=$(echo "$input" | jq -c '.tool_input // {}' 2>/dev/null || echo "{}")
|
||||
tool_response=$(echo "$input" | jq -c '.result_json // {}' 2>/dev/null || echo "{}")
|
||||
|
||||
# Validate JSON
|
||||
if ! echo "$tool_input" | jq empty 2>/dev/null; then
|
||||
tool_input="{}"
|
||||
fi
|
||||
if ! echo "$tool_response" | jq empty 2>/dev/null; then
|
||||
tool_response="{}"
|
||||
fi
|
||||
|
||||
# Prepare observation payload
|
||||
payload=$(jq -n \
|
||||
--arg sessionId "$session_id" \
|
||||
--arg toolName "$tool_name" \
|
||||
--argjson toolInput "$tool_input" \
|
||||
--argjson toolResponse "$tool_response" \
|
||||
--arg cwd "$workspace_root" \
|
||||
'{
|
||||
contentSessionId: $sessionId,
|
||||
tool_name: $toolName,
|
||||
tool_input: $toolInput,
|
||||
tool_response: $toolResponse,
|
||||
cwd: $cwd
|
||||
}' 2>/dev/null)
|
||||
|
||||
elif [ "$hook_event" = "afterShellExecution" ]; then
|
||||
# Shell command execution
|
||||
command=$(json_get "$input" "command" "")
|
||||
|
||||
if is_empty "$command"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
output=$(json_get "$input" "output" "")
|
||||
|
||||
# Treat shell commands as "Bash" tool usage
|
||||
tool_input=$(jq -n --arg cmd "$command" '{command: $cmd}' 2>/dev/null || echo '{}')
|
||||
tool_response=$(jq -n --arg out "$output" '{output: $out}' 2>/dev/null || echo '{}')
|
||||
|
||||
payload=$(jq -n \
|
||||
--arg sessionId "$session_id" \
|
||||
--arg cwd "$workspace_root" \
|
||||
--argjson toolInput "$tool_input" \
|
||||
--argjson toolResponse "$tool_response" \
|
||||
'{
|
||||
contentSessionId: $sessionId,
|
||||
tool_name: "Bash",
|
||||
tool_input: $toolInput,
|
||||
tool_response: $toolResponse,
|
||||
cwd: $cwd
|
||||
}' 2>/dev/null)
|
||||
else
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Exit if payload creation failed
|
||||
if [ -z "$payload" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Ensure worker is running (with retries like claude-mem hooks)
|
||||
if ! ensure_worker_running "$worker_port"; then
|
||||
# Worker not ready - exit gracefully (don't block Cursor)
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Send observation to claude-mem worker (fire-and-forget)
|
||||
curl -s -X POST \
|
||||
"http://127.0.0.1:${worker_port}/api/sessions/observations" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$payload" \
|
||||
>/dev/null 2>&1 || true
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
# Session Initialization Hook for Cursor (PowerShell)
|
||||
# Maps to claude-mem's new-hook functionality
|
||||
# Initializes a new session when a prompt is submitted
|
||||
#
|
||||
# NOTE: This hook runs as part of beforeSubmitPrompt and MUST output valid JSON
|
||||
# with at least {"continue": true} to allow prompt submission.
|
||||
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
|
||||
# Source common utilities
|
||||
$commonPath = Join-Path $PSScriptRoot "common.ps1"
|
||||
if (Test-Path $commonPath) {
|
||||
. $commonPath
|
||||
} else {
|
||||
# Fallback - output continue and exit
|
||||
Write-Output '{"continue": true}'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Read JSON input from stdin with error handling
|
||||
$input = Read-JsonInput
|
||||
|
||||
# Extract common fields with safe fallbacks
|
||||
$conversationId = Get-JsonField $input "conversation_id" ""
|
||||
$generationId = Get-JsonField $input "generation_id" ""
|
||||
$prompt = Get-JsonField $input "prompt" ""
|
||||
$workspaceRoot = Get-JsonField $input "workspace_roots[0]" ""
|
||||
|
||||
# Fallback to current directory if no workspace root
|
||||
if (Test-IsEmpty $workspaceRoot) {
|
||||
$workspaceRoot = Get-Location
|
||||
}
|
||||
|
||||
# Get project name from workspace root
|
||||
$projectName = Get-ProjectName $workspaceRoot
|
||||
|
||||
# Use conversation_id as session_id (stable across turns), fallback to generation_id
|
||||
$sessionId = $conversationId
|
||||
if (Test-IsEmpty $sessionId) {
|
||||
$sessionId = $generationId
|
||||
}
|
||||
|
||||
# Exit gracefully if no session_id available (still allow prompt)
|
||||
if (Test-IsEmpty $sessionId) {
|
||||
Write-Output '{"continue": true}'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Get worker port from settings with validation
|
||||
$workerPort = Get-WorkerPort
|
||||
|
||||
# Ensure worker is running (with retries like claude-mem hooks)
|
||||
if (-not (Test-WorkerReady -Port $workerPort)) {
|
||||
# Worker not ready - still allow prompt to continue
|
||||
Write-Output '{"continue": true}'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Strip leading slash from commands for memory agent (parity with new-hook.ts)
|
||||
# /review 101 → review 101 (more semantic for observations)
|
||||
$cleanedPrompt = $prompt
|
||||
if (-not [string]::IsNullOrEmpty($prompt) -and $prompt.StartsWith("/")) {
|
||||
$cleanedPrompt = $prompt.Substring(1)
|
||||
}
|
||||
|
||||
# Initialize session via HTTP - handles DB operations and privacy checks
|
||||
$payload = @{
|
||||
contentSessionId = $sessionId
|
||||
project = $projectName
|
||||
prompt = $cleanedPrompt
|
||||
}
|
||||
|
||||
# Send request to worker (fire-and-forget, don't wait for response)
|
||||
$uri = "http://127.0.0.1:$workerPort/api/sessions/init"
|
||||
Send-HttpPostAsync -Uri $uri -Body $payload
|
||||
|
||||
# Always allow prompt to continue
|
||||
Write-Output '{"continue": true}'
|
||||
exit 0
|
||||
@@ -1,93 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Session Initialization Hook for Cursor
|
||||
# Maps to claude-mem's new-hook functionality
|
||||
# Initializes a new session when a prompt is submitted
|
||||
#
|
||||
# NOTE: This hook runs as part of beforeSubmitPrompt and MUST output valid JSON
|
||||
# with at least {"continue": true} to allow prompt submission.
|
||||
|
||||
# Source common utilities
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/common.sh" 2>/dev/null || {
|
||||
# Fallback - output continue and exit
|
||||
echo '{"continue": true}'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Check dependencies (non-blocking - just warn)
|
||||
check_dependencies >/dev/null 2>&1 || true
|
||||
|
||||
# Read JSON input from stdin with error handling
|
||||
input=$(read_json_input)
|
||||
|
||||
# Extract common fields with safe fallbacks
|
||||
conversation_id=$(json_get "$input" "conversation_id" "")
|
||||
generation_id=$(json_get "$input" "generation_id" "")
|
||||
prompt=$(json_get "$input" "prompt" "")
|
||||
workspace_root=$(json_get "$input" "workspace_roots[0]" "")
|
||||
|
||||
# Fallback to current directory if no workspace root
|
||||
if is_empty "$workspace_root"; then
|
||||
workspace_root=$(pwd)
|
||||
fi
|
||||
|
||||
# Get project name from workspace root
|
||||
project_name=$(get_project_name "$workspace_root")
|
||||
|
||||
# Use conversation_id as session_id (stable across turns), fallback to generation_id
|
||||
session_id="$conversation_id"
|
||||
if is_empty "$session_id"; then
|
||||
session_id="$generation_id"
|
||||
fi
|
||||
|
||||
# Exit gracefully if no session_id available (still allow prompt)
|
||||
if is_empty "$session_id"; then
|
||||
echo '{"continue": true}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get worker port from settings with validation
|
||||
worker_port=$(get_worker_port)
|
||||
|
||||
# Ensure worker is running (with retries like claude-mem hooks)
|
||||
if ! ensure_worker_running "$worker_port"; then
|
||||
# Worker not ready - still allow prompt to continue
|
||||
echo '{"continue": true}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Strip leading slash from commands for memory agent (parity with new-hook.ts)
|
||||
# /review 101 → review 101 (more semantic for observations)
|
||||
cleaned_prompt="$prompt"
|
||||
if [ -n "$prompt" ] && [ "${prompt:0:1}" = "/" ]; then
|
||||
cleaned_prompt="${prompt:1}"
|
||||
fi
|
||||
|
||||
# Initialize session via HTTP - handles DB operations and privacy checks
|
||||
payload=$(jq -n \
|
||||
--arg sessionId "$session_id" \
|
||||
--arg project "$project_name" \
|
||||
--arg promptText "$cleaned_prompt" \
|
||||
'{
|
||||
contentSessionId: $sessionId,
|
||||
project: $project,
|
||||
prompt: $promptText
|
||||
}' 2>/dev/null)
|
||||
|
||||
# Exit if payload creation failed (still allow prompt)
|
||||
if [ -z "$payload" ]; then
|
||||
echo '{"continue": true}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Send request to worker (fire-and-forget, don't wait for response)
|
||||
curl -s -X POST \
|
||||
"http://127.0.0.1:${worker_port}/api/sessions/init" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$payload" \
|
||||
>/dev/null 2>&1 &
|
||||
|
||||
# Always allow prompt to continue
|
||||
echo '{"continue": true}'
|
||||
exit 0
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
# Session Summary Hook for Cursor (stop) - PowerShell
|
||||
# Called when agent loop ends
|
||||
#
|
||||
# This hook:
|
||||
# 1. Generates session summary
|
||||
# 2. Updates context file for next session
|
||||
#
|
||||
# Output: Empty JSON {} or {"followup_message": "..."} for auto-iteration
|
||||
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
|
||||
# Source common utilities
|
||||
$commonPath = Join-Path $PSScriptRoot "common.ps1"
|
||||
if (Test-Path $commonPath) {
|
||||
. $commonPath
|
||||
} else {
|
||||
Write-Output '{}'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Read JSON input from stdin with error handling
|
||||
$input = Read-JsonInput
|
||||
|
||||
# Extract common fields with safe fallbacks
|
||||
$conversationId = Get-JsonField $input "conversation_id" ""
|
||||
$generationId = Get-JsonField $input "generation_id" ""
|
||||
$workspaceRoot = Get-JsonField $input "workspace_roots[0]" ""
|
||||
$status = Get-JsonField $input "status" "completed"
|
||||
|
||||
# Fallback workspace to current directory
|
||||
if (Test-IsEmpty $workspaceRoot) {
|
||||
$workspaceRoot = Get-Location
|
||||
}
|
||||
|
||||
# Get project name
|
||||
$projectName = Get-ProjectName $workspaceRoot
|
||||
|
||||
# Use conversation_id as session_id, fallback to generation_id
|
||||
$sessionId = $conversationId
|
||||
if (Test-IsEmpty $sessionId) {
|
||||
$sessionId = $generationId
|
||||
}
|
||||
|
||||
# Exit if no session_id available
|
||||
if (Test-IsEmpty $sessionId) {
|
||||
Write-Output '{}'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Get worker port from settings with validation
|
||||
$workerPort = Get-WorkerPort
|
||||
|
||||
# Ensure worker is running (with retries)
|
||||
if (-not (Test-WorkerReady -Port $workerPort)) {
|
||||
Write-Output '{}'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# 1. Request summary generation (fire-and-forget)
|
||||
# Note: Cursor doesn't provide transcript_path like Claude Code does,
|
||||
# so we can't extract last_user_message and last_assistant_message.
|
||||
$summaryPayload = @{
|
||||
contentSessionId = $sessionId
|
||||
last_user_message = ""
|
||||
last_assistant_message = ""
|
||||
}
|
||||
|
||||
$summaryUri = "http://127.0.0.1:$workerPort/api/sessions/summarize"
|
||||
Send-HttpPostAsync -Uri $summaryUri -Body $summaryPayload
|
||||
|
||||
# 2. Update context file for next session
|
||||
# Fetch fresh context (includes observations from this session)
|
||||
$projectEncoded = Get-UrlEncodedString $projectName
|
||||
$contextUri = "http://127.0.0.1:$workerPort/api/context/inject?project=$projectEncoded"
|
||||
$context = Get-HttpResponse -Uri $contextUri
|
||||
|
||||
if (-not [string]::IsNullOrEmpty($context)) {
|
||||
$rulesDir = Join-Path $workspaceRoot ".cursor\rules"
|
||||
$rulesFile = Join-Path $rulesDir "claude-mem-context.mdc"
|
||||
|
||||
# Create rules directory if it doesn't exist
|
||||
if (-not (Test-Path $rulesDir)) {
|
||||
New-Item -ItemType Directory -Path $rulesDir -Force | Out-Null
|
||||
}
|
||||
|
||||
# Write context as a Cursor rule with alwaysApply: true
|
||||
$ruleContent = @"
|
||||
---
|
||||
alwaysApply: true
|
||||
description: "Claude-mem context from past sessions (auto-updated)"
|
||||
---
|
||||
|
||||
# Memory Context from Past Sessions
|
||||
|
||||
The following context is from claude-mem, a persistent memory system that tracks your coding sessions.
|
||||
|
||||
$context
|
||||
|
||||
---
|
||||
*Updated after last session. Use claude-mem's MCP search tools for more detailed queries.*
|
||||
"@
|
||||
|
||||
Set-Content -Path $rulesFile -Value $ruleContent -Encoding UTF8 -Force
|
||||
}
|
||||
|
||||
# Output empty JSON - no followup message
|
||||
Write-Output '{}'
|
||||
exit 0
|
||||
@@ -1,111 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Session Summary Hook for Cursor (stop)
|
||||
# Called when agent loop ends
|
||||
#
|
||||
# This hook:
|
||||
# 1. Generates session summary
|
||||
# 2. Updates context file for next session
|
||||
#
|
||||
# Output: Empty JSON {} or {"followup_message": "..."} for auto-iteration
|
||||
|
||||
# Source common utilities
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/common.sh" 2>/dev/null || {
|
||||
echo '{}'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Check dependencies (non-blocking)
|
||||
check_dependencies >/dev/null 2>&1 || true
|
||||
|
||||
# Read JSON input from stdin with error handling
|
||||
input=$(read_json_input)
|
||||
|
||||
# Extract common fields with safe fallbacks
|
||||
conversation_id=$(json_get "$input" "conversation_id" "")
|
||||
generation_id=$(json_get "$input" "generation_id" "")
|
||||
workspace_root=$(json_get "$input" "workspace_roots[0]" "")
|
||||
status=$(json_get "$input" "status" "completed")
|
||||
|
||||
# Fallback workspace to current directory
|
||||
if is_empty "$workspace_root"; then
|
||||
workspace_root=$(pwd)
|
||||
fi
|
||||
|
||||
# Get project name
|
||||
project_name=$(get_project_name "$workspace_root")
|
||||
|
||||
# Use conversation_id as session_id, fallback to generation_id
|
||||
session_id="$conversation_id"
|
||||
if is_empty "$session_id"; then
|
||||
session_id="$generation_id"
|
||||
fi
|
||||
|
||||
# Exit if no session_id available
|
||||
if is_empty "$session_id"; then
|
||||
echo '{}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get worker port from settings with validation
|
||||
worker_port=$(get_worker_port)
|
||||
|
||||
# Ensure worker is running (with retries)
|
||||
if ! ensure_worker_running "$worker_port"; then
|
||||
echo '{}'
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 1. Request summary generation (fire-and-forget)
|
||||
# Note: Cursor doesn't provide transcript_path like Claude Code does,
|
||||
# so we can't extract last_user_message and last_assistant_message.
|
||||
payload=$(jq -n \
|
||||
--arg sessionId "$session_id" \
|
||||
'{
|
||||
contentSessionId: $sessionId,
|
||||
last_user_message: "",
|
||||
last_assistant_message: ""
|
||||
}' 2>/dev/null)
|
||||
|
||||
if [ -n "$payload" ]; then
|
||||
curl -s -X POST \
|
||||
"http://127.0.0.1:${worker_port}/api/sessions/summarize" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$payload" \
|
||||
>/dev/null 2>&1 &
|
||||
fi
|
||||
|
||||
# 2. Update context file for next session
|
||||
# Fetch fresh context (includes observations from this session)
|
||||
project_encoded=$(url_encode "$project_name")
|
||||
context=$(curl -s -f "http://127.0.0.1:${worker_port}/api/context/inject?project=${project_encoded}" 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$context" ]; then
|
||||
rules_dir="${workspace_root}/.cursor/rules"
|
||||
rules_file="${rules_dir}/claude-mem-context.mdc"
|
||||
|
||||
# Create rules directory if it doesn't exist
|
||||
mkdir -p "$rules_dir" 2>/dev/null || true
|
||||
|
||||
# Write context as a Cursor rule with alwaysApply: true
|
||||
cat > "$rules_file" 2>/dev/null << EOF
|
||||
---
|
||||
alwaysApply: true
|
||||
description: "Claude-mem context from past sessions (auto-updated)"
|
||||
---
|
||||
|
||||
# Memory Context from Past Sessions
|
||||
|
||||
The following context is from claude-mem, a persistent memory system that tracks your coding sessions.
|
||||
|
||||
${context}
|
||||
|
||||
---
|
||||
*Updated after last session. Use claude-mem's MCP search tools for more detailed queries.*
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Output empty JSON - no followup message
|
||||
echo '{}'
|
||||
exit 0
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
# User Message Hook for Cursor (PowerShell)
|
||||
# Displays context information to the user
|
||||
# Maps to claude-mem's user-message-hook functionality
|
||||
# Note: Cursor doesn't have a direct equivalent, but we can output to stderr
|
||||
# for visibility in Cursor's output channels
|
||||
#
|
||||
# This is an OPTIONAL hook. It can be added to beforeSubmitPrompt if desired,
|
||||
# but may be verbose since it runs on every prompt submission.
|
||||
|
||||
$ErrorActionPreference = "SilentlyContinue"
|
||||
|
||||
# Read JSON input from stdin (if any)
|
||||
$inputJson = $null
|
||||
try {
|
||||
$inputText = [Console]::In.ReadToEnd()
|
||||
if (-not [string]::IsNullOrEmpty($inputText)) {
|
||||
$inputJson = $inputText | ConvertFrom-Json -ErrorAction SilentlyContinue
|
||||
}
|
||||
} catch {
|
||||
$inputJson = $null
|
||||
}
|
||||
|
||||
# Extract workspace root
|
||||
$workspaceRoot = ""
|
||||
if ($null -ne $inputJson -and $inputJson.PSObject.Properties.Name -contains "workspace_roots") {
|
||||
$wsRoots = $inputJson.workspace_roots
|
||||
if ($null -ne $wsRoots -and $wsRoots.Count -gt 0) {
|
||||
$workspaceRoot = $wsRoots[0]
|
||||
}
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrEmpty($workspaceRoot)) {
|
||||
$workspaceRoot = Get-Location
|
||||
}
|
||||
|
||||
# Get project name
|
||||
$projectName = Split-Path $workspaceRoot -Leaf
|
||||
if ([string]::IsNullOrEmpty($projectName)) {
|
||||
$projectName = "unknown-project"
|
||||
}
|
||||
|
||||
# Get worker port from settings
|
||||
$settingsPath = Join-Path $env:USERPROFILE ".claude-mem\settings.json"
|
||||
$workerPort = 37777
|
||||
|
||||
if (Test-Path $settingsPath) {
|
||||
try {
|
||||
$settings = Get-Content $settingsPath -Raw | ConvertFrom-Json
|
||||
if ($settings.CLAUDE_MEM_WORKER_PORT) {
|
||||
$workerPort = [int]$settings.CLAUDE_MEM_WORKER_PORT
|
||||
}
|
||||
} catch {
|
||||
# Use default
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure worker is running
|
||||
$maxRetries = 75
|
||||
$workerReady = $false
|
||||
|
||||
for ($i = 0; $i -lt $maxRetries; $i++) {
|
||||
try {
|
||||
$response = Invoke-RestMethod -Uri "http://127.0.0.1:$workerPort/api/readiness" -Method Get -TimeoutSec 1 -ErrorAction Stop
|
||||
$workerReady = $true
|
||||
break
|
||||
} catch {
|
||||
Start-Sleep -Milliseconds 200
|
||||
}
|
||||
}
|
||||
|
||||
# If worker not ready, exit silently
|
||||
if (-not $workerReady) {
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Fetch formatted context from worker API (with colors)
|
||||
$projectEncoded = [System.Uri]::EscapeDataString($projectName)
|
||||
$contextUrl = "http://127.0.0.1:$workerPort/api/context/inject?project=$projectEncoded&colors=true"
|
||||
|
||||
$output = $null
|
||||
try {
|
||||
$output = Invoke-RestMethod -Uri $contextUrl -Method Get -TimeoutSec 5 -ErrorAction Stop
|
||||
} catch {
|
||||
$output = $null
|
||||
}
|
||||
|
||||
# Output to stderr for visibility (parity with user-message-hook.ts)
|
||||
# Note: Cursor may not display stderr the same way Claude Code does,
|
||||
# but this is the best we can do without direct UI integration
|
||||
if (-not [string]::IsNullOrEmpty($output)) {
|
||||
[Console]::Error.WriteLine("")
|
||||
[Console]::Error.WriteLine("📝 Claude-Mem Context Loaded")
|
||||
[Console]::Error.WriteLine(" ℹ️ Viewing context from past sessions")
|
||||
[Console]::Error.WriteLine("")
|
||||
[Console]::Error.WriteLine($output)
|
||||
[Console]::Error.WriteLine("")
|
||||
[Console]::Error.WriteLine("💡 Tip: Wrap content with <private> ... </private> to prevent storing sensitive information.")
|
||||
[Console]::Error.WriteLine("💬 Community: https://discord.gg/J4wttp9vDu")
|
||||
[Console]::Error.WriteLine("📺 Web Viewer: http://localhost:$workerPort/")
|
||||
[Console]::Error.WriteLine("")
|
||||
}
|
||||
|
||||
exit 0
|
||||
@@ -1,70 +0,0 @@
|
||||
#!/bin/bash
|
||||
# User Message Hook for Cursor
|
||||
# Displays context information to the user
|
||||
# Maps to claude-mem's user-message-hook functionality
|
||||
# Note: Cursor doesn't have a direct equivalent, but we can output to stderr
|
||||
# for visibility in Cursor's output channels
|
||||
#
|
||||
# This is an OPTIONAL hook. It can be added to beforeSubmitPrompt if desired,
|
||||
# but may be verbose since it runs on every prompt submission.
|
||||
|
||||
# Read JSON input from stdin (if any)
|
||||
input=$(cat 2>/dev/null || echo "{}")
|
||||
|
||||
# Extract workspace root
|
||||
workspace_root=$(echo "$input" | jq -r '.workspace_roots[0] // empty' 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$workspace_root" ]; then
|
||||
workspace_root=$(pwd)
|
||||
fi
|
||||
|
||||
# Get project name
|
||||
project_name=$(basename "$workspace_root" 2>/dev/null || echo "unknown-project")
|
||||
|
||||
# Get worker port from settings
|
||||
data_dir="${HOME}/.claude-mem"
|
||||
settings_file="${data_dir}/settings.json"
|
||||
worker_port="37777"
|
||||
|
||||
if [ -f "$settings_file" ]; then
|
||||
worker_port=$(jq -r '.CLAUDE_MEM_WORKER_PORT // "37777"' "$settings_file" 2>/dev/null || echo "37777")
|
||||
fi
|
||||
|
||||
# Ensure worker is running
|
||||
max_retries=75
|
||||
retry_count=0
|
||||
while [ $retry_count -lt $max_retries ]; do
|
||||
if curl -s -f "http://127.0.0.1:${worker_port}/api/readiness" > /dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 0.2
|
||||
retry_count=$((retry_count + 1))
|
||||
done
|
||||
|
||||
# If worker not ready, exit silently
|
||||
if [ $retry_count -eq $max_retries ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Fetch formatted context from worker API (with colors)
|
||||
context_url="http://127.0.0.1:${worker_port}/api/context/inject?project=${project_name}&colors=true"
|
||||
output=$(curl -s -f "$context_url" 2>/dev/null || echo "")
|
||||
|
||||
# Output to stderr for visibility (parity with user-message-hook.ts)
|
||||
# Note: Cursor may not display stderr the same way Claude Code does,
|
||||
# but this is the best we can do without direct UI integration
|
||||
if [ -n "$output" ]; then
|
||||
echo "" >&2
|
||||
echo "📝 Claude-Mem Context Loaded" >&2
|
||||
echo " ℹ️ Viewing context from past sessions" >&2
|
||||
echo "" >&2
|
||||
echo "$output" >&2
|
||||
echo "" >&2
|
||||
echo "💡 Tip: Wrap content with <private> ... </private> to prevent storing sensitive information." >&2
|
||||
echo "💬 Community: https://discord.gg/J4wttp9vDu" >&2
|
||||
echo "📺 Web Viewer: http://localhost:${worker_port}/" >&2
|
||||
echo "" >&2
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# Context Hook Investigation Report
|
||||
|
||||
**Date:** 2026-01-05
|
||||
**Branch:** `feature/no-more-hook-files`
|
||||
**Status:** Partial fix committed, additional issues identified
|
||||
|
||||
## Problem
|
||||
|
||||
User reported no startup context appearing when testing the new unified CLI hook architecture.
|
||||
|
||||
## Root Cause Identified
|
||||
|
||||
**SessionStart hooks don't receive stdin data from Claude Code.**
|
||||
|
||||
The unified CLI architecture assumed all hooks receive stdin JSON data. When `readJsonFromStdin()` returns `undefined` for SessionStart, the platform adapters crashed:
|
||||
|
||||
```
|
||||
TypeError: undefined is not an object (evaluating 'e.session_id')
|
||||
```
|
||||
|
||||
**Location:** `src/cli/adapters/claude-code.ts:6` and `src/cli/adapters/cursor.ts:7`
|
||||
|
||||
The adapters did `const r = raw as any;` then accessed `r.session_id`, which fails when `raw` is `undefined`.
|
||||
|
||||
## Fix Applied
|
||||
|
||||
Changed both adapters to handle undefined input:
|
||||
|
||||
```typescript
|
||||
// Before
|
||||
const r = raw as any;
|
||||
|
||||
// After
|
||||
const r = (raw ?? {}) as any;
|
||||
```
|
||||
|
||||
**Commit:** `78c2a0ef` - Pushed to `feature/no-more-hook-files`
|
||||
|
||||
## Additional Issue Discovered (Not Yet Fixed)
|
||||
|
||||
There's a **path mismatch** in the hooks.json that may cause issues:
|
||||
|
||||
- hooks.json references: `${CLAUDE_PLUGIN_ROOT}/scripts/worker-service.cjs`
|
||||
- Actual file location: `${CLAUDE_PLUGIN_ROOT}/plugin/scripts/worker-service.cjs`
|
||||
|
||||
The marketplace sync copies the whole repo structure, so files end up in a `plugin/` subdirectory. Need to verify what `CLAUDE_PLUGIN_ROOT` resolves to and whether the paths are correct.
|
||||
|
||||
## Verification Needed
|
||||
|
||||
1. Start a new Claude Code session and verify context appears
|
||||
2. Check that `CLAUDE_PLUGIN_ROOT` points to correct directory
|
||||
3. Verify hooks.json paths match actual file locations
|
||||
|
||||
## Files Changed
|
||||
|
||||
- `src/cli/adapters/claude-code.ts` - Added null coalescing for stdin
|
||||
- `src/cli/adapters/cursor.ts` - Added null coalescing for stdin
|
||||
- `plugin/scripts/worker-service.cjs` - Rebuilt with fix
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Test the fix in a live Claude Code session
|
||||
2. Investigate the `CLAUDE_PLUGIN_ROOT` path resolution
|
||||
3. Fix paths in hooks.json if needed
|
||||
+4
-24
@@ -3,35 +3,15 @@
|
||||
|
||||
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||
|
||||
### Jan 5, 2026
|
||||
### Jan 3, 2026
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #37962 | 8:18 PM | 🔴 | Fixed SessionStart hook crash when stdin is undefined | ~440 |
|
||||
| #36651 | 11:03 PM | 🔵 | Critical Design Decision Documented: Memory Session ID Must Never Equal Content Session ID | ~481 |
|
||||
|
||||
### Jan 7, 2026
|
||||
### Jan 8, 2026
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #38475 | 10:31 PM | ⚖️ | Log Level Philosophy: Error-Adjacent Messages Promoted to ERROR | ~412 |
|
||||
| #38467 | 10:29 PM | ⚖️ | Log Level Audit Strategy: Tighten ERROR Messages for Runtime Issue Discovery | ~464 |
|
||||
| #38445 | 10:26 PM | 🔵 | DEBUG Level Logging: SessionRoutes Line 211 Error-During-Recovery Pattern | ~500 |
|
||||
| #38442 | 10:25 PM | 🔵 | Log Audit Contains 409 Source File Log Entries | ~293 |
|
||||
| #38441 | " | 🔵 | DEBUG-level logging patterns for diagnostics and non-critical operations | ~595 |
|
||||
| #38439 | " | 🔵 | Log Audit Shows SessionRoutes.ts Has Two WARN Messages for Generator Failures | ~493 |
|
||||
| #38438 | " | 🔵 | WARN Level Log Patterns: Graceful Degradation and Fallback Behavior | ~539 |
|
||||
| #38437 | 10:24 PM | 🔵 | Claude-mem core functionality and logging patterns identified | ~710 |
|
||||
| #38428 | " | 🔵 | Log level audit report structure and content examined | ~559 |
|
||||
| #38425 | " | ⚖️ | Log Level Architecture: Fail-Critical Over Fail-Fast for Chroma | ~467 |
|
||||
| #38416 | 10:22 PM | 🔵 | ChromaDB Is Critical Not Optional - Log Audit Findings Challenged | ~405 |
|
||||
| #38405 | 10:07 PM | ⚖️ | DEBUG Log Level Analysis - One Message Requires WARN Promotion | ~819 |
|
||||
| #38404 | 10:06 PM | ⚖️ | Log Level Audit Analysis - WARN to ERROR Promotion Criteria Established | ~769 |
|
||||
| #38403 | 10:04 PM | 🔵 | Log Level Audit - INFO and DEBUG Level Messages Catalogued | ~688 |
|
||||
| #38402 | 10:03 PM | 🔵 | Log Level Audit Report Analysis - Critical Error Messages Identified | ~642 |
|
||||
| #38401 | 10:00 PM | 🔵 | Enhanced Audit Report Reveals Error Logging Patterns and Message Extraction Issues | ~498 |
|
||||
| #38394 | 9:58 PM | ✅ | Created Log Level Audit Report Documentation | ~319 |
|
||||
| #38393 | " | ✅ | Enhanced Log Audit Report Format with Component Tags and Full Logger Calls | ~393 |
|
||||
| #38386 | 9:56 PM | ✅ | Log Audit Report Generated and Saved to Documentation | ~442 |
|
||||
| #38385 | " | ✅ | Log Level Audit Report Saved to Documentation | ~379 |
|
||||
| #38251 | 7:46 PM | 🔵 | Comprehensive Windows Platform Issues Report | ~982 |
|
||||
| #38731 | 6:49 PM | 🟣 | Comprehensive Sonnet vs Opus Behavioral Analysis Report Generated and Saved | ~700 |
|
||||
</claude-mem-context>
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "9.0.1",
|
||||
"version": "9.0.2",
|
||||
"description": "Memory compression system for Claude Code - persist context across sessions",
|
||||
"keywords": [
|
||||
"claude",
|
||||
|
||||
@@ -3,101 +3,21 @@
|
||||
|
||||
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||
|
||||
### Dec 28, 2025
|
||||
### Nov 6, 2025
|
||||
|
||||
**plugin.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #33503 | 10:43 PM | 🔵 | Plugin metadata contains no skill or MCP server definitions | ~216 |
|
||||
| #33486 | 10:35 PM | ✅ | Committed Version Bump to 8.2.6 | ~212 |
|
||||
| #33484 | " | ✅ | Verified Version Synchronization at 8.2.6 | ~173 |
|
||||
| #33483 | " | ✅ | Synchronized Plugin Metadata Version to 8.2.6 | ~183 |
|
||||
| #33478 | 10:34 PM | 🔵 | Plugin Metadata Configuration Structure | ~195 |
|
||||
| #33477 | " | 🔵 | Current Version Across Package Files is 8.2.5 | ~185 |
|
||||
| #4091 | 1:12 PM | 🔵 | Claude Plugin Configuration Structure | ~170 |
|
||||
|
||||
### Dec 29, 2025
|
||||
### Nov 9, 2025
|
||||
|
||||
**plugin.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #34368 | 11:30 PM | ✅ | Bumped version to 8.5.1 for patch release | ~218 |
|
||||
| #34351 | 11:13 PM | ✅ | Version 8.5.0 Release Committed to Git | ~402 |
|
||||
| #34349 | 11:12 PM | ✅ | Version 8.5.0 Synchronized Across All Files | ~220 |
|
||||
| #33997 | 7:10 PM | ✅ | Version Bumped to 8.2.10 | ~179 |
|
||||
| #33995 | " | ✅ | Version Bump to 8.2.10 in plugin.json | ~250 |
|
||||
| #33992 | " | 🔵 | Version Consistency Verified Across Package Files | ~133 |
|
||||
| #33956 | 6:45 PM | 🔵 | Version 8.2.9 Release Verification Complete | ~227 |
|
||||
| #33950 | 6:42 PM | ✅ | Version Sync to 8.2.9 in Plugin Manifest | ~172 |
|
||||
| #33948 | " | 🔵 | Current Version State Across Project Files | ~195 |
|
||||
| #33854 | 4:33 PM | 🔵 | Worker service build and execution architecture mapped | ~509 |
|
||||
| #33840 | 4:24 PM | ✅ | Version 8.2.8 Changes Committed to Git | ~233 |
|
||||
| #33838 | 4:23 PM | ✅ | Patch Release Version Bump to 8.2.8 Completed | ~216 |
|
||||
| #33836 | " | 🔵 | Current Version State Before Patch Release | ~125 |
|
||||
| #5739 | 4:43 PM | 🔵 | Plugin Metadata Configuration | ~199 |
|
||||
|
||||
### Dec 31, 2025
|
||||
### Dec 8, 2025
|
||||
|
||||
**plugin.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #34723 | 4:51 PM | ✅ | Version 8.5.2 Release Committed and Tagged | ~198 |
|
||||
| #34722 | " | ✅ | Synchronized version to 8.5.2 in plugin metadata | ~194 |
|
||||
| #34721 | " | ✅ | Version 8.5.2 Synchronized Across All Configuration Files | ~186 |
|
||||
| #34718 | 4:50 PM | 🔵 | Plugin Configuration Version Check | ~191 |
|
||||
| #34715 | " | 🔵 | Version 8.5.1 confirmed across all package configuration files | ~181 |
|
||||
|
||||
### Jan 1, 2026
|
||||
|
||||
**plugin.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #35680 | 11:43 PM | 🟣 | Automated version 8.5.3 release workflow completed | ~470 |
|
||||
| #35674 | 11:41 PM | 🔵 | Release staged files identified for version 8.5.3 | ~287 |
|
||||
| #35673 | " | ✅ | Staged version files for commit | ~155 |
|
||||
| #35672 | " | ✅ | Version bumped to 8.5.3 in plugin configuration | ~231 |
|
||||
| #35670 | 11:40 PM | 🔵 | Current plugin version identified as 8.5.2 | ~240 |
|
||||
|
||||
### Jan 2, 2026
|
||||
|
||||
**plugin.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #35944 | 2:57 PM | 🔵 | Current Project Version 8.5.4 Verified | ~200 |
|
||||
| #35926 | 2:53 PM | ✅ | Committed Version Bump to 8.5.4 | ~210 |
|
||||
| #35924 | " | ✅ | Verified Version Consistency Across All Files | ~186 |
|
||||
| #35923 | " | ✅ | Updated Plugin Configuration Version to 8.5.4 | ~167 |
|
||||
| #35920 | 2:52 PM | 🔵 | Plugin.json Version Configuration | ~200 |
|
||||
| #35917 | " | 🔵 | Current Version Identified as 8.5.3 | ~170 |
|
||||
| #35905 | 2:49 PM | 🔵 | Current Version is 8.5.3 Across All Package Files | ~193 |
|
||||
|
||||
### Jan 3, 2026
|
||||
|
||||
**plugin.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #36669 | 11:37 PM | ✅ | Merge conflicts resolved automatically - only 5 metadata files modified | ~345 |
|
||||
|
||||
### Jan 4, 2026
|
||||
|
||||
**plugin.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #36924 | 2:25 AM | ✅ | Merged fix/pr-538-followups branch into main with comprehensive updates | ~481 |
|
||||
| #36922 | " | ✅ | Version Bump Committed to Git | ~261 |
|
||||
| #36920 | " | 🔵 | Version 8.5.8 already set across all configuration files | ~203 |
|
||||
| #36917 | 2:24 AM | ✅ | Version bumped to 8.5.8 in plugin.json | ~204 |
|
||||
| #36912 | " | 🔵 | Current version identified as 8.5.7 across all package files | ~190 |
|
||||
| #36700 | 12:00 AM | ✅ | Committed Version 8.5.7 Across All Package Files | ~260 |
|
||||
| #36699 | " | ✅ | Version Bumped to 8.5.7 Across All Package Files | ~271 |
|
||||
| #36697 | " | ✅ | Version Bumped to 8.5.7 for Patch Release | ~243 |
|
||||
|
||||
### Jan 5, 2026
|
||||
|
||||
**plugin.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #38093 | 10:44 PM | ✅ | Committed version bump to 9.0.0 | ~217 |
|
||||
| #38091 | " | ✅ | Version bumped to 9.0.0 in plugin.json | ~187 |
|
||||
| #38087 | 10:43 PM | 🔵 | Current version identified as 8.5.10 across all version files | ~200 |
|
||||
| #37548 | 4:48 PM | ✅ | Issue #543 Analysis Report Created for Slash Command Availability | ~540 |
|
||||
| #37532 | 4:43 PM | 🔵 | Plugin Metadata Missing Slash Command Registration | ~372 |
|
||||
| #22284 | 9:41 PM | 🔵 | Claude Plugin Metadata Configuration | ~183 |
|
||||
</claude-mem-context>
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "9.0.1",
|
||||
"version": "9.0.2",
|
||||
"description": "Persistent memory system for Claude Code - seamlessly preserve context across sessions",
|
||||
"author": {
|
||||
"name": "Alex Newman"
|
||||
|
||||
+33
-57
@@ -3,88 +3,64 @@
|
||||
|
||||
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||
|
||||
### Dec 29, 2025
|
||||
### Nov 10, 2025
|
||||
|
||||
**package.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #34351 | 11:13 PM | ✅ | Version 8.5.0 Release Committed to Git | ~402 |
|
||||
| #34350 | 11:12 PM | ✅ | Version 8.5.0 Build Completed Successfully | ~425 |
|
||||
| #34214 | 10:07 PM | 🔵 | Cursor Integration Feature Set Discovered via Memory Search | ~427 |
|
||||
| #34208 | 10:00 PM | ✅ | claude-mem v8.2.10 built and synced to marketplace | ~416 |
|
||||
| #6276 | 12:58 PM | 🔴 | MCP Configuration Path Missing "plugin/" Directory Component | ~418 |
|
||||
| #6274 | 12:57 PM | 🔵 | Installed MCP Configuration Uses Incorrect Path | ~389 |
|
||||
| #6270 | " | 🔵 | MCP Search Server Enable/Disable Documentation | ~314 |
|
||||
| #6253 | 12:55 PM | 🔵 | MCP Configuration Files Are Identical | ~311 |
|
||||
| #6250 | 12:54 PM | 🔵 | MCP Search Server Connection Failure Reported | ~329 |
|
||||
| #6249 | " | 🔴 | MCP Enable/Disable Implementation Overcomplicated | ~329 |
|
||||
| #6244 | 12:50 PM | 🔴 | MCP search configuration persistence fixed in settings UI | ~378 |
|
||||
| #6225 | 12:44 PM | 🔄 | Renamed MCP Template File from .optional to .template Convention | ~336 |
|
||||
| #6217 | 12:40 PM | ✅ | MCP Documentation Updated to Reference UI Toggle | ~360 |
|
||||
| #6214 | 12:39 PM | ✅ | Repository Cleanup - Removed Temporary Documentation Files | ~321 |
|
||||
| #6201 | 12:28 PM | 🟣 | Created Quick Start Guide for Enabling MCP Search | ~338 |
|
||||
| #6193 | 12:27 PM | 🟣 | Created Optional MCP Configuration File | ~313 |
|
||||
| #6190 | " | 🟣 | Created optional MCP configuration file for user-controlled search server enablement | ~364 |
|
||||
| #6189 | " | 🟣 | Created MCP search server enable/disable documentation | ~307 |
|
||||
| #6183 | 12:26 PM | 🔵 | MCP Search Server Commented Out in Plugin Configuration | ~322 |
|
||||
| #6180 | 12:25 PM | 🔵 | MCP configuration file current state | ~127 |
|
||||
|
||||
### Dec 30, 2025
|
||||
### Dec 10, 2025
|
||||
|
||||
**package.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #34476 | 2:25 PM | ✅ | V2 Branch Builds Successfully Despite TypeScript Errors | ~316 |
|
||||
| #34451 | 2:20 PM | ✅ | Successful Build of Claude-Mem v8.5.1 Components | ~346 |
|
||||
| #23444 | 2:25 PM | 🟣 | Build Pipeline Execution Successful | ~293 |
|
||||
|
||||
### Dec 31, 2025
|
||||
### Dec 11, 2025
|
||||
|
||||
**package.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #34724 | 4:51 PM | ✅ | Built claude-mem v8.5.2 with issue 499 fix | ~287 |
|
||||
| #34710 | 4:48 PM | ✅ | Built and deployed claude-mem v8.5.1 to marketplace | ~371 |
|
||||
| #24055 | 2:55 PM | ✅ | Build Successful with Bun Runtime Shebangs | ~355 |
|
||||
|
||||
### Jan 1, 2026
|
||||
### Dec 14, 2025
|
||||
|
||||
**package.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #35680 | 11:43 PM | 🟣 | Automated version 8.5.3 release workflow completed | ~470 |
|
||||
| #35613 | 10:57 PM | ✅ | Build System Compiled Updated Middleware | ~320 |
|
||||
| #35427 | 6:32 PM | ✅ | Claude-Mem Project Built Successfully | ~319 |
|
||||
| #35397 | 5:23 PM | ✅ | Build System Successfully Compiled All Components | ~282 |
|
||||
| #35343 | 3:00 PM | ✅ | Phase 1 Git Status Shows Modified Files | ~315 |
|
||||
| #26800 | 11:39 PM | ✅ | Version 7.2.3 Build Complete With Worker Restart Fix | ~394 |
|
||||
| #26766 | 11:30 PM | ⚖️ | Root Cause Identified: Missing Post-Install Worker Restart Trigger in Plugin Update Flow | ~604 |
|
||||
| #25815 | 5:31 PM | 🔵 | Comprehensive MCP Server and SKILL.md Structure Analysis | ~575 |
|
||||
| #25813 | " | 🔵 | MCP Server Configuration with Stdio Transport | ~367 |
|
||||
|
||||
### Jan 2, 2026
|
||||
### Dec 16, 2025
|
||||
|
||||
**package.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #35982 | 5:09 PM | ✅ | Built and deployed claude-mem version 8.5.4 with LogsModal UI component | ~295 |
|
||||
| #35976 | 4:48 PM | ✅ | Claude-mem build and marketplace sync completed | ~335 |
|
||||
| #35925 | 2:53 PM | ✅ | Built Project for Version 8.5.4 Release | ~294 |
|
||||
| #35815 | 2:26 PM | ✅ | Claude-mem plugin built and deployed to marketplace | ~381 |
|
||||
| #27555 | 4:48 PM | ✅ | Version bump committed to main branch | ~242 |
|
||||
| #27554 | " | ✅ | Project built successfully with version 7.3.1 | ~306 |
|
||||
|
||||
### Jan 3, 2026
|
||||
### Dec 17, 2025
|
||||
|
||||
**package.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #36687 | 11:53 PM | ✅ | MCP SDK and esbuild Dependencies Updated | ~332 |
|
||||
| #36669 | 11:37 PM | ✅ | Merge conflicts resolved automatically - only 5 metadata files modified | ~345 |
|
||||
| #28922 | 7:28 PM | 🔵 | Plugin MCP Server Configuration for mem-search | ~245 |
|
||||
|
||||
### Jan 4, 2026
|
||||
### Dec 19, 2025
|
||||
|
||||
**package.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #36926 | 2:26 AM | ✅ | Git rebase aborted and working directory restored | ~242 |
|
||||
| #36924 | 2:25 AM | ✅ | Merged fix/pr-538-followups branch into main with comprehensive updates | ~481 |
|
||||
| #36827 | 1:03 AM | ✅ | Branch diff shows 1,293 insertions and 98 deletions across 15 files | ~464 |
|
||||
| #36701 | 12:01 AM | ✅ | Built Version 8.5.7 Plugin Artifacts | ~406 |
|
||||
|
||||
### Jan 5, 2026
|
||||
|
||||
**package.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #38092 | 10:44 PM | ✅ | Built version 9.0.0 with complete plugin bundle | ~329 |
|
||||
|
||||
**.mcp.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #37545 | 4:47 PM | ✅ | Issue #544 Analysis Report Created for mem-search Skill Messaging Problem | ~480 |
|
||||
|
||||
### Jan 7, 2026
|
||||
|
||||
**package.json**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #38186 | 7:33 PM | 🟣 | Claude-mem plugin v9.0.0 built and deployed to marketplace | ~327 |
|
||||
| #38120 | 5:46 PM | 🔴 | Rebuilt all plugin hooks and worker service | ~278 |
|
||||
| #30096 | 8:09 PM | 🔵 | Claude-Mem Plugin Includes MCP Server Configuration | ~246 |
|
||||
</claude-mem-context>
|
||||
@@ -17,7 +17,12 @@
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js\"",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/worker-service.cjs\" hook claude-code context",
|
||||
"timeout": 60
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/worker-service.cjs\" hook claude-code user-message",
|
||||
"timeout": 60
|
||||
}
|
||||
]
|
||||
@@ -33,7 +38,7 @@
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/new-hook.js\"",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/worker-service.cjs\" hook claude-code session-init",
|
||||
"timeout": 60
|
||||
}
|
||||
]
|
||||
@@ -50,7 +55,7 @@
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/save-hook.js\"",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/worker-service.cjs\" hook claude-code observation",
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
@@ -66,7 +71,7 @@
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/summary-hook.js\"",
|
||||
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/worker-service.cjs\" hook claude-code summarize",
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem-plugin",
|
||||
"version": "9.0.0",
|
||||
"version": "9.0.1",
|
||||
"private": true,
|
||||
"description": "Runtime dependencies for claude-mem bundled hooks",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
import{stdin as A}from"process";import C from"path";import{homedir as z}from"os";import{readFileSync as q}from"fs";import{appendFileSync as j,existsSync as h,mkdirSync as H,readFileSync as G}from"fs";import{join as T}from"path";import{homedir as K}from"os";var S=(i=>(i[i.DEBUG=0]="DEBUG",i[i.INFO=1]="INFO",i[i.WARN=2]="WARN",i[i.ERROR=3]="ERROR",i[i.SILENT=4]="SILENT",i))(S||{}),U=T(K(),".claude-mem"),M=class{level=null;useColor;logFilePath=null;logFileInitialized=!1;constructor(){this.useColor=process.stdout.isTTY??!1}ensureLogFileInitialized(){if(!this.logFileInitialized){this.logFileInitialized=!0;try{let t=T(U,"logs");h(t)||H(t,{recursive:!0});let r=new Date().toISOString().split("T")[0];this.logFilePath=T(t,`claude-mem-${r}.log`)}catch(t){console.error("[LOGGER] Failed to initialize log file:",t),this.logFilePath=null}}}getLevel(){if(this.level===null)try{let t=T(U,"settings.json");if(h(t)){let r=G(t,"utf-8"),n=(JSON.parse(r).CLAUDE_MEM_LOG_LEVEL||"INFO").toUpperCase();this.level=S[n]??1}else this.level=1}catch{this.level=1}return this.level}correlationId(t,r){return`obs-${t}-${r}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message}
|
||||
${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let r=Object.keys(t);return r.length===0?"{}":r.length<=3?JSON.stringify(t):`{${r.length} keys: ${r.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,r){if(!r)return t;let e=r;if(typeof r=="string")try{e=JSON.parse(r)}catch{e=r}if(t==="Bash"&&e.command)return`${t}(${e.command})`;if(e.file_path)return`${t}(${e.file_path})`;if(e.notebook_path)return`${t}(${e.notebook_path})`;if(t==="Glob"&&e.pattern)return`${t}(${e.pattern})`;if(t==="Grep"&&e.pattern)return`${t}(${e.pattern})`;if(e.url)return`${t}(${e.url})`;if(e.query)return`${t}(${e.query})`;if(t==="Task"){if(e.subagent_type)return`${t}(${e.subagent_type})`;if(e.description)return`${t}(${e.description})`}return t==="Skill"&&e.skill?`${t}(${e.skill})`:t==="LSP"&&e.operation?`${t}(${e.operation})`:t}formatTimestamp(t){let r=t.getFullYear(),e=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0"),i=String(t.getHours()).padStart(2,"0"),s=String(t.getMinutes()).padStart(2,"0"),E=String(t.getSeconds()).padStart(2,"0"),c=String(t.getMilliseconds()).padStart(3,"0");return`${r}-${e}-${n} ${i}:${s}:${E}.${c}`}log(t,r,e,n,i){if(t<this.getLevel())return;this.ensureLogFileInitialized();let s=this.formatTimestamp(new Date),E=S[t].padEnd(5),c=r.padEnd(6),a="";n?.correlationId?a=`[${n.correlationId}] `:n?.sessionId&&(a=`[session-${n.sessionId}] `);let _="";i!=null&&(i instanceof Error?_=this.getLevel()===0?`
|
||||
${i.message}
|
||||
${i.stack}`:` ${i.message}`:this.getLevel()===0&&typeof i=="object"?_=`
|
||||
`+JSON.stringify(i,null,2):_=" "+this.formatData(i));let p="";if(n){let{sessionId:d,memorySessionId:at,correlationId:lt,...R}=n;Object.keys(R).length>0&&(p=` {${Object.entries(R).map(([x,b])=>`${x}=${b}`).join(", ")}}`)}let D=`[${s}] [${E}] [${c}] ${a}${e}${p}${_}`;if(this.logFilePath)try{j(this.logFilePath,D+`
|
||||
`,"utf8")}catch(d){process.stderr.write(`[LOGGER] Failed to write to log file: ${d}
|
||||
`)}else process.stderr.write(D+`
|
||||
`)}debug(t,r,e,n){this.log(0,t,r,e,n)}info(t,r,e,n){this.log(1,t,r,e,n)}warn(t,r,e,n){this.log(2,t,r,e,n)}error(t,r,e,n){this.log(3,t,r,e,n)}dataIn(t,r,e,n){this.info(t,`\u2192 ${r}`,e,n)}dataOut(t,r,e,n){this.info(t,`\u2190 ${r}`,e,n)}success(t,r,e,n){this.info(t,`\u2713 ${r}`,e,n)}failure(t,r,e,n){this.error(t,`\u2717 ${r}`,e,n)}timing(t,r,e,n){this.info(t,`\u23F1 ${r}`,n,{duration:`${e}ms`})}happyPathError(t,r,e,n,i=""){let a=((new Error().stack||"").split(`
|
||||
`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),_=a?`${a[1].split("/").pop()}:${a[2]}`:"unknown",p={...e,location:_};return this.warn(t,`[HAPPY-PATH] ${r}`,p,n),i}},l=new M;var m={DEFAULT:3e5,HEALTH_CHECK:3e4,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:300,PRE_RESTART_SETTLE_DELAY:2e3,WINDOWS_MULTIPLIER:1.5};function N(o){return process.platform==="win32"?Math.round(o*m.WINDOWS_MULTIPLIER):o}import{readFileSync as X,writeFileSync as P,existsSync as y,mkdirSync as V}from"fs";import{join as B,dirname as Y}from"path";import{homedir as J}from"os";var I="bugfix,feature,refactor,discovery,decision,change",k="how-it-works,why-it-exists,what-changed,problem-solution,gotcha,pattern,trade-off";var g=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-sonnet-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_WORKER_HOST:"127.0.0.1",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_PROVIDER:"claude",CLAUDE_MEM_GEMINI_API_KEY:"",CLAUDE_MEM_GEMINI_MODEL:"gemini-2.5-flash-lite",CLAUDE_MEM_GEMINI_RATE_LIMITING_ENABLED:"true",CLAUDE_MEM_OPENROUTER_API_KEY:"",CLAUDE_MEM_OPENROUTER_MODEL:"xiaomi/mimo-v2-flash:free",CLAUDE_MEM_OPENROUTER_SITE_URL:"",CLAUDE_MEM_OPENROUTER_APP_NAME:"claude-mem",CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES:"20",CLAUDE_MEM_OPENROUTER_MAX_TOKENS:"100000",CLAUDE_MEM_DATA_DIR:B(J(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_MODE:"code",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:I,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:k,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let r=this.get(t);return parseInt(r,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){try{if(!y(t)){let s=this.getAllDefaults();try{let E=Y(t);y(E)||V(E,{recursive:!0}),P(t,JSON.stringify(s,null,2),"utf-8"),console.log("[SETTINGS] Created settings file with defaults:",t)}catch(E){console.warn("[SETTINGS] Failed to create settings file, using in-memory defaults:",t,E)}return s}let r=X(t,"utf-8"),e=JSON.parse(r),n=e;if(e.env&&typeof e.env=="object"){n=e.env;try{P(t,JSON.stringify(n,null,2),"utf-8"),console.log("[SETTINGS] Migrated settings file from nested to flat schema:",t)}catch(s){console.warn("[SETTINGS] Failed to auto-migrate settings file:",t,s)}}let i={...this.DEFAULTS};for(let s of Object.keys(this.DEFAULTS))n[s]!==void 0&&(i[s]=n[s]);return i}catch(r){return console.warn("[SETTINGS] Failed to load settings, using defaults:",t,r),this.getAllDefaults()}}};function $(o={}){let{port:t,includeSkillFallback:r=!1,customPrefix:e,actualError:n}=o,i=e||"Worker service connection failed.",s=t?` (port ${t})`:"",E=`${i}${s}
|
||||
|
||||
`;return E+=`To restart the worker:
|
||||
`,E+=`1. Exit Claude Code completely
|
||||
`,E+=`2. Run: npm run worker:restart
|
||||
`,E+="3. Restart Claude Code",r&&(E+=`
|
||||
|
||||
If that doesn't work, try: /troubleshoot`),n&&(E=`Worker Error: ${n}
|
||||
|
||||
${E}`),E}var Q=C.join(z(),".claude","plugins","marketplaces","thedotmack"),It=N(m.HEALTH_CHECK),O=null;function u(){if(O!==null)return O;let o=C.join(g.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=g.loadFromFile(o);return O=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),O}async function Z(){let o=u();return(await fetch(`http://127.0.0.1:${o}/api/readiness`)).ok}function tt(){let o=C.join(Q,"package.json");return JSON.parse(q(o,"utf-8")).version}async function et(){let o=u(),t=await fetch(`http://127.0.0.1:${o}/api/version`);if(!t.ok)throw new Error(`Failed to get worker version: ${t.status}`);return(await t.json()).version}async function rt(){let o=tt(),t=await et();o!==t&&l.debug("SYSTEM","Version check",{pluginVersion:o,workerVersion:t,note:"Mismatch will be auto-restarted by worker-service start command"})}async function v(){for(let r=0;r<75;r++){try{if(await Z()){await rt();return}}catch(e){l.debug("SYSTEM","Worker health check failed, will retry",{attempt:r+1,maxRetries:75,error:e instanceof Error?e.message:String(e)})}await new Promise(e=>setTimeout(e,200))}throw new Error($({port:u(),customPrefix:"Worker did not become ready within 15 seconds."}))}import it from"path";import{statSync as nt,readFileSync as ot}from"fs";import L from"path";var f={isWorktree:!1,worktreeName:null,parentRepoPath:null,parentProjectName:null};function W(o){let t=L.join(o,".git"),r;try{r=nt(t)}catch{return f}if(!r.isFile())return f;let e;try{e=ot(t,"utf-8").trim()}catch{return f}let n=e.match(/^gitdir:\s*(.+)$/);if(!n)return f;let s=n[1].match(/^(.+)[/\\]\.git[/\\]worktrees[/\\]([^/\\]+)$/);if(!s)return f;let E=s[1],c=L.basename(o),a=L.basename(E);return{isWorktree:!0,worktreeName:c,parentRepoPath:E,parentProjectName:a}}function st(o){if(!o||o.trim()==="")return l.warn("PROJECT_NAME","Empty cwd provided, using fallback",{cwd:o}),"unknown-project";let t=it.basename(o);if(t===""){if(process.platform==="win32"){let e=o.match(/^([A-Z]):\\/i);if(e){let i=`drive-${e[1].toUpperCase()}`;return l.info("PROJECT_NAME","Drive root detected",{cwd:o,projectName:i}),i}}return l.warn("PROJECT_NAME","Root directory detected, using fallback",{cwd:o}),"unknown-project"}return t}function F(o){let t=st(o);if(!o)return{primary:t,parent:null,isWorktree:!1,allProjects:[t]};let r=W(o);return r.isWorktree&&r.parentProjectName?{primary:t,parent:r.parentProjectName,isWorktree:!0,allProjects:[r.parentProjectName,t]}:{primary:t,parent:null,isWorktree:!1,allProjects:[t]}}async function w(o){await v();let t=o?.cwd??process.cwd(),r=F(t),e=u(),n=r.allProjects.join(","),i=`http://127.0.0.1:${e}/api/context/inject?projects=${encodeURIComponent(n)}`,s=await fetch(i);if(!s.ok)throw new Error(`Context generation failed: ${s.status}`);return(await s.text()).trim()}var Et=process.argv.includes("--colors");if(A.isTTY||Et)w(void 0).then(o=>{console.log(o),process.exit(0)});else{let o="";A.on("data",t=>o+=t),A.on("end",async()=>{let t;try{t=o.trim()?JSON.parse(o):void 0}catch(e){throw new Error(`Failed to parse hook input: ${e instanceof Error?e.message:String(e)}`)}let r=await w(t);console.log(JSON.stringify({hookSpecificOutput:{hookEventName:"SessionStart",additionalContext:r}})),process.exit(0)})}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
import{stdin as b}from"process";var T=JSON.stringify({continue:!0,suppressOutput:!0});import L from"path";import{homedir as Y}from"os";import{readFileSync as J}from"fs";import{appendFileSync as F,existsSync as R,mkdirSync as H,readFileSync as x}from"fs";import{join as f}from"path";import{homedir as j}from"os";var S=(i=>(i[i.DEBUG=0]="DEBUG",i[i.INFO=1]="INFO",i[i.WARN=2]="WARN",i[i.ERROR=3]="ERROR",i[i.SILENT=4]="SILENT",i))(S||{}),h=f(j(),".claude-mem"),m=class{level=null;useColor;logFilePath=null;logFileInitialized=!1;constructor(){this.useColor=process.stdout.isTTY??!1}ensureLogFileInitialized(){if(!this.logFileInitialized){this.logFileInitialized=!0;try{let t=f(h,"logs");R(t)||H(t,{recursive:!0});let r=new Date().toISOString().split("T")[0];this.logFilePath=f(t,`claude-mem-${r}.log`)}catch(t){console.error("[LOGGER] Failed to initialize log file:",t),this.logFilePath=null}}}getLevel(){if(this.level===null)try{let t=f(h,"settings.json");if(R(t)){let r=x(t,"utf-8"),n=(JSON.parse(r).CLAUDE_MEM_LOG_LEVEL||"INFO").toUpperCase();this.level=S[n]??1}else this.level=1}catch{this.level=1}return this.level}correlationId(t,r){return`obs-${t}-${r}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message}
|
||||
${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let r=Object.keys(t);return r.length===0?"{}":r.length<=3?JSON.stringify(t):`{${r.length} keys: ${r.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,r){if(!r)return t;let e=r;if(typeof r=="string")try{e=JSON.parse(r)}catch{e=r}if(t==="Bash"&&e.command)return`${t}(${e.command})`;if(e.file_path)return`${t}(${e.file_path})`;if(e.notebook_path)return`${t}(${e.notebook_path})`;if(t==="Glob"&&e.pattern)return`${t}(${e.pattern})`;if(t==="Grep"&&e.pattern)return`${t}(${e.pattern})`;if(e.url)return`${t}(${e.url})`;if(e.query)return`${t}(${e.query})`;if(t==="Task"){if(e.subagent_type)return`${t}(${e.subagent_type})`;if(e.description)return`${t}(${e.description})`}return t==="Skill"&&e.skill?`${t}(${e.skill})`:t==="LSP"&&e.operation?`${t}(${e.operation})`:t}formatTimestamp(t){let r=t.getFullYear(),e=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0"),i=String(t.getHours()).padStart(2,"0"),E=String(t.getMinutes()).padStart(2,"0"),s=String(t.getSeconds()).padStart(2,"0"),c=String(t.getMilliseconds()).padStart(3,"0");return`${r}-${e}-${n} ${i}:${E}:${s}.${c}`}log(t,r,e,n,i){if(t<this.getLevel())return;this.ensureLogFileInitialized();let E=this.formatTimestamp(new Date),s=S[t].padEnd(5),c=r.padEnd(6),l="";n?.correlationId?l=`[${n.correlationId}] `:n?.sessionId&&(l=`[session-${n.sessionId}] `);let _="";i!=null&&(i instanceof Error?_=this.getLevel()===0?`
|
||||
${i.message}
|
||||
${i.stack}`:` ${i.message}`:this.getLevel()===0&&typeof i=="object"?_=`
|
||||
`+JSON.stringify(i,null,2):_=" "+this.formatData(i));let u="";if(n){let{sessionId:d,memorySessionId:nt,correlationId:ot,...D}=n;Object.keys(D).length>0&&(u=` {${Object.entries(D).map(([w,W])=>`${w}=${W}`).join(", ")}}`)}let A=`[${E}] [${s}] [${c}] ${l}${e}${u}${_}`;if(this.logFilePath)try{F(this.logFilePath,A+`
|
||||
`,"utf8")}catch(d){process.stderr.write(`[LOGGER] Failed to write to log file: ${d}
|
||||
`)}else process.stderr.write(A+`
|
||||
`)}debug(t,r,e,n){this.log(0,t,r,e,n)}info(t,r,e,n){this.log(1,t,r,e,n)}warn(t,r,e,n){this.log(2,t,r,e,n)}error(t,r,e,n){this.log(3,t,r,e,n)}dataIn(t,r,e,n){this.info(t,`\u2192 ${r}`,e,n)}dataOut(t,r,e,n){this.info(t,`\u2190 ${r}`,e,n)}success(t,r,e,n){this.info(t,`\u2713 ${r}`,e,n)}failure(t,r,e,n){this.error(t,`\u2717 ${r}`,e,n)}timing(t,r,e,n){this.info(t,`\u23F1 ${r}`,n,{duration:`${e}ms`})}happyPathError(t,r,e,n,i=""){let l=((new Error().stack||"").split(`
|
||||
`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),_=l?`${l[1].split("/").pop()}:${l[2]}`:"unknown",u={...e,location:_};return this.warn(t,`[HAPPY-PATH] ${r}`,u,n),i}},a=new m;var M={DEFAULT:3e5,HEALTH_CHECK:3e4,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:300,PRE_RESTART_SETTLE_DELAY:2e3,WINDOWS_MULTIPLIER:1.5};function N(o){return process.platform==="win32"?Math.round(o*M.WINDOWS_MULTIPLIER):o}import{readFileSync as K,writeFileSync as k,existsSync as P,mkdirSync as G}from"fs";import{join as X,dirname as V}from"path";import{homedir as B}from"os";var I="bugfix,feature,refactor,discovery,decision,change",U="how-it-works,why-it-exists,what-changed,problem-solution,gotcha,pattern,trade-off";var g=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-sonnet-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_WORKER_HOST:"127.0.0.1",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_PROVIDER:"claude",CLAUDE_MEM_GEMINI_API_KEY:"",CLAUDE_MEM_GEMINI_MODEL:"gemini-2.5-flash-lite",CLAUDE_MEM_GEMINI_RATE_LIMITING_ENABLED:"true",CLAUDE_MEM_OPENROUTER_API_KEY:"",CLAUDE_MEM_OPENROUTER_MODEL:"xiaomi/mimo-v2-flash:free",CLAUDE_MEM_OPENROUTER_SITE_URL:"",CLAUDE_MEM_OPENROUTER_APP_NAME:"claude-mem",CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES:"20",CLAUDE_MEM_OPENROUTER_MAX_TOKENS:"100000",CLAUDE_MEM_DATA_DIR:X(B(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_MODE:"code",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:I,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:U,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let r=this.get(t);return parseInt(r,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){try{if(!P(t)){let E=this.getAllDefaults();try{let s=V(t);P(s)||G(s,{recursive:!0}),k(t,JSON.stringify(E,null,2),"utf-8"),console.log("[SETTINGS] Created settings file with defaults:",t)}catch(s){console.warn("[SETTINGS] Failed to create settings file, using in-memory defaults:",t,s)}return E}let r=K(t,"utf-8"),e=JSON.parse(r),n=e;if(e.env&&typeof e.env=="object"){n=e.env;try{k(t,JSON.stringify(n,null,2),"utf-8"),console.log("[SETTINGS] Migrated settings file from nested to flat schema:",t)}catch(E){console.warn("[SETTINGS] Failed to auto-migrate settings file:",t,E)}}let i={...this.DEFAULTS};for(let E of Object.keys(this.DEFAULTS))n[E]!==void 0&&(i[E]=n[E]);return i}catch(r){return console.warn("[SETTINGS] Failed to load settings, using defaults:",t,r),this.getAllDefaults()}}};function y(o={}){let{port:t,includeSkillFallback:r=!1,customPrefix:e,actualError:n}=o,i=e||"Worker service connection failed.",E=t?` (port ${t})`:"",s=`${i}${E}
|
||||
|
||||
`;return s+=`To restart the worker:
|
||||
`,s+=`1. Exit Claude Code completely
|
||||
`,s+=`2. Run: npm run worker:restart
|
||||
`,s+="3. Restart Claude Code",r&&(s+=`
|
||||
|
||||
If that doesn't work, try: /troubleshoot`),n&&(s=`Worker Error: ${n}
|
||||
|
||||
${s}`),s}var z=L.join(Y(),".claude","plugins","marketplaces","thedotmack"),Rt=N(M.HEALTH_CHECK),O=null;function p(){if(O!==null)return O;let o=L.join(g.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=g.loadFromFile(o);return O=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),O}async function q(){let o=p();return(await fetch(`http://127.0.0.1:${o}/api/readiness`)).ok}function Q(){let o=L.join(z,"package.json");return JSON.parse(J(o,"utf-8")).version}async function Z(){let o=p(),t=await fetch(`http://127.0.0.1:${o}/api/version`);if(!t.ok)throw new Error(`Failed to get worker version: ${t.status}`);return(await t.json()).version}async function tt(){let o=Q(),t=await Z();o!==t&&a.debug("SYSTEM","Version check",{pluginVersion:o,workerVersion:t,note:"Mismatch will be auto-restarted by worker-service start command"})}async function $(){for(let r=0;r<75;r++){try{if(await q()){await tt();return}}catch(e){a.debug("SYSTEM","Worker health check failed, will retry",{attempt:r+1,maxRetries:75,error:e instanceof Error?e.message:String(e)})}await new Promise(e=>setTimeout(e,200))}throw new Error(y({port:p(),customPrefix:"Worker did not become ready within 15 seconds."}))}import et from"path";function v(o){if(!o||o.trim()==="")return a.warn("PROJECT_NAME","Empty cwd provided, using fallback",{cwd:o}),"unknown-project";let t=et.basename(o);if(t===""){if(process.platform==="win32"){let e=o.match(/^([A-Z]):\\/i);if(e){let i=`drive-${e[1].toUpperCase()}`;return a.info("PROJECT_NAME","Drive root detected",{cwd:o,projectName:i}),i}}return a.warn("PROJECT_NAME","Root directory detected, using fallback",{cwd:o}),"unknown-project"}return t}async function rt(o){if(await $(),!o)throw new Error("newHook requires input");let{session_id:t,cwd:r,prompt:e}=o,n=v(r),i=p();a.debug("HOOK","new-hook: Calling /api/sessions/init",{contentSessionId:t,project:n});let E=await fetch(`http://127.0.0.1:${i}/api/sessions/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({contentSessionId:t,project:n,prompt:e})});if(!E.ok)throw new Error(`Session initialization failed: ${E.status}`);let s=await E.json(),c=s.sessionDbId,l=s.promptNumber;if(a.debug("HOOK","new-hook: Received from /api/sessions/init",{sessionDbId:c,promptNumber:l,skipped:s.skipped}),a.debug("HOOK",`[ALIGNMENT] Hook Entry | contentSessionId=${t} | prompt#=${l} | sessionDbId=${c}`),s.skipped&&s.reason==="private"){a.info("HOOK",`INIT_COMPLETE | sessionDbId=${c} | promptNumber=${l} | skipped=true | reason=private`,{sessionId:c}),console.log(T);return}let _=e.startsWith("/")?e.substring(1):e;a.debug("HOOK","new-hook: Calling /sessions/{sessionDbId}/init",{sessionDbId:c,promptNumber:l});let u=await fetch(`http://127.0.0.1:${i}/sessions/${c}/init`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userPrompt:_,promptNumber:l})});if(!u.ok)throw new Error(`SDK agent start failed: ${u.status}`);a.info("HOOK",`INIT_COMPLETE | sessionDbId=${c} | promptNumber=${l} | project=${n}`,{sessionId:c}),console.log(T)}var C="";b.on("data",o=>C+=o);b.on("end",async()=>{try{let o;try{o=C?JSON.parse(C):void 0}catch(t){throw new Error(`Failed to parse hook input: ${t instanceof Error?t.message:String(t)}`)}await rt(o)}catch(o){a.error("HOOK","new-hook failed",{},o)}finally{process.exit(0)}});
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
import{stdin as v}from"process";var d=JSON.stringify({continue:!0,suppressOutput:!0});import{appendFileSync as H,existsSync as U,mkdirSync as W,readFileSync as b}from"fs";import{join as T}from"path";import{homedir as x}from"os";var f=(o=>(o[o.DEBUG=0]="DEBUG",o[o.INFO=1]="INFO",o[o.WARN=2]="WARN",o[o.ERROR=3]="ERROR",o[o.SILENT=4]="SILENT",o))(f||{}),R=T(x(),".claude-mem"),M=class{level=null;useColor;logFilePath=null;logFileInitialized=!1;constructor(){this.useColor=process.stdout.isTTY??!1}ensureLogFileInitialized(){if(!this.logFileInitialized){this.logFileInitialized=!0;try{let t=T(R,"logs");U(t)||W(t,{recursive:!0});let r=new Date().toISOString().split("T")[0];this.logFilePath=T(t,`claude-mem-${r}.log`)}catch(t){console.error("[LOGGER] Failed to initialize log file:",t),this.logFilePath=null}}}getLevel(){if(this.level===null)try{let t=T(R,"settings.json");if(U(t)){let r=b(t,"utf-8"),n=(JSON.parse(r).CLAUDE_MEM_LOG_LEVEL||"INFO").toUpperCase();this.level=f[n]??1}else this.level=1}catch{this.level=1}return this.level}correlationId(t,r){return`obs-${t}-${r}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message}
|
||||
${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let r=Object.keys(t);return r.length===0?"{}":r.length<=3?JSON.stringify(t):`{${r.length} keys: ${r.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,r){if(!r)return t;let e=r;if(typeof r=="string")try{e=JSON.parse(r)}catch{e=r}if(t==="Bash"&&e.command)return`${t}(${e.command})`;if(e.file_path)return`${t}(${e.file_path})`;if(e.notebook_path)return`${t}(${e.notebook_path})`;if(t==="Glob"&&e.pattern)return`${t}(${e.pattern})`;if(t==="Grep"&&e.pattern)return`${t}(${e.pattern})`;if(e.url)return`${t}(${e.url})`;if(e.query)return`${t}(${e.query})`;if(t==="Task"){if(e.subagent_type)return`${t}(${e.subagent_type})`;if(e.description)return`${t}(${e.description})`}return t==="Skill"&&e.skill?`${t}(${e.skill})`:t==="LSP"&&e.operation?`${t}(${e.operation})`:t}formatTimestamp(t){let r=t.getFullYear(),e=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0"),o=String(t.getHours()).padStart(2,"0"),E=String(t.getMinutes()).padStart(2,"0"),i=String(t.getSeconds()).padStart(2,"0"),a=String(t.getMilliseconds()).padStart(3,"0");return`${r}-${e}-${n} ${o}:${E}:${i}.${a}`}log(t,r,e,n,o){if(t<this.getLevel())return;this.ensureLogFileInitialized();let E=this.formatTimestamp(new Date),i=f[t].padEnd(5),a=r.padEnd(6),l="";n?.correlationId?l=`[${n.correlationId}] `:n?.sessionId&&(l=`[session-${n.sessionId}] `);let c="";o!=null&&(o instanceof Error?c=this.getLevel()===0?`
|
||||
${o.message}
|
||||
${o.stack}`:` ${o.message}`:this.getLevel()===0&&typeof o=="object"?c=`
|
||||
`+JSON.stringify(o,null,2):c=" "+this.formatData(o));let O="";if(n){let{sessionId:D,memorySessionId:et,correlationId:rt,...m}=n;Object.keys(m).length>0&&(O=` {${Object.entries(m).map(([F,w])=>`${F}=${w}`).join(", ")}}`)}let C=`[${E}] [${i}] [${a}] ${l}${e}${O}${c}`;if(this.logFilePath)try{H(this.logFilePath,C+`
|
||||
`,"utf8")}catch(D){process.stderr.write(`[LOGGER] Failed to write to log file: ${D}
|
||||
`)}else process.stderr.write(C+`
|
||||
`)}debug(t,r,e,n){this.log(0,t,r,e,n)}info(t,r,e,n){this.log(1,t,r,e,n)}warn(t,r,e,n){this.log(2,t,r,e,n)}error(t,r,e,n){this.log(3,t,r,e,n)}dataIn(t,r,e,n){this.info(t,`\u2192 ${r}`,e,n)}dataOut(t,r,e,n){this.info(t,`\u2190 ${r}`,e,n)}success(t,r,e,n){this.info(t,`\u2713 ${r}`,e,n)}failure(t,r,e,n){this.error(t,`\u2717 ${r}`,e,n)}timing(t,r,e,n){this.info(t,`\u23F1 ${r}`,n,{duration:`${e}ms`})}happyPathError(t,r,e,n,o=""){let l=((new Error().stack||"").split(`
|
||||
`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),c=l?`${l[1].split("/").pop()}:${l[2]}`:"unknown",O={...e,location:c};return this.warn(t,`[HAPPY-PATH] ${r}`,O,n),o}},_=new M;import L from"path";import{homedir as B}from"os";import{readFileSync as Y}from"fs";var p={DEFAULT:3e5,HEALTH_CHECK:3e4,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:300,PRE_RESTART_SETTLE_DELAY:2e3,WINDOWS_MULTIPLIER:1.5};function I(s){return process.platform==="win32"?Math.round(s*p.WINDOWS_MULTIPLIER):s}import{readFileSync as G,writeFileSync as y,existsSync as P,mkdirSync as K}from"fs";import{join as X,dirname as V}from"path";import{homedir as j}from"os";var h="bugfix,feature,refactor,discovery,decision,change",N="how-it-works,why-it-exists,what-changed,problem-solution,gotcha,pattern,trade-off";var g=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-sonnet-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_WORKER_HOST:"127.0.0.1",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_PROVIDER:"claude",CLAUDE_MEM_GEMINI_API_KEY:"",CLAUDE_MEM_GEMINI_MODEL:"gemini-2.5-flash-lite",CLAUDE_MEM_GEMINI_RATE_LIMITING_ENABLED:"true",CLAUDE_MEM_OPENROUTER_API_KEY:"",CLAUDE_MEM_OPENROUTER_MODEL:"xiaomi/mimo-v2-flash:free",CLAUDE_MEM_OPENROUTER_SITE_URL:"",CLAUDE_MEM_OPENROUTER_APP_NAME:"claude-mem",CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES:"20",CLAUDE_MEM_OPENROUTER_MAX_TOKENS:"100000",CLAUDE_MEM_DATA_DIR:X(j(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_MODE:"code",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:h,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:N,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let r=this.get(t);return parseInt(r,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){try{if(!P(t)){let E=this.getAllDefaults();try{let i=V(t);P(i)||K(i,{recursive:!0}),y(t,JSON.stringify(E,null,2),"utf-8"),console.log("[SETTINGS] Created settings file with defaults:",t)}catch(i){console.warn("[SETTINGS] Failed to create settings file, using in-memory defaults:",t,i)}return E}let r=G(t,"utf-8"),e=JSON.parse(r),n=e;if(e.env&&typeof e.env=="object"){n=e.env;try{y(t,JSON.stringify(n,null,2),"utf-8"),console.log("[SETTINGS] Migrated settings file from nested to flat schema:",t)}catch(E){console.warn("[SETTINGS] Failed to auto-migrate settings file:",t,E)}}let o={...this.DEFAULTS};for(let E of Object.keys(this.DEFAULTS))n[E]!==void 0&&(o[E]=n[E]);return o}catch(r){return console.warn("[SETTINGS] Failed to load settings, using defaults:",t,r),this.getAllDefaults()}}};function $(s={}){let{port:t,includeSkillFallback:r=!1,customPrefix:e,actualError:n}=s,o=e||"Worker service connection failed.",E=t?` (port ${t})`:"",i=`${o}${E}
|
||||
|
||||
`;return i+=`To restart the worker:
|
||||
`,i+=`1. Exit Claude Code completely
|
||||
`,i+=`2. Run: npm run worker:restart
|
||||
`,i+="3. Restart Claude Code",r&&(i+=`
|
||||
|
||||
If that doesn't work, try: /troubleshoot`),n&&(i=`Worker Error: ${n}
|
||||
|
||||
${i}`),i}var J=L.join(B(),".claude","plugins","marketplaces","thedotmack"),mt=I(p.HEALTH_CHECK),S=null;function u(){if(S!==null)return S;let s=L.join(g.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=g.loadFromFile(s);return S=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),S}async function z(){let s=u();return(await fetch(`http://127.0.0.1:${s}/api/readiness`)).ok}function q(){let s=L.join(J,"package.json");return JSON.parse(Y(s,"utf-8")).version}async function Q(){let s=u(),t=await fetch(`http://127.0.0.1:${s}/api/version`);if(!t.ok)throw new Error(`Failed to get worker version: ${t.status}`);return(await t.json()).version}async function Z(){let s=q(),t=await Q();s!==t&&_.debug("SYSTEM","Version check",{pluginVersion:s,workerVersion:t,note:"Mismatch will be auto-restarted by worker-service start command"})}async function k(){for(let r=0;r<75;r++){try{if(await z()){await Z();return}}catch(e){_.debug("SYSTEM","Worker health check failed, will retry",{attempt:r+1,maxRetries:75,error:e instanceof Error?e.message:String(e)})}await new Promise(e=>setTimeout(e,200))}throw new Error($({port:u(),customPrefix:"Worker did not become ready within 15 seconds."}))}async function tt(s){if(await k(),!s)throw new Error("saveHook requires input");let{session_id:t,cwd:r,tool_name:e,tool_input:n,tool_response:o}=s,E=u(),i=_.formatTool(e,n);if(_.dataIn("HOOK",`PostToolUse: ${i}`,{workerPort:E}),!r)throw new Error(`Missing cwd in PostToolUse hook input for session ${t}, tool ${e}`);let a=await fetch(`http://127.0.0.1:${E}/api/sessions/observations`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({contentSessionId:t,tool_name:e,tool_input:n,tool_response:o,cwd:r})});if(!a.ok)throw new Error(`Observation storage failed: ${a.status}`);_.debug("HOOK","Observation sent successfully",{toolName:e}),console.log(d)}var A="";v.on("data",s=>A+=s);v.on("end",async()=>{try{let s;try{s=A?JSON.parse(A):void 0}catch(t){throw new Error(`Failed to parse hook input: ${t instanceof Error?t.message:String(t)}`)}await tt(s)}catch(s){_.error("HOOK","save-hook failed",{},s)}finally{process.exit(0)}});
|
||||
@@ -1,23 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
import{stdin as v}from"process";var T=JSON.stringify({continue:!0,suppressOutput:!0});import{appendFileSync as H,existsSync as R,mkdirSync as W,readFileSync as b}from"fs";import{join as O}from"path";import{homedir as G}from"os";var p=(o=>(o[o.DEBUG=0]="DEBUG",o[o.INFO=1]="INFO",o[o.WARN=2]="WARN",o[o.ERROR=3]="ERROR",o[o.SILENT=4]="SILENT",o))(p||{}),U=O(G(),".claude-mem"),M=class{level=null;useColor;logFilePath=null;logFileInitialized=!1;constructor(){this.useColor=process.stdout.isTTY??!1}ensureLogFileInitialized(){if(!this.logFileInitialized){this.logFileInitialized=!0;try{let t=O(U,"logs");R(t)||W(t,{recursive:!0});let r=new Date().toISOString().split("T")[0];this.logFilePath=O(t,`claude-mem-${r}.log`)}catch(t){console.error("[LOGGER] Failed to initialize log file:",t),this.logFilePath=null}}}getLevel(){if(this.level===null)try{let t=O(U,"settings.json");if(R(t)){let r=b(t,"utf-8"),n=(JSON.parse(r).CLAUDE_MEM_LOG_LEVEL||"INFO").toUpperCase();this.level=p[n]??1}else this.level=1}catch{this.level=1}return this.level}correlationId(t,r){return`obs-${t}-${r}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message}
|
||||
${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let r=Object.keys(t);return r.length===0?"{}":r.length<=3?JSON.stringify(t):`{${r.length} keys: ${r.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,r){if(!r)return t;let e=r;if(typeof r=="string")try{e=JSON.parse(r)}catch{e=r}if(t==="Bash"&&e.command)return`${t}(${e.command})`;if(e.file_path)return`${t}(${e.file_path})`;if(e.notebook_path)return`${t}(${e.notebook_path})`;if(t==="Glob"&&e.pattern)return`${t}(${e.pattern})`;if(t==="Grep"&&e.pattern)return`${t}(${e.pattern})`;if(e.url)return`${t}(${e.url})`;if(e.query)return`${t}(${e.query})`;if(t==="Task"){if(e.subagent_type)return`${t}(${e.subagent_type})`;if(e.description)return`${t}(${e.description})`}return t==="Skill"&&e.skill?`${t}(${e.skill})`:t==="LSP"&&e.operation?`${t}(${e.operation})`:t}formatTimestamp(t){let r=t.getFullYear(),e=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0"),o=String(t.getHours()).padStart(2,"0"),E=String(t.getMinutes()).padStart(2,"0"),i=String(t.getSeconds()).padStart(2,"0"),_=String(t.getMilliseconds()).padStart(3,"0");return`${r}-${e}-${n} ${o}:${E}:${i}.${_}`}log(t,r,e,n,o){if(t<this.getLevel())return;this.ensureLogFileInitialized();let E=this.formatTimestamp(new Date),i=p[t].padEnd(5),_=r.padEnd(6),a="";n?.correlationId?a=`[${n.correlationId}] `:n?.sessionId&&(a=`[session-${n.sessionId}] `);let l="";o!=null&&(o instanceof Error?l=this.getLevel()===0?`
|
||||
${o.message}
|
||||
${o.stack}`:` ${o.message}`:this.getLevel()===0&&typeof o=="object"?l=`
|
||||
`+JSON.stringify(o,null,2):l=" "+this.formatData(o));let f="";if(n){let{sessionId:D,memorySessionId:st,correlationId:ot,...d}=n;Object.keys(d).length>0&&(f=` {${Object.entries(d).map(([F,x])=>`${F}=${x}`).join(", ")}}`)}let C=`[${E}] [${i}] [${_}] ${a}${e}${f}${l}`;if(this.logFilePath)try{H(this.logFilePath,C+`
|
||||
`,"utf8")}catch(D){process.stderr.write(`[LOGGER] Failed to write to log file: ${D}
|
||||
`)}else process.stderr.write(C+`
|
||||
`)}debug(t,r,e,n){this.log(0,t,r,e,n)}info(t,r,e,n){this.log(1,t,r,e,n)}warn(t,r,e,n){this.log(2,t,r,e,n)}error(t,r,e,n){this.log(3,t,r,e,n)}dataIn(t,r,e,n){this.info(t,`\u2192 ${r}`,e,n)}dataOut(t,r,e,n){this.info(t,`\u2190 ${r}`,e,n)}success(t,r,e,n){this.info(t,`\u2713 ${r}`,e,n)}failure(t,r,e,n){this.error(t,`\u2717 ${r}`,e,n)}timing(t,r,e,n){this.info(t,`\u23F1 ${r}`,n,{duration:`${e}ms`})}happyPathError(t,r,e,n,o=""){let a=((new Error().stack||"").split(`
|
||||
`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),l=a?`${a[1].split("/").pop()}:${a[2]}`:"unknown",f={...e,location:l};return this.warn(t,`[HAPPY-PATH] ${r}`,f,n),o}},c=new M;import L from"path";import{homedir as Y}from"os";import{readFileSync as J}from"fs";var m={DEFAULT:3e5,HEALTH_CHECK:3e4,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:300,PRE_RESTART_SETTLE_DELAY:2e3,WINDOWS_MULTIPLIER:1.5};function h(s){return process.platform==="win32"?Math.round(s*m.WINDOWS_MULTIPLIER):s}import{readFileSync as K,writeFileSync as N,existsSync as $,mkdirSync as X}from"fs";import{join as V,dirname as j}from"path";import{homedir as B}from"os";var I="bugfix,feature,refactor,discovery,decision,change",y="how-it-works,why-it-exists,what-changed,problem-solution,gotcha,pattern,trade-off";var g=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-sonnet-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_WORKER_HOST:"127.0.0.1",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_PROVIDER:"claude",CLAUDE_MEM_GEMINI_API_KEY:"",CLAUDE_MEM_GEMINI_MODEL:"gemini-2.5-flash-lite",CLAUDE_MEM_GEMINI_RATE_LIMITING_ENABLED:"true",CLAUDE_MEM_OPENROUTER_API_KEY:"",CLAUDE_MEM_OPENROUTER_MODEL:"xiaomi/mimo-v2-flash:free",CLAUDE_MEM_OPENROUTER_SITE_URL:"",CLAUDE_MEM_OPENROUTER_APP_NAME:"claude-mem",CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES:"20",CLAUDE_MEM_OPENROUTER_MAX_TOKENS:"100000",CLAUDE_MEM_DATA_DIR:V(B(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_MODE:"code",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:I,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:y,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let r=this.get(t);return parseInt(r,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){try{if(!$(t)){let E=this.getAllDefaults();try{let i=j(t);$(i)||X(i,{recursive:!0}),N(t,JSON.stringify(E,null,2),"utf-8"),console.log("[SETTINGS] Created settings file with defaults:",t)}catch(i){console.warn("[SETTINGS] Failed to create settings file, using in-memory defaults:",t,i)}return E}let r=K(t,"utf-8"),e=JSON.parse(r),n=e;if(e.env&&typeof e.env=="object"){n=e.env;try{N(t,JSON.stringify(n,null,2),"utf-8"),console.log("[SETTINGS] Migrated settings file from nested to flat schema:",t)}catch(E){console.warn("[SETTINGS] Failed to auto-migrate settings file:",t,E)}}let o={...this.DEFAULTS};for(let E of Object.keys(this.DEFAULTS))n[E]!==void 0&&(o[E]=n[E]);return o}catch(r){return console.warn("[SETTINGS] Failed to load settings, using defaults:",t,r),this.getAllDefaults()}}};function k(s={}){let{port:t,includeSkillFallback:r=!1,customPrefix:e,actualError:n}=s,o=e||"Worker service connection failed.",E=t?` (port ${t})`:"",i=`${o}${E}
|
||||
|
||||
`;return i+=`To restart the worker:
|
||||
`,i+=`1. Exit Claude Code completely
|
||||
`,i+=`2. Run: npm run worker:restart
|
||||
`,i+="3. Restart Claude Code",r&&(i+=`
|
||||
|
||||
If that doesn't work, try: /troubleshoot`),n&&(i=`Worker Error: ${n}
|
||||
|
||||
${i}`),i}var z=L.join(Y(),".claude","plugins","marketplaces","thedotmack"),Ut=h(m.HEALTH_CHECK),S=null;function u(){if(S!==null)return S;let s=L.join(g.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=g.loadFromFile(s);return S=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),S}async function q(){let s=u();return(await fetch(`http://127.0.0.1:${s}/api/readiness`)).ok}function Q(){let s=L.join(z,"package.json");return JSON.parse(J(s,"utf-8")).version}async function Z(){let s=u(),t=await fetch(`http://127.0.0.1:${s}/api/version`);if(!t.ok)throw new Error(`Failed to get worker version: ${t.status}`);return(await t.json()).version}async function tt(){let s=Q(),t=await Z();s!==t&&c.debug("SYSTEM","Version check",{pluginVersion:s,workerVersion:t,note:"Mismatch will be auto-restarted by worker-service start command"})}async function P(){for(let r=0;r<75;r++){try{if(await q()){await tt();return}}catch(e){c.debug("SYSTEM","Worker health check failed, will retry",{attempt:r+1,maxRetries:75,error:e instanceof Error?e.message:String(e)})}await new Promise(e=>setTimeout(e,200))}throw new Error(k({port:u(),customPrefix:"Worker did not become ready within 15 seconds."}))}import{readFileSync as et,existsSync as rt}from"fs";function w(s,t,r=!1){if(!s||!rt(s))throw new Error(`Transcript path missing or file does not exist: ${s}`);let e=et(s,"utf-8").trim();if(!e)throw new Error(`Transcript file exists but is empty: ${s}`);let n=e.split(`
|
||||
`),o=!1;for(let E=n.length-1;E>=0;E--){let i=JSON.parse(n[E]);if(i.type===t&&(o=!0,i.message?.content)){let _="",a=i.message.content;if(typeof a=="string")_=a;else if(Array.isArray(a))_=a.filter(l=>l.type==="text").map(l=>l.text).join(`
|
||||
`);else throw new Error(`Unknown message content format in transcript. Type: ${typeof a}`);return r&&(_=_.replace(/<system-reminder>[\s\S]*?<\/system-reminder>/g,""),_=_.replace(/\n{3,}/g,`
|
||||
|
||||
`).trim()),_}}if(!o)throw new Error(`No message found for role '${t}' in transcript: ${s}`);return""}async function nt(s){if(await P(),!s)throw new Error("summaryHook requires input");let{session_id:t}=s,r=u();if(!s.transcript_path)throw new Error(`Missing transcript_path in Stop hook input for session ${t}`);let e=w(s.transcript_path,"assistant",!0);c.dataIn("HOOK","Stop: Requesting summary",{workerPort:r,hasLastAssistantMessage:!!e});let n=await fetch(`http://127.0.0.1:${r}/api/sessions/summarize`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({contentSessionId:t,last_assistant_message:e})});if(!n.ok)throw console.log(T),new Error(`Summary generation failed: ${n.status}`);c.debug("HOOK","Summary request sent successfully"),console.log(T)}var A="";v.on("data",s=>A+=s);v.on("end",async()=>{try{let s;try{s=A?JSON.parse(A):void 0}catch(t){throw new Error(`Failed to parse hook input: ${t instanceof Error?t.message:String(t)}`)}await nt(s)}catch(s){c.error("HOOK","summary-hook failed",{},s)}finally{process.exit(0)}});
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
import{basename as Z}from"path";import p from"path";import{homedir as j}from"os";import{readFileSync as B}from"fs";import{appendFileSync as w,existsSync as R,mkdirSync as W,readFileSync as b}from"fs";import{join as T}from"path";import{homedir as x}from"os";var M=(o=>(o[o.DEBUG=0]="DEBUG",o[o.INFO=1]="INFO",o[o.WARN=2]="WARN",o[o.ERROR=3]="ERROR",o[o.SILENT=4]="SILENT",o))(M||{}),U=T(x(),".claude-mem"),S=class{level=null;useColor;logFilePath=null;logFileInitialized=!1;constructor(){this.useColor=process.stdout.isTTY??!1}ensureLogFileInitialized(){if(!this.logFileInitialized){this.logFileInitialized=!0;try{let t=T(U,"logs");R(t)||W(t,{recursive:!0});let r=new Date().toISOString().split("T")[0];this.logFilePath=T(t,`claude-mem-${r}.log`)}catch(t){console.error("[LOGGER] Failed to initialize log file:",t),this.logFilePath=null}}}getLevel(){if(this.level===null)try{let t=T(U,"settings.json");if(R(t)){let r=b(t,"utf-8"),n=(JSON.parse(r).CLAUDE_MEM_LOG_LEVEL||"INFO").toUpperCase();this.level=M[n]??1}else this.level=1}catch{this.level=1}return this.level}correlationId(t,r){return`obs-${t}-${r}`}sessionId(t){return`session-${t}`}formatData(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean")return t.toString();if(typeof t=="object"){if(t instanceof Error)return this.getLevel()===0?`${t.message}
|
||||
${t.stack}`:t.message;if(Array.isArray(t))return`[${t.length} items]`;let r=Object.keys(t);return r.length===0?"{}":r.length<=3?JSON.stringify(t):`{${r.length} keys: ${r.slice(0,3).join(", ")}...}`}return String(t)}formatTool(t,r){if(!r)return t;let e=r;if(typeof r=="string")try{e=JSON.parse(r)}catch{e=r}if(t==="Bash"&&e.command)return`${t}(${e.command})`;if(e.file_path)return`${t}(${e.file_path})`;if(e.notebook_path)return`${t}(${e.notebook_path})`;if(t==="Glob"&&e.pattern)return`${t}(${e.pattern})`;if(t==="Grep"&&e.pattern)return`${t}(${e.pattern})`;if(e.url)return`${t}(${e.url})`;if(e.query)return`${t}(${e.query})`;if(t==="Task"){if(e.subagent_type)return`${t}(${e.subagent_type})`;if(e.description)return`${t}(${e.description})`}return t==="Skill"&&e.skill?`${t}(${e.skill})`:t==="LSP"&&e.operation?`${t}(${e.operation})`:t}formatTimestamp(t){let r=t.getFullYear(),e=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0"),o=String(t.getHours()).padStart(2,"0"),E=String(t.getMinutes()).padStart(2,"0"),s=String(t.getSeconds()).padStart(2,"0"),g=String(t.getMilliseconds()).padStart(3,"0");return`${r}-${e}-${n} ${o}:${E}:${s}.${g}`}log(t,r,e,n,o){if(t<this.getLevel())return;this.ensureLogFileInitialized();let E=this.formatTimestamp(new Date),s=M[t].padEnd(5),g=r.padEnd(6),_="";n?.correlationId?_=`[${n.correlationId}] `:n?.sessionId&&(_=`[session-${n.sessionId}] `);let a="";o!=null&&(o instanceof Error?a=this.getLevel()===0?`
|
||||
${o.message}
|
||||
${o.stack}`:` ${o.message}`:this.getLevel()===0&&typeof o=="object"?a=`
|
||||
`+JSON.stringify(o,null,2):a=" "+this.formatData(o));let u="";if(n){let{sessionId:D,memorySessionId:rt,correlationId:nt,...m}=n;Object.keys(m).length>0&&(u=` {${Object.entries(m).map(([P,F])=>`${P}=${F}`).join(", ")}}`)}let A=`[${E}] [${s}] [${g}] ${_}${e}${u}${a}`;if(this.logFilePath)try{w(this.logFilePath,A+`
|
||||
`,"utf8")}catch(D){process.stderr.write(`[LOGGER] Failed to write to log file: ${D}
|
||||
`)}else process.stderr.write(A+`
|
||||
`)}debug(t,r,e,n){this.log(0,t,r,e,n)}info(t,r,e,n){this.log(1,t,r,e,n)}warn(t,r,e,n){this.log(2,t,r,e,n)}error(t,r,e,n){this.log(3,t,r,e,n)}dataIn(t,r,e,n){this.info(t,`\u2192 ${r}`,e,n)}dataOut(t,r,e,n){this.info(t,`\u2190 ${r}`,e,n)}success(t,r,e,n){this.info(t,`\u2713 ${r}`,e,n)}failure(t,r,e,n){this.error(t,`\u2717 ${r}`,e,n)}timing(t,r,e,n){this.info(t,`\u23F1 ${r}`,n,{duration:`${e}ms`})}happyPathError(t,r,e,n,o=""){let _=((new Error().stack||"").split(`
|
||||
`)[2]||"").match(/at\s+(?:.*\s+)?\(?([^:]+):(\d+):(\d+)\)?/),a=_?`${_[1].split("/").pop()}:${_[2]}`:"unknown",u={...e,location:a};return this.warn(t,`[HAPPY-PATH] ${r}`,u,n),o}},f=new S;var L={DEFAULT:3e5,HEALTH_CHECK:3e4,WORKER_STARTUP_WAIT:1e3,WORKER_STARTUP_RETRIES:300,PRE_RESTART_SETTLE_DELAY:2e3,WINDOWS_MULTIPLIER:1.5};function d(i){return process.platform==="win32"?Math.round(i*L.WINDOWS_MULTIPLIER):i}import{readFileSync as G,writeFileSync as N,existsSync as y,mkdirSync as H}from"fs";import{join as K,dirname as X}from"path";import{homedir as V}from"os";var h="bugfix,feature,refactor,discovery,decision,change",I="how-it-works,why-it-exists,what-changed,problem-solution,gotcha,pattern,trade-off";var l=class{static DEFAULTS={CLAUDE_MEM_MODEL:"claude-sonnet-4-5",CLAUDE_MEM_CONTEXT_OBSERVATIONS:"50",CLAUDE_MEM_WORKER_PORT:"37777",CLAUDE_MEM_WORKER_HOST:"127.0.0.1",CLAUDE_MEM_SKIP_TOOLS:"ListMcpResourcesTool,SlashCommand,Skill,TodoWrite,AskUserQuestion",CLAUDE_MEM_PROVIDER:"claude",CLAUDE_MEM_GEMINI_API_KEY:"",CLAUDE_MEM_GEMINI_MODEL:"gemini-2.5-flash-lite",CLAUDE_MEM_GEMINI_RATE_LIMITING_ENABLED:"true",CLAUDE_MEM_OPENROUTER_API_KEY:"",CLAUDE_MEM_OPENROUTER_MODEL:"xiaomi/mimo-v2-flash:free",CLAUDE_MEM_OPENROUTER_SITE_URL:"",CLAUDE_MEM_OPENROUTER_APP_NAME:"claude-mem",CLAUDE_MEM_OPENROUTER_MAX_CONTEXT_MESSAGES:"20",CLAUDE_MEM_OPENROUTER_MAX_TOKENS:"100000",CLAUDE_MEM_DATA_DIR:K(V(),".claude-mem"),CLAUDE_MEM_LOG_LEVEL:"INFO",CLAUDE_MEM_PYTHON_VERSION:"3.13",CLAUDE_CODE_PATH:"",CLAUDE_MEM_MODE:"code",CLAUDE_MEM_CONTEXT_SHOW_READ_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_WORK_TOKENS:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_AMOUNT:"true",CLAUDE_MEM_CONTEXT_SHOW_SAVINGS_PERCENT:"true",CLAUDE_MEM_CONTEXT_OBSERVATION_TYPES:h,CLAUDE_MEM_CONTEXT_OBSERVATION_CONCEPTS:I,CLAUDE_MEM_CONTEXT_FULL_COUNT:"5",CLAUDE_MEM_CONTEXT_FULL_FIELD:"narrative",CLAUDE_MEM_CONTEXT_SESSION_COUNT:"10",CLAUDE_MEM_CONTEXT_SHOW_LAST_SUMMARY:"true",CLAUDE_MEM_CONTEXT_SHOW_LAST_MESSAGE:"false"};static getAllDefaults(){return{...this.DEFAULTS}}static get(t){return this.DEFAULTS[t]}static getInt(t){let r=this.get(t);return parseInt(r,10)}static getBool(t){return this.get(t)==="true"}static loadFromFile(t){try{if(!y(t)){let E=this.getAllDefaults();try{let s=X(t);y(s)||H(s,{recursive:!0}),N(t,JSON.stringify(E,null,2),"utf-8"),console.log("[SETTINGS] Created settings file with defaults:",t)}catch(s){console.warn("[SETTINGS] Failed to create settings file, using in-memory defaults:",t,s)}return E}let r=G(t,"utf-8"),e=JSON.parse(r),n=e;if(e.env&&typeof e.env=="object"){n=e.env;try{N(t,JSON.stringify(n,null,2),"utf-8"),console.log("[SETTINGS] Migrated settings file from nested to flat schema:",t)}catch(E){console.warn("[SETTINGS] Failed to auto-migrate settings file:",t,E)}}let o={...this.DEFAULTS};for(let E of Object.keys(this.DEFAULTS))n[E]!==void 0&&(o[E]=n[E]);return o}catch(r){return console.warn("[SETTINGS] Failed to load settings, using defaults:",t,r),this.getAllDefaults()}}};function $(i={}){let{port:t,includeSkillFallback:r=!1,customPrefix:e,actualError:n}=i,o=e||"Worker service connection failed.",E=t?` (port ${t})`:"",s=`${o}${E}
|
||||
|
||||
`;return s+=`To restart the worker:
|
||||
`,s+=`1. Exit Claude Code completely
|
||||
`,s+=`2. Run: npm run worker:restart
|
||||
`,s+="3. Restart Claude Code",r&&(s+=`
|
||||
|
||||
If that doesn't work, try: /troubleshoot`),n&&(s=`Worker Error: ${n}
|
||||
|
||||
${s}`),s}var Y=p.join(j(),".claude","plugins","marketplaces","thedotmack"),mt=d(L.HEALTH_CHECK),O=null;function c(){if(O!==null)return O;let i=p.join(l.get("CLAUDE_MEM_DATA_DIR"),"settings.json"),t=l.loadFromFile(i);return O=parseInt(t.CLAUDE_MEM_WORKER_PORT,10),O}async function J(){let i=c();return(await fetch(`http://127.0.0.1:${i}/api/readiness`)).ok}function z(){let i=p.join(Y,"package.json");return JSON.parse(B(i,"utf-8")).version}async function q(){let i=c(),t=await fetch(`http://127.0.0.1:${i}/api/version`);if(!t.ok)throw new Error(`Failed to get worker version: ${t.status}`);return(await t.json()).version}async function Q(){let i=z(),t=await q();i!==t&&f.debug("SYSTEM","Version check",{pluginVersion:i,workerVersion:t,note:"Mismatch will be auto-restarted by worker-service start command"})}async function k(){for(let r=0;r<75;r++){try{if(await J()){await Q();return}}catch(e){f.debug("SYSTEM","Worker health check failed, will retry",{attempt:r+1,maxRetries:75,error:e instanceof Error?e.message:String(e)})}await new Promise(e=>setTimeout(e,200))}throw new Error($({port:c(),customPrefix:"Worker did not become ready within 15 seconds."}))}await k();var v=c(),tt=Z(process.cwd()),C=await fetch(`http://127.0.0.1:${v}/api/context/inject?project=${encodeURIComponent(tt)}&colors=true`,{method:"GET"});if(!C.ok)throw new Error(`Failed to fetch context: ${C.status}`);var et=await C.text();console.error(`
|
||||
|
||||
\u{1F4DD} Claude-Mem Context Loaded
|
||||
\u2139\uFE0F Note: This appears as stderr but is informational only
|
||||
|
||||
`+et+`
|
||||
|
||||
\u{1F4A1} New! Wrap all or part of any message with <private> ... </private> to prevent storing sensitive information in your observation history.
|
||||
|
||||
\u{1F4AC} Community https://discord.gg/J4wttp9vDu
|
||||
\u{1F4FA} Watch live in browser http://localhost:${v}/
|
||||
`);process.exit(1);
|
||||
+149
-125
File diff suppressed because one or more lines are too long
@@ -1,2 +1,2 @@
|
||||
#!/usr/bin/env bun
|
||||
"use strict";var m=Object.create;var w=Object.defineProperty;var u=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var f=Object.getPrototypeOf,x=Object.prototype.hasOwnProperty;var g=(e,i,n,o)=>{if(i&&typeof i=="object"||typeof i=="function")for(let s of I(i))!x.call(e,s)&&s!==n&&w(e,s,{get:()=>i[s],enumerable:!(o=u(i,s))||o.enumerable});return e};var k=(e,i,n)=>(n=e!=null?m(f(e)):{},g(i||!e||!e.__esModule?w(n,"default",{value:e,enumerable:!0}):n,e));var c=require("child_process"),p=k(require("path"),1),y=process.platform==="win32",P=__dirname,l=p.default.join(P,"worker-service.cjs"),t=null,a=!1;function r(e){let i=new Date().toISOString();console.log(`[${i}] [wrapper] ${e}`)}function h(){r(`Spawning inner worker: ${l}`),t=(0,c.spawn)(process.execPath,[l],{stdio:["inherit","inherit","inherit","ipc"],env:{...process.env,CLAUDE_MEM_MANAGED:"true"},cwd:p.default.dirname(l)}),t.on("message",async e=>{(e.type==="restart"||e.type==="shutdown")&&(r(`${e.type} requested by inner`),a=!0,await d(),r("Exiting wrapper"),process.exit(0))}),t.on("exit",(e,i)=>{r(`Inner exited with code=${e}, signal=${i}`),t=null,a||(r("Inner exited unexpectedly, wrapper exiting (hooks will restart if needed)"),process.exit(e??1))}),t.on("error",e=>{r(`Inner error: ${e.message}`)})}async function d(){if(!t||!t.pid){r("No inner process to kill");return}let e=t.pid;if(r(`Killing inner process tree (pid=${e})`),y)try{(0,c.execSync)(`taskkill /PID ${e} /T /F`,{timeout:1e4,stdio:"ignore"}),r(`taskkill completed for pid=${e}`)}catch(i){r(`taskkill failed (process may be dead): ${i}`)}else{t.kill("SIGTERM");let i=new Promise(o=>{if(!t){o();return}t.on("exit",()=>o())}),n=new Promise(o=>setTimeout(()=>o(),5e3));await Promise.race([i,n]),t&&!t.killed&&(r("Inner did not exit gracefully, force killing"),t.kill("SIGKILL"))}await S(e,5e3),t=null,r("Inner process terminated")}async function S(e,i){let n=Date.now();for(;Date.now()-n<i;)try{process.kill(e,0),await new Promise(o=>setTimeout(o,100))}catch{return}r(`Timeout waiting for process ${e} to exit`)}process.on("SIGTERM",async()=>{r("Wrapper received SIGTERM"),a=!0,await d(),process.exit(0)});process.on("SIGINT",async()=>{r("Wrapper received SIGINT"),a=!0,await d(),process.exit(0)});r("Wrapper starting");h();
|
||||
"use strict";var m=Object.create;var w=Object.defineProperty;var u=Object.getOwnPropertyDescriptor;var I=Object.getOwnPropertyNames;var f=Object.getPrototypeOf,x=Object.prototype.hasOwnProperty;var g=(e,i,n,o)=>{if(i&&typeof i=="object"||typeof i=="function")for(let s of I(i))!x.call(e,s)&&s!==n&&w(e,s,{get:()=>i[s],enumerable:!(o=u(i,s))||o.enumerable});return e};var k=(e,i,n)=>(n=e!=null?m(f(e)):{},g(i||!e||!e.__esModule?w(n,"default",{value:e,enumerable:!0}):n,e));var c=require("child_process"),p=k(require("path"),1),y=process.platform==="win32",P=__dirname,l=p.default.join(P,"worker-service.cjs"),t=null,a=!1;function r(e){let i=new Date().toISOString();console.log(`[${i}] [wrapper] ${e}`)}function h(){r(`Spawning inner worker: ${l}`),t=(0,c.spawn)(process.execPath,[l],{stdio:["inherit","inherit","inherit","ipc"],env:{...process.env,CLAUDE_MEM_MANAGED:"true"},cwd:p.default.dirname(l)}),t.on("message",async e=>{(e.type==="restart"||e.type==="shutdown")&&(r(`${e.type} requested by inner`),a=!0,await d(),r("Exiting wrapper"),process.exit(0))}),t.on("exit",(e,i)=>{r(`Inner exited with code=${e}, signal=${i}`),t=null,a||(r("Inner exited unexpectedly, wrapper exiting (hooks will restart if needed)"),process.exit(e??0))}),t.on("error",e=>{r(`Inner error: ${e.message}`)})}async function d(){if(!t||!t.pid){r("No inner process to kill");return}let e=t.pid;if(r(`Killing inner process tree (pid=${e})`),y)try{(0,c.execSync)(`taskkill /PID ${e} /T /F`,{timeout:1e4,stdio:"ignore"}),r(`taskkill completed for pid=${e}`)}catch(i){r(`taskkill failed (process may be dead): ${i}`)}else{t.kill("SIGTERM");let i=new Promise(o=>{if(!t){o();return}t.on("exit",()=>o())}),n=new Promise(o=>setTimeout(()=>o(),5e3));await Promise.race([i,n]),t&&!t.killed&&(r("Inner did not exit gracefully, force killing"),t.kill("SIGKILL"))}await S(e,5e3),t=null,r("Inner process terminated")}async function S(e,i){let n=Date.now();for(;Date.now()-n<i;)try{process.kill(e,0),await new Promise(o=>setTimeout(o,100))}catch{return}r(`Timeout waiting for process ${e} to exit`)}process.on("SIGTERM",async()=>{r("Wrapper received SIGTERM"),a=!0,await d(),process.exit(0)});process.on("SIGINT",async()=>{r("Wrapper received SIGINT"),a=!0,await d(),process.exit(0)});r("Wrapper starting");h();
|
||||
|
||||
+48
-123
@@ -3,135 +3,60 @@
|
||||
|
||||
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||
|
||||
### Dec 25, 2025
|
||||
### Nov 5, 2025
|
||||
|
||||
**viewer-bundle.js**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #32794 | 9:51 PM | ✅ | Built claude-mem v8.2.0 production artifacts | ~376 |
|
||||
| #32789 | 9:49 PM | 🟣 | Gemini AI Provider Integration Merged to Main | ~409 |
|
||||
| #32654 | 8:51 PM | 🔵 | Identified multiple files related to queue recovery | ~375 |
|
||||
| #32643 | 8:46 PM | ✅ | Plugin Build and Marketplace Synchronization | ~336 |
|
||||
| #32600 | 8:42 PM | 🔵 | Identified potential UI element for billing toggle | ~146 |
|
||||
| #32559 | 8:18 PM | 🔵 | Listed files changed in the current branch | ~169 |
|
||||
| #32458 | 5:42 PM | ✅ | Rebuilt and synchronized plugin files after merge | ~286 |
|
||||
| #3910 | 8:28 PM | ✅ | Refined stats counter visual design | ~343 |
|
||||
| #3909 | " | 🟣 | Added clarifying descriptions to settings UI | ~335 |
|
||||
| #3812 | 6:08 PM | 🟣 | Enhanced card typography and centered content layout | ~358 |
|
||||
|
||||
### Nov 8, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #5133 | 7:29 PM | ✅ | Version 5.2.3 Released with Build Process | ~487 |
|
||||
| #4916 | 1:49 PM | ⚖️ | Claude Mem Pro Premium Offering Implementation Plan Finalized | ~946 |
|
||||
| #4902 | 1:35 PM | 🟣 | Claude Mem Pro Premium Project Initialization | ~679 |
|
||||
| #4901 | 1:31 PM | ⚖️ | Premium claude-mem Project Architecture and Planning | ~797 |
|
||||
|
||||
### Dec 1, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #18480 | 3:39 PM | ✅ | Successfully Rebuilt Plugin After Merge Conflict Resolution | ~294 |
|
||||
|
||||
### Dec 4, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #20052 | 3:23 PM | ✅ | Built and deployed version 6.5.2 to marketplace | ~321 |
|
||||
|
||||
### Dec 9, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #22557 | 1:08 AM | ✅ | Build completed for version 7.0.3 | ~342 |
|
||||
|
||||
### Dec 10, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #23444 | 2:25 PM | 🟣 | Build Pipeline Execution Successful | ~293 |
|
||||
|
||||
### Dec 16, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #27554 | 4:48 PM | ✅ | Project built successfully with version 7.3.1 | ~306 |
|
||||
|
||||
### Dec 26, 2025
|
||||
|
||||
**viewer-bundle.js**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #32985 | 11:24 PM | ✅ | Built claude-mem version 8.2.2 release artifacts | ~299 |
|
||||
| #32975 | 11:04 PM | ✅ | Build and sync pipeline completed successfully | ~208 |
|
||||
| #32864 | 7:30 PM | ✅ | Built and deployed claude-mem v8.2.0 to marketplace | ~375 |
|
||||
| #32861 | 7:08 PM | ✅ | Built claude-mem v8.2.0 production artifacts | ~360 |
|
||||
|
||||
### Dec 27, 2025
|
||||
|
||||
**viewer-bundle.js**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #33053 | 5:28 PM | ✅ | Built claude-mem 8.2.3 release artifacts | ~315 |
|
||||
|
||||
### Dec 28, 2025
|
||||
|
||||
**viewer-bundle.js**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #33670 | 11:52 PM | ✅ | Built and deployed memory session ID fix to marketplace | ~288 |
|
||||
| #33649 | 11:43 PM | ✅ | Build and Deployment System Synced to Marketplace | ~450 |
|
||||
| #33612 | 11:22 PM | ✅ | Built and deployed claude-mem version 8.2.6 with mem-search skill | ~300 |
|
||||
| #33603 | 11:20 PM | ✅ | Claude-mem plugin build and marketplace sync completed | ~425 |
|
||||
| #33485 | 10:35 PM | ✅ | Built Project with Version 8.2.6 Across All Components | ~303 |
|
||||
| #33279 | 3:07 PM | ✅ | Changed Default OpenRouter Model to Free Tier Option | ~285 |
|
||||
|
||||
### Dec 29, 2025
|
||||
|
||||
**viewer-bundle.js**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #34350 | 11:12 PM | ✅ | Version 8.5.0 Build Completed Successfully | ~425 |
|
||||
| #34208 | 10:00 PM | ✅ | claude-mem v8.2.10 built and synced to marketplace | ~416 |
|
||||
| #34163 | 9:38 PM | ✅ | Project rebuilt with updated interactive setup wizard | ~326 |
|
||||
| #34092 | 9:02 PM | ✅ | Built claude-mem project with updated interactive setup wizard | ~452 |
|
||||
| #33996 | 7:10 PM | 🟣 | Built claude-mem v8.2.10 with all hooks and services | ~387 |
|
||||
| #33951 | 6:42 PM | ✅ | Project Build Completed for Version 8.2.9 | ~326 |
|
||||
| #33877 | 5:02 PM | 🟣 | Phase 1 build process successfully tested with worker source copying | ~372 |
|
||||
| #33839 | 4:24 PM | ✅ | Build Executed Successfully for Version 8.2.8 | ~313 |
|
||||
| #33833 | 4:17 PM | ✅ | Rebuilt Project With Early Signal Handler Registration | ~353 |
|
||||
| #33818 | 4:10 PM | ✅ | Project Rebuilt Successfully After PR 489 Review Fixes | ~338 |
|
||||
| #33802 | 3:55 PM | ✅ | Project Built Successfully for Version 8.2.7 | ~258 |
|
||||
|
||||
### Dec 30, 2025
|
||||
|
||||
**viewer-bundle.js**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #34476 | 2:25 PM | ✅ | V2 Branch Builds Successfully Despite TypeScript Errors | ~316 |
|
||||
| #34451 | 2:20 PM | ✅ | Successful Build of Claude-Mem v8.5.1 Components | ~346 |
|
||||
|
||||
### Dec 31, 2025
|
||||
|
||||
**viewer-bundle.js**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #34724 | 4:51 PM | ✅ | Built claude-mem v8.5.2 with issue 499 fix | ~287 |
|
||||
| #34710 | 4:48 PM | ✅ | Built and deployed claude-mem v8.5.1 to marketplace | ~371 |
|
||||
|
||||
### Jan 1, 2026
|
||||
|
||||
**viewer-bundle.js**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #35613 | 10:57 PM | ✅ | Build System Compiled Updated Middleware | ~320 |
|
||||
| #35427 | 6:32 PM | ✅ | Claude-Mem Project Built Successfully | ~319 |
|
||||
| #35397 | 5:23 PM | ✅ | Build System Successfully Compiled All Components | ~282 |
|
||||
| #35373 | 3:05 PM | ✅ | V2 Migration Build Successful | ~305 |
|
||||
|
||||
### Jan 2, 2026
|
||||
|
||||
**viewer-bundle.js**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #35982 | 5:09 PM | ✅ | Built and deployed claude-mem version 8.5.4 with LogsModal UI component | ~295 |
|
||||
| #35976 | 4:48 PM | ✅ | Claude-mem build and marketplace sync completed | ~335 |
|
||||
| #35925 | 2:53 PM | ✅ | Built Project for Version 8.5.4 Release | ~294 |
|
||||
| #35901 | 2:49 PM | 🔵 | PR #525 File Changes Summary | ~376 |
|
||||
| #35815 | 2:26 PM | ✅ | Claude-mem plugin built and deployed to marketplace | ~381 |
|
||||
| #35776 | 1:24 PM | ✅ | Successful Build of ChromaSync Bugfix | ~246 |
|
||||
|
||||
### Jan 3, 2026
|
||||
|
||||
**viewer-bundle.js**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #36687 | 11:53 PM | ✅ | MCP SDK and esbuild Dependencies Updated | ~332 |
|
||||
|
||||
### Jan 4, 2026
|
||||
|
||||
**viewer-bundle.js**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #36701 | 12:01 AM | ✅ | Built Version 8.5.7 Plugin Artifacts | ~406 |
|
||||
|
||||
### Jan 5, 2026
|
||||
|
||||
**viewer-bundle.js**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #38092 | 10:44 PM | ✅ | Built version 9.0.0 with complete plugin bundle | ~329 |
|
||||
| #37990 | 9:00 PM | 🔵 | CLAUDE_MEM_WORKER_HOST setting used across 19 files | ~289 |
|
||||
|
||||
**CLAUDE.md**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #38078 | 9:54 PM | ✅ | CLAUDE.md Documentation Cleanup - 1,233 Lines Removed Across 18 Files | ~590 |
|
||||
|
||||
### Jan 7, 2026
|
||||
|
||||
**viewer-bundle.js**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #38186 | 7:33 PM | 🟣 | Claude-mem plugin v9.0.0 built and deployed to marketplace | ~327 |
|
||||
| #38120 | 5:46 PM | 🔴 | Rebuilt all plugin hooks and worker service | ~278 |
|
||||
| #32983 | 11:04 PM | 🟣 | Complete build and deployment pipeline executed | ~260 |
|
||||
| #32965 | 10:53 PM | 🔵 | Found plugin/ui/viewer.html - potential styling source | ~201 |
|
||||
| #32966 | " | 🔵 | viewer.html contains modal CSS including modal-header and modal-body | ~218 |
|
||||
| #32967 | " | 🔵 | ContextSettingsModal.tsx uses CSS classes defined in viewer.html | ~218 |
|
||||
| #32968 | " | 🔵 | Need to add CSS for footer to viewer.html | ~223 |
|
||||
</claude-mem-context>
|
||||
+2
-44
@@ -12,14 +12,6 @@ import { fileURLToPath } from 'url';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
const HOOKS = [
|
||||
{ name: 'context-hook', source: 'src/hooks/context-hook.ts' },
|
||||
{ name: 'new-hook', source: 'src/hooks/new-hook.ts' },
|
||||
{ name: 'save-hook', source: 'src/hooks/save-hook.ts' },
|
||||
{ name: 'summary-hook', source: 'src/hooks/summary-hook.ts' },
|
||||
{ name: 'user-message-hook', source: 'src/hooks/user-message-hook.ts' }
|
||||
];
|
||||
|
||||
const WORKER_SERVICE = {
|
||||
name: 'worker-service',
|
||||
source: 'src/services/worker-service.ts'
|
||||
@@ -159,45 +151,11 @@ async function buildHooks() {
|
||||
const contextGenStats = fs.statSync(`${hooksDir}/${CONTEXT_GENERATOR.name}.cjs`);
|
||||
console.log(`✓ context-generator built (${(contextGenStats.size / 1024).toFixed(2)} KB)`);
|
||||
|
||||
// Build each hook
|
||||
for (const hook of HOOKS) {
|
||||
console.log(`\n🔧 Building ${hook.name}...`);
|
||||
|
||||
const outfile = `${hooksDir}/${hook.name}.js`;
|
||||
|
||||
await build({
|
||||
entryPoints: [hook.source],
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
target: 'node18',
|
||||
format: 'esm',
|
||||
outfile,
|
||||
minify: true,
|
||||
external: ['bun:sqlite'],
|
||||
define: {
|
||||
'__DEFAULT_PACKAGE_VERSION__': `"${version}"`
|
||||
},
|
||||
banner: {
|
||||
js: '#!/usr/bin/env bun'
|
||||
}
|
||||
});
|
||||
|
||||
// Make executable
|
||||
fs.chmodSync(outfile, 0o755);
|
||||
|
||||
// Check file size
|
||||
const stats = fs.statSync(outfile);
|
||||
const sizeInKB = (stats.size / 1024).toFixed(2);
|
||||
console.log(`✓ ${hook.name} built (${sizeInKB} KB)`);
|
||||
}
|
||||
|
||||
console.log('\n✅ All hooks, worker service, and MCP server built successfully!');
|
||||
console.log('\n✅ Worker service, MCP server, and context generator built successfully!');
|
||||
console.log(` Output: ${hooksDir}/`);
|
||||
console.log(` - Hooks: *-hook.js`);
|
||||
console.log(` - Worker: worker-service.cjs`);
|
||||
console.log(` - MCP Server: mcp-server.cjs`);
|
||||
console.log('\n💡 Note: Dependencies will be auto-installed on first hook execution');
|
||||
console.log('📝 Cursor hooks are in cursor-hooks/ (no build needed - plain shell scripts)');
|
||||
console.log(` - Context Generator: context-generator.cjs`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('\n❌ Build failed:', error.message);
|
||||
|
||||
+13
-105
@@ -3,126 +3,34 @@
|
||||
|
||||
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||
|
||||
### Oct 25, 2025
|
||||
### Dec 10, 2025
|
||||
|
||||
**settings-cli.ts**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #2415 | 4:06 PM | 🟣 | Settings System with Multi-Interface Configuration Management | ~516 |
|
||||
| #2414 | " | 🟣 | Multi-Interface Settings System Implementation | ~498 |
|
||||
| #2413 | " | 🟣 | Settings System with Schema, Service, and CLI Implementation | ~428 |
|
||||
| #23825 | 11:12 PM | ✅ | Worker Port Set to 38888 for Migration Phase | ~283 |
|
||||
| #23824 | " | 🔵 | Worker Port Sourced from getWorkerPort() Utility | ~247 |
|
||||
| #23816 | 10:52 PM | 🟣 | Worker CLI Command Interface Created | ~325 |
|
||||
|
||||
### Dec 11, 2025
|
||||
|
||||
**worker-cli.ts**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #24076 | 3:16 PM | 🔵 | 45 files modified with nearly equal additions and deletions during Bun migration | ~443 |
|
||||
| #24061 | 2:58 PM | 🔴 | Added explicit process.exit() to worker CLI commands | ~274 |
|
||||
| #23961 | 1:59 PM | 🔵 | CLI Module Contains Worker Command Interface | ~205 |
|
||||
| #23949 | 1:45 PM | 🔴 | CLI Worker Management Unified to Settings-Based Port | ~373 |
|
||||
| #23947 | 1:40 PM | 🔵 | Comprehensive Port Configuration Audit Complete | ~532 |
|
||||
| #23939 | 1:38 PM | 🔵 | worker-cli.ts Hardcoded MIGRATION_PORT Usage | ~372 |
|
||||
| #23934 | 1:36 PM | 🔵 | Port 38888 Hardcoded in Two Migration Files | ~341 |
|
||||
| #23933 | " | 🔵 | Comprehensive Port 37777 References Across Documentation and Code | ~427 |
|
||||
| #23931 | " | 🔵 | Comprehensive Port Usage Mapping Across Codebase | ~449 |
|
||||
|
||||
**cli**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #23986 | 2:04 PM | 🔵 | Code Quality Audit Completed - 25 Issues Identified Across Six Principles | ~602 |
|
||||
| #24060 | 2:58 PM | 🔴 | Worker CLI Start Command Exit Behavior Fixed | ~232 |
|
||||
|
||||
### Dec 12, 2025
|
||||
|
||||
**worker-cli.ts**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #24394 | 7:24 PM | 🟣 | Phase 6 Agent Completed Git Commit and PR Update | ~444 |
|
||||
| #24391 | " | ✅ | Committed Complete Migration from better-sqlite3 to bun:sqlite | ~436 |
|
||||
| #24390 | 7:23 PM | ✅ | Staged All 19 Modified Files for Git Commit | ~301 |
|
||||
| #24389 | " | 🟣 | Phase 5 Agent Completed Full System Verification | ~726 |
|
||||
| #24388 | 7:22 PM | 🟣 | Phase 5 Complete - All Verification Passed for Production Deployment | ~600 |
|
||||
| #24387 | " | 🔵 | Uncommitted Changes Identified Across Documentation and Core Services | ~368 |
|
||||
| #24369 | 7:10 PM | 🔵 | Worker CLI Process Management Architecture | ~242 |
|
||||
| #24328 | 6:51 PM | 🔵 | Worker CLI Switch Statement Structure Confirmed | ~634 |
|
||||
| #24327 | " | ⚖️ | Exploration Phase Plan for PR #248 Fixes | ~695 |
|
||||
| #24324 | 6:50 PM | ⚖️ | Pre-Merge Scope Definition and Implementation Path | ~651 |
|
||||
| #24322 | 6:49 PM | 🔵 | Missing Break Statement in Worker CLI Switch Case | ~542 |
|
||||
| #24320 | " | ⚖️ | PR #248 Issue Prioritization Strategy | ~616 |
|
||||
| #24319 | 6:48 PM | 🔵 | PR #248 Review Status: PM2 to Bun Migration Assessment | ~736 |
|
||||
| #24276 | 5:20 PM | 🔵 | worker-cli.ts provides command-line interface for worker management | ~529 |
|
||||
| #24359 | 7:00 PM | 🟣 | Phase 1 Critical Code Fixes Completed via Agent Task | ~441 |
|
||||
| #24358 | 6:59 PM | ✅ | Completed Phase 1 Code Fixes for better-sqlite3 Migration | ~385 |
|
||||
| #24348 | 6:57 PM | 🔴 | Added Defensive Break Statement to worker-cli.ts Restart Case | ~269 |
|
||||
| #24345 | " | 🔵 | worker-cli.ts Missing Break Statement in Switch Case | ~318 |
|
||||
|
||||
### Dec 13, 2025
|
||||
### Dec 14, 2025
|
||||
|
||||
**worker-cli.ts**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #25370 | 9:26 PM | 🔵 | Worker CLI Process Management Interface | ~429 |
|
||||
| #25343 | 9:17 PM | 🔵 | Console.error Used for Migration Progress and Error Logging | ~451 |
|
||||
| #25321 | 9:12 PM | 🔵 | Console.error Usage Found in 29 Files | ~366 |
|
||||
| #24757 | 4:46 PM | 🔵 | Worker CLI Provides Direct Interface to ProcessManager | ~342 |
|
||||
|
||||
### Dec 16, 2025
|
||||
|
||||
**worker-cli.ts**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #27825 | 6:56 PM | 🔵 | CLI Commands Rely on ProcessManager PID File Methods | ~301 |
|
||||
| #27373 | 3:15 PM | 🔵 | Worker CLI Command Interface | ~331 |
|
||||
|
||||
### Dec 17, 2025
|
||||
|
||||
**worker-cli.ts**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #28930 | 7:30 PM | 🔵 | Worker CLI Distribution and Build System | ~275 |
|
||||
| #28929 | " | 🔵 | ProcessManager Usage Across Codebase | ~319 |
|
||||
|
||||
### Dec 21, 2025
|
||||
|
||||
**worker-cli.ts**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #31603 | 8:21 PM | 🔵 | Complete Console.* Statement Audit Across Codebase | ~813 |
|
||||
| #31599 | 8:19 PM | 🔵 | 136 console logging statements found in TypeScript source files | ~538 |
|
||||
|
||||
### Dec 24, 2025
|
||||
|
||||
**worker-cli.ts**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #32344 | 8:42 PM | 🔵 | Timeline Reveals Cleanup Hook Caused Data Loss via Premature SDK Agent Termination | ~627 |
|
||||
| #32317 | 8:41 PM | 🔄 | Removed unreachable break statement from worker CLI start command | ~204 |
|
||||
| #32191 | 7:38 PM | 🔴 | Worker CLI now outputs HOOK_STANDARD_RESPONSE format | ~277 |
|
||||
| #32189 | 7:37 PM | 🔴 | Worker CLI Now Outputs Standard Hook Response Format | ~306 |
|
||||
| #32068 | 3:23 PM | 🔵 | Worker CLI Supports Start/Stop/Restart/Status Commands | ~275 |
|
||||
|
||||
### Dec 25, 2025
|
||||
|
||||
**worker-cli.ts**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #32456 | 5:41 PM | ✅ | Completed merge of main branch into feature/titans-phase1-3 | ~354 |
|
||||
|
||||
### Dec 26, 2025
|
||||
|
||||
**worker-cli.ts**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #32855 | 7:04 PM | 🔄 | Consolidated worker process management into single service | ~322 |
|
||||
|
||||
### Dec 28, 2025
|
||||
|
||||
**worker-cli.ts**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #33370 | 3:47 PM | 🔵 | ToxMox Wrapper Architecture Deleted December 26, Six Days After Implementation | ~506 |
|
||||
| #33284 | 3:07 PM | 🔄 | Consolidated Worker Lifecycle Management (-580 Lines) | ~327 |
|
||||
|
||||
### Jan 5, 2026
|
||||
|
||||
**types.ts**
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #38071 | 9:52 PM | 🟣 | Unified CLI Phase 1 - Infrastructure Foundation with Type System and Stdin Reader | ~451 |
|
||||
| #26766 | 11:30 PM | ⚖️ | Root Cause Identified: Missing Post-Install Worker Restart Trigger in Plugin Update Flow | ~604 |
|
||||
| #26722 | 11:23 PM | 🔵 | Worker CLI TypeScript Source Shows Simple ProcessManager Delegation | ~394 |
|
||||
| #26721 | " | 🔵 | Worker CLI Source Code Shows Simple Restart Logic Without Delays | ~425 |
|
||||
</claude-mem-context>
|
||||
@@ -0,0 +1,7 @@
|
||||
<claude-mem-context>
|
||||
# Recent Activity
|
||||
|
||||
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||
|
||||
*No recent activity*
|
||||
</claude-mem-context>
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { PlatformAdapter, NormalizedHookInput, HookResult } from '../types.js';
|
||||
|
||||
// Maps Claude Code stdin format (session_id, cwd, tool_name, etc.)
|
||||
// SessionStart hooks receive no stdin, so we must handle undefined input gracefully
|
||||
export const claudeCodeAdapter: PlatformAdapter = {
|
||||
normalizeInput(raw) {
|
||||
const r = (raw ?? {}) as any;
|
||||
return {
|
||||
sessionId: r.session_id,
|
||||
cwd: r.cwd ?? process.cwd(),
|
||||
prompt: r.prompt,
|
||||
toolName: r.tool_name,
|
||||
toolInput: r.tool_input,
|
||||
toolResponse: r.tool_response,
|
||||
transcriptPath: r.transcript_path,
|
||||
};
|
||||
},
|
||||
formatOutput(result) {
|
||||
if (result.hookSpecificOutput) {
|
||||
return { hookSpecificOutput: result.hookSpecificOutput };
|
||||
}
|
||||
return { continue: result.continue ?? true, suppressOutput: result.suppressOutput ?? true };
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { PlatformAdapter, NormalizedHookInput, HookResult } from '../types.js';
|
||||
|
||||
// Maps Cursor stdin format - field names differ from Claude Code
|
||||
// Cursor uses: conversation_id, workspace_roots[], result_json, command/output
|
||||
// Handle undefined input gracefully for hooks that don't receive stdin
|
||||
export const cursorAdapter: PlatformAdapter = {
|
||||
normalizeInput(raw) {
|
||||
const r = (raw ?? {}) as any;
|
||||
// Cursor-specific: shell commands come as command/output instead of tool_name/input/response
|
||||
const isShellCommand = !!r.command && !r.tool_name;
|
||||
return {
|
||||
sessionId: r.conversation_id || r.generation_id, // conversation_id preferred
|
||||
cwd: r.workspace_roots?.[0] ?? process.cwd(), // First workspace root
|
||||
prompt: r.prompt,
|
||||
toolName: isShellCommand ? 'Bash' : r.tool_name,
|
||||
toolInput: isShellCommand ? { command: r.command } : r.tool_input,
|
||||
toolResponse: isShellCommand ? { output: r.output } : r.result_json, // result_json not tool_response
|
||||
transcriptPath: undefined, // Cursor doesn't provide transcript
|
||||
// Cursor-specific fields for file edits
|
||||
filePath: r.file_path,
|
||||
edits: r.edits,
|
||||
};
|
||||
},
|
||||
formatOutput(result) {
|
||||
// Cursor expects simpler response - just continue flag
|
||||
return { continue: result.continue ?? true };
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { PlatformAdapter } from '../types.js';
|
||||
import { claudeCodeAdapter } from './claude-code.js';
|
||||
import { cursorAdapter } from './cursor.js';
|
||||
import { rawAdapter } from './raw.js';
|
||||
|
||||
export function getPlatformAdapter(platform: string): PlatformAdapter {
|
||||
switch (platform) {
|
||||
case 'claude-code': return claudeCodeAdapter;
|
||||
case 'cursor': return cursorAdapter;
|
||||
case 'raw': return rawAdapter;
|
||||
default: throw new Error(`Unknown platform: ${platform}`);
|
||||
}
|
||||
}
|
||||
|
||||
export { claudeCodeAdapter, cursorAdapter, rawAdapter };
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { PlatformAdapter, NormalizedHookInput, HookResult } from '../types.js';
|
||||
|
||||
// Raw adapter passes through with minimal transformation - useful for testing
|
||||
export const rawAdapter: PlatformAdapter = {
|
||||
normalizeInput(raw) {
|
||||
const r = raw as any;
|
||||
return {
|
||||
sessionId: r.sessionId ?? r.session_id ?? 'unknown',
|
||||
cwd: r.cwd ?? process.cwd(),
|
||||
prompt: r.prompt,
|
||||
toolName: r.toolName ?? r.tool_name,
|
||||
toolInput: r.toolInput ?? r.tool_input,
|
||||
toolResponse: r.toolResponse ?? r.tool_response,
|
||||
transcriptPath: r.transcriptPath ?? r.transcript_path,
|
||||
filePath: r.filePath ?? r.file_path,
|
||||
edits: r.edits,
|
||||
};
|
||||
},
|
||||
formatOutput(result) {
|
||||
return result;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
<claude-mem-context>
|
||||
# Recent Activity
|
||||
|
||||
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||
|
||||
*No recent activity*
|
||||
</claude-mem-context>
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Context Handler - SessionStart
|
||||
*
|
||||
* Extracted from context-hook.ts - calls worker to generate context.
|
||||
* Returns context as hookSpecificOutput for Claude Code to inject.
|
||||
*/
|
||||
|
||||
import type { EventHandler, NormalizedHookInput, HookResult } from '../types.js';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../../shared/worker-utils.js';
|
||||
import { getProjectContext } from '../../utils/project-name.js';
|
||||
|
||||
export const contextHandler: EventHandler = {
|
||||
async execute(input: NormalizedHookInput): Promise<HookResult> {
|
||||
// Ensure worker is running before any other logic
|
||||
await ensureWorkerRunning();
|
||||
|
||||
const cwd = input.cwd ?? process.cwd();
|
||||
const context = getProjectContext(cwd);
|
||||
const port = getWorkerPort();
|
||||
|
||||
// Pass all projects (parent + worktree if applicable) for unified timeline
|
||||
const projectsParam = context.allProjects.join(',');
|
||||
const url = `http://127.0.0.1:${port}/api/context/inject?projects=${encodeURIComponent(projectsParam)}`;
|
||||
|
||||
// Note: Removed AbortSignal.timeout due to Windows Bun cleanup issue (libuv assertion)
|
||||
// Worker service has its own timeouts, so client-side timeout is redundant
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Context generation failed: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.text();
|
||||
const additionalContext = result.trim();
|
||||
|
||||
return {
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'SessionStart',
|
||||
additionalContext
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* File Edit Handler - Cursor-specific afterFileEdit
|
||||
*
|
||||
* Handles file edit observations from Cursor IDE.
|
||||
* Similar to observation handler but with file-specific metadata.
|
||||
*/
|
||||
|
||||
import type { EventHandler, NormalizedHookInput, HookResult } from '../types.js';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../../shared/worker-utils.js';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
|
||||
export const fileEditHandler: EventHandler = {
|
||||
async execute(input: NormalizedHookInput): Promise<HookResult> {
|
||||
// Ensure worker is running before any other logic
|
||||
await ensureWorkerRunning();
|
||||
|
||||
const { sessionId, cwd, filePath, edits } = input;
|
||||
|
||||
if (!filePath) {
|
||||
throw new Error('fileEditHandler requires filePath');
|
||||
}
|
||||
|
||||
const port = getWorkerPort();
|
||||
|
||||
logger.dataIn('HOOK', `FileEdit: ${filePath}`, {
|
||||
workerPort: port,
|
||||
editCount: edits?.length ?? 0
|
||||
});
|
||||
|
||||
// Validate required fields before sending to worker
|
||||
if (!cwd) {
|
||||
throw new Error(`Missing cwd in FileEdit hook input for session ${sessionId}, file ${filePath}`);
|
||||
}
|
||||
|
||||
// Send to worker as an observation with file edit metadata
|
||||
// The observation handler on the worker will process this appropriately
|
||||
const response = await fetch(`http://127.0.0.1:${port}/api/sessions/observations`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
contentSessionId: sessionId,
|
||||
tool_name: 'write_file',
|
||||
tool_input: { filePath, edits },
|
||||
tool_response: { success: true },
|
||||
cwd
|
||||
})
|
||||
// Note: Removed signal to avoid Windows Bun cleanup issue (libuv assertion)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`File edit observation storage failed: ${response.status}`);
|
||||
}
|
||||
|
||||
logger.debug('HOOK', 'File edit observation sent successfully', { filePath });
|
||||
|
||||
return { continue: true, suppressOutput: true };
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Event Handler Factory
|
||||
*
|
||||
* Returns the appropriate handler for a given event type.
|
||||
*/
|
||||
|
||||
import type { EventHandler } from '../types.js';
|
||||
import { contextHandler } from './context.js';
|
||||
import { sessionInitHandler } from './session-init.js';
|
||||
import { observationHandler } from './observation.js';
|
||||
import { summarizeHandler } from './summarize.js';
|
||||
import { userMessageHandler } from './user-message.js';
|
||||
import { fileEditHandler } from './file-edit.js';
|
||||
|
||||
export type EventType =
|
||||
| 'context' // SessionStart - inject context
|
||||
| 'session-init' // UserPromptSubmit - initialize session
|
||||
| 'observation' // PostToolUse - save observation
|
||||
| 'summarize' // Stop - generate summary
|
||||
| 'user-message' // SessionStart (parallel) - display to user
|
||||
| 'file-edit'; // Cursor afterFileEdit
|
||||
|
||||
const handlers: Record<EventType, EventHandler> = {
|
||||
'context': contextHandler,
|
||||
'session-init': sessionInitHandler,
|
||||
'observation': observationHandler,
|
||||
'summarize': summarizeHandler,
|
||||
'user-message': userMessageHandler,
|
||||
'file-edit': fileEditHandler
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the event handler for a given event type.
|
||||
*
|
||||
* @param eventType The type of event to handle
|
||||
* @returns The appropriate EventHandler
|
||||
* @throws Error if event type is not recognized
|
||||
*/
|
||||
export function getEventHandler(eventType: EventType): EventHandler {
|
||||
const handler = handlers[eventType];
|
||||
if (!handler) {
|
||||
throw new Error(`Unknown event type: ${eventType}`);
|
||||
}
|
||||
return handler;
|
||||
}
|
||||
|
||||
// Re-export individual handlers for direct access if needed
|
||||
export { contextHandler } from './context.js';
|
||||
export { sessionInitHandler } from './session-init.js';
|
||||
export { observationHandler } from './observation.js';
|
||||
export { summarizeHandler } from './summarize.js';
|
||||
export { userMessageHandler } from './user-message.js';
|
||||
export { fileEditHandler } from './file-edit.js';
|
||||
@@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Observation Handler - PostToolUse
|
||||
*
|
||||
* Extracted from save-hook.ts - sends tool usage to worker for storage.
|
||||
*/
|
||||
|
||||
import type { EventHandler, NormalizedHookInput, HookResult } from '../types.js';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../../shared/worker-utils.js';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
|
||||
export const observationHandler: EventHandler = {
|
||||
async execute(input: NormalizedHookInput): Promise<HookResult> {
|
||||
// Ensure worker is running before any other logic
|
||||
await ensureWorkerRunning();
|
||||
|
||||
const { sessionId, cwd, toolName, toolInput, toolResponse } = input;
|
||||
|
||||
if (!toolName) {
|
||||
throw new Error('observationHandler requires toolName');
|
||||
}
|
||||
|
||||
const port = getWorkerPort();
|
||||
|
||||
const toolStr = logger.formatTool(toolName, toolInput);
|
||||
|
||||
logger.dataIn('HOOK', `PostToolUse: ${toolStr}`, {
|
||||
workerPort: port
|
||||
});
|
||||
|
||||
// Validate required fields before sending to worker
|
||||
if (!cwd) {
|
||||
throw new Error(`Missing cwd in PostToolUse hook input for session ${sessionId}, tool ${toolName}`);
|
||||
}
|
||||
|
||||
// Send to worker - worker handles privacy check and database operations
|
||||
const response = await fetch(`http://127.0.0.1:${port}/api/sessions/observations`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
contentSessionId: sessionId,
|
||||
tool_name: toolName,
|
||||
tool_input: toolInput,
|
||||
tool_response: toolResponse,
|
||||
cwd
|
||||
})
|
||||
// Note: Removed signal to avoid Windows Bun cleanup issue (libuv assertion)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Observation storage failed: ${response.status}`);
|
||||
}
|
||||
|
||||
logger.debug('HOOK', 'Observation sent successfully', { toolName });
|
||||
|
||||
return { continue: true, suppressOutput: true };
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Session Init Handler - UserPromptSubmit
|
||||
*
|
||||
* Extracted from new-hook.ts - initializes session and starts SDK agent.
|
||||
*/
|
||||
|
||||
import type { EventHandler, NormalizedHookInput, HookResult } from '../types.js';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../../shared/worker-utils.js';
|
||||
import { getProjectName } from '../../utils/project-name.js';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
|
||||
export const sessionInitHandler: EventHandler = {
|
||||
async execute(input: NormalizedHookInput): Promise<HookResult> {
|
||||
// Ensure worker is running before any other logic
|
||||
await ensureWorkerRunning();
|
||||
|
||||
const { sessionId, cwd, prompt } = input;
|
||||
|
||||
if (!prompt) {
|
||||
throw new Error('sessionInitHandler requires prompt');
|
||||
}
|
||||
|
||||
const project = getProjectName(cwd);
|
||||
const port = getWorkerPort();
|
||||
|
||||
logger.debug('HOOK', 'session-init: Calling /api/sessions/init', { contentSessionId: sessionId, project });
|
||||
|
||||
// Initialize session via HTTP - handles DB operations and privacy checks
|
||||
const initResponse = await fetch(`http://127.0.0.1:${port}/api/sessions/init`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
contentSessionId: sessionId,
|
||||
project,
|
||||
prompt
|
||||
})
|
||||
// Note: Removed signal to avoid Windows Bun cleanup issue (libuv assertion)
|
||||
});
|
||||
|
||||
if (!initResponse.ok) {
|
||||
throw new Error(`Session initialization failed: ${initResponse.status}`);
|
||||
}
|
||||
|
||||
const initResult = await initResponse.json() as {
|
||||
sessionDbId: number;
|
||||
promptNumber: number;
|
||||
skipped?: boolean;
|
||||
reason?: string;
|
||||
};
|
||||
const sessionDbId = initResult.sessionDbId;
|
||||
const promptNumber = initResult.promptNumber;
|
||||
|
||||
logger.debug('HOOK', 'session-init: Received from /api/sessions/init', { sessionDbId, promptNumber, skipped: initResult.skipped });
|
||||
|
||||
// Debug-level alignment log for detailed tracing
|
||||
logger.debug('HOOK', `[ALIGNMENT] Hook Entry | contentSessionId=${sessionId} | prompt#=${promptNumber} | sessionDbId=${sessionDbId}`);
|
||||
|
||||
// Check if prompt was entirely private (worker performs privacy check)
|
||||
if (initResult.skipped && initResult.reason === 'private') {
|
||||
logger.info('HOOK', `INIT_COMPLETE | sessionDbId=${sessionDbId} | promptNumber=${promptNumber} | skipped=true | reason=private`, {
|
||||
sessionId: sessionDbId
|
||||
});
|
||||
return { continue: true, suppressOutput: true };
|
||||
}
|
||||
|
||||
// Only initialize SDK agent for Claude Code (not Cursor)
|
||||
// Cursor doesn't use the SDK agent - it only needs session/observation storage
|
||||
if (input.platform !== 'cursor' && sessionDbId) {
|
||||
// Strip leading slash from commands for memory agent
|
||||
// /review 101 -> review 101 (more semantic for observations)
|
||||
const cleanedPrompt = prompt.startsWith('/') ? prompt.substring(1) : prompt;
|
||||
|
||||
logger.debug('HOOK', 'session-init: Calling /sessions/{sessionDbId}/init', { sessionDbId, promptNumber });
|
||||
|
||||
// Initialize SDK agent session via HTTP (starts the agent!)
|
||||
const response = await fetch(`http://127.0.0.1:${port}/sessions/${sessionDbId}/init`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ userPrompt: cleanedPrompt, promptNumber })
|
||||
// Note: Removed signal to avoid Windows Bun cleanup issue (libuv assertion)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`SDK agent start failed: ${response.status}`);
|
||||
}
|
||||
} else if (input.platform === 'cursor') {
|
||||
logger.debug('HOOK', 'session-init: Skipping SDK agent init for Cursor platform', { sessionDbId, promptNumber });
|
||||
}
|
||||
|
||||
logger.info('HOOK', `INIT_COMPLETE | sessionDbId=${sessionDbId} | promptNumber=${promptNumber} | project=${project}`, {
|
||||
sessionId: sessionDbId
|
||||
});
|
||||
|
||||
return { continue: true, suppressOutput: true };
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Summarize Handler - Stop
|
||||
*
|
||||
* Extracted from summary-hook.ts - sends summary request to worker.
|
||||
* Transcript parsing stays in the hook because only the hook has access to
|
||||
* the transcript file path.
|
||||
*/
|
||||
|
||||
import type { EventHandler, NormalizedHookInput, HookResult } from '../types.js';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../../shared/worker-utils.js';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
import { extractLastMessage } from '../../shared/transcript-parser.js';
|
||||
|
||||
export const summarizeHandler: EventHandler = {
|
||||
async execute(input: NormalizedHookInput): Promise<HookResult> {
|
||||
// Ensure worker is running before any other logic
|
||||
await ensureWorkerRunning();
|
||||
|
||||
const { sessionId, transcriptPath } = input;
|
||||
|
||||
const port = getWorkerPort();
|
||||
|
||||
// Validate required fields before processing
|
||||
if (!transcriptPath) {
|
||||
throw new Error(`Missing transcriptPath in Stop hook input for session ${sessionId}`);
|
||||
}
|
||||
|
||||
// Extract last assistant message from transcript (the work Claude did)
|
||||
// Note: "user" messages in transcripts are mostly tool_results, not actual user input.
|
||||
// The user's original request is already stored in user_prompts table.
|
||||
const lastAssistantMessage = extractLastMessage(transcriptPath, 'assistant', true);
|
||||
|
||||
logger.dataIn('HOOK', 'Stop: Requesting summary', {
|
||||
workerPort: port,
|
||||
hasLastAssistantMessage: !!lastAssistantMessage
|
||||
});
|
||||
|
||||
// Send to worker - worker handles privacy check and database operations
|
||||
const response = await fetch(`http://127.0.0.1:${port}/api/sessions/summarize`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
contentSessionId: sessionId,
|
||||
last_assistant_message: lastAssistantMessage
|
||||
})
|
||||
// Note: Removed signal to avoid Windows Bun cleanup issue (libuv assertion)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
// Return standard response even on failure (matches original behavior)
|
||||
return { continue: true, suppressOutput: true };
|
||||
}
|
||||
|
||||
logger.debug('HOOK', 'Summary request sent successfully');
|
||||
|
||||
return { continue: true, suppressOutput: true };
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* User Message Handler - SessionStart (parallel)
|
||||
*
|
||||
* Extracted from user-message-hook.ts - displays context info to user via stderr.
|
||||
* Uses exit code 3 to show user message without injecting into Claude's context.
|
||||
*/
|
||||
|
||||
import { basename } from 'path';
|
||||
import type { EventHandler, NormalizedHookInput, HookResult } from '../types.js';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../../shared/worker-utils.js';
|
||||
import { HOOK_EXIT_CODES } from '../../shared/hook-constants.js';
|
||||
|
||||
export const userMessageHandler: EventHandler = {
|
||||
async execute(input: NormalizedHookInput): Promise<HookResult> {
|
||||
// Ensure worker is running
|
||||
await ensureWorkerRunning();
|
||||
|
||||
const port = getWorkerPort();
|
||||
const project = basename(input.cwd ?? process.cwd());
|
||||
|
||||
// Fetch formatted context directly from worker API
|
||||
// Note: Removed AbortSignal.timeout to avoid Windows Bun cleanup issue (libuv assertion)
|
||||
const response = await fetch(
|
||||
`http://127.0.0.1:${port}/api/context/inject?project=${encodeURIComponent(project)}&colors=true`,
|
||||
{ method: 'GET' }
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch context: ${response.status}`);
|
||||
}
|
||||
|
||||
const output = await response.text();
|
||||
|
||||
// Write to stderr for user visibility (Claude Code UI shows stderr)
|
||||
console.error(
|
||||
"\n\n" + String.fromCodePoint(0x1F4DD) + " Claude-Mem Context Loaded\n" +
|
||||
" " + String.fromCodePoint(0x2139, 0xFE0F) + " Note: This appears as stderr but is informational only\n\n" +
|
||||
output +
|
||||
"\n\n" + String.fromCodePoint(0x1F4A1) + " New! Wrap all or part of any message with <private> ... </private> to prevent storing sensitive information in your observation history.\n" +
|
||||
"\n" + String.fromCodePoint(0x1F4AC) + " Community https://discord.gg/J4wttp9vDu" +
|
||||
`\n` + String.fromCodePoint(0x1F4FA) + ` Watch live in browser http://localhost:${port}/\n`
|
||||
);
|
||||
|
||||
return { exitCode: HOOK_EXIT_CODES.USER_MESSAGE_ONLY };
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { readJsonFromStdin } from './stdin-reader.js';
|
||||
import { getPlatformAdapter } from './adapters/index.js';
|
||||
import { getEventHandler } from './handlers/index.js';
|
||||
import { HOOK_EXIT_CODES } from '../shared/hook-constants.js';
|
||||
|
||||
export async function hookCommand(platform: string, event: string): Promise<void> {
|
||||
try {
|
||||
const adapter = getPlatformAdapter(platform);
|
||||
const handler = getEventHandler(event);
|
||||
|
||||
const rawInput = await readJsonFromStdin();
|
||||
const input = adapter.normalizeInput(rawInput);
|
||||
input.platform = platform; // Inject platform for handler-level decisions
|
||||
const result = await handler.execute(input);
|
||||
const output = adapter.formatOutput(result);
|
||||
|
||||
console.log(JSON.stringify(output));
|
||||
process.exit(result.exitCode ?? HOOK_EXIT_CODES.SUCCESS);
|
||||
} catch (error) {
|
||||
console.error(`Hook error: ${error}`);
|
||||
// Use exit code 2 (blocking error) so users see the error message
|
||||
// Exit code 1 only shows in verbose mode per Claude Code docs
|
||||
process.exit(HOOK_EXIT_CODES.BLOCKING_ERROR); // = 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Stdin reading utility extracted from hook patterns
|
||||
// See src/hooks/save-hook.ts for the original pattern
|
||||
|
||||
export async function readJsonFromStdin(): Promise<unknown> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let input = '';
|
||||
process.stdin.on('data', (chunk) => input += chunk);
|
||||
process.stdin.on('end', () => {
|
||||
try {
|
||||
resolve(input.trim() ? JSON.parse(input) : undefined);
|
||||
} catch (e) {
|
||||
reject(new Error(`Failed to parse hook input: ${e}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
export interface NormalizedHookInput {
|
||||
sessionId: string;
|
||||
cwd: string;
|
||||
platform?: string; // 'claude-code' or 'cursor'
|
||||
prompt?: string;
|
||||
toolName?: string;
|
||||
toolInput?: unknown;
|
||||
toolResponse?: unknown;
|
||||
transcriptPath?: string;
|
||||
// Cursor-specific fields
|
||||
filePath?: string; // afterFileEdit
|
||||
edits?: unknown[]; // afterFileEdit
|
||||
}
|
||||
|
||||
export interface HookResult {
|
||||
continue?: boolean;
|
||||
suppressOutput?: boolean;
|
||||
hookSpecificOutput?: { hookEventName: string; additionalContext: string };
|
||||
exitCode?: number;
|
||||
}
|
||||
|
||||
export interface PlatformAdapter {
|
||||
normalizeInput(raw: unknown): NormalizedHookInput;
|
||||
formatOutput(result: HookResult): unknown;
|
||||
}
|
||||
|
||||
export interface EventHandler {
|
||||
execute(input: NormalizedHookInput): Promise<HookResult>;
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/**
|
||||
* Context Hook - SessionStart
|
||||
*
|
||||
* Pure HTTP client - calls worker to generate context.
|
||||
* This allows the hook to run under any runtime (Node.js or Bun) since it has no
|
||||
* native module dependencies.
|
||||
*/
|
||||
|
||||
import { stdin } from "process";
|
||||
import { ensureWorkerRunning, getWorkerPort } from "../shared/worker-utils.js";
|
||||
import { HOOK_TIMEOUTS } from "../shared/hook-constants.js";
|
||||
import { getProjectContext } from "../utils/project-name.js";
|
||||
import { logger } from "../utils/logger.js";
|
||||
|
||||
export interface SessionStartInput {
|
||||
session_id: string;
|
||||
transcript_path: string;
|
||||
cwd: string;
|
||||
hook_event_name?: string;
|
||||
}
|
||||
|
||||
async function contextHook(input?: SessionStartInput): Promise<string> {
|
||||
// Ensure worker is running before any other logic
|
||||
await ensureWorkerRunning();
|
||||
|
||||
const cwd = input?.cwd ?? process.cwd();
|
||||
const context = getProjectContext(cwd);
|
||||
const port = getWorkerPort();
|
||||
|
||||
// Pass all projects (parent + worktree if applicable) for unified timeline
|
||||
const projectsParam = context.allProjects.join(',');
|
||||
const url = `http://127.0.0.1:${port}/api/context/inject?projects=${encodeURIComponent(projectsParam)}`;
|
||||
|
||||
// Note: Removed AbortSignal.timeout due to Windows Bun cleanup issue (libuv assertion)
|
||||
// Worker service has its own timeouts, so client-side timeout is redundant
|
||||
const response = await fetch(url);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Context generation failed: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.text();
|
||||
return result.trim();
|
||||
}
|
||||
|
||||
// Entry Point - handle stdin/stdout
|
||||
const forceColors = process.argv.includes("--colors");
|
||||
|
||||
if (stdin.isTTY || forceColors) {
|
||||
contextHook(undefined).then((text) => {
|
||||
console.log(text);
|
||||
process.exit(0);
|
||||
});
|
||||
} else {
|
||||
let input = "";
|
||||
stdin.on("data", (chunk) => (input += chunk));
|
||||
stdin.on("end", async () => {
|
||||
let parsed: SessionStartInput | undefined;
|
||||
try {
|
||||
parsed = input.trim() ? JSON.parse(input) : undefined;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse hook input: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
const text = await contextHook(parsed);
|
||||
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
hookSpecificOutput: {
|
||||
hookEventName: "SessionStart",
|
||||
additionalContext: text,
|
||||
},
|
||||
})
|
||||
);
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
import { stdin } from 'process';
|
||||
import { STANDARD_HOOK_RESPONSE } from './hook-response.js';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
|
||||
import { getProjectName } from '../utils/project-name.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
|
||||
export interface UserPromptSubmitInput {
|
||||
session_id: string;
|
||||
cwd: string;
|
||||
prompt: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* New Hook Main Logic
|
||||
*/
|
||||
async function newHook(input?: UserPromptSubmitInput): Promise<void> {
|
||||
// Ensure worker is running before any other logic
|
||||
await ensureWorkerRunning();
|
||||
|
||||
if (!input) {
|
||||
throw new Error('newHook requires input');
|
||||
}
|
||||
|
||||
const { session_id, cwd, prompt } = input;
|
||||
const project = getProjectName(cwd);
|
||||
const port = getWorkerPort();
|
||||
|
||||
logger.debug('HOOK', 'new-hook: Calling /api/sessions/init', { contentSessionId: session_id, project });
|
||||
|
||||
// Initialize session via HTTP - handles DB operations and privacy checks
|
||||
const initResponse = await fetch(`http://127.0.0.1:${port}/api/sessions/init`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
contentSessionId: session_id,
|
||||
project,
|
||||
prompt
|
||||
})
|
||||
// Note: Removed signal to avoid Windows Bun cleanup issue (libuv assertion)
|
||||
});
|
||||
|
||||
if (!initResponse.ok) {
|
||||
throw new Error(`Session initialization failed: ${initResponse.status}`);
|
||||
}
|
||||
|
||||
const initResult = await initResponse.json();
|
||||
const sessionDbId = initResult.sessionDbId;
|
||||
const promptNumber = initResult.promptNumber;
|
||||
|
||||
logger.debug('HOOK', 'new-hook: Received from /api/sessions/init', { sessionDbId, promptNumber, skipped: initResult.skipped });
|
||||
|
||||
// Debug-level alignment log for detailed tracing
|
||||
logger.debug('HOOK', `[ALIGNMENT] Hook Entry | contentSessionId=${session_id} | prompt#=${promptNumber} | sessionDbId=${sessionDbId}`);
|
||||
|
||||
// Check if prompt was entirely private (worker performs privacy check)
|
||||
if (initResult.skipped && initResult.reason === 'private') {
|
||||
logger.info('HOOK', `INIT_COMPLETE | sessionDbId=${sessionDbId} | promptNumber=${promptNumber} | skipped=true | reason=private`, {
|
||||
sessionId: sessionDbId
|
||||
});
|
||||
console.log(STANDARD_HOOK_RESPONSE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Strip leading slash from commands for memory agent
|
||||
// /review 101 → review 101 (more semantic for observations)
|
||||
const cleanedPrompt = prompt.startsWith('/') ? prompt.substring(1) : prompt;
|
||||
|
||||
logger.debug('HOOK', 'new-hook: Calling /sessions/{sessionDbId}/init', { sessionDbId, promptNumber });
|
||||
|
||||
// Initialize SDK agent session via HTTP (starts the agent!)
|
||||
const response = await fetch(`http://127.0.0.1:${port}/sessions/${sessionDbId}/init`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ userPrompt: cleanedPrompt, promptNumber })
|
||||
// Note: Removed signal to avoid Windows Bun cleanup issue (libuv assertion)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`SDK agent start failed: ${response.status}`);
|
||||
}
|
||||
|
||||
logger.info('HOOK', `INIT_COMPLETE | sessionDbId=${sessionDbId} | promptNumber=${promptNumber} | project=${project}`, {
|
||||
sessionId: sessionDbId
|
||||
});
|
||||
|
||||
console.log(STANDARD_HOOK_RESPONSE);
|
||||
}
|
||||
|
||||
// Entry Point
|
||||
let input = '';
|
||||
stdin.on('data', (chunk) => input += chunk);
|
||||
stdin.on('end', async () => {
|
||||
try {
|
||||
let parsed: UserPromptSubmitInput | undefined;
|
||||
try {
|
||||
parsed = input ? JSON.parse(input) : undefined;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse hook input: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
await newHook(parsed);
|
||||
} catch (error) {
|
||||
logger.error('HOOK', 'new-hook failed', {}, error as Error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
@@ -1,89 +0,0 @@
|
||||
/**
|
||||
* Save Hook - PostToolUse
|
||||
*
|
||||
* Pure HTTP client - sends data to worker, worker handles all database operations
|
||||
* including privacy checks. This allows the hook to run under any runtime
|
||||
* (Node.js or Bun) since it has no native module dependencies.
|
||||
*/
|
||||
|
||||
import { stdin } from 'process';
|
||||
import { STANDARD_HOOK_RESPONSE } from './hook-response.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
|
||||
import { HOOK_TIMEOUTS } from '../shared/hook-constants.js';
|
||||
|
||||
export interface PostToolUseInput {
|
||||
session_id: string;
|
||||
cwd: string;
|
||||
tool_name: string;
|
||||
tool_input: any;
|
||||
tool_response: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Hook Main Logic - Fire-and-forget HTTP client
|
||||
*/
|
||||
async function saveHook(input?: PostToolUseInput): Promise<void> {
|
||||
// Ensure worker is running before any other logic
|
||||
await ensureWorkerRunning();
|
||||
|
||||
if (!input) {
|
||||
throw new Error('saveHook requires input');
|
||||
}
|
||||
|
||||
const { session_id, cwd, tool_name, tool_input, tool_response } = input;
|
||||
|
||||
const port = getWorkerPort();
|
||||
|
||||
const toolStr = logger.formatTool(tool_name, tool_input);
|
||||
|
||||
logger.dataIn('HOOK', `PostToolUse: ${toolStr}`, {
|
||||
workerPort: port
|
||||
});
|
||||
|
||||
// Validate required fields before sending to worker
|
||||
if (!cwd) {
|
||||
throw new Error(`Missing cwd in PostToolUse hook input for session ${session_id}, tool ${tool_name}`);
|
||||
}
|
||||
|
||||
// Send to worker - worker handles privacy check and database operations
|
||||
const response = await fetch(`http://127.0.0.1:${port}/api/sessions/observations`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
contentSessionId: session_id,
|
||||
tool_name,
|
||||
tool_input,
|
||||
tool_response,
|
||||
cwd
|
||||
})
|
||||
// Note: Removed signal to avoid Windows Bun cleanup issue (libuv assertion)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Observation storage failed: ${response.status}`);
|
||||
}
|
||||
|
||||
logger.debug('HOOK', 'Observation sent successfully', { toolName: tool_name });
|
||||
|
||||
console.log(STANDARD_HOOK_RESPONSE);
|
||||
}
|
||||
|
||||
// Entry Point
|
||||
let input = '';
|
||||
stdin.on('data', (chunk) => input += chunk);
|
||||
stdin.on('end', async () => {
|
||||
try {
|
||||
let parsed: PostToolUseInput | undefined;
|
||||
try {
|
||||
parsed = input ? JSON.parse(input) : undefined;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse hook input: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
await saveHook(parsed);
|
||||
} catch (error) {
|
||||
logger.error('HOOK', 'save-hook failed', {}, error as Error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
@@ -1,93 +0,0 @@
|
||||
/**
|
||||
* Summary Hook - Stop
|
||||
*
|
||||
* Pure HTTP client - sends data to worker, worker handles all database operations
|
||||
* including privacy checks. This allows the hook to run under any runtime
|
||||
* (Node.js or Bun) since it has no native module dependencies.
|
||||
*
|
||||
* Transcript parsing stays in the hook because only the hook has access to
|
||||
* the transcript file path.
|
||||
*/
|
||||
|
||||
import { stdin } from 'process';
|
||||
import { STANDARD_HOOK_RESPONSE } from './hook-response.js';
|
||||
import { logger } from '../utils/logger.js';
|
||||
import { ensureWorkerRunning, getWorkerPort } from '../shared/worker-utils.js';
|
||||
import { HOOK_TIMEOUTS } from '../shared/hook-constants.js';
|
||||
import { extractLastMessage } from '../shared/transcript-parser.js';
|
||||
|
||||
export interface StopInput {
|
||||
session_id: string;
|
||||
cwd: string;
|
||||
transcript_path: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Summary Hook Main Logic - Fire-and-forget HTTP client
|
||||
*/
|
||||
async function summaryHook(input?: StopInput): Promise<void> {
|
||||
// Ensure worker is running before any other logic
|
||||
await ensureWorkerRunning();
|
||||
|
||||
if (!input) {
|
||||
throw new Error('summaryHook requires input');
|
||||
}
|
||||
|
||||
const { session_id } = input;
|
||||
|
||||
const port = getWorkerPort();
|
||||
|
||||
// Validate required fields before processing
|
||||
if (!input.transcript_path) {
|
||||
throw new Error(`Missing transcript_path in Stop hook input for session ${session_id}`);
|
||||
}
|
||||
|
||||
// Extract last assistant message from transcript (the work Claude did)
|
||||
// Note: "user" messages in transcripts are mostly tool_results, not actual user input.
|
||||
// The user's original request is already stored in user_prompts table.
|
||||
const lastAssistantMessage = extractLastMessage(input.transcript_path, 'assistant', true);
|
||||
|
||||
logger.dataIn('HOOK', 'Stop: Requesting summary', {
|
||||
workerPort: port,
|
||||
hasLastAssistantMessage: !!lastAssistantMessage
|
||||
});
|
||||
|
||||
// Send to worker - worker handles privacy check and database operations
|
||||
const response = await fetch(`http://127.0.0.1:${port}/api/sessions/summarize`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
contentSessionId: session_id,
|
||||
last_assistant_message: lastAssistantMessage
|
||||
})
|
||||
// Note: Removed signal to avoid Windows Bun cleanup issue (libuv assertion)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.log(STANDARD_HOOK_RESPONSE);
|
||||
throw new Error(`Summary generation failed: ${response.status}`);
|
||||
}
|
||||
|
||||
logger.debug('HOOK', 'Summary request sent successfully');
|
||||
|
||||
console.log(STANDARD_HOOK_RESPONSE);
|
||||
}
|
||||
|
||||
// Entry Point
|
||||
let input = '';
|
||||
stdin.on('data', (chunk) => input += chunk);
|
||||
stdin.on('end', async () => {
|
||||
try {
|
||||
let parsed: StopInput | undefined;
|
||||
try {
|
||||
parsed = input ? JSON.parse(input) : undefined;
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse hook input: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
await summaryHook(parsed);
|
||||
} catch (error) {
|
||||
logger.error('HOOK', 'summary-hook failed', {}, error as Error);
|
||||
} finally {
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
@@ -1,47 +0,0 @@
|
||||
/**
|
||||
* User Message Hook - SessionStart
|
||||
*
|
||||
* @deprecated This hook is no longer used as of Claude Code 2.1.0 (ultrathink update).
|
||||
* SessionStart hooks no longer display any user-visible messages in the Claude Code UI.
|
||||
* Context is still injected via hookSpecificOutput.additionalContext in context-hook.ts,
|
||||
* but users don't see any startup output.
|
||||
*
|
||||
* This file is kept for reference but is not registered in hooks.json.
|
||||
*
|
||||
* Historical behavior:
|
||||
* - Displayed context information to the user via stderr
|
||||
* - Ran in parallel with context-hook to show users what context was loaded
|
||||
* - Used stderr + exit code 1 to display to user only without adding to Claude's context
|
||||
*/
|
||||
import { basename } from "path";
|
||||
import { ensureWorkerRunning, getWorkerPort } from "../shared/worker-utils.js";
|
||||
|
||||
// Ensure worker is running
|
||||
await ensureWorkerRunning();
|
||||
|
||||
const port = getWorkerPort();
|
||||
const project = basename(process.cwd());
|
||||
|
||||
// Fetch formatted context directly from worker API
|
||||
// Note: Removed AbortSignal.timeout to avoid Windows Bun cleanup issue (libuv assertion)
|
||||
const response = await fetch(
|
||||
`http://127.0.0.1:${port}/api/context/inject?project=${encodeURIComponent(project)}&colors=true`,
|
||||
{ method: 'GET' }
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch context: ${response.status}`);
|
||||
}
|
||||
|
||||
const output = await response.text();
|
||||
|
||||
console.error(
|
||||
"\n\n📝 Claude-Mem Context Loaded\n" +
|
||||
" ℹ️ Note: This appears as stderr but is informational only\n\n" +
|
||||
output +
|
||||
"\n\n💡 New! Wrap all or part of any message with <private> ... </private> to prevent storing sensitive information in your observation history.\n" +
|
||||
"\n💬 Community https://discord.gg/J4wttp9vDu" +
|
||||
`\n📺 Watch live in browser http://localhost:${port}/\n`
|
||||
);
|
||||
|
||||
process.exit(1); // Exit code 1 for SessionStart = show stderr to user only
|
||||
+94
-1
@@ -3,5 +3,98 @@
|
||||
|
||||
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||
|
||||
*No recent activity*
|
||||
### Nov 6, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #4185 | 10:25 PM | 🔴 | Prefixed unused id parameters with underscore in filter callbacks | ~299 |
|
||||
|
||||
### Nov 8, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #5539 | 10:20 PM | 🔵 | Harsh critical audit of context-hook reveals systematic anti-patterns | ~3154 |
|
||||
| #5497 | 9:29 PM | 🔵 | Harsh critical audit of context-hook reveals systematic anti-patterns | ~2815 |
|
||||
|
||||
### Nov 9, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #5757 | 5:16 PM | 🔵 | MCP search server exposes 9 tools consuming ~2,000-3,000 tokens per session | ~421 |
|
||||
| #5754 | 5:14 PM | 🔵 | MCP search server provides 9 search tools with hybrid semantic/FTS5 | ~402 |
|
||||
|
||||
### Nov 10, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #6250 | 12:54 PM | 🔵 | MCP Search Server Connection Failure Reported | ~329 |
|
||||
|
||||
### Nov 17, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #10744 | 11:47 PM | ✅ | Search Query Parameter Made Optional for Filter-Only Queries | ~373 |
|
||||
| #10572 | 7:47 PM | 🟣 | Unified cross-type search with search_everything tool | ~501 |
|
||||
| #10571 | 7:46 PM | 🔵 | Search server architecture and hybrid search implementation | ~553 |
|
||||
|
||||
### Nov 18, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #11462 | 7:55 PM | 🔵 | Ready to Apply Fix to Contextualize Handler | ~261 |
|
||||
| #11460 | " | 🔴 | Identified Root Cause of Contextualize Endpoint Bug | ~413 |
|
||||
| #11454 | 7:54 PM | 🔵 | Unified Search Handler Shows Correct Pattern for Filter-Only Queries | ~334 |
|
||||
| #11447 | " | 🔵 | Contextualize Handler Calls Search Methods with Query='*' | ~279 |
|
||||
| #11432 | 7:52 PM | 🔵 | Contextualize Handler Formats Results with Sections | ~286 |
|
||||
| #11431 | 7:51 PM | 🔵 | Confirmed Empty Results Trigger in Contextualize Handler | ~289 |
|
||||
| #11430 | " | 🔵 | Contextualize Handler Implementation Uses Search Methods | ~424 |
|
||||
| #11429 | " | 🔵 | Search Server Defines Six Main Search Tools | ~358 |
|
||||
| #11428 | " | 🔵 | Contextualize Tool Definition Found in Search Server | ~357 |
|
||||
| #11332 | 3:55 PM | 🔵 | Comprehensive FTS5 Removal Audit Completed for Architecture Migration | ~792 |
|
||||
| #11206 | 3:01 PM | 🔵 | mem-search skill architecture and migration details retrieved in full format | ~538 |
|
||||
| #11181 | 4:09 AM | 🔵 | Store methods for ID-based lookups exist but not exposed as MCP tools | ~495 |
|
||||
| #11013 | 2:12 AM | 🔵 | Search Server Implements Three-Path Query Strategy with ChromaDB Primary and FTS5 Fallback | ~462 |
|
||||
|
||||
### Nov 28, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #16711 | 4:34 PM | 🟣 | include_inactive Parameter Extracted in Search Handler | ~369 |
|
||||
| #16710 | " | 🔵 | Search Tool Schema Definition with Type and Filter Parameters | ~527 |
|
||||
| #16708 | " | 🔵 | Search Server MCP Tool Architecture and ChromaDB Integration | ~491 |
|
||||
| #16682 | 4:10 PM | 🔵 | Comprehensive Exploration Task Completed on Observation System | ~601 |
|
||||
|
||||
### Dec 14, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #26238 | 8:28 PM | 🔵 | MCP Server Architecture Maps Tools to Worker API Endpoints | ~355 |
|
||||
| #26138 | 7:55 PM | ✅ | Updated Comment to Reference progressive_description Tool | ~238 |
|
||||
| #26137 | " | ✅ | Completed Tool Description Minimization - All 9 Tools Updated | ~335 |
|
||||
| #26136 | " | ✅ | Minimized Get Session Tool Description | ~218 |
|
||||
| #26135 | " | ✅ | Minimized Get Batch Observations Tool Description | ~258 |
|
||||
| #26134 | " | ✅ | Minimized Get Observation Tool Description | ~228 |
|
||||
| #26133 | " | ✅ | Minimized Get Context Timeline Tool Description | ~245 |
|
||||
| #26132 | 7:54 PM | ✅ | Minimized Get Recent Context Tool Description | ~214 |
|
||||
| #26131 | " | ✅ | Minimized Timeline Tool Description | ~232 |
|
||||
| #26130 | " | ✅ | Minimized Search Tool Description | ~235 |
|
||||
| #26129 | " | ✅ | Renamed progressive_ix Tool to progressive_description with Minimized Description | ~296 |
|
||||
| #26128 | " | ✅ | Renamed Tool Endpoint Mapping from progressive_ix to progressive_description | ~229 |
|
||||
| #26127 | " | ✅ | Completed Format Parameter Removal from All Four MCP Tools | ~318 |
|
||||
| #26126 | 7:53 PM | ✅ | Removed Format Parameter from Get Recent Context Tool Schema | ~244 |
|
||||
| #26125 | " | ✅ | Removed Format Parameter from Timeline Tool Schema | ~248 |
|
||||
| #26124 | " | ✅ | Removed Format Parameter from Search Tool Schema | ~283 |
|
||||
| #26123 | " | 🔵 | Current MCP Server Tool Schema Analysis | ~337 |
|
||||
| #25815 | 5:31 PM | 🔵 | Comprehensive MCP Server and SKILL.md Structure Analysis | ~575 |
|
||||
| #25807 | 5:30 PM | 🔵 | MCP Server Architecture with 14 HTTP-Delegating Tools | ~545 |
|
||||
| #25788 | 5:15 PM | 🔵 | MCP Server Capabilities and Request Handlers | ~256 |
|
||||
|
||||
### Dec 17, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #29078 | 10:16 PM | ✅ | Updated get_recent_context tool schema to accept dynamic parameters | ~318 |
|
||||
| #29077 | 10:15 PM | ✅ | Updated timeline tool schema to accept dynamic parameters | ~292 |
|
||||
| #29076 | " | ✅ | Updated search tool schema to accept dynamic parameters | ~315 |
|
||||
| #28923 | 7:28 PM | 🔵 | MCP Server Architecture: Thin HTTP Wrapper Pattern | ~402 |
|
||||
</claude-mem-context>
|
||||
@@ -310,5 +310,7 @@ async function main() {
|
||||
|
||||
main().catch((error) => {
|
||||
logger.error('SYSTEM', 'Fatal error', undefined, error);
|
||||
process.exit(1);
|
||||
// Exit gracefully: Windows Terminal won't keep tab open on exit 0
|
||||
// The wrapper/plugin will handle restart logic if needed
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
+50
-25
@@ -3,36 +3,61 @@
|
||||
|
||||
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||
|
||||
### Jan 5, 2026
|
||||
### Dec 10, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #37989 | 8:39 PM | 🔵 | Worker Service CLI Commands | ~370 |
|
||||
| #37988 | " | 🔵 | Worker Service Hook Command Integration | ~356 |
|
||||
| #37943 | 8:09 PM | 🔵 | Worker service provides hook command interface | ~372 |
|
||||
| #37936 | 8:08 PM | 🔵 | Worker service module located in budapest architecture | ~173 |
|
||||
| #37837 | 6:55 PM | 🔵 | Worker Service CLI Structure and Extension Pattern | ~536 |
|
||||
| #37836 | 6:54 PM | 🔵 | Worker Service HTTP API Architecture and Hook Integration Pattern | ~639 |
|
||||
| #37830 | 6:50 PM | ⚖️ | Architectural Refactoring Plan: Unified CLI Hook System | ~631 |
|
||||
| #37812 | 6:43 PM | 🔵 | WorkerService Architecture and CLI Entry Points | ~447 |
|
||||
| #37738 | 6:17 PM | 🔵 | ActiveSession and PendingMessage Structures Analyzed for Phase 3 | ~327 |
|
||||
| #37701 | 6:01 PM | 🔵 | Complete cwd data flow traced from hooks through observation processing | ~447 |
|
||||
| #37691 | 5:55 PM | 🔵 | ActiveSession interface contains project name but no project root path | ~346 |
|
||||
| #37418 | 1:04 AM | 🔴 | Fixed CLAUDE.md duplicate content by filtering to direct children only | ~469 |
|
||||
| #37391 | 12:48 AM | ✅ | Staged 23 CLAUDE.md files with mix of new and modified content | ~400 |
|
||||
| #37390 | 12:47 AM | ✅ | Regenerated 23 CLAUDE.md files in budapest workspace | ~365 |
|
||||
| #23832 | 11:15 PM | 🔵 | Current worker-service.ts Lacks Admin Endpoints | ~393 |
|
||||
|
||||
### Jan 7, 2026
|
||||
### Dec 14, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #38467 | 10:29 PM | ⚖️ | Log Level Audit Strategy: Tighten ERROR Messages for Runtime Issue Discovery | ~464 |
|
||||
| #38446 | 10:26 PM | 🔵 | Worker Service Logs Startup Recovery Failures at WARN Level | ~419 |
|
||||
| #38444 | 10:25 PM | 🔵 | Worker service auto-recovery error handling pattern | ~393 |
|
||||
| #38431 | 10:24 PM | 🔵 | ERROR-Level Logging Used Across 21 Source Files | ~480 |
|
||||
| #38404 | 10:06 PM | ⚖️ | Log Level Audit Analysis - WARN to ERROR Promotion Criteria Established | ~769 |
|
||||
| #38354 | 9:10 PM | 🔵 | Provider Selection Architecture and Default Settings Location | ~478 |
|
||||
| #38352 | 9:09 PM | 🔵 | Worker Service Environment Inheritance and Process Spawning Mechanisms | ~469 |
|
||||
| #38269 | 7:53 PM | 🔵 | Worker Types Shared Type Definitions | ~847 |
|
||||
| #38256 | 7:49 PM | 🔵 | Worker Service Orchestration Architecture | ~1166 |
|
||||
| #26740 | 11:26 PM | 🔵 | Worker Service Refactored to Orchestrator with Background Initialization | ~421 |
|
||||
| #26739 | 11:25 PM | 🔵 | Worker Service Architecture Uses Domain Services and Background Initialization | ~438 |
|
||||
| #26255 | 8:31 PM | 🔵 | Context Generator Timeline Rendering Logic Details File Grouping Implementation | ~397 |
|
||||
| #26251 | 8:30 PM | 🔵 | Worker Service Orchestrates Domain Services and Route Handlers | ~292 |
|
||||
| #26246 | 8:29 PM | 🔵 | Context Generator Implements Rich Date-Grouped Timeline Format | ~468 |
|
||||
|
||||
### Dec 17, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #28548 | 4:49 PM | 🔵 | Worker service cleanup method uses Unix-specific process management | ~323 |
|
||||
| #28446 | 4:23 PM | 🔵 | Worker Service Refactored to Orchestrator Pattern | ~529 |
|
||||
|
||||
### Dec 18, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #29340 | 3:11 PM | ✅ | Constructor Initialization Comment Updated | ~267 |
|
||||
| #29339 | " | ✅ | Class Member Comment Updated in WorkerService | ~267 |
|
||||
| #29338 | " | ✅ | Service Import Comment Updated | ~222 |
|
||||
| #29337 | 3:10 PM | ✅ | Terminology Update in Worker Service Documentation | ~268 |
|
||||
| #29239 | 12:11 AM | 🔵 | Worker Service Refactored as Domain-Driven Orchestrator | ~477 |
|
||||
|
||||
### Dec 20, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #30808 | 6:05 PM | 🔴 | Fixed worker readiness check to fail on initialization errors | ~315 |
|
||||
| #30800 | 6:03 PM | 🔵 | Dual Error Logging in Background Initialization | ~367 |
|
||||
| #30799 | " | 🔵 | Background Initialization Invocation Pattern | ~365 |
|
||||
| #30797 | " | 🔵 | Background Initialization Sequence and Error Handler Confirmed | ~450 |
|
||||
| #30795 | 6:02 PM | 🔵 | Readiness Endpoint Returns 503 During Initialization | ~397 |
|
||||
| #30793 | " | 🔵 | Dual Initialization State Tracking Pattern | ~388 |
|
||||
| #30791 | " | 🔵 | Worker Service Constructor Defers SearchRoutes Initialization | ~387 |
|
||||
| #30790 | " | 🔵 | Initialization Promise Resolver Pattern Located | ~321 |
|
||||
| #30788 | " | 🔵 | Worker Service Initialization Resolves Promise Despite Errors | ~388 |
|
||||
|
||||
### Jan 1, 2026
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #35654 | 11:29 PM | ✅ | Added APPROVED OVERRIDE annotation for instruction loading HTTP route error handler | ~339 |
|
||||
| #35651 | 11:28 PM | ✅ | Added APPROVED OVERRIDE annotation for shutdown error handler with process.exit | ~354 |
|
||||
| #35649 | " | ✅ | Added APPROVED OVERRIDE annotation for readiness check retry loop error handling | ~374 |
|
||||
| #35647 | " | ✅ | Added APPROVED OVERRIDE annotation for port availability probe error handling | ~327 |
|
||||
| #35646 | " | ✅ | Added APPROVED OVERRIDE annotation for Cursor context file update error handling | ~342 |
|
||||
| #35643 | 11:27 PM | ✅ | Added APPROVED OVERRIDE annotation for PID file cleanup error handling | ~320 |
|
||||
</claude-mem-context>
|
||||
@@ -3,5 +3,10 @@
|
||||
|
||||
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||
|
||||
*No recent activity*
|
||||
### Jan 4, 2026
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #36864 | 1:52 AM | 🔵 | ProcessManager Module Imports Reviewed | ~245 |
|
||||
| #36860 | 1:50 AM | 🔵 | ProcessManager Source Code Reviewed for WMIC Implementation | ~608 |
|
||||
</claude-mem-context>
|
||||
@@ -14,6 +14,7 @@ import { existsSync, writeFileSync, readFileSync, unlinkSync, mkdirSync } from '
|
||||
import { exec, execSync, spawn } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
import { HOOK_TIMEOUTS } from '../../shared/hook-constants.js';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
@@ -88,16 +89,16 @@ export async function getChildProcesses(parentPid: number): Promise<number[]> {
|
||||
}
|
||||
|
||||
try {
|
||||
const cmd = `wmic process where "parentprocessid=${parentPid}" get processid /format:list`;
|
||||
const { stdout } = await execAsync(cmd, { timeout: 60000 });
|
||||
// PowerShell Get-Process instead of WMIC (deprecated in Windows 11)
|
||||
const cmd = `powershell -NoProfile -NonInteractive -Command "Get-Process | Where-Object { \\$_.ParentProcessId -eq ${parentPid} } | Select-Object -ExpandProperty Id"`;
|
||||
const { stdout } = await execAsync(cmd, { timeout: HOOK_TIMEOUTS.POWERSHELL_COMMAND });
|
||||
// PowerShell outputs just numbers (one per line), simpler than WMIC's "ProcessId=1234" format
|
||||
return stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
const match = line.match(/ProcessId=(\d+)/i);
|
||||
return match ? parseInt(match[1], 10) : NaN;
|
||||
})
|
||||
.filter(n => !isNaN(n) && Number.isInteger(n) && n > 0);
|
||||
.map(line => line.trim())
|
||||
.filter(line => line.length > 0 && /^\d+$/.test(line))
|
||||
.map(line => parseInt(line, 10))
|
||||
.filter(pid => pid > 0);
|
||||
} catch (error) {
|
||||
// Shutdown cleanup - failure is non-critical, continue without child process cleanup
|
||||
logger.error('SYSTEM', 'Failed to enumerate child processes', { parentPid }, error as Error);
|
||||
@@ -120,7 +121,7 @@ export async function forceKillProcess(pid: number): Promise<void> {
|
||||
try {
|
||||
if (process.platform === 'win32') {
|
||||
// /T kills entire process tree, /F forces termination
|
||||
await execAsync(`taskkill /PID ${pid} /T /F`, { timeout: 60000 });
|
||||
await execAsync(`taskkill /PID ${pid} /T /F`, { timeout: HOOK_TIMEOUTS.POWERSHELL_COMMAND });
|
||||
} else {
|
||||
process.kill(pid, 'SIGKILL');
|
||||
}
|
||||
@@ -170,24 +171,26 @@ export async function cleanupOrphanedProcesses(): Promise<void> {
|
||||
|
||||
try {
|
||||
if (isWindows) {
|
||||
// Windows: Use WMIC to find chroma-mcp processes (avoids PowerShell $_ issues in Git Bash/WSL)
|
||||
const cmd = `wmic process where "name like '%python%' and commandline like '%chroma-mcp%'" get processid /format:list`;
|
||||
const { stdout } = await execAsync(cmd, { timeout: 60000 });
|
||||
// Windows: Use PowerShell Get-CimInstance instead of WMIC (deprecated in Windows 11)
|
||||
const cmd = `powershell -NoProfile -NonInteractive -Command "Get-CimInstance Win32_Process | Where-Object { \\$_.Name -like '*python*' -and \\$_.CommandLine -like '*chroma-mcp*' } | Select-Object -ExpandProperty ProcessId"`;
|
||||
const { stdout } = await execAsync(cmd, { timeout: HOOK_TIMEOUTS.POWERSHELL_COMMAND });
|
||||
|
||||
if (!stdout.trim()) {
|
||||
logger.debug('SYSTEM', 'No orphaned chroma-mcp processes found (Windows)');
|
||||
return;
|
||||
}
|
||||
|
||||
const lines = stdout.trim().split('\n');
|
||||
// PowerShell outputs just numbers (one per line), simpler than WMIC's "ProcessId=1234" format
|
||||
const lines = stdout
|
||||
.split('\n')
|
||||
.map(line => line.trim())
|
||||
.filter(line => line.length > 0 && /^\d+$/.test(line));
|
||||
|
||||
for (const line of lines) {
|
||||
const match = line.match(/ProcessId=(\d+)/i);
|
||||
if (match) {
|
||||
const pid = parseInt(match[1], 10);
|
||||
// SECURITY: Validate PID is positive integer before adding to list
|
||||
if (!isNaN(pid) && Number.isInteger(pid) && pid > 0) {
|
||||
pids.push(pid);
|
||||
}
|
||||
const pid = parseInt(line, 10);
|
||||
// SECURITY: Validate PID is positive integer before adding to list
|
||||
if (!isNaN(pid) && Number.isInteger(pid) && pid > 0) {
|
||||
pids.push(pid);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -236,7 +239,7 @@ export async function cleanupOrphanedProcesses(): Promise<void> {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
execSync(`taskkill /PID ${pid} /T /F`, { timeout: 60000, stdio: 'ignore' });
|
||||
execSync(`taskkill /PID ${pid} /T /F`, { timeout: HOOK_TIMEOUTS.POWERSHELL_COMMAND, stdio: 'ignore' });
|
||||
} catch (error) {
|
||||
// [ANTI-PATTERN IGNORED]: Cleanup loop - process may have exited, continue to next PID
|
||||
logger.debug('SYSTEM', 'Failed to kill process, may have already exited', { pid }, error as Error);
|
||||
@@ -306,7 +309,9 @@ export function createSignalHandler(
|
||||
} catch (error) {
|
||||
// Top-level signal handler - log any shutdown error and exit
|
||||
logger.error('SYSTEM', 'Error during shutdown', {}, error as Error);
|
||||
process.exit(1);
|
||||
// Exit gracefully: Windows Terminal won't keep tab open on exit 0
|
||||
// Even on shutdown errors, exit cleanly to prevent tab accumulation
|
||||
process.exit(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -171,6 +171,28 @@ export function findMcpServerPath(): string | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find worker-service.cjs path for unified CLI
|
||||
* Searches in order: marketplace install, source repo
|
||||
*/
|
||||
export function findWorkerServicePath(): string | null {
|
||||
const possiblePaths = [
|
||||
// Marketplace install location
|
||||
path.join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack', 'plugin', 'scripts', 'worker-service.cjs'),
|
||||
// Development/source location (relative to built worker-service.cjs in plugin/scripts/)
|
||||
path.join(path.dirname(__filename), 'worker-service.cjs'),
|
||||
// Alternative dev location
|
||||
path.join(process.cwd(), 'plugin', 'scripts', 'worker-service.cjs'),
|
||||
];
|
||||
|
||||
for (const p of possiblePaths) {
|
||||
if (existsSync(p)) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target directory for Cursor hooks based on install target
|
||||
*/
|
||||
@@ -261,13 +283,11 @@ export function configureCursorMcp(target: CursorInstallTarget): number {
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Install Cursor hooks
|
||||
* Install Cursor hooks using unified CLI
|
||||
* No longer copies shell scripts - uses node CLI directly
|
||||
*/
|
||||
export async function installCursorHooks(sourceDir: string, target: CursorInstallTarget): Promise<number> {
|
||||
const platform = detectPlatform();
|
||||
const scriptExt = getScriptExtension();
|
||||
|
||||
console.log(`\nInstalling Claude-Mem Cursor hooks (${target} level, ${platform})...\n`);
|
||||
export async function installCursorHooks(_sourceDir: string, target: CursorInstallTarget): Promise<number> {
|
||||
console.log(`\nInstalling Claude-Mem Cursor hooks (${target} level)...\n`);
|
||||
|
||||
const targetDir = getTargetDir(target);
|
||||
if (!targetDir) {
|
||||
@@ -275,52 +295,30 @@ export async function installCursorHooks(sourceDir: string, target: CursorInstal
|
||||
return 1;
|
||||
}
|
||||
|
||||
const hooksDir = path.join(targetDir, 'hooks');
|
||||
// Find the worker-service.cjs path
|
||||
const workerServicePath = findWorkerServicePath();
|
||||
if (!workerServicePath) {
|
||||
console.error('Could not find worker-service.cjs');
|
||||
console.error(' Expected at: ~/.claude/plugins/marketplaces/thedotmack/plugin/scripts/worker-service.cjs');
|
||||
return 1;
|
||||
}
|
||||
|
||||
const workspaceRoot = process.cwd();
|
||||
|
||||
try {
|
||||
// Create directories
|
||||
mkdirSync(hooksDir, { recursive: true });
|
||||
// Create target directory
|
||||
mkdirSync(targetDir, { recursive: true });
|
||||
|
||||
// Determine which scripts to copy based on platform
|
||||
const commonScript = platform === 'windows' ? 'common.ps1' : 'common.sh';
|
||||
const hookScripts = [
|
||||
`session-init${scriptExt}`,
|
||||
`context-inject${scriptExt}`,
|
||||
`save-observation${scriptExt}`,
|
||||
`save-file-edit${scriptExt}`,
|
||||
`session-summary${scriptExt}`
|
||||
];
|
||||
|
||||
const scripts = [commonScript, ...hookScripts];
|
||||
|
||||
for (const script of scripts) {
|
||||
const srcPath = path.join(sourceDir, script);
|
||||
const dstPath = path.join(hooksDir, script);
|
||||
|
||||
if (existsSync(srcPath)) {
|
||||
const content = readFileSync(srcPath, 'utf-8');
|
||||
// Unix scripts need execute permission; Windows PowerShell doesn't need it
|
||||
const mode = platform === 'windows' ? undefined : 0o755;
|
||||
writeFileSync(dstPath, content, mode ? { mode } : undefined);
|
||||
console.log(` Copied ${script}`);
|
||||
} else {
|
||||
console.warn(` ${script} not found in source`);
|
||||
}
|
||||
}
|
||||
|
||||
// Generate hooks.json with correct paths and platform-appropriate commands
|
||||
// Generate hooks.json with unified CLI commands
|
||||
const hooksJsonPath = path.join(targetDir, 'hooks.json');
|
||||
const hookPrefix = target === 'project' ? './.cursor/hooks/' : `${hooksDir}/`;
|
||||
|
||||
// For PowerShell, we need to invoke via powershell.exe
|
||||
const makeHookCommand = (scriptName: string) => {
|
||||
const scriptPath = `${hookPrefix}${scriptName}${scriptExt}`;
|
||||
if (platform === 'windows') {
|
||||
// PowerShell execution: use -ExecutionPolicy Bypass to ensure scripts run
|
||||
return `powershell.exe -ExecutionPolicy Bypass -File "${scriptPath}"`;
|
||||
}
|
||||
return scriptPath;
|
||||
// Use the absolute path to worker-service.cjs
|
||||
// Escape backslashes for JSON on Windows
|
||||
const escapedWorkerPath = workerServicePath.replace(/\\/g, '\\\\');
|
||||
|
||||
// Helper to create hook command using unified CLI
|
||||
const makeHookCommand = (command: string) => {
|
||||
return `node "${escapedWorkerPath}" hook cursor ${command}`;
|
||||
};
|
||||
|
||||
const hooksJson: CursorHooksJson = {
|
||||
@@ -328,25 +326,26 @@ export async function installCursorHooks(sourceDir: string, target: CursorInstal
|
||||
hooks: {
|
||||
beforeSubmitPrompt: [
|
||||
{ command: makeHookCommand('session-init') },
|
||||
{ command: makeHookCommand('context-inject') }
|
||||
{ command: makeHookCommand('context') }
|
||||
],
|
||||
afterMCPExecution: [
|
||||
{ command: makeHookCommand('save-observation') }
|
||||
{ command: makeHookCommand('observation') }
|
||||
],
|
||||
afterShellExecution: [
|
||||
{ command: makeHookCommand('save-observation') }
|
||||
{ command: makeHookCommand('observation') }
|
||||
],
|
||||
afterFileEdit: [
|
||||
{ command: makeHookCommand('save-file-edit') }
|
||||
{ command: makeHookCommand('file-edit') }
|
||||
],
|
||||
stop: [
|
||||
{ command: makeHookCommand('session-summary') }
|
||||
{ command: makeHookCommand('summarize') }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
writeFileSync(hooksJsonPath, JSON.stringify(hooksJson, null, 2));
|
||||
console.log(` Created hooks.json (${platform} mode)`);
|
||||
console.log(` Created hooks.json (unified CLI mode)`);
|
||||
console.log(` Worker service: ${workerServicePath}`);
|
||||
|
||||
// For project-level: create initial context file
|
||||
if (target === 'project') {
|
||||
@@ -357,7 +356,7 @@ export async function installCursorHooks(sourceDir: string, target: CursorInstal
|
||||
Installation complete!
|
||||
|
||||
Hooks installed to: ${targetDir}/hooks.json
|
||||
Scripts installed to: ${hooksDir}
|
||||
Using unified CLI: node worker-service.cjs hook cursor <command>
|
||||
|
||||
Next steps:
|
||||
1. Start claude-mem worker: claude-mem start
|
||||
@@ -453,7 +452,7 @@ export function uninstallCursorHooks(target: CursorInstallTarget): number {
|
||||
const hooksDir = path.join(targetDir, 'hooks');
|
||||
const hooksJsonPath = path.join(targetDir, 'hooks.json');
|
||||
|
||||
// Remove hook scripts for both platforms (in case user switches platforms)
|
||||
// Remove legacy shell scripts if they exist (from old installations)
|
||||
const bashScripts = ['common.sh', 'session-init.sh', 'context-inject.sh',
|
||||
'save-observation.sh', 'save-file-edit.sh', 'session-summary.sh'];
|
||||
const psScripts = ['common.ps1', 'session-init.ps1', 'context-inject.ps1',
|
||||
@@ -465,7 +464,7 @@ export function uninstallCursorHooks(target: CursorInstallTarget): number {
|
||||
const scriptPath = path.join(hooksDir, script);
|
||||
if (existsSync(scriptPath)) {
|
||||
unlinkSync(scriptPath);
|
||||
console.log(` Removed ${script}`);
|
||||
console.log(` Removed legacy script: ${script}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -527,32 +526,36 @@ export function checkCursorHooksStatus(): number {
|
||||
console.log(`${loc.name}: Installed`);
|
||||
console.log(` Config: ${hooksJson}`);
|
||||
|
||||
// Detect which platform's scripts are installed
|
||||
const bashScripts = ['session-init.sh', 'context-inject.sh', 'save-observation.sh'];
|
||||
const psScripts = ['session-init.ps1', 'context-inject.ps1', 'save-observation.ps1'];
|
||||
// Check if using unified CLI mode or legacy shell scripts
|
||||
try {
|
||||
const hooksContent = JSON.parse(readFileSync(hooksJson, 'utf-8'));
|
||||
const firstCommand = hooksContent?.hooks?.beforeSubmitPrompt?.[0]?.command || '';
|
||||
|
||||
const hasBash = bashScripts.some(s => existsSync(path.join(hooksDir, s)));
|
||||
const hasPs = psScripts.some(s => existsSync(path.join(hooksDir, s)));
|
||||
if (firstCommand.includes('worker-service.cjs') && firstCommand.includes('hook cursor')) {
|
||||
console.log(` Mode: Unified CLI (node worker-service.cjs)`);
|
||||
} else {
|
||||
// Detect legacy shell scripts
|
||||
const bashScripts = ['session-init.sh', 'context-inject.sh', 'save-observation.sh'];
|
||||
const psScripts = ['session-init.ps1', 'context-inject.ps1', 'save-observation.ps1'];
|
||||
|
||||
if (hasBash && hasPs) {
|
||||
console.log(` Platform: Both (bash + PowerShell)`);
|
||||
} else if (hasBash) {
|
||||
console.log(` Platform: Unix (bash)`);
|
||||
} else if (hasPs) {
|
||||
console.log(` Platform: Windows (PowerShell)`);
|
||||
} else {
|
||||
console.log(` No hook scripts found`);
|
||||
}
|
||||
const hasBash = bashScripts.some(s => existsSync(path.join(hooksDir, s)));
|
||||
const hasPs = psScripts.some(s => existsSync(path.join(hooksDir, s)));
|
||||
|
||||
// Check for appropriate scripts based on current platform
|
||||
const platform = detectPlatform();
|
||||
const scripts = platform === 'windows' ? psScripts : bashScripts;
|
||||
const missing = scripts.filter(s => !existsSync(path.join(hooksDir, s)));
|
||||
|
||||
if (missing.length > 0) {
|
||||
console.log(` Missing ${platform} scripts: ${missing.join(', ')}`);
|
||||
} else {
|
||||
console.log(` Scripts: All present for ${platform}`);
|
||||
if (hasBash || hasPs) {
|
||||
console.log(` Mode: Legacy shell scripts (consider reinstalling for unified CLI)`);
|
||||
if (hasBash && hasPs) {
|
||||
console.log(` Platform: Both (bash + PowerShell)`);
|
||||
} else if (hasBash) {
|
||||
console.log(` Platform: Unix (bash)`);
|
||||
} else if (hasPs) {
|
||||
console.log(` Platform: Windows (PowerShell)`);
|
||||
}
|
||||
} else {
|
||||
console.log(` Mode: Unknown configuration`);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
console.log(` Mode: Unable to parse hooks.json`);
|
||||
}
|
||||
|
||||
// Check for context file (project only)
|
||||
|
||||
@@ -636,7 +636,9 @@ async function main() {
|
||||
const freed = await waitForPortFree(port, getPlatformTimeout(15000));
|
||||
if (!freed) {
|
||||
logger.error('SYSTEM', 'Port did not free up after shutdown for version mismatch restart', { port });
|
||||
process.exit(1);
|
||||
// Exit gracefully: Windows Terminal won't keep tab open on exit 0
|
||||
// The wrapper/plugin will handle restart logic if needed
|
||||
process.exit(0);
|
||||
}
|
||||
removePidFile();
|
||||
} else {
|
||||
@@ -654,14 +656,18 @@ async function main() {
|
||||
process.exit(0);
|
||||
}
|
||||
logger.error('SYSTEM', 'Port in use but worker not responding to health checks');
|
||||
process.exit(1);
|
||||
// Exit gracefully: Windows Terminal won't keep tab open on exit 0
|
||||
// The wrapper/plugin will handle restart logic if needed
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
logger.info('SYSTEM', 'Starting worker daemon');
|
||||
const pid = spawnDaemon(__filename, port);
|
||||
if (pid === undefined) {
|
||||
logger.error('SYSTEM', 'Failed to spawn worker daemon');
|
||||
process.exit(1);
|
||||
// Exit gracefully: Windows Terminal won't keep tab open on exit 0
|
||||
// The wrapper/plugin will handle restart logic if needed
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
writePidFile({ pid, port, startedAt: new Date().toISOString() });
|
||||
@@ -670,7 +676,9 @@ async function main() {
|
||||
if (!healthy) {
|
||||
removePidFile();
|
||||
logger.error('SYSTEM', 'Worker failed to start (health check timeout)');
|
||||
process.exit(1);
|
||||
// Exit gracefully: Windows Terminal won't keep tab open on exit 0
|
||||
// The wrapper/plugin will handle restart logic if needed
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
logger.info('SYSTEM', 'Worker started successfully');
|
||||
@@ -694,14 +702,18 @@ async function main() {
|
||||
const freed = await waitForPortFree(port, getPlatformTimeout(15000));
|
||||
if (!freed) {
|
||||
logger.error('SYSTEM', 'Port did not free up after shutdown, aborting restart', { port });
|
||||
process.exit(1);
|
||||
// Exit gracefully: Windows Terminal won't keep tab open on exit 0
|
||||
// The wrapper/plugin will handle restart logic if needed
|
||||
process.exit(0);
|
||||
}
|
||||
removePidFile();
|
||||
|
||||
const pid = spawnDaemon(__filename, port);
|
||||
if (pid === undefined) {
|
||||
logger.error('SYSTEM', 'Failed to spawn worker daemon during restart');
|
||||
process.exit(1);
|
||||
// Exit gracefully: Windows Terminal won't keep tab open on exit 0
|
||||
// The wrapper/plugin will handle restart logic if needed
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
writePidFile({ pid, port, startedAt: new Date().toISOString() });
|
||||
@@ -710,7 +722,9 @@ async function main() {
|
||||
if (!healthy) {
|
||||
removePidFile();
|
||||
logger.error('SYSTEM', 'Worker failed to restart');
|
||||
process.exit(1);
|
||||
// Exit gracefully: Windows Terminal won't keep tab open on exit 0
|
||||
// The wrapper/plugin will handle restart logic if needed
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
logger.info('SYSTEM', 'Worker restarted successfully');
|
||||
@@ -737,13 +751,29 @@ async function main() {
|
||||
process.exit(cursorResult);
|
||||
}
|
||||
|
||||
case 'hook': {
|
||||
const platform = process.argv[3];
|
||||
const event = process.argv[4];
|
||||
if (!platform || !event) {
|
||||
console.error('Usage: claude-mem hook <platform> <event>');
|
||||
console.error('Platforms: claude-code, cursor, raw');
|
||||
console.error('Events: context, session-init, observation, summarize, user-message');
|
||||
process.exit(1);
|
||||
}
|
||||
const { hookCommand } = await import('../cli/hook-command.js');
|
||||
await hookCommand(platform, event);
|
||||
break;
|
||||
}
|
||||
|
||||
case '--daemon':
|
||||
default: {
|
||||
const worker = new WorkerService();
|
||||
worker.start().catch((error) => {
|
||||
logger.failure('SYSTEM', 'Worker failed to start', {}, error as Error);
|
||||
removePidFile();
|
||||
process.exit(1);
|
||||
// Exit gracefully: Windows Terminal won't keep tab open on exit 0
|
||||
// The wrapper/plugin will handle restart logic if needed
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+112
-36
@@ -3,47 +3,123 @@
|
||||
|
||||
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||
|
||||
### Jan 5, 2026
|
||||
### Dec 10, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #37751 | 6:21 PM | 🔴 | OpenRouterAgent Fully Updated with projectRoot Threading | ~307 |
|
||||
| #37748 | " | ✅ | OpenRouterAgent Observation Handler Updated with lastCwd | ~276 |
|
||||
| #37746 | 6:20 PM | ✅ | GeminiAgent Summary Handler Updated with lastCwd | ~278 |
|
||||
| #37745 | " | ✅ | OpenRouterAgent Init Prompt Call Updated with undefined projectRoot | ~299 |
|
||||
| #37744 | " | ✅ | GeminiAgent First processAgentResponse Updated with lastCwd | ~274 |
|
||||
| #37743 | " | ✅ | Added lastCwd Tracking to OpenRouterAgent | ~265 |
|
||||
| #37742 | 6:19 PM | ✅ | Phase 3 Started: Added lastCwd Tracking to GeminiAgent | ~291 |
|
||||
| #37741 | " | 🔵 | OpenRouterAgent Structure Analyzed for Phase 3 Implementation | ~367 |
|
||||
| #37740 | " | 🔵 | SDKAgent Structure Analyzed for Phase 3 Implementation | ~430 |
|
||||
| #37739 | 6:18 PM | 🔵 | GeminiAgent Structure Analyzed for Phase 3 Implementation | ~381 |
|
||||
| #37737 | 6:16 PM | 🔵 | Identified processAgentResponse Callers for Phase 3 | ~275 |
|
||||
| #37701 | 6:01 PM | 🔵 | Complete cwd data flow traced from hooks through observation processing | ~447 |
|
||||
| #37421 | 1:06 AM | 🔵 | Identified by-file API endpoint usage across codebase | ~406 |
|
||||
| #37416 | 1:02 AM | 🔴 | Fixed find_by_file Parameter Handling for Comma-Separated Values | ~375 |
|
||||
| #37415 | " | 🟣 | SearchManager Parameter Normalization Enhanced for Folder Queries | ~406 |
|
||||
| #37414 | 1:01 AM | 🔵 | SearchManager findByFile Uses filePath Parameter for File Search | ~429 |
|
||||
| #37413 | " | 🔵 | Hybrid Search Strategy: Metadata-First with Semantic Ranking | ~475 |
|
||||
| #37391 | 12:48 AM | ✅ | Staged 23 CLAUDE.md files with mix of new and modified content | ~400 |
|
||||
| #37390 | 12:47 AM | ✅ | Regenerated 23 CLAUDE.md files in budapest workspace | ~365 |
|
||||
| #23673 | 8:36 PM | ✅ | Add Project Filter Parameter to Session and Prompt Hydration in Search | ~306 |
|
||||
| #23596 | 5:54 PM | ⚖️ | Import/Export Bug Fix Priority and Scope | ~415 |
|
||||
| #23595 | 5:53 PM | 🔴 | SearchManager Returns Wrong Format for Empty Results | ~320 |
|
||||
| #23594 | " | 🔵 | SearchManager Search Method Control Flow | ~313 |
|
||||
| #23591 | 5:51 PM | 🔵 | SearchManager JSON Response Structure | ~231 |
|
||||
| #23590 | " | 🔵 | Import/Export Feature Status Review | ~490 |
|
||||
| #23583 | 5:50 PM | 🔵 | SearchManager Hybrid Search Architecture | ~495 |
|
||||
|
||||
### Jan 7, 2026
|
||||
### Dec 13, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #38467 | 10:29 PM | ⚖️ | Log Level Audit Strategy: Tighten ERROR Messages for Runtime Issue Discovery | ~464 |
|
||||
| #38437 | 10:24 PM | 🔵 | Claude-mem core functionality and logging patterns identified | ~710 |
|
||||
| #38433 | " | 🔵 | SessionManager Uses ERROR Logging for Database Persistence Failures | ~503 |
|
||||
| #38431 | " | 🔵 | ERROR-Level Logging Used Across 21 Source Files | ~480 |
|
||||
| #38425 | " | ⚖️ | Log Level Architecture: Fail-Critical Over Fail-Fast for Chroma | ~467 |
|
||||
| #38418 | 10:22 PM | 🔵 | ChromaDB Operates as Optional Subsystem with Graceful Degradation | ~586 |
|
||||
| #38416 | " | 🔵 | ChromaDB Is Critical Not Optional - Log Audit Findings Challenged | ~405 |
|
||||
| #38405 | 10:07 PM | ⚖️ | DEBUG Log Level Analysis - One Message Requires WARN Promotion | ~819 |
|
||||
| #38404 | 10:06 PM | ⚖️ | Log Level Audit Analysis - WARN to ERROR Promotion Criteria Established | ~769 |
|
||||
| #38372 | 9:43 PM | 🔵 | Logging Anti-Pattern Found: Critical Errors Logged as DEBUG Instead of ERROR | ~464 |
|
||||
| #38354 | 9:10 PM | 🔵 | Provider Selection Architecture and Default Settings Location | ~478 |
|
||||
| #38353 | " | 🔵 | Session ID Architecture and Message Yielding for SDK Interaction | ~536 |
|
||||
| #38253 | 7:47 PM | 🔵 | SDK Agent Implementation with Resume Logic | ~1036 |
|
||||
| #38250 | 7:46 PM | 🔵 | Session Manager Event-Driven Architecture | ~865 |
|
||||
| #38248 | 7:45 PM | 🔵 | OpenRouter Agent Implementation | ~851 |
|
||||
| #25191 | 8:04 PM | 🔵 | ChromaSync Instantiated in DatabaseManager Constructor | ~315 |
|
||||
|
||||
### Dec 14, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #26263 | 8:32 PM | 🔵 | SearchManager Timeline Methods Use Rich Formatting, Search Method Uses Flat Tables | ~464 |
|
||||
| #26243 | 8:29 PM | 🔵 | FormattingService Provides Basic Table Format Without Dates or File Grouping | ~390 |
|
||||
| #26240 | " | 🔵 | SearchManager Formats Results as Tables, Timeline Uses Rich Date-Grouped Format | ~416 |
|
||||
| #26108 | 7:43 PM | ✅ | changes() Method Format Logic Removed | ~401 |
|
||||
| #26107 | " | ✅ | changes() Method Format Parameter Removed | ~317 |
|
||||
| #26106 | 7:42 PM | ✅ | decisions() Method Format Logic Removed | ~405 |
|
||||
| #26105 | " | ✅ | decisions() Method Format Parameter Removed | ~310 |
|
||||
| #26104 | " | ✅ | Main search() Method Format Handling Removed | ~430 |
|
||||
| #26103 | 7:41 PM | ✅ | FormattingService.ts Rewritten to Table Format | ~457 |
|
||||
| #26102 | " | 🔵 | SearchManager.ts Format Parameter Removal Status | ~478 |
|
||||
|
||||
### Dec 15, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #27043 | 6:04 PM | 🔵 | Subagent confirms no version switcher UI exists, only orphaned backend infrastructure | ~539 |
|
||||
| #27041 | 6:03 PM | 🔵 | Branch switching code isolated to two backend files, no frontend UI components | ~473 |
|
||||
| #27037 | 6:02 PM | 🔵 | Branch switching functionality exists in SettingsRoutes with UI switcher removal intent | ~463 |
|
||||
|
||||
### Dec 16, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #27727 | 5:45 PM | 🔵 | SearchManager returns raw data arrays when format=json is specified | ~349 |
|
||||
|
||||
### Dec 17, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #28473 | 4:25 PM | 🔵 | PaginationHelper LIMIT+1 Trick and Project Path Sanitization | ~499 |
|
||||
| #28458 | 4:24 PM | 🔵 | SDK Agent Observer-Only Event-Driven Query Loop | ~513 |
|
||||
| #28455 | " | 🔵 | Event-Driven Session Manager with Zero-Latency Queuing | ~566 |
|
||||
|
||||
### Dec 18, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #29240 | 12:12 AM | 🔵 | SDK Agent Event-Driven Query Loop with Tool Restrictions | ~507 |
|
||||
|
||||
### Dec 20, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #31100 | 8:01 PM | 🔵 | Summary and Memory Message Generation in SDK Agent | ~324 |
|
||||
|
||||
### Dec 25, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #32616 | 8:43 PM | 🔵 | Comprehensive analysis of "enable billing" setting and its impact on rate limiting | ~533 |
|
||||
| #32599 | 8:40 PM | 🔄 | Added validation and explicit default for Gemini model configuration | ~393 |
|
||||
| #32598 | " | 🔵 | Gemini configuration loaded from settings or environment variables | ~363 |
|
||||
| #32591 | 8:38 PM | 🔴 | Removed Unsupported Gemini Model from Agent | ~282 |
|
||||
| #32583 | " | 🔵 | Gemini Agent Implementation Details | ~434 |
|
||||
| #32543 | 7:29 PM | 🔄 | Rate limiting applied conditionally based on billing status | ~164 |
|
||||
| #32542 | " | 🔄 | Query Gemini now accepts billing status | ~163 |
|
||||
| #32541 | " | 🔄 | Gemini config now includes billing status | ~182 |
|
||||
| #32540 | " | 🔄 | Rate limiting logic refactored for Gemini billing | ~164 |
|
||||
|
||||
### Dec 26, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #32949 | 10:55 PM | 🔵 | Complete settings persistence flow for Xiaomi MIMO v2 Flash model | ~320 |
|
||||
| #32948 | 10:53 PM | 🔵 | OpenRouterAgent uses CLAUDE_MEM_OPENROUTER_MODEL setting with Xiaomi as default | ~183 |
|
||||
|
||||
### Dec 27, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #33215 | 9:06 PM | 🔵 | SessionManager Implements Event-Driven Lifecycle with Database-First Persistence and Auto-Initialization | ~853 |
|
||||
| #33214 | " | 🔵 | SDKAgent Implements Event-Driven Query Loop with Init/Continuation Prompt Selection | ~769 |
|
||||
|
||||
### Dec 28, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #33551 | 11:00 PM | 🔵 | GeminiAgent Does Not Implement Resume Functionality | ~307 |
|
||||
| #33550 | " | 🔵 | OpenRouterAgent Does Not Implement Resume Functionality | ~294 |
|
||||
| #33549 | 10:59 PM | 🔴 | SDKAgent Now Checks memorySessionId Differs From contentSessionId Before Resume | ~419 |
|
||||
| #33547 | " | 🔵 | All Agents Call storeObservation with contentSessionId Instead of memorySessionId | ~407 |
|
||||
| #33543 | 10:56 PM | 🔵 | SDKAgent Already Implements Memory Session ID Capture and Resume Logic | ~467 |
|
||||
| #33542 | " | 🔵 | SessionManager Already Uses Renamed Session ID Fields | ~390 |
|
||||
|
||||
### Dec 30, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #34504 | 2:31 PM | 🔵 | SDKAgent V2 Message Handling and Processing Flow Detailed | ~583 |
|
||||
| #34459 | 2:23 PM | 🔵 | Complete SDKAgent V2 Architecture with Comprehensive Message Processing | ~619 |
|
||||
| #34453 | 2:21 PM | 🔵 | Memory Agent Configured as Observer-Only | ~379 |
|
||||
|
||||
### Jan 4, 2026
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #36853 | 1:49 AM | 🔵 | GeminiAgent Implementation Reviewed for Model Support | ~555 |
|
||||
</claude-mem-context>
|
||||
@@ -4,6 +4,7 @@ export const HOOK_TIMEOUTS = {
|
||||
WORKER_STARTUP_WAIT: 1000,
|
||||
WORKER_STARTUP_RETRIES: 300,
|
||||
PRE_RESTART_SETTLE_DELAY: 2000, // Give files time to sync before restart
|
||||
POWERSHELL_COMMAND: 10000, // PowerShell process enumeration (10s - typically completes in <1s)
|
||||
WINDOWS_MULTIPLIER: 1.5 // Platform-specific adjustment
|
||||
} as const;
|
||||
|
||||
|
||||
+54
-1
@@ -3,5 +3,58 @@
|
||||
|
||||
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||
|
||||
*No recent activity*
|
||||
### Nov 5, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #4035 | 10:24 PM | 🔵 | logger.ts file exists but is empty | ~220 |
|
||||
|
||||
### Nov 10, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #6521 | 5:43 PM | 🔵 | Code Review: Enhanced HTTP Logging and Double Entries Bug Fix | ~482 |
|
||||
|
||||
### Nov 17, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #10019 | 12:14 AM | 🔵 | TranscriptParser Utility: JSONL Parsing with Type-Safe Entry Filtering | ~569 |
|
||||
|
||||
### Nov 23, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #14626 | 6:25 PM | 🔵 | Stop Hook Summary Not in Transcript Validator Schema | ~359 |
|
||||
|
||||
### Nov 28, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #17238 | 11:34 PM | 🔵 | Existing TranscriptParser TypeScript implementation handles nested message structure | ~493 |
|
||||
|
||||
### Dec 5, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #20407 | 7:20 PM | 🔵 | Tag stripping utilities implement dual-tag privacy system with ReDoS protection | ~415 |
|
||||
|
||||
### Dec 8, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #22310 | 9:46 PM | 🟣 | Complete Hook Lifecycle Documentation Generated | ~603 |
|
||||
| #22306 | 9:45 PM | 🔵 | Dual-Tag Privacy System with ReDoS Protection | ~461 |
|
||||
|
||||
### Dec 14, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #25691 | 4:24 PM | 🔵 | happy_path_error__with_fallback utility logs errors to silent.log and returns fallback values | ~460 |
|
||||
|
||||
### Dec 20, 2025
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #30883 | 6:38 PM | 🔵 | Tag-Stripping DRY Violation Analysis | ~152 |
|
||||
</claude-mem-context>
|
||||
@@ -0,0 +1,7 @@
|
||||
<claude-mem-context>
|
||||
# Recent Activity
|
||||
|
||||
<!-- This section is auto-generated by claude-mem. Edit content outside the tags. -->
|
||||
|
||||
*No recent activity*
|
||||
</claude-mem-context>
|
||||
@@ -47,6 +47,10 @@ describe('hook-constants', () => {
|
||||
it('should define WINDOWS_MULTIPLIER', () => {
|
||||
expect(HOOK_TIMEOUTS.WINDOWS_MULTIPLIER).toBe(1.5);
|
||||
});
|
||||
|
||||
it('should define POWERSHELL_COMMAND timeout as 10000ms', () => {
|
||||
expect(HOOK_TIMEOUTS.POWERSHELL_COMMAND).toBe(10000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('HOOK_EXIT_CODES', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
||||
|
||||
/**
|
||||
* Tests for WMIC output parsing logic used in Windows process enumeration.
|
||||
* Tests for PowerShell output parsing logic used in Windows process enumeration.
|
||||
*
|
||||
* This tests the parsing behavior directly since mocking promisified exec
|
||||
* is unreliable across module boundaries. The parsing logic matches exactly
|
||||
@@ -9,16 +9,14 @@ import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
||||
*/
|
||||
|
||||
// Extract the parsing logic from ProcessManager for direct testing
|
||||
// This matches the implementation in src/services/infrastructure/ProcessManager.ts lines 93-100
|
||||
function parseWmicOutput(stdout: string): number[] {
|
||||
// This matches the implementation in src/services/infrastructure/ProcessManager.ts lines 95-100
|
||||
function parsePowerShellOutput(stdout: string): number[] {
|
||||
return stdout
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map(line => {
|
||||
const match = line.match(/ProcessId=(\d+)/i);
|
||||
return match ? parseInt(match[1], 10) : NaN;
|
||||
})
|
||||
.filter(n => !isNaN(n) && Number.isInteger(n) && n > 0);
|
||||
.map(line => line.trim())
|
||||
.filter(line => line.length > 0 && /^\d+$/.test(line))
|
||||
.map(line => parseInt(line, 10))
|
||||
.filter(pid => pid > 0);
|
||||
}
|
||||
|
||||
// Validate parent PID - matches ProcessManager.getChildProcesses() lines 85-88
|
||||
@@ -26,131 +24,119 @@ function isValidParentPid(parentPid: number): boolean {
|
||||
return Number.isInteger(parentPid) && parentPid > 0;
|
||||
}
|
||||
|
||||
describe('WMIC output parsing (Windows)', () => {
|
||||
describe('parseWmicOutput - ProcessId format parsing', () => {
|
||||
it('should parse ProcessId=12345 format correctly', () => {
|
||||
const stdout = 'ProcessId=12345\r\nProcessId=67890\r\n';
|
||||
describe('PowerShell output parsing (Windows)', () => {
|
||||
describe('parsePowerShellOutput - simple number format parsing', () => {
|
||||
it('should parse simple number format correctly', () => {
|
||||
const stdout = '12345\r\n67890\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
const result = parsePowerShellOutput(stdout);
|
||||
|
||||
expect(result).toEqual([12345, 67890]);
|
||||
});
|
||||
|
||||
it('should parse single PID from WMIC output', () => {
|
||||
const stdout = 'ProcessId=54321\r\n';
|
||||
it('should parse single PID from PowerShell output', () => {
|
||||
const stdout = '54321\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
const result = parsePowerShellOutput(stdout);
|
||||
|
||||
expect(result).toEqual([54321]);
|
||||
});
|
||||
|
||||
it('should handle WMIC output with mixed case', () => {
|
||||
// WMIC output can vary in case on different Windows versions
|
||||
const stdout = 'PROCESSID=11111\r\nprocessid=22222\r\nProcessId=33333\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
|
||||
expect(result).toEqual([11111, 22222, 33333]);
|
||||
});
|
||||
|
||||
it('should handle empty WMIC output', () => {
|
||||
it('should handle empty PowerShell output', () => {
|
||||
const stdout = '';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
const result = parsePowerShellOutput(stdout);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle WMIC output with only whitespace', () => {
|
||||
it('should handle PowerShell output with only whitespace', () => {
|
||||
const stdout = ' \r\n \r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
const result = parsePowerShellOutput(stdout);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should filter invalid PIDs from WMIC output', () => {
|
||||
const stdout = 'ProcessId=12345\r\nProcessId=invalid\r\nProcessId=67890\r\n';
|
||||
it('should filter invalid PIDs from PowerShell output', () => {
|
||||
const stdout = '12345\r\ninvalid\r\n67890\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
const result = parsePowerShellOutput(stdout);
|
||||
|
||||
expect(result).toEqual([12345, 67890]);
|
||||
});
|
||||
|
||||
it('should filter negative PIDs from WMIC output', () => {
|
||||
// Negative PIDs won't match the regex /ProcessId=(\d+)/i (only digits)
|
||||
const stdout = 'ProcessId=12345\r\nProcessId=-1\r\nProcessId=67890\r\n';
|
||||
it('should filter negative PIDs from PowerShell output', () => {
|
||||
const stdout = '12345\r\n-1\r\n67890\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
const result = parsePowerShellOutput(stdout);
|
||||
|
||||
expect(result).toEqual([12345, 67890]);
|
||||
});
|
||||
|
||||
it('should filter zero PIDs from WMIC output', () => {
|
||||
// Zero is filtered out by the n > 0 check
|
||||
const stdout = 'ProcessId=0\r\nProcessId=12345\r\n';
|
||||
it('should filter zero PIDs from PowerShell output', () => {
|
||||
const stdout = '0\r\n12345\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
const result = parsePowerShellOutput(stdout);
|
||||
|
||||
expect(result).toEqual([12345]);
|
||||
});
|
||||
|
||||
it('should handle WMIC output with extra lines and noise', () => {
|
||||
const stdout = '\r\n\r\nProcessId=12345\r\n\r\nSome other output\r\nProcessId=67890\r\n\r\n';
|
||||
it('should handle PowerShell output with extra lines and noise', () => {
|
||||
const stdout = '\r\n\r\n12345\r\n\r\nSome other output\r\n67890\r\n\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
const result = parsePowerShellOutput(stdout);
|
||||
|
||||
expect(result).toEqual([12345, 67890]);
|
||||
});
|
||||
|
||||
it('should handle Windows line endings (CRLF)', () => {
|
||||
const stdout = 'ProcessId=111\r\nProcessId=222\r\nProcessId=333\r\n';
|
||||
const stdout = '111\r\n222\r\n333\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
const result = parsePowerShellOutput(stdout);
|
||||
|
||||
expect(result).toEqual([111, 222, 333]);
|
||||
});
|
||||
|
||||
it('should handle Unix line endings (LF)', () => {
|
||||
const stdout = 'ProcessId=111\nProcessId=222\nProcessId=333\n';
|
||||
const stdout = '111\n222\n333\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
const result = parsePowerShellOutput(stdout);
|
||||
|
||||
expect(result).toEqual([111, 222, 333]);
|
||||
});
|
||||
|
||||
it('should handle lines with extra equals signs', () => {
|
||||
const stdout = 'ProcessId=12345\r\nSomeOther=value=with=equals\r\nProcessId=67890\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
|
||||
expect(result).toEqual([12345, 67890]);
|
||||
});
|
||||
|
||||
it('should handle very large PIDs', () => {
|
||||
// Windows PIDs can be large but are still 32-bit integers
|
||||
const stdout = 'ProcessId=2147483647\r\n';
|
||||
const stdout = '2147483647\r\n';
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
const result = parsePowerShellOutput(stdout);
|
||||
|
||||
expect(result).toEqual([2147483647]);
|
||||
});
|
||||
|
||||
it('should handle typical WMIC list format output', () => {
|
||||
// Real WMIC output often has blank lines and extra spacing
|
||||
it('should handle typical PowerShell output with blank lines and extra spacing', () => {
|
||||
const stdout = `
|
||||
|
||||
ProcessId=1234
|
||||
1234
|
||||
|
||||
|
||||
ProcessId=5678
|
||||
5678
|
||||
|
||||
`;
|
||||
|
||||
const result = parseWmicOutput(stdout);
|
||||
const result = parsePowerShellOutput(stdout);
|
||||
|
||||
expect(result).toEqual([1234, 5678]);
|
||||
});
|
||||
|
||||
it('should filter lines with text and numbers mixed', () => {
|
||||
const stdout = '12345\r\nPID: 67890\r\n11111\r\n';
|
||||
|
||||
const result = parsePowerShellOutput(stdout);
|
||||
|
||||
expect(result).toEqual([12345, 11111]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parent PID validation', () => {
|
||||
|
||||
Reference in New Issue
Block a user