@mirnoorata/codexa 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +93 -29
  2. package/dist/cli/hooks.js +11 -6
  3. package/dist/cli/hooks.js.map +1 -1
  4. package/dist/cli.js +13 -4
  5. package/dist/cli.js.map +1 -1
  6. package/dist/implicit-baseline.d.ts +8 -0
  7. package/dist/implicit-baseline.js +94 -0
  8. package/dist/implicit-baseline.js.map +1 -0
  9. package/dist/init.d.ts +3 -0
  10. package/dist/init.js +124 -15
  11. package/dist/init.js.map +1 -1
  12. package/dist/mcp/compaction.d.ts +1 -0
  13. package/dist/mcp/compaction.js +24 -0
  14. package/dist/mcp/compaction.js.map +1 -1
  15. package/dist/mcp/envelope.d.ts +4 -1
  16. package/dist/mcp/envelope.js +45 -5
  17. package/dist/mcp/envelope.js.map +1 -1
  18. package/dist/mcp/prompts.d.ts +1 -1
  19. package/dist/mcp/prompts.js +5 -2
  20. package/dist/mcp/prompts.js.map +1 -1
  21. package/dist/mcp/tool-registry.d.ts +1 -0
  22. package/dist/mcp/tool-registry.js +5 -0
  23. package/dist/mcp/tool-registry.js.map +1 -1
  24. package/dist/mcp/tools.d.ts +1 -0
  25. package/dist/mcp/tools.js +6 -0
  26. package/dist/mcp/tools.js.map +1 -1
  27. package/dist/mcp-tool-catalog.d.ts +1 -1
  28. package/dist/mcp-tool-catalog.js +1 -1
  29. package/dist/mcp-tool-catalog.js.map +1 -1
  30. package/dist/mcp.js +10 -5
  31. package/dist/mcp.js.map +1 -1
  32. package/dist/query/post-edit/decision.d.ts +1 -0
  33. package/dist/query/post-edit/decision.js +13 -4
  34. package/dist/query/post-edit/decision.js.map +1 -1
  35. package/dist/query/post-edit.js +10 -2
  36. package/dist/query/post-edit.js.map +1 -1
  37. package/dist/task-snapshots.js +29 -0
  38. package/dist/task-snapshots.js.map +1 -1
  39. package/dist/types.d.ts +2 -0
  40. package/dist/types.js.map +1 -1
  41. package/integrations/.claude-plugin/marketplace.json +23 -0
  42. package/integrations/claude-code/.claude-plugin/plugin.json +16 -0
  43. package/integrations/claude-code/.mcp.json +8 -0
  44. package/integrations/claude-code/README.md +177 -0
  45. package/integrations/claude-code/commands/codexa-brief.md +14 -0
  46. package/integrations/claude-code/commands/codexa-impact.md +14 -0
  47. package/integrations/claude-code/commands/codexa-plan.md +20 -0
  48. package/integrations/claude-code/commands/codexa-review.md +23 -0
  49. package/integrations/claude-code/commands/codexa-status.md +10 -0
  50. package/integrations/claude-code/hooks/hooks.json +39 -0
  51. package/integrations/claude-code/scripts/cmd/brief.sh +18 -0
  52. package/integrations/claude-code/scripts/cmd/impact.sh +35 -0
  53. package/integrations/claude-code/scripts/cmd/lib.sh +136 -0
  54. package/integrations/claude-code/scripts/cmd/plan.sh +52 -0
  55. package/integrations/claude-code/scripts/cmd/review.sh +66 -0
  56. package/integrations/claude-code/scripts/cmd/status.sh +52 -0
  57. package/integrations/claude-code/scripts/codexa-mcp.js +111 -0
  58. package/integrations/claude-code/scripts/lib/codexa-repo.sh +773 -0
  59. package/integrations/claude-code/scripts/pre-edit.sh +116 -0
  60. package/integrations/claude-code/scripts/session-start.sh +201 -0
  61. package/integrations/claude-code/scripts/stop.sh +443 -0
  62. package/integrations/claude-code/tests/cmd-smoke.sh +310 -0
  63. package/integrations/claude-code/tests/hook-smoke.sh +1412 -0
  64. package/package.json +4 -2
  65. package/plugins/codexa/.codex-plugin/plugin.json +1 -1
@@ -0,0 +1,177 @@
1
+ # Codexa for Claude Code
2
+
3
+ Ships Codexa's edit-safety loop into Claude Code the way it already ships into
4
+ Codex. One install, both tools wired to the same engine and the same
5
+ `<repo>/.codex/` state.
6
+
7
+ ## What it does
8
+
9
+ When Claude Code is running in a Codexa-wired repo (one that contains
10
+ `<repo>/.codex/config.toml`), this plugin:
11
+
12
+ - **MCP server** — exposes the Codexa query tools (`task_brief`,
13
+ `change_plan`, `post_edit_review`, `impact`, `search`, …) directly to
14
+ Claude through the plugin's `.mcp.json`. The launcher resolves the repo
15
+ from the session's project directory and the CLI from `CODEXA_CLI`, the
16
+ package's own `dist/cli.js`, or a global install.
17
+ - **SessionStart** — injects a short Codexa freshness status and the top
18
+ read-first files from `.codex/codebase/README.md` into Claude's session
19
+ context.
20
+ - **PreToolUse** — before `Edit`/`Write`/`MultiEdit`/`NotebookEdit` lands on a
21
+ file inside the wired repo, saves an implicit pre-edit baseline via
22
+ `codexa hook-pre-edit` when no change-plan snapshot exists yet, so the
23
+ post-edit drift review always has a pre-edit reference. Falls back to an
24
+ advisory nudge when the CLI is unavailable. Never blocks the edit.
25
+ - **Stop** — at the end of every assistant turn, if a snapshot exists and
26
+ has not been reviewed on this session yet, runs `codexa post-edit-review`
27
+ and prints the drift summary to stderr. When the review ran against an
28
+ **explicit** `change_plan` snapshot and the verdict is `replan` or a
29
+ blocking `inspect`, the summary is surfaced to the model through the Stop
30
+ hook's `{"decision":"block","reason":…}` contract so Claude can act on the
31
+ drift. Reviews against hook-saved implicit baselines never block — saving
32
+ a plan is the opt-in — and neither do parent-scan reviews of other
33
+ workspace repos: only the repo the session is working inside can block.
34
+ Clean and advisory verdicts stay quiet. Debounced
35
+ per session+repo+dirty-tree state, with a `stop_hook_active` re-entrancy
36
+ guard, so it blocks at most once per stop and never loops. Set
37
+ `CLAUDIO_STOP_BLOCK=0` for stderr-only behavior.
38
+
39
+ Slash commands available to Claude:
40
+
41
+ | Command | Wraps |
42
+ | ------------------ | ------------------------------------ |
43
+ | `/codexa-status` | `codexa status <repo>` |
44
+ | `/codexa-brief` | `codexa brief <repo> --diff` |
45
+ | `/codexa-plan` | `codexa change-plan --save-snapshot` |
46
+ | `/codexa-review` | `codexa post-edit-review` |
47
+ | `/codexa-impact` | `codexa impact` / `diff-impact` |
48
+
49
+ Current Codexa packets are proof-carrying. Impact and symbol lookups can include
50
+ edge evidence, confidence labels, stale/degraded flags, and structured
51
+ `nextTools` entries that name the next read-only or cache-writing Codexa call.
52
+ Post-edit review compares against planned-test provenance from the saved
53
+ snapshot, degrades legacy or scope-mismatched tests, and persists compact local
54
+ outcomes that may visibly influence future ranking/test recommendations.
55
+
56
+ ## Thin Adapter Contract
57
+
58
+ Claude Code commands and hooks are adapters over the shared Codexa engine.
59
+ They do not maintain a separate index, ranking layer, planner, or source-editing
60
+ path. The primary Codexa path stays:
61
+
62
+ ```text
63
+ session_context -> task_brief -> change_plan(saveSnapshot) -> post_edit_review -> test_plan
64
+ ```
65
+
66
+ Use `symbol_context`, `impact`, `callers`, and `callees` when Claude needs to
67
+ audit who uses a symbol, what may break, and which tests are relationship-backed.
68
+ For non-TypeScript/JavaScript/Python repositories, the shared engine can consume
69
+ `CodexaSymbolReportV1` reports through
70
+ `codexa static-analysis <repo> --symbol-report <path>` and labels those
71
+ relationships as report-backed derived evidence.
72
+
73
+ The adapter may write Codexa-owned `.codex/cache/` state through the CLI, but it
74
+ must not introduce source-mutating MCP tools or host-only behavior that bypasses
75
+ the shared Codexa MCP/CLI contract.
76
+
77
+ ## Install
78
+
79
+ Treat `codexa/integrations/` as a local Claude Code plugin marketplace. It
80
+ ships inside the npm package, so both a git checkout and an npm install work
81
+ as the marketplace source.
82
+
83
+ ### Quick, supported path (persistent)
84
+
85
+ From a Claude Code session:
86
+
87
+ ```text
88
+ /plugin marketplace add <codexa-root>/integrations
89
+ /plugin install codexa@codexa-integrations
90
+ ```
91
+
92
+ `<codexa-root>` is either a local checkout (example: `~/code/codexa`) or the
93
+ installed npm package root — for a global install that is
94
+ `$(npm root -g)/@mirnoorata/codexa`.
95
+
96
+ Under the hood, `<codexa-root>/integrations/.claude-plugin/marketplace.json`
97
+ registers this directory as the `codexa-integrations` marketplace, and the
98
+ plugin `codexa` lives at `./claude-code` relative to that manifest. After
99
+ install, restart Claude Code so the MCP server and the SessionStart,
100
+ PreToolUse, and Stop hooks load.
101
+
102
+ ### MCP-only alternative (no plugin)
103
+
104
+ If you only want the Codexa tools (no hooks or slash commands), skip the
105
+ plugin and run `codexa init <repo> --claude` instead — it writes the codexa
106
+ MCP server into the repo's `.mcp.json`. Use the plugin **or** `init
107
+ --claude`, not both, to avoid registering the server twice.
108
+
109
+ ### Development (per-session)
110
+
111
+ No install, one-shot:
112
+
113
+ ```bash
114
+ claude --plugin-dir <codexa-checkout>/integrations/claude-code
115
+ ```
116
+
117
+ ## Requirements
118
+
119
+ - Node.js >= 22 on `$PATH` (override with `CLAUDIO_NODE_BIN`)
120
+ - Codexa must be locatable one of three ways (tried in this order):
121
+ 1. `CODEXA_CLI` env var set to an absolute path to `dist/cli.js`
122
+ 2. `<codexa-checkout>/dist/cli.js` auto-detected from the plugin's own
123
+ location (works when the plugin is loaded via `--plugin-dir
124
+ <codexa-checkout>/integrations/claude-code`)
125
+ 3. `codexa` on `$PATH` (works after the public package is published and the
126
+ user ran `npm install -g @mirnoorata/codexa`; recommended when the plugin
127
+ is installed via `/plugin marketplace add`, which copies the plugin out of
128
+ the source checkout)
129
+ - `awk`, `python3`, `shasum` (or `md5sum`). GNU coreutils `timeout` is used
130
+ when present; on stock macOS (bash 3.2, no `timeout`) the hooks fall back
131
+ to a `python3` timeout wrapper, so no Homebrew packages are required.
132
+
133
+ ## Configuration
134
+
135
+ Environment variables the hooks honor:
136
+
137
+ | Variable | Default | Purpose |
138
+ | ---------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------- |
139
+ | `CODEXA_CLI` | `<codexa-checkout>/dist/cli.js` (auto) | Path to the built CLI |
140
+ | `CLAUDIO_NODE_BIN` | `node` on `$PATH` | Node binary to run the CLI |
141
+ | `CLAUDIO_DEBUG` | unset | Set to `1` for `[claudio]` stderr traces |
142
+ | `CLAUDIO_STOP_BLOCK` | `1` | Set to `0` to keep drift verdicts stderr-only (never block) |
143
+ | `CODEXA_REPO` | session project dir | Repository the plugin MCP server serves |
144
+ | `CODEXA_PLUGIN_TOOLS` | `core` | MCP tool profile served by the plugin (`full` exposes all 20 tools) |
145
+ | `CODEXA_PLUGIN_AUTO_REFRESH` | `1` | Set to `0` to stop the MCP server refreshing stale indexes |
146
+ | `CODEXA_PLUGIN_ALLOW_NPX_FALLBACK` | unset | Set to `1` to let the MCP launcher fall back to `npx -y @mirnoorata/codexa` |
147
+
148
+ ## Safety properties
149
+
150
+ - Every hook has a hard Claude hook timeout (SessionStart 6s, PreToolUse 10s,
151
+ Stop 35s). The shell scripts also wrap Codexa CLI calls with shorter
152
+ subprocess budgets (`timeout(1)` or the python3 fallback).
153
+ - Every hook exits 0 on any error — Claude sessions are never blocked by a
154
+ Codexa outage. The Stop hook's drift block is a JSON decision on a clean
155
+ exit, gated to replan/blocking-inspect verdicts parsed against a strict
156
+ enum allowlist; raw CLI output never flows into the block reason.
157
+ - Hooks never write to the user's repo. Codexa's own `.codex/cache/` state
158
+ is managed by the CLI, not the hooks.
159
+ - Repo detection refuses to climb above `$HOME` or treat `/` as a wired repo.
160
+ - Re-entrancy guard on the Stop hook via `stop_hook_active`.
161
+ - Slash-command argument parsing routes through `python3 shlex.split` — no
162
+ `eval`, no word-splitting of user input — and `/codexa-review` allowlists
163
+ the flags that can reach the CLI.
164
+
165
+ ## Testing
166
+
167
+ ```bash
168
+ bash integrations/claude-code/tests/hook-smoke.sh
169
+ bash integrations/claude-code/tests/cmd-smoke.sh
170
+ ```
171
+
172
+ Hook smoke: non-wired cwd, empty/malformed payloads, read-first extraction,
173
+ snapshot presence/absence, `MultiEdit`/`NotebookEdit` dispatch, relative-path
174
+ rejection, Stop debouncing, re-entrancy, failed post-edit passthrough.
175
+
176
+ Command smoke: `shlex` parsing of quoted tasks and paths with spaces,
177
+ unknown-flag rejection, path-traversal-like tokens, empty arguments.
@@ -0,0 +1,14 @@
1
+ ---
2
+ description: Get a Codexa task brief for the current dirty tree + a user task
3
+ argument-hint: "<task description>"
4
+ disable-model-invocation: true
5
+ allowed-tools: Bash(bash:*)
6
+ ---
7
+
8
+ Ask Codexa for a focused task brief. This is the first call before any
9
+ non-trivial codexa-wired edit. It bundles impact, risks, covering tests,
10
+ freshness, read-first files, relationship evidence where available, quality
11
+ signals, and structured `nextTools` for the stated task plus the existing dirty
12
+ diff.
13
+
14
+ !`bash "${CLAUDE_PLUGIN_ROOT}/scripts/cmd/brief.sh" "$ARGUMENTS"`
@@ -0,0 +1,14 @@
1
+ ---
2
+ description: Show Codexa blast-radius impact for a file/symbol, or diff-impact when no argument
3
+ argument-hint: "[path or symbol]"
4
+ disable-model-invocation: true
5
+ allowed-tools: Bash(bash:*)
6
+ ---
7
+
8
+ Show Codexa impact evidence. With an argument, query `impact` for that file or
9
+ symbol. Without an argument, show `diff-impact` for the current dirty tree.
10
+ Relationship-backed results may include edge evidence ids, confidence labels,
11
+ stale/degraded flags, and structured next Codexa tools for symbol context,
12
+ change planning, or targeted tests.
13
+
14
+ !`bash "${CLAUDE_PLUGIN_ROOT}/scripts/cmd/impact.sh" "$ARGUMENTS"`
@@ -0,0 +1,20 @@
1
+ ---
2
+ description: Save a Codexa change-plan snapshot before editing concrete files
3
+ argument-hint: '"<task>" [file ...]'
4
+ allowed-tools: Bash(bash:*)
5
+ ---
6
+
7
+ Save a Codexa change-plan snapshot so the post-edit review can compute drift
8
+ afterward. Quote the task so it is parsed as a single argument, then list the
9
+ files you intend to edit. The saved snapshot includes planned-test provenance,
10
+ verification recipes, freshness, and dirty-file hashes so the later review can
11
+ distinguish trusted coverage from degraded legacy or stale evidence.
12
+
13
+ Examples:
14
+
15
+ ```
16
+ /codexa-plan "fix auth bug" src/auth.py
17
+ /codexa-plan "redesign frame header" web/src/App.tsx web/src/styles.css
18
+ ```
19
+
20
+ !`bash "${CLAUDE_PLUGIN_ROOT}/scripts/cmd/plan.sh" "$ARGUMENTS"`
@@ -0,0 +1,23 @@
1
+ ---
2
+ description: Run Codexa post-edit review against the saved change-plan snapshot
3
+ argument-hint: "[--change-type <type>] [--ran-test <path> ...] [--ran-command <cmd> ...]"
4
+ allowed-tools: Bash(bash:*)
5
+ ---
6
+
7
+ Compare the current dirty tree against the saved Codexa change-plan snapshot.
8
+ Reports tests still unaccounted for, drift signals, and known gaps. Saved
9
+ planned tests now carry provenance; legacy, stale, or scope-mismatched snapshot
10
+ tests are reported as degraded instead of silently trusted. Passing structured
11
+ command reports lets Codexa account for verification and persist compact local
12
+ outcomes for future visible ranking/test boosts.
13
+
14
+ Allowlisted flags (others are rejected): `--change-type`, `--ran-test`, `--ran-command`, `--ran-command-report`, `--waive-check`, `--waiver`, `--file`, `--symbol`, `--budget`, `--limit`, `--snippets`, `--no-snippets`, `--auto-refresh`, `--no-auto-refresh`, `--task-id`.
15
+
16
+ Common use:
17
+
18
+ ```
19
+ /codexa-review --change-type style
20
+ /codexa-review --change-type behavior --ran-test tests/test_queue.py --ran-command "pytest tests/"
21
+ ```
22
+
23
+ !`bash "${CLAUDE_PLUGIN_ROOT}/scripts/cmd/review.sh" "$ARGUMENTS"`
@@ -0,0 +1,10 @@
1
+ ---
2
+ description: Show Codexa index freshness for the current repo
3
+ argument-hint: ""
4
+ disable-model-invocation: true
5
+ allowed-tools: Bash(bash:*)
6
+ ---
7
+
8
+ Show the Codexa index freshness, commit, indexed-at, and dirty-file count for the repo you are focused on. Return the output verbatim.
9
+
10
+ !`bash "${CLAUDE_PLUGIN_ROOT}/scripts/cmd/status.sh"`
@@ -0,0 +1,39 @@
1
+ {
2
+ "description": "Codexa integration for Claude Code.",
3
+ "hooks": {
4
+ "SessionStart": [
5
+ {
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/session-start.sh\"",
10
+ "timeout": 6
11
+ }
12
+ ]
13
+ }
14
+ ],
15
+ "PreToolUse": [
16
+ {
17
+ "matcher": "Edit|Write|MultiEdit|NotebookEdit",
18
+ "hooks": [
19
+ {
20
+ "type": "command",
21
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/pre-edit.sh\"",
22
+ "timeout": 10
23
+ }
24
+ ]
25
+ }
26
+ ],
27
+ "Stop": [
28
+ {
29
+ "hooks": [
30
+ {
31
+ "type": "command",
32
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/scripts/stop.sh\"",
33
+ "timeout": 35
34
+ }
35
+ ]
36
+ }
37
+ ]
38
+ }
39
+ }
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+ set -u
3
+ CMD_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
4
+ # shellcheck source=lib.sh
5
+ . "$CMD_DIR/lib.sh"
6
+
7
+ raw_args="${1-}"
8
+ if [[ -z "$raw_args" ]]; then
9
+ printf 'Usage: /codexa-brief <task description>\n' >&2
10
+ exit 2
11
+ fi
12
+
13
+ # Task may include shell metacharacters; treat the whole string as the task.
14
+ task="$raw_args"
15
+
16
+ repo="$(cmd_require_codexa_repo)" || exit 1
17
+ cmd_require_codexa_cli
18
+ exec "$NODE_BIN" "$CODEXA_CLI" brief "$repo" --task "$task" --diff
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env bash
2
+ set -u
3
+ CMD_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
4
+ # shellcheck source=lib.sh
5
+ . "$CMD_DIR/lib.sh"
6
+
7
+ raw_args="${1-}"
8
+
9
+ repo="$(cmd_require_codexa_repo)" || exit 1
10
+ cmd_require_codexa_cli
11
+
12
+ if [[ -z "$raw_args" ]]; then
13
+ exec "$NODE_BIN" "$CODEXA_CLI" diff-impact "$repo"
14
+ fi
15
+
16
+ declare -a tokens
17
+ if ! cmd_shlex_split "$raw_args" tokens; then
18
+ exit 2
19
+ fi
20
+
21
+ if [[ ${#tokens[@]} -ne 1 ]]; then
22
+ printf 'Usage: /codexa-impact [path or symbol]\n' >&2
23
+ exit 2
24
+ fi
25
+
26
+ target="${tokens[0]}"
27
+ cmd_validate_path_token "$target"
28
+
29
+ # If the argument resolves to a real file (relative to repo or absolute),
30
+ # treat it as a path; otherwise fall back to --symbol.
31
+ if [[ -e "$repo/$target" ]] || [[ -e "$target" ]]; then
32
+ exec "$NODE_BIN" "$CODEXA_CLI" impact "$repo" --file "$target"
33
+ else
34
+ exec "$NODE_BIN" "$CODEXA_CLI" impact "$repo" --symbol "$target"
35
+ fi
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env bash
2
+ # Shared helpers for the /codexa-* slash-command implementations.
3
+ #
4
+ # Every slash-command `.md` file passes the raw "$ARGUMENTS" string as the
5
+ # single first positional argument. We do NOT eval it or let the shell
6
+ # word-split it — shlex handles quoting and shell metacharacters safely.
7
+
8
+ set -u
9
+
10
+ CMD_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
11
+ CMD_LIB_INTEGRATION_ROOT="$(cd "$CMD_LIB_DIR/../.." && pwd -P)"
12
+ # shellcheck source=../lib/codexa-repo.sh
13
+ . "$CMD_LIB_INTEGRATION_ROOT/scripts/lib/codexa-repo.sh"
14
+
15
+ # Populate a bash array from shell-like tokenization of a single string.
16
+ # Usage: cmd_shlex_split "quoted string \"with escapes\"" arr_name
17
+ # After the call, ${arr_name[@]} holds the parsed tokens. Tokens may include
18
+ # newlines/tabs — we use a NUL delimiter end-to-end. Returns 2 on malformed
19
+ # input (unbalanced quotes, etc.), with the error written to stderr.
20
+ cmd_shlex_split() {
21
+ local raw="${1:-}"
22
+ # No `local -n` nameref: that is bash 4.3+, and stock macOS ships bash
23
+ # 3.2. The array name is allowlist-validated before any eval so untrusted
24
+ # input can never reach the evaluated identifier.
25
+ local arr_name="${2:-}"
26
+ case "$arr_name" in
27
+ "" | *[!a-zA-Z0-9_]*) return 2 ;;
28
+ esac
29
+ eval "$arr_name=()"
30
+ [[ -z "$raw" ]] && return 0
31
+ local tokens_file err_file
32
+ tokens_file="$(mktemp)" || return 2
33
+ err_file="$(mktemp)" || { rm -f "$tokens_file"; return 2; }
34
+ python3 - "$raw" >"$tokens_file" 2>"$err_file" <<'PY'
35
+ import shlex, sys
36
+ try:
37
+ tokens = shlex.split(sys.argv[1])
38
+ except ValueError as exc:
39
+ sys.stderr.write("argument parse error: " + str(exc) + "\n")
40
+ sys.exit(2)
41
+ for t in tokens:
42
+ sys.stdout.write(t)
43
+ sys.stdout.write("\0")
44
+ PY
45
+ local rc=$?
46
+ if [[ $rc -ne 0 ]]; then
47
+ cat "$err_file" >&2
48
+ rm -f "$tokens_file" "$err_file"
49
+ return "$rc"
50
+ fi
51
+ rm -f "$err_file"
52
+ local token
53
+ while IFS= read -r -d '' token; do
54
+ eval "$arr_name+=(\"\$token\")"
55
+ done <"$tokens_file"
56
+ rm -f "$tokens_file"
57
+ return 0
58
+ }
59
+
60
+ # Resolve the target codexa-wired repo for a slash command.
61
+ #
62
+ # Resolution order:
63
+ # (1) Walk up from PWD looking for .codex/config.toml (existing behavior).
64
+ # (2) If no ancestor is wired, scan direct children of PWD for wired repos.
65
+ # If exactly one, auto-pick it and note the choice on stderr so the
66
+ # user sees which repo the command ran against. If more than one,
67
+ # error with the list so the user can disambiguate by cd-ing in.
68
+ #
69
+ # Writes the repo root to stdout and returns 0 on success. On failure writes
70
+ # a diagnostic to stderr and returns 1 — callers use `|| exit 1` in command
71
+ # substitution so the parent script actually exits.
72
+ cmd_require_codexa_repo() {
73
+ local repo
74
+ repo="$(claudio_find_codexa_repo "$PWD")"
75
+ if [[ -n "$repo" ]]; then
76
+ printf '%s' "$repo"
77
+ return 0
78
+ fi
79
+
80
+ local -a children=()
81
+ local line
82
+ while IFS= read -r line; do
83
+ [[ -z "$line" ]] && continue
84
+ children+=("$line")
85
+ done < <(claudio_list_child_codexa_repos "$PWD")
86
+
87
+ case "${#children[@]}" in
88
+ 0)
89
+ printf 'No codexa-wired repo (.codex/config.toml) found from %s.\n' "$PWD" >&2
90
+ return 1
91
+ ;;
92
+ 1)
93
+ printf '[codexa] no wired repo at %s; auto-selected sole child: %s\n' \
94
+ "$PWD" "${children[0]}" >&2
95
+ printf '%s' "${children[0]}"
96
+ return 0
97
+ ;;
98
+ *)
99
+ printf 'Ambiguous codexa target: %s has %d wired child repos.\n' \
100
+ "$PWD" "${#children[@]}" >&2
101
+ printf 'cd into one of these and re-run:\n' >&2
102
+ local child
103
+ for child in "${children[@]}"; do
104
+ printf ' - %s\n' "$child" >&2
105
+ done
106
+ return 1
107
+ ;;
108
+ esac
109
+ }
110
+
111
+ # Require a usable codexa CLI and print a clear error if missing.
112
+ cmd_require_codexa_cli() {
113
+ if ! claudio_codexa_available; then
114
+ printf 'codexa CLI not available at %s (NODE=%s).\n' "$CODEXA_CLI" "$NODE_BIN" >&2
115
+ exit 127
116
+ fi
117
+ }
118
+
119
+ # Reject tokens that look like path-traversal or obvious shell injection
120
+ # before passing them through to --file flags. Call once per user-supplied
121
+ # file path. We do not try to sandbox — just block the dumb cases.
122
+ cmd_validate_path_token() {
123
+ local tok="${1:-}"
124
+ if [[ -z "$tok" ]]; then
125
+ return 0
126
+ fi
127
+ case "$tok" in
128
+ *$'\n'*|*$'\r'*|*$'\t'*)
129
+ printf 'rejecting path with control character: %q\n' "$tok" >&2
130
+ exit 2
131
+ ;;
132
+ esac
133
+ # Allow relative .. under repo root (valid monorepo paths may include it)
134
+ # but disallow an absolute path outside known workspace, home, or repo roots.
135
+ return 0
136
+ }
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env bash
2
+ set -u
3
+ CMD_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
4
+ # shellcheck source=lib.sh
5
+ . "$CMD_DIR/lib.sh"
6
+
7
+ raw_args="${1-}"
8
+ if [[ -z "$raw_args" ]]; then
9
+ cat >&2 <<'USAGE'
10
+ Usage: /codexa-plan "<task>" [file ...]
11
+
12
+ The task must be a quoted string. Additional tokens are treated as file
13
+ paths to snapshot. Examples:
14
+
15
+ /codexa-plan "fix auth bug" src/auth.py
16
+ /codexa-plan "redesign frame header" web/src/App.tsx web/src/styles.css
17
+ USAGE
18
+ exit 2
19
+ fi
20
+
21
+ declare -a tokens
22
+ if ! cmd_shlex_split "$raw_args" tokens; then
23
+ exit 2
24
+ fi
25
+
26
+ if [[ ${#tokens[@]} -eq 0 ]]; then
27
+ printf 'Parsed zero tokens from arguments.\n' >&2
28
+ exit 2
29
+ fi
30
+
31
+ task="${tokens[0]}"
32
+ files=("${tokens[@]:1}")
33
+
34
+ if [[ -z "$task" ]]; then
35
+ printf 'First argument (task description) must be non-empty.\n' >&2
36
+ exit 2
37
+ fi
38
+
39
+ # Validate file tokens BEFORE touching codexa.
40
+ for f in "${files[@]}"; do
41
+ cmd_validate_path_token "$f"
42
+ done
43
+
44
+ repo="$(cmd_require_codexa_repo)" || exit 1
45
+ cmd_require_codexa_cli
46
+
47
+ cli_args=(change-plan "$repo" --task "$task" --save-snapshot)
48
+ for f in "${files[@]}"; do
49
+ cli_args+=(--file "$f")
50
+ done
51
+
52
+ exec "$NODE_BIN" "$CODEXA_CLI" "${cli_args[@]}"
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env bash
2
+ set -u
3
+ CMD_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
4
+ # shellcheck source=lib.sh
5
+ . "$CMD_DIR/lib.sh"
6
+
7
+ raw_args="${1-}"
8
+
9
+ repo="$(cmd_require_codexa_repo)" || exit 1
10
+ cmd_require_codexa_cli
11
+
12
+ if [[ ! -f "$repo/.codex/cache/codexa-tasks/latest.json" ]]; then
13
+ printf 'No change-plan snapshot found at %s/.codex/cache/codexa-tasks/latest.json.\nRun /codexa-plan first.\n' "$repo" >&2
14
+ exit 1
15
+ fi
16
+
17
+ declare -a tokens
18
+ if ! cmd_shlex_split "$raw_args" tokens; then
19
+ exit 2
20
+ fi
21
+
22
+ # Only allow a known flag set through. The CLI has many flags; we allow
23
+ # the post-edit-relevant ones and refuse anything else so a stray argument
24
+ # can't slip a shell metachar or an unknown subcommand.
25
+ allowed_flags=(--change-type --ran-test --ran-command --ran-command-report \
26
+ --waive-check --waiver --file --symbol --budget --limit \
27
+ --snippets --no-snippets --auto-refresh --no-auto-refresh \
28
+ --task-id)
29
+ is_allowed() {
30
+ local candidate="$1"
31
+ for f in "${allowed_flags[@]}"; do
32
+ if [[ "$candidate" == "$f" ]]; then
33
+ return 0
34
+ fi
35
+ done
36
+ return 1
37
+ }
38
+
39
+ i=0
40
+ cli_args=(post-edit-review "$repo")
41
+ while [[ $i -lt ${#tokens[@]} ]]; do
42
+ tok="${tokens[$i]}"
43
+ if [[ "$tok" == --* ]]; then
44
+ if ! is_allowed "$tok"; then
45
+ printf 'refusing unknown flag %q\n' "$tok" >&2
46
+ exit 2
47
+ fi
48
+ cli_args+=("$tok")
49
+ # flags with a value: consume next token too unless it's the start of
50
+ # another flag or we're at end of list.
51
+ if [[ $((i + 1)) -lt ${#tokens[@]} ]]; then
52
+ next="${tokens[$((i + 1))]}"
53
+ if [[ "$next" != --* ]]; then
54
+ cli_args+=("$next")
55
+ i=$((i + 2))
56
+ continue
57
+ fi
58
+ fi
59
+ i=$((i + 1))
60
+ else
61
+ printf 'positional arguments are not supported for /codexa-review: %q\n' "$tok" >&2
62
+ exit 2
63
+ fi
64
+ done
65
+
66
+ exec "$NODE_BIN" "$CODEXA_CLI" "${cli_args[@]}"
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env bash
2
+ # /codexa-status
3
+ #
4
+ # Single-repo case: walks up from PWD for an ancestor with .codex/config.toml
5
+ # and runs `codexa status` on it. That matches the common "terminal is inside
6
+ # the repo" workflow.
7
+ #
8
+ # Multi-repo case: when PWD is above a set of wired children (e.g. an IDE
9
+ # opened at a workspace root with two sibling projects both wired via
10
+ # `codexa init`), we fan out and run `codexa status` on every wired child
11
+ # rather than erroring on ambiguity. The IDE-workspace-root view is
12
+ # "show me everything."
13
+ set -u
14
+ CMD_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
15
+ # shellcheck source=lib.sh
16
+ . "$CMD_DIR/lib.sh"
17
+
18
+ cmd_require_codexa_cli
19
+
20
+ ancestor="$(claudio_find_codexa_repo "$PWD")"
21
+ if [[ -n "$ancestor" ]]; then
22
+ exec "$NODE_BIN" "$CODEXA_CLI" status "$ancestor"
23
+ fi
24
+
25
+ declare -a children=()
26
+ while IFS= read -r line; do
27
+ [[ -z "$line" ]] && continue
28
+ children+=("$line")
29
+ done < <(claudio_list_child_codexa_repos "$PWD")
30
+
31
+ case "${#children[@]}" in
32
+ 0)
33
+ printf 'No codexa-wired repo (.codex/config.toml) found from %s.\n' "$PWD" >&2
34
+ exit 1
35
+ ;;
36
+ 1)
37
+ exec "$NODE_BIN" "$CODEXA_CLI" status "${children[0]}"
38
+ ;;
39
+ *)
40
+ printf '[codexa] no wired repo at %s; fanning out status across %d wired children:\n' \
41
+ "$PWD" "${#children[@]}" >&2
42
+ rc=0
43
+ for child in "${children[@]}"; do
44
+ printf '=== %s ===\n' "$(claudio_display_path "$child")"
45
+ if ! "$NODE_BIN" "$CODEXA_CLI" status "$child"; then
46
+ rc=1
47
+ fi
48
+ printf '\n'
49
+ done
50
+ exit "$rc"
51
+ ;;
52
+ esac