devflow-kit 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@
4
4
  * Computes the initial state (seed) for init prompts from:
5
5
  * - The existing manifest (from a prior install)
6
6
  * - The project feature config (.devflow/config.json)
7
- * - The current settings.json snapshot (for viewMode)
7
+ * - The current settings.json snapshot (for view-mode resolution)
8
8
  * - The plugin registry
9
9
  *
10
10
  * All exported functions are pure — no I/O, no side effects.
@@ -13,7 +13,7 @@
13
13
  * beside init.ts in src/cli/commands/ rather than in src/core/ (which holds
14
14
  * agent-neutral, target-agnostic utilities).
15
15
  */
16
- import { resolveExistingViewMode, FLAG_REGISTRY } from '../../core/flags.js';
16
+ import { resolveExistingViewMode, FLAG_REGISTRY, defaultValueOf, readViewMode, } from '../../core/flags.js';
17
17
  import { partitionSelectablePlugins } from '../../core/plugins.js';
18
18
  /** Registry defaults — all features enabled except proxy (advanced-only, off by default). */
19
19
  export const FEATURE_DEFAULTS = {
@@ -63,41 +63,43 @@ export function resolveSeedFeatures(manifest, projectConfig) {
63
63
  return { ambient, memory, hud, knowledge, learning, rules, proxy, compliance };
64
64
  }
65
65
  /**
66
- * Resolve the enabled flag set for the init seed.
66
+ * Resolve the flag record for the init seed.
67
67
  *
68
- * @param enabledFlags - Currently-enabled flag IDs from the manifest,
69
- * or null for a fresh install (no prior manifest).
70
- * @param knownFlags - Snapshot of all flag IDs known at the last install
71
- * (manifest.features.knownFlags), or undefined when
72
- * the manifest pre-dates the snapshot feature.
73
- * @param registry - Flag registry to consult; injectable for tests.
68
+ * Returns a FlagsRecord containing ALL registry flags at their resolved values.
69
+ * FlagsRecord key-presence encodes the "known" concept (ADR-014): present key =
70
+ * known at last install, absent key = new to this install → adopt default on seed.
71
+ *
72
+ * @param manifestFlags - FlagsRecord from the manifest, or null for fresh install.
73
+ * @param registry - Flag registry to consult; injectable for tests.
74
74
  *
75
75
  * Rules:
76
- * - null enabledFlags (fresh) → all default-ON flags in the registry
77
- * - knownFlags === undefined (old manifest, migration) → return enabledFlags
78
- * as-is; adopt nothing new (safe: user's prior choices preserved)
79
- * - Otherwise → enabledFlags ∪ {default-ON flags whose id ∉ knownFlags}
80
- * (newly added registry entries that the user never saw before are auto-adopted)
81
- * - Default-OFF flags are NEVER auto-added regardless of knownFlags
76
+ * - null manifestFlags (fresh install) → all flags at registry defaults
77
+ * - Entry present → keep (coerceFlagValue is applied at read
78
+ * time via sanitizeFlagsRecord; PF-023)
79
+ * - Entry absent → adopt registry default (ADR-014)
80
+ * - Unknown IDs from old manifest → pass through unchanged (forward-compat)
81
+ *
82
+ * Applies ADR-014: absent key = unknown to this install → adopt default.
83
+ * view-mode is not set here; resolveInitSeed sets flags['view-mode'] after composing.
82
84
  */
83
- export function resolveSeedFlags(enabledFlags, knownFlags, registry = FLAG_REGISTRY) {
84
- // Fresh install → all default-ON flags from the registry
85
- if (enabledFlags === null) {
86
- return registry.filter(f => f.defaultEnabled).map(f => f.id);
87
- }
88
- // Old manifest without a knownFlags snapshot → adopt nothing new
89
- if (knownFlags === undefined) {
90
- return [...enabledFlags];
85
+ export function resolveSeedFlags(manifestFlags, registry = FLAG_REGISTRY) {
86
+ // Fresh install → all flags at registry defaults
87
+ if (manifestFlags === null) {
88
+ const result = {};
89
+ for (const flag of registry) {
90
+ result[flag.id] = defaultValueOf(flag); // single default-rule source (CONS-M2)
91
+ }
92
+ return result;
91
93
  }
92
- // Re-init with a knownFlags snapshot: union existing + newly-added default-ON entries
93
- const knownSet = new Set(knownFlags);
94
- const result = new Set(enabledFlags);
94
+ // Existing install: copy present entries then adopt defaults for absent flags.
95
+ // Unknown IDs from the old manifest pass through unchanged (forward-compat).
96
+ const result = { ...manifestFlags };
95
97
  for (const flag of registry) {
96
- if (flag.defaultEnabled && !knownSet.has(flag.id)) {
97
- result.add(flag.id);
98
- }
98
+ if (flag.id in result)
99
+ continue; // known → keep
100
+ result[flag.id] = defaultValueOf(flag); // single default-rule source (CONS-M2)
99
101
  }
100
- return [...result];
102
+ return result;
101
103
  }
102
104
  /**
103
105
  * Resolve the plugin selection buckets for the init seed.
@@ -161,23 +163,40 @@ export function resolveSeedPlugins(manifestPlugins, knownPlugins, allPlugins) {
161
163
  /**
162
164
  * Compose the full init seed from manifest, project config, settings, and registry.
163
165
  *
164
- * viewMode priority: existing settings.json (non-default) → manifest → 'default'
166
+ * view-mode priority: existing settings.json (non-default) → manifest → 'default'.
167
+ * The resolved view mode is encoded into flags['view-mode'] so all flag state lives
168
+ * in one FlagsRecord (applying PF-015: fold before strip — the fold happens here).
165
169
  *
166
170
  * This is the single composition point; callers (init.ts hoist block) call this
167
- * once and pass `seed` down to Phase 4's prompt wiring.
171
+ * once and pass `seed` down to prompt wiring.
168
172
  */
169
173
  export function resolveInitSeed(seedManifest, seedConfig, settingsSnapshot, plugins) {
170
174
  const features = resolveSeedFeatures(seedManifest, seedConfig);
171
- // null for a fresh install (no manifest); string[] from manifest otherwise
172
- const enabledFlags = seedManifest !== null ? seedManifest.features.flags : null;
173
- const flags = resolveSeedFlags(enabledFlags, seedManifest?.features.knownFlags);
175
+ // features.flags is FlagsRecord; null for fresh install (no manifest).
176
+ // seedManifest?.features.flags may be absent at runtime on very old manifests not yet
177
+ // healed — ?? null collapses to fresh-install behavior (all flags at registry defaults).
178
+ const manifestFlags = seedManifest?.features.flags ?? null;
179
+ const flags = resolveSeedFlags(manifestFlags);
174
180
  const manifestPlugins = seedManifest !== null ? seedManifest.plugins : null;
175
181
  const { workflowPlugins, languagePlugins } = resolveSeedPlugins(manifestPlugins, seedManifest?.knownPlugins, plugins);
176
- // viewMode: non-default setting wins; else manifest; else 'default'
177
- const viewMode = resolveExistingViewMode(settingsSnapshot) ??
178
- seedManifest?.features.viewMode ??
179
- 'default';
180
- return { features, flags, viewMode, workflowPlugins, languagePlugins };
182
+ // Encode the resolved view mode into flags['view-mode'] (PF-015: all flag state in FlagsRecord).
183
+ // Priority: existing settings.json (non-default) → flags['view-mode'] from manifest → 'default'.
184
+ // resolveExistingViewMode returns undefined when absent or 'default' — treated as no-opinion.
185
+ const existingViewMode = resolveExistingViewMode(settingsSnapshot);
186
+ const manifestViewMode = readViewMode(flags); // already in flags via resolveSeedFlags spread
187
+ let resolvedViewMode;
188
+ if (existingViewMode !== undefined) {
189
+ resolvedViewMode = existingViewMode; // settings.json non-default wins
190
+ }
191
+ else if (manifestViewMode !== 'default') {
192
+ resolvedViewMode = manifestViewMode; // manifest non-default wins
193
+ }
194
+ else {
195
+ resolvedViewMode = 'default'; // fall back to neutral
196
+ }
197
+ // Return a fresh spread rather than mutating flags in place — keeps this function pure
198
+ // per the module docblock and avoids aliasing if the caller inspects seed.flags.
199
+ return { features, flags: { ...flags, 'view-mode': resolvedViewMode }, workflowPlugins, languagePlugins };
181
200
  }
182
201
  /**
183
202
  * Resolve the three seed inputs under the --reset gate.
@@ -208,8 +227,7 @@ export function resolveResetGatedInputs(reset, manifest, projectConfig, settings
208
227
  * Per-key: `toggles.X ?? base.X` — an explicit CLI value (true/false) wins;
209
228
  * undefined means "user did not specify this flag, keep the seed value".
210
229
  *
211
- * Used in Phase 4 to honour --ambient/--no-ambient etc. passed alongside
212
- * --recommended.
230
+ * Applies explicit --ambient/--no-ambient etc. passed alongside --recommended.
213
231
  */
214
232
  export function applyCliToggles(base, toggles) {
215
233
  return {
@@ -24,7 +24,7 @@ import { stripDevflowTeammateModeFromJson } from '../../core/teammate-mode-clean
24
24
  import { addHudStatusLine, removeHudStatusLine } from './hud.js';
25
25
  import { loadConfig as loadHudConfig, saveConfig as saveHudConfig } from '../../hud/config.js';
26
26
  import { readManifest, writeManifest, resolvePluginList, detectUpgrade } from '../../core/manifest.js';
27
- import { applyFlags, stripFlags, applyViewMode, stripViewMode, FLAG_REGISTRY, resolveExistingViewMode, resolveFinalViewMode } from '../../core/flags.js';
27
+ import { convergeFlagsIntoSettings, countActiveFlags, readViewMode } from '../../core/flags.js';
28
28
  import { addContextHook, removeContextHook, hasContextHook } from './context.js';
29
29
  import { writeConfig, readConfigIfPresent } from '../../core/feature-config.js';
30
30
  import { resolveInitSeed, applyCliToggles, resolveResetGatedInputs } from './init-seed.js';
@@ -276,7 +276,7 @@ export const initCommand = new Command('init')
276
276
  scope,
277
277
  features: {
278
278
  ambient: false, memory: false, hud: true, knowledge: false,
279
- learning: false, rules: false, flags: [], proxy: false,
279
+ learning: false, rules: false, flags: {}, proxy: false,
280
280
  compliance: existingHudManifest?.features.compliance ?? { enabled: false, frameworks: [] },
281
281
  },
282
282
  installedAt: now,
@@ -505,12 +505,11 @@ export const initCommand = new Command('init')
505
505
  // CLI override applied below in both Recommended and Advanced paths.
506
506
  let complianceEnabled = seed.features.compliance.enabled;
507
507
  let complianceFrameworks = seed.features.compliance.frameworks;
508
- let enabledFlags = seed.flags;
509
- let viewMode = seed.viewMode;
510
- // viewModeExplicit: true when the user made an explicit interactive selection or --reset was passed.
511
- // Used by resolveFinalViewMode to decide whether to clobber an externally-set /focus value.
512
- // --reset forces viewMode back to 'default': resolveResetGatedInputs empties the settings snapshot
513
- // so seed.viewMode collapses to 'default', and explicit=true makes that 'default' win at write time.
508
+ let enabledFlags = { ...seed.flags };
509
+ // viewModeExplicit: true when --reset is passed; signals resolveFinalViewMode to let the
510
+ // seed-time view-mode win over an externally-set value in settings.json.
511
+ // --reset empties the settings snapshot via resolveResetGatedInputs so seed.flags['view-mode']
512
+ // collapses to 'default', and explicit=true makes it take effect at settings write time.
514
513
  let viewModeExplicit = !!options.reset;
515
514
  let claudeignoreEnabled = !!earlyGitRoot;
516
515
  let discoveredProjects = [];
@@ -574,7 +573,7 @@ export const initCommand = new Command('init')
574
573
  proxyEnabled = effectiveFeatures.proxy;
575
574
  complianceEnabled = effectiveFeatures.compliance.enabled;
576
575
  complianceFrameworks = effectiveFeatures.compliance.frameworks;
577
- // enabledFlags and viewMode are already initialised to seed values above.
576
+ // enabledFlags is already initialised to seed.flags above.
578
577
  // Compute safe-delete block synchronously so we know whether to fetch installed version
579
578
  if (profilePath && safeDeleteAvailable) {
580
579
  const trashCmd = safeDeleteInfo.command;
@@ -598,7 +597,7 @@ export const initCommand = new Command('init')
598
597
  safeDeleteAction = 'install';
599
598
  }
600
599
  // Print summary
601
- const defaultFlagCount = enabledFlags.length;
600
+ const defaultFlagCount = countActiveFlags(enabledFlags);
602
601
  const complianceSummary = formatComplianceSummary(complianceEnabled, complianceFrameworks);
603
602
  const summaryLines = [
604
603
  `Ambient mode: ${ambientEnabled ? 'enabled' : 'disabled'}`,
@@ -609,8 +608,8 @@ export const initCommand = new Command('init')
609
608
  `Knowledge bases: ${knowledgeEnabled ? 'enabled' : 'disabled'}`,
610
609
  `Ext model routing: ${proxyEnabled ? 'enabled' : 'disabled'}`,
611
610
  `Compliance: ${complianceSummary}`,
612
- `View mode: ${viewMode}`,
613
- `Claude Code flags: ${defaultFlagCount} enabled`,
611
+ `View mode: ${readViewMode(enabledFlags)}`,
612
+ `Claude Code flags: ${defaultFlagCount} configured`,
614
613
  `${claudeignoreEnabled ? '.claudeignore: created' : ''}`,
615
614
  `${safeDeleteAction !== 'skip' ? 'Safe delete: installed' : ''}`,
616
615
  ].filter(l => l.trim()).join('\n');
@@ -794,59 +793,15 @@ export const initCommand = new Command('init')
794
793
  // No third case in practice: on this path the predicate only returns false for a
795
794
  // CLI override (isTTY is guaranteed true by the non-TTY guard above). If it ever
796
795
  // did, the seed values assigned at declaration stand — which is the right default.
797
- // Claude Code flags multiselect (advanced only)
798
- const recommended = FLAG_REGISTRY.filter(f => f.defaultEnabled);
799
- const optional = FLAG_REGISTRY.filter(f => !f.defaultEnabled);
800
- const flagChoices = [
801
- ...recommended.map(f => ({
802
- value: f.id,
803
- label: f.label,
804
- hint: `${f.hint} · recommended`,
805
- })),
806
- { value: '_separator', label: color.dim('── Optional (skip if unsure) ──'), hint: '' },
807
- ...optional.map(f => ({
808
- value: f.id,
809
- label: f.label,
810
- hint: f.hint,
811
- })),
812
- ];
813
- p.note('Recommended flags are pre-selected. Optional flags are for\n' +
814
- 'advanced users — if you don\'t recognize one, skip it.', 'Claude Code Flags');
815
- const flagSelection = await p.multiselect({
816
- message: 'Claude Code flags',
817
- options: flagChoices,
818
- // Pre-seeded from prior state; fresh installs start with all default-ON flags.
819
- initialValues: seed.flags,
820
- required: false,
821
- });
822
- if (p.isCancel(flagSelection)) {
823
- p.cancel('Installation cancelled.');
824
- process.exit(0);
825
- }
826
- enabledFlags = flagSelection.filter(id => id !== '_separator');
827
- // View mode selector (advanced only)
828
- p.note('Controls how much detail Claude Code shows in the transcript.\n' +
829
- '• default — normal display with expandable tool output\n' +
830
- '• verbose — shows everything including thinking blocks\n' +
831
- '• focus — minimal: prompt, one-line tool summaries, final response', 'View Mode');
832
- const viewModeChoice = await p.select({
833
- message: 'View mode',
834
- options: [
835
- { value: 'default', label: 'Default', hint: 'expandable tool output · recommended' },
836
- { value: 'verbose', label: 'Verbose', hint: 'shows everything including thinking' },
837
- { value: 'focus', label: 'Focus', hint: 'minimal output, one-line summaries' },
838
- ],
839
- // Pre-seeded from prior state (fresh installs default to 'default').
840
- initialValue: seed.viewMode,
841
- });
842
- if (p.isCancel(viewModeChoice)) {
843
- p.cancel('Installation cancelled.');
844
- process.exit(0);
796
+ /**
797
+ * D40: init applies seeded flag defaults non-interactively. Flags are customized
798
+ * exclusively via `devflow flags`; re-init preserves existing values and adopts
799
+ * registry defaults only for absent flags (ADR-014). No TUI is opened during init.
800
+ */
801
+ {
802
+ const activeCount = countActiveFlags(enabledFlags);
803
+ p.log.info(`Flags: ${activeCount} active — customize any time with 'devflow flags'`);
845
804
  }
846
- viewMode = viewModeChoice;
847
- // Mark as explicit: user actively selected this mode, so resolveFinalViewMode will
848
- // let it win over an externally-set /focus value.
849
- viewModeExplicit = true;
850
805
  // .claudeignore prompt
851
806
  if (earlyGitRoot) {
852
807
  if (scope === 'user') {
@@ -1431,17 +1386,21 @@ export const initCommand = new Command('init')
1431
1386
  content = removeDreamHook(content);
1432
1387
  // Strip Devflow-managed teammateMode ("auto"). User-set values (e.g. "tmux") are preserved.
1433
1388
  content = stripDevflowTeammateModeFromJson(content);
1434
- // Claude Code flags — strip all managed keys, then re-apply selected flags
1435
- content = stripFlags(content);
1436
- content = applyFlags(content, enabledFlags);
1437
- // Resolve the final viewMode to write.
1438
- // - explicit=true (interactive selection or --reset): selected value always wins
1439
- // - explicit=false (recommended/non-TTY): preserve an externally-set /focus value;
1440
- // otherwise use the seeded viewMode (which already reflects the prior manifest value)
1441
- viewMode = resolveFinalViewMode(resolveExistingViewMode(content), viewMode, viewModeExplicit);
1442
- // View mode — strip then apply for upgrade safety
1443
- content = stripViewMode(content);
1444
- content = applyViewMode(content, viewMode);
1389
+ // Claude Code flags — convergeFlagsIntoSettings is the single pipeline entry point
1390
+ // (ARCH-H1, applies PF-015/PF-017/ADR-014): fold valued flags and view-mode from
1391
+ // existing settings before strip, then strip all managed keys and apply the folded
1392
+ // record. ownedRecord=existingManifest?.features.flags??null distinguishes keys
1393
+ // devflow previously wrote (must not be overridden by fold) from keys newly adopted
1394
+ // by resolveSeedFlags from registry defaults (may be overridden by fold to preserve
1395
+ // user-set hand values — e.g., a hand-set concurrency of '8' survives upgrade).
1396
+ {
1397
+ const { settings: flaggedContent, record: foldedFlags } = convergeFlagsIntoSettings(content, enabledFlags, {
1398
+ viewModeExplicit,
1399
+ ownedRecord: existingManifest?.features.flags ?? null,
1400
+ });
1401
+ content = flaggedContent;
1402
+ enabledFlags = foldedFlags;
1403
+ }
1445
1404
  // Proxy hooks (SessionStart + UserPromptSubmit) — strip-then-add, idempotent.
1446
1405
  // Parse Settings once for the hook mutation; env mutation stays in string space.
1447
1406
  {
@@ -1755,11 +1714,9 @@ export const initCommand = new Command('init')
1755
1714
  knowledge: knowledgeEnabled,
1756
1715
  learning: learningEnabled,
1757
1716
  rules: rulesEnabled,
1717
+ // FlagsRecord written directly — key-presence encodes "known" (ADR-014).
1718
+ // view-mode is encoded as flags['view-mode'] (the resolved final value).
1758
1719
  flags: enabledFlags,
1759
- // Snapshot of known flag ids at this install — used by resolveSeedFlags on next init
1760
- // to detect new default-ON flags and auto-adopt them.
1761
- knownFlags: FLAG_REGISTRY.map(f => f.id),
1762
- viewMode,
1763
1720
  security: securityMode,
1764
1721
  // Final resolved value — may be forced off by preflight failure.
1765
1722
  proxy: proxyEnabled,
@@ -98,22 +98,37 @@ function getDevflowVersion() {
98
98
  }
99
99
  // ─── Internal object helpers (used for single-pass atomic settings write) ────
100
100
  /**
101
- * Mutate a parsed Settings object in place: set ANTHROPIC_BASE_URL to our relay.
102
- * Returns true when the object was changed (used to detect if a write is needed).
101
+ * D-P4-1: Env var paired with ANTHROPIC_BASE_URL so Claude Code does not enforce
102
+ * its conservative context-window limit on relay-routed (non-Claude-model) sessions.
103
+ * Stripped together with the URL — always governed by URL ownership, never independently.
104
+ */
105
+ const UNKNOWN_MODEL_WINDOW_ENV = 'CLAUDE_CODE_DISABLE_UNKNOWN_MODEL_WINDOW_ENFORCEMENT';
106
+ /**
107
+ * Mutate a parsed Settings object in place: set ANTHROPIC_BASE_URL to our relay
108
+ * and set UNKNOWN_MODEL_WINDOW_ENV to '1'.
109
+ *
110
+ * D-P4-1: Each condition is evaluated independently (PF-015 — no short-circuit that
111
+ * skips the second write when the first reports no change).
112
+ *
113
+ * Returns true when the object was changed by either assignment.
103
114
  */
104
115
  function _applyProxyEnvToObject(settings, port) {
105
116
  const s = settings;
106
117
  s.env = s.env ?? {};
107
118
  const env = s.env;
108
119
  const newUrl = proxyBaseUrl(port);
109
- if (env.ANTHROPIC_BASE_URL === newUrl)
110
- return false;
120
+ // D-P4-1: evaluate each condition independently before combining (avoids PF-015 short-circuit)
121
+ const urlChanged = env.ANTHROPIC_BASE_URL !== newUrl;
122
+ const windowVarChanged = env[UNKNOWN_MODEL_WINDOW_ENV] !== '1';
111
123
  env.ANTHROPIC_BASE_URL = newUrl;
112
- return true;
124
+ env[UNKNOWN_MODEL_WINDOW_ENV] = '1';
125
+ return urlChanged || windowVarChanged;
113
126
  }
114
127
  /**
115
- * Mutate a parsed Settings object in place: remove ANTHROPIC_BASE_URL only when
116
- * its value exactly matches our relay on the given managed port.
128
+ * Mutate a parsed Settings object in place: remove UNKNOWN_MODEL_WINDOW_ENV
129
+ * unconditionally (Devflow is its only producer — there is no foreign value to protect),
130
+ * and remove ANTHROPIC_BASE_URL only when it exactly matches our relay on the given
131
+ * managed port.
117
132
  *
118
133
  * Scoped to `managedPort` so a user's own localhost gateway (LiteLLM,
119
134
  * local Ollama proxy, etc.) on ANY other port is never clobbered.
@@ -123,19 +138,31 @@ function _applyProxyEnvToObject(settings, port) {
123
138
  * - enable path → the new port being applied (followed immediately by _applyProxyEnvToObject)
124
139
  * - uninstall → proxy.json.port (or DEFAULT_PROXY_PORT)
125
140
  *
126
- * Returns true when the object was changed.
141
+ * D-P4-1: URL ownership gates the URL delete only; the window var is always ours to
142
+ * remove (applies PF-015, ADR-003). Each outcome is evaluated into a local and OR-ed
143
+ * afterwards — never short-circuit composed inline (PF-015).
144
+ *
145
+ * Returns true when the object was changed by either deletion.
127
146
  */
128
147
  function _stripProxyEnvFromObject(settings, managedPort) {
129
148
  const s = settings;
130
149
  const env = s.env;
131
- if (typeof env?.ANTHROPIC_BASE_URL !== 'string')
132
- return false;
133
- if (env.ANTHROPIC_BASE_URL !== proxyBaseUrl(managedPort))
150
+ if (!env)
134
151
  return false;
135
- delete env.ANTHROPIC_BASE_URL;
152
+ // Devflow is the only producer of this var — always remove it, regardless of whether
153
+ // the URL is still ours. Port-scoping protects a FOREIGN url value; there is no
154
+ // foreign value of this key to protect. (applies PF-015, ADR-003)
155
+ const hadWindowVar = env[UNKNOWN_MODEL_WINDOW_ENV] !== undefined;
156
+ delete env[UNKNOWN_MODEL_WINDOW_ENV];
157
+ let removedUrl = false;
158
+ if (typeof env.ANTHROPIC_BASE_URL === 'string' &&
159
+ env.ANTHROPIC_BASE_URL === proxyBaseUrl(managedPort)) {
160
+ delete env.ANTHROPIC_BASE_URL;
161
+ removedUrl = true;
162
+ }
136
163
  if (Object.keys(env).length === 0)
137
164
  delete s.env;
138
- return true;
165
+ return removedUrl || hadWindowVar; // OR the locals — never compose with || inline (PF-015)
139
166
  }
140
167
  /** Internal: add ensure-proxy hook to one event. Returns true when added. */
141
168
  function _ensureProxyHook(settings, eventName, hookCmd) {
@@ -1327,6 +1354,8 @@ async function runEnable(portOption) {
1327
1354
  s.message('Warming model cache...');
1328
1355
  await discoverExternalModels(cacheDir, logPath).catch(() => { });
1329
1356
  s.stop(color.green('External model routing enabled'));
1357
+ // D-P4-1 / PF-022: applies-on-restart — env var takes effect only for new sessions
1358
+ p.log.info(color.dim('Context-window enforcement disabled for relay-routed models — applies to new Claude Code sessions'));
1330
1359
  if (adopted) {
1331
1360
  p.log.info(`Relay already running on port ${port} — adopted`);
1332
1361
  }
@@ -23,7 +23,7 @@ import { detectShell, getProfilePath } from '../../core/safe-delete.js';
23
23
  import { isAlreadyInstalled, removeFromProfile } from '../../core/safe-delete-install.js';
24
24
  import { removeManagedSettings, stripUserDenyList, detectDenyState, DEVFLOW_HISTORICAL_DENY } from '../../targets/claude-code/post-install.js';
25
25
  import { writeFileAtomicExclusive } from '../../core/fs-atomic.js';
26
- import { stripFlags, stripViewMode } from '../../core/flags.js';
26
+ import { stripFlags } from '../../core/flags.js';
27
27
  import { stripDevflowTeammateModeFromJson } from '../../core/teammate-mode-cleanup.js';
28
28
  import { getPackageRoot, isContainedIn } from '../../core/paths.js';
29
29
  /**
@@ -765,8 +765,7 @@ export async function runCleanupPhase(opts) {
765
765
  settingsContent = removeDreamHook(settingsContent);
766
766
  settingsContent = removeHudStatusLine(settingsContent);
767
767
  settingsContent = removeContextHook(settingsContent);
768
- settingsContent = stripFlags(settingsContent);
769
- settingsContent = stripViewMode(settingsContent);
768
+ settingsContent = stripFlags(settingsContent); // also strips viewMode via view-mode registry entry
770
769
  settingsContent = stripDevflowTeammateModeFromJson(settingsContent);
771
770
  // Remove proxy hooks and ANTHROPIC_BASE_URL env in a single parse-mutate-serialize pass.
772
771
  // REG-1: scope the URL strip to the port Devflow manages — use the pre-captured port
@@ -0,0 +1,9 @@
1
+ /**
2
+ * flags-view public barrel.
3
+ *
4
+ * Commands should import from this barrel to avoid coupling to internals.
5
+ */
6
+ export { runFlagsTui } from './terminal.js';
7
+ export { buildFlagRows, collectFlagRecord } from './state.js';
8
+ export { computeViewportHeight, FIXED_ROWS } from './render.js';
9
+ //# sourceMappingURL=index.js.map