Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
da419cc
Use local SDK and preserve fork promotion across publication
Var1377 Sep 18, 2026
86be1dc
Bound Windows daemon calls with overlapped pipe I/O
Var1377 Sep 18, 2026
07ff70e
Use IOCP pipe client and invalidate failed calls
Var1377 Sep 18, 2026
e7e661a
Fold daemon protocol into the binary crate
Var1377 Sep 18, 2026
44c1cc9
Consolidate plugin engine and wire domain types
Var1377 Sep 18, 2026
7927d91
Make daemon readiness wait for pipeline baseline
Var1377 Sep 18, 2026
8e4ee8c
Expose baseline phase timing in latency probe
Var1377 Sep 18, 2026
b86d728
Return permanent daemon baseline errors immediately
Var1377 Sep 18, 2026
2ba1c94
Collapse plugin qualification into shared SDK gate
Var1377 Sep 18, 2026
47f442c
Fix shared gate wrapper commands
Var1377 Sep 18, 2026
a4a5702
Reject plugin stores inside repository trees
Var1377 Sep 18, 2026
4619f1b
Refresh local SDK dependency lock for Windows publication
Var1377 Sep 18, 2026
b2e1e01
Stop old watcher before intentional root swaps
Var1377 Sep 18, 2026
3a8f9f2
Assert Windows baseline readiness without polling sleep
Var1377 Sep 18, 2026
dcf3924
Verify Windows daemon restart after promotion
Var1377 Sep 18, 2026
7460a26
Lock SDK listing dependencies for plugin lab
Var1377 Sep 18, 2026
f7f1cff
Centralize plugin replies and harden rewind recovery
Var1377 Sep 18, 2026
fc7f5e2
Use SDK materialization for restore and recover hard-link rescans
Var1377 Sep 18, 2026
3d48d19
Keep checkpoint roles in plugin and serialize timeline writes
Var1377 Sep 18, 2026
82a4d92
Bound prompt excerpt work to stored length
Var1377 Sep 18, 2026
4724012
Cut plugin hot paths and harden Windows workflows
Var1377 Sep 18, 2026
049cbf6
Guard recursive native imports
Var1377 Sep 19, 2026
75d8bf4
Use verified ProjFS forks when available
Var1377 Sep 19, 2026
1b18559
Fix Windows smoke metric tool resolution
Var1377 Sep 19, 2026
6422638
Track folded SDK dependencies
Var1377 Sep 19, 2026
a7d1ed2
Remove legacy Safe Mode surface
Var1377 Sep 19, 2026
79ec582
Avoid duplicate daemon repository recovery
Var1377 Sep 19, 2026
ac48a3f
Require native mounts for plugin forks
Var1377 Sep 19, 2026
6a5827a
Remove obsolete plugin compatibility paths
Var1377 Sep 19, 2026
d66a827
Remove legacy Codex hook migration
Var1377 Sep 19, 2026
8a48708
Remove plugin recovery fallback paths
Var1377 Sep 19, 2026
17e8b45
Make fork teardown transactional
Var1377 Sep 19, 2026
b804692
Fail closed on stale fork cleanup
Var1377 Sep 19, 2026
b953b7b
Defer plugin baseline until repository use
Var1377 Sep 19, 2026
4889301
Defer plugin cleanup until first use
Var1377 Sep 19, 2026
5831cae
Use SDK text merge semantics
Var1377 Sep 19, 2026
de7032a
Use SDK bounded byte merge
Var1377 Sep 19, 2026
9f53367
Use SDK whole-tree exchange for rewind
Var1377 Sep 19, 2026
5b8f0a1
Use SDK native entry exchange
Var1377 Sep 19, 2026
60d0501
Avoid full rescan after known tree publication
Var1377 Sep 19, 2026
0384799
Preserve exact rewind watcher admission
Var1377 Sep 19, 2026
ca0840f
Skip unchanged Windows startup scans
Var1377 Sep 19, 2026
a9ab212
Centralize rewind head restoration in SDK
Var1377 Sep 19, 2026
323bbc6
Use workspace facade for plugin checkouts
Var1377 Sep 19, 2026
5340d89
Avoid reopening adopted plugin volumes
Var1377 Sep 19, 2026
3a5a4bf
Observe daemon readiness without fixed delay
Var1377 Sep 19, 2026
d971957
Document the single plugin crate
Var1377 Sep 19, 2026
25d27fd
Keep restart rewind fixture protocol-valid
Var1377 Sep 19, 2026
11951a3
Remove deferred startup sweep from production
Var1377 Sep 19, 2026
ee65506
Keep idle startup constant in repository size
Var1377 Sep 19, 2026
f5278c6
Keep Unix sweep dependencies with test coverage
Var1377 Sep 19, 2026
d76d251
Activate auto checkpointing after first real use
Var1377 Sep 19, 2026
a4223b3
Compile deferred Unix cleanup only in its test
Var1377 Sep 19, 2026
8ee12b5
Describe lazy daemon readiness accurately
Var1377 Sep 19, 2026
73c305b
Prove idle startup remains scan free
Var1377 Sep 19, 2026
72f1978
Open native watcher on first filesystem demand
Var1377 Sep 19, 2026
e33869e
Use SDK change sets for plugin diffs
Var1377 Sep 19, 2026
c036931
Make merge planning proportional to changed paths
Var1377 Sep 19, 2026
c2a2cc5
Use SDK exact host path conversion
Var1377 Sep 19, 2026
0f4b0c6
Centralize exact native paths in SDK
Var1377 Sep 19, 2026
1f69478
Capture restored subtrees without watcher delay
Var1377 Sep 19, 2026
de04c94
Deduplicate overlapping restore roots
Var1377 Sep 19, 2026
98b06bc
Linearize restore root reduction
Var1377 Sep 19, 2026
f56d707
Centralize capture exclusions in SDK
Var1377 Sep 19, 2026
786fd6b
Batch restored subtree capture
Var1377 Sep 19, 2026
46d7a43
Batch generation file lookups
Var1377 Sep 19, 2026
81f18e7
Overlap generation file reads
Var1377 Sep 19, 2026
ca03a98
Batch merge planning content reads
Var1377 Sep 19, 2026
53094d7
Batch merge metadata reads
Var1377 Sep 19, 2026
20f11ba
Batch merge parent lookups
Var1377 Sep 19, 2026
bf26334
Batch subtree copy frontiers
Var1377 Sep 19, 2026
dd577ba
Reuse resolved symlink records
Var1377 Sep 19, 2026
6b761fd
Batch subtree reads and writes
Var1377 Sep 19, 2026
f579fbe
Batch subtree removals
Var1377 Sep 19, 2026
a28ead9
Batch sibling directory scans
Var1377 Sep 19, 2026
209e99a
Reuse merge source checkouts
Var1377 Sep 19, 2026
ab12c9d
Avoid metadata reads while walking subtrees
Var1377 Sep 19, 2026
e084115
Keep session hooks scan free
Var1377 Sep 19, 2026
45b8ce3
Use SDK durable rename
Var1377 Sep 19, 2026
2c9cf53
Reuse SDK durable rename for continuity
Var1377 Sep 19, 2026
8a33b06
Keep status constant time
Var1377 Sep 19, 2026
f783745
Use exact SDK rename semantics
Var1377 Sep 19, 2026
8144648
Use generation safe pinned reads
Var1377 Sep 19, 2026
71328e2
Share rewind operation identity
Var1377 Sep 19, 2026
64e9f15
Reuse pinned file resolution
Var1377 Sep 19, 2026
280c2aa
Overlap resolved subtree reads
Var1377 Sep 19, 2026
4f29567
Reuse resolved subtree traversal
Var1377 Sep 19, 2026
6218a1c
Track owned resolved file handles
Var1377 Sep 19, 2026
8e37518
Consume lazy generation readers
Var1377 Sep 19, 2026
a9808cc
Traverse subtree records once
Var1377 Sep 19, 2026
d0ad607
Qualify against the active SDK branch
Var1377 Sep 19, 2026
1ef0f58
Make qualification prerequisites exact
Var1377 Sep 19, 2026
537fdc3
Use platform thread stacks
Var1377 Sep 19, 2026
ca1746f
Use generation materialization facade
Var1377 Sep 19, 2026
e49ffef
Use SDK native exchange journal
Var1377 Sep 19, 2026
5858b90
Recover partial rewind staging directly
Var1377 Sep 19, 2026
ff846b4
Isolate legacy rewind recovery
Var1377 Sep 19, 2026
7a8f940
Minimize rewind staging state
Var1377 Sep 19, 2026
e8d138c
Cache TypeScript qualification dependencies
Var1377 Sep 19, 2026
56cec8a
Use SDK host path restore
Var1377 Sep 19, 2026
0daffc8
Use canonical SDK materialization bounds
Var1377 Sep 19, 2026
035ac0e
Batch SDK host path restores
Var1377 Sep 19, 2026
e82de7f
Reconcile crash recovery before serving
Var1377 Sep 19, 2026
3100e06
Guard SDK head recovery by journal kind
Var1377 Sep 19, 2026
b3d6d5f
Remove fork copy compatibility surface
Var1377 Sep 19, 2026
dd0d034
Trigger cross-platform qualification
Var1377 Sep 19, 2026
b6d25e9
Merge current plugin main
Var1377 Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
273 changes: 32 additions & 241 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,270 +1,61 @@
name: ci
name: qualification

on:
push:
branches: [main]
pull_request:

# A new push to the same PR (or to main) supersedes the run in flight;
# stop paying for the old one. release.yml deliberately does not cancel.
concurrency:
group: ci-${{ github.event.pull_request.number || github.ref }}
group: qualify-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

env:
FUSE_T_VERSION: 1.2.7
CARGO_TERM_COLOR: always
# acyclic-fs is fetched from its own git repo (see Cargo.toml); cargo's
# built-in libgit2 fetcher can't resolve a pinned commit SHA that isn't a
# branch tip on that host, so delegate to the system git CLI instead.
CARGO_NET_GIT_FETCH_WITH_CLI: true
permissions:
contents: read

jobs:
# Three cheap jobs (changes, deny, lint) run in parallel; the expensive
# test matrix (FUSE-T install, release build, acceptance suite on two
# OSes) only starts once deny and lint pass, so a formatting slip or a
# banned crate is reported in about a minute instead of after a full
# matrix run — and it is skipped outright when nothing that could change
# a test result was touched.

# Which parts of the tree a change touches. A job-level filter rather
# than a workflow-level `paths-ignore` because branch protection on main
# requires the `test (...)` checks: a workflow that never runs leaves
# them "expected" forever and blocks the merge, whereas a job skipped by
# `if:` reports as skipped, which counts as passing.
changes:
runs-on: ubuntu-24.04
# paths-filter lists a PR's files through the API; the default token
# here is read-only on contents and nothing else.
permissions:
contents: read
pull-requests: read
outputs:
code: ${{ steps.filter.outputs.code }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
# On a push to main the filter diffs against the pre-push commit,
# which a depth-1 checkout does not have.
fetch-depth: 0
- id: filter
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
with:
# "some file changed that is not docs/prose": a negation-only list
# matches nothing, so the positive `**` is required, and
# some-with-excludes makes the negations apply to it.
predicate-quantifier: some-with-excludes
filters: |
code:
- '**'
- '!**/*.md'
- '!docs/**'
- '!LICENSE'
- '!.github/CODEOWNERS'

# Licenses, advisories, and sources per deny.toml. Keeps the published
# SBOM inside the permissive allowlist and fails on known-vulnerable or
# yanked crates. Always runs: the secrets scan applies to docs too.
deny:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Product name is single-sourced (product.toml)
run: bash scripts/check-product-name.sh
- name: No forbidden files or credential patterns
run: bash scripts/check-no-secrets.sh
- name: Code quality (line width, TODO format, comment blocks, duplication)
run: bash scripts/check-code-quality.sh
- uses: EmbarkStudios/cargo-deny-action@3c6349835b2b7b196a839186cb8b78e02f7b5f25 # v2.1.1
with:
command: check
arguments: --locked
log-level: warn

# Formatting and lint. Fast and toolchain-only (no FUSE-T needed).
lint:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Install toolchain
run: |
rustup toolchain install stable --profile minimal --component rustfmt --component clippy
rustup default stable
- name: Cache cargo
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: lint-cargo-${{ hashFiles('Cargo.lock') }}
- name: cargo fmt --check
run: cargo fmt --all --check
- name: cargo clippy
run: cargo clippy --workspace --all-targets --all-features -- -D warnings

# Line coverage of the unit and integration tests, as a job summary and
# an lcov artifact, with a floor so a change can't quietly delete tests.
# The daemon, client, and MCP server are exercised by the acceptance
# scripts rather than `cargo test`, so they read as 0% here; raise the
# floor as unit coverage of those grows, not by counting the scripts.
coverage:
needs: [changes, deny, lint]
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Install toolchain
run: |
rustup toolchain install stable --profile minimal --component llvm-tools-preview
rustup default stable
- uses: taiki-e/install-action@3f74d7c16a4242f1c95561e98edc25d36adb4375 # v2.87.12
with:
tool: cargo-llvm-cov
- name: Cache cargo
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: coverage-cargo-${{ hashFiles('Cargo.lock') }}
- name: cargo llvm-cov
run: |
cargo llvm-cov --workspace --all-features --lcov --output-path lcov.info --fail-under-lines 48
{
echo '## Test coverage (lines)'
echo '```'
cargo llvm-cov report --summary-only
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: lcov
path: lcov.info

# Windows, which nothing else in this file covers. The lint job runs on
# Linux, so every `#[cfg(windows)]` block in the tree is invisible to it —
# a Windows-only arm can stop compiling and no other check notices. This
# job builds and lints those arms, runs the workspace tests, and drives
# the parts of the product whose behaviour genuinely differs there
# (named-pipe transport, UTF-16LE names, renaming the repo root, copy
# forks). The POSIX acceptance suite is not run: it assumes mount tooling,
# symlinks and modes that Windows does not have.
windows:
needs: [changes, deny, lint]
if: >-
always()
&& needs.deny.result == 'success'
&& needs.lint.result == 'success'
&& (needs.changes.result != 'success' || needs.changes.outputs.code == 'true')
runs-on: windows-2022
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false

- name: Install toolchain
run: |
rustup toolchain install stable --profile minimal --component rustfmt --component clippy
rustup default stable

- name: Cache cargo
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: windows-cargo-${{ hashFiles('Cargo.lock') }}

- name: cargo fmt --check
run: cargo fmt --all --check

# The point of the job: lint the cfg(windows) arms the Linux lint job
# never compiles.
- name: cargo clippy
run: cargo clippy --workspace --all-targets --all-features -- -D warnings

- name: Unit and integration tests
run: cargo test --workspace

- name: Release build
run: cargo build --release --locked -p acyclic

- name: Windows end-to-end smoke
shell: bash
env:
ACYCLIC_BIN: ${{ github.workspace }}/target/release/acyclic.exe
run: bash tests/acceptance/windows-smoke.sh

test:
needs: [changes, deny, lint]
# Fail closed: run when the change filter says code moved, and also
# when the filter job itself failed (an empty output must not read as
# "nothing to test", since a skipped required check counts as passing).
# deny and lint failing still skip this job; those are required checks
# in their own right, so the PR stays red.
if: >-
always()
&& needs.deny.result == 'success'
&& needs.lint.result == 'success'
&& (needs.changes.result != 'success' || needs.changes.outputs.code == 'true')
qualify:
strategy:
fail-fast: false
matrix:
os: [macos-14, ubuntu-24.04]
os: [ubuntu-24.04, windows-2022, macos-14]
runs-on: ${{ matrix.os }}
timeout-minutes: 20
steps:
- name: Checkout plugin
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
path: graphcoder-plugin

- name: Install FUSE-T (macOS)
path: worktrees/graphcoder/plugin-sdk-lab
persist-credentials: false
- name: Checkout SDK
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
repository: acyclic-labs/sdk
ref: codex/generation-reader
path: sdk
Comment on lines +29 to +34

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 CI Uses Mutable SDK

The sole CI gate executes qualification code from the mutable codex/generation-reader branch. Updates to that external branch can change which checks this repository runs without a reviewed change here, making CI results non-reproducible. Pin the SDK checkout to an immutable commit and update it deliberately.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/ci.yml
Line: 29-34

Comment:
**CI Uses Mutable SDK**

The sole CI gate executes qualification code from the mutable `codex/generation-reader` branch. Updates to that external branch can change which checks this repository runs without a reviewed change here, making CI results non-reproducible. Pin the SDK checkout to an immutable commit and update it deliberately.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

persist-credentials: false
- name: Install FUSE-T on macOS
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
curl --fail --location --retry 5 \
"https://github.com/macos-fuse-t/fuse-t/releases/download/${FUSE_T_VERSION}/fuse-t-macos-installer-${FUSE_T_VERSION}.pkg" \
https://github.com/macos-fuse-t/fuse-t/releases/download/1.2.7/fuse-t-macos-installer-1.2.7.pkg \
--output /tmp/fuse-t.pkg
sudo installer -pkg /tmp/fuse-t.pkg -target /
test -d /usr/local/include/fuse3

- name: Install toolchain
run: rustup toolchain install stable --profile minimal && rustup default stable

- name: Cache cargo
- name: Install Bun
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
with:
bun-version: 1.3.14
- name: Cache immutable dependencies and build outputs
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: |
~/.cargo/registry
~/.cargo/git
graphcoder-plugin/target
key: ${{ matrix.os }}-cargo-${{ hashFiles('graphcoder-plugin/Cargo.lock') }}

- name: Unit and integration tests
working-directory: graphcoder-plugin
run: cargo test --workspace

- name: Release build (acceptance + latency gate run against it)
working-directory: graphcoder-plugin
run: cargo build --release

- name: Acceptance suite
working-directory: graphcoder-plugin
env:
ACYCLIC_BIN: ${{ github.workspace }}/graphcoder-plugin/target/release/acyclic
ACYCLIC_QUAL: ${{ github.workspace }}/graphcoder-plugin/target/release/acyclic-qual
# Smaller corpus keeps CI wall-clock sane; the budget is unchanged.
ACYCLIC_LAT_FILES: "5000"
ACYCLIC_LAT_MB: "64"
ACYCLIC_SOAK_ROUNDS: "30"
run: bash tests/acceptance/run-all.sh
~/.bun/install/cache
sdk/target-*
key: qualify-${{ matrix.os }}-${{ hashFiles('sdk/Cargo.lock', 'sdk/bun.lock', 'sdk/rust/**/*.rs', 'sdk/typescript/**/*.ts', 'worktrees/graphcoder/plugin-sdk-lab/Cargo.lock', 'worktrees/graphcoder/plugin-sdk-lab/crates/**/*.rs') }}
restore-keys: |
qualify-${{ matrix.os }}-
- name: Run the shared bounded gate
shell: pwsh
run: python sdk/scripts/qualify-local.py --plugin-root worktrees/graphcoder/plugin-sdk-lab
23 changes: 15 additions & 8 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ Pre-1.0; `main` is the only supported line (see `SECURITY.md`).

### Changed

- **The unfinished session-shadowing feature was removed.** It depended on
replacing the live repository with a native mount, was not wired into host
approval flows, and could not work consistently across platforms. Existing
`dry_run` configuration is now rejected; explicit forks remain available.
Before replacing the old binary, use that binary to stop every daemon with
an active shadow mount. A new protocol-v2 CLI cannot stop a protocol-v1
daemon. If the old daemon already crashed, recover the real tree with
`fusermount3 -u <repo>` (or `fusermount -u <repo>`) on Linux, or
`umount -f <repo>` (then `diskutil unmount force <repo>` if needed) on macOS.
- **A cold daemon no longer delays the agent's first turn.** The session-start
hook waits at most 300ms for the daemon; past that it prints a one-line
notice and returns while the first snapshot builds in the background (251s
Expand All @@ -19,8 +28,8 @@ Pre-1.0; `main` is the only supported line (see `SECURITY.md`).
O(tree) (7s per fork and 6s per promote on 5,000 files); it now runs on the
existing idle and every-N timers, like every other checkpoint. A fork is cut
at its exact base generation, published or not.
- **The daemon exits after an hour idle** with no session, fork, or Safe Mode
session (`daemon_idle_exit_ms`, 0 disables). Twenty daemons were found alive
- **The daemon exits after an hour idle** with no session or fork
(`daemon_idle_exit_ms`, 0 disables). Twenty daemons were found alive
on one machine, eleven for repos that no longer existed.
- `acyclic status` reports the store size from a cache refreshed in the
background instead of walking the object directory on every call.
Expand Down Expand Up @@ -79,8 +88,6 @@ were 0.0.1. Host coverage, Speculation and Windows are what this release adds.
the conversation, not just by commit.
- **Forks** (Launch 3) — copy-on-write materialization for running parallel attempts, with
promotion back via three-way merge.
- **Safe Mode** (Launch 4) — session redirection and interposition so agent mistakes land in a
redirected session rather than the working tree; needs the native mount layer.
- **Speculation** — the daemon computes what the agent is about to ask for while nobody is
waiting: the previous-session brief when a session ends, and (optionally, with a model
command the developer names) a summary of each turn at the turn boundary. Results are keyed
Expand All @@ -91,10 +98,10 @@ were 0.0.1. Host coverage, Speculation and Windows are what this release adds.
- Published to npm as `@acyclic-labs/plugin`.
- **Windows x64 support** — the daemon transport gains a named-pipe implementation alongside the
Unix domain socket, and host names are encoded per platform (UTF-16LE on Windows) so capture,
diff, exclusions and the Safe Mode guard agree with the filesystem. Verified on Windows 11 and
covered by a `windows-2022` CI job. Two caveats: forks are always full copies there (ProjFS
projects a fork but does not carry writes back, so a mounted fork would silently lose work) and
Safe Mode needs a real mount, so it is unavailable. See `docs/windows-verification.md`.
diff, exclusions and guarded fork paths agree with the filesystem. Verified on Windows 11 and
covered by a `windows-2022` CI job. Forks use full copies there because ProjFS projects a fork
but does not carry writes back, so a mounted fork would silently lose work. See
`docs/windows-verification.md`.

### Fixed

Expand Down
8 changes: 4 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Acceptance suites (end-to-end, run against a real repo) live in `tests/acceptanc
tests/acceptance/run-all.sh
```

Individual suites (`journey.sh`, `timeline.sh`, `forks.sh`, `merge.sh`, `safe-mode.sh`, etc.) can
Individual suites (`journey.sh`, `timeline.sh`, `forks.sh`, `merge.sh`, etc.) can
be run with `bash tests/acceptance/<suite>.sh` if you're iterating on one feature.

The `*-e2e.sh` suites drive the real host CLIs (Claude Code, Codex, cursor-agent, OpenCode) and
Expand Down Expand Up @@ -76,7 +76,7 @@ guards what those can't express. The rules, and why each exists:
- **No lossy `as` casts** between integer widths or signs (`cast_possible_truncation`,
`cast_sign_loss`, `cast_possible_wrap`, `cast_precision_loss`, `cast_lossless`). Use
`u64::from`, `i64::try_from(x).unwrap_or(i64::MAX)`, or an `allow` that says why the value
is in range. `acyclic_engine::unix_now()` and `short_hex()` exist so the two most common
is in range. `acyclic::unix_now()` and `short_hex()` exist so the two most common
cases are written once.
- **Closed sets are enums, not strings.** Anything the CLI parses or the wire carries with a
fixed vocabulary — checkpoint kinds, hook events, host names, restore actions, diff change
Expand All @@ -100,13 +100,13 @@ guards what those can't express. The rules, and why each exists:
The public product name is defined once, in `product.toml`, and threaded through everywhere else:
`product::NAME` in Rust, `scripts/product.sh` in shell. Never hardcode the name as a literal
string or path in source — `scripts/check-product-name.sh` fails CI if it drifts. Crate names
(`acyclic`, `acyclic-engine`, `acyclic-proto`, `acyclic-qual`) are internal identifiers and are
(`acyclic`, `acyclic-qual`) are internal identifiers and are
exempt from this check.

## Design context

`docs/design/` has the design docs and implementation notes behind the bigger features (Rewind,
Timeline, Forks, Safe Mode). Worth a skim before working on any of them — they capture the
Timeline, Forks). Worth a skim before working on any of them — they capture the
tradeoffs and constraints that shaped the current architecture, including a few (like snapshot
GC) that are intentionally deferred.

Expand Down
Loading
Loading