@jimhoyd/urlcode 0.4.2 → 0.4.6

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