Skip to content

Hotfix: gobby-hooks 0.1.1 (Windows extern block requires unsafe in edition 2024)#2

Closed
joshwilhelmi wants to merge 16 commits intomainfrom
dev
Closed

Hotfix: gobby-hooks 0.1.1 (Windows extern block requires unsafe in edition 2024)#2
joshwilhelmi wants to merge 16 commits intomainfrom
dev

Conversation

@joshwilhelmi
Copy link
Copy Markdown
Contributor

@joshwilhelmi joshwilhelmi commented Apr 17, 2026

Summary

Hotfix for gobby-hooks after the 0.1.0 release. Bumps to 0.1.1.

gobby-hooks 0.1.0 published to crates.io fine, but the
x86_64-pc-windows-msvc build target in the Release workflow failed
because edition 2024 requires extern "system" { ... } to be
unsafe extern "system" { ... }. The #[cfg(windows)] path in
crates/ghook/src/detach.rs was the only such block in the
workspace, and Mac/Linux dev never exercised it.

What's in the release

  • Fixedcrates/ghook/src/detach.rs:36: unsafe extern "system" { fn FreeConsole() -> i32; }. One-line fix.
  • Bumpedcrates/ghook/Cargo.toml to 0.1.1.
  • CHANGELOG — new [0.1.1] — gobby-hooks section above the existing [0.1.0] entry.

Test plan

  • cargo build -p gobby-hooks clean
  • cargo test -p gobby-hooks — 27 passing
  • cargo clippy -p gobby-hooks --all-targets -- -D warnings clean
  • After merge + gobby-hooks-v0.1.1 tag: Release ghook workflow succeeds across all 5 build targets and publishes a GitHub Release with binaries for all platforms

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Introduced gobby-hooks, a new hook dispatcher for sandbox-tolerant AI CLI integration with enqueue-first delivery and automatic retry capability.
    • Created gobby-core shared library providing standardized project discovery and daemon configuration across all Gobby tools.
  • Bug Fixes

    • Hardened FTS5 search escaping in gcode to correctly handle special characters.
    • Suppressed low-savings marker in gsqz when it would increase output size.
  • Improvements

    • Enhanced CI/CD with crate-specific release workflows and automated testing gates.
    • Refactored project discovery logic for code reuse across tools.

joshwilhelmi and others added 16 commits April 10, 2026 18:06
…grow output

When a named pipeline only trimmed a few bytes, gsqz still prepended the
~20-byte [gsqz:low-savings] marker, producing net-negative "savings" on
small outputs (e.g. short git diffs showing -1%). Now the marker is only
applied when the marked output is strictly smaller than the original;
otherwise the original output is returned unchanged with strategy_name
"{pipeline}/no-op". Existing low-savings test updated to a scenario where
the marker still fits; new test covers the no-op suppression path.
Canonical plan for making Gobby hooks work across Claude/Codex/Gemini/
QwenCode sandboxes. Introduces a new ghook crate as the fourth member
of this workspace (alongside gcode, gsqz, gloc), plus runtime-detection
on the Python daemon side so the two repos can ship independently.

Companion task: gobby #11843 (bootstrap) and gobby #11842 (cleanup
follow-up to remove the legacy hook_dispatcher.py path once ghook is
universal.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)
PR 1 of the sandbox-tolerant-hooks Rust migration (R2-01 + R2-04 + R2-05).
Scaffolds the shared gobby-core lib and ports find_project_root and
read_project_id as public API. gcode keeps its local copy until PR 4
(R2-08) to keep this diff behavior-free. See
docs/plans/sandbox-tolerant-hooks-rust.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ports hook_dispatcher.py:145-175 port-resolution into `bootstrap` + a
new `daemon_url` module. Reads `daemon_port` and `bind_host` from
`~/.gobby/bootstrap.yaml`; falls back to 60887 and 127.0.0.1 on missing
or malformed files. `daemon_url` normalizes wildcard listen addresses
(`0.0.0.0`, `::`, `::0`) to `127.0.0.1` for dialing — the dispatcher and
gcode both silently broke when users set `bind_host: 0.0.0.0`.

13 unit tests cover default fallback, malformed YAML, custom port/host,
and wildcard normalization. No consumers yet — ghook (PR 3) is first.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…atch

Port hook_dispatcher.py to a native Rust binary with enqueue-first
semantics: every invocation writes an envelope to
~/.gobby/hooks/inbox/<p>-<ts13>-<uuid>.json (fsync + rename) before the
daemon POST, so sandbox FS-read denials and transient network failures
never drop a hook — the drain worker replays from disk.

CLI: --gobby-owned, --diagnose, --version. Exit 0 on success or
non-critical failure (enqueued); 2 on critical failure (enqueued,
signals host CLI). Malformed stdin goes directly to inbox/quarantine/
with a .meta.json sidecar (reason, json_error, stdin_bytes_b64) —
drain never replays quarantined envelopes.

Headers (X-Gobby-Project-Id, X-Gobby-Session-Id) are omitted entirely
when unknown — never empty strings. Walk-up for project context runs
before detach on Unix (setsid). Windows detach uses FreeConsole(); the
spawn-time flags in the plan cannot be self-applied to a running
process, and the enqueue-first file is the source of truth regardless.

Terminal-context enrichment gated by per-CLI hook set; TMUX_PANE only
emits when TMUX is also set to avoid parent/child pane confusion.

Schemas frozen at v1 (inbox-envelope, diagnose-output), validated in
unit tests. Release workflow mirrors release-gcode with tag prefix
gobby-hook-v*; publishes to crates.io as gobby-hook.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lpers

Remove duplicate find_project_root and read_project_id from gcode, now importing
them from gobby_core::project. Updates call sites in config.rs and commands/index.rs.
Deletes redundant tests (now tested in gobby-core). Adds gobby-core as a path
dependency.

Validation:
- crates/gcode/Cargo.toml adds gobby-core path dependency
- find_project_root and read_project_id removed from crates/gcode/src/project.rs
- Call sites updated to import from gobby_core::project
- Tests removed (behavior tested in gobby-core)
- cargo test --workspace: PASS (148 tests)
- cargo clippy --workspace --all-targets -- -D warnings: PASS
- cargo fmt --all --check: PASS

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Mirrors updates made to the Python-side sandbox-tolerant-hooks-rev1
plan after the gobby-cli Phase 2 review:

- Windows detach spec corrected: `FreeConsole()` is the post-spawn
  analog; `DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP` are
  CreateProcess parent-side flags and cannot be self-applied from the
  already-spawned child. Updated in both PR 3 description and the
  contract-resolution section.
- Crate & distribution: documents the gobby-core → ghook publish-order
  constraint, with the `version` + `path` dep declaration pattern that
  lets workspace builds and crates.io both resolve.
- PR 3 CI bullet: explicit note that `release-gobby-core.yml` must
  land in the repo first and the first `ghook` release is gated on
  the matching `gobby-core` version being live on the registry.
- Empty-stdin normalization: ghook treats empty as `input_data = {}`;
  dispatcher exits non-zero. Documented transition-window divergence;
  exit code still governed by `--critical`, no silent drop.
- POST body shape: daemon endpoint accepts both legacy bare and
  schema-v1 envelope. Python plan's §2.8 adds a handler test to pin
  the tolerance.
…es on crates.io success

gobby-hook depends on gobby-core as a path dep with a version; the release
flow requires gobby-core to exist on crates.io first, so add a library-only
release workflow keyed on gobby-core-v* tags. Also tighten release-gcode and
release-ghook so the GitHub Release job waits on the crates.io step —
prevents the footgun where binaries ship with a version that failed upstream.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s/gobby-core → crates/gcore

Two pre-release naming fixes, landed together while nothing is on crates.io
yet so the rename is free:

(A) Package gobby-hook → gobby-hooks. Binary (ghook), crate dir (crates/ghook),
    and schema IDs unchanged. Convention: plural package name for domain
    families (gobby-hooks, future gobby-tasks), singular binary for the tool
    (ghook, gtask). Release tag prefix becomes gobby-hooks-v*.

(B) Directory crates/gobby-core → crates/gcore to match the g-prefix short-name
    pattern (gcode, gsqz, gloc, ghook). Package name stays gobby-core — only
    the filesystem layout changed. Workflow renamed release-gobby-core.yml →
    release-gcore.yml for consistency with the other release-<short>.yml files.

Verified: cargo fmt, cargo clippy --workspace --all-targets -- -D warnings,
cargo test --workspace (267 passing), YAML syntax on all workflow files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n fts.rs LIKE escape

Extract empty_response_for_unresolved helper in commands/graph.rs and use it
from callers/usages/blast_radius. resolve_symbol_name in search/fts.rs no
longer includes the resolved name in its suggestions vec — callers get only
alternatives. LIKE fallback now escapes %/_/\ via new escape_like helper and
uses ESCAPE '\' so symbol names containing underscores (snake_case) match
literally. glob_to_like_prefix refactored to reuse escape_like; behavior
unchanged for existing callers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…040)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…0.1.0, ghook 0.1.0

Bump crate versions, add new ghook user/dev guides and gcore dev
guide, note gobby-core dependency in gcode dev guide and low-savings
marker suppression in gsqz user guide, add four versioned CHANGELOG
sections, and surface ghook + gcore in the top-level README.

Adds version="0.1" to gcode's path dep on gobby-core so the manifest
is valid for crates.io upload. Staged release order: tag gcore-v0.1.0
first (so gobby-core lands on the registry), then tag gcode-v0.6.0,
gsqz-v0.4.1, ghook-v0.1.0 together.

Verified: cargo build/test/clippy/fmt all clean across the workspace
(267 tests passing). cargo package -p gobby-core succeeded;
gobby-code packaging waits on gobby-core being on the registry
(expected -- release ordering handles this).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…o-op strategy

Follow-up to #111. The inner [gsqz:low-savings] marker is suppressed
when adding it would grow output, but the outer
"[Output compressed by gsqz -- STRATEGY, X% reduction]" header was
still being prepended for the resulting */no-op strategy. That's
noise -- no compression actually happened, so the header is just
spurious.

Adds CompressionResult::is_passthrough() classifying passthrough,
excluded, and */no-op together. main.rs now uses it for both the
outer-header decision and the daemon savings report so the two stay
in sync. Adds a unit test for the classification and asserts the
existing low-savings-suppressed test result also reports as
passthrough.

Rolls into the unreleased 0.4.1 -- no version bump. Tests: 149 in
gsqz (+1 new), 268 across the workspace, all passing. Verified
end-to-end: the compound-git command that previously emitted
"git-mutation/low-savings, -1% reduction" now emits clean original
output (strategy=git-mutation/no-op, savings=0.0%, no header).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ry_sort_by

Two pre-existing call sites in crates/gsqz/src/primitives/group.rs
trigger Rust 1.95.0's new clippy::unnecessary_sort_by lint. Local
toolchain is 1.94 so they slipped through; CI runs 1.95 and rejects
them. Mechanical replacement of

    sort_by(|a, b| b.1.len().cmp(&a.1.len()))

with

    sort_by_key(|b| std::cmp::Reverse(b.1.len()))

at lines 373 and 414. No behavior change. Rolls into the unreleased
gsqz 0.4.1 -- no version bump, no CHANGELOG entry (this is a CI
correctness fix, not a user-facing change).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ble_match

crates/gcode/src/commands/init.rs:34 trips clippy::collapsible_match
on a nested `match Err(e) => { if !quiet { ... } }`. Convert to a
match guard `Err(e) if !quiet => { ... }`; the `_ => {}` arm catches
the quiet=true case as before, so behavior is preserved.

Updated local toolchain to 1.95 (was 1.94) so all 1.95 lints fire
locally going forward. Verified all five CI clippy commands pass on
1.95: gobby-core, gobby-hooks, gobby-squeeze, gobby-local,
gobby-code (no embeddings). Rolls into unreleased gcode 0.6.0 -- no
version bump.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e 0.1.1

ghook 0.1.0 published to crates.io but the Windows build target
failed: edition 2024 requires `extern "system" { ... }` to be
`unsafe extern "system" { ... }`. Local Mac dev never exercised the
#[cfg(windows)] branch in detach.rs so it slipped through.

Fix is one line in crates/ghook/src/detach.rs:36 plus a Cargo.toml
bump to 0.1.1 and a CHANGELOG entry. Linux/Mac use the
#[cfg(unix)] setsid(2) branch and were never affected -- the
crates.io 0.1.0 install works on those platforms but not Windows.

Verified: cargo build/test/clippy clean (27 ghook tests passing).
Once gobby-hooks-v0.1.1 is tagged, the Release ghook workflow
should succeed across all 5 build targets and create the GitHub
Release that 0.1.0 missed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 17, 2026 08:51
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

A new shared gobby-core library crate extracts project-root discovery, bootstrap configuration, and daemon-URL helpers. A new gobby-hooks binary implements sandbox-tolerant hook dispatch by atomically enqueueing envelopes before posting to the daemon. Existing gcode is refactored to use shared helpers. CI/release workflows added for all crates.

Changes

Cohort / File(s) Summary
Workflow CI/CD
.github/workflows/ci.yml, .github/workflows/release-gcore.yml, .github/workflows/release-ghook.yml, .github/workflows/release-gcode.yml
Added clippy/test steps for new crates in CI. New release workflows for gobby-core and gobby-hooks with tag-based triggers, testing, and crates.io publishing. gobby-hooks includes multi-platform release builds (macOS/Linux/Windows) with artifact packaging. Release-gcode updated to depend on publish job.
New gobby-core Crate
crates/gcore/Cargo.toml, crates/gcore/src/lib.rs, crates/gcore/src/project.rs, crates/gcore/src/bootstrap.rs, crates/gcore/src/daemon_url.rs
New shared library exposing project-root discovery, project-ID reading from .gobby/project.json, bootstrap endpoint parsing from ~/.gobby/bootstrap.yaml with default fallbacks, and HTTP daemon-URL composition with wildcard-host normalization. Includes unit tests for all modules.
New gobby-hooks Binary
crates/ghook/Cargo.toml, crates/ghook/README.md, crates/ghook/src/main.rs, crates/ghook/src/cli_config.rs, crates/ghook/src/detach.rs, crates/ghook/src/diagnose.rs, crates/ghook/src/envelope.rs, crates/ghook/src/terminal_context.rs, crates/ghook/src/transport.rs, crates/ghook/schemas/inbox-envelope.v1.schema.json, crates/ghook/schemas/diagnose-output.v1.schema.json
New hook dispatcher binary supporting stdin→envelope→queue→POST flow. Includes CLI argument parsing (gobby-owned/diagnose/version modes), envelope creation with schema v1, atomic inbox writing via tmp+fsync+rename, optional terminal-context enrichment, POST delivery with cleanup on success, quarantine handling for malformed input, cross-platform detach (setsid/FreeConsole), and JSON schema validation.
gcode Refactoring
crates/gcode/Cargo.toml, crates/gcode/src/project.rs, crates/gcode/src/config.rs, crates/gcode/src/commands/index.rs, crates/gcode/src/commands/init.rs, crates/gcode/src/commands/graph.rs, crates/gcode/src/search/fts.rs
Added dependency on gobby-core. Removed inline find_project_root and read_project_id implementations, now delegating to shared library. FTS LIKE pattern escaping hardened with dedicated escape_like function for %, _, \ metacharacters plus ESCAPE '\\' SQL clause. Graph command refactored to use empty_response_for_unresolved helper. Ambiguity logging simplified. Version bumped to 0.6.0.
gsqz Improvements
crates/gsqz/Cargo.toml, crates/gsqz/src/compressor.rs, crates/gsqz/src/main.rs, crates/gsqz/src/primitives/group.rs
Added is_passthrough() method to classify strategies where output should be unmodified. Low-savings marker now suppressed when it would increase output size; marked cases return with /no-op strategy suffix. Main logic updated to use boolean predicate instead of string checks. Sort implementation simplified. Version bumped to 0.4.1.
Workspace/Manifest Files
Cargo.toml, crates/gsqz/Cargo.toml
Workspace members expanded to include crates/gcore and crates/ghook. Release profile extended with size optimization (opt-level = "z") for gobby-hooks.
Documentation & Planning
README.md, CHANGELOG.md, docs/guides/gcore-development-guide.md, docs/guides/ghook-development-guide.md, docs/guides/ghook-user-guide.md, docs/guides/gcode-development-guide.md, docs/guides/gsqz-user-guide.md, docs/plans/sandbox-tolerant-hooks-rust.md, docs/plans/sandbox-tolerant-hooks.md
Updated README to describe four Gobby CLI tools plus shared library. Comprehensive changelog documenting all version bumps and feature additions. New developer guides for gcore (module map, versioning, testing), ghook (control flow, detach, transport, terminal context, testing), and updated gcode guide. New user guides for ghook (CLI modes, inbox mechanics, troubleshooting) and gsqz (low-savings marker suppression). Planning documents detailing sandbox-tolerant hooks design and Rust-side implementation sequence.

Sequence Diagram

sequenceDiagram
    actor Host_CLI
    participant ghook_CLI as ghook (stdin mode)
    participant Inbox as ~/.gobby/hooks/inbox/
    participant Daemon as Gobby Daemon
    
    Host_CLI->>ghook_CLI: stdin (JSON hook payload)
    ghook_CLI->>ghook_CLI: Parse & validate JSON
    ghook_CLI->>ghook_CLI: Discover project root
    ghook_CLI->>ghook_CLI: Build Envelope struct
    ghook_CLI->>Inbox: Atomic write (tmp + fsync + rename)
    ghook_CLI->>ghook_CLI: Optional: detach from terminal
    ghook_CLI->>Daemon: POST /api/hooks/execute
    alt Success (2xx)
        Daemon-->>ghook_CLI: 2xx response
        ghook_CLI->>Inbox: Delete envelope file
        ghook_CLI-->>Host_CLI: exit 0 (or 2 if critical)
    else Failure (non-2xx/timeout)
        Daemon-->>ghook_CLI: error/timeout
        ghook_CLI->>Inbox: Leave envelope for replay
        ghook_CLI-->>Host_CLI: exit 0 (or 2 if critical)
    end
    
    Note over Inbox,Daemon: Daemon drains inbox files<br/>on next startup/tick
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 New crates hop into place,
Hooks now spool with atomic grace—
No sandbox can hold them back,
Shared libraries fill the lack,
From daemon to CLI, we're on track! 🎯✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically identifies the main change: a Windows build fix for gobby-hooks 0.1.1 related to Rust edition 2024 extern block safety requirements.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch dev

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

@joshwilhelmi
Copy link
Copy Markdown
Contributor Author

Reopening as PR from hotfix/ghook-0.1.1 \u2014 dev branch was based on the pre-rebase chain after PR #1 merged with rebase, so this PR's diff against main was conflicting. The fix is unchanged; new PR coming.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR expands the workspace to include a new sandbox-tolerant hook dispatcher (ghook / gobby-hooks) and a shared primitives crate (gobby-core), updates gcode and gsqz behavior/docs accordingly, and adjusts CI/release automation (in addition to the Windows edition-2024 unsafe extern hotfix noted in the PR description).

Changes:

  • Added gobby-core (crates/gcore) for shared project discovery + bootstrap/daemon URL helpers, and migrated gcode call sites to use it.
  • Added ghook (crates/ghook, package gobby-hooks) including spool-first transport, schemas, docs, and a release workflow; includes the Windows unsafe extern "system" fix.
  • Updated gsqz low-savings behavior and reporting, hardened gcode LIKE escaping, and bumped crate versions + changelog/CI.

Reviewed changes

Copilot reviewed 41 out of 42 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
docs/plans/sandbox-tolerant-hooks.md Adds detailed multi-CLI sandbox-tolerant hooks plan/spec.
docs/plans/sandbox-tolerant-hooks-rust.md Rust-side handoff plan describing gobby-core + ghook approach.
docs/guides/gsqz-user-guide.md Documents /no-op and marker suppression behavior in gsqz.
docs/guides/ghook-user-guide.md New user-facing guide for installing/wiring/diagnosing ghook.
docs/guides/ghook-development-guide.md New developer guide describing ghook architecture, schema, and internals.
docs/guides/gcore-development-guide.md New developer guide defining gobby-core API and design constraints.
docs/guides/gcode-development-guide.md Updates gcode docs to reference gobby-core extraction.
crates/gsqz/src/primitives/group.rs Refactors grouping sort to use sort_by_key(Reverse(..)).
crates/gsqz/src/main.rs Uses CompressionResult::is_passthrough() to unify header/reporting rules.
crates/gsqz/src/compressor.rs Implements low-savings marker suppression and /no-op strategy + tests.
crates/gsqz/Cargo.toml Bumps gobby-squeeze version to 0.4.1.
crates/ghook/src/transport.rs Implements spool-first enqueue + POST + quarantine + atomic write helper.
crates/ghook/src/terminal_context.rs Captures/injects terminal context for selected hooks.
crates/ghook/src/main.rs Adds ghook CLI modes (--gobby-owned/--diagnose/--version) and orchestration.
crates/ghook/src/envelope.rs Defines v1 inbox envelope schema + schema-validation tests.
crates/ghook/src/diagnose.rs Implements --diagnose output + schema-validation tests.
crates/ghook/src/detach.rs Implements detach (setsid/FreeConsole); includes Windows 2024 unsafe extern fix.
crates/ghook/src/cli_config.rs Defines per-CLI critical/terminal-context hook registries.
crates/ghook/schemas/inbox-envelope.v1.schema.json Adds JSON Schema for inbox envelope v1.
crates/ghook/schemas/diagnose-output.v1.schema.json Adds JSON Schema for diagnose output v1.
crates/ghook/README.md Adds crate README describing CLI surface and schemas.
crates/ghook/Cargo.toml Defines gobby-hooks crate metadata/deps; bumps to 0.1.1.
crates/gcore/src/project.rs Adds shared project-root walk-up + project-id read helpers.
crates/gcore/src/lib.rs Exposes bootstrap, daemon_url, and project modules.
crates/gcore/src/daemon_url.rs Adds dial-url normalization for wildcard listen hosts + tests.
crates/gcore/src/bootstrap.rs Adds bootstrap.yaml parsing with infallible defaults + tests.
crates/gcore/Cargo.toml Defines gobby-core crate metadata/deps.
crates/gcode/src/search/fts.rs Adds LIKE escaping helper and adjusts symbol resolution fallback behavior.
crates/gcode/src/project.rs Removes duplicated project helpers now provided by gobby-core.
crates/gcode/src/config.rs Migrates project root/id resolution to gobby-core::project.
crates/gcode/src/commands/init.rs Simplifies warning emission match arm.
crates/gcode/src/commands/index.rs Migrates project root/id resolution to gobby-core::project.
crates/gcode/src/commands/graph.rs Deduplicates unresolved-symbol empty response path.
crates/gcode/Cargo.toml Adds gobby-core dependency and bumps gobby-code version to 0.6.0.
README.md Updates workspace overview and install instructions to include ghook/gobby-core.
Cargo.toml Adds gcore/ghook to workspace members and release opt-level override.
Cargo.lock Updates dependency graph for new crates/versions (but currently inconsistent for gobby-hooks).
CHANGELOG.md Adds entries for gcode 0.6.0, gsqz 0.4.1, gobby-hooks 0.1.1/0.1.0, gobby-core 0.1.0.
.github/workflows/release-ghook.yml Adds release workflow for gobby-hooks (ghook) binaries + crates.io publish.
.github/workflows/release-gcore.yml Adds crates.io publish workflow for gobby-core.
.github/workflows/release-gcode.yml Gates GitHub Release on crates.io publish (needs: [build, publish]).
.github/workflows/ci.yml Adds dedicated clippy/test steps for gobby-core and gobby-hooks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

.and_then(|v| v.as_str())
.map(String::from)
.context("'id' field not found in .gobby/project.json")
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

gobby_core::project::{find_project_root, read_project_id} was moved here and the equivalent unit tests were removed from crates/gcode/src/project.rs. To avoid a coverage regression (and to keep this shared crate safe to evolve), please add unit tests in gcore covering: finding roots via project.json and gcode.json, returning None when absent, and read_project_id preferring id with fallback to project_id.

Suggested change
}
}
#[cfg(test)]
mod tests {
use super::{find_project_root, read_project_id};
use std::fs;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
struct TestDir {
path: PathBuf,
}
impl TestDir {
fn new() -> Self {
let unique = format!(
"gcore-project-tests-{}-{}",
std::process::id(),
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system time before unix epoch")
.as_nanos()
);
let path = std::env::temp_dir().join(unique);
fs::create_dir_all(&path).expect("failed to create test directory");
Self { path }
}
fn path(&self) -> &Path {
&self.path
}
}
impl Drop for TestDir {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.path);
}
}
#[test]
fn find_project_root_detects_project_json() {
let temp = TestDir::new();
let root = temp.path().join("workspace");
let nested = root.join("a").join("b");
fs::create_dir_all(root.join(".gobby")).expect("failed to create .gobby");
fs::create_dir_all(&nested).expect("failed to create nested directories");
fs::write(root.join(".gobby").join("project.json"), r#"{"id":"proj-1"}"#)
.expect("failed to write project.json");
let found = find_project_root(&nested);
assert_eq!(found, Some(root));
}
#[test]
fn find_project_root_detects_gcode_json() {
let temp = TestDir::new();
let root = temp.path().join("workspace");
let nested = root.join("src").join("deep");
fs::create_dir_all(root.join(".gobby")).expect("failed to create .gobby");
fs::create_dir_all(&nested).expect("failed to create nested directories");
fs::write(root.join(".gobby").join("gcode.json"), r#"{"name":"proj"}"#)
.expect("failed to write gcode.json");
let found = find_project_root(&nested);
assert_eq!(found, Some(root));
}
#[test]
fn find_project_root_returns_none_when_absent() {
let temp = TestDir::new();
let start = temp.path().join("no").join("project").join("here");
fs::create_dir_all(&start).expect("failed to create start directory");
let found = find_project_root(&start);
assert_eq!(found, None);
}
#[test]
fn read_project_id_prefers_id_over_project_id() {
let temp = TestDir::new();
let root = temp.path().join("workspace");
fs::create_dir_all(root.join(".gobby")).expect("failed to create .gobby");
fs::write(
root.join(".gobby").join("project.json"),
r#"{"id":"preferred-id","project_id":"legacy-id"}"#,
)
.expect("failed to write project.json");
let id = read_project_id(&root).expect("failed to read project id");
assert_eq!(id, "preferred-id");
}
#[test]
fn read_project_id_falls_back_to_project_id() {
let temp = TestDir::new();
let root = temp.path().join("workspace");
fs::create_dir_all(root.join(".gobby")).expect("failed to create .gobby");
fs::write(
root.join(".gobby").join("project.json"),
r#"{"project_id":"legacy-id"}"#,
)
.expect("failed to write project.json");
let id = read_project_id(&root).expect("failed to read project id");
assert_eq!(id, "legacy-id");
}
}

Copilot uses AI. Check for mistakes.
Comment thread Cargo.toml
Comment on lines 1 to 3
[workspace]
members = ["crates/gcode", "crates/gsqz", "crates/gloc"]
members = ["crates/gcode", "crates/gcore", "crates/ghook", "crates/gsqz", "crates/gloc"]
resolver = "3"
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

PR title/description frames this as a narrow Windows hotfix for gobby-hooks, but this PR also introduces gobby-core, adds the ghook crate/workflow/docs, bumps gcode to 0.6.0, and bumps gsqz to 0.4.1. Please either update the PR description/title to reflect the broader scope or split into smaller PRs so the hotfix can ship independently.

Copilot uses AI. Check for mistakes.
Comment on lines +173 to +185
let b64 = base64::engine::general_purpose::STANDARD.encode(stdin_bytes);
let body = serde_json::json!({
"quarantined": true,
"stdin_bytes_b64": b64,
});
atomic_write(&body_path, &serde_json::to_vec_pretty(&body)?)?;

let meta = serde_json::json!({
"reason": "malformed_stdin",
"json_error": json_error,
"stdin_bytes_b64": b64,
});
atomic_write(&meta_path, &serde_json::to_vec_pretty(&meta)?)?;
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

quarantine_malformed_at uses b64 in two serde_json::json! invocations, which will move the String the first time and fail to compile when reused for meta. Clone the value (or encode twice / store as &str) so both JSON objects can include the same base64 payload.

Copilot uses AI. Check for mistakes.
Comment on lines +68 to +90
pub fn atomic_write(final_path: &Path, bytes: &[u8]) -> Result<()> {
if let Some(parent) = final_path.parent() {
fs::create_dir_all(parent)
.with_context(|| format!("create_dir_all {}", parent.display()))?;
}
let mut tmp = final_path.to_path_buf();
let mut name = tmp
.file_name()
.context("final path has no file name")?
.to_owned();
name.push(".tmp");
tmp.set_file_name(name);

{
let mut f = File::create(&tmp).with_context(|| format!("create tmp {}", tmp.display()))?;
f.write_all(bytes)
.with_context(|| format!("write tmp {}", tmp.display()))?;
f.sync_all()
.with_context(|| format!("fsync tmp {}", tmp.display()))?;
}
fs::rename(&tmp, final_path)
.with_context(|| format!("rename {} -> {}", tmp.display(), final_path.display()))?;
Ok(())
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

atomic_write uses std::fs::rename(tmp, final_path) which will fail on Windows if final_path already exists (unlike POSIX rename-overwrite). This breaks rewriting stable paths like ~/.gobby/bin/.ghook-compatibility on subsequent runs, potentially leaving the daemon with stale compatibility info. Consider explicitly handling the destination-exists case (e.g., remove/replace semantics on Windows) for callers that overwrite a fixed filename.

Copilot uses AI. Check for mistakes.
Comment on lines 21 to 33
@@ -15,10 +28,7 @@ fn glob_to_like_prefix(pattern: &str) -> Option<String> {
if prefix.is_empty() {
None
} else {
Some(format!(
"{}%",
prefix.replace('%', r"\%").replace('_', r"\_")
))
Some(format!("{}%", escape_like(&prefix)))
}
}
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

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

glob_to_like_prefix now escapes %, _, and \ with backslashes, but the SQL that consumes this value (e.g. cs.file_path LIKE ?) does not use an ESCAPE '\' clause. In SQLite, backslash is not an escape character unless ESCAPE is specified, so the escaping is currently ineffective and may produce incorrect prefilter behavior for paths containing these characters. Either add ESCAPE '\' wherever the prefix is used, or stop inserting backslashes here.

Copilot uses AI. Check for mistakes.
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