Repository navigation
feat: team rules from a linked Altimate workspace in Codex sessions - #7
Conversation
- Workspace-rules hooks (SessionStart, UserPromptSubmit, Stop) call `altimate-code learn hook <event>`. - Session start gives the agent the workspace's approved team rules, the number of proposals waiting, and new reviews. - Corrections are recorded with no model call. - The post-turn check runs in the background. - The hooks always exit 0, run the per-turn events only where workspace rules state exists, and print only the CLI's hook JSON. - `/altimate-code:rules` answers stored proposals through `learn proposals reply`. - Tests: a stub-CLI test for the hook script, and the resolver test covers the new command and `learn` calls. - README, CHANGELOG and PRIVACY (what the hooks read and send), version 1.2.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| [ -n "$ALTIMATE_BIN" ] || { echo "altimate-code CLI not found (tried altimate-code, altimate)" >&2; exit 127; } | ||
| PROPOSAL_ID='<the proposal id>' | ||
| WORDING="$(cat <<'ALTIMATE_RULE' | ||
| <the new wording> |
There was a problem hiding this comment.
WARNING: Heredoc body picks up the block's 3-space indent
The snippet sits inside a 3-space-indented list item, so this line renders as <the new wording>. cat <<'ALTIMATE_RULE' keeps leading whitespace, so WORDING starts with three spaces: the rule is stored with them and they count against the 140-character limit (possibly triggering invalid_text). Line 52 only says to move the closing ALTIMATE_RULE to column 0 — and if the agent misses that, the indented terminator is not recognised at all, so the heredoc runs to EOF, swallows the learn proposals reply call on line 50, and the script fails with a syntax error. Put the body at column 0 as well (or say "de-indent this whole block before running it"), and state it before the fence rather than after it.
| ``` | ||
| Keep `--edit="$WORDING"` in one argument (a wording that starts with `-` is otherwise read as a flag), and start the closing `ALTIMATE_RULE` line at the beginning of its line. For any other choice, run the same command with that flag instead of `--edit="$WORDING"` (and without the here-doc). | ||
|
|
||
| The JSON answer is `{"ok": true, "message": …, "closed": …}` or `{"ok": false, "code": …, "error": …}`. Show the `message` or `error` to the user as it is. Then: |
There was a problem hiding this comment.
SUGGESTION: Say which field each reply code lives in
Two shapes are given — {"ok": true, "message", "closed"} and {"ok": false, "code", "error"} — but the bullets below list closed: false, verification_flagged, invalid_text, refused, busy and not_open without saying whether they are code values or top-level keys. An agent that looks for verification_flagged as a top-level field will miss it and just print the error, instead of asking for the explicit confirmation that the follow-up --approve-anyway reply depends on. Spell it out: when ok is false, branch on code; when ok is true, check closed.
There was a problem hiding this comment.
Fixed in 1c54845: when ok is false, branch on code; when it is true, check closed.
|
|
||
| - The plugin itself collects **no data**. It is a routing layer (a skill, a slash command, and a static session hook) that tells Codex when to delegate a task to the altimate-code CLI installed on your machine. | ||
| - The plugin itself collects **no data**. It is a routing layer (skills, slash commands, and session hooks) that tells Codex when to delegate a task to the altimate-code CLI installed on your machine. | ||
| - In a project linked to an Altimate workspace with learning on, its workspace-rules hooks hand your messages and the session transcript to the local altimate-code CLI, which keeps corrections on your machine and may send a digest to your LLM provider to word a proposed team rule. A proposal reaches your workspace only when you send or approve it. See [Workspace rules](#workspace-rules). |
There was a problem hiding this comment.
WARNING: Effective date not revised for the new practices
This bullet and the new "Workspace rules" section add data flows (your messages, the session transcript, a redacted digest sent to your LLM provider, proposals and usage counts sent to the workspace), yet **Effective date:** July 18, 2026 stays unchanged. The policy's own "Changes to this policy" section says the effective date is revised "when our practices change", so the document now contradicts itself about which practices that date covers. Add a revision line (e.g. "Revised October 30, 2026") so users can see what changed and when.
| - **Command** — `commands/review.md` — `/altimate-code:review [scope]` explicit-review slash command; scope can be empty (working tree), a commit SHA, a branch name, or a GitHub PR URL / number | ||
| - **Hook** — `hooks/hooks.json` + `hooks/altimate-code-session-start.sh` — SessionStart capability description (static text only; no side effects) | ||
| - **Command** — `commands/rules.md` — `/altimate-code:rules` to answer proposed team rules | ||
| - **Hooks** — `hooks/hooks.json` + `hooks/altimate-code-session-start.sh` — SessionStart capability description (static text only; no side effects); `hooks/altimate-code-workspace-rules.sh` — the [team rules](#team-rules) hooks (SessionStart, UserPromptSubmit, Stop), which call `altimate-code learn hook <event>` and never block or fail the session |
There was a problem hiding this comment.
SUGGESTION: "never block" is not true for session start
session-start is synchronous: hooks.json waits up to 15 s for altimate-code learn hook session-start before the session context is built (the script's own header calls it a "bounded sync"). Only user-prompt-submit and stop are detached. Say "the per-turn hooks never block; session start makes one bounded synchronous call", so a stalled session start from a slow or unreachable CLI is not a surprise.
There was a problem hiding this comment.
Fixed in 1c54845: the per-turn hooks never block; session start makes one bounded call (at most 15s).
| - **"Remember this as a team rule: …"** in a task handed to altimate-code is proposed the same way. | ||
| - **`/altimate-code:rules`** shows the proposals waiting for you, the team rules, and the status of the rules you proposed. For each proposal you choose: send it to the workspace owner's review (or approve it, if you are the owner), edit the wording, save it as a memory, or "Not a rule". Nothing reaches teammates until the owner approves it. | ||
|
|
||
| The same commands work in a terminal: `altimate-code learn proposals`, `altimate-code learn proposals reply <id> --send`. This needs an altimate-code release with workspace rules. |
There was a problem hiding this comment.
SUGGESTION: altimate-code is not on PATH for curl installs
The Prerequisites section and the troubleshooting table both note that curl/PowerShell installs may expose only altimate. A user on that install gets command not found from altimate-code learn proposals. Use the same parenthetical as the rest of the README (altimate learn proposals on installs without altimate-code), or show the altimate form here.
| - Team rules from a linked Altimate workspace apply in Codex sessions. At session start the plugin gives Codex the workspace's approved rules for the repository, the number of proposed rules waiting for you, and the owner's reviews of your proposals since your last session. | ||
| - Corrections you make to Codex are recorded locally (UserPromptSubmit, no model call). After a turn, altimate-code checks them in the background (Stop) and keeps a proposed rule when you corrected the same topic in two sessions or corrected a team rule Codex was given. | ||
| - `/altimate-code:rules`: answer proposed rules (send for review, approve as owner, edit, save as memory, or "Not a rule"), and see the team rules and the review status of your proposals. | ||
| - The hooks run only in a linked project with learning on. They need an altimate-code release with `learn hook`; with an older one they do nothing. |
There was a problem hiding this comment.
SUGGESTION: SessionStart is not gated on the linked project
"The hooks run only in a linked project with learning on" holds for the per-turn events, which gate on .altimate-code/learn/workspace-rules/rules.json. The session-start hook invokes altimate-code learn hook session-start in every project — it has to, in order to learn whether the project is linked — and the linked/learning gates are applied by the CLI, not before the call. Reword to match the code (PRIVACY.md already words this correctly).
| esac | ||
| ;; | ||
| user-prompt-submit|stop) | ||
| ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" |
There was a problem hiding this comment.
SUGGESTION: The gate and the CLI invocation resolve different roots
The existence check uses git rev-parse --show-toplevel, but the CLI on line 40 is spawned with the current directory and no --dir. The two only agree if the CLI independently walks up from $PWD to the same root; if it does not, a session started in a subdirectory (or a dbt project nested inside a monorepo git root) either never fires the per-turn events or fires them somewhere without state. The new test's "works from a subdirectory" case passes only because the stub ignores its cwd. Pass --dir "$ROOT" (or cd "$ROOT" before the spawn) so the gate and the call agree, and have the stub assert the directory it ran in.
| [ "$(hook "$WORK/project" "$event" "$BIN:$SYS")" = "0|" ] || fail "$event without rules state did not exit quietly" | ||
| done | ||
| sleep 0.5 | ||
| [ ! -s "$LOG" ] || fail "per-turn events ran the CLI without workspace rules state: $(cat "$LOG")" |
There was a problem hiding this comment.
SUGGESTION: Fixed 0.5 s sleep makes the negative assertion racy
If the hook wrongly spawns the CLI without workspace-rules state, the stub may not have appended to $LOG within 0.5 s on a loaded machine, so this check passes anyway. Wait deterministically — e.g. have the stub touch a sentinel as its first action and poll for its absence over a longer bounded window, or reuse an await_log-style loop — instead of a single fixed sleep.
There was a problem hiding this comment.
Fixed in 1c54845: the stub records a sentinel as its first action, and the negative checks are made deterministic.
Code Review SummaryStatus: 2 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (7 files)
Reviewed increment Previous Review Summaries (4 snapshots, latest commit 1c54845)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 1c54845)Status: 3 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (10 files)
Reviewed increment Fix these issues in Kilo Cloud Previous review (commit adad91a)Status: 10 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (9 files)
Reviewed increment Fix these issues in Kilo Cloud Previous review (commit 3f97ab6)Status: 9 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (9 files)
Reviewed increment Previous review (commit d713c51)Status: 8 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (9 files)
Verified sound: the per-turn background spawn genuinely returns immediately ( Reviewed by deepseek-v4.1-flash-1017 · Input: 97.8K · Output: 41.2K · Cached: 719.4K |
…terrupted answer - README and CHANGELOG: "Remember this as a team rule: …" said to the agent waits as a proposal, and rule drafting needs altimate-code's own model. - `/altimate-code:rules`: handles an answer that stopped part-way (`not_available` … "was being answered with"). - Codex: the sandbox has no network, so a proposal sent from `/altimate-code:rules` is queued until the next turn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| - A message saying the proposal was queued and not sent yet: normal inside Codex's sandbox, which has no network; it is sent after the user's next turn. | ||
| - `busy`: another session is answering it; skip it for now. | ||
| - `not_open`: it was already answered or closed; move on. | ||
| - `not_available` saying it "was being answered with" another action: an earlier answer stopped part-way; offer to finish it with that action. |
There was a problem hiding this comment.
SUGGESTION: A queued reply is itself an answer that "stopped part-way"
The two new bullets overlap without saying which one wins. Line 59 says that inside Codex's sandbox a reply is accepted locally and queued, not sent. That is exactly a proposal left "being answered with" an action until the queue drains after the turn. An agent that hits not_available in that state and follows this bullet re-issues the same action (e.g. --send), so once the queued copy flushes the proposal can be queued twice or the owner sees a duplicate. This also matters for --approve: "finish it with that action" must not be read as permission to repeat --approve-anyway without the explicit confirmation required by line 56 for verification_flagged. Say to check for the queued case first and to re-apply the verification_flagged confirmation when finishing an approve.
There was a problem hiding this comment.
Fixed in 1c54845: a queued answer counts as answered and is never re-sent; --approve-anyway always needs fresh confirmation.
- Session start runs the CLI only where workspace rules state exists, or where the workspace link cache names the repository or a folder inside it. Elsewhere the hook costs nothing. - The per-turn hooks share the same state check. The CLI still runs from the host's directory: a link can be for a folder inside the repository. - README: direct "remember" needs "team rule" wording. `/altimate-code:rules`: a here-doc delimiter note. - Hook test covers the pre-filter and a link for a folder inside the repository. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| # Only a repository with workspace rules state, or one the workspace link cache names (the repository or a folder | ||
| # inside it), can have team rules: elsewhere the CLI is not started at all. | ||
| if [ ! -f "$STATE" ]; then | ||
| CACHE="${XDG_STATE_HOME:-$HOME/.local/state}/altimate-code/altimate-workspace-bindings.json" |
There was a problem hiding this comment.
WARNING: Session start now depends on an undocumented private cache file
This path and its bindings key schema are owned by the CLI and appear nowhere else in the repository (no README, PRIVACY or CHANGELOG mention), and the test writes the file itself, so the real location and format are never exercised. Node tooling commonly resolves its state directory to ~/Library/Application Support on macOS and %LOCALAPPDATA% on Windows rather than XDG, and a rename or schema change on the CLI side has the same effect: session start silently stops running the CLI in every project. The user then gets no team rules, no "check failed" notice and no diagnostic, because the hook always exits 0. Document the contract next to the plugin's version requirement, fall back to the CLI when .altimate-code/ exists, or assert the path in a test that does not create the file.
There was a problem hiding this comment.
Partly: see the matching thread on altimate-claude-plugin#13. The location is the CLI's xdg-basedir state path on every platform, now documented in PRIVACY.md and the hook comment.
| if [ ! -f "$STATE" ]; then | ||
| CACHE="${XDG_STATE_HOME:-$HOME/.local/state}/altimate-code/altimate-workspace-bindings.json" | ||
| REAL="$(cd "$ROOT" 2>/dev/null && pwd -P)" | ||
| [ -n "$REAL" ] && grep -qF "\"$REAL" "$CACHE" 2>/dev/null || exit 0 |
There was a problem hiding this comment.
WARNING: The path match has no boundary
grep -qF "\"$REAL" accepts any quoted path that merely starts with REAL, so a binding for /work/acme-old also matches REAL=/work/acme: the CLI is started at session start in an unlinked project and pays up to the 15 s synchronous timeout this commit exists to avoid. In the other direction the match is literal and REAL is pwd -P (symlinks resolved) while the cache holds whatever the CLI recorded, so a linked project reached through a symlink or a git worktree misses and loses its team rules with no message. Match "$ROOT"/"$REAL" only when followed by " or /, and add negative tests for a sibling directory sharing the prefix and for a link recorded on a parent folder.
There was a problem hiding this comment.
Fixed in 1c54845: exact key with both quotes, JSON-escaped, native paths on Windows. Negative tests added.
| esac | ||
| ;; | ||
| user-prompt-submit|stop) | ||
| [ -f "$STATE" ] || exit 0 |
There was a problem hiding this comment.
SUGGESTION: The per-turn gate and the CLI can still resolve different project roots
The new comment (lines 29-30) says a link may be for a folder inside the repository, and session start now accepts that through the cache; but $STATE is always <git toplevel>/.altimate-code/learn/workspace-rules/rules.json. If the CLI keeps workspace-rules state beside the linked folder (repo/dbt/.altimate-code/…) instead of at the git root, user-prompt-submit and stop exit here before recording anything: the session receives the team rules but corrections never become proposals, silently. The test cannot catch it, because the stub ignores its cwd and rules.json is only ever created at the repo root. Either state that the CLI always writes this state at the git root, or check the linked path as well.
There was a problem hiding this comment.
No change needed: the CLI keeps workspace-rules state at the repository root (Instance.worktree), even for a link made for a subfolder, so the per-turn gate and the CLI agree. A comment in the hook now says so.
hkaltai
left a comment
There was a problem hiding this comment.
Peer review: APPROVE (with minor comments)
Reviewers: lead (Claude), Claude (fresh claude -p session), Codex (gpt-6.1-sol), with one convergence round.
The hook wrapper is defensive: every path exits 0, only the CLI's JSON is passed through, per-turn calls are detached with every file descriptor closed, and repositories without rules state or a link start no CLI. The /altimate-code:rules flow offers at most 4 choices. Nothing blocks merge here. The minor comments are inline, and the PRIVACY.md wording should be settled before release.
Dependency: merge and release after AltimateAI/altimate-code#1626, which has an open MAJOR. Without a configured model, the background Stop check auto-registers for Altimate Base and sends the digest with no disclosure. That affects what this PRIVACY.md can promise.
A linked-worktree root mismatch was raised, then withdrawn in convergence: the CLI's Instance.worktree is the --show-toplevel checkout, which matches $ROOT here.
Nits (consolidated)
plugins/altimate-code/hooks/altimate-code-workspace-rules.sh:29-30: the comment "Run from the host's directory, not the repository root" sits right above the line that computes the repository root, so it reads as contradicting it. Suggest: "$ROOTonly locates the state file; the CLI runs in the host's directory (nocd), because a link can be for a folder inside the repository."plugins/altimate-code/hooks/altimate-code-workspace-rules.sh:41:grep -qF "\"$REAL"doesn't JSON-escape the path (a directory containing"or\never matches) and is a prefix match (/x/appmatches/x/app-old). The walk-up fix in the inline comment covers both.- Docs mix
altimate learn …andaltimate-code learn …for the same commands (README and PRIVACY). Pick one. tests/workspace-rules-hook.sh: the slow stub's background processes outlive the test and are still sleeping when the trap deletes$WORK. Kill them before cleanup.- README: a new clone or worktree gets no team rules until altimate-code has run (or been linked) in that checkout, because both the gate and the CLI read only the directory-keyed bindings cache. Worth one line.
Scope vs ticket: cannot verify, because there's no ticket reference. Unrelated files: clean. Naming: clean, apart from the mixed command names above.
🤖 Generated with Claude Code
| if [ ! -f "$STATE" ]; then | ||
| CACHE="${XDG_STATE_HOME:-$HOME/.local/state}/altimate-code/altimate-workspace-bindings.json" | ||
| REAL="$(cd "$ROOT" 2>/dev/null && pwd -P)" | ||
| [ -n "$REAL" ] && grep -qF "\"$REAL" "$CACHE" 2>/dev/null || exit 0 | ||
| fi |
There was a problem hiding this comment.
MINOR — The gate lets through a child-folder link that the CLI can't find from the repo root
if [ ! -f "$STATE" ]; then
CACHE="${XDG_STATE_HOME:-$HOME/.local/state}/altimate-code/altimate-workspace-bindings.json"
REAL="$(cd "$ROOT" 2>/dev/null && pwd -P)"
[ -n "$REAL" ] && grep -qF "\"$REAL" "$CACHE" 2>/dev/null || exit 0
fi
When only dbt/ is linked and the session starts at the repository root, this check passes (the key starts with $REAL). But the CLI's RulesService.linked() (altimate-code rules/service.ts:59-67) only walks up from the cwd to the root, so it returns not linked. The CLI starts, waits up to the first-pull budget, and gives no rules. The comment ("or a folder inside it") and the test at tests/workspace-rules-hook.sh:50 ("started from the repository root: the CLI runs") pass only because the stub always answers.
Suggested fix: mirror the CLI. Walk from pwd -P up to $ROOT, matching "\"$DIR\"" with the closing quote at each step; that also removes the /x/app vs /x/app-old prefix overmatch. Or teach the CLI to look below the cwd. Then change the test to start inside project/dbt/models, and add a negative case for a root start with only a child folder linked.
There was a problem hiding this comment.
Fixed in 1c54845 (same walk as the Claude plugin).
|
|
||
| ## Workspace rules | ||
|
|
||
| These hooks act only in a project linked to an Altimate workspace, with learning on, workspaces on and you signed in to Altimate. Otherwise the session-start hook reads only local altimate-code state and sends nothing, and the per-turn hooks do not run the CLI at all in a project without workspace rules state. |
There was a problem hiding this comment.
MINOR — The learning-off guarantee is stronger than the code
These hooks act only in a project linked to an Altimate workspace, with learning on, workspaces on and you signed in to Altimate. Otherwise the session-start hook reads only local altimate-code state and sends nothing, ...
RulesHost.start() checks rulesEnabled() (the learn switch and workspaces) before the pull. But the workspace's "Learn from sessions" setting and capture (learn disable sets capture=false) are checked only afterwards, in on(). So with either opt-out from line 36, a signed-in, linked project can still pull from Altimate at session start. Also, the per-turn hooks pass every prompt and the transcript path to a local CLI process wherever rules.json exists, even after learn disable, sign-out or unlink. The CLI then drops them, so nothing leaves the machine, but the wording implies the hooks stop.
Separately, the digest goes to "your configured LLM provider" only if one is configured. Otherwise the CLI currently auto-registers Altimate Base (see the MAJOR on AltimateAI/altimate-code#1626).
Suggested fix: narrow the wording to what is gated: capture and drafting are off; a rules pull still happens unless ALTIMATE_LEARN=0. Or change the CLI to check the workspace setting before pulling. Optionally, skip the per-turn hooks early on ALTIMATE_LEARN=0.
There was a problem hiding this comment.
Fixed in 1c54845; see the matching thread on altimate-claude-plugin#13.
There was a problem hiding this comment.
Re-reviewed at this head with independent Claude and Codex reviewers: partially fixed. Delivery and capture are now separated correctly, but the last sentence of line 33 is still stronger than the CLI's behaviour:
When learning is off, you are signed out or the project is not linked, the CLI discards them and nothing leaves your machine.
With learning off in a signed-in, linked project, RulesHost.check() still calls syncAfterCheck() (altimate-code rules/host.ts:351-355). That can send queued proposal replies and usage counts and pull rules at the next Stop. The CLI's own test says so: "with learning from sessions off, a queued reply is still sent at the next Stop".
Suggested fix: narrow it to what's true. With learning off, your messages and the transcript are discarded locally. Answers you already queued, the rules sync and usage counts can still be sent. Only ALTIMATE_LEARN=0 stops everything. (MINOR, not blocking.)
|
|
||
| - The plugin itself collects **no data**. It is a routing layer (a skill, a slash command, and a static session hook) that tells Codex when to delegate a task to the altimate-code CLI installed on your machine. | ||
| - The plugin itself collects **no data**. It is a routing layer (skills, slash commands, and session hooks) that tells Codex when to delegate a task to the altimate-code CLI installed on your machine. | ||
| - In a project linked to an Altimate workspace with learning on, its workspace-rules hooks hand your messages and the session transcript to the local altimate-code CLI, which keeps corrections on your machine and may send a digest to your LLM provider to word a proposed team rule. A proposal reaches your workspace only when you send or approve it. See [Workspace rules](#workspace-rules). |
There was a problem hiding this comment.
MINOR — Effective date (line 3) unchanged after a material change, anchored here because line 3 isn't in the diff
**Effective date:** July 18, 2026
The policy now covers every prompt and the transcript, plus a digest sent to an LLM provider. The policy itself promises to revise the effective date when practices change. Set it to the 1.2.0 release date, and get the privacy sign-off the PR description asks for.
| cd "$(dirname "$0")/.." | ||
| HOOK="$(find "$PWD/plugins/altimate-code" -name '*workspace-rules.sh' | head -n1)" | ||
| [ -f "$HOOK" ] || { echo "FAIL: no workspace-rules hook script"; exit 1; } | ||
| grep -q "$(basename "$HOOK")" plugins/altimate-code/hooks/hooks.json || { echo "FAIL: hooks.json does not run $(basename "$HOOK")"; exit 1; } |
There was a problem hiding this comment.
MINOR — hooks.json is only checked by grepping for the script name
grep -q "$(basename "$HOOK")" plugins/altimate-code/hooks/hooks.json || { echo "FAIL: hooks.json does not run $(basename "$HOOK")"; exit 1; }
A typo in an event name (UserPromptSubmit/Stop), an argument (user-prompt-submit, which the script's case silently ignores), or a timeout would still pass. Parse it with python3 -c 'import json…' or jq, and assert each event runs the script with its matching argument and the 15/5/5 timeouts.
| - In a project linked to an Altimate workspace with learning on, its workspace-rules hooks hand your messages and the session transcript to the local altimate-code CLI, which keeps corrections on your machine and may send a digest to your LLM provider to word a proposed team rule. A proposal reaches your workspace only when you send or approve it. See [Workspace rules](#workspace-rules). | ||
| - When a task **is** delegated, your task text is passed to the altimate-code CLI, which sends it to an LLM provider to plan and execute the work — the Altimate LLM Gateway by default, or a provider you configure (BYOK). | ||
| - Warehouse queries, dbt commands, and file edits run **locally on your machine, under your credentials**. Your warehouse credentials are never sent to Altimate. | ||
| - The plugin ships as opt-in. Adding the marketplace does not enable the plugin — you must explicitly install it from Codex's `/plugins` browser and approve its SessionStart hook before any delegation happens. |
There was a problem hiding this comment.
MINOR — Consent wording still names only the SessionStart hook
... you must explicitly install it from Codex's `/plugins` browser and approve its SessionStart hook before any delegation happens.
1.2.0 also installs UserPromptSubmit and Stop hooks, and those are the ones that see every prompt and the transcript. Say "approve its hooks (SessionStart, UserPromptSubmit, Stop)" here and at line 75. In the README, mention that Codex may ask again after upgrading from 1.1.0.
There was a problem hiding this comment.
Fixed in 1c54845: PRIVACY (lines 16 and 75), SECURITY.md and the README's trust section now name SessionStart, UserPromptSubmit and Stop. The README also says Codex may ask again after upgrading from 1.1.0.
ralphstodomingo
left a comment
There was a problem hiding this comment.
Requesting changes to PRIVACY.md, which the description asks to have reviewed. The code reads fine. These are the places where the policy doesn't match what altimate-code does:
- What the digest contains. The "session transcript" row says "a bounded, redacted digest". The digest altimate-code builds (
packages/opencode/src/altimate/learn/digest.tsin altimate-code) holds the session's prompts, every tool call's input and output (up to 300 and 400 characters per call, so file contents, command output and warehouse rows), and the final reply, up to 24,000 characters in all. Redaction masks secrets only, not other data. The policy should say so. - Where it goes. "Your configured LLM provider" reads as Codex's provider (OpenAI). It's actually altimate-code's own model:
learn.model, else altimate-code's default (the Altimate LLM Gateway, or the provider configured for altimate-code). Name it, since for a Codex user it's a party that doesn't otherwise see the session. - Telemetry. "does not phone home and does not collect analytics" is true of the plugin itself, but the CLI the hooks start sends rules telemetry:
rules_sync,rules_delivered,rule_draftedandrule_proposal_shown(the last with the host session id). Add a row to the table, or point this section at "Usage telemetry" below. - Who turns learning on. "with learning on" doesn't say that, in a linked project, the workspace's "Learn from sessions" setting turns capture on for a member who hasn't made a choice of their own. The policy lists that setting as an off switch; it should also say the workspace owner can switch capture on with it.
- Effective date. The data flows changed, and "Changes to this policy" says the date is revised when practices change.
Smaller:
- In the description,
stacked on #1605links to this repository's own number 1605, not altimate-code's. Use AltimateAI/altimate-code#1605. - On Windows the session-start check of the link cache never matches. It greps for the Git Bash path (
/c/Users/...), but altimate-code keys the cache by the native path, JSON-escaped (C:\\Users\\...). So on Windows, session start only runs the CLI where workspace-rules state already exists. - The edit here-doc in
commands/rules.mdis indented inside its list item. Copied as written, the closing delimiter doesn't end it, or the wording picks up leading spaces (Kilo's thread). Showing the block flush left avoids both.
As the description says, this must merge and ship after an altimate-code release that has learn hook, and also after the changes requested on AltimateAI/altimate-code#1626.
Appendix: complexity delta
No TypeScript or Python in this diff (a shell hook, Markdown and JSON), so there's nothing to measure.
- `hooks/altimate-code-workspace-rules.sh`: at session start, walk from the host's directory up to the repository root and match each folder as an exact JSON key of the link cache (escaped, native form via `cygpath -w` on Windows), as the CLI does. A link for a subfolder no longer starts the CLI from the repository root, and `/x/app` no longer matches `/x/app-old`. - Skip every event with `ALTIMATE_LEARN=0` (or `false`). - Correct the comments on `$ROOT`, blocking and model calls per `Stop`. - `tests/workspace-rules-hook.sh`: parse `hooks.json` and check each event's argument and timeout; cover a start below a linked subfolder, a root start with only a subfolder linked, a sibling-prefix link, quote/backslash paths, Windows native keys and `ALTIMATE_LEARN`; replace the fixed sleep with a sentinel; stop the slow stub on exit. - `commands/rules.md`: show the edit here-doc flush left, branch on `code` when `ok` is false and on `closed` when it is true, and never repeat `--approve-anyway` without fresh confirmation. - `PRIVACY.md`, `README.md`: approving the plugin means approving its hooks (SessionStart, `UserPromptSubmit`, `Stop`); Codex may ask again after upgrading from 1.1.0. - `commands/rules.md`: a sandbox-queued answer is treated as answered and never re-sent, including when a later `not_available` names it. - `PRIVACY.md`: state what is gated by what, who can turn capture on, the digest's contents and caps, that it goes to altimate-code's own model rather than Codex's, and the rules telemetry the CLI sends; add a revision date. - `README.md`, `CHANGELOG.md`: `altimate learn …` throughout, bounded session start, start in the linked folder, new clones and worktrees, where proposals are answered, and the corrected gating. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
| # paths written as JSON strings, so each folder is converted (`cygpath -w` on Windows) and escaped, and matched with | ||
| # both quotes, so `/x/app` never matches `/x/app-old`. | ||
| linked() { | ||
| local cache="${XDG_STATE_HOME:-$HOME/.local/state}/altimate-code/altimate-workspace-bindings.json" |
There was a problem hiding this comment.
WARNING: The link-cache directory is still an unverified, hard-coded XDG state path
linked() reads ${XDG_STATE_HOME:-$HOME/.local/state}/altimate-code/altimate-workspace-bindings.json, but nothing in this repository proves the CLI writes its link cache there. The plugin's own troubleshooting table documents the CLI's global directory as ~/.local/share/altimate-code/ (a data root, not a state root), and on macOS/Windows a Node CLI normally resolves to ~/Library/Application Support / %LOCALAPPDATA%. A data-vs-state or platform difference means grep never finds the cache, so session start starts nothing in every newly linked repository. Because the hook always exits 0, the user gets no team rules, no warning and no diagnostic.
The new test cannot catch this: link() writes the cache at exactly the path the test sets through XDG_STATE_HOME, so it only proves the hook can read a file it created itself. Verify the real location against the paired altimate-code release (or ask the CLI for the cache path), and add a test that expects the cache at the CLI's location rather than creating it in the hook's location.
There was a problem hiding this comment.
Checked against altimate-code: the link cache is path.join(Global.Path.state, "altimate-workspace-bindings.json") (workspace/state.ts), and Global.Path.state is xdgState/altimate-code from xdg-basedir. That is $XDG_STATE_HOME or ~/.local/state on every platform, not Application Support or %LOCALAPPDATA%. The hook reads the same path.
| done | ||
| [ "$(hook "$WORK/project" stop "$BIN:$SYS")" = "0|" ] || fail "stop with rules state did not exit quietly" | ||
| await_log "learn hook stop --host $HOST" || fail "stop with rules state did not run the CLI" | ||
| for _ in $(seq 20); do [ "$(grep -c . "$STARTED")" -gt 1 ] && break; sleep 0.1; done |
There was a problem hiding this comment.
SUGGESTION: This wait can only ever time out
The loop breaks when $STARTED has more than one line, but the next assertion requires exactly one (= "learn hook stop --host $HOST"). On a correct run it therefore always burns the full two seconds; on a broken run it breaks early only to fail the following line, so it does not wait for anything meaningful. await_log has already confirmed the positive stop landed (the stub appends to $STARTED before it reads stdin and writes $LOG), so the loop can be dropped. If the intent is to catch a stray background call, use a deterministic sentinel or a bounded await_absent that also covers calls arriving after the assertion instead of sleeping for a second line that must not exist.
|
Review round addressed in 1c54845 (it also names all three hooks in the consent wording, SECURITY.md and the README). Each inline thread has a reply. PRIVACY.md, for ralphstodomingo's points 1–5 and hkaltai's comments:
Legal sign-off is still pending. Smaller points:
hkaltai's nits:
Dependency: merge and release after altimate-code#1626 ships in a release; the no-model and gating fixes are there. 🤖 Generated with Claude Code |
Every hook event now starts altimate-code only where altimate-code's workspace link cache (in the person's own state directory) names the session's folder or a folder above it in the repository. The per-turn hooks no longer run because `.altimate-code/learn/workspace-rules/rules.json` exists: a file in the repository can come from a branch or a merge, and altimate-code no longer keeps its rules state there. - Hook script: one gate (`linked`) for every event; `$STATE` removed. - Tests: a rules state file planted in the repository starts nothing, at session start or per turn; the per-turn checks use the link cache. - PRIVACY.md, README, CHANGELOG: the hooks run only in a linked folder; the rules state, the proposals waiting to be sent and review notices are kept in the person's own state directory, outside the project. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Kilo Code Review could not run — your account is out of credits. Add credits or switch to a free model to enable reviews on this change. |
hkaltai
left a comment
There was a problem hiding this comment.
Peer review: REQUEST CHANGES (docs only)
Reviewers: lead (Claude), Claude (fresh claude -p session) and Codex (gpt-6.1-sol), as independent reviewers, with one convergence round. This PR was reviewed together with its sibling for parity, against the altimate-code#1626 CLI at b7f42b63.
The hook code is in good shape:
- The calls match the CLI:
learn hook <event> --host codex, the input passed on stdin, and the output passed through only when it's the hook's JSON. - Every path exits 0, and per-turn events run detached.
- The link-cache walk matches
RulesService.linked(): physical paths (pwd -Pagainst keys canonicalized withrealpathSync), stopping at the repository root, segment-safe matching, JSON escaping andcygpathon Windows. - The newest commit means a file brought in by a branch can no longer start the CLI.
Both hook scripts are identical apart from HOST and the order of CLI names, which follows each repo's convention.
Blocking: PRIVACY.md:39 states where data is kept, and it's wrong (inline). The privacy wording was the reason for the last request-changes, so it should be accurate before release. Three MINOR findings are inline: the telemetry and model disclosure, the link-cache over-match, and no test run.
Prior findings
Resolved, checked in the code:
- the
$ROOTcomment, escaping and prefix matching, the Windows path, the flush-left here-doc, the slow stub cleanup, and the new-clone line in the README; - the
hooks.jsonparse test, the digest contents and destination, who turns learning on, and the dated revision. - ralph's telemetry point is partly fixed (inline on L44).
- The PR-description link to #1605 and the reply-only threads couldn't be verified from the checkout.
Nits (consolidated)
- The hook header (lines 4-6) uses "learning" for two different switches.
ALTIMATE_LEARN/learn.enabledgates delivery, while capture also needs the workspace's "Learn from sessions" setting. Say which is which. - The CONTRIBUTING.md layout still describes only the SessionStart hook. Add the workspace-rules script, the three events and the new test.
- The hook comment (line 5) and
plugins/altimate-code/README.md:100sayaltimate-code learn, but the rest of the docs sayaltimate learn. Use one form, or say once that both names run the same CLI.
Not verified
The test scripts were read, not run. Nothing checks whether Codex kills the hook's process group, which would also end the detached per-turn CLI. No fixture pins the link-cache format the hooks depend on.
Note for altimate-code#1626 (not this PR): the CLI's own RulesService.linked() still counts a repository rules.json that has scope as linked.
Scope vs ticket: cannot verify, because there's no ticket reference. Unrelated files: clean.
🤖 Generated with Claude Code
|
|
||
| | Data | Where it goes | Retention | | ||
| |---|---|---| | ||
| | Your workspace's approved team rules | Pulled by the local CLI from your Altimate workspace, given to Codex as session context | Mirrored locally in the project's git-ignored `.altimate-code/` directory, and given only while altimate-code's own rules state (in your state directory, `~/.local/state/altimate-code/`, outside the project) holds the same rules. That state also keeps proposals waiting to be sent and your review notices | |
There was a problem hiding this comment.
MAJOR (privacy policy): the policy says rules state is kept outside the project, but it's in the repository
…and given only while altimate-code's own rules state (in your state directory,
~/.local/state/altimate-code/, outside the project) holds the same rules. That state also keeps proposals waiting to be sent and your review notices
The CLI in altimate-code#1626 keeps this state in <repo>/.altimate-code/learn/workspace-rules/rules.json:
rules/store.ts:259-261puts the file underStore.paths(root, …).learnDir, which is<root>/.altimate-code/learn/<name>.- Nothing under
altimate/rules/usesGlobal.Path.state.
Only the workspace link cache (altimate-workspace-bindings.json) lives in ~/.local/state/altimate-code/. Someone who trusts the policy will think the proposal text and review notices are outside the checkout. A git add -f, an archive or a copy of the project includes them.
Suggested fix: say the rules state, proposals waiting to be sent and review notices are kept in the project's git-ignored .altimate-code/learn/workspace-rules/ folder. Mention the user state directory only for the link cache. The "given only while…" clause could become "given only while you're signed in as the account they were pulled for", which is what delivering() checks.
There was a problem hiding this comment.
Fixed in 03f71e2. The wording now matches the CLI from altimate-code#1669: the rules state, proposals waiting to be sent and review notices are in the user state directory ($XDG_STATE_HOME/altimate-code/workspace-rules/, usually ~/.local/state/...), keyed by the project's real path, and the project's copy of the rules is given only while that state holds the same rules and you are signed in as the account that pulled them (#1669, b600a0b5b6). You were right that at the reviewed head (#1626 at b7f42b63) the state was still in the repository. #1626 rebases onto #1669 and this plugin ships after a release that includes it. The row also says that earlier releases keep the state in the project's .altimate-code/learn/workspace-rules/ folder.
There was a problem hiding this comment.
Re-reviewed at this head: the wording is now conditional ("This needs an altimate-code release that keeps the rules state there"), which is fine. But nothing gates the release on that CLI change:
- The PR description still says "Merge and release after #1626 ships". The state move is fix: workspace rules state lives outside the project altimate-code#1669, which is still OPEN.
- The description also still says the per-turn hooks run "only where
.altimate-code/learn/workspace-rules/rules.jsonexists". The code now gates every event on the user-level link cache, and the test asserts that arules.jsonin the repository does not start the CLI.
Suggested fix: make the gate "after an altimate-code release that includes #1626 and #1669", name that minimum altimate-code version in the README/CHANGELOG, and update the hook-gating bullet. The privacy reviewer will read the description. (MINOR, not blocking.)
| | The session transcript, after a turn with new corrections (learning on) | Read locally by the CLI; the digest described above goes to altimate-code's own model to word one proposed rule | The transcript stays local; the model provider's handling is as for delegated tasks below | | ||
| | A proposed rule, with an excerpt of your correction if your workspace allows quotes | Your Altimate workspace's review queue, only when you send or approve it in `/altimate-code:rules` or `altimate learn proposals` | Per the Altimate Privacy Policy | | ||
| | Rule usage counts (sessions a rule was shown in, corrections after it) | Your Altimate workspace | Per the Altimate Privacy Policy | | ||
| | Rules telemetry events: `rules_sync`, `rules_delivered`, `rule_drafted`, `rule_proposal_shown` (with the Codex session id, as `codex:<id>`), `rule_proposal_answered`, `rule_proposal_skipped`, `rule_remember_routed`. No rule text, prompts or file paths | Altimate's usage telemetry, as described in [Usage telemetry](#usage-telemetry) | As for usage telemetry; turned off with the telemetry opt-out there | |
There was a problem hiding this comment.
MINOR (privacy policy): the telemetry and model disclosures are incomplete
Rules telemetry events:
rules_sync,rules_delivered,rule_drafted,rule_proposal_shown(with the Codex session id, ascodex:<id>), …
rules_commandis missing./altimate-code:rulesrunslearn proposalsandlearn proposals reply, and thelearnmiddleware sendsrules_commandfor every subcommand excepthook(learn.ts:1445-1450in #1626).- The session id isn't specific to one event. Every rules event carries
session_id, and host-driven events usecodex:<id>. Putting it next torule_proposal_shownonly is ambiguous. ALTIMATE_LEARN_MODELis missing. On line 35,learnModel()prefersALTIMATE_LEARN_MODELoverlearn.model(learn/auto.ts:38-40). Someone who set the variable is told the digest goes elsewhere.
Suggested fix: add rules_command, say the rules events carry the session id, and write "ALTIMATE_LEARN_MODEL, else learn.model, else …".
There was a problem hiding this comment.
Fixed in 03f71e2: rules_command is listed (the learn subcommand name; not sent for the hooks' own learn hook calls), the row says events about a session carry its id (codex:<id> for this host's sessions; rules_sync and rules_command carry none), and the model line reads ALTIMATE_LEARN_MODEL, otherwise learn.model, otherwise altimate-code's default model.
| key="$dir" | ||
| command -v cygpath >/dev/null 2>&1 && key="$(cygpath -w "$dir")" | ||
| key="$(printf '%s' "$key" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g')" | ||
| grep -qF "\"$key\"" "$cache" 2>/dev/null && return 0 |
There was a problem hiding this comment.
MINOR: the link-cache check matches JSON values as well as keys
grep -qF "\"$key\"" "$cache" 2>/dev/null && return 0Each cache row also has a projectPath value. If "/x/repo" appears as the projectPath of the row keyed /x/repo/dbt, a session started at /x/repo passes the gate, though the cache has no key for it. That contradicts the newest commit's promise to run only where the link cache names the folder. The CLI checks the link again, so the cost is a CLI start that does nothing on every event, but the gate isn't doing what it says.
Suggested fix: match the key form only, for example grep -qE on "<escaped>"[[:space:]]*: (keys are always followed by :, even in pretty-printed JSON). Add a negative fixture where the folder appears only as a value.
There was a problem hiding this comment.
Fixed in 03f71e2: the folder is matched only as a key, grep -qE '"<escaped>"[[:space:]]*:', with the JSON-escaped path also escaped for the regular expression. New fixtures: a folder that appears only as another row's projectPath value does not start the CLI, a dot in a folder name doesn't act as a wildcard, and a pretty-printed cache key still matches.
| #!/usr/bin/env bash | ||
| # Checks the workspace-rules hook against a stub CLI: hooks.json runs it for the right events, arguments and timeouts; | ||
| # it passes the hook input through, prints only the CLI's hook JSON at session start, starts the CLI for any event only |
There was a problem hiding this comment.
MINOR (testing): nothing runs this test
#!/usr/bin/env bash
# Checks the workspace-rules hook against a stub CLI: hooks.json runs it for the right events, arguments and timeouts;The repo has no workflow that runs the shell tests, and CONTRIBUTING only mentions tests/resolve-cli.sh. A later edit that breaks the link-cache walk would merge unnoticed, for example dropping the closing quote that stops /x/app from matching /x/app-old.
Suggested fix: list the new test in CONTRIBUTING's pre-PR steps. Better still, add a small workflow that runs bash tests/*.sh on macOS and Linux.
There was a problem hiding this comment.
Fixed in 03f71e2: .github/workflows/tests.yml runs every tests/*.sh on ubuntu-latest and macos-latest (zsh installed on Linux for resolve-cli.sh), and CONTRIBUTING lists both tests and the current layout (the workspace-rules script, the three events, rules.md, tests/). The header nit is fixed too: it says the kill switch (ALTIMATE_LEARN / learn.enabled) gates team rules, and capture also needs learning from sessions (your choice, else the workspace setting). The naming nit: the README now says altimate and altimate-code are two names for the same CLI and that the hooks call altimate-code learn hook; the hook header says the same.
- The link-cache check matches a folder only as a JSON key (quoted path then `:`), escaped for the regular expression, so a folder named only as another row's `projectPath` value does not start the CLI. New fixtures: a folder named only as a value, a dot in a folder name, and a pretty-printed cache. - PRIVACY.md: team rules are given only while signed in as the account that pulled them; the rules state is in the user state directory from the altimate-code release that moves it there (earlier releases keep it in the project). Adds `rules_command`, says session events carry the session id, and names `ALTIMATE_LEARN_MODEL` ahead of `learn.model`. - CI: a workflow runs `tests/*.sh` on Linux and macOS; CONTRIBUTING lists the tests and the current layout. - The hook header says which switch gates what. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hkaltai
left a comment
There was a problem hiding this comment.
Re-review at 03f71e2 (independent Claude and Codex reviewers, one convergence round)
Approve, with conditions. All earlier findings are fixed or narrowed down to documentation. The hook can't fail the host, the link-cache gate is keyed on user-level state only, and the CLI contract matches altimate-code#1626: events, --host, the hookSpecificOutput JSON, proposals reply flags and failure codes. Both shell tests pass locally under macOS bash 3.2.
Before merge/release:
- Ship only after an altimate-code release that includes #1626 and #1669. #1626 currently has changes requested, for a blocker that #1669 fixes.
- Fix the learning-off wording in PRIVACY.md (replied in the existing thread).
Inline (MINOR)
plugins/altimate-code/hooks/altimate-code-workspace-rules.sh:56-62: non-canonical keys in link caches written by older CLIs can keep the hooks from ever starting.- PRIVACY.md line 33: the learning-off guarantee is still too broad (reply in thread).
- Release gate and stale PR description: needs #1669 too (reply in thread).
Nits
- PRIVACY.md lines 3-4: the effective date stays at July while line 96 promises to "revise the effective date when our practices change". Either update it at release, or change line 96 to refer to "Last revised". (Contested between the reviewers; leave it to the legal sign-off.)
plugins/altimate-code/CHANGELOG.md: the[1.2.0] — 2026-10-30date predates the release. Set it when tagging..github/workflows/tests.yml:actions/checkout@v4is pinned by tag, not SHA. Low risk withcontents: read.
Missing tests (advisory)
- A link-cache fixture with a non-canonical (symlinked) key.
- A live host run: the hook's working directory, the detached Stop process surviving the hook's exit, and SessionStart
additionalContextfrom the second hook in the group.
| dir="$(pwd -P)" | ||
| [ -n "$top" ] && [ -n "$dir" ] || return 1 | ||
| while :; do | ||
| key="$dir" | ||
| command -v cygpath >/dev/null 2>&1 && key="$(cygpath -w "$dir")" | ||
| key="$(printf '%s' "$key" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/[][\.*^$+?(){}|]/\\&/g')" | ||
| grep -qE "\"$key\"[[:space:]]*:" "$cache" 2>/dev/null && return 0 |
There was a problem hiding this comment.
MINOR (logic error): link caches written by older CLIs can keep the hooks from ever starting
dir="$(pwd -P)"
[ -n "$top" ] && [ -n "$dir" ] || return 1
while :; do
key="$dir"
command -v cygpath >/dev/null 2>&1 && key="$(cygpath -w "$dir")"
key="$(printf '%s' "$key" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e 's/[][\.*^$+?(){}|]/\\&/g')"
grep -qE "\"$key\"[[:space:]]*:" "$cache" 2>/dev/null && return 0The gate only matches the physical path. Caches written by older altimate-code builds can have keys that aren't real paths: a folder reached through a symlink, or /tmp/... instead of /private/tmp/... on macOS. The CLI rewrites those to canonical keys only during a CLI read that misses (readLocalBinding → migrateToCanonicalKeys, workspace/state.ts:337-352). This gate now stops that read from ever happening.
Failure scenario: a repo linked through a symlinked path with an older CLI. After upgrading, the person only uses this host. linked() never matches, so team rules aren't given and corrections aren't recorded, with nothing saying why. Running altimate learn status once in the folder "fixes" it as a side effect, which hides the cause.
Suggested fix: also try the logical pwd and its parents as keys. At minimum, add a README troubleshooting line ("after upgrading, run altimate learn status once in the linked folder"). Add a fixture with a symlinked key to pin down whichever behaviour you choose. Same script in the sibling plugin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and never matches an empty key - `linked` tries the physical path, then the path as the host reached it (a symlink, `/tmp`), still bounded by the repository root. - An empty key (a `cygpath` that prints nothing) no longer matches an empty string in the cache. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Re-review notes from 11-05, as on altimate-claude-plugin#13: the link-cache gate now also finds a link keyed by a symlinked path, and never matches an empty key (5ef887e, with tests that fail on the old script). The description now names the merge gate (the first altimate-code release with #1626) and the link-cache gating. |
ralphstodomingo
left a comment
There was a problem hiding this comment.
Re-read at 5ef887e108, against my 11-04 points, your 11-10 notes and AltimateAI/altimate-code#1626 as merged. I read it; I didn't run the tests, but CI ran them on Linux and macOS and they pass. Approving: everything I asked for is in.
My 11-04 points:
- PRIVACY.md: the Workspace rules section is the Claude plugin's, with Codex named: what the digest holds, that it goes to altimate-code's own model, the rules telemetry row, the workspace's "Learn from sessions" setting, and a revised date.
- The smaller ones: the description links AltimateAI/altimate-code#1605, the Windows check converts each folder with
cygpath -wand JSON-escapes it, and the edit here-doc incommands/rules.mdis flush left.
Today's commit is the same as the Claude plugin's. The hook is identical apart from the host and the order of the CLI names. The walk tries the physical path first, then $PWD, and an empty key returns before the grep, with the same new tests. Kilo's new point on the Claude plugin's line 60 applies here too, at the same line:
Kilo's new point on line 60 is right, but narrow. When a symlink outside the repository points at a folder inside it, no folder on the $PWD walk has the repository root as its physical path. So the walk climbs to /, and a link on a folder above the symlink (say ~/projects) lets the CLI start. The CLI then does its own check from the physical path, finds no link, and does nothing, so the cost is a wasted start, not a wrong result. But it's the one case where "elsewhere they start nothing" doesn't hold, and the new test (a symlink at the repository root) doesn't cover it. Kilo's bound, which stops when a folder's physical path is outside the repository, fixes it. It's worth fixing or answering before this merges.
Kilo's thread on tests/workspace-rules-hook.sh:192 reads the wait as a bug, but the loop is a bounded watch for a second start, so a passing run waits it out. A reply saying so would close it.
Against AltimateAI/altimate-code#1626 as merged: learn hook takes these three events and --host codex, learn proposals reply has every flag /altimate-code:rules uses, and the link cache is altimate-workspace-bindings.json in the state directory, keyed by real path, which is how the hook reads it. The release gate in the description is still the one that matters: merge and release this after the first altimate-code release that includes AltimateAI/altimate-code#1626.
Appendix: complexity delta
No TypeScript or Python in this diff (a shell hook, its shell test, Markdown and JSON), so there's nothing to measure.
Summary
Team rules from a linked Altimate workspace now apply in Codex sessions. This needs altimate-code with
learn hook. That is AltimateAI/altimate-code#1626 (merged; it builds on AltimateAI/altimate-code#1605), with the rules state in the user's state directory from AltimateAI/altimate-code#1669 (merged, in v0.12.10). #1626 is not in a release yet (v0.12.10 does not have it): merge and release this after the first altimate-code release that includes #1626. With an older altimate-code the hooks do nothing: the session-start output is dropped unless it is the hook's JSON.SessionStart,UserPromptSubmit,Stop) callaltimate-code learn hook <event>with the host's hook input on stdin.~/.local/state/altimate-code/altimate-workspace-bindings.json, or under$XDG_STATE_HOME). The physical path is tried first, then the path the host used (a symlink). No file in the repository turns the hooks on.ALTIMATE_LEARN=0starts nothing./altimate-code:ruleslists the stored proposals, the team rules and your proposals' status, asks with numbered choices in chat (Codex has no question tool), and answers throughlearn proposals reply.Test plan
bash tests/resolve-cli.sh(now coverscommands/rules.mdandlearncalls)bash tests/workspace-rules-hook.sh(new, stub CLI):rules.json, a prefix sibling, a value-only path, an empty key, or a parent above the repository through a symlink);🤖 Generated with Claude Code