diff --git a/.githooks/pre-push b/.githooks/pre-push
deleted file mode 100755
index 47764b3c606..00000000000
--- a/.githooks/pre-push
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/usr/bin/env bash
-# Exact-head PR contract preflight (issue: red "PR contract bootstrap" runs).
-#
-# git feeds "" on stdin for every ref
-# being pushed. The PR identity is the REMOTE destination, not the local source: for
-# `git push origin HEAD:refs/heads/feature` the local ref is bare `HEAD`, and for a
-# renamed refspec the two names differ. So branch is derived from while
-# stays the commit under validation.
-#
-# Install: git config core.hooksPath .githooks (or: bun run install:dev)
-# Bypass: GJC_SKIP_PR_PREFLIGHT=1 git push (or: git push --no-verify)
-set -euo pipefail
-
-if [[ -n "${GJC_SKIP_PR_PREFLIGHT:-}" ]]; then
- exit 0
-fi
-
-repo_root="$(git rev-parse --show-toplevel)"
-remote="${1:-origin}"
-push_url="${2:-}"
-zero="0000000000000000000000000000000000000000"
-status=0
-
-while read -r _local_ref local_sha remote_ref _remote_sha; do
- # A zero local sha is a branch deletion: no head to validate.
- [[ "$local_sha" == "$zero" ]] && continue
- # Only branch destinations carry a PR; tags and notes never do.
- [[ "$remote_ref" == refs/heads/* ]] || continue
- branch="${remote_ref#refs/heads/}"
- if ! bun "$repo_root/scripts/verify-pr-verdict.ts" --push-preflight "$branch" "$local_sha" --push-remote "$remote" --push-url "$push_url" --repo "$repo_root" --trusted-root "$repo_root"; then
- status=1
- fi
-done
-
-if [[ "$status" -ne 0 ]]; then
- echo "pre-push: PR contract preflight failed. Fix the PR body/rebase, or push with GJC_SKIP_PR_PREFLIGHT=1." >&2
-fi
-exit "$status"
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 138139b9bb1..c12cbb38918 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -10,21 +10,9 @@
-## Risk classification
+## Approval
-
-
-- [ ] `low-risk` — ordinary fix/maintenance; the repository owner may use the explicit `merge-self-approved` solo verdict (no independent human review; the verdict name itself records this) with a risk-record comment bound to the exact head.
-- [ ] `regression-risk` — fix with material regression risk; requires one assigned independent domain reviewer whose authenticated exact-head `APPROVED` review the gate verifies (`extra:independent:`; the token alone never suffices).
-- [ ] `high-risk` — large refactor, feature, or materially high-risk change (security/auth/install/remove/public API/destructive lifecycle/architecture); requires one assigned independent domain reviewer with an authenticated exact-head `APPROVED` review (`extra:independent:`).
-
-## GJC verdict
-
-
-
-```text
-gajae.pr-review-verdict.v1 sha256: reviewer: reviewer-id: evidence:
-```
+Merges to `dev` require one approving GitHub review from a write-access maintainer on the current head. Agent reviews (`architect`/`critic`) are advisory comments.
---
@@ -32,5 +20,5 @@ gajae.pr-review-verdict.v1 /changelog.d/` (if user-facing)
-- [ ] Verdict above matches the exact PR head, not an earlier commit
+- [ ] Human approval or the required agent/owner verdict matches the exact PR head, not an earlier commit
- [ ] Risk classification above matches the actual review path taken
diff --git a/.github/actions/build-native/action.yml b/.github/actions/build-native/action.yml
index f90a9cdcdc1..e141f404a15 100644
--- a/.github/actions/build-native/action.yml
+++ b/.github/actions/build-native/action.yml
@@ -37,7 +37,7 @@ runs:
steps:
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
- toolchain: nightly-2026-04-29
+ toolchain: nightly-2026-08-12
components: ${{ inputs.rust_checks == 'true' && 'clippy, rustfmt' || '' }}
targets: ${{ inputs.target }}
- name: Prepend rustup toolchain bin to PATH
@@ -93,13 +93,16 @@ runs:
# Tag builds ship the size-tuned dist profile (strip=debuginfo,
# fat LTO, panic=unwind preserved); PR/branch builds keep ci.
PI_NATIVE_PROFILE: ${{ (startsWith(github.ref, 'refs/tags/v') || inputs.nightly_version != '') && 'dist' || '' }}
+ PCRE2_SYS_STATIC: "1"
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
run: bun run ci:build:native
- name: Upload native addon(s)
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: pi-natives-${{ inputs.platform }}-${{ inputs.arch }}${{ inputs.variant && format('-{0}', inputs.variant) || '' }}-h${{ inputs.hash }}
- path: packages/natives/native/pi_natives.${{ inputs.platform }}-${{ inputs.arch }}*.node
+ path: |
+ packages/natives/native/pi_natives.${{ inputs.platform }}-${{ inputs.arch }}*.node
+ packages/natives/native/pi_natives.${{ inputs.platform }}-${{ inputs.arch }}*.node.provenance.json
if-no-files-found: error
# Explicit so the rust-hash canary lookup keeps working even if org
# defaults shift; bump if Rust source ever stays stable for >90 days
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9b81b483637..10a843433ef 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -200,6 +200,12 @@ jobs:
run: |
test -f packages/natives/native/pi_natives.linux-x64-baseline.node
test -f packages/natives/native/pi_natives.linux-x64-modern.node
+ - name: Verify producer provenance sidecars
+ shell: bash
+ run: |
+ set -euo pipefail
+ test -f packages/natives/native/pi_natives.linux-x64-baseline.node.provenance.json
+ test -f packages/natives/native/pi_natives.linux-x64-modern.node.provenance.json
- name: Upload native addon(s)
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
@@ -207,6 +213,8 @@ jobs:
path: |
packages/natives/native/pi_natives.linux-x64-baseline.node
packages/natives/native/pi_natives.linux-x64-modern.node
+ packages/natives/native/pi_natives.linux-x64-baseline.node.provenance.json
+ packages/natives/native/pi_natives.linux-x64-modern.node.provenance.json
if-no-files-found: error
retention-days: 1
overwrite: true
@@ -238,7 +246,7 @@ jobs:
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
if: ${{ matrix.rust }}
with:
- toolchain: nightly-2026-04-29
+ toolchain: nightly-2026-08-12
- uses: taiki-e/install-action@56545b37b57562edd73171cb6c62cc509db4c34e # v2
if: ${{ matrix.nextest }}
with:
@@ -277,6 +285,13 @@ jobs:
# native shard on an expired artifact (#5715).
echo "::notice::main-native-${{ github.run_id }} is no longer retained; rebuilding the native addon from source for this re-run."
bun run ci:build:native
+ - name: Rebuild and verify transferred native provenance
+ if: ${{ matrix.native }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ bun scripts/verify-diagnostic-artifact-provenance.ts --rebuild-from-sidecars packages/natives/native
+ bun scripts/verify-diagnostic-artifact-provenance.ts --verify packages/natives/native
- name: Run task shard
env:
GITHUB_ACTIONS: ""
@@ -319,6 +334,12 @@ jobs:
# re-run is not a code failure (#5715).
echo "::notice::main-native-${{ github.run_id }} is no longer retained; rebuilding the native addon from source for this re-run."
bun run ci:build:native
+ - name: Rebuild and verify transferred native provenance
+ shell: bash
+ run: |
+ set -euo pipefail
+ bun scripts/verify-diagnostic-artifact-provenance.ts --rebuild-from-sidecars packages/natives/native
+ bun scripts/verify-diagnostic-artifact-provenance.ts --verify packages/natives/native
- name: Run ACP conformance
run: |
# Real (non-symlinked) seeded scratch workspace, matching the promoted baseline:
@@ -376,6 +397,7 @@ jobs:
# platform, then standalone binaries. Rehearsals stop after binary verification;
# nightly publication waits for `nightly_gate` before touching npm or GitHub.
# ---------------------------------------------------------------------------
+
native:
if: ${{ always() && needs.release_metadata.result == 'success' && (startsWith(github.ref, 'refs/tags/v') || (github.event_name == 'workflow_dispatch' && inputs.rehearsal == 'tag-build-verify') || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.rehearsal == 'nightly-release')) }}
needs: [release_metadata]
@@ -441,6 +463,14 @@ jobs:
pattern: pi-natives-${{ matrix.platform }}-${{ matrix.arch }}*
path: packages/natives/native
merge-multiple: true
+ - name: Rebuild the diagnostic artifact provenance record
+ # The binaries matrix includes a Windows runner, whose default shell is
+ # PowerShell; this body is Bash.
+ shell: bash
+ run: |
+ set -euo pipefail
+ bun scripts/verify-diagnostic-artifact-provenance.ts --rebuild-from-sidecars packages/natives/native
+ bun scripts/verify-diagnostic-artifact-provenance.ts --verify packages/natives/native
- name: Verify memory-guard native loader export
run: bun test packages/natives/test/memory-guard-native.test.ts
@@ -448,6 +478,23 @@ jobs:
env:
RELEASE_TARGETS: ${{ matrix.target_id }}
run: bun run ci:release:build-binaries
+ - name: Verify removed PDF references
+ run: bun scripts/verify-agpl-removal.ts --active-refs
+ - name: Verify packaged PDF payloads
+ shell: bash
+ run: |
+ set -euo pipefail
+ package_dir="$RUNNER_TEMP/coding-agent-package"
+ mkdir -p "$package_dir"
+ (cd packages/coding-agent && bun pm pack --destination "$package_dir")
+ # `bun pm pack` on Windows records non-POSIX member modes, which the
+ # strict release tar reader rejects. The published tarball is packed on
+ # Linux; for this scan-only copy, repack the identical member bytes with
+ # uniform POSIX modes via Bun.Archive (no shell tar, no drive-letter paths).
+ if [ "$RUNNER_OS" = "Windows" ]; then
+ bun -e 'const [p] = await Array.fromAsync(new Bun.Glob("*.tgz").scan({ cwd: process.argv[1], absolute: true })); const files = await new Bun.Archive(await Bun.file(p).bytes()).files(); await Bun.write(p, await new Bun.Archive(Object.fromEntries(files), { compress: "gzip" }).bytes());' "$package_dir"
+ fi
+ bun scripts/verify-agpl-removal.ts --payload --tarball "$package_dir"/*.tgz --binary "${{ matrix.binary_path }}" --addon packages/natives/native/*.node --prose-root .
- name: Smoke memory-guard native route (Windows)
if: runner.os == 'Windows'
shell: pwsh
@@ -488,6 +535,95 @@ jobs:
name: gjc-binary-${{ matrix.target_id }}
path: ${{ matrix.binary_path }}
+ # ---------------------------------------------------------------------------
+ # Per-release RSS checkpoint (#5941). Every stable tag and nightly records a
+ # whole-process-tree RSS checkpoint for the exact linux-x64 release binary, so
+ # regressions stay bisectable release-to-release. The compare against the
+ # previous published stable release is ADVISORY (docs/perf-profiling-corpus.md
+ # threshold promotion stage 1): both binaries are measured in this job on the
+ # same runner so host noise cancels, and regressions are reported, never
+ # gating. The job has no write scope; checkpoints ship as a run artifact.
+ # ---------------------------------------------------------------------------
+ rss_checkpoint:
+ if: ${{ always() && needs.release_metadata.result == 'success' && needs.binaries.result == 'success' }}
+ needs: [release_metadata, binaries]
+ runs-on: ubuntu-22.04
+ timeout-minutes: 60
+ permissions:
+ contents: read
+ steps:
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+ with:
+ fetch-depth: 0
+ - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
+ with:
+ bun-version: "1.4.0"
+ - name: Cache bun dependencies
+ uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
+ with:
+ path: ~/.bun/install/cache
+ key: bun-1.4.0-${{ runner.os }}-${{ hashFiles('**/bun.lock') }}
+ - run: bun install --frozen-lockfile
+ - name: Download release binary
+ uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
+ with:
+ name: gjc-binary-linux-x64
+ path: ${{ runner.temp }}/rss/current
+ - name: Resolve previous published stable release
+ id: previous
+ env:
+ GH_TOKEN: ${{ github.token }}
+ CURRENT_TAG: ${{ needs.release_metadata.outputs.tag_name }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ # Newest published, non-draft, non-prerelease release other than this one.
+ # Paginated: daily nightly prereleases can push every stable release off
+ # the first page. The API lists newest first, so the first match wins.
+ # A missing predecessor is not an error: the checkpoint is still written.
+ previous="$(gh api --paginate "repos/$GITHUB_REPOSITORY/releases?per_page=100" \
+ --jq ".[] | select(.draft == false and .prerelease == false and .tag_name != \"$CURRENT_TAG\") | .tag_name" \
+ | sed -n 1p)"
+ echo "tag=$previous" >> "$GITHUB_OUTPUT"
+ if [ -n "$previous" ]; then
+ git fetch --quiet --force origin "refs/tags/$previous:refs/tags/$previous"
+ echo "commit=$(git rev-parse "$previous^{commit}")" >> "$GITHUB_OUTPUT"
+ gh release download "$previous" --pattern gjc-linux-x64 --dir "$RUNNER_TEMP/rss/previous"
+ chmod +x "$RUNNER_TEMP/rss/previous/gjc-linux-x64"
+ fi
+ - name: Install GNU time
+ run: test -x /usr/bin/time || { sudo apt-get update -qq && sudo apt-get install -y -qq time; }
+ - name: Write RSS checkpoints with advisory compare
+ env:
+ PREVIOUS_TAG: ${{ steps.previous.outputs.tag }}
+ PREVIOUS_COMMIT: ${{ steps.previous.outputs.commit }}
+ CURRENT_COMMIT: ${{ github.sha }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ out="$RUNNER_TEMP/rss/checkpoints"
+ current="$RUNNER_TEMP/rss/current/gjc-linux-x64"
+ chmod +x "$current"
+ if [ -z "$PREVIOUS_TAG" ]; then
+ bun scripts/verify-rss-checkpoints.ts --all --write-baseline --matrix release \
+ --binary "$current" --commit "$CURRENT_COMMIT" --output-dir "$out"
+ exit 0
+ fi
+ bun scripts/verify-rss-checkpoints.ts --all --write-baseline --matrix "release-previous,$PREVIOUS_TAG" \
+ --binary "$RUNNER_TEMP/rss/previous/gjc-linux-x64" --commit "$PREVIOUS_COMMIT" --output-dir "$out"
+ # One measurement of the current binary both writes its checkpoint and
+ # compares it; --advisory reports regressions without failing the job.
+ bun scripts/verify-rss-checkpoints.ts --all --write-baseline --compare --advisory --allow-baseline-drift \
+ --matrix release --baseline "$out/$PREVIOUS_COMMIT.json" \
+ --binary "$current" --commit "$CURRENT_COMMIT" --output-dir "$out" 2>&1 | tee -a "$GITHUB_STEP_SUMMARY"
+ - name: Upload RSS checkpoints
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ with:
+ name: rss-checkpoint-${{ needs.release_metadata.outputs.version }}
+ path: ${{ runner.temp }}/rss/checkpoints
+ if-no-files-found: error
+ retention-days: 90
+
release_prepare:
if: ${{ always() && needs.native.result == 'success' && needs.binaries.result == 'success' && ((needs.release_metadata.outputs.channel == 'stable' && startsWith(github.ref, 'refs/tags/v') && github.event_name != 'workflow_dispatch') || (needs.release_metadata.outputs.channel == 'nightly' && needs.nightly_gate.result == 'success')) }}
needs: [native, binaries, release_metadata, nightly_gate]
@@ -524,6 +660,11 @@ jobs:
pattern: pi-natives-*
path: packages/natives/native
merge-multiple: true
+ - name: Rebuild the diagnostic artifact provenance record
+ run: |
+ set -euo pipefail
+ bun scripts/verify-diagnostic-artifact-provenance.ts --rebuild-from-sidecars packages/natives/native
+ bun scripts/verify-diagnostic-artifact-provenance.ts --verify packages/natives/native
- name: Prepare immutable package evidence
env:
RELEASE_CHANNEL: ${{ needs.release_metadata.outputs.channel }}
@@ -722,7 +863,13 @@ jobs:
# unchanged: nightly publishes on channel, stable only behind approval.
if: ${{ always() && needs.release_prepare.result == 'success' && (needs.release_metadata.outputs.channel == 'nightly' || needs.release_approval.result == 'success') }}
needs: [release_prepare, release_approval, release_metadata]
- timeout-minutes: 45
+ # 13 sequential publishes; each package gets a 10-minute integrity and a
+ # 5-minute dist-tag window of wall time, but uncached observation normally
+ # settles in seconds.
+ # 0.17.4 took 43 minutes and 0.17.5 timed out at 45 before uncached
+ # observation; 90 keeps a slow-registry day from stranding a half-published
+ # release without letting a hung publish hold the OIDC token indefinitely.
+ timeout-minutes: 90
runs-on: ubuntu-22.04
permissions:
# npm trusted publishing (OIDC): npm >= 11.5.1 exchanges this job's
@@ -773,16 +920,36 @@ jobs:
nightly) npm_tag=nightly ;;
*) echo "Unknown release channel $RELEASE_CHANNEL" >&2; exit 1 ;;
esac
- records="$RUNNER_TEMP/receipt-records.json"
- echo '[]' > "$records"
- order_file="$evidence_dir/gajae-release-publish-order-v1.json"
- # The sealed plan publishes dependencies before dependents; it
- # must cover exactly the expected package set.
- if [ "$(jq -r '.order[]' "$order_file" | sort)" != "$(jq -r '.packages[].name' "$expected" | sort)" ]; then
+ # Observe the registry through its uncached endpoints. The npm CLI's
+ # view command reads the full packument, which the registry CDN serves with
+ # max-age=300, so a fresh publish stayed invisible for up to five
+ # minutes per package and serialized ~13 packages into ~45 minutes.
+ # The per-version document and the dist-tags endpoint are served
+ # uncached (cf-cache-status: DYNAMIC) and reflect the publish at once.
+ registry_path() { local name="$1"; printf '%s' "${name/\//%2f}"; }
+ registry_integrity() {
+ curl --silent --fail --max-time 30 "${registry}$(registry_path "$1")/$2" 2>/dev/null \
+ | jq -r '.dist.integrity // empty' 2>/dev/null || true
+ }
+ registry_dist_tag() {
+ curl --silent --fail --max-time 30 "${registry}-/package/$(registry_path "$1")/dist-tags" 2>/dev/null \
+ | jq -r --arg tag "$2" '.[$tag] // empty' 2>/dev/null || true
+ }
+ records_dir="$RUNNER_TEMP/receipt-records"
+ rm -rf "$records_dir" && mkdir -p "$records_dir"
+ order_file="$evidence_dir/gajae-release-publish-order-v2.json"
+ # The sealed plan groups packages into dependency waves: nothing in a
+ # wave depends on anything in the same or a later wave. It must cover
+ # exactly the expected package set, each package exactly once.
+ if [ "$(jq -r '.waves[][]' "$order_file" | sort)" != "$(jq -r '.packages[].name' "$expected" | sort)" ]; then
echo "Publish order file does not cover exactly the expected package set" >&2
exit 1
fi
- for name in $(jq -r '.order[]' "$order_file"); do
+ # Publish one sealed package and wait until the registry exposes its
+ # integrity and dist-tag. Runs in a subshell per package, so `exit`
+ # fails only that package; the wave barrier below fails the job.
+ publish_one() {
+ local name="$1" index="$2" record version sha expected_sri tarball published observed output raced tagged deadline
record="$(jq -c --arg name "$name" '.packages[] | select(.name == $name)' "$expected")"
if [ -z "$record" ]; then
echo "Publish order references unknown package $name" >&2
@@ -795,12 +962,12 @@ jobs:
# Re-verify the sealed bytes at the credential boundary.
echo "$sha $tarball" | sha512sum --check --strict -
published=false
- observed="$(npm view "${name}@${version}" dist.integrity --registry="$registry" 2>/dev/null || true)"
+ observed="$(registry_integrity "$name" "$version")"
if [ "$observed" = "$expected_sri" ]; then
echo "Skipping $name@$version (registry already carries the sealed bytes)"
else
if ! output="$(npm publish "$tarball" --access public --registry="$registry" --tag="$npm_tag" 2>&1)"; then
- raced="$(npm view "${name}@${version}" dist.integrity --registry="$registry" 2>/dev/null || true)"
+ raced="$(registry_integrity "$name" "$version")"
if [ "$raced" != "$expected_sri" ]; then
echo "npm publish failed for $name@$version: $output" >&2
exit 1
@@ -811,34 +978,73 @@ jobs:
echo "Published $name@$version"
fi
# npm is read-after-write eventually consistent: re-observe
- # with bounded backoff until the sealed integrity is visible.
- # 0.15.4 proved 60s is not enough (agent-core became visible
- # only after the window elapsed, failing an otherwise clean
- # publish); allow up to 10 minutes.
- attempt=0
+ # until the sealed integrity is visible. 0.15.4 proved 60s is
+ # not enough (agent-core became visible only after the window
+ # elapsed, failing an otherwise clean publish), and v0.18.3
+ # proved 600s is not enough either: the 110MB win32-x64
+ # native was published at 09:47:26 but only became visible at
+ # 09:59:40, blowing the window by 2m11s AFTER five sibling
+ # packages had already gone immutable, which stranded the
+ # release. Size this for the slowest plausible replica (large
+ # Windows natives) rather than the median package, and keep
+ # the window bounded so a truly stalled registry still fails.
+ deadline=$((SECONDS + 1800))
while :; do
- attempt=$((attempt + 1))
- observed="$(npm view "${name}@${version}" dist.integrity --registry="$registry" 2>/dev/null || true)"
+ observed="$(registry_integrity "$name" "$version")"
[ "$observed" = "$expected_sri" ] && break
- if [ "$attempt" -ge 40 ]; then
+ if [ "$SECONDS" -ge "$deadline" ]; then
echo "$name@$version did not become visible with the sealed integrity" >&2
exit 1
fi
- sleep 15
+ sleep 5
done
fi
- tagged="$(npm view "${name}@${npm_tag}" version --registry="$registry" 2>/dev/null || true)"
- if [ "$tagged" != "$version" ]; then
- echo "npm tag $npm_tag for $name resolves $tagged, expected $version" >&2
+ # The dist-tag lands with the publish but is read from a separate
+ # endpoint; a transient miss must not abort a release whose bytes
+ # are already immutable, so it gets its own bounded window.
+ deadline=$((SECONDS + 300))
+ while :; do
+ tagged="$(registry_dist_tag "$name" "$npm_tag")"
+ [ "$tagged" = "$version" ] && break
+ if [ "$SECONDS" -ge "$deadline" ]; then
+ echo "npm tag $npm_tag for $name resolves $tagged, expected $version" >&2
+ exit 1
+ fi
+ sleep 5
+ done
+ jq -n --arg name "$name" --arg version "$version" --arg sha "$sha" --arg sri "$observed" --argjson published "$published" \
+ '{name:$name, version:$version, tarball_sha512:$sha, registry_integrity:$sri, published:$published}' \
+ > "$records_dir/$(printf '%03d' "$index").json"
+ }
+ # npm takes minutes to accept each publish, so packages within a wave
+ # publish concurrently; a wave starts only after every package of the
+ # previous wave is visible, which keeps dependencies ahead of dependents.
+ index=0
+ wave_count="$(jq '.waves | length' "$order_file")"
+ for wave in $(seq 0 $((wave_count - 1))); do
+ pids=()
+ for name in $(jq -r --argjson wave "$wave" '.waves[$wave][]' "$order_file"); do
+ ( publish_one "$name" "$index" ) &
+ pids+=("$!")
+ index=$((index + 1))
+ done
+ failed=0
+ for pid in "${pids[@]}"; do
+ wait "$pid" || failed=1
+ done
+ if [ "$failed" -ne 0 ]; then
+ echo "Publish wave $wave failed; later waves were not started" >&2
exit 1
fi
- jq --arg name "$name" --arg version "$version" --arg sha "$sha" --arg sri "$observed" --argjson published "$published" \
- '. += [{name:$name, version:$version, tarball_sha512:$sha, registry_integrity:$sri, published:$published}]' \
- "$records" > "$records.tmp"
- mv "$records.tmp" "$records"
done
- jq -n --arg version "$RELEASE_VERSION" --arg channel "$RELEASE_CHANNEL" --slurpfile packages "$records" \
- '{schema_version: 1, release_version: $version, release_channel: $channel, packages: $packages[0]}' \
+ if [ "$(jq -r '.name' "$records_dir"/*.json | sort)" != "$(jq -r '.packages[].name' "$expected" | sort)" ]; then
+ echo "Publish receipt records do not cover exactly the expected package set" >&2
+ exit 1
+ fi
+ # Receipt records keep sealed plan order regardless of completion order.
+ jq -s --arg version "$RELEASE_VERSION" --arg channel "$RELEASE_CHANNEL" \
+ '{schema_version: 1, release_version: $version, release_channel: $channel, packages: .}' \
+ "$records_dir"/*.json \
> "$evidence_dir/gajae-release-oidc-publish-receipt-v1.json"
- name: Persist OIDC publish receipt
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
diff --git a/.github/workflows/dev-ci.yml b/.github/workflows/dev-ci.yml
index 7b0d06d8b71..6219c0c4947 100644
--- a/.github/workflows/dev-ci.yml
+++ b/.github/workflows/dev-ci.yml
@@ -114,229 +114,6 @@ jobs:
if-no-files-found: error
retention-days: 14
- pr-contract-bootstrap:
- name: PR contract bootstrap
- if: ${{ github.event_name == 'pull_request' }}
- runs-on: ubuntu-22.04
- timeout-minutes: 10
- permissions:
- contents: read
- pull-requests: read
- outputs:
- # Fail closed: absent means the bootstrap never reported an authorized merge.
- merge_authorized: ${{ steps.contract.outputs['gjc-merge-authorized'] }}
- env:
- PR_BODY: ${{ github.event.pull_request.body }}
- PR_AUTHOR: ${{ github.event.pull_request.user.login }}
- PR_BASE_REF: ${{ github.event.pull_request.base.ref }}
- PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
- PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
- PR_NUMBER: ${{ github.event.pull_request.number }}
- GITHUB_TOKEN: ${{ github.token }}
- steps:
- - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- with:
- fetch-depth: 0
- repository: ${{ github.event.pull_request.head.repo.full_name }}
- ref: ${{ github.event.pull_request.head.sha }}
- persist-credentials: false
- - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- with:
- bun-version: "1.4.0"
- - name: Validate bootstrap PR contract
- id: contract
- shell: bash
- run: |
- set -euo pipefail
- unset BUN_OPTIONS
- empty_bunfig="$RUNNER_TEMP/gjc-pr-contract-empty-bunfig.toml"
- printf '# trusted empty Bun configuration\n' > "$empty_bunfig"
- # The program is materialised through a QUOTED heredoc rather than passed as a
- # single-quoted `bun -e` argument. An apostrophe anywhere in the JavaScript --
- # including in a prose comment -- closes that quote and hands the rest of the
- # program to bash. That is exactly what happened here: two of my own comments
- # ("timeline's", "bootstrap's") broke this job for every PR targeting dev, and
- # it stayed hidden because pushes straight to dev never run it (#5740 review).
- program="$RUNNER_TEMP/gjc-pr-contract-bootstrap.mjs"
- cat > "$program" <<'GJC_BOOTSTRAP_PROGRAM'
- const pattern = /^gajae\.pr-review-verdict\.v1 (merge-approved|merge-self-approved|merge-blocked|needs-human) sha256:([0-9a-f]{64}) reviewer:(architect|critic|human) reviewer-id:([^\s]+) evidence:(.+)$/;
- const lines = (Bun.env.PR_BODY ?? "").split(/\r?\n/).map(line => line.trim()).filter(line => line.startsWith("gajae.pr-review-verdict.v1"));
- if (lines.length !== 1) throw new Error(`Expected exactly one verdict line; found ${lines.length}.`);
- const match = pattern.exec(lines[0]);
- if (!match) throw new Error("Malformed gajae.pr-review-verdict.v1 line.");
- if (Bun.env.PR_BASE_REF !== "dev") throw new Error(`PR base must be dev, not ${Bun.env.PR_BASE_REF}.`);
- const [verdict, declaredDigest, , reviewerId] = match.slice(1);
- const run = async argv => { const child = Bun.spawn(argv, { stdout: "pipe", stderr: "pipe" }); const [stdout, stderr, exitCode] = await Promise.all([new Response(child.stdout).bytes(), new Response(child.stderr).text(), child.exited]); if (exitCode !== 0) throw new Error(`${argv.join(" ")} failed: ${stderr}`); return stdout; };
- const head = new TextDecoder().decode(await run(["git", "rev-parse", "HEAD"])).trim();
- if (head !== Bun.env.PR_HEAD_SHA) throw new Error(`Checked-out head ${head} != event head ${Bun.env.PR_HEAD_SHA}.`);
- await run(["git", "fetch", "--no-tags", "origin", Bun.env.PR_BASE_SHA]);
- await run(["git", "merge-base", "--is-ancestor", Bun.env.PR_BASE_SHA, head]);
- const diff = await run(["git", "diff", "--binary", "--full-index", "--no-ext-diff", `${Bun.env.PR_BASE_SHA}...${head}`]);
- const digest = new Bun.CryptoHasher("sha256").update(diff).digest("hex");
- if (digest !== declaredDigest) throw new Error(`Stale verdict digest ${declaredDigest}; exact digest is ${digest}.`);
- // Exactly one risk classification is mandatory (review finding 3): a missing
- // classification must never waive the stricter review tiers.
- const bodyRiskLines = (Bun.env.PR_BODY ?? "").split(/\r?\n/).map(line => line.trim()).filter(line => /^-\s*\[(x|X)\]\s*`(low-risk|regression-risk|high-risk)`/.test(line));
- if (bodyRiskLines.length !== 1) throw new Error(`PR body must check exactly one risk classification; found ${bodyRiskLines.length}.`);
- const bodyRiskMatch = /`(low-risk|regression-risk|high-risk)`/.exec(bodyRiskLines[0]);
- if (!bodyRiskMatch) throw new Error("PR body risk classification line is malformed.");
- const bodyRisk = bodyRiskMatch[1];
- const reviewPermission = async login => { const response = await fetch(`https://api.github.com/repos/${Bun.env.GITHUB_REPOSITORY}/collaborators/${encodeURIComponent(login)}/permission`, { headers: { Accept: "application/vnd.github+json", Authorization: `Bearer ${Bun.env.GITHUB_TOKEN}`, "X-GitHub-Api-Version": "2022-11-28" } }); if (!response.ok) throw new Error(`Reviewer permission lookup failed: ${response.status}`); return (await response.json()).permission; };
- const fetchReviews = async () => { const reviews = []; for (let page = 1; ; page++) { const response = await fetch(`https://api.github.com/repos/${Bun.env.GITHUB_REPOSITORY}/pulls/${Bun.env.PR_NUMBER}/reviews?per_page=100&page=${page}`, { headers: { Accept: "application/vnd.github+json", Authorization: `Bearer ${Bun.env.GITHUB_TOKEN}`, "X-GitHub-Api-Version": "2022-11-28" } }); if (!response.ok) throw new Error(`Reviews API failed: ${response.status}`); const batch = await response.json(); reviews.push(...batch); if (batch.length < 100) break; } return reviews; };
- // Freshness authority, mirroring scripts/verify-pr-verdict.ts. `commit_id` is
- // NOT immutable: GitHub re-points it at the new tip on force-push, and `dev` is
- // unprotected so stale-review dismissal never fires. The PR timeline's
- // `head_ref_force_pushed.created_at` is server-written and a contributor cannot
- // set it, unlike the commit's own committer date (#5692).
- //
- // DUPLICATION IS THE STANDING DEFECT HERE: this job reimplements the approval
- // rule instead of invoking the script, which is why the original hole survived
- // three rounds of fixes to the script alone. Repaired in place rather than
- // restructured because CI wiring cannot be exercised from a dev machine.
- const fetchHeadKnownAt = async () => {
- const forcePushes = [];
- for (let page = 1; ; page++) {
- const response = await fetch(`https://api.github.com/repos/${Bun.env.GITHUB_REPOSITORY}/issues/${Bun.env.PR_NUMBER}/timeline?per_page=100&page=${page}`, { headers: { Accept: "application/vnd.github+json", Authorization: `Bearer ${Bun.env.GITHUB_TOKEN}`, "X-GitHub-Api-Version": "2022-11-28" } });
- if (!response.ok) return { kind: "unreadable" };
- const batch = await response.json();
- for (const entry of batch) if (entry?.event === "head_ref_force_pushed") forcePushes.push(entry?.created_at);
- if (batch.length < 100) break;
- }
- const present = forcePushes.map(value => (typeof value === "string" ? value.trim() : "")).filter(value => value.length > 0);
- if (present.length !== forcePushes.length) return { kind: "unreadable" };
- if (present.some(value => !Number.isFinite(Date.parse(value)))) return { kind: "unreadable" };
- // Server-observed evidence is the ONLY authority. The committer date is
- // contributor-controlled in both directions: backdating was the original
- // hole and forward-dating blocked legitimate approvals, so it is not a
- // floor at all. "unconstrained" means no force-push on record, hence no
- // re-binding vector and nothing to disprove (#5692).
- if (present.length > 0) return { kind: "bound", at: present.reduce((latest, value) => (Date.parse(value) > Date.parse(latest) ? value : latest)) };
- return { kind: "unconstrained" };
- };
- // Refuses on unknown or unparseable times, and on a tie: GitHub serializes both
- // sides to whole seconds, so an equal value still means the review predates the
- // head it claims.
- const reviewPrecedesHead = (submittedAt, appearance) => {
- if (appearance.kind === "unconstrained") return false;
- if (appearance.kind === "unreadable") return true;
- if (typeof submittedAt !== "string") return true;
- const submitted = Date.parse(submittedAt);
- const known = Date.parse(appearance.at);
- if (!Number.isFinite(submitted) || !Number.isFinite(known)) return true;
- return submitted <= known;
- };
- let selfAuthorized = false;
- let mergeAuthorized = false;
- if (verdict === "merge-self-approved") {
- // Honest solo path (issue #4703): owner-only, low-risk-only, backed by the
- // risk-record comment bound to this exact head. merge-approved NEVER accepts
- // the author as reviewer; higher risk classes need independent review.
- if (reviewerId.toLowerCase() !== (Bun.env.PR_AUTHOR ?? "").toLowerCase()) throw new Error("merge-self-approved reviewer-id must name the PR author; this verdict is exclusively the owner self-authorization.");
- const comments = [];
- for (let page = 1; ; page++) { const response = await fetch(`https://api.github.com/repos/${Bun.env.GITHUB_REPOSITORY}/issues/${Bun.env.PR_NUMBER}/comments?per_page=100&page=${page}`, { headers: { Accept: "application/vnd.github+json", Authorization: `Bearer ${Bun.env.GITHUB_TOKEN}`, "X-GitHub-Api-Version": "2022-11-28" } }); if (!response.ok) throw new Error(`Comments API failed: ${response.status}`); const batch = await response.json(); comments.push(...batch); if (batch.length < 100) break; }
- const recordPattern = /^gajae\.pr-self-review\.v1 verdict:(merge-approved|merge-self-approved|merge-blocked) base:([0-9a-f]{40}) head:([0-9a-f]{40}) sha256:([0-9a-f]{64}) reviewer-id:([^\s]+) risk:(low-risk|regression-risk|high-risk) extra:(none|independent:[^\s]+) evidence:(.+)$/;
- const signaturePattern = /^self-review-signature: sha256:([0-9a-f]{64})$/;
- const eligible = comments.filter(comment => comment.user?.login?.toLowerCase() === reviewerId.toLowerCase() && (comment.body ?? "").split(/\r?\n/).some(line => line.trim().startsWith("gajae.pr-self-review.v1")));
- // Newest record wins: iterate newest-first and stop at the first valid one.
- for (const comment of eligible.reverse()) {
- const lines = (comment.body ?? "").split(/\r?\n/).map(line => line.trim());
- // Parity with the canonical parser: exactly one record, one signature,
- // and one signed-off-by footer line per comment.
- const recordLines = lines.filter(line => line.startsWith("gajae.pr-self-review.v1"));
- const signatureLines = lines.filter(line => signaturePattern.test(line));
- const footerLines = lines.filter(line => line === "Signed-off-by: gaebal-gajae (clawdbot) 🦞");
- if (recordLines.length !== 1 || signatureLines.length !== 1 || footerLines.length !== 1) throw new Error(`Self-review comment must contain exactly one record, signature, and footer line; found ${recordLines.length}/${signatureLines.length}/${footerLines.length}.`);
- const recordMatch = recordPattern.exec(recordLines[0]);
- const signatureMatch = signaturePattern.exec(signatureLines[0]);
- if (!recordMatch || !signatureMatch) throw new Error("Self-review comment present but malformed or unsigned; refusing self-authorization.");
- const [, sVerdict, sBase, sHead, sDigest, sReviewer, sRisk, sExtra, sEvidence] = recordMatch;
- const payload = [`gajae.pr-self-review.v1 verdict:${sVerdict}`, `base:${sBase}`, `head:${sHead}`, `sha256:${sDigest}`, `reviewer-id:${sReviewer}`, `risk:${sRisk}`, `extra:${sExtra}`, `evidence:${sEvidence}`].join("\n");
- const expected = new Bun.CryptoHasher("sha256").update("gajae.pr-self-review.v1.signature-domain").update(payload).digest("hex");
- if (signatureMatch[1] !== expected) throw new Error("Self-review integrity digest does not match the record.");
- // Only the repository owner account (delegated maintainer identity) may self-authorize.
- if (comment.author_association !== "OWNER" || comment.user?.login?.toLowerCase() !== reviewerId.toLowerCase()) throw new Error(`Self-review comment identity ${comment.user?.login} (${comment.author_association}) is not the repository owner matching reviewer-id ${reviewerId}.`);
- if (sVerdict !== "merge-self-approved") throw new Error(`Self-review verdict ${sVerdict} does not authorize the merge-self-approved path.`);
- if (sBase !== Bun.env.PR_BASE_SHA || sHead !== head || sDigest !== digest) throw new Error("Self-review is stale: base/head/digest do not match this exact PR.");
- if (sRisk !== bodyRisk) throw new Error(`Self-review risk ${sRisk} does not match the PR body risk classification ${bodyRisk}; the classifications must agree.`);
- if (sRisk !== "low-risk") throw new Error(`merge-self-approved is only available for low-risk changes; this record classifies ${sRisk} and requires independent review (merge-approved).`);
- if (sExtra !== "none") throw new Error(`merge-self-approved requires extra:none; got ${sExtra}.`);
- selfAuthorized = true;
- break;
- }
- if (!selfAuthorized) throw new Error("merge-self-approved requires a valid gajae.pr-self-review.v1 risk record (owner, low-risk, extra:none) bound to this exact head.");
- } else if (verdict === "merge-approved") {
- // The reviewed path: ALWAYS an authenticated exact-head APPROVED review
- // from a distinct identity with repository review authority.
- if (reviewerId.toLowerCase() === (Bun.env.PR_AUTHOR ?? "").toLowerCase()) throw new Error("merge-approved cannot be self-approved: the reviewer must be distinct from the PR author. A solo merge is only the explicitly named merge-self-approved verdict.");
- // Select the reviewer's last word FIRST, then judge its freshness. Filtering
- // on freshness inside this selection would let an unreadable later
- // CHANGES_REQUESTED drop out and promote an earlier APPROVED back to last
- // word, authorizing a merge the reviewer had withdrawn (#5692).
- const latest = (await fetchReviews()).filter(review => review.user?.login?.toLowerCase() === reviewerId.toLowerCase() && review.state !== "COMMENTED" && review.commit_id === head).at(-1);
- if (latest?.state !== "APPROVED") throw new Error(`Reviewer ${reviewerId} lacks an effective exact-head approval.`);
- const headKnownAt = await fetchHeadKnownAt();
- // Distinguish a PROVEN stale approval from unreadable evidence: the remedies
- // differ and conflating them sends the author after a new review that would
- // not clear a read failure (#5692 review).
- if (reviewPrecedesHead(latest.submitted_at, headKnownAt)) {
- if (headKnownAt.kind === "unreadable") throw new Error(`Reviewer ${reviewerId} has an APPROVED review on this exact head, but the PR timeline's force-push evidence could not be read, so its freshness cannot be established. This is a read failure, not a missing or stale approval; re-run once the timeline is readable. A new review will not clear it.`);
- throw new Error(`Reviewer ${reviewerId}'s APPROVED review reports this exact head but was submitted before the head appeared. GitHub has been observed to re-point stale approvals after a force-push, so this is not evidence of an approval of this code; obtain a review submitted after the current head.`);
- }
- const permission = await reviewPermission(reviewerId);
- if (!["admin", "maintain", "write"].includes(permission)) throw new Error(`Reviewer ${reviewerId} lacks repository review authority.`);
- mergeAuthorized = true;
- } else {
- // Waiting for a reviewer is a PENDING AUTHORIZATION, not a contract defect.
- // The separate "Merge approval bootstrap" job turns this into its own red
- // check, so this check keeps reporting contract validity alone. Every other
- // failure above remains a throw and still fails THIS check.
- console.log(`::notice::Verdict ${verdict} intentionally blocks merge. Obtain independent review (merge-approved) or, for a low-risk owner change, the explicit merge-self-approved path.`);
- }
- mergeAuthorized ||= selfAuthorized;
- const { appendFileSync } = await import("node:fs");
- appendFileSync(Bun.env.GITHUB_OUTPUT, `gjc-merge-authorized=${mergeAuthorized}\n`);
- console.log(`Bootstrap PR contract valid: ${digest}${selfAuthorized ? " (SELF-AUTHORIZED: merge-self-approved, no independent human review)" : ""}${mergeAuthorized ? "" : " (merge NOT authorized yet)"}`);
- GJC_BOOTSTRAP_PROGRAM
- bun --no-env-file --config="$empty_bunfig" "$program"
- - name: Run bootstrap G1 writer gate
- run: bun scripts/verify-gjc-state-writers.ts --fail --root .
-
- merge-approval-bootstrap:
- name: Merge approval bootstrap
- needs: [pr-contract-bootstrap]
- # always() is load-bearing. GitHub reports a SKIPPED job as Success for required
- # checks (https://docs.github.com/en/pull-requests/reference/status-checks), so a
- # plain `needs:` would skip this job when the contract job fails and publish a GREEN
- # merge-approval check for a red contract — the precise hole this must not open.
- # The job therefore always runs and decides from the upstream result plus its output.
- # The event guard mirrors pr-contract-bootstrap's own: outside a pull_request there is
- # no PR to authorize, and both jobs skip together.
- if: ${{ always() && github.event_name == 'pull_request' }}
- runs-on: ubuntu-22.04
- timeout-minutes: 5
- # Reads only the upstream job's result and output: no checkout, no bun, no token.
- permissions: {}
- steps:
- - name: Require an authorized merge verdict for this exact head
- env:
- CONTRACT_RESULT: ${{ needs.pr-contract-bootstrap.result }}
- MERGE_AUTHORIZED: ${{ needs.pr-contract-bootstrap.outputs.merge_authorized }}
- shell: bash
- run: |
- set -euo pipefail
- echo "contract job result: ${CONTRACT_RESULT:-unknown}; merge authorized: ${MERGE_AUTHORIZED:-false}"
- # Fail closed on anything that is not an explicit success + explicit true, so a
- # skipped, cancelled, or failed bootstrap can never read as an authorized merge.
- if [[ "${CONTRACT_RESULT:-}" != "success" ]]; then
- echo "::error::PR contract bootstrap did not succeed (${CONTRACT_RESULT:-unknown}); no merge authorization can be reported."
- exit 1
- fi
- if [[ "${MERGE_AUTHORIZED:-}" != "true" ]]; then
- echo "::error::This exact head is not authorized to merge: the verdict is not merge-approved (or merge-self-approved) backed by the review evidence the contract requires. The PR contract itself is valid — only the approval is pending."
- exit 1
- fi
- echo "Merge authorized for this exact head."
-
# Planner: resolve changed-path relevance and the affected task plan once, then
# fan the plan out into per-task shards. Emits the matrix, has_tasks/has_native
# flags, and the resolved changed paths so every downstream job reuses this
@@ -344,10 +121,7 @@ jobs:
affected-plan:
name: Affected path validation / plan
# A PR body/title edit without a base change changes no code, so the DAG is
- # skipped for it. `edited` stays in the trigger list because the verdict line
- # lives in the body and `pr-contract-bootstrap` must re-check it; that job is
- # independent of this one. Without this guard every verdict update cancelled
- # the in-flight run and re-queued the full matrix against an identical tree.
+ # skipped for it.
if: ${{ !(github.event_name == 'workflow_dispatch' && inputs.head_sha != '') && !(github.event_name == 'pull_request' && github.event.action == 'edited' && (github.event.changes.body != null || github.event.changes.title != null) && github.event.changes.base == null) }}
runs-on: ubuntu-22.04
@@ -361,6 +135,7 @@ jobs:
has_risk_canaries: ${{ steps.plan.outputs.has_risk_canaries }}
has_darwin_arm64_tab_worker_smoke: ${{ steps.plan.outputs.has_darwin_arm64_tab_worker_smoke }}
has_windows_session_path: ${{ steps.plan.outputs.has_windows_session_path }}
+ has_protected_daemon_decl: ${{ steps.plan.outputs.has_protected_daemon_decl }}
changed_paths: ${{ steps.plan.outputs.changed_paths }}
plan_mode: ${{ steps.plan.outputs.plan_mode }}
plan_digest: ${{ steps.plan.outputs.plan_digest }}
@@ -379,6 +154,9 @@ jobs:
run: |
head="$(git rev-parse HEAD)"
test "$head" = "$CI_DEV_SOURCE_SHA" || { echo "Checked-out SHA $head does not match $CI_DEV_SOURCE_SHA"; exit 1; }
+ - name: Fetch exact workflow-dispatch base
+ if: ${{ github.event_name == 'workflow_dispatch' }}
+ run: git fetch --no-tags origin "$GITHUB_BASE_SHA"
- name: Verify PR head contains exact base
if: ${{ github.event_name == 'pull_request' }}
shell: bash
@@ -404,7 +182,7 @@ jobs:
bun-version: "1.4.0"
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
- toolchain: nightly-2026-04-29
+ toolchain: nightly-2026-08-12
# Released CHANGELOG sections are append-only. These files have no
# `merge=union` driver, so a rebase conflicts here for real and a bad
# resolution can silently drop the whole file — which is exactly what
@@ -434,7 +212,7 @@ jobs:
telegram-daemon-generation:
name: Telegram daemon generation guard
needs: [affected-plan]
- if: ${{ needs.affected-plan.outputs.relevant == 'true' && (contains(needs.affected-plan.outputs.changed_paths, 'telegram-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'chat-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/broker/process-incarnation.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/host/host.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/client/discovery.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/telegram-daemon-generation-guard.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/telegram-daemon-generation-manifest.json')) }}
+ if: ${{ needs.affected-plan.outputs.relevant == 'true' && needs.affected-plan.outputs.has_protected_daemon_decl == 'true' }}
runs-on: ubuntu-22.04
timeout-minutes: 15
env:
@@ -483,7 +261,9 @@ jobs:
git remote add guard-head "https://github.com/${HEAD_REPOSITORY}.git"
git remote add guard-base "https://github.com/${BASE_REPOSITORY}.git"
git fetch --no-tags guard-head "refs/heads/${HEAD_REF}:refs/remotes/guard-head/${HEAD_REF}"
+ head_ref_sha="$(git rev-parse --verify "refs/remotes/guard-head/${HEAD_REF}^{commit}")"
base_ref_sha=''
+ head_ref_descends_from_event_head=''
case "${GUARD_EVENT_NAME}" in
pull_request)
# The immutable event base object remains authoritative while a queued
@@ -497,15 +277,27 @@ jobs:
;;
push)
git fetch --no-tags guard-base "${GITHUB_BASE_SHA}"
+ if git merge-base --is-ancestor "${GITHUB_HEAD_SHA}" "${head_ref_sha}"; then
+ head_ref_descends_from_event_head=true
+ else
+ status=$?
+ if [ "${status}" -eq 1 ]; then
+ echo "::error::Fetched push head ref ${HEAD_REF} does not descend from immutable event head ${GITHUB_HEAD_SHA}."
+ else
+ echo "::error::Could not prove immutable event head ${GITHUB_HEAD_SHA} is an ancestor of fetched push head ref ${HEAD_REF} (merge-base exit ${status})."
+ fi
+ exit 1
+ fi
;;
esac
{
echo "GUARD_CHECKED_OUT_HEAD=$(git rev-parse --verify HEAD^{commit})"
- echo "GUARD_HEAD_REF_SHA=$(git rev-parse --verify "refs/remotes/guard-head/${HEAD_REF}^{commit}")"
+ echo "GUARD_HEAD_REF_SHA=${head_ref_sha}"
+ echo "GUARD_HEAD_REF_DESCENDS_FROM_EVENT_HEAD=${head_ref_descends_from_event_head}"
echo "GUARD_BASE_OBJECT_SHA=$(git rev-parse --verify "${GITHUB_BASE_SHA}^{commit}")"
echo "GUARD_BASE_REF_SHA=${base_ref_sha}"
} >> "${GITHUB_ENV}"
- printf 'guard evidence: base %s@%s; head %s@%s\n' "${BASE_REPOSITORY}" "${GITHUB_BASE_SHA}" "${HEAD_REPOSITORY}" "${HEAD_REF}"
+ printf 'guard evidence: base %s@%s; event head %s; fetched head %s@%s; descendant proof %s\n' "${BASE_REPOSITORY}" "${GITHUB_BASE_SHA}" "${GITHUB_HEAD_SHA}" "${HEAD_REPOSITORY}" "${head_ref_sha}" "${head_ref_descends_from_event_head:-not-applicable}"
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: "1.4.0"
@@ -523,7 +315,7 @@ jobs:
windows-dev-doctor:
name: Windows dev:doctor + session-path regression
needs: [affected-plan]
- if: ${{ needs.affected-plan.outputs.relevant == 'true' && (contains(needs.affected-plan.outputs.changed_paths, 'scripts/dev-link') || needs.affected-plan.outputs.has_windows_session_path == 'true' || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/blob-store.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/session-manager.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/capability/fs.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/pr-4834-home-isolation.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/session/resident-cache-win32-gate.windows.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/cli/doctor') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/doctor-windows-repair-gate.test.ts')) }}
+ if: ${{ needs.affected-plan.outputs.relevant == 'true' && (contains(needs.affected-plan.outputs.changed_paths, 'scripts/dev-link') || needs.affected-plan.outputs.has_windows_session_path == 'true' || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/blob-store.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/session-manager.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/capability/fs.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/pr-4834-home-isolation.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/session/resident-cache-win32-gate.windows.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/cli/doctor') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/doctor-windows-repair-gate.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/config/file-lock.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/file-lock-empty-dir.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-natives/src/path_identity.rs')) }}
runs-on: windows-latest
timeout-minutes: 60
env:
@@ -542,7 +334,7 @@ jobs:
bun-version: "1.4.0"
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
- toolchain: nightly-2026-04-29
+ toolchain: nightly-2026-08-12
- name: Prepend rustup toolchain bin to PATH
shell: bash
run: |
@@ -552,7 +344,7 @@ jobs:
with:
shared-key: windows-dev-doctor-win32-x64
cache-on-failure: true
- save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' }}
+ save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' || github.event_name == 'pull_request' }}
cache-workspace-crates: true
- name: Cache bun dependencies
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
@@ -566,6 +358,13 @@ jobs:
TARGET_ARCH: x64
TARGET_VARIANTS: baseline
run: bun run ci:build:native
+ - name: Verify Windows native lock retirement semantics
+ shell: pwsh
+ run: |
+ cargo test -p pi-natives retirement_collision_preserves_canonical_owner_metadata
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ cargo test -p pi-natives generic_removal_does_not_reserve_the_lock_retirement_name
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Verify Windows workspace shim and doctor
shell: pwsh
run: |
@@ -594,6 +393,14 @@ jobs:
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
bun test ./packages/coding-agent/test/sdk-session-index-lock-contention.test.ts
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ bun test ./packages/coding-agent/test/file-lock-empty-dir.test.ts
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ bun test ./packages/coding-agent/test/doctor-plugin-restore.test.ts
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ bun test ./packages/natives/test/path-identity-windows.test.ts
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ bun test ./packages/coding-agent/test/file-lock-signed-identity.test.ts
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
# The session-state lock's owner-record create/retract protocol depends on
# Windows handle-sharing semantics (no FILE_SHARE_DELETE on the create
# descriptor), so its suite is proven here rather than on Ubuntu (#4990).
@@ -609,6 +416,21 @@ jobs:
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
bun test ./packages/natives/test/windows-hidden-shell.windows.test.ts --timeout 90000
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ bun test ./packages/natives/test/windows-shell-path.windows.test.ts --timeout 90000
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ bun test ./packages/natives/test/walker-pool-unavailable.windows.test.ts
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ bun test ./packages/utils/test/issue-935-repro.test.ts
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ bun test ./packages/coding-agent/test/task-cache-key.test.ts
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ bun test ./packages/coding-agent/test/sdk-session-router-attach-scope.test.ts
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ - name: Windows constrained-memory native runtime install
+ shell: pwsh
+ run: |
+ bun test ./packages/natives/test/windows-runtime-install.windows.test.ts
+ if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Windows hidden-console creation-flag unit tests
shell: pwsh
run: |
@@ -640,7 +462,7 @@ jobs:
bun-version: "1.4.0"
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
- toolchain: nightly-2026-04-29
+ toolchain: nightly-2026-08-12
- name: Cache bun dependencies
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
@@ -677,7 +499,7 @@ jobs:
bun-version: "1.4.0"
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
- toolchain: nightly-2026-04-29
+ toolchain: nightly-2026-08-12
- name: Prepend rustup toolchain bin to PATH
shell: bash
run: |
@@ -712,6 +534,142 @@ jobs:
bun test ./packages/natives/test/path-identity-windows.test.ts
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
+ # RSS checkpoint compare: runs the existing harness scripts/verify-rss-checkpoints.ts
+ # against the compiled gjc binary and compares it with the previous published
+ # stable release binary (same baseline convention and same --advisory stage as the
+ # release rss_checkpoint job in ci.yml). Regressions over tolerance are reported in
+ # the job summary, not failed; a missing baseline or harness error still fails the
+ # job. Only the cheap subset runs by default:
+ # S1(--help startup), S2(--version), S3(interactive first render). Each scenario
+ # costs 13 short-lived runs, so the gate stays minutes-scale; the heavy
+ # tool-workload scenarios S4(1 MiB read), S5(8 MiB bash) and S7(MCP echo) remain
+ # in the release rss_checkpoint job. The cheap subset still spans the startup,
+ # version-print and interactive entrypoints where bundle-splitting changes and
+ # session-manager residency show up first.
+ #
+ # Trigger scoping follows the affected-plan machinery like every sibling job:
+ # skipped entirely for metadata-only PR edits and head-only dispatch (affected-plan
+ # is skipped there, so this job never schedules), runs on dev push and PR, and
+ # only when RSS-relevant paths changed (binary build path, compiled-binary boot
+ # entrypoints -- cli.ts, cli-main.ts, cli-ordinary.ts, cli-commands.ts, whose module
+ # graph decides what the measured startup processes load -- native addon sources,
+ # session manager, or the harness itself).
+ rss-checkpoint:
+ name: RSS checkpoint compare (S1,S2,S3)
+ needs: [affected-plan]
+ if: ${{ needs.affected-plan.outputs.relevant == 'true' && (contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/scripts/build-binary.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/scripts/compile-args.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/cli.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/cli-main.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/cli-ordinary.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/cli-commands.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/verify-rss-checkpoints.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/ci-build-native.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/native') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/scripts') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-natives/src') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/session-manager.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/blob-store.ts')) }}
+ runs-on: ubuntu-22.04
+ timeout-minutes: 60
+ permissions:
+ contents: read
+ env:
+ CI_DEV_SOURCE_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
+ steps:
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+ with:
+ ref: ${{ github.event.pull_request.head.sha || github.sha }}
+ - name: Verify checked-out source head
+ shell: bash
+ run: |
+ head="$(git rev-parse HEAD)"
+ test "$head" = "$CI_DEV_SOURCE_SHA" || { echo "Checked-out SHA $head does not match $CI_DEV_SOURCE_SHA"; exit 1; }
+ - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
+ with:
+ bun-version: "1.4.0"
+ - uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
+ with:
+ toolchain: nightly-2026-08-12
+ - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
+ with:
+ shared-key: dev-rss-checkpoint-linux-x64
+ cache-on-failure: true
+ save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' }}
+ cache-workspace-crates: true
+ - name: Cache bun dependencies
+ uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
+ with:
+ path: ~/.bun/install/cache
+ key: bun-1.4.0-${{ runner.os }}-${{ hashFiles('**/bun.lock') }}
+ - name: Install system deps
+ run: bash scripts/ci-install-system-deps.sh
+ - run: bun install --frozen-lockfile
+ - name: Build required Linux native addon variants
+ env:
+ TARGET_PLATFORM: linux
+ TARGET_ARCH: x64
+ TARGET_VARIANTS: baseline modern
+ run: bun run ci:build:native
+ - name: Build compiled gjc binary
+ run: bun run --cwd=packages/coding-agent build
+ - name: Install GNU time
+ run: test -x /usr/bin/time || { sudo apt-get update -qq && sudo apt-get install -y -qq time; }
+ - name: Resolve previous published stable release
+ id: previous
+ env:
+ GH_TOKEN: ${{ github.token }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ # Fail closed on a missing baseline: with no published stable release
+ # there is no comparable baseline for the measured commit, so the job
+ # stops with an actionable diagnostic instead of passing silently.
+ previous="$(gh api --paginate "repos/$GITHUB_REPOSITORY/releases?per_page=100" \
+ --jq ".[] | select(.draft == false and .prerelease == false) | .tag_name" \
+ | sed -n 1p)"
+ if [ -z "$previous" ]; then
+ echo "::error::No comparable RSS baseline: this repository has no published stable release to measure as the baseline. Publish one stable release (its rss_checkpoint artifact ships gjc-linux-x64) and re-run this job."
+ exit 1
+ fi
+ echo "tag=$previous" >> "$GITHUB_OUTPUT"
+ git fetch --quiet --force origin "refs/tags/$previous:refs/tags/$previous"
+ echo "commit=$(git rev-parse "$previous^{commit}")" >> "$GITHUB_OUTPUT"
+ gh release download "$previous" --pattern gjc-linux-x64 --dir "$RUNNER_TEMP/rss/previous"
+ chmod +x "$RUNNER_TEMP/rss/previous/gjc-linux-x64"
+ - name: Write previous-release RSS baseline
+ env:
+ PREVIOUS_COMMIT: ${{ steps.previous.outputs.commit }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ out="$RUNNER_TEMP/rss/checkpoints"
+ bun scripts/verify-rss-checkpoints.ts --scenario S1,S2,S3 --write-baseline --matrix dev-ci \
+ --binary "$RUNNER_TEMP/rss/previous/gjc-linux-x64" --commit "$PREVIOUS_COMMIT" --output-dir "$out"
+ - name: Measure and compare current binary (advisory)
+ env:
+ PREVIOUS_TAG: ${{ steps.previous.outputs.tag }}
+ PREVIOUS_COMMIT: ${{ steps.previous.outputs.commit }}
+ CURRENT_COMMIT: ${{ github.event.pull_request.head.sha || github.sha }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ out="$RUNNER_TEMP/rss/checkpoints"
+ # Stage 1 of the threshold-promotion process (docs/perf-profiling-corpus.md):
+ # RSS thresholds stay advisory until variance is characterized and a human
+ # approves enforcement. --advisory reports regressions in the job summary
+ # without failing; every other harness error (missing baseline, bad binary,
+ # usage) still exits non-zero, and pipefail keeps tee from masking it.
+ # The baseline is the previous stable release, so a reported delta is
+ # release-to-head drift, not a delta caused by this commit alone.
+ {
+ echo "### RSS checkpoint compare (advisory)"
+ echo "Baseline: \`$PREVIOUS_TAG\` (\`$PREVIOUS_COMMIT\`). Deltas are release-to-head drift, not attributable to this commit alone."
+ echo '```'
+ } >> "$GITHUB_STEP_SUMMARY"
+ bun scripts/verify-rss-checkpoints.ts --scenario S1,S2,S3 --compare --advisory --allow-baseline-drift --matrix dev-ci \
+ --baseline "$out/$PREVIOUS_COMMIT.json" \
+ --binary packages/coding-agent/dist/gjc --commit "$CURRENT_COMMIT" --output-dir "$out" 2>&1 | tee -a "$GITHUB_STEP_SUMMARY"
+ echo '```' >> "$GITHUB_STEP_SUMMARY"
+ - name: Upload RSS checkpoint evidence
+ if: ${{ always() }}
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ with:
+ name: dev-rss-checkpoint-${{ github.run_id }}
+ path: ${{ runner.temp }}/rss/checkpoints
+ if-no-files-found: ignore
+ retention-days: 1
+ overwrite: true
+
+
# Native addon build runs at most once per run and publishes the built `.node`
# files as an artifact the runtime-dependent shards download. A content-hash
# cache of the native sources lets PRs that don't touch crates/natives restore
@@ -754,12 +712,12 @@ jobs:
- run: bun scripts/ci-dev-affected.ts --validate-plan
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
- toolchain: nightly-2026-04-29
+ toolchain: nightly-2026-08-12
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
shared-key: dev-affected-native-linux-x64
cache-on-failure: true
- save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' }}
+ save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' || github.event_name == 'pull_request' }}
cache-workspace-crates: true
- name: Cache bun dependencies
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
@@ -775,6 +733,12 @@ jobs:
run: |
test -f packages/natives/native/pi_natives.linux-x64-baseline.node
test -f packages/natives/native/pi_natives.linux-x64-modern.node
+ # `if-no-files-found: error` only proves the glob matched something, so assert every
+ # named producer sidecar exists before the upload claims to transport trust.
+ - name: Verify producer provenance sidecars
+ run: |
+ test -f packages/natives/native/pi_natives.linux-x64-baseline.node.provenance.json
+ test -f packages/natives/native/pi_natives.linux-x64-modern.node.provenance.json
- name: Upload native addon(s)
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
@@ -782,6 +746,8 @@ jobs:
path: |
packages/natives/native/pi_natives.linux-x64-baseline.node
packages/natives/native/pi_natives.linux-x64-modern.node
+ packages/natives/native/pi_natives.linux-x64-baseline.node.provenance.json
+ packages/natives/native/pi_natives.linux-x64-modern.node.provenance.json
if-no-files-found: error
retention-days: 1
overwrite: true
@@ -815,12 +781,12 @@ jobs:
bun-version: "1.4.0"
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
- toolchain: nightly-2026-04-29
+ toolchain: nightly-2026-08-12
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
shared-key: dev-affected-darwin-arm64-tab-worker
cache-on-failure: true
- save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' }}
+ save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' || github.event_name == 'pull_request' }}
cache-workspace-crates: true
- name: Cache bun dependencies
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
@@ -835,6 +801,11 @@ jobs:
run: bun run ci:build:native
- name: Build darwin-arm64 coding-agent binary
run: bun --cwd=packages/coding-agent run build
+ - name: Verify local diagnostic manifests on Darwin arm64
+ shell: bash
+ run: |
+ bun test ./packages/natives/test/diagnostic-loader.test.ts --test-name-pattern "verifies a local source addon"
+ bun test ./packages/natives/test/diagnostic-loader.test.ts --test-name-pattern "uses the verified local build digest carried by a compiled binary"
- name: Smoke compiled tab worker with fresh owner directories
shell: bash
run: |
@@ -914,7 +885,7 @@ jobs:
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
if: ${{ matrix.rust }}
with:
- toolchain: nightly-2026-04-29
+ toolchain: nightly-2026-08-12
- uses: taiki-e/install-action@56545b37b57562edd73171cb6c62cc509db4c34e # v2
if: ${{ matrix.nextest }}
with:
@@ -924,7 +895,7 @@ jobs:
with:
shared-key: dev-affected-rust-linux-x64
cache-on-failure: true
- save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' }}
+ save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' || github.event_name == 'pull_request' }}
cache-workspace-crates: true
- name: Cache bun dependencies
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
@@ -940,6 +911,16 @@ jobs:
with:
name: dev-affected-native-${{ github.run_id }}
path: packages/natives/native
+ # The transferred addons are only trusted through their producer sidecars: rebuild
+ # the record from them and verify every addon in the directory before anything in
+ # the shard can load one. Version/digest mismatches stay fail-closed.
+ - name: Rebuild and verify transferred native provenance
+ if: ${{ matrix.native }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ bun scripts/verify-diagnostic-artifact-provenance.ts --rebuild-from-sidecars packages/natives/native
+ bun scripts/verify-diagnostic-artifact-provenance.ts --verify packages/natives/native
- name: Run affected task shard
env:
AFFECTED_TASK_KEY: ${{ matrix.key }}
@@ -1029,11 +1010,11 @@ jobs:
CI_DEV_HAS_PYTHON: ${{ needs.affected-plan.outputs.has_python }}
CI_DEV_PYTHON_RESULT: skipped
CI_DEV_WINDOWS_DOCTOR_RESULT: ${{ needs.windows-dev-doctor.result }}
- CI_DEV_WINDOWS_DOCTOR_REQUIRED: ${{ contains(needs.affected-plan.outputs.changed_paths, 'scripts/dev-link') || needs.affected-plan.outputs.has_windows_session_path == 'true' || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/blob-store.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/session-manager.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/capability/fs.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/pr-4834-home-isolation.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/session/resident-cache-win32-gate.windows.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/cli/doctor') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/doctor-windows-repair-gate.test.ts') }}
+ CI_DEV_WINDOWS_DOCTOR_REQUIRED: ${{ contains(needs.affected-plan.outputs.changed_paths, 'scripts/dev-link') || needs.affected-plan.outputs.has_windows_session_path == 'true' || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/blob-store.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/session-manager.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/capability/fs.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/pr-4834-home-isolation.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/session/resident-cache-win32-gate.windows.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/cli/doctor') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/doctor-windows-repair-gate.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/config/file-lock.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/file-lock-empty-dir.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-natives/src/path_identity.rs') }}
CI_DEV_WINDOWS_NATIVE_TOOLCHAIN_RESULT: ${{ needs.windows-native-build-toolchain.result }}
CI_DEV_WINDOWS_NATIVE_TOOLCHAIN_REQUIRED: ${{ contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/scripts/build-native.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/scripts/rust-toolchain-path.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/test/build-native-profile.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/ci-build-native.ts') }}
CI_DEV_TELEGRAM_GUARD_RESULT: ${{ needs.telegram-daemon-generation.result }}
- CI_DEV_TELEGRAM_GUARD_REQUIRED: ${{ contains(needs.affected-plan.outputs.changed_paths, 'telegram-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'chat-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/broker/process-incarnation.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/host/host.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/client/discovery.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/telegram-daemon-generation-guard.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/telegram-daemon-generation-manifest.json') }}
+ CI_DEV_TELEGRAM_GUARD_REQUIRED: ${{ needs.affected-plan.outputs.has_protected_daemon_decl }}
CI_DEV_TELEGRAM_WINDOWS_RESULT: ${{ needs.windows-telegram-daemon-safety.result }}
CI_DEV_TELEGRAM_WINDOWS_REQUIRED: ${{ contains(needs.affected-plan.outputs.changed_paths, 'telegram-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'chat-daemon-control.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/broker/process-incarnation.ts') || contains(needs.affected-plan.outputs.changed_paths, 'daemon-control.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'notifications-telegram-daemon.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'chat-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-natives/src/path_identity.rs') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-natives/src/ps.rs') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-shell/src/process.rs') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/native/index.d.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/native/index.js') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/test/path-identity-windows.test.ts') }}
CI_DEV_DARWIN_ARM64_TAB_WORKER_SMOKE_RESULT: ${{ needs.affected-darwin-arm64-tab-worker-smoke.result }}
@@ -1075,13 +1056,13 @@ jobs:
CI_DEV_HAS_PYTHON: ${{ needs.affected-plan.outputs.has_python }}
CI_DEV_PYTHON_RESULT: skipped
CI_DEV_WINDOWS_DOCTOR_RESULT: ${{ needs.windows-dev-doctor.result }}
- CI_DEV_WINDOWS_DOCTOR_REQUIRED: ${{ contains(needs.affected-plan.outputs.changed_paths, 'scripts/dev-link') || needs.affected-plan.outputs.has_windows_session_path == 'true' || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/blob-store.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/session-manager.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/capability/fs.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/pr-4834-home-isolation.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/session/resident-cache-win32-gate.windows.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/cli/doctor') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/doctor-windows-repair-gate.test.ts') }}
+ CI_DEV_WINDOWS_DOCTOR_REQUIRED: ${{ contains(needs.affected-plan.outputs.changed_paths, 'scripts/dev-link') || needs.affected-plan.outputs.has_windows_session_path == 'true' || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/blob-store.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/session/session-manager.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/capability/fs.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/pr-4834-home-isolation.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/session/resident-cache-win32-gate.windows.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/cli/doctor') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/doctor-windows-repair-gate.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/config/file-lock.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/test/file-lock-empty-dir.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-natives/src/path_identity.rs') }}
CI_DEV_WINDOWS_NATIVE_TOOLCHAIN_RESULT: ${{ needs.windows-native-build-toolchain.result }}
CI_DEV_WINDOWS_NATIVE_TOOLCHAIN_REQUIRED: ${{ contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/scripts/build-native.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/scripts/rust-toolchain-path.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/test/build-native-profile.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/ci-build-native.ts') }}
CI_DEV_DARWIN_ARM64_TAB_WORKER_SMOKE_RESULT: ${{ needs.affected-plan.outputs.has_darwin_arm64_tab_worker_smoke == 'true' && 'success' || 'skipped' }}
CI_DEV_DARWIN_ARM64_TAB_WORKER_SMOKE_REQUIRED: ${{ needs.affected-plan.outputs.has_darwin_arm64_tab_worker_smoke }}
CI_DEV_TELEGRAM_GUARD_RESULT: ${{ needs.telegram-daemon-generation.result }}
- CI_DEV_TELEGRAM_GUARD_REQUIRED: ${{ contains(needs.affected-plan.outputs.changed_paths, 'telegram-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'chat-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/broker/process-incarnation.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/host/host.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/client/discovery.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/telegram-daemon-generation-guard.ts') || contains(needs.affected-plan.outputs.changed_paths, 'scripts/telegram-daemon-generation-manifest.json') }}
+ CI_DEV_TELEGRAM_GUARD_REQUIRED: ${{ needs.affected-plan.outputs.has_protected_daemon_decl }}
CI_DEV_TELEGRAM_WINDOWS_RESULT: ${{ needs.windows-telegram-daemon-safety.result }}
CI_DEV_TELEGRAM_WINDOWS_REQUIRED: ${{ contains(needs.affected-plan.outputs.changed_paths, 'telegram-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'chat-daemon-control.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/coding-agent/src/sdk/broker/process-incarnation.ts') || contains(needs.affected-plan.outputs.changed_paths, 'daemon-control.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'notifications-telegram-daemon.test.ts') || contains(needs.affected-plan.outputs.changed_paths, 'chat-daemon') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-natives/src/path_identity.rs') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-natives/src/ps.rs') || contains(needs.affected-plan.outputs.changed_paths, 'crates/pi-shell/src/process.rs') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/native/index.d.ts') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/native/index.js') || contains(needs.affected-plan.outputs.changed_paths, 'packages/natives/test/path-identity-windows.test.ts') }}
steps:
@@ -1175,12 +1156,12 @@ jobs:
bun-version: "1.4.0"
- uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
- toolchain: nightly-2026-04-29
+ toolchain: nightly-2026-08-12
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
shared-key: dev-state-gates-native-linux-x64
cache-on-failure: true
- save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' }}
+ save-if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/dev' || github.event_name == 'pull_request' }}
cache-workspace-crates: true
- name: Restore bun dependency cache
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
@@ -1361,7 +1342,7 @@ jobs:
if: ${{ env.CI_VI_REQUIRED == 'true' }}
uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
with:
- toolchain: nightly-2026-04-29
+ toolchain: nightly-2026-08-12
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
if: ${{ env.CI_VI_REQUIRED == 'true' }}
with:
diff --git a/.github/workflows/native-bench-ab.yml b/.github/workflows/native-bench-ab.yml
new file mode 100644
index 00000000000..7e5032e38f2
--- /dev/null
+++ b/.github/workflows/native-bench-ab.yml
@@ -0,0 +1,121 @@
+name: Native benchmark A/B
+
+on:
+ workflow_dispatch:
+ inputs:
+ suite:
+ description: "Benchmark suite to compare (any SUITES key in scripts/native-bench-ab.ts, or profile-stability)"
+ required: true
+ type: string
+ default: rss
+ base:
+ description: Git ref to use as the baseline
+ required: true
+ type: string
+ default: origin/main
+ iterations:
+ description: Optional per-block iteration count passed to the adapter (empty keeps its default)
+ required: false
+ type: string
+ default: ""
+
+permissions:
+ contents: read
+
+jobs:
+ benchmark:
+ name: Compare native benchmark on a quiet macOS host
+ runs-on: macos-14
+ timeout-minutes: 120
+ steps:
+ - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+ with:
+ fetch-depth: 0
+ - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
+ with:
+ bun-version: "1.4.0"
+ - uses: dtolnay/rust-toolchain@5b842231ba77f5c045dba54ac5560fed2db780e2 # nightly
+ with:
+ toolchain: nightly-2026-08-12
+ - name: Prepend rustup toolchain bin to PATH
+ shell: bash
+ run: echo "$(dirname "$(rustup which cargo)")" >> "$GITHUB_PATH"
+ - name: Install workspace dependencies
+ run: bun install --frozen-lockfile
+ - name: Build native addon
+ if: ${{ inputs.suite == 'profile-stability' }}
+ run: bun run build:native
+ - name: Profile twice and compare top self-time functions (AC1a.3)
+ if: ${{ inputs.suite == 'profile-stability' }}
+ run: |
+ set -euo pipefail
+ mkdir -p .gjc/profiles/profile-stability
+ # The native build leaves the runner saturated; profile only once the
+ # 1-minute load settles below half the cores (AC1a.3 needs a quiet host).
+ cores=$(sysctl -n hw.ncpu)
+ quiet=0
+ for _ in $(seq 1 90); do
+ load=$(sysctl -n vm.loadavg | awk '{print $2}')
+ if awk -v l="$load" -v c="$cores" 'BEGIN { exit !(l < c / 2) }'; then quiet=1; break; fi
+ sleep 10
+ done
+ if [ "$quiet" -ne 1 ]; then
+ echo "::error::runner never became quiet (1-minute load $load, need < $cores/2); stability comparison not run"
+ uptime | tee .gjc/profiles/profile-stability/load-never-quiet.txt
+ exit 1
+ fi
+ uptime | tee .gjc/profiles/profile-stability/load-before.txt
+ # Warm module, transpiler and fixture caches so both measured runs start alike.
+ bun run bench:profile -- --scenario all --out .gjc/profiles/profile-stability/warmup > /dev/null
+ bun run bench:profile -- --scenario all --out .gjc/profiles/profile-stability/run1 | tee .gjc/profiles/profile-stability/run1.log
+ bun run bench:profile -- --scenario all --out .gjc/profiles/profile-stability/run2 --compare .gjc/profiles/profile-stability/run1/report.json | tee .gjc/profiles/profile-stability/run2.log
+ uptime | tee .gjc/profiles/profile-stability/load-after.txt
+ - name: Upload profile stability evidence
+ if: ${{ always() && inputs.suite == 'profile-stability' }}
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ with:
+ name: profile-stability-${{ github.run_id }}-${{ github.run_attempt }}
+ path: |
+ .gjc/profiles/profile-stability/*.log
+ .gjc/profiles/profile-stability/*.txt
+ .gjc/profiles/profile-stability/*/report.json
+ .gjc/profiles/profile-stability/*/report.md
+ include-hidden-files: true
+ if-no-files-found: error
+ retention-days: 30
+ - name: Run native benchmark A/B
+ if: ${{ inputs.suite != 'profile-stability' }}
+ env:
+ SUITE: ${{ inputs.suite }}
+ BASE: ${{ inputs.base }}
+ ITERATIONS: ${{ inputs.iterations }}
+ run: |
+ set -euo pipefail
+ # native-bench-ab.ts rejects a 1-minute load above half the physical
+ # cores; wait for dependency install/toolchain setup to drain first.
+ cores=$(sysctl -n hw.physicalcpu)
+ quiet=0
+ for _ in $(seq 1 90); do
+ load=$(sysctl -n vm.loadavg | awk '{print $2}')
+ if awk -v l="$load" -v c="$cores" 'BEGIN { exit !(l <= c / 2) }'; then quiet=1; break; fi
+ sleep 10
+ done
+ if [ "$quiet" -ne 1 ]; then
+ echo "::error::runner never became quiet (1-minute load $load, need <= $cores/2); A/B not run"
+ exit 1
+ fi
+ # macOS ships Bash 3.2, where expanding an empty array under `set -u`
+ # is an unbound-variable error, so branch instead of splicing args.
+ if [ -n "$ITERATIONS" ]; then
+ bun scripts/native-bench-ab.ts --suite "$SUITE" --base "$BASE" --allow-baseline-drift --json --iterations "$ITERATIONS" > native-bench-ab.json
+ else
+ bun scripts/native-bench-ab.ts --suite "$SUITE" --base "$BASE" --allow-baseline-drift --json > native-bench-ab.json
+ fi
+ - name: Upload benchmark JSON
+ if: ${{ always() && inputs.suite != 'profile-stability' }}
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ with:
+ name: native-bench-ab-${{ github.run_id }}-${{ github.run_attempt }}
+ path: native-bench-ab.json
+ if-no-files-found: error
+ retention-days: 30
diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml
deleted file mode 100644
index 87e329dc8c4..00000000000
--- a/.github/workflows/pr-validation.yml
+++ /dev/null
@@ -1,350 +0,0 @@
-name: PR contract
-
-on:
- # This workflow is loaded from the trusted default branch. It never executes
- # code from the pull request; the base-owned validator only inspects checked-out bytes.
- pull_request_target:
- branches: [dev]
- types: [opened, edited, synchronize, reopened, ready_for_review]
- pull_request_review:
- branches: [dev]
- types: [submitted, edited, dismissed]
- # Maintainer self-review comments (issue #4703) are trusted workflow input: a comment
- # event re-runs the base-owned validator so the signed exact-head record is evaluated
- # server-side. Comment bytes are data only; head code is never executed. The event
- # payload has no pull_request object, so the job resolves the PR from the comment's
- # issue number and skips cleanly for comments that are not on a dev-based PR.
- issue_comment:
- # `deleted` is included so revoking the sole authorizing self-review comment
- # re-evaluates the contract: the validator re-fetches the remaining comments
- # and the PR-head result turns red when no valid backing record remains.
- types: [created, edited, deleted]
-
-
-permissions:
- contents: read
- pull-requests: read
-
-concurrency:
- group: pr-contract-${{ github.event.pull_request.number || github.event.issue.number }}
- cancel-in-progress: true
-
-jobs:
- validate:
- name: Validate exact-head PR contract
- runs-on: ubuntu-22.04
- timeout-minutes: 10
- # Job-level least privilege: comment-triggered runs publish a head-bound check
- # run under the required context name (checks: write) so authorization and
- # revocation update the SAME required authority as pull_request_target runs;
- # everything else stays read-only.
- permissions:
- contents: read
- pull-requests: read
- checks: write
- outputs:
- # Fail closed: absent means the validator never reported an authorized merge.
- merge_authorized: ${{ steps.validate.outputs.merge_authorized }}
- steps:
- - name: Resolve PR head/base from the event or the comment's PR
- id: pr
- env:
- GITHUB_TOKEN: ${{ github.token }}
- # Untrusted author-controlled text: passed through the environment so it is
- # never expanded into the script body.
- COMMENT_BODY: ${{ github.event.comment.body }}
- COMMENT_PREVIOUS_BODY: ${{ github.event.changes.body.from }}
- COMMENT_AUTHOR: ${{ github.event.comment.user.login }}
- PR_AUTHOR: ${{ github.event.issue.user.login }}
- shell: bash
- run: |
- set -euo pipefail
- if [[ -n "${{ github.event.pull_request.number }}" ]]; then
- echo "head_repo=${{ github.event.pull_request.head.repo.full_name }}" >> "$GITHUB_OUTPUT"
- echo "head_sha=${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT"
- echo "base_sha=${{ github.event.pull_request.base.sha }}" >> "$GITHUB_OUTPUT"
- exit 0
- fi
- number="${{ github.event.issue.number }}"
- if [[ -z "$number" ]]; then
- echo "::notice::event carries neither a pull_request nor an issue number; nothing to validate"
- echo "skip=true" >> "$GITHUB_OUTPUT"
- exit 0
- fi
- if ! pr_json="$(gh api "repos/${{ github.repository }}/pulls/${number}" 2>/dev/null)"; then
- echo "::notice::issue #$number is not a pull request; the PR contract does not apply"
- echo "skip=true" >> "$GITHUB_OUTPUT"
- exit 0
- fi
- base_ref="$(printf '%s' "$pr_json" | jq -r '.base.ref')"
- if [[ "$base_ref" != "dev" ]]; then
- echo "::notice::PR #$number targets ${base_ref}, not dev; the PR contract does not apply"
- echo "skip=true" >> "$GITHUB_OUTPUT"
- exit 0
- fi
- echo "head_repo=$(printf '%s' "$pr_json" | jq -r '.head.repo.full_name')" >> "$GITHUB_OUTPUT"
- echo "head_sha=$(printf '%s' "$pr_json" | jq -r '.head.sha')" >> "$GITHUB_OUTPUT"
- echo "base_sha=$(printf '%s' "$pr_json" | jq -r '.base.sha')" >> "$GITHUB_OUTPUT"
- # Does the TRIGGERING comment even look like a self-review record?
- #
- # `issue_comment` fires for every created, edited or deleted comment by anyone.
- # Revoking on all of them let any commenter red the exact-head `Merge approval`
- # and then, because `cancel-in-progress` kills the in-flight revalidation, keep
- # an authorized PR blocked indefinitely by commenting repeatedly — a denial of
- # service that changes no authorization evidence (#5740 review, Codex P2).
- #
- # Only a comment that is, or just stopped being, a candidate record can change
- # the answer, so only those justify revoking. `changes.body.from` covers an edit
- # that removed the marker; the marker alone is checked here, and the validator
- # still does the real authentication and signature work.
- # Read through the environment, NEVER by interpolating the comment into the
- # script. An inline expression referencing the comment body is shell injection
- # from an untrusted author, and it is unsafe even inside a shell comment:
- # GitHub expands the expression before bash ever sees the script, so a
- # multiline body injects a new executable line. The literal form is therefore
- # not written anywhere in this run block, including in prose.
- # The marker alone is not enough: anyone may write it, so gating on the string
- # only would let a third party force revocation and, with cancel-in-progress,
- # reinstate the denial of service behind one extra step. A self-review record is
- # author-owned by definition, so the comment must also come from the PR author.
- # The validator still authenticates author_association and the signature.
- record_marker="gajae.pr-self-review.v1"
- if [[ "${COMMENT_AUTHOR:-}" == "${PR_AUTHOR:-}" && -n "${COMMENT_AUTHOR:-}" ]] \
- && [[ "${COMMENT_BODY:-}" == *"$record_marker"* || "${COMMENT_PREVIOUS_BODY:-}" == *"$record_marker"* ]]; then
- echo "self_review_event=true" >> "$GITHUB_OUTPUT"
- else
- echo "self_review_event=false" >> "$GITHUB_OUTPUT"
- fi
- - name: Skip non-dev comments
- if: ${{ steps.pr.outputs.skip == 'true' }}
- run: echo "::notice::no dev-based PR to validate; contract check skipped"
- - name: Revoke any prior approval for this head before re-validating
- # MUST run before checkout, Bun setup, and the validator, and after PR
- # resolution only because the exact head SHA is required to address the check.
- #
- # Revoking inside the publication step still left the whole validation phase
- # uncovered: a failure during checkout, setup, or validation never reached the
- # revocation, so a previously-green "Merge approval" survived as the
- # authoritative required check. Writing the red here means any failure from
- # this point on leaves the merge blocked (#5692 review).
- # Only a comment that is, or just stopped being, a candidate self-review record
- # can change the authorization answer. Revoking on every comment let any
- # commenter block an authorized PR indefinitely (#5740 review, Codex P2).
- if: ${{ steps.pr.outputs.skip != 'true' && github.event_name == 'issue_comment' && steps.pr.outputs.self_review_event == 'true' }}
- env:
- GH_TOKEN: ${{ github.token }}
- shell: bash
- run: |
- set -euo pipefail
- gh api --method POST "repos/${{ github.repository }}/check-runs" \
- -H "Accept: application/vnd.github+json" \
- -f name="Merge approval" \
- -f head_sha="${{ steps.pr.outputs.head_sha }}" \
- -f status="completed" \
- -f conclusion="failure" \
- -f 'output[title]="Merge approval (re-validating)"' \
- -f "output[summary]=re-validation in progress; any prior approval for this head is revoked until it completes" \
- -f "details_url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
- - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- if: ${{ steps.pr.outputs.skip != 'true' }}
- with:
- fetch-depth: 0
- repository: ${{ steps.pr.outputs.head_repo }}
- ref: ${{ steps.pr.outputs.head_sha }}
- path: pr-head
- persist-credentials: false
- - name: Check out trusted validator from immutable event base
- if: ${{ steps.pr.outputs.skip != 'true' }}
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- with:
- fetch-depth: 1
- ref: ${{ steps.pr.outputs.base_sha }}
- path: trusted-base
- persist-credentials: false
- - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
- if: ${{ steps.pr.outputs.skip != 'true' }}
- with:
- bun-version: "1.4.0"
- - name: Validate body, exact head, immutable base, reviewer, and fast gate
- id: validate
- if: ${{ steps.pr.outputs.skip != 'true' }}
- env:
- GITHUB_ACTOR: ${{ github.event.pull_request.user.login }}
- GITHUB_TOKEN: ${{ github.token }}
- shell: bash
- run: |
- set -euo pipefail
- unset BUN_OPTIONS
- trusted_root="$PWD/trusted-base"
- repo_root="$PWD/pr-head"
- # This check reports CONTRACT validity only; the merge-approval verdict is
- # reported by the separate "Merge approval" job from the output captured here.
- # Every path writes merge_authorized, and only an explicit authorized line from
- # the trusted validator can set it to true.
- if [[ ! -f "$trusted_root/scripts/verify-pr-verdict.ts" ]]; then
- echo "::notice::immutable base ${{ steps.pr.outputs.base_sha }} predates the trusted validator; Dev CI PR contract bootstrap remains authoritative"
- echo "merge_authorized=false" >> "$GITHUB_OUTPUT"
- exit 0
- fi
- empty_bunfig="$RUNNER_TEMP/gjc-pr-contract-empty-bunfig.toml"
- printf '# trusted empty Bun configuration\n' > "$empty_bunfig"
- log="$RUNNER_TEMP/gjc-pr-contract.log"
- cd "$trusted_root"
- status=0
- bun --no-env-file --config="$empty_bunfig" "$trusted_root/scripts/verify-pr-verdict.ts" \
- --event "$GITHUB_EVENT_PATH" --repo "$repo_root" --trusted-root "$trusted_root" \
- --gate contract 2>&1 | tee "$log" || status=$?
- authorized=false
- if grep -qxF 'gjc-merge-authorized=true' "$log"; then
- authorized=true
- fi
- echo "merge_authorized=$authorized" >> "$GITHUB_OUTPUT"
- exit "$status"
- - name: Keep the approval revoked when revalidation does not complete
- # Covers the window the dedicated revoke step cannot: a failure AFTER the head
- # SHA is known but BEFORE the publication step raises the approval. Without this
- # a failed checkout, Bun setup, or validator leaves whatever the dedicated step
- # wrote — which is correct — but a failure in the RESOLVE step itself skips the
- # dedicated step entirely, and any partial SHA we do have should still be used.
- #
- # RESIDUAL, deliberately not claimed as closed: if resolution fails before
- # emitting `head_sha`, or the runner is hard-cancelled, no step runs and a
- # previously-green check survives. A workflow cannot close that; it needs branch
- # protection to require a check bound to the current run rather than any green
- # one (#5692 review).
- if: ${{ failure() && github.event_name == 'issue_comment' && steps.pr.outputs.self_review_event == 'true' && steps.pr.outputs.head_sha != '' }}
- env:
- GH_TOKEN: ${{ github.token }}
- shell: bash
- run: |
- set -euo pipefail
- gh api --method POST "repos/${{ github.repository }}/check-runs" \
- -H "Accept: application/vnd.github+json" \
- -f name="Merge approval" \
- -f head_sha="${{ steps.pr.outputs.head_sha }}" \
- -f status="completed" \
- -f conclusion="failure" \
- -f 'output[title]="Merge approval (re-validation failed)"' \
- -f "output[summary]=re-validation did not complete; any prior approval for this head remains revoked" \
- -f "details_url=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
- - name: Publish head-bound check results for comment-triggered validation
- # issue_comment runs are associated with the default-branch SHA, not the PR
- # head. Create/update a CHECK RUN on the resolved exact head with the exact
- # name of each required context ("Validate exact-head PR contract" and
- # "Merge approval" in this "PR contract" workflow), so comment events —
- # including deletion of the backing self-review record — publish to the SAME
- # required authorities the pull_request_target runs report. A parallel status
- # context could never revoke a green required check; this can. BOTH names must
- # be published: revoking a self-review comment while only republishing the
- # contract name would leave a stale green "Merge approval" behind it.
- # Branch-protection rollout contract: once this lands, dev branch
- # protection should require BOTH "PR contract / Validate exact-head PR contract"
- # and "PR contract / Merge approval" (strict); requiring only the contract name
- # would make an unapproved PR mergeable. Until protection is enabled, the Dev CI
- # PR contract bootstrap jobs plus the verdict discipline remain the enforced path.
- if: ${{ always() && github.event_name == 'issue_comment' && steps.pr.outputs.skip != 'true' }}
- env:
- GITHUB_TOKEN: ${{ github.token }}
- shell: bash
- run: |
- set -euo pipefail
- head_sha="${{ steps.pr.outputs.head_sha }}"
- base_sha="${{ steps.pr.outputs.base_sha }}"
- trusted_root="$PWD/trusted-base"
- conclusion="failure"
- summary="PR contract re-evaluated from a comment event"
- approval_conclusion="failure"
- approval_summary="merge authorization re-evaluated from a comment event"
- if [[ "${{ steps.validate.outcome }}" == "success" ]]; then
- conclusion="success"
- summary="PR contract valid at exact head (comment-triggered re-validation)"
- if [[ "${{ steps.validate.outputs.merge_authorized }}" == "true" ]]; then
- approval_conclusion="success"
- approval_summary="merge authorized at exact head (comment-triggered re-validation)"
- else
- approval_summary="merge not authorized at exact head; the PR contract itself is valid"
- fi
- # A trusted base that predates a required validator capability cannot
- # authorize anything; never publish success for that case, under EITHER name.
- #
- # Each capability needs its OWN marker. The self-review check alone was not
- # enough: `main` carries the self-review validator but not the approval
- # freshness rule, so a comment-triggered re-validation ran the old
- # `commit_id`-only logic and published a GREEN "Merge approval" on the exact
- # head — the #5692 hole, live on a required context, behind a guard that
- # reported the base as current (#5692 review).
- stale_base=""
- if [[ -f "$trusted_root/scripts/verify-pr-verdict.ts" ]]; then
- # Both markers must be CODE-ONLY tokens. `gajae.pr-self-review.v1` and
- # `head_ref_force_pushed` each also appear in prose, so a base that kept
- # the comments while losing the implementation would pass the guard — the
- # same shape as the bug the guard exists to catch (#5692 review).
- grep -q "selfReviewSatisfiesPolicy(" "$trusted_root/scripts/verify-pr-verdict.ts" \
- || stale_base="self-review validation"
- grep -q "reviewPrecedesHead(" "$trusted_root/scripts/verify-pr-verdict.ts" \
- || stale_base="${stale_base:+$stale_base and }approval freshness binding"
- fi
- if [[ -n "$stale_base" ]]; then
- conclusion="failure"
- summary="trusted base ${base_sha:0:10} predates $stale_base; bootstrap remains authoritative"
- approval_conclusion="failure"
- approval_summary="$summary"
- fi
- fi
- details_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
- # The approval authority was already revoked in its own step, before checkout
- # and validation, so any failure since then has left the merge blocked. Publish
- # the contract result, then raise the approval only if that write succeeded.
- gh api --method POST "repos/${{ github.repository }}/check-runs" \
- -H "Accept: application/vnd.github+json" \
- -f name="Validate exact-head PR contract" \
- -f head_sha="$head_sha" \
- -f status="completed" \
- -f conclusion="$conclusion" \
- -f 'output[title]="PR contract (comment-triggered exact-head re-validation)"' \
- -f "output[summary]=$summary" \
- -f "details_url=$details_url"
- # Only now may the approval authority be raised to its computed value. Reaching
- # this line means the contract publication above succeeded.
- gh api --method POST "repos/${{ github.repository }}/check-runs" \
- -H "Accept: application/vnd.github+json" \
- -f name="Merge approval" \
- -f head_sha="$head_sha" \
- -f status="completed" \
- -f conclusion="$approval_conclusion" \
- -f 'output[title]="Merge approval (comment-triggered exact-head re-validation)"' \
- -f "output[summary]=$approval_summary" \
- -f "details_url=$details_url"
-
- merge-approval:
- name: Merge approval
- needs: [validate]
- # always() is load-bearing. GitHub reports a SKIPPED job as Success for required
- # checks (https://docs.github.com/en/pull-requests/reference/status-checks), so a
- # plain `needs:` would skip this job when the contract job fails and publish a GREEN
- # merge-approval check for a red contract — the precise hole this must not open.
- # The job therefore always runs and decides from the upstream result plus its output.
- if: ${{ always() }}
- runs-on: ubuntu-22.04
- timeout-minutes: 5
- # Reads only the upstream job's result and output: no checkout, no bun, no token.
- permissions: {}
- steps:
- - name: Require an authorized merge verdict for this exact head
- env:
- CONTRACT_RESULT: ${{ needs.validate.result }}
- MERGE_AUTHORIZED: ${{ needs.validate.outputs.merge_authorized }}
- shell: bash
- run: |
- set -euo pipefail
- echo "contract job result: ${CONTRACT_RESULT:-unknown}; merge authorized: ${MERGE_AUTHORIZED:-false}"
- # Fail closed on anything that is not an explicit success + explicit true.
- if [[ "${CONTRACT_RESULT:-}" != "success" ]]; then
- echo "::error::PR contract validation did not succeed (${CONTRACT_RESULT:-unknown}); no merge authorization can be reported."
- exit 1
- fi
- if [[ "${MERGE_AUTHORIZED:-}" != "true" ]]; then
- echo "::error::This exact head is not authorized to merge: the verdict is not merge-approved (or merge-self-approved) backed by the review evidence the contract requires. The PR contract itself is valid — only the approval is pending."
- exit 1
- fi
- echo "Merge authorized for this exact head."
\ No newline at end of file
diff --git a/.github/workflows/re-review.yml b/.github/workflows/re-review.yml
new file mode 100644
index 00000000000..0e473612a5b
--- /dev/null
+++ b/.github/workflows/re-review.yml
@@ -0,0 +1,252 @@
+name: Re-request Review
+
+on:
+ issue_comment:
+ types: [created]
+
+permissions: {}
+
+defaults:
+ run:
+ shell: bash
+
+jobs:
+ request-review-on-comment:
+ name: Re-request review via comment
+ runs-on: ubuntu-22.04
+ timeout-minutes: 5
+ permissions:
+ pull-requests: write
+ issues: write
+ steps:
+ - name: Check authorization and comment format
+ id: authorize
+ uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
+ with:
+ result-encoding: string
+ script: |
+ const comment = context.payload.comment;
+
+ // Check if this is a PR (issue_comment doesn't include pull_request directly)
+ // We need to fetch the PR using the issue number
+ if (!context.payload.issue.pull_request) {
+ return 'not-pr';
+ }
+
+ // Match the exact command so similarly prefixed text cannot notify reviewers.
+ if (comment.body.trim() !== '/re-review') {
+ return 'no-match';
+ }
+
+ // Fetch full PR details by issue number
+ const pr = await github.rest.pulls.get({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: context.issue.number,
+ });
+
+ const commentAuthor = comment.user.login;
+ const prAuthor = pr.data.user?.login;
+
+ // Check if comment author is PR author
+ if (prAuthor && commentAuthor === prAuthor) {
+ return 'authorized';
+ }
+
+ // Check if comment author has admin, maintain, or write permission
+ try {
+ const collaborator = await github.rest.repos.getCollaboratorPermissionLevel({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ username: commentAuthor,
+ });
+
+ const permission = collaborator.data.permission;
+ if (permission === 'admin' || permission === 'maintain' || permission === 'write') {
+ return 'authorized';
+ }
+ } catch (error) {
+ if (error.status === 404) {
+ return 'unauthorized';
+ }
+ throw error;
+ }
+
+ return 'unauthorized';
+
+ - name: Get reviewers to re-request
+ if: steps.authorize.outputs.result == 'authorized'
+ id: get-reviewers
+ uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
+ with:
+ result-encoding: string
+ script: |
+ // Fetch full PR details by issue number
+ const pr = await github.rest.pulls.get({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: context.issue.number,
+ });
+ const prAuthor = pr.data.user?.login;
+
+ // Get all reviews on the PR with pagination
+ const reviews = await github.paginate(github.rest.pulls.listReviews, {
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: pr.data.number,
+ });
+
+ // Get currently requested reviewers
+ const currentlyRequested = new Set(
+ pr.data.requested_reviewers.map(r => r.login)
+ );
+
+ // Find reviewers with CHANGES_REQUESTED or COMMENTED (latest review only)
+ function latestReviewByUser(reviews, prAuthor) {
+ const reviewerMap = new Map();
+
+ for (const review of reviews) {
+ if (!review.user) {
+ continue;
+ }
+
+ const reviewer = review.user.login;
+
+ // GitHub does not allow a pull-request author to be requested
+ // as a reviewer, even when they submitted a self-review.
+ if (reviewer === prAuthor) {
+ continue;
+ }
+
+ // Skip bots (check if user type is Bot)
+ if (review.user.type === 'Bot') {
+ continue;
+ }
+
+ // Reviews are returned chronologically. Let the later response
+ // win when timestamps share the same second.
+ if (!reviewerMap.has(reviewer)) {
+ reviewerMap.set(reviewer, review);
+ } else {
+ const existing = reviewerMap.get(reviewer);
+ if (new Date(review.submitted_at) >= new Date(existing.submitted_at)) {
+ reviewerMap.set(reviewer, review);
+ }
+ }
+ }
+
+ return reviewerMap;
+ }
+
+ const reviewerMap = latestReviewByUser(reviews, prAuthor);
+
+ // Find reviewers whose latest review is CHANGES_REQUESTED or COMMENTED
+ // and who are not currently requested
+ const reviewersToRequest = [];
+ for (const [reviewer, review] of reviewerMap) {
+ if (
+ (review.state === 'CHANGES_REQUESTED' || review.state === 'COMMENTED') &&
+ !currentlyRequested.has(reviewer)
+ ) {
+ reviewersToRequest.push(reviewer);
+ }
+ }
+
+ function canRequestReviewer(permission) {
+ return ['admin', 'maintain', 'write', 'triage', 'read'].includes(permission);
+ }
+
+ // GitHub permits review requests for users with repository read
+ // access. A single ineligible user would reject a batch,
+ // so filter candidates before submitting the request.
+ const eligibleReviewers = [];
+ for (const reviewer of reviewersToRequest) {
+ try {
+ const collaborator = await github.rest.repos.getCollaboratorPermissionLevel({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ username: reviewer,
+ });
+
+ if (canRequestReviewer(collaborator.data.permission)) {
+ eligibleReviewers.push(reviewer);
+ } else {
+ console.log(`Skipping ${reviewer}: permission ${collaborator.data.permission} cannot be requested`);
+ }
+ } catch (error) {
+ if (error.status === 404) {
+ console.log(`Skipping ${reviewer}: no current collaborator access`);
+ continue;
+ }
+ throw error;
+ }
+ }
+
+ console.log('Eligible reviewers to request:', eligibleReviewers);
+ return JSON.stringify(eligibleReviewers);
+
+ - name: Request reviews
+ id: request-reviewers
+ if: steps.get-reviewers.outcome == 'success' && steps.get-reviewers.outputs.result != '[]'
+ uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
+ env:
+ REVIEWERS_JSON: ${{ steps.get-reviewers.outputs.result }}
+ with:
+ result-encoding: string
+ script: |
+ // Fetch full PR details by issue number
+ const pr = await github.rest.pulls.get({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: context.issue.number,
+ });
+
+ const reviewers = JSON.parse(process.env.REVIEWERS_JSON);
+
+ // GitHub's dedicated re-request endpoint returned an empty HTTP 500 for every
+ // call on this repository, so use the standard
+ // review-request endpoint, which re-notifies reviewers who already reviewed.
+ try {
+ await github.rest.pulls.requestReviewers({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ pull_number: pr.data.number,
+ reviewers,
+ });
+ } catch (error) {
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ body: `Failed to re-request reviews from ${reviewers.join(', ')}: ${error.message}`,
+ });
+ throw error;
+ }
+
+ - name: React to comment on success
+ if: steps.get-reviewers.outcome == 'success' && steps.get-reviewers.outputs.result != '[]'
+ uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
+ with:
+ script: |
+ const comment = context.payload.comment;
+
+ await github.rest.reactions.createForIssueComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: comment.id,
+ content: '+1',
+ });
+
+ - name: Reply to comment on no-op
+ if: steps.get-reviewers.outcome == 'success' && steps.get-reviewers.outputs.result == '[]'
+ uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
+ with:
+ script: |
+ const comment = context.payload.comment;
+
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ body: 'No eligible reviewers to re-request (reviewers with pending feedback may already be requested or lack repository access).',
+ });
diff --git a/.github/workflows/spoofed-version-sync.yml b/.github/workflows/spoofed-version-sync.yml
index 513d6f0e7aa..a3be7f71e42 100644
--- a/.github/workflows/spoofed-version-sync.yml
+++ b/.github/workflows/spoofed-version-sync.yml
@@ -9,10 +9,10 @@ name: Spoofed version drift
#
# Detection only, deliberately. The job holds no write scope: the repository
# runs a default-deny token policy (scripts/check-workflow-permissions.ts) with
-# exactly three narrowly-justified exceptions, and dev requires the signed
-# exact-head PR contract — so a bot-opened PR would still need a human verdict.
+# exactly three narrowly-justified exceptions, and the dev ruleset requires
+# an approving GitHub review — so a bot-opened PR would still need a maintainer.
# Buying `contents: write` + `pull-requests: write` for a daily cron would not
-# remove that human step, it would only widen the token surface. A red
+# remove that review, it would only widen the token surface. A red
# scheduled run is the signal; remediation is one command:
#
# bun scripts/check-spoofed-versions.ts --update
diff --git a/.gitignore b/.gitignore
index 73616cab60d..cca135af9cf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,8 +10,10 @@ packages/*/dist-chrome/
packages/*/dist-firefox/
target/
*.tsbuildinfo
+packages/natives/native/diagnostic-artifact.local.json
*.node
*.node.build.json
+*.node.provenance.json
*.b64.js
# `bun build --compile` leaves these next to the entrypoint when it is interrupted.
*.bun-build
@@ -62,6 +64,7 @@ packages/ai/test/.temp-images/
.gjc/_session-*/
.gjc/backlog-*/
.gjc/rss-checkpoints/
+.gjc/profiles/
.gjc/metrics.json
**/.gjc/settings.json.fallback-invalid
**/.gjc/settings.json.config-root.fallback-invalid
@@ -93,6 +96,12 @@ packages/coding-agent/binaries/
# Tests write evidence there and read it back within the same run;
# test fixtures belong in packages/*/test/fixtures/ instead.
/artifacts/
+# These suites also write relative to packages/tui when run through its package script.
+# Keep the issue-3798 historical evidence pair tracked in the same directory.
+/packages/tui/artifacts/g003-qa-report.json
+/packages/tui/artifacts/g011-qa-report.json
+/packages/tui/artifacts/g014-qa-report.json
+/packages/tui/artifacts/g015-qa-report.json
.release-040-artifacts/
# Python SDK build output
diff --git a/.plans/2026-07-03-gjc-mcp-hardening.md b/.plans/2026-07-03-gjc-mcp-hardening.md
deleted file mode 100644
index c45672560ab..00000000000
--- a/.plans/2026-07-03-gjc-mcp-hardening.md
+++ /dev/null
@@ -1,95 +0,0 @@
-# GJC Coordinator MCP Hardening Plan
-
-**Goal:** Make the GJC Coordinator MCP path work end-to-end for the real `gjc --worktree` runtime instead of repeatedly fixing mocked tmux delivery symptoms.
-**Issue lineage:** #1409, #1416, #1417, #1418; new upstream follow-up issue/PR to be created after private-fork patch is verified.
-**Status:** Approved for execution by Grant in chat: "Do not stop until complete."
-
-## Context inspected
-- `AGENTS.md`: repo-local law; primary surface is `packages/coding-agent/`; no arbitrary workflow defaults; use Bun commands; no `tsc`/`npx tsc`; no `console.*` in coding-agent.
-- `packages/coding-agent/src/coordinator-mcp/server.ts`: Coordinator starts tmux sessions, sends prompt via paste buffer + Enter, and waits for runtime sidecar ack.
-- `packages/coding-agent/src/session/agent-session.ts`: runtime emits coordinator state from agent session events.
-- `packages/coding-agent/src/gjc-runtime/session-state-sidecar.ts`: sidecar state writer/reader boundary.
-- `packages/tui/src/components/editor.ts`: TUI editor owns paste, autocomplete, slash-command matching, and Enter behavior.
-- Upstream issues/PRs: #1409/#1410 fixed submit key; #1416/#1417 fixed multiline paste preservation; #1418 preserved tmux sessions after PTY close.
-- Live repro after #1417: multiline prompt is preserved, but runtime still does not emit `turn_start`; pane shows slash autocomplete state for `skill:ultragoal`, so Enter is likely being consumed by slash-command autocomplete rather than submitting the pasted prompt.
-- Existing tests: mostly mock Coordinator command runners or fake runtime scripts; no true live-runtime Coordinator MCP E2E that launches actual `gjc --worktree` and verifies real sidecar ack.
-
-## Non-goals
-- Do not redesign the public MCP contract unless evidence proves tmux/TUI delivery cannot be made reliable.
-- Do not add new workflow skills or change public workflow defaults.
-- Do not mass-refactor TUI/editor architecture.
-- Do not rely on mocks as proof of fix.
-- Do not merge or publish upstream without Grant approval.
-
-## Recommendation
-Start with the smallest root-cause fix: make Coordinator-delivered pasted prompts submit as prompts instead of being intercepted by TUI autocomplete/slash-command selection. Add the missing real-runtime E2E/smoke harness so future maintainer fixes cannot pass with mocks only. If tmux/TUI delivery remains fragile after this proof, escalate to a follow-up architecture change where Coordinator preserves MCP contract but uses GJC RPC/live-control underneath.
-
-Simple path: patch the current Coordinator/TUI path and add a real E2E proof.
-Skipped complexity: no full RPC-backed Coordinator rewrite yet, no new daemon, no broad TUI rewrite.
-Upgrade trigger: if real E2E remains flaky after a narrow TUI submit fix, or if multiple further tmux/TUI state failures appear in the same MCP path.
-
-## Plan
-
-### Task 1: Reproduce on latest upstream dev
-**Objective:** Confirm whether upstream `0a5eac6d` still fails before patching.
-**Steps:**
-1. Build/link latest branch runtime from this worktree or a dedicated source path.
-2. Restart Coordinator MCP so it uses the new runtime.
-3. Create a clean scratch repo.
-4. Run `gjc_delegate_execute` with a no-edit smoke task.
-5. Capture session id, turn id, delivery state, sidecar state, pane tail, and cleanup.
-
-### Task 2: Add failing behavioral coverage
-**Objective:** Capture the real broken invariant before implementation.
-**Candidate files:**
-- `packages/tui/test/editor.test.ts`
-- `packages/tui/test/editor-autocomplete-actions.test.ts`
-- `packages/coding-agent/test/coordinator-mcp-server.test.ts`
-
-**Invariant:** A multi-line pasted delegated prompt beginning with `/skill:ultragoal` must submit as a prompt when Coordinator sends Enter, not remain in autocomplete selection state.
-
-### Task 3: Patch narrow submit/autocomplete behavior
-**Objective:** Prevent pasted multi-line delegated prompts from being hijacked by slash autocomplete on submit.
-**Candidate files:**
-- `packages/tui/src/components/editor.ts`
-- `packages/tui/src/autocomplete.ts` only if needed.
-
-**Constraints:**
-- Preserve normal interactive slash autocomplete for typed single-line commands.
-- Preserve normal Tab/Enter selection behavior when the user intentionally opens autocomplete.
-- Keep change small and canonical style.
-
-### Task 4: Add real-runtime E2E proof
-**Objective:** Stop accepting mock-only fixes for this failure class.
-**Candidate locations:**
-- `packages/coding-agent/test/coordinator-mcp-server.test.ts` for test harness extension, or a dedicated smoke script if full test is too environment-sensitive.
-
-**Proof shape:**
-- Launch actual `gjc --worktree` in tmux through Coordinator code path.
-- Send Coordinator prompt.
-- Verify sidecar moves to `source=agent_session_event` / matching `current_turn_id` or equivalent acknowledged delivery.
-- Cleanup tmux/worktree in `finally`.
-
-### Task 5: Verify and iterate to green
-**Commands/surfaces:**
-- Focused TUI/editor test(s).
-- Focused Coordinator MCP test(s).
-- `bun run build:native` if native/runtime path changed or needed for live smoke.
-- `gjc --smoke-test` using built/linked runtime.
-- Real `gjc_delegate_execute` MCP repro against clean scratch repo.
-- Broader package checks as practical: `bun run check:ts`, targeted `bun test ...`.
-
-### Task 6: Preserve docs/changelog and upstream handoff
-**Files:**
-- `packages/coding-agent/CHANGELOG.md` and/or `packages/tui/CHANGELOG.md` if user-visible behavior changes.
-- This plan and a GJC/implementation receipt under `.plans/` or `.gjc/`.
-- Upstream issue with lineage and real E2E proof.
-- PR from private fork/branch to upstream after verification.
-
-## Risks / approval gates
-- Material architecture fork: if tmux/TUI prompt delivery cannot be made robust and Coordinator should switch to RPC/live-control underneath, stop and summarize evidence before broad rewrite unless the needed change is clearly the smallest reliable path.
-- External side effects: opening upstream issue/PR is approved by Grant's current request, but merging/publishing is not approved.
-- Live sessions: do not kill unrelated `gjc --worktree` processes; only cleanup sessions created for this repro/fix.
-
-## Execution handoff
-Approved coding loop execution is degraded by the broken GJC Coordinator MCP itself. Use direct local tools and bounded subagents as needed to repair GJC; verify with real MCP tool output before claiming completion.
diff --git a/.plans/2026-08-07-sdk-terminal-receipt-correlated-steer.md b/.plans/2026-08-07-sdk-terminal-receipt-correlated-steer.md
deleted file mode 100644
index 5d0f07f24ce..00000000000
--- a/.plans/2026-08-07-sdk-terminal-receipt-correlated-steer.md
+++ /dev/null
@@ -1,188 +0,0 @@
-# SDK Terminal Receipt and Correlated Steer Plan
-
-**Status:** approved implementation contract
-**Repository:** `/private/tmp/gjc-3825-current-dev-20260807`
-**Branch:** `fix/sdk-terminal-receipt-steer-20260807`
-**Base:** `origin/dev@b9cb17124b9cefadf9e5acc0e1ecabaddb7a0609`
-**Source work:** closed PR #3825 and open issue #3956
-
-## Goal
-
-Make SDK terminal outcomes truthful and retry-safe: execution completion must not imply a usable receipt, and `turn.steer` must support durable caller correlation and authoritative status lookup after uncertain transport or process outcomes.
-
-## Evidence and root constraint
-
-- Current Coordinator code can mark a turn completed while returning only the advisory `completion_missing_final_response`.
-- Current state has no shared receipt-state module, no `receipt_missing` terminal outcome, and no dual execution/receipt projection.
-- Current `turn.steer` accepts text without a caller correlation key and exposes no `turn.steer_status` query.
-- Current architecture has two steering paths: the SDK bus control surface and the host session-runtime control surface. Both must implement one contract.
-- Closed PR #3825 contains reusable pure modules, test cases, and invariants, but its integration predates the current SDK surface factory.
-- Open issue #3956 covers the correlated-steering half and has no implementing pull request at this base.
-
-## Settled behavior
-
-### Terminal receipt truth
-
-- Track execution state separately from receipt state.
-- A terminal execution with a reportable final response has a present receipt.
-- A terminal execution without a reportable final response fails closed as `receipt_missing`; it must not appear as successful completion.
-- Preserve existing terminal failure and cancellation meaning.
-- Persist only outcome metadata. Do not store prompt, steer text, transcript, credentials, or provider response bodies in reconciliation records.
-- Preserve current schema compatibility where existing readers require it.
-
-### Correlated steering
-
-- `turn.steer` accepts optional `clientRef`, using the same trimmed, non-empty, 128-character contract as existing prompt and skill correlation.
-- A retained duplicate `clientRef` is rejected before dispatch.
-- A caller can query `turn.steer_status` using either `clientRef` or the canonical command/turn identity returned at acceptance.
-- A durable accepted record allows callers to reconcile uncertain replies without duplicate delivery.
-- Status hydration after process restart is conservative. Missing durable proof must never become successful delivery.
-- Both current steering entrypoints use the same reconciliation owner and observable contract.
-
-## Non-goals
-
-- Do not restore the old inline SDK factory architecture.
-- Do not revive committed generated docs-index files that current `dev` removed.
-- Do not change prompt or skill correlation semantics.
-- Do not store request or response bodies in durable reconciliation records.
-- Do not change unrelated Coordinator lifecycle, provider, model, workflow, or UI behavior.
-- Do not split receipt truth and steering into separate pull requests without Grant's approval.
-- Do not edit this approved plan during implementation.
-
-## Architecture
-
-Use the current kind-aware reconciliation store as the single durable correlation owner. Add `steer` as a typed record kind and add one small receipt-state module for execution/receipt reduction. Wire current bus and host factory paths to those primitives rather than copying the old PR's integration hunks.
-
-Coordinator and sidecar projections consume the shared receipt-state semantics. The operation registry remains the public SDK authority and regenerates its inventory through the repository generator.
-
-## Implementation tasks
-
-### Task 1: Add receipt-state semantics with failing tests
-
-**Create:**
-- `packages/coding-agent/src/sdk/receipt-state.ts`
-- `packages/coding-agent/test/sdk-receipt-state.test.ts`
-
-**Adapt tests:**
-- `packages/coding-agent/test/agent-session-terminal-receipt-state.test.ts`
-
-Add behavioral tests for reportable output, empty output, failure, cancellation, and conservative unknown states. Run the focused tests and record the expected RED result before implementation. Add the minimum typed reducer required to pass.
-
-### Task 2: Extend durable reconciliation for steering
-
-**Modify:**
-- `packages/coding-agent/src/sdk/bus/reconciliation-store.ts`
-- `packages/coding-agent/src/sdk/bus/kind-aware-reconciliation.ts`
-- `packages/coding-agent/src/sdk/bus/prompt-reconciliation.ts`
-- `packages/coding-agent/src/sdk/prompt-status.ts`
-
-**Add or adapt tests:**
-- `packages/coding-agent/test/sdk-reconciliation-store.test.ts`
-- `packages/coding-agent/test/sdk-reconciliation-recovery.test.ts`
-- `packages/coding-agent/test/sdk-kind-aware-reconciliation.test.ts`
-- `packages/coding-agent/test/sdk-steer-reconciliation.test.ts`
-
-Introduce a discriminated `steer` record with correlation metadata and digest-only request identity. Prove admission, duplicate rejection, accepted/in-flight/terminal status, recovery, eviction, corruption handling, and no body persistence.
-
-### Task 3: Wire both steering entrypoints
-
-**Modify:**
-- `packages/coding-agent/src/sdk/bus/index.ts`
-- `packages/coding-agent/src/sdk/host/session-runtime.ts`
-- `packages/coding-agent/src/sdk/host/control/operations.ts`
-- `packages/coding-agent/src/sdk/host/control/dispatch.ts`
-- `packages/coding-agent/src/sdk/host/query/handlers.ts`
-
-**Add or adapt tests:**
-- `packages/coding-agent/test/sdk-steer-dispatch-integration.test.ts`
-- `packages/coding-agent/test/sdk-q30-steer-status.test.ts`
-- `packages/coding-agent/test/sdk-host-wiring.test.ts`
-- `packages/coding-agent/test/helpers/sdk-production-host.ts`
-
-Add optional `clientRef`, return canonical correlation after durable acceptance, and expose authoritative `turn.steer_status`. Prove the SDK bus and host session-runtime paths behave identically. Prove status-only replay never dispatches another steer.
-
-### Task 4: Make terminal projections fail closed
-
-**Modify:**
-- `packages/coding-agent/src/gjc-runtime/session-state-sidecar.ts`
-- `packages/coding-agent/src/coordinator-mcp/server.ts`
-
-**Adapt tests:**
-- `packages/coding-agent/test/session-state-sidecar.test.ts`
-- `packages/coding-agent/test/coordinator-mcp-server.test.ts`
-- `packages/coding-agent/test/sdk-prompt-terminal-arbiter.test.ts`
-- `packages/coding-agent/test/sdk-q26-prompt-status.test.ts`
-
-Project execution and receipt states independently. Replace advisory success for missing final output with the typed `receipt_missing` failure. Preserve attributable terminal authority and existing failure/cancellation paths.
-
-### Task 5: Update public contract and generated inventory
-
-**Modify:**
-- `packages/coding-agent/src/sdk/protocol/operation-registry.ts`
-- `packages/coding-agent/src/sdk/protocol/operation-inventory.generated.json` through the repository generator
-- `packages/coding-agent/test/sdk-operation-inventory.test.ts`
-- `packages/coding-agent/test/sdk-operation-matrix.test.ts`
-- `packages/coding-agent/test/sdk-adapter-dispositions.test.ts`
-- `packages/coding-agent/test/manifests/sdk-adapter-parity-v1.json`
-- `docs/sdk.md`
-- `packages/coding-agent/CHANGELOG.md`
-
-Document receipt truth, `turn.steer` correlation, `turn.steer_status`, conservative restart semantics, duplicate behavior, and retention limits. Regenerate rather than hand-edit generated authority.
-
-## Verification
-
-Run focused tests after each task. Final verification must include:
-
-- Receipt-state and terminal-receipt tests.
-- Reconciliation store, recovery, kind-aware, prompt arbiter, and Q26 tests.
-- Steering reconciliation, dispatch, Q30, and both control-surface integration tests.
-- Coordinator and sidecar tests.
-- Operation inventory, matrix, and adapter parity tests.
-- Mutation proof that removing receipt classification makes missing-output tests fail.
-- Mutation proof that bypassing durable steer acceptance makes replay/status tests fail.
-- Mutation proof that either steering entrypoint omits correlation makes its path-specific test fail.
-- `bun --cwd=packages/coding-agent run check`.
-- Repository-required generated inventory checks.
-- `bun run build` or the current canonical coding-agent build command.
-- Built artifact `gjc --smoke-test`.
-- `git diff --check`.
-- Independent adversarial review after implementation.
-- Current-head pull-request checks and unresolved-thread review after push.
-
-## Risks and stop conditions
-
-Stop before mutation beyond this contract when:
-
-- Current source requires separate public semantics for the two steering entrypoints.
-- Durable receipt truth requires storing prompt, steer, transcript, credential, or provider-response bodies.
-- Current upstream adds a materially overlapping implementation.
-- The implementation must split into separate pull requests.
-- A generated or public contract change exceeds the behavior settled above.
-
-Mechanical current-source changes needed to apply the settled contract are not material forks.
-
-## Routing
-
-```yaml
-routing:
- reasoning_mode: investigative
- execution_topology: direct
- gjc_profile: investigative
- gjc_workflow: direct
- capability_evidence:
- - The root constraint and public behavior are settled, but integration spans persistence, two control paths, terminal projection, and generated SDK authority.
- - Current architecture differs materially from the old pull-request integration and requires source-guided adaptation.
- - Behavioral and mutation tests provide strong oracles, so adversarial architecture routing is unnecessary.
- topology_evidence:
- - One coherent SDK outcome owns all changes and can be implemented and proven in one isolated worktree.
- - No independent long-lived work lanes or cross-session coordination are required.
- escalation_triggers:
- - Public SDK behavior differs between current steering entrypoints.
- - Receipt truth requires sensitive body persistence.
- - Upstream adds overlapping work.
- - One-pull-request scope becomes invalid.
-```
-
-## Execution handoff
-
-Run the GJC CLI from `/private/tmp/gjc-3825-current-dev-20260807` so repository `AGENTS.md` applies. Use the approved investigative direct route. The writer must keep this plan unchanged, implement through test-driven development, commit coherent checkpoints, and return exact test and commit receipts. The final pull request must target `Yeachan-Heo/gajae-code:dev`, reference closed PR #3825 and issue #3956, and remain unmerged.
diff --git a/.plans/2026-08-09-gjc-child-provider-identity.md b/.plans/2026-08-09-gjc-child-provider-identity.md
deleted file mode 100644
index be4d75e245a..00000000000
--- a/.plans/2026-08-09-gjc-child-provider-identity.md
+++ /dev/null
@@ -1,95 +0,0 @@
-# GJC Parallel Child Provider Identity Fix
-
-**Goal:** Prevent parallel GJC task children from contending for one Pooler websocket owner by assigning each child a unique, stable provider-facing continuity identity through the real managed persistence path.
-**Saved:** `/private/tmp/gjc-child-provider-identity-20260809/.plans/2026-08-09-gjc-child-provider-identity.md`
-**Status:** Active approved implementation plan; immutable during execution.
-
-## Context inspected
-- Production Pooler telemetry recorded 415 `owner_busy` bridge fallbacks in one hour while GJC websocket share fell to about 48%.
-- Two hot parent sessions produced 15 parallel child JSONL artifacts whose headers reused their respective parent UUIDs.
-- `packages/coding-agent/src/sdk/session.ts:createAgentSession` defaults provider identity to the logical `SessionManager` ID.
-- `packages/coding-agent/src/task/executor.ts:ManagedTaskPersistence.openSession` uses `SessionManager.openNestedManaged` for production child persistence.
-- Commit `b621997ee` removed fork-seed identity inheritance but its regression used a standalone explicit child file, not the production nested managed path.
-- Pooler deliberately rejects a busy websocket owner and falls back to HTTP; it exposes no bridge queue or multiplexing setting.
-
-## Settled direction
-- Give each parallel task child a distinct provider-facing identity at the task-child ownership seam.
-- Keep that identity stable for the child’s serial turns and detached resume.
-- Preserve existing logical session headers and parent artifact hierarchy; do not rewrite persistence semantics merely to obtain provider uniqueness.
-- Prefer an existing durable child identifier already owned by task persistence/execution. Do not add a global registry, queue, Pooler option, or dependency.
-
-## Non-goals
-- No Pooler code, deployment, restart, or configuration change.
-- No GJC concurrency reduction or task serialization.
-- No model/provider configuration changes.
-- No public API or persisted session schema change.
-- No deletion or modification of `~/.gjc/agent/models.yml`.
-- The separately approved removal of stale `gjc.ralplan.autoHandoff` from `~/.gjc/agent/config.yml` is an operator cleanup outside this repository diff.
-
-## Global verification
-- Capture RED through a behavioral test using `ManagedTaskPersistence`/nested managed artifacts that reproduces shared provider identity for parallel children.
-- Capture GREEN proving distinct child identities and stable detached resume identity.
-- Run targeted task identity/fork-context/replay suites and `bun --cwd=packages/coding-agent run check`.
-- Run repository lint/format checks applicable to changed files and canonical broader checks required by project law.
-- Perform adversarial review of the shared-transport concurrency boundary.
-- Run one bounded live parallel GJC smoke and query Pooler receipts for distinct continuity IDs, websocket transport, zero `owner_busy`, and cache behavior. No process kill or deployment is authorized.
-- Open an unmerged PR against `dev` with exact receipts.
-
-## Verification of verification
-- Trigger: this regression class escaped once because a standalone fixture did not model the production managed persistence seam.
-- Silent failure mode: coverage gap/drifted oracle—the test can stay green while nested managed children still share identity.
-- Cheapest effective protection: the regression itself must construct children through `ManagedTaskPersistence` and assert outbound/provider identity behavior for concurrent children plus resume. During TDD, revert or disable the production fix once and confirm this exact test goes RED. No extra framework or recursive meta-layer.
-
-## Risks and escalation triggers
-- Stop if the minimal fix requires changing persisted session compatibility, the public API, credential/auth behavior, dependencies, or Pooler runtime behavior.
-- Stop if distinct identities cannot remain stable across detached resume without a new persisted schema.
-- Stop before destructive commands, process termination, deployment, or config mutation beyond the separately named stale setting removal.
-
-## Routing
-- `reasoning_mode: adversarial`
-- `execution_topology: direct`
-- `gjc_profile: adversarial`
-- `gjc_workflow: direct`
-- `capability_evidence:` Shared-transport concurrency and persistence identity are subtle cross-system invariants; the same regression escaped an earlier fix and caused material cache waste.
-- `topology_evidence:` One tightly coupled outcome owns the production seam, implementation, regression, resume proof, and live smoke; splitting writers would increase coordination risk without independent deliverables.
-- `escalation_triggers:` persisted schema compatibility, public API, credentials/auth, dependencies, Pooler runtime/deploy, destructive operations, or inability to preserve resume identity.
-
-## Plan
-
-### Task 1: Reproduce the production persistence collision
-**Objective:** Add a behavioral regression that creates parallel task children through the nested managed persistence path and fails because provider identities collide.
-**Files:**
-- Modify: `packages/coding-agent/test/task-cache-key.test.ts` or the nearest production-faithful task persistence test selected from current source.
-- Inspect/possibly modify test helpers only in the same test surface.
-**Steps:**
-1. Build parent artifacts and child persistence using `createManagedTaskPersistence`/`ManagedTaskPersistence.openSession` as production does.
-2. Create at least two parallel-equivalent children from the same parent context.
-3. Assert inherited conversation content as applicable, distinct provider identities, and no parent identity reuse.
-4. Run the focused test and preserve the expected RED caused by identity collision.
-
-### Task 2: Implement the smallest stable child provider identity
-**Objective:** Derive provider identity from a child-owned durable identifier while preserving logical headers and artifact hierarchy.
-**Files:**
-- Modify only the exact task execution/session creation files proven by the failing test, expected among `packages/coding-agent/src/task/executor.ts`, `packages/coding-agent/src/task/index.ts`, and `packages/coding-agent/src/sdk/session.ts`.
-- Modify: `packages/coding-agent/CHANGELOG.md` under Unreleased.
-**Steps:**
-1. Trace initial child creation and detached resume through the same persistence owner.
-2. Pass or derive one stable child provider identity at that seam without changing parent logical persistence.
-3. Keep explicit `providerSessionId` precedence intact.
-4. Run the focused regression and existing provider-identity suites to GREEN.
-5. Prove the new regression goes RED against the pre-fix behavior and GREEN with the fix.
-
-### Task 3: Verify concurrency, resume, and repository integrity
-**Objective:** Close all acceptance evidence before publication.
-**Files:**
-- Test files only if an uncovered approved invariant needs direct coverage; no unrelated refactors.
-**Steps:**
-1. Run targeted task cache-key, fork-context, and Responses replay tests.
-2. Run package check and applicable lint/format/canonical gates.
-3. Perform adversarial review focused on uniqueness, stability, explicit override precedence, credential stickiness, cancellation, and nested managed persistence.
-4. Repair only verified in-scope findings and rerun checks.
-5. Run the bounded live parallel smoke; verify distinct Pooler continuity IDs, websocket attempts, zero `owner_busy` for the smoke sessions, and report cache receipts without inventing a threshold.
-6. Commit atomically, push, and open an unmerged PR against `dev`.
-
-## Execution handoff
-Use one canonical `gjc_run` with `profile=adversarial`, `workflow=direct`, and this isolated worktree. The plan is immutable. Any escalation trigger stops the writer and returns evidence rather than changing scope.
diff --git a/.plans/2026-08-09-resume-session-memory-oracle.md b/.plans/2026-08-09-resume-session-memory-oracle.md
deleted file mode 100644
index d4f688994e0..00000000000
--- a/.plans/2026-08-09-resume-session-memory-oracle.md
+++ /dev/null
@@ -1,86 +0,0 @@
-# Resume Session Memory Test Oracle Repair
-
-**Goal:** Update two stale resume assertions so they verify the current `SessionManager.open` contract, including session memory mode.
-**Saved:** `/private/tmp/gjc-resume-session-memory-oracle-20260809/.plans/2026-08-09-resume-session-memory-oracle.md`
-**Status:** Active; approved for implementation.
-
-## Context inspected
-
-- Current base is `origin/dev@3793db0f7a27dd8f80889980feb50498dda63691`.
-- Dev CI run `31302619126` fails only two assertions in `packages/coding-agent/test/resume-confirm-continue.test.ts`.
-- Upstream commit `95c00d09e7` added `sessionMemoryMode` as the fifth argument to direct-path `SessionManager.open` calls.
-- The two assertions still expect the prior four-argument call.
-- `packages/coding-agent/src/main.ts` already passes the current setting value. No production repair is required.
-
-## Non-goals
-
-- Do not change production code.
-- Do not change session behavior or public contracts.
-- Do not change PR #4080 feature code.
-- Do not add dependencies, native builds, unrelated tests, workflow files, or generated files.
-- Do not edit this approved plan during implementation.
-
-## Recommendation
-
-Set the test settings fixture to return explicit values by key. Assert that each direct-resume call passes the selected session memory mode as its fifth argument. Preserve all existing destination and migration assertions.
-
-## Global verification
-
-- Run the focused test file and require all tests to pass.
-- Run the coding-agent package check and require Biome and TypeScript to pass.
-- Run `git diff --check`.
-- Review the final diff for test-only scope.
-- Push the branch and open an unmerged PR against `dev`.
-- Monitor current-head CI and review threads.
-
-## Risks and approval gates
-
-- Stop if production code must change.
-- Stop if current `dev` moves incompatibly before push.
-- Stop if the focused test remains red after the one minimal repair.
-- Stop if review requests work outside the stale test oracle.
-- Do not merge any PR.
-
-## Routing
-
-- `reasoning_mode`: `procedural`
-- `execution_topology`: `direct`
-- `gjc_profile`: `procedural`
-- `gjc_workflow`: `direct`
-- `capability_evidence`: The cause and intended contract are proven by current source, Git blame, and exact CI failures. One test file requires a mechanical expectation update.
-- `topology_evidence`: One writer can change and prove one coherent test-only outcome in one isolated worktree.
-- `escalation_triggers`: Production changes, unresolved behavior choices, incompatible upstream movement, or focused-test failure after the minimal update.
-
-## Plan
-
-### Task 1: Update the stale resume assertions
-
-**Objective:** Make the existing resume tests validate session memory mode propagation without weakening destination or migration checks.
-
-**Files:**
-- Modify: `packages/coding-agent/test/resume-confirm-continue.test.ts`
-
-**Steps:**
-1. Keep the current failing CI output as the RED receipt.
-2. Make the settings fixture return explicit migration and session-memory values by key.
-3. Add the expected fifth argument to all three affected call assertions.
-4. Run the focused test file and require success.
-5. Run package checks and `git diff --check`.
-6. Commit one atomic test repair.
-
-### Task 2: Review and publish the isolated repair
-
-**Objective:** Prove the branch contains only the approved test correction and publish an unmerged PR.
-
-**Steps:**
-1. Review the final diff against this plan.
-2. Run an adversarial review of the current branch.
-3. Repair only verified findings already decided by this plan.
-4. Push to `grantjayy/gajae-code`.
-5. Open a PR against upstream `dev` with exact verification receipts.
-6. Monitor current-head CI and review threads.
-7. If the upstream repair lands, refresh #4080 and monitor its current-head checks.
-
-## Execution handoff
-
-Use canonical Hermes `gjc_run` with `profile=procedural`, `workflow=direct`, and the assigned worktree. GJC must not edit this plan. Hermes independently verifies Git state, tests, checks, PR metadata, CI, and review threads. The PR remains unmerged.
diff --git a/AGENTS.md b/AGENTS.md
index cd8befa9583..a3448b5a5f7 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -54,10 +54,12 @@ A second checkout created with `git worktree add` starts with no `node_modules/`
built native addon, so every `bun test` fails with a bare module-resolution error until
dependencies are installed. Run `bun run setup:worktree` there (equivalent to
`bun install && bun run build:native`). Do **not** run `bun run install:dev` in a
-worktree: it repoints the global `gjc` on `PATH`, rewrites `core.hooksPath`, and
-overwrites user-level defaults for your primary checkout. `bun run dev:doctor -- --worktree`
-reports whether the current checkout can resolve workspace packages and load the native
-addon, and names the fix when it cannot.
+worktree: it repoints the global `gjc` on `PATH` and overwrites user-level defaults
+for your primary checkout. `bun run dev:doctor -- --worktree` reports whether the
+current checkout can resolve workspace packages and load the native addon, and names
+the fix when it cannot. If an older `install:dev` set `core.hooksPath` to the now-removed
+`.githooks` directory, run `git config --local --unset core.hooksPath` in the primary
+checkout to restore Git's default hook lookup; do not unset a custom hooks path.
Removing build output (never touches sources, `node_modules/`, `.gjc/` state, or `artifacts/` test working space):
@@ -71,6 +73,8 @@ bun scripts/clean.ts --dry-run # list targets without deleting
`artifacts/` policy (issue #4420): The root `artifacts/` directory is untracked test working space. Red-team and QA tests write evidence there and read it back within the same run (`g011`/`g014`/`g015`/`vb001-gen5`). The directory is gitignored at root and is not committed. Deterministic test fixtures that must be tracked belong in `packages/*/test/fixtures/`. The `clean` command deliberately does not touch `artifacts/`.
+The generated `g003`, `g011`, `g014`, and `g015` QA-report JSON files are also ignored under `packages/tui/artifacts/` because package-local test runs write there. This exception does not apply to the intentionally tracked `issue-3798` validation/capture evidence pair. `scripts/artifacts-tracked-policy.test.ts` checks both output locations from the repository root, independent of the test runner's working directory.
+
Verification (never run `tsc`/`npx tsc` directly at repo root; use these):
```sh
@@ -118,6 +122,7 @@ GJC exposes exactly four default workflow skills (`deep-interview`, `ralplan`, `
- Keep source-bundled skills/agents in sync with tests/gates; do not rely on committed `.gjc` copies.
- Planning workflows (`deep-interview`, `ralplan`) never execute implementation without explicit user approval; artifacts stay `pending approval` until then. `autoresearch` is a research workflow: it produces findings and a verdict, never an implementation.
- Subagent await timeouts are observation windows, not failure signals; inspect before cancelling.
+- GitHub comment `/re-review` on PRs re-requests reviews from those with pending `CHANGES_REQUESTED` or `COMMENTED` feedback.
## Code quality
@@ -174,7 +179,7 @@ Avoid placeholder tests, tautologies, broad `not.toThrow()` assertions, duplicat
- Always commit incrementally; atomic commits are preferred. One logical change per commit — never batch unrelated work.
- For targeted branch / PR-like work, always open a PR targeting `dev`.
-- Enable the local exact-head PR preflight with `bun run dev:hooks` (also included in `install:dev`). The Git hook passes the destination branch, pushed object SHA, and actual push URL to `scripts/verify-pr-verdict.ts --push-preflight --push-url `. It rejects stale digests, unrebased bases, malformed verdicts, missing risk classifications, unresolved repository/fork authority, and a `merge-self-approved` verdict that its own signed risk-record comment does not back at the exact head. Valid blocking verdicts (`needs-human`, `merge-blocked`) are allowed locally; only the server merge gate authorizes merge. Bypass with `GJC_SKIP_PR_PREFLIGHT=1` or `--no-verify`. The opt-in `gh pr create` preflight (`--preflight-command`) is evaluated before the PR exists, so it can never see a self-review record and rejects a `merge-self-approved` body; create the PR, post the record, then validate at push time.
+- Agent reviews (`architect`/`critic`) are advisory comments on the PR. Merges to `dev` require one approving GitHub review from a write-access maintainer on the exact current head (enforced by native ruleset).
- Commit messages use the lore format: conventional-commit subject, a short why-focused body, then structured trailers. Include only the trailers that apply.
```
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 349311bfbd0..29a2ddc7811 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -52,7 +52,7 @@ The file holds one or more `### ` blocks (`### Added`, `### Changed`, `
- The thing that was broken now works (#1234).
```
-This pattern exists because of what it replaces. Contributors used to append their entry under `## [Unreleased]` in `packages//CHANGELOG.md`, which put every pull request on the same lines of the same file. Git cannot auto-merge two insertions at the same position, so each merge dirtied every other open PR — and the exact-head approval contract re-binds approval to the head (`scripts/verify-pr-verdict.ts` requires `review.commit?.oid === headSha`), so clearing that dirt with a rebase threw the approval away and cost a fresh reviewer round-trip. Issue #5491 measured a six-line change paying three rebases and two approval rounds, with `CHANGELOG.md` the only conflicted path every time. Distinct fragment file names cannot conflict, so the shared insertion point stops existing.
+This pattern exists because of what it replaces. Contributors used to append their entry under `## [Unreleased]` in `packages//CHANGELOG.md`, which put every pull request on the same lines of the same file. Git cannot auto-merge two insertions at the same position, so each merge dirtied every other open PR. Distinct fragment file names cannot conflict, so the shared insertion point stops existing.
`scripts/release.ts` folds every pending fragment into its package's `## [Unreleased]` section and deletes the consumed files as part of the release commit. Run `bun scripts/changelog-fragments.ts check` to validate fragments locally, and Dev CI rejects a pull request that edits a guarded `## [Unreleased]` section directly or deletes a fragment.
@@ -83,63 +83,16 @@ The `CI` workflow publishes a scheduled nightly prerelease from `main` at 04:23
- Relevant focused tests or checks are listed in the PR description.
- User-facing changes include a changelog fragment under `packages//changelog.d/` when appropriate.
-## Exact-head PR verdict gate
+## Approval requirement
-Every pull request to `dev` must keep exactly one `gajae.pr-review-verdict.v1` line from the pull request template. The `PR contract / Validate exact-head PR contract` status is produced by a narrowly scoped `pull_request_target` workflow loaded from the trusted default branch. It has read-only permissions, receives no secrets, consumes no caches or artifacts, and executes only the base-owned validator while inspecting the event's immutable base and exact head. The validator recomputes the binary diff digest, requires the head to contain the base, runs the fast GJC state-writer scan against the PR-head bytes, and rejects `merge-approved` verdicts whose reviewer-id matches the PR author — no exception. The repository owner's solo path is the separately named `merge-self-approved` verdict (below). `needs-human` and `merge-blocked` are valid review states but intentionally keep the status red until an independent reviewer records `merge-approved` for the current head.
+Merges to `dev` require one approving GitHub review from a write-access maintainer on the exact current head. This is enforced by the repository's native GitHub ruleset.
-### Risk-classified review policy (issue #4703, post-review semantics)
+**Release backmerge note.** `bun run release` automates the propagation of the just-published release commit from `main` into `dev` to keep them in sync. The backmerge runs after the release is tagged and published to main, and creates a pull request to bring the new release commit into dev. This PR must be reviewed and approved by a maintainer before it can be merged, following the same exact-head approval requirement as all other dev merges. The release publication is not blocked by the backmerge PR — if the PR encounters issues during creation, the release is already published and the step reports it, leaving manual synchronization for later correction.
-The review requirement is classified by the change's real risk; the PR body must check **exactly one** risk class (zero or multiple checked boxes fail closed — a missing classification never waives the stricter tiers).
-
-- **Low-risk fix / ordinary maintenance** — the repository owner may merge through the explicitly named `merge-self-approved` verdict with a `gajae.pr-self-review.v1` risk-record comment bound to the exact base/head/digest. This is a deliberate solo force path: the verdict name itself, the bootstrap log line (`SELF-AUTHORIZED: merge-self-approved, no independent human review`), and the check output all record that no independent human reviewed the change. It exists to keep routine maintainer fixes unblocked, and it is named honestly instead of being dressed up as review.
-- **Fix with material regression risk** — requires one assigned independent domain reviewer selected from recent merged PR/commit ownership and demonstrated expertise in the touched files/contracts, whose authenticated exact-head `APPROVED` GitHub review the validator verifies (`extra:independent:`; the token alone never satisfies the gate). The `extra:gpt-heavy` token was removed: an author-supplied claim with no authenticated run artifact behind it cannot substitute for review evidence.
-- **Large refactors, features, and materially high-risk changes** (security, auth, install, remove, public API, destructive lifecycle, architecture) — exactly one assigned independent domain reviewer, selected the same way and verified the same way (authenticated exact-head `APPROVED` review with write+ permission).
-
-For every risk class, `merge-approved` requires an authenticated exact-head `APPROVED` review from an identity distinct from the PR author — the original universal invariant is intact and no comment of the author's own can reach it. External contributor PRs unchanged: ordinary authenticated `APPROVED` review from a repository maintainer.
-
-### Risk-record comment format
-
-For the owner's low-risk solo path (and to carry the risk classification for higher tiers), post the record as a **PR comment** (never in the PR body — body text cannot authorize anything):
-
-```text
-gajae.pr-self-review.v1 verdict:merge-self-approved base:<40-hex event base> head:<40-hex exact head> sha256:<64-hex base...head diff digest> reviewer-id: risk: extra:> evidence:
-self-review-signature: sha256:
-Signed-off-by: gaebal-gajae (clawdbot) 🦞
-```
-
-The `self-review-signature` is an **integrity digest, not authentication**: `sha256("gajae.pr-self-review.v1.signature-domain" + canonical record lines)`, generated with `bun scripts/verify-pr-verdict.ts --self-review-sign `. It proves the comment text matches what was posted for the bound head — tamper evidence, exactly like the diff digest. Authorization never rests on it: the same identity authors the PR and posts the record, so it could never prove independent review, and the contract does not pretend otherwise. The validator accepts the record only when all of the following hold; otherwise it fails closed:
-
-- the record is fetched through the trusted workflow token from the GitHub issue-comments API (comment bytes are data; head-controlled code is never executed); only records from the PR author are eligible, every page is scanned and the **newest** record wins, and any API failure fails closed;
-- the record author is exactly the **repository owner account** (`author_association: OWNER`, the delegated maintainer identity gaebal-gajae operates) and matches `reviewer-id`, which must equal the PR author; members and collaborators cannot use the solo path;
-- base, head, and digest match the immutable event base, the exact PR head, and the recomputed `base...head` diff digest (stale values never authorize a newer head);
-- the integrity digest matches the record, so any post-hoc edit of verdict, head, digest, risk, extra, or evidence is detected;
-- the risk classification matches the PR body's exactly-one checked risk classification;
-- `merge-self-approved` additionally requires `risk:low-risk` and `extra:none`; higher risk classes must use independent review (`merge-approved`);
-- `extra:independent:` (used to carry the risk classification for higher tiers) is only honored when that login is a distinct maintainer (not the PR author) with admin/maintain/write permission **and** an authenticated exact-head `APPROVED` GitHub review.
-
-Comment events (`issue_comment: [created, edited, deleted]`) re-run the trusted-base validator, so posting, editing, or deleting the record re-evaluates the contract server-side; deleting the record revokes the authorization on the exact head.
-
-Comment-triggered validation publishes to the **same required authority**: `issue_comment` runs associate with the default-branch SHA, so the workflow creates/updates a check run named `Validate exact-head PR contract` on the resolved exact PR head. Authorization and revocation therefore update the one head-bound required context that `pull_request_target` runs also report; a parallel status context could never revoke a green required check. Branch-protection rollout contract: once this lands, enable branch protection on `dev` requiring `PR contract / Validate exact-head PR contract` (strict); until protection is enabled, the Dev CI `PR contract bootstrap` job plus the verdict discipline remain the enforced path. Because `issue_comment` workflows only trigger from the repository default branch (`main`), the comment path activates when this workflow definition reaches `main` — until then the Dev CI `PR contract bootstrap` job (a `pull_request` event, natively head-bound) remains the authoritative gate, which is how the introducing PR itself is validated.
-
-The first PR that introduces this workflow uses a trusted two-phase bootstrap. Phase 1 landed `Dev CI / PR contract bootstrap` directly on `dev`, so its inline validation exists in the immutable event base before the implementation PR is evaluated. Phase 2 enables the isolated `PR contract` consumer in this implementation PR. Review events run only that cheap, read-only contract workflow; they never launch or cancel the affected Dev CI pipeline. The validator still executes exclusively from the immutable event-base checkout and treats PR-head bytes as data. The same bootstrap ordering applies to the solo path: a PR whose immutable base predates the `merge-self-approved`-aware validator cannot use it on that base; the introducing PR relies on the Dev CI bootstrap job and merges before the comment path becomes active for later PRs.
-
-After the final commit and rebase, compute the digest with:
-
-```sh
-git fetch origin dev
-git merge-base --is-ancestor origin/dev HEAD
-git diff --binary --full-index --no-ext-diff origin/dev...HEAD | sha256sum
-bun scripts/verify-gjc-state-writers.ts --fail
-```
-
-The verdict line must use the resulting lowercase digest and name the GitHub reviewer whose effective `APPROVED` review targets the exact PR head — never the PR author. The owner's low-risk solo alternative is the explicitly named `merge-self-approved`:
-
-```text
-gajae.pr-review-verdict.v1 merge-approved sha256:<64-hex-digest> reviewer: reviewer-id: evidence:
-```
+Metadata-only PR edits must not manufacture successful required checks by skipping the aggregate jobs. Dev CI validates those events through real code-validation jobs; separate metadata concurrency preserves an already-running code-validation run. Pending, failed, cancelled, or missing dependency evidence cannot satisfy the aggregate. State-gate relevance is decided before native provisioning, with runtime prompt Markdown in dependency workspaces treated as executable input rather than documentation.
-GJC users can opt into fast feedback before `gh pr create` by copying `docs/examples/gjc-hooks/pre/bash.ts` to this checkout's `.gjc/hooks/pre/bash.ts`. Keep the hook project-local: installing it under `~/.gjc/agent` would incorrectly impose this repository's policy on unrelated repositories. The local hook is advisory and bypassable; the server-side status check is authoritative and covers humans and other runtimes.
+## Re-requesting reviews
-`bun run dev:hooks` enables the repository's Git pre-push hook (also included in `install:dev`). It validates the commit being pushed and the remote destination branch, including `HEAD:` refspecs. Repository lookup uses Git's actual push destination, not a remote's potentially different fetch URL, and refuses unresolved or malformed fork metadata instead of assuming that no upstream PR exists. An explicit no-open-PR result is allowed; API failure is not that result. Local preflight accepts valid `needs-human`/`merge-blocked` records so review work can be pushed; those records remain blocked by the server merge gate. `GJC_SKIP_PR_PREFLIGHT=1` or `--no-verify` bypasses only this local feedback, never server approval.
+If you want reviewers to re-examine your PR after addressing feedback, comment with `/re-review`. The workflow will request reviews from all human reviewers whose latest review is `CHANGES_REQUESTED` or `COMMENTED` but are not currently requested. Authorized users are the PR author or anyone with `admin`, `maintain`, or `write` permission to the repository.
-Metadata-only PR edits must not manufacture successful required checks by skipping the aggregate jobs. Dev CI validates those events through real code-validation jobs; separate metadata concurrency preserves an already-running code-validation run. Pending, failed, cancelled, or missing dependency evidence cannot satisfy the aggregate. State-gate relevance is decided before native provisioning, with runtime prompt Markdown in dependency workspaces treated as executable input rather than documentation.
+**Note:** `issue_comment` workflows only activate once the triggering file (`.github/workflows/re-review.yml`) is present on the repository's default branch, `main`. Merging this PR to `dev` alone does not activate `/re-review`; the workflow will not trigger on comments until the file reaches `main`.
diff --git a/Cargo.lock b/Cargo.lock
index 47f6ca16897..49ee56a9e08 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -8,6 +8,17 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+[[package]]
+name = "aes"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35f0f96ce78e38c3dc6d8948aa8163d06385be74000f3c7a95bf1eef35d3ea32"
+dependencies = [
+ "cipher",
+ "cpubits",
+ "cpufeatures 0.3.0",
+]
+
[[package]]
name = "ahash"
version = "0.8.12"
@@ -31,6 +42,21 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "alloc-no-stdlib"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
+
+[[package]]
+name = "alloc-stdlib"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195"
+dependencies = [
+ "alloc-no-stdlib",
+]
+
[[package]]
name = "allocator-api2"
version = "0.2.21"
@@ -132,6 +158,12 @@ dependencies = [
"triomphe",
]
+[[package]]
+name = "arrayref"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
+
[[package]]
name = "arrayvec"
version = "0.7.8"
@@ -152,13 +184,98 @@ dependencies = [
[[package]]
name = "astral-tl"
-version = "0.7.11"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d90933ffb0f97e2fc2e0de21da9d3f20597b804012d199843a6fe7c2810d28f3"
+checksum = "04d4bb5df351abb6758a04240b0193794ddaf34a7c0b9452fab3c25dcce39e88"
dependencies = [
"memchr",
]
+[[package]]
+name = "async-broadcast"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532"
+dependencies = [
+ "event-listener",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-channel"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
+dependencies = [
+ "concurrent-queue",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"
+dependencies = [
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "async-io"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-io",
+ "futures-lite",
+ "parking",
+ "polling",
+ "rustix",
+ "slab",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
+dependencies = [
+ "event-listener",
+ "event-listener-strategy",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-process"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
+dependencies = [
+ "async-channel",
+ "async-io",
+ "async-lock",
+ "async-signal",
+ "async-task",
+ "blocking",
+ "cfg-if",
+ "event-listener",
+ "futures-lite",
+ "rustix",
+]
+
[[package]]
name = "async-recursion"
version = "1.1.1"
@@ -167,9 +284,33 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
+]
+
+[[package]]
+name = "async-signal"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485"
+dependencies = [
+ "async-io",
+ "async-lock",
+ "atomic-waker",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "rustix",
+ "signal-hook-registry",
+ "slab",
+ "windows-sys 0.61.2",
]
+[[package]]
+name = "async-task"
+version = "4.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
+
[[package]]
name = "async-trait"
version = "0.1.89"
@@ -178,9 +319,15 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
[[package]]
name = "autocfg"
version = "1.5.1"
@@ -189,9 +336,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "base64"
-version = "0.22.1"
+version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
[[package]]
name = "bigdecimal"
@@ -263,6 +410,37 @@ dependencies = [
"generic-array",
]
+[[package]]
+name = "block-buffer"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "710f1dd022ef4e93f8a438b4ba958de7f64308434fa6a87104481645cc30068b"
+dependencies = [
+ "hybrid-array",
+]
+
+[[package]]
+name = "blocking"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa"
+dependencies = [
+ "async-channel",
+ "async-task",
+ "futures-io",
+ "futures-lite",
+ "piper",
+]
+
[[package]]
name = "bon"
version = "3.9.3"
@@ -285,7 +463,17 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
- "syn",
+ "syn 2.0.118",
+]
+
+[[package]]
+name = "brotli-decompressor"
+version = "5.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
]
[[package]]
@@ -293,7 +481,7 @@ name = "brush-builtins"
version = "0.2.0"
dependencies = [
"brush-core",
- "brush-parser 0.4.0",
+ "brush-parser",
"cfg-if",
"chrono",
"clap",
@@ -318,8 +506,8 @@ dependencies = [
"async-recursion",
"async-trait",
"bon",
- "brush-parser 0.4.0",
- "cached 0.59.0",
+ "brush-parser",
+ "cached",
"cfg-if",
"check_elevation",
"chrono",
@@ -350,21 +538,6 @@ dependencies = [
"windows-sys 0.59.0",
]
-[[package]]
-name = "brush-parser"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7367124d4f38fdcd65f4b815bda7caeb3de377b9cd95ffa1b23627989c93718"
-dependencies = [
- "bon",
- "cached 0.56.0",
- "indenter",
- "peg",
- "thiserror 2.0.18",
- "tracing",
- "utf8-chars",
-]
-
[[package]]
name = "brush-parser"
version = "0.4.0"
@@ -372,7 +545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f64b19efc02f0dd6cac1d462e20a1098f425f09cb13624efbb1b7d63f061735"
dependencies = [
"bon",
- "cached 0.59.0",
+ "cached",
"getrandom 0.4.3",
"indenter",
"insta",
@@ -423,7 +596,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -444,21 +617,6 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
-[[package]]
-name = "cached"
-version = "0.56.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "801927ee168e17809ab8901d9f01f700cd7d8d6a6527997fee44e4b0327a253c"
-dependencies = [
- "ahash",
- "cached_proc_macro 0.25.0",
- "cached_proc_macro_types",
- "hashbrown 0.15.5",
- "once_cell",
- "thiserror 2.0.18",
- "web-time",
-]
-
[[package]]
name = "cached"
version = "0.59.0"
@@ -466,7 +624,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53b6f5d101f0f6322c8646a45b7c581a673e476329040d97565815c2461dd0c4"
dependencies = [
"ahash",
- "cached_proc_macro 0.27.0",
+ "cached_proc_macro",
"cached_proc_macro_types",
"hashbrown 0.16.1",
"once_cell",
@@ -475,18 +633,6 @@ dependencies = [
"web-time",
]
-[[package]]
-name = "cached_proc_macro"
-version = "0.25.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9225bdcf4e4a9a4c08bf16607908eb2fbf746828d5e0b5e019726dbf6571f201"
-dependencies = [
- "darling 0.20.11",
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "cached_proc_macro"
version = "0.27.0"
@@ -496,7 +642,7 @@ dependencies = [
"darling 0.20.11",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -505,6 +651,15 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
+[[package]]
+name = "cbc"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896"
+dependencies = [
+ "cipher",
+]
+
[[package]]
name = "cc"
version = "1.2.66"
@@ -512,6 +667,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996"
dependencies = [
"find-msvc-tools",
+ "jobserver",
+ "libc",
"shlex",
]
@@ -566,6 +723,16 @@ dependencies = [
"windows-link",
]
+[[package]]
+name = "cipher"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
+dependencies = [
+ "crypto-common 0.2.2",
+ "inout",
+]
+
[[package]]
name = "clap"
version = "4.6.1"
@@ -598,7 +765,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -634,7 +801,7 @@ dependencies = [
"nom 7.1.3",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -659,6 +826,15 @@ dependencies = [
"thiserror 2.0.18",
]
+[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
+
[[package]]
name = "console"
version = "0.16.4"
@@ -670,6 +846,12 @@ dependencies = [
"windows-sys 0.61.2",
]
+[[package]]
+name = "const-oid"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
+
[[package]]
name = "const-random"
version = "0.1.18"
@@ -705,6 +887,12 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+[[package]]
+name = "cpubits"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae"
+
[[package]]
name = "cpufeatures"
version = "0.2.17"
@@ -773,6 +961,15 @@ dependencies = [
"typenum",
]
+[[package]]
+name = "crypto-common"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453"
+dependencies = [
+ "hybrid-array",
+]
+
[[package]]
name = "ctor"
version = "1.0.7"
@@ -810,7 +1007,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -823,7 +1020,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -834,7 +1031,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core 0.20.11",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -845,7 +1042,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
dependencies = [
"darling_core 0.23.0",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -868,17 +1065,65 @@ version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
+[[package]]
+name = "data-url"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
+
+[[package]]
+name = "defmt"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
+dependencies = [
+ "bitflags 1.3.2",
+ "defmt-macros",
+]
+
+[[package]]
+name = "defmt-macros"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
+dependencies = [
+ "defmt-parser",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.118",
+]
+
+[[package]]
+name = "defmt-parser"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
+dependencies = [
+ "thiserror 2.0.18",
+]
+
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
- "block-buffer",
- "crypto-common",
+ "block-buffer 0.10.4",
+ "crypto-common 0.1.7",
"subtle",
]
+[[package]]
+name = "digest"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
+dependencies = [
+ "block-buffer 0.12.1",
+ "const-oid",
+ "crypto-common 0.2.2",
+]
+
[[package]]
name = "dispatch2"
version = "0.3.1"
@@ -897,7 +1142,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -906,6 +1151,15 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
+[[package]]
+name = "ecb"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26f2a8b3e564eba0877223dc343703ad0385794e882e6d13f3a4dd5c6b1f41ac"
+dependencies = [
+ "cipher",
+]
+
[[package]]
name = "either"
version = "1.16.0"
@@ -936,6 +1190,56 @@ dependencies = [
"encoding_rs",
]
+[[package]]
+name = "endi"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099"
+
+[[package]]
+name = "enumflags2"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef"
+dependencies = [
+ "enumflags2_derive",
+ "serde",
+]
+
+[[package]]
+name = "enumflags2_derive"
+version = "0.7.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.118",
+]
+
+[[package]]
+name = "env_filter"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217"
+dependencies = [
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.11.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "env_filter",
+ "jiff",
+ "log",
+]
+
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -958,6 +1262,35 @@ version = "3.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59"
+[[package]]
+name = "euclid"
+version = "0.22.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "event-listener"
+version = "5.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"
+dependencies = [
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
+dependencies = [
+ "event-listener",
+ "pin-project-lite",
+]
+
[[package]]
name = "fancy-regex"
version = "0.16.2"
@@ -1020,9 +1353,9 @@ dependencies = [
[[package]]
name = "find-msvc-tools"
-version = "0.1.9"
+version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
[[package]]
name = "fixedbitset"
@@ -1040,6 +1373,12 @@ dependencies = [
"miniz_oxide",
]
+[[package]]
+name = "float-cmp"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
+
[[package]]
name = "fluent"
version = "0.17.0"
@@ -1103,6 +1442,37 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+[[package]]
+name = "font-types"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8eb065f3251655b3c90e22e5e363f310fc5332fb3402e37bbc94752283248f6"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "fontconfig-parser"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646"
+dependencies = [
+ "roxmltree 0.20.0",
+]
+
+[[package]]
+name = "fontdb"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2660c5e9157bf76d2db1294e4a9feba604ef610819a3b591088d0d8392a3290f"
+dependencies = [
+ "fontconfig-parser",
+ "log",
+ "memmap2",
+ "slotmap",
+ "tinyvec",
+]
+
[[package]]
name = "funty"
version = "2.0.0"
@@ -1157,6 +1527,19 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
+[[package]]
+name = "futures-lite"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "parking",
+ "pin-project-lite",
+]
+
[[package]]
name = "futures-macro"
version = "0.3.32"
@@ -1165,7 +1548,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -1261,12 +1644,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159"
dependencies = [
"color_quant",
- "weezl",
+ "weezl 0.1.12",
]
[[package]]
name = "gjc-sdk"
-version = "0.17.2"
+version = "0.19.0"
dependencies = [
"futures-util",
"hmac",
@@ -1274,7 +1657,7 @@ dependencies = [
"parking_lot",
"serde",
"serde_json",
- "sha2",
+ "sha2 0.10.9",
"tokio",
"tokio-tungstenite",
"tokio-util",
@@ -1308,6 +1691,17 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "grep-pcre2"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b752bb6f57dd3716e55b63a33ca05b5825c3d66cd427468874a5564af6ca365c"
+dependencies = [
+ "grep-matcher",
+ "log",
+ "pcre2",
+]
+
[[package]]
name = "grep-regex"
version = "0.1.14"
@@ -1347,6 +1741,27 @@ dependencies = [
"zerocopy",
]
+[[package]]
+name = "harfrust"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c03d949a14aa089bbb282f7dd76a498a7f684428e4257202efc119ec010376f9"
+dependencies = [
+ "bitflags 2.13.0",
+ "bytemuck",
+ "read-fonts",
+ "smallvec",
+]
+
+[[package]]
+name = "hash32"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
+dependencies = [
+ "byteorder",
+]
+
[[package]]
name = "hashbrown"
version = "0.14.5"
@@ -1359,8 +1774,6 @@ version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
- "allocator-api2",
- "equivalent",
"foldhash 0.1.5",
]
@@ -1381,12 +1794,28 @@ version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+[[package]]
+name = "heapless"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4"
+dependencies = [
+ "hash32",
+ "stable_deref_trait",
+]
+
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+[[package]]
+name = "hermit-abi"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284"
+
[[package]]
name = "hex"
version = "0.4.3"
@@ -1399,7 +1828,7 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
dependencies = [
- "digest",
+ "digest 0.10.7",
]
[[package]]
@@ -1415,35 +1844,39 @@ dependencies = [
[[package]]
name = "html-escape"
-version = "0.2.14"
+version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46c1ff2d1cbf39efe5af0900ced8a069b5e61557a17544eb0c4a50239937389e"
+checksum = "c9356095b4b41197bba32173600e1582792cda618f65d12f68e2e77d273413c5"
[[package]]
name = "html-to-markdown-rs"
-version = "2.30.0"
+version = "3.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ea41945a2fd834381642a000ef75b03f0030f3023f3dd3291fc5c372d3dda33"
+checksum = "03602615945d9872da947326d6054d51f891c1b42d611461eff35c0565be03e8"
dependencies = [
"ahash",
"astral-tl",
"base64",
+ "bitflags 2.13.0",
"html-escape",
"html5ever",
- "lru",
+ "memchr",
"once_cell",
+ "phf 0.14.0",
"regex",
"thiserror 2.0.18",
+ "tracing",
]
[[package]]
name = "html5ever"
-version = "0.39.0"
+version = "0.40.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46a1761807faccc9a19e86944bbf40610014066306f96edcdedc2fb714bcb7b8"
+checksum = "456a1a377e608e555d22ddab27ac0114bc7a7b4199078108e34c2aeae6c9b130"
dependencies = [
"log",
"markup5ever",
+ "memchr",
]
[[package]]
@@ -1462,6 +1895,15 @@ version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+[[package]]
+name = "hybrid-array"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17"
+dependencies = [
+ "typenum",
+]
+
[[package]]
name = "iana-time-zone"
version = "0.1.65"
@@ -1554,9 +1996,9 @@ dependencies = [
[[package]]
name = "icy_sixel"
-version = "0.5.0"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85518b9086bf01117761b90e7691c0ef3236fa8adfb1fb44dd248fe5f87215d5"
+checksum = "c917a041c4c1e8438172eb0dc7a4cb0eba2f22975fbf6681c51e78e4f507e996"
dependencies = [
"quantette",
"thiserror 2.0.18",
@@ -1613,6 +2055,31 @@ dependencies = [
"quick-error",
]
+[[package]]
+name = "imagesize"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65b27460c2c92b037f3f94c538ed9a3342f3fdf923606781629ccb35f82d042a"
+
+[[package]]
+name = "include_dir"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd"
+dependencies = [
+ "include_dir_macros",
+]
+
+[[package]]
+name = "include_dir_macros"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
[[package]]
name = "indenter"
version = "0.3.4"
@@ -1651,9 +2118,19 @@ version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c727f80bfa4a6c6e2508d2f05b6f4bfce242030bd88ed15ae5331c5b5d30fba7"
dependencies = [
- "proc-macro2",
- "quote",
- "syn",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.118",
+]
+
+[[package]]
+name = "inout"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
+dependencies = [
+ "block-padding",
+ "hybrid-array",
]
[[package]]
@@ -1711,6 +2188,53 @@ version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+[[package]]
+name = "jiff"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb"
+dependencies = [
+ "defmt",
+ "jiff-core",
+ "jiff-static",
+ "log",
+ "portable-atomic",
+ "portable-atomic-util",
+ "serde_core",
+]
+
+[[package]]
+name = "jiff-core"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c"
+dependencies = [
+ "defmt",
+ "log",
+]
+
+[[package]]
+name = "jiff-static"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212"
+dependencies = [
+ "jiff-core",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.118",
+]
+
+[[package]]
+name = "jobserver"
+version = "0.1.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
+dependencies = [
+ "getrandom 0.4.3",
+ "libc",
+]
+
[[package]]
name = "js-sys"
version = "0.3.103"
@@ -1722,6 +2246,18 @@ dependencies = [
"wasm-bindgen",
]
+[[package]]
+name = "kurbo"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2"
+dependencies = [
+ "arrayvec",
+ "euclid",
+ "polycool",
+ "smallvec",
+]
+
[[package]]
name = "lazy_static"
version = "1.5.0"
@@ -1759,6 +2295,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "linked-hash-map"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
+
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
@@ -1787,25 +2329,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
-name = "lru"
-version = "0.16.4"
+name = "lopdf"
+version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f66e8d5d03f609abc3a39e6f08e4164ebf1447a732906d39eb9b99b7919ef39"
+checksum = "bfffda0fe1ab0157e1a13c14bebd3f28671f2fccb7922f0722ec53926e6922d3"
dependencies = [
- "hashbrown 0.16.1",
+ "aes",
+ "bitflags 2.13.0",
+ "brotli-decompressor",
+ "cbc",
+ "chrono",
+ "ecb",
+ "encoding_rs",
+ "flate2",
+ "getrandom 0.4.3",
+ "indexmap",
+ "itoa",
+ "log",
+ "md-5",
+ "nom 8.0.0",
+ "rand 0.10.2",
+ "rangemap",
+ "rayon",
+ "sha2 0.11.0",
+ "stringprep",
+ "thiserror 2.0.18",
+ "weezl 0.2.1",
]
[[package]]
name = "markup5ever"
-version = "0.39.0"
+version = "0.40.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7122d987ec5f704ee56f6e5b41a7d93722e9aae27ae07cafa4036c4d3f9757de"
+checksum = "0ab3dc68ac4a0f5719e560136778c1ee716e296030d75dbd4484e37e39e3a842"
dependencies = [
"log",
"tendril",
"web_atoms",
]
+[[package]]
+name = "md-5"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98"
+dependencies = [
+ "cfg-if",
+ "digest 0.11.3",
+]
+
[[package]]
name = "memchr"
version = "2.8.2"
@@ -1821,6 +2393,15 @@ dependencies = [
"libc",
]
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@@ -1892,7 +2473,7 @@ dependencies = [
"napi-derive-backend",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -1905,7 +2486,7 @@ dependencies = [
"proc-macro2",
"quote",
"semver",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -2120,6 +2701,16 @@ dependencies = [
"num-traits",
]
+[[package]]
+name = "ordered-stream"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+]
+
[[package]]
name = "os_display"
version = "0.1.4"
@@ -2160,9 +2751,15 @@ dependencies = [
"by_address",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
+[[package]]
+name = "parking"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
+
[[package]]
name = "parking_lot"
version = "0.12.5"
@@ -2186,6 +2783,47 @@ dependencies = [
"windows-link",
]
+[[package]]
+name = "pcre2"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e970b0fcce0c7ee6ef662744ff711f21ccd6f11b7cf03cd187a80e89797fc67"
+dependencies = [
+ "libc",
+ "log",
+ "pcre2-sys",
+]
+
+[[package]]
+name = "pcre2-sys"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18b9073c1a2549bd409bf4a32c94d903bb1a09bf845bc306ae148897fa0760a4"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "pdf-inspector"
+version = "1.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e22dc125a533d212c847c8c85e4fcb7358f4384869ef76b2b8721f039b1b633a"
+dependencies = [
+ "env_logger",
+ "include_dir",
+ "log",
+ "lopdf",
+ "once_cell",
+ "rayon",
+ "regex",
+ "thiserror 2.0.18",
+ "ttf-parser",
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
[[package]]
name = "peg"
version = "0.8.6"
@@ -2249,7 +2887,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -2283,12 +2921,12 @@ dependencies = [
[[package]]
name = "phf"
-version = "0.13.1"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
+checksum = "010378780309880b08997fae13be7834dba947d36393bd372f2b1556deb2a2f6"
dependencies = [
"phf_macros",
- "phf_shared 0.13.1",
+ "phf_shared 0.14.0",
"serde",
]
@@ -2304,12 +2942,12 @@ dependencies = [
[[package]]
name = "phf_codegen"
-version = "0.13.1"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
+checksum = "41b585a510fb76fdebead6897982ef2a03a21d8e6cbcca904999742a4afc6ffe"
dependencies = [
- "phf_generator 0.13.1",
- "phf_shared 0.13.1",
+ "phf_generator 0.14.0",
+ "phf_shared 0.14.0",
]
[[package]]
@@ -2324,25 +2962,25 @@ dependencies = [
[[package]]
name = "phf_generator"
-version = "0.13.1"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
+checksum = "aeb62e0959d5a1bebc965f4d15d9e2b7cea002b6b0f5ba8cde6cc26738467100"
dependencies = [
"fastrand",
- "phf_shared 0.13.1",
+ "phf_shared 0.14.0",
]
[[package]]
name = "phf_macros"
-version = "0.13.1"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
+checksum = "5fa8d0ca26d424d27630da600c6624696e7dec8bf7b3b492b383c5dc49e5e085"
dependencies = [
- "phf_generator 0.13.1",
- "phf_shared 0.13.1",
+ "phf_generator 0.14.0",
+ "phf_shared 0.14.0",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -2356,22 +2994,22 @@ dependencies = [
[[package]]
name = "phf_shared"
-version = "0.13.1"
+version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
+checksum = "c6fd9027e2d9319be6349febd1db4e8d02aa544921200c9b777720ac34a3aa89"
dependencies = [
"siphasher",
]
[[package]]
name = "pi-ast"
-version = "0.17.2"
+version = "0.19.0"
dependencies = [
"anyhow",
"ast-grep-core",
"globset",
"ignore",
- "phf 0.13.1",
+ "phf 0.14.0",
"serde",
"tree-sitter",
"tree-sitter-astro-next",
@@ -2385,8 +3023,10 @@ dependencies = [
"tree-sitter-dart",
"tree-sitter-diff",
"tree-sitter-dockerfile-updated",
+ "tree-sitter-elisp",
"tree-sitter-elixir",
"tree-sitter-erlang",
+ "tree-sitter-fortran",
"tree-sitter-go",
"tree-sitter-graphql",
"tree-sitter-haskell",
@@ -2429,58 +3069,97 @@ dependencies = [
"tree-sitter-xml",
"tree-sitter-yaml",
"tree-sitter-zig",
+ "xxhash-rust",
+]
+
+[[package]]
+name = "pi-diff"
+version = "0.19.0"
+
+[[package]]
+name = "pi-edit"
+version = "0.19.0"
+dependencies = [
+ "async-trait",
+ "parking_lot",
+ "pi-ast",
+ "pi-diff",
+ "pi-walker",
+ "regex",
+ "serde",
+ "serde_json",
+ "tempfile",
+ "thiserror 2.0.18",
+ "tokio",
+ "xutf",
+ "xxhash-rust",
]
[[package]]
name = "pi-iso"
-version = "0.17.2"
+version = "0.19.0"
dependencies = [
"async-trait",
"libc",
"parking_lot",
- "similar 3.1.1",
+ "similar 3.2.0",
"tokio",
"windows-sys 0.61.2",
]
[[package]]
name = "pi-natives"
-version = "0.17.2"
+version = "0.19.0"
dependencies = [
"anyhow",
"arboard",
"ast-grep-core",
+ "async-trait",
"clap",
+ "clipboard-win",
"dashmap",
+ "find-msvc-tools",
+ "flate2",
"gjc-sdk",
"globset",
"grep-matcher",
+ "grep-pcre2",
"grep-regex",
"grep-searcher",
+ "heapless",
"html-to-markdown-rs",
"icy_sixel",
"ignore",
"image",
"inferno",
"libc",
+ "log",
"memmap2",
"napi",
"napi-build",
"napi-derive",
"parking_lot",
- "phf 0.13.1",
+ "pdf-inspector",
+ "phf 0.14.0",
"pi-ast",
+ "pi-diff",
+ "pi-edit",
"pi-iso",
"pi-shell",
+ "pi-vfs",
+ "pi-walker",
+ "png",
"portable-pty",
"rayon",
"regex",
+ "resvg",
"serde",
"serde_json",
- "sha2",
- "similar 3.1.1",
+ "sha2 0.10.9",
+ "similar 3.2.0",
"smallvec",
"syntect",
+ "tinyvec",
"tokio",
"tokio-util",
"toml",
@@ -2489,27 +3168,32 @@ dependencies = [
"unicode-width",
"windows-sys 0.61.2",
"winreg 0.56.0",
+ "xutf",
"xxhash-rust",
+ "zbus",
+ "zune-jpeg",
]
[[package]]
name = "pi-shell"
-version = "0.17.2"
+version = "0.19.0"
dependencies = [
"anyhow",
"brush-builtins",
"brush-core",
- "brush-parser 0.3.0",
+ "brush-parser",
"bytes",
"clap",
"hmac",
"libc",
"os_pipe",
+ "pi-vfs",
"regex",
"serde",
"serde_json",
- "sha2",
+ "sha2 0.10.9",
"smallvec",
+ "tempfile",
"tokio",
"tokio-util",
"toml",
@@ -2518,12 +3202,55 @@ dependencies = [
"xxhash-rust",
]
+[[package]]
+name = "pi-vfs"
+version = "0.19.0"
+dependencies = [
+ "async-trait",
+ "libc",
+ "parking_lot",
+ "tokio",
+ "tokio-util",
+ "windows-sys 0.61.2",
+ "xattr",
+]
+
+[[package]]
+name = "pi-walker"
+version = "0.19.0"
+dependencies = [
+ "globset",
+ "ignore",
+ "libc",
+ "parking_lot",
+ "pi-vfs",
+ "rayon",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "pico-args"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
+
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+[[package]]
+name = "piper"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
+dependencies = [
+ "atomic-waker",
+ "fastrand",
+ "futures-io",
+]
+
[[package]]
name = "pkg-config"
version = "0.3.33"
@@ -2543,6 +3270,44 @@ dependencies = [
"miniz_oxide",
]
+[[package]]
+name = "polling"
+version = "3.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "hermit-abi",
+ "pin-project-lite",
+ "rustix",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "polycool"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6"
+dependencies = [
+ "arrayvec",
+]
+
+[[package]]
+name = "portable-atomic"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
+
+[[package]]
+name = "portable-atomic-util"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715"
+dependencies = [
+ "portable-atomic",
+]
+
[[package]]
name = "portable-pty"
version = "0.9.0"
@@ -2597,7 +3362,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
- "syn",
+ "syn 2.0.118",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
+dependencies = [
+ "toml_edit",
]
[[package]]
@@ -2641,20 +3415,18 @@ checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
[[package]]
name = "quantette"
-version = "0.5.1"
+version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c98fecda8b16396ff9adac67644a523dd1778c42b58606a29df5c31ca925d174"
+checksum = "ba5d37e94c17b8870a5b936001d2845a6782a22ebdb1706c84294eca777f6729"
dependencies = [
"bitvec",
"bytemuck",
- "image",
"libm",
"num-traits",
"ordered-float",
"palette",
- "rand 0.9.4",
+ "rand 0.10.2",
"rand_xoshiro",
- "rayon",
"ref-cast",
"wide",
]
@@ -2721,15 +3493,6 @@ dependencies = [
"rand_core 0.6.4",
]
-[[package]]
-name = "rand"
-version = "0.9.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
-dependencies = [
- "rand_core 0.9.5",
-]
-
[[package]]
name = "rand"
version = "0.10.2"
@@ -2760,12 +3523,6 @@ dependencies = [
"getrandom 0.2.17",
]
-[[package]]
-name = "rand_core"
-version = "0.9.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
-
[[package]]
name = "rand_core"
version = "0.10.1"
@@ -2774,13 +3531,19 @@ checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
[[package]]
name = "rand_xoshiro"
-version = "0.7.0"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41"
+checksum = "662effc7698e08ea324d3acccf8d9d7f7bf79b9785e270a174ea36e56900c91d"
dependencies = [
- "rand_core 0.9.5",
+ "rand_core 0.10.1",
]
+[[package]]
+name = "rangemap"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a611d15b50743feb4c76b7d03edcb0e64f399c26961e4efe6975bc398be6aa3d"
+
[[package]]
name = "rayon"
version = "1.12.0"
@@ -2801,6 +3564,17 @@ dependencies = [
"crossbeam-utils",
]
+[[package]]
+name = "read-fonts"
+version = "0.41.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709"
+dependencies = [
+ "bytemuck",
+ "font-types",
+ "once_cell",
+]
+
[[package]]
name = "redox_syscall"
version = "0.5.18"
@@ -2827,14 +3601,14 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
name = "regex"
-version = "1.12.4"
+version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
+checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
dependencies = [
"aho-corasick",
"memchr",
@@ -2844,9 +3618,9 @@ dependencies = [
[[package]]
name = "regex-automata"
-version = "0.4.14"
+version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
+checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
dependencies = [
"aho-corasick",
"memchr",
@@ -2859,6 +3633,24 @@ version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
+[[package]]
+name = "resvg"
+version = "0.48.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67e3803f97b999e80cbf7c6ecdd07a8102204d92e1633cf48783720c521196bd"
+dependencies = [
+ "bytemuck",
+ "gif",
+ "image-webp",
+ "log",
+ "pico-args",
+ "rgb",
+ "svgtypes",
+ "tiny-skia",
+ "usvg",
+ "zune-jpeg",
+]
+
[[package]]
name = "rgb"
version = "0.8.53"
@@ -2869,12 +3661,27 @@ dependencies = [
]
[[package]]
-name = "rlimit"
-version = "0.11.0"
+name = "rlimit"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f35ee2729c56bb610f6dba436bf78135f728b7373bdffae2ec815b2d3eb98cc3"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "roxmltree"
+version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f35ee2729c56bb610f6dba436bf78135f728b7373bdffae2ec815b2d3eb98cc3"
+checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97"
+
+[[package]]
+name = "roxmltree"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1964b10c76125c36f8afe190065a4bf9a87bf324842c05701330bba9f1cacbb"
dependencies = [
- "libc",
+ "memchr",
]
[[package]]
@@ -2914,9 +3721,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "safe_arch"
-version = "0.9.3"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "629516c85c29fe757770fa03f2074cf1eac43d44c02a3de9fc2ef7b0e207dfdd"
+checksum = "42c6efa15875e6ecb39ca61fb0b0c1a40b84fac5a5ffe71eef7d1000c8eb3f5f"
dependencies = [
"bytemuck",
]
@@ -2975,7 +3782,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -2992,6 +3799,17 @@ dependencies = [
"zmij",
]
+[[package]]
+name = "serde_repr"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.6",
+]
+
[[package]]
name = "serde_spanned"
version = "1.1.1"
@@ -3020,7 +3838,7 @@ checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
- "digest",
+ "digest 0.10.7",
]
[[package]]
@@ -3031,10 +3849,21 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
- "digest",
+ "digest 0.10.7",
"sha2-asm",
]
+[[package]]
+name = "sha2"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "digest 0.11.3",
+]
+
[[package]]
name = "sha2-asm"
version = "0.6.4"
@@ -3090,30 +3919,58 @@ checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
[[package]]
name = "similar"
-version = "3.1.1"
+version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6505efef05804732ed8a3f2d4f279429eb485bd69d5b0cc6b19cc02005cda16"
+checksum = "4f66ca1f7aca2474dc10c942eb22feffc897735f54cd1db90138c2fddb490987"
dependencies = [
"bstr",
]
+[[package]]
+name = "simplecss"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c"
+dependencies = [
+ "log",
+]
+
[[package]]
name = "siphasher"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
+[[package]]
+name = "skrifa"
+version = "0.44.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68"
+dependencies = [
+ "bytemuck",
+ "read-fonts",
+]
+
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+[[package]]
+name = "slotmap"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038"
+dependencies = [
+ "version_check",
+]
+
[[package]]
name = "smallvec"
-version = "1.15.2"
+version = "1.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
+checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891"
dependencies = [
"serde",
]
@@ -3146,30 +4003,50 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520"
+[[package]]
+name = "strict-num"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731"
+dependencies = [
+ "float-cmp",
+]
+
[[package]]
name = "string_cache"
-version = "0.9.0"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
+checksum = "ffa8a5dbe8b3f0bbe29d4c3225daafaeead63afdc1b65fc4c01a1384166038e6"
dependencies = [
"new_debug_unreachable",
"parking_lot",
- "phf_shared 0.13.1",
+ "phf_shared 0.14.0",
"precomputed-hash",
]
[[package]]
name = "string_cache_codegen"
-version = "0.6.1"
+version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69"
+checksum = "928dcdf75e47626b3617a976ec205d9f057584c371c1f23b782129268d0e6edc"
dependencies = [
- "phf_generator 0.13.1",
- "phf_shared 0.13.1",
+ "phf_generator 0.14.0",
+ "phf_shared 0.14.0",
"proc-macro2",
"quote",
]
+[[package]]
+name = "stringprep"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+ "unicode-properties",
+]
+
[[package]]
name = "strsim"
version = "0.11.1"
@@ -3191,7 +4068,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -3200,6 +4077,16 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+[[package]]
+name = "svgtypes"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "695b5790b3131dafa99b3bbfd25a216edb3d216dad9ca208d4657bfb8f2abc3d"
+dependencies = [
+ "kurbo",
+ "siphasher",
+]
+
[[package]]
name = "syn"
version = "2.0.118"
@@ -3211,6 +4098,17 @@ dependencies = [
"unicode-ident",
]
+[[package]]
+name = "syn"
+version = "3.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
[[package]]
name = "synstructure"
version = "0.13.2"
@@ -3219,7 +4117,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -3238,6 +4136,7 @@ dependencies = [
"serde_derive",
"thiserror 2.0.18",
"walkdir",
+ "yaml-rust",
]
[[package]]
@@ -3317,7 +4216,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -3328,7 +4227,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -3341,7 +4240,7 @@ dependencies = [
"flate2",
"half",
"quick-error",
- "weezl",
+ "weezl 0.1.12",
"zune-jpeg",
]
@@ -3354,6 +4253,32 @@ dependencies = [
"crunchy",
]
+[[package]]
+name = "tiny-skia"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47ffee5eaaf5527f630fb0e356b90ebdec84d5d18d937c5e440350f88c5a91ea"
+dependencies = [
+ "arrayref",
+ "arrayvec",
+ "bytemuck",
+ "cfg-if",
+ "log",
+ "png",
+ "tiny-skia-path",
+]
+
+[[package]]
+name = "tiny-skia-path"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edca365c3faccca67d06593c5980fa6c57687de727a03131735bb85f01fdeeb9"
+dependencies = [
+ "arrayref",
+ "bytemuck",
+ "strict-num",
+]
+
[[package]]
name = "tinystr"
version = "0.8.3"
@@ -3367,18 +4292,9 @@ dependencies = [
[[package]]
name = "tinyvec"
-version = "1.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
-dependencies = [
- "tinyvec_macros",
-]
-
-[[package]]
-name = "tinyvec_macros"
-version = "0.1.1"
+version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
+checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
[[package]]
name = "tokio"
@@ -3405,7 +4321,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -3461,6 +4377,18 @@ dependencies = [
"serde_core",
]
+[[package]]
+name = "toml_edit"
+version = "0.25.13+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "toml_parser",
+ "winnow",
+]
+
[[package]]
name = "toml_parser"
version = "1.1.2+spec-1.1.0"
@@ -3495,7 +4423,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -3574,9 +4502,9 @@ dependencies = [
[[package]]
name = "tree-sitter-cmake"
-version = "0.7.1"
+version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c1b35d1dd7396d24b3e826bb0f975b915ec7e9125b989d5e9d24ebb6a08509a"
+checksum = "d22893fac54133fdb93497efc45713d53a96d93b035288c7a8ab0c2af77c0ddd"
dependencies = [
"cc",
"tree-sitter-language",
@@ -3632,6 +4560,16 @@ dependencies = [
"tree-sitter",
]
+[[package]]
+name = "tree-sitter-elisp"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b8b82ab7474028f20d2119285566f219209ebdbeec767fa28025b70f94f8cf3"
+dependencies = [
+ "cc",
+ "tree-sitter-language",
+]
+
[[package]]
name = "tree-sitter-elixir"
version = "0.3.5"
@@ -3652,6 +4590,16 @@ dependencies = [
"tree-sitter-language",
]
+[[package]]
+name = "tree-sitter-fortran"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea5fea41363a9b59666001dc26c4b356d4e164da27c6ce31145e00aba6bf9b16"
+dependencies = [
+ "cc",
+ "tree-sitter-language",
+]
+
[[package]]
name = "tree-sitter-go"
version = "0.25.0"
@@ -4096,6 +5044,12 @@ version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b40688ea6389c8171614b25491f71d4a27946e0c7ce2da1c6de27e25abf1a0ae"
+[[package]]
+name = "ttf-parser"
+version = "0.25.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
+
[[package]]
name = "tungstenite"
version = "0.24.0"
@@ -4135,6 +5089,17 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
+[[package]]
+name = "uds_windows"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e"
+dependencies = [
+ "memoffset",
+ "tempfile",
+ "windows-sys 0.61.2",
+]
+
[[package]]
name = "unic-langid"
version = "0.9.6"
@@ -4153,6 +5118,12 @@ dependencies = [
"tinystr",
]
+[[package]]
+name = "unicode-bidi"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
+
[[package]]
name = "unicode-ident"
version = "1.0.24"
@@ -4168,12 +5139,30 @@ dependencies = [
"tinyvec",
]
+[[package]]
+name = "unicode-properties"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
+
+[[package]]
+name = "unicode-script"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee"
+
[[package]]
name = "unicode-segmentation"
version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
+[[package]]
+name = "unicode-vo"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
+
[[package]]
name = "unicode-width"
version = "0.2.2"
@@ -4186,6 +5175,34 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3"
+[[package]]
+name = "usvg"
+version = "0.48.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "977d0a4abdef933f424a99fe09f95576e089b90aebc6f016a3bc813762493e91"
+dependencies = [
+ "base64",
+ "data-url",
+ "flate2",
+ "fontdb",
+ "harfrust",
+ "imagesize",
+ "kurbo",
+ "log",
+ "pico-args",
+ "roxmltree 0.21.1",
+ "simplecss",
+ "siphasher",
+ "skrifa",
+ "strict-num",
+ "svgtypes",
+ "tiny-skia-path",
+ "unicode-bidi",
+ "unicode-script",
+ "unicode-vo",
+ "xmlwriter",
+]
+
[[package]]
name = "utf-8"
version = "0.7.6"
@@ -4255,6 +5272,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53"
dependencies = [
"js-sys",
+ "serde_core",
"wasm-bindgen",
]
@@ -4349,7 +5367,7 @@ dependencies = [
"bumpalo",
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
"wasm-bindgen-shared",
]
@@ -4454,12 +5472,12 @@ dependencies = [
[[package]]
name = "web_atoms"
-version = "0.2.5"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "075474b12bcb3d2e3d4546580e9de478eeeead668a1761e2a8860c836b7ef297"
+checksum = "7572660c8890448ba236b7376f27e389c6a7e1c70195622faced601f855c0ada"
dependencies = [
- "phf 0.13.1",
- "phf_codegen 0.13.1",
+ "phf 0.14.0",
+ "phf_codegen 0.14.0",
"string_cache",
"string_cache_codegen",
]
@@ -4470,6 +5488,12 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
+[[package]]
+name = "weezl"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4ca08e5ef825b65b056d9efbd95c8750683f0a6d0466d02e96dc2e4e360f3d2"
+
[[package]]
name = "whoami"
version = "2.1.2"
@@ -4485,9 +5509,9 @@ dependencies = [
[[package]]
name = "wide"
-version = "0.8.3"
+version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13ca908d26e4786149c48efcf6c0ea09ab0e06d1fe3c17dc1b4b0f1ca4a7e788"
+checksum = "d920ac99c3c8edce110cb8d07dbb324d6d026011dce85b1e9355b70f0adacc4f"
dependencies = [
"bytemuck",
"safe_arch",
@@ -4573,7 +5597,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -4584,7 +5608,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -4829,6 +5853,9 @@ name = "winnow"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
+dependencies = [
+ "memchr",
+]
[[package]]
name = "winreg"
@@ -4905,12 +5932,43 @@ version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd"
+[[package]]
+name = "xattr"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156"
+dependencies = [
+ "libc",
+ "rustix",
+]
+
+[[package]]
+name = "xmlwriter"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
+
+[[package]]
+name = "xutf"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "990cc0159ef1d38b900524ac2308778ce60de2d32af70f40a71517c6b299fe8a"
+
[[package]]
name = "xxhash-rust"
version = "0.8.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d93c89cdc2d3a63c3ec48ffe926931bdc069eafa8e4402fe6d8f790c9d1e576"
+[[package]]
+name = "yaml-rust"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
+dependencies = [
+ "linked-hash-map",
+]
+
[[package]]
name = "yoke"
version = "0.8.3"
@@ -4930,10 +5988,80 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
"synstructure",
]
+[[package]]
+name = "zbus"
+version = "5.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907"
+dependencies = [
+ "async-broadcast",
+ "async-executor",
+ "async-io",
+ "async-lock",
+ "async-process",
+ "async-recursion",
+ "async-task",
+ "async-trait",
+ "blocking",
+ "enumflags2",
+ "event-listener",
+ "futures-core",
+ "futures-lite",
+ "hex",
+ "libc",
+ "ordered-stream",
+ "rustix",
+ "serde",
+ "serde_repr",
+ "tracing",
+ "uds_windows",
+ "uuid",
+ "windows-sys 0.61.2",
+ "winnow",
+ "zbus_macros",
+ "zbus_names",
+ "zvariant",
+]
+
+[[package]]
+name = "zbus_macros"
+version = "5.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.6",
+ "zbus_names",
+ "zvariant",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zbus_names"
+version = "4.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e"
+dependencies = [
+ "serde",
+ "winnow",
+ "zvariant",
+]
+
+[[package]]
+name = "zcheapstr"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "zerocopy"
version = "0.8.52"
@@ -4951,7 +6079,7 @@ checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -4971,7 +6099,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
"synstructure",
]
@@ -5005,7 +6133,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
]
[[package]]
@@ -5019,6 +6147,9 @@ name = "zune-core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
+dependencies = [
+ "log",
+]
[[package]]
name = "zune-jpeg"
@@ -5028,3 +6159,44 @@ checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
dependencies = [
"zune-core",
]
+
+[[package]]
+name = "zvariant"
+version = "5.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147"
+dependencies = [
+ "endi",
+ "enumflags2",
+ "serde",
+ "winnow",
+ "zcheapstr",
+ "zvariant_derive",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zvariant_derive"
+version = "5.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.6",
+ "zvariant_utils",
+]
+
+[[package]]
+name = "zvariant_utils"
+version = "4.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde",
+ "syn 3.0.6",
+ "winnow",
+]
diff --git a/Cargo.toml b/Cargo.toml
index 6caba62e568..be2b6e43f5b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,7 +4,7 @@ exclude = ["crates/brush-core-vendored", "crates/brush-builtins-vendored", "crat
resolver = "3"
[workspace.package]
-version = "0.17.2"
+version = "0.19.0"
edition = "2024"
license = "MIT"
authors = ["Yeachan-Heo"]
@@ -48,6 +48,12 @@ codegen-units = 16
incremental = true
strip = false
+[profile.profiling]
+inherits = "release"
+debug = "line-tables-only"
+split-debuginfo = "unpacked"
+strip = false
+
[profile.dev]
opt-level = 0
lto = false
@@ -138,6 +144,7 @@ option_if_let_else = "allow" # match/if-let-else often clearer
enum_glob_use = "allow"
items_after_statements = "allow" # Sometimes more readable
wildcard_imports = "allow" # Cleaner for preludes and test modules
+redundant_pub_crate = "allow"
# ──────────────────────────────────────────────────────────────────────────────
# Variables & Type Inference
@@ -156,6 +163,7 @@ verbose_bit_mask = "allow" # Explicit bit patterns can be clearer than hex
# ──────────────────────────────────────────────────────────────────────────────
default_trait_access = "allow" # Default::default() sometimes clearer
significant_drop_tightening = "allow" # We pay attention to this already
+assert_is_empty = "allow" # `is_empty()` states intent without brittle type annotations
# ──────────────────────────────────────────────────────────────────────────────
# Documentation
@@ -175,8 +183,12 @@ too_many_lines = "allow" # Arbitrary limits don't account for necessary comp
# Internal Libraries
# ──────────────────────────────────────────────────────────────────────────────
pi-ast = { path = "crates/pi-ast" }
+pi-diff = { path = "crates/pi-diff" }
+pi-edit = { path = "crates/pi-edit" }
pi-iso = { path = "crates/pi-iso" }
pi-shell = { path = "crates/pi-shell" }
+pi-vfs = { path = "crates/pi-vfs" }
+pi-walker = { path = "crates/pi-walker" }
brush-core = { path = "crates/brush-core-vendored" }
brush-builtins = { path = "crates/brush-builtins-vendored" }
@@ -184,7 +196,7 @@ brush-builtins = { path = "crates/brush-builtins-vendored" }
# Async Runtime & Concurrency
# ──────────────────────────────────────────────────────────────────────────────
async-trait = "0.1"
-dashmap = "6.1"
+dashmap = "6.2"
parking_lot = "0.12.5"
rayon = "1.12"
tokio = { version = "1", features = ["full"] }
@@ -203,6 +215,7 @@ toml = "1.1"
anyhow = "1.0"
bytes = "1"
futures-util = "0.3"
+thiserror = "2"
tokio-tungstenite = "0.24"
# ──────────────────────────────────────────────────────────────────────────────
@@ -214,16 +227,18 @@ clap = { version = "4", features = ["derive"] }
# Text Processing & Parsing
# ──────────────────────────────────────────────────────────────────────────────
regex = "1"
-similar = "3.1.0"
+similar = "3.2.0"
unicode-normalization = "0.1"
unicode-segmentation = "1.13"
unicode-width = "0.2"
+xutf = "1.5"
# ──────────────────────────────────────────────────────────────────────────────
# Data Structures - Collections
# ──────────────────────────────────────────────────────────────────────────────
-phf = { version = "0.13", features = ["macros"] }
-smallvec = { version = "1.15.1", features = [
+heapless = "0.9"
+phf = { version = "0.14", features = ["macros"] }
+smallvec = { version = "1.16.1", features = [
"serde",
"write",
"union",
@@ -246,9 +261,11 @@ memmap2 = "0.9"
# System & Platform
# ──────────────────────────────────────────────────────────────────────────────
libc = "0.2"
+log = "0.4"
os_pipe = "1"
windows-sys = { version = "0.61", features = [
"Win32_Foundation",
+ "Win32_Globalization",
"Win32_Security",
"Win32_Security_Authorization",
"Win32_Storage_FileSystem",
@@ -262,7 +279,12 @@ windows-sys = { version = "0.61", features = [
"Win32_System_ProcessStatus",
"Win32_System_Threading",
] }
+# Transitive-only pin (wired through crates/pi-natives build-deps): cc 1.2.x passes
+# FILE_ATTRIBUTE_TEMPORARY as u32 but find-msvc-tools 0.1.13 retyped it to i32,
+# breaking Windows builds. Mirrors the upstream pin recorded in docs/rust-porting-inventory.md.
+find-msvc-tools = "=0.1.12"
winreg = "0.56"
+zbus = { version = "5.19", default-features = false, features = ["async-io", "blocking-api"] }
# ──────────────────────────────────────────────────────────────────────────────
# Node.js Bindings (NAPI)
@@ -275,7 +297,8 @@ napi-derive = "3"
# Terminal & PTY
# ──────────────────────────────────────────────────────────────────────────────
arboard = { version = "3.6.1", features = ["wayland-data-control"] }
-icy_sixel = "0.5"
+clipboard-win = "5.4"
+icy_sixel = "0.7"
portable-pty = "0.9"
# ──────────────────────────────────────────────────────────────────────────────
@@ -283,11 +306,15 @@ portable-pty = "0.9"
# ──────────────────────────────────────────────────────────────────────────────
globset = "0.4"
grep-matcher = "0.1"
+grep-pcre2 = "0.1"
grep-regex = "0.1"
grep-searcher = "0.1"
ignore = "0.4"
# ──────────────────────────────────────────────────────────────────────────────
+# PDF inspection
+pdf-inspector = "1"
+
# Image Processing & Syntax Highlighting
# ──────────────────────────────────────────────────────────────────────────────
image = { version = "0.25", default-features = false, features = [
@@ -296,21 +323,31 @@ image = { version = "0.25", default-features = false, features = [
"gif",
"webp",
] }
+flate2 = "1.1"
+png = "0.18"
+resvg = "0.48"
+# Not used directly: activates zune-jpeg's 'log' feature so image's JPEG decoder
+# compiles (0.5.15 no-log stub macros are not expression-safe).
+zune-jpeg = { version = "0.5", default-features = false, features = ["log"] }
+# Not used directly: tinyvec 1.13.0 was published broken; pin the fixed release.
+tinyvec = "=1.13.3"
inferno = { version = "0.12", default-features = false }
syntect = { version = "5.3", default-features = false, features = [
"default-syntaxes",
+ "default-themes",
"regex-fancy",
+ "yaml-load",
] }
# ──────────────────────────────────────────────────────────────────────────────
# Markup Conversion
# ──────────────────────────────────────────────────────────────────────────────
-html-to-markdown-rs = { version = "2.24", default-features = false }
+html-to-markdown-rs = { version = "3.12.4", default-features = false }
# ──────────────────────────────────────────────────────────────────────────────
# Shell Parsing
# ──────────────────────────────────────────────────────────────────────────────
-brush-parser = "0.3"
+brush-parser = "0.4"
# ──────────────────────────────────────────────────────────────────────────────
# AST & Tree-Sitter
@@ -324,14 +361,17 @@ tree-sitter-bash = "0.25"
tree-sitter-c = "0.24"
tree-sitter-c-sharp = "0.23"
tree-sitter-clojure = "0.1"
-tree-sitter-cmake = "0.7.1"
+tree-sitter-cmake = "0.7.5"
tree-sitter-cpp = "0.23"
tree-sitter-css = "0.25"
tree-sitter-dart = "0.2"
tree-sitter-diff = "0.1"
tree-sitter-dockerfile = { version = "0.2.0", package = "tree-sitter-dockerfile-updated" }
+# Added for the full-langs AST grammar set at the pi-ast pin.
+tree-sitter-elisp = "1.7.2"
tree-sitter-elixir = "0.3"
tree-sitter-erlang = "0.16.0"
+tree-sitter-fortran = "0.6.0"
tree-sitter-go = "0.25"
tree-sitter-graphql = "0.1.0"
tree-sitter-haskell = "0.23"
@@ -356,7 +396,7 @@ tree-sitter-php = "0.24"
tree-sitter-powershell = "0.26.4"
tree-sitter-proto = "0.4.0"
tree-sitter-python = "0.25"
-tree-sitter-r = "1.2.0"
+tree-sitter-r = "1.3.0"
tree-sitter-regex = "0.25"
tree-sitter-ruby = "0.23"
tree-sitter-rust = "0.24"
diff --git a/Dockerfile.dockerignore b/Dockerfile.dockerignore
index 52bf1b3c44e..e4d06f4fe75 100644
--- a/Dockerfile.dockerignore
+++ b/Dockerfile.dockerignore
@@ -18,6 +18,8 @@ geobench/
.opencode/
.pi_config/
.gjc/plugins/
+.gjc/state/sdk/
+.gjc/state/chat/sdk/
# VCS, editors, IDEs — irrelevant to the build, churn on every IDE keystroke.
.git/
diff --git a/HANDOFF.md b/HANDOFF.md
new file mode 100644
index 00000000000..25ce0077d80
--- /dev/null
+++ b/HANDOFF.md
@@ -0,0 +1,93 @@
+# t_f611090c — doctor restart lease expiry 진단 인계
+
+## 판정 / STOP
+
+Historical 최초 `session.list` 거절의 code/message는 원문에 없으며, 이번 실제 FS/native 실행에서는 재현되지 않았다. 제품 결함 / fixture 동기화 결함 / CI 환경 영향 중 하나로 확정할 증거가 부족하다. 수리 완료 또는 flake로 판정하지 않는다. STOP 사유는 최초 응답 및 native 완료와 reservation 해제의 실제 시간 순서가 historical 로그에 없는 구체적 관측 한계다.
+
+`intent === null`은 cancellation 완료의 증명이 아니다. 아래 실제 코드 순서상 native unlink와 JS reservation 해제 사이에 관측 창이 존재한다. 이 창이 historical 거절을 발생시켰다는 것은 아직 가설이다. SDK enrollment와 공통 원인을 주장하지 않으며 #6699/#6915 lock, #6904 reload, #6907 C52를 수정하지 않았다.
+
+## 실행 기준
+
+- branch: doctor-lease-t_f611090c
+- 실행 전 HEAD / origin/dev: 90621699835a312c2fb4b5c738a90240ae1c2229
+- historical: f5ae6dc8dc00b5d5f6730a6d471112f2efd56d6e (run 39199582022, job 117896924170, attempt 1)
+- Bun: 1.4.0 (34cbb9a40), Linux x64. mac 실행으로 간주하지 않았다.
+- historical과 SHA는 다르다. test/doctor-broker-restart.test.ts, src/sdk/broker/broker.ts, src/sdk/broker/discovery.ts는 두 SHA 간 동일하다. crates/pi-natives/src/path_identity.rs에는 별도 exact-unlink/Windows error 관련 변경이 있다. 해당 전체 diff를 증거 폴더에 보존했다. native cancel 구간 자체는 이 diff에 없다. 전체 환경 동등성은 주장하지 않는다.
+- 모든 shell 명령에 PORT_BASE=45080, COMPOSE_PROJECT_NAME=doctor-lease-t_f611090c를 export했다.
+
+## 실행 결과 / AC1
+
+명령: `bun test packages/coding-agent/test/doctor-broker-restart.test.ts`
+
+1. 초기 호출: exit 1, 575ms. native addon 부재로 import 중단, 0 pass / 1 fail / 1 error. 실제 23개 case는 실행되지 않았다. lease failure의 재현으로 계산하지 않는다.
+2. worktree-local `bun run build:native`: exit 0. 기존 workspace dependency가 있어 install/global link는 실행하지 않았다. 빌드가 만든 index.d.ts 공백 1줄 차이는 원상복구했다.
+3. 준비 완료 후 전체 파일 유효 실행 1회: exit 0, 35.17s, 23 pass / 0 fail / 99 expect. 대상 expiry case 224.13ms; 명시적 lease cancellation case 49.41ms. 반복 재현 시도 없음.
+4. `bunx --no-install biome lint packages/coding-agent/test/doctor-broker-restart.test.ts packages/coding-agent/src/sdk/broker/broker.ts packages/coding-agent/src/sdk/broker/discovery.ts`: exit 0, 3 files, no fixes.
+
+23개 전수표는 증거 폴더의 cases.csv 및 cases.txt에 있다. source에서 임의 추정하지 않고 local 결과와 historical 해당 파일 구간을 파싱해 각각 23개 및 이름 일치를 검증했다. historical은 22 pass / 1 fail / 98 expect / 36.78s / file exit 1이다.
+
+## 원문 타임라인 / AC2·AC3
+
+historical-job.log의 원문 행 번호:
+
+- 4431: 2026-10-19T05:01:31.3766338Z 전체 파일 START.
+- 4439: 05:01:33.1493561Z 명시적 cancellation case pass (253.52ms).
+- 4440–4458: 05:01:33.4492496Z 이후 expiry case assertion 출력. :193 intent-null 검사를 통과한 다음 :194 session.list의 `.ok`가 false였다. :194는 response를 저장/출력하지 않아 code/message는 알 수 없다. 실패 case 300.04ms.
+- 4482–4486: 05:02:08.1619405Z 이후 22 pass / 1 fail, 23 tests, 36.78s.
+- 9287–9290: shard에서 이 파일 exit=1, job exit=1.
+
+실제 구현에서 보존해야 할 순서 (현재 dev 파일:줄, TS 3개 파일은 historical과 동일):
+
+1. broker.ts:4402–4407 admission close, expiry timer 설정, in-memory #restart 저장. :4406 timer는 `void cancelRestart(...)`로 cancellation 완료를 기다리지 않는다.
+2. broker.ts:4470–4479 current reservation을 유지한 채 retained publication.cancelRestartIntent()를 await한다.
+3. discovery.ts:315–317은 native cancelRestartIntentAsync() 완료를 기다린다. crates/pi-natives/src/path_identity.rs:310–314는 blocking task를 사용하고 :394–398에서 serialized publication write를 실행한다.
+4. 같은 Rust 파일 :2610–2622에서 identity-bound unlink (:2618, 실제 unlinkat :2655) → root.sync_all (:2621) → cancelled 반환 순서다. FS 이름이 사라진 뒤에도 native 작업/Promise 전달이 아직 끝나지 않았을 수 있다.
+5. broker.ts:4480–4482는 위 await가 끝난 후 timer clear → #restart undefined → admission reopen을 한다. 따라서 :193 FS null은 :4481이 수행됐다는 barrier가 아니다.
+6. broker.ts:5193–5199는 canonical ownership 검사, :5214–5215는 stopping/publication health 검사, :5216은 #restart 검사 순서다. reservation을 보기 전에 ownership 검사 자체도 await한다. 이 await 동안 cancellation이 끝나면 성공할 수 있으므로 위 창만으로 실패를 단정할 수 없다.
+7. test:151–178 명시적 cancellation case는 :161에서 cancelRestart 완료를 await한 후 :164에서 session.list를 호출한다. expiry case :192–194는 sleep 150ms 및 FS null만 확인하고 cancellation 완료는 await하지 않는다. fixture의 관측 barrier 후보 차이이며 수정 제안의 확정 근거는 아니다.
+
+최초 응답 후보 (소스상 가능한 거절 분기이며 관측된 응답으로 오인하면 안 됨):
+
+- broker.ts:5199: unavailable / broker lock ownership is not held
+- broker.ts:5215: unavailable / broker publication is unavailable
+- broker.ts:5216: broker_restarting / broker restart is prepared
+
+historical code/message: UNKNOWN (원문은 `.ok=false`만 기록).
+local 대상 code/message: 해당 없음 (pass; response payload를 추가 계측하지 않음).
+intent-null, publication health, reservation 해제의 historical 실제 상호 순서는 UNKNOWN이다. 상기 순서는 코드의 happens-before 제약이지 측정 타임라인이 아니다.
+
+## 산출물
+
+증거 디렉터리 (gitignored; 원문은 수정하지 않음):
+`/home/probe/.hermes-wtpool/Yeachan-Heo_gajae-code/p4/artifacts/doctor-lease-t_f611090c/`
+
+- historical-job.log: GitHub job 전체 원문 9307행. gh api --allow-escape-sequences로 확보. 이 옵션은 원문의 ANSI 보존용이며 테스트 변경이 아니다.
+- historical-doctor-file.log: 해당 전체 파일 실행 구간 원문 추출.
+- historical-excerpt.log: 원문 행 번호를 붙인 관련 발췌.
+- historical-source.diff: historical → 현재 dev의 지정 코드 차이.
+- full-file.log / full-file.exit: 초기 native import 오류.
+- native-build.log: 실제 native 빌드 출력.
+- full-file-native.log / full-file-native.exit: 준비 후 전체 파일 유효 실행.
+- lint.log: targeted lint 원문.
+- cases.csv / cases.txt: 23개 전수 비교표.
+- environment.json / cleanup.json: 실행 기준 및 프로세스 정리 확인.
+
+PM 경로 `/opt/data/kanban/boards/gajae-pr/workspaces/t_69994218/doctor-shard.log`는 이 host에 없었다. 대신 지정 GitHub job 원문을 직접 확보했다. 원문 로그는 위 gitignored 경로에 있으므로 runner는 worktree 정리 전에 함께 수거해야 한다.
+
+## Assumptions
+
+- isolation의 port 0 허용 조항을 적용했다. 원본 BrokerTransport는 transport.ts:72 기본값 0, :85–89 loopback ephemeral bind를 사용한다. 고정/default 프로젝트 포트를 사용하지 않았다. 45080..45099에 강제 고정하면 원본 fixture/자식 broker 설정을 바꿔야 하므로 하지 않았다.
+- 초기 import 실패는 재현 retry가 아닌 실행 준비 문제로 분리한다. native 준비 후 전체 파일은 한 번만 유효 실행했다.
+- 진단 전용 원문/문서 산출물만 인계하며 production/test 영구변경, PR, timeout/sleep/assert 변경은 금지로 해석했다.
+
+## 우회
+
+- AC1의 문자 그대로 총 1회 호출은 충족하지 못했다. 첫 호출이 native 부재로 collection 이전 중단되어 빌드 후 한 번 유효 실행했다. 두 로그 모두 보존했다.
+- PM 로컬 근거 파일 부재는 지정 GitHub 원문 직접 확보로 대체했다.
+- 최소 관측 instrumentation도 적용하지 않았다. 무계측 유효 실행에서 미재현이므로 반복/강제 지연으로 다른 실패를 만드는 대신 원래 응답 관측 한계를 STOP으로 채택했다.
+
+## 정리 / 후속 판단
+
+이 세션에서 시작한 외부 서버/컨테이너/워처는 없다. 테스트가 시작한 successor/fixture child는 테스트 정리 경로를 거쳤으며 완료 후 worktree cwd의 live Bun 프로세스는 0이다. 다른 실행의 broker/port/fixture를 조작하거나 kill하지 않았다. 빌드 addon은 이 worktree에만 남겼다. 임시 계측 diff는 없음; production/test 최종 diff는 없음.
+
+PM 후속: 최초 거절 branch를 확정하려면 원실패 당시 response code/message 및 native cancel 완료 전후와 publication/reservation 상태의 같은 실행 내 관측이 필요하다. 현 증거만으로 product fix, fixture fix, 환경 탓 중 하나를 승인하지 않는다. SDK enrollment 관련 연계나 flake acknowledgment도 하지 않는다.
diff --git a/NOTICE.md b/NOTICE.md
index 0fbae5d0b2c..e7238d48e2f 100644
--- a/NOTICE.md
+++ b/NOTICE.md
@@ -3,6 +3,48 @@
Gajae-Code builds on lessons from a small family of agent harnesses and keeps attribution visible:
- [`oh-my-pi`](https://github.com/can1357/oh-my-pi) — the upstream red-claw lineage and implementation DNA.
+- MIT-licensed Rust diff primitives and N-API bridge vendored from `can1357/oh-my-pi` at `a85bd5228d9f0f619deade1db78fa49420a721e1` under `crates/pi-diff` and `crates/pi-natives/src/diff.rs`; see `crates/pi-diff/LICENSE`.
+- SVG rasterization uses `resvg` and `png` under their MIT licenses; `crates/pi-natives/src/svg.rs` is vendored from `can1357/oh-my-pi` commit `a85bd5228d9f0f619deade1db78fa49420a721e1` (MIT).
+- `pi-iso` — MIT-licensed crate source vendored from `can1357/oh-my-pi@a85bd5228d9f0f619deade1db78fa49420a721e1`; see [crates/pi-iso/LICENSE](crates/pi-iso/LICENSE).
+- `crates/pi-natives/src/{text.rs,keys.rs}` include MIT-licensed code re-synced from oh-my-pi commit `a85bd5228d9f0f619deade1db78fa49420a721e1`.
+- Mermaid rendering in `crates/pi-natives/src/mermaid/` is vendored from `can1357/oh-my-pi` commit `a85bd5228d9f0f619deade1db78fa49420a721e1` (MIT); the pinned Mermaid test corpus is retained in `crates/pi-natives/fixtures/mermaid.json`.
+- `tty_writer.rs` is vendored from `can1357/oh-my-pi` commit `a85bd5228d9f0f619deade1db78fa49420a721e1` (MIT); native terminal writes run on a dedicated Unix thread.
- [`oh-my-codex`](https://github.com/Yeachan-Heo/oh-my-codex) — Codex-focused orchestration experiments.
- [`oh-my-claudecode`](https://github.com/Yeachan-Heo/oh-my-claudecode) — Claude Code workflow exploration.
- [`insane-search`](https://github.com/fivetaku/insane-search) — MIT-licensed public-route fetch engine by @fivetaku, vendored as the safe `insane` fallback/search provider lineage.
+- [`Markit`](https://github.com/Michaelliv/markit) — MIT-licensed document converter, pinned to `markit-ai` 0.5.3 under `packages/coding-agent/vendor/markit-ai`. Its license, upstream package metadata, integrity/hash inventory and reproducible patch are retained alongside the vendored code.
+- [`pdf-inspector`](https://crates.io/crates/pdf-inspector) — MIT-licensed PDF inspector maintained by Firecrawl, used with its default Rust features for native PDF-to-Markdown conversion.
+- `pi-ast` — MIT-licensed AST language, pattern, and structural-summary support from oh-my-pi@a85bd5228d9f0f619deade1db78fa49420a721e1, vendored under `crates/pi-ast` with its upstream license retained.
+- `crates/pi-ast/src/block.rs` is vendored from `can1357/oh-my-pi@a85bd5228d9f0f619deade1db78fa49420a721e1` as a Rust-only pi-edit dependency; no `blockRange` N-API export is added.
+- `pi-vfs` and `pi-walker` are MIT-licensed crates vendored at `a85bd5228d9f0f619deade1db78fa49420a721e1`; their retained license texts are in `crates/pi-vfs/LICENSE` and `crates/pi-walker/LICENSE`.
+- The local `brush-builtins-vendored` fork selectively reconciles base-builtin behavior from `can1357/oh-my-pi@a85bd5228d9f0f619deade1db78fa49420a721e1`; adopted source files retain per-file attribution, while utility builtins remain excluded.
+- Pi-shell's `shell.rs` is selectively reconciled with the pinned source; GJC's process, cancellation, HMAC, minimizer, output-budget, and hidden-console changes remain local. The shell uses upstream `output_decode.rs` for incremental UTF-8 and Windows ANSI-code-page decoding.
+- `crates/pi-edit` is the MIT-licensed crate vendored at `a85bd5228d9f0f619deade1db78fa49420a721e1`; see `crates/pi-edit/LICENSE`. Local matcher bindings do not expose upstream descriptions, grammars, or sloppy mode; GJC prompts and grammars remain authoritative.
+
+## Vendored Rust from oh-my-pi @ a85bd522
+
+`crates/pi-natives/src/{js.rs,task.rs,testing.rs,utils.rs}` include MIT-licensed material from `can1357/oh-my-pi` at commit `a85bd5228d9f0f619deade1db78fa49420a721e1`. Per-file attribution and local modifications are recorded in each source file.
+
+- The syntax-highlighting re-sync and six bundled `.sublime-syntax` files are derived from `can1357/oh-my-pi@a85bd5228d9f0f619deade1db78fa49420a721e1` and distributed under MIT.
+- The PTY binding is derived from `crates/pi-natives/src/pty.rs` at the pinned commit and retains GJC's bounded output-loss reporting, ConPTY single-flight guard, and child cleanup.
+- The process binding is derived from `crates/pi-natives/src/ps.rs` at the pinned commit and delegates process operations to the local `pi_shell::process`, retaining identity-aware observations and pinned-root signaling.
+
+Copyright (c) 2025 Mario Zechner, 2025–2026 Can Bölük, and 2026 Stencil Labs, Inc. Per-file headers retain the upstream source path and local modifications.
+
+## Vendored Rust modules
+
+The files listed below are adapted from the MIT-licensed `can1357/oh-my-pi` source pinned to `a85bd5228d9f0f619deade1db78fa49420a721e1`; each source file carries its own attribution and local-modification header.
+- `crates/pi-natives/src/grep.rs` is re-synced from the pinned source. `grep-pcre2` uses `PCRE2_SYS_STATIC=1` so PCRE2 is bundled into the addon rather than loaded from a host library; GJC omits the upstream-only injectable `shell::vfs` filesystem option.
+- `crates/pi-natives/src/html.rs`
+- `crates/pi-natives/src/sixel.rs`
+- `crates/pi-natives/src/clipboard.rs`
+- `crates/pi-natives/src/power.rs`
+- `crates/pi-natives/src/prof.rs`
+- `crates/pi-natives/src/appearance.rs`
+- `crates/pi-natives/src/crash.rs` (alloc-error handling merged from `crash_handler.rs`)
+- `crates/pi-natives/src/fd.rs`
+- `crates/pi-natives/src/workspace.rs`
+
+## Notice history
+
+MuPDF.js under AGPL-3.0-or-later was removed from the PDF conversion path; version 0.17.6 was the last release that shipped it.
diff --git a/README.ja.md b/README.ja.md
index a2e76cb7257..306c01d2c86 100644
--- a/README.ja.md
+++ b/README.ja.md
@@ -39,9 +39,9 @@
ドキュメント
-**すでに持っているサブスクリプションでログインし、ファイルが 1 つ変わる前に計画し、証拠とともに実行する — エージェントからの質問にはターミナル・スマホ・自前の bot、どこからでも答えられます。**
+**対応プランで認証し、必要に応じて変更前に計画し、証拠とともに実行する — エージェントからの質問にはターミナル・スマホ・自前の bot、どこからでも答えられます。**
-Gajae-Code(`gjc`)は外付けのコーディングエージェントハーネスです。任意のリポジトリやワークツリーに放り込むだけ。追加の API 課金なし。トークン単価の不安なし。ターミナルの張り付きも不要。
+Gajae-Code(`gjc`)は外付けのコーディングエージェントハーネスです。任意のリポジトリやワークツリーで使えます。対応するサブスクリプション型プランで認証すれば、そのプランの利用枠で動作します。API キーや従量課金の条件は各プロバイダに従います。ターミナルに張り付く必要もありません。
> Gajae-Code は実験的なベータ段階のプロジェクトです。粗い部分が残っている可能性があるため、重要な作業では出力を検証してから利用してください。
>
@@ -55,8 +55,8 @@ Gajae-Code(`gjc`)は外付けのコーディングエージェントハー
| 問題 | 何が起きるか | Gajae-Code の解決策 |
| :--- | :--- | :--- |
-| 別建ての API 課金 | プラン料金*に加えて*トークン従量の API 費用 | すでに払っているコーディングプランで `/login` — Claude、Codex、Cursor、Copilot、OpenCode Go、GOAT、ClinePass など |
-| コードから触るエージェント | 理解する前に編集し、手戻りが発生 | 計画ゲート付きワークフロー: インタビュー → 計画 → 批評 → *それから*変更、承認ゲートあり |
+| 別建ての API 課金 | プラン料金*に加えて*トークン従量の API 費用 | 対応するサブスクリプション型コーディングプランは `/login` で接続できます。API キーを使う場合はプロバイダの課金条件が適用されます。 |
+| コードから触るエージェント | 理解する前に編集し、手戻りが発生 | 必要に応じて計画ワークフローを選択: インタビュー → 計画 → 批評 → 承認または設定済みの引き渡し後に変更 |
| ターミナル拘束のセッション | 深夜 2 時の質問で朝まで作業停止 | 質問は Telegram/Discord/Slack にルーティング — どこからでも回答 |
| コンテキスト肥大 | ファイル全読みとログ洪水がウィンドウを焼く | 構造サマリー、artifact 退避、キャッシュ考慮ルーティング、コンパクション |
@@ -64,7 +64,7 @@ Gajae-Code(`gjc`)は外付けのコーディングエージェントハー
## クイックスタート
-**インストール** — Linux(x64/arm64)、macOS(arm64/x64)、Windows(x64)向けビルド済みバイナリ。Bun は不要です:
+**インストール** — Linux(glibc のみ、x64/arm64)、macOS(arm64/x64)、Windows(x64)向けビルド済みバイナリ。Bun は不要です:
```sh
curl -fsSL https://raw.githubusercontent.com/Yeachan-Heo/gajae-code/v0.15.3/scripts/install.sh -o gjc-install.sh
@@ -72,6 +72,8 @@ sh gjc-install.sh
gjc
```
+URL のタグはインストーラースクリプトの版を固定します。既定では最新の stable バイナリをインストールします。特定のバイナリリリースを選ぶ場合は、Unix で `--ref `、PowerShell で `-Ref ` を指定します。
+
`main` をパイプすると可変のインストーラが実行されます。最新インストーラが必要なときだけ使ってください:
```sh
@@ -85,15 +87,22 @@ Invoke-WebRequest -UseBasicParsing https://raw.githubusercontent.com/Yeachan-Heo
powershell -File gjc-install.ps1
```
-**初回利用** — プランを選んで出発:
+**初回利用** — 以下のスラッシュコマンドは GJC の対話セッション内で入力します:
```text
/login プロバイダ / コーディングプランを選択
/skill:deep-interview 曖昧な要件を明確化
/skill:ralplan 計画の立案と批評
+```
+
+計画を承認した後、シェルで目標を作成します。このコマンド自体はコード変更を開始しません:
+
+```sh
gjc ultragoal create-goals --brief-file <承認済みの計画>
```
+この例は計画ワークフローを使う依頼向けです。明確で低リスクな依頼は直接実行できます。既定では `gjc.ralplan.autoHandoff` は `off` です。
+
**実行モード:**
```sh
@@ -103,6 +112,8 @@ gjc --tmux --worktree my-task # リスクの高い作業向けの隔離ワ
gjc @screenshot.png "何を変えるべき?" # 画像入力
```
+Windows ではシェルツールに Bash 互換シェルが必要です。`--tmux` には tmux 互換の実行ファイルも必要で、GJC 管理セッションの保証には WSL と通常の tmux を使ってください。
+
Nightly チャンネル: `sh gjc-install.sh --channel nightly`(上でダウンロードしたタグ付きインストーラ)。インストールマトリクス全体、Windows 設定、更新チャンネル、シェル補完: [docs/install.md](docs/install.md)。Bun が必要なのはソースからのビルドだけです。
---
@@ -113,35 +124,37 @@ Nightly チャンネル: `sh gjc-install.sh --channel nightly`(上でダウン
-一度ログインすれば、すでに払っているサブスクリプションで GJC が動きます。セッション内で `/login` を実行してプランを選択:
+セッション内で `/login` を実行し、使うプランまたはプロバイダを選択します。方式は OAuth だけではなく、API キーの貼り付けやアカウントのメール認証も含みます:
-| プラン / サブスクリプション | OAuth ログイン |
+| プラン / サブスクリプション | 認証方式 / プロバイダ ID |
| :--- | :--- |
-| Claude Pro / Max | `anthropic` |
-| ChatGPT Plus / Pro(Codex) | `openai-codex`(ブラウザ)· `openai-codex-device`(ヘッドレス) |
-| Cursor | `cursor` |
-| GitHub Copilot | `github-copilot` |
-| OpenCode Zen / OpenCode Go | `opencode-zen` · `opencode-go` |
-| Kimi Code / Coding Plan / Moonshot | `kimi-code` · `moonshot` |
-| Z.AI GLM Coding Plan | `zai` |
-| MiniMax Coding Plan(国際 / 中国) | `minimax-code` · `minimax-code-cn` |
-| xAI(Grok) | `xai` |
-| Alibaba Token Plan / Qwen Portal | `alibaba-token-plan` · `qwen-portal` |
-
-その他の OAuth プラン — Google Gemini CLI、GitLab Duo、Perplexity Pro/Max、Fire Pass、Xiaomi Token Plan — は [docs/models.md](docs/models.md) を参照。
+| Claude Pro / Max | OAuth: `anthropic` |
+| ChatGPT Plus / Pro(Codex) | OAuth: `openai-codex`(ブラウザ)· `openai-codex-device`(ヘッドレス) |
+| Cursor | OAuth: `cursor` |
+| GitHub Copilot | OAuth: `github-copilot` |
+| OpenCode Zen / OpenCode Go | API キー: `opencode-zen` · `opencode-go` |
+| Kimi Code / Coding Plan / Moonshot | OAuth: `kimi-code` · API キー: `moonshot` |
+| Z.AI GLM Coding Plan | API キー: `zai` |
+| MiniMax Coding Plan(国際 / 中国) | API キー: `minimax-code` · `minimax-code-cn` |
+| xAI(Grok) | OAuth: `xai` |
+| Alibaba Token Plan / Qwen Portal | API キー: `alibaba-token-plan` · OAuth トークンまたは API キー: `qwen-portal` |
+
+一部の API キー方式の `/login` は認証確認のため小さな推論リクエストを送信します。プロバイダの利用枠や課金が適用される場合があります。
+
+その他の主な対応方式 — OAuth の Google Gemini CLI と GitLab Duo、メール認証の Perplexity Pro/Max、API キーの Fire Pass と Xiaomi Token Plan — は [docs/models.md](docs/models.md) を参照。
### 新着: コーディングプラン・プリセット
-API キー方式のコーディングプランはコマンド 1 つでオンボード — プリセットが API 種別、ベース URL、環境変数、互換フラグ、そして**ライブモデルカタログ**を一括で書き込むため、新モデルは GJC の更新なしで現れます:
+API キー方式のコーディングプランはコマンド 1 つで設定できます。プリセットは `models.yml` に API 種別、ベース URL、環境変数名、互換フラグ、静的モデル一覧またはモデル検出設定を書き込みます。プリセットは実際の認証情報を保存・検証しません。ライブモデル検出は対応するプリセットで実行時に行われ、プロバイダが公開した新モデルを GJC の更新なしで検出できる場合があります:
```sh
gjc setup provider --preset commandcode-goat # Command Code GOAT プラン(CMD_API_KEY)
gjc setup provider --preset cline-pass # ClinePass(CLINE_API_KEY)
```
-- **Command Code GOAT** — プロバイダのライブ `/models` カタログを取得。`claude-*` モデルはネイティブの Anthropic Messages 経由、それ以外は Chat Completions 経由でルーティング。エイリアス: `commandcode`、`goat`。
-- **ClinePass** — モデルのハードコードなし。Cline 自身がカタログを生成するのと同じ方法でライブカタログを取得。エイリアス: `clinepass`、`cline`。
-- ほかに利用できるプリセット: `minimax`、`minimax-cn`、`glm`、`alibaba-token-plan` — TUI 内では `/provider add --preset `。
+- **Command Code GOAT** — 実行時にプロバイダのライブ `/models` カタログを取得。`claude-*` モデルは Anthropic Messages、それ以外は Chat Completions 経由でルーティング。エイリアス: `commandcode`、`goat`。
+- **ClinePass** — モデルのハードコードなし。実行時に `models.dev` の Cline プロバイダーカタログから取得。エイリアス: `clinepass`、`cline`。
+- ほかに利用できるプリセット: `minimax`、`minimax-cn`、`glm`、`alibaba-token-plan`、`ionet`。`ionet` は実行時にモデルを検出し、ほかのここに挙げたプリセットは静的なモデル一覧を使います。TUI 内では `/provider add --preset `。
コーディングプランの先へ: 50+ プロバイダ、ゲートウェイ、ローカルランタイム
@@ -163,11 +176,11 @@ API キーのプロバイダ、ローカルランタイム(Ollama、LM Studio
-エージェントが判断を必要とすると Telegram に通知が届き、どこからでも答えられます:
+通知機能は既定で無効です。有効にしたセッションでエージェントが判断を必要とすると、Telegram に通知が届き、どこからでも答えられます:
- **Coordinator/lifecycle セッション用フォーラムトピック** — ライブ/確定出力、コンテキスト更新、画像添付、インラインボタン、自由テキスト返信、入力中インジケータ。
- **設定は一度だけ** — 実行中セッションの `/settings` → Notifications から、またはヘッドレスで `gjc notify setup|status|health|test|recovery`。トークンは入力時にマスクされ、以後表示されません。
-- **`gjc daemon`** が bot トークンごとに安全な long-poll 所有者を 1 つ維持し、新しいセッションが Telegram 409 競合なしにクリーンに接続します。
+- **`gjc daemon`** は同じ GJC の agent ディレクトリ内で Telegram の long-poll 所有者を 1 つ管理し、後続セッションをそのローカルデーモンに接続します。同じ bot token を使う別プロセスや別ホストの poller との競合は防げず、Telegram 409 が発生する場合があります。
- Discord と Slack への配信も同梱。汎用の `action_needed`/`reply` プロトコルにより、どんな bot やモバイルアプリでもターミナルスクレイピングなしで回答を返せます。
[Telegram オンボーディング](docs/telegram-onboarding.md) · [Discord](docs/discord-onboarding.md) · [Slack](docs/slack-onboarding.md)
@@ -176,13 +189,15 @@ API キーのプロバイダ、ローカルランタイム(Ollama、LM Studio
## 変更の前に計画
-意図的に小さく絞ったワークフロー表面 — スキル 4 つ、ロールエージェント 4 つ、それ以上はなし:
+標準搭載のワークフロースキルは 4 つ、ロールエージェントも 4 つです。`computer` はスキルではなく、別の実験的ツールです:
```text
deep-interview -> ralplan -> ultragoal
└─ リサーチが計画を裏付ける必要がある場合の任意の autoresearch ミッション
```
+これは代表的な流れで、すべての依頼に必須の自動連鎖ではありません。既定では `ralplan` の最終計画は `pending approval` のままで、現在の計画に対する明示的な実行承認後に進みます。`gjc.ralplan.autoHandoff` を `ultragoal` または `autoresearch` に設定すると、ランタイムが既存の引き渡し経路を認めます。`autoresearch` は調査を続けるもので、実装は行いません。
+
| 表面 | 役割 |
| :--- | :--- |
| `deep-interview` | 曖昧な依頼を具体的な要件に変える。 |
@@ -191,7 +206,7 @@ deep-interview -> ralplan -> ultragoal
| `autoresearch` | 目標指向のリサーチミッションを実行し、構造化された判定で締めくくる。 |
| `executor` / `architect` / `planner` / `critic` | 実装と読み取り専用レビューのための同梱ロールエージェント。 |
-オプトインで利用可能: **`computer-use`**(実験的なデスクトップ制御)。[Python REPL](docs/python-repl.md) と [docs/tools/computer.md](docs/tools/computer.md) を参照。
+実験的な **`computer` ツール**は、対応する Apple Silicon 搭載 macOS で既定で利用できます。macOS の画面収録・アクセシビリティ権限と supervisor の許可が必要で、`computer.enabled=false` を明示すると無効になります。[docs/tools/computer.md](docs/tools/computer.md) を参照。[Python REPL](docs/python-repl.md) も利用できます。
## カスタムスキル
@@ -215,10 +230,10 @@ Claude Code と Codex のスキルディレクトリはインポートソース
## トークンを節約
-GJC はトークン請求の両側を最適化します:
+GJC はキャッシュ再利用とコンテキスト管理でトークン消費を抑えるのに役立ちます:
-- **キャッシュヒット** — プロバイダごとの `cacheRetention` 制御。Anthropic は短いキャッシュが長時間のエージェント実行に脆弱なため、デフォルトで長期(1 時間)のキャッシュ保持。プロバイダランキングは安価な `cacheRead` 経路を優先し、オプトインの session-affinity ヘッダで OpenAI 互換リレーがサーバ側プロンプトキャッシュを再利用できます。
-- **コンテキスト節約** — ファイル読み取りはファイル全体でなく構造サマリーを返し、過大なシェル出力は最小化されて取得可能な `artifact://` 参照へ退避。コンパクションとブランチサマリーが、過去の作業を失わずに長いセッションをウィンドウ内に保ちます。
+- **キャッシュ再利用** — プロバイダごとの `cacheRetention` 制御。Anthropic は長時間の実行で短いキャッシュが失効しやすいため、既定で長期(1 時間)の保持を要求します。キャッシュヒットにはプロバイダ側の対応が必要です。モデル選択では `input` と `cacheRead` のコストを考慮し、オプトインの session-affinity ヘッダを使うと OpenAI 互換リレーがサーバ側プロンプトキャッシュを再利用できる場合があります。
+- **コンテキスト節約** — ファイル読み取りではコードの構造サマリーやサイズ制限付きの範囲が返ることがあり、必要に応じてフッターの指定から全文を読み直せます。大きなシェル出力は切り詰められ、`artifact://` 参照が付く場合がありますが、保存に失敗したり上限に達したりすると完全な出力を取得できないことがあります。コンパクションとブランチサマリーは履歴を要約しますが、詳細が失われることがあるため、重要な事実は元の証拠を確認してください。
[キャッシュ保持とプロバイダ互換](docs/models.md) · [コンパクションとブランチサマリー](docs/compaction.md)
@@ -253,38 +268,49 @@ endpoint records or credentials under .gjc/state/sdk, never open a raw session W
2. Prove the surface works (read-only). Run from inside the target repository:
gjc --version
- gjc sdk session list
+ gjc sdk session list --json
+ Add `--json` to every machine SDK call. On failure, read the structured
+ `gjc.command-error` envelope, preserve its outcome certainty and references,
+ and never infer non-execution or retry from a nonzero exit; reconcile uncertain
+ prompts with `status`. Do not publish raw stderr.
`list` returns a credential-free JSON DTO of indexed sessions. Fail closed on missing,
unavailable, stale, dead, unknown, or ambiguous rows. Exit 2 = usage error, exit 1 =
- operational failure (broker unavailable, session unavailable, retention gap, wait timeout).
+ operational failure (broker unavailable, session unavailable, retention gap, or `send --wait` timeout).
+ A `tail` observation window can end normally with `terminal: false`.
3. Understand a session before touching it:
- gjc sdk session inspect
- gjc sdk session raw query --query session.metadata
- ... then context.get, goal.list, todo.list, workflow.gates.list, session.stats
+ gjc sdk session inspect --json
+ gjc sdk session raw query --query session.metadata --json
+ ... then context.get, goal.list/get, todo.list, workflow.gates.list, session.stats
These reads are not an atomic snapshot: label every reported field confirmed / inferred /
stale / unavailable / unknown. Never invent a missing value.
-4. Start work in an isolated session:
+4. Start a new session:
gjc sdk session raw global --op session.create \
- --idempotency-key --json-input '{"cwd":"/abs/path/to/repo"}'
+ --idempotency-key --json-input '{"cwd":"/abs/path/to/repo"}' --json
Lifecycle ops allowed: session.create, session.fork, session.resume, session.close.
session.delete is NOT allowed. session.get_endpoint is refused unconditionally.
+ A new session separates session state, not repository files; use a dedicated worktree
+ as the target when filesystem isolation is required.
5. Drive a turn and reconcile it:
- gjc sdk session send --text "" --op-ref
- gjc sdk session status # lossless turn.result lookup
- gjc sdk session tail --until-idle # replay + live follow
- Use `send --wait --timeout-ms ` for a bounded wait; a wait window that elapses reports
+ gjc sdk session send --text "" --op-ref --json
+ gjc sdk session status --json # lossless turn.result lookup
+ gjc sdk session tail --until-idle --json # replay + live follow
+ Use `send --wait --timeout-ms --json` for a bounded wait; a wait window that elapses reports
wait_timeout and never cancels the running turn. One fresh op-ref per logical prompt --
`unknown` means uncertainty, never proof of non-execution, so reconcile with `status`
instead of replaying a prompt.
-6. Answer what the agent asks you:
- gjc sdk session raw control --op ask.answer --json-input '{...}'
- gjc sdk session raw control --op workflow.gate_answer --json-input '{...}'
+6. Answer durable workflow-gate questions:
+ gjc sdk session raw control --op workflow.gate_answer --json-input '{...}' --json
For gate answers use the durable workflow gate ID plus expectedSessionId; a transient
- action_needed.id is never durable authority. Other allowed per-session controls:
+ action_needed.id is never durable authority. General ask.answer availability depends
+ on the host: the SDK-only host does not implement it, while the notifications-enabled
+ host supports it for ordinary pending asks:
+ gjc sdk session raw control --op ask.answer --json-input '{...}' --json
+ Use workflow.gate_answer, not ask.answer, for durable workflow gates.
+ Other allowed per-session controls:
turn.prompt, turn.steer, turn.follow_up, todo.replace, session.switch, session.rename.
7. Show the human the exact operation and target before any mutating call, and treat the
@@ -293,11 +319,10 @@ endpoint records or credentials under .gjc/state/sdk, never open a raw session W
-長いプロンプトを残したままにしても安全です。SDK プロンプトのデッドラインは進捗を反映する無操作リース
-(`sdk.promptDeadlineMs`、デフォルト 60 分)で、`sdk.promptMaxRuntimeMs`(デフォルト 6 時間)により上限が定められます。更新されるのは受理された turn に帰属するツール実行だけであり、ハートビートやストリーミングテキストでは更新されません。
+長時間の SDK プロンプトには進捗を反映する無操作リースが適用されます(`sdk.promptDeadlineMs`、既定 60 分)。受理された turn に帰属するツール実行でリースは更新されますが、60 分間そのような実行がない場合は期限切れになり、`sdk.promptMaxRuntimeMs`(既定 6 時間)が絶対上限です。ハートビートやストリーミングテキストではリースは更新されません。
1 セッションずつではなく多くのワークツリーへイベント駆動で展開する必要がある場合、ネイティブの
-[Coordinator MCP ブリッジ](docs/hermes-mcp-bridge.md)(`gjc mcp-serve coordinator`、`gjc setup hermes` でインストール)が、その形の委任ツールを提供します。
+[Coordinator MCP ブリッジ](docs/hermes-mcp-bridge.md)(`gjc mcp-serve coordinator`)が、その形の委任ツールを提供します。`gjc setup hermes` は既定では設定プレビューのみです。Hermes にインストールするには、対象を確認したうえで `--install` と `--target <設定ファイル>` または `--profile-dir <プロファイル>` を指定します。
- [外部コントローラ / bot 統合ガイド](docs/bot-integration.md) — プロバイダ非依存のスモーク。[`docs/aside-integration.md`](docs/aside-integration.md) はオプトインの検索/コンテキストサイドカーと `/aside` コンポーザーコマンドを扱います
- [SDK session CLI](docs/sdk-session-cli.md) · [SDK とワイヤプロトコル](docs/sdk.md) · [SDK アプリガイド](docs/sdk-app-guide.md) · [外部制御レディネス](docs/external-control-readiness.md)
@@ -390,13 +415,13 @@ gjc skills read ralplan
gjc setup defaults --check
```
-`gjc setup defaults` は同梱の GJC ワークフロースキル 4 つをユーザーの `.gjc` ディレクトリにインストールし、デフォルトでは既存のローカルファイルを保持します。`--check` が欠落または差異のあるファイルを報告したら、まず `gjc skills read ` で埋め込みコピーと比較してください。ローカルのデフォルトワークフロースキルファイルを意図して置き換える場合にだけ `gjc setup defaults --force` を使います。
+`gjc setup defaults` は同梱の GJC ワークフロースキル 4 つをユーザーの agent ディレクトリ(既定では `~/.gjc/agent/`)にインストールし、デフォルトでは既存のローカルファイルを保持します。`--check` が欠落または差異のあるファイルを報告したら、まず `gjc skills read ` で埋め込みコピーと比較してください。ローカルのデフォルトワークフロースキルファイルを意図して置き換える場合にだけ `gjc setup defaults --force` を使います。
## 既存のエージェントや bot と併用する
| ツールまたは bot | 推奨する GJC コマンド | 境界 |
| --- | --- | --- |
-| Codex CLI | `gjc --tmux --worktree ` または `gjc` | `--worktree` は GJC 管理の隣接ワークツリーに名前を付けます。既存のパスでは先にそこへ `cd` してください。 |
+| Codex CLI | `gjc --tmux --worktree ` または `gjc` | 既定では `--worktree` は `/.worktrees/` に GJC 管理ワークツリーを作成します。ディレクトリ名は指定名を正規化し、8 桁のハッシュを付加して生成されます。正確なパスは `git worktree list` で確認できます。`GJC_WORKTREE_DIR` で保存先を変更できます。既存のパスでは先にそこへ `cd` してください。 |
| Claude Code | `gjc --tmux` または `gjc --tmux --worktree ` | GJC は Claude Code の拡張にはなりません。 |
| OpenCode | `gjc` または `gjc --tmux` | 現時点では外部ランナーのワークフローのみです。 |
| Claw Code | `gjc --tmux --worktree ` | GJC は Claw Code にインストールされたり、置き換えたりしません。 |
@@ -416,12 +441,12 @@ gjc setup defaults --check
## 設定
-プロバイダの再試行予算は `~/.gjc/config.yml` にあります:
+プロバイダの再試行設定はユーザー設定ファイル `~/.gjc/agent/config.yml` にあります(別の agent ディレクトリを指定した場合はその `config.yml`):
```yaml
retry:
- requestMaxRetries: 4
- streamMaxRetries: 100
+ requestMaxRetries: 5
+ streamMaxRetries: 5
maxRetries: 3
maxDelayMs: 300000
```
@@ -430,7 +455,7 @@ retry:
### 起動時の更新
-対話的な起動では、デフォルトでバックグラウンドから新しい GJC バージョンを GitHub Releases で確認します。この確認は通知専用で変更を加えません。GJC が起動中に自分自身をインストールまたは置き換えることはありません。対応プラットフォームでは `gjc update` が一致する GitHub リリースバイナリをダウンロードして原子的に置き換えます(パッケージマネージャの shim は上書きしません)。ソースチェックアウトと `dev:link` 実行ファイルはそのチェックアウト経由で更新する必要があり、`gjc update` はそれらの自己上書きを拒否します。未対応プラットフォームでは文書化されたインストーラを再実行してください。
+対話的な起動では、デフォルトでバックグラウンドから新しい GJC バージョンを GitHub Releases で確認します。この確認は通知専用で変更を加えません。GJC が起動中に自分自身をインストールまたは置き換えることはありません。対応プラットフォームでは `gjc update` が一致する GitHub リリースバイナリをダウンロードして原子的に置き換えます。パッケージマネージャからのインストールは別のユーザー用バイナリパスへ移行し、パッケージマネージャの shim は上書き・削除しません。ソースチェックアウトと `dev:link` 実行ファイルはそのチェックアウト経由で更新する必要があり、`gjc update` はそれらの自己上書きを拒否します。未対応プラットフォームでは文書化されたインストーラを再実行してください。
起動時の確認を無効にするには `gjc config set startup.checkUpdate false` を実行します。ネットワーク障害は起動を妨げないよう無視されます。
diff --git a/README.ko.md b/README.ko.md
index 00e3e33e934..1d15d71a7ff 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -39,9 +39,9 @@
문서
-**이미 구독 중인 플랜으로 로그인하고, 파일 하나 바뀌기 전에 계획하고, 증거와 함께 실행하고 — 에이전트의 질문에는 터미널·휴대폰·자체 봇 어디서든 답하세요.**
+**지원되는 구독 플랜을 연결하고, 필요하면 계획 워크플로로 범위를 정한 뒤 증거와 함께 실행하고 — 알림을 설정하면 터미널·휴대폰·자체 봇에서도 에이전트 질문에 답할 수 있습니다.**
-Gajae-Code(`gjc`)는 외부 코딩 에이전트 하네스입니다. 아무 저장소나 워크트리에 넣고 돌리세요. 별도 API 과금 없음. 토큰 단가 불안 없음. 터미널 앞 대기 없음.
+Gajae-Code(`gjc`)는 외부 코딩 에이전트 하네스입니다. 지원되는 구독 로그인은 이미 결제 중인 플랜을 활용할 수 있습니다. API 키나 자체 엔드포인트를 쓰면 요금은 해당 제공자의 정책을 따릅니다. 알림을 설정하면 터미널을 떠나서도 질문에 답할 수 있습니다.
> Gajae-Code는 실험적인 베타 단계 프로젝트입니다. 거친 부분이 있을 수 있으니 중요한 작업에는 출력을 검증한 뒤 사용하세요.
>
@@ -55,16 +55,16 @@ Gajae-Code(`gjc`)는 외부 코딩 에이전트 하네스입니다. 아무 저
| 문제 | 어떻게 되나 | Gajae-Code의 해법 |
| :--- | :--- | :--- |
-| 별도 API 과금 | 플랜 요금 *플러스* 토큰당 API 비용 | 이미 결제 중인 코딩 플랜으로 `/login` — Claude, Codex, Cursor, Copilot, OpenCode Go, GOAT, ClinePass 등 |
-| 코드부터 고치는 에이전트 | 이해 전에 수정 → 재작업 | 계획 게이트 워크플로: 인터뷰 → 계획 → 비평 → *그 다음에* 변경, 승인 게이트 포함 |
-| 터미널 종속 세션 | 새벽 2시에 질문이 오면 아침까지 정지 | 질문이 텔레그램/Discord/Slack으로 라우팅 — 어디서든 답변 |
-| 컨텍스트 폭발 | 전체 파일 읽기와 로그 홍수가 윈도를 태움 | 구조 요약, artifact 스필, 캐시 인지 라우팅, 컴팩션 |
+| 별도 API 과금 | 직접 API를 쓰면 플랜 요금과 토큰 비용이 함께 들 수 있음 | 지원되는 구독은 `/login`으로 기존 플랜 이용 가능 — API 키·게이트웨이는 해당 제공자 요금 적용 |
+| 코드부터 고치는 에이전트 | 이해 전에 수정 → 재작업 | 선택형 계획 워크플로: 인터뷰 → 계획·비평 → 승인 또는 유효한 Ultragoal handoff 뒤 실행 |
+| 터미널 종속 세션 | 터미널을 떠나면 질문에 답하기 어려움 | 알림을 설정하면 질문을 Telegram/Discord/Slack으로 라우팅해 응답 가능 |
+| 컨텍스트 폭발 | 긴 파일·출력이 컨텍스트를 채울 수 있음 | 코드 구조 요약, 출력 제한·조건부 artifact 저장, 캐시 설정, 요약 기반 컴팩션 |
---
## 빠른 시작
-**설치** — Linux(x64/arm64), macOS(arm64/x64), Windows(x64) 프리빌드 바이너리. Bun은 필요 없습니다:
+**설치** — Linux(glibc, x64/arm64), macOS(arm64/x64), Windows(x64) 프리빌드 바이너리. Linux musl/Alpine은 지원하지 않습니다. Bun은 필요 없습니다:
```sh
curl -fsSL https://raw.githubusercontent.com/Yeachan-Heo/gajae-code/v0.15.3/scripts/install.sh -o gjc-install.sh
@@ -78,19 +78,26 @@ gjc
curl -fsSL https://raw.githubusercontent.com/Yeachan-Heo/gajae-code/main/scripts/install.sh | sh
```
-Windows (PowerShell, 태그 고정):
+Windows (PowerShell; 설치기 스크립트 태그 고정):
```powershell
Invoke-WebRequest -UseBasicParsing https://raw.githubusercontent.com/Yeachan-Heo/gajae-code/v0.15.3/scripts/install.ps1 -OutFile gjc-install.ps1
powershell -File gjc-install.ps1
```
-**첫 실행** — 플랜 고르고 바로 시작:
+`v0.15.3` URL은 설치기 스크립트 버전을 고정합니다. 설치할 바이너리 릴리스까지 고정하려면 Unix 설치기에는 `--ref v0.15.3`, PowerShell 설치기에는 `-Ref v0.15.3`을 전달하세요. Windows 설정과 셸 요구사항은 [docs/install.md](docs/install.md)를 참고하세요.
+
+**첫 실행** — 아래 슬래시 명령은 실행 중인 GJC 대화형 세션에서 입력합니다:
```text
/login 프로바이더 / 코딩 플랜 선택
/skill:deep-interview 모호한 요구사항 명확화
/skill:ralplan 계획 수립 및 비평
+```
+
+최종 계획을 승인한 뒤, GJC 세션의 셸에서 다음 명령으로 목표 계획을 만들 수 있습니다. 이 명령은 목표를 만들 뿐 코드를 실행하지 않습니다:
+
+```sh
gjc ultragoal create-goals --brief-file <승인된-계획>
```
@@ -99,10 +106,14 @@ gjc ultragoal create-goals --brief-file <승인된-계획>
```sh
gjc # 현재 체크아웃에서 실행
gjc --tmux # tmux 기반 리더 세션
-gjc --tmux --worktree my-task # 위험한 작업을 위한 격리 워크트리
+gjc --tmux --worktree my-task # my-task라는 별도 격리 워크트리에서 실행
gjc @screenshot.png "뭘 바꿔야 할까?" # 이미지 입력
```
+관리형 워크트리는 기본적으로 저장소의 `.worktrees/` 아래에 생성되므로 해당 디렉터리를 Git에서 무시하도록 설정하세요. 다른 위치는 `GJC_WORKTREE_DIR`로 지정할 수 있습니다.
+
+Windows에서는 셸 도구에 bash 호환 셸이 필요하며, `--tmux`에는 PATH에서 찾을 수 있는 tmux 호환 실행 파일이 필요합니다. 세부 사항은 [docs/install.md](docs/install.md)를 참고하세요.
+
나이틀리 채널: `sh gjc-install.sh --channel nightly` (위에서 받은 태그 설치기). 전체 설치 매트릭스, Windows 설정, 업데이트 채널, 셸 자동완성: [docs/install.md](docs/install.md). Bun은 소스 빌드에만 필요합니다.
**한국어 실행 명령어** — `가재씨`는 패키지 매니저 설치에서 `gjc`와 함께 제공되는 별칭입니다. 독립 바이너리 설치는 `gjc`만 제공합니다:
@@ -119,34 +130,36 @@ gjc @screenshot.png "뭘 바꿔야 할까?" # 이미지 입력
-한 번 로그인하면 이미 결제 중인 구독으로 GJC가 돌아갑니다. 세션 안에서 `/login`을 실행하고 플랜을 고르세요:
+세션에서 `/login`을 실행해 프로바이더를 선택하세요. 프로바이더별 로그인 방식과 저장되는 자격 증명 유형은 서로 다릅니다:
-| 플랜 / 구독 | OAuth 로그인 |
-| :--- | :--- |
-| Claude Pro / Max | `anthropic` |
-| ChatGPT Plus / Pro (Codex) | `openai-codex` (브라우저) · `openai-codex-device` (헤드리스) |
-| Cursor | `cursor` |
-| GitHub Copilot | `github-copilot` |
-| OpenCode Zen / OpenCode Go | `opencode-zen` · `opencode-go` |
-| Kimi Code / Coding Plan / Moonshot | `kimi-code` · `moonshot` |
-| Z.AI GLM Coding Plan | `zai` |
-| MiniMax Coding Plan (해외 / 중국) | `minimax-code` · `minimax-code-cn` |
-| xAI (Grok) | `xai` |
-| Alibaba Token Plan / Qwen Portal | `alibaba-token-plan` · `qwen-portal` |
-
-그 외 OAuth 플랜 — Google Gemini CLI, GitLab Duo, Perplexity Pro/Max, Fire Pass, Xiaomi Token Plan — 은 [docs/models.md](docs/models.md)에서 다룹니다.
+| 플랜 / 구독 | GJC 로그인 ID | 인증 방식 |
+| :--- | :--- | :--- |
+| Claude Pro / Max | `anthropic` | OAuth |
+| ChatGPT Plus / Pro (Codex) | `openai-codex` (브라우저) · `openai-codex-device` (기기 코드; `openai-codex`에 자격 증명 저장) | OAuth |
+| Cursor | `cursor` | OAuth |
+| GitHub Copilot | `github-copilot` | OAuth |
+| Kimi Code / Coding Plan | `kimi-code` | OAuth |
+| xAI (Grok) | `xai` | OAuth |
+| OpenCode Zen / OpenCode Go | `opencode-zen` · `opencode-go` | API 키 자격 증명 |
+| Moonshot | `moonshot` | API 키 자격 증명 |
+| Z.AI GLM Coding Plan | `zai` | API 키 자격 증명 |
+| MiniMax Coding Plan (해외 / 중국) | `minimax-code` · `minimax-code-cn` | API 키 자격 증명 |
+| Alibaba Token Plan | `alibaba-token-plan` | API 키 자격 증명 |
+| Qwen Portal | `qwen-portal` | API 키 자격 증명 |
+
+그 외 OAuth 로그인은 Google Gemini CLI, GitLab Duo, Perplexity Pro/Max입니다. Fire Pass와 Xiaomi Token Plan은 API 키 자격 증명을 사용합니다. 지원 프로바이더와 인증 세부 정보는 [docs/models.md](docs/models.md)를 참고하세요.
### 신규: 코딩 플랜 프리셋
-키 기반 코딩 플랜은 명령 하나로 온보딩됩니다 — 프리셋이 API 타입, base URL, 환경 변수, 호환 플래그, **라이브 모델 카탈로그**를 한 번에 기록하므로 새 모델이 GJC 업데이트 없이 바로 나타납니다:
+키 기반 코딩 플랜은 프리셋으로 API 유형, 기본 URL, 환경 변수, 호환 옵션 등 프로바이더 설정을 기록해 온보딩할 수 있습니다. 아래 GOAT와 ClinePass 프리셋은 실행 중 라이브 모델 카탈로그를 검색합니다. 아래 열거한 `minimax`, `minimax-cn`, `glm`, `alibaba-token-plan` 프리셋은 정적 모델 목록을 사용하며, 다른 프리셋의 동작은 설정에 따라 다릅니다. 설정 명령은 자격 증명을 검증하지 않습니다:
```sh
gjc setup provider --preset commandcode-goat # Command Code GOAT 플랜 (CMD_API_KEY)
gjc setup provider --preset cline-pass # ClinePass (CLINE_API_KEY)
```
-- **Command Code GOAT** — 프로바이더의 라이브 `/models` 카탈로그를 가져오고, `claude-*` 모델은 네이티브 Anthropic Messages로, 나머지는 Chat Completions로 라우팅합니다. 별칭: `commandcode`, `goat`.
-- **ClinePass** — 하드코딩된 모델이 없습니다. GJC가 Cline이 자체 카탈로그를 생성하는 방식 그대로 라이브 카탈로그를 가져옵니다. 별칭: `clinepass`, `cline`.
+- **Command Code GOAT** — 실행 중 프로바이더의 라이브 `/models` 카탈로그를 검색하고, Claude 계열 ID는 Anthropic Messages로, 나머지는 OpenAI Chat Completions로 라우팅합니다. `/login`의 자격 증명 확인은 `max_tokens=1` 추론 요청을 보낼 수 있어 제공자 사용량을 소비할 수 있습니다. 별칭: `commandcode`, `goat`.
+- **ClinePass** — 모델 ID를 하드코딩하지 않고 실행 중 프로바이더 카탈로그를 검색합니다. 별칭: `clinepass`, `cline`.
- 그 외 프리셋: `minimax`, `minimax-cn`, `glm`, `alibaba-token-plan` — TUI 안에서는 `/provider add --preset `.
@@ -169,11 +182,11 @@ API 키 프로바이더, 로컬 런타임(Ollama, LM Studio, vLLM), 게이트웨
-에이전트가 결정을 요청하면 텔레그램으로 알림이 오고, 어디서든 답할 수 있습니다:
+알림은 기본적으로 꺼져 있습니다. 텔레그램 알림을 설정하고 활성화하면 에이전트 질문을 받을 수 있습니다. Coordinator/lifecycle 세션은 Threaded Mode를 사용할 수 있고 토픽 생성에 성공하면 포럼 토픽을 사용합니다. 런타임이 토픽 생성 거절이나 토픽 사용 불가로 판별한 경우에만 페어링된 비공개 채팅으로 알림과 질문 카드가 대체 전달되며, 인라인 버튼이나 질문 카드에 대한 직접 답장으로 응답할 수 있습니다. 토픽 생성 성공 응답에 유효한 `message_thread_id`가 없으면 오류로 처리하고 대체 전달하지 않습니다(fail closed). 일반 자유 텍스트와 세션 명령 라우팅에는 토픽이 필요합니다. 일반 세션은 기본 채팅으로 전달됩니다:
- **Coordinator/lifecycle 세션용 포럼 토픽** — 실시간/최종 출력, 컨텍스트 업데이트, 이미지 첨부, 인라인 버튼, 자유 텍스트 답장, 타이핑 표시.
- **한 번만 설정** — 실행 중인 세션의 `/settings` → Notifications에서, 또는 헤드리스로 `gjc notify setup|status|health|test|recovery`. 토큰은 입력 시 마스킹되고 이후 절대 표시되지 않습니다.
-- **`gjc daemon`** — 봇 토큰당 하나의 안전한 long-poll 소유자를 유지해 새 세션이 텔레그램 409 충돌 없이 깔끔하게 붙습니다.
+- **`gjc daemon`** — 같은 로컬 agent 디렉터리와 봇/채팅 설정을 공유하는 세션은 기존 long-poll 소유자를 재사용합니다. 같은 토큰을 쓰는 다른 GJC 프로필·호스트나 외부 폴러와는 Telegram 409 충돌이 여전히 발생할 수 있습니다.
- Discord와 Slack 전달도 함께 제공됩니다. 범용 `action_needed`/`reply` 프로토콜로 어떤 봇/모바일 앱이든 터미널 스크래핑 없이 답을 되돌릴 수 있습니다.
[텔레그램 온보딩](docs/telegram-onboarding.md) · [Discord](docs/discord-onboarding.md) · [Slack](docs/slack-onboarding.md)
@@ -182,7 +195,7 @@ API 키 프로바이더, 로컬 런타임(Ollama, LM Studio, vLLM), 게이트웨
## 변경 전에 계획
-의도적으로 작은 워크플로 표면 — 스킬 4개, 역할 에이전트 4개, 그 이상은 없습니다:
+기본 번들 워크플로는 스킬 4개와 역할 에이전트 4개입니다. 아래 워크플로는 사용자가 명시적으로 선택해 실행합니다:
```text
deep-interview -> ralplan -> ultragoal
@@ -195,9 +208,11 @@ deep-interview -> ralplan -> ultragoal
| `ralplan` | 코드 변경 전에 구현 계획을 세우고 비평합니다. |
| `ultragoal` | 실행·수정·검증·증거까지 목표를 추적합니다. |
| `autoresearch` | 목표 지향 리서치 미션을 수행하고 구조화된 판정으로 마무리합니다. |
-| `executor` / `architect` / `planner` / `critic` | 구현 및 읽기 전용 리뷰 레인을 위한 번들 역할 에이전트. |
+| `executor` / `architect` / `planner` / `critic` | 구현, 계획 및 읽기 전용 리뷰를 위한 번들 역할 에이전트. |
+
+`deep-interview`와 `ralplan`은 계획만 작성하며, 최종 계획에 대한 명시적 승인이 있거나 런타임이 유효한 Ultragoal handoff를 인정하기 전에는 구현을 시작하지 않습니다. `autoresearch`는 코드 구현이 아닌 리서치와 판정을 수행합니다.
-옵트인 기능: **`computer-use`** (실험적 데스크톱 제어). [Python REPL](docs/python-repl.md), [docs/tools/computer.md](docs/tools/computer.md) 참고.
+컴퓨터 도구는 지원되는 Apple Silicon macOS에서 기본으로 사용할 수 있으며, supervisor와 macOS 접근성·화면 기록 권한이 필요합니다. `computer.enabled=false`로 비활성화할 수 있습니다. [Python REPL](docs/python-repl.md), [컴퓨터 도구](docs/tools/computer.md) 참고.
## 사용자 스킬
@@ -223,14 +238,14 @@ Claude Code와 Codex 스킬 디렉터리는 가져오기 소스일 뿐입니다.
GJC는 토큰 비용의 양쪽을 모두 최적화합니다:
-- **캐시 히트** — 프로바이더별 `cacheRetention` 제어. Anthropic은 짧은 캐시가 긴 에이전트 실행에 취약하므로 기본이 장기(1시간) 캐시 유지입니다. 프로바이더 랭킹은 저렴한 `cacheRead` 경로를 우선하고, 옵트인 session-affinity 헤더로 OpenAI 호환 릴레이가 서버측 프롬프트 캐시를 재사용할 수 있습니다.
-- **컨텍스트 절약** — 파일 읽기는 전체 파일 대신 구조 요약을 반환하고, 과대한 셸 출력은 컨텍스트를 채우는 대신 최소화되어 회수 가능한 `artifact://` 참조로 넘어갑니다. 컴팩션과 브랜치 요약이 긴 세션을 윈도 안에 유지하면서 이전 작업 맥락을 잃지 않게 합니다.
+- **캐시 히트** — 프로바이더별 `cacheRetention`을 제어합니다. Anthropic은 기본 보존 설정이 `long`이지만, 1시간 TTL은 정식 Anthropic API 등 이를 지원하는 엔드포인트에서만 전송됩니다. 호환 엔드포인트는 별도 지원 설정이 없으면 1시간 TTL을 생략할 수 있습니다. 모델 선택은 입력 비용과 `cacheRead` 비용 등을 고려하며, 옵트인 session-affinity 헤더는 OpenAI 호환 릴레이가 서버 측 프롬프트 캐시를 재사용하도록 도울 수 있습니다.
+- **컨텍스트 절약** — 코드 파일 읽기는 구조 요약을 제공할 수 있고, 전체 내용은 필요할 때 명시적으로 요청할 수 있습니다. 과대한 셸 출력은 제한·축약되며, 저장이 가능할 때 `artifact://` 참조가 함께 제공됩니다. 컴팩션과 브랜치 요약은 긴 세션을 이어 가도록 돕지만 원문을 그대로 보존하지 않는 요약이므로, 중요한 세부 사항은 확인하세요.
[캐시 유지 & 프로바이더 호환](docs/models.md) · [컴팩션 & 브랜치 요약](docs/compaction.md)
---
-기본 다크 TUI 아이덴티티는 GJC red-claw 테마이며, 라이트 외관 터미널은 번들된 blue-crab 테마가 기본입니다. 전체 카탈로그와 `theme.dark` / `theme.light` 설정은 [docs/theme.md](docs/theme.md)를 참고하세요.
+기본 다크 TUI 아이덴티티는 GJC red-claw 테마이며, 라이트 외관 터미널은 번들된 blue-crab 테마가 기본입니다. 테마 선택과 `theme.dark` / `theme.light` 설정은 [docs/theme.md](docs/theme.md)를 참고하세요.
## OpenClaw / Hermes / Grokbot / 내가 만든 봇이 GJC를 부리게 하기
@@ -260,36 +275,40 @@ endpoint records or credentials under .gjc/state/sdk, never open a raw session W
2. Prove the surface works (read-only). Run from inside the target repository:
gjc --version
- gjc sdk session list
+ gjc sdk session list --json
`list` returns a credential-free JSON DTO of indexed sessions. Fail closed on missing,
unavailable, stale, dead, unknown, or ambiguous rows. Exit 2 = usage error, exit 1 =
operational failure (broker unavailable, session unavailable, retention gap, wait timeout).
+ On failure, preserve the structured error, outcome certainty, and references; a nonzero
+ exit does not prove a mutation was not applied. Reconcile with the original operation's
+ status or lookup instead of blindly replaying it (`session.lookup` `not_found` is not proof
+ that a create did not execute).
3. Understand a session before touching it:
- gjc sdk session inspect
- gjc sdk session raw query --query session.metadata
- ... then context.get, goal.list, todo.list, workflow.gates.list, session.stats
+ gjc sdk session inspect --json
+ gjc sdk session raw query --query session.metadata --json
+ ... then context.get, goal.list/get, todo.list, workflow.gates.list, session.stats
These reads are not an atomic snapshot: label every reported field confirmed / inferred /
stale / unavailable / unknown. Never invent a missing value.
4. Start work in an isolated session:
gjc sdk session raw global --op session.create \
- --idempotency-key --json-input '{"cwd":"/abs/path/to/repo"}'
- Lifecycle ops allowed: session.create, session.fork, session.resume, session.close.
+ --idempotency-key --json-input '{"cwd":"/abs/path/to/repo"}' --json
+ Mutating lifecycle ops allowed: session.create, session.fork, session.resume, session.close.
+ For a lost create response, reconcile with the read-only session.lookup using the same key and target.
session.delete is NOT allowed. session.get_endpoint is refused unconditionally.
5. Drive a turn and reconcile it:
- gjc sdk session send --text "" --op-ref
- gjc sdk session status # lossless turn.result lookup
- gjc sdk session tail --until-idle # replay + live follow
- Use `send --wait --timeout-ms ` for a bounded wait; a wait window that elapses reports
+ gjc sdk session send --text "" --op-ref --json
+ gjc sdk session status --json # lossless turn.result lookup
+ gjc sdk session tail --until-idle --json # replay + live follow
+ Use `send --wait --timeout-ms --json` for a bounded wait; a wait window that elapses reports
wait_timeout and never cancels the running turn. One fresh op-ref per logical prompt --
`unknown` means uncertainty, never proof of non-execution, so reconcile with `status`
instead of replaying a prompt.
-6. Answer what the agent asks you:
- gjc sdk session raw control --op ask.answer --json-input '{...}'
- gjc sdk session raw control --op workflow.gate_answer --json-input '{...}'
+6. Answer durable workflow-gate questions from the agent:
+ gjc sdk session raw control --op workflow.gate_answer --json-input '{...}' --json
For gate answers use the durable workflow gate ID plus expectedSessionId; a transient
action_needed.id is never durable authority. Other allowed per-session controls:
turn.prompt, turn.steer, turn.follow_up, todo.replace, session.switch, session.rename.
@@ -305,8 +324,8 @@ endpoint records or credentials under .gjc/state/sdk, never open a raw session W
귀속되는 툴 실행만으로 이루어지고, 하트비트나 스트리밍 텍스트로는 갱신되지 않는다.
세션 하나가 아니라 여러 워크트리에 이벤트 기반으로 펼쳐야 한다면, 네이티브
-[Coordinator MCP 브리지](docs/hermes-mcp-bridge.md)(`gjc mcp-serve coordinator`, `gjc setup hermes`로 설치)가
-그 형태의 위임 도구를 제공한다.
+[Coordinator MCP 브리지](docs/hermes-mcp-bridge.md)(`gjc mcp-serve coordinator`)가
+그 형태의 위임 도구를 제공한다. `gjc setup hermes`는 기본적으로 설정 미리보기만 출력하며, 실제 프로필에 설치하려면 `--install`과 함께 `--target` 또는 `--profile-dir`을 지정해야 한다.
- [외부 컨트롤러 / 봇 통합 가이드](docs/bot-integration.md) — 프로바이더 독립 스모크; [`docs/aside-integration.md`](docs/aside-integration.md)는 옵트인 검색/컨텍스트 사이드카와 `/aside` 컴포저 명령을 다룬다
- [SDK 세션 CLI](docs/sdk-session-cli.md) · [SDK & 와이어 프로토콜](docs/sdk.md) · [SDK 앱 가이드](docs/sdk-app-guide.md) · [외부 제어 준비도](docs/external-control-readiness.md)
@@ -327,7 +346,7 @@ endpoint records or credentials under .gjc/state/sdk, never open a raw session W
저장소
★★★★★ 1급 지원
-
GJC가 스스로 설치하는 네이티브 ACP 프로바이더. 모델 카탈로그, Default/Plan 모드, thinking 레벨, 실제 권한 승인 프롬프트, 소유 서브에이전트까지 끊는 취소, 모바일 제어.
+
GJC가 스스로 설치하는 네이티브 ACP 프로바이더. 모델 카탈로그, Default/Plan 모드, thinking 레벨, 실제 권한 승인 프롬프트, 모바일 제어. 기본 취소는 현재 턴만 중단하며, 소유 백그라운드 작업까지 종료하려면 Paseo 프로바이더의 env에 GJC_ACP_ABORT_SCOPE=owned를 설정해야 합니다.
gjc setup paseo 이후 paseo daemon restart
@@ -390,7 +409,7 @@ Orca는 필드 하나다: GJC를 설치([docs/install.md](docs/install.md))하
### 로컬 커스터마이징: `/extensions`
-대화형 세션에서 `/extensions`는 기본 커스터마이징 설정 화면입니다. 프로젝트(`/.gjc/`)와 사용자 전역(`~/.gjc/agent/`) 범위의 스킬·훅·MCP를 구성하고, 상태·출처 진단, 활성화·비활성화·제거, Claude Code/Codex에서 가져오는 안내형 흐름(정규화된 미리보기, 명시적 확인, 충돌 시 건너뛰기·이름 변경·덮어쓰기 정책, 롤백 가능한 원자적 쓰기)을 제공합니다. 비대화형 설정에서는 MCP 서버에 `gjc mcp`, Claude Code/Codex 가져오기에 `gjc migrate`를 사용하세요.
+대화형 세션에서 `/extensions`는 기본 커스터마이징 설정 화면입니다. 프로젝트(`/.gjc/`)와 사용자 전역(`~/.gjc/agent/`) 범위의 스킬·훅·MCP를 구성하고, 상태·출처 진단, 활성화·비활성화·제거, Claude Code/Codex에서 가져오는 안내형 흐름(정규화된 미리보기, 명시적 확인, 충돌 시 건너뛰기·이름 변경·덮어쓰기 정책, 롤백 가능한 원자적 쓰기)을 제공합니다. 비대화형 설정에서는 MCP 서버 관리에 `gjc mcp`, Claude Code/Codex에서 지원되는 MCP·스킬·지침 가져오기에 `gjc migrate`를 사용하세요.
### 스킬 마이그레이션 및 번들 스킬 검사
@@ -408,7 +427,7 @@ gjc setup defaults --check
| 도구 또는 봇 | 권장 GJC 명령 | 경계 |
| --- | --- | --- |
-| Codex CLI | `gjc --tmux --worktree ` 또는 `gjc` | `--worktree`는 GJC가 관리하는 인접 워크트리에 이름을 붙입니다. 기존 경로에서는 먼저 그곳으로 `cd`하세요. |
+| Codex CLI | `gjc --tmux --worktree ` 또는 `gjc` | 기본 워크트리 버킷은 저장소의 `.worktrees/`이며 Git에서 무시되어야 합니다. 기존 경로에서 실행하려면 먼저 해당 경로로 `cd`하세요. |
| Claude Code | `gjc --tmux` 또는 `gjc --tmux --worktree ` | GJC는 Claude Code 확장이 되지 않습니다. |
| OpenCode | `gjc` 또는 `gjc --tmux` | 현재는 외부 러너 워크플로만 지원합니다. |
| Claw Code | `gjc --tmux --worktree ` | GJC는 Claw Code에 설치되거나 이를 대체하지 않습니다. |
@@ -428,21 +447,21 @@ gjc setup defaults --check
## 설정
-프로바이더 재시도 예산은 `~/.gjc/config.yml`에 있습니다:
+프로바이더 재시도 예산은 기본 전역 에이전트 디렉터리의 `config.yml`(기본 경로: `~/.gjc/agent/config.yml`)에 설정합니다. 사용자 지정 에이전트 디렉터리를 쓰면 해당 경로를 기준으로 합니다:
```yaml
retry:
- requestMaxRetries: 4
- streamMaxRetries: 100
+ requestMaxRetries: 5
+ streamMaxRetries: 5
maxRetries: 3
maxDelayMs: 300000
```
-`requestMaxRetries`는 스트림이 수립되기 전에 적용됩니다. `streamMaxRetries`는 재생 안전한 일시적 스트림 실패에만 적용됩니다. 잘못된 인증, 지원하지 않는 모델/프로바이더, 잘못된 요청, 컨텍스트 초과, 사용자 중단, 영구적인 할당량 실패는 계속 즉시 실패합니다.
+이는 현재 기본값입니다. `requestMaxRetries`와 `streamMaxRetries`는 최초 시도가 아닌 재시도 횟수입니다. 전자는 스트림이 수립되기 전에 적용되고, 후자는 재생 안전한 일시적 스트림 실패에만 적용됩니다. 잘못된 인증, 지원하지 않는 모델/프로바이더, 잘못된 요청, 컨텍스트 초과, 사용자 중단, 영구적인 할당량 실패는 계속 즉시 실패합니다.
### 시작 시 업데이트
-대화형 시작은 기본적으로 백그라운드에서 더 새 GJC 버전이 있는지 GitHub 릴리스를 확인합니다. 이 검사는 알림 전용이며 변경하지 않습니다. GJC는 시작 중 스스로를 설치하거나 교체하지 않습니다. 지원 플랫폼에서는 `gjc update`가 일치하는 GitHub 릴리스 바이너리를 내려받아 원자적으로 교체합니다(패키지 매니저 shim은 덮어쓰지 않습니다). 소스 체크아웃과 `dev:link` 실행 파일은 해당 체크아웃을 통해 업데이트해야 하며, `gjc update`는 이를 스스로 덮어쓰지 않습니다. 지원하지 않는 플랫폼에서는 문서화된 설치기를 다시 실행하세요.
+대화형 시작은 기본적으로 백그라운드에서 더 새 GJC 버전이 있는지 GitHub 릴리스를 확인합니다. 이 검사는 알림 전용이며 변경하지 않습니다. GJC는 시작 중 스스로를 설치하거나 교체하지 않습니다. 지원 플랫폼에서 `gjc update`는 독립 바이너리를 원자적으로 교체하고, 패키지 매니저 설치는 shim을 덮어쓰거나 제거하지 않고 별도 사용자 바이너리 경로로 이전합니다. 소스 체크아웃과 `dev:link` 실행 파일은 해당 체크아웃을 통해 업데이트해야 하며, `gjc update`는 이를 스스로 덮어쓰지 않습니다. 지원하지 않는 플랫폼에서는 문서화된 설치기를 다시 실행하세요.
시작 시 확인을 끄려면 `gjc config set startup.checkUpdate false`를 실행하세요. 네트워크 실패는 시작을 막지 않도록 무시됩니다.
@@ -465,6 +484,8 @@ Settings(`Appearance -> Dark theme` / `Light theme`) 또는 `/theme`에서 고
| `claude-code` | 테라코타·핑크 하이라이트를 갖춘 Claude Code 풍 다크 팔레트. | GJC 안에서 유지하는 Claude Code의 익숙함. |
| `codex` | 코딩 세션 대비를 또렷하게 한 선명한 다크 블루그레이 팔레트. | Codex 같은 다크 작업 공간. |
| `opencode` | 더 강한 터미널 포인트를 갖춘 OpenCode 풍 다크 팔레트. | 번들 피커에서 쓰는 OpenCode의 익숙함. |
+| `gruvbox-dark` | Gruvbox 계열의 따뜻한 황토·주황 포인트를 쓰는 다크 팔레트. | 따뜻한 레트로 색감을 선호할 때. |
+| `ouroboros` | 짙은 남색·청록 바탕에 라임색과 금색 포인트를 더한 다크 팔레트. | 어두운 배경에서 선명한 청록·녹색 대비를 원할 때. |
## 문제 해결
@@ -475,7 +496,7 @@ gjc customize doctor # 사람이 읽는 출처와 해결책
gjc customize doctor --json # CI/설정 도구용 안정적인 JSON
```
-`gjc customize doctor`는 유일한 읽기 전용 문제 해결 표면입니다. 발견한 모든 커스터마이징, 출처 규약과 범위(`gjc`, Claude 프로젝트, Codex 프로젝트, 플러그인, 명시적 설정), 실제 우선순위 및 섀도잉, loaded/enabled/disabled/quarantined/rejected/stored-only 상태, 제한된 이유 코드, 해결 명령, 신뢰 요구사항, 재시작/새 세션 필요 여부를 보고합니다. 크리덴셜, 엔드포인트 토큰, 인증 헤더, 안전하지 않은 원시 설정 덤프는 절대 출력하지 않습니다.
+`gjc customize doctor`는 커스터마이징 상태를 확인하는 읽기 전용 문제 해결 표면입니다. 발견한 커스터마이징, 출처 규약과 범위(`gjc`, Claude 프로젝트, Codex 프로젝트, 플러그인, 명시적 설정), 실제 우선순위와 섀도잉, loaded/enabled/disabled/quarantined/rejected/stored-only 상태, 제한된 이유 코드, 해결 명령, 신뢰 요구사항, 재시작/새 세션 필요 여부를 보고합니다. 크리덴셜, 엔드포인트 토큰, 인증 헤더, 안전하지 않은 원시 설정 덤프는 출력하지 않습니다.
## 개발
diff --git a/README.md b/README.md
index bc2f53cf809..ff74b8088e4 100644
--- a/README.md
+++ b/README.md
@@ -39,9 +39,9 @@
Docs
-**Log in with the subscription you already have, plan before a single file mutates, execute with evidence — and answer the agent's questions from your terminal, your phone, or your own bot.**
+**Connect a supported subscription or API key, choose a planning workflow when needed, execute with evidence — and answer the agent's questions from your terminal, your phone, or your own bot.**
-Gajae-Code (`gjc`) is an external coding-agent harness: drop it into any repository or worktree. No separate API billing. No per-token anxiety. No terminal babysitting.
+Gajae-Code (`gjc`) is an external coding-agent harness: drop it into any repository or worktree. Supported subscription routes use your provider's plan; API-key routes follow that provider's billing and usage limits.
> Gajae-Code is an experimental, beta-stage project. Expect rough edges and verify outputs before relying on it for important work.
@@ -54,7 +54,7 @@ Most coding agents fail on three fronts: they bill you twice, they mutate before
| Problem | What Happens | Gajae-Code Fix |
| :--- | :--- | :--- |
| Separate API billing | You pay for a plan *and* per-token API costs | `/login` with the coding plan you already pay for — Claude, Codex, Cursor, Copilot, OpenCode Go, GOAT, ClinePass, and more |
-| Code-first agents | The agent edits before it understands; you rework | Plan-gated workflow: interview → plan → critique → *then* mutate, with approval gates |
+| Code-first agents | The agent edits before it understands; you rework | Opt-in plan-gated workflow: interview → plan → critique → *then* authorized execution |
| Terminal-bound sessions | Agent asks a question at 2 AM; work stalls until morning | Questions route to Telegram/Discord/Slack; you answer from anywhere |
| Context bloat | Whole-file reads and log floods burn the window | Structural summaries, artifact spill, cache-aware routing, compaction |
@@ -62,7 +62,7 @@ Most coding agents fail on three fronts: they bill you twice, they mutate before
## Quick Start
-**Install** — prebuilt binaries for Linux (x64/arm64), macOS (arm64/x64), and Windows (x64). Bun is not required:
+**Install** — prebuilt binaries for Linux (x64/arm64, glibc only), macOS (arm64/x64), and Windows (x64). Bun is not required:
```sh
curl -fsSL https://raw.githubusercontent.com/Yeachan-Heo/gajae-code/v0.15.3/scripts/install.sh -o gjc-install.sh
@@ -83,12 +83,17 @@ Invoke-WebRequest -UseBasicParsing https://raw.githubusercontent.com/Yeachan-Heo
powershell -File gjc-install.ps1
```
-**First use** — pick your plan and go:
+**First use** — inside an interactive `gjc` session:
```text
/login pick a provider / coding plan
/skill:deep-interview clarify ambiguous requirements
/skill:ralplan build and critique the plan
+```
+
+After approving the plan, ask the active GJC session to run this through its Bash tool, which supplies `GJC_SESSION_ID`, to create durable goals. With goal mode enabled, the session can activate inline goal mode and begin work in the same turn, before another user action; run this only when ready for execution. The command does not start the durable scheduler. A standalone shell without that session context fails:
+
+```sh
gjc ultragoal create-goals --brief-file
```
@@ -113,7 +118,7 @@ Nightly channel: `sh gjc-install.sh --channel nightly` (use the tagged installer
Log in once and run GJC on the subscription you already pay for. Run `/login` inside a session and pick your plan:
-| Plan / subscription | OAuth login |
+| Plan / subscription | `/login` provider ID |
| :--- | :--- |
| Claude Pro / Max | `anthropic` |
| ChatGPT Plus / Pro (Codex) | `openai-codex` (browser) · `openai-codex-device` (headless) |
@@ -126,18 +131,18 @@ Log in once and run GJC on the subscription you already pay for. Run `/login` in
| xAI (Grok) | `xai` |
| Alibaba Token Plan / Qwen Portal | `alibaba-token-plan` · `qwen-portal` |
-More OAuth plans — Google Gemini CLI, GitLab Duo, Perplexity Pro/Max, Fire Pass, Xiaomi Token Plan — are covered in [docs/models.md](docs/models.md).
+`/login` supports both OAuth and key-based onboarding; for example, Z.AI and MiniMax Coding Plan require a plan API key, not an OAuth browser login. More login options — Google Gemini CLI, GitLab Duo, Perplexity Pro/Max, Fire Pass, Xiaomi Token Plan — are covered in [docs/models.md](docs/models.md). Availability, entitlement, and usage limits depend on the provider.
### New: coding-plan presets
-Key-based coding plans onboard with one command — the preset writes the API type, base URL, env var, compatibility flags, and a **live model catalog** together, so new models show up without a GJC update:
+Key-based coding plans onboard with one command — the preset writes the API type, base URL, env-var reference, compatibility flags, and model configuration. The two discovery-backed presets below resolve a **live model catalog** at runtime; setup itself does not validate credentials:
```sh
gjc setup provider --preset commandcode-goat # Command Code GOAT plan (CMD_API_KEY)
gjc setup provider --preset cline-pass # ClinePass (CLINE_API_KEY)
```
-- **Command Code GOAT** — discovers the provider's live `/provider/v1/models` catalog and keeps every discovered model, including Claude-named IDs, on the provider's OpenAI-compatible `/chat/completions` transport. Login verifies entitlement with a fixed harmless inference probe before persisting credentials. Aliases: `commandcode`, `goat`.
+- **Command Code GOAT** — discovers the provider's live `/provider/v1/models` catalog. Claude model IDs use the Anthropic Messages transport; other models use OpenAI-compatible Chat Completions. `/login` verifies the key with a small inference probe before persisting it; this can consume provider quota and is separate from preset setup. Aliases: `commandcode`, `goat`.
- **ClinePass** — no hardcoded models; GJC fetches Cline's live catalog the same way Cline generates its own. Aliases: `clinepass`, `cline`.
- Also available as presets: `minimax`, `minimax-cn`, `glm`, `alibaba-token-plan` — or `/provider add --preset ` inside the TUI.
@@ -161,11 +166,11 @@ API-key providers, local runtimes (Ollama, LM Studio, vLLM), and gateways (Cloud
-When the agent needs a decision, it pings you on Telegram — and you answer from anywhere:
+After you configure notifications (off by default), the agent can ping you on Telegram when it needs a decision — and you answer from anywhere:
- **Coordinator/lifecycle session forum topics** with live/finalized output, context updates, image attachments, inline buttons, free-text replies, and typing indicators.
- **Configure once** from `/settings` → Notifications in a running session, or headless via `gjc notify setup|status|health|test|recovery`. Tokens are masked on entry and never displayed again.
-- **`gjc daemon`** keeps one safe long-poll owner per bot token, so new sessions attach cleanly without Telegram 409 conflicts.
+- **`gjc daemon`** lets sessions sharing a local agent directory and bot/chat configuration reuse a long-poll owner. Other GJC profiles, hosts, or applications polling the same token can still cause Telegram 409 conflicts.
- Discord and Slack delivery ship alongside; the generic `action_needed`/`reply` protocol lets any bot or mobile app route answers back without terminal scraping.
[Telegram onboarding](docs/telegram-onboarding.md) · [Discord](docs/discord-onboarding.md) · [Slack](docs/slack-onboarding.md)
@@ -181,6 +186,8 @@ deep-interview -> ralplan -> ultragoal
└─ optional autoresearch mission when research must ground the plan
```
+These workflows are explicitly invoked, not an automatic prerequisite for every request. Clear, low-risk changes can use direct execution. Planning workflows do not authorize implementation by themselves: execution requires explicit approval or an admitted `ralplan` runtime handoff.
+
| Surface | What it does |
| :--- | :--- |
| `deep-interview` | Turns vague requests into concrete requirements. |
@@ -189,7 +196,7 @@ deep-interview -> ralplan -> ultragoal
| `autoresearch` | Runs goal-directed research missions and ends on a structured verdict. |
| `executor` / `architect` / `planner` / `critic` | Bundled role agents for implementation and read-only review lanes. |
-Also included, opt-in: **`computer-use`** (experimental desktop control). See [Python REPL](docs/python-repl.md) and [docs/tools/computer.md](docs/tools/computer.md).
+Also available: the experimental **`computer` tool** for Apple Silicon macOS desktop control (not a fifth bundled workflow skill). It is callable by default on supported hosts, subject to supervisor and macOS permissions; set `computer.enabled=false` to disable it. See [Python REPL](docs/python-repl.md) and [docs/tools/computer.md](docs/tools/computer.md).
## Custom skills
@@ -215,7 +222,7 @@ The default dark TUI identity is the GJC red-claw theme; light-appearance termin
GJC optimizes both sides of the token bill:
- **Cache hits** — per-provider `cacheRetention` control; Anthropic defaults to long (1h) cache retention because short caches are fragile for long agent runs; provider ranking prefers cheap `cacheRead` paths; opt-in session-affinity headers let OpenAI-compatible relays reuse server-side prompt caches.
-- **Context savings** — file reads return structural summaries instead of whole files; oversized shell output is minimized and spilled to retrievable `artifact://` references instead of flooding the context; compaction and branch summaries keep long sessions inside the window without losing prior work.
+- **Context savings** — supported code reads return structural summaries by default; oversized shell output can be minimized and spilled to `artifact://` references. Retrieve omitted output only when an artifact reference is actually returned. Compaction and branch summaries reduce context size but are lossy, not a guarantee that every detail is retained.
[Cache retention & provider compat](docs/models.md) · [Compaction & branch summaries](docs/compaction.md)
@@ -249,41 +256,43 @@ endpoint records or credentials under .gjc/state/sdk, never open a raw session W
sdk-skills/gjc-sdk-operate/SKILL.md -- the allowlisted control/lifecycle operations
sdk-skills/gjc-sdk-author/SKILL.md -- TypeScript/Python templates for scripted flows
Follow their allowlists exactly. Pass every value as an argv item, never as a shell string.
+ Add --json to machine CLI calls so ordinary errors use the structured stdout envelope;
+ preserve outcome certainty and references. Missing success JSON is not proof of non-execution.
2. Prove the surface works (read-only). Run from inside the target repository:
gjc --version
- gjc sdk session list
+ gjc sdk session list --json
`list` returns a credential-free JSON DTO of indexed sessions. Fail closed on missing,
unavailable, stale, dead, unknown, or ambiguous rows. Exit 2 = usage error, exit 1 =
operational failure (broker unavailable, session unavailable, retention gap, wait timeout).
3. Understand a session before touching it:
- gjc sdk session inspect
- gjc sdk session raw query --query session.metadata
- ... then context.get, goal.list, todo.list, workflow.gates.list, session.stats
+ gjc sdk session inspect --json
+ gjc sdk session raw query --query session.metadata --json
+ ... then context.get, goal.list/get, todo.list, workflow.gates.list, session.stats
These reads are not an atomic snapshot: label every reported field confirmed / inferred /
stale / unavailable / unknown. Never invent a missing value.
4. Start work in an isolated session:
gjc sdk session raw global --op session.create \
- --idempotency-key --json-input '{"cwd":"/abs/path/to/repo"}'
+ --idempotency-key --json-input '{"cwd":"/abs/path/to/repo"}' --json
Lifecycle ops allowed: session.create, session.fork, session.resume, session.close.
session.delete is NOT allowed. session.get_endpoint is refused unconditionally.
5. Drive a turn and reconcile it:
- gjc sdk session send --text "" --op-ref
- gjc sdk session status # lossless turn.result lookup
- gjc sdk session tail --until-idle # replay + live follow
+ gjc sdk session send --text "" --op-ref --json
+ gjc sdk session status --json # lossless turn.result lookup
+ gjc sdk session tail --until-idle --json # replay + live follow
Use `send --wait --timeout-ms ` for a bounded wait; a wait window that elapses reports
wait_timeout and never cancels the running turn. One fresh op-ref per logical prompt --
`unknown` means uncertainty, never proof of non-execution, so reconcile with `status`
instead of replaying a prompt.
-6. Answer what the agent asks you:
- gjc sdk session raw control --op ask.answer --json-input '{...}'
- gjc sdk session raw control --op workflow.gate_answer --json-input '{...}'
+6. Answer durable workflow-gate questions:
+ gjc sdk session raw control --op workflow.gate_answer --json-input '{...}' --json
For gate answers use the durable workflow gate ID plus expectedSessionId; a transient
- action_needed.id is never durable authority. Other allowed per-session controls:
+ action_needed.id is never durable authority. The current host does not implement the
+ protocol's general ask.answer operation. Other allowed per-session controls:
turn.prompt, turn.steer, turn.follow_up, todo.replace, session.switch, session.rename.
7. Show the human the exact operation and target before any mutating call, and treat the
@@ -297,8 +306,9 @@ Long prompts are safe to leave running: the SDK prompt deadline is a progress-aw
by attributable tool execution for the accepted turn — not by heartbeats or streaming text.
Need event-driven fan-out across many worktrees instead of one session at a time? The native
-[Coordinator MCP bridge](docs/hermes-mcp-bridge.md) (`gjc mcp-serve coordinator`, installed by
-`gjc setup hermes`) exposes the delegation tools for that shape.
+[Coordinator MCP bridge](docs/hermes-mcp-bridge.md) (`gjc mcp-serve coordinator`) exposes the delegation
+tools for that shape. `gjc setup hermes` renders a configuration preview; installation requires
+`--install` with `--target` or `--profile-dir` (see the guide).
- [External controller / bot integration guide](docs/bot-integration.md) — provider-independent smokes; [`docs/aside-integration.md`](docs/aside-integration.md) covers the opt-in search/context sidecar and the `/aside` composer command
- [SDK session CLI](docs/sdk-session-cli.md) · [SDK & wire protocol](docs/sdk.md) · [SDK app guide](docs/sdk-app-guide.md) · [External-control readiness](docs/external-control-readiness.md)
@@ -401,7 +411,7 @@ gjc setup defaults --check
| Tool or bot | Recommended GJC command | Boundary |
| ----------- | ----------------------- | -------- |
-| Codex CLI | `gjc --tmux --worktree ` or `gjc` | `--worktree` names a GJC-managed sibling worktree; for an existing path, `cd` there first. |
+| Codex CLI | `gjc --tmux --worktree ` or `gjc` | `--worktree` names a GJC-managed worktree (by default under `/.worktrees/`); for an existing path, `cd` there first. |
| Claude Code | `gjc --tmux` or `gjc --tmux --worktree ` | GJC does not become a Claude Code extension. |
| OpenCode | `gjc` or `gjc --tmux` | External-runner workflow only today. |
| Claw Code | `gjc --tmux --worktree ` | GJC does not install into or replace Claw Code. |
@@ -421,12 +431,12 @@ For evaluating Aside as an opt-in search/context retrieval sidecar, see [`docs/a
## Configuration
-Provider retry budgets live in `~/.gjc/config.yml`:
+Provider retry budgets can be configured in `~/.gjc/agent/config.yml` (the default global agent directory). The defaults for these keys are:
```yaml
retry:
- requestMaxRetries: 4
- streamMaxRetries: 100
+ requestMaxRetries: 5
+ streamMaxRetries: 5
maxRetries: 3
maxDelayMs: 300000
```
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 65b26d6e118..1ae917aa721 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -11,7 +11,7 @@