32 Commits
Author SHA1 Message Date
esengine 81729ee851 test(relay): drive a hosted page against a real Studio through a stand-in relay in CI 2026-10-17 00:19:05 -07:00
xiexian 3f35e37388 feat(examples): add a runnable stdio MCP plugin 2026-10-01 19:14:56 +08:00
yhh 887c17e4bf feat(delta): update an install by the chunks it lacks, not a whole installer
Every Studio update downloads the whole installer (115 MB on Windows),
even when a release changes a few megabytes. Measured on the real
v2.18.1 -> v2.19.0 Windows builds: the Electron binary is identical but
for its version resource, the kernel changes, and the frontend changes
by about half a megabyte.

internal/platform/delta describes a release as a signed-to-be Index: every
file's path, size and SHA-256, and the content-defined chunks it is made
of (normalized gear CDC, 4/16/64 KiB). Chunks are stored zstd-compressed
under the SHA-256 of their plain bytes, so any chunk two releases share
is stored and fetched once. An update cuts the running install with the
same chunker; content-defined boundaries move with an insertion instead
of shifting every chunk after it. It fetches only the missing chunks,
checking each against its name, and writes the new tree to a staging
directory, checking every file against the Index. Validate refuses
paths that leave the tree, backslashes and drive letters, two paths
that are one file on Windows, and chunks that do not add up to their
file.

cmd/studio-delta builds an Index and chunk store from a release zip, and
replays an update from an unpacked older release. On the real pair it
fetches 21.9 MB instead of 115 MB, reuses 344 of 387 MB, and verifies
the staged tree, in about five seconds.

Nothing publishes or consumes this yet. Wiring it into the release
workflow and the updater comes next.

Adds github.com/klauspost/compress for zstd.
2026-09-24 03:47:15 -07:00
esengine 04e9bbe4a0 chore: remove site/, the main-v2 website, from the studio line
reasonix.io is built and deployed from main-v2 only. pages.yml, removed
in 68b031ae5, ran on main-v2 pushes. On studio the directory was a
remnant, and it still read release-notes/releases.json, which that
commit deleted. main-v2 keeps the site; nothing here published it.

- site/ (60 files) is deleted.
- ci.yml loses the site job, which ran site/'s node tests, and the
  `site` output of the path filter. The code and desktop skip patterns
  no longer name site/.
- .gitignore loses the five site build-output rules.
- The crash-report worker's shell comment names the reasonix.io website
  instead of a stylesheet path that no longer exists here.

studio has no branch protection or required status checks, so dropping
the job leaves no check pending. actionlint passes and make lint is
clean. internal/fileref still skips site/.astro in file pickers: that is
workspace behavior for users' Astro projects, not a dependency on this
directory.
2026-09-16 23:05:14 -07:00
esengine 0b8b28e3c3 chore: ignore the tool-surface-cache build artifact
The benchmark is a command, so building it writes a binary at the repo root.
repolint refuses one that nothing ignores.
2026-09-14 09:42:23 -07:00
esengine e6db271d9f feat(config): the DeepSeek catalog is one declaration, and its rates are one table
The flash model is `deepseek-flash` everywhere: the id it is served under
lost the generation it never needed. An installed entry is renamed rather
than left behind, and the rename moves every key that named the old id with
it — a price or an effort list stranded under the old key stops answering
for the model that replaced it, which reads downstream as a setting the
user edited reverting to its default.

A preset is now what settles which of its models read images. Reading a
name misses every Kimi, Qwen, MiniMax and Claude model these presets
declare, and `deepseek-flash` spells nothing at all; the spelling stays the
last resort for an address nothing has declared, and stays a correctable
suggestion rather than a claim. The connection that existed only to reach an
image-taking model folds into the entry beside it once that entry reaches
the same model on the same key — unless its user configured it, because
folding would answer their edit by deleting where it lived.

A preset changing shape reaches installs holding the old one through one
declared shape per generation and one upgrade that brings a matching entry
forward. The guard is the whole shape, because an entry holding what we
shipped is ours to move and anything else its user curated. That retires
nine hand-written migrations that each recognised one vendor by hand.

Official list prices live once, in `officialRates`, oldest generation first.
A price change appends rather than edits: the superseded rate is what lets
an installed config be recognised as holding one of ours and brought
forward, instead of quoting a retired rate for the rest of its life. `config`
reads them through `billing.CurrentRate` and `SupersededRates` and restates
none of them — a table written twice is one that drifts, which is how a
vendor's USD rate ended up published on one side only.

`make pricecheck` reads each vendor's own page and compares it against that
table, and says how long ago a person last confirmed each one. It reaches
the network, so it stays out of lint and out of CI. A page it cannot read
exits non-zero: unread is its own verdict and never counts as agreement.
2026-09-13 18:47:33 -07:00
esengine babe9be7e1 feat(workgroup): freeze the natural-value protocol before its sample exists
Whether a turn's execution is worth offering as one foldable unit is a
question about ordinary use, and the only honest way to answer it is to watch
ordinary use. Designing tasks to cover the interesting cases would put the
task designer's choices into the granularity being measured, which is the one
thing this cannot afford.

So the protocol is fixed now, while no sample exists to fit it to. The first
twenty eligible authored turns after the freeze timestamp, in the order they
happen — not the last twenty, not a chosen twenty, and not twenty-four because
the answer landed near a line. Eligible means the turn carries a named
authored turn, which only the current schema emits and a synthetic
continuation never gets, and that its trajectory reads complete, because a
prefix cannot answer a question about a whole turn.

A turn with no tool calls is in the sample. The question is how much of
ordinary use has something to fold, and dropping the quiet turns would answer
a different one and flatter the feature. There are no route quotas either: the
mix of one-model and two-model turns is reported as a description of what
happened, never selected for.

The tool refuses to say anything but the count until the sample is whole.
Reading a partial result and acting on it is the sequential decision the
freeze exists to prevent, so there is nothing to read.

The verdict is one gate with both halves required — most groups holding a
single call AND few turns having three or more to fold — and failing it is not
an automatic go: a partition can be coarse and still rare, or common and still
thin, and those are different products.

The fold this measures with is the one the semantics pass settled, moved out
of that test rather than copied: two implementations of a partition drift, and
the assay and the fixtures have to be reading the same rules. The gate itself
runs on samples built to sit on each side of it, because an instrument nobody
has seen produce a number is one nobody has checked.
2026-09-07 19:55:24 -07:00
esengine 37fdd2ac17 bench(catalog-detector): who owns "the listing is owed again"
12a fixed the skills listing by having each writer announce itself, which is
correct and does not scale: the same probe showed a skill file written past
those writers — an editor, a script, the model's own write tool — is in the
canonical registry and still owes nothing. A contract that reads "every writer
remembers to announce" is one forgotten call from the defect it just fixed.

So this measures the three candidates rather than picking one. Rebuild the
projection and compare it; fingerprint path, size and mtime; or trust that every
write announced itself. Owed is measured, not declared: the block is rendered
before and after each transition, and owed means those differ. Under that
definition rebuilding the projection is not an estimate of the truth, it is the
truth, and what the arms establish is where the two cheap detectors diverge
from it.

Both diverge, in opposite directions. Metadata misses a change whose length and
mtime are preserved, which is not exotic — a branch switch restores content and
stamps it with the mtime the file had — and it fires on a touch and on a body
edit, neither of which changes a line the model sees. Observed-write is blind to
every out-of-band write by construction; it can be an optimization, never the
owner of correctness.

The cost argument for the cheap ones then mostly evaporates. Rebuilding the
projection costs 166µs on a workspace with no skills of its own, 668µs at ten,
2.4ms at fifty and 6.4ms at two hundred; metadata saves 25% of that at two
hundred and 60% at ten, because the walk is the cost and reading the files is
not. A turn costs seconds. Paying the truth once per turn is noise, and it was
avoided on an assumption nobody had measured.

Two measurement traps are worth naming. Timing a detector with its own
accounting inside it measures the accounting: attaching the file/byte counter to
the projection detector reported it 2.5x slower than it is. And pointing the
project root and the home root at one directory walks it twice, which doubled
every number until they were separated.
2026-09-05 05:35:01 -07:00
esengine 9d6a8b80b4 build: a command's binary is not something git can notice
`go build ./tools/repolint` writes the binary into the working directory, and
two of those — repolint and covergate — had been committed. Nothing builds them
that way on purpose: every caller, Makefile target and CI step runs them with
`go run`. So any probe, agent or script that builds one produced a diff on a
tracked file that had nothing to do with what it was doing.

Untracking them is half of it. The other half is that the same accident is
available to all thirteen commands in the root module, and only three were
ignored, so the ignore list is completed from the package list rather than from
the two names that happened to get caught.

`build-artifact` holds the two lists together: every main package in the root
module must have its output name covered by a root-anchored .gitignore entry.
It reads the package list, so a command added tomorrow fails before anyone
builds it, and it passes on a clean checkout where there is nothing lying around
to observe — a rule that only fired on a dirtied tree would be silent in CI,
which is the one place this needs to hold. A pattern that is not root-anchored,
or that names a directory, does not count as covering the drop.

The walk stops at a nested go.mod: a command in another module builds into that
module's directory, and the root .gitignore is not what covers it.
2026-09-05 04:43:54 -07:00
esengine fe476b8494 test(bench): preflight the request the model is handed, not the projection
The fixtures were checked against the model-visible projection. That is what
the agent believes; a provider.Request is what the model is told, and
rendering, sanitization and tool-schema construction sit in between. So each of
the 36 arm states now resumes the way a restart does — transcript from disk,
projection from its sidecar, the shipped tool surface at that arm — runs one
round against a capture provider, and is judged on the request it produced.

The capture answers with a clean terminal response. An empty one would buy an
empty-final retry and a second request, and the round under inspection is the
first. The turn's request is the one carrying tools: a fold on the way in asks
the same provider for a digest, and taking request[0] blindly inspected the
summarizer's prompt instead.

The scan enumerates the provider-visible surface rather than marshalling a
Message whole — content, reasoning_content, tool-call names and arguments,
responses_items, and every tool name, description and parameter schema — so a
leak is reported where it is, and local-only fields the boundary is supposed to
strip cannot produce a false hit. Those are asserted absent in their own right:
raw_content, LocalOnly, decision receipts, tool execution and memory citations
crossing the boundary is a regression whether or not a marker rides along.

It found what the projection check could not. i05's answer marker "session"
was readable in tools[0].parameters and tools[2].parameters — in all four arms,
because a tool schema is not part of any projection. r01's "partial" was one
ordinary word away from the same fate. Both are nonces now.

Search arms are held to the ablation's definition: both keep positions, only
the control has query, and the search-off arm's request may not contain the
wording that offers a search — checked over the whole surface, not just the
schema, so a description or an index line cannot reintroduce it.

Getting a resumed fixture to keep its projection turned up one more thing worth
recording: projectionCoversTail cannot revalidate a projection that covers the
entire transcript, since it has neither a matching transcript version nor a
live tail to sit in front of. Real sessions always have one, because work
continues after a fold. The fixtures now do too.

Verified by making each fail: stop scanning tool schemas and i05's leak goes
quiet; leave the search tool unswapped and the search-off arm is named twice,
once for the parameter and once for the wording.

benchmarks/* join cmd/ and desktop/ as hosts in the layering rule. Each is a
main that assembles a real agent, and a harness reproducing the shipped tool
surface has to reach the frontend that defines it. Nothing is opened to the
kernel by this: the rule that matters is that nothing below a frontend may
import one, and a benchmark is above every frontend.

No provider is called anywhere in this commit. This is the gate that says the
API spend would measure what it claims to.
2026-08-28 10:08:40 -07:00
yhh 72dfeaadf8 fix(installsource): a symlink case skips on what the filesystem answers, not on the OS name
TestApplyLocalSkillLinkMode asked runtime.GOOS, and asked it after asserting
resp.OK — so a machine that cannot create a symlink failed on the refusal the
tool had correctly reported, rather than skipping. Windows allows symlinks under
Developer Mode or elevation, which the platform name cannot tell apart from a
plain account, so the check is now the read three cases in this file already
make: try it, and skip on the error the filesystem gives back. A Windows host
that can make one now runs the case instead of skipping it.

Also ignore *.exe~ — what the linker leaves when it cannot replace a binary that
is still running, which on Windows is every rebuild while Studio is open.
2026-08-26 21:27:38 -07:00
yhh c777cb73ab fix(repo): a built repolint binary is not a source file
go build ./tools/repolint leaves repolint.exe in the tree, and it went in with
the previous commit. Removed, and *.exe ignored so the next one cannot.
2026-08-20 23:44:38 -07:00
YHH 84c257e56b feat(bench): measure delegation independence and anchor resistance (#8154)
Reasonix isolates a child's session but not its conclusions. The context
capsule records, field by field, what a child inherited from its parent
and every field is false — yet the one channel that actually carries
information, the parent's free-text prompt, is not modelled at all. Two
host-recorded instruments close that gap.

Evidence origin splits what a delegation's own text pointed at into
directory scope, which is the unavoidable cost of handing work off, and
named files, which say where the answer is. The child is credited only
for paths no named file already covered, so a scope hint never erases
its work. The two are counted apart because one number reports the cheap
act as the expensive one — the combined form read 0-3% independence
where the split reads 94-100%.

The -anchor arm hands the agent a hypothesis before it has read
anything: the task's real cause, a plausible wrong one, or none. Tasks
without an authored seed are skipped rather than run as the control, so
a seeded denominator never absorbs a control run.

Measured over 210 runs across two corpora (deepseek-flash, 5 reps each),
anchor resistance is 1.00 on both — including one whose answers no
command can check — while a wrong hypothesis costs ~2x wall and ~1.35x
tokens to unwind. Children discovered 94-100% of their own evidence:
this parent hands scope, not answers.

Also guards two failures found while running it: the bench read metrics
tags the agent had stopped emitting, and a failed grader's output never
reached the record that had to explain the failure.
2026-08-10 14:08:43 +08:00
yhh 63d409359d build(lint): make function size and complexity a gate, not a report
The complexity monitor ran funlen and cyclop with issues-exit-code forced to 0
and said so in its own script: "Future work: block only new violations relative
to this baseline." A report nobody is blocked by is a report nobody reads, and
that future is now buildable — repolint's ratchet already carries five rules.

Measuring in repolint rather than parsing golangci-lint output keeps one set of
semantics (weight is the excess over the limit, per file and repo-wide) and
widens the scope from internal/agent to the whole tree. The thresholds are the
ones the monitor already used: 120 lines, complexity 30, tests excluded.

What that surfaces is not marginal: build is 1932 lines at complexity 309, and
chatTUI.update is 1005 at 252. Both are frozen now — they cannot gain a line or
a branch.

The report-only script, its config, and its two CI steps are deleted rather
than kept alongside: the same metrics now have a gate that stops people.
2026-08-07 08:04:10 -07:00
YHH ee8a85ec04 chore(standards): machine-enforce comment, file-size, and layering rules (#7806)
* chore(standards): gate comments, file size, and layering in CI

The comment and file-size rules lived only in an untracked CLAUDE.md written
for a TypeScript project, so nothing ever checked them, and REASONIX.md told
editors to match the surrounding comment density -- which is what reproduced
the essays. tools/repolint makes the rules machine-checkable and ratchets
them: recorded debt passes, anything new fails the required lint check.

Weights are excess lines rather than counts, so trading a short violation for
a long one still trips the gate. Layering encodes the contract REASONIX.md
already claimed and the import graph already satisfies, with one recorded
exception (capdiag reaching into boot).

* chore(standards): point CLAUDE.md at REASONIX.md

Claude Code loads CLAUDE.md and Reasonix loads REASONIX.md, so a repo with
only the latter leaves Claude Code sessions with no standing instructions.
A committed one-line pointer keeps the single source of truth without the
two files drifting apart again; the negation un-ignores it for contributors
whose global gitignore drops CLAUDE.md.
2026-08-07 11:25:52 +08:00
SivanCola b6da01dbdc feat(release): separate Stable and Preview histories
Context: Stable and Preview need independent reviewed identities while CLI, Desktop, npm, and changelog publication remain one protected release event.

Changes:
- Add a protected Preview orchestrator with a shared ordinal and SignPath preflight.
- Bind reviewed release notes to immutable all-surface publication markers.
- Route downloads and changelog archives by channel with accessible theme-aware navigation.
- Harden release authorization, manifests, tests, and operator documentation.

Compatibility:
- Legacy catalog entries without managed status remain readable and public.
- Existing Stable pointers remain unchanged, and Preview continues mirroring the legacy canary pointer for older Desktop clients.
- No persisted user state or Wails JSON contract changes.

Verification:
- bash scripts/release-workflows.test.sh
- node --test scripts/release-notes.test.mjs
- cd desktop && go test ./cmd/sign ./internal/update
- cd site && npm test
- cd site && npm run build
- actionlint for all release workflows
2026-07-31 01:12:23 +08:00
SivanCola 26b256b5d5 refactor(agent): simplify run/tool state and harden live tests / 重构 Agent 状态并隔离真实测试 (#7010)
* refactor(agent): add TaskTool Options constructor / 增加 TaskTool Options 构造入口

Problem:
NewTaskTool takes 18 positional parameters, which is hard to extend and
easy to mis-order at call sites.

Root cause:
Construction was only exposed as a long positional helper with no
structured options type.

Fix:
- Add TaskToolOptions and NewTaskToolWithOptions as the internal standard
  entry point (empty SysPrompt still maps to DefaultTaskSystemPrompt).
- Keep the 18-arg NewTaskTool as a compatibility wrapper for one full
  iteration cycle.
- Migrate boot wiring and the shared test helper to Options.
- Add equivalence tests for config, schema, empty prompt, resolver, and
  execute behavior.

Verification:
go test ./internal/agent -run 'TestNewTaskToolWithOptions|TestTaskTool' -count=1
go test ./internal/boot -count=1

* refactor(agent): split Run and executeOne state machines / 拆分 Run 与 executeOne 状态机

Problem:
Agent.Run and executeOne mixed lifecycle, turn policy, and tool policy in
two very large methods, raising cognitive load without changing behavior.

Root cause:
Turn-loop counters and tool-call gating lived inline instead of package-
private staged helpers with a single ownership model for locks/defers.

Fix:
- Keep Run as the lifecycle owner of workspace lease, evidence commit,
  delivery checkpoint, steer queue, and active-turn cleanup.
- Extract runLoopState plus beginRunTurn, runToolLoop, handleFinalResponse,
  and handleToolRound without changing stream/persist/batch/compact order.
- Split executeOne into parseToolCall, resolveToolPolicy (plan/proxy,
  delivery, recovery/permission), prepareToolExecution, and
  finishToolExecution behind toolCallPlan, preserving permission-before-
  lease and PreToolUse-before-execute semantics.

Verification:
go test ./internal/agent/... -count=1
go test -race ./internal/agent/... -count=1
REASONIX_RELEASE_CACHE_GUARD=1 go test ./internal/agent -run '^TestReleaseCacheHitGuard$' -count=1

* ci: report-only agent complexity monitor / 增加 agent 复杂度报告模式监控

Problem:
The repo already runs gofmt, vet, build, full tests, race, and
golangci-lint, but had no trend signal for remaining internal/agent
hotspots after structured extraction.

Root cause:
Main lint intentionally avoids funlen/cyclop hard fails that would
generate a flood of pre-existing noise.

Fix:
- Add a report-only funlen+cyclop config scoped to internal/agent
  production code with generous thresholds (not a hard 50-line rule).
- Run it beside the existing lint job with issues-exit-code=0, write a
  CI summary, and upload the report as an artifact.
- Ignore the generated local report file.

Verification:
bash scripts/agent-complexity-report.sh

* test(provider): isolate live DeepSeek cache probe / 隔离 DeepSeek 真实缓存探针

Problem:
The environment-gated DeepSeek cache probe ran during ordinary test suites whenever a stale API key was present, and its diagnostics still claimed tool-call reasoning replay should have zero prompt-token cost.

Root cause:
The probe had no explicit live build tag and retained commentary from the obsolete policy that stripped reasoning_content from DeepSeek tool-call turns.

Fix:
Gate the probe behind the live build tag, document the tagged invocation, describe required replay versus the empty-key recovery fallback, and fail when the required replay request is rejected.

Verification:
- go test ./...
- go test -race ./internal/provider/openai -count=1
- env -u DEEPSEEK_API_KEY go test -tags live ./internal/provider/openai -run TestRealDeepSeekCacheProbe -count=1 -v
- go vet ./internal/provider/openai
- ./scripts/cache-guard.sh
2026-07-28 23:16:58 +08:00
SivanCola 65fcd46525 Add a Remote SSH module (remote serve + tunnel) / 新增远程 SSH 模块(远端 serve + 隧道) (#6673)
* feat(serve): support --port-file/--token-file/--pid-file for supervised headless serve

Extract RunGracefulListener so callers can bind first (--addr 127.0.0.1:0),
record the real port, and keep the auth token out of argv. Groundwork for the
remote-SSH module's detached remote serve bootstrap.

* feat(config): add user-global-only [remote] section for SSH hosts

[[remote.hosts]] entries carry connection/forward/workspace settings and name
credential env vars (passphrase_env/password_env) instead of secrets, mirroring
the provider api_key_env idiom. Like [secrets], loadForRoot pins the section
back to the user-global value after the project merge — with a deep copy,
because TOML decoding mutates slice backing arrays in place and a shallow pin
would alias the project-injected hosts. Remote credential env names flow into
CredentialEnvNames so they are filtered from tool subprocess environments.
Adds RemoteStateDir/RemoteKnownHostsPath and the internal/store name helpers
for the remote-side serve state layout.

* feat(remote): SSH transport kernel — dial, auth, host keys, forwards, SFTP

New internal/remote package group provides the connection layer for the
remote module:

- client.go: supervised connection with a single-goroutine state machine,
  keepalive ([email protected]), full-jitter exponential-backoff
  reconnect, shared SFTP (generation-checked), and forward re-attach.
- auth.go: OpenSSH-like method order (agent, identity file, default
  identities, password, keyboard-interactive); interactively-obtained
  secrets are cached in-process so reconnects never re-prompt.
- knownhosts.go: system known_hosts read-only + a Reasonix-managed TOFU
  file; key mismatch is a non-promptable hard error.
- dial.go: ProxyJump chains (netclient proxy applies to the first hop only).
- forward/: -L (persistent local listener across reconnects) and -R
  (re-registered on each attach) with typed ErrBindBusy/ErrDuplicateForward.
- sftpfs/: list/stat/capped-read-with-text-detection/atomic-write/mkdir/
  rename/remove, quarantining the new github.com/pkg/sftp dependency.
- netclient/dialer.go: StreamDialer honoring the user's proxy policy
  (SOCKS5 via x/net/proxy, http/https via CONNECT).
- sshconfig.go: ~/.ssh/config import via github.com/kevinburke/ssh_config.
- sshtest/: in-process SSH+SFTP server for tests.

New deps: github.com/pkg/sftp, github.com/kevinburke/ssh_config (both pure
Go). x/* versions kept at main-v2's pins.

Cache-impact: none — no prompt/tool/provider path touched.
Cache-guard: n/a (new leaf subsystem, not in the system-prompt prefix).

* feat(remote): remote serve bootstrap over SSH

internal/remote/bootstrap ensures a detached 'reasonix serve' runs for a
(host, workspace) pair: reuse a live process (the reconnect path), else detect
the remote OS/arch (uname), locate/install/upload reasonix, write a 32-byte
token 0600 via SFTP, launch serve bound to 127.0.0.1:0 with --token-file/
--port-file (token never in argv), poll the port file, and persist ServeState
JSON. Status/Stop/Logs manage the recorded process. Every remote command
operand is POSIX single-quote-escaped (golden test covers '; rm -rf ~').
V1 targets Linux/macOS remotes; cross-platform upload without a matching local
binary is a documented limitation directing to serve_install = npm.

Cache-impact: none — new leaf subsystem, no prompt/tool/provider path.
Cache-guard: n/a.

* feat(cli): reasonix remote command group + /remote slash

Adds the remote subcommand tree (add/list/remove/import/test/connect/open/
status/forward/serve/fs), mirroring the mcp command shape. Host CRUD writes to
the user-global config only (remote hosts are pinned there); connect runs a
foreground supervisor that dials, bootstraps remote serve, forwards the serve
port with the token in a loopback URL, applies configured -L/-R forwards, and
holds the tunnel until Ctrl-C while the remote serve keeps running. Terminal
prompts handle key passphrases, passwords, and TOFU host-key confirmation;
dialing honors the user's proxy settings via netclient. The /remote slash
command shows a read-only host summary. New i18n fields added to all three
catalogs.

Cache-impact: none — CLI surface + i18n only, no prompt/tool/provider path.
Cache-guard: n/a.

* feat(desktop): remote SSH Go bindings (remote_app.go)

25 App bindings behind a narrow remoteKernel interface (fake-injected in
tests): host CRUD (user-global config), Connect/Disconnect/status,
status-driven TOFU host-key confirmation (ConfirmRemoteHostKey resumes a
parked dial), SFTP list/read/write-with-mtime-conflict/mkdir/rename/delete,
port-forward CRUD, and remote serve EnsureServer/Open/Stop/Status/Logs.

desktopRemoteManager wraps internal/remote per host; status/forward/server
transitions bridge to remote:status / remote:forwards / remote:server through
the asyncRuntimeEmitter (never blocking the kernel). The manager is created
lazily on first use and closed in App.shutdown, rejecting any pending host-key
prompt. OpenRemoteWorkspace forwards the serve port and opens the tunneled URL
via Wails BrowserOpenURL; last workspace/host persist to desktop-remote.json
(all-optional fields, backward-compatible).

desktop/go.mod gains pkg/sftp + kevinburke/ssh_config via go mod tidy.

Cache-impact: none — desktop bindings only, no prompt/tool/provider path.
Cache-guard: n/a.

* feat(desktop): remote SSH frontend (host manager, explorer, ports, server)

- lib/types.ts: Remote* TS mirrors + SettingsTab "remote".
- lib/bridge.ts: 25 Remote* AppBindings + browser-dev mock + onRemoteStatus/
  onRemoteForwards/onRemoteServer subscribers with a mock fan-out for tests.
- store/remote.ts: zustand store; pending_hostkey status drives the global
  fingerprint dialog and resolves on the next non-pending status.
- RemoteHostsPage (Settings > Remote SSH tab): host list with status chip,
  connect/disconnect/open, add/edit form, ssh_config import picker.
- RemotePanel: overlay drawer with Files (lazy tree + textarea editor with
  mtime-conflict Reload/Overwrite), Ports (live forward rows + add form), and
  Server (bootstrap phases + open-in-browser + logs) tabs.
- RemoteHostKeyDialog: global TOFU modal off the store's pendingFingerprint.
- StatusBar remote chip (worst live state, click opens explorer); App.tsx
  subscribes remote:* app-wide and renders the panel + dialog.
- i18n keys added to en / zh / zh-TW (key sets stay aligned).
- Editor is a monospace textarea (no CodeMirror/Monaco) per lean-deps.
- Tests: src/__tests__/remote-store.test.ts (added to the test script).

Cache-impact: none — desktop frontend only.
Cache-guard: n/a.

* docs: document the Remote SSH module (SPEC, GUIDE, CONFIG_PATHS, CHANGELOG)

Adds the internal/remote layout + acyclic dependency note to SPEC 2, a bilingual
Remote SSH section to GUIDE.md / GUIDE.zh-CN.md (config, CLI, reconnect/persistence
semantics, host-key TOFU, remote-side state layout, desktop UI), the managed
known_hosts path to CONFIG_PATHS(.zh-CN), and a CHANGELOG entry.

Cache-impact: none — docs only.
Cache-guard: n/a.

* chore: ignore stray .reasonix state under package dirs

A bootstrap test whose RealPath resolved to CWD once wrote a .reasonix/remote/
token under the package dir and it was swept into a commit. The offending file
has been scrubbed from history; this guards against recurrence.

* fix(remote): CI green — errcheck + cross-platform bind-busy and Windows test skips

- client.go: check cl.Wait() return (errcheck lint).
- forward: classify WSAEADDRINUSE as bind-busy on Windows (build-tagged
  addrinuse_windows.go), so ErrBindBusy is reported cross-platform.
- sftpfs / bootstrap integration tests skip on Windows: their in-process SFTP
  server serves the LOCAL filesystem, so on Windows it serves drive paths and
  the POSIX-remote path translation breaks — a harness limitation, not a
  product one (V1 remotes are Linux/macOS). Linux/macOS CI covers the flows.

Cache-impact: none — test/lint-only + a Windows errno check.
Cache-guard: n/a.

* test(remote): skip EnsureServe reuse/relaunch/stop tests on Windows at setup

TestEnsureServeReusesLiveProcess and siblings pre-write state via pathsFor()
(POSIX path.Join) before newFakeConn runs, so on Windows the temp-dir remote
home yields a malformed drive path and the setup os.WriteFile fails. Move the
Windows skip to the top of each such test (shared skipOnWindows helper) so it
fires before any pathsFor/os setup, not just inside the harness ctor.

* fix(remote): address review — P1 security / data-integrity / lifecycle

Resolves the 14 findings from code review (all verified against the code):

Security
- Serve token no longer leaks to the remote log: runServe suppresses the
  token share line under --port-file (supervised), and the launch script
  creates the log 0600 (umask 077 + chmod). (F1)
- ProxyJump no longer sends the target's password to jump hosts: each hop gets
  its own credential-free AuthOptions with a per-hop cache (persisted on the
  Client so reconnects don't re-prompt). (F2)
- CLI surfaces a misconfigured proxy instead of silently dialing direct; the
  https-proxy path now TLS-wraps the connection before CONNECT; desktop passes
  the proxy Dialer it previously omitted. (F14)

Data integrity
- `remote fs get` streams the full file via sftpfs.Download instead of the
  4 MiB-capped preview reader that silently truncated. (F3)
- Workspace slug carries an FNV hash of the exact path, so /srv/a-b and
  /srv/a/b no longer share serve state files. (F10)
- Desktop host edit merges: passphrase_env/password_env/forwards (absent from
  the desktop input) are preserved instead of wiped. (F7)

Lifecycle / correctness
- Bootstrap gates on `serve --help` advertising --port-file, not a version
  number (the flags ship in this change, so no released version qualifies). (F4)
- Launch uses setsid only when present, so stock macOS (no setsid) works. (F5)
- Desktop no longer uploads the GUI binary as the remote CLI (LocalBinary=""
  makes the upload path fail clearly toward npm). (F6)
- Serve tunnel re-binds to the current serve address on every EnsureServer and
  is torn down on Stop; per-host serveMu serializes concurrent ensures. (F8)
- Reuse/Stop verify the pid is actually a reasonix serve (ps args), not just
  kill -0, so a recycled PID is never mistaken or signalled. (F9)
- Desktop connect retries after failure (failed Start drops the managed host)
  and builds a fully-populated host under the lock so a concurrent Disconnect
  can always close it. (F11)
- SSH handshake is bounded by a conn deadline (ClientConfig.Timeout does not
  cover NewClientConn), so a banner-less host can't hang the dial or Close. (F12)
- ScanSSHConfig returns a non-nil slice so an empty result encodes as [] not
  null. (F13)

Tests: per-hop auth isolation, full-file download vs preview cap, slug
collision (/srv/a-b vs /srv/a/b), serve-alive/launch/locate command shape,
desktop edit-merge and non-nil scan. go test ./... green in both modules;
gofmt/vet clean; Windows cross-build green; cache-guard pass.

Cache-impact: none — no prompt/tool/provider/memory path touched.
Cache-guard: ./scripts/cache-guard.sh — all cases pass.
System-prompt-review: not required — no system-prompt-feeding config field changed.

* Harden Remote SSH lifecycle and packaging

Problem:
Remote SSH could misapply ProxyJump credentials, race connection and service lifecycles, interrupt live forwarding during replacement, and ship Desktop builds without the CLI sidecar required by upload installation. Several UI failures were also silent or left stale state behind.

Root cause:
Jump aliases shared endpoint-keyed authentication, bootstrap ownership relied on weak state checks without cross-process serialization, and Desktop operations lacked generation-scoped cancellation. Forward replacement stopped the working listener before the replacement was ready, while packaging and updater contracts did not include the CLI binary.

Fix:
Resolve and authenticate every SSH hop independently, make dialing and Desktop operations context-aware, serialize bootstrap with an atomic remote lock, verify exact process ownership, and make forward replacement failure-atomic. Harden token, SFTP, agent, config, and ssh_config handling; package and update the CLI sidecar on macOS, Windows, and Linux; and surface actionable validation and lifecycle errors in the Desktop UI.

Verification:
- go test ./...
- cd desktop && go test ./...
- go test -race for Remote, CLI, store, and Desktop modules
- go vet ./... for root and Desktop modules
- pnpm test:all and pnpm build
- Windows amd64 cross-compilation for Remote, bootstrap, forwarding, SFTP, and CLI packages
- clean merge snapshot against latest origin/main-v2 with root, Desktop, and frontend checks passing

* Add native Remote SSH workspace windows

Problem:
Remote SSH was buried in settings, and opening a remote workspace handed the authenticated loopback URL to the system browser instead of preserving a desktop workspace experience.

Root cause:
The desktop had no window-level Remote SSH control and no isolated Wails launch mode for a second native workspace window.

Fix:
Add a VS Code-style status entry, host switcher, command actions, and Remote dock. Launch remote workspaces in a lightweight Reasonix process that skips local session ownership, single-instance focus, tray services, and Wails bindings. Pass the loopback URL through a one-shot permission-restricted ticket so the token never appears in process arguments.

Verification:
- go test ./...
- pnpm test:all
- pnpm build
- wails build -clean
- native macOS multi-window smoke test
- git diff --check

* Tidy Remote SSH module metadata

Record the SSH config and SFTP packages as direct dependencies after rebasing onto the latest main-v2 baseline. Remove stale module checksums so the root module remains tidy in CI.

* Fix Remote SSH Windows test assumptions

Load the fixture SSH config by its explicit path while setting both platform home variables, and use the Windows .exe suffix for the desktop CLI fixture. This keeps the Remote SSH coverage portable across the CI matrix.
2026-07-19 21:48:20 +08:00
SivanCola 7f00d2c260 feat(desktop): Redesign theme experience with official packs / 主题体验改造与官方主题 (#6614)
* feat(desktop): Add Theme Pack V1 engine with theme library / 新增 Theme Pack V1 引擎与主题库

Controlled, non-executable desktop skins: semantic color tokens, density and
corner recipes, and an optional local background image with focus and safe
area. Includes manifest validation, atomic import with replace confirmation,
content-addressed asset serving, live preview with cancel restore, density
consumers, transparent layout under backgrounds, contrast warnings, and
safe-mode fallback to Graphite. / 受控不可执行的桌面皮肤:语义色令牌、密度
与圆角配方、可选本地背景图(焦点与安全区)。包含清单校验、原子导入与覆盖
确认、内容寻址资源服务、可取消的实时预览、密度消费者、背景下布局透明化、
对比度警告与安全模式回退 Graphite。

* feat(desktop): Redesign theme experience with official packs / 主题体验改造与官方主题

Problem:
Appearance settings exposed two competing base-style pickers and applied
theme packs immediately on card click, while activeThemeId could store base
style ids. Official skins and a gallery/overview IA were still missing.

Root cause:
Theme Pack V1 layered packs on top of desktop.theme_style without a unified
state model or select-then-apply UX, so base styles, official themes, and
user packs competed for the same controls.

Fix:
- Bump desktop-theme-state to schema v2: activeThemeId is official/user only;
  migrate legacy base-style active ids into desktop.theme_style.
- Add GetThemeExperience, ActivateBaseStyle, DisableThemePack, and
  RestoreGraphiteAppearance; reject base ids in ActivateThemePack.
- Embed eight official themes with fixed order, asset routing, and budgets.
- Ship AppearanceOverview + ThemeGallery (select ≠ apply, temp preview,
  immersive detail) driven by themeExperience.ts.
- Document the IA and provenance; keep procedural art as placeholders.

Verification:
- cd desktop && go test ./...
- cd desktop/frontend && pnpm typecheck && pnpm test:all && pnpm build
- wails generate module (bindings match, no drift)

* feat(desktop): complete the theme gallery and editor experience

Problem:
The theme gallery still had fragmented preview actions, placeholder flagship art, generic base-style previews, and a minimal custom-theme dialog that could not tune semantic colors or independent scenes.

Root cause:
Theme Pack V1 modeled one background and the gallery treated selection, preview, and persistence as separate interactions without a shared localized editor state.

Fix:
Upgrade the pack contract compatibly to V2 with independent home and workspace scenes, rebuild the gallery around immediate reversible preview and explicit apply, add localized semantic color and scene controls, replace the eight official artworks, and align base-style previews and actions with the settings UI.

Verification:
- cd desktop && go test ./...
- cd desktop/frontend && pnpm test:all
- cd desktop/frontend && pnpm typecheck
- cd desktop/frontend && pnpm check:css
- cd desktop/frontend && pnpm build
- Browser-verified Chinese and English labels, live preview, scene switching, and cancel restoration

* fix(desktop): satisfy the full-module formatting gate

Problem:
The desktop CI job stopped before tests because its repository-wide gofmt check reported main.go and theme_assets.go.

Root cause:
The earlier theme integration left one struct field with manual alignment and one extra trailing blank line; package tests compile these files but do not enforce gofmt byte formatting.

Fix:
Run gofmt on both reported files without changing runtime behavior.

Verification:
- cd desktop && gofmt -l .
- cd desktop && go test ./...

* fix(desktop): clarify theme scene content placement

Problem:
The scene editor exposed a vague content-safe-area label without explaining that it controls foreground text and card placement. The desktop CI lint gate also found four empty or unused Theme Pack leftovers.

Root cause:
The persisted safeArea field was surfaced directly as user-facing copy, while placeholder branches and helpers remained after the Theme Pack integration.

Fix:
Rename the control to Interface content area, add localized guidance and radio semantics, and remove or complete the four lint findings without changing theme behavior.

Verification:
- cd desktop && go test ./...
- cd desktop && go vet ./...
- cd desktop/frontend && pnpm typecheck
- cd desktop/frontend && pnpm check:css
- cd desktop/frontend && pnpm test:all
- cd desktop/frontend && pnpm build
- Theme Pack contract: 195 passed
- Verified the localized control in the running browser UI

* fix(desktop): unify theme confirmation dialogs

Problem:
Theme deletion, replacement imports, and export-rights checks used window.confirm, which surfaced platform-native dialogs that did not match the Reasonix settings UI.

Root cause:
The Theme Pack flows lacked a reusable in-app confirmation primitive, so both the gallery and legacy library called the browser API directly.

Fix:
Add an accessible portal-based Reasonix confirmation dialog with danger styling, safe focus behavior, Escape and backdrop cancellation, and focus restoration. Migrate all six theme confirmation call sites and localize their titles and actions.

Verification:
- pnpm typecheck
- pnpm check:css
- pnpm test:all
- pnpm build
- Browser: delete confirmation stayed in-app, Cancel received focus, Escape restored focus, and the temporary theme was deleted without a native dialog

* fix(desktop): restore theme preview and custom fonts

Problem:
- Custom UI and monospace fonts could no longer be configured from the redesigned appearance page.
- Selecting a user theme updated details without starting the advertised global preview.

Root cause:
- AppearanceOverview discarded both custom font name callbacks and omitted their editors.
- ThemeGallery limited non-immersive previews to the catalog tab.

Fix:
- Restore localized custom font editors and their persistence callbacks.
- Preview every non-immersive theme selection, including user packs.
- Add focused source-contract coverage for both behaviors.

Verification:
- pnpm test:all
- pnpm build
- pnpm check:css
- Browser verification of custom font variables, user-pack preview, and Back restoration
2026-07-18 01:11:36 +08:00
SivanColaandClaude Fable 5 7e83df9641 chore(gitignore): ignore local temp/ scratch directories
Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-08 14:42:06 +00:00
SivanCola 11963ffa3b chore(desktop): refresh app icon assets (#4321)
Replace the desktop app icon with a blue-background single-mark design (R + whale), add the SVG source under the build-asset ignore exceptions, and regenerate the Windows ICO so macOS, Linux, and Windows share consistent artwork.

Closes #4321
2026-06-14 20:39:00 +08:00
SivanCola ea0c3a9b69 Improve crash reporting diagnostics / 增强崩溃上报诊断 (#4285)
* feat: enrich crash reporting pipeline

* fix: address crash report worker review
2026-06-13 23:25:40 +08:00
YHHandreasonix 3025f5af2a feat(agent): deterministic tool-result pruning + cache-TTL-aware cold-resume maintenance (#3968)
* feat(agent): prune stale tool results before folding and on cold resume

Stale tool results are re-derivable (files re-read, commands re-run), so
eliding them is a free, lossless alternative to the paid summarize fold.
Prune runs only where a cache reset is already being paid: at the compact
trigger, where it skips the fold entirely when eliding alone clears the
threshold, and on resume after the provider prefix cache has expired
(cacheColdAfter), where rewriting history costs no extra misses and
directly shrinks the full-price first request. No message is ever removed,
so tool_call/result pairing and signed reasoning stay intact by
construction; originals are archived like fold drops.

* bench(e2e): context-maintenance driver — cold-resume A/B + placeholder comprehension

Three real-API scenarios for the prune work: seed two identical-shape fat
sessions, A/B the cold-restart miss tokens with and without pruning after
the provider cache has expired, and verify the model re-reads a file behind
a prune placeholder instead of answering from nothing.

* fix(control): widen cold-resume prune threshold to a risk-asymmetric default

Pruning a still-cached session costs ~4x the miss tokens of leaving it
alone (measured warm-cache A/B), while a threshold that is too large only
forgoes a free prune. Default to 24h until the cache-ttl probe pins the
real retention; never set below it.

* bench(e2e): check write/unmarshal errors in the maintenance driver (errcheck)

* fix(control): derive cold-resume idle from branch meta only (CodeQL go/path-injection)

The os.Stat mtime fallback fed a user-influenced path straight into a
filesystem call. Branch meta is guaranteed for every session the controller
has snapshotted, so the fallback only ever covered never-saved imports —
those now skip one prune until their first snapshot creates the meta.

---------

Co-authored-by: reasonix <[email protected]>
2026-06-11 14:38:27 +08:00
YHHandreasonix 842aff852a site: serve downloads from dl.reasonix.io, bake contributor avatars into the build (#3923)
Mainland-China visitors hit two walls on the new site: the contributor
wall loads 40+ images from avatars.githubusercontent.com (blocked
there), and downloads went through the rate-limited pub-*.r2.dev dev
subdomain.

- a prebuild script now fetches community stats and downloads every
  contributor avatar into public/av/, so the wall is served same-origin
  with zero third-party requests; any avatar that fails to fetch falls
  back to its remote URL, and the whole script degrades to the
  committed snapshot rather than failing the build
- download links and the runtime version check now use the R2 bucket's
  custom domain dl.reasonix.io (Cloudflare-cached, range requests
  verified) instead of pub-*.r2.dev

Co-authored-by: reasonix <[email protected]>
2026-06-11 11:30:32 +08:00
SivanColaandreasonix b87a878cad feat(desktop): project topic tabs and context inspector (#2925)
* feat(desktop): add project tabs and context inspector

* feat(desktop): refine project workspace UI

* fix 打开一致加载

* 顶部统一logo+左侧边栏+统一菜单

* 底部最低宽度

* update much

* update code

* update code

* feat(desktop): refine workspace layout and message actions

* fix(desktop): scope file refs to active tab root

* test(desktop): stabilize capabilities config dirs

* fix(desktop): protect sessions open in other tabs

* fix(desktop): resume scoped history in matching tab

* chore(desktop): drop committed scratch artifacts; ignore tmp/ and .codex/

The project-tabs branch carried 30 throwaway screenshots, a mock HTML,
and a codex env file into the tree because tmp/ and .codex/ were never
ignored. Remove them and add the ignore rules so scratch output stays
out of future commits.

* test(desktop): isolate AppData per test so the suite passes on Windows

The new project/topic/workspace tests only overrode HOME and
XDG_CONFIG_HOME, but desktopConfigDir() and config.MemoryUserDir()
resolve through os.UserConfigDir(), which reads %AppData% on Windows and
ignores those vars. With AppData pointed at one shared TestMain dir, the
project/topic/workspace registries leaked across tests: full-package
runs failed (project tree len 2 want 1, trash mixing other tests'
sessions, Plugins[0] reading a neighbor's MCP entry) while each test
passed alone.

Route every test through isolateDesktopUserDirs, which already sets
HOME, USERPROFILE, XDG_CONFIG_HOME, and AppData to a per-test temp dir.
go test ./... (and -race) now pass on Windows.

---------

Co-authored-by: reasonix <[email protected]>
2026-06-04 19:41:16 -07:00
YHHandreasonix 324c3bb6fb feat(site): rebuild the landing page in Astro, deploy via Actions (#2818)
Replace the hand-written single-file page with a real Astro project (site/) and
a GitHub Actions Pages deploy. Minimal, editorial design aligned with Codex /
Claude Code: warm paper bg, near-black type with one restrained accent, Inter
Variable, generous whitespace, a typing terminal demo, and an elevated install
widget in the hero (npm / Homebrew / Download) so the very first thing a visitor
sees is how to install or download — downloads go through the R2 CDN.

Pages source is switched from the legacy branch build to the Actions workflow.

Co-authored-by: reasonix <[email protected]>
2026-06-02 21:12:25 -07:00
YHHandreasonix 45927ce7e6 feat(release): npm + Homebrew distribution for the CLI (#2775)
* feat(release): npm + homebrew distribution for the CLI

The CLI only shipped as raw GitHub-release archives; the README promised
`npm i -g reasonix` and brew, neither of which existed.

- goreleaser (.goreleaser.yaml) now owns archives, SHA256SUMS, the GitHub
  release, and an auto-maintained esengine/homebrew-reasonix formula,
  replacing the hand-rolled scripts/release.sh.
- npm/ ships the binary the modern way: a `reasonix` wrapper whose bin shim
  exec's a per-platform `@reasonix/cli-<os>-<arch>` optionalDependency, so
  npm installs only the matching native binary — no postinstall, offline-OK.
  npm/build.mjs cross-compiles all six targets and publishes the set.
- release.yml fans out to goreleaser + npm jobs on a v* tag.

Publishing needs three one-time setup items: the @reasonix npm org, the
esengine/homebrew-reasonix tap repo, and NPM_TOKEN + HOMEBREW_TAP_TOKEN
secrets. Desktop (desktop-v* / Wails + R2 updater) is unchanged.

* fix(release): homebrew_casks + tag npm prereleases

Two issues caught running the release locally (goreleaser snapshot + npm
publish --dry-run):

- goreleaser removed `brews` in v2.16; the `~> v2` action would have failed
  the release. Migrate to `homebrew_casks` (binary cask + a postflight that
  strips the macOS quarantine xattr, since the archives aren't notarized).
  Cask install is macOS-only, so the README brew line drops the Linux claim —
  Linux installs via npm or the release tarball.
- npm refuses to publish a SemVer prerelease without an explicit dist-tag, so
  a `v1.0.0-rc1` tag would have errored. build.mjs now publishes prereleases
  under `next` and stable versions under `latest`.

---------

Co-authored-by: reasonix <[email protected]>
2026-06-02 09:01:02 -07:00
Porun ca5c6a204b fix(gitignore): update ignores for the Go v2 layout (#2415)
Refresh root .gitignore for v2: ignore Go test/profiling scratch, Wails desktop build outputs, frontend deps/caches, Windows artifacts, and local .reasonix state, while keeping tracked placeholders (desktop/build/appicon.png, .reasonix/commands/review.md) via negation rules. Verified no tracked file is newly ignored.
2026-05-31 03:34:46 -07:00
esengine fd0dfff0fd ci: add CI and release workflows
CI (ci.yml): on push/PR to main-v2, run gofmt check (root module),
go vet, build, and test on ubuntu + macOS, with the Go version pinned to
go.mod.

Release (release.yml + scripts/release.sh): on a v* tag, cross-compile all
six targets, bundle the matching CodeGraph runtime beside the binary on
darwin/linux (verified against CodeGraph's published SHA256SUMS), package
per-platform archives, and publish a GitHub release with a SHA256SUMS
manifest. Windows ships a bare binary for now — its bundle layout is not yet
verified, so it falls back to a PATH-installed codegraph.

Verified release.sh end-to-end locally: all six archives build, the four
darwin/linux ones carry codegraph/bin/codegraph beside reasonix (so the
bundled-path resolver finds it), checksums match.
2026-05-31 01:10:01 -07:00
esengine 1e68e2e018 feat(codegraph): integrate CodeGraph as a built-in MCP server
Wire CodeGraph (tree-sitter + SQLite symbol/call graph) in as a built-in
stdio MCP server so the agent gains codegraph_* search/context/explore/
trace/node tools — replacing the old embedding semantic search with a
zero-API-cost, cross-language code-intelligence layer.

- internal/codegraph: Resolve (config path > bundle beside the executable >
  PATH) and EnsureInit. Init is a bare `codegraph init` (~100ms, size-
  independent); serve's daemon indexes in the background, so startup never
  blocks even on a large repo.
- plugin.Spec gains Dir; the stdio transport honors it so the cwd-aware
  server is pinned to the project root (also fixes a latent cwd issue).
- boot injects the server when [codegraph].enabled (default true) and the
  bundle resolves; a missing bundle is a silent no-op (falls back to grep).
- mcp list reports the built-in server's resolved status, doubling as a
  headless preflight.
- .gitignore the per-project .codegraph/ index.

Tested: unit tests for Resolve/EnsureInit, plus an env-gated end-to-end
(make e2e-codegraph) that handshakes a real CodeGraph bundle over MCP and
calls codegraph_search. Distribution (bundling the binary in the release
archive) is intentionally left as a follow-up.
2026-05-31 00:48:01 -07:00
yhhandClaude Opus 4.8 7de6a2474f feat: import Go implementation as v2 kernel (renamed duo → reasonix)
Bring in the finished Go rewrite (developed under the placeholder name
"duo") as the v2 codebase. Code-only import — no upstream git history.

Config- and plugin-driven coding agent: registry core, OpenAI-compatible
providers, builtin tools, MCP (stdio + HTTP) plugins, permission + sandbox,
custom slash commands, @-references, two-model collaboration.

Renamed throughout: duo→reasonix, Duo→Reasonix, DUO→REASONIX — module path,
imports, CLI name, cmd/ dirs, reasonix.toml, .reasonix/commands, and
$REASONIX_* env prefixes. Kept the v2 MIT LICENSE (the Go project had none).

Verified: go build ./... · go vet ./... · go test ./... all green; gofmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-05-29 18:13:20 -07:00
yhhandClaude Opus 4.8 32a4c02e54 chore: initialize v2 — ground-up rewrite
Clean-slate rebuild on an orphan branch:
- Kernel rewritten in Go (was TypeScript)
- Web client removed
- Desktop client redone from scratch

Legacy TS implementation remains on `main` for reference.
Seeded with LICENSE (carried over), a Go-oriented .gitignore, and a minimal README.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-05-29 17:53:10 -07:00