@jimhoyd/urlcode 0.5.5 → 0.5.8

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 (136) hide show
  1. package/.claude/skills/urlcode-authoring/SKILL.md +16 -1
  2. package/.claude/skills/urlcode-operations/SKILL.md +5 -1
  3. package/README.md +24 -14
  4. package/dist/BUILD-MANIFEST.json +55 -54
  5. package/dist/agents-guide.js +5 -2
  6. package/dist/assets.js +2 -2
  7. package/dist/authoring.js +3 -58
  8. package/dist/aws.js +21 -4
  9. package/dist/body-schema.js +2 -3
  10. package/dist/capabilities.js +1 -1
  11. package/dist/capability-query.js +3 -3
  12. package/dist/catalog.js +1 -1
  13. package/dist/cli.js +42 -15
  14. package/dist/cloudflare.js +26 -1
  15. package/dist/config.js +1 -1
  16. package/dist/context.js +6 -6
  17. package/dist/egress.js +1 -1
  18. package/dist/examples.js +2 -2
  19. package/dist/explain.js +4 -6
  20. package/dist/extension-artifacts.js +8 -8
  21. package/dist/extension-transport.js +6 -4
  22. package/dist/function-worker.js +5 -2
  23. package/dist/functions.js +1 -1
  24. package/dist/guest-api.js +22 -15
  25. package/dist/http-policy.js +1 -3
  26. package/dist/http-response.js +61 -10
  27. package/dist/init-with.js +8 -8
  28. package/dist/interchange.js +3 -2
  29. package/dist/logging.js +4 -4
  30. package/dist/match.js +1 -1
  31. package/dist/mcp-authoring.js +1 -2
  32. package/dist/mcp.js +2 -2
  33. package/dist/object-guards.js +17 -0
  34. package/dist/pattern-guard.js +33 -3
  35. package/dist/policies/agents.js +1 -1
  36. package/dist/policies/cache.js +16 -4
  37. package/dist/policies/compression.js +1 -1
  38. package/dist/policies/security.js +1 -1
  39. package/dist/policies/throttle.js +1 -1
  40. package/dist/policies.js +1 -1
  41. package/dist/policy.js +3 -3
  42. package/dist/prerender.js +1 -2
  43. package/dist/project-dependencies.js +8 -8
  44. package/dist/proxy.js +1 -1
  45. package/dist/readiness.js +16 -17
  46. package/dist/review.js +2 -2
  47. package/dist/route-diff.js +4 -4
  48. package/dist/router.js +1 -1
  49. package/dist/runtime.js +4 -1
  50. package/dist/scaffold.js +1 -1
  51. package/dist/schema-query.js +1 -1
  52. package/dist/server.js +49 -7
  53. package/dist/signals.js +2 -2
  54. package/dist/site.js +2 -2
  55. package/dist/trusted-functions.js +1 -1
  56. package/dist/types/assets.d.ts +3 -2
  57. package/dist/types/authoring.d.ts +3 -6
  58. package/dist/types/body-schema.d.ts +2 -1
  59. package/dist/types/catalog.d.ts +2 -1
  60. package/dist/types/config.d.ts +0 -8
  61. package/dist/types/context.d.ts +3 -1
  62. package/dist/types/egress.d.ts +1 -1
  63. package/dist/types/explain.d.ts +4 -4
  64. package/dist/types/extension-artifacts.d.ts +4 -13
  65. package/dist/types/extension-transport.d.ts +6 -4
  66. package/dist/types/functions.d.ts +1 -1
  67. package/dist/types/http-response.d.ts +7 -3
  68. package/dist/types/init-with.d.ts +3 -9
  69. package/dist/types/logging.d.ts +4 -3
  70. package/dist/types/match.d.ts +2 -1
  71. package/dist/types/object-guards.d.ts +7 -0
  72. package/dist/types/pattern-guard.d.ts +0 -1
  73. package/dist/types/policies/cache.d.ts +4 -3
  74. package/dist/types/policies/compression.d.ts +1 -1
  75. package/dist/types/policies/throttle.d.ts +1 -1
  76. package/dist/types/policy.d.ts +4 -3
  77. package/dist/types/project-dependencies.d.ts +1 -40
  78. package/dist/types/proxy.d.ts +2 -1
  79. package/dist/types/readiness.d.ts +12 -16
  80. package/dist/types/review.d.ts +0 -2
  81. package/dist/types/route-diff.d.ts +4 -3
  82. package/dist/types/router.d.ts +1 -1
  83. package/dist/types/server.d.ts +22 -0
  84. package/dist/types/signals.d.ts +3 -2
  85. package/dist/types/site.d.ts +1 -2
  86. package/dist/types/trusted-functions.d.ts +1 -1
  87. package/dist/types/types.d.ts +17 -11
  88. package/dist/types/vercel.d.ts +1 -0
  89. package/dist/types/verify-deployment.d.ts +1 -1
  90. package/dist/types.js +19 -12
  91. package/dist/vercel.js +12 -3
  92. package/dist/verify-deployment.js +29 -11
  93. package/docs/AI-AUTHORING.md +10 -1
  94. package/docs/FUNCTION-SECURITY.md +4 -0
  95. package/docs/README.md +2 -2
  96. package/docs/TOOLING.md +27 -1
  97. package/examples/assets/Makefile +1 -1
  98. package/examples/assets/example.yaml +3 -3
  99. package/examples/aws/example.yaml +3 -3
  100. package/examples/cloudflare/example.yaml +3 -3
  101. package/examples/compliance/README.md +1 -1
  102. package/examples/compliance/example.yaml +1 -1
  103. package/examples/compliance/rules.mjs +2 -2
  104. package/examples/conditions/example.yaml +3 -3
  105. package/examples/cookbook/README.md +4 -4
  106. package/examples/cookbook/example.yaml +3 -3
  107. package/examples/coverage-waiver/example.yaml +3 -3
  108. package/examples/egress/example.yaml +2 -2
  109. package/examples/extensions/example.yaml +1 -1
  110. package/examples/lifecycle/example.yaml +2 -2
  111. package/examples/not-found/README.md +2 -2
  112. package/examples/not-found/example.yaml +3 -3
  113. package/examples/prerender/README.md +4 -4
  114. package/examples/prerender/example.yaml +2 -2
  115. package/examples/provider-conformance/example.yaml +2 -2
  116. package/examples/shared-blocks/example.yaml +3 -3
  117. package/examples/vercel/example.yaml +3 -3
  118. package/llms-full.txt +49 -18
  119. package/llms.txt +9 -2
  120. package/package.json +14 -13
  121. package/skills/urlcode/SKILL.md +7 -7
  122. package/starters/default/.github/workflows/urlcode.yml +2 -1
  123. package/starters/default/AGENTS.md +3 -3
  124. package/starters/default/Makefile +1 -1
  125. package/starters/default/README.md +15 -13
  126. package/starters/default/tests/requests.json +0 -50
  127. package/starters/default/urlcode.yaml +1 -4
  128. package/dist/scripts/operational-drills.js +0 -33
  129. package/starters/default/functions/hello.mjs +0 -3
  130. package/starters/default/middleware/headers.mjs +0 -6
  131. package/starters/default/routes/functions.yaml +0 -20
  132. package/starters/default/routes/marketing/links.yaml +0 -7
  133. package/starters/page/README.md +0 -14
  134. package/starters/page/public/index.html +0 -12
  135. package/starters/page/tests/requests.json +0 -17
  136. package/starters/page/urlcode.yaml +0 -6
package/llms-full.txt CHANGED
@@ -1,5 +1,5 @@
1
1
  <!-- Generated by scripts/build-llms-full.ts (npm run docs:llms). Do not edit; edit the source documents. -->
2
- <!-- Consolidated URLCode authoring reference: 16 documents, about 61,239 tokens (estimate: characters / 4). -->
2
+ <!-- Consolidated URLCode authoring reference: 16 documents, about 61,970 tokens (estimate: characters / 4). -->
3
3
 
4
4
  <!-- urlcode-current-version:start -->
5
5
  # URLCode
@@ -7,7 +7,7 @@
7
7
  > A portable runtime for programmable URL behavior, and the framework that grows
8
8
  > from it: routes in YAML, functions and middleware, then accounts, administration and
9
9
  > stored links as operator-installed extensions. Stable project format
10
- > `version: "1"`. Core is Apache-2.0; this revision is `0.5.5`. `function`/`middleware`
10
+ > `version: "1"`. Core is Apache-2.0; this revision is `0.5.8`. `function`/`middleware`
11
11
  > routes are trusted by default with `sandbox: true` as the per-route opt-in. The
12
12
  > auth, admin, ui and store workspace packages are bundle sources. New sites install
13
13
  > core from npm and verified executable extensions from an immutable GitHub Release;
@@ -114,8 +114,10 @@ Each rung's YAML is valid on every rung above it.
114
114
 
115
115
  Stored short links previously sat here as a native `link` route; that handler
116
116
  was removed from core. A `urlcode-dynamic-link` package owned them the same way
117
- `auth`/`admin` own their mounts, but it has been retired and unpublished; no
118
- package occupies this rung today.
117
+ `auth`/`admin` own their mounts, but it has been retired and unpublished. It has
118
+ no direct successor; a project that wants stored short links declares a
119
+ collection through the `store` extension above (see [docs/STORE.md](https://github.com/jimhoyd-com/urlcode/blob/main/docs/STORE.md))
120
+ rather than a native `link` route.
119
121
 
120
122
  Rungs 1 to 3 need only the core package. Rungs 4 to 7 need a verified extension
121
123
  bundle installed into an explicit operator host, once its source package appears
@@ -364,6 +366,11 @@ The root [llms.txt](https://github.com/jimhoyd-com/urlcode/blob/main/llms.txt) i
364
366
  reading order for agents that want complete context in one fetch. It is a convenience,
365
367
  not a runtime protocol or a guarantee that AI clients automatically consume it.
366
368
  The generated reference is checked against the schema in `npm run verify`.
369
+ [URLCode AI](https://urlcode.ai/) is the optional hosted companion for shared
370
+ skills and LLM tooling. It complements the project-local MCP server; see
371
+ [hosted AI MCP setup](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TOOLING.md#optional-hosted-ai-mcp) for its authenticated
372
+ remote connection details. Its machine-readable entry point is
373
+ [`https://urlcode.ai/llms.txt`](https://urlcode.ai/llms.txt).
367
374
 
368
375
  Follow [organization and readability practices](https://github.com/jimhoyd-com/urlcode/blob/main/docs/BEST-PRACTICES.md): preserve local
369
376
  conventions, use clear names, keep middleware focused and avoid needless layers.
@@ -439,7 +446,7 @@ urlcode benchmark --project ./my-links --requests 100 --concurrency 2
439
446
  ```
440
447
 
441
448
  Use the intentional actual count, not always 2. Runtime checkout users can replace
442
- `urlcode` with `node src/cli.ts`. Template users can use the equivalent npm scripts.
449
+ `urlcode` with `node packages/core/src/cli.ts`. Template users can use the equivalent npm scripts.
443
450
  External bindings require an already reviewed policy; add `--policy` where needed.
444
451
  The benchmark operates locally; it is not a load test of an external deployment.
445
452
 
@@ -733,6 +740,10 @@ the operator, never by tool arguments; `--allow-authoring` on the operator's
733
740
  command line adds project-confined route, recipe, scaffold and runner tools.
734
741
  `urlcode init` writes `.mcp.json` so Claude Code and Codex register the read-only
735
742
  server for the project ([registering the server](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TOOLING.md#registering-the-server)).
743
+ The optional hosted URLCode AI MCP is a separate authenticated connection for
744
+ shared skills and LLM tools; it does not replace the local project server. Its
745
+ endpoint and credential-handling requirements are in
746
+ [hosted AI MCP setup](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TOOLING.md#optional-hosted-ai-mcp).
736
747
  Inspection is not activation/deployment readiness: real grants, asset snapshots
737
748
  and service availability still need normal runtime checks. Provider conformance replay is local evidence; only
738
749
  explicit live [deployment observations](https://github.com/jimhoyd-com/urlcode/blob/main/docs/PROVIDER-VERIFICATION.md) test ingress.
@@ -773,7 +784,7 @@ programmatic compatibility analysis and provider verification limits.
773
784
  Source: https://github.com/jimhoyd-com/urlcode/blob/main/docs/YAML-GUIDE.md
774
785
 
775
786
  <!-- urlcode-current-version:start -->
776
- This guide targets URLCode 0.5.5. Start with the function example below,
787
+ This guide targets URLCode 0.5.8. Start with the function example below,
777
788
  then add only the fields your route needs. The authoritative machine-readable
778
789
  shape is [JSON Schema](https://github.com/jimhoyd-com/urlcode/blob/main/schemas/urlcode.schema.json); semantic rules are in the
779
790
  [specification](https://github.com/jimhoyd-com/urlcode/blob/main/docs/SPECIFICATION.md). Unsupported fields fail validation.
@@ -788,15 +799,16 @@ From the runtime checkout:
788
799
 
789
800
  ```sh
790
801
  npm ci
791
- node src/cli.ts validate --project examples/cookbook
792
- node src/cli.ts test --project examples/cookbook
793
- node src/cli.ts audit --project examples/cookbook --expect-routes 40
794
- node src/cli.ts dev --project examples/cookbook
802
+ node packages/core/src/cli.ts validate --project examples/cookbook
803
+ node packages/core/src/cli.ts test --project examples/cookbook
804
+ node packages/core/src/cli.ts audit --project examples/cookbook --expect-routes 40
805
+ node packages/core/src/cli.ts dev --project examples/cookbook
795
806
  ```
796
807
 
797
808
  The cookbook is a larger learning project. The normal `urlcode init ../my-links`
798
- remains a small two-route starter. For an independent application with a pinned
799
- runtime dependency, clone [urlcode-template](https://github.com/jimhoyd-com/urlcode-template).
809
+ creates a bare zero-route scaffold; add only the routes the application needs.
810
+ For an independent application with a pinned runtime dependency, clone
811
+ [urlcode-template](https://github.com/jimhoyd-com/urlcode-template).
800
812
 
801
813
  ### Pages
802
814
 
@@ -1920,10 +1932,18 @@ values into header literals. Header configuration applies to handler responses,
1920
1932
  including declared error statuses, but not runtime validation/errors (400, 404,
1921
1933
  405, 413, 415, 500, etc.). Defaults remain `no-store`, `nosniff` and a request ID.
1922
1934
  Header policy is bounded to 64 keys/16 KiB; merged function headers remain bounded.
1935
+ A header repeated in the result the runtime writes (from a policy, or from a
1936
+ handler result that carries more than one pair for the same name) is sent as
1937
+ separate wire lines, the same as a declared `Set-Cookie` list; it is never
1938
+ collapsed to only its last value.
1923
1939
 
1924
1940
  Framing, hop-by-hop headers, Location, Allow, range/cache validators,
1925
1941
  Content-Encoding, X-Request-ID and X-Content-Type-Options are reserved to the
1926
- runtime/handler. Configure redirect URLs/status on `redirect`; asset content type,
1942
+ runtime/handler. The runtime frames every response the same way on every host:
1943
+ Content-Length is the UTF-8 byte length of the body it sends, whatever length a
1944
+ handler states. Only a HEAD answer carries a stated length, the one GET would
1945
+ send, and no body. The self-hosted and Vercel writers also make Node refuse a
1946
+ body that differs from the stated length. Configure redirect URLs/status on `redirect`; asset content type,
1927
1947
  cache and disposition on its own handler. Asset metadata cannot be overridden by
1928
1948
  `response.headers`. On functions/declared responses, Content-Type may be configured;
1929
1949
  JSON declarations require a JSON type. No response header secret interpolation.
@@ -2393,9 +2413,11 @@ cannot enforce`; the Cloudflare build reports
2393
2413
  build time and carried in the Worker artifact; the Worker has no filesystem and
2394
2414
  no Node imports, so only modules free of both qualify. The Worker's client
2395
2415
  identity is the platform's `cf-connecting-ip`; the serverless adapters use the
2396
- platform-set source address (`sourceIp` on Lambda, the leftmost
2397
- `X-Forwarded-For` entry the platform writes on Vercel). None of these read a
2398
- forwarded header a client could have set.
2416
+ platform-set source address (`sourceIp` on Lambda, Vercel's own
2417
+ `X-Vercel-Forwarded-For` Vercel's documented copy of the client IP that
2418
+ survives even when a project puts another proxy in front of Vercel, unlike
2419
+ plain `X-Forwarded-For`, which that outer proxy can overwrite before Vercel
2420
+ ever sees it). None of these read a forwarded header a client could have set.
2399
2421
 
2400
2422
  The cross-request state a policy keeps (throttle counters, the origin cache) is
2401
2423
  per runtime instance on every target, never shared between replicas or
@@ -2760,6 +2782,11 @@ portable scalar header contract. Upstream redirects are returned as responses;
2760
2782
  Location is forwarded only if explicitly selected and is never fetched. Headers
2761
2783
  nominated by an incoming or upstream Connection field are removed even if selected.
2762
2784
  Header names are normalized to lowercase; array-valued upstream fields are omitted.
2785
+ A proxy route reads incoming headers from the same credential-free projection a
2786
+ trusted function receives: a header an installed plugin or extension declares
2787
+ as a credential is withheld before `requestHeaders` selection runs, even when a
2788
+ project's own `requestHeaders` names it explicitly, so a route cannot forward a
2789
+ credential header upstream by declaring it.
2763
2790
  An encoded request body requires explicitly selecting its Content-Encoding
2764
2791
  header; literal header injection cannot change or replace that coding. Bodies
2765
2792
  remain raw bytes, including content encoding; select Content-Encoding when
@@ -3673,9 +3700,9 @@ navigation or labels to an auth/admin view without editing either package.
3673
3700
 
3674
3701
  | Hook | Input | Returns | Called |
3675
3702
  |---|---|---|---|
3676
- | `beforeRoleChange` | `{accountId, currentRoles, requestedRoles, actorId, reason}` | `{allow: boolean, reason?}` | Before roles are applied, after the administrator's permission check. A veto means the auth service is never asked. |
3703
+ | `beforeRoleChange` | `{accountId, currentRoles, requestedRoles, actorId, reason}` | `{allow: boolean, reason?}` | Before roles are applied, after the administrator's permission check, on every path that assigns roles: `/users/roles`, bulk role assignment through `/account-operations`, and a role-change support case's creation and approval. A veto means the auth service is never asked; for a bulk assignment it runs once per affected account before anything is staged. |
3677
3704
  | `onRegistrationApproved` | `{requestId, accountId, email, actorId, reason}` | ignored | After a registration request is approved. |
3678
- | `onAccountStatusChanged` | `{accountId, status, actorId, reason}` | ignored | After an account is locked or unlocked. |
3705
+ | `onAccountStatusChanged` | `{accountId, status, actorId, reason}` | ignored | After an account is locked or unlocked, on every path: `/users/status`, `/users/bulk` (once per affected account), and an approved lock/unlock support case. |
3679
3706
 
3680
3707
  #### Verdicts and failure
3681
3708
 
@@ -3816,6 +3843,10 @@ into it; only the default for routes that declare neither option has changed.
3816
3843
  - A fresh guest heap/module state per invocation prevents state crossing requests.
3817
3844
  - 32 MiB guest heap, 512 KiB stack, source/input/output/header limits, bounded
3818
3845
  concurrency, guest interruption and an independent worker termination deadline.
3846
+ - The guest's result is recorded where guest code cannot rewrite it, and the
3847
+ host checks its shape before trusting it. A result that states a body length
3848
+ for any method other than HEAD is invalid and answers 502; the runtime frames
3849
+ the response by the bytes it sends ([responses](https://github.com/jimhoyd-com/urlcode/blob/main/docs/HTTP.md#responses)).
3819
3850
  - External bindings are denied by default. Project YAML cannot self-authorize.
3820
3851
  Operator grants are exact-name, route-scoped and pinned to configuration/source.
3821
3852
 
package/llms.txt CHANGED
@@ -4,7 +4,7 @@
4
4
  > A portable runtime for programmable URL behavior, and the framework that grows
5
5
  > from it: routes in YAML, functions and middleware, then accounts, administration and
6
6
  > stored links as operator-installed extensions. Stable project format
7
- > `version: "1"`. Core is Apache-2.0; this revision is `0.5.5`. `function`/`middleware`
7
+ > `version: "1"`. Core is Apache-2.0; this revision is `0.5.8`. `function`/`middleware`
8
8
  > routes are trusted by default with `sandbox: true` as the per-route opt-in. The
9
9
  > auth, admin, ui and store workspace packages are bundle sources. New sites install
10
10
  > core from npm and verified executable extensions from an immutable GitHub Release;
@@ -21,9 +21,16 @@ grants. Unsupported features fail with the route named; nothing degrades silentl
21
21
  1. Run `npx --no --package @jimhoyd/urlcode urlcode context --project DIR` (MCP `get_context`). For redirect-only work run
22
22
  `npx --no --package @jimhoyd/urlcode urlcode context --project DIR --task redirects`: supported shapes with exact YAML, gaps
23
23
  with their exact validation error, a paste-ready `starter`, and this project's own redirects.
24
- 2. After installing `@jimhoyd/urlcode`, run `npx --no --package @jimhoyd/urlcode urlcode init . --template redirects` (it works in a directory holding only `package.json`, `package-lock.json`, `node_modules` or `.git`, and merges `scripts.start` into your `package.json`): yaml, 404 page, `npm start` honoring `PORT`, fixtures. Otherwise write `urlcode.yaml`, then run `npx --no --package @jimhoyd/urlcode urlcode validate --local --project DIR` and `npx --no --package @jimhoyd/urlcode urlcode test`. `--no` refuses a registry fetch, so it always uses the installed scoped runtime.
24
+ 2. After installing `@jimhoyd/urlcode`, run `npx --no --package @jimhoyd/urlcode urlcode init .` (it works in a directory holding only `package.json`, `package-lock.json`, `node_modules` or `.git` and preserves existing package metadata). It writes a bare agent-ready scaffold: `urlcode.yaml`, a minimal 404 fixture, `AGENTS.md`, local `.mcp.json`, and project CI. Then run `npx --no --package @jimhoyd/urlcode urlcode validate --local --project DIR` and `npx --no --package @jimhoyd/urlcode urlcode test`. `--no` refuses a registry fetch, so it always uses the installed scoped runtime.
25
25
  3. Do not read or grep [llms-full.txt](llms-full.txt) (about 60k tokens) for a routine task; it is the
26
26
  deliberate fallback when the steps above do not answer the question.
27
+ 4. Hosted skills and LLM assistance are optional: connect a compatible HTTP MCP
28
+ client to `https://mcp.urlcode.ai/mcp` with
29
+ `Authorization: Bearer <URLCODE_AI_TOKEN>`. Keep `urlcode mcp` registered
30
+ for project-aware inspection and validation, and store the hosted token in
31
+ the client's secret facility rather than in project files. Start with the
32
+ hosted [agent guide](https://urlcode.ai/llms.txt), then see
33
+ [hosted AI MCP setup](https://github.com/jimhoyd-com/urlcode/blob/main/docs/TOOLING.md#optional-hosted-ai-mcp).
27
34
 
28
35
  | Shape | Supported? | Use |
29
36
  |---|---|---|
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jimhoyd/urlcode",
3
- "version": "0.5.5",
3
+ "version": "0.5.8",
4
4
  "description": "Portable runtime for programmable URL behavior",
5
5
  "type": "module",
6
6
  "workspaces": [
@@ -75,6 +75,7 @@
75
75
  "files": [
76
76
  ".claude/skills",
77
77
  "dist",
78
+ "!dist/scripts",
78
79
  "schemas",
79
80
  "data",
80
81
  "starters",
@@ -99,22 +100,22 @@
99
100
  "test:shard": "node --conditions=development --test",
100
101
  "lint": "eslint .",
101
102
  "check": "npm run check:code && npm run check:docs",
102
- "check:code": "node scripts/check.ts && node scripts/check-release-tags.ts && node scripts/check-workspace-links.ts && node scripts/check-issue-labels.ts && npm run release:check",
103
+ "check:code": "node scripts/check.ts && node scripts/check-core-boundaries.ts && node scripts/check-release-tags.ts && node scripts/check-workspace-links.ts && node scripts/check-issue-labels.ts && npm run release:check",
103
104
  "test:workerd": "node scripts/workerd-parity.ts",
104
105
  "verify": "npm run lint && npm run typecheck && npm run check && npm run build && npm test && npm run verify:workspaces",
105
106
  "workspace:styles": "npm run styles --workspace @jimhoyd/urlcode-ui",
106
107
  "verify:workspaces": "npm run verify --workspace @jimhoyd/urlcode-ui && npm run verify --workspace @jimhoyd/urlcode-auth && npm run verify --workspace @jimhoyd/urlcode-admin && npm run verify --workspace @jimhoyd/urlcode-store && npm run verify --workspace @jimhoyd/urlcode-forms && npm run audit:packages && npm run test:workspace-integration",
107
108
  "audit:packages": "node scripts/package-audit.ts --all",
108
109
  "test:package": "npm run build && npm run test:package:built",
109
- "dev": "node src/cli.ts dev --project starters/default",
110
- "serve": "node src/cli.ts serve --project starters/default",
111
- "validate": "node src/cli.ts validate --local --project starters/default",
112
- "test:project": "node src/cli.ts test --project starters/default",
113
- "init": "node src/cli.ts init",
114
- "doctor": "node src/cli.ts doctor",
115
- "routes": "node src/cli.ts routes --project starters/default",
116
- "audit:routes": "node src/cli.ts audit --project starters/default",
117
- "benchmark:project": "node src/cli.ts benchmark --project starters/default",
110
+ "dev": "node packages/core/src/cli.ts dev --project starters/default",
111
+ "serve": "node packages/core/src/cli.ts serve --project starters/default",
112
+ "validate": "node packages/core/src/cli.ts validate --local --project starters/default",
113
+ "test:project": "node packages/core/src/cli.ts test --project starters/default",
114
+ "init": "node packages/core/src/cli.ts init",
115
+ "doctor": "node packages/core/src/cli.ts doctor",
116
+ "routes": "node packages/core/src/cli.ts routes --project starters/default",
117
+ "audit:routes": "node packages/core/src/cli.ts audit --project starters/default",
118
+ "benchmark:project": "node packages/core/src/cli.ts benchmark --project starters/default",
118
119
  "docs:reference": "node scripts/generate-yaml-reference.ts",
119
120
  "docs:plugin": "node scripts/generate-claude-plugin.ts",
120
121
  "docs:llms": "node scripts/build-llms-full.ts",
@@ -155,9 +156,9 @@
155
156
  "@changesets/cli": "3.0.3",
156
157
  "@eslint/js": "10.0.1",
157
158
  "@types/mime-types": "3.0.1",
158
- "@types/node": "26.5.1",
159
+ "@types/node": "26.6.2",
159
160
  "@types/semver": "7.8.0",
160
- "eslint": "10.10.0",
161
+ "eslint": "10.11.0",
161
162
  "globals": "17.12.0",
162
163
  "semver": "7.8.5",
163
164
  "typescript": "6.0.3",
@@ -12,24 +12,23 @@ A URLCode project declares URL behavior in YAML; the installed runtime serves it
12
12
  > Use URLCode's highest-level declarative features whenever possible. Generate custom code only when the framework cannot express the requirement.
13
13
 
14
14
  Check installed primitives, YAML configuration, policies, extensions and recipes/templates before writing custom code. Keep it focused and report the gap; never invent fields or bypass target limits or operator grants. Source checkouts have `docs/PROJECT-DIRECTION.md`; npm installations have it in `llms-full.txt`.
15
-
16
15
  ## 1. Recognize the project
17
16
 
17
+ - No `urlcode.yaml` yet? `npm install @jimhoyd/urlcode` (scoped; no unscoped `urlcode` package exists), then `npx urlcode init .`.
18
18
  - The root has `urlcode.yaml` with `version: "1"`. Included route files are
19
- listed under `includes`; functions, middleware and assets are referenced from
20
- the project root.
19
+ listed under `includes`; functions, middleware and assets are project-relative.
21
20
  - Read the project's `AGENTS.md` first if present; it lists the handlers,
22
21
  policies and commands this runtime version supports.
23
22
  - Find the runtime: `urlcode` on the PATH, or
24
23
  `node node_modules/@jimhoyd/urlcode/dist/cli.js`, or
25
- `node /path/to/urlcode/src/cli.ts` for a source checkout. Use one form for
24
+ `node /path/to/urlcode/packages/core/src/cli.ts` for a source checkout. Use one form for
26
25
  every command below.
27
-
28
26
  ## 2. Retrieve the minimum, do not read everything
29
27
 
30
28
  **First step: one bounded query.** MCP `get_context` when the `urlcode` server is registered, otherwise `urlcode context --project DIR` (add `--budget N` to cap it). It returns a compact summary, constraints and exact commands, never a schema dump. Then retrieve only what the task needs: `capabilities NAME`/`get_capability` (its limits), `get_schema`, `recipes search`/`search_recipes`, `explain`, and `get_extensions` when an operator host file exists. The bare `urlcode capabilities` and `recipes list` catalogs, `llms.txt` and `llms-full.txt` are deliberate fallback/reference, not the opening move.
31
29
 
32
30
  If the project carries `.mcp.json` (written by `urlcode init`) and your client has the `urlcode` server, prefer its tools: `get_context` (project summary, constraints, exact commands), `get_capability` and `get_schema` (one capability or YAML fragment), `search_recipes`, `search_examples`, `explain` (a route's effective behavior) and `get_manifest`. For framework discovery, use `list_skills` before `get_skill`, `search_docs` for a short package-owned excerpt, and `get_example` for one runnable example. Use `validate_yaml` for pasted YAML syntax/schema feedback only; use `validate` for the actual project. The server is read-only; `--allow-authoring` is an operator opt-in you never add yourself.
31
+ [URLCode AI](https://urlcode.ai/) is an optional, separate hosted service for shared skills and LLM tooling. Its remote MCP supplements this local project server; never replace `.mcp.json` or put its bearer token in project files. Its machine-readable entry point is `https://urlcode.ai/llms.txt`; connection details belong to the MCP client's secret facility and are documented in the URLCode tooling guide.
33
32
  When the MCP server was started with an operator host file, `get_extensions`
34
33
  returns installed extension configuration/policy schemas, declared project
35
34
  hook contracts, supported authoring surfaces and fast checks. Otherwise use `urlcode extensions --project DIR --host-file
@@ -118,8 +117,9 @@ urlcode audit --project DIR --expect-routes N
118
117
  ```
119
118
 
120
119
  Add fixtures to `tests/requests.json` for each new route: positive and negative
121
- cases, every active method, `HEAD`. Update `N` deliberately when routes are
122
- added or removed, and update any `--expect-routes` in the project's README,
120
+ cases, every active method, `HEAD`. `N` is declared routes plus one per active
121
+ `site.*` convention; an audit mismatch reports the declared/generated split. Update it
122
+ deliberately when routes are added or removed, and update any `--expect-routes` in the project's README,
123
123
  Makefile or CI workflow to match. A failing validation names the route; fix
124
124
  the declaration rather than working around it.
125
125
 
@@ -19,5 +19,6 @@ jobs:
19
19
  # Pin to a release tag or commit of the runtime you depend on.
20
20
  - uses: jimhoyd-com/urlcode/action@main
21
21
  with:
22
- expect-routes: 2 # update when adding or removing routes
22
+ expect-routes: 0 # update when adding or removing routes
23
+ allow-empty-project: true # remove after adding the first active route
23
24
  compliance: baseline
@@ -22,7 +22,7 @@ static serving and authentication. Read this file before changing anything.
22
22
 
23
23
  When present, `.mcp.json` registers the read-only `urlcode mcp` server; prefer its
24
24
  tools (also `get_manifest`) to reading documents. Inspect `get_extensions` before
25
- replacing extension behavior. `--allow-authoring` is an operator opt-in; never add it. For a committed artifact lock, use `get_extension_artifacts`/`get_extension_artifact`; they expose verified inert data and never activate an extension.
25
+ replacing extension behavior. `--allow-authoring` is an operator opt-in; never add it. For a committed artifact lock, use `get_extension_artifacts`/`get_extension_artifact`; they expose verified inert data and never activate an extension. [URLCode AI](https://urlcode.ai/) is a separate optional hosted service for shared skills and LLM tooling; its remote MCP never replaces this local project server, and its credential belongs only in a client secret facility, never project files. Its machine-readable entry point is `https://urlcode.ai/llms.txt`.
26
26
 
27
27
  ## What the runtime provides (this version)
28
28
 
@@ -56,10 +56,10 @@ record the reason in `sandboxReason`. Try `redirect` (relative or `/**`) or `res
56
56
  ```sh
57
57
  urlcode validate --local
58
58
  urlcode test
59
- urlcode audit --expect-routes 2
59
+ urlcode audit --expect-routes 0
60
60
  ```
61
61
 
62
- Run all three after every change, updating the route count deliberately and adding `tests/requests.json` fixtures for every new route (positive/negative, every active method, HEAD). No global install: use `node /path/to/urlcode/src/cli.ts`.
62
+ With no active routes, this initial audit intentionally exits nonzero with `no-active-routes`. Add the first route and its fixture, then make the audit pass; remove `allow-empty-project: true` from the generated GitHub workflow at that point. `N` counts declared routes plus one route for each active `site.*` convention; an audit mismatch reports the declared/generated split. Update it deliberately and add `tests/requests.json` fixtures for every new route (positive/negative, every active method, HEAD). No global install: use `node /path/to/urlcode/packages/core/src/cli.ts`.
63
63
 
64
64
  ## Feedback
65
65
 
@@ -1,6 +1,6 @@
1
1
  .DEFAULT_GOAL := help
2
2
 
3
- # Install the runtime separately, or override: URLCODE='node /path/to/urlcode/src/cli.js'
3
+ # Install the runtime separately, or override: URLCODE='node /path/to/urlcode/dist/cli.js'
4
4
  URLCODE ?= urlcode
5
5
  HOST ?= 127.0.0.1
6
6
  PORT ?= 3000
@@ -1,29 +1,31 @@
1
1
  # Your URLCode project
2
2
 
3
- One starter: a URL that runs your function, and a regular redirect.
4
- Created with `urlcode init ../my-links`. No template choice is needed.
3
+ This is a bare, agent-ready URLCode scaffold. It starts with no routes so your
4
+ application's YAML and tests describe only the behavior you intend to ship. Its
5
+ single fixture proves the empty project returns 404.
5
6
 
6
7
  ```sh
7
8
  urlcode dev
8
9
  # In another terminal:
9
10
  urlcode test
10
- urlcode audit --expect-routes 2
11
- urlcode benchmark --requests 1000 --concurrency 2
12
11
  ```
13
12
 
14
- Visit http://127.0.0.1:3000/hello/Ada or http://127.0.0.1:3000/go.
15
- `urlcode.yaml` loads `routes/functions.yaml` and `routes/marketing/links.yaml`.
16
- Organize those files however you like; references are relative to the project root.
17
- The function route uses `middleware/headers.mjs` to add a response header around
18
- `await next()`. Edit or reuse it on other routes as needed.
19
- GET/HEAD, redirect 302 and no-store defaults keep YAML short. Valid edits reload.
13
+ `urlcode audit --expect-routes 0` intentionally reports `no-active-routes` until
14
+ you add the first route. The included GitHub workflow permits only that initial
15
+ audit result; remove `allow-empty-project: true` after adding a route.
16
+
17
+ Start with the local MCP `get_context` tool (or `urlcode context --project .`),
18
+ then add the smallest declarative route or custom code the task requires. Keep
19
+ `tests/requests.json` aligned with every route you add and update the audit
20
+ count deliberately. `AGENTS.md` explains the workflow and points to the
21
+ optional hosted shared tooling at https://urlcode.ai/llms.txt.
20
22
 
21
23
  This app uses the runtime you installed separately (compatible with 0.1.0).
22
- Without a global install, invoke `node /path/to/urlcode/src/cli.ts` instead of
23
- `urlcode`. Optional Make shortcuts accept `URLCODE='node /path/to/urlcode/src/cli.ts'`.
24
+ Without a global install, invoke `node /path/to/urlcode/packages/core/src/cli.ts` instead of
25
+ `urlcode`. Optional Make shortcuts accept `URLCODE='node /path/to/urlcode/packages/core/src/cli.ts'`.
24
26
  For a cloneable project with a pinned npm runtime dependency, use
25
27
  [urlcode-template](https://github.com/jimhoyd-com/urlcode-template). Both start
26
- with the same two route examples. No runtime fork or provider account is needed.
28
+ with the same bare scaffold. No runtime fork or provider account is needed.
27
29
 
28
30
  Keep this app in your own Git repository. Secrets stay in ignored `.env.local`
29
31
  or provider environment values, with external operator policy for function grants.
@@ -1,56 +1,6 @@
1
1
  [
2
- {
3
- "path": "/hello/Ada",
4
- "status": 200,
5
- "expectBody": "{\"message\":\"Hello, Ada!\"}",
6
- "expectHeaders": {
7
- "x-powered-by": "URLCode",
8
- "cache-control": "no-store",
9
- "x-example-middleware": "active"
10
- }
11
- },
12
- {
13
- "path": "/hello/Ada",
14
- "method": "HEAD",
15
- "status": 200,
16
- "expectBody": "",
17
- "expectHeaders": {
18
- "x-example-middleware": "active"
19
- }
20
- },
21
- {
22
- "path": "/go",
23
- "status": 302,
24
- "expectHeaders": {
25
- "location": "https://example.com/"
26
- }
27
- },
28
- {
29
- "path": "/go",
30
- "method": "POST",
31
- "status": 405,
32
- "expectHeaders": {
33
- "allow": "GET, HEAD"
34
- }
35
- },
36
2
  {
37
3
  "path": "/missing",
38
4
  "status": 404
39
- },
40
- {
41
- "path": "/hello/Ada",
42
- "method": "POST",
43
- "status": 405,
44
- "expectHeaders": {
45
- "allow": "GET, HEAD"
46
- }
47
- },
48
- {
49
- "path": "/hello/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
50
- "status": 400
51
- },
52
- {
53
- "path": "/hello/%ZZ",
54
- "status": 400
55
5
  }
56
6
  ]
@@ -1,7 +1,4 @@
1
1
  # yaml-language-server: $schema=https://raw.githubusercontent.com/jimhoyd-com/urlcode/669632fb4f188f293a0f622847f87ee019c0627f/schemas/urlcode.schema.json
2
- # Organize files however you like; list their paths from this project root.
2
+ # Start with no routes. Add only the files and routes your application needs.
3
3
  version: "1"
4
- includes:
5
- - routes/functions.yaml
6
- - routes/marketing/links.yaml
7
4
  routes: {}
@@ -1,33 +0,0 @@
1
- // Disposable local proof, never a claim about the production deployment.
2
- import assert from 'node:assert/strict';
3
- import {mkdtemp,mkdir,writeFile,rm} from 'node:fs/promises';
4
- import {tmpdir} from 'node:os';
5
- import {join} from 'node:path';
6
- import {startServer} from '../server.js';
7
-
8
-
9
- const seconds=Number(process.env.URLCODE_SOAK_SECONDS||5);
10
- assert(Number.isInteger(seconds)&&seconds>=1&&seconds<=3600,'Soak must be 1–3600 seconds');
11
- const directory=await mkdtemp(join(tmpdir(),'urlcode-drills-')),project=join(directory,'app');
12
- let app ;
13
- try {
14
- await mkdir(project);
15
- const config='version: "1"\nroutes:\n /go:\n redirect: {url: "https://example.com/v1"}\n /function:\n function: {source: f.mjs}\n';
16
- await writeFile(join(project,'urlcode.yaml'),config);
17
- await writeFile(join(project,'f.mjs'),'export default () => new Response("isolated");');
18
- app=await startServer({project,port:0,log:()=>{}});
19
- const server=app;
20
- const get=async(path )=>{const r=await fetch(`http://127.0.0.1:${server.address.port}${path}`,{redirect:'manual'});const body=await r.text();return {status:r.status,location:r.headers.get('location'),body};};
21
- const start=performance.now(),samples =[];let requests=0;
22
- while(performance.now()-start<seconds*1000){
23
- const before=performance.now();const responses=await Promise.all(['/go','/function'].map(get));
24
- assert.deepEqual(responses.map(r=>r.status),[302,200]);assert.equal(responses[1]?.body,'isolated');
25
- samples.push(performance.now()-before);requests+=2;
26
- }
27
- await writeFile(join(project,'urlcode.yaml'),'invalid: configuration');assert.equal(await app.reload(),false);assert.equal((await get('/go')).location,'https://example.com/v1');
28
- await writeFile(join(project,'urlcode.yaml'),config.replace('/v1','/v2'));assert.equal(await app.reload(),true);assert.equal((await get('/go')).location,'https://example.com/v2');
29
- await writeFile(join(project,'urlcode.yaml'),config);assert.equal(await app.reload(),true);assert.equal((await get('/go')).location,'https://example.com/v1');
30
- await app.close();app=undefined;
31
- samples.sort((a,b)=>a-b);
32
- console.log(JSON.stringify({event:'local-operational-proof',seconds,requests,batchP99Ms:samples[Math.floor(samples.length*.99)],rssBytes:process.memoryUsage().rss,checks:['mixed HTTP load','invalid reload preserves last-good','configuration rollback'],deploymentProof:false}));
33
- } finally {await app?.close();await rm(directory,{recursive:true,force:true});}
@@ -1,3 +0,0 @@
1
- export default function hello(request, { args }) {
2
- return Response.json({ message: `Hello, ${args.name}!` });
3
- }
@@ -1,6 +0,0 @@
1
- // Reusable logic around a route: run downstream, then add a response header.
2
- export default async function headers(request, context, next) {
3
- const response = await next();
4
- response.headers.set('x-example-middleware', 'active');
5
- return response;
6
- }
@@ -1,20 +0,0 @@
1
- # yaml-language-server: $schema=https://raw.githubusercontent.com/jimhoyd-com/urlcode/669632fb4f188f293a0f622847f87ee019c0627f/schemas/urlcode.schema.json
2
- # File locations do not change route paths. GET/HEAD are the default methods.
3
- version: "1"
4
- routes:
5
- # Your code runs when this URL is requested.
6
- /hello/{name}:
7
- parameters:
8
- - name: name
9
- in: path
10
- required: true
11
- schema: {type: string, minLength: 1, maxLength: 80}
12
- middleware:
13
- - source: middleware/headers.mjs
14
- function:
15
- source: functions/hello.mjs
16
- args:
17
- name: {from: path, name: name}
18
- response:
19
- headers:
20
- X-Powered-By: URLCode
@@ -1,7 +0,0 @@
1
- # yaml-language-server: $schema=https://raw.githubusercontent.com/jimhoyd-com/urlcode/669632fb4f188f293a0f622847f87ee019c0627f/schemas/urlcode.schema.json
2
- version: "1"
3
- routes:
4
- # A regular redirect needs no custom code.
5
- /go:
6
- redirect:
7
- url: https://example.com
@@ -1,14 +0,0 @@
1
- # Your URLCode page
2
-
3
- The smallest project: one route that serves `public/index.html`.
4
- Created with `urlcode init <dir> --template page`.
5
-
6
- ```sh
7
- urlcode validate --local
8
- urlcode test
9
- urlcode dev # http://127.0.0.1:3000/
10
- ```
11
-
12
- Edit `public/index.html`. Add more pages, a `static` directory, downloads,
13
- redirects or functions as routes in `urlcode.yaml` (see docs/ASSETS.md in the
14
- URLCode repository). Keep `tests/requests.json` in step with the routes.
@@ -1,12 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
6
- <title>Hello, URLCode</title>
7
- </head>
8
- <body>
9
- <h1>Hello, URLCode</h1>
10
- <p>This page is served by a page route in urlcode.yaml.</p>
11
- </body>
12
- </html>
@@ -1,17 +0,0 @@
1
- [
2
- {
3
- "path": "/",
4
- "status": 200,
5
- "expectBody": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Hello, URLCode</title>\n</head>\n<body>\n<h1>Hello, URLCode</h1>\n<p>This page is served by a page route in urlcode.yaml.</p>\n</body>\n</html>\n"
6
- },
7
- {
8
- "path": "/",
9
- "method": "HEAD",
10
- "status": 200,
11
- "expectBody": ""
12
- },
13
- {
14
- "path": "/missing",
15
- "status": 404
16
- }
17
- ]
@@ -1,6 +0,0 @@
1
- version: "1"
2
- routes:
3
- /:
4
- description: The home page, served from a file. Add routes beside it as the project grows.
5
- page:
6
- file: public/index.html