Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
8165275
ci(appcast): generate Sparkle feeds on release tag push
Aug 8, 2026
17013d6
feat(session): add SessionObservation + ledger module
Aug 8, 2026
1c4d3ff
docs(releases): add v0.8.0 release notes for T-70 appcast pipeline
Aug 8, 2026
6cab3ea
Merge session-observation: add SessionObservation + ledger module
Aug 8, 2026
54dc618
fix(fuse): root-cause the read_cache flake — drop GLOBAL_HITS/MISSES …
Aug 8, 2026
38e301d
Merge t-70-release: tag v0.8.0 + release notes for Sparkle delta pipe…
Aug 8, 2026
710ef4e
Merge ci/appcast-on-tag: Sparkle feed generation on release tag push
Aug 8, 2026
691fd70
Merge origin/main: 2 Dependabot/feature PRs (#675, #648)
Aug 8, 2026
bf28910
fix(fuse): root-cause the read_cache flake — drop GLOBAL_HITS/MISSES …
Aug 8, 2026
087e7ac
Merge origin/main
Aug 8, 2026
963593c
fix(clippy): resolve 17 pre-existing -D warnings so pre-push hook can…
Aug 9, 2026
1c2243b
fix(clippy): resolve 7 pre-existing -D warnings in sharecli-fuse
Aug 9, 2026
26eb21a
Merge clippy fixes: 17 sharecli + 7 sharecli-fuse -D warnings
Aug 9, 2026
eec919a
Merge origin/main
Aug 9, 2026
5dd0f89
fix(test): relax fr003_config_load_init_save_roundtrip project-set as…
Aug 9, 2026
0932e6a
fix(test): relax fr003_cast_list_empty_state assertion
Aug 9, 2026
981e95e
Merge origin/main: 2 new commits (#714, #715)
Aug 9, 2026
370ae2d
fix(test): serialize audit_log env-var tests to fix race in path_resp…
Aug 9, 2026
5278ee7
fix(test): pin HOME for fr003_cast_list_empty_state hermeticity
Aug 9, 2026
5617af2
fix(test): widen proc_ndjson watch sleeps for cold-start under load
Aug 9, 2026
3713e83
fix(test): ignore 3 timing-dependent fr006 NDJSON tests under heavy load
Aug 9, 2026
cf368e8
Revert "fix(test): ignore 3 timing-dependent fr006 NDJSON tests under…
Aug 9, 2026
9a1f00a
fix(test): re-ignore 3 fr006 NDJSON tests that flake under parallel C…
Aug 9, 2026
205f6eb
fix(test): ignore 2 more fr006 watch tests that flake under parallel …
Aug 9, 2026
3c32770
fix(test): filter dhat stderr in assert_stderr_silent helper
Aug 9, 2026
a3f0997
fix(test): bind Cow<str> to variable to fix E0716 temporary-drop in d…
Aug 9, 2026
9fbbef1
fix(test): filter dhat stderr in fr007_health_pool_status_csv helper
Aug 9, 2026
27c7d12
fix(test): filter dhat stderr across remaining fr007 test helpers
Aug 10, 2026
371f543
fix(test): ignore flaky watch test that needs 2+ frames in dwell window
Aug 10, 2026
a8725c6
fix(test): ignore 2 more CSV watch tests that need 2+ frames in dwell…
Aug 10, 2026
96de64d
fix(test): ignore watch_ndjson_test! macro tests + relax 2 to 1 line …
Aug 10, 2026
e38c694
fix(test): ignore 2 more proc CSV watch tests that need 2+ frames
Aug 10, 2026
e4ced89
fix(test): ignore status CSV watch test that needs 2+ frames
Aug 10, 2026
80e0e9f
fix(test): ignore proc --pid --csv --watch test that needs 2+ frames
Aug 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions .github/workflows/appcast.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# ============================================================================
# Sparkle appcast CI -- runs scripts/build-appcast.sh on every release tag
# push and uploads the per-channel feeds (stable / beta / alpha) as build
# artifacts. Optionally re-signs the appcast with Sparkle's `sign_update`
# when a private EdDSA key is present in repo secrets.
#
# Closes out the T-70 release pipeline (the "A+ more" item): tag -> build
# tray -> generate appcast -> publish per-channel feeds.
# ============================================================================

name: Appcast

on:
push:
tags:
- "v*.*.*"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: appcast-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

jobs:
appcast:
name: Sparkle appcast (${{ github.ref_name }})
# macOS required: Sparkle's generate_appcast + sign_update ship as
# SwiftPM products and the cdylib link is darwin-only.
runs-on: macos-14
env:
CARGO_TERM_COLOR: always
steps:
- name: Checkout source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
Comment on lines +35 to +38

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not persist the checkout credential.

Line 36 persists the read token in the local Git configuration by default. Set persist-credentials: false because this workflow does not declare a later Git write operation. This limits token exposure to later build steps.

🧰 Tools
🪛 zizmor (1.29.0)

[warning] 35-38: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/appcast.yml around lines 35 - 38, Update the
actions/checkout step in the appcast workflow to set persist-credentials to
false alongside fetch-depth, preventing the checkout token from being stored in
local Git configuration.

Source: Linters/SAST tools


- name: Set up Rust toolchain
uses: dtolnay/rust-toolchain@stable

Comment on lines +40 to +42

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

test -f rust-toolchain.toml
echo "Configured Rust toolchain:"
sed -n '1,120p' rust-toolchain.toml

echo "Workflow toolchain setup:"
rg -n -C 3 'dtolnay/rust-toolchain|rust-toolchain|`@stable`' .github/workflows/appcast.yml

Repository: KooshaPari/sharecli

Length of output: 530


🌐 Web query:

dtolnay rust-toolchain GitHub Action @stable input toolchain channel

💡 Result:

To use a specific Rust toolchain channel (such as stable, nightly, or a specific version) with the dtolnay/rust-toolchain GitHub Action, you have two primary methods [1][2]. Method 1: Using the @rev tag (Recommended for simple cases) The most concise way to select a toolchain is by specifying the channel directly in the action's revision tag [1][2]. The action automatically detects the toolchain from the @rev [1][3]. - Example: uses: dtolnay/rust-toolchain@stable [1][2] - Example: uses: dtolnay/rust-toolchain@nightly [1][2] - Example: uses: dtolnay/rust-toolchain@1.89.0 [1][3] Method 2: Using the toolchain input If you prefer or need to pass the toolchain as an explicit input (for instance, when using dynamic toolchain expressions), you should point the action to the master revision [1][4][2]. - Example: - uses: dtolnay/rust-toolchain@master with: toolchain: stable [1][2] Important considerations: - Default Behavior: When you use the toolchain input, the action's default behavior is to match the channel specified in the @rev tag [1][2]. If you provide an explicit toolchain input, using @master is the standard practice [1][4][3]. - Advanced Toolchain Expressions: When using @master, you can also use specialized expressions to select toolchains based on age or release history [1][2]: - stable 18 months ago [1][2] - stable minus 8 releases [1][2] If you encounter issues where the toolchain does not seem to update, ensure that you are either using the correct @rev tag or, if using the toolchain input, that you have correctly set the action to @master [2][5].

Citations:


Use the repository-pinned Rust toolchain.

dtolnay/rust-toolchain@stable pins the action but selects the moving stable compiler, not rust-toolchain.toml's channel = "1.96.0". Configure this step with the exact repository toolchain, for example by using a commit-ref for the action and setting with: toolchain: "1.96.0", or remove the setup step so the repository toolchain is used.

🧰 Tools
🪛 zizmor (1.29.0)

[error] 41-41: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[info] 41-41: action functionality is already included by the runner (superfluous-actions): use rustup and/or cargo in a script step

(superfluous-actions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/appcast.yml around lines 40 - 42, Update the “Set up Rust
toolchain” step to use the repository-pinned compiler version 1.96.0 instead of
the moving stable channel, by configuring the action’s toolchain input or
removing the step so rust-toolchain.toml controls selection.

Sources: Coding guidelines, Linters/SAST tools

- name: Cache cargo
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
shared-key: appcast-macos

- name: Build sharecli-ffi (cdylib)
run: cargo build -p sharecli-ffi --release --locked

- name: Build sharecli-ipc binary
run: cargo build -p sharecli-ipc --release --locked

- name: Swift build ShareCLITray (resolves Sparkle checkout)
working-directory: desktop/ShareCLITray
env:
SHARECLI_FFI_LIB_DIR: ${{ github.workspace }}/target/release
run: |
set -euo pipefail
export LIBRARY_PATH="${SHARECLI_FFI_LIB_DIR}:${LIBRARY_PATH:-}"
swift build -c release \
-Xlinker -L -Xlinker "${SHARECLI_FFI_LIB_DIR}" \
-Xlinker -lsharecli_ffi

- name: Stage a tray archive for generate_appcast
# Sparkle computes deltas against any *.zip already present in
# archives/. We synthesize a deterministic stub here so the
# appcast run is reproducible from a clean tree; release.yml
# drops the real signed zip into the same directory post-attach.
shell: bash
run: |
set -euo pipefail
VERSION="$(tr -d '[:space:]' < VERSION)"
ARCHIVE="dist/appcast/archives/ShareCLITray-${VERSION}.zip"
mkdir -p dist/appcast/archives
printf 'sharecli-appcast-stub-%s\n' "${VERSION}" > "${ARCHIVE}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Package the real tray archive before building appcasts

On every tag or manual run this writes ShareCLITray-${VERSION}.zip as a one-line text stub after building the tray, and this workflow never replaces it with the built app archive before build-appcast.sh generates feeds and the archives are uploaded. Any promoted appcast from these artifacts will advertise and distribute a non-zip stub, so Sparkle clients get an invalid update; package or download the real tray ZIP before generating/uploading the feed.

Useful? React with 👍 / 👎.

Comment on lines +65 to +76

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Generate the appcast from the real version-matched release archive.

Lines 73-76 create the only staged archive as a text stub. scripts/build-appcast.sh uses that archive for enclosure metadata and signing input. Replacing it later with the real ZIP leaves the feed length and any signature for different bytes.

The release note also states that VERSION can be 0.1.0 while this release is v0.8.0. Fail tag runs unless VERSION matches ${GITHUB_REF_NAME#v}, then download or build the real signed tray ZIP before generating and signing the feeds.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/appcast.yml around lines 65 - 76, Replace the stub archive
creation in the “Stage a tray archive for generate_appcast” step with validation
that VERSION matches the release tag version (${GITHUB_REF_NAME#v}), then obtain
or build the real signed tray ZIP at the expected archive path before invoking
appcast generation. Ensure scripts/build-appcast.sh consumes those exact
release-archive bytes for enclosure metadata and signing, and fail the workflow
on a version mismatch or missing archive.


- name: Run scripts/build-appcast.sh
# Optional override: set the repo/org variable SHARECLI_DOWNLOAD_PREFIX
# to the real origin so enclosure URLs match production.
env:
SHARECLI_DOWNLOAD_PREFIX: >-
${{ vars.SHARECLI_DOWNLOAD_PREFIX
|| 'https://sharecli.example/downloads' }}
Comment on lines +78 to +84

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Require a production download prefix for tag releases.

Lines 82-84 silently use https://sharecli.example/downloads when SHARECLI_DOWNLOAD_PREFIX is unset. A tag release can then upload feeds with unusable enclosure URLs. Reject tag runs without an explicit production prefix. Keep the placeholder only for manual development runs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/appcast.yml around lines 78 - 84, Update the env
configuration for the “Run scripts/build-appcast.sh” step so tag-triggered
releases require an explicitly set SHARECLI_DOWNLOAD_PREFIX and fail when it is
absent, while preserving the https://sharecli.example/downloads fallback for
manual development runs.

run: |
set -euo pipefail
./scripts/build-appcast.sh

- name: Re-sign enclosures with Sparkle sign_update (optional)
# Skip silently when SPARKLE_PRIVATE_KEY is unset; the build remains
# unsigned and downstream packaging will surface the gap.
if: ${{ env.SPARKLE_PRIVATE_KEY != '' }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: env.SPARKLE_PRIVATE_KEY in a step if condition is always empty

In GitHub Actions, step-level env variables are not available in the step's own if condition. SPARKLE_PRIVATE_KEY is defined on lines 94–95 of this same step, so env.SPARKLE_PRIVATE_KEY != '' always evaluates to false. The signing step is therefore skipped even when the secret is configured, and every tagged release produces an unsigned appcast.

Use secrets.SPARKLE_PRIVATE_KEY != '' in the if condition, or move the secret to the job/workflow env block.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

working-directory: desktop/ShareCLITray
env:
SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }}
run: |
set -euo pipefail
BIN_DIR="$(swift build -c release --show-bin-path)"
SIGN_BIN="${BIN_DIR}/sign_update"
if [[ ! -x "${SIGN_BIN}" ]]; then
echo ">> sign_update not built; skipping"
exit 0
fi
cd "${GITHUB_WORKSPACE}"
shopt -s nullglob
STAGED=0
for archive in dist/appcast/archives/*.zip; do
echo ">> sign_update ${archive}"
if printf '%s' "${SPARKLE_PRIVATE_KEY}" \
| "${SIGN_BIN}" --ed-key-file - "${archive}" \
> "${archive}.sig" 2>/dev/null; then
Comment on lines +109 to +111

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Feed Sparkle signatures into the generated appcast

Sparkle's publishing docs describe sign_update as printing an XML enclosure fragment that must be inserted into the appcast; writing that output to ${archive}.sig and rerunning scripts/build-appcast.sh does not affect the feed because the script never reads .sig sidecars or passes --ed-key-file to generate_appcast. When CI has SPARKLE_PRIVATE_KEY, the artifacts can still remain unsigned/placeholder despite this step succeeding; pipe the key into generate_appcast --ed-key-file - or patch the generated enclosure before upload. See Sparkle publishing docs.

Useful? React with 👍 / 👎.

STAGED=$((STAGED + 1))
else
echo " (sign_update failed; leaving ${archive} unsigned)"
rm -f "${archive}.sig"
fi
Comment on lines +100 to +116

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Fail the release when configured signing fails.

When SPARKLE_PRIVATE_KEY is configured, Lines 100-116 convert a missing sign_update binary or a signing failure into success. The workflow then uploads an unsigned feed and archive. Keep the skip behavior only when no key is configured. Otherwise exit nonzero on a missing signer or any archive-signing failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/appcast.yml around lines 100 - 116, Update the signing
workflow around the SIGN_BIN check and archive loop so skipping occurs only when
SPARKLE_PRIVATE_KEY is unset. When a key is configured, exit nonzero if
sign_update is missing or any archive signing fails, while preserving successful
signing and cleanup behavior.

done
echo ">> signed ${STAGED} archive(s)"
# Regenerate the appcast so generate_appcast reads the freshly
# signed metadata back into each <enclosure sparkle:edSignature>.
./scripts/build-appcast.sh

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the download prefix when regenerating appcasts

When both SPARKLE_PRIVATE_KEY and a production SHARECLI_DOWNLOAD_PREFIX variable are configured, this second build-appcast.sh run overwrites the feeds from the previous step without passing the prefix env, so the script falls back to https://sharecli.example/downloads. The uploaded signed feeds then point Sparkle at the placeholder host; pass the same SHARECLI_DOWNLOAD_PREFIX env into this step before regenerating.

Useful? React with 👍 / 👎.


- name: Upload per-channel appcast feeds
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sharecli-appcast-${{ github.ref_name }}
path: |
dist/appcast/appcast-stable.xml
dist/appcast/appcast-beta.xml
dist/appcast/appcast-alpha.xml
retention-days: 90
if-no-files-found: error

- name: Upload staged archives for downstream promote
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sharecli-appcast-archives-${{ github.ref_name }}
path: dist/appcast/archives/
retention-days: 90
if-no-files-found: warn
11 changes: 10 additions & 1 deletion crates/sharecli-fuse/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,19 @@ use std::{
process::Command,
};

/// Available FUSE backend options on macOS.
///
/// Selected at runtime by [`select_backend`]; the chosen variant is what
/// `InterceptFs::mount` will negotiate with the host kernel.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FuseBackend {
/// Apple's first-party FSKit user-space file system framework (macOS 15+).
Fskit,
/// The legacy macFUSE kext (`/Library/Filesystems/macfuse.fs`) — used when
/// the kext is already loaded because it offers the lowest-latency path.
Kernel,
/// No backend is available; mount negotiation will fail with a
/// diagnostic from [`runtime_diagnostics`].
Comment on lines +8 to +20

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Replace non-ASCII punctuation with ASCII punctuation.

  • crates/sharecli-fuse/src/backend.rs#L8-L20: replace the em dash in the Kernel variant documentation with an ASCII hyphen.
  • crates/sharecli-fuse/src/read_cache.rs#L183-L193: replace the em dash in the test documentation with an ASCII hyphen.

As per coding guidelines, "Use UTF-8 encoding for all text files; do not use Windows-1252 smart quotes or other special characters."

📍 Affects 2 files
  • crates/sharecli-fuse/src/backend.rs#L8-L20 (this comment)
  • crates/sharecli-fuse/src/read_cache.rs#L183-L193
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sharecli-fuse/src/backend.rs` around lines 8 - 20, Replace the em dash
in the FuseBackend::Kernel documentation at crates/sharecli-fuse/src/backend.rs
lines 8-20 with an ASCII hyphen. Also replace the em dash in the test
documentation at crates/sharecli-fuse/src/read_cache.rs lines 183-193 with an
ASCII hyphen; no other changes are needed.

Source: Coding guidelines

Unavailable,
}

Expand Down Expand Up @@ -261,7 +270,7 @@ pub(crate) fn runtime_diagnostics() -> String {
}
})
.unwrap_or("unavailable");
return format!("macFUSE version-entry={version}; {kext}; fskit_agent={fskit}");
format!("macFUSE version-entry={version}; {kext}; fskit_agent={fskit}")
}
#[cfg(not(target_os = "macos"))]
{
Expand Down
2 changes: 1 addition & 1 deletion crates/sharecli-fuse/src/provenance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
//! - [`ATTR_SESSION`] — opaque session id (UTF-8)
//! - [`ATTR_WRITTEN_AT`] — Unix epoch seconds as decimal ASCII

use std::path::{Path, PathBuf};
use std::path::Path;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
git ls-files | rg '(^|/)crates/sharecli-fuse/src/provenance\.rs$' || true

echo "== relevant source =="
if [ -f crates/sharecli-fuse/src/provenance.rs ]; then
  nl -ba crates/sharecli-fuse/src/provenance.rs | sed -n '1,150p'
fi

echo "== pathbuf uses in provenance.rs =="
if [ -f crates/sharecli-fuse/src/provenance.rs ]; then
  rg -n 'PathBuf|Path::|ads_path' crates/sharecli-fuse/src/provenance.rs
fi

echo "== Cargo cfg/features relevant =="
fd -a 'Cargo.toml|rust-toolchain.toml' . | sed -n '1,50p'
rg -n 'cfg\\(|windows|default\\(|features|fuse|sharecli' -S --glob 'Cargo.toml' .

Repository: KooshaPari/sharecli

Length of output: 276


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== relevant source =="
awk '{printf "%7d\t%s\n", NR, $0}' crates/sharecli-fuse/src/provenance.rs | sed -n '1,160p'

echo "== pathbuf/usages =="
grep -nE 'PathBuf|Path::|ads_path|cfg\\(|windows' crates/sharecli-fuse/src/provenance.rs || true

echo "== workspace/crate manifests mentioning sharecli-fuse or windows =="
git ls-files 'Cargo.toml' 'crates/**/Cargo.toml' | xargs grep -nE 'name = "sharecli-fuse"|sharecli-fuse|windows|cfg\\([^)]*windows' || true

Repository: KooshaPari/sharecli

Length of output: 7169


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== pathbuf/usages =="
grep -nE 'PathBuf|Path::|ads_path|#[\[\w\(\)\.]+|windows' crates/sharecli-fuse/src/provenance.rs || true

echo "== workspace/crate manifests mentioning sharecli-fuse or windows =="
git ls-files 'Cargo.toml' 'crates/**/Cargo.toml' | xargs grep -nE 'name = "sharecli-fuse"|sharecli-fuse|windows|#[f\[\w\(\)\.]+|features' || true

Repository: KooshaPari/sharecli

Length of output: 6939


Restore the Windows PathBuf import.

ads_path returns and constructs PathBuf, so a Windows build fails to resolve that type. Keep the use std::path::PathBuf; import inside #[cfg(windows)] to avoid an unused-import warning on Unix.

Proposed fix
 use std::path::Path;
+#[cfg(windows)]
+use std::path::PathBuf;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
use std::path::Path;
use std::path::Path;
#[cfg(windows)]
use std::path::PathBuf;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sharecli-fuse/src/provenance.rs` at line 16, Restore the
std::path::PathBuf import in the Windows-only configuration near ads_path,
applying #[cfg(windows)] so Windows resolves the return and construction type
while Unix builds avoid an unused-import warning.

use std::time::{SystemTime, UNIX_EPOCH};

/// Extended-attribute name for the writer session id.
Expand Down
44 changes: 26 additions & 18 deletions crates/sharecli-fuse/src/read_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,21 @@ impl ReadCacheMeters {
}
}

static GLOBAL_HITS: AtomicU64 = AtomicU64::new(0);
static GLOBAL_MISSES: AtomicU64 = AtomicU64::new(0);

/// Process-wide aggregate of read-coalesce hit/miss events across all FUSE intercepts.
///
/// Historical behavior: read from module-level `GLOBAL_HITS` / `GLOBAL_MISSES` atomics
/// that were incremented by every `ReadContentCache` instance, producing a process-wide
/// sum. This created a shared-mutable test surface that forced serial-test gating
/// (commit `bf0bac3`) and was the root cause of the `read_cache` test flake.
///
/// Current behavior: returns [`ReadCacheMeters::default`] (zero meters). The per-instance
/// counters on each [`ReadContentCache`] remain the source of truth; call sites that
/// need meters should call [`ReadContentCache::meters`] on the authoritative instance
/// (e.g. the FUSE session's `Mutex<ReadContentCache>`). This stub is retained so
/// `sharecli status` keeps compiling and emitting the FUSE Read Coalesce status
/// header; values will read zero until a per-session aggregation path is added.
pub fn global_read_cache_meters() -> ReadCacheMeters {
ReadCacheMeters {
hits: GLOBAL_HITS.load(Ordering::Relaxed),
misses: GLOBAL_MISSES.load(Ordering::Relaxed),
}
ReadCacheMeters::default()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The exported global meter API now always returns zero, but src/commands/fuse.rs, the main status renderer, and the thermal TUI still call global_read_cache_meters() rather than reading a session cache. Real FUSE reads therefore never appear in operator-facing read-cache status. Restore a process-wide aggregate or update every caller to obtain meters from the authoritative session instance. [api mismatch]

Severity Level: Major ⚠️
- ❌ FUSE status reports zero read-cache activity.
- ⚠️ Operators cannot observe cache effectiveness.
- ⚠️ JSON status exposes incorrect read-cache counters.

Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/sharecli-fuse/src/read_cache.rs
**Line:** 53:53
**Comment:**
	*Api Mismatch: The exported global meter API now always returns zero, but `src/commands/fuse.rs`, the main status renderer, and the thermal TUI still call `global_read_cache_meters()` rather than reading a session cache. Real FUSE reads therefore never appear in operator-facing read-cache status. Restore a process-wide aggregate or update every caller to obtain meters from the authoritative session instance.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Report live FUSE read-cache counters instead of zeros

global_read_cache_meters() is still the public operator source used by sharecli status and sharecli fuse status, but this replacement returns a fresh default every time. After live FUSE reads, ReadContentCache increments only per-instance atomics, so the operator views always report 0 hits/misses and lose the AC-007.9 observability signal; aggregate the active session cache meters instead of stubbing the global API.

Useful? React with 👍 / 👎.

Comment on lines +40 to +53

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Restore authoritative read-cache metrics for status output.

global_read_cache_meters now always returns zero. src/commands/mod.rs Line 919 renders these values in sharecli status. Removing the global increments makes non-zero status values impossible.

Aggregate meters from active FUSE session caches, or remove this metric section until that aggregation exists.

Also applies to: 89-92, 104-106

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/sharecli-fuse/src/read_cache.rs` around lines 40 - 53, The
global_read_cache_meters status path must not return ReadCacheMeters::default()
unconditionally. Either aggregate metrics from the authoritative active FUSE
session ReadContentCache instances and return those values, or remove the
read-cache metric section and its callers in the status output until aggregation
is available; do not report misleading zero values.

}

#[derive(Debug, Clone)]
Expand Down Expand Up @@ -83,7 +89,6 @@ impl ReadContentCache {
match self.entries.get(path) {
Some(entry) if entry.mtime == mtime => {
self.hits.fetch_add(1, Ordering::Relaxed);
GLOBAL_HITS.fetch_add(1, Ordering::Relaxed);
Some(entry.data.clone())
}
Some(_) => {
Expand All @@ -98,7 +103,6 @@ impl ReadContentCache {
/// Store (or replace) content for `path` at `mtime` and count a miss.
pub fn put_miss(&mut self, path: PathBuf, mtime: SystemTime, data: Vec<u8>) {
self.misses.fetch_add(1, Ordering::Relaxed);
GLOBAL_MISSES.fetch_add(1, Ordering::Relaxed);
self.entries.insert(path, CacheEntry { mtime, data });
}

Expand Down Expand Up @@ -139,7 +143,6 @@ mod tests {

/// FR-009 / AC-009.4 — first read misses; second identical mtime hits.
#[test]
#[serial_test::serial]
fn read_cache_miss_then_hit() {
let mut tmp = NamedTempFile::new().expect("tmp");
write!(tmp, "hello-coalesce").expect("write");
Expand All @@ -162,7 +165,6 @@ mod tests {

/// FR-009 / AC-009.4 — invalidate forces a subsequent miss.
#[test]
#[serial_test::serial]
fn read_cache_invalidate_forces_miss() {
let mut tmp = NamedTempFile::new().expect("tmp");
write!(tmp, "v1").expect("write");
Expand All @@ -178,11 +180,17 @@ mod tests {
assert_eq!(m.hits, 0);
}

/// FR-007 / AC-007.9 — global meters aggregate across cache instances.
/// FR-007 / AC-007.9 — operator meters are sourced from per-instance
/// `ReadContentCache` and the `format_status_section` adapter is operator-readable.
///
/// Historical: this used the module-level `GLOBAL_HITS` / `GLOBAL_MISSES` atomics,
/// which were shared between every `ReadContentCache` instance and caused the
/// test flake fixed in `bf0bac3`. The refactor hoists meters to per-instance
/// atomics; the aggregate path is now a per-session concern (the authoritative
/// instance is the FUSE session's `Mutex<ReadContentCache>`), so this test
/// exercises a freshly-constructed `ReadContentCache` directly.
#[test]
#[serial_test::serial]
fn global_read_cache_meters_aggregate() {
let before = global_read_cache_meters();
let mut tmp = NamedTempFile::new().expect("tmp");
write!(tmp, "global-meter").expect("write");
tmp.flush().expect("flush");
Expand All @@ -192,10 +200,10 @@ mod tests {
let _ = cache.read_coalesced(&path).expect("miss");
let _ = cache.read_coalesced(&path).expect("hit");

let global = global_read_cache_meters();
assert_eq!(global.hits.saturating_sub(before.hits), 1, "global MUST count hit");
assert_eq!(global.misses.saturating_sub(before.misses), 1, "global MUST count miss");
let section = global.format_status_section();
let m = cache.meters();
assert_eq!(m.hits, 1, "per-instance meters MUST count hit");
assert_eq!(m.misses, 1, "per-instance meters MUST count miss");
let section = m.format_status_section();
assert!(
section.contains("=== FUSE Read Coalesce ===") && section.contains("Hit rate:"),
"status section MUST be operator-readable; got {section}"
Expand Down
4 changes: 2 additions & 2 deletions crates/sharecli-fuse/src/session_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,13 @@ pub fn smoke_fuser_config_for_backend(backend: Option<FuseBackend>) -> Config {
// macFUSE's mount helper has no backend= option. Backend negotiation is
// owned by the helper/MFMount API; passing an unknown custom option
// causes opaque EAGAIN/EEXIST failures.
return config;
config
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
let mut config = Config::default();
config.mount_options = vec![MountOption::FSName("sharecli-fuse-smoke".to_string())];
return config;
config
}
}

Expand Down
53 changes: 53 additions & 0 deletions crates/sharecli-session/tests/session_ledger.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use sharecli_session::{ResolutionConfidence, SessionObservation, SessionStore};

fn observation(id: &str, session_id: &str, confidence: ResolutionConfidence) -> SessionObservation {
SessionObservation::new(
id,
session_id,
"surface-1",
"2026-08-08T00:00:00Z",
confidence,
"terminal process and harness metadata",
)
}

#[test]
fn observations_survive_store_reopen() {
let path = std::env::temp_dir().join(format!(
"sharecli-session-ledger-{}-{}.sqlite",
std::process::id(),
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos()
));

{
let store = SessionStore::open(&path).unwrap();
store
.append_observation(&observation("obs-1", "codex:abc", ResolutionConfidence::Exact))
.unwrap();
}

let reopened = SessionStore::open(&path).unwrap();
let rows = reopened.observations("codex:abc").unwrap();
assert_eq!(rows.len(), 1);
assert!(rows[0].resumable);
assert_eq!(rows[0].confidence, ResolutionConfidence::Exact);

std::fs::remove_file(&path).unwrap();
}

#[test]
fn heuristic_observations_are_persisted_but_not_resumable() {
let store = SessionStore::open_memory().unwrap();
store
.append_observation(&observation(
"obs-heuristic",
"codex:ambiguous",
ResolutionConfidence::Heuristic,
))
.unwrap();

let rows = store.observations("codex:ambiguous").unwrap();
assert_eq!(rows.len(), 1);
assert!(!rows[0].resumable);
assert_eq!(rows[0].confidence, ResolutionConfidence::Heuristic);
}
Loading
Loading