Skip to content

Commit 540a2a3

Browse files
joshwilhelmiclaude
andcommitted
Merge dev into main for gcode 0.7.0 release
Brings in: - project-#151: tighten gcode project-scoped search (search-symbol, --language filter, search-content broadened, scope helpers) - project-#152: graph enhancements draft (docs) - project-#153: isolate gcode index roots (IsolationMarker, linked worktrees, generated identities, freshness module, --no-freshness) - project-#154: gcode 0.7.0 release prep (Cargo.toml/lock, CHANGELOG, README, user guide, dev guide) - ghook fix from project-#147 (already tagged ghook-v0.4.1 from dev) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 parents 4fd5f44 + 930b8c6 commit 540a2a3

28 files changed

Lines changed: 2960 additions & 275 deletions

CHANGELOG.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,30 @@ All notable changes to gobby-cli are documented in this file.
77
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
88
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
99

10-
## [0.4.0] — gobby-hooks
10+
## [0.7.0] — gcode
11+
12+
### Added
13+
14+
#### gcode
15+
16+
- **`gcode search-symbol` command** — Exact-first symbol/name lookup with deterministic ranking. Resolves precise names ahead of fuzzy matches before falling back to FTS5, and accepts the same `--kind`, `--language`, and `--path` filters as `gcode search`. Use it when you already know (most of) the name and want the canonical hit at rank 0 instead of letting RRF rerank it. (#151)
17+
- **`--language` filter on search commands**`search`, `search-symbol`, `search-text`, and `search-content` accept `--language <lang>` to narrow results to a tree-sitter language (e.g. `rust`, `python`, `css`). Composes with `--kind` and `--path`. (#151)
18+
- **`search-content` covers comments, config, and CSS** — content search now indexes and matches the same comment/config/CSS chunks the indexer already wrote, so doc strings, `*.toml`/`*.yaml`/`*.json` config, and stylesheets are reachable from `gcode search-content`. (#151)
19+
- **Isolated index roots**`Context::resolve` now distinguishes five project-identity sources, written up as `ProjectIdentitySource` (`ProjectJson`, `GcodeJson`, `IsolatedRoot`, `LinkedWorktree`, `Generated`):
20+
- **Isolation marker** — when `.gobby/project.json` carries `parent_project_path` and/or `parent_project_id`, the directory gets its own filesystem-derived code-index id (UUID5 of the canonical path, namespace `c0de1de0-…`) and is no longer conflated with the parent project. Reading the marker is via the new `project::read_isolation_marker` helper.
21+
- **Linked git worktrees** — runs from inside a `git worktree add` directory now resolve to the worktree's own top-level (via `git rev-parse --show-toplevel` + `git worktree list --porcelain` parsing in the new `git` module), and the code-index id is derived from that path rather than from any inherited `.gobby/project.json`. A warning is printed when an inherited id would have been used.
22+
- **Generated** — directories without any identity file get a deterministic UUID5 from the canonical path; `.gobby/gcode.json` is only written when `gcode init` runs (via `MissingIdentity::Generate`). Other commands fall back to `MissingIdentity::Error` and ask the user to run `gcode init`.
23+
- **Read-time freshness checks** — search, symbol, outline, and graph read commands now verify that on-disk source still matches the index before returning results, and incrementally re-index the affected file(s) transparently when they don't. Backed by the new `freshness` module (`FreshnessScope::Project` for project-wide commands, `FreshnessScope::Files` for file-scoped commands like `outline`, plus `ensure_symbol_fresh` for `gcode symbol`). Disable per-call with the new global `--no-freshness` flag, or via `GCODE_FRESHNESS_INFLIGHT=1` for nested processes (a re-entrancy guard so the indexer doesn't recurse into itself). Not a substitute for `gcode index` on bulk changes — intended to keep individual reads honest. (#153)
24+
- **Project-root walk-up consults git worktree top-level**`Context::resolve`'s walk-up now prefers `git rev-parse --show-toplevel` (treating linked worktrees as their own top-level) before falling back to generic `.git`/`.hg` markers, so commands invoked deep inside a worktree resolve to the right project root. (#153)
25+
26+
### Changed
27+
28+
#### gcode
29+
30+
- **Project-scoped search and graph commands tightened** — search, symbol, outline, and graph commands now validate that resolved file paths still belong to the current project context before returning results. Stale entries from other projects sharing the same `gobby-hub.db` no longer leak across project boundaries. The new `commands::scope` module owns the path-validation helpers (`normalize_file_arg`, `path_exists_in_current_project`, `indexed_file_exists`, `content_chunks_exist`, `current_indexed_path_is_valid`). (#151)
31+
- **`gcode init` reports identity source** — init output now distinguishes `initialized`, `existing`, `gobby`, `isolated`, and `linked-worktree` cases when announcing the project context, so it's obvious which identity source resolved.
32+
33+
## [0.4.1] — gobby-hooks
1134

1235
### Added
1336

@@ -22,6 +45,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2245

2346
- **Droid blocking semantics** — droid daemon responses with `continue:false` now exit 2 with the daemon reason while preserving the response JSON on stdout. Other droid block JSON is forwarded on stdout with exit 0 for droid's hook protocol, and daemon transport failures surface as exit 1 stderr diagnostics.
2447

48+
### Fixed
49+
50+
#### gobby-hooks
51+
52+
- **Stop double-emitting Claude PreToolUse denies** — for `--cli=claude`, ghook now narrows the legacy `stderr+exit(2)` channel to daemon responses that explicitly set top-level `continue:false` with a non-empty `stopReason` (the HARD_STOP shape). All other responses — including PreToolUse denies that arrive via `hookSpecificOutput.permissionDecision:"deny"` — are emitted as JSON on stdout with exit 0, matching the structured-channel contract the Python `ClaudeCodeAdapter` already targets. Previously, ghook synthesized a second deny channel on top of the structured one, causing Claude Code to render every PreToolUse deny twice (once as a permission denial, once as a "hook blocking error"). Codex/Gemini/Qwen/Droid paths are unchanged.
53+
2554
## [0.3.1] — gobby-hooks
2655

2756
### Added

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/gcode/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "gobby-code"
3-
version = "0.6.2"
3+
version = "0.7.0"
44
edition = "2024"
55
rust-version = "1.85"
66
authors = ["Josh Wilhelmi <hello@gobby.ai>"]

crates/gcode/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,12 @@ gcode init
105105
# Search
106106
gcode search "query" # Hybrid: FTS + semantic + graph boost
107107
gcode search "query" --kind function # Filter by symbol kind
108+
gcode search "query" --language rust # Filter by source language
108109
gcode search "query" --path "src/**/*.rs" # Filter by file path glob
110+
gcode search-symbol "outline" # Exact-first symbol/command lookup
111+
gcode search-symbol "outline" --kind function --language rust
109112
gcode search-text "query" # FTS5 on symbol names/signatures
110-
gcode search-content "query" # FTS5 on file content
113+
gcode search-content "query" # FTS5 on file content, comments, config, CSS
111114

112115
# Symbol retrieval
113116
gcode outline src/auth.ts # Hierarchical symbol tree
@@ -138,6 +141,7 @@ gcode search --project /path/to/app "q" # By path
138141
# Global flags
139142
--format text|json # Output format (default: json)
140143
--quiet # Suppress warnings and progress
144+
--no-freshness # Skip read-time index/source freshness checks
141145
```
142146

143147
## Standalone vs Gobby

crates/gcode/src/commands/index.rs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use gobby_core::project::{find_project_root, read_project_id};
2-
31
use crate::config::Context;
42
use crate::db;
53
use crate::index::indexer;
@@ -15,23 +13,28 @@ pub fn run(
1513
let (root, project_id, conn) = match path.as_deref() {
1614
Some(p) => {
1715
let target = std::path::PathBuf::from(p);
18-
let target_root = find_project_root(&target).unwrap_or_else(|| target.clone());
16+
let target_root = crate::config::detect_project_root_from(&target)?;
1917
if target_root != ctx.project_root {
2018
// Path belongs to a different project — re-resolve everything
2119
let db_path = crate::config::resolve_db_path(&target_root)?;
22-
let project_id = read_project_id(&target_root)
23-
.or_else(|_| crate::project::read_gcode_json(&target_root))
24-
.unwrap_or_else(|_| crate::project::generate_project_id(&target_root));
20+
let identity = crate::config::resolve_project_identity(
21+
&target_root,
22+
crate::config::MissingIdentity::Generate,
23+
)?;
24+
crate::config::warn_project_identity(&identity, ctx.quiet);
2525
if !ctx.quiet {
2626
eprintln!(
2727
"Warning: path '{}' belongs to project {} (not {}), re-resolving context",
2828
p,
29-
&project_id[..8],
29+
short_id(&identity.project_id),
3030
&ctx.project_id[..8]
3131
);
3232
}
3333
let conn = db::open_readwrite(&db_path)?;
34-
(target_root, project_id, conn)
34+
if identity.should_write_gcode_json {
35+
crate::project::ensure_gcode_json(&target_root)?;
36+
}
37+
(target_root, identity.project_id, conn)
3538
} else {
3639
let conn = db::open_readwrite(&ctx.db_path)?;
3740
(target, ctx.project_id.clone(), conn)
@@ -43,9 +46,6 @@ pub fn run(
4346
}
4447
};
4548

46-
// Auto-init: ensure identity file exists before indexing
47-
crate::project::ensure_gcode_json(&root)?;
48-
4949
if let Some(file_list) = files {
5050
let result = indexer::index_files(&conn, &root, &project_id, &file_list)?;
5151
if !ctx.quiet {
@@ -69,3 +69,7 @@ pub fn run(
6969

7070
Ok(())
7171
}
72+
73+
fn short_id(id: &str) -> &str {
74+
id.get(..8).unwrap_or(id)
75+
}

crates/gcode/src/commands/init.rs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,21 @@ use crate::project;
88
use crate::skill;
99

1010
pub fn run(project_root: &Path, format: Format, quiet: bool) -> anyhow::Result<()> {
11-
let (project_id, was_created) = project::ensure_gcode_json(project_root)?;
12-
13-
let status = if was_created {
14-
"initialized"
15-
} else if project_root.join(".gobby").join("project.json").exists() {
16-
"gobby"
11+
let identity =
12+
config::resolve_project_identity(project_root, config::MissingIdentity::Generate)?;
13+
config::warn_project_identity(&identity, quiet);
14+
let (project_id, was_created) = if identity.should_write_gcode_json {
15+
project::ensure_gcode_json(project_root)?
1716
} else {
18-
"existing"
17+
(identity.project_id.clone(), false)
18+
};
19+
20+
let status = match identity.source {
21+
config::ProjectIdentitySource::IsolatedRoot => "isolated",
22+
config::ProjectIdentitySource::LinkedWorktree => "linked-worktree",
23+
config::ProjectIdentitySource::ProjectJson => "gobby",
24+
config::ProjectIdentitySource::Generated if was_created => "initialized",
25+
_ => "existing",
1926
};
2027

2128
// Detect AI CLIs and install skills (skip if gobby manages this project)
@@ -81,6 +88,14 @@ pub fn run(project_root: &Path, format: Format, quiet: bool) -> anyhow::Result<(
8188
project_root.display()
8289
);
8390
}
91+
"isolated" | "linked-worktree" => {
92+
eprintln!(
93+
"Using {} code index: {} ({})",
94+
status,
95+
project_id,
96+
project_root.display()
97+
);
98+
}
8499
_ => {
85100
eprintln!(
86101
"Already initialized: {} ({})",

crates/gcode/src/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
pub mod graph;
22
pub mod index;
33
pub mod init;
4+
pub(crate) mod scope;
45
pub mod search;
56
pub mod status;
67
pub mod symbols;

0 commit comments

Comments
 (0)