Skip to content

fix: recover selection features lost from main by the PR #13 merge#18

Merged
Ghost-Frame merged 8 commits into
mainfrom
feat/recover-selection-features
Jul 6, 2026
Merged

fix: recover selection features lost from main by the PR #13 merge#18
Ghost-Frame merged 8 commits into
mainfrom
feat/recover-selection-features

Conversation

@Ghost-Frame

Copy link
Copy Markdown
Owner

Summary

The PR #13 -s ours merge took feat/intelligent-selection wholesale, but the stacked feat/land-m3 branch (PRs #10/#11) was never merged into it, so main silently lost a full layer of shipped, previously CI-green work. This PR recovers it on top of current main without touching the newer PR #15/#16 surfaces (registry install, telemetry endpoint, memory health, publish metadata).

Recovered (cherry-picks from feat/land-m3)

  • Git working-set language weighting in context sensing
  • Project manifest dependency parsing into selection (capped additive bonus)
  • Detected build frameworks scored in selection
  • frameshift use records preference feedback (learn-from-use)
  • A1: daemon learns from manual persona overrides (record_override + adopt_active)
  • A2 Phase 1: dependency-free semantic scaffold (Embedder trait, cosine similarity, semantic score channel)
  • Download counter: download_pack_bytes increments total_downloads again

Recovered (ported, final commit)

M3 hardening that the merge also dropped: symlink-safe project walk, case-insensitive anti-keyword matching, capped streaming audit-log load, atomic preferences save, and honoring min_confidence/switch_margin in the switch controller.

Verification

scripts/preflight.sh green: rustfmt, clippy -D warnings, full workspace test suite (64 binaries, 0 failures), cargo audit with the documented ignores.

🤖 Generated with Claude Code

Context sensing previously ranked personas against a static whole-repo
file-extension census, which a file-count-dominant language can swamp.
sense() now also consults the active git working set (staged, unstaged,
and untracked changes via `git status --porcelain`) and boosts those
languages above the census, so selection favors what is being edited
right now: a polyglot repo where you are editing Rust no longer ranks a
JavaScript-heavy persona first.

Best-effort and dependency-free. A non-git directory or a missing git
binary yields no boost and prior behavior is unchanged. The boost weight
(1.5) sits between the census ceiling (1.0) and the prose sentinel (2.0),
so it never overrides an explicit prose-writing signal.

Dependency-manifest parsing (dep names to task tokens) is the next
increment; it needs toml/serde_json on the currently serde-only crate.

cargo test -p frameshift-orchestrator: 77 passed, 0 failed (6 new);
clippy -D warnings clean.
Local rustfmt wraps the success arm into a block; CI rustfmt collapses it
onto one line. A two-statement block with a named stdout binding is stable
across both rustfmt versions and reads clearer.
Context sensing now parses root manifests (Cargo.toml, package.json) for
dependency names and exposes them as ContextSignal.context_tokens. The
policy scores them as a small capped additive bonus when they match a
persona's keywords, so framework-level signals (axum vs actix, react vs
vue) sharpen selection. Dependency-free line-based parsing keeps the
orchestrator crate serde-only.

The bonus is deliberately kept out of the lexical IDF channel: folding
dependency names into task_tokens would inflate the IDF denominator with
high-weight non-matching deps and dilute genuine task-token matches. As
an independent additive term (cap 0.12) it can only raise a persona that
matches project dependencies, never redistribute the primary weights.

cargo test -p frameshift-orchestrator: 84 passed, 0 failed (7 new);
clippy -D warnings clean.
ContextSignal.frameworks (cargo/npm/go/python, detected from marker files)
was populated by context sensing but never read by the scorer. Route it
through the existing capped context-signal bonus alongside dependency
tokens, so a persona whose keywords name a project's build framework gets
the same small boost. No new fields, no weight change.

cargo test -p frameshift-orchestrator: 85 passed, 0 failed (1 new);
clippy -D warnings clean.
`frameshift use <name>` activated a persona but never recorded a
preference, so the orchestrator's bias machinery never learned from manual
choices. After a successful activation, record the persona into the shared
automate-prefs.json (the same store `select`, the daemon, and the `prefs`
command read), nudging future automatic selection toward personas the user
actually picks.

Best-effort: a preferences load/save failure warns but does not fail the
command, since activation has already succeeded.

cargo test -p frameshift-cli use_persona: 2 passed; clippy and fmt clean.
The catalog `total_downloads` counter shown on the marketplace was never
incremented by any route: the download handler only recorded a `pack_downloads`
event (which feeds the 7-day Trending ranking), while the counter that backs
`total_downloads` is bumped exclusively by `increment_download_counter`, which
no handler called. As a result every pack reported 0 total downloads.

Wire `increment_download_counter` into `download_pack_bytes` alongside the
existing `record_download` call, as a best-effort step that warns and continues
on failure so a download the client already received is never failed. Document
why the signed `/dl/{hash}` path cannot increment the counter (it has only the
content hash, not name/version; the official client uses the direct route).

Add an integration test asserting a successful download increments the counter.
…ing scaffold

Two sharpen-arc slices for automate-mode persona selection.

A1 -- daemon learns from manual overrides. evaluate_and_apply now compares the
SwitchController's tracked auto-pick against the on-disk active marker before
deciding. When they diverge (the user switched by hand), it rewards the chosen
persona and decays the rejected auto-pick via Preferences::record_override,
persists the shared automate-prefs.json, and re-baselines the controller through
the new SwitchController::adopt_active so the same override is not re-learned on
every tick. A new SwitchController::active_persona exposes the tracked pick.

A2 phase 1 -- dependency-free semantic scoring scaffold. A new embed module
defines the Embedder trait plus cosine_similarity/semantic_similarity. The scorer
gains a semantic ScoreComponents channel: rank_with_embedder folds a cosine
bonus (task text vs persona text, scaled by SEMANTIC_WEIGHT) additively into the
blend, and select_with_embedder threads an optional embedder through selection.
rank/select keep their signatures and delegate with no embedder, so the semantic
channel is 0.0 by default and there is zero behavior change. A real embedding
engine and model distribution are phase 2, deferred pending sign-off.

clippy -D warnings clean; 92 orchestrator + 13 daemon tests pass, including a
manual-override regression test and semantic-channel tests with a mock embedder.
Ports the remaining feat/land-m3 content that the PR #13 merge dropped:
symlink-safe project walk, case-insensitive anti-keyword matching,
capped streaming audit-log load, atomic preferences save, and honoring
min_confidence/switch_margin in SwitchPolicy and SwitchController.
@Ghost-Frame
Ghost-Frame merged commit 9faa3d5 into main Jul 6, 2026
5 checks passed
@Ghost-Frame
Ghost-Frame deleted the feat/recover-selection-features branch July 6, 2026 16:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant