diff --git a/llp/0011-setup-and-onboarding.decision.md b/llp/0011-setup-and-onboarding.decision.md
index b65a3e2..cc35558 100644
--- a/llp/0011-setup-and-onboarding.decision.md
+++ b/llp/0011-setup-and-onboarding.decision.md
@@ -20,6 +20,10 @@
> realizing the plugin-contributed picks this document already named. The
> prose below is rewritten with the implementation.
+> **Amended by [LLP 0137](./0137-onboarding-retention-defaults.decision.md)**:
+> the "Cache retention (days)" question is removed; the pathway sets the
+> default instead (30-day team / 120-day local).
+
## Interactive walkthrough
The primary way to get a HypAware install on the ground is the interactive
@@ -27,14 +31,16 @@ setup (`npx hypaware`):
```text
What do you want to collect? (Claude / Codex / raw Anthropic+OpenAI / OTEL)
-Where should HypAware export? (local cache only / local Parquet / central / later)
-Cache retention (days, default 30): 30
✓ Wrote ~/.hyp/hypaware-config.json
✓ Wired Claude Code to use the local proxy
✓ Started the HypAware daemon
```
+Export and retention are not asked: export defaults to local Parquet, and
+the retention window comes from the pathway chosen at the fork
+([LLP 0137](./0137-onboarding-retention-defaults.decision.md#pathway-defaults)).
+
The walkthrough is the canonical first-run experience. It composes
**plugin-contributed picks** — each source/client plugin registers what it
collects; each sink plugin registers what it exports to — and writes a config
@@ -92,7 +98,9 @@ HypAware picks the plugin set. There are no names like "standalone" or
a source on, never hides one. Raw proxy sources and OTEL are never
autodetected (no installed tool to find). See [LLP 0012](./0012-sources.spec.md#source-kinds).
- **Default** is a fixed starting selection not derived from system state:
- export defaults to local Parquet, retention defaults to 30 days.
+ export defaults to local Parquet; retention defaults by pathway, 30 days
+ on a team install and 120 days on a local-only one
+ ([LLP 0137](./0137-onboarding-retention-defaults.decision.md#pathway-defaults)).
(Canonical definitions live in `CONTEXT.md`.)
diff --git a/llp/0137-onboarding-retention-defaults.decision.md b/llp/0137-onboarding-retention-defaults.decision.md
new file mode 100644
index 0000000..2856bef
--- /dev/null
+++ b/llp/0137-onboarding-retention-defaults.decision.md
@@ -0,0 +1,53 @@
+# LLP 0137: Onboarding never asks for retention; the pathway sets the default
+
+**Type:** Decision
+**Status:** Active
+**Systems:** Onboarding, CLI, Config
+**Author:** Phil / Claude
+**Date:** 2026-07-24
+**Related:** LLP 0011, LLP 0013, LLP 0129, LLP 0135
+
+> Amends [LLP 0011](./0011-setup-and-onboarding.decision.md): the
+> "Cache retention (days)" question is removed from the interactive
+> walkthrough, and the flat 30-day default becomes pathway-scoped.
+
+## Decision
+
+**Onboarding never asks for a cache
+retention window. The pathway chosen at the fork
+([LLP 0129](./0129-init-wizard-fork.decision.md#fork)) decides the
+default instead:**
+
+- **Join a team: 30 days.** The org server holds the durable copy of
+ forwarded history, so the local cache is a working window, not the
+ record.
+- **Local install: 120 days.** Nothing leaves the machine; the local
+ cache is the only copy of history, so it keeps a much longer window.
+- A managed machine's **scoped re-entry** counts as the team side
+ (30 days), same rationale.
+
+The wizard orchestrator passes the local default into the pick phase as
+`retentionDefault`; team and scoped runs fall through to the pick
+phase's `DEFAULT_RETENTION_DAYS`. The superseded programmatic
+walkthrough (`runPickerWalkthrough`), which has no fork, keeps the flat
+30-day default.
+
+## Why not ask
+
+Retention was the last free-text question in onboarding, and it asked
+for a judgment the user has no basis to make on first run (how many
+days of an as-yet-empty cache to keep). Every other pick already
+defaults (export defaults to local Parquet,
+[LLP 0011](./0011-setup-and-onboarding.decision.md#autodetect-vs-default));
+the window is a config value (`query.cache.retention.default_days`,
+[LLP 0013](./0013-local-query-cache.decision.md#retention-is-the-central-tradeoff))
+that remains freely editable after the fact, so asking up front bought
+nothing but friction.
+
+## Overrides unchanged
+
+`hyp init --retention-days ` still sets the window explicitly on the
+non-interactive path (its flag default stays 30), and editing
+`query.cache.retention` in the written config remains the post-install
+knob. Fleet-enforced retention stays deliberately deferred
+([LLP 0031](./0031-layered-config.decision.md#open-questions--deferred)).
diff --git a/src/core/cli/types.d.ts b/src/core/cli/types.d.ts
index a0ab8dc..4e05017 100644
--- a/src/core/cli/types.d.ts
+++ b/src/core/cli/types.d.ts
@@ -14,7 +14,6 @@ import type { KernelRuntime } from '../runtime/types.d.ts'
import type { createCommandRegistry } from '../registry/commands.js'
export type AsyncPickPrompt = (question: WalkthroughQuestion) => Promise
-export type AsyncRetentionPrompt = (prompt: string, defaultDays: number) => Promise
export type AsyncBackfillConsentPrompt = (args: {
providers: string[]
retentionDays: number
@@ -82,7 +81,6 @@ export interface WalkthroughOptions {
env: NodeJS.ProcessEnv
/** Override prompt resolver (tests pre-bake answers). */
prompt?: AsyncPickPrompt
- retentionPrompt?: AsyncRetentionPrompt
}
export interface WalkthroughResult {
@@ -143,7 +141,6 @@ export interface RunPickerWalkthroughOptions {
*/
exportOrigin?: PickerExportOrigin
prompt?: AsyncPickPrompt
- retentionPrompt?: AsyncRetentionPrompt
/**
* Override the system source detector. Defaults to a catalog-backed
* wrapper around the real filesystem-based `detectPickerSources`.
diff --git a/src/core/cli/walkthrough.js b/src/core/cli/walkthrough.js
index a3cbfc1..ec63bc3 100644
--- a/src/core/cli/walkthrough.js
+++ b/src/core/cli/walkthrough.js
@@ -12,7 +12,7 @@ import { discoverBundledPlugins } from '../runtime/bundled.js'
import { isWithinDir } from '../runtime/contribution_names.js'
import { buildPluginCatalog } from '../plugin_catalog.js'
import { detectPickerSources } from './detect.js'
-import { multiselect, select, text } from './tui/index.js'
+import { multiselect, select } from './tui/index.js'
import { isPromptCancelledError } from './tui/runtime.js'
import { shouldUseTui } from './tui-router.js'
import { copyDir } from '../util/fs_copy.js'
@@ -35,7 +35,6 @@ export const WALKTHROUGH_CANCEL_EXIT_CODE = 130
* @import {
* AsyncBackfillConsentPrompt,
* AsyncPickPrompt,
- * AsyncRetentionPrompt,
* BackfillFinaleResult,
* PickerBackfillRunner,
* PickerSource,
@@ -50,7 +49,14 @@ export const WALKTHROUGH_CANCEL_EXIT_CODE = 130
* } from '../../../src/core/cli/types.js'
*/
+// Onboarding never asks for a retention window; these are the pathway
+// defaults the wizard applies instead. A team (or managed) install keeps
+// the 30-day window because the org server holds the durable copy; a
+// local-only install keeps 120 days because the local cache is the only
+// copy of history. `hyp init --retention-days ` remains the override.
+// @ref LLP 0137#pathway-defaults [implements]: no retention question; 30-day team / 120-day local defaults
export const DEFAULT_RETENTION_DAYS = 30
+export const LOCAL_INSTALL_RETENTION_DAYS = 120
/**
* Resolve the HYP_HOME root the same way the kernel does (matches
@@ -103,28 +109,6 @@ function legacyNumberedPromptFactory(opts) {
}
}
-/**
- * @param {Pick} opts
- * @returns {AsyncRetentionPrompt}
- */
-function legacyRetentionPromptFactory(opts) {
- const input = /** @type {NodeJS.ReadableStream} */ (opts.stdin ?? process.stdin)
- const output = /** @type {NodeJS.WritableStream} */ (opts.stdout)
- return async function (prompt, defaultDays) {
- const rl = readline.createInterface({ input, output, terminal: false })
- try {
- const answer = await rl.question(`${prompt} [${defaultDays}]: `)
- const trimmed = answer.trim()
- if (!trimmed) return defaultDays
- const parsed = Number.parseInt(trimmed, 10)
- if (!Number.isInteger(parsed) || parsed < 0) return defaultDays
- return parsed
- } finally {
- rl.close()
- }
- }
-}
-
/**
* Build the interactive "overwrite existing config?" confirm. Defaults
* to **no** (a bare Enter keeps the existing config), so a stray
@@ -177,37 +161,6 @@ function tuiPromptFactory(opts) {
}
}
-/**
- * Prompt for the cache retention window through the TUI text input.
- * Empty input falls through to the supplied default to match the legacy
- * behavior.
- *
- * @param {Pick} opts
- * @returns {AsyncRetentionPrompt}
- */
-function tuiRetentionPromptFactory(opts) {
- return async function (prompt, defaultDays) {
- const v = await text({
- title: prompt,
- default: String(defaultDays),
- validate: (s) => {
- if (s.trim() === '') return null
- const n = Number.parseInt(s.trim(), 10)
- return Number.isInteger(n) && n >= 0 ? null : 'enter a non-negative integer'
- },
- clearOnResolve: true,
- stdin: opts.stdin ?? process.stdin,
- stdout: /** @type {NodeJS.WritableStream} */ (/** @type {unknown} */ (opts.stdout)),
- env: opts.env,
- })
- const trimmed = v.trim()
- if (trimmed === '') return defaultDays
- const parsed = Number.parseInt(trimmed, 10)
- if (!Number.isInteger(parsed) || parsed < 0) return defaultDays
- return parsed
- }
-}
-
/**
* Route between the TUI and legacy prompts. Tests and CI keep getting
* the legacy numbered list, but only real TTYs without `HYP_NO_TUI=1` see
@@ -221,15 +174,6 @@ export function defaultPromptFactory(opts) {
return legacyNumberedPromptFactory(opts)
}
-/**
- * @param {Pick} opts
- * @returns {AsyncRetentionPrompt}
- */
-export function defaultRetentionPromptFactory(opts) {
- if (shouldUseTui(opts)) return tuiRetentionPromptFactory(opts)
- return legacyRetentionPromptFactory(opts)
-}
-
/**
* Build the interactive backfill-consent prompt. Routes to the TUI
* arrow-navigable yes/no select on a real TTY, else a legacy readline
@@ -423,7 +367,6 @@ export async function runPickerWalkthrough(opts) {
exportOrigin = opts.exportOrigin ?? 'default'
} else {
const ask = opts.prompt ?? defaultPromptFactory(opts)
- const retentionAsk = opts.retentionPrompt ?? defaultRetentionPromptFactory(opts)
stdout.write('Welcome to HypAware - the local logs+telemetry collector.\n\n')
@@ -450,8 +393,10 @@ export async function runPickerWalkthrough(opts) {
// editing the written config later.
const exportChoice = /** @type {PickerExport} */ ('local-parquet')
- const retentionDays = await retentionAsk('Cache retention (days)', DEFAULT_RETENTION_DAYS)
- picks = { sources, exportChoice, retentionDays }
+ // Retention is not asked either (LLP 0137): this legacy surface has
+ // no pathway fork, so it takes the flat default. The wizard applies
+ // the pathway-aware defaults; `--retention-days` overrides via picks.
+ picks = { sources, exportChoice, retentionDays: DEFAULT_RETENTION_DAYS }
} catch (err) {
if (isPromptCancelledError(err)) {
return await cancelledResult(opts)
diff --git a/src/core/cli/wizard/index.js b/src/core/cli/wizard/index.js
index e037a6f..f4312e5 100644
--- a/src/core/cli/wizard/index.js
+++ b/src/core/cli/wizard/index.js
@@ -18,7 +18,7 @@ import { discoverBundledPlugins } from '../../runtime/bundled.js'
import { buildPluginCatalog } from '../../plugin_catalog.js'
import { collectHypAwareStatus } from '../../daemon/status.js'
import { formatFirstSyncDeadline, readFirstSyncDeadline } from '../../usage-policy/first_sync_hold.js'
-import { runPickerFinale, writeWalkthroughRunSummary } from '../walkthrough.js'
+import { LOCAL_INSTALL_RETENTION_DAYS, runPickerFinale, writeWalkthroughRunSummary } from '../walkthrough.js'
import { LOGIN_ORG_SELECTION_MESSAGE } from '../remote_commands.js'
import { evaluateReturningGate, runWizardFork } from './fork.js'
import { computeCentralLockedSources, runWizardJoin } from './join.js'
@@ -127,7 +127,12 @@ export async function runInitWizard(opts) {
...(opts.exportOrigin ? { exportOrigin: opts.exportOrigin } : {}),
...(opts.force ? { force: opts.force } : {}),
...(opts.prompt ? { prompt: opts.prompt } : {}),
- ...(opts.retentionPrompt ? { retentionPrompt: opts.retentionPrompt } : {}),
+ // Retention is never asked; the pathway decides the default. A local
+ // install keeps the longer 120-day window (the cache is the only copy
+ // of history); team and scoped runs fall through to the pick phase's
+ // 30-day default (the org server holds the durable copy).
+ // @ref LLP 0137#pathway-defaults [implements]: 30-day team / 120-day local retention defaults
+ ...(pathway === 'local' ? { retentionDefault: LOCAL_INSTALL_RETENTION_DAYS } : {}),
...(opts.detect ? { detect: opts.detect } : {}),
...(opts.confirmOverwrite ? { confirmOverwrite: opts.confirmOverwrite } : {}),
})
diff --git a/src/core/cli/wizard/pick.js b/src/core/cli/wizard/pick.js
index 2b0faf5..271aabf 100644
--- a/src/core/cli/wizard/pick.js
+++ b/src/core/cli/wizard/pick.js
@@ -14,7 +14,6 @@ import {
defaultOverwriteConfirmFactory,
defaultPickerDetect,
defaultPromptFactory,
- defaultRetentionPromptFactory,
loadPickerDescriptors,
orderPickerDescriptors,
resolveHypHome,
@@ -133,7 +132,6 @@ export async function runWizardPick(opts) {
exportOrigin = opts.exportOrigin ?? 'default'
} else {
const ask = opts.prompt ?? defaultPromptFactory(opts)
- const retentionAsk = opts.retentionPrompt ?? defaultRetentionPromptFactory(opts)
opts.stdout.write('Welcome to HypAware - the local logs+telemetry collector.\n\n')
@@ -151,7 +149,11 @@ export async function runWizardPick(opts) {
// Parquet exports so the first run produces durable files out of the
// box. Other destinations remain available via `hyp init --export`.
exportChoice = /** @type {PickerExport} */ ('local-parquet')
- retentionDays = await retentionAsk('Cache retention (days)', DEFAULT_RETENTION_DAYS)
+ // Retention is not asked either: the orchestrator supplies the
+ // pathway default (30-day team / 120-day local), overridable only
+ // via `hyp init --retention-days` on the non-interactive path.
+ // @ref LLP 0137#pathway-defaults [implements]: the retention question is removed from onboarding
+ retentionDays = opts.retentionDefault ?? DEFAULT_RETENTION_DAYS
} catch (err) {
if (isPromptCancelledError(err)) return cancelledResult(opts)
throw err
diff --git a/src/core/cli/wizard/types.d.ts b/src/core/cli/wizard/types.d.ts
index c38562c..96df0ab 100644
--- a/src/core/cli/wizard/types.d.ts
+++ b/src/core/cli/wizard/types.d.ts
@@ -4,7 +4,6 @@ import type { PickerDescriptor, PluginCatalog } from '../../types.d.ts'
import type {
AsyncBackfillConsentPrompt,
AsyncPickPrompt,
- AsyncRetentionPrompt,
FinaleSummary,
PickerBackfillRunner,
PickerExport,
@@ -251,8 +250,12 @@ export interface RunWizardPickOptions {
exportOrigin?: PickerExportOrigin
/** Override the source prompt (tests pre-bake answers). */
prompt?: AsyncPickPrompt
- /** Override the retention prompt (tests pre-bake answers). */
- retentionPrompt?: AsyncRetentionPrompt
+ /**
+ * The retention window applied without asking (LLP 0137): the
+ * orchestrator passes the pathway default (120-day local; team and
+ * scoped runs omit it and take the 30-day `DEFAULT_RETENTION_DAYS`).
+ */
+ retentionDefault?: number
/** Override the system source detector (interactive only). */
detect?: (opts: { env: NodeJS.ProcessEnv }) => Promise>
/** Overwrite an existing local config non-interactively (`--force`). */
@@ -313,7 +316,6 @@ export interface RunInitWizardOptions {
finaleRunner?: (args: Record) => Promise
/** Pick-phase prompt seams, threaded through unchanged (tests). */
prompt?: AsyncPickPrompt
- retentionPrompt?: AsyncRetentionPrompt
detect?: (opts: { env: NodeJS.ProcessEnv }) => Promise>
confirmOverwrite?: (targetPath: string) => Promise
backfillConsentPrompt?: AsyncBackfillConsentPrompt
diff --git a/test/core/cli/wizard/index.test.js b/test/core/cli/wizard/index.test.js
index 4e71b31..209a4b7 100644
--- a/test/core/cli/wizard/index.test.js
+++ b/test/core/cli/wizard/index.test.js
@@ -152,6 +152,9 @@ test('runInitWizard: local pathway runs pick -> configure -> finale, no join', a
assert.deepEqual(calls, ['gate', 'fork', 'pick', 'configure', 'finale'])
assert.equal(result.pathway, 'local')
assert.equal(opts._pickOpts.managed, undefined)
+ // The local pathway supplies the 120-day retention default without a
+ // prompt (LLP 0137 #pathway-defaults).
+ assert.equal(opts._pickOpts.retentionDefault, 120)
})
test('runInitWizard: fork quit exits 0 before the pick phase', async () => {
@@ -172,6 +175,9 @@ test('runInitWizard: team pathway threads locked + managed into the pick phase',
assert.equal(result.pathway, 'team')
assert.deepEqual(opts._pickOpts.locked, ['claude'])
assert.equal(opts._pickOpts.managed, true)
+ // The team pathway takes the pick phase's 30-day default: no
+ // retentionDefault override (LLP 0137 #pathway-defaults).
+ assert.equal(opts._pickOpts.retentionDefault, undefined)
})
test('runInitWizard: a failed join explains and returns to the fork', async () => {
diff --git a/test/core/cli/wizard/pick.test.js b/test/core/cli/wizard/pick.test.js
index bb125d9..8303a3a 100644
--- a/test/core/cli/wizard/pick.test.js
+++ b/test/core/cli/wizard/pick.test.js
@@ -118,7 +118,6 @@ test('runWizardPick: interactive prompt options pre-check detected sources', asy
const { prompt, state } = capturingPrompt(['codex'])
const result = await runWizardPick(/** @type {any} */ ({
stdout: makeBuf(), stderr: makeBuf(), env: hermeticEnv(tmp), catalog, prompt,
- retentionPrompt: async (/** @type {string} */ _p, /** @type {number} */ d) => d,
detect: async () => new Set(['codex']),
}))
// The codex row came pre-checked from detection.
@@ -129,13 +128,53 @@ test('runWizardPick: interactive prompt options pre-check detected sources', asy
assert.equal(result.retentionDays, 30)
})
+// --- retention defaults (LLP 0137): never asked, pathway-supplied ---
+// @ref LLP 0137#pathway-defaults [tests]:
+
+test('runWizardPick: interactive runs take the 30-day default without a retention prompt', async () => {
+ const tmp = await mkTmp()
+ const catalog = await realCatalog()
+ const { prompt } = capturingPrompt(['otel'])
+ const result = await runWizardPick(/** @type {any} */ ({
+ stdout: makeBuf(), stderr: makeBuf(), env: hermeticEnv(tmp), catalog, prompt,
+ detect: async () => new Set(),
+ }))
+ assert.equal(result.retentionDays, 30)
+ const written = JSON.parse(await fs.readFile(result.configPath, 'utf8'))
+ assert.equal(written.query.cache.retention.default_days, 30)
+})
+
+test('runWizardPick: retentionDefault (the local pathway) lands in the composed config', async () => {
+ const tmp = await mkTmp()
+ const catalog = await realCatalog()
+ const { prompt } = capturingPrompt(['otel'])
+ const result = await runWizardPick(/** @type {any} */ ({
+ stdout: makeBuf(), stderr: makeBuf(), env: hermeticEnv(tmp), catalog, prompt,
+ detect: async () => new Set(),
+ retentionDefault: 120,
+ }))
+ assert.equal(result.retentionDays, 120)
+ const written = JSON.parse(await fs.readFile(result.configPath, 'utf8'))
+ assert.equal(written.query.cache.retention.default_days, 120)
+})
+
+test('runWizardPick: pre-baked picks override retentionDefault', async () => {
+ const tmp = await mkTmp()
+ const catalog = await realCatalog()
+ const result = await runWizardPick(/** @type {any} */ ({
+ stdout: makeBuf(), stderr: makeBuf(), env: hermeticEnv(tmp), catalog,
+ retentionDefault: 120,
+ picks: { sources: /** @type {PickerSource[]} */ (['otel']), exportChoice: 'local-parquet', retentionDays: 7 },
+ }))
+ assert.equal(result.retentionDays, 7)
+})
+
test('runWizardPick: options come from catalog.pickerDescriptors, not a hardcoded table', async () => {
const tmp = await mkTmp()
const catalog = await realCatalog()
const { prompt, state } = capturingPrompt([])
await runWizardPick(/** @type {any} */ ({
stdout: makeBuf(), stderr: makeBuf(), env: hermeticEnv(tmp), catalog, prompt,
- retentionPrompt: async (/** @type {string} */ _p, /** @type {number} */ d) => d,
detect: async () => new Set(),
}))
const ids = state.question.options.map((/** @type {any} */ o) => o.value).sort()
@@ -150,7 +189,6 @@ test('runWizardPick: a locked row renders checked, disabled, and fleet-labeled',
const { prompt, state } = capturingPrompt(['claude'])
await runWizardPick(/** @type {any} */ ({
stdout: makeBuf(), stderr: makeBuf(), env: hermeticEnv(tmp), catalog, prompt,
- retentionPrompt: async (/** @type {string} */ _p, /** @type {number} */ d) => d,
detect: async () => new Set(),
locked: ['claude'],
}))
@@ -168,7 +206,6 @@ test('runWizardPick: a locked source is filtered out of the returned picks and c
const { prompt } = capturingPrompt(['claude', 'codex'])
const result = await runWizardPick(/** @type {any} */ ({
stdout: makeBuf(), stderr: makeBuf(), env: hermeticEnv(tmp), catalog, prompt,
- retentionPrompt: async (/** @type {string} */ _p, /** @type {number} */ d) => d,
detect: async () => new Set(),
locked: ['claude'],
}))
@@ -188,7 +225,6 @@ test('runWizardPick: an unknown locked id is ignored, not surfaced as a row', as
const { prompt, state } = capturingPrompt([])
const result = await runWizardPick(/** @type {any} */ ({
stdout: makeBuf(), stderr: makeBuf(), env: hermeticEnv(tmp), catalog, prompt,
- retentionPrompt: async (/** @type {string} */ _p, /** @type {number} */ d) => d,
detect: async () => new Set(),
locked: ['does-not-exist'],
}))
@@ -205,7 +241,6 @@ test('runWizardPick: on a managed machine, non-locked rows say "stays on this ma
const { prompt, state } = capturingPrompt([])
await runWizardPick(/** @type {any} */ ({
stdout: makeBuf(), stderr: makeBuf(), env: hermeticEnv(tmp), catalog, prompt,
- retentionPrompt: async (/** @type {string} */ _p, /** @type {number} */ d) => d,
detect: async () => new Set(['codex']),
locked: ['claude'],
managed: true,
@@ -228,7 +263,6 @@ test('runWizardPick: an unmanaged (solo) machine never shows the local-only suff
const { prompt, state } = capturingPrompt([])
await runWizardPick(/** @type {any} */ ({
stdout: makeBuf(), stderr: makeBuf(), env: hermeticEnv(tmp), catalog, prompt,
- retentionPrompt: async (/** @type {string} */ _p, /** @type {number} */ d) => d,
detect: async () => new Set(['claude']),
}))
for (const row of state.question.options) {
diff --git a/test/core/init-guard.test.js b/test/core/init-guard.test.js
index aece9f5..b2c6b99 100644
--- a/test/core/init-guard.test.js
+++ b/test/core/init-guard.test.js
@@ -126,7 +126,6 @@ function interactiveOpts(hypHome) {
env: { ...process.env, HYP_HOME: hypHome, HYP_CONFIG: '' },
detect: async () => new Set(),
prompt: async () => [],
- retentionPrompt: async () => 30,
},
}
}
diff --git a/test/core/walkthrough-backfill.test.js b/test/core/walkthrough-backfill.test.js
index f4c3ab4..092100c 100644
--- a/test/core/walkthrough-backfill.test.js
+++ b/test/core/walkthrough-backfill.test.js
@@ -191,7 +191,6 @@ test('interactive onboarding defaults backfill to enabled (consent yes runs it)'
env,
// No `picks` ⇒ interactive: prompts are driven by injected resolvers.
prompt: async (q) => (q.pickType === 'sources' ? ['claude'] : ['keep-local']),
- retentionPrompt: async () => 30,
backfillConsentPrompt: async (args) => {
consentCalls.push(args)
return true
@@ -219,7 +218,6 @@ test('interactive onboarding lets the user decline backfill', async () => {
stderr,
env,
prompt: async (q) => (q.pickType === 'sources' ? ['claude'] : ['keep-local']),
- retentionPrompt: async () => 30,
backfillConsentPrompt: async () => false,
backfill,
finale: { skipDaemon: true },
@@ -243,7 +241,6 @@ test('interactive onboarding maps cancelled backfill consent to the cancel exit
stderr,
env,
prompt: async (q) => (q.pickType === 'sources' ? ['claude'] : ['keep-local']),
- retentionPrompt: async () => 30,
backfillConsentPrompt: async () => {
throw new PromptCancelledError()
},
@@ -422,7 +419,6 @@ test('interactive onboarding prompts codex backfill consent and runs it on yes',
env,
// No `picks` ⇒ interactive: the source resolver picks codex.
prompt: async (q) => (q.pickType === 'sources' ? ['codex'] : ['keep-local']),
- retentionPrompt: async () => 30,
backfillConsentPrompt: async (args) => {
consentCalls.push(args)
return true
diff --git a/test/core/walkthrough-detect.test.js b/test/core/walkthrough-detect.test.js
index dfe6e90..dbc6225 100644
--- a/test/core/walkthrough-detect.test.js
+++ b/test/core/walkthrough-detect.test.js
@@ -42,7 +42,6 @@ test('picker pre-checks detected sources, labels them, and defaults export to lo
env: await tmpEnv(),
detect: async () => new Set(['claude']),
prompt,
- retentionPrompt: async (_p, d) => d,
})
const sources = seen.find((q) => q.pickType === 'sources')
@@ -79,7 +78,6 @@ test('nothing detected → no source pre-checked, export still defaults to local
env: await tmpEnv(),
detect: async () => new Set(),
prompt,
- retentionPrompt: async (_p, d) => d,
})
const sources = seen.find((q) => q.pickType === 'sources')
diff --git a/test/core/walkthrough-prompt.test.js b/test/core/walkthrough-prompt.test.js
index 322d218..d390ffa 100644
--- a/test/core/walkthrough-prompt.test.js
+++ b/test/core/walkthrough-prompt.test.js
@@ -12,9 +12,9 @@ import { runPickerWalkthrough } from '../../src/core/cli/walkthrough.js'
test('picker prompt prints context under source options and defaults export to local-parquet', async () => {
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), 'hypaware-walkthrough-prompt-'))
const input = new PassThrough()
- // Only the source question and the retention prompt are asked; the
- // export question was removed in favour of the local-parquet default.
- const stdout = answerDrivenOutput(input, ['3\n', '\n'])
+ // Only the source question is asked; export defaults to local-parquet
+ // and retention takes its default without a prompt (LLP 0137).
+ const stdout = answerDrivenOutput(input, ['3\n'])
const stderr = makeBuf()
const result = await runPickerWalkthrough({
@@ -37,6 +37,8 @@ test('picker prompt prints context under source options and defaults export to l
// The export question is no longer rendered.
assert.doesNotMatch(text, /keep local query cache only/)
assert.doesNotMatch(text, /Where should HypAware export/)
+ // Neither is the retention question (LLP 0137).
+ assert.doesNotMatch(text, /Cache retention/)
assert.equal(stderr.text(), '')
})
@@ -50,7 +52,7 @@ function answerDrivenOutput(input, answers) {
write(chunk) {
const text = String(chunk)
value += text
- if (text.includes('select (e.g. 1,3 or "all"): ') || text.includes('Cache retention (days)')) {
+ if (text.includes('select (e.g. 1,3 or "all"): ')) {
const answer = answers.shift()
if (answer !== undefined) input.write(answer)
if (answers.length === 0) input.end()
diff --git a/test/core/walkthrough-tui-happy.test.js b/test/core/walkthrough-tui-happy.test.js
index 6b25120..c0191c5 100644
--- a/test/core/walkthrough-tui-happy.test.js
+++ b/test/core/walkthrough-tui-happy.test.js
@@ -86,15 +86,11 @@ test('runPickerWalkthrough drives the TUI multiselect end-to-end when stdin+stdo
// Sources prompt (PICKER_SOURCES: claude, codex, raw-anthropic,
// raw-openai, otel). Move down twice to land on raw-anthropic,
- // toggle, then enter.
+ // toggle, then enter. It is the only prompt: export defaults to
+ // local-parquet and retention is never asked (LLP 0137).
await settle()
await feed(io.stdin, ['\x1b[B', '\x1b[B', ' ', '\r'])
- // No export prompt: the picker always defaults to local-parquet now.
- // Retention prompt: empty buffer + enter accepts the 30-day default.
- await settle()
- await feed(io.stdin, ['\r'])
-
const result = await promise
assert.equal(result.exitCode, 0)
assert.deepEqual(result.sourcesPicked, ['raw-anthropic'])
@@ -177,10 +173,10 @@ test('runPickerWalkthrough falls back to the legacy numbered prompt under HYP_NO
const input = new PassThrough()
// Mark BOTH ends as TTYs so the only signal that flips the router is
// the HYP_NO_TUI escape. This proves the env override wins over the
- // TTY probe. Answers: source '3' (raw-anthropic), then retention
- // default: the export question is no longer asked.
+ // TTY probe. One answer: source '3' (raw-anthropic). Neither export
+ // nor retention is asked.
Object.defineProperty(input, 'isTTY', { value: true })
- const stdout = answerDrivenOutput(input, ['3\n', '\n'], true)
+ const stdout = answerDrivenOutput(input, ['3\n'], true)
const stderr = makeBuf()
// HYP_NO_TUI flows through opts.env: the same channel real callers
@@ -214,7 +210,7 @@ function answerDrivenOutput(input, answers, withIsTty = false) {
write(chunk) {
const text = String(chunk)
value += text
- if (text.includes('select (e.g. 1,3 or "all"): ') || text.includes('Cache retention (days)')) {
+ if (text.includes('select (e.g. 1,3 or "all"): ')) {
const answer = answers.shift()
if (answer !== undefined) input.write(answer)
if (answers.length === 0) input.end()