Skip to content

feat(skills): geospatial skill, multi-skill install, and auto-update#68

Merged
eddietejeda merged 6 commits intomainfrom
feat/geospatial-skill
Apr 29, 2026
Merged

feat(skills): geospatial skill, multi-skill install, and auto-update#68
eddietejeda merged 6 commits intomainfrom
feat/geospatial-skill

Conversation

@eddietejeda
Copy link
Copy Markdown
Contributor

@eddietejeda eddietejeda commented Apr 29, 2026

Summary

  • Add hotdata-geospatial agent skill (skills/hotdata-geospatial/SKILL.md) with PostGIS-style / geospatial SQL guidance so agents can load it only when work is geospatial.
  • hotdata skills install (global and --project) installs both hotdata and hotdata-geospatial under ~/.hotdata/skills/ and symlinks/copies into ~/.agents and optional agent roots (.claude, .pi).
  • Cargo.toml: pre-release-replacements bumps version: in both skill SKILL.md files on release (same as base hotdata skill).
  • Robustness: if the primary skill semver is current but a bundled skill directory is missing, re-download; hotdata skills status shows agent symlink rows after partial install (missing store shows ).
  • Auto-update: when ~/.hotdata/skills/hotdata already exists and the bundle is older than this CLI or incomplete, refresh from the matching release tarball before other commands (skips bare hotdata, hotdata skills …, and installs managed via ~/.agents/.skill-lock.json). Skill download/progress goes to stderr so JSON/piped stdout stays clean. Failures warn on stderr and do not abort the command.

Test plan

  • cargo test
  • Manual: hotdata skills install / hotdata skills status with both skills present and with one skill dir removed (partial).
  • Manual: upgrade CLI with outdated SKILL.md version under ~/.hotdata/skills/hotdata and run e.g. hotdata workspaces list — expect skill refresh on stderr when eligible.

Ships a separate hotdata-geospatial skill and installs it alongside the base hotdata skill so agents can load geospatial guidance only when relevant.
@sentry
Copy link
Copy Markdown

sentry Bot commented Apr 29, 2026

Codecov Report

❌ Patch coverage is 9.41176% with 154 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/skill.rs 6.66% 154 Missing ⚠️

📢 Thoughts on this report? Let us know!

Comment thread src/skill.rs Outdated
Comment on lines 400 to 413
if !all_exist {
row("Installed", &"Partial".yellow().to_string());
for skill_name in SKILL_NAMES {
let ok = skill_store_path(skill_name).exists();
let status = if ok {
"Yes".green().to_string()
} else {
"No".red().to_string()
};
row(&format!("{skill_name}"), &status);
}
println!("\nRun 'hotdata skills install' to install.");
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: the early return after printing the partial-install breakdown means agent symlink status is never shown for the skills that are installed. A user with hotdata present but hotdata-geospatial missing can't tell whether their .claude/skills/hotdata link is intact without running install again. Consider falling through to the agent-skills loop rather than returning, or at least showing the symlink rows for installed skills. (not blocking)

Comment thread src/skill.rs
Comment on lines +369 to +375
for skill_name in SKILL_NAMES {
println!(
"{:<20}{}",
format!("{skill_name}:"),
skill_store_path(skill_name).display().to_string().cyan()
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this correctly prints all skill store paths, but install() decides whether to re-download using only the primary skill's version (read_installed_version()). If hotdata-geospatial is absent from the store while hotdata is up-to-date, the version check returns "already up to date", ensure_symlinks() runs, and produces a dangling symlink for the geospatial skill. Consider also checking that all skill store paths exist before skipping the download:

Some(ref v) if *v >= current && SKILL_NAMES.iter().all(|n| skill_store_path(n).exists()) => {
    // already up to date
}

(not blocking)

Copy link
Copy Markdown
Contributor

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Blocking Issues

Cargo.toml — missing pre-release-replacements entry for skills/hotdata-geospatial/SKILL.md

The pre-release-replacements table only bumps skills/hotdata/SKILL.md. skills/hotdata-geospatial/SKILL.md has no entry, so its version: field will stay pinned at 0.1.14 on every future release. The tarball for v0.1.15+ will ship geospatial skill metadata that claims the wrong version — agents that read the SKILL.md front-matter for version awareness will see a stale value.

Fix: add the missing replacement alongside the existing one in Cargo.toml:

{ file = "skills/hotdata-geospatial/SKILL.md", search = "^version: .+", replace = "version: {{version}}", exactly = 1 },

Action Required

Add the hotdata-geospatial/SKILL.md entry to pre-release-replacements in Cargo.toml before merge.

Re-download when the primary skill version is current but a skill store directory is missing. Show agent skill symlink rows after partial install, and omit agent rows for skills not present in the store.
Comment thread src/skill.rs
Some(ref v) if *v >= current => {
Some(ref v) if *v >= current && all_skill_stores_present() => {
println!("Managed by skills agent — already up to date (v{v}).");
false
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: same issue from the prior review — read_installed_version() only checks hotdata's SKILL.md. If hotdata-geospatial is absent from the store while hotdata is current, needs_download is false, the download is skipped, ensure_symlinks() runs, and the geospatial skill gets a dangling symlink.

Suggested change
false
Some(ref v) if *v >= current && SKILL_NAMES.iter().all(|n| skill_store_path(n).exists()) => {

Same fix applies to the identical guard in install_project() (line 221). (not blocking)

claude[bot]
claude Bot previously approved these changes Apr 29, 2026
When ~/.hotdata/skills/hotdata exists but the bundle is older than the CLI or incomplete, refresh from the matching release tarball before other commands. Skip if skills were never installed, if ~/.agents/.skill-lock.json manages installs, or when HOTDATA_SKILLS_AUTO_UPDATE is disabled. Route skill download progress to stderr so stdout stays clean for JSON/piped output.
claude[bot]
claude Bot previously approved these changes Apr 29, 2026
Comment thread src/skill.rs
row("Installed", &"Yes".green().to_string());

match &installed_version {
Some(v) if *v < current => {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: when neither skill is installed (both paths absent), all_exist is false and the output reads Installed: Partial — which implies a previous interrupted install rather than "never installed". The old code showed Installed: No cleanly for this case.

Consider distinguishing zero-installs from partial-installs:

Suggested change
Some(v) if *v < current => {
let any_exist = SKILL_NAMES
.iter()
.any(|name| skill_store_path(name).exists());
let all_exist = any_exist
&& SKILL_NAMES
.iter()
.all(|name| skill_store_path(name).exists());
if !any_exist {
row("Installed", &"No".red().to_string());
println!("\nRun 'hotdata skills install' to install.");
return;
}
if !all_exist {

(not blocking)

claude[bot]
claude Bot previously approved these changes Apr 29, 2026
Comment thread src/skill.rs
.iter()
.all(|name| skill_store_path(name).exists());

if !all_exist {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: still shows Partial when neither skill has ever been installed. all_exist is false whether zero or one skill is missing, so a fresh machine prints Installed: Partial rather than Installed: No. Consider distinguishing the two cases:

let any_exist = SKILL_NAMES.iter().any(|name| skill_store_path(name).exists());
let all_exist = any_exist && SKILL_NAMES.iter().all(|name| skill_store_path(name).exists());

if !any_exist {
    row("Installed", &"No".red().to_string());
    println!("\nRun 'hotdata skills install' to install.");
    return;
}
if !all_exist {

(not blocking)

@eddietejeda eddietejeda changed the title feat(skills): add optional geospatial agent skill feat(skills): geospatial skill, multi-skill install, and auto-update Apr 29, 2026
@eddietejeda eddietejeda merged commit c90bbf4 into main Apr 29, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant