Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,28 @@

Minimal root README. Full developer & architecture guide: see [`CLAUDE.md`](CLAUDE.md). Assistants should read [`AGENTS.md`](AGENTS.md).

## Quick Start

Status varies by STT backend and platform. For current “what works” details, see [`docs/plans/critical-action-plan.md`](docs/plans/critical-action-plan.md).

```bash
# Main app
just run

# TUI dashboard
just tui
```

Common Rust commands:

```bash
# Fast local feedback
cargo check -p coldvox-app

# Format check
cargo fmt --all -- --check
```

## Development

### Developer Git Hooks
Expand Down
6 changes: 3 additions & 3 deletions docs/MasterDocumentationPlaybook.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,13 @@ The following files configure AI coding agents and MUST live at standard locatio

- `AGENTS.md` (root): Canonical agent instructions following the [AGENTS.md standard](https://agents.md/). This is the single source of truth for all AI agents.
- `CLAUDE.md` (root): Claude Code configuration. Should import from or reference `AGENTS.md`.
- `.github/copilot-instructions.md`: GitHub Copilot instructions. Must match `AGENTS.md` (locally hardlinked where possible).
- `.kilocode/rules/agents.md`: Kilo Code rules. Must match `AGENTS.md` (locally hardlinked where possible).
- `.github/copilot-instructions.md`: GitHub Copilot instructions. Must match `AGENTS.md` (hardlink preferred; symlink/copy acceptable where needed).
- `.kilocode/rules/agents.md`: Kilo Code rules. Must match `AGENTS.md` (hardlink preferred; symlink/copy acceptable where needed).
- `.gemini/settings.json`: Gemini CLI configuration. Set `"contextFileName": "AGENTS.md"` to use root AGENTS.md.
- `.cursorrules` (root, optional): Cursor-specific rules if needed beyond `AGENTS.md`.
- `.builderrules` (root, optional): Builder.io-specific rules if needed.

**Hierarchy**: `AGENTS.md` is authoritative. Tool-specific files should either be hardlinked mirrors (preferred) or contain only tool-specific overrides that reference `AGENTS.md`.
**Hierarchy**: `AGENTS.md` is authoritative. Tool-specific files should either mirror it (hardlink preferred; symlink/copy acceptable) or contain only tool-specific overrides that reference `AGENTS.md`.

**Do NOT create** `docs/agents.md` - agent configuration lives at the root for tool discovery.

Expand Down
5 changes: 1 addition & 4 deletions docs/dev/CI/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ jobs:
- run: cargo deny check

# ═══════════════════════════════════════════════════════════════
# SELF-HOSTED: THE build, THE tests (runs immediately, no waiting)
# SELF-HOSTED: Hardware/display-only tests (runs immediately, no waiting)
# ═══════════════════════════════════════════════════════════════

hardware:
Expand All @@ -235,9 +235,6 @@ jobs:
shared-key: "nobara-hardware"
cache-on-failure: true

- name: Build (cached, incremental)
run: cargo build -p coldvox-text-injection -p coldvox-app --locked

- name: Validate display environment
run: |
echo "DISPLAY=$DISPLAY"
Expand Down
53 changes: 43 additions & 10 deletions scripts/ensure_agent_hardlinks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,19 @@
set -euo pipefail

quiet=0
if [[ "${1:-}" == "--quiet" ]]; then
quiet=1
fi
require_hardlink=0

for arg in "$@"; do
case "$arg" in
--quiet) quiet=1 ;;
--require-hardlink) require_hardlink=1 ;;
*)
echo "error: unknown arg: $arg" >&2
echo "usage: $0 [--quiet] [--require-hardlink]" >&2
exit 64
;;
esac
done

repo_root="$({
cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd
Expand Down Expand Up @@ -54,21 +64,40 @@ link_count_of() {
return 1
}

link_or_copy() {
link_or_symlink_or_copy() {
local dst="$1"

# Remove symlinks explicitly; ln -f replaces regular files but not all symlinks reliably.
if [[ -L "$dst" ]]; then
rm -f "$dst"
fi

# 1) Prefer hardlink.
if ln -f "$src" "$dst" 2>/dev/null; then
return 0
fi

# If hardlinking fails (e.g., cross-filesystem), keep content correct but warn.
# 2) Fallback to symlink (works across filesystems).
local rel
rel="$(python - <<'PY'
import os
import sys
src = sys.argv[1]
dst = sys.argv[2]
print(os.path.relpath(src, os.path.dirname(dst)))
PY
"$src" "$dst" 2>/dev/null || true)"
Comment on lines +82 to +89
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The Python heredoc is missing the arguments to the Python interpreter. The command should pass arguments to the Python script, but line 89 starts with "$src" "$dst" which will be interpreted as shell commands rather than arguments to Python.

The Python script expects sys.argv[1] and sys.argv[2], but these won't be available. This should be:

rel="$(python - "$src" "$dst" <<'PY'

This ensures the arguments are passed to Python correctly.

Suggested change
rel="$(python - <<'PY'
import os
import sys
src = sys.argv[1]
dst = sys.argv[2]
print(os.path.relpath(src, os.path.dirname(dst)))
PY
"$src" "$dst" 2>/dev/null || true)"
rel="$(
python - "$src" "$dst" <<'PY' 2>/dev/null
import os
import sys
src = sys.argv[1]
dst = sys.argv[2]
print(os.path.relpath(src, os.path.dirname(dst)))
PY
)" || true

Copilot uses AI. Check for mistakes.

if [[ -n "$rel" ]]; then
if ln -sf "$rel" "$dst" 2>/dev/null; then
echo "warning: could not hardlink $dst; created symlink instead" >&2
return 0
fi
fi

# 3) Last resort: copy contents.
cp -f "$src" "$dst"
echo "warning: could not hardlink $dst; copied contents instead" >&2
echo "warning: could not hardlink or symlink $dst; copied contents instead" >&2
return 0
Comment on lines +93 to 101
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The warning message for symlink fallback (line 93) writes to stderr, but the successful hardlink case (line 76) doesn't produce any output. However, the final "AGENTS mirrors hardlinked" message (lines 148-150) may be misleading when symlinks or copies are used instead.

Consider checking if symlinks or copies were used and adjusting the final message accordingly, or emit warnings through the say() function so they respect the --quiet flag. Currently, fallback warnings always appear even with --quiet, which may be unintended.

Copilot uses AI. Check for mistakes.
}

Expand All @@ -91,17 +120,21 @@ is_hardlinked_pair() {
ensure_pair() {
local dst="$1"

link_or_copy "$dst"
link_or_symlink_or_copy "$dst"

if ! cmp -s "$src" "$dst"; then
echo "error: $dst does not match $src" >&2
exit 1
fi

if ! is_hardlinked_pair "$src" "$dst"; then
echo "error: $dst is not hardlinked to $src (same contents, different inode)" >&2
echo "hint: ensure both files are on the same filesystem; rerun scripts/ensure_agent_hardlinks.sh" >&2
exit 2
if [[ "$require_hardlink" -eq 1 ]]; then
echo "error: $dst is not hardlinked to $src (same contents, different inode)" >&2
echo "hint: ensure both files are on the same filesystem; rerun scripts/ensure_agent_hardlinks.sh" >&2
exit 2
fi

echo "warning: $dst is not hardlinked to $src (content matches)" >&2
fi
}

Expand Down
22 changes: 18 additions & 4 deletions scripts/install_git_hardlink_hooks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,19 @@
set -euo pipefail

quiet=0
if [[ "${1:-}" == "--quiet" ]]; then
quiet=1
fi
require_hardlink=0

for arg in "$@"; do
case "$arg" in
--quiet) quiet=1 ;;
--require-hardlink) require_hardlink=1 ;;
*)
echo "error: unknown arg: $arg" >&2
echo "usage: $0 [--quiet] [--require-hardlink]" >&2
exit 64
;;
esac
done

repo_root="$({
cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd
Expand Down Expand Up @@ -35,4 +45,8 @@ if [[ "$quiet" -eq 0 ]]; then
fi
fi

"$repo_root/scripts/ensure_agent_hardlinks.sh" ${quiet:+--quiet}
ensure_args=()
if [[ "$quiet" -eq 1 ]]; then ensure_args+=(--quiet); fi
if [[ "$require_hardlink" -eq 1 ]]; then ensure_args+=(--require-hardlink); fi

"$repo_root/scripts/ensure_agent_hardlinks.sh" "${ensure_args[@]}"
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

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

The array expansion "${ensure_args[@]}" will fail with an "unbound variable" error when the array is empty due to set -euo pipefail. When neither --quiet nor --require-hardlink is set, ensure_args will be empty, and accessing it with @ will trigger the error.

Use this pattern instead:

"$repo_root/scripts/ensure_agent_hardlinks.sh" ${ensure_args[@]+"${ensure_args[@]}"}

This safely expands to nothing when the array is empty.

Suggested change
"$repo_root/scripts/ensure_agent_hardlinks.sh" "${ensure_args[@]}"
"$repo_root/scripts/ensure_agent_hardlinks.sh" ${ensure_args[@]+"${ensure_args[@]}"}

Copilot uses AI. Check for mistakes.
Loading