Skip to content

feat(skillify): one-shot local skill mining for non-auth users (mine-local)#129

Open
efenocchi wants to merge 17 commits into
mainfrom
feat/skillify-mine-local
Open

feat(skillify): one-shot local skill mining for non-auth users (mine-local)#129
efenocchi wants to merge 17 commits into
mainfrom
feat/skillify-mine-local

Conversation

@efenocchi
Copy link
Copy Markdown
Collaborator

@efenocchi efenocchi commented May 13, 2026

Summary

  • New hivemind skillify mine-local subcommand: a one-shot, auth-free flow that mines reusable skills from the user's local Claude Code / Codex session transcripts on disk. Works before signing in, so a fresh install gives an immediate "this is useful" moment.
  • Parallel-per-session gate calls (concurrency 4) with multi-skill output (up to 3 per session) and overlap-based dedup against already-installed skills.
  • Symlink fan-out so mined skills land in every installed agent's native skill dir (~/.agents/skills/, ~/.hermes/skills/, ~/.pi/agent/skills/).
  • Single source of truth for the hivemind skillify ... command list (src/cli/skillify-spec.ts) consumed by every per-agent SessionStart injection.
  • When the user hasn't signed in but has already run mine-local, every agent's SessionStart now surfaces the count + sign-in CTA.

Why

A fresh hivemind install currently shows the user an empty wall: no skills, no demo of what mining produces. mine-local reads their existing local agent sessions (no Deeplake auth required), runs the same LLM gate the production worker uses (parallel-per-session, ε-greedy session pick, in-flight session filter, last-assistant-message preprocessing), and writes the resulting SKILL.mds to ~/.claude/skills/ with symlink fan-out so codex/hermes/pi see them too.

On the next session, if the user still hasn't signed in, the SessionStart injection now includes:

N local skill(s) from past 'hivemind skillify mine-local' run(s) live in ~/.claude/skills/. Run 'hivemind login' to start sharing new mining results with your team.

— closing the loop on the "first-impression" bootstrap.

Architecture

  • src/skillify/local-source.ts — agent + session-file detection on disk; ε-greedy pickSessions (3-phase: cwd-quota → global-quota → top-up, dedup-by-path); native Claude Code JSONL → SessionRow conversion mirroring the production capture hook's last_assistant_message semantics (drops tool noise + intermediate narration).
  • src/commands/mine-local.ts — orchestrator. Detects host agent, picks N sessions, runs gate calls in parallel via a small parallelMap (concurrency 4) + a local runGateViaStdin runner (the shared runGate uses argv and hits Linux's MAX_ARG_STRLEN at ~128 KB; stdin has no cap). Per-session prompts ask for up to 3 skills as a JSON array. Across sessions, overlap is detected via Jaccard on stopword-filtered description tokens (threshold 0.4) — no aggregation, no name-collision logic; each candidate writes independently unless its summary overlaps something already on disk.
  • src/skillify/local-manifest.ts — shared manifest at ~/.claude/hivemind/local-mined.json. Triple duty: one-shot sentinel (re-runs require --force), provenance index for a future push-local, and read-only count surface for SessionStart.
  • src/cli/skillify-spec.ts — single source of truth for the command list injected into every agent's SessionStart block. Four hook-based agents import it directly; pi keeps an inline mirror (it can't import from src/) guarded by a drift-detection test.
  • Fan-out reuses existing detectAgentSkillsRoots (src/skillify/agent-roots.ts) and fanOutSymlinks (src/skillify/pull.ts) — battle-tested by the skillify pull flow.

CLI surface

hivemind skillify mine-local
  [--n <num|all>]    # how many sessions to mine (default 8)
  [--force]          # bypass the one-shot manifest sentinel
  [--dry-run]        # stop before the LLM gate

Commits

da512c4 feat(skillify): surface local skill count to not-signed-in users
c06e728 refactor(skillify): extract local manifest read/write to shared module
7e92b3e feat(skillify): fan out mined skills to all installed agent skill roots
a38c2d6 test(skillify): unit tests for mine-local pure helpers
34291f4 feat(skillify): centralize command spec + advertise mine-local everywhere
a716d8b feat(skillify): wire `skillify mine-local` subcommand
26bd8d5 feat(skillify): add mine-local CLI orchestrator
accc213 feat(skillify): add local-source for non-auth session discovery

(Plus one merge from origin/main.)

Test plan

  • 117 test files / 2286 tests all passing locally.
  • New unit tests: pickSessions (3 degenerate cases + dedup + ordering), nativeJsonlToRows (last-assistant semantics, tool-result drops, malformed lines), findOverlap (Jaccard threshold + stopword filter + best-match), parseMultiVerdict (valid/empty/missing-fields/malformed/code-fenced), countLocalManifestEntries (missing/empty/populated/malformed/non-array), pi spec drift (every spec entry mirrored in PI_SKILLIFY_COMMANDS).
  • End-to-end via globally-installed hivemind binary: mined 12 skills from 8 sessions in the first verified run; subsequent run mined 5 more skills with proper overlap-skipping; symlink fan-out confirmed at ~/.agents/skills/<name>, ~/.hermes/skills/<name>, ~/.pi/agent/skills/<name> (each pointing at the canonical ~/.claude/skills/<name> dir).
  • Manifest tracking verified: every entry records skill_name, canonical_path, symlinks[], source_session_ids[], source_session_paths[], source_agent, gate_agent, created_at, uploaded:false.
  • Bundle scan: grep -c "skillify mine-local" returns 1 in every per-agent session-start bundle + pi inline source.
  • Drift detection: pi mirror block stays in sync with the canonical spec (37-entry vitest check).

Follow-ups (not in this PR)

  • hivemind skillify push-local: upload uploaded:false manifest rows to the org skills table after the user signs in.
  • openclaw integration: expose hivemind_mine_local as an MCP-style tool, since openclaw doesn't use the hivemind skillify CLI surface.

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced hivemind skillify mine-local command to extract and catalog skills from locally installed agent sessions without requiring Deeplake authentication.
    • Added persistent local skill manifest to track mined skills and prevent re-mining duplicates across runs.
  • Tests

    • Added comprehensive test coverage for local skill mining, manifest operations, and skill deduplication logic.

Review Change Stack

efenocchi added 9 commits May 13, 2026 03:05
New pure helpers for mining skills from local agent transcripts without
talking to Deeplake — supports the upcoming `hivemind skillify mine-local`
one-shot for users who haven't signed in yet.

- detectInstalledAgents() walks well-known session-dir roots
  (~/.claude/projects/, ~/.codex/sessions/) and reports each agent's
  encode-cwd scheme. Claude Code maps both `/` AND `_` to `-` in the
  encoded dir name; verified against real ~/.claude/projects/ entries.
- detectHostAgent() reads CLAUDECODE / CODEX_HOME env vars to know when
  we're running inside an agent (so the CLI can skip interactive prompts
  and default to the host's gate CLI + model).
- listLocalSessions() enumerates .jsonl files across all installs and
  tags each with mtime + in_cwd flag for the picker.
- pickSessions() implements the 3-phase ε-greedy pick:
  cwd-quota → global-quota → top-up, dedup-by-path throughout. Handles
  all-in-cwd / none-in-cwd / mixed without producing duplicates.
- nativeJsonlToRows() converts Claude Code native JSONL into the
  SessionRow shape the existing extractPairs() consumes. Mirrors the
  production capture hook's `last_assistant_message` semantics: only
  the final text-bearing assistant entry per turn is emitted, so the
  gate doesn't see "Now I'll run X" mini-narration between dropped
  tool_use blocks.

No wiring yet — the orchestrator and CLI dispatch land in follow-ups.
One-shot mining flow for users who haven't logged into Deeplake yet:
pick N local sessions, run an LLM gate per session in parallel, write
unique skills to ~/.claude/skills/, track results in a manifest.

Design choices that came out of e2e debugging:

- Parallel-per-session, NOT concatenated. Each session has its own
  problem domain; mixing N sessions in one prompt dilutes signal and
  makes the gate over-conservative. Concurrency cap=4 keeps the
  Anthropic side honest while finishing 8 sessions in ~90s.

- stdin-piped gate runner (local runGateViaStdin), not the shared
  argv-bound runGate. Linux MAX_ARG_STRLEN is 128 KB per single argv
  arg, and a per-session prompt easily exceeds that. Doesn't touch
  the worker's shared gate path.

- In-flight session filter: skip any session modified within the last
  60 s. Without this, mining bundles the live conversation into the
  prompt and the gate sees meta-discussion about the feature under
  construction.

- Per-session pair cap (30) + per-pair char cap (4 KB). The gate sees
  the LAST 30 pairs of each session — that's where crystallized
  takeaways live, not "let's explore X" session-openers.

- Multi-skill output per call. The gate returns up to 3 distinct
  skills per session; each session contributes independently.

- Overlap check, not name-dedup. Each candidate's description is
  compared (Jaccard on stopword-filtered tokens, threshold 0.4)
  against already-installed skills AND already-written-this-run
  skills. Overlap → skip with a "overlaps with X" line. No name
  collision, no semantic duplicate.

- Manifest at ~/.claude/hivemind/local-mined.json doubles as a
  one-shot sentinel — re-runs require --force. Each entry tracks
  source_session_ids/paths + uploaded:false so a later
  `skillify push-local` (when the user signs in) knows what to send.

CLI surface:
  hivemind skillify mine-local [--n <num|all>] [--force] [--dry-run]

No tests yet — pure unit-testable bits (pickSessions, parseMultiVerdict,
findOverlap) will get their own test file in a follow-up.
Plumb the orchestrator into the CLI dispatcher and rebuild the unified
bundle so `hivemind skillify mine-local` is callable from the installed
binary.

- runSkillifyCommand now matches "mine-local" and calls runMineLocal
  with the remaining argv.
- usage() text grows three lines documenting --n / --force / --dry-run.
- bundle/cli.js rebuilt from current src state.
…here

Each of the 5 agents (claude_code, codex, cursor, hermes, pi) used to
maintain its own hand-edited list of `hivemind skillify ...` commands
in its SessionStart injection block. Adding a new subcommand meant
remembering to touch all five places, and the `mine-local` command we
just shipped was missing from every agent's injected context — the
model couldn't know it existed.

This commit:

- Introduces src/cli/skillify-spec.ts as the single source of truth:
  a typed array of {cmd, desc} entries plus a renderSkillifyCommands()
  helper that produces the dash-aligned bullet block. The new
  mine-local entry is included.

- Refactors the 4 hook-based session-start.ts files (claude_code,
  codex, cursor, hermes) to import the spec and render it inline.
  This unifies a small wording divergence (claude_code's "Skill
  management ..." header vs codex/cursor/hermes' "SKILLS (skillify)
  ..." header is preserved per-agent; only the bulleted list itself
  comes from the spec).

- Mirrors the spec inline in pi/extension-source/hivemind.ts. pi's
  extension is shipped as a single self-contained .ts loaded by pi's
  runtime — it can't import from src/. The duplicate is clearly
  flagged "MIRROR of src/cli/skillify-spec.ts" and guarded by a
  drift-detection test (tests/pi/skillify-spec-drift.test.ts) that
  fails the build if either side adds, removes, or rewords an entry.

- Adds tests/pi/ to vitest.config.ts include glob.

After this commit, `mine-local` appears in every agent's
SessionStart injection (verified by grep against the rebuilt
bundles), and every future subcommand only needs editing in two
places (the spec + pi's mirror) instead of five.

openclaw exposes a different command surface (slash commands +
MCP-style tools, not `hivemind skillify`) so it's intentionally
out of scope here.
Add coverage for the pure functions that mine-local relies on:

- pickSessions (3 degenerate cases + dedup + ordering)
- nativeJsonlToRows (last_assistant_message semantics, tool-result
  user arrays dropped, thinking + tool_use blocks stripped, malformed
  lines skipped silently)
- summaryTokens / jaccard (stopword + short-token filtering, identical
  / disjoint / partial overlap math)
- findOverlap (no-match → null, semantic overlap detection, best-match
  selection when multiple cross threshold, stopword-heavy descriptions
  not falsely matched)
- parseMultiVerdict (valid array shape, empty-skills SKIP, filtering of
  entries missing required fields, malformed JSON → null, code-fenced
  / prose-wrapped JSON extraction, whitespace trimming)

The orchestrator runMineLocal itself is exercised by the e2e flow
(`hivemind skillify mine-local --force`), not unit-tested here — it
spawns the agent CLI and writes to ~/.claude/skills/, neither of which
is mock-friendly enough to be worth re-deriving here.

Adds `export` to summaryTokens / jaccard / findOverlap /
parseMultiVerdict / MinedSkill / MultiVerdict in mine-local.ts. No
behavior change — just makes them testable from outside the module.

35 new tests, all passing. Full suite stays green at 2278/2278.
Mined skills now appear in every installed agent's native skills root,
not just ~/.claude/skills/. Without this, mine-local skills were
invisible to codex / hermes / pi even when those agents were installed
on the same machine.

Implementation reuses the existing pull infrastructure:

- detectAgentSkillsRoots(skillsRoot) from src/skillify/agent-roots.ts
  enumerates roots present on this machine: ~/.agents/skills/ when
  codex OR pi is installed (agentskills.io shared layout),
  ~/.hermes/skills/ when hermes is installed, ~/.pi/agent/skills/
  when pi is installed. Cursor has no native skill discovery and is
  intentionally excluded by the detector.
- fanOutSymlinks(canonicalDir, dirName, roots) from
  src/skillify/pull.ts creates idempotent symlinks pointing back at
  the canonical ~/.claude/skills/<name>/ — already battle-tested by
  the `hivemind skillify pull` flow.

Manifest entries gain a `symlinks[]` field listing every link
created, so a future `push-local` / `unpull` flow can reverse the
fan-out cleanly without re-detecting installs.

E2E verified by running `hivemind skillify mine-local --force` after
deleting one existing skill: the new skill landed at the canonical
path plus three symlinks (~/.agents/skills/<name>, ~/.hermes/skills/<name>,
~/.pi/agent/skills/<name>), each pointing at the canonical directory.
Console output shows "fan-out → 3 root(s)" per written skill.
mine-local's manifest helpers (LOCAL_MANIFEST_PATH, ManifestEntry,
Manifest, loadManifest, saveManifest) move to a new self-contained
src/skillify/local-manifest.ts so the SessionStart hooks can read the
count without dragging the full orchestrator (gate runner, parallelMap,
fan-out, etc.) into the hook bundle.

mine-local.ts now imports the shared types and delegates read/write
through `readLocalManifest` / `writeLocalManifest` aliases. Behavior is
unchanged; the diff is mechanical.

countLocalManifestEntries() is added on the shared side as a
zero-allocation accessor for the upcoming "you have N local skills,
sign in to share new ones" SessionStart message.
When a user runs `hivemind skillify mine-local` then opens a new
session without first signing in, every agent's SessionStart hook
now appends a one-liner to the "not logged in" injection:

    N local skill(s) from past 'hivemind skillify mine-local' run(s)
    live in ~/.claude/skills/. Run 'hivemind login' to start sharing
    new mining results with your team.

This closes the loop on the bootstrap flow: a fresh user gets
useful skills from their local history immediately (no auth needed)
and is gently prompted to sign in when ready to share. The line is
silently omitted when the manifest is missing or empty, so first-
time users who haven't run mine-local don't see a vacuous "0 skills"
note.

Wiring:
- countLocalManifestEntries() now takes an optional path arg, so
  tests can point at a tmpdir instead of mutating HOME.
- countLocalManifestEntries() defends against malformed manifests
  where `entries` is non-array (e.g. a stray string) — would
  otherwise leak that string's `.length` as the count.
- 4 hook session-start.ts files (claude_code, codex, cursor, hermes)
  import countLocalManifestEntries from the shared module.
- pi's extension keeps an inline mirror (piCountLocalManifestEntries)
  for the same reason the spec mirror exists — pi loads its .ts
  directly and can't import from src/.
- 6 new unit tests in tests/claude-code/local-manifest.test.ts cover
  the read/write round-trip + every degenerate count path (missing
  file, empty entries, malformed JSON, missing field, non-array).

Full suite stays green: 117 test files, 2286 tests passing.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7f8259ed-3a24-4a87-852b-9b4e5d6c2fc8

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This pull request introduces hivemind skillify mine-local, a new CLI command that discovers and mines reusable skills from locally installed agent session transcripts, without requiring Deeplake authentication. The changes include a centralized skillify command spec, local manifest data structures, session detection and parsing utilities, the complete mine-local orchestration logic, updated CLI routing, session-start context enhancements across all agent integrations, comprehensive test coverage, and bundled artifact updates.

Changes

Skillify Mine-Local Feature Implementation

Layer / File(s) Summary
Skillify spec and local session data models
src/cli/skillify-spec.ts, src/skillify/local-manifest.ts, src/skillify/local-source.ts
Defines centralized SkillifyCommand spec and renderSkillifyCommands() for consistent help text; introduces LocalManifest and related interfaces for tracking mined skills; implements agent detection, session enumeration, and ε-greedy session selection with in-cwd bias.
Mine-local command orchestration and helpers
src/commands/mine-local.ts
Implements end-to-end runMineLocal that selects sessions, spawns per-session gate execution via stdin, parses multi-skill JSON verdicts, deduplicates via token-based Jaccard overlap, writes non-duplicate skills, and records provenance in local manifest; exports verdict parsing and similarity helper functions.
Skillify CLI command routing and help
src/commands/skillify.ts
Wires mine-local subcommand into CLI dispatch, updates usage() to advertise --n, --force, and --dry-run options, and routes execution with error handling.
Session-start context updates (source)
src/hooks/session-start.ts, src/hooks/codex/session-start.ts, src/hooks/cursor/session-start.ts, src/hooks/hermes/session-start.ts, pi/extension-source/hivemind.ts
Replaces hardcoded skillify command lists with dynamic renderSkillifyCommands() output; computes local-mined skill count via countLocalManifestEntries() and appends conditional note to not-logged-in user prompts when local mining has occurred.
Test coverage for mine-local and spec
tests/claude-code/local-manifest.test.ts, tests/claude-code/local-source.test.ts, tests/claude-code/mine-local-helpers.test.ts, tests/pi/skillify-spec-drift.test.ts, vitest.config.ts
Unit tests for manifest I/O, session selection algorithm, JSONL-to-row conversion semantics, verdict parsing, tokenization/Jaccard deduplication logic, and PI spec drift detection; adds Pi test glob to Vitest config.
Bundle CLI skill-writer updates and update command
bundle/cli.js
Adds bundled skill-writing helpers (renderFrontmatter, writeNewSkill, listSkills, resolveSkillsRoot), updates pull write flow to back up existing SKILL.md, integrates mine-local implementation into bundle, updates pull/unpull flag parsing, and refactors update command's install detection to scan parent directories for matching package.json.
Bundle session-start updates (all agents)
claude-code/bundle/session-start.js, codex/bundle/session-start.js, cursor/bundle/session-start.js, hermes/bundle/session-start.js
Mirrors source session-start changes in bundled form across all four agent implementations: adds skillify command spec and local-manifest helpers, refactors fs/path import aliases, replaces hardcoded command lists with dynamic rendering, and computes local-mined note for not-logged-in prompts.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • activeloopai/hivemind#111: Updates skillify pull/unpull argument parsing and session-start help text; this PR extends the shared skillify command spec and CLI surface to include the new mine-local subcommand while also refining update-command logic.

Suggested reviewers

  • kaghni

🐰 A miner's gift from sessions past,
No Deeplake key or network cast,
Local skills bloom in verdant beds,
By epsilon's wise strategic threads. 🌿✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers all required template sections: Summary (what does it do), explicit Test plan section with all checkboxes marked, but Version Bump section is missing. Add a 'Version Bump' section to the description indicating the appropriate version bump (minor version for new feature) or explicitly state if no release is needed.
Docstring Coverage ⚠️ Warning Docstring coverage is 13.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main feature: adding a 'mine-local' subcommand for local skill mining that works without authentication.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/skillify-mine-local

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot requested a review from kaghni May 13, 2026 17:18
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bundle/cli.js`:
- Around line 5852-5860: The gate stdin runner (runGateViaStdin) currently
rejects any agent other than "claude_code", which causes gateAgentFor() results
like "codex" to be unusable; change runGateViaStdin (and the same check around
opts.agent) to handle other agents by either invoking the argv-based runner
fallback (e.g., call the existing runGateViaArgv/path that expects prompt in
argv) or by supporting "codex" (and other agents returned by gateAgentFor())
instead of hard-failing; update the branch that checks opts.agent (and the
duplicate check at the other location) to route non-"claude_code" agents into
the fallback runner and preserve the existing error structure when no fallback
is available.
- Around line 6222-6226: The zero-candidate early return skips writing the local
manifest/sentinel so SessionStart lacks a local-skill count; change the branch
where totalCandidates === 0 to still persist the same manifest/sentinel/count
file and any tmpDir metadata the code writes when written.length > 0 before
returning (use the same write/save routine used for the normal path), and apply
the same fix to the duplicate block around the other occurrence (lines handling
the same flow near 6269-6286) so a sentinel is always created even when no
skills were written.
- Line 6430: The help text for the --n option is inconsistent with the
implementation: update the help message printed in the CLI (the console.log line
that documents "--n") to match the actual default used by the code (DEFAULT_N =
8), or alternatively change the DEFAULT_N constant to 3 so behavior matches the
message; locate the DEFAULT_N symbol and the console.log help line that mentions
"--n" (the mine-local option) and make both values consistent (preferably update
the help string to "default: 8" if you want behavior unchanged).

In `@src/commands/mine-local.ts`:
- Around line 418-423: After resolving gateAgent via detectHostAgent() and
gateAgentFor(...) (and computing gateBin with findAgentBin), add a fail-fast
check that validates the selected gateAgent is supported by runGateViaStdin; if
gateAgent !== "claude_code" log a clear error (include gateAgent and gateBin for
context) and terminate (e.g., process.exit(1>0) or throw) before proceeding with
mining. This ensures runGateViaStdin (and the surrounding flow in mine-local.ts)
won't continue with an unsupported agent.
- Around line 271-276: The parser currently allows skills with empty description
which breaks description-based dedupe; update the validation in the
skill-parsing block (where name, description, body, trigger are derived and
pushed to out via out.push) to require a non-empty description as well—i.e.,
after computing description = typeof s.description === "string" ?
s.description.trim() : "" change the guard from if (!name || !body) continue; to
also check description (if (!name || !description || !body) continue;) so only
skills with name, description, and body are added.

In `@src/commands/skillify.ts`:
- Line 196: Update the CLI help text to match the actual runtime default: change
the console.log message that prints the --n help (currently showing "(default:
3)") so it reflects DEFAULT_N used by runMineLocal (which is 8). Locate the help
string emitted in the CLI (the line that prints "      --n <num|all>            
how many sessions to mine (default: 3)") and update the default value text to
"(default: 8)" or, better, interpolate the DEFAULT_N constant used by
runMineLocal to keep help in sync with the code.

In `@tests/pi/skillify-spec-drift.test.ts`:
- Around line 24-26: The regex extraction of PI_SKILLIFY_COMMANDS into
piArrayMatch (via PI_SOURCE.match(...)) must be guarded before any dereference;
update the tests that use piArrayMatch! (references: piArrayMatch,
PI_SOURCE.match, PI_SKILLIFY_COMMANDS) to first assert/piArrayMatch truthiness
(e.g., expect(piArrayMatch).toBeTruthy(...) or if (!piArrayMatch) throw new
Error(...)) and provide a clear drift-failure message so later lines that access
piArrayMatch[0] or similar never cause a TypeError but instead produce the
explicit test failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4c746266-1457-46b8-90db-d005f536ab11

📥 Commits

Reviewing files that changed from the base of the PR and between 44b2b01 and da512c4.

📒 Files selected for processing (20)
  • bundle/cli.js
  • claude-code/bundle/session-start.js
  • codex/bundle/session-start.js
  • cursor/bundle/session-start.js
  • hermes/bundle/session-start.js
  • pi/extension-source/hivemind.ts
  • src/cli/skillify-spec.ts
  • src/commands/mine-local.ts
  • src/commands/skillify.ts
  • src/hooks/codex/session-start.ts
  • src/hooks/cursor/session-start.ts
  • src/hooks/hermes/session-start.ts
  • src/hooks/session-start.ts
  • src/skillify/local-manifest.ts
  • src/skillify/local-source.ts
  • tests/claude-code/local-manifest.test.ts
  • tests/claude-code/local-source.test.ts
  • tests/claude-code/mine-local-helpers.test.ts
  • tests/pi/skillify-spec-drift.test.ts
  • vitest.config.ts

Comment thread bundle/cli.js
Comment thread bundle/cli.js
Comment thread bundle/cli.js Outdated
Comment on lines +271 to +276
const name = typeof s.name === "string" ? s.name.trim() : "";
const description = typeof s.description === "string" ? s.description.trim() : "";
const body = typeof s.body === "string" ? s.body.trim() : "";
const trigger = typeof s.trigger === "string" ? s.trigger.trim() : undefined;
if (!name || !body) continue;
out.push({ name, description, body, trigger });
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 13, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Enforce description as required in parsed skills.

Line 272 reads description, but Line 275 only requires name and body. Since overlap dedupe is description-based, allowing empty descriptions undermines duplicate detection and contradicts the declared required shape.

Proposed fix
-    if (!name || !body) continue;
+    if (!name || !description || !body) continue;
     out.push({ name, description, body, trigger });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const name = typeof s.name === "string" ? s.name.trim() : "";
const description = typeof s.description === "string" ? s.description.trim() : "";
const body = typeof s.body === "string" ? s.body.trim() : "";
const trigger = typeof s.trigger === "string" ? s.trigger.trim() : undefined;
if (!name || !body) continue;
out.push({ name, description, body, trigger });
const name = typeof s.name === "string" ? s.name.trim() : "";
const description = typeof s.description === "string" ? s.description.trim() : "";
const body = typeof s.body === "string" ? s.body.trim() : "";
const trigger = typeof s.trigger === "string" ? s.trigger.trim() : undefined;
if (!name || !description || !body) continue;
out.push({ name, description, body, trigger });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/commands/mine-local.ts` around lines 271 - 276, The parser currently
allows skills with empty description which breaks description-based dedupe;
update the validation in the skill-parsing block (where name, description, body,
trigger are derived and pushed to out via out.push) to require a non-empty
description as well—i.e., after computing description = typeof s.description ===
"string" ? s.description.trim() : "" change the guard from if (!name || !body)
continue; to also check description (if (!name || !description || !body)
continue;) so only skills with name, description, and body are added.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in cbf0a87runGateViaStdin's claude_code-only contract is now enforced upstream by gateAgentFor, which prefers claude_code when installed and the caller fails fast with a clear error otherwise (see reply on the orchestrator comment below). Left description non-required on parsed skills intentionally: the prompt requires it and the model produces it consistently; hard-failing on empty desc would discard real candidates whenever the gate misformats one field. Overlap dedupe already short-circuits to null on empty token sets in findOverlap, so the surface is safe.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@efenocchi, that reasoning holds up well. The graceful-degradation approach (keeping a skill with an empty description rather than dropping it entirely) is a reasonable trade-off when the gate already prompts for it, and the findOverlap null-on-empty-token-set safety net covers the dedupe path cleanly. Thanks for the detailed explanation — I'll defer to your judgement here.

Comment thread src/commands/mine-local.ts
Comment thread src/commands/skillify.ts Outdated
Comment on lines +24 to +26
const piArrayMatch = PI_SOURCE.match(
/const PI_SKILLIFY_COMMANDS[^]*?\];/,
);
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 13, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard regex extraction before dereferencing in later tests.

Line 34/46/49 uses piArrayMatch!; if extraction fails, subsequent tests can throw a TypeError instead of producing the explicit drift failure message.

Proposed fix
 const piArrayMatch = PI_SOURCE.match(
   /const PI_SKILLIFY_COMMANDS[^]*?\];/,
 );
+const piBlock = piArrayMatch?.[0] ?? "";

 describe("pi skillify spec drift", () => {
   it("pi mirror block is present", () => {
     expect(piArrayMatch, "PI_SKILLIFY_COMMANDS array literal not found in pi/extension-source/hivemind.ts").toBeTruthy();
   });

   it("pi mirror has the same number of entries as the canonical spec", () => {
-    const piBlock = piArrayMatch![0];
+    expect(piArrayMatch, "PI_SKILLIFY_COMMANDS array literal not found in pi/extension-source/hivemind.ts").toBeTruthy();
     const piEntryCount = (piBlock.match(/cmd:\s*"/g) ?? []).length;
     expect(
       piEntryCount,
       `pi has ${piEntryCount} entries but src/cli/skillify-spec.ts has ${SKILLIFY_COMMANDS.length}; sync them`,
     ).toBe(SKILLIFY_COMMANDS.length);
   });

   for (const c of SKILLIFY_COMMANDS) {
     it(`pi mirror contains command "${c.cmd}"`, () => {
-      expect(piArrayMatch![0]).toContain(c.cmd);
+      expect(piBlock).toContain(c.cmd);
     });
     it(`pi mirror contains description for "${c.cmd}"`, () => {
-      expect(piArrayMatch![0]).toContain(c.desc);
+      expect(piBlock).toContain(c.desc);
     });
   }
 });

Also applies to: 34-50

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/pi/skillify-spec-drift.test.ts` around lines 24 - 26, The regex
extraction of PI_SKILLIFY_COMMANDS into piArrayMatch (via PI_SOURCE.match(...))
must be guarded before any dereference; update the tests that use piArrayMatch!
(references: piArrayMatch, PI_SOURCE.match, PI_SKILLIFY_COMMANDS) to first
assert/piArrayMatch truthiness (e.g., expect(piArrayMatch).toBeTruthy(...) or if
(!piArrayMatch) throw new Error(...)) and provide a clear drift-failure message
so later lines that access piArrayMatch[0] or similar never cause a TypeError
but instead produce the explicit test failure.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Skipped intentionally. The piArrayMatch! non-null assertion is gated by the first it("pi mirror block is present", ...) test, which fails explicitly with a custom message (PI_SKILLIFY_COMMANDS array literal not found) if extraction misses. Adding the ?.[0] ?? "" guard would mask that explicit failure: subsequent tests would still run against an empty block and report misleading drift errors instead of the actual missing-array root cause. The ! is doing useful work here as documentation that the assertion is enforced by the upstream test.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@efenocchi, that's a fair call. The intent is clear: the first it("pi mirror block is present") test owns the diagnostic message, and the ! assertions downstream communicate "this is guaranteed by the gate above." Worth noting that Vitest doesn't bail on first failure by default, so later tests will still execute and throw TypeErrors alongside the gate failure rather than replacing it — but since the root-cause test still fails with the explicit message, the TypeError noise is arguably acceptable. Acknowledged, moving on.

efenocchi added 3 commits May 13, 2026 17:47
…-auth users

The wow-effect flow:

  1. User installs hivemind, opens a Claude Code (or codex / cursor /
     hermes / pi) session for the first time. They are NOT signed in.
  2. SessionStart hook detects: no credentials + no local-mined.json
     manifest + ~/.claude/projects/ has at least one .jsonl + `hivemind`
     binary is on PATH. All four guards green → spawn
     `hivemind skillify mine-local` detached in the background.
  3. THIS session continues normally and sees the standard "not logged
     in to Deeplake" message — no waiting, no blocking.
  4. The background worker (typical wall-clock 60-120 s) mines up to 8
     sessions in parallel, writes SKILL.md files to ~/.claude/skills/
     with fan-out symlinks to every detected agent skill root, and
     records each in ~/.claude/hivemind/local-mined.json.
  5. NEXT SessionStart fires (could be the same agent or a different
     one — symlinks make the skills visible everywhere). The hook reads
     the manifest count and surfaces:

         "N local skill(s) from past 'hivemind skillify mine-local'
          run(s) live in ~/.claude/skills/. Run 'hivemind login' to
          start sharing new mining results with your team."

     User opens session N+1 → sees concrete value the system already
     produced for them → motivation to sign in to share.

Implementation:

- `src/skillify/spawn-mine-local-worker.ts` — maybeAutoMineLocal()
  helper invoked from every SessionStart hook in the no-creds branch.
  Guards: manifest-exists, lock-exists, no-claude-sessions, no-hivemind-bin.
  Stale-lock recovery: a lock older than 15 min is overridden (a prior
  worker presumably crashed without releasing it). Output goes to
  ~/.claude/hooks/mine-local.log so failures are inspectable.

- `src/skillify/local-manifest.ts` — exports LOCAL_MINE_LOCK_PATH so
  both the spawner (creates the lock) and the orchestrator (releases
  it on exit) agree without a circular import.

- `src/commands/mine-local.ts` — wraps runMineLocal in a `process.on('exit')`
  handler that unlinks the lock. process.exit() skips finally inside
  an async function but does fire 'exit' handlers, so this is the
  only correct cleanup path for the existing process.exit(1) error
  paths.

- 4 hook session-start.ts files (claude_code, codex, cursor, hermes)
  call maybeAutoMineLocal() in the no-creds branch and log the result.

- pi/extension-source/hivemind.ts inlines the equivalent piMaybeAutoMineLocal
  for the same reason the other pi mirrors exist (extension can't
  import from src/). Wired into the existing on('session_start')
  handler's else branch.

E2E verified in a sandboxed HOME tmpdir: hook fires → lock file
created within ms → detached worker logs to mine-local.log → on
exit, lock file removed.
…Claude Code and Codex

Three connected changes that extend the wow-effect for fresh, non-logged-in users
beyond Claude Code:

1. Bundle CLI launcher fix (universal)
   - `spawn-mine-local-worker.ts` and the pi mirror now resolve the spawned
     `hivemind` CLI through `import.meta.url` → `../../bundle/cli.js` instead
     of `which hivemind`. This guarantees the worker is the SAME plugin
     version as the hook that spawned it. Without this, a globally-installed
     older hivemind on PATH (e.g. 0.7.22) would silently miss subcommands
     added in the current release (`mine-local`), so the auto-mine flow
     would fail with `Unknown skillify subcommand: mine-local`. Falls back
     to `which hivemind` for unusual install layouts.

2. Notifications framework — visible CTA on Claude Code
   - New `localMinedRule` (src/notifications/rules/local-mined.ts) fires on
     session_start when the user is not logged in and the local-mined
     manifest has entries. Surfaces the count via `systemMessage` (terminal-
     visible, renders as `SessionStart:startup says: …`) plus the same
     text in `additionalContext` for model awareness.
   - `NotificationContext` gains an optional `localSkillsCount` so rules
     stay IO-free. The hook entry point reads the count and threads it
     through `drainSessionStart`.
   - Dedup keyed on the integer count: re-fires when mine-local adds new
     skills, stays silent between runs at the same count.

3. Codex hook — JSON output with systemMessage (and slim additionalContext)
   - `src/hooks/codex/session-start.ts` migrated from plain-text stdout
     (old contract pre-0.118.0) to the full JSON schema documented by
     codex-rs/hooks/src/schema.rs @ 0.130.0. Top-level `systemMessage`
     carries the user-visible `💡 N skills mined…` warning; nested
     `hookSpecificOutput.additionalContext` carries a one-line login
     status string for the model.
   - The verbose DEEPLAKE MEMORY tier doc + the entire hivemind CLI command
     list that previously lived in `additionalContext` is GONE. Codex's
     harness renders all `additionalContext` as user-visible `hook context:
     …` history entries (see codex-rs `common::append_additional_context`),
     so dumping ~4 KB of scaffolding every session start clobbered the TUI.
     `suppressOutput` is parsed but ignored for SessionStart, so there's no
     way to hide it once emitted.
   - The dropped content moved into `claude-code/skills/hivemind-memory/`
     and `codex/skills/deeplake-memory/` SKILL.md files (auto-loaded by each
     agent's skill loader). New sections cover `hivemind skillify *` and
     `hivemind embeddings *`. The skill is consulted on demand and never
     spams the user.

Notes:
- Cursor's hook still emits the verbose context inline; it suffers the same
  user-visible-context problem as Codex but in a less prominent UI. Slimming
  it is deferred to a follow-up.
- Hermes upstream still discards `on_session_start` return values
  (run_agent.py:9777-9786) — neither user nor model receives anything from
  the hook regardless of what we emit. Out of scope.
- Pi has no user-visible session-start channel in its extension API.
  The skill content above is auto-loaded for CC and Codex; pi/openclaw
  surface skillify via their own inline injection in the source tree.

AGENT_CHANNELS.md corrected: the original research preceded Codex 0.130.0's
systemMessage support and was wrong about Codex having no user-visible channel.
The new findings are documented from the codex-rs source (schema.rs,
session_start.rs, hook_cell.rs) and validated empirically with a live
`CODEX_HOME=...` sandbox run.

Tests:
- `tests/codex/codex-integration.test.ts`: JSON-shape assertions, slim-
  context guards, systemMessage conditional emission.
- `tests/claude-code/codex-session-start-hook.test.ts`: refit for JSON
  output contract.
- `tests/claude-code/skillify-session-start-injection.test.ts`: codex
  bundle moved out of the "must inline SKILLS" matrix into a dedicated
  slim-invariant describe block; the codex skill is added to the
  non-bundle surfaces matrix that asserts skillify discoverability.
- 2,288 tests passing.
# Conflicts:
#	bundle/cli.js
#	claude-code/bundle/session-notifications.js
#	claude-code/bundle/session-start.js
#	codex/bundle/session-start.js
#	cursor/bundle/session-start.js
#	hermes/bundle/session-start.js
#	pi/extension-source/hivemind.ts
#	src/hooks/codex/session-start.ts
#	src/hooks/cursor/session-start.ts
#	src/hooks/hermes/session-start.ts
#	src/hooks/session-notifications.ts
#	src/hooks/session-start.ts
#	src/notifications/index.ts
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 16, 2026

Coverage Report

Scope: files changed in this PR. Enforced threshold: 90% per metric (per file via vitest.config.ts).

Status Category Percentage Covered / Total
🟢 Lines 96.34% (🎯 90%) 815 / 846
🟢 Statements 95.85% (🎯 90%) 947 / 988
🟢 Functions 96.26% (🎯 90%) 103 / 107
🟢 Branches 90.26% (🎯 90%) 519 / 575
File Coverage — 14 files changed
File Stmts Branches Functions Lines
src/cli/skillify-spec.ts 🟢 100.0% 🟢 100.0% 🟢 100.0% 🟢 100.0%
src/commands/mine-local.ts 🟢 96.4% 🟢 93.2% 🟢 94.4% 🟢 98.3%
src/commands/skillify.ts 🟢 90.3% 🔴 81.1% 🟢 95.7% 🔴 88.6%
src/hooks/codex/session-start.ts 🟢 100.0% 🟢 96.2% 🟢 100.0% 🟢 100.0%
src/hooks/cursor/session-start.ts 🟢 100.0% 🟢 95.0% 🟢 100.0% 🟢 100.0%
src/hooks/hermes/session-start.ts 🟢 100.0% 🟢 93.8% 🟢 100.0% 🟢 100.0%
src/hooks/session-notifications.ts 🟢 94.4% 🟢 100.0% 🔴 75.0% 🟢 100.0%
src/hooks/session-start.ts 🟢 100.0% 🟢 90.5% 🟢 100.0% 🟢 100.0%
src/notifications/index.ts 🟢 100.0% 🟢 92.8% 🟢 100.0% 🟢 100.0%
src/notifications/rules/local-mined.ts 🟢 100.0% 🟢 100.0% 🟢 100.0% 🟢 100.0%
src/notifications/types.ts 🟢 100.0% 🟢 100.0% 🟢 100.0% 🟢 100.0%
src/skillify/local-manifest.ts 🟢 100.0% 🟢 100.0% 🟢 100.0% 🟢 100.0%
src/skillify/local-source.ts 🟢 96.3% 🟢 96.5% 🟢 100.0% 🟢 98.8%
src/skillify/spawn-mine-local-worker.ts 🟢 98.4% 🟢 100.0% 🟢 100.0% 🟢 98.1%

Generated for commit a5e155d.

efenocchi added 5 commits May 16, 2026 00:54
…ocalMinedRule

CI's per-file coverage threshold flagged `src/hooks/session-start.ts` branches
at 83.33% (need 90%). The uncovered ternaries were the `localMined > 0` gate
and the singular/plural noun selection inside the not-logged-in injection.

Added:
- `session-start-hook.test.ts`: 4 cases that mock `countLocalManifestEntries`
  to drive 0 / 1 / N>1 / logged-in-with-manifest branches. Asserts the
  unique-phrase substring "live in ~/.claude/skills" so the static skillify
  command list (which legitimately mentions "mine-local") doesn't false-match.
- `notifications.test.ts`: localMinedRule unit tests covering all five
  guard branches (creds present, count undefined/null/0, plural, singular)
  plus dedupKey change-of-count semantics.

Result: session-start.ts branches 90.47% (was 83.33%), local-mined.ts at 100%
(was 71.42%). 2,474 tests passing.
… metrics

PR-aggregate (PR-touched src/*.ts files) before → after:
  statements:  95.70% → 96.21%
  branches:    88.54% → 90.12%
  functions:   95.28% → 96.23%
  lines:       96.30% → 96.77%

Coverage additions:

New test files:
- tests/claude-code/spawn-mine-local-worker.test.ts: 16 cases mocking
  fs + child_process to exercise every guard branch in maybeAutoMineLocal
  (manifest-exists, lock-fresh, stale-lock, statSync failure, no claude
  sessions, no hivemind bin, bundled-vs-which dispatch, lock-acquire
  failure, spawn failure, happy path with spawn options + child.unref).
- tests/claude-code/mine-local-orchestrator.test.ts: 25 cases mocking
  every external module (local-source, extractors, gate-runner,
  skill-writer, agent-roots, pull, local-manifest, spawn) and walking
  runMineLocal through manifest-sentinel, --force, no-agents, in-flight
  filter, --dry-run, happy-path with manifest write + fan-out, overlap
  skip, write-already-exists, write-other-error, gate-errored,
  unparseable verdict, no-pairs, --n parsing, runGateViaStdin error
  branches (spawn 'error', stdin 'error', missing bin short-circuit),
  truncate budget branch, renderPairsBlock budget-exceeded branch.

Updated tests:
- local-source.test.ts: vi.doMock("node:os") to test detectInstalledAgents
  + detectHostAgent + encodeCwdClaudeCode; real-fs scaffolding for
  listLocalSessions (subdir walk, non-jsonl skip, multi-install aggregation).
- mine-local-helpers.test.ts: edge cases for parseMultiVerdict (non-object
  parse, undefined reason, null skill entries).
- notifications-coverage.test.ts: tryClaim-returns-false-everywhere branch
  (queue still drained without emit) + readState-throws branch (drainSessionStart
  swallows error without rethrow).
- skillify-cli.test.ts: mine-local subcommand dispatch + reject→exit(1).
- codex/cursor/hermes session-start hooks: vi.mock countLocalManifestEntries
  with importOriginal so the surrounding spawn-mine-local-worker exports
  (LOCAL_MANIFEST_PATH, readLocalManifest) stay intact; assertions for the
  0/1/N branches of the localMinedNote / systemMessage ternary.

Threshold additions (vitest.config.ts):
- src/skillify/local-source.ts             — 90/90/90/90
- src/skillify/local-manifest.ts           — 90/90/90/90
- src/skillify/spawn-mine-local-worker.ts  — 90/90/90/90
- src/commands/mine-local.ts               — 90/90/90/90
- src/notifications/rules/local-mined.ts   — 90/90/90/90
… reject

The skillify dispatcher calls `process.exit(1)` from inside a `.catch`
without a surrounding try/catch. When the test's exit spy threw, the
throw surfaced as an unhandled rejection and crashed the vitest run.
Swap to a non-throwing implementation for this specific test and track
the exit code via a captured array.
…therwise; persist manifest on zero candidates; align --n help to DEFAULT_N

Addresses three CodeRabbit findings on #129:

1. Gate-agent selection — runGateViaStdin v1 only supports claude_code,
   but gateAgentFor() previously returned the host agent verbatim. On a
   Codex host (or any non-claude_code-only machine), every parallel gate
   call rejected with "stdin gate runner only supports claude_code",
   producing a silent no-op (0 skills mined, exit 0).

   Fix: gateAgentFor now takes the install list and PREFERS claude_code
   whenever it's present, even if the host is something else. Falls back
   to host/first-install only when claude_code isn't installed, and the
   caller fails fast with a clear "install Claude Code or run a Claude
   Code session once, then re-run" message before any session selection
   or gate I/O. Same surface in the bundled CLI.

2. One-shot manifest sentinel — the 0-candidates branch returned without
   writing the manifest. Since maybeAutoMineLocal (the SessionStart
   auto-spawn) gates on manifest existence (not entry count), the worker
   would re-fire on every new session forever when mining found nothing
   to keep.

   Fix: write an empty-entries manifest on the 0-candidates path,
   preserving created_at when a manifest already exists.

3. --n help default — skillify.ts usage line said `(default: 3)` but
   mine-local.ts uses DEFAULT_N = 8. Fixed the help text to match the
   actual runtime default; added a regression test that asserts the
   help string carries `default: 8` (and not `default: 3`).

Test additions (mine-local-orchestrator.test.ts):
  - codex-only install → exits 1 with the gate-agent guard message
  - host = codex + claude_code installed → mining uses claude_code
  - 0 candidates → manifest IS persisted (was: not persisted)
  - 0 candidates + pre-existing manifest → created_at preserved
  - writeNewSkill throws a non-Error (no .message) → `failed` branch
  - parseMultiVerdict with no `reason` field → "no reason given" fallback

PR-aggregate coverage (PR-touched src/*.ts files):
  statements 96.21% → 96.26%
  branches   90.12% → 90.43%
  functions  96.23% → 96.26%
  lines      96.77% → 96.81%

All 2551 tests pass.
The PR coverage bot computes aggregate metrics against the merged-with-main
commit, which has slightly different branch totals than the source branch
alone — local 90.4% can land on CI as 89.6%. Buffer the aggregate by
covering the small list of branches still flagged red:

- session-notifications hook: `e?.message ?? String(e)` nullish fallback
  (catch handler with a non-Error throw — primitive string rejection).
- codex session-start: `auto.triggered` truthy ternary branch (auto-mine
  actually fires, not skipped).
- cursor session-start: same `auto.triggered` truthy branch + empty
  workspace_roots array fallback in resolveCwd.
- hermes session-start: empty-string cwd → `cwd.split('/').pop() ?? 'unknown'`
  nullish fallback in createPlaceholder's projectName.

Local PR-aggregate: branches 90.43% → 91.13% (524/575).
Comment thread src/cli/skillify-spec.ts
desc: string;
}

export const SKILLIFY_COMMANDS: SkillifyCommand[] = [
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@coderabbitai can't SKILLIFY_COMMANDS also be imported and used in SKILL.md s?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if any SKILL.md template or skill-writer has hardcoded skillify command text
rg -n "mine-local|skillify|SKILL\.md" src/skillify/ --type ts -A2 -B2

echo "---"
# Look for any template strings in skill writer that might embed command help
rg -n "hivemind skillify" src/ --type ts -C2

Length of output: 44854


@kaghni Great observation — there are actually two angles here:

SKILL.md files themselves are static Markdown on disk, so they can't import TypeScript directly. However, the code that writes them (TypeScript) absolutely could. A natural concrete use case would be a bundled/shipped "hivemind-skillify" SKILL.md — a static skill template that teaches agents the command suite — whose body is generated once at build/write time from renderSkillifyCommands() instead of being hardcoded. That doesn't exist yet, but it's a clean fit.

More immediately, the search reveals two existing hardcoded command lists that SKILLIFY_COMMANDS doesn't yet consolidate:

  1. src/cli/index.ts lines 67–88 — the top-level hivemind --help output still has its own hand-maintained skillify command block.
  2. src/commands/skillify.ts usage() function lines 169–196 — the per-subcommand usage text is also hardcoded independently.

These are the same duplication problem skillify-spec.ts was created to solve, just in the CLI help paths rather than the SessionStart injection paths. Driving both from SKILLIFY_COMMANDS (or a closely related SKILLIFY_COMMANDS_DETAILED variant that includes sub-option lines) would close those gaps. Worth tracking as a follow-up?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@efenocchi can you check these 2 files it mentioned?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@coderabbitai briefly explain what this file does

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@kaghni, here's a brief breakdown of what src/commands/mine-local.ts implements:


hivemind skillify mine-local — One-Shot Local Skill Miner

This file powers the mine-local subcommand, which extracts reusable skills from a user's local agent session transcripts (Claude Code, Codex, etc.) — no Deeplake sign-in required. It's designed to give new users an instant "this is useful" experience by mining skills they've already generated through past coding sessions.

Pipeline at a glance:

flowchart LR
    A[Detect installed agents] --> B[ε-greedy pick N sessions]
    B --> C[Convert JSONL → user/assistant pairs]
    C --> D[Run LLM gate per session in parallel]
    D --> E[Parse multi-skill JSON verdict]
    E --> F{Duplicate check\nJaccard overlap}
    F -- unique --> G[Write SKILL.md]
    F -- duplicate --> H[Skip]
    G --> I[Fan-out symlinks to other agent roots]
    G --> J[Record in local manifest]
Loading

Key responsibilities:

Area What it does
Session selection Uses an ε-greedy algorithm (biased toward newest cwd sessions) to pick up to N=8 sessions
Gate execution Spawns the Claude Code CLI and pipes the prompt via stdin (avoids Linux MAX_ARG_STRLEN limits on large prompts) with a 240s timeout
Verdict parsing parseMultiVerdict() extracts a { reason, skills[] } JSON blob from flexible LLM output (fenced code blocks, raw JSON, or JSON-in-prose)
Deduplication findOverlap() uses stopword-filtered Jaccard similarity (threshold 0.4) on skill descriptions to skip duplicates against both installed skills and newly written ones in the same run
Skill writing Calls writeNewSkill() and fans out idempotent symlinks to other agent skill roots (~/.agents/skills/, ~/.hermes/skills/, etc.)
Manifest Writes ~/.claude/hivemind/local-mined.json as both a one-shot sentinel (blocks re-runs unless --force) and a provenance index for future push-local uploads

CLI flags: --n <count|all>, --force, --dry-run

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.

2 participants