Skip to content

release: v0.1.8 - #14

Merged
This-Is-NPC merged 15 commits into
masterfrom
test
Apr 12, 2026
Merged

release: v0.1.8#14
This-Is-NPC merged 15 commits into
masterfrom
test

Conversation

@This-Is-NPC

Copy link
Copy Markdown
Owner

Before

  • No AI agent interface — omakure was TUI-only with no machine-readable output
  • Run history stored as loose JSON files per run, not queryable
  • TUI could only open the global workspace, no positional path argument
  • No run queue, worker daemon, or structured trace system

After

  • Full AI agent CLI surface with --json stable envelope on every verb (describe, search, history, help-ai, run, init, scripts, config)
  • SQLite-backed run history (runs.sqlite) with filters, actor/reason audit fields, and a privacy contract
  • TUI opens any directory via positional path (omakure ., omakure /abs/path)
  • State-machine run queue with worker daemon, structured traces, tag/state filters, and per-job timeout/cancel

Summary of Changes

Aspect Change
AI agent interface New --json envelope, describe, search, history list/show/tail, help-ai verbs, --actor/--reason/--run-id flags on run
Run storage src/history.rs (JSON files) replaced by src/runs.rs (SQLite WAL). Legacy .history/*.json cleaned up on first launch
Run queue & worker omakure queue add/worker with 7-state machine, shared executor, heartbeat lease, SIGINT graceful shutdown
Execution traces omakure trace emits structured events; omakure history traces reads them with --level/--since-sequence
Positional path omakure <path> browses any dir as session scripts root; workspace/history stay anchored to global root
Tag & state filters --tag on scripts/search; --state/--state-set on history list
TUI History widget gains colored State column, Actor column, in-flight rows on top
Docs ai-interface.md, updated architecture.md, requirements.md, README.md, AGENTS.md, storage privacy contract
Contributing test as default base branch, GitHub Projects workflow with gh CLI

Files Updated

  • src/runs.rs (new, 2116 lines — SQLite run store + state machine)
  • src/run_executor.rs (new, 582 lines — shared executor)
  • src/cli/queue.rs, src/cli/history.rs, src/cli/json.rs, src/cli/describe.rs, src/cli/search.rs, src/cli/trace.rs, src/cli/help_ai.rs (new CLI modules)
  • src/cli/args.rs, src/cli/run.rs, src/cli/init.rs, src/cli/list.rs, src/cli/config.rs, src/cli/mod.rs (extended)
  • src/main.rs, src/workspace.rs, src/error.rs, src/lua_widget.rs (extended)
  • src/adapters/tui/app.rs, src/adapters/tui/mod.rs, src/adapters/tui/widgets/history.rs, src/adapters/tui/widgets/run_result.rs (TUI rewrite)
  • src/history.rs (deleted)
  • .docs/ai-interface.md, .docs/architecture.md, .docs/requirements.md, .docs/environments.md, .docs/scripts-path.md, .docs/usage.md, .docs/workspace.md, .docs/lua-widgets.md (docs)
  • CONTRIBUTING.md, AGENTS.md, README.md, Cargo.toml, Cargo.lock
  • tests/cli_positional_path.rs (new integration tests)

Validation

Scenario Outcome
omakure . opens current dir as scripts root Positional path integration tests pass (tests/cli_positional_path.rs)
--json envelope on all AI verbs Envelope shape documented and enforced in src/cli/json.rs
Queue worker drains atomically UPDATE ... RETURNING prevents double-claim; SIGINT finishes in-flight job
Legacy .history/*.json cleanup runs::open deletes top-level JSON files, preserves subdirs and SQLite files
State machine transitions Typed helpers gate invalid transitions in src/runs.rs

Risks / Follow-ups

  • Breaking change: RunRow fields (started_at, finished_at, duration_ms, exit_code, success) are now Option<...> in JSON — downstream consumers must handle nulls
  • Breaking change: runs.sqlite tables missing state column are dropped and recreated on first launch — legacy rows are lost
  • Breaking change: src/history.rs and HistoryEntry JSON format removed with no compatibility shim
  • Worker daemon has no systemd/launchd service file yet — must be run manually

References

This-Is-NPC and others added 15 commits April 11, 2026 02:18
Add an optional positional PATH argument to the no-subcommand TUI entry
so users can run `omakure .`, `omakure ../team-scripts`, or
`omakure /abs/path` and browse any directory as a session-only scripts
root, while history, environments, the search index, and `omakure.toml`
stay anchored to the global workspace.

The Workspace type now tracks two distinct path anchors. Its
`ensure_layout()` is strictly bound to the global root and never writes
inside the positional target. History entries are recorded with the
absolute canonical path of the executed script and filtered by the
active scripts root for the in-session view; legacy relative entries
keep loading and stay visible in the plain `omakure` case. When a
positional path is supplied and `<scripts-root>/omakure.conf` exists,
it becomes the read-only session-active environment without ever
touching `.omaken/envs/active` or copying into `.omaken/envs/`. The
positional path is mutually exclusive with `--scripts-dir`, and
non-existent / file / conflict cases produce deterministic errors via
`Display` and exit before launching the TUI.
…ctory

feat: open TUI from current directory with positional path argument
CONTRIBUTING.md now states that new feature/fix work must branch off
`test` rather than `master`. `test` is the integration branch and is
always ahead of `master`; `master` is only updated when `test` is
merged for a release.
Make omakure trivially usable by AI agents as a workflow runner. Every
AI-relevant verb now supports a global --json flag that emits a stable
envelope { ok, data, error, schema_version: "1" } so agents can branch
on `ok` instead of parsing human text. Run history moves from per-run
JSON files to a queryable SQLite store.

Trust model: the AI is a full user. No allowlists, denylists, or
per-script confirmation gates. Restriction is replaced with heavy
auditing — every invocation records actor, optional reason, full
argv, exit code, stdout, stderr, start/end timestamps, and a stable
run_id in .history/runs.sqlite.

New modules:
- src/cli/json.rs: single envelope writer + stable error code constants
  (not_found, schema_invalid, script_exists, missing_required_field,
  invalid_argument, not_implemented, internal).
- src/runs.rs: SQLite-backed run log with RunRow/RunFilters,
  open/init_schema/insert_run/get_run/query_runs, generate_run_id
  (`<unix_ms>-<pid>-<counter>`), and format_run_timestamp. Reuses the
  WAL + busy-timeout PRAGMA setup from search_index.rs.
- src/cli/describe.rs: `omakure describe <script>` returns the full
  parsed schema, with not_found / schema_invalid error codes.
- src/cli/search.rs: `omakure search <query>` surfaces the existing
  SQLite script index from the CLI (previously TUI-only).
- src/cli/history.rs: `omakure history list|show|tail` with filters
  --script, --actor, --since, --until, --success, --failure, --limit;
  show <run_id> returns the full row.
- src/cli/help_ai.rs: `omakure help-ai` returns one JSON capability
  payload built by walking Cli::command() so it cannot drift from
  --help.

New flags:
- omakure run: --actor, --reason, --run-id, --parent-run-id,
  --no-prompt, --json. --json implies --no-prompt; --no-prompt is a
  pre-flight check that fails with missing_required_field and writes
  no history row when a required field has no --<arg> on the command
  line.
- omakure init: --schema-json '<json>|@file', --body-stdin, --force.
  Schema is validated before the script file is written. Existing
  paths fail with script_exists unless --force is set.
- omakure scripts / config: --json envelopes.

BREAKING CHANGE: src/history.rs and the entire HistoryEntry JSON-file
format are removed. There is no shim, no fallback, no compatibility
re-export. On first launch after upgrade, runs::open deletes every
top-level *.json file in <workspace>/.history/ — subdirectories,
runs.sqlite, search-index.sqlite, and the .omaken/ tree are left
untouched. Users wanting to keep historical run data must back up
.history/ themselves before upgrading.

The TUI history screen is rewritten on top of RunRow and gains an
Actor column. The headless `omakure run` and the TUI both write
through runs::insert_run.
Add .docs/ai-interface.md as the contract for AI agents using omakure
as a workflow runner: trust model, JSON envelope shape, stable error
codes, every AI verb with its data shape, run_id format, destructive
upgrade notice, and a worked example chain.

Update README.md with a "Using Omakure from an AI agent" section that
links to the new doc.

Refresh AGENTS.md, .docs/architecture.md, and .docs/requirements.md to
match the new module layout: src/runs.rs replaces the deleted
src/history.rs, the new cli/{json,describe,search,history,help_ai}.rs
modules are listed, and FR-009/FR-010 are rewritten alongside FR-034
through FR-041 covering the JSON envelope, the new AI verbs, the AI
flags on run, the non-interactive init mode, and the legacy
.history/*.json cleanup.
feat(cli)!: add AI agent CLI surface and SQLite run history
Layer a state machine on runs.sqlite with seven final states (queued,
running, completed, failed, cancelled, timed_out, dead_letter) and the
typed transition helpers that gate them. Both omakure run (synchronous
fast path) and the new omakure queue worker daemon drive their child
processes through one shared executor that injects OMAKURE_RUN_ID and
OMAKURE_SCRIPTS_DIR, refreshes the SQLite lease via heartbeat, and
reacts to mid-execution cancel and per-job --timeout. Producers push
work via omakure queue add, the worker drains atomically via UPDATE ...
RETURNING so two threads never claim the same row, and SIGINT/SIGTERM
finishes the in-flight job before exit. Scripts emit structured trace
events via omakure trace into a new run_traces table (monotonic
per-run sequence, FK cascade); agents read them back via omakure
history traces with --level and --since-sequence filters. omakure
scripts and omakure search gain a repeatable --tag flag (case-sensitive
AND); omakure history list gains --state and --state-set filters
defaulting to the terminal set so v0.1 callers see no behavior change.
The TUI history widget renders a per-state colored State column and
surfaces in-flight rows on top.

BREAKING CHANGE: RunRow's started_at, finished_at, duration_ms,
exit_code, and success fields are now Option<...> in the JSON envelope.
On first launch, runs.sqlite tables that lack the state column are
dropped and recreated; legacy rows are lost.
Update the AI agent contract, architecture knowledge base, project
README, and AGENTS.md with the new state machine, queue verbs, worker
daemon, structured trace stream, tag filter, --state/--state-set
history filter, and the cron producer contract. Add ten new functional
requirement entries (FR-042..FR-051) covering the storage layer,
shared executor, producers, worker, traces, tag filter, state filter,
and TUI state column. Expand the destructive upgrade notice to cover
the runs.sqlite schema rebuild on first launch.
Add a "Storage privacy contract" section to the AI interface document
that formalizes runs.sqlite as private internal storage of the omakure
CLI: scripts, agents, and orchestrators must never open the file
directly, and the only legitimate access paths are the documented
verbs. The section pairs with the existing trust model — together
they bound what the AI is allowed to do (run anything as a full user)
against what is structurally off-limits (touching its own audit log
through anything other than the omakure CLI).

The section also recommends a kernel-enforced enforcement pattern for
sandboxed deployments based on file capabilities (cap_dac_override+ep
on the omakure binary, distinct UID for the sandbox, .history/ owned
by a UID the sandbox cannot write directly), and explicitly steers
pipelines that need their own persistent state toward a separate
datastore provisioned by the orchestrator instead of leaking into
runs.sqlite.

This is a documentation-only commit; no code changes are needed
because the omakure code is already structurally consistent with the
contract: src/runs.rs is the only writer in the codebase and every
verb routes through it.
…runs (#11) (#12)

* feat(tui): add history dashboards, themed spinners, and async inline runs (#11)

- New `Dashboards` view inside the History screen, toggled with `Tab`.
  Renders a "Top runs" horizontal-bar leaderboard, a "Runs by status"
  state-bar panel, and a per-script panel (state bars + duration
  sparkline + avg/p50/p95) bound to the row highlighted in `List`.
- Per-script charts also surface on the `ScriptSelect` screen, stacked
  below the schema preview when a script is highlighted. Press `e` to
  expand the charts to fullscreen, `Esc` to collapse.
- Themed loading spinners powered by the `rattles` crate: `Scan` on
  the search-index banner, `Sand` on bootstrap, the Lua widget loader,
  and the foreground Running screen. Driven by a single `App.tick`
  counter incremented once per main-loop iteration; spinner color
  tracks `theme.text_secondary()` so all built-in themes stay coherent.
- Inline script execution now runs on a background thread via the
  existing `widget_loading` mpsc pattern, so the main loop keeps
  drawing and bumping `tick` while the script runs — without this the
  Sand spinner on the Running screen would freeze on a single frame.
- Aggregations live in pure functions (`aggregate`,
  `aggregate_top_scripts`, `aggregate_for_script`, `percentile`) so the
  math is unit-tested against fabricated `RunRow` fixtures with no
  terminal involved.

* docs(architecture): document spinners, dashboards, and rattles dep (#11)

Add `rattles` to the dependency tables, list the new `dashboards.rs`
and `spinner.rs` widget modules in the project tree, and document the
History Dashboards view and the themed spinner system as architectural
patterns.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
chore: bump version to 0.1.8 and add release notes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
style: apply cargo fmt formatting
@This-Is-NPC
This-Is-NPC requested a review from Copilot April 12, 2026 04:30
@This-Is-NPC
This-Is-NPC merged commit fd05944 into master Apr 12, 2026
5 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Release v0.1.8 introduces an AI-agent-friendly CLI surface (stable --json envelope) and upgrades execution observability/storage by moving run history into SQLite with a queue/worker state machine, structured traces, and a TUI positional scripts-root override.

Changes:

  • Added AI-oriented CLI verbs (describe, search, history, queue, trace, help-ai) with a shared JSON envelope.
  • Replaced JSON-file run history with SQLite-backed runs/traces plus a shared executor for run and queue worker.
  • Updated TUI to support positional scripts root, session env override, and richer history rendering (state/actor + dashboards/spinners).

Reviewed changes

Copilot reviewed 55 out of 56 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
tests/cli_positional_path.rs End-to-end tests for positional scripts-root validation and subcommand parsing regression checks.
src/workspace.rs Splits global root vs scripts root; adds invariants/tests to ensure metadata stays under global root.
src/use_cases/mod.rs Retains historical runner wiring while new execution path uses run_executor.
src/run_executor.rs Shared child-process executor with heartbeat/timeout/cancel and stdout/stderr capture.
src/ports/mod.rs Keeps ScriptRunner port/ScriptRunOutput types while execution is routed elsewhere.
src/ports/environment.rs Adds session env override path field to environment config.
src/main.rs Routes new CLI verbs, adds positional scripts-root resolution, and changes top-level error handling.
src/lua_widget.rs Adds tests for widget loader behavior when index.lua is missing/returns a table.
src/history.rs Removes legacy JSON-file history implementation.
src/error.rs Adds positional scripts-root related error variants/messages.
src/cli/trace.rs Adds omakure trace writer command for structured per-run traces.
src/cli/search.rs Adds omakure search CLI surface over the SQLite search index (+ tag filter).
src/cli/run.rs Reworks omakure run to write through the state machine and shared executor (+ actor/reason/run-id/no-prompt/json semantics).
src/cli/queue.rs Adds queue producers + worker daemon wiring using state machine + shared executor.
src/cli/mod.rs Exposes newly added CLI modules.
src/cli/list.rs Adds structured scripts --json output and tag filtering.
src/cli/json.rs Introduces single JSON envelope helper + stable error codes.
src/cli/init.rs Extends init to accept schema/body inputs and emit JSON payloads + stable error codes.
src/cli/history.rs Adds SQLite-backed history queries, stats, and trace readers with state filters and JSON support.
src/cli/help_ai.rs Adds help-ai capability discovery payload generated from clap metadata.
src/cli/describe.rs Adds describe verb to return parsed script schema (human + JSON).
src/cli/config.rs Adds config --json payload with resolved paths/env metadata.
src/cli/args.rs Adds global --json, positional PATH, and new verb/subcommand argument structures.
src/adapters/tui/widgets/spinner.rs Adds themed deterministic spinners driven by App.tick.
src/adapters/tui/widgets/search.rs Shows indexing spinner/status in search UI.
src/adapters/tui/widgets/running.rs Adds spinner to Running screen and threads through theme.
src/adapters/tui/widgets/run_result.rs Migrates output formatting to RunRow-based history.
src/adapters/tui/widgets/mod.rs Registers new dashboards/spinner widgets.
src/adapters/tui/widgets/loading.rs Adds spinner to bootstrap loading screen.
src/adapters/tui/widgets/history.rs Migrates history widget to RunRow, adds state/actor columns and dashboards view.
src/adapters/tui/widgets/environment.rs Adds spinner to environment loading status.
src/adapters/tui/widgets/common.rs Adds per-run-state color/style helpers for history/dashboards.
src/adapters/tui/ui.rs Threads tick/theme to widgets; adds dashboards rendering + expanded script charts logic.
src/adapters/tui/state/mod.rs Exposes history dashboards view/layout state.
src/adapters/tui/state/history.rs Adds HistoryView/DashboardLayout state machine and key handling helpers.
src/adapters/tui/mod.rs Switches TUI history to SQLite runs; runs scripts via background thread to keep UI responsive/spinners animated.
src/adapters/tui/events.rs Adds Tab switching between history views; adds e expansion toggles; fixes Alt+E precedence.
src/adapters/tui/app.rs Filters history by scripts root; adds session env override loader; adds inline run receiver + tick counter.
src/adapters/script_runner.rs Adds build_command helper used by executor; factors runtime checks into helper.
src/adapters/environments.rs Exposes env parsers for session env support and sets new config field.
release-notes/v0.1.8.md Release notes for v0.1.8 features/breaking changes.
README.md Documents positional PATH behavior and AI agent CLI usage with examples.
CONTRIBUTING.md Updates base-branch guidance and documents GitHub Projects workflow via gh.
Cargo.toml Bumps version to 0.1.8 and adds new deps (humantime, signal-hook, rattles).
Cargo.lock Locks added dependency versions.
AGENTS.md Updates architecture map/docs to reflect runs/queue/trace/json additions and history removal.
.docs/workspace.md Documents global vs session scripts root and history anchoring.
.docs/usage.md Documents positional PATH and global state invariants.
.docs/scripts-path.md Adds session-only TUI scripts-root override usage.
.docs/requirements.md Updates FR list to reflect SQLite history/state machine/trace/JSON contract.
.docs/lua-widgets.md Clarifies widget behavior with positional PATH.
.docs/environments.md Documents session omakure.conf override behavior.
.docs/architecture.md Updates dependency list and architecture notes for new subsystems.

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

Comment thread src/run_executor.rs
Comment on lines +145 to +158
// Pull stdout/stderr off threads so a child that prints a lot does
// not block on its own pipe buffer. We use a channel + timed drain
// (instead of join()) so a killed child whose orphaned grandchildren
// keep its pipe open does not deadlock the executor.
let stdout = child.stdout.take();
let stderr = child.stderr.take();
let (stdout_tx, stdout_rx) = channel::<String>();
let (stderr_tx, stderr_rx) = channel::<String>();
if let Some(h) = stdout {
spawn_pipe_reader_to_channel(h, stdout_tx);
}
if let Some(h) = stderr {
spawn_pipe_reader_to_channel(h, stderr_tx);
}

Copilot AI Apr 12, 2026

Copy link

Choose a reason for hiding this comment

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

The stdout/stderr capture uses std::sync::mpsc::channel() (unbounded) but the receiver is only drained after the child exits. A noisy script can enqueue an unbounded number of lines and grow memory until OOM. Consider switching to a bounded channel (sync_channel) with backpressure, or streaming to a size-capped buffer/file while the process runs so output volume can’t exhaust memory.

Copilot uses AI. Check for mistakes.
Comment thread src/run_executor.rs
Comment on lines +305 to +307
/// Heartbeat tick interval. Short enough to detect external cancel
/// quickly, but long enough not to thrash SQLite.
const HEARTBEAT_TICK_MS: u64 = 250;

Copilot AI Apr 12, 2026

Copy link

Choose a reason for hiding this comment

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

HEARTBEAT_TICK_MS is 250ms and each tick opens a new SQLite connection and runs runs::heartbeat. This is likely to cause unnecessary CPU/disk wakeups and lock contention (especially with multiple workers/concurrency) for a 60s lease. Consider a much larger tick (e.g. 5–30s) and/or reusing a connection, while still keeping cancel detection responsive via a lighter-weight check.

Copilot uses AI. Check for mistakes.
Comment thread src/runs.rs
Comment on lines +843 to +867
/// Refresh the heartbeat lease on a `running` row currently held by
/// `worker_id`. Returns the row's current state on success, or `None` if
/// the row is no longer owned by `worker_id` (e.g. cancelled or stolen).
///
/// Callers use the returned state to detect mid-execution cancel: if it is
/// not [`RunState::Running`], the worker should kill the script.
pub fn heartbeat(
conn: &Connection,
run_id: &str,
worker_id: &str,
) -> Result<Option<RunState>, String> {
let now = current_unix_ms();
let updated = conn
.execute(
"UPDATE runs
SET lease_until = ?
WHERE run_id = ? AND worker_id = ? AND state = 'running'",
params![now + HEARTBEAT_MS, run_id, worker_id],
)
.map_err(|err| format!("Heartbeat failed: {}", err))?;
if updated == 0 {
// Either the row was reclaimed/cancelled, or terminated already.
let row = get_run(conn, run_id)?;
return Ok(row.map(|r| r.state));
}

Copilot AI Apr 12, 2026

Copy link

Choose a reason for hiding this comment

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

heartbeat’s doc says it returns None when the row is no longer owned by worker_id (e.g. stolen), but when updated == 0 it returns only row.state. If another worker steals the row and leaves it in state='running', this returns Some(RunState::Running) and the original worker will keep running the job, defeating the lease mechanism. Consider checking the fetched row’s worker_id against the caller’s worker_id and returning None on mismatch (or returning a distinct value) so executors reliably stop when ownership is lost.

Copilot uses AI. Check for mistakes.
Comment thread src/cli/queue.rs
Comment on lines +273 to +297
/// Execute one claimed row through the shared executor and write the
/// terminal transition.
fn execute_and_finalize(workspace: &Workspace, row: &RunRow, cancel_flag: Arc<AtomicBool>) {
let result = execute_with_heartbeat(workspace, row, vec![], Some(cancel_flag));
let conn = match runs::open(workspace) {
Ok(c) => c,
Err(_) => return,
};
match result.terminal {
ExecutionTerminal::Completed => {
let _ = runs::complete(&conn, &row.run_id, result.completion);
}
ExecutionTerminal::Failed | ExecutionTerminal::Errored => {
let _ = runs::fail(&conn, &row.run_id, result.completion);
}
ExecutionTerminal::TimedOut => {
let _ = runs::time_out(&conn, &row.run_id, result.completion);
}
ExecutionTerminal::Cancelled => {
// The cancel transition was already written by the
// heartbeat-detection path (or is being written now). Either
// way, record the captured stdout/stderr on the cancelled
// row.
let _ = runs::record_cancelled_output(&conn, &row.run_id, result.completion);
}

Copilot AI Apr 12, 2026

Copy link

Choose a reason for hiding this comment

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

The worker passes its SIGINT/SIGTERM cancel_flag into execute_with_heartbeat, which treats it as an immediate cancellation signal (kills the child). This both contradicts the stated “finish in-flight job before exit” behavior and can leave the DB row stuck in state='running' because the executor doesn’t transition it to cancelled (and record_cancelled_output only updates rows already in cancelled). Consider not passing the shutdown flag into the executor (stop claiming new work but let current job finish), or explicitly transitioning the run to cancelled (via runs::cancel) before recording output if shutdown should cancel.

Copilot uses AI. Check for mistakes.
Comment thread src/cli/help_ai.rs
Comment on lines +89 to +93
data_shapes: json!({
"scripts": crate::cli::describe::sample_envelope(), // describe shape doubles as a per-script summary example
"describe": crate::cli::describe::sample_envelope(),
"search": crate::cli::describe::sample_envelope(),
"run": json!({

Copilot AI Apr 12, 2026

Copy link

Choose a reason for hiding this comment

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

help-ai advertises scripts and search data shapes using describe::sample_envelope(), but scripts --json and search --json actually return a list of ScriptListEntry (absolute_path/relative_path/field_count/schema_error/etc.). This mismatch can cause agents to generate incorrect parsers. Consider providing accurate sample envelopes per verb (or referencing the real structs) so the discovery payload matches the actual JSON contract.

Copilot uses AI. Check for mistakes.
Comment thread src/cli/search.rs
Comment on lines +20 to +26
// The search index normally rebuilds in the background from the TUI.
// For one-shot CLI use we trigger the rebuild and block on it so the
// results are always fresh, even on a workspace that has never opened
// the TUI.
index.start_background_rebuild(workspace.root().to_path_buf());
block_until_ready(&index);

Copilot AI Apr 12, 2026

Copy link

Choose a reason for hiding this comment

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

The comment says the CLI search “block[s] on [rebuild] so the results are always fresh”, but block_until_ready returns after ~5s even if the index is still Indexing, and it also proceeds even when status is Error. Either adjust the comment to reflect best-effort freshness, or make the behavior deterministic (e.g. return an error if not Ready within the timeout, or wait until Ready when invoked from the CLI).

Copilot uses AI. Check for mistakes.
Comment thread src/cli/history.rs
Comment on lines +372 to +394
/// Parse a relative-duration string like `30s`, `15m`, `2h`, `7d` into
/// milliseconds. Returns an error message string on parse failure.
pub fn parse_duration_to_ms(s: &str) -> Result<i64, String> {
let s = s.trim();
if s.is_empty() {
return Err("empty duration".into());
}
let (digits, unit) = s.split_at(s.len() - 1);
let unit_char = unit.chars().next().ok_or("missing unit")?;
let value: i64 = digits
.parse()
.map_err(|_| format!("invalid duration value: {}", s))?;
let multiplier = match unit_char {
's' => 1_000_i64,
'm' => 60 * 1_000,
'h' => 60 * 60 * 1_000,
'd' => 24 * 60 * 60 * 1_000,
_ => return Err(format!("invalid duration unit: {}", unit_char)),
};
value
.checked_mul(multiplier)
.ok_or_else(|| format!("duration overflow: {}", s))
}

Copilot AI Apr 12, 2026

Copy link

Choose a reason for hiding this comment

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

history list uses a custom parse_duration_to_ms that only supports a single trailing unit (e.g. 15m) while queue add --timeout uses humantime, which supports richer formats. This inconsistency makes the CLI harder to reason about and can surprise callers. Consider reusing humantime::parse_duration here as well (and reject negative durations explicitly), so duration flags behave consistently across commands.

Copilot uses AI. Check for mistakes.
Comment thread src/main.rs
Comment on lines +162 to +168
fn main() {
if let Err(err) = run() {
// Top-level errors are rendered via Display so users see the
// configured error messages instead of Rust's `Debug` rendering.
eprintln!("error: {err}");
std::process::exit(1);
}

Copilot AI Apr 12, 2026

Copy link

Choose a reason for hiding this comment

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

With the new --json contract, any uncaught error that bubbles up to main() will currently print error: ... to stderr and exit without emitting a JSON envelope on stdout. Several verbs still use ? for I/O paths (e.g. ensure_layout, listing scripts), so this can violate the “stable envelope on every verb” guarantee. Consider making the top-level error handler aware of cli.json (print json::print_err(...) to stdout and suppress stderr) or ensuring each AI verb catches and envelopes all error paths.

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