Skip to content

Fix/oauth identity desync - #142

Merged
NagyVikt merged 24 commits into
mainfrom
fix/oauth-identity-desync
Aug 10, 2026
Merged

Fix/oauth identity desync#142
NagyVikt merged 24 commits into
mainfrom
fix/oauth-identity-desync

Conversation

@NagyVikt

Copy link
Copy Markdown
Contributor

No description provided.

NagyVikt and others added 24 commits July 27, 2026 01:22
…ith rules

The suggestion engine ranked profiles from five hand-maintained sources —
dependency rules, path conventions, combo history, recents, featured. Between
them they cover 19 of 85 profiles. The other 66 could only ever surface if the
user had launched them before, so a directory that genuinely wanted one had no
way to say so, and cycling past the third suggestion ran out of answers.

profile-match scores every profile's OWN vocabulary (name, description, skill
ids, MCP ids) against what the directory reveals about itself (dependencies,
languages, marker files, entry names). Coverage goes to 85/85 and the card's
tail keeps landing on something the repo justifies. Wired in as a new `matched`
origin scored 8-30, so curation still leads: it can pass `featured` from ~0.32
strength but never outranks a detection, a confirmed combo, or something
launched in this very directory.

Four properties earned their place by being wrong first:

  Absolute strength, not relative to the run's best hit. Normalizing against
  the top scorer manufactures confidence from noise — a directory with nothing
  to say still produced a 1.00 "match", because the weakest signal present is
  still the strongest signal present. cue and gitguardex now correctly match
  nothing.

  Corroboration: a filename alone never carries a match. Every repo here has a
  CLAUDE.md, so every repo matched `claude-api` — above a real ROS workspace
  backed by an actual robot.urdf. Chasing each such word with the stopword list
  was a losing game; requiring one dependency, language, or marker hit ends the
  class.

  IDF weighting plus size damping, so `gstack` (70 terms, mentions everything)
  cannot beat `rust` (18 terms, mentions Rust) on a Cargo.toml by surface area.

  Both sides normalize through the same `tokenize`. Skipping it on the evidence
  side meant a profile indexed "robotic" while EXT_LANGUAGE emitted "robotics" —
  they never met, silently. Same failure class as the bash/TS drift the hook
  guards against.

gstack itself goes to _featured.yaml rather than being made detectable, because
it structurally cannot be: it is a WORKFLOW profile, describing how you want to
work, and repo evidence only ever describes what the project IS. A .urdf says
robotics; nothing on disk says "role-routed engineering". It scored 0.27 at
rank #6 across the test repos; always-available is the honest mechanism.

Also: `-js` is no longer folded as a plural (medusajs -> medusaj, nextjs ->
nextj), which affected skill matching too; the df cut is skipped below 10
profiles, where it discarded every term shared by two and matched nothing; and
manifest metadata keys are filtered, so `requires-python` and `[urls] issues`
stop reading as dependencies named "research" and "linear".

Verified on real repos: agv_stack surfaces ros2 #1 (previously unreachable),
api-tester surfaces python + backend-base, kolarortopedia surfaces postgres +
supabase, cue and gitguardex correctly surface nothing. 2819 pass / 0 fail.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
src/commands/handoff.test.ts registered mock.module("../lib/handoff", ...).
Bun's module mock lives in a PROCESS-GLOBAL registry that outlives the file
that installs it, so whenever this file landed in the same worker before
src/lib/handoff.test.ts, that file asserted against the stub instead of the
real formatHandoffForAgent. The stub emitted only the header and the task
summary, so exactly the four sections it omitted failed:

  Most useful skills / Also helpful / MCPs used / Notes

Green locally, red in CI, purely on file ordering.

Drive the command through spawnSync with XDG_CONFIG_HOME pointed at a temp
dir instead. HANDOFFS_DIR is a module-level const baked from that env var at
import time, so a fresh child picks up the temp dir and no global state is
touched. Nothing is left to leak — this was the last mock.module in the repo.

Also stronger: the router now runs against the real lib rather than a stub.
10 -> 18 tests, covering the --json branches, --skills level parsing, the
--from default and the unknown-subcommand fallback.

Verified: bun test --timeout 30000 (the CI command), 4 consecutive runs,
2819 pass / 1 skip / 0 fail. typecheck clean, lint unchanged at 6 warnings.
Every deletion was re-verified by hand against a repo-wide fixed-string grep
(excluding node_modules/dist/vendor, but INCLUDING .sh, .yaml, .md and
extensionless bin scripts) plus a within-file usage check. Dead code's tests
go with it — a test for dead code is also dead.

Whole files, referenced by nothing but their own test:
  src/lib/incremental-materialize.ts   81 (+112 test)
  src/lib/skill-compressor.ts          68 (+59 test)
  src/lib/webhooks.ts                  58 (+175 test)
  scripts/_test-ecc-materialize.ts     20  (referenced by nothing at all)

Functions with exactly one occurrence in the repo — their own definition:
  discover.ts buildGemBadgeSvg        196
  skill-deps.ts topologicalSort        61 (+18 test)

Stale docs:
  bin/README.md                        32  describes a `soul` CLI and a
                                           bin/cli/lib/ layout, neither of
                                           which exists

Deliberately NOT deleted, though a generated report flagged them — 28 of its
76 non-picker findings were false positives, and deleting them blind would
have broken the build:

  analytics.ts recordSkillUsage  called by resources/hooks/skill-fire-tracker.sh
  bin/cue-slug                   called by bin/cue-learnings (the report's own
                                 grep passed --include='*.sh', so the
                                 extensionless caller never matched)
  runtime-gc.ts                  imported by commands/gc.ts and launch.ts
  handoff.ts (all 4 exports)     imported by commands/handoff.ts
  kitty-image probeKittyTerminal,
  clearKittyImagesSequence,
  skill-deps parseDependencies,
  skill-router Router* types     used within their own file; only the `export`
                                 keyword is redundant, which is churn, not dead
                                 code
  cloud.ts:255 "dead" branch     reachable: `cue cloud push x` gives
                                 argv[2]="cloud" and falls to the default arm

Also left alone: the picker block (~1057 lines) while that migration is in
flight, and launch.ts's token-budget re-export shim (all five symbols are used
inside launch.ts, so it is an export->import rewrite worth zero lines).

Verified: bun test --timeout 30000, 4 consecutive runs, 2819 pass / 1 skip /
0 fail across 219 files. typecheck exit 0. lint 0 errors, 6 warnings (baseline).
…e rate

28 of 76 non-picker findings were wrong. Record which ones and why, so the
next pass re-verifies instead of trusting the confidence column: a 'high'
rating only means the agent's grep came back empty, and those greps missed
shell hooks, extensionless bin scripts, dynamic imports, and string-keyed
command dispatch.
…nches in

A profile teaches the agent a domain. It cannot know that this repo runs on
bun rather than npm, that the tests are behind `just check`, or where the
entry point lives — so the agent guesses, and burns turns finding out.

`lib/project-brief` scans that off the filesystem and the launcher hands it
over: package manager (from the lockfile), the real test/build/lint/typecheck
commands (package.json scripts, Makefile, justfile, Cargo, pyproject),
entry points, layout, workspaces, data layer, what CI actually runs, and the
default branch. Verified only — nothing inferred, because a wrong fact costs
more than a missing one. `.env` values are never read; the scan notes only
that a committed `.env.example` exists.

Delivery is per process, deliberately NOT through the materialized memory
file: the runtime is keyed by profile and shared by every directory and every
parallel session using it, so repo-specific text there would leak across
projects and race between sessions. claude-code takes the brief inline via
`--append-system-prompt`; codex, which has no such flag, gets a per-cwd file
plus one *static* pointer line in AGENTS.md.

`cue brief` shows exactly what the agent receives. `--write` turns it into
`.cue/project.md`: a machine block that refreshes and a `## Notes` section
that never does — for the conventions no scanner can infer. `CUE_BRIEF=0`
opts out entirely.

Two bugs the real-repo smoke test caught, both fixed with tests: the layout
list sorted alphabetically and spent its budget on `action/ agentshield/…`
while cutting `src/`; and `--write` folded the notes back into the machine
block, duplicating them on every rewrite.

Tests: 33 new, driving the scanner through a stub probe (bun/pnpm/cargo/
python/go/monorepo fixtures, CI harvesting, caps, truncation, no-manifest
→ null, and an assertion that no `.env` is ever read), plus the brief-file
merge and the per-agent injection.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ing on it

Every failure the lexical matcher has is the same shape: a word means one thing
in a manifest and another in a profile description, and the fix is another
stopword. CLAUDE.md, @clack/core, requires-python, base-template each cost a
round of tuning, and the list is unbounded. A model reads the same evidence and
doesn't make that class of mistake. So: lexical proposes, the model judges.

This fits here in a way it did not fit the skill matcher. That hook runs on
every prompt and the prompt is always different, so an LLM call is a per-message
tax with a near-zero cache hit rate — which is why it ended up as an opt-in
`--deep` escalation. A repo's shape is stable for weeks. Keyed on the evidence
rather than the clock, the cache hits ~always and the model is consulted about
once per project. Measured: 9.8s cold, 0.124s warm.

The launch path still never waits. A warm entry is read in ~1ms; a cold one
serves the lexical answer immediately and spawns a detached process to fill the
cache for next time. Picker cost measured at 98ms warm, 45-74ms cold — the
model's 10 seconds are never on anyone's critical path.

claude-classifier extracts the spawn, the ephemeral CLAUDE_CONFIG_DIR isolation
and the credential copy-back out of skill-subset (477 -> 331 lines). That
machinery is subtle enough — the rotation race, the shared timeout budget across
the binary fallback — that a second copy would drift rather than stay honest.

`cue profile match [dir] --explain --deep` exists because the matcher's early
versions stayed wrong for as long as they did purely because nothing showed
which term caused a bad suggestion. It earned its keep within minutes: on a
small Python CLI it revealed "dependencies" named environment, intended,
operating, programming and topic — PyPI trove classifiers, read as packages by
the loose manifest scanner. Handed that same garbage, the model confidently
picked profiles about building cue itself. With the scanner fixed to skip `::`
lines it picks python + backend-base, which is right.

The scanner now also harvests inline dependency arrays (`dependencies = [...]`),
which are often the only place a pyproject declares anything real.

Verified live: agv_stack -> ros2 ("ROS 2 robot control, .urdf files, MRS
platform"); kolarortopedia -> backend + postgres, with the model correctly
dropping `vite` (matched on the vitest test runner) and `supabase` (matched on
the substring "postgre"). 2889 pass / 0 fail, tsc and lint clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The combine suggestion engine remembered every stack globally: combo-history
rows carried no directory, so "you launched this stack 4x" counted launches
from every project at once. At SCORE_COMBO (50-65 with the count bonus) that
outranked the cwd-scoped recent (max 50), so a favourite stack from an
unrelated repo led the picker in a repo that had never seen it.

Rows now record the launch directory, and readCombos scopes them by repository
root - a launch inside packages/core still sees the stack confirmed at the repo
root above it. Stacks confirmed here score 60-75; stacks known only from other
repos drop to 31-40, below anything cwd-scoped, and say so in their reason.

Backward compatible on both ends: an unattributed row (written before cwd was
recorded) never claims the current directory, and a caller that passes no scope
keeps the exact score and wording it had before.
growStack grafts your top historical partner onto every suggested stack, and it
does so with no reason line of its own - the profile just appears in the card.
That partner came from a global affinity map mined across every project, so a
pairing learned in one repo rode silently into the suggestions of an unrelated
one: open an API repo and backend+medusa-dev showed up because that is how you
work in a shop repo.

computeAffinityMap now takes an optional repo scope, and launch builds the
picker's partner map from the scoped one. Cross-repo habits still surface, but
only through channels that say where they came from - universalSuggestions, and
`cue suggest-pairs`, which now splits its table into pairings made here and
pairings made in other projects.

The scope predicate moves to lib/repo-scope, shared with combo-history so the
two history readers cannot drift into different notions of "here". Unscoped
callers - the universal-suggestion frequency pass, the dashboard - are
unchanged, and rows with no recorded directory are excluded under a scope
rather than credited to whichever repo happens to be open.
Recent was the last suggestion source still scoping by raw downward path
prefix, which the previous two rounds turned into an inconsistency: launch in
packages/core and cue would show you the stacks and pairings confirmed at the
repo root, but none of the sessions. The prefix rule cannot match a parent
path, so scoped Recent came back empty and launch silently fell back to the
global list - handing that subdirectory the profiles you use in other projects.

computeStats now takes the same repo scope as the other two readers, so all
three agree on what "here" means. The reason line becomes "last used in this
repo", which is what it now measures.

This path had no test coverage at all; it now has five, including the
subdirectory case that was broken and the outside-a-repository fallback.
Three defects made the card lead with a stack the user had never launched.

1. Saturation. The usage bonus was `min(sessions, 5) * 2`, so everything with
   five or more sessions scored exactly 50 - a stack launched 112 times in this
   repo tied with one launched five times, and the card's headline was decided
   by the alphabetical tie-break. Replaced with a logarithmic bonus: every
   doubling of use adds a fixed step, calibrated so the five-session case lands
   where it did before and everything above it keeps climbing.

2. Truncation. A recalled stack was grown and then cut to MAX_STACK_PARTS, so a
   four-part recent became a three-part stack that was never launched, captioned
   with the four-part stack's session count. Recollections are now shown as
   launched: conflicts still resolve, but no companions are bolted on and no
   parts are dropped. The cap still applies to stacks this module proposes.

3. First-write-wins dedup. Sources are scanned in a fixed order but are no
   longer ranked by it, so a foreign combo used 4x claimed the part-set and
   permanently suppressed the recent used 112x here. Dedup now keeps the
   best-scoring claim; ties keep the incumbent, preserving origin order where
   scores don't separate.

Measured in this repo, top suggestion before and after:
  before: career+skill-writer+core   (truncated from a 4-part stack, 5 sessions)
  after:  core+skill-writer          (112 sessions here)
`cue suggest` recommended a wedding-invitations skill because "date" appeared
195 times, and rated everything at confidence 1.00. Four compounding faults.

Counting the wrong text. It scanned raw transcript JSONL, so assistant prose,
tool names, tool output and file contents all counted as things the user
"mentioned" - "read" scored 798x because that is the Read tool. Only user text
parts are read now; tool_result parts arrive under role "user" too and are
excluded.

Substring matching. `indexOf` found "ops" inside "operations" and "and" inside
"command". Matching is now whole-word, via a single tokenizing pass that also
replaces a per-keyword regex compiled over megabytes of text - the command went
from seconds to ~0.2s end to end.

No stopword filter. Every SKILL.md description opens with "Use this when the
user asks...", so "use", "when" and "user" became keywords for the entire
catalogue. Filtered now, along with transcript-structure words, and the
remaining keywords are weighted by catalogue-wide rarity: a stopword list only
knows what is common in English, not that "mcp" appears in hundreds of these
skills and separates none of them.

Meaningless confidence. `min(1, mentions / 50)` reached 1.00 for every skill,
so the printed number carried no information and the ranking was arbitrary.
Score is now the strongest few signals - summing every match rewarded a long
description over a relevant one - on an asymptotic curve calibrated against a
real 355-candidate run. Measured confidence spread over this repo's
transcripts: p10 0.32, p50 0.54, max 0.72.

The reason line is computed from the same frequency map as the score, so it can
no longer name a keyword that never contributed - which is how "and" came to be
cited 8044 times.
Anthropic's OAuth rotates the refresh token on every refresh, and cue gives
each profile runtime its own copy of .credentials.json. Two sessions on
different profiles therefore hold two copies of one token: whichever refreshes
first silently revokes the other, which then hits a login prompt mid-session.
Measured on a real machine - 121 runtime copies, 75 distinct refresh tokens,
114 of 117 access tokens expired.

Sharing one file via symlink is the obvious fix and does not work: Claude Code
rewrites .credentials.json atomically (tmp -> rename), which replaces a symlink
with a regular file on the first refresh. Observable in any authmux runtime,
where cue symlinks .claude.json and every one has since become a plain file
while its neighbours (projects/, agents/) are still links.

So the sessions have to talk instead. cue already published a rotation to the
owning account dir on exit, which is too late - by then the sibling has already
been dropped. A live session now reconciles once a minute for as long as it
runs: it republishes its own rotation, and adopts anyone else's.

pullFreshestToRuntime is the missing inbound direction, gated on matching
accountUuid so alternating accounts can't hand each other tokens, and on
strictly newer expiresAt so two reconcilers settle rather than trading the file
back and forth. Polling rather than watching is deliberate: the same atomic
rename that defeats symlinks also breaks an inode watch.
…s it

The rotation heal added in ae59ebf never ran for the default account. Every
direction in credentials-sync is gated on a known accountUuid, and
readAccountUuid looked only at <dir>/.claude.json. With no CLAUDE_CONFIG_DIR
set, Claude Code keeps oauthAccount in the home-root ~/.claude.json and leaves
~/.claude/.claude.json a settings-only stub - so the default account read as
unknown and all three heals silently no-op'd: no candidates to sync from, no
publish to ~/.claude, no adopt out of it. Measured here: 76 distinct refresh
tokens across 123 runtimes, and 32 runtimes the heal could not see. The bug
hid because authmux account dirs DO carry identity in-dir, so they worked.

The basename gate keeps the fallback off those account dirs and off runtime
dirs, which all carry identity in-dir - a stray sibling .claude.json must
never be read as an account's identity or two accounts could trade tokens.

Second path to the same symptom: overlaySourceState copied .credentials.json
unconditionally, with none of the expiresAt comparison the rebuild path does.
It runs on every cache-hit launch, and cue sync / cue install resolve their
source with healFromRuntime: false - so one bulk sync could stamp a dead token
over every runtime at once. It now keeps whichever side is newer, scoped to a
single account so a deliberate account switch still re-seeds wholesale.

Last, the reconcile cadence. Copies of a blob share one expiresAt, so
concurrent sessions reach expiry together and refresh within moments of each
other; only the first rotation survives. A flat 60s poll cannot help when the
contended window is seconds wide, so the cadence now tightens to 5s across
that window and idles at a minute elsewhere. This narrows the race and does
not close it: cue does not perform the refresh, Claude Code does in-process,
so there is no point at which cue can serialize the two callers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A skill maintained in its own repo can be linked into the tree rather than
copied — browser/ego-browser now points at ~/Documents/ego-lite-linux, so the
Linux port's skill has exactly one source of truth instead of a hand-synced
duplicate that silently drifts.

walk() filtered category entries on dirent.isDirectory(), which is false for the
symlink itself, so the linked skill vanished from the index and `cue validate`
failed it as E3 SKILL_NOT_FOUND. Stat through symlinks; dangling links are
treated as absent. The materializer already symlinks skills, so this only makes
the read path agree with the write path.

Also register ego-browser in KNOWN_CLIS so the skill's Prerequisites section is
picked up by the CLI extractor.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
core declared browser/ego-browser, but the per-project loadout deferred it
everywhere no project signal matched — which is most projects, since the signal
set comes from package.json deps and framework detection and nothing there says
"browser". The declaration was real and the skill still never loaded.

Deferring it does not save a browser session; it sends the agent to MCP
round-trips or web fetch instead, which costs more than this skill's
frontmatter. So it joins the operational primitives in ALWAYS_KEEP, and the
matching slug set in profile-merge so a budgeted composite can't drop it either.

Verified on a neutral cwd with zero project signals: browser/ego-browser now
classifies full, where it previously landed in deferred.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every path that lands a new skill on disk — `skills add`, `discover install`,
`marketplace install-skill` — now scans it before anything registers it to a
profile. SkillSpector covers 68 vulnerability patterns across 17 categories
(prompt injection, data exfiltration, supply chain, dangerous code via AST,
YARA, MCP tool poisoning) on top of cue's own SEC1-3 criticals.

Policy reads the report's `recommendation` rather than the exit code, so the
three verdicts stay distinguishable: DO_NOT_INSTALL blocks, CAUTION registers
with a visible warning, SAFE is quiet. `--allow-unsafe` overrides the block.

install-skill differs from the others on purpose: the files are already on disk
by the time it runs, so a block reports findings and exits non-zero rather than
deleting anything, leaving them for review.

When the scanner isn't installed the gate degrades to cue's own rules and says
so, rather than silently passing everything.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rematerializing did `rm -rf runtimeDir` and only then renamed the new tree into
place, so the live path stayed nonexistent for the whole recursive delete —
seconds, on a runtime carrying a plugin cache and a backup chain. A Claude Code
session already running against that profile resolves its hooks through exactly
that path, so every hook firing inside the gap died with "No such file or
directory" (observed 2026-08-03: nine Stop hooks at once, mid-session).

Move the old tree aside instead: the path is unresolvable only between two
renames, and the delete runs after the new runtime is live. The `.old-*` dir is
a sibling of the swap target, so it cannot cross a filesystem boundary and sits
one level below the root runtime-gc scans. Leftovers from a swap killed between
the renames are swept best-effort on the next materialize.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…alette

The v2 card and the stack palette had grown their own copies of the same
drawing code. Both now hang off one set of primitives in picker/ui.ts, styled
after iOS grouped-inset lists: one rounded card per idea, uppercase muted
section headers instead of heavy rules, a filled pill for the single primary
action, circular selection marks instead of ASCII brackets, page dots for
"there is more to see here".

Everything in ui.ts is pure — no I/O, no TTY — and styleText is a no-op off a
TTY, so the tests assert on plain text.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Collects the remaining working-tree changes from prior sessions:

- resources/personas/integrity-protocol{,-compact}.md — wording pass
- resources/hooks/{tag-audit,liedetector-tag-density}.sh — confidence-tag
  density checks
- profiles/frontend-design, profiles/reverse-skill — two new profiles
- README.md, .cue.profile (cue's own pin: core -> core+skill-writer)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Brings 36 commits of main onto the branch, including the pane-badge fixes
(#111, #120) the branch predates. Six conflicts, all resolved to main's side:
main's SkillSpector (#108) is a strict superset of the branch's earlier take —
same exports plus baselineDirs/resolveBaselineFor and a --baseline runner arg —
and its security.ts keeps both `security scan` and `security baseline`.
README and the smart-loader perf test take main's wording likewise.

Full suite vs both parents, same conditions: 29 failures on the merge, 28 on
main, 34 on the branch, and zero that fail on the merge but on neither parent.
Nine smart-loader tests that were red on the branch go green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ge (#125)

The confidence protocol stated four different rules for the ~N% calibration
on yellow/orange tags. The always-on compact persona called it optional; the
skill called a bare [INFERRED] a protocol violation. So the rule that applied
depended on which source happened to be in context.

Settle on one: required, snapped to a 5-point raster (yellow ~50-85%, orange
~20-45%). The tiers no longer overlap each other or green. 14 steps rather
than a coarser ladder, because the number exists to order claims against each
other; not finer, because self-reported confidence is miscalibrated in
absolute terms and ~67% would read as a measurement that never happened.

liedetector-tag-density.sh gains an exact check for it: a yellow/orange tag
with no ~N%, or one off the raster, is now flagged. The two heuristics around
it stay heuristics; this one is a fact, since the protocol names the legal
values.

src/lib/integrity-ladder.test.ts is a drift guard. The raster lives in two
scripts that cannot import from each other -- the hook here, and the eval
grader in the resources/skills submodule, which also ships standalone via npx
to agents with no cue tree. The guard reads both definitions and asserts they
match, plus that all four prose sources state the same rule. Verified by
injecting a one-sided change and watching it fail.

src/lib/liedetector-hooks.test.ts is the first coverage under resources/hooks:
16 tests driving both Stop hooks through synthetic transcripts. Note the two
traps it documents -- transcript records must be compact JSON, and pointing
HOME at a temp dir breaks a wrapper-script python3, which silently turns every
"expects no output" assertion into a vacuous pass.

summon.test.ts: the mcp_status test pinned browser/lightpanda + core, and
1857088 (#121) dropped the lightpanda MCP from every profile that pinned it,
so it asserted a pairing that no longer existed. It now derives a live pairing
instead of hardcoding one, and fails loudly rather than skipping if none is
satisfiable.

README/llms.txt: 86 -> 87 profiles, which 062b40a left behind.

resources/skills: fast-forward the pointer onto opencue/skills#18, which
carries the matching grader change. e9e6657 is an ancestor, so nothing is
lost; the bump also closes a pre-existing 4-commit lag.

Co-authored-by: NagyVikt <nagy.viktordp@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
main carries a squashed copy (#122) of commits also in this branch's
history, so git saw the shared liedetector work as two independent
edits and conflicted on all three files.

Resolved toward this branch for the calibration sources: main's side is
the superseded decile ladder (~50/60/70/80, ~20/30/40), this branch's is
the 5-point raster from #125. Everything unique to main's side of those
three files was old-ladder text, so nothing is lost.

Both submodules resolved to main's pointers (#126): resources/skills
ba3d06d over the branch's 5a49e6c. The two are identical in the raster
sources and differ only in the generated catalog, where ba3d06d is the
more complete regen and is the submodule's own main head.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Cause: Cue isolates CODEX_HOME per profile while AuthMux manages ~/.codex/auth.json.

Tested: bun test src/lib/codex-auth-sync.test.ts

Tested: bunx tsc --noEmit

Co-authored-by: NagyVikt <nagy.viktordp@gmail.com>
@NagyVikt
NagyVikt merged commit c81d460 into main Aug 10, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant