TODO — Dev Sentinel
Maps an 8-phase build plan for a CLI tool that detects developer frustration and generates learning notes from LLM transcripts.
What this file does
Maps an 8-phase build plan for a CLI tool that detects developer frustration and generates learning notes from LLM transcripts.
When to use it
- Planning a multi-phase project with parallel task dependencies
- Building a tool that hooks into LLM chat sessions to capture failure patterns
- Implementing RAG-based memory matching from user prompts
- Creating a CLI with init, review, and status commands for local agent logs
Assumes this stack
TODO — Dev Sentinel
Dependency Graph
Phase 1: Foundation
1.1 Project Setup
1.2 Types & Schemas
│
├─────────────────────┐
│ │
Phase 2: Core Infrastructure (parallel triple)
2.1 SQLite Store 2.2 LLM Provider 2.3 Settings Loader
│ │
│ ┌────────────────┤
│ │ │
Phase 3: Storage & Parsing (parallel pair, both need LLM Provider)
│ 3.1 Vector Store 3.2 Transcript Parser
│ │ │
│ │ │
Phase 4: Analysis Modules (parallel pair)
│ 4.1 RAG Matcher 4.2 Note Generator
│ │ (needs 2.2, │ (needs 2.2,
│ │ 3.1) │ 3.2)
│ │ │
Phase 5: Frustration Detection (needs 2.2)
5.1 Frustration Analyzer
│
├─────────────────────┐
│ │
Phase 6: Hook Handlers (parallel pair)
6.1 UserPromptSubmit 6.2 Stop Hook
(needs 2.1, 5.1, 4.1) (needs 2.1, 3.2, 4.2)
│ │
└──────────┬──────────┘
│
Phase 7: CLI & Integration (parallel triple)
7.1 CLI (review command)
7.2 sentinel init
7.3 Integration Tests
Phase 1: Foundation
No parallelism. Sequential.
-
1.1 Project Setup ✅
package.json,tsconfig.json, Jest config, fast-check- Directory structure (
src/,tests/unit/,tests/property/) - Build script, test script
.claude/settings.local.jsonhook config template
-
1.2 Types & Schemas ✅
- Zod schemas + TypeScript types for:
LLMProviderinterface (generateCompletion,generateEmbedding,isAvailable)TranscriptData,TranscriptMessage,ToolCallEntryFailureExperience(frustration_signature, failed_approaches, successful_approach, lessons)AutoMemoryCandidate(draft with status lifecycle)FrustrationAnalysis(type:'normal'|'frustrated'|'resolution'|'abandonment', confidence, intent, context, reasoning)MatchResult(experience, confidence, suggestedAction)SentinelSettings(llm provider config)
- Barrel export
src/types/index.ts - 100 tests (unit 60 + property 16 + index 1) all passing
- Zod schemas + TypeScript types for:
Phase 2: Core Infrastructure
2.1, 2.2, and 2.3 are parallel. No dependency between them.
-
2.1 SQLite Store ✅
src/storage/sqlite-store.ts- Tables:
session_turns,session_flags,auto_memory_candidates,experiences session_flagsschema:session_id TEXT PK,status TEXT ('frustrated'|'capture'),flagged_at TEXT,updated_at TEXT- CRUD:
storeTurn,getTurnsBySession,setFlag(session_id, status),getFlag(session_id),upgradeFlag(session_id, newStatus),clearFlag(session_id),storeCandidate,getPendingDrafts,storeExperience,getExperience,deleteCandidate,updateCandidateStatus runInTransaction()for atomic multi-table writesinitialize(),close()
- Tables:
-
2.2 LLM Provider ✅
src/llm/LLMProviderinterface implementationlocal-llm-provider.ts— Ollama (generateCompletionvia/api/generate,generateEmbeddingvia/api/embeddings)bedrock-llm-provider.ts— AWS Bedrock (ConverseCommand,InvokeModelCommand)mock-llm-provider.ts— Testing (deterministic, spy-capable,shouldFailmode)llm-provider-manager.ts— Provider selection, health tracking, fallbackprompts.ts— System prompts (frustration analysis, lesson summarization, RAG judge)
-
2.3 Settings Loader ✅
src/config/settings-loader.tsloadSettings(): SentinelSettings— read~/.sentinel/settings.json, Zod validate, return defaults on missing/invalid
Phase 3: Storage & Parsing
3.1 and 3.2 are parallel. Both depend on Phase 2 completion.
- 3.1 has no direct dependency on LLM — it stores/retrieves
number[]- 3.2 has no dependency on 2.1 or 2.2, but is grouped here for phase ordering
-
3.1 Vector Store ✅
src/storage/vector-store.ts- SQLite-backed:
vectorstable (id TEXT PK, embedding BLOB, metadata TEXT, created_at TEXT) store(id, embedding, metadata),search(queryEmbedding, topK, minSimilarity),delete(id),clearVectors()- Cosine similarity in TypeScript (zero-vector safe)
- UPSERT behavior (INSERT OR REPLACE), Float64Array BLOB storage
VectorSearchResultinterface exported:{ id, similarity, metadata }- Store embedding model name in metadata for migration detection
- 20 unit tests + 9 property tests (self-similarity, symmetry, range, round-trip, topK, delete, metadata)
- SQLite-backed:
-
3.2 Transcript Parser ✅
src/capture/transcript-parser.tsparseTranscriptFile(filePath): TranscriptData | null- Parse Claude Code JSONL format (human, assistant, tool_use, tool_result)
- Extract: user messages, assistant messages, tool calls (from both tool_use entries and assistant tool_calls), error messages
- Error detection from tool output (regex — allowed here, this is transcript parsing not prompt analysis)
- Never-throw guarantee (double try-catch), null on file not found / empty / no valid data
- 23 unit tests + 8 property tests (round-trip, invalid JSON resilience, error count bound, message count)
Phase 4: Analysis Modules
4.1 and 4.2 are parallel.
- 4.1 needs 2.1 (SQLite) + 2.2 (LLM) + 3.1 (Vector Store)
- 4.2 needs 2.2 (LLM) + 3.2 (Transcript Parser)
-
4.1 RAG Memory Matcher ✅
src/recall/memory-matcher.tssearchMemory(prompt, llmProvider, vectorStore, sqliteStore): MatchResult | null- Pipeline: embed → vectorStore.search(topK=3, minSimilarity=0.7) → getExperience → LLM judge (PROMPTS.ragJudge) → best match by confidence
- Inner try-catch per candidate: skip on failure, try next
- Outer try-catch: never throws, returns null on any unhandled error
- Confidence clamped to [0, 1] range
- 30 unit tests (happy path, empty results, irrelevant judge, embedding/judge/store failures, multiple candidates, malformed JSON, edge cases)
-
4.2 Note Generator ✅
src/capture/note-generator.tsgenerateNote(transcriptData, sessionId, llmProvider?): AutoMemoryCandidate | null- Error detection: errors array + tool call error fields (both trigger note generation)
- Fallback-first pattern: extract from transcript, then LLM overrides selectively
- LLM lessons via PROMPTS.lessonSummarization, JSON parsing with markdown code block support
- Fallback: assistant message sentences as lessons when LLM unavailable/fails
- Resolution detection heuristic for successfulApproach
- Never throws (double try-catch)
- 37 unit tests (normal flow, LLM extraction, LLM failure fallback, no LLM, null returns, frustration signature, failed approaches, edge cases)
Phase 5: Frustration Detection
Sequential. Depends on 2.2 (LLM Provider). Separate from Phase 4 because this is the NEW design piece — needs dedicated attention.
- 5.1 Frustration Analyzer ✅
src/analysis/frustration-analyzer.tsanalyzeFrustration(prompt, llmProvider): FrustrationAnalysis- Single LLM call:
generateCompletion(PROMPTS.frustrationAnalysis, prompt) - 2-stage JSON parsing: direct
JSON.parse→ markdown fence extraction (regex) - Zod
safeParsevalidation for type safety - Graceful fallback:
{ type: 'normal', confidence: 0, reasoning: '' }on any failure - Never throws (outer try-catch wraps entire pipeline)
- Korean + English support (handled by LLM, not by code)
- 34 unit tests + 12 property tests (totality, type safety, confidence bounds, schema conformance, deterministic fallback)
Phase 6: Hook Handlers
6.1 and 6.2 are parallel.
- 6.1 needs 2.1 (SQLite) + 5.1 (Frustration) + 4.1 (RAG)
- 6.2 needs 2.1 (SQLite) + 3.2 (Transcript Parser) + 4.2 (Note Generator)
-
6.1 UserPromptSubmit Handler ✅
src/hook/user-prompt-submit-handler.tshandleUserPromptSubmit({ prompt, sessionId, llmProvider, sqliteStore, vectorStore }): Promise<string>- Step 1:
analyzeFrustration(prompt, llmProvider)→ FrustrationAnalysis (single LLM call) - Step 2: Branch on
type:frustrated:setFlag(sessionId, 'frustrated')→searchMemory(prompt, ...)→ match? → systemMessage with suggestedActionresolution/abandonment: existing flag ='frustrated'? →upgradeFlag(sessionId, 'capture')normal: pass through
- Step 3:
storeTurn(sessionId, prompt, JSON.stringify(analysis))— always called regardless of type - Step 4:
getPendingDrafts()→ filter other sessions → append notification to systemMessage - Output:
'{}'or'{"systemMessage":"..."}'(match advice + draft notification joined with\n\n) - Never throws — outer try-catch →
'{}' EMPTY_RESPONSEconstant for consistency- 48 unit tests (frustrated+match, frustrated+no match, resolution/abandonment flag upgrade, normal passthrough, pending drafts, storeTurn always, error handling for every dependency, input validation, combined scenarios)
-
6.2 Stop Hook Handler ✅
src/hook/stop-hook-handler.tshandleStop({ sessionId, transcriptPath, llmProvider, sqliteStore }): Promise<string>- Step 1:
getFlag(sessionId)→ status !=='capture'? → approve immediately- Includes: no flag, status =
'frustrated', unknown statuses, empty string
- Includes: no flag, status =
- Step 2:
parseTranscriptFile(transcriptPath)→ TranscriptData | null - Step 3:
generateNote(transcriptData, sessionId, llmProvider)→ AutoMemoryCandidate | null - Step 4: Dedup check via
getPendingDrafts()→storeCandidate(note)if no duplicate - Step 5:
clearFlag(sessionId)— guaranteed viatry...finallypattern - Extracted
runCapturePipeline()helper for readability (4-level nesting → 2-level) - Extracted
safeClearFlag()utility for error-safe flag clearing APPROVE_RESPONSEconstant:'{"decision":"approve"}'(always)- Never throws — multi-layered error handling (getFlag, parse, generate, store, clearFlag each isolated)
- 43 unit tests (flag absent, frustrated, capture full pipeline, null parse/note, throws at every stage, dedup, invalid input, clearFlag guarantee, edge cases)
Phase 7: CLI & Integration
7.1, 7.2, and 7.3 are parallel. All depend on all previous phases.
-
7.1 CLI ✅
src/cli.tscreateProgram(deps)factory with Commander.js — dependency injection for testabilitysentinel review list— show pending drafts (formatted list with ID + frustrationSignature)sentinel review confirm <id>— draft → experience (SQLite) + embedding (Vector Store)- Embedding text:
"{frustrationSignature}. Failed: {failedApproaches}. Fixed: {successfulApproach}. Lessons: {lessons}" - Order: generate embedding first (async, may fail) → then store experience + vector + delete candidate
- Embedding text:
sentinel review reject <id>— delete draftsentinel --hook user-prompt-submit— parse stdin JSON, delegate to handlersentinel --hook stop— parse stdin JSON, delegate to handlersentinel status— DB stats (experience count + pending draft count)- Added
getExperienceCount()to SqliteStore for status command - Output through
configureOutput()for testability - 34 unit tests (review list/confirm/reject, hook routing, status, edge cases)
-
7.2
sentinel init✅src/cli/init-command.tsinitCommand(options: InitOptions): Promise<InitResult>— pure function with injected paths- Auto-generate
.claude/settings.local.jsonwith hook config- Merge with existing file if present (don't overwrite user settings)
- UserPromptSubmit + Stop hooks pointing to
sentinel --hook ... - Idempotent:
hasSentinelHook()detects existing sentinel hooks, prevents duplication - Graceful: malformed JSON in existing file treated as empty
- Create
~/.sentinel/directory ({ recursive: true }) - Create
~/.sentinel/settings.jsonwith DEFAULT_SETTINGS (only if not exists) - Injectable
ollamaHealthCheck— warn if unreachable, skip if undefined, treat throw as unreachable - Returns
{ messages, warnings }— never throws - 38 unit tests (clean env, merge, idempotency, settings contents, Ollama checks, edge cases)
-
7.3 Integration Tests ✅
tests/integration/pipeline.integration.test.tsSmartMockLLMProvider— extends MockLLMProvider with sequential JSON responses for frustration/RAG- Active Recall: seed DB → matching prompt → systemMessage returned
- Flag lifecycle: frustrated → resolution → Stop hook → draft created, flag cleared
- Frustrated → abandonment → Stop hook → draft created
- Frustrated → Stop fires while 'frustrated' → NO draft (still waiting)
- CLI: review confirm → experience + vector stored
- CLI: sentinel init → hook config + settings files created
- Graceful degradation: LLM down → silent pass-through (both hooks)
- Full round-trip: frustrated → resolution → Stop → confirm → recall
- Pending draft notifications across sessions
- 26 integration tests, all using MockLLMProvider (no Ollama dependency)
Phase 8: Packaging & Distribution
Sequential. Depends on all previous phases.
- 8.1 npm Package Setup ✅
package.json:name: "dev-sentinel",version,bin,files,enginesbinfield:{ "sentinel": "./dist/cli.js" }— npm package name isdev-sentinel, CLI binary issentinelfilesfield:["dist"]— only shipdist/, notsrc/ortests/prepublishOnlyscript:npm run build && npm test- Shebang line in cli.ts:
#!/usr/bin/env node - Verify:
npm pack --dry-run→ confirm tarball contains onlydist/+package.json - 27 unit tests (package.json structure, shebang, build output, npm pack verification)
Parallel Work Summary
| Phase | Tasks | Parallel? |
|---|---|---|
| 1 | 1.1 → 1.2 | Sequential |
| 2 | 2.1, 2.2, 2.3 | Parallel |
| 3 | 3.1, 3.2 | Parallel |
| 4 | 4.1, 4.2 | Parallel |
| 5 | 5.1 | Sequential |
| 6 | 6.1, 6.2 | Parallel |
| 7 | 7.1, 7.2, 7.3 | Parallel |
| 8 | 8.1 | Sequential |
Critical path: 1.1 → 1.2 → 2.2 → 5.1 → 6.1 → 7.2
What's inside
8 phases, 15 task groups, 7 parallel execution blocks, 1 critical path diagram
Change this for your project
- Replace
elbanic/dev-sentinelwith your own repository name - Replace
~/.sentinel/settings.jsonwith your own config path - Replace
dev-sentinelinpackage.jsonname field with your package name
Where it goes
Keep it in your repository where the agent or team that needs it will read it.
Worth borrowing
- Parallel phase grouping with explicit dependency arrows between tasks
- Critical path calculation to identify the longest sequential chain
- Checkbox progress tracking per task with test counts
Related Documents
Building SupportX AI Assist: A Multi-Agent IT Support System
Describes building a multi-agent IT support system with AutoGen, Azure AI Search, and Gemini embeddings for instant issue resolution and automatic escalation.
Intelligent Document Query Platform — GitHub-ready Low-Level Design (LLD)
Provides a copy-ready low-level design for a serverless document query platform with vector search and LLM integration.
Graph Matching with Topological Features
Teaches enhanced graph matching by combining spatial distances with node2vec and commute times embeddings, then applying the Hungarian algorithm.
Pulse — Life Cofounder | Build Log
Documents a full-stack monorepo that ingests LinkedIn and GitHub data, generates embeddings in-browser, and provides a RAG chat with an AI cofounder.