feat(init): state-aware re-init, registry accuracy, complete uninstall#267
Conversation
Six registry deltas verified against dist/commands/*.md grep output:
- devflow-debug: add simplifier, knowledge (debug.md spawns both)
- devflow-implement: add knowledge (implement.md spawns Knowledge writeback)
- devflow-resolve: add knowledge (resolve.md spawns Knowledge writeback)
- devflow-self-review: add knowledge (self-review.md spawns Knowledge writeback)
- devflow-plan: remove git, knowledge (plan.md uses gh directly; knowledge_load
only, no Knowledge agent spawn)
- devflow-release: remove synthesizer (release.md spawns only Validator + Git)
Also adds PluginDefinition JSDoc D-series comments clarifying that skills[] are
ownership declarations (universal-install invariant, not usage lists) and that
agents[] must match compiled command subagent_type spawns exactly.
Adds getAllCommandNames() helper near the existing getAll* cluster.
Adds Guard 4 (command source forward/reverse + dist check) and Guard 5 (build-
gated bidirectional spawn accuracy) to tests/registry-integrity.test.ts. Guard 5
uses normalize(name) = name.replace(/-/g,'').toLowerCase() to reconcile registry
filenames (bug-analyzer) with frontmatter subagent_type values (BugAnalyzer).
Guard 5 correctly failed pre-fix on all six deltas, now passes.
The devflow list command was the sole user of src/cli/commands/list.ts
and its utility functions (formatFeatures, resolveSecurityTriState,
getPluginInstallStatus, formatPluginCommands, resolveScope). No replacement
or back-compat shim — clean end-state per ADR-003.
Removes:
- src/cli/commands/list.ts
- tests/list-logic.test.ts
- import + addCommand in src/cli.ts
- help-text example mentioning devflow list
- devflow list entries in README.md, docs/cli-reference.md,
docs/reference/file-organization.md
On full install only (!isPartialInstall — mirrors the existing cleanup guard), installViaFileCopy now scans ~/.claude/skills/ for devflow:* directories and removes any whose bare name (via unprefixSkillName) is no longer present in getAllSkillNames(). The devflow: namespace is exclusively Devflow's so removal is safe. Bare (pre-namespace) dirs are intentionally untouched — they are handled by the frozen LEGACY_SKILLS_* lists in legacy.ts (avoids PF-012). Adds SKILL_NAMESPACE, unprefixSkillName, getAllSkillNames to the installer import from plugins.ts (they were already exported, just not imported here). Tests (installer-new.test.ts, 3 new): - planted devflow:zzz-orphan removed on full install - devflow:zzz-orphan retained on partial (--plugin) install - bare non-prefixed dir untouched on full install (PF-012 guard) Also fixes plugins.test.ts assertion: 'git' is now first declared by devflow-implement (not devflow-plan, which no longer lists it per WS2 fix).
A declared source file absent from disk is a build/packaging failure — throw a plain Error with a clear message rather than silently skipping. This matches the existing command pattern and surfaces broken installs immediately rather than deploying a partial plugin set. Scope: only DEVFLOW_PLUGINS-declared sources hard-error. Shadow validation paths remain tolerant (ADR-010: invalid shadow → warn-and-install-source). Per-item copy failures are still isolated (avoids PF-009 blast-radius). Tests: 3 new cases in installer-new.test.ts covering agent, skill, and rule missing-source scenarios. Co-Authored-By: Claude <noreply@anthropic.com>
User scope (~/.devflow/): offer full directory removal behind an explicit confirm gate that enumerates user-authored content worth backing up (skill shadows, rule shadows, preference-profile.md, learning.json). Only items that actually exist are listed. If the user declines or the session is non-interactive, fall back to install-artifacts-only removal (scripts/ already removed + manifest.json). Local scope (gitRoot/.devflow/): always remove only install artifacts (scripts/ + manifest.json). Never touch project data directories (memory/, learning/, features/, docs/, config.json). Also removes manifest.json in both scopes (was previously left behind). The post-hoc shadow-leftover warning block is replaced by the pre-deletion confirm gate, which captures and presents user content BEFORE any removal. New export: enumerateUserDevFlowContent() — pure async helper, unit-tested in uninstall-logic.test.ts (12 new cases). computeShadowLeftoverWarnings retained for existing test coverage. Co-Authored-By: Claude <noreply@anthropic.com>
Remove the already-enabled early-return from the --enable path so that statusLine-ensure and syncManifestFeature always run. This mirrors the --disable self-healing design (D1) that already runs unconditionally. Before: calling --enable when config.enabled=true returned after logging "HUD already enabled", bypassing syncManifestFeature entirely. A drifted manifest (hud=false while config says enabled) required a --disable/--enable cycle to repair. After (D2): --enable always calls syncManifestFeature so a single --enable call repairs any drift between config, statusLine, and manifest regardless of the prior config state. Tests: 2 new behavioral tests in hud-enable-selfheal.test.ts: - already-enabled --enable repairs a drifted manifest (hud=false → true) - already-enabled --enable adds a missing statusLine to settings.json Co-Authored-By: Claude <noreply@anthropic.com>
…source WS6a changed installRuleFile to throw when a declared rule source is absent. Three tests in rules.test.ts documented the old silent-skip behavior; update them to assert the new Error throw. Keeps description, addsrejects.toThrow matcher, removes now-false 'skipped' return expectations.
Add three groups of pure helpers that Phase 4 will wire into the init
prompts (no behavior change until then):
src/core/flags.ts
- resolveExistingViewMode(settingsJson): ViewMode | undefined
Extracts a non-default viewMode from a settings JSON string so callers
can chain via ?? to manifest / 'default' fallbacks. Returns undefined
for absent/default/unrecognised values rather than 'default', enabling
clean ?? chaining in resolveInitSeed.
- resolveFinalViewMode(current, selected, explicit): ViewMode
Composes explicit CLI flag, non-default existing setting, and prompt
selection into the final value to write (Phase 4 wire-in).
src/core/feature-config.ts
- readConfigIfPresent(projectRoot): Promise<FeatureConfig | null>
Null-returning variant of readConfig — distinguishes "not configured
yet" (null) from "all features enabled by default" (DEFAULT_CONFIG).
Applies ADR-001: .devflow/config.json is the source of truth; null
means the file is absent or unreadable.
src/cli/commands/init-seed.ts (new)
FeatureSeed / FEATURE_DEFAULTS / InitSeed types plus five pure helpers:
- resolveSeedFeatures: manifest + projectConfig → FeatureSeed
projectConfig wins for memory/learning/knowledge whenever present
(ADR-001); manifest/defaults govern ambient/hud/rules.
- resolveSeedFlags: enabledFlags + knownFlags + registry → string[]
null=fresh → default-ON set; undefined knownFlags → adopt nothing
(migration-safe); else union existing with newly-added default-ON
flags (never auto-adds default-OFF flags).
- resolveSeedPlugins: manifestPlugins + knownPlugins → buckets
null=fresh → non-optional workflow plugins preselected; undefined
knownPlugins → split into buckets, adopt nothing; else split + adopt
new non-optional selectable plugins ∉ knownPlugins.
- resolveInitSeed: composes all three + viewMode resolution
- applyCliToggles: per-key toggles.X ?? base.X
Applies ADR-013: seeding helpers are CLI-init-specific, live beside
init.ts in src/cli/commands/ rather than src/core/.
Uses a local ManifestWithKnownFields intersection type to forward-read
knownFlags/knownPlugins (added to ManifestData in 7b) without
touching manifest.ts in this commit.
Tests: 53 new tests (init-seed.test.ts 30, flags.test.ts +15,
learning-config.test.ts +8). Full suite: 65 files, 1896 tests, all green.
Co-Authored-By: Claude <noreply@anthropic.com>
Add optional snapshot fields to ManifestData: - features.knownFlags?: string[] — FLAG_REGISTRY ids at last install - knownPlugins?: string[] — DEVFLOW_PLUGINS names at last install readManifest self-heals non-array/absent values to undefined (never partial/garbage). init.ts manifest write block populates both snapshots from the live registries. Tests: 7 new round-trip, absent→undefined, and non-array→undefined tests in tests/manifest.test.ts. TASK-2025-07-23_init-composability-phase3
No behavior change — pure read hoist for Phase 4 pre-seeding. Move existingManifest, project FeatureConfig, and settings.json reads to before the plugin multiselect. getInstallationPaths(scope) is deterministic for a given scope, so the early call is safe. The authoritative error gate for failed path resolution stays at the install-begins spinner. Computes _seed = resolveInitSeed(...) immediately after the hoisted reads. _seed is unused in Phase 3; Phase 4 wires it into plugin and feature prompts. Removes duplicate late reads: - existingManifest: was re-read via readManifest(devflowDir) at ~L869 - userSettingsJson: was re-read from claudeDir/settings.json at ~L882 New imports: ManifestData (type), readConfigIfPresent, FeatureConfig (type), resolveInitSeed from init-seed.ts. TASK-2025-07-23_init-composability-phase3
…reset flag
Implements plan commits 7d + 7e + 7f:
7d — Seed interactive prompts + skip mode prompt on re-init
- Renames _seed → seed (touched every use)
- workflowInitialValues uses seed.workflowPlugins (prior selection on re-init,
non-optional defaults on fresh install)
- Language multiselect gains initialValues: seed.languagePlugins
- Re-init routing gate: when seedManifest !== null (and not --recommended /
--advanced / non-TTY), skip the Recommended/Advanced mode prompt entirely;
print "Existing installation detected — press Enter to keep current settings."
- Feature toggle declarations initialised from seed.features.* instead of
hardcoded true; enabledFlags = seed.flags; viewMode = seed.viewMode
- Advanced path: all six feature confirm prompts gain initialValue: seed.features.X
- Flag multiselect: initialValues: seed.flags (removes getDefaultFlags() call)
- viewMode select: initialValue: seed.viewMode; sets viewModeExplicit = true
so commit 7g's resolveFinalViewMode knows the selection was explicit
7e — State-aware non-interactive path
- Recommended branch: replaces six individual if-checks with applyCliToggles
(precedence: explicit CLI flag > seed > registry default)
- enabledFlags and viewMode already seeded above; no redundant re-assignment
- Non-interactive re-init plugin preserve: when !options.plugin && !isTTY &&
seedManifest !== null, sets selectedPlugins from seed.workflowPlugins +
seed.languagePlugins (fixes the composability bug — prior plugin set is now
preserved instead of resetting to all-non-optional)
- Summary line View mode now shows the real effective viewMode (seed value)
— absorbs the fix/init-viewmode-summary-display branch intent
7f — --reset flag
- Adds reset?: boolean to InitOptions and .option('--reset', ...)
- Early validation: --reset + --plugin → p.log.error + process.exit(1)
- Introduces seedManifest / seedConfig (null when --reset, real values
otherwise); resolveInitSeed called with seed inputs, not real values
- Routing gate and non-interactive plugin preserve both use seedManifest !== null
so --reset shows the mode prompt again and takes fresh defaults
- viewModeExplicit = !!options.reset (forces viewMode 'default' through 7g gate)
- Real existingManifest is still used for installedAt preservation and upgrade
messaging — not nulled by --reset
Tests: extends tests/init-seed.test.ts with 3 WS1 composability scenarios:
- Non-interactive re-init preserves existing workflow + language plugin selection
- Factory reset (null manifest) → fresh seed, not prior state
- applyCliToggles on seed correctly preserves non-overridden keys
Co-Authored-By: Claude <noreply@anthropic.com>
…undant warning - Drop ManifestWithKnownFields type alias and extManifest cast from init-seed.ts — ManifestData now carries knownFlags/knownPlugins directly; the bridging type was a mid-commit artifact that outlived its purpose. - Rename _earlyProjectConfig/_earlySettingsJson to earlyProjectConfig/ earlySettingsJson in init.ts — these variables are used, not ignored; the underscore prefix misleadingly signals 'intentionally unused'. - Rename block-local _earlyPaths/_earlyGitRoot to earlyPaths/ earlyGitRootHoist to match. - Collapse settingsConfigured boolean + post-try warn into a single catch message; the two separate warnings conveyed the same failure with duplicated guidance. - Strip D-hoist-7c commit-reference tag from the hoist-reads comment; keep the explanation, drop the artifact identifier.
….json mode
Under --reset the REAL settings.json snapshot was still passed to
resolveInitSeed, so a persisted viewMode (e.g. /focus) surfaced as
seed.viewMode and — with viewModeExplicit=true — survived the factory
reset at write time, violating the USER-LOCKED contract '--reset forces
viewMode default'.
Extract resolveResetGatedInputs (pure) to null the manifest, config, AND
settings snapshot together under --reset, and wire it in. Add regression
tests covering the settings-snapshot path the prior --reset test missed
(it passed an empty '{}' snapshot instead of a real one).
The scope-aware cleanup replaced the post-removal leftover-shadow warning with the enumerateUserDevFlowContent + confirm-gate flow, leaving computeShadowLeftoverWarnings, its ShadowWarning type, and the now-unused getDevFlowDirectory import as transition residue (production-dead, kept alive only by tests). Remove the function, type, import, and its tests — users are now informed of leftover content up front by the enumerator.
devflow-audit-claude (and any future non-selectable optional plugin)
was silently dropped on any plugin-less re-init — interactive or
non-interactive — because partitionSelectablePlugins excludes it from
the prompt buckets, so it never appeared in seed.workflowPlugins or
seed.languagePlugins, and the full-install dir wipe removed its
command dir before re-installing without it.
Root cause chain:
- partitionSelectablePlugins excludes devflow-audit-claude (by design)
- resolveSeedPlugins only carries selectable bucket entries
- selectedPlugins therefore never contained audit-claude on re-init
- pluginsToInstall built from selectedPlugins → audit-claude absent
- resolvePluginList (full install, !isPartialInstall) returns only
installedPluginNames → audit-claude dropped from manifest too
Fix: add resolveNonSelectableOptionalCarry() to init-seed.ts —
a pure helper that returns manifest plugins that are optional and
absent from the selectable buckets. init.ts injects the carry set
into pluginsToInstall on plugin-less full re-inits only.
--reset falls out correctly: resolveResetGatedInputs sets seedManifest
to null → carry is empty → factory reset drops audit-claude as intended.
--plugin X partial installs are unaffected: resolvePluginList already
merges the full manifest list; command dirs are not wiped on partial
installs; the carry block is guarded by !options.plugin.
Co-Authored-By: Claude <noreply@anthropic.com>
Commander's root .action() fires for unrecognised subcommands (e.g. the removed `devflow list`) because they land in program.args rather than routing to a registered handler. The prior code called program.help() unconditionally, exiting 0 for any input — including typos. Check program.args.length inside the root action: when it is non-empty an unknown subcommand was given, so emit an error via program.error() (which writes "error: unknown command '<cmd>'" to stderr and exits 1). Bare `devflow` with no arguments keeps the existing help + exit-0 path. Add tests/cli-unknown-command.test.ts to assert the three required behaviours: bare invocation exits 0, unknown commands exit 1, real subcommands (init/flags/rules --help) remain unaffected. Co-Authored-By: Claude <noreply@anthropic.com>
PR Code Review — High-Confidence Findings1. Precondition Guard + Pure Extraction (uninstall.ts:387)Confidence: 80-85% (Architecture + Reliability) The recursive Recommended Fixes:
// Guard the destructive op: devflowDir must be a resolved .devflow path, never home/root.
if (!devflowDir || path.basename(devflowDir) !== '.devflow' || devflowDir === os.homedir()) {
throw new Error(`Refusing to rm -rf unexpected devflow dir: ${devflowDir}`);
}
|
2. JSDoc Accuracy — Always-Installed vs Optional (init-seed.ts ~line 152)Confidence: 90% JSDoc comment states: "Excluded always-installed plugins (devflow-core-skills, devflow-ambient, devflow-audit-claude)..." However, in Fix: Update the comment to accurately reflect the plugin classification: 3. JSDoc References Non-Existent Flag (flags.ts ~line 1339)Confidence: 85% The Fix: Correct the JSDoc to reflect the actual implementation:
4. Transitional Residue Comments (init.ts ~lines 476, 755)Confidence: 80% Comments reference intermediate commit label "7g" and "commit 7g gate" — residue from the development process (ADR-003 transition residue). Example: "// Apply per WS1 seeding + 7g gate: plug-carry only when..." Fix: Remove these transitional references. Code should reflect the end-state, not intermediate development artifacts. 5. Test Guard Scoping Issue (tests/registry-integrity.test.ts:320)Confidence: 85% Guard 5 reverse-check runs inside the per-command loop but declares intent to verify "at least one command" spawns each declared agent. Failure Scenario: When a plugin gains a second Fix: Accumulate spawned agents across all of the plugin's commands first, then run a single reverse check per plugin: // build spawnedByPlugin: Map<pluginName, Set<normAgent>> in the command loop (forward check stays per-command)
// after the loop, for each plugin: for (const a of declared) if (!spawnedByPlugin.get(name)?.has(a)) violation(...)6. Private State Mutation in Tests (tests/hud-enable-selfheal.test.ts:106)Confidence: 85% Test resets Commander's private Failure Scenario: Commander renames/relocates the private field → the reset becomes a no-op → subsequent tests leak state silently with no signal of broken isolation. Fix: Construct a fresh 7. Stale Compiled Output in Process Tests (tests/cli-unknown-command.test.ts:13)Confidence: 80% Test spawns Failure Scenario: Developer edits Fix: Add a 8. Undocumented User-Facing Flag (docs/cli-reference.md ~lines 15-29)Confidence: 95% (HIGH) New Fix: Add to the Init Options table: Note the mutual-exclusivity gate with |
Summary Comment: Medium-Confidence & Pre-Existing FindingsMedium-Confidence Issues (60-79%)Security (65-70%): Uninstall confirm prompt understates deletion scope. The Performance (85% LOW): Redundant TypeScript (LOW): manifest.ts:93-99 self-heal validates array-ness only while comment claims "never partial/garbage". Downstream-benign (non-string elements simply never match in Set lookups), consistent with existing patterns in the file, but comment overstates the guarantee. Testing (80% MEDIUM): No integration coverage for the non-selectable-optional carry wiring — the core "re-init preserves all state" behavior (init.ts:1025). Testing (82% LOW): "idempotent — does not overwrite existing package.json content" test doesn't actually verify non-overwrite (tests/installer-new.test.ts:74). Fix: write a sentinel key before the second call, then assert the sentinel survives. Reliability (62-65%): Reliability (62-65%): Cancelling the full-cleanup confirm exits 0 after assets were already removed (uninstall.ts:381-384), leaving a stale manifest.json. Consider removing install artifacts on cancel or treating cancel as "keep everything" for consistent end-state. Pre-Existing Issues & Documentation GapsCLAUDE.md (MEDIUM): Still lists removed CLAUDE.md (MEDIUM): "Two-Mode Init" section omits state-aware re-init and cli-reference.md (PRE-EXISTING, MEDIUM): Uninstall Overall AssessmentBlocking Issues: None. No CRITICAL or HIGH defects in the changed lines. High-Confidence Should-Fix (Items 1-8 above): 8 findings across architecture, reliability, testing, and documentation. These are all fixable and improve quality without blocking merge. Low-Risk Pre-Existing: The god-function length of init.ts (1356 lines) and redundant git spawns are architectural debt noted for future refactoring, not merge-blockers. Regression Testing: Excellent. The pure seeding helpers are well-covered; the feature behavior (re-init with state carry, --reset handling, non-selectable-optional preservation) is correct per manual inspection. 1915 tests pass green. Recommendation: APPROVED_WITH_SUGGESTED_FIXES Merge with medium priority on Items 1-8 (especially the scope-cleanup guard at uninstall.ts:387 and the --reset documentation at cli-reference.md). The high-confidence testing and reliability fixes ensure the destructive uninstall and seeding logic remain robust. — Claude Code |
…, fix cancel stale-manifest
ARCH/CPLX: Extract exported `resolveDevflowDirCleanup(scope, isTTY, userContent,
devflowDir, homeDir)` pure decision function mirroring the existing
`resolveSecurityRemovalDecision` pattern. No I/O inside the resolver; the
.action() caller performs all prompting. Collapses the ~5-level inline nesting
into a flat if/else on the two return values ('artifacts-only' | 'prompt').
REL/SEC (precondition guard): the pure resolver checks four invariants before
allowing 'prompt' to proceed to an fs.rm on devflowDir:
1. basename(devflowDir) === '.devflow'
2. devflowDir is not the home directory itself
3. devflowDir is not the filesystem root '/'
4. devflowDir resides inside $HOME (guards DEVFLOW_DIR env overrides)
Any failing invariant falls back to 'artifacts-only' — business logic must not
throw (engineering rule); explicit check satisfies the reliability rule that
invariants be asserted in production code, not only in tests.
SEC: Confirm prompt now enumerates the ENTIRE scope: "plus logs and install
metadata" added to the message body so the destructive action is honest about
what fs.rm(devflowDir, {recursive}) actually removes.
REL (avoids PF-014): cancel branch previously called process.exit(0) AFTER
removeAllDevFlow had already run, leaving manifest.json stale (pointing to
assets no longer on disk). Now falls through to removeDevFlowInstallArtifacts
on both cancel and decline paths, leaving a clean end-state (applies ADR-003).
Tests: TDD red-green on resolveDevflowDirCleanup covering all 5 required cases
(local-scope, user+TTY+content, non-TTY, outside-HOME guard, bad-basename guard)
plus home-dir-itself guard, root guard, no-content case, and exhaustiveness.
All 1927 tests pass. Snyk code scan: 0 issues.
Co-Authored-By: Claude <noreply@anthropic.com>
…s test The @param explicit doc referenced --view-mode which does not exist; the real triggers are --reset and the Advanced interactive prompt selection. Updated the JSDoc to name both sources of truth from init.ts. The getDefaultFlags test was tautological — its expected value was re-derived from the implementation's own filter, so adding or removing a default-on flag would never fail it. Pin it to the hard-coded list of the eight current default-on flag IDs. Co-Authored-By: Claude <noreply@anthropic.com>
…test assertion CONS-1: resolveSeedPlugins JSDoc incorrectly listed devflow-audit-claude as an "always-installed plugin". It is optional: true and non-selectable — exactly why resolveNonSelectableOptionalCarry exists. Updated the comment to distinguish always-installed plugins (core-skills, ambient) from non-selectable optional plugins (audit-claude). PERF-3: resolveNonSelectableOptionalCarry used allPlugins.find() inside .filter() (O(n·m)). Replaced with a name→plugin Map built once before the filter (O(n+m)), matching the file's existing Set-based style. CPLX-4: resolveSeedFeatures repeated an identical projectConfig !== null ternary three times. Extracted a local helper `fromConfig` that removes the duplication while keeping identical behavior. TEST-6: Two assertions in init-seed.test.ts were tautological — one re-derived the expected flag list using the same filter/map the implementation uses, and one re-derived expected values from the seed itself. Both are now pinned to hard-coded values (the 8 known default-ON flag IDs; explicit boolean true for ambient/learning/ knowledge) so they actually constrain behavior. Co-Authored-By: Claude <noreply@anthropic.com>
- Add --recommended and --advanced rows to Init Options table - Add --reset row with factory-reset description and --plugin mutual-exclusivity note - Fix uninstall --scope entry: "default: user" → "default: auto-detect all installed scopes" - Add init-seed.ts to CLI module list in file-organization.md - Remove deleted list.ts from CLAUDE.md src/cli/ module enumeration - Expand Two-Mode Init paragraph: state-aware re-init pre-seeding and --reset flag Co-Authored-By: Claude <noreply@anthropic.com>
The only production caller (uninstall shadow-warning block) was removed in an earlier commit; only skills.test.ts still referenced it. Applies ADR-003 (leave the end-state, no tombstone residue).
…n-string elements Array.isArray alone accepted mixed arrays like [1, null] and cast them as string[], bypassing the self-heal invariant. Add .every(e => typeof e === 'string') so garbage arrays produce undefined, matching the comment's "never partial/garbage" contract. Add two regression tests.
…d rejection installAllRules hard-errors when a declared rule source is missing (packaging bug). Without a catch, the preceding fs.rm leaves the rules directory cleared while the error becomes an unhandled rejection with no user-visible message. Wrap the call in try/catch and surface a clean error before exiting 1. Avoids PF-009 blast-radius.
…nel) TEST-1 (registry-integrity): Guard 5 reverse-check now aggregates spawned agents per-plugin across all commands before checking each declared agent is spawned in at least one command. Previously the per-command spawned set was checked against the whole plugin's declaredAgents, asserting every command spawned every agent — contradicting the documented "at least one command" intent. The agentType-only skip is preserved (plugins with no subagent_type syntax never enter the aggregate map). TEST-3 (cli-unknown-command): Added a dist-existence guard using a top-level await + describe.skipIf(!distExists) on all three describe blocks. When dist/cli.js is absent (no pretest build step), all tests skip gracefully instead of failing with a confusing ENOENT error. Mirrors the Guard 4/5 skip pattern in registry-integrity.test.ts. TEST-5 (installer-new): The "does not overwrite existing package.json" test now writes a _sentinel key into the package.json between the two composeScripts calls. After the second call the sentinel must survive, proving the wx (exclusive-create) flag left the existing file untouched. Previously both calls wrote identical content, so the test passed whether the flag was wx or w. Co-Authored-By: Claude <noreply@anthropic.com>
… wiring helper CONS-3 (applies ADR-003): Remove ephemeral squash-label references `(7g)` and `commit 7g gate` from two viewModeExplicit comments. Keep the substantive explanation; only the transient commit-sigils are removed. PERF-1: Resolve git root once and reuse. `earlyGitRoot` is now declared before the hoisted-reads try block and assigned inside it (`earlyPaths.gitRoot ?? getGitRoot()`), eliminating the redundant unconditional `getGitRoot()` call that previously fired at the Feature-toggles site regardless of the earlier result. TEST-4 (seam): Extract `applyNonSelectableCarry` from the inline wiring in initAction into a pure exported helper in init-seed.ts. The helper encapsulates the `!options.plugin` gate + resolveNonSelectableOptionalCarry call + dedup-merge loop, making the carry wiring independently testable. initAction is updated to delegate to the helper (no behavior change). Co-Authored-By: Claude <noreply@anthropic.com>
Tests the full carry wiring (gate + merge + dedup) extracted in the companion fix commit. Covers: partial-install gate skips carry, null manifest is a no-op, audit-claude is carried on full re-init, dedup prevents double-add, stale names are safely excluded, and the pluginsToInstall argument is not mutated. Co-Authored-By: Claude <noreply@anthropic.com>
…test isolation CONS-5: Replace inline D1:/D2: sigils with @D1/@D2 to match the JSDoc @D-series convention used in sibling files (uninstall.ts). TEST-7: Add three tests for the previously uncovered non-Devflow statusLine branch in hud --enable: non-interactive skip, user-decline, and user-confirm. The confirm test also exposed a production bug: addHudStatusLine's guard for non-Devflow statusLines fired even after the user confirmed the overwrite, leaving settings.json unchanged. Fixed by clearing the non-Devflow statusLine from settingsContent before the addHudStatusLine call when the user confirms. TEST-2 / TEST-8: Replace Commander's private _optionValues = {} reset with a createHudCommand() factory export so tests build a fresh Command per test without coupling to Commander internals. Replace manual process.env save/restore with vi.stubEnv() + vi.unstubAllEnvs() for leak-proof env-mutation hygiene. Co-Authored-By: Claude <noreply@anthropic.com>
Summary
Implements the "Devflow Init Composability & Registry Accuracy Overhaul" plan — six workstreams:
WS1: State-aware init —
devflow initnow reads prior state (manifest +.devflow/config.json+ settings.json) before prompting. Re-init (interactive) skips the Recommended/Advanced question and pre-seeds every prompt from actual state — Enter-through is a no-op. Non-interactive/--recommendedre-init preserves all existing state and adopts defaults only for genuinely new flags/features/plugins, tracked via newknownFlags/knownPluginsmanifest snapshots. New--resetflag = factory reset (also forces viewMode to default;--reset --pluginrejected). Pure seeding helpers in newsrc/cli/commands/init-seed.ts(resolveInitSeed, resolveSeedFeatures/Flags/Plugins, applyCliToggles, resolveResetGatedInputs, resolveNonSelectableOptionalCarry); viewMode helpers (resolveExistingViewMode/resolveFinalViewMode) insrc/core/flags.ts;readConfigIfPresentin feature-config. Settings write now gates viewMode (explicit selection wins, external /focus preserved otherwise), strips legacy teammateMode, and warns instead of silently swallowing write failures.WS2: Registry accuracy — DEVFLOW_PLUGINS agent arrays aligned with actual
subagent_typespawns in compiled commands (debug +simplifier+knowledge; implement/resolve/self-review +knowledge; plan −git−knowledge; release −synthesizer). New registry Guards 4 (command forward/reverse integrity) + 5 (spawn-vs-declaration, would have caught all six deltas).getAllCommandNames()added; skills arrays documented as ownership declarations.WS3: Subcommand safety —
devflow listdeleted outright (broken; no back-compat). Unknown subcommands now exit 1 with an error instead of exiting 0 via help.WS4: Stale asset cleanup — Installer sweeps stale
devflow:*skill dirs on full install via registry prefix-diff (bare pre-namespace dirs untouched — PF-012 invariant intact); replaces reliance on manual legacy-list upkeep for future removals.WS5: Complete uninstall — Uninstall is now a scope-aware complete cleanup: user-scope removes the entire devflow dir behind an explicit confirm that enumerates user-authored content to back up (skill/rule shadows, preference-profile.md, learning.json); declined/non-TTY falls back to install-artifacts-only; local-scope never touches project data (memory/, learning/, features/, docs/, config.json).
WS6: HUD self-healing —
hud --enableis self-healing (repairs statusLine + manifest even when already enabled); installer hard-errors on missing DECLARED agent/skill/rule sources (parity with commands; shadow paths stay tolerant).Behavior Change
Non-interactive re-init now preserves the prior plugin set instead of resetting to all-non-optional plugins.
devflow init --resetrestores the old behavior.Verification
tsc --noEmitclean.--resetpreviously failed to force viewMode default when settings.json held /focus) → re-validate → Evaluator (ALIGNED after preserving non-selectable optional plugins across re-init) → Tester: 10/10 acceptance scenarios PASS, including the original composability bug (flags --disable tui + memory --disable surviveinit --recommended), knownFlags/knownPlugins snapshots, old-manifest migration adopting nothing, --reset semantics, registry subset install (debug plugin gets simplifier+knowledge agents), orphan sweep full-vs-partial, uninstall scope safety, HUD self-heal, external /focus preservation.