Skip to content

Commit d0e0c16

Browse files
authored
docs(roadmap): add phased improvement roadmap documentation (#46)
Add comprehensive roadmap documentation outlining the strategic direction for inup, including the north star vision and seven development phases (correctness, headless, trust & UX, intelligence, infra, ecosystem, distribution) with sequenced, verified tasks and implementation notes.
1 parent 8095fe6 commit d0e0c16

9 files changed

Lines changed: 596 additions & 0 deletions

File tree

docs/roadmap/00-north-star.md

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# 00 — North Star: from upgrader to upgrade co-pilot
2+
3+
> The phase files (01–07) are *tasks* — they make inup **better**.
4+
> This file is the *thesis* — what would make inup **inevitable**.
5+
6+
## The reframe
7+
8+
Nobody wants to "upgrade dependencies." They want to **stay current without breaking their app and
9+
without spending an afternoon finding out the hard way.** The job inup is hired for isn't editing
10+
version strings — it's *removing the fear*. Today inup, like `npm-check-updates`, `taze`, and
11+
`npm outdated`, answers **"what's outdated?"** That question is commoditized. The unsolved, valuable
12+
one is:
13+
14+
> **"Is this upgrade safe *for my code*, and if not, what exactly do I have to change?"**
15+
16+
A god-tier inup is the only tool that answers that — **locally, privately, before you commit.**
17+
"Upgrade with confidence," not "upgrade and pray." That is the entire game.
18+
19+
## Why inup specifically can win this
20+
21+
inup already gathers, on most runs, much of the raw material the intelligence layer needs — it just
22+
doesn't *reason* over it. **But be precise about what's actually in memory** (verified 2026-05-30),
23+
because the previous version of this document over-claimed it:
24+
25+
| Asset | Where | Status today | Unlocks |
26+
|---|---|---|---|
27+
| Every published version per package | [npm-registry.ts](../../src/services/npm-registry.ts) | ✅ fetched | semver-distance scoring |
28+
| `deprecated` field | abbreviated packument | ⚠️ fetched, **dropped** by [package-metadata.ts](../../src/features/changelog/parsers/package-metadata.ts) | hard-"hold" signal — just extract it |
29+
| `engines` field | manifest | ⚠️ present, **read nowhere** | runtime-compat warning — just extract it |
30+
| Vulnerability advisories incl. `vulnerable_versions` | [vulnerability-checker.ts](../../src/services/vulnerability-checker.ts) | ⚠️ fetched, **never cross-referenced to the target** | "does this upgrade *fix* the CVE?" |
31+
| Download counts | [npm-registry-client.ts](../../src/features/changelog/clients/npm-registry-client.ts) | ✅ fetched (`weeklyDownloads`) | adoption/maturity signal |
32+
| Changelogs / GitHub releases | [features/changelog](../../src/features/changelog/) | ✅ fetched | breaking-change detection |
33+
| Per-version publish **`time`** | npm registry |**NOT fetched** — inup requests the *abbreviated* doc ([npm-registry.ts:88](../../src/services/npm-registry.ts#L88)) | release-age scoring — **needs a fetch decision** ([04](04-intelligence.md) #5) |
34+
| Full package.json graph | [package-detector.ts](../../src/core/package-detector.ts) || the constraint set to solve |
35+
| Streaming + persistent cache | [persistent-cache.ts](../../src/services/persistent-cache.ts) || doing the above *fast* |
36+
| Git working-tree state | [utils/git.ts](../../src/utils/git.ts) | ✅ (read-only) | safe, revertible application |
37+
38+
The honest read: **most signals are a field-extraction away; `time` (release age) is the one that
39+
isn't.** That distinction *is* the sequencing of [P3](04-intelligence.md).
40+
41+
## The wedges, tiered by honesty
42+
43+
They differ by an order of magnitude in cost and risk. **Build down the list; don't pretend the
44+
bottom tier is plannable today.**
45+
46+
### NEAR — plannable now, mostly arithmetic ([04-intelligence.md](04-intelligence.md))
47+
48+
**The Risk Score — turn a wall of versions into a verdict.** A single, **explainable** 0–100 number
49+
per upgrade, composed from: semver distance, the vuln-fix cross-reference, deprecation, `engines.node`,
50+
changelog breaking-section presence, adoption, and (once the fetch decision is made) release age.
51+
Output: the list sorts and colours into **"safe now / review / hold."** The cheapest wedge with the
52+
highest perceived-intelligence payoff — and, crucially, its **first three inputs need no new fetch**
53+
(see [04](04-intelligence.md) Tier A). This is the next real feature after the headless spine.
54+
55+
### FAR — large but independently shippable (each is its own project)
56+
57+
**The Verify Loop — "upgrade and pray" becomes "upgrade, verified."** Apply the selected set in an
58+
**isolated git worktree**, run `install``build``test`, report pass/fail; on failure
59+
**auto-bisect** the culprit and offer to keep only the green subset. *Seed:* the git-revert + test-hook
60+
work in [02-headless.md](02-headless.md) #8 is the first rung.
61+
62+
**The Constraint Solver — never propose a set that won't install.** Treat a multi-select as a
63+
constraint problem over **peer dependencies** and **ecosystem groups** (react + react-dom +
64+
@types/react move together; all `@babel/*` align), and solve for a coherent version set. **Note:**
65+
inup has *no* grouping primitive today (the previously-cited `scope-grouping.ts` is not in the
66+
source) — so this builds the semantic-grouping layer from scratch; it doesn't extend an existing one.
67+
68+
### SPECULATIVE — research, not a backlog item
69+
70+
**Usage-Aware Impact — the holy grail.** Parse the user's own imports/call sites, intersect with each
71+
package's **breaking surface** (changelog `BREAKING` entries + removed/renamed exports inferred from
72+
shipped `.d.ts` between versions), and say:
73+
74+
> "You call `foo.bar()` in 3 files. It was **removed in v3**. Here are the call sites."
75+
76+
The difference between "react-router went 6→7 (risky)" and "you use two APIs from it and neither
77+
changed — this major is safe *for you*." No competitor does this. It is also a genuine research
78+
problem (JS/TS/ESM/CJS/JSX, re-exports, dynamic imports). **Treat it as a spike, not a sprint.** Start
79+
absurdly narrow (static ESM named imports, TS-only) and prove signal before committing.
80+
81+
**The AI migration co-pilot.** Once Usage-Aware Impact yields *a breaking change + the exact affected
82+
call sites*, an LLM can draft the migration diff for *this* repo. The god-tier leap — and it must
83+
honor inup's ethos (**no telemetry, no data collection**) as a hard constraint:
84+
85+
- **Explicit opt-in**, off by default.
86+
- **Bring-your-own-key**, provider-agnostic; nothing leaves the machine without consent.
87+
- The deterministic engine is the product; AI is an *accelerator*, never a dependency. inup stays
88+
fully useful with network and AI both off.
89+
90+
> **Why this tier exists:** the most exciting ideas here are the least ready to plan. Keeping them
91+
> visible but un-scheduled protects near-term work from being starved by ambition.
92+
93+
## Non-negotiables (the identity that must survive)
94+
95+
1. **Zero-config still works.** Intelligence is progressive: great with no setup, deeper if you opt
96+
in. Never a wizard on first run.
97+
2. **Privacy-first, no telemetry.** A tool that edits source-controlled files earns trust by keeping
98+
computation local.
99+
3. **Fast.** The streaming/cache architecture is a feature; the intelligence layer must stay off the
100+
critical path (compute async, render progressively).
101+
4. **Beautiful & multi-PM.** TUI polish and npm/yarn/pnpm/bun parity are table stakes we don't regress.
102+
103+
## The roadmap arc
104+
105+
```
106+
GOOD (today) GREAT (P0→P3) GOD TIER (the moat)
107+
───────────── ─────────────────── ─────────────────────────
108+
interactive multi-PM P0 protect correct writes Verify Loop (FAR)
109+
upgrader, themes, P1 headless engine Constraint Solver (FAR)
110+
vuln badges, P2 honest, discoverable UX Usage-Aware Impact (SPECULATIVE)
111+
changelog viewer P3 Risk Score (NEAR wedge) + AI migration (SPECULATIVE)
112+
113+
"what's outdated?" "what's safe?" "fix it for me, safely"
114+
```
115+
116+
Sequencing logic:
117+
118+
- **P0 is small now** — the feared write bug was already fixed; P0 just *locks* that with a test and
119+
cleans up three small mis-detections ([01](01-correctness.md)). Correctness is still the floor;
120+
it's just mostly already poured.
121+
- **"Great" is mostly reasoning over data inup already fetches** — high leverage, low new
122+
infrastructure. But it needs the **headless engine** ([02](02-headless.md)) underneath it first, so
123+
a verdict has a consumer.
124+
- **The NEAR wedge (Risk Score) is the only god-tier-adjacent feature worth scheduling now**, and its
125+
first inputs are free.
126+
- **FAR and SPECULATIVE need net-new capability** — each lands one at a time, after the critical path
127+
is paid down.
128+
129+
## The repositioning, in one line
130+
131+
> **inup** — not "an interactive dependency upgrader," but **the dependency upgrade co-pilot: know
132+
> what's safe, see what it touches, and fix it — locally, privately, before you ship.**
133+
134+
## Honest risks
135+
136+
- **Source parsing is hard** — why Usage-Aware Impact is *speculative*. Prove it on a spike first.
137+
- **Verify Loop is slow & environment-dependent.** Opt-in, cache aggressively, bisect only on failure.
138+
- **Risk scoring can mislead if it pretends to be precise.** Keep it *explainable* — always show the
139+
why, never a bare number.
140+
- **AI cost/accuracy/privacy.** Strictly opt-in + BYO-key; suggestions are drafts the user reviews.
141+
- **The newest risk: drift between the plan and the code.** This revision exists because the prior
142+
roadmap's headline rested on a bug that no longer existed. Every claim here is dated and verifiable —
143+
keep it that way.

docs/roadmap/01-correctness.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# P0 — Correctness: protect the writes that already work
2+
3+
> **Read this first, because it overturns the old plan.** The previous roadmap opened by claiming
4+
> inup *"silently strips the `^`/`~` range prefix and pins your dependency on every upgrade."*
5+
> **That is false against the current code** (verified 2026-05-30). The write at
6+
> [upgrader.ts:142](../../src/core/upgrader.ts#L142)/[154](../../src/core/upgrader.ts#L154) does
7+
> write `choice.targetVersion` verbatim — but that value is **already prefixed** upstream:
8+
> [selection-state-builder.ts:127-139](../../src/ui/session/selection-state-builder.ts#L127-L139)
9+
> builds it through `applyVersionPrefix()`
10+
> ([ui/utils/version.ts:6-10](../../src/ui/utils/version.ts#L6-L10)), which lifts the original
11+
> operator and re-applies it. So `"^1.2.3"``"^1.5.0"`. The headline bug doesn't exist.
12+
>
13+
> That changes this phase's job. It is no longer *"stop producing wrong results"* — it's **"protect
14+
> the correct behavior with a test, then fix three genuinely-silent mis-detections."** P0 went from a
15+
> phase to an afternoon. The trust floor is mostly already poured; we're sealing it.
16+
17+
See the [legend](README.md#legend) for rating definitions.
18+
19+
| # | Task | Value | Cx | Effort | Notes / reuse |
20+
|---|---|:--:|:--:|---|---|
21+
| 1 | **Lock the write path with a characterization test** | 🔴 | S | 0.5d | **New, and the most important item here.** Prefix-preservation works but has **no test guarding it** — one careless refactor of `applyVersionPrefix` or `createUpgradeChoices` silently reintroduces the exact bug the old roadmap feared. Golden-test the write end-to-end: feed a `package.json` with a `^` dep, a `~` dep, an exact pin, tabs *and* 4-space siblings; assert the output preserves each operator, the indentation, the trailing newline, and is a **no-op when nothing changed**. This is the cheapest insurance in the project and the guardrail for #2. Extend [test/unit/core](../../test/unit/core). |
22+
| 2 | **Preserve `package.json` formatting on write** | 🔴 | S | 0.5–1d | [upgrader.ts:161](../../src/core/upgrader.ts#L161) does `JSON.stringify(pkg, null, 2) + '\n'`, normalizing tabs/4-space/odd-ordered files to 2-space and forcing a trailing newline. Real, but **S not M**: detect the existing indent (read the raw text before parse) and the original final-newline, round-trip both, and **skip the write entirely if the serialized result is unchanged**. Re-rated down — the old roadmap called this M by lumping it with the (non-existent) prefix bug. |
23+
| 3 | **Detect Bun's text lockfile `bun.lock`** | 🔴 | S | 0.5d | Bun ≥ 1.2 writes a text `bun.lock`; the detector only lists `bun.lockb` ([package-manager-detector.ts:44](../../src/services/package-manager-detector.ts#L44),[:111](../../src/services/package-manager-detector.ts#L111)). A lockfile-only Bun repo falls through to the npm default. One array entry. *Narrow blast radius:* repos with `"packageManager": "bun@…"` are already caught by the field regex at [:92](../../src/services/package-manager-detector.ts#L92). |
24+
| 4 | **Fix the `lib/` silent skip — make `SKIP_DIRS` overridable** | 🟡 | S | 0.5d | `SKIP_DIRS` hardcodes `lib`/`es`/`esm`/`cjs` ([scan.ts:9-19](../../src/utils/filesystem/scan.ts#L9-L19)), so a monorepo package living under `lib/` is **silently skipped, no warning**. The `.inuprc` reader already exists ([project-config.ts:8](../../src/config/project-config.ts#L8)) — add an `includeDirs`/`scanDirs` override there, and at minimum log when a `package.json`-bearing dir is pruned by the default list. *(Broader discovery work — `.gitignore` awareness, scanner de-dup — lives in [06-ecosystem.md](06-ecosystem.md).)* |
25+
26+
## Why this ordering
27+
28+
- **#1 before #2.** Write the lock test first, then make the formatting change against it — the test
29+
is what lets you touch [upgrader.ts:160-162](../../src/core/upgrader.ts#L160-L162) without fear,
30+
and it permanently documents the prefix-preservation contract so it can't quietly rot.
31+
- **#3 and #4 are independent afternoon fixes.** Each kills one class of *silent* wrongness
32+
(mis-detected PM, skipped package). They don't depend on #1/#2 and can land in any order.
33+
34+
## What was removed from the old P0 (and why)
35+
36+
- **"Preserve the range-prefix style" (was the 🔴 headline).****Already implemented.** Deleted,
37+
and its emotional framing with it. Its only legacy here is #1, which *locks* the behavior.
38+
- **"Cross-platform `package.json` path handling."** Demoted to a footnote. The string
39+
`packageJsonPath.replace('/package.json', '')` at
40+
[upgrader.ts:122](../../src/core/upgrader.ts#L122) is POSIX-only, but it only feeds a **spinner
41+
label** (`packageDir`); the actual file write at [:161](../../src/core/upgrader.ts#L161) uses the
42+
real path, and install dir resolution at [:52](../../src/core/upgrader.ts#L52) already uses
43+
`dirname()`. It's a cosmetic log glitch on Windows, not a correctness bug. *Fix it opportunistically
44+
by copying the `dirname()` call from line 52 — don't schedule it.*
45+
46+
## Relationship to later phases
47+
48+
- The **`--save-exact`** opt-out (for users who *want* exact pins) lives in
49+
[06-ecosystem.md](06-ecosystem.md). Because preservation is already the default, that flag is now
50+
the *primary* prefix control rather than a follow-on — see its reframing there.
51+
- **Routing detector warnings to stderr** (so they don't corrupt `--json`) is an output-hygiene
52+
concern handled in [02-headless.md](02-headless.md), not here.
53+
- The **broader write-path coverage** (beyond the #1 golden test) is tracked in
54+
[05-infra.md](05-infra.md).

0 commit comments

Comments
 (0)