@jimhoyd/urlcode 0.3.0 → 0.4.0-alpha.1

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 (326) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +106 -0
  2. package/.claude/skills/urlcode-operations/SKILL.md +114 -0
  3. package/.claude-plugin/marketplace.json +18 -0
  4. package/CONTRIBUTING.md +30 -2
  5. package/README.md +157 -230
  6. package/ROADMAP.md +85 -2
  7. package/SECURITY.md +8 -1
  8. package/dist/BUILD-MANIFEST.json +62 -32
  9. package/dist/adapters.js +4 -23
  10. package/dist/agent-lists.js +1 -1
  11. package/dist/agents-guide.js +113 -0
  12. package/dist/authoring-files.js +60 -0
  13. package/dist/authoring.js +11 -1
  14. package/dist/aws.js +4 -3
  15. package/dist/build-cloudflare.js +11 -24
  16. package/dist/bulk.js +37 -0
  17. package/dist/capabilities.js +208 -0
  18. package/dist/capability-query.js +72 -0
  19. package/dist/catalog.js +105 -0
  20. package/dist/cli.js +154 -15
  21. package/dist/client-address.js +1 -1
  22. package/dist/compliance-rules/baseline.js +8 -8
  23. package/dist/compliance-rules/privacy.js +3 -3
  24. package/dist/compliance-rules/strict.js +5 -5
  25. package/dist/conditions.js +88 -0
  26. package/dist/config.js +60 -4
  27. package/dist/context.js +156 -0
  28. package/dist/ecosystem-cli.js +88 -0
  29. package/dist/egress.js +98 -0
  30. package/dist/examples.js +92 -0
  31. package/dist/explain-cli.js +64 -0
  32. package/dist/explain.js +132 -0
  33. package/dist/extensions.js +172 -0
  34. package/dist/function-sources.js +15 -3
  35. package/dist/index.js +37 -0
  36. package/dist/init-with.js +165 -0
  37. package/dist/interchange-cli.js +42 -0
  38. package/dist/interchange.js +189 -0
  39. package/dist/link-cli.js +1 -1
  40. package/dist/management-policy.js +0 -1
  41. package/dist/manifest.js +111 -0
  42. package/dist/match.js +2 -2
  43. package/dist/mcp-authoring.js +147 -0
  44. package/dist/mcp.js +97 -0
  45. package/dist/observability.js +6 -0
  46. package/dist/operator-host.js +29 -0
  47. package/dist/plugins.js +12 -0
  48. package/dist/policies/agents.js +2 -2
  49. package/dist/policies/compression.js +2 -1
  50. package/dist/policies/security.js +0 -0
  51. package/dist/policies.js +1 -1
  52. package/dist/policy.js +29 -7
  53. package/dist/prerender.js +100 -41
  54. package/dist/project-tests.js +3 -3
  55. package/dist/provider-verification.js +92 -0
  56. package/dist/proxy.js +44 -0
  57. package/dist/readiness.js +5 -5
  58. package/dist/recipes.js +41 -0
  59. package/dist/route-diff.js +106 -0
  60. package/dist/router.js +42 -2
  61. package/dist/runtime.js +95 -14
  62. package/dist/schema-query.js +62 -0
  63. package/dist/signals.js +24 -0
  64. package/dist/site.js +0 -0
  65. package/dist/tooling.js +96 -0
  66. package/dist/types/adapters.d.ts +7 -4
  67. package/dist/types/agent-lists.d.ts +0 -1
  68. package/dist/types/agents-guide.d.ts +17 -0
  69. package/dist/types/authoring-files.d.ts +10 -0
  70. package/dist/types/aws.d.ts +3 -1
  71. package/dist/types/build-cloudflare.d.ts +1 -0
  72. package/dist/types/bulk.d.ts +27 -0
  73. package/dist/types/capabilities.d.ts +55 -0
  74. package/dist/types/capability-query.d.ts +24 -0
  75. package/dist/types/catalog.d.ts +65 -0
  76. package/dist/types/client-address.d.ts +0 -1
  77. package/dist/types/compliance-rules/baseline.d.ts +1 -9
  78. package/dist/types/compliance-rules/privacy.d.ts +1 -4
  79. package/dist/types/compliance-rules/strict.d.ts +0 -5
  80. package/dist/types/conditions.d.ts +19 -0
  81. package/dist/types/config.d.ts +20 -2
  82. package/dist/types/context.d.ts +65 -0
  83. package/dist/types/ecosystem-cli.d.ts +17 -0
  84. package/dist/types/egress.d.ts +46 -0
  85. package/dist/types/examples.d.ts +50 -0
  86. package/dist/types/explain-cli.d.ts +11 -0
  87. package/dist/types/explain.d.ts +95 -0
  88. package/dist/types/extensions.d.ts +122 -0
  89. package/dist/types/function-sources.d.ts +5 -0
  90. package/dist/types/index.d.ts +33 -0
  91. package/dist/types/init-with.d.ts +30 -0
  92. package/dist/types/interchange-cli.d.ts +16 -0
  93. package/dist/types/interchange.d.ts +42 -0
  94. package/dist/types/link-cli.d.ts +2 -1
  95. package/dist/types/management-policy.d.ts +0 -1
  96. package/dist/types/manifest.d.ts +81 -0
  97. package/dist/types/match.d.ts +1 -0
  98. package/dist/types/mcp-authoring.d.ts +92 -0
  99. package/dist/types/mcp.d.ts +12 -0
  100. package/dist/types/observability.d.ts +2 -0
  101. package/dist/types/operator-host.d.ts +8 -0
  102. package/dist/types/plugins.d.ts +2 -0
  103. package/dist/types/policies/agents.d.ts +0 -2
  104. package/dist/types/policies/compression.d.ts +2 -0
  105. package/dist/types/policies/security.d.ts +0 -1
  106. package/dist/types/policy.d.ts +15 -4
  107. package/dist/types/project-tests.d.ts +3 -1
  108. package/dist/types/provider-verification.d.ts +53 -0
  109. package/dist/types/proxy.d.ts +21 -0
  110. package/dist/types/readiness.d.ts +1 -1
  111. package/dist/types/recipes.d.ts +30 -0
  112. package/dist/types/route-diff.d.ts +27 -0
  113. package/dist/types/runtime.d.ts +11 -0
  114. package/dist/types/schema-query.d.ts +12 -0
  115. package/dist/types/signals.d.ts +25 -0
  116. package/dist/types/site.d.ts +0 -1
  117. package/dist/types/tooling.d.ts +115 -0
  118. package/dist/types/types.d.ts +57 -0
  119. package/dist/types/typescript-authoring.d.ts +12 -0
  120. package/dist/types/vercel.d.ts +3 -1
  121. package/dist/types/verify-deployment.d.ts +47 -0
  122. package/dist/types.js +21 -2
  123. package/dist/typescript-authoring.js +104 -0
  124. package/dist/vercel.js +4 -3
  125. package/dist/verify-deployment.js +270 -0
  126. package/docs/AI-AUTHORING.md +130 -8
  127. package/docs/BULK.md +79 -0
  128. package/docs/CAPABILITIES.md +179 -0
  129. package/docs/CAPACITY.md +1 -1
  130. package/docs/CI.md +142 -0
  131. package/docs/CONDITIONS.md +74 -0
  132. package/docs/DEPLOYMENT-CHECKS.md +108 -0
  133. package/docs/DYNAMIC-LINKS.md +18 -518
  134. package/docs/EGRESS.md +125 -0
  135. package/docs/EXTENSIONS.md +226 -0
  136. package/docs/FRAMEWORK.md +182 -0
  137. package/docs/INSTALL.md +45 -7
  138. package/docs/INTERCHANGE.md +134 -0
  139. package/docs/MIDDLEWARE-EXAMPLES.md +75 -0
  140. package/docs/MIDDLEWARE.md +2 -0
  141. package/docs/NEXT-PHASE-PLAN.md +90 -0
  142. package/docs/NEXT-STEPS.md +415 -0
  143. package/docs/OBSERVABILITY.md +4 -2
  144. package/docs/OPERATIONAL-PROOF.md +4 -1
  145. package/docs/OPERATIONS.md +6 -3
  146. package/docs/PLUGINS.md +37 -0
  147. package/docs/POLICIES.md +12 -309
  148. package/docs/PRERENDER.md +40 -0
  149. package/docs/PROJECT-DIRECTION.md +42 -0
  150. package/docs/PROVIDER-VERIFICATION.md +84 -0
  151. package/docs/READINESS.md +21 -1
  152. package/docs/README.md +82 -31
  153. package/docs/RECIPES.md +99 -0
  154. package/docs/RELEASE-READINESS.md +11 -9
  155. package/docs/RELEASE-SECURITY.md +27 -4
  156. package/docs/SECURITY-AUDIT.md +1 -1
  157. package/docs/SPECIFICATION.md +95 -8
  158. package/docs/SPIKE-BUSINESS-SUITE.md +1013 -0
  159. package/docs/SPIKE-EXTENSION-MODEL.md +419 -0
  160. package/docs/SPIKE-EXTENSIONS.md +1 -0
  161. package/docs/SPIKE-LAMBDA-COMPILE.md +199 -0
  162. package/docs/STANDARDS.md +150 -142
  163. package/docs/STARTERS.md +21 -1
  164. package/docs/TOOLING.md +291 -0
  165. package/docs/TYPESCRIPT-AUTHORING.md +67 -0
  166. package/docs/TYPESCRIPT.md +1 -1
  167. package/docs/USABILITY-REVIEW.md +123 -0
  168. package/docs/YAML-GUIDE.md +18 -479
  169. package/docs/YAML-REFERENCE.md +127 -16
  170. package/docs/links/cli.md +110 -0
  171. package/docs/links/limits.md +175 -0
  172. package/docs/links/management-api.md +80 -0
  173. package/docs/links/pools.md +75 -0
  174. package/docs/links/setup.md +135 -0
  175. package/docs/policies/agents.md +1 -1
  176. package/docs/policies/contract.md +52 -0
  177. package/docs/policies/hardened.md +56 -0
  178. package/docs/policies/interoperability.md +169 -0
  179. package/docs/policies/operations.md +45 -0
  180. package/docs/yaml/assets.md +36 -0
  181. package/docs/yaml/conditions.md +20 -0
  182. package/docs/yaml/functions.md +160 -0
  183. package/docs/yaml/links.md +30 -0
  184. package/docs/yaml/middleware.md +29 -0
  185. package/docs/yaml/organization.md +74 -0
  186. package/docs/yaml/policies.md +37 -0
  187. package/docs/yaml/redirects.md +64 -0
  188. package/docs/yaml/responses.md +57 -0
  189. package/docs/yaml/site.md +24 -0
  190. package/examples/assets/example.yaml +17 -0
  191. package/examples/aws/example.yaml +20 -0
  192. package/examples/cloudflare/example.yaml +19 -0
  193. package/examples/compliance/example.yaml +11 -0
  194. package/examples/conditions/README.md +12 -0
  195. package/examples/conditions/example.yaml +19 -0
  196. package/examples/conditions/tests/requests.json +13 -0
  197. package/examples/conditions/urlcode.yaml +24 -0
  198. package/examples/cookbook/README.md +8 -4
  199. package/examples/cookbook/example.yaml +17 -0
  200. package/examples/cookbook/functions/catalog.mjs +3 -0
  201. package/examples/cookbook/functions/fail.mjs +4 -0
  202. package/examples/cookbook/functions/items.mjs +3 -0
  203. package/examples/cookbook/functions/profile.mjs +3 -0
  204. package/examples/cookbook/functions/resource.mjs +3 -0
  205. package/examples/cookbook/functions/status.mjs +3 -0
  206. package/examples/cookbook/middleware/auth.mjs +48 -0
  207. package/examples/cookbook/middleware/body.mjs +15 -0
  208. package/examples/cookbook/middleware/bucket.mjs +19 -0
  209. package/examples/cookbook/middleware/cors.mjs +21 -0
  210. package/examples/cookbook/middleware/debug.mjs +13 -0
  211. package/examples/cookbook/middleware/envelope.mjs +11 -0
  212. package/examples/cookbook/middleware/errors.mjs +11 -0
  213. package/examples/cookbook/middleware/etag.mjs +18 -0
  214. package/examples/cookbook/middleware/locale.mjs +16 -0
  215. package/examples/cookbook/middleware/maintenance.mjs +10 -0
  216. package/examples/cookbook/middleware/methods.mjs +15 -0
  217. package/examples/cookbook/middleware/negotiate.mjs +20 -0
  218. package/examples/cookbook/middleware/referer.mjs +12 -0
  219. package/examples/cookbook/middleware/request-id.mjs +16 -0
  220. package/examples/cookbook/route-index.json +676 -0
  221. package/examples/cookbook/routes/middleware.yaml +126 -0
  222. package/examples/cookbook/tests/requests.json +526 -0
  223. package/examples/cookbook/urlcode.yaml +1 -0
  224. package/examples/egress/README.md +22 -0
  225. package/examples/egress/example.yaml +19 -0
  226. package/examples/egress/urlcode.yaml +19 -0
  227. package/examples/extensions/README.md +7 -0
  228. package/examples/extensions/example.yaml +21 -0
  229. package/examples/extensions/urlcode.yaml +25 -0
  230. package/examples/live-links/example.yaml +21 -0
  231. package/examples/monitoring/example.yaml +8 -0
  232. package/examples/prerender/example.yaml +16 -0
  233. package/examples/provider-conformance/README.md +12 -0
  234. package/examples/provider-conformance/example.yaml +14 -0
  235. package/examples/provider-conformance/urlcode.yaml +34 -0
  236. package/examples/tunnel/example.yaml +8 -0
  237. package/examples/vercel/example.yaml +19 -0
  238. package/llms-full.txt +2709 -0
  239. package/llms.txt +48 -19
  240. package/package.json +29 -7
  241. package/packaging/claude-plugin/.claude-plugin/plugin.json +19 -0
  242. package/packaging/claude-plugin/skills/urlcode-authoring/SKILL.md +106 -0
  243. package/packaging/claude-plugin/skills/urlcode-operations/SKILL.md +114 -0
  244. package/recipes/authenticated-json-api/README.md +51 -0
  245. package/recipes/authenticated-json-api/functions/profile.mjs +5 -0
  246. package/recipes/authenticated-json-api/recipe.yaml +34 -0
  247. package/recipes/authenticated-json-api/tests/requests.json +39 -0
  248. package/recipes/authenticated-json-api/urlcode.yaml +12 -0
  249. package/recipes/contact-form/README.md +25 -0
  250. package/recipes/contact-form/functions/contact.mjs +17 -0
  251. package/recipes/contact-form/recipe.yaml +33 -0
  252. package/recipes/contact-form/tests/requests.json +47 -0
  253. package/recipes/contact-form/urlcode.yaml +18 -0
  254. package/recipes/cors-api/README.md +16 -0
  255. package/recipes/cors-api/functions/items.mjs +3 -0
  256. package/recipes/cors-api/middleware/cors.mjs +21 -0
  257. package/recipes/cors-api/recipe.yaml +26 -0
  258. package/recipes/cors-api/tests/requests.json +65 -0
  259. package/recipes/cors-api/urlcode.yaml +12 -0
  260. package/recipes/health-page/README.md +13 -0
  261. package/recipes/health-page/recipe.yaml +23 -0
  262. package/recipes/health-page/tests/requests.json +36 -0
  263. package/recipes/health-page/urlcode.yaml +19 -0
  264. package/recipes/json-api/README.md +6 -0
  265. package/recipes/json-api/functions/echo.mjs +3 -0
  266. package/recipes/json-api/recipe.yaml +25 -0
  267. package/recipes/json-api/tests/requests.json +34 -0
  268. package/recipes/json-api/urlcode.yaml +12 -0
  269. package/recipes/middleware/README.md +34 -0
  270. package/recipes/middleware/functions/catalog.mjs +3 -0
  271. package/recipes/middleware/functions/fail.mjs +4 -0
  272. package/recipes/middleware/functions/items.mjs +3 -0
  273. package/recipes/middleware/functions/profile.mjs +3 -0
  274. package/recipes/middleware/functions/resource.mjs +3 -0
  275. package/recipes/middleware/functions/status.mjs +3 -0
  276. package/recipes/middleware/middleware/auth.mjs +48 -0
  277. package/recipes/middleware/middleware/body.mjs +15 -0
  278. package/recipes/middleware/middleware/bucket.mjs +19 -0
  279. package/recipes/middleware/middleware/cors.mjs +21 -0
  280. package/recipes/middleware/middleware/debug.mjs +13 -0
  281. package/recipes/middleware/middleware/envelope.mjs +11 -0
  282. package/recipes/middleware/middleware/errors.mjs +11 -0
  283. package/recipes/middleware/middleware/etag.mjs +18 -0
  284. package/recipes/middleware/middleware/locale.mjs +16 -0
  285. package/recipes/middleware/middleware/maintenance.mjs +10 -0
  286. package/recipes/middleware/middleware/methods.mjs +15 -0
  287. package/recipes/middleware/middleware/negotiate.mjs +20 -0
  288. package/recipes/middleware/middleware/referer.mjs +12 -0
  289. package/recipes/middleware/middleware/request-id.mjs +16 -0
  290. package/recipes/middleware/public/guide.txt +1 -0
  291. package/recipes/middleware/recipe.yaml +50 -0
  292. package/recipes/middleware/tests/requests.json +528 -0
  293. package/recipes/middleware/urlcode.yaml +127 -0
  294. package/recipes/protected-download/README.md +22 -0
  295. package/recipes/protected-download/files/report.txt +1 -0
  296. package/recipes/protected-download/recipe.yaml +31 -0
  297. package/recipes/protected-download/tests/requests.json +32 -0
  298. package/recipes/protected-download/urlcode.yaml +15 -0
  299. package/recipes/redirect/README.md +7 -0
  300. package/recipes/redirect/recipe.yaml +25 -0
  301. package/recipes/redirect/tests/requests.json +19 -0
  302. package/recipes/redirect/urlcode.yaml +9 -0
  303. package/recipes/static-plus-api/README.md +15 -0
  304. package/recipes/static-plus-api/functions/info.mjs +3 -0
  305. package/recipes/static-plus-api/public/assets/index.html +3 -0
  306. package/recipes/static-plus-api/public/assets/site.css +1 -0
  307. package/recipes/static-plus-api/public/index.html +8 -0
  308. package/recipes/static-plus-api/recipe.yaml +29 -0
  309. package/recipes/static-plus-api/tests/requests.json +56 -0
  310. package/recipes/static-plus-api/urlcode.yaml +23 -0
  311. package/recipes/typescript/README.md +7 -0
  312. package/recipes/typescript/functions/hello.ts +5 -0
  313. package/recipes/typescript/recipe.yaml +23 -0
  314. package/recipes/typescript/tests/requests.json +18 -0
  315. package/recipes/typescript/urlcode.yaml +5 -0
  316. package/recipes/webhook-receiver/README.md +16 -0
  317. package/recipes/webhook-receiver/functions/receive.mjs +16 -0
  318. package/recipes/webhook-receiver/recipe.yaml +26 -0
  319. package/recipes/webhook-receiver/tests/requests.json +59 -0
  320. package/recipes/webhook-receiver/urlcode.yaml +16 -0
  321. package/schemas/recipe.schema.json +138 -0
  322. package/schemas/urlcode.schema.json +656 -80
  323. package/skills/urlcode/SKILL.md +98 -0
  324. package/starters/default/.github/workflows/urlcode.yml +23 -0
  325. package/starters/default/.mcp.json +12 -0
  326. package/starters/default/AGENTS.md +79 -0
package/docs/STARTERS.md CHANGED
@@ -30,7 +30,9 @@ Neither path forks the runtime or needs a hosting account or database.
30
30
  ## Files and growth
31
31
 
32
32
  `urlcode.yaml` includes a function route file and a redirect file in a nested
33
- folder. A JavaScript function, HTTP assertions and optional Makefile are included.
33
+ folder. A JavaScript function, HTTP assertions and optional Makefile are included,
34
+ plus `.github/workflows/urlcode.yml`, which runs the [project checks action](CI.md)
35
+ on every push and pull request once the project is on GitHub.
34
36
  See [organization](ORGANIZATION.md) for choosing your own layout. Defaults allow
35
37
  GET/HEAD and use redirect 302; add configuration only when changing behavior.
36
38
 
@@ -40,6 +42,24 @@ application files. Add pages, downloads, more functions and business-specific
40
42
  features to this same project. Update tests and the expected route count as it grows.
41
43
  See [readiness](READINESS.md) and [security](FUNCTION-SECURITY.md).
42
44
 
45
+ To start an extended site instead, install the extension packages from npm
46
+ (`npm install @jimhoyd/urlcode-auth @jimhoyd/urlcode-admin`, published as
47
+ `0.1.0-alpha.1` prereleases) in the directory you run from and pass their names: `urlcode init ../my-site --with
48
+ auth,admin` writes the same starter under `my-site/app/`, merges each package's
49
+ routes and declarations into it, and generates one `host.mjs` and README beside
50
+ it. The contract each package fulfils is in [extensions](EXTENSIONS.md#scaffolding-with-init---with).
51
+
52
+ Both paths carry an `AGENTS.md` for repository-aware assistants. `urlcode init`
53
+ generates it from the installed runtime's capability catalog (the same source as
54
+ `urlcode capabilities`), so it names only the handlers, policies and site keys
55
+ that version implements, plus the exact `validate`, `test` and `audit` commands
56
+ with the starter's route count. The committed copy in `starters/default` is
57
+ regenerated from the same function and a test keeps the two identical. The file
58
+ points at the agent skill the package ships at `skills/urlcode/SKILL.md`.
59
+ Both paths also write `.mcp.json`, which registers the read-only `urlcode mcp`
60
+ server for Claude Code and Codex (`--project app` for an extended site); it is
61
+ never overwritten and carries no `--allow-authoring` ([tooling](TOOLING.md#registering-the-server)).
62
+
43
63
  `examples/assets` contains a larger runnable file/page/download demonstration:
44
64
  `make dev PROJECT=examples/assets`. It is an example, not a separate init choice.
45
65
 
@@ -0,0 +1,291 @@
1
+ # Tooling SDK and local MCP
2
+
3
+ The tooling API consolidates authoring operations without starting a runtime:
4
+
5
+ - `inspectProject(project, {origin?, target?, offset?, limit?})` loads and
6
+ semantically compiles the project and returns route metadata, revision hash and
7
+ target compatibility. Route pages default to 100 entries, maximum 1,000.
8
+ Compatibility contains global `compatible`, `requirementCount` and `issueCount`
9
+ plus a separate `issues` page. Both arrays use the same zero-based `offset`
10
+ and `limit`, independently: an issue page is indexed over all compatibility
11
+ issues, not filtered to the route page. Compatibility includes `hasMore` for
12
+ its issue page. Empty pages never imply compatibility; the verdict and counts
13
+ always cover the entire project. Full requirement arrays are omitted because
14
+ route entries already contain their capability names.
15
+ - `validateProject(project, options)` returns the same compilation verdict and
16
+ compatibility verdict and global counts without route or issue pages.
17
+ `firstIssue` is the first issue across the whole project, or null; supplied
18
+ pagination settings do not change it. The low-level
19
+ `analyzeCompiledCapabilities` API still returns the complete report.
20
+ For unusually long route paths, request smaller pages to fit the MCP output
21
+ byte limit; page entry bounds do not override that transport limit.
22
+ - `explainRoute(project, path, options)` selects the route for a path and
23
+ describes its effective behavior from the compiled IR: methods, handler (with
24
+ its destination, module and export, file, extension or link collection),
25
+ the middleware chain in order, validated inputs (parameters and the request
26
+ body policy), the policies in effect with each compiled policy's inventory,
27
+ extension requirements, the cache outcome (the policy strategy, an explicit
28
+ header, an asset declaration, or the `no-store` the runtime forces on
29
+ extension, proxy and conditional routes), binding names (never values),
30
+ egress origins, response headers, capabilities and per-target support. A miss
31
+ returns `matched: false` with the nearest route patterns. With `extensions`
32
+ (a host file's registrations) each extension requirement also reports whether
33
+ a provider is registered, whether its revision pin matches and whether the
34
+ requirement satisfies the provider's policy schema; nothing is activated.
35
+ `explainProject(project, options)` returns every route the same way.
36
+ - `buildManifest(project, options)` returns the generated semantic manifest
37
+ described under [`urlcode manifest`](#explain-and-manifest).
38
+ - `getCapabilities(target?)` describes local implementation support and separate
39
+ deployment evidence.
40
+ - `getCapability(name)` returns one catalog entry: kind, summary, resolved schema
41
+ fragments, constraints, required operator grants, per-target support, refused
42
+ targets and the bundled recipes and cookbook routes that use it. Unknown names
43
+ throw a `ConfigError` listing the valid names.
44
+ - `getSchemaFragment(path)` returns only the fragment of
45
+ `schemas/urlcode.schema.json` for a dotted path (`route`, `redirect`,
46
+ `policies.cache`, `site.sitemap`) with local `$ref`s inlined; `schemaPathNames()`
47
+ lists the accepted top-level names. Both read bundled package data only.
48
+ - `previewImport(options)` and `previewExport(project, format, acknowledgment?)`
49
+ return conversion reports and candidate text, never writing files. Provider
50
+ semantic differences require the existing explicit acknowledgment and remain
51
+ non-lossless.
52
+ - `listRecipes()` and `showRecipe(name)` expose the fixed bundled recipe catalog.
53
+ - `inspectExtensions({project, hostFile?})` reports each operator-registered
54
+ extension's name, contract version, targets, credential headers, configuration
55
+ and policy JSON Schemas, whether the project declares it, whether its revision
56
+ pin matches and where routes mount or require it, plus the project's declared
57
+ names. With `hostFile` it executes that trusted operator module under the
58
+ `--host-file` rules (absolute path, outside the project) and releases it
59
+ afterwards; without one it lists declarations only. `describeExtensions(project,
60
+ registrations?)` produces the same report from registrations already in hand.
61
+ Neither activates an extension. See [EXTENSIONS.md](EXTENSIONS.md).
62
+ - `buildContext(project, {target?, hostFile?, budget?})` returns the compact
63
+ project context an authoring agent needs before it writes anything (see
64
+ below); `renderContext` produces the YAML rendering and `estimateTokens`
65
+ the characters-per-token estimate the budget uses.
66
+
67
+ ## Project context
68
+
69
+ `urlcode context [--project DIR] [--target T] [--host-file F] [--budget N]
70
+ [--json] [--stats]` emits one deterministic YAML document (JSON with
71
+ `--json`) derived only from the compiled project and the capability catalog,
72
+ never from prose. It uses the same loader and semantic compiler as
73
+ `inspectProject`: no binding values, guest execution, environment reads or
74
+ network. Keys always appear in this order:
75
+
76
+ - `urlcode` (package version) and `schema` (`"1"`).
77
+ - `project`: entry file, route count, handlers used with counts, extensions
78
+ declared, policies in effect at the top level and the number of routes each
79
+ policy applies to, requested env and secret binding names, `dynamicLinks`,
80
+ `site` keys, and `files` (include, function and middleware paths). With
81
+ `--host-file`, `host` counts the operator module's extensions and plugins
82
+ without activating them.
83
+ - `routes`: path, methods and handler per route, sorted by path.
84
+ - `constraints`: a fixed list that holds for every project (no guest network,
85
+ no Node APIs, no regex routes, one handler per route, exact or `{param}`
86
+ path segments, subtree mounts only for static and extension routes, no YAML
87
+ interpolation, secrets by operator grant only), each with a value and a note.
88
+ - `targets`: for each capability target (or the one `--target`), which of this
89
+ project's used features are supported, conditional, refused or unknown.
90
+ - `commands`: the exact `validate`, `test`, `audit --expect-routes N` (N is
91
+ the compiled route count), `routes` and `capabilities` invocations.
92
+
93
+ `--budget N` drops sections in a fixed order until the YAML rendering fits
94
+ the estimate: per-route detail, then `targets`, then the constraint notes
95
+ (keys and values stay), then `project.files`, then `commands`. The dropped
96
+ sections are listed under `omitted`. The estimate is `ceil(characters / 4)`;
97
+ there is no tokenizer dependency, so treat both numbers as approximate. A
98
+ budget the smallest rendering cannot meet is an error rather than an
99
+ overrun. `--stats` writes a JSON line to stderr comparing the estimated size
100
+ of the shipped documentation (`docs/*.md` and `llms.txt`) with the emitted
101
+ context, labeled `estimate: characters/4`. The MCP tool `get_context` takes
102
+ `target` and `budget` and returns the same object with `--project .` in the
103
+ commands; it never takes a host file or any other path.
104
+
105
+ Inspection reads declared configuration and function source graphs to validate
106
+ references and compute revision hashes. It compiles route and policy semantics
107
+ using dummy binding values. It never reads environment or dotenv credentials,
108
+ starts guest execution, follows network destinations, or opens operator link
109
+ stores. The result contains no raw compiled route, binding values or source text.
110
+ Inspection is not deployment readiness: missing operator grants, live service
111
+ availability, asset snapshot activation and provider behavior require their own
112
+ checks. Build output remains an explicit separate build API/CLI operation.
113
+
114
+ The package root also exports existing operator-invoked workflow APIs:
115
+ `buildCloudflare(project, options)` compiles and writes a Cloudflare artifact;
116
+ `runProjectTests(project, options)` starts the local runtime, executes request
117
+ fixtures and closes it; `scaffoldProject(project, {dryRun})` creates missing
118
+ placeholders while preserving existing files; `initProject(destination)` creates
119
+ the standard starter; and `addRedirect(project, destination, alias?)` updates
120
+ project YAML under the authoring lock. `CloudflareBuildOptions`,
121
+ `CloudflareBuildReport`, `ProjectTestOptions`, `ProjectTestResult`, `ScaffoldReport`
122
+ and `ScaffoldUnresolved` describe these existing operations.
123
+
124
+ These SDK functions have explicit write or execution effects and are available
125
+ to trusted callers only. Project tests use normal runtime activation, grants and
126
+ sandboxing; granted proxy/signal fixtures can perform real outbound operations.
127
+ Compilation and authoring write caller-selected destinations under each existing
128
+ helper's documented rules. They are **not** MCP tools. MCP remains limited to
129
+ the read-only operations below; adding a package-root export does not grant an
130
+ assistant file-write, guest-execution, deployment or network authority.
131
+
132
+ ## Explain and manifest
133
+
134
+ `urlcode explain [/route] [--project DIR] [--target T] [--host-file F] [--json]`
135
+ prints what `explainRoute` returns: one route in detail, or without a path a
136
+ one-line-per-route table (methods, handler, state, middleware count, policies,
137
+ cache outcome and target support). `--target` narrows the support columns to
138
+ one deployment target; `--host-file` supplies the operator registry so
139
+ extension requirements show their provider. An unknown route exits 1 and names
140
+ the nearest patterns. Everything comes from the compiled configuration: no
141
+ request is evaluated, no function runs and no binding is read.
142
+
143
+ `urlcode manifest [--project DIR] [--json]` emits the semantic manifest:
144
+ `schemaVersion`, the `urlcode` version, the entry file and its includes, the
145
+ `revision` (the same digest `inspectExtensionRevision` returns, so an operator
146
+ pin can be checked against it), the config `configVersion`, every route (path,
147
+ methods, handler, state, middleware, inputs, policy names, extension
148
+ requirements, cache outcome, binding names, egress origins, capabilities and
149
+ per-target support), the union of capabilities used, extension declarations
150
+ (version, configuration keys, mounts and protected routes), recipe provenance
151
+ (from a `recipe.yaml` beside the entry file when one exists), external
152
+ requirements (environment and secret names, proxy and signal origins,
153
+ extensions, link-store collections, dynamic links), the function and middleware
154
+ modules with the routes that use them, and per-target compatibility. Without
155
+ `--json` a short summary prints. The manifest is deterministic: the same
156
+ project produces the same bytes. `urlcode build` writes the same document as
157
+ `manifest.json` beside its output, and `buildManifest` returns it from the SDK.
158
+ It is generated output, never a checked-in source of truth; regenerate it
159
+ rather than editing it.
160
+
161
+ `serveMcp({project, input?, output?, origin?, allowAuthoring?, hostFile?})` serves one
162
+ operator-selected root on stdio. Its tools are `inspect`, `validate`,
163
+ `capabilities`, `get_capability`, `get_schema`, `explain`, `get_manifest`,
164
+ `import_preview`, `export_preview`, `recipes_list`, `recipes_show`,
165
+ `search_recipes`, `search_examples` and `get_context`. When the operator starts
166
+ the server with `--host-file`, it loads that trusted module once for the session
167
+ and additionally advertises `get_extensions`, which returns the
168
+ `inspectExtensions` report; without the option the tool is absent and calls to
169
+ it are rejected. Tools accept no project/file/output path argument; recipe names
170
+ come from the fixed catalog, `get_capability` names from the capability catalog,
171
+ `get_schema` paths from the bundled schema, and the two searches match bundled
172
+ metadata locally (see [recipes](RECIPES.md)).
173
+ There is no shell, arbitrary file read, remote fetch, binding access, write or
174
+ route-execution tool without the explicit [authoring mode](#authoring-mode) flag. Configuration includes and module references retain the
175
+ runtime's existing root containment checks. Returned project and recipe content
176
+ is data, not trusted instructions for the consuming agent.
177
+
178
+ The server implements the MCP **2025-11-25** lifecycle and stdio framing. Clients
179
+ initialize, verify the returned protocol version, then send
180
+ `notifications/initialized` before tool operations. Other requested revisions
181
+ negotiate to this explicit supported version; a client that cannot support it
182
+ must disconnect. Newer lifecycle revisions are not claimed. Requests use UTF-8
183
+ newline-delimited JSON-RPC 2.0, with one request at a time and stream backpressure.
184
+ There is a 1 MiB input-frame and output-message limit; oversized input terminates
185
+ the session after a fixed error, and truncated/invalid frames return protocol
186
+ errors. Import text is additionally capped at 512 KiB. Tool schemas reject
187
+ unknown arguments. Tool operation errors are generic to avoid exposing local
188
+ source paths, credentials or configuration excerpts; inspect locally for details.
189
+
190
+ ## Registering the server
191
+
192
+ `urlcode init` (and `init --with`) writes `.mcp.json` at the project root, the
193
+ shape Claude Code and Codex read:
194
+
195
+ ```json
196
+ { "mcpServers": { "urlcode": { "command": "urlcode", "args": ["mcp", "--project", "."] } } }
197
+ ```
198
+
199
+ For an `init --with` site the file sits beside `host.mjs` and passes
200
+ `--project app`. An existing `.mcp.json` is never overwritten. The file registers
201
+ the read-only server only: `--allow-authoring` (and `--host-file`) are operator
202
+ choices added by hand, never by `init` or by an agent.
203
+
204
+ - **Claude Code** reads `.mcp.json` in the project directory as a project-scoped
205
+ server and asks for approval on first use. Without a global install, replace
206
+ `"command": "urlcode"` with `"node"` and prefix the arguments with
207
+ `node_modules/@jimhoyd/urlcode/dist/cli.js`.
208
+ - **Codex** reads the same `mcpServers` shape; alternatively register it in
209
+ `~/.codex/config.toml`:
210
+
211
+ ```toml
212
+ [mcp_servers.urlcode]
213
+ command = "urlcode"
214
+ args = ["mcp", "--project", "."]
215
+ ```
216
+ - **Any stdio client** spawns `urlcode mcp --project DIR` with the project as the
217
+ working directory, speaks newline-delimited JSON-RPC 2.0 over stdin/stdout,
218
+ and follows the 2025-11-25 lifecycle described above. Nothing listens on a
219
+ port; closing stdin ends the session.
220
+
221
+ The generated `AGENTS.md` and the packaged skill tell agents to prefer
222
+ `get_context`, `get_capability`, `get_schema`, `search_recipes`, `explain` and
223
+ `get_manifest` when the server is registered and to fall back to the matching
224
+ CLI commands otherwise.
225
+
226
+ ## Authoring mode
227
+
228
+ `urlcode mcp --allow-authoring --project DIR` adds six tools to the fourteen read
229
+ tools above. The flag is honored from the operator's command line only: no
230
+ tool argument, environment variable or client capability enables it, and
231
+ without it the server is exactly the read-only server described above.
232
+
233
+ What it can do, all inside the selected project root (resolved with realpath):
234
+
235
+ - `create_route {path, handler, middleware?, file?}` adds one route to
236
+ `urlcode.yaml` or to an include listed in it. `handler` is a route object
237
+ (`{redirect: {...}}`, `{function: {...}}`, `{page: {...}}`, ...) or a short
238
+ form: an `http(s)://` URL becomes a redirect, a `.js`/`.mjs` path becomes a
239
+ function whose `{param}` path segments expand to required bounded string
240
+ parameters and matching `args`. `middleware` entries are sources or objects.
241
+ The merged project is checked before the write (schema, duplicate routes,
242
+ the `auth` short form, and the same reference compilation `urlcode add`
243
+ performs when every referenced source exists). The edit runs under the
244
+ authoring lock and replaces the file atomically. Missing sources are listed
245
+ in `missingSources` for `scaffold_feature`.
246
+ - `add_recipe {name, destination, dryRun?}` runs `recipes add` into a new
247
+ directory under the project. The parent must exist; an existing destination
248
+ is refused, never merged. `dryRun` reports the destination and writes nothing.
249
+ - `scaffold_feature {dryRun?}` runs `urlcode scaffold`: placeholder modules,
250
+ pages and directories for references the YAML makes and the disk lacks.
251
+ Existing files are preserved, never overwritten.
252
+ - `run_validate`, `run_test`, `run_audit` spawn `urlcode validate --local`,
253
+ `urlcode test` and `urlcode audit` against the project with a minimal
254
+ environment (`PATH` only), a two-minute deadline and stdout/stderr each capped
255
+ at 32 KiB. The result carries `exitCode`, `signal`, `stdout`, `stderr` and
256
+ `truncated`. `run_test` activates the local runtime and executes fixtures,
257
+ under the same rules as the CLI.
258
+
259
+ Every tool returns `validation`, the `validateProject` verdict of the project
260
+ after the operation (or `valid: false` with a generic note; use `run_validate`
261
+ for the CLI report).
262
+
263
+ What it cannot do:
264
+
265
+ - Write outside the project root. Paths are project-relative; absolute paths,
266
+ `..`, backslashes, drive letters, and any symlink on the walk are refused
267
+ before the write, and the recipe, scaffold and `urlcode add` paths keep their
268
+ own containment checks.
269
+ - Touch `.env*`, anything under `.git`, `node_modules`, `package.json`,
270
+ credential files (`.pem`, `.key`, `.p12`, `.pfx`), the authoring lock, or
271
+ operator files by name: `*policy*.json`, `*compliance*`, `host.mjs` /
272
+ `host-file.mjs` and link stores (`.sqlite`, `.db` and their WAL/SHM files).
273
+ Operator files belong outside the checkout in the first place.
274
+ - Create or change grants, read bindings or secret values, deploy, build, run
275
+ arbitrary commands, delete or edit existing files (except the one YAML file a
276
+ `create_route` targets), or serve a project other than the one the operator
277
+ selected.
278
+
279
+ Authoring mode is a local, unauthenticated stdio process for an operator who
280
+ already trusts the assistant to edit this checkout. Review the resulting diff
281
+ as you would any contributor's before running `serve` or deploying.
282
+
283
+ Only tools are advertised. Resources, prompts, subscriptions, sampling,
284
+ elicitation, HTTP transport, cancellation and durable tasks are not implemented.
285
+ Closing stdin ends the session after the current bounded operation. Existing
286
+ configuration-loader and semantic-compiler deadlines still apply. This local
287
+ process is not an authenticated remote service or an independent security review.
288
+
289
+ Protocol references: [MCP stdio transport](https://modelcontextprotocol.io/specification/2025-11-25/basic/transports),
290
+ [MCP lifecycle](https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle),
291
+ and [MCP tools](https://modelcontextprotocol.io/specification/2025-11-25/server/tools).
@@ -0,0 +1,67 @@
1
+ # Build-time TypeScript guests
2
+
3
+ TypeScript guest authoring is a separate build step. Serving still accepts only
4
+ JavaScript ES modules and executes them exclusively in QuickJS/WebAssembly.
5
+ The build never imports application modules into Node or runs application code.
6
+
7
+ ```sh
8
+ urlcode recipes add typescript --out ./hello-source
9
+ urlcode build-typescript --project ./hello-source --out ./hello-built --dry-run
10
+ urlcode build-typescript --project ./hello-source --out ./hello-built
11
+ urlcode validate --local --project ./hello-built
12
+ urlcode serve --project ./hello-built
13
+ ```
14
+
15
+ The SDK equivalent is
16
+ `await buildTypeScriptProject(source, output, {dryRun: false})`. The report lists
17
+ emitted modules and files and explicitly returns `typeChecked: false`.
18
+
19
+ Point `function.source` or `middleware[].source` at a `.ts`, `.js` or `.mjs`
20
+ module. Use explicit relative extensions in imports, such as
21
+ `import {message} from './message.ts'`. The build emits `.ts` as `.js` and
22
+ rewrites the runtime imports and YAML source references. Existing JavaScript
23
+ modules are preserved. Output name collisions, including case-only collisions,
24
+ are refused for portability. Source `.d.ts` entries are not executable modules.
25
+ The runtime's existing handling of relative imports/re-exports applies to the
26
+ emitted graph; unsupported re-export forms remain refused.
27
+
28
+ The trusted pinned TypeScript compiler transpiles ES2022/ES modules with fixed
29
+ settings. It does not read `tsconfig.json`, package scripts, plugins, compiler
30
+ transformers, dependency packages, Node declarations or ambient environment
31
+ files. It diagnoses syntax errors, but does **not** perform semantic type
32
+ checking. Type-only relative imports are erased without reading their targets.
33
+ Bare/npm imports (including static type-only imports), CommonJS import/export
34
+ syntax, dynamic runtime imports, `import.meta`, import attributes and imports
35
+ outside the project are refused. No import extension inference occurs.
36
+
37
+ The source graph is limited to 128 modules, 1 MiB per source and 4 MiB aggregate.
38
+ The emitted graph must pass the runtime's own source parser and byte limits
39
+ before publication. This does not execute the modules or replace normal route,
40
+ policy, binding or sandbox validation at activation. Unsupported host/browser
41
+ APIs remain unavailable in QuickJS, even if TypeScript accepts their names.
42
+ Run `urlcode validate --local` and project tests on the output.
43
+
44
+ Includes are flattened into a duplicate-checked entry document. Only referenced
45
+ modules, page/download assets, static trees, site favicon/llms files and the
46
+ `tests/requests.json` fixtures (when present) are snapshotted. Unreferenced files, dotenv, hidden files, package manifests and
47
+ credential extensions are not copied. An explicit reference to a forbidden file
48
+ fails rather than silently excluding it. Asset limits are 16 MiB per file,
49
+ 64 MiB aggregate and 10,000 output files. Empty static directories are refused;
50
+ add an asset before building. Paths have at most 32 segments and 1,024 characters;
51
+ symlinked source files/directories and multiply linked files are refused.
52
+ The input project and output parent must be stable and controlled by the caller
53
+ while building; this is not a hardened service for concurrent hostile writers.
54
+
55
+ Output must be a new directory whose parent already exists. Dependencies are
56
+ written before an atomic rename publishes the complete entry YAML; existing
57
+ outputs are never overwritten. Failed builds remove their newly created output.
58
+ Dry-run still transpiles and validates an isolated temporary snapshot, then
59
+ removes it; it never writes the requested output. Configuration parsing keeps
60
+ the loader's worker limits; transpilation runs in the authoring process and is
61
+ not a sandboxed multi-tenant compilation service.
62
+
63
+ Create operator grants against the **built** configuration/code revision.
64
+ Transpilation and flattened includes change the snapshot digest, so source
65
+ project grants must not be reused. External capabilities remain denied without
66
+ an exact revision-pinned operator policy. The build never resolves bindings or
67
+ copies credentials; it preserves logical binding declarations only.
@@ -16,7 +16,7 @@ beside them, and `dist/BUILD-MANIFEST.json` with a SHA-256 per emitted file.
16
16
  | Import | Runtime | Declarations |
17
17
  |---|---|---|
18
18
  | `urlcode` | `dist/index.js` | `dist/types/index.d.ts` |
19
- | `@jimhoyd/urlcode/plugins`, `@jimhoyd/urlcode/policies`, `@jimhoyd/urlcode/observability`, `@jimhoyd/urlcode/compliance`, `@jimhoyd/urlcode/prerender` | `dist/<name>.js` | `dist/types/<name>.d.ts` |
19
+ | `@jimhoyd/urlcode/plugins`, `@jimhoyd/urlcode/policies`, `@jimhoyd/urlcode/observability`, `@jimhoyd/urlcode/compliance`, `@jimhoyd/urlcode/prerender`, `@jimhoyd/urlcode/extensions` | `dist/<name>.js` | `dist/types/<name>.d.ts` |
20
20
  | `@jimhoyd/urlcode/aws`, `@jimhoyd/urlcode/vercel`, `@jimhoyd/urlcode/cloudflare` | `dist/<name>.js` | `dist/types/<name>.d.ts` |
21
21
  | `@jimhoyd/urlcode/schema` | `schemas/urlcode.schema.json` | — |
22
22
 
@@ -0,0 +1,123 @@
1
+ # Usability review: URLCode against the tools people reach for instead
2
+
3
+ Status: an honest assessment written 2026-09-18 against the source in the four
4
+ repositories at that date. It is opinion grounded in the code and docs, not a
5
+ benchmark. Its purpose is to name the friction a first-time user or an AI
6
+ agent meets, compare it with what competing tools do, and rank the changes
7
+ that would matter most. It does not change any contract by itself.
8
+
9
+ ## 1. Who the competitors are, by rung
10
+
11
+ URLCode is not one product; it competes with a different tool on each rung
12
+ of [the ladder](FRAMEWORK.md#the-ladder).
13
+
14
+ | Rung | What people use today | Where URLCode is better | Where it is worse today |
15
+ |---|---|---|---|
16
+ | Redirects, responses | Netlify `_redirects`, Cloudflare `_redirects`/Rules, Vercel `vercel.json`, nginx | One portable file that validates before deploy, tests with fixtures, counts routes, converts from those formats and refuses lossy conversions | Those files are two columns; `urlcode.yaml` needs `version`, a route key and a handler object. Nobody needs a validator for ten redirects |
17
+ | Pages, files | Any static host, Astro, Hugo | Same file, no build step, native ranges/ETags, `site` conventions | No content pipeline, no templating for pages outside the extension kit |
18
+ | Functions, middleware | Cloudflare Workers, Vercel Functions, Hono, Express | Real isolation with a fresh heap per call; typed args from YAML; secrets only by revision-pinned grant; runs the same on a laptop | No `fetch`, no timers, no streaming, no npm packages in guests. Every framework above lets you call an API from a function; here you declare a `proxy` or stop |
19
+ | Live links | Bitly, Dub, Short.io, a Postgres table | Records without reloads, versioned writes, private management API, no account system to run | Single host SQLite that needs a patched Node build; no dashboard until you install admin |
20
+ | Accounts | Clerk, Auth0, Auth.js, Better Auth, Lucia, Supabase Auth, Devise | Everything is operator-owned and reviewable: no vendor, no callback URLs on someone else's dashboard, no per-MAU bill; passkeys, OIDC, TOTP, recovery and an account page in one package | Install is a reviewed-tarball ritual, a host file, a SHA-256 pin and a JSON-on-stdin bootstrap. Clerk is `npm i` and one env var. Better Auth is a config object and a CLI migration |
21
+ | Administration | Django admin, Rails Administrate, Retool, Forest Admin, Supabase Studio | Built on the same service with two-person cases and audited reveal, which none of the generic admins give you | Only manages auth's entities; there is no way to expose the project's own data. Django admin is one line per model |
22
+ | Full app in one place | Rails, Django, Laravel, Next.js + Clerk + Prisma, Supabase | The whole thing is declarative and portable; an agent can generate and validate it without a build; the trust boundary between generated code and the operator is explicit | Everything that is not a route, a function or an account is out of scope: no data model, no ORM, no forms for your own tables, no client bundle, no email templates for your app |
23
+
24
+ The honest summary: URLCode wins on portability, validation, isolation and
25
+ operator ownership, and loses on the first fifteen minutes and on anything
26
+ that needs application data.
27
+
28
+ ## 2. The first fifteen minutes
29
+
30
+ What a new person does today, compared with the fastest competitor at each
31
+ step. Times are what the docs imply, not measurements.
32
+
33
+ | Step | URLCode today | Fastest competitor | Gap |
34
+ |---|---|---|---|
35
+ | Install | `npm i -g @jimhoyd/urlcode` or brew or a checksum script; three channels documented at equal weight | `npx create-next-app` | Too many equal choices on the first screen. Pick one, put the rest in the install guide |
36
+ | First project | `urlcode init` writes a function route, a redirect and fixtures | `wrangler init` | Equal |
37
+ | First redirect | 5 lines of YAML for one redirect | 1 line in `_redirects` | Acceptable once, heavy for 200. `bulk-import` exists but a person starting small never learns it |
38
+ | First function | Must know: `parameters` with `schema`, `args` with `from: path`, the Request/Response subset, that `fetch` is absent | Hono: `app.get('/x', c => c.json(...))` | The typed-args design is right but needs a two-line minimal form. Today the smallest function route is 10 lines |
39
+ | Add accounts | Clone two private repos, build tarballs with `pack-sources.mjs --core --auth --ui --admin --core-revision SHA`, install four tarballs, `urlcode-auth init`, compute `inspectExtensionRevision`, paste the SHA into an env var, `bootstrap` with JSON on stdin, `serve --host-file --origin` | Clerk: `npm i @clerk/nextjs`, one env var, wrap the app | This is the largest gap in the framework. Most of it is a consequence of the packages being unpublished and of the revision pin, and it is the step the README leads with |
40
+ | Add admin | `urlcode-admin init` writes everything | Django `admin.site.register(Model)` | Comparable once installed |
41
+ | Deploy | Container, Node process, or an adapter that refuses functions, links and extensions | `vercel deploy` | The refusals are correct but the person learns them at deploy time. `urlcode capabilities` exists and is not in the quick start |
42
+
43
+ ## 3. What an AI agent meets
44
+
45
+ The framework's claim is that an agent should build from a handful of
46
+ redirects to a full application without rebuilding the core. Measured
47
+ against that claim:
48
+
49
+ - **Strong:** one JSON Schema, a generated field reference, fixtures the agent
50
+ must write, `validate`, `test` and `audit` that give exact failures with the
51
+ route named, an MCP server for read-only inspection, and a capability
52
+ matrix that lists what does not exist. Few frameworks tell an agent what it
53
+ cannot do. This is the right foundation.
54
+ - **Weak:** the documentation was organized by feature history rather than by
55
+ task. Before this review, `llms.txt` listed 40 documents at equal weight,
56
+ three of them status logs, and the README opened with release history and
57
+ "unreleased source" caveats. An agent reading it spent its context on
58
+ provenance rather than on the shape of a project. The framework page and the
59
+ reorganized index in this change address that; the remaining cost is the
60
+ size of the reference documents themselves (the YAML guide, policies and
61
+ dynamic links are each over 400 lines).
62
+ - **Missing:** an agent cannot yet discover the extension packages' YAML from
63
+ the core schema. `extensions.auth.config` is validated by auth's schema at
64
+ activation, but there is no way to ask the installed runtime "what config
65
+ does `auth` accept" without the host file. A `urlcode extensions --schema`
66
+ command that reads the host file and prints each extension's configuration
67
+ and policy schemas would close this, and would let `urlcode mcp` serve them.
68
+ - **Missing:** no single command creates the whole layered project. Today it
69
+ is `urlcode init`, then `urlcode-auth init`, then `urlcode-admin init`, each
70
+ with its own directory conventions. One `urlcode init --with auth,admin`
71
+ that delegates to the installed extension packages' scaffolds would make
72
+ the ladder real for an agent.
73
+
74
+ ## 4. Ranked recommendations
75
+
76
+ Ordered by how much each would change the experience per unit of work, and
77
+ whether it touches a contract.
78
+
79
+ 1. **Publish the three extension packages** (even as `0.1.0-alpha` with the
80
+ caveats their status files carry). Every install step in section 2's
81
+ "add accounts" row except the revision pin existed because they were
82
+ unpublished. This was a decision, not code; the repositories already had
83
+ the release checks. No contract change. Done 2026-09-18: all three are on
84
+ npm as `0.1.0-alpha.1` (review still pending, issue 58).
85
+ 2. **`urlcode init --with auth,admin,ui`.** Delegate to each installed
86
+ package's existing scaffold; write one host file and one README. No contract
87
+ change; a CLI addition in core that calls into optional peers.
88
+ 3. **Print extension schemas.** `urlcode extensions --host-file … --json`
89
+ listing each registered extension's name, version, configuration schema and
90
+ policy schema, and expose it through `urlcode mcp`. No contract change.
91
+ 4. **A short form for the common function route.** Allow `function:
92
+ functions/hello.mjs` as a string with path parameters inferred as required
93
+ strings of bounded length, expanding to today's long form. This is a schema
94
+ addition (`version: "1"` stays valid) and the single largest cut in YAML
95
+ for first-time users and agents. Needs the usual generated-reference and
96
+ cookbook updates.
97
+ 5. **Lead the README with the ladder, not the release history.** Done in this
98
+ change; keep it that way. Move status caveats to the readiness register.
99
+ 6. **One install channel on the first screen.** npm first; brew, script and
100
+ container in the install guide.
101
+ 7. **Fold the `presentation`/`ui` split.** Auth and admin still render through
102
+ the primitives while the kit is the documented way to restyle. Finishing
103
+ kit adoption (already listed in the ui status file) removes the one place
104
+ where the framework's story and its code differ.
105
+ 8. **Later, and a real contract question:** application data. The thing every
106
+ competitor on the last rung has and URLCode does not is a place for the
107
+ project's own records with an admin view. The runtime already has one
108
+ bounded store (links) and one admin surface (auth's entities). Whether a
109
+ declared `collection` handler with operator-owned SQLite and an admin
110
+ registration belongs in the framework is the next spike worth writing.
111
+ Without it, "full-fledged application" means "site with accounts".
112
+
113
+ ## 5. What is fine and should stay
114
+
115
+ - The operator host file. It is the reason the project can be untrusted and
116
+ portable at the same time; Clerk's convenience is bought with a vendor in
117
+ the loop. Keep the boundary, make the file generated.
118
+ - The revision pin. It is unusual and it is what makes "an agent changed the
119
+ YAML" a reviewable event rather than a silent grant. Keep it, print it
120
+ loudly, and make `init` write it.
121
+ - Refusing instead of degrading on every target. The failure names the route.
122
+ - The capability matrix in the AI guide. Extend it to the extension packages
123
+ rather than softening it.