Skip to content

New folders sync by default; one gate accepts the lot (LLP 0200/0201) - #675

Closed
bgmcmullen wants to merge 4 commits into
masterfrom
folders-sync-by-default-and-express-gate
Closed

New folders sync by default; one gate accepts the lot (LLP 0200/0201)#675
bgmcmullen wants to merge 4 commits into
masterfrom
folders-sync-by-default-and-express-gate

Conversation

@bgmcmullen

@bgmcmullenbgmcmullen commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Two changes to the same complaint: setup asks too much, and one of its questions comes back every session.

The per-folder ask becomes opt-in (LLP 0200)

The session-start classification prompt (LLP 0106) fires in every unclassified folder on an enrolled machine. For someone working across many repos whose answer is "sync" every time, that is a question per session, spending the opening turn on a decision they already made in general.

A machine-local preference (<state>/usage-policy/folder-ask.json) now decides whether it happens, and its default is sync: the folder keeps the implicit default it already had, and nobody is interrupted. The ask still exists for people who want it.

This gates the question only. No class moves, .hypignore is untouched, folders already marked local-only/ignore stay so, and nothing new leaves a machine that was not already syncing by default (LLP 0188 #rule). What the ask ever added was a prompt, never an enforcement.

It is a real default change, so it is not silent about itself:

  • hyp status names the mode on every enrolled run.
  • hyp policy folders [ask|sync] [--json] is the standing control, sibling of hyp policy client.
  • hyp policy list names it when it is not the default.
  • The classification prompt now teaches its own off switch, so "stop asking me this" is answerable in the session that asked.

The wizard asks for it in its own step, after the per-adapter sync lane rather than as a row on it. That lane answers "which adapters ship"; this one answers "what happens the next time I work somewhere new". Folding the second into the first let a per-adapter checklist decide a per-folder policy silently.

Step 4 of 5 · Choose how new folders are handled
New folders you work in:
Folders you already marked keep their class either way.
> Sync them all
No question at session start; mark exceptions with hyp policy set
Ask me about each new folder
A session opened somewhere new asks once: sync, local-only, or ignore

An express gate up front (LLP 0201)

Every lane already opens with a defaults gate, so the common run was a sequence of bare enters whose answers were knowable before the first one. One question now accepts all of them:

HypAware found these on this machine:
Claude Code · managed by your fleet
Codex
> Record and sync all of these
Configures each to record through HypAware; new folders sync too.
Let me choose

The list is the explanation - an earlier draft asked "Set up with defaults?" over four lines paraphrasing them, which read as policy text.

Accepting skips the prompts, never the statements. Each lane still narrates the block its gate would have shown, so the fast path prints the same rows in the same order, minus the keypresses - which is what keeps it inside LLP 0188 #never-silent:

HypAware will record:
Claude Code · managed by your fleet
Codex
These will sync to your server:
Claude Code · managed by your fleet
Codex
New folders you work in:
Folders you already marked keep their class either way.
Syncing them all; change later with hyp policy folders ask

The rows come from the pick lane's own computation (hoisted to resolvePickSeeding + defaultRowLabels), with detection run once per wizard run and shared with the lane, so the two screens cannot disagree about what "all of these" means. With nothing detected and nothing locked there is nothing to accept, so no gate is shown and the pick lane opens its menu as before. Escape backs to the fork, ctrl+c cancels at 130, unenrolled runs drop the two server claims, and non-interactive runs never see it.

Also

The config overwrite confirm is laid out instead of arriving as one paragraph with the question buried at the end - same facts, same order.

Verification

  • npm test: 3765 pass, 1 fail (usage_policy.fold_tightened, fails identically on a clean checkout)
  • npm run typecheck: clean
  • Smokes: walkthrough_picker_to_first_query, status_diagnostics, cli_bundled_plugins_activated, daemon_foreground_start_stop green
  • Every screen rendered from the real code paths, express and step-by-step; the back-navigation end-to-end walks fork → express → pick → back → fork → express → pick → sync → folders through the real prompts

New coverage: the express gate and all three autoAccept arms, the new-folder lane, the preference store, the flipped hook decision, hyp policy folders, and the status line.

🤖 Generated with Claude Code

Two changes to the same complaint: setup asks too much, and one of its
questions comes back every session.
LLP 0200 makes the per-folder classification ask (LLP 0106) opt-in. A
machine-local preference decides whether an unclassified folder is asked
about at session start, and its default is `sync` - the folder keeps the
implicit default it already had and nobody is interrupted. The ask still
exists for people who want it, chosen in the wizard's new-folder step or
with `hyp policy folders ask`. This gates the question only: no class
moves, `.hypignore` is untouched, and nothing new leaves a machine that
was not already syncing by default. `hyp status` names the mode on every
enrolled run so it is never a silent state, and the classification prompt
itself now teaches its own off switch.
The new-folder question is its own wizard step, after the per-adapter
sync lane rather than a row on it: that lane answers "which adapters
ship" and this one answers "what happens the next time I work somewhere
new", and folding the second into the first let a checklist decide a
policy silently.
LLP 0201 adds an express gate before the question lanes: it lists what
was found and offers "Record and sync all of these" or "Let me choose".
Accepting skips the prompts, never the statements - each lane still
narrates the block its gate would have shown, so the fast path prints the
same rows in the same order, minus the keypresses. The rows come from the
pick lane's own computation (hoisted to `resolvePickSeeding`), with
detection run once per wizard run, so the two screens cannot disagree
about what "all of these" means. With nothing detected and nothing locked
there is nothing to accept, so no gate is shown.
Also: the overwrite confirm is laid out instead of run together as one
paragraph, same facts and order.
npm test: 3765 pass, 1 fail (usage_policy.fold_tightened, identical on a
clean checkout). npm run typecheck: clean. Smokes
walkthrough_picker_to_first_query, status_diagnostics,
cli_bundled_plugins_activated, daemon_foreground_start_stop: green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bgmcmullenbgmcmullen added the neutral:adopt Foreign PR adopted into neutral's reconcile scope label Aug 7, 2026
…shows
CI failed on `runInitWizard end-to-end`: it read the real machine for
detection, and the express gate (LLP 0201) is shown only when there is
something to accept. A developer laptop with Claude installed gets the
gate; a CI runner with no AI clients does not, so every scripted answer
after the fork landed on the wrong prompt and the run finished on the
team pathway it was supposed to step back out of.
The rows themselves were never what the test was about ('all' picks
whatever the menu offers); that there *are* rows is. So detection is
injected like every other seam in that call, and the script is stable on
any host.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@philcunliffephilcunliffe added the neutral:adopted Adoption completion record: merged while carrying neutral:adopt (LLP 0031) label Aug 7, 2026
Escape at the pick lane reached past the new gate to the fork, and escape
at the sync lane landed on the gate instead of the picker: the question was
inserted between the fork and the pick lane without being given a loop
level, so both of its neighbours overshot by one screen. LLP 0191
#back-edges is that escape steps exactly one screen back.
The gate now owns `atExpress` between `atFork` and `atPick`. Pick backs to
it when the pass showed one and to the fork when it did not (LLP 0201
#no-default-no-accept), and a sync back re-runs the picker without
re-asking it. Three tests pin the chain; the end-to-end walk gains the
keypress the corrected edge needs.
Also corrects three doc blocks left over from a draft where `ask` was the
default. `src/core/usage-policy/types.d.ts` and `src/core/daemon/types.d.ts`
ship in the published declaration build, so they were telling consumers the
opposite of both the code and LLP 0200 #default about a privacy-relevant
default; `usage-policy/index.js` said the same and attributed the write to
the sync lane, which LLP 0200 #wizard explicitly rejects. Its export block
had also been spliced between the client-sync comment and the export that
comment describes.
`hyp policy list` no longer drops "no machine-local entries (policy store:
...)" on a store that is empty while the folder mode is non-default; the
mode is an addition to that line, not a replacement for it.
Co-Authored-By: Claude <noreply@anthropic.com>
@philcunliffe

Copy link
Copy Markdown
Contributor

neutral review - round 1

Verdict: findings, all fixed and pushed. Reviewed 46e8f0b (head at review time). CI was already green there (test 22/24, typecheck 22/24, duplicate-numbers). Fixes landed as 125f92e.

This is a well-built change. LLP 0200 and 0201 are written before the code, the forward-refs on 0106 and 0190 are there, the @ref annotations say things the filenames do not, resolvePickSeeding/defaultRowLabels genuinely close the "two derivations of the defaults" hole 0201 #gate worries about, and the user-facing docs (README, PRIVACY, both skill pairs) all state the new default correctly. The findings below are a back-edge defect and a set of doc blocks left behind by an earlier draft.


1. The express gate joins the forward chain but not the back chain - Medium

src/core/cli/wizard/index.js:327 put the gate insideatPick: rather than giving it a loop level. The gate became a screen between the fork and the picker on the way forward, but neither neighbour's back edge knew about it, so both overshot by exactly one screen. LLP 0191 #back-edges is that escape steps one screen back, and LLP 0201 #edges opens with "it behaves like any other wizard question".

Confirmed by driving the real orchestrator with the repo's own harness (lane call order, express gate shown):

escape at pick fork -> express -> pick -> FORK -> express -> pick (wanted: back to the gate)
escape at sync fork -> express -> pick -> syncScope -> EXPRESS -> pick (wanted: back to the picker)
escape at folders fork -> express -> pick -> syncScope -> folderAsk -> syncScope (correct)

Two user-visible consequences on the step-by-step path: escape at the picker throws away the pathway answer and re-asks the fork, and a user at the sync lane who wants to change their picks gets the express gate instead of the picker.

The existing tests could not catch either, because both filter the gate out of the assertion - back_navigation.test.js:366 filters to fork|pick and :385 to pick|syncScope.

Fixed. The gate now owns atExpress: between atFork: and atPick:. Pick backs to it when the pass showed one and straight to the fork when it did not (LLP 0201 #no-default-no-accept), and a sync back re-runs the picker without re-asking it. Three tests pin the chain, including the no-gate case; the end-to-end walk at :699 gained the second b the corrected edge needs. LLP 0201 #edges now states the edge in both directions and LLP 0191 gained the Extended-by: forward-ref it was missing (0190 and 0106 both got theirs).

2. Three doc blocks assert the opposite of the shipped default - Medium

Leftovers from a draft where ask was the default. Two of them are .d.ts files that go out through npm run build:types, so consumers of the published package were being told the wrong thing about a privacy-relevant default:

  • src/core/usage-policy/types.d.ts:112 - "ask is the default and the behavior every machine had before the preference existed", and :123 "File absence means ask". Both contradict DEFAULT_FOLDER_ASK_MODE = 'sync' (folder_ask.js:29) and LLP 0200 #default.
  • src/core/daemon/types.d.ts:273 - "ask is the default ... sync means the user retired that question".
  • src/core/usage-policy/index.js:65 - "Absence reads as ask, so a machine that never set it behaves exactly as before", plus "Written by the wizard's sync lane", which is the exact folding LLP 0200 #wizard rejects by name.

Fixed, all three rewritten to match the code and LLP 0200 #default/#suppression/#wizard.

3. The folder-ask export block split a comment from its export - Low

src/core/usage-policy/index.js:63 spliced the new block between the LLP 0188 client-sync comment and the export { clientSyncListPath, ... } it describes, so the client-sync export ended up undocumented and the folder-ask export inherited a comment about a different store. Fixed by moving the block below.

4. hyp policy list drops a line it used to print - Low

src/core/commands/policy.js:735 made the folder-ask line a replacement for no machine-local entries (policy store: ...) rather than an addition, so a machine with the ask on and an empty store lost the store path. The non-empty branch at :748 correctly prints both. Fixed to print both; no comment explained the asymmetry, so I read it as an oversight.


Not actionable, noted only

  • expressRowsSafe (index.js:851) casts to /** @type {any} */ to call resolvePickSeeding with a subset of RunWizardPickOptions. It is the one call that has to stay structurally in step with the pick lane, so it is the call least well served by an any. A narrow PickSeedingInput interface that RunWizardPickOptions satisfies structurally would close it. Left alone as a judgement call rather than a defect.
  • Accepting express overwrites a standing ask with sync. That is stated on the accept row ("new folders sync too") and narrated by the lane, so it is inside LLP 0188 #never-silent, but it is worth being deliberate that it does not round-trip the way LLP 0200 #wizard's step-by-step re-run does.

Checks

npm test 3765 pass / 0 fail, npm run typecheck clean, in a fresh worktree at the pushed head. No em dashes, no semicolons, no inline import('...') types, type-import specifiers repo-root-anchored.

…/0201)
The sync lane asks nothing when everything picked is fleet-locked, and
nothing when the client store is corrupt: it states its outcome and
returns. The new-folder lane behind it backed unconditionally into it, so
escape re-ran the statement and re-asked the same question. On a fully
managed machine that is a trap with no exit but ctrl+c.
The lane now reports `noQuestion` on both non-prompting returns and the
orchestrator steps past it to the picker, which is the last screen the user
could answer.
Same class one step earlier: the express gate lists the picker's own
confirmed defaults, so a confirmed empty selection leaves it nothing to
show. A back out of the re-entered picker landed on a gate that could not
render and fell forward into the picker again. It now reaches the fork.
Also: the pick lane re-derived `defaultRows` instead of taking the
`resolvePickSeeding` value the express gate reads, which is the second
derivation of "the defaults" LLP 0201 #gate says must not exist.
Co-Authored-By: Claude <noreply@anthropic.com>
@philcunliffe

Copy link
Copy Markdown
Contributor

neutral review - round 2 (final)

Verdict: findings, all fixed and pushed. Reviewed 125f92e (round 1's own fixes included, and reviewed no more gently than the contributor's). Fixes landed as a0420ee. npm test 3768 pass / 0 fail, npm run typecheck clean, in a fresh worktree at the pushed head.

Round 1's work holds up. I re-derived the back chain empirically rather than reading it off the diff, driving the real orchestrator with the repo's harness for eight scenarios (escape at folders, at sync, at pick with and without a gate, at the gate itself, pick-then-gate, the full folders-to-fork walk, and an express accept). Every one of them steps exactly one screen:

folders -> sync fork join express pick syncScope folderAsk syncScope folderAsk
sync -> pick fork join express pick syncScope pick syncScope folderAsk
pick -> express fork join express pick express pick syncScope folderAsk
express -> fork fork join express fork express pick syncScope folderAsk
full walk -> fork ... folderAsk syncScope pick express fork express pick ...

The atExpress restructuring, the expressShown gate on pick's back edge, the three doc-block corrections, the moved export block and the policy list addition are all correct as landed. What round 2 found is one screen further along the same chain, in the contributor's new lane.


1. Escape at the new-folder lane is a trap on a fleet-managed machine - Medium

src/core/cli/wizard/index.js:521 backed unconditionally into the sync lane. But the sync lane is not always a screen: sync_scope.js:78 returns after printing Everything you picked is managed by your fleet and always syncs. when candidates is empty, and :71 warns and returns when the client store is unreadable. Neither prompts.

So on a fully fleet-managed machine - every picked row locked, nothing left to opt out of, which is the ordinary shape for a managed install - escape at Choose how new folders are handled re-ran the sync lane's statement and re-asked the same question. Forever. There is no exit but ctrl+c (which abandons the run at 130) or answering the question the user was trying to step away from.

Driven against the real runWizardSyncScope, escaping seven times:

calls: fork express pick folderAsk folderAsk folderAsk folderAsk folderAsk folderAsk folderAsk
stdout: "Step 3 of 5 · Choose what syncs / Everything you picked is managed by your
fleet and always syncs. / Claude Code" x7

This is the same defect class round 1 fixed one screen earlier - a back edge pointed at something that is not a screen - and it was invisible for the same reason: back_navigation.test.js had no case where the sync lane declines to ask.

Fixed. The lane now reports noQuestion: true on both of its non-prompting returns (sync_scope.js:71, :87) and the orchestrator steps past it: if (syncScope.noQuestion) continue atPick (index.js:549). Past a lane that only narrated, the last screen the user could answer is the picker. Two tests pin it, one for each side of the branch. LLP 0200 #wizard (Draft) gained the paragraph stating the edge.

2. A back out of the picker can land on an express gate that cannot render - Low

index.js:446's if (expressShown) continue atExpress is right, but the pass it re-enters recomputes the gate's rows from pickSeed - and pickSeed is the picker's last confirmed selection. Confirm an empty selection, back out of the sync lane, then escape at the re-entered picker: the gate now has nothing to list, expressRowsSafe returns [], no gate is shown, and control falls forward into the picker again. The escape is a redraw; it takes a second one to reach the fork.

Reproduced: fork express pick syncScope pick pick syncScope folderAsk - the second pick back produced a third pick.

Self-healing after one extra keypress, hence Low, but it is the same rule (escape reaches the previous screen) and the fix is three lines. Fixed with a backFromPick flag (index.js:348, :394, :463): a back into a gate that this pass cannot show continues to the fork, which is exactly what LLP 0201 #no-default-no-accept already says the edge is when no gate exists. LLP 0201 #edges (Draft) now covers the re-entered pass explicitly.

3. Two derivations of "the defaults", in the two screens LLP 0201 says must not disagree - Low

resolvePickSeeding returns defaultRows and the express gate reads it (index.js:885), but promptPickSelection re-derived the identical expression at pick.js:469 for the pick gate. They agree today by construction, which is why nothing failed; LLP 0201 #gate is nonetheless explicit that "Two derivations of 'the defaults' could disagree, and the screen that accepts them all at once is the worst place for that." Fixed: defaultRows is threaded from the seeding, so there is one definition.


Deliberately left (carried forward from round 1, re-examined, not worse)

  • expressRowsSafe casts to any (index.js:889). Confirmed runtime-safe: resolvePickSeeding touches only env, catalog, locked, initialSelection, picks and detect, all of which are supplied or optional, and it performs no writes. It stays a typing hole on the invariant the gate protects; a narrow PickSeedingInput interface that RunWizardPickOptions satisfies structurally would close it. Finding 3 narrows the blast radius a little (the two screens now share the value, not just the expression).
  • Accepting express overwrites a standing ask with sync.folder_ask.js:86 writes DEFAULT_FOLDER_ASK_MODE, not before, on the auto-accept arm. Disclosed twice on that path (the accept row's new folders sync too, and the lane's own narration), so it is inside LLP 0188 #never-silent. Worth a human's eye because there is a real tension with LLP 0201 #gate's framing - the gate "accepts every default the wizard would ask for", and on a re-run the default the wizard would ask for is the standing answer (LLP 0200 #wizard: "A re-run defaults to the standing answer, so re-entering the wizard round-trips the preference"). It does not round-trip through express. Left as the contributor's call.

Also checked, clean

  • hyp init --dry-run and --yes never reach the folders lane (init.js always sets picks, so interactive is false); no store is written on a scripted or dry run.
  • All 20 new @ref anchors resolve; 0200#{store,default,machine-local,suppression,wizard,cli,escape-hatch,fail-safe} and 0201#{gate,narrate,no-default-no-accept,edges} all exist.
  • The hook's read order matches LLP 0200 #suppression (mode after the enrolled check, before every per-folder reason), and reason correctly stops reporting resolve-error over ask-disabled.
  • readFolderAskModeSafe falls to ask on a corrupt present file in both the hook and hyp status, so the two agree about what is in force; the CLI surfaces fail loudly instead (LLP 0200 #fail-safe).
  • Both client hooks consume only evaluation.prompt, so the new ask-disabled reason needs nothing from them.
  • No em dashes, no statement semicolons, no inline import('...') types, no @typedef; type-import specifiers repo-root-anchored in .js, relative .d.ts inside .d.ts as the rest of those files do.

Checks

npm test 3768 pass / 0 fail (3765 before, +3 new). npm run typecheck clean. Both new back-edge tests were proved to fail against the unfixed orchestrator (not ok 18, not ok 20) and pass with it. All four fixes verified in the pushed blob at origin/folders-sync-by-default-and-express-gate (a0420ee).

This was the last review round; the ladder caps at 2.

@philcunliffephilcunliffe added the neutral:changes-requested neutral reviewed an adopted PR and requests changes (non-binding; maintainer decides) label Aug 7, 2026

@philcunliffephilcunliffe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review capacity for this PR is spent (2 rounds, the ladder's cap), and the current head a0420ee carries neutral's own round-2 work, which has not itself been reviewed. Handing the remainder back rather than deferring it - this is your PR, so the residual calls are yours.

What the two rounds did. Round 1 (head 46e8f0b) found 4 findings and fixed them. Round 2 (head 125f92e) found 3 more and fixed them. Round 1's own work held up under re-examination: the atExpress loop level, the doc-block corrections, the moved export block and the policy list addition were all re-derived empirically against the repo's back_navigation.test.js harness across eight scenarios, and every documented edge steps exactly one screen. Suite at the current head: 3768 passing, 0 failing; typecheck clean.

The round-2 finding worth your attention.src/core/cli/wizard/index.js:521 - escape at the new-folder lane was an inescapable trap on a fleet-managed machine. The folders lane backed unconditionally into the sync lane, but the sync lane presents no prompt when every picked row is fleet-locked (sync_scope.js:78) or the client store is corrupt (:71). Escaping re-ran its statement and re-asked the same question indefinitely; the only exits were ctrl+c or answering. Reproduced against the real runWizardSyncScope: 7 escapes, 7 identical redraws.

That is the same defect class round 1 fixed one screen earlier, and it stayed invisible for the same reason: no test exercises the case where that lane declines to ask. If you add screens to this chain, that gap is worth closing structurally rather than case by case.

What needs your decision:

  1. Express accept overwrites a standing ask with sync. Disclosed on the accept row (twice), so it is not silent and stays inside LLP 0188 #never-silent. But it does not round-trip the way LLP 0200 #wizard's step-by-step re-run promises. That tension is real and is a product call, not a defect - left for you.

  2. expressRowsSafe casts to any to call resolvePickSeeding. Confirmed runtime-safe in both rounds, but it is a typing hole on precisely the invariant LLP 0201 #gate exists to protect. A narrow PickSeedingInput interface would close it.

  3. The current head is unreviewed.a0420ee is neutral's own round-2 work. Given round 2 found a Medium defect adjacent to round 1's fix, that head deserves your eyes rather than an automatic pass.

Cross-PR conflict that no single PR's checks can see.origin/master stops at LLP 0199, and three in-flight PRs each add a differentllp/0200-*.decision.md:

  • #6740200-hidden-picker-rows.decision.md
  • #675 (this one) 0200-folder-ask-is-a-preference.decision.md
  • #6770200-setup-offers-the-first-sync.decision.md

duplicate-numbers passes for each in isolation and will fail for the second and third to merge. Renumbering is mechanical under LLP 0156, but which doc keeps 0200 is your call, so neutral has not renumbered anyone's branch.

Both review records are in this thread with full file:line evidence. Nothing here is a known production defect - this is an exhausted-ladder handback.

Push to the branch or reply here and neutral will re-engage on its next tick.

bgmcmullen added a commit that referenced this pull request Aug 8, 2026
…, LLP 0200 collision renumbered, cross-PR seams closed (#679)
* A picker row can be hidden from the menu without ceasing to be a source (LLP 0200)
The `raw-anthropic` / `raw-openai` wizard rows cost every first-run user a
question and earn it back for almost nobody. Beside a client row they compose
byte-identical config (claude already contributes the `anthropic` upstream,
codex `openai` + `chatgpt`, and the fold dedupes by name), so their checked
state is cosmetic. Alone they capture nothing at all: projection is
adapter-owned, `@hypaware/ai-gateway` registers no projector of its own, and
the default `config` boot profile activates only what the config names - so a
raw-only install proxies traffic correctly and writes zero rows.
Add `hidden` to the `contributes.picker` contract rather than a row-id list in
core, which would undo what LLP 0130 settled about who owns the picker table.
`visiblePickerDescriptors` is the one display filter, used by the legacy
prompt and by the wizard's gate and menu.
Hiding is a display filter, never a catalog deletion. The owner map arming
LLP 0192's fail-closed withholding is folded from picker descriptors, so
deleting the two `contributes.picker` blocks would give `ai_gateway_messages`
an empty owner list - which both withhold rules read as "never withhold",
turning off a privacy guard under cover of a UI cleanup. The descriptors stay.
Carry-through is narrower than it first looks. A hidden row rides through the
selection only when the config on disk collects nothing the menu can show (a
`--source raw-anthropic` install being reconfigured). Carrying on seed
membership was tried and is wrong: seeding is derivative for these rows -
`raw-openai` reads as configured whenever codex's `openai` upstream is present
- so it resurrected that upstream the moment someone unchecked codex. The
existing "unchecking a row still removes its gateway upstream" test caught it.
What this does not fix: raw traffic still needs an adapter's projector to be
recorded, and generic Anthropic-dialect traffic is still stamped
`client_name: 'claude'` by the LLP 0115 fallback. Both are projector
behaviour, independent of any picker row, and belong to LLP 0192's deferred
attribution decision. Filed as #673.
npm test: 3728 pass, 1 fail (`usage_policy.fold_tightened`, pre-existing on
master). typecheck clean. Smokes green: walkthrough_picker_to_first_query,
cli_bundled_plugins_activated, status_diagnostics.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* New folders sync by default; one gate accepts the lot (LLP 0200/0201)
Two changes to the same complaint: setup asks too much, and one of its
questions comes back every session.
LLP 0200 makes the per-folder classification ask (LLP 0106) opt-in. A
machine-local preference decides whether an unclassified folder is asked
about at session start, and its default is `sync` - the folder keeps the
implicit default it already had and nobody is interrupted. The ask still
exists for people who want it, chosen in the wizard's new-folder step or
with `hyp policy folders ask`. This gates the question only: no class
moves, `.hypignore` is untouched, and nothing new leaves a machine that
was not already syncing by default. `hyp status` names the mode on every
enrolled run so it is never a silent state, and the classification prompt
itself now teaches its own off switch.
The new-folder question is its own wizard step, after the per-adapter
sync lane rather than a row on it: that lane answers "which adapters
ship" and this one answers "what happens the next time I work somewhere
new", and folding the second into the first let a checklist decide a
policy silently.
LLP 0201 adds an express gate before the question lanes: it lists what
was found and offers "Record and sync all of these" or "Let me choose".
Accepting skips the prompts, never the statements - each lane still
narrates the block its gate would have shown, so the fast path prints the
same rows in the same order, minus the keypresses. The rows come from the
pick lane's own computation (hoisted to `resolvePickSeeding`), with
detection run once per wizard run, so the two screens cannot disagree
about what "all of these" means. With nothing detected and nothing locked
there is nothing to accept, so no gate is shown.
Also: the overwrite confirm is laid out instead of run together as one
paragraph, same facts and order.
npm test: 3765 pass, 1 fail (usage_policy.fold_tightened, identical on a
clean checkout). npm run typecheck: clean. Smokes
walkthrough_picker_to_first_query, status_diagnostics,
cli_bundled_plugins_activated, daemon_foreground_start_stop: green.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Review fixes: carry-through is scoped to a chosen seed, and the docs stop offering the hidden rows
Three review findings on the `hidden` picker field.
1. Carry-through could fire off a DETECTION seed. `promptPickSelection`
read `carried` from `seed`, but `seed` is `initialSelection ?? config
?? detected`. LLP 0200 #carry-through scopes carry-through to "the
config on disk collects nothing the menu can show"; off a detection
seed it would instead compose a hidden row the user was never shown
and cannot uncheck, which is what LLP 0011 #autodetect-vs-default
forbids and what `pick.js`'s own @ref to it promises. Unreachable
today only because no bundled hidden row declares a `detect` probe,
and `hidden` is a kernel-contract field any plugin can set beside
one. `seedIsChosen` now gates it, and a test drives detection
directly to pin it.
2. README and CONTEXT.md still listed `raw-anthropic` / `raw-openai`
among the rows the interactive walkthrough offers. Both now say the
rows are `--source`-only and why.
3. `picker-disclosure.test.js` justified its raw-row assertions with
"these two rows carry no adapter to disclose it anywhere else",
which stopped being true when the menu (the only consumer of
`summary`) stopped rendering them. Comment corrected; the
assertions stay, because hiding is a display filter, not a deletion.
Co-Authored-By: Claude <noreply@anthropic.com>
* Script detection in the wizard end-to-end so the express gate always shows
CI failed on `runInitWizard end-to-end`: it read the real machine for
detection, and the express gate (LLP 0201) is shown only when there is
something to accept. A developer laptop with Claude installed gets the
gate; a CI runner with no AI clients does not, so every scripted answer
after the fork landed on the wrong prompt and the run finished on the
team pathway it was supposed to step back out of.
The rows themselves were never what the test was about ('all' picks
whatever the menu offers); that there *are* rows is. So detection is
injected like every other seam in that call, and the script is stable on
any host.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Trim two wizard screens: the overwrite confirm, and the asset install
The overwrite confirm ran 44 words for a y/N. It still says the two
things LLP 0183 #say-so requires - that the file is rewritten from the
picks, and what carries over - in 27: the path already reads as "the
config", and the enumeration compresses without losing its reassurance.
The finale's client-asset step printed one line per copy, which on a
two-client install is a dozen destination paths burying the step's one
fact. It now reports counts per client, and counts them per client
rather than summed: six skills copied to two clients is twelve copies,
and neither client got twelve. The materializer keeps its per-copy
output for `hyp skills install`, where the copies are the subject; the
paths stay in the run summary and the skills.install span.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Setup offers the first sync instead of only naming it (LLP 0200)
LLP 0101's amendment already conceded the case: on an attended
onboarding the hold blocks the demonstration that the product works,
and the user had no way to say "I have seen enough". It minted `hyp
sync` for that and stopped one step short - the wizard names the verb
in the fifth line of a six-line narration, then hands the terminal to
a client. A user with no privacy concern has to notice a sentence,
remember a command, and find a terminal setup is giving away.
So an enrolled attended run now asks: wait until the deadline, or send
now. Waiting leads and is the default; the unwanted outcome of a
mis-keyed wait is a few hours' latency, and of a mis-keyed send is
permanent.
The question is an offer, not the consent. "Send now" runs the real
`hyp sync`, which prints the destination plan, escalates its warning
because the window is open, and asks its own y/N - only that y clears
the marker. LLP 0100 R2 and LLP 0101 #no-release hold verbatim: there
is still exactly one release path, and this adds no second one.
It spawns a child rather than running in process, and that is
correctness, not convenience: `hyp init` boots all-available, which
withholds @hypaware/central because a CLI boot must not acquire a
server identity, so this process has no central sink handle. An
in-process sync would render a plan missing the destination the
release unblocks - the misleading artifact R2 requires the plan to
prevent. Whether it sent is read back from the hold marker rather than
from the exit code, which is 0 both for a release and for a user who
read the list and said no.
Position: narration, offer, first ask. The narration stays the last
thing HypAware says about privacy; the first ask stays last on screen
because it may take the terminal for good.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Review fixes round 2: a carried hidden row survives stepping back into pick
Carry-through re-tested its own precondition on every pass. A re-entry
seeds with the selection the previous pass confirmed (LLP 0191
#re-entry-seeding), which for a raw-only install holds the carried hidden
row beside whatever visible rows the user just added. Asking "does the
seed collect nothing the menu can show?" of that seed answers no, so the
carried row was dropped: `back` then `enter` deleted the gateway upstream
the install runs on, silently.
Replaces the boolean `seedIsChosen` with a `SeedOrigin` the seed carries
alongside it. `detected` never carries (unchanged), `config` carries only
when the menu can show nothing the config collects (unchanged), and
`selection` carries unconditionally: read-back never reaches that tier,
so a hidden id is in a previous answer only because this rule put it
there. LLP 0200 #carry-through records the re-entry case.
Also pins the `hidden` manifest validation branch, which shipped
untested.
Co-Authored-By: Claude <noreply@anthropic.com>
* The express gate joins the forward chain but not the back chain
Escape at the pick lane reached past the new gate to the fork, and escape
at the sync lane landed on the gate instead of the picker: the question was
inserted between the fork and the pick lane without being given a loop
level, so both of its neighbours overshot by one screen. LLP 0191
#back-edges is that escape steps exactly one screen back.
The gate now owns `atExpress` between `atFork` and `atPick`. Pick backs to
it when the pass showed one and to the fork when it did not (LLP 0201
#no-default-no-accept), and a sync back re-runs the picker without
re-asking it. Three tests pin the chain; the end-to-end walk gains the
keypress the corrected edge needs.
Also corrects three doc blocks left over from a draft where `ask` was the
default. `src/core/usage-policy/types.d.ts` and `src/core/daemon/types.d.ts`
ship in the published declaration build, so they were telling consumers the
opposite of both the code and LLP 0200 #default about a privacy-relevant
default; `usage-policy/index.js` said the same and attributed the write to
the sync lane, which LLP 0200 #wizard explicitly rejects. Its export block
had also been spliced between the client-sync comment and the export that
comment describes.
`hyp policy list` no longer drops "no machine-local entries (policy store:
...)" on a store that is empty while the folder mode is non-default; the
mode is an addition to that line, not a replacement for it.
Co-Authored-By: Claude <noreply@anthropic.com>
* An unreadable hold re-read claimed the sync happened
LLP 0200 #read-back settles the direction: "An unreadable re-read is
treated as 'still held', because claiming a sync happened is the one
wrong answer that cannot be corrected later." `readHold` said the same
thing in its own docstring and then did the opposite - it returned
`null` on a failed read, and `null` is the caller's word for "the marker
is gone, it sent". So a state dir that would not resolve ended the run
reporting `released: true`, logging `sync_now: released`, and skipping
the line that restates the deadline: setup's last word on the subject
was a release nobody observed.
A throw from the `readDeadline` seam was worse in a second way - it
escaped to the outer catch and returned `{ asked: false, reason:
'error' }` after the child had already run, so the run ended on silence
with no statement of what still held.
Both now return the deadline the step started from, which reads as
"still held" and prints the restatement.
Co-Authored-By: Claude <noreply@anthropic.com>
* Escape at the new-folder lane traps a fleet-managed machine (LLP 0200/0201)
The sync lane asks nothing when everything picked is fleet-locked, and
nothing when the client store is corrupt: it states its outcome and
returns. The new-folder lane behind it backed unconditionally into it, so
escape re-ran the statement and re-asked the same question. On a fully
managed machine that is a trap with no exit but ctrl+c.
The lane now reports `noQuestion` on both non-prompting returns and the
orchestrator steps past it to the picker, which is the last screen the user
could answer.
Same class one step earlier: the express gate lists the picker's own
confirmed defaults, so a confirmed empty selection leaves it nothing to
show. A back out of the re-entered picker landed on a gate that could not
render and fell forward into the picker again. It now reaches the fork.
Also: the pick lane re-derived `defaultRows` instead of taking the
`resolvePickSeeding` value the express gate reads, which is the second
derivation of "the defaults" LLP 0201 #gate says must not exist.
Co-Authored-By: Claude <noreply@anthropic.com>
* The sync read-back's production body was never run by a test
Every test of `runWizardSyncNow` replaced the hold re-read with the
`readDeadline` seam, so the body that actually decides the release claim -
resolve the state dir from the environment, read the marker there - had no
coverage at all. That is the body a wrong answer cannot be walked back in
(LLP 0200 #read-back), and the blind spot is what let the earlier
`catch { return null }` report a release nobody observed.
Two tests drive it end to end against a real marker on disk with no seam:
a spawn stub that clears the marker (a release) or leaves it (a declined
plan). Proven to bite: dropping `opts.env` from the state-dir resolution -
the drift that would make every run claim a release - fails only these two
and leaves all eight pre-existing tests green.
Co-Authored-By: Claude <noreply@anthropic.com>
* Renumber 674's LLP 0200 to 0202 (folder-ask kept 0200; mechanical, LLP 0156)
* Renumber 677's LLP 0200 to 0203 (mechanical, LLP 0156)
* Restore 677's per-client asset counts dropped by the confirm-copy resolution
* Add cross-PR regression test: express auto-accept honors hidden-row carry-through
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: test <test@test.com>
Co-authored-by: neutral <neutral@hyparam.com>
Co-authored-by: neutral <neutral@hyperparam.app>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

neutral:adoptForeign PR adopted into neutral's reconcile scopeneutral:adoptedAdoption completion record: merged while carrying neutral:adopt (LLP 0031)neutral:changes-requestedneutral reviewed an adopted PR and requests changes (non-binding; maintainer decides)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@bgmcmullen@philcunliffe