fix(decisions): update --review to deprecate in rendered markdown - #206
Conversation
Tests verify that decision/pitfall observations created via process-observations and merge-observation get confidence=0.95 and status=ready on first creation when quality_ok=true. Also verifies workflow/procedural are unaffected. Co-Authored-By: Claude <noreply@anthropic.com>
…lity_ok=true New decision/pitfall entries were hardcoded to INITIAL_CONFIDENCE (0.33) regardless of type, preventing immediate promotion. Since decision/pitfall have required=1 and spread=0, calculateConfidence(1, 'decision') returns 0.95 which already exceeds the promote threshold (0.65). Fix: in both process-observations and merge-observation new-entry paths, use calculateConfidence(1, type) for immediate types, then check promotion immediately after creation. workflow/procedural behaviour is unchanged (still get 0.33). Co-Authored-By: Claude <noreply@anthropic.com>
- cli-subcommands: tests for capacity review filtering, 7-day protection, least-used sort algorithm, and .decisions-notifications.json naming convention - review-command: tests verifying learn --review flagged filter behavior after capacity review is moved to decisions --review Co-Authored-By: Claude <noreply@anthropic.com>
Capacity review (deprecate least-used decisions/pitfalls by usage count) was in devflow learn --review. This is a decisions-system concern, so it now lives in devflow decisions --review behind a mode picker (observations | capacity | cancel). Changes: - decisions.ts: add mode picker to --review; add capacity block (identical logic but using .decisions-notifications.json, no outer lock - each updateDecisionsStatus call acquires .decisions.lock internally) - decisions.ts: add updateDecisionsStatus import and isCountActiveResult local guard - learn.ts: simplify --review to go directly to observations (no mode picker); remove capacity block and isCountActiveResult (now unused) - CLAUDE.md: document threshold promotion behavior and decisions --review modes Co-Authored-By: Claude <noreply@anthropic.com>
… files When deprecating an observation via decisions --review observations mode, also update the Status field in the rendered decisions/pitfalls markdown file, matching the behavior in learn --review.
|
Lock Inconsistency: fs.mkdir vs acquireMkdirLock 🔒 File: The decisions Comparison: Your
Suggested Fix: import { acquireMkdirLock } from './learn.js';
// Replace lines 586-594:
const decisionsLockDir = path.join(memoryDir, '.decisions.lock');
const lockAcquired = await acquireMkdirLock(decisionsLockDir);
if (!lockAcquired) {
p.log.error('Decisions system is currently running. Try again in a moment.');
return;
}Note: Claude Code Review | PR #206 |
|
Capacity Review Lock Strategy 🔐 File: The capacity review batch deprecation loop calls Compare with your observations mode (line 586) which holds the full lock for the entire loop — use the same pattern: const decisionsLockDir = path.join(memoryDir, '.decisions.lock');
const lockAcquired = await acquireMkdirLock(decisionsLockDir);
if (!lockAcquired) {
p.log.error('Decisions system is currently running. Try again in a moment.');
return;
}
try {
// ... existing batch deprecation loop (lines 829-841) ...
} finally {
try { await fs.rmdir(decisionsLockDir); } catch { /* already cleaned */ }
}Claude Code Review | PR #206 |
|
Type Safety: Unvalidated usageData Narrowing File: After validating the top-level structure, the code casts via Suggested Fix: // Safe coercion in the sort comparator:
const aCites = typeof aUsage.cites === 'number' ? aUsage.cites : 0;
const bCites = typeof bUsage.cites === 'number' ? bUsage.cites : 0;
if (aCites !== bCites) return aCites - bCites;Claude Code Review | PR #206 |
|
Duplicated Promotion Logic 📋 File: The identical "immediate type promotion on first creation" block is copy-pasted into both Suggested Fix: Extract a shared helper: function tryImmediatePromotion(entry) {
if (entry.type !== 'decision' && entry.type !== 'pitfall') return;
const th = THRESHOLDS[entry.type] || THRESHOLDS.procedural;
if (entry.confidence >= th.promote && entry.quality_ok === true) {
const firstSeenMs = new Date(entry.first_seen).getTime();
const spread = (Date.now() - firstSeenMs) / 1000;
if (!isNaN(firstSeenMs) && spread >= th.spread) {
entry.status = 'ready';
}
}
}Then call from both Claude Code Review | PR #206 |
|
Type Guard Duplication 🔄 File: The Suggested Fix: Export from learn.ts and import: // learn.ts:
export function isCountActiveResult(val: unknown): val is { count: number } {
return (
typeof val === 'object' && val !== null && 'count' in val &&
typeof (val as any).count === 'number'
);
}
// decisions.ts:
import { ..., isCountActiveResult } from './learn.js';This maintains single-source-of-truth and eliminates maintenance burden if the shape ever changes. Claude Code Review | PR #206 |
PR Review Summary: fix-threshold-promotion-bug (#206)✅ Strengths
📊 Medium-Confidence Issues (75-79%)Monolithic action handler (Complexity, 85%):
Test logic duplication (Testing, 85%):
Type-label formatting inconsistency (Consistency, 82%):
Inconsistent reason formatting (Consistency, 82%):
💡 Lower-Confidence Suggestions (60-79%)
RecommendationCHANGES_REQUESTED — The five blocking issues (≥80% confidence, marked in inline comments) must be resolved. Medium-confidence refactoring can be deferred if preferred. Generated by Claude Code Review |
The identical D3+D4 immediate-promotion block was duplicated across the process-observations new-entry path and the merge-observation new-entry path. Extract into tryImmediatePromotion(entry) so future changes to the promotion protocol have a single point of change. Co-Authored-By: Claude <noreply@anthropic.com>
…sistency - Replace bare fs.mkdir in --review observations mode with acquireMkdirLock() (exported from learn.ts) to get stale-lock recovery and timeout, matching the pattern already used in learn.ts --review. Addresses deadlock risk on crashed background process. - Export formatStaleReason from learn.ts and use it in decisions.ts --review instead of duplicating the inline reason-building logic. - Use generic capitalization (charAt(0).toUpperCase() + slice(1)) for type labels in --review observations mode, matching learn.ts style. - Extract filterEligibleEntries() and sortByLeastUsed() as exported functions from decisions.ts so tests call production logic rather than re-implementing it inline. - Add DecisionsEntry interface as an exported type at module scope. - Update tests/decisions/cli-subcommands.test.ts to import and call the extracted functions; add three tests covering the D28 notification clearing path (count below/above threshold, absent key). Co-Authored-By: Claude <noreply@anthropic.com>
Code Review FindingsNote: Creating inline comments for high-confidence (≥80%) blocking findings from 8 reviewer reports. Blocking Issues Identified1. Type Safety: DecisionsEntry.status should use literal union (85% confidence)File: src/cli/commands/decisions.ts:139 2. Type Safety: DecisionsEntry.file should use literal union (82% confidence)File: src/cli/commands/decisions.ts:137 3. Incomplete Refactoring: Promotion logic duplication (85% confidence)File: scripts/hooks/json-helper.cjs:1017-1030 4. Test Anti-Pattern: D28 notification tests replicate inline logic (85% confidence)File: tests/decisions/cli-subcommands.test.ts:505-565 5. Test Inconsistency: First capacity test doesn't call filterEligibleEntries (90% confidence)File: tests/decisions/cli-subcommands.test.ts:436-455 6. Lock Release Asymmetry (85% confidence)File: src/cli/commands/decisions.ts:729 Lower-Confidence Suggestions (60-79%)
Summary: 6 blocking findings (5 require code changes), 3 medium-severity architectural/pattern issues. PR is otherwise a clean refactoring that reduces duplication across multiple modules. Claude Code | devflow:git |
…on helper - Narrow DecisionsEntry.file to 'decisions' | 'pitfalls' literal union - Narrow DecisionsEntry.status to explicit literal union with toDecisionsStatus() helper for safe coercion from markdown-parsed strings - Fix acquireMkdirLock in learn.ts to check EEXIST explicitly instead of swallowing all mkdir errors — aligns with json-helper.cjs sync version - Document intentional acquire/release asymmetry with inline comment (decisions.ts) - Extract clearCapacityNotifications() from capacity review handler so tests can call the real function instead of replicating logic inline (D28) Co-Authored-By: Claude <noreply@anthropic.com>
…n opts The inline promotion block in process-observations (existing-entry path) was structurally identical to tryImmediatePromotion but required two extra guards: status !== 'created' and a first_seen fallback to epoch-0 for legacy entries. Extend tryImmediatePromotion with guardCreated and firstSeenFallback options (both default false to preserve new-entry semantics) and replace the inline block with a single call. All 27 threshold/merge-observation tests still pass. Co-Authored-By: Claude <noreply@anthropic.com>
… inline status filter Replace inline notification-clearing logic in three D28 tests with calls to the now-exported clearCapacityNotifications function (batch-2 extraction). Add a clarifying comment to the status-filter test explaining it intentionally mirrors the parser-level continue-skip (decisions.ts ~line 804), which is distinct from filterEligibleEntries (7-day protection window). Co-Authored-By: Claude <noreply@anthropic.com>
…cityNotifications
Review Findings - Inline CommentsCreating inline comments for high-confidence (≥80%) findings from code review. Processing 6 findings across 3 files. |
Type Safety:
|
Type Safety:
|
Security: Unsanitized
|
Test Coverage: Type annotation missing (tests/decisions/cli-subcommands.test.ts:444-449)The Fix: Add explicit type annotation: const allEntries: DecisionsEntry[] = [
{ id: 'ADR-001', pattern: 'Use X', file: 'decisions', filePath: '/tmp/decisions.md', status: 'Accepted', createdDate: '2026-01-01' },
// ...
];Note: The other two tests in this describe block correctly use Claude Code Review | devflow:review:orch |
Test Coverage:
|
Test Coverage:
|
Summary: PR #206 Review CommentsSuccessfully created 6 inline comments for high-confidence (≥80%) findings. Each comment links to specific files and line ranges with actionable fixes. Inline Comments Posted
Lower-Confidence Suggestions (60-79%) — Not Posted as InlineThese should be considered but have lower confidence:
Deduplication
Status: READY | All findings documented with suggested fixes Claude Code Review | devflow:review:orch |
…es Move LearningObservation type/guards/parsing/formatting into observations.ts (pure data, no I/O), acquireMkdirLock into mkdir-lock.ts (generic lock primitive), and readObservations/writeObservations/warnIfInvalid/ updateDecisionsStatus into observation-io.ts (filesystem bridge). decisions.ts and legacy-decisions-purge.ts now import from the new modules instead of the duplicated/learn.ts copies. Test imports updated to match. Per ADR-001: no backward-compat re-exports. Co-Authored-By: Claude <noreply@anthropic.com>
…thored-By: Claude <noreply@anthropic.com>
Move DecisionsEntryStatus to observations.ts (pure data module) so both observation-io.ts and decisions.ts can import it without a utility→command circular dependency. - observations.ts: define and export DecisionsEntryStatus - observation-io.ts: import DecisionsEntryStatus from observations.ts; updateDecisionsStatus now accepts DecisionsEntryStatus instead of string - decisions.ts: re-export DecisionsEntryStatus from observations.ts; derive VALID_STATUSES as Set<DecisionsEntryStatus> from a const-asserted array so type and set cannot drift; export toDecisionsStatus for testing - decisions.ts: add path containment guard before updateDecisionsStatus call — artifact_path from JSONL is now verified to reside inside .memory/decisions/ - tests: add toDecisionsStatus unit tests covering all valid statuses and unknown-string fallback Co-Authored-By: Claude <noreply@anthropic.com>
The EEXIST discrimination hardening (re-throw non-EEXIST errors) and stale lock recovery lacked any test coverage. Add real-filesystem tests (no mocks) verifying: successful acquisition, timeout on held lock, non-EEXIST error propagation, and stale lock removal. Co-Authored-By: Claude <noreply@anthropic.com>
…ent loss Move the resolution-summary.md write from the final Report phase to the Collect Results phase, where aggregated Resolver results are fresh in context. Previously the orchestrator was instructed to write the file at the tail end of the pipeline, after Simplifier and tech debt phases — if context compacted during those steps, the write was silently skipped. Co-Authored-By: Claude <noreply@anthropic.com>
fix(decisions): update --review to deprecate in rendered markdown
When deprecating an observation via
decisions --reviewobservations mode, also update the Status field in the rendered decisions/pitfalls markdown file, matching the behavior inlearn --review.Fixes
Testing
Co-Authored-By: Claude noreply@anthropic.com