deciduous 0.15.0

Decision graph tooling for AI-assisted development. Track every goal, decision, and outcome. Survive context loss. Query your reasoning.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
//! Embedded changelog for version update notifications
//!
//! This module contains release notes that are shown when users
//! upgrade deciduous and run `check-update` or other commands.

/// A single release entry
pub struct Release {
    pub version: &'static str,
    pub highlights: &'static [&'static str],
}

/// All releases, newest first
pub const RELEASES: &[Release] = &[
    Release {
        version: "0.15.0",
        highlights: &[
            "Built-in MCP server: `deciduous mcp` exposes 31 tools over Model Context Protocol",
            "Works with Claude Code, Claude Desktop, and cowork",
            "Graph CRUD, querying, full-text search, chain tracing, node context, pulse, orphan detection",
            "Session management: each conversation gets its own decision tree (start/end/resume)",
            "Sessions persist across server restarts for long-running cowork conversations",
            "/query slash command for natural language reports from the decision graph",
            "Run `deciduous update` to apply to existing projects",
        ],
    },
    Release {
        version: "0.14.0",
        highlights: &[
            "Observations now require both a title and description (-d flag)",
            "CLI warns when creating observations without a description",
            "Observation descriptions shown inline in `deciduous nodes` listing",
            "Web viewer detail panel shows observation description prominently below title",
            "All templates and instructions updated for observation title+description convention",
            "Run `deciduous update` to apply to existing projects",
        ],
    },
    Release {
        version: "0.13.15",
        highlights: &[
            "Templates now include 'What NOT to Log' guidance to reduce meta-process noise",
            "Decision graph nodes should capture user project decisions, not AI internal process",
            "Run `deciduous update` to apply to existing projects",
        ],
    },
    Release {
        version: "0.13.14",
        highlights: &[
            "Post-commit hook is now advisory (exit 0) instead of blocking (exit 2)",
            "Run `deciduous update` to apply the fix to existing projects",
        ],
    },
    Release {
        version: "0.13.13",
        highlights: &[
            "Web viewer sorts narratives by most recent activity instead of tree size",
        ],
    },
    Release {
        version: "0.13.12",
        highlights: &[
            "Web viewer now defaults to showing all goals instead of only 10+ node trees",
        ],
    },
    Release {
        version: "0.13.11",
        highlights: &[
            "Version checking is now always-on (no opt-in needed)",
            "Patch updates show a quiet one-liner notification",
            "Minor/major updates show a prominent banner encouraging upgrade",
            "Deprecated `deciduous auto-update on/off` and `--no-auto-update` flag",
        ],
    },
    Release {
        version: "0.13.10",
        highlights: &[
            "Opt-in auto-update version check hook - checks crates.io once per 24h, non-blocking",
            "Toggle with `deciduous auto-update on/off`, or `deciduous init --no-auto-update`",
            "Copy markdown button added to Q&A chat responses in web viewer",
            "CI: auto-format on push to non-main branches",
        ],
    },
    Release {
        version: "0.13.9",
        highlights: &[
            "Fix: OpenCode plugins write to .deciduous/plugin.log instead of console.error (stderr also corrupts TUI)",
            "Neither stdout nor stderr is safe for OpenCode TUI - all plugin output now goes to log file",
            "Fix: OpenCode install runs directory migration before creating new structure",
        ],
    },
    Release {
        version: "0.13.8",
        highlights: &[
            "Fix: OpenCode post-commit hook still used console.log (stdout) instead of console.error (stderr)",
            "Completes the stdout fix from 0.13.7 - both pre-edit and post-commit hooks now use stderr",
        ],
    },
    Release {
        version: "0.13.7",
        highlights: &[
            "Fix: OpenCode plugin uses console.error instead of console.log to avoid breaking TUI STDOUT",
            "require-action-node and post-commit-reminder plugins no longer pollute OpenCode display",
        ],
    },
    Release {
        version: "0.13.6",
        highlights: &[
            "decision-graph: new Layer 4 - use gh CLI to find PRs for design context and review discussion",
            "PR descriptions and review threads mined for decision rationale, alternatives considered, and trade-offs",
            "Updated in both Claude Code and OpenCode templates",
        ],
    },
    Release {
        version: "0.13.5",
        highlights: &[
            "OpenCode command templates now match Claude Code verbosity (decision-graph, document, decision, archaeology)",
            "decision-graph: added Hardening Phase, Cross-Narrative Connections, Narrative Discipline, Rich Node Content, Edge Types",
            "document: added documentation structure template, test refinement step, decision criteria, example usage",
            "decision: added Web UI Branch Filter, disconnected node auditing, expanded multi-user sync",
            "archaeology skill: added Querying the Graph section and descriptive details",
        ],
    },
    Release {
        version: "0.13.3",
        highlights: &[
            "OpenCode bootstrapping upgraded to modern directory conventions (plugins/, commands/, skills/)",
            "Skills now use proper SKILL.md format in .opencode/skills/<name>/SKILL.md",
            "New: custom deciduous agent in .opencode/agents/deciduous.md",
            "New: custom deciduous tool in .opencode/tools/deciduous.ts wraps CLI for direct graph ops",
            "Migration logic: deciduous update auto-migrates from old singular dirs to plural",
        ],
    },
    Release {
        version: "0.13.2",
        highlights: &[
            "OpenCode integration fully updated to match Claude Code feature parity",
            "3 new OpenCode commands: /document, /sync, /decision-graph",
            "All 9 OpenCode commands updated with document attachments, event sync, graph integrity",
            "All 3 OpenCode skills updated: /pulse, /narratives, /archaeology use latest CLI commands",
            "OpenCode plugins improved: .deciduous check, better git commit detection",
            "AGENTS.md workflow section now includes full command/skill tables and node flow rule",
        ],
    },
    Release {
        version: "0.13.1",
        highlights: &[
            "Fix: deciduous update no longer deletes user content after the Decision Graph Workflow section in CLAUDE.md",
            "Added <!-- deciduous:start/end --> markers for safe section replacement",
            "Legacy CLAUDE.md files auto-migrate to markers on first update",
            "6 new tests for section replacement edge cases",
        ],
    },
    Release {
        version: "0.13.0",
        highlights: &[
            "Document attachments: attach files (images, PDFs, diagrams) to decision nodes",
            "7 doc subcommands: attach, list, show, describe, open, detach, gc",
            "AI-generated descriptions for attached documents (--ai-describe)",
            "Content-hash deduplication and soft-delete with garbage collection",
            "Theme system: tag and group nodes by theme",
            "Web viewer shows attached documents and themes in detail panel",
            "REST API: /api/documents and /api/documents/file/<id> endpoints",
            "Event-based sync support for document attachments",
            "All skills, commands, and docs updated with document awareness",
            "Init now creates .deciduous/documents/ directory",
        ],
    },
    Release {
        version: "0.12.2",
        highlights: &[
            "README rewritten to lead with /pulse, /archaeology, /narratives skills",
            "New Deep Q&A Interface section documenting POST /api/ask and FTS5 search",
            "Landing page updated: 1,170+ nodes, 1,020+ edges, 74 days of development",
            "Elevator pitch rewritten around three skills and conversational Q&A",
            "Revisit node type added to landing page (7 node types)",
            "Web viewer section updated with Archaeology view and Q&A panel",
        ],
    },
    Release {
        version: "0.12.1",
        highlights: &[
            "Bootstrap all slash commands via init/update: /document, /build-test, /serve-ui, /sync-graph, /decision-graph, /sync",
            "Updated CLAUDE.md template with full command and skill reference tables",
            "Documentation updated across README, QUICK_REFERENCE, ARCHITECTURE",
        ],
    },
    Release {
        version: "0.12.0",
        highlights: &[
            "Redesigned web viewer with hierarchical narrative view",
            "Smart narrative detection - filters to significant 10+ node trees",
            "D3 DAG flowchart visualization with left-to-right hierarchical layout",
            "Full-text search with type filter buttons",
            "Resizable panels - drag to resize all sections",
            "Gold shimmer effect for selected node highlighting",
            "Automatic graph loading when clicking search results",
            "Removed 20k+ lines of old scattered components for unified App.tsx",
        ],
    },
    Release {
        version: "0.11.2",
        highlights: &[
            "Fix: `deciduous opencode install` now works standalone without `deciduous init`",
            "Creates core infrastructure (.deciduous/, config, database path, docs/) automatically",
        ],
    },
    Release {
        version: "0.11.1",
        highlights: &[
            "Pre-built binaries for Linux, macOS, and Windows on GitHub Releases",
            "Added Linux ARM64 support",
            "SHA256 checksums included with releases",
        ],
    },
    Release {
        version: "0.11.0",
        highlights: &[
            "OpenCode integration support via `deciduous init` and `deciduous update`",
            "New `integration-status` command to check Claude Code and OpenCode setup",
            "Auto-generates opencode.json with deciduous plugin configuration",
        ],
    },
    Release {
        version: "0.10.3",
        highlights: &[
            "Fix card stack shrinking - cards now render at consistent size",
            "Remove node count limits - graphs show all nodes without truncation",
        ],
    },
    Release {
        version: "0.10.2",
        highlights: &[
            "Deep linking with parameterized routes (/archaeology/:id, /dag/:nodeId, etc.)",
            "Q&A history view with full-text search across all sessions",
            "Prompt modal for viewing verbatim user prompts on nodes",
            "Improved CardStack navigation and keyboard shortcuts",
            "BrowserRouter for cleaner URLs (no more hash fragments)",
        ],
    },
    Release {
        version: "0.10.1",
        highlights: &[
            "Fix card stack not appearing when clicking nodes in archaeology view",
            "Card stack now shows description, branch, and files by default",
        ],
    },
    Release {
        version: "0.10.0",
        highlights: &[
            "Archaeology view is now the default at `deciduous serve`",
            "Narrative-focused exploration with AI-powered explanations",
            "Session-based card stack UI with keyboard navigation (j/k/g/G/Space)",
            "Mobile-responsive with touch swipe gestures",
            "Persistent chat history and filters via localStorage",
            "Graph performance optimizations for large node counts",
        ],
    },
    Release {
        version: "0.9.6",
        highlights: &[
            "Embedded changelog shows what's new when upgrading",
            "Update reminder shown after all commands when outdated",
            "`check-update` now shows detailed feature list",
        ],
    },
    Release {
        version: "0.9.5",
        highlights: &[
            "New `check-update` command - detect when integration files need updating",
            "`update` no longer overwrites your configs (settings.json, config.toml, docs/)",
            "Version tracking via .deciduous/.version",
        ],
    },
    Release {
        version: "0.9.4",
        highlights: &[
            "Improved `update` command help text",
            "Better documentation for update workflow",
        ],
    },
    Release {
        version: "0.9.3",
        highlights: &[
            "Skills added to init/update: /pulse, /narratives, /archaeology",
            "`update` now refreshes skills and agents.toml",
        ],
    },
    Release {
        version: "0.9.2",
        highlights: &[
            "New `revisit` node type for pivots and direction changes",
            "Orange color coding in web viewer",
        ],
    },
    Release {
        version: "0.9.1",
        highlights: &["Republish of 0.9.0 (yanked)"],
    },
    Release {
        version: "0.9.0",
        highlights: &[
            "New `--date` flag for backdating nodes",
            "Archaeology workflow support",
        ],
    },
    Release {
        version: "0.8.25",
        highlights: &[
            "New `delete` command to remove nodes",
            "New `unlink` command to remove edges",
        ],
    },
];

/// Get releases between two versions (exclusive of from, inclusive of to)
/// Returns releases in order from oldest to newest for display
pub fn get_releases_between(from_version: &str, to_version: &str) -> Vec<&'static Release> {
    let from_parts = parse_version(from_version);
    let to_parts = parse_version(to_version);

    let mut releases: Vec<&Release> = RELEASES
        .iter()
        .filter(|r| {
            let v = parse_version(r.version);
            v > from_parts && v <= to_parts
        })
        .collect();

    // Reverse to show oldest first (chronological order)
    releases.reverse();
    releases
}

/// Parse version string into comparable tuple
fn parse_version(v: &str) -> (u32, u32, u32) {
    let parts: Vec<u32> = v.split('.').filter_map(|p| p.parse().ok()).collect();

    (
        parts.first().copied().unwrap_or(0),
        parts.get(1).copied().unwrap_or(0),
        parts.get(2).copied().unwrap_or(0),
    )
}

/// Format releases for display
pub fn format_releases(releases: &[&Release]) -> String {
    if releases.is_empty() {
        return String::new();
    }

    let mut output = String::new();
    for release in releases {
        output.push_str(&format!("\n  v{}\n", release.version));
        for highlight in release.highlights {
            output.push_str(&format!("{}\n", highlight));
        }
    }
    output
}

/// Returns true if the version difference is patch-only (same major.minor)
pub fn is_patch_only(current: &str, latest: &str) -> bool {
    let (cur_major, cur_minor, _) = parse_version(current);
    let (lat_major, lat_minor, _) = parse_version(latest);
    cur_major == lat_major && cur_minor == lat_minor
}

/// Check if integration files are outdated and return a brief reminder message
/// Returns None if up to date or if version file doesn't exist
pub fn check_version_reminder(current_binary_version: &str) -> Option<String> {
    let version_file = std::path::Path::new(".deciduous/.version");

    if !version_file.exists() {
        return None;
    }

    let installed_version = std::fs::read_to_string(version_file)
        .ok()?
        .trim()
        .to_string();

    if installed_version == current_binary_version {
        return None;
    }

    let releases = get_releases_between(&installed_version, current_binary_version);
    let feature_count: usize = releases.iter().map(|r| r.highlights.len()).sum();

    Some(format!(
        "Update available: v{} → v{} ({} new features). Run 'deciduous check-update' for details.",
        installed_version, current_binary_version, feature_count
    ))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_version() {
        assert_eq!(parse_version("0.9.5"), (0, 9, 5));
        assert_eq!(parse_version("1.0.0"), (1, 0, 0));
        assert_eq!(parse_version("0.8.25"), (0, 8, 25));
    }

    #[test]
    fn test_get_releases_between() {
        let releases = get_releases_between("0.9.3", "0.9.5");
        assert_eq!(releases.len(), 2);
        assert_eq!(releases[0].version, "0.9.4");
        assert_eq!(releases[1].version, "0.9.5");
    }

    #[test]
    fn test_get_releases_between_same_version() {
        let releases = get_releases_between("0.9.5", "0.9.5");
        assert!(releases.is_empty());
    }

    #[test]
    fn test_is_patch_only() {
        assert!(is_patch_only("0.13.10", "0.13.11"));
        assert!(is_patch_only("1.2.3", "1.2.9"));
        assert!(!is_patch_only("0.13.10", "0.14.0"));
        assert!(!is_patch_only("0.13.10", "1.0.0"));
        assert!(!is_patch_only("1.0.0", "2.0.0"));
    }

    #[test]
    fn test_format_releases() {
        let releases = get_releases_between("0.9.4", "0.9.5");
        let formatted = format_releases(&releases);
        assert!(formatted.contains("v0.9.5"));
        assert!(formatted.contains("check-update"));
    }
}