Skip to content

Commit d269264

Browse files
committed
chore: release v0.7.0 — bump version, update CHANGELOG and docs
1 parent 9a370f6 commit d269264

27 files changed

Lines changed: 1347 additions & 144 deletions

CHANGELOG.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,49 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1111

1212
---
1313

14+
## [v0.7.0] — 2026-03-26
15+
16+
### Added
17+
18+
- **Conflict detection**`haven apply --files` now detects when you've edited a
19+
deployed file since the last apply and asks what to do: `[s]kip`, `[o]verwrite`,
20+
`[A]pply all`, or `[d]iff` (view a diff before deciding). Haven records a SHA-256
21+
fingerprint of every file it writes, so it can tell the difference between "I
22+
updated this in source" and "you edited this live copy". The `--on-conflict=<mode>`
23+
flag skips the prompt: `skip` (CI-friendly, exits 1 when anything was skipped),
24+
`overwrite` (always clobber), or `prompt` (default on a TTY).
25+
26+
- **`C` marker in `haven status`** — files you've edited since the last apply now show
27+
a `C` marker. Combined with the source-drift marker: `MC` means both the source and
28+
your live copy have diverged. Run `haven status` before `haven apply` to see exactly
29+
what you've locally modified.
30+
31+
- **SkillKit `dir:` source support** — skills declared with `source = "dir:~/path"`
32+
now work with the SkillKit backend. Haven expands the path and passes it to
33+
`skillkit team install`, so local-development skills and marketplace skills can live
34+
in the same manifest.
35+
36+
- **`haven ai update` with SkillKit** — when the SkillKit backend is configured,
37+
`haven ai update` now delegates to `skillkit team install --update` instead of the
38+
native lock-clear path, letting SkillKit manage version pinning while haven manages
39+
state tracking.
40+
41+
- **SkillKit init guidance** — if `skillkit team install` exits non-zero, haven now
42+
surfaces an actionable hint: run `npx skillkit@latest init` if the error looks like
43+
an uninitialized agent, or `npx skillkit@latest doctor` for other failures.
44+
45+
### Changed
46+
47+
- **`docs/reference/skill-backends.md`** — the "native → skillkit" setup guide now
48+
includes `skillkit init` as a required one-time-per-machine step. The `dir:` source
49+
restriction note is corrected: `dir:` sources are now supported; only `repo:` sources
50+
are unsupported with SkillKit.
51+
52+
- **`docs/guides/ai-skills.md`** — SkillKit prerequisites now explicitly include
53+
`npx skillkit@latest init` with an explanation of what it does.
54+
55+
---
56+
1457
## [v0.6.0] — 2026-03-24
1558

1659
### Added

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "haven"
3-
version = "0.6.0"
3+
version = "0.7.0"
44
edition = "2021"
55
description = "AI-first dotfiles & environment manager"
66
license = "MIT"

README.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,10 +110,12 @@ for what's imported and what's skipped.
110110
haven init # initialize a new haven repo
111111
haven add ~/.zshrc # start tracking a file
112112
haven remove ~/.zshrc # stop tracking a file (live file untouched)
113-
haven apply # deploy tracked files to this machine
114-
haven apply --dry-run # preview without writing anything
115-
haven apply --dest ~/staging # apply to a staging directory (for testing)
116-
haven status # show drift between source and live files
113+
haven apply # deploy tracked files to this machine
114+
haven apply --dry-run # preview without writing anything
115+
haven apply --on-conflict=skip # skip user-edited files (CI-safe, exits 1)
116+
haven apply --on-conflict=overwrite # always overwrite user-edited files
117+
haven apply --dest ~/staging # apply to a staging directory (for testing)
118+
haven status # show drift; C = you edited, M = source changed
117119
haven diff # show file-level diff between source and live
118120
haven source-path # print the path to the haven repo
119121
haven brew install <formula> # brew install + update Brewfile
@@ -202,14 +204,22 @@ haven ai backends # list available skill backends
202204
```
203205

204206
The default backend (`native`) fetches skills directly from GitHub with SHA-256
205-
verification. To use the [SkillKit](https://skillkit.dev) marketplace instead,
206-
add `ai/config.toml`:
207+
verification. To use the [SkillKit](https://skillkit.dev) marketplace instead:
208+
209+
```sh
210+
npm install -g skillkit
211+
npx skillkit@latest init # one-time: initialize agent directories
212+
```
207213

208214
```toml
215+
# ai/config.toml
209216
[skills]
210217
backend = "skillkit" # delegates to `skillkit team install`
211218
```
212219

220+
SkillKit supports both `gh:` marketplace skills and `dir:~/path` local skills.
221+
Run `npx skillkit@latest init` once per machine (and again when you install a new agent).
222+
213223
---
214224

215225
## Module and profile config

TODOS.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,3 +472,30 @@ dead code — replaced with `state_dir` to enable cache lookup.
472472

473473
**Effort:** S (human) → S (CC+gstack)
474474
**Priority:** P3
475+
476+
---
477+
478+
## P3: `haven status --changed` filter
479+
480+
**What:** Add a `--changed` flag to `haven status` that filters output to only files with
481+
the `C` or `MC` marker (changed since last apply).
482+
483+
**Why:** On large repos (200+ tracked files), `haven status` will list every file. After
484+
the conflict detection feature ships, users will often want to see *only* the files they've
485+
personally modified — not all drift. `--changed` makes triage fast.
486+
487+
**Pros:** Trivial to implement — filter the already-computed status list before printing.
488+
Complements `haven apply --on-conflict=skip` in CI: run `haven status --changed` to see
489+
what was skipped.
490+
491+
**Cons:** Adds one CLI flag. Zero implementation risk.
492+
493+
**Context:** The `C` marker is added by the "conflict detection" feature (office-hours
494+
design: `jstegeman-HEAD-design-20260326-122506.md`). This TODO is only meaningful after
495+
that feature ships. The filter is a one-liner on the existing status output loop.
496+
497+
**Depends on / blocked by:** ~~Conflict detection feature~~ shipped in v0.7.0 — unblocked.
498+
499+
**Effort:** XS (human ~30min) → XS (CC+gstack ~2min)
500+
**Priority:** P3
501+

docs/guides/ai-skills.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,14 @@ No configuration required. This is what you get without any `ai/config.toml`.
169169

170170
Delegates to the [SkillKit](https://skillkit.dev) CLI for access to its 400K+ skill marketplace, cross-agent skill translation, and AI-powered recommendations.
171171

172-
**Prerequisites:** Node.js + `npm install -g skillkit` (or Bun).
172+
**Prerequisites:**
173+
174+
```sh
175+
npm install -g skillkit # or: bun add -g skillkit
176+
npx skillkit@latest init # one-time per machine: initialize agent platforms
177+
```
178+
179+
`skillkit init` detects which AI agent platforms you have installed (Claude Code, Cursor, etc.) and sets up their skill directories. Re-run it whenever you install a new agent.
173180

174181
```toml
175182
[skills]

docs/reference/skill-backends.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,19 @@ backend = "native" # "native" | "skillkit"
4747

4848
Delegates to the [SkillKit](https://skillkit.dev) CLI. Provides access to the SkillKit marketplace, cross-agent translation, and AI-powered skill recommendations.
4949

50-
**Prerequisites:** Node.js (for `npx`) or Bun (for `bunx`), with SkillKit installed globally:
50+
**Prerequisites:** Node.js (for `npx`) or Bun (for `bunx`), with SkillKit installed and initialized:
5151

5252
```sh
5353
npm install -g skillkit
5454
# or
5555
bun add -g skillkit
56+
57+
# One-time per-machine setup: detect installed agents and create their skill directories
58+
npx skillkit@latest init
5659
```
5760

61+
`skillkit init` is interactive — it detects which AI agent platforms are installed on your machine (Claude Code, Cursor, etc.) and creates the necessary skill directories for each one. Run it once per machine; re-run it when you install a new agent.
62+
5863
**Configuration:**
5964

6065
```toml
@@ -82,7 +87,7 @@ On `haven apply --ai`, haven:
8287

8388
**Lock file behavior:** `haven.lock` does NOT record SHAs for SkillKit-managed skills. Version pinning is delegated to SkillKit internally. The `fetch()` step returns a synthetic `sha: "managed-by-skillkit"` and is otherwise a no-op — SkillKit handles downloading during deployment.
8489

85-
**Source restrictions:** SkillKit only supports `gh:` sources. Skills declared with `repo:` or `dir:` sources cause an immediate error when the SkillKit backend is active.
90+
**Source restrictions:** SkillKit supports `gh:` and `dir:` sources. Skills declared with a `repo:` source cause an immediate error when the SkillKit backend is active`repo:` is a haven-internal source type with no SkillKit equivalent.
8691

8792
**Unavailability behavior:** If the configured runner is not on PATH, `haven apply` exits immediately:
8893

@@ -135,20 +140,26 @@ Your skill declarations (`ai/skills/`), platform config (`ai/platforms.toml`), a
135140
# or: bun add -g skillkit
136141
```
137142

138-
2. Create or update `ai/config.toml`:
143+
2. Initialize SkillKit for your agent platforms (one-time per machine):
144+
```sh
145+
npx skillkit@latest init
146+
```
147+
This detects which AI agents are installed (Claude Code, Cursor, etc.) and creates their skill directories. Re-run this whenever you install a new agent platform.
148+
149+
3. Create or update `ai/config.toml`:
139150
```toml
140151
[skills]
141152
backend = "skillkit"
142153
runner = "npx" # or "bunx" if you installed via bun
143154
```
144155

145-
3. Run apply:
156+
4. Run apply:
146157
```sh
147158
haven apply --ai
148159
```
149160
haven generates a `.skills` manifest from your existing `ai/skills/` declarations and calls `skillkit team install`. Your skills are redeployed to the same `skills_dir` locations as before.
150161

151-
4. (Optional) Commit the change:
162+
5. (Optional) Commit the change:
152163
```sh
153164
git add ai/config.toml && git commit -m "chore: switch to skillkit backend"
154165
```

src/chezmoi.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,7 +481,13 @@ fn try_convert_if_directive(directive: &str) -> Option<String> {
481481
///
482482
/// Directories starting with `.` are skipped entirely (they are chezmoi-internal
483483
/// or system directories — legitimate dotfile dirs use the `dot_` prefix).
484-
pub fn scan(source_dir: &Path, include_ignored: bool) -> Result<(Vec<ChezmoiEntry>, Vec<ChezmoiExternalEntry>, Vec<SkippedEntry>, Vec<ChezmoiScriptEntry>, Vec<ChezmoiBrewfileEntry>)> {
484+
type ScanResultKeeps = Vec<ChezmoiEntry>;
485+
type ScanResultExternals = Vec<ChezmoiExternalEntry>;
486+
type ScanResultSkipped = Vec<SkippedEntry>;
487+
type ScanResultScripts = Vec<ChezmoiScriptEntry>;
488+
type ScanResultBrewfiles = Vec<ChezmoiBrewfileEntry>;
489+
490+
pub fn scan(source_dir: &Path, include_ignored: bool) -> Result<(ScanResultKeeps, ScanResultExternals, ScanResultSkipped, ScanResultScripts, ScanResultBrewfiles)> {
485491
let managed = chezmoi_managed_paths(source_dir);
486492

487493
let mut keeps: Vec<ChezmoiEntry> = Vec::new();
@@ -1240,7 +1246,7 @@ fn extract_brew_bundle_file(rest: &str) -> Option<String> {
12401246
return Some(path.to_string());
12411247
}
12421248
} else if let Some(after) = rest.strip_prefix("--file ") {
1243-
let path = after.trim_start().split_whitespace().next()?.trim_matches(|c| c == '"' || c == '\'');
1249+
let path = after.split_whitespace().next()?.trim_matches(|c| c == '"' || c == '\'');
12441250
if !path.is_empty() {
12451251
return Some(path.to_string());
12461252
}

src/commands/add.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,11 @@ fn install_symlink(dest: &Path, source_file: &Path) -> Result<()> {
143143
use std::os::unix::fs::symlink;
144144

145145
// If dest already is the correct symlink, nothing to do.
146-
if dest.is_symlink() {
147-
if std::fs::read_link(dest).ok().as_deref() == Some(source_file) {
148-
println!("Symlink already in place: {}", dest.display());
149-
return Ok(());
150-
}
146+
if dest.is_symlink()
147+
&& std::fs::read_link(dest).ok().as_deref() == Some(source_file)
148+
{
149+
println!("Symlink already in place: {}", dest.display());
150+
return Ok(());
151151
}
152152

153153
// Back up the existing file before replacing it.
@@ -220,7 +220,7 @@ fn add_as_extdir(repo_root: &Path, dir: &Path, _remote_name: &str, url: &str, ig
220220
return Ok(());
221221
}
222222

223-
if !dir.strip_prefix(&home).is_ok() {
223+
if dir.strip_prefix(&home).is_err() {
224224
bail!("{} is not under your home directory", dir.display());
225225
}
226226

src/commands/ai.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::ai_config::{AiConfig, BackendKind};
1616
use crate::ai_platform::{platform_registry, PlatformsConfig};
1717
use crate::ai_skill::{DeployMethod, SkillSource, SkillsConfig};
1818
use crate::lock::LockFile;
19+
use crate::skill_backend_factory::create_backend;
1920
use crate::skill_cache::SkillCache;
2021
use crate::state::State;
2122

@@ -476,9 +477,18 @@ pub struct UpdateOptions<'a> {
476477
/// Fetch the latest version of skills, ignoring the current lock SHA.
477478
///
478479
/// Unlike `fetch`, this clears the lock entry before fetching so that
479-
/// `SkillCache::ensure()` always downloads from source.
480+
/// `SkillCache::ensure()` always downloads from source. When the configured
481+
/// backend is SkillKit the update is delegated to `skillkit team install
482+
/// --update` via `SkillBackend::update_all()`.
480483
pub fn update(opts: &UpdateOptions<'_>) -> Result<()> {
481484
let skills = load_skills_required(opts.repo_root)?;
485+
let ai_config = AiConfig::load(opts.repo_root).unwrap_or_default();
486+
487+
if matches!(ai_config.backend, BackendKind::SkillKit) {
488+
return update_skillkit(opts, &skills, &ai_config);
489+
}
490+
491+
// ── Native backend path ──────────────────────────────────────────────────
482492
let mut lock = LockFile::load(opts.repo_root)?;
483493
let cache = SkillCache::new(opts.state_dir);
484494

@@ -527,6 +537,48 @@ pub fn update(opts: &UpdateOptions<'_>) -> Result<()> {
527537
Ok(())
528538
}
529539

540+
/// SkillKit-specific update path: delegates to `skillkit team install --update`.
541+
fn update_skillkit(
542+
opts: &UpdateOptions<'_>,
543+
skills: &SkillsConfig,
544+
ai_config: &AiConfig,
545+
) -> Result<()> {
546+
let backend = create_backend(ai_config, opts.state_dir)?;
547+
let to_update = filter_skills(&skills.skills, opts.name);
548+
549+
// Collect (name, source) pairs for updatable sources.
550+
// repo: skills live inside the haven repo itself — SkillKit has no concept of them.
551+
let mut pairs: Vec<(&str, &str)> = Vec::new();
552+
for decl in &to_update {
553+
match SkillSource::parse(&decl.source)? {
554+
SkillSource::Repo => {
555+
if opts.name.is_some() {
556+
println!(
557+
"Skill '{}' uses a repo: source — nothing to update via SkillKit.",
558+
decl.name
559+
);
560+
}
561+
}
562+
_ => pairs.push((&decl.name, &decl.source)),
563+
}
564+
}
565+
566+
if pairs.is_empty() {
567+
println!("No updatable skills found. Nothing to update.");
568+
return Ok(());
569+
}
570+
571+
let updated = backend.update_all(&pairs)?;
572+
if updated.is_empty() {
573+
println!("Skills are already up to date.");
574+
} else {
575+
for name in &updated {
576+
println!("Updated '{}'.", name);
577+
}
578+
}
579+
Ok(())
580+
}
581+
530582
// ─── remove ───────────────────────────────────────────────────────────────────
531583

532584
/// Options for `haven ai remove`.

0 commit comments

Comments
 (0)