@jimhoyd/urlcode 0.4.0-alpha.2 → 0.4.0-alpha.3
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.
- package/.claude/skills/urlcode-authoring/SKILL.md +17 -19
- package/.claude/skills/urlcode-operations/SKILL.md +9 -9
- package/.claude-plugin/marketplace.json +1 -1
- package/CONTRIBUTING.md +20 -16
- package/README.md +44 -54
- package/ROADMAP.md +61 -376
- package/dist/BUILD-MANIFEST.json +15 -15
- package/dist/agents-guide.js +6 -6
- package/dist/build-static.js +2 -0
- package/dist/cli.js +3 -3
- package/dist/explain-cli.js +4 -2
- package/dist/explain.js +7 -1
- package/dist/extensions.js +1 -1
- package/dist/manifest.js +7 -1
- package/dist/mcp-authoring.js +2 -2
- package/dist/mcp.js +1 -1
- package/dist/policies.js +3 -1
- package/dist/prerender.js +4 -0
- package/dist/readiness.js +3 -0
- package/dist/route-diff.js +12 -5
- package/dist/types/explain.d.ts +6 -0
- package/dist/types/extensions.d.ts +1 -1
- package/dist/types/manifest.d.ts +5 -1
- package/dist/types/readiness.d.ts +2 -0
- package/dist/types/types.d.ts +8 -1
- package/dist/types.js +8 -1
- package/dist/typescript-authoring.js +18 -7
- package/docs/AI-AUTHORING.md +10 -6
- package/docs/ASSETS.md +2 -1
- package/docs/AUTH-BACKUP.md +32 -0
- package/docs/BEST-PRACTICES.md +17 -9
- package/docs/CAPABILITIES.md +1 -1
- package/docs/CI-FOLLOWUP-2026-09-19.md +97 -0
- package/docs/CI-RELEASE-AUDIT-2026-09-19.md +322 -0
- package/docs/CI.md +8 -3
- package/docs/CODEBASE-AUDIT-2026-09-20.md +278 -0
- package/docs/DEVELOPMENT-PIPELINE.md +181 -0
- package/docs/EXTENSIONS.md +15 -5
- package/docs/FRAMEWORK.md +22 -18
- package/docs/FUNCTION-SECURITY.md +44 -0
- package/docs/LOCAL-DEVELOPMENT.md +3 -0
- package/docs/OPEN-DECISIONS.md +184 -212
- package/docs/OPERATIONAL-PROOF.md +3 -3
- package/docs/OPERATIONS.md +3 -3
- package/docs/POLICIES.md +13 -5
- package/docs/PRERENDER.md +23 -11
- package/docs/PROJECT-DIRECTION.md +3 -3
- package/docs/READINESS.md +6 -2
- package/docs/README.md +18 -41
- package/docs/RELEASE-0.4.0-alpha.3.md +50 -0
- package/docs/RELEASE-READINESS.md +6 -6
- package/docs/RELEASE-SECURITY.md +68 -168
- package/docs/RESILIENCE.md +3 -3
- package/docs/ROUTING.md +3 -4
- package/docs/SECURITY-AUDIT.md +2 -2
- package/docs/SPECIFICATION.md +7 -5
- package/docs/SPIKE-AI-FRAMEWORK-BENCHMARK.md +6 -5
- package/docs/SPIKE-BUSINESS-SUITE.md +14 -6
- package/docs/SPIKE-CORE-LAYERING.md +48 -17
- package/docs/SPIKE-DEFAULT-TRUST-MODEL.md +7 -5
- package/docs/SPIKE-LAMBDA-COMPILE.md +8 -0
- package/docs/STATIC.md +14 -3
- package/docs/TOOLING.md +4 -3
- package/docs/TYPESCRIPT-AUTHORING.md +6 -1
- package/docs/VERSION-ALIGNMENT.md +46 -205
- package/docs/archive/2026-09-19/EXTENSION-IMPLEMENTATION.md +68 -0
- package/docs/archive/2026-09-19/MANAGEMENT-SECURITY.md +102 -0
- package/docs/{NEXT-PHASE-PLAN.md → archive/2026-09-19/NEXT-PHASE-PLAN.md} +15 -5
- package/docs/{NEXT-STEPS.md → archive/2026-09-19/NEXT-STEPS.md} +15 -3
- package/docs/archive/2026-09-19/OPEN-DECISIONS.md +277 -0
- package/docs/archive/2026-09-19/RELEASE-SECURITY.md +186 -0
- package/docs/archive/2026-09-19/ROADMAP.md +386 -0
- package/docs/{SPIKE-EXTENSION-MODEL.md → archive/2026-09-19/SPIKE-EXTENSION-MODEL.md} +10 -0
- package/docs/{SPIKE-EXTENSIONS.md → archive/2026-09-19/SPIKE-EXTENSIONS.md} +25 -14
- package/docs/archive/2026-09-19/SPIKE-MONOREPO.md +776 -0
- package/docs/{USABILITY-REVIEW.md → archive/2026-09-19/USABILITY-REVIEW.md} +12 -2
- package/docs/archive/README.md +27 -0
- package/docs/policies/agents.md +1 -1
- package/docs/policies/compression.md +3 -2
- package/docs/policies/security.md +3 -2
- package/examples/assets/example.yaml +1 -1
- package/examples/cookbook/route-index.json +1 -1
- package/examples/cookbook/routes/middleware.yaml +1 -1
- package/examples/prerender/README.md +14 -6
- package/examples/prerender/functions/page.mjs +4 -2
- package/examples/prerender/middleware/template.mjs +1 -1
- package/examples/prerender/prerender.mjs +1 -1
- package/examples/prerender/urlcode.yaml +8 -4
- package/llms-full.txt +117 -45
- package/llms.txt +5 -4
- package/package.json +24 -4
- package/packaging/claude-plugin/.claude-plugin/plugin.json +2 -2
- package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +17 -19
- package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +9 -9
- package/recipes/authenticated-json-api/README.md +4 -3
- package/recipes/authenticated-json-api/functions/profile.mjs +2 -1
- package/recipes/authenticated-json-api/recipe.yaml +1 -1
- package/recipes/contact-form/functions/contact.mjs +2 -1
- package/recipes/contact-form/recipe.yaml +2 -2
- package/recipes/cors-api/README.md +2 -2
- package/recipes/cors-api/recipe.yaml +1 -1
- package/recipes/health-page/README.md +1 -1
- package/recipes/json-api/README.md +1 -1
- package/recipes/json-api/recipe.yaml +3 -3
- package/recipes/middleware/README.md +8 -4
- package/recipes/protected-download/README.md +1 -1
- package/recipes/protected-download/recipe.yaml +1 -1
- package/recipes/static-plus-api/README.md +2 -2
- package/recipes/static-plus-api/public/index.html +1 -1
- package/recipes/static-plus-api/recipe.yaml +1 -1
- package/recipes/static-plus-api/urlcode.yaml +1 -1
- package/recipes/typescript/recipe.yaml +4 -4
- package/skills/urlcode/SKILL.md +6 -6
- package/starters/default/AGENTS.md +6 -6
- package/docs/SPIKE-MONOREPO.md +0 -322
|
@@ -0,0 +1,776 @@
|
|
|
1
|
+
# Spike: consolidating core, auth, admin and ui into one repo
|
|
2
|
+
|
|
3
|
+
> **Closed 2026-09-19: the migration is done, and this document is history.**
|
|
4
|
+
>
|
|
5
|
+
> `urlcode-ui`, `urlcode-auth` and `urlcode-admin` are workspace packages under
|
|
6
|
+
> `packages/`, and all three have been released from this repository:
|
|
7
|
+
> `@jimhoyd/urlcode-ui@0.1.0-alpha.6`, `@jimhoyd/urlcode-auth@0.1.0-alpha.6`,
|
|
8
|
+
> `@jimhoyd/urlcode-admin@0.1.0-alpha.4`, each on the `alpha` dist-tag with
|
|
9
|
+
> `latest` deliberately held behind. Core's tags are unchanged. The three
|
|
10
|
+
> source repositories are gone.
|
|
11
|
+
>
|
|
12
|
+
> **The operational runbook is now
|
|
13
|
+
> [docs/DEVELOPMENT-PIPELINE.md](../../DEVELOPMENT-PIPELINE.md) and
|
|
14
|
+
> [docs/RELEASE-SECURITY.md](../../RELEASE-SECURITY.md).** Read those, not this.
|
|
15
|
+
> Nothing below is an instruction.
|
|
16
|
+
>
|
|
17
|
+
> ### What the plan got wrong, for the next migration
|
|
18
|
+
>
|
|
19
|
+
> 1. **Its strongest argument was overstated.** "What consolidation would newly
|
|
20
|
+
> enforce" assumed the enforcing checks would follow the code. They did not:
|
|
21
|
+
> `check-guidance-claims.ts` had a hardcoded target list that could never
|
|
22
|
+
> match `packages/`, and `check-trust-model-prose.ts` was root-anchored, so
|
|
23
|
+
> package source comments and each package's `llms.txt` stayed unscanned.
|
|
24
|
+
> Consolidation bought Markdown coverage until both scripts were taught to
|
|
25
|
+
> discover workspace packages.
|
|
26
|
+
> 2. **The real wins were the ones it never claimed.** CodeQL had never run on
|
|
27
|
+
> any of the three repositories, despite each `GOVERNANCE.md` saying it did;
|
|
28
|
+
> the first scan produced a high-severity finding in auth (a false positive,
|
|
29
|
+
> HIBP k-anonymity rather than password storage). Windows had never run
|
|
30
|
+
> either, and produced four genuine latent bugs.
|
|
31
|
+
> 3. **Layout A has a cost the comparison missed.** Core is the repository root
|
|
32
|
+
> rather than a workspace member, so npm resolved the packages' core peer
|
|
33
|
+
> **from the registry** until each declared `file:../..`. Everything passed
|
|
34
|
+
> while building against a published core.
|
|
35
|
+
> 4. **Mechanics #1 did not survive.** The repository allows only squash
|
|
36
|
+
> merges, so the ~130 imported commits collapsed into one and `git blame` on
|
|
37
|
+
> `main` resolves to the merge. That made archiving the source repositories
|
|
38
|
+
> load-bearing — and they were deleted instead. Their history survives only
|
|
39
|
+
> as verified bundles beside the earlier retirements.
|
|
40
|
+
> 5. **Nothing was mentioned about releases, which is where the work was.**
|
|
41
|
+
> Four distinct release-path defects surfaced only by releasing: `--prefix`
|
|
42
|
+
> not isolating from workspace links, Changesets narrowing a peer range,
|
|
43
|
+
> the prepare script not building what it typechecks against, and the
|
|
44
|
+
> coordinator racing npm's propagation.
|
|
45
|
+
>
|
|
46
|
+
> The three open questions at the end were answered by doing it: `peers.json`
|
|
47
|
+
> was deleted outright, `git subtree` was used over `filter-repo`, and the
|
|
48
|
+
> core/monorepo naming overlap was never a problem in practice.
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
> Maintainer update: monorepo work is starting now. The older proposal-only
|
|
52
|
+
> status and instruction to postpone repository changes below are superseded.
|
|
53
|
+
> Retired short-link packages and the deleted docs repository are historical
|
|
54
|
+
> entries, not migration scope.
|
|
55
|
+
>
|
|
56
|
+
> **Correction (2026-09-19, later the same day): middleware is no longer
|
|
57
|
+
> migration scope either.** The instruction this note used to carry —
|
|
58
|
+
> "middleware moves in as its own package with its existing behavior; folding
|
|
59
|
+
> it into core comes afterward" — was overtaken by events.
|
|
60
|
+
> `jimhoyd-com/urlcode-middleware` has been **deleted**, and
|
|
61
|
+
> `@jimhoyd/urlcode-middleware` unpublished from npm at `0.1.0-alpha.2`. There
|
|
62
|
+
> is no `packages/middleware` to create and nothing to fold into core
|
|
63
|
+
> afterward: per-route middleware was already native to core all along
|
|
64
|
+
> (`docs/MIDDLEWARE.md`), and the deleted package only ever offered the same
|
|
65
|
+
> behavior through the extension seam. Every "five repositories" count below
|
|
66
|
+
> is now **four**: core, `urlcode-auth`, `urlcode-admin`, `urlcode-ui`.
|
|
67
|
+
|
|
68
|
+
Status: migration direction accepted and work starting; completion is not claimed.
|
|
69
|
+
The updated analysis from main is retained below.
|
|
70
|
+
|
|
71
|
+
**Precondition re-surveyed 2026-09-19, after the cleanup PRs landed.** An
|
|
72
|
+
earlier revision of this header said the coordinated cleanup PRs were open
|
|
73
|
+
across core, auth, admin, UI and middleware, and told the reader to settle them
|
|
74
|
+
before each package moves. They have since settled — core merged `#175` and
|
|
75
|
+
`#176`, and a fresh survey reports **zero open pull requests across all four**
|
|
76
|
+
in-scope repositories. Core holds open issues; `auth`, `admin` and `ui` report
|
|
77
|
+
zero. Mechanics #0 is therefore satisfied again and mechanics #7 is again a
|
|
78
|
+
no-op. Treat that as perishable and re-run it per repository immediately before
|
|
79
|
+
that repository moves, exactly as mechanics #0 says. Trust by default and
|
|
80
|
+
explicit sandbox choices remain unchanged.
|
|
81
|
+
|
|
82
|
+
> **Update (2026-09-19) — reviewed against the live repositories, npm and the
|
|
83
|
+
> checks that have landed since. Four things changed; the recommendation did
|
|
84
|
+
> not.**
|
|
85
|
+
>
|
|
86
|
+
> 1. **Scope is five repositories, not six.** `urlcode-dynamic-link` was
|
|
87
|
+
> deleted rather than migrated, along with `urlcode-short` and
|
|
88
|
+
> `urlcode-docs` — all three unpublished from npm and their GitHub
|
|
89
|
+
> repositories removed. In scope: core, `urlcode-auth`, `urlcode-admin`,
|
|
90
|
+
> `urlcode-ui`, `urlcode-middleware`. **Superseded later the same day —
|
|
91
|
+
> `urlcode-middleware` was deleted too, making it four. See the correction
|
|
92
|
+
> at the top.**
|
|
93
|
+
> 2. **The hard precondition is currently met.** Zero open pull requests *and*
|
|
94
|
+
> zero open issues across all five (surveyed 2026-09-19), so mechanics #0 is
|
|
95
|
+
> satisfied and mechanics #7 is a no-op. This window closes on its own.
|
|
96
|
+
> **Re-surveyed after the cleanup PRs landed: still zero open PRs, now
|
|
97
|
+
> across four repositories; core has since accumulated open issues. See the
|
|
98
|
+
> top of this document.**
|
|
99
|
+
> 3. **The drift this plan opened with has already recurred** — see "The
|
|
100
|
+
> problem this is answering" below. It is now an observation, not a forecast.
|
|
101
|
+
> 4. **A second argument exists that this document does not make:** the
|
|
102
|
+
> enforcing trust-model check cannot reach the four downstream repositories.
|
|
103
|
+
> See "What consolidation would newly enforce".
|
|
104
|
+
>
|
|
105
|
+
> Sections below carry their own dated notes where the text they replace is
|
|
106
|
+
> kept for the record. Anything not marked still reads as originally written.
|
|
107
|
+
|
|
108
|
+
Treat this the same way as the other `SPIKE-*.md` documents in this
|
|
109
|
+
directory: a recorded decision trail for the maintainer to accept, amend or
|
|
110
|
+
reject. **Superseded in part — the maintainer has since accepted the
|
|
111
|
+
direction (see the top), so the plan below is committed scope, not a
|
|
112
|
+
proposal.**
|
|
113
|
+
|
|
114
|
+
## What this is not
|
|
115
|
+
|
|
116
|
+
> **Superseded, and now factually false.** The paragraph below is kept because
|
|
117
|
+
> it describes the state the plan was drafted in. It said "no git history has
|
|
118
|
+
> been merged"; that stopped being true when `urlcode-ui` was merged in as
|
|
119
|
+
> `packages/ui`. See "What has landed" immediately below for the current
|
|
120
|
+
> state.
|
|
121
|
+
|
|
122
|
+
This is not a recommendation to touch any of `urlcode`, `urlcode-auth`,
|
|
123
|
+
`urlcode-admin` or `urlcode-ui` tonight. No git history has been merged, no
|
|
124
|
+
package has been moved, no CI has been reconfigured. Everything below is a
|
|
125
|
+
sequenced plan to review, not a changelog of what happened.
|
|
126
|
+
|
|
127
|
+
## What has landed
|
|
128
|
+
|
|
129
|
+
> **Added 2026-09-19.** This section is a changelog, not a plan. Everything
|
|
130
|
+
> above it that reads as a proposal should be checked against this first.
|
|
131
|
+
|
|
132
|
+
**All three extensions are in: `packages/ui`, `packages/auth`,
|
|
133
|
+
`packages/admin`.** Sequencing steps 1-5 are done -- step 5 being void, since
|
|
134
|
+
the package it named was deleted rather than migrated. Step 6 and the
|
|
135
|
+
npm re-registrations have not happened, and nothing outward-facing has been
|
|
136
|
+
done at all. The precondition was re-verified immediately before each move and
|
|
137
|
+
held every time: zero open PRs, zero open issues.
|
|
138
|
+
|
|
139
|
+
Suite sizes after the move, all green against core's working tree: core
|
|
140
|
+
512/514 (2 pre-existing skips), auth 204, admin 65, ui 57 with none skipped.
|
|
141
|
+
Auth and admin each lost their `peers.test.ts` along with the file it tested.
|
|
142
|
+
|
|
143
|
+
Specifically:
|
|
144
|
+
|
|
145
|
+
**A cost of layout A that this document does not mention, found on auth's
|
|
146
|
+
move.** Core is the repository root rather than a workspace member, so npm does
|
|
147
|
+
not link it as a workspace sibling: it resolved auth's `@jimhoyd/urlcode` peer
|
|
148
|
+
from the **registry** instead, at the published `0.4.0-alpha.2`, which is 24
|
|
149
|
+
commits behind this tree. Auth would have been built and tested against a
|
|
150
|
+
published core while sitting next to the real one -- the exact drift this
|
|
151
|
+
consolidation exists to remove, reintroduced by the consolidation itself, and
|
|
152
|
+
silently. The fix took two attempts, and the first one failed in a way worth recording.
|
|
153
|
+
A root `"overrides": {"@jimhoyd/urlcode": "file:."}` produced the right symlink
|
|
154
|
+
on disk but wrote a lockfile entry resolving it to `packages/auth`, so
|
|
155
|
+
`npm ci` rejected the tree outright -- caught by CI, not locally, because
|
|
156
|
+
`npm install` tolerates the drift that `npm ci` refuses. Each package now
|
|
157
|
+
declares `"@jimhoyd/urlcode": "file:../.."` as a devDependency instead, which
|
|
158
|
+
is unambiguous.
|
|
159
|
+
|
|
160
|
+
Both failure modes are invisible to the test suites: in the first, everything
|
|
161
|
+
passed against a published core. So
|
|
162
|
+
[`scripts/check-workspace-links.ts`](../scripts/check-workspace-links.ts)
|
|
163
|
+
asserts in `npm run check` that every package declaring core as a peer
|
|
164
|
+
resolves it to this repository's own `package.json`. Layout B would not have
|
|
165
|
+
had this problem at all; it is a real, if small, entry on layout A's side of
|
|
166
|
+
the ledger that the original comparison missed.
|
|
167
|
+
|
|
168
|
+
Auth's own numbers, for the record: 206 tests passed against core's working
|
|
169
|
+
tree at HEAD, so the 24-commit pin gap was stale bookkeeping and nothing more.
|
|
170
|
+
204 remain after `peers.test.ts` was deleted with the file it tested.
|
|
171
|
+
|
|
172
|
+
- `git subtree add --prefix=packages/<name>` for each, with the precondition
|
|
173
|
+
re-verified immediately before every move (zero open PRs, zero open issues).
|
|
174
|
+
On the branch, authorship history was preserved and `git blame` resolved
|
|
175
|
+
through the move. **It does not survive the merge -- see below.**
|
|
176
|
+
|
|
177
|
+
### The merge squashed, and mechanics #1 did not survive it
|
|
178
|
+
|
|
179
|
+
> **Decided 2026-09-19.** `jimhoyd-com/urlcode` allows squash merges only:
|
|
180
|
+
> merge commits and rebase are both disabled, and the `Protect main` ruleset
|
|
181
|
+
> requires linear history, which is *why* they are disabled. A
|
|
182
|
+
> history-preserving merge was therefore not available without suspending that
|
|
183
|
+
> rule, and the maintainer chose to keep linear history and accept the loss.
|
|
184
|
+
|
|
185
|
+
Mechanics #1 says to preserve history "so `git log`/`git blame` on
|
|
186
|
+
`packages/auth/src/auth.ts` still resolves to the real authorship history."
|
|
187
|
+
On `main`, **it does not.** The 142 commits on the branch -- including roughly
|
|
188
|
+
130 imported from the three source repositories -- collapsed into one squash
|
|
189
|
+
commit, so every migrated file blames to that single commit.
|
|
190
|
+
|
|
191
|
+
Two consequences follow, and the second is the important one:
|
|
192
|
+
|
|
193
|
+
1. `git subtree add` bought nothing that a file copy would not have, *for
|
|
194
|
+
`main`*. It was not wasted: the branch history is what made each move
|
|
195
|
+
reviewable, and it is why the imported commits exist anywhere at all.
|
|
196
|
+
2. **Archiving the three source repositories rather than deleting them is now
|
|
197
|
+
load-bearing, not a preference.** They are the only remaining copy of the
|
|
198
|
+
authorship history for `packages/ui`, `packages/auth` and `packages/admin`.
|
|
199
|
+
Step 6 already said archive rather than delete; that instruction has
|
|
200
|
+
stopped being about inbound links and redirects and become the retention
|
|
201
|
+
policy for the history itself. Deleting them would destroy it outright, the
|
|
202
|
+
way the September retirements did for the packages they withdrew -- and
|
|
203
|
+
unlike those, this code is still shipping.
|
|
204
|
+
- The root `package.json` declares `"workspaces": ["packages/*"]`, and the root
|
|
205
|
+
`verify` script now runs each workspace's own `verify` — without that, ui's
|
|
206
|
+
57 tests silently stop running the moment it becomes a workspace.
|
|
207
|
+
- Changesets is configured in `.changeset/`, with `fixed` and `linked` empty so
|
|
208
|
+
independent versioning is preserved. Core is not covered by it, because under
|
|
209
|
+
layout A core is the repository root rather than a workspace member.
|
|
210
|
+
- **Mechanics #3 is done for ui: `peers.json` is gone**, along with
|
|
211
|
+
`scripts/peer-revisions.mjs` and the workflow that read them. ui's
|
|
212
|
+
cross-repository test resolves core from the repository root instead, so it
|
|
213
|
+
runs by default rather than skipping. There is no pinned peer revision left
|
|
214
|
+
to go stale -- a workspace package and its sibling are the same commit by
|
|
215
|
+
construction. ui had no `peers.test.ts`; `auth` and `admin` do, and theirs
|
|
216
|
+
will need deleting with the file.
|
|
217
|
+
- **Mechanics #4 and #5 are done for ui.** `packages/ui/.github/` has been
|
|
218
|
+
removed rather than left inert: GitHub reads workflows, `CODEOWNERS`,
|
|
219
|
+
`dependabot.yml` and issue templates only from the repository root, so every
|
|
220
|
+
file in it was dead where it sat. Verification moved to core's `ci.yml`,
|
|
221
|
+
which now covers ui through the root `verify` on a 3x3 OS/Node matrix --
|
|
222
|
+
wider than the ubuntu-only workflow ui had of its own. Releases moved to
|
|
223
|
+
[`.github/workflows/release-ui.yml`](../.github/workflows/release-ui.yml).
|
|
224
|
+
Root `CODEOWNERS` already matched ui's (`* @jimhoyd`) so nothing was lost,
|
|
225
|
+
and Dependabot's npm entry at `/` covers workspaces from the root. The
|
|
226
|
+
cross-repository links in ui's docs are now relative, including
|
|
227
|
+
`docs/SPIKE-UI.md`'s link to the extension model review, which had been a
|
|
228
|
+
404 since `98b5659` archived its target -- independent of this migration.
|
|
229
|
+
ui's `package.json` `repository`/`homepage`/`bugs` name this repository, with
|
|
230
|
+
`repository.directory` set to `packages/ui`, since those ship to npm.
|
|
231
|
+
|
|
232
|
+
**`pack-sources.mjs` is decided and done.** The two near-identical copies in
|
|
233
|
+
`packages/auth/scripts/` and `packages/admin/scripts/` are replaced by one
|
|
234
|
+
[`scripts/pack-sources.mjs`](../scripts/pack-sources.mjs) at the root. Its four
|
|
235
|
+
repository paths and its `peers.json`-derived core pin are replaced by a single
|
|
236
|
+
required `--revision`. The guarantee an operator verifies got *shorter and
|
|
237
|
+
stronger*: one commit identifies core, ui, auth and admin simultaneously, where
|
|
238
|
+
before it took four revisions plus trust that the `peers.json` pins agreed. What
|
|
239
|
+
is given up is building a mix of revisions across packages, which was the drift
|
|
240
|
+
vector this consolidation exists to remove. The step that installed each freshly
|
|
241
|
+
built tarball as the next package's peer is gone too: the workspace resolves
|
|
242
|
+
every sibling to this tree by construction, which is a stronger guarantee than
|
|
243
|
+
installing tarballs built from it moments earlier, and
|
|
244
|
+
`scripts/check-workspace-links.ts` enforces it.
|
|
245
|
+
|
|
246
|
+
**Not done, and outward-facing -- the maintainer's to do:**
|
|
247
|
+
|
|
248
|
+
1. **Re-register the npm trusted publishers**, all three under
|
|
249
|
+
`jimhoyd-com/urlcode`: `@jimhoyd/urlcode-ui` against
|
|
250
|
+
`.github/workflows/release-ui.yml`, `@jimhoyd/urlcode-auth` against
|
|
251
|
+
`release-auth.yml`, `@jimhoyd/urlcode-admin` against `release-admin.yml`
|
|
252
|
+
(mechanics #6).
|
|
253
|
+
The entry is pinned to a repository *and a workflow filename*, and the
|
|
254
|
+
filename had to change because core already owns `release.yml`. Until this
|
|
255
|
+
is done the publish step fails closed, which is correct behavior rather
|
|
256
|
+
than a bug: **ui cannot be released from here yet.**
|
|
257
|
+
2. **Archive `jimhoyd-com/urlcode-ui`, `-auth` and `-admin`** (step 6)
|
|
258
|
+
-- but only after a release
|
|
259
|
+
from the new location has actually worked. Archive, do not delete: unlike
|
|
260
|
+
the September retirements, this code continues to live at a new path, so
|
|
261
|
+
the clone-URL redirect is the entire point.
|
|
262
|
+
3. **Retag.** `git subtree add` did not carry ui's four `v0.1.0-alpha.*` tags,
|
|
263
|
+
and they are not re-creatable under the scheme decided above anyway. They
|
|
264
|
+
remain in the source repository until it is archived.
|
|
265
|
+
|
|
266
|
+
### Corrections this migration forced on the plan
|
|
267
|
+
|
|
268
|
+
Four claims above did not survive contact, and one of them was the document's
|
|
269
|
+
strongest argument.
|
|
270
|
+
|
|
271
|
+
1. **"What consolidation would newly enforce" was substantially overstated, and
|
|
272
|
+
is now true only because the checks were changed to make it true.** As
|
|
273
|
+
written, `check-trust-model-prose.ts` matched `PROJECT_ROOTS` against
|
|
274
|
+
root-relative prefixes, so `packages/ui/src/*.ts` comments were **not**
|
|
275
|
+
scanned; its `EXTRA_FILES` was root-only, so `packages/ui/llms.txt` -- the
|
|
276
|
+
most agent-facing file the package ships -- was scanned by neither check;
|
|
277
|
+
and `check-guidance-claims.ts` used a hardcoded ten-path `TARGETS` list that
|
|
278
|
+
could never reach a package at all. Consolidation on its own would have
|
|
279
|
+
bought Markdown coverage and nothing else. Both scripts now discover
|
|
280
|
+
workspace packages from disk, which was verified by planting violations in
|
|
281
|
+
`packages/ui/src/kit.ts` and `packages/ui/llms.txt` and confirming a
|
|
282
|
+
non-zero exit. File counts went 417 -> 467 and targets 10 -> 12.
|
|
283
|
+
2. **"21 commits behind" was 24** by the time the move happened, and would have
|
|
284
|
+
kept drifting. Figures in this document go stale within a day; re-measure
|
|
285
|
+
rather than cite.
|
|
286
|
+
3. **`git log <new path>` does not show pre-move history**, contrary to
|
|
287
|
+
mechanics #1. `git blame` does, and nothing is lost, but `git log
|
|
288
|
+
packages/ui/src/kit.ts` returns only the subtree-add commit because the
|
|
289
|
+
original 47 commits record the path as `src/kit.ts`. Use
|
|
290
|
+
`git log <old-sha> -- src/kit.ts`.
|
|
291
|
+
4. **Two day-one breakages the plan did not anticipate.** Core's `eslint .`
|
|
292
|
+
reaches `packages/` immediately, and ui had never been linted: 10 errors on
|
|
293
|
+
the merge commit, plus more from generated `dist/` output once built,
|
|
294
|
+
because the root eslint ignores were root-anchored rather than `**/`-
|
|
295
|
+
anchored. Separately, `scripts/build-styles.mjs` hardcoded a package-local
|
|
296
|
+
`node_modules` path that does not exist once npm hoists devDependencies to
|
|
297
|
+
the workspace root. Both are fixed. Expect the same class of breakage from
|
|
298
|
+
`auth` and `admin`, which have 41 and 7 lint errors respectively and have
|
|
299
|
+
also never been linted.
|
|
300
|
+
|
|
301
|
+
**A collision this document does not mention at all -- now settled.** Core and
|
|
302
|
+
every extension trigger releases on `tags: ['v*']`, and their alpha tags
|
|
303
|
+
literally overlap: ui carries `v0.1.0-alpha.2` through `-alpha.5`, admin
|
|
304
|
+
`v0.1.0-alpha.1` and `-alpha.3`, auth `v0.1.0-alpha.1` through `-alpha.3`. In
|
|
305
|
+
one repository, pushing a bare `v*` tag fires more than one release workflow.
|
|
306
|
+
|
|
307
|
+
Decided: workspace packages release on Changesets' `<package name>@<version>`
|
|
308
|
+
form, core keeps `v*`, and the two cannot collide because a scoped name starts
|
|
309
|
+
with `@`. See [open decisions, "Accepted: per-package release
|
|
310
|
+
tags"](OPEN-DECISIONS.md) for the reasoning and
|
|
311
|
+
[`scripts/check-release-tags.ts`](../scripts/check-release-tags.ts), which
|
|
312
|
+
fails `npm run check` if a future package workflow breaks the scheme. `ui`'s
|
|
313
|
+
workflow has been moved onto it already, including the tag-to-version parsing
|
|
314
|
+
that depended on the old `v` prefix.
|
|
315
|
+
|
|
316
|
+
`git subtree add` does not carry tags, so none of ui's four came across. They
|
|
317
|
+
would not be re-creatable under the old scheme anyway.
|
|
318
|
+
|
|
319
|
+
## The problem this is answering
|
|
320
|
+
|
|
321
|
+
Four repos (`urlcode`, `urlcode-auth`, `urlcode-admin`, `urlcode-ui`) already
|
|
322
|
+
coordinate tightly — `auth`/`admin`/`ui` each pin an exact core revision in
|
|
323
|
+
their own `peers.json`, and `docs/FRAMEWORK.md` describes them as one
|
|
324
|
+
composed product, not four independent ones. Concretely observed cost of that
|
|
325
|
+
coordination happening across four repos, from an evening spent reading all
|
|
326
|
+
four:
|
|
327
|
+
|
|
328
|
+
- **Observed and since fixed, which is the point rather than a counterpoint.**
|
|
329
|
+
When core landed trusted-by-default execution (`b3bde4e`), `urlcode-auth`
|
|
330
|
+
and `urlcode-admin` were both still pinning core at `50790d3a`
|
|
331
|
+
(`0.4.0-alpha.1`), predating it, and `urlcode-auth/SECURITY.md` still
|
|
332
|
+
carried a sentence ("sandboxed guest code") that assumed the old model.
|
|
333
|
+
Both have since been corrected — both repos now pin `d5e86017`, and that
|
|
334
|
+
sentence is gone. Nothing was ever broken in production by either.
|
|
335
|
+
The cost this plan is describing is not "drift goes unnoticed forever"; it
|
|
336
|
+
is that catching and fixing it took a manual pass across three separate
|
|
337
|
+
repositories, with nothing structural to catch it automatically — no
|
|
338
|
+
mechanism flags a downstream repo's prose or pin as stale when an upstream
|
|
339
|
+
contract changes underneath it. That pass has to be repeated by hand on
|
|
340
|
+
every future contract change, for every downstream repo, indefinitely.
|
|
341
|
+
Consolidation removes the class of work, not just this instance of it.
|
|
342
|
+
|
|
343
|
+
> **Update (2026-09-19): it recurred, which settles the argument.** The
|
|
344
|
+
> correction recorded above held for roughly one day. `urlcode-auth`,
|
|
345
|
+
> `urlcode-admin` and `urlcode-ui` all still pin core at `d5e86017`
|
|
346
|
+
> (2026-09-18), now **21 commits behind core's `main`** — a span that
|
|
347
|
+
> includes the trusted-by-default propagation in `db375bf` and the
|
|
348
|
+
> retirements in `10c2439`. Nothing is broken in production again, and that
|
|
349
|
+
> is again beside the point: the manual pass this document described as
|
|
350
|
+
> repeating indefinitely repeated within twenty-four hours of being
|
|
351
|
+
> performed. This is no longer a predicted cost.
|
|
352
|
+
- Two more repos, planned in `docs/SPIKE-CORE-LAYERING.md` and originally
|
|
353
|
+
drafted here as "not yet created," turned out to already exist by the time
|
|
354
|
+
this doc was reviewed: `urlcode-dynamic-link` (7 commits, Phase 2 already
|
|
355
|
+
implemented, `v0.1.0-alpha.1` released) and `urlcode-middleware` (5 commits,
|
|
356
|
+
implemented, `v0.1.0-alpha.1` released), each with its own real commit
|
|
357
|
+
history, release workflow and open issues. That raises the
|
|
358
|
+
actively-coordinated repo count from four to six today, not hypothetically
|
|
359
|
+
— before this plan even accounts for `urlcode-template`, `urlcode-short`,
|
|
360
|
+
`urlcode-docs`, `urlcode-cloud` and `homebrew-urlcode`. It also means
|
|
361
|
+
"create them directly in the monorepo" (this doc's original framing) is no
|
|
362
|
+
longer available for these two — they now need the same history-preserving
|
|
363
|
+
migration as `auth`/`admin`/`ui`, covered in "Migration mechanics" below.
|
|
364
|
+
|
|
365
|
+
None of this is a defect in any one repo. It's the accumulating tax of
|
|
366
|
+
coordinating tightly-coupled, independently-versioned packages across
|
|
367
|
+
separate git histories, issue trackers and CI pipelines by hand.
|
|
368
|
+
|
|
369
|
+
## Scope: what moves, what doesn't
|
|
370
|
+
|
|
371
|
+
Decided (see conversation this spike is drafted from):
|
|
372
|
+
|
|
373
|
+
**In scope — originally six existing repos, all with real history, folded
|
|
374
|
+
into one repo as workspace packages. Two of the six were deleted instead of
|
|
375
|
+
migrated, leaving four:**
|
|
376
|
+
|
|
377
|
+
| Repo today | Becomes |
|
|
378
|
+
|---|---|
|
|
379
|
+
| `urlcode` (core) | `packages/core` (or repo root stays core-shaped, TBD in "Layout options" below) |
|
|
380
|
+
| `urlcode-auth` | `packages/auth` |
|
|
381
|
+
| `urlcode-admin` | `packages/admin` |
|
|
382
|
+
| `urlcode-ui` | `packages/ui` |
|
|
383
|
+
| ~~`urlcode-dynamic-link`~~ | **No longer applicable — repository deleted 2026-09-19, not migrated.** See the note below. |
|
|
384
|
+
| ~~`urlcode-middleware`~~ | **No longer applicable — repository deleted 2026-09-19 at `0.1.0-alpha.2`, not migrated.** Per-route middleware is native to core; see the correction at the top. |
|
|
385
|
+
|
|
386
|
+
> **Update (2026-09-19): five, not six.** `urlcode-dynamic-link` was created,
|
|
387
|
+
> released `v0.1.0-alpha.1`, and deleted within days. Read as evidence rather
|
|
388
|
+
> than as a lost migration target, it is the sharpest data point this document
|
|
389
|
+
> has: standing up a repository per extension was costly enough that one of
|
|
390
|
+
> them was unwound outright rather than maintained. The section "Why six, and
|
|
391
|
+
> not four" below should be read as "why five, and not four"; its argument
|
|
392
|
+
> about `middleware` having already paid the coordination cost was unaffected
|
|
393
|
+
> at the time — though `middleware` was itself deleted later the same day, so
|
|
394
|
+
> the pattern this note reads as a one-off turned out to repeat.
|
|
395
|
+
|
|
396
|
+
**Explicitly out of scope — three live repositories, each for a distinct, real
|
|
397
|
+
reason, not just "left for later":**
|
|
398
|
+
|
|
399
|
+
> **Update (2026-09-19):** this list was four. Two of its entries no longer
|
|
400
|
+
> exist: `urlcode-short` and `urlcode-docs` were both deleted, so neither is a
|
|
401
|
+
> candidate for anything. The three that remain — `homebrew-urlcode`,
|
|
402
|
+
> `urlcode-cloud`, `urlcode-template` — are unaffected, and their reasons hold
|
|
403
|
+
> exactly as written.
|
|
404
|
+
|
|
405
|
+
- **`homebrew-urlcode`** — cannot move. Homebrew tap conventions require a
|
|
406
|
+
repo literally named `homebrew-<name>`; this is an external platform
|
|
407
|
+
constraint, not a project choice.
|
|
408
|
+
- **`urlcode-docs`** — `AGENTS.md` is explicit that public documentation is
|
|
409
|
+
"authored there directly," deliberately separate from code, "no longer
|
|
410
|
+
generated from this repository." Folding it in would reverse a stated,
|
|
411
|
+
recent decision, not follow one.
|
|
412
|
+
|
|
413
|
+
> **Update (2026-09-19): resolved — the repository is deleted.** The decision
|
|
414
|
+
> quoted above was reversed, and then `urlcode-docs` was unpublished and
|
|
415
|
+
> **removed from GitHub**, not merely archived. Documentation is authored in
|
|
416
|
+
> this repository's `docs/`, and `AGENTS.md` no longer sends pages anywhere
|
|
417
|
+
> else — see [open decisions, item 6](OPEN-DECISIONS.md). This entry is kept
|
|
418
|
+
> only so the reversal is legible; there is nothing left to include or
|
|
419
|
+
> exclude.
|
|
420
|
+
- **`urlcode-cloud`** — a separately-lifecycled hosted product (private
|
|
421
|
+
repo); its release cadence and access model have no reason to match a
|
|
422
|
+
library monorepo's.
|
|
423
|
+
- **`urlcode-template`** (and, when it existed, `urlcode-short`) — an
|
|
424
|
+
example/starter project, not a library package. Mixing "things you
|
|
425
|
+
`npm install`" with "things you `git clone` as a starting point" in one
|
|
426
|
+
workspace is a different kind of repo than what this spike is solving for.
|
|
427
|
+
`urlcode-short` was deleted on 2026-09-19; the reasoning survives it and
|
|
428
|
+
still governs `urlcode-template`, which remains out of scope.
|
|
429
|
+
|
|
430
|
+
## Why five, and not four — resolved: it is four
|
|
431
|
+
|
|
432
|
+
> **Update (2026-09-19):** written as "why six", then narrowed to five when
|
|
433
|
+
> `dynamic-link` was deleted. `middleware` has since been deleted as well, so
|
|
434
|
+
> the answer is **four**, and this section is now entirely historical. It is
|
|
435
|
+
> kept because the reasoning is what the outcome refutes: the argument below
|
|
436
|
+
> was that `link` and `middleware` had already paid the coordination cost and
|
|
437
|
+
> should therefore be folded in rather than left outside the fix. Both were
|
|
438
|
+
> instead withdrawn altogether. That is a third possible response to the
|
|
439
|
+
> coordination cost this document is about — not "consolidate it" and not
|
|
440
|
+
> "keep paying it", but "stop shipping the thing" — and it is the one that
|
|
441
|
+
> actually happened, twice. Worth weighing before the next extension gets its
|
|
442
|
+
> own repository.
|
|
443
|
+
|
|
444
|
+
`link` and `middleware` were extracted *out* of core specifically so core
|
|
445
|
+
stays "the smallest thing that is still a complete product on its own"
|
|
446
|
+
(`docs/SPIKE-CORE-LAYERING.md`). Both are now real, shipped repos: they
|
|
447
|
+
already paid the coordination cost this spike is trying to remove —
|
|
448
|
+
`urlcode-dynamic-link`'s and `urlcode-middleware`'s own `peers.json`-style
|
|
449
|
+
pins against core, their own CI, their own docs that can drift the same way
|
|
450
|
+
`urlcode-auth/SECURITY.md` already did. Folding them into this consolidation
|
|
451
|
+
alongside `auth`/`admin`/`ui` stops that from compounding further, rather
|
|
452
|
+
than leaving two more repos outside the fix.
|
|
453
|
+
|
|
454
|
+
## Layout: decided — option A
|
|
455
|
+
|
|
456
|
+
**A. Root repo is core, extensions live under `packages/`.**
|
|
457
|
+
```
|
|
458
|
+
urlcode/
|
|
459
|
+
src/ # core, unchanged in place
|
|
460
|
+
packages/
|
|
461
|
+
auth/
|
|
462
|
+
admin/
|
|
463
|
+
ui/
|
|
464
|
+
```
|
|
465
|
+
Lowest-friction for core's own history (nothing moves), but makes "core" and
|
|
466
|
+
"the monorepo" the same name, which may read as core absorbing the
|
|
467
|
+
extensions rather than the extensions and core coexisting as peers — worth a
|
|
468
|
+
naming discussion given `AGENTS.md`'s "Core never imports them" independence
|
|
469
|
+
framing.
|
|
470
|
+
|
|
471
|
+
**B. Everything moves under `packages/`, including core — considered, not
|
|
472
|
+
chosen.** Would have been symmetric and avoided the naming overlap noted
|
|
473
|
+
above, at real cost: core's own history would need to move too, and every
|
|
474
|
+
external reference to `urlcode`'s current repo path (`docs/`, READMEs
|
|
475
|
+
elsewhere, the `@jimhoyd/urlcode` package's repository field, CI badges,
|
|
476
|
+
this evening's own `peer-camera`/`peer-eyes` citations) would need updating.
|
|
477
|
+
Decided against for exactly that reason.
|
|
478
|
+
|
|
479
|
+
**Decided: (A).** Core's repo and history stay exactly where they are; the
|
|
480
|
+
extension packages move to it — three of them, `auth`, `admin` and `ui`,
|
|
481
|
+
after the `dynamic-link` and `middleware` deletions. The one open item this
|
|
482
|
+
still leaves,
|
|
483
|
+
worth a short naming discussion rather than blocking anything: "core" and
|
|
484
|
+
"the consolidated repo" now share a name, which could read as core absorbing
|
|
485
|
+
the extensions rather than the two coexisting as independent packages
|
|
486
|
+
(`AGENTS.md`'s "Core never imports them" framing still holds in code either
|
|
487
|
+
way — this is a naming-perception question, not a contract question).
|
|
488
|
+
|
|
489
|
+
## Migration mechanics, per repo
|
|
490
|
+
|
|
491
|
+
For each of `urlcode-auth`, `urlcode-admin` and `urlcode-ui` — three repos
|
|
492
|
+
with real history, joining core, which stays in place (as of 2026-09-19;
|
|
493
|
+
drafted as five, before `urlcode-dynamic-link` and then `urlcode-middleware`
|
|
494
|
+
were deleted):
|
|
495
|
+
|
|
496
|
+
0. **Drain open pull requests first — a hard precondition, not a courtesy.**
|
|
497
|
+
Before a repo is migrated, it must have zero open PRs (and no unmerged
|
|
498
|
+
release branch). A PR open against the source repo at the moment its code
|
|
499
|
+
moves is stranded: its branch targets a `main` that no longer receives
|
|
500
|
+
code, its diff is written against paths (`src/…`) that no longer exist at
|
|
501
|
+
that location, and re-creating it against the consolidated repo means
|
|
502
|
+
rebasing onto a different repository and a new path prefix
|
|
503
|
+
(`packages/<name>/src/…`) by hand. GitHub cannot retarget a PR across
|
|
504
|
+
repositories. So for each repo, in order: stop merging new work, merge or
|
|
505
|
+
close what is open, confirm `gh pr list`/the API reports none, then
|
|
506
|
+
migrate. Any PR that cannot be merged in time should be closed with its
|
|
507
|
+
branch preserved and re-opened against the consolidated repo afterwards —
|
|
508
|
+
a deliberate choice recorded on the PR, not an accident discovered later.
|
|
509
|
+
This is also the real reason to pick a quiet window for the migration
|
|
510
|
+
rather than a busy one: the cost of this step scales with how much is
|
|
511
|
+
in flight.
|
|
512
|
+
|
|
513
|
+
> **Update (2026-09-19): this precondition is met right now.** Surveyed
|
|
514
|
+
> across all five in-scope repositories: `urlcode`, `urlcode-auth`,
|
|
515
|
+
> `urlcode-admin`, `urlcode-ui` and `urlcode-middleware` each report **zero
|
|
516
|
+
> open pull requests**, and all but core report zero open issues (core holds
|
|
517
|
+
> `#168` and `#58`, neither of which is a migration blocker). Nothing is in
|
|
518
|
+
> flight anywhere. This is the quiet window this step asks for, and it is not
|
|
519
|
+
> a stable state — it closes the moment work resumes on any of the five.
|
|
520
|
+
>
|
|
521
|
+
> **Re-surveyed later the same day, after the cleanup PRs opened and
|
|
522
|
+
> merged:** four in-scope repositories now (`urlcode-middleware` is
|
|
523
|
+
> deleted), still **zero open pull requests across all four**. `auth`,
|
|
524
|
+
> `admin` and `ui` report zero open issues; core's open-issue count has
|
|
525
|
+
> grown past the two named above and none of them block migration either.
|
|
526
|
+
> The window described as closing on its own has so far reopened each time
|
|
527
|
+
> — which is an argument for re-running the survey, not for trusting any
|
|
528
|
+
> recorded figure in this document.
|
|
529
|
+
1. **Preserve history with `git subtree add` or `git filter-repo` +
|
|
530
|
+
merge**, not a fresh copy — so `git log`/`git blame` on
|
|
531
|
+
`packages/auth/src/auth.ts` still resolves to the real authorship history
|
|
532
|
+
from `urlcode-auth`, and so a future "actually, let's give this its own
|
|
533
|
+
repo back" is a clean `git filter-repo` extraction, not archaeology.
|
|
534
|
+
`git subtree` is the lower-risk default (reversible, no force-push
|
|
535
|
+
required on the source repos); `git filter-repo` gives cleaner resulting
|
|
536
|
+
history at the cost of being a one-way rewrite of the joining repo's
|
|
537
|
+
local copy (the original `urlcode-auth` GitHub repo is untouched either
|
|
538
|
+
way — this only rewrites what gets pulled in).
|
|
539
|
+
2. **npm workspace restructuring**: `package.json` at the monorepo root gets
|
|
540
|
+
`"workspaces": ["packages/*"]` (the same shape `peer-camera` already
|
|
541
|
+
uses); each `packages/<name>/package.json` keeps its own name/version,
|
|
542
|
+
independently publishable — this is what preserves "independently
|
|
543
|
+
versioned packages" as a property, not something this migration gives up.
|
|
544
|
+
**Decided: [Changesets](https://github.com/changesets/changesets) for the
|
|
545
|
+
release flow, not Nx or Turborepo.** A changeset is a small, bounded,
|
|
546
|
+
git-diffable markdown file (package name + semver bump + description) —
|
|
547
|
+
cheap and low-risk for an agent or a human to generate correctly, easy
|
|
548
|
+
for CI to verify mechanically ("does every touched package have one"),
|
|
549
|
+
and it's the deliberate checkpoint that stops local workspace-linked
|
|
550
|
+
development (testing against a sibling package's unreleased state, which
|
|
551
|
+
is now the default once auth/admin/ui sit next to
|
|
552
|
+
core) from silently becoming a real release. Nx/Turborepo were considered
|
|
553
|
+
and set aside: both add a much larger, more inference-heavy configuration
|
|
554
|
+
surface (task graphs, remote caching semantics) that's a bigger, more
|
|
555
|
+
opaque thing to get wrong than this repo's four packages currently need —
|
|
556
|
+
plain `npm test -w packages/auth`-style workspace scoping already covers
|
|
557
|
+
what this size of repo actually requires. Revisit only if the package
|
|
558
|
+
count grows enough that rebuild/retest time becomes a real problem.
|
|
559
|
+
3. **`peers.json` becomes unnecessary for the three that moved** — a
|
|
560
|
+
workspace package can depend on a sibling workspace package directly
|
|
561
|
+
(`"@jimhoyd/urlcode": "workspace:*"` or npm's equivalent), which is
|
|
562
|
+
inherently always in sync, no separate pin file, no drift possible by
|
|
563
|
+
construction. `peers.json`-the-mechanism might still matter if any
|
|
564
|
+
*external* consumer needs a reviewed-revision pin story — worth deciding
|
|
565
|
+
explicitly rather than silently dropping the safeguard.
|
|
566
|
+
4. **CI consolidation**: one `verify.yml` (or similar) with
|
|
567
|
+
path-filtered jobs per package, replacing four separate workflow files.
|
|
568
|
+
`CODEOWNERS` can still express per-package ownership within one repo
|
|
569
|
+
(path-scoped rules), so "who reviews auth changes" doesn't have to
|
|
570
|
+
become "everyone reviews everything."
|
|
571
|
+
5. **Docs cross-references**: every `EXTENSIONS.md` links into another repository-
|
|
572
|
+
style cross-repo link in `auth`/`admin`/`ui`'s current docs becomes a
|
|
573
|
+
same-repo relative link once consolidated — this is a real cleanup
|
|
574
|
+
opportunity, not just migration overhead, since it directly targets the
|
|
575
|
+
"docs silently drifted apart" problem this spike opened with.
|
|
576
|
+
6. **Re-register npm Trusted Publishing per package.** Every repo's
|
|
577
|
+
release workflow publishes via OIDC trusted publishing, no long-lived npm
|
|
578
|
+
token (`docs/SPIKE-CORE-LAYERING.md`'s governance section, confirmed by
|
|
579
|
+
`urlcode-middleware`'s own "Add trusted-publishing release workflow"
|
|
580
|
+
commit). That trust is registered on npmjs.com per package, pinned to an
|
|
581
|
+
exact GitHub repo + workflow filename (+ optional environment) — it does
|
|
582
|
+
not follow the code when the repo path changes. Each of
|
|
583
|
+
`@jimhoyd/urlcode-auth`, `-admin` and `-ui` needs its
|
|
584
|
+
npmjs.com trusted-publisher entry updated to the new repo and new workflow
|
|
585
|
+
path *before* that package's first release from the consolidated location,
|
|
586
|
+
or the publish step fails closed (correctly — not a security gap, just an
|
|
587
|
+
ordering dependency this plan needs to carry explicitly rather than
|
|
588
|
+
discover at release time).
|
|
589
|
+
7. **Issue migration — decided: recreate open issues in the consolidated
|
|
590
|
+
repo, not leave-and-link.** GitHub doesn't move issues across repos
|
|
591
|
+
natively, so this means bulk-recreating each open issue at the new
|
|
592
|
+
location with a back-link to the original (closed with a pointer) rather
|
|
593
|
+
than leaving it where it is.
|
|
594
|
+
|
|
595
|
+
> **Update (2026-09-19): currently a no-op — there is nothing to
|
|
596
|
+
> recreate.** `urlcode-auth`, `urlcode-admin` and `urlcode-ui` all report
|
|
597
|
+
> **zero open issues**. Middleware's two, which this step was written
|
|
598
|
+
> around, were both closed before its repository was deleted (`#1`,
|
|
599
|
+
> `sandbox: true` unsupported, and `#3`, the vendored core tarball), along
|
|
600
|
+
> with a later `#4`. Those issue links no longer resolve — the deletion
|
|
601
|
+
> took the tracker with it — but the issue bodies were captured to
|
|
602
|
+
> `urlcode-middleware-issues.json` alongside the code bundle, so the
|
|
603
|
+
> content survives even though the URLs do not. `urlcode-dynamic-link`'s
|
|
604
|
+
> tally is equally moot: that repository is gone too. The decision above
|
|
605
|
+
> stands as policy for whatever is open at migration time; the concrete
|
|
606
|
+
> scope it enumerated has emptied out. Re-survey immediately before
|
|
607
|
+
> migrating rather than trusting this line.
|
|
608
|
+
|
|
609
|
+
The original scope, for the record:
|
|
610
|
+
|
|
611
|
+
> `auth`, `admin` and `ui`'s own open-issue counts weren't re-audited here,
|
|
612
|
+
> but `urlcode-dynamic-link` and `urlcode-middleware` were, since they're
|
|
613
|
+
> the two repos whose "does this even apply" status changed mid-conversation:
|
|
614
|
+
> `urlcode-dynamic-link` had 0 open issues — nothing to migrate.
|
|
615
|
+
> `urlcode-middleware` had 2 open issues to recreate, `#1`
|
|
616
|
+
> ("`sandbox: true` is not supported — needs its own QuickJS/WASM worker
|
|
617
|
+
> pool") and `#3` ("Remove vendored core tarball once `@jimhoyd/urlcode`
|
|
618
|
+
> 0.4.0-alpha.2+ is published to npm"). Both were to move to the
|
|
619
|
+
> consolidated repo's tracker when the merge actually happened, each closed
|
|
620
|
+
> in its original location with a link to the new issue.
|
|
621
|
+
|
|
622
|
+
## What consolidation would newly enforce
|
|
623
|
+
|
|
624
|
+
> **Added 2026-09-19.** This section did not exist when the spike was drafted,
|
|
625
|
+
> because the checks it describes did not exist either. It is the strongest
|
|
626
|
+
> argument in the document.
|
|
627
|
+
|
|
628
|
+
Since this plan was written, two checks landed in `npm run check`, and both
|
|
629
|
+
**fail CI** rather than reporting:
|
|
630
|
+
|
|
631
|
+
- [`scripts/check-trust-model-prose.ts`](../scripts/check-trust-model-prose.ts)
|
|
632
|
+
rejects prose describing the pre-`0.4.0-alpha.2` trust model as current.
|
|
633
|
+
Since `db375bf` it reaches well past Markdown: comments in `src/`,
|
|
634
|
+
`scripts/`, `examples/`, `starters/`, `recipes/` and `benchmarks/`, plus
|
|
635
|
+
`llms.txt`/`llms-full.txt`, and it cross-checks that a project whose prose
|
|
636
|
+
claims isolation actually declares `sandbox: true` somewhere in its YAML.
|
|
637
|
+
- [`scripts/check-guidance-claims.ts`](../scripts/check-guidance-claims.ts)
|
|
638
|
+
rejects agent-facing guidance that contradicts
|
|
639
|
+
`schemas/urlcode.schema.json` — including the inverse case, guidance calling
|
|
640
|
+
a field invented when the schema defines it
|
|
641
|
+
([historical decisions, item 7](archive/2026-09-19/OPEN-DECISIONS.md)).
|
|
642
|
+
|
|
643
|
+
**Both stop at this checkout.** The specific failure this document opens with —
|
|
644
|
+
`urlcode-auth/SECURITY.md` asserting "sandboxed guest code" after core inverted
|
|
645
|
+
the default — sits in a file that neither check can see, and cannot see while
|
|
646
|
+
`auth` lives in its own repository. The same is true of `admin` and `ui`.
|
|
647
|
+
It was also true of `middleware`, whose repository was deleted before the
|
|
648
|
+
question could be settled either way.
|
|
649
|
+
|
|
650
|
+
That reframes what consolidation buys. The original case was that it removes a
|
|
651
|
+
class of manual coordination work. The stronger case, available only now, is
|
|
652
|
+
that it places three packages' prose under an **existing, working, enforcing
|
|
653
|
+
correctness gate** for the contract most likely to be misdescribed downstream —
|
|
654
|
+
trusted-by-default execution, which is precisely where the observed drift
|
|
655
|
+
happened. No other proposal on the table extends that check's reach; writing a
|
|
656
|
+
cross-repository variant of it would mean building and maintaining a CI job that
|
|
657
|
+
clones three repositories on every core change, which is the coordination cost
|
|
658
|
+
again wearing a different hat.
|
|
659
|
+
|
|
660
|
+
One related gap, unchanged: `npm run check:downstream-skills` is advisory and
|
|
661
|
+
sits outside both `check` and `verify`, consistent with
|
|
662
|
+
[historical decisions, item 9](archive/2026-09-19/OPEN-DECISIONS.md).
|
|
663
|
+
The cleanup review has since adjudicated the template's skill drift and prepared
|
|
664
|
+
aligned copies in its draft PR; the report itself remains advisory.
|
|
665
|
+
|
|
666
|
+
## What this preserves, unchanged
|
|
667
|
+
|
|
668
|
+
- **The trust/extension model itself.** `packages/auth` published from the
|
|
669
|
+
monorepo is exactly as separate a package, with exactly the same
|
|
670
|
+
`RuntimeExtension` contract, revision-pinning and operator-registration
|
|
671
|
+
requirements, as `urlcode-auth` published from its own repo today. This
|
|
672
|
+
spike changes where the source lives, not what the extension mechanism
|
|
673
|
+
guarantees.
|
|
674
|
+
- **Independent versioning and release cadence per package** — a monorepo
|
|
675
|
+
with workspaces is not "one version number for everything."
|
|
676
|
+
|
|
677
|
+
## What this gives up, honestly
|
|
678
|
+
|
|
679
|
+
- **Per-repo maturity gating.** `docs/SPIKE-CORE-LAYERING.md` records that
|
|
680
|
+
`auth`/`admin`/`ui` used a "`private: true` until reviewed" pattern before
|
|
681
|
+
their first public release, and that the two new repos are deliberately
|
|
682
|
+
*not* following that pattern ("published public from the start"). A
|
|
683
|
+
monorepo can't easily make one folder private and another public — the
|
|
684
|
+
repo-level visibility setting is all-or-nothing on GitHub. Once
|
|
685
|
+
consolidated, "private until reviewed" stops being available as a pattern
|
|
686
|
+
for whatever the next extension after `middleware`/`dynamic-link` turns
|
|
687
|
+
out to be, unless it's built in yet another separate private repo first
|
|
688
|
+
and merged in later — which reintroduces a version of the coordination
|
|
689
|
+
cost this spike is trying to remove, just for pre-release work instead of
|
|
690
|
+
ongoing maintenance.
|
|
691
|
+
- **"Fork just one piece" stops being a plain `git clone` — but scoped to a
|
|
692
|
+
narrow audience, not every auth user.** `SPIKE-AUTH.md` names forkability
|
|
693
|
+
as a deliberate design goal specifically for `auth`. It's important not to
|
|
694
|
+
overstate who this actually affects: a developer customizing auth's look
|
|
695
|
+
or copy (theme, relabeling, `extra.css`, a shadowed template) works
|
|
696
|
+
entirely inside *their own* project repo via the `ui` extension's layering
|
|
697
|
+
system (`ui/copy`, `ui/extra.css`, `ui/templates`) — they never clone or
|
|
698
|
+
fork `urlcode-auth` at all, install it from npm like any dependency, and
|
|
699
|
+
this migration changes nothing for them. The friction increase applies
|
|
700
|
+
only to the much narrower case of someone changing auth's actual *logic*
|
|
701
|
+
(a new sign-in method, different session semantics) — something the
|
|
702
|
+
layering system can't express because it's behavior, not presentation.
|
|
703
|
+
For that persona, forking just the auth package post-consolidation means a
|
|
704
|
+
`git filter-repo`-style history extraction instead of `git clone
|
|
705
|
+
jimhoyd-com/urlcode-auth` — solvable, but a real step up in friction, for
|
|
706
|
+
a small population, not the common path.
|
|
707
|
+
- **Blast radius of a bad CI run.** One consolidated CI means a
|
|
708
|
+
misconfigured job can, in principle, block merges across all four
|
|
709
|
+
packages at once, where today a broken `urlcode-ui` pipeline can't stop an
|
|
710
|
+
unrelated `urlcode-auth` merge. Path-filtered jobs mitigate this but don't
|
|
711
|
+
eliminate it the way full repo separation does.
|
|
712
|
+
|
|
713
|
+
## Sequencing, if this is accepted
|
|
714
|
+
|
|
715
|
+
1. Decide layout (A vs. B above) and confirm the out-of-scope list.
|
|
716
|
+
2. **Check open pull requests across all three joining repos before starting,
|
|
717
|
+
and again per repo immediately before its own migration** (mechanics #0). A
|
|
718
|
+
repo with anything open is not ready to move. Doing this as a survey first
|
|
719
|
+
also sizes the whole migration honestly: the number of in-flight PRs is the
|
|
720
|
+
real scheduling constraint, not the git mechanics.
|
|
721
|
+
|
|
722
|
+
> **Update (2026-09-19):** surveyed — zero open pull requests across all
|
|
723
|
+
> five repositories including core, and zero open issues outside core. The
|
|
724
|
+
> survey this step asks for has been done once and came back clean. Re-run
|
|
725
|
+
> it rather than relying on that, since it goes stale the moment work
|
|
726
|
+
> resumes. **It did go stale, twice over, within the same day:** the cleanup
|
|
727
|
+
> PRs opened and merged, and `urlcode-middleware` stopped existing. The
|
|
728
|
+
> current figure is zero open PRs across four repositories — see the top
|
|
729
|
+
> of this document, and re-run it again anyway.
|
|
730
|
+
3. Migrate `urlcode-ui` first (fewest inbound dependents — `auth`/`admin`
|
|
731
|
+
both depend on it, nothing depends on them), proving the subtree +
|
|
732
|
+
workspace mechanics on the lowest-risk package. Re-register its npm
|
|
733
|
+
trusted publisher (mechanics #6) before cutting its first release from
|
|
734
|
+
the new location — treat this as part of "done," not a follow-up.
|
|
735
|
+
4. Migrate `urlcode-auth`, then `urlcode-admin` — same re-registration step
|
|
736
|
+
each time.
|
|
737
|
+
5. ~~Migrate `urlcode-middleware`.~~ **Void — nothing to migrate.**
|
|
738
|
+
|
|
739
|
+
> **Update (2026-09-19):** this step read "migrate `urlcode-dynamic-link`,
|
|
740
|
+
> then `urlcode-middleware`," and carried their issue tallies. Both halves
|
|
741
|
+
> are now void: each repository was deleted rather than migrated. The
|
|
742
|
+
> migration therefore ends at step 4, with `ui`, `auth` and `admin` moved
|
|
743
|
+
> and core in place. No `packages/middleware` is created, and no
|
|
744
|
+
> trusted-publisher entry is re-registered for `@jimhoyd/urlcode-middleware`
|
|
745
|
+
> — that package is unpublished.
|
|
746
|
+
6. Retire (archive, don't delete — GitHub redirects an archived repo's clone
|
|
747
|
+
URL) the three now-empty source repos, with their READMEs pointing at the
|
|
748
|
+
new location.
|
|
749
|
+
|
|
750
|
+
> **Update (2026-09-19): "archive, don't delete" now has a counter-example
|
|
751
|
+
> in this project's own history, and it is worth weighing rather than
|
|
752
|
+
> restating.** `urlcode-dynamic-link`, `urlcode-short` and `urlcode-docs`
|
|
753
|
+
> were all **deleted outright**, so their clone URLs and every inbound link
|
|
754
|
+
> to them 404 with no redirect; the only surviving copies are verified
|
|
755
|
+
> `git bundle`s held locally (`fd9dc84`), which preserve the code but not
|
|
756
|
+
> the issues, the pull request history or any inbound URL. That was
|
|
757
|
+
> defensible for repositories whose code was being withdrawn entirely — and
|
|
758
|
+
> it still cost a citation: `urlcode-docs#17` is quoted as evidence in
|
|
759
|
+
> [historical decisions, item 7](archive/2026-09-19/OPEN-DECISIONS.md) and no longer resolves. It is a different case from a
|
|
760
|
+
> repository whose code continues to live at a new path, which is what this
|
|
761
|
+
> step covers and where the redirect is the entire point. Keep the
|
|
762
|
+
> archive-don't-delete rule here, and note explicitly that it diverges from
|
|
763
|
+
> what was done during the September 2026 retirements — the two situations
|
|
764
|
+
> are not the same and the precedent should not be read across.
|
|
765
|
+
|
|
766
|
+
## Open questions for the maintainer, not answered here
|
|
767
|
+
|
|
768
|
+
- Does `peers.json`'s reviewed-pin discipline need an equivalent for any
|
|
769
|
+
external (non-workspace) consumer, or does workspace-linking fully replace
|
|
770
|
+
its purpose?
|
|
771
|
+
- `git subtree` vs. `git filter-repo` for history preservation — a real
|
|
772
|
+
tradeoff between migration safety and final history cleanliness, worth a
|
|
773
|
+
deliberate call rather than defaulting.
|
|
774
|
+
- The naming-perception question from "Layout: decided — option A" above
|
|
775
|
+
(core's repo and the consolidated repo sharing a name) — worth a short
|
|
776
|
+
discussion, not blocking.
|