stitchkit 0.81.0 → 0.82.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -506,6 +506,10 @@ The full guide and API reference, in [`docs/`](./docs/README.md):
506
506
  [multi-tenant](./docs/guide/multi-tenant.md) ·
507
507
  [upgrading](./docs/guide/upgrading.md)
508
508
  - **[API reference](./docs/api/reference.md)** — every export, by entrypoint.
509
+ - **Moving to a newer version?** Run `bunx stitchkit@latest upgrade` inside your
510
+ project. It reads the version you have from `node_modules` and the changelog
511
+ from inside the package, and prints every breaking change between the two —
512
+ oldest first, each with who must act. Nothing to clone, nobody to ask.
509
513
  - **Coding agent?** The package ships **`llms.txt`** (a curated index) and
510
514
  **`llms-full.txt`** (the whole guide inlined) — your agent reads them from
511
515
  `node_modules/stitchkit/`. For Claude Code, the repo also provides a
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The consumer upgrade plan: every `### ⚠️ Breaking changes` section an
3
+ * installed → target range crosses, oldest first.
4
+ *
5
+ * Pure over the changelog text. The reader is `stitchkit upgrade`, the binary
6
+ * this package installs; the changelog it reads ships in the package, so a
7
+ * consumer recovers the plan without cloning the repository or being told the
8
+ * range by whoever cut the release.
9
+ */
10
+ export interface UpgradeBreakingChange {
11
+ version: string;
12
+ whoMustAct: string;
13
+ markdown: string;
14
+ }
15
+ /** Extract every breaking section crossed by an exact `from` (exclusive) → `to` (inclusive) upgrade. */
16
+ export declare function planUpgrade(changelog: string, from: string, to: string): UpgradeBreakingChange[];
17
+ export declare function renderUpgradePlan(changes: readonly UpgradeBreakingChange[], from: string, to: string): string;
18
+ //# sourceMappingURL=upgrade-plan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upgrade-plan.d.ts","sourceRoot":"","sources":["../../src/internal/upgrade-plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAsBD,wGAAwG;AACxG,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,qBAAqB,EAAE,CAgCzB;AAED,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,qBAAqB,EAAE,EACzC,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,GACT,MAAM,CASR"}
@@ -1,6 +1,19 @@
1
1
  import type { ReleaseMarker } from './marker.js';
2
2
  import type { ReleaseWatcher } from './watcher.js';
3
- /** The little of a Socket.IO server this binding uses — structural, no peer import. */
3
+ /**
4
+ * The little of a Socket.IO server this binding uses — structural, no peer
5
+ * import.
6
+ *
7
+ * `emit` is declared over `string` because the event name is configurable. A
8
+ * **typed** `Server<ClientToServer, ServerToClient>` narrows its own `emit` to
9
+ * the names in its map, so it satisfies this only once that map declares the
10
+ * event — `release: (payload: { buildId: string | null }) => void`, or whatever
11
+ * `options.event` renames it to. Until then the compiler reports a structural
12
+ * mismatch between two large socket.io types, which reads like a defect in this
13
+ * binding and is a missing line in the application's event map. Adding it is
14
+ * step 3 of the guide's adoption list for exactly this reason: the same
15
+ * declaration is what lets the application's own `on` know the event.
16
+ */
4
17
  export interface ReleaseSocketServer {
5
18
  on(event: 'connection', handler: (socket: ReleaseSocketEmitter) => void): unknown;
6
19
  emit(event: string, payload: {
@@ -1 +1 @@
1
- {"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../src/release/socket.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,uFAAuF;AACvF,MAAM,WAAW,mBAAmB;IAClC,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,GAAG,OAAO,CAAC;IAClF,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC;CACnE;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC;CACnE;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC;IACnF,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC;CACtF;AAED,MAAM,WAAW,oBAAoB;IACnC,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,mBAAmB,EACvB,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE,oBAAoB,GAAG;IAAE,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAAO,GACrE,MAAM,IAAI,CAcZ;AAED,qEAAqE;AACrE,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,qBAAqB,EAC7B,OAAO,EAAE,cAAc,EACvB,OAAO,GAAE,oBAAyB,GACjC,MAAM,IAAI,CAQZ"}
1
+ {"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../src/release/socket.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,mBAAmB;IAClC,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,IAAI,GAAG,OAAO,CAAC;IAClF,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC;CACnE;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC;CACnE;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC;IACnF,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE;QAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,KAAK,IAAI,GAAG,OAAO,CAAC;CACtF;AAED,MAAM,WAAW,oBAAoB;IACnC,iDAAiD;IACjD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,EAAE,EAAE,mBAAmB,EACvB,MAAM,EAAE,aAAa,EACrB,OAAO,GAAE,oBAAoB,GAAG;IAAE,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAAO,GACrE,MAAM,IAAI,CAcZ;AAED,qEAAqE;AACrE,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,qBAAqB,EAC7B,OAAO,EAAE,cAAc,EACvB,OAAO,GAAE,oBAAyB,GACjC,MAAM,IAAI,CAQZ"}
@@ -27,6 +27,7 @@ export type TrackingContractEndpoints<TSchemas extends TrackingSchemas<z.ZodObje
27
27
  track: {
28
28
  method: 'POST';
29
29
  path: '/events';
30
+ expose: readonly ['HTTP'];
30
31
  desc: string;
31
32
  input: TSchemas['request'];
32
33
  output: TSchemas['response'];
@@ -40,6 +41,16 @@ export type TrackingContractEndpoints<TSchemas extends TrackingSchemas<z.ZodObje
40
41
  * `sendUnloadBeacon` with a string body — the only body a document that is
41
42
  * being unloaded can deliver to another origin (ADR 0165) — and the server
42
43
  * therefore needs an explicit `cors.origin` allow-list for it to arrive.
44
+ *
45
+ * **Both endpoints are HTTP-only, and that is not a formality.** An endpoint
46
+ * with no `expose` is a tool on MCP and AGENT by default, and this contract is
47
+ * built here rather than by the application's own contract factory — so a
48
+ * project that sets `toolExposure: 'explicit'` for everything it authors was
49
+ * still handed a `track` tool it never asked for. An agent has nothing to gain
50
+ * from a browser-event ingest and one thing to lose by having it: writes into
51
+ * the application's visitor data, under its own name, indistinguishable
52
+ * afterwards from a real visitor's. `bootstrap` said `['HTTP']` from the start;
53
+ * `track` not saying it was an omission, not a decision.
43
54
  */
44
55
  export declare function createTrackingContract<TType extends string, TScope extends string>(config: TrackingContractConfig<TType, undefined, TScope>): ContractDef<TrackingContractEndpoints<TrackingSchemas<z.ZodObject<TrackingEventShape<TType>>>>, TScope>;
45
56
  export declare function createTrackingContract<TType extends string, TExtras extends z.ZodRawShape, TScope extends string>(config: TrackingContractConfig<TType, TExtras, TScope>): ContractDef<TrackingContractEndpoints<TrackingSchemas<z.ZodObject<TrackingEventShape<TType> & TExtras>>>, TScope>;
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/tracking/contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAAE,KAAK,WAAW,EAAkB,MAAM,aAAa,CAAC;AAC/D,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC3B,MAAM,WAAW,CAAC;AAEnB,MAAM,WAAW,sBAAsB,CACrC,KAAK,SAAS,MAAM,EACpB,OAAO,SAAS,CAAC,CAAC,WAAW,GAAG,SAAS,EACzC,MAAM,SAAS,MAAM,CACrB,SAAQ,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC;IAC7C,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,yFAAyF;AACzF,MAAM,MAAM,yBAAyB,CAAC,QAAQ,SAAS,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI;IACrF,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,QAAQ,CAAC;QACf,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;QAC1B,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;QACzB,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;KAC/B,CAAC;IACF,KAAK,EAAE;QACL,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,SAAS,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC3B,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC7B,cAAc,EAAE,IAAI,CAAC;QACrB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;CACH,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,SAAS,MAAM,EAAE,MAAM,SAAS,MAAM,EAChF,MAAM,EAAE,sBAAsB,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,GACvD,WAAW,CACZ,yBAAyB,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAClF,MAAM,CACP,CAAC;AACF,wBAAgB,sBAAsB,CACpC,KAAK,SAAS,MAAM,EACpB,OAAO,SAAS,CAAC,CAAC,WAAW,EAC7B,MAAM,SAAS,MAAM,EAErB,MAAM,EAAE,sBAAsB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,GACrD,WAAW,CACZ,yBAAyB,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAC5F,MAAM,CACP,CAAC"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/tracking/contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAC7B,OAAO,EAAE,KAAK,WAAW,EAAkB,MAAM,aAAa,CAAC;AAC/D,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC3B,MAAM,WAAW,CAAC;AAEnB,MAAM,WAAW,sBAAsB,CACrC,KAAK,SAAS,MAAM,EACpB,OAAO,SAAS,CAAC,CAAC,WAAW,GAAG,SAAS,EACzC,MAAM,SAAS,MAAM,CACrB,SAAQ,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC;IAC7C,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,yFAAyF;AACzF,MAAM,MAAM,yBAAyB,CAAC,QAAQ,SAAS,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI;IACrF,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,QAAQ,CAAC;QACf,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;QAC1B,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;QACzB,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;KAC/B,CAAC;IACF,KAAK,EAAE;QACL,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,SAAS,CAAC;QAChB,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;QAC1B,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC3B,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC7B,cAAc,EAAE,IAAI,CAAC;QACrB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;CACH,CAAC;AAEF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,SAAS,MAAM,EAAE,MAAM,SAAS,MAAM,EAChF,MAAM,EAAE,sBAAsB,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,CAAC,GACvD,WAAW,CACZ,yBAAyB,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAClF,MAAM,CACP,CAAC;AACF,wBAAgB,sBAAsB,CACpC,KAAK,SAAS,MAAM,EACpB,OAAO,SAAS,CAAC,CAAC,WAAW,EAC7B,MAAM,SAAS,MAAM,EAErB,MAAM,EAAE,sBAAsB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,GACrD,WAAW,CACZ,yBAAyB,CAAC,eAAe,CAAC,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,EAC5F,MAAM,CACP,CAAC"}
@@ -5,6 +5,7 @@
5
5
  * back; `issueVisitLease` runs the visit algorithm over a store interface the
6
6
  * application implements. No database, no schema, no domain. → ADR 0166.
7
7
  */
8
+ export type { TrackingDisposition, VisitBootstrapResponse, VisitEntryContext, } from './tracking/schemas.js';
8
9
  export { type ActiveIntervalOptions, type ActiveTimeInterval, activeIntervalOf, } from './tracking/server/active-interval.js';
9
10
  export { DEFAULT_BOT_USER_AGENT_PATTERN, isBotUserAgent } from './tracking/server/bot.js';
10
11
  export { type DispositionEvent, type DispositionInput, type DispositionResult, dispositionTrackingBatch, type KnownVisit, } from './tracking/server/disposition.js';
@@ -1 +1 @@
1
- {"version":3,"file":"tracking-server.d.ts","sourceRoot":"","sources":["../src/tracking-server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,gBAAgB,GACjB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,8BAA8B,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,wBAAwB,EACxB,KAAK,UAAU,GAChB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EACL,sBAAsB,EACtB,KAAK,aAAa,EAClB,KAAK,gBAAgB,GACtB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,+BAA+B,CAAC"}
1
+ {"version":3,"file":"tracking-server.d.ts","sourceRoot":"","sources":["../src/tracking-server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH,YAAY,EACV,mBAAmB,EACnB,sBAAsB,EACtB,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,gBAAgB,GACjB,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,8BAA8B,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvF,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,wBAAwB,EACxB,KAAK,UAAU,GAChB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EACL,sBAAsB,EACtB,KAAK,aAAa,EAClB,KAAK,gBAAgB,GACtB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,eAAe,EACf,KAAK,kBAAkB,EACvB,KAAK,UAAU,EACf,KAAK,iBAAiB,EACtB,KAAK,cAAc,GACpB,MAAM,+BAA+B,CAAC"}
package/dist/tracking.js CHANGED
@@ -778,6 +778,7 @@ function createTrackingContract(config) {
778
778
  track: {
779
779
  method: "POST",
780
780
  path: "/events",
781
+ expose: ["HTTP"],
781
782
  desc: "Batch track browser events",
782
783
  input: schemas.request,
783
784
  output: schemas.response,
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ export interface UpgradeCliResult {
3
+ /** What to write to stdout. */
4
+ readonly output: string;
5
+ /** Process exit code — 0 when the plan (or "nothing to do") was produced. */
6
+ readonly code: number;
7
+ }
8
+ /**
9
+ * The whole command as a function of its arguments and the filesystem, so the
10
+ * gate can exercise the paths that matter — a project with no stitchkit
11
+ * installed, a range that runs backwards, a changelog the package did not ship.
12
+ */
13
+ export declare function runUpgradeCli(argv: readonly string[]): UpgradeCliResult;
14
+ /**
15
+ * Was this file executed as a program, rather than imported?
16
+ *
17
+ * Through the real install path that is a **symlink** question, not a string
18
+ * one: npm and bun link `node_modules/.bin/stitchkit` at the built file, Node
19
+ * keeps the link in `argv[1]` and resolves `import.meta.url` to its target. A
20
+ * direct comparison of the two is therefore false for every consumer, and the
21
+ * binary becomes a silent no-op — exit 0, no output, nothing to notice — while
22
+ * running the same file in place works perfectly. Both arguments are passed in
23
+ * so this is decidable without launching a process: under Bun `argv[1]` is
24
+ * already resolved, so a spawned symlink proves nothing about Node.
25
+ */
26
+ export declare function isDirectInvocation(invoked: string | undefined, self: string): boolean;
27
+ //# sourceMappingURL=upgrade-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upgrade-cli.d.ts","sourceRoot":"","sources":["../src/upgrade-cli.ts"],"names":[],"mappings":";AA4DA,MAAM,WAAW,gBAAgB;IAC/B,+BAA+B;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,gBAAgB,CAqCvE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CASrF"}
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/upgrade-cli.ts
4
+ import { readFileSync, realpathSync } from "node:fs";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { fileURLToPath, pathToFileURL } from "node:url";
7
+
8
+ // src/internal/upgrade-plan.ts
9
+ function semverParts(version) {
10
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
11
+ if (!match)
12
+ throw new Error(`Expected an exact semver, received "${version}"`);
13
+ const [, major, minor, patch] = match;
14
+ if (major === undefined || minor === undefined || patch === undefined) {
15
+ throw new Error(`Expected an exact semver, received "${version}"`);
16
+ }
17
+ return [Number(major), Number(minor), Number(patch)];
18
+ }
19
+ function compareSemver(left, right) {
20
+ const a = semverParts(left);
21
+ const b = semverParts(right);
22
+ for (const index of [0, 1, 2]) {
23
+ const difference = a[index] - b[index];
24
+ if (difference !== 0)
25
+ return difference;
26
+ }
27
+ return 0;
28
+ }
29
+ function planUpgrade(changelog, from, to) {
30
+ semverParts(from);
31
+ semverParts(to);
32
+ if (compareSemver(from, to) >= 0) {
33
+ throw new Error(`Upgrade range must increase: ${from} → ${to}`);
34
+ }
35
+ const releases = [...changelog.matchAll(/^## \[(\d+\.\d+\.\d+)\].*$/gm)];
36
+ const changes = [];
37
+ for (const [index, release] of releases.entries()) {
38
+ const version = release[1];
39
+ if (version === undefined)
40
+ continue;
41
+ if (compareSemver(version, from) <= 0 || compareSemver(version, to) > 0)
42
+ continue;
43
+ const bodyStart = (release.index ?? 0) + release[0].length;
44
+ const bodyEnd = releases[index + 1]?.index ?? changelog.length;
45
+ const body = changelog.slice(bodyStart, bodyEnd);
46
+ const breakingHeader = /^### ⚠️ Breaking changes\s*$/m.exec(body);
47
+ if (!breakingHeader)
48
+ continue;
49
+ const breakingStart = (breakingHeader.index ?? 0) + breakingHeader[0].length;
50
+ const afterHeader = body.slice(breakingStart);
51
+ const nextSection = /^### /m.exec(afterHeader);
52
+ const markdown = afterHeader.slice(0, nextSection?.index ?? afterHeader.length).trim();
53
+ const who = /^\*\*Who must act:\*\*\s*([\s\S]*?)(?=\n\s*\n|\n[-*] )/m.exec(markdown);
54
+ changes.push({
55
+ version,
56
+ whoMustAct: who?.[1]?.replace(/\s+/g, " ").trim() ?? "Not declared in this legacy changelog section.",
57
+ markdown
58
+ });
59
+ }
60
+ return changes.sort((left, right) => compareSemver(left.version, right.version));
61
+ }
62
+ function renderUpgradePlan(changes, from, to) {
63
+ const header = `# Stitchkit upgrade ${from} → ${to}`;
64
+ if (changes.length === 0)
65
+ return `${header}
66
+
67
+ No breaking sections in this range.
68
+ `;
69
+ return `${header}
70
+
71
+ ${changes.map((change) => `## ${change.version}
72
+
73
+ **Who must act:** ${change.whoMustAct}
74
+
75
+ ${change.markdown.replace(/^\*\*Who must act:\*\*[\s\S]*?(?=\n\s*\n|\n[-*] )/m, "").trim()}`).join(`
76
+
77
+ `)}
78
+ `;
79
+ }
80
+
81
+ // src/upgrade-cli.ts
82
+ var USAGE = `stitchkit upgrade — print every breaking change between the installed version and this one
83
+
84
+ bunx stitchkit@latest upgrade in a project that depends on stitchkit
85
+ npx stitchkit@latest upgrade
86
+
87
+ Options
88
+ --from <version> installed version (default: the stitchkit in ./node_modules)
89
+ --to <version> target version (default: the version of this package)
90
+ --cwd <dir> project to read the installed version from (default: .)
91
+ --changelog <path> changelog to plan from (default: the one in this package)
92
+ `;
93
+ function option(argv, name) {
94
+ const index = argv.indexOf(`--${name}`);
95
+ if (index < 0)
96
+ return;
97
+ const value = argv[index + 1];
98
+ if (value === undefined || value.startsWith("--")) {
99
+ throw new Error(`--${name} needs a value`);
100
+ }
101
+ return value;
102
+ }
103
+ function packageRoot() {
104
+ return resolve(dirname(fileURLToPath(import.meta.url)), "..");
105
+ }
106
+ function versionOf(packageJsonPath) {
107
+ let text;
108
+ try {
109
+ text = readFileSync(packageJsonPath, "utf8");
110
+ } catch {
111
+ return;
112
+ }
113
+ const parsed = JSON.parse(text);
114
+ if (typeof parsed !== "object" || parsed === null || !("version" in parsed))
115
+ return;
116
+ const { version } = parsed;
117
+ return typeof version === "string" ? version : undefined;
118
+ }
119
+ function runUpgradeCli(argv) {
120
+ if (argv.includes("--help") || argv.includes("-h") || argv.length === 0) {
121
+ return { output: USAGE, code: 0 };
122
+ }
123
+ const [command] = argv;
124
+ if (command !== "upgrade") {
125
+ return { output: `Unknown command "${command}".
126
+
127
+ ${USAGE}`, code: 1 };
128
+ }
129
+ const root = packageRoot();
130
+ const to = option(argv, "to") ?? versionOf(join(root, "package.json"));
131
+ if (to === undefined) {
132
+ throw new Error(`Cannot read the version of the stitchkit at ${root}`);
133
+ }
134
+ const cwd = resolve(option(argv, "cwd") ?? process.cwd());
135
+ const from = option(argv, "from") ?? versionOf(join(cwd, "node_modules", "stitchkit", "package.json"));
136
+ if (from === undefined) {
137
+ throw new Error(`No stitchkit found in ${join(cwd, "node_modules")} — run this in the project that depends on it, or pass --from <installed version>`);
138
+ }
139
+ if (from === to) {
140
+ return { output: `Already on ${to}. Nothing to upgrade.
141
+ `, code: 0 };
142
+ }
143
+ const changelogPath = option(argv, "changelog") ?? join(root, "CHANGELOG.md");
144
+ let changelog;
145
+ try {
146
+ changelog = readFileSync(changelogPath, "utf8");
147
+ } catch {
148
+ throw new Error(`Cannot read the changelog at ${changelogPath}`);
149
+ }
150
+ return { output: renderUpgradePlan(planUpgrade(changelog, from, to), from, to), code: 0 };
151
+ }
152
+ function isDirectInvocation(invoked, self) {
153
+ if (invoked === undefined)
154
+ return false;
155
+ if (invoked === self || pathToFileURL(invoked).href === pathToFileURL(self).href)
156
+ return true;
157
+ try {
158
+ return realpathSync(invoked) === realpathSync(self);
159
+ } catch {
160
+ return false;
161
+ }
162
+ }
163
+ if (isDirectInvocation(process.argv[1], fileURLToPath(import.meta.url))) {
164
+ try {
165
+ const { output, code } = runUpgradeCli(process.argv.slice(2));
166
+ process.stdout.write(output);
167
+ process.exit(code);
168
+ } catch (error) {
169
+ process.stderr.write(`${error instanceof Error ? error.message : String(error)}
170
+ `);
171
+ process.exit(1);
172
+ }
173
+ }
174
+ export {
175
+ runUpgradeCli,
176
+ isDirectInvocation
177
+ };
package/llms-full.txt CHANGED
@@ -67,7 +67,7 @@ own, recorded as an ADR.
67
67
  | `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, plus the store and managed-resource conformance kits |
68
68
  | `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
69
69
  | `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
70
- | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 11 of the 26 minors since 0.56.2, most recently 0.75.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
70
+ | `stitchkit/agent-runtime` | server | evolving<br>_redefined in 11 of the 27 minors since 0.56.2, most recently 0.75.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
71
71
  | `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
72
72
  | `stitchkit/agent-runtime/coding-tools` | server (Bun or Node) | evolving | bounded host-authorized direct file and shell tools; a root boundary, not an OS sandbox |
73
73
  | `stitchkit/agent-runtime/openrouter` | server | evolving | isolated OpenRouter language-model adapter |
@@ -75,7 +75,7 @@ own, recorded as an ADR.
75
75
  | `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
76
76
  | `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
77
77
  | `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
78
- | `stitchkit/application` | browser + server | evolving<br>_redefined in 6 of the 26 minors since 0.56.2, most recently 0.79.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
78
+ | `stitchkit/application` | browser + server | evolving<br>_redefined in 6 of the 27 minors since 0.56.2, most recently 0.79.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
79
79
  | `stitchkit/application/grammy` | server | evolving | isolated grammY polling and webhook lifecycle adapters |
80
80
  | `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
81
81
  | `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
@@ -350,13 +350,42 @@ records the verdict and reloads nothing.
350
350
 
351
351
  ## Adopting it
352
352
 
353
- 1. Bake the build id into the bundle: `NEXT_PUBLIC_BUILD_ID=$(git rev-parse
354
- --short HEAD)` at build time, or Next's `generateBuildId`. Without it the
355
- watcher has no `own` and never reloads.
353
+ 1. Bake the build id into the bundle, from **one** source. Without it the
354
+ watcher has no `own` and never reloads and it never says so, which is the
355
+ part worth spending a paragraph on.
356
+
357
+ `NEXT_PUBLIC_BUILD_ID=$(git rev-parse --short HEAD)` in the build command is
358
+ the short version and it has a failure mode: on an immutable-release layout
359
+ that variable usually lives in a static environment file, so it is easy for
360
+ two releases to ship the same id. Then `own` equals what the server reports
361
+ on every response, the watcher is correct to stay quiet, and nothing
362
+ anywhere is red. A reload that never happens looks exactly like a reload
363
+ that was not needed.
364
+
365
+ So mint it once and let one value reach all three readers. In Next, that is
366
+ `next.config`:
367
+
368
+ ```ts
369
+ const buildId = process.env.BUILD_ID ?? execSync('git rev-parse --short HEAD').toString().trim()
370
+
371
+ export default {
372
+ generateBuildId: () => buildId, // → .next/BUILD_ID, which the server marker reads
373
+ env: { NEXT_PUBLIC_BUILD_ID: buildId }, // → the bundle, which becomes `own`
374
+ }
375
+ ```
376
+
377
+ The bundle's id and the file the server reports now come from the same
378
+ expression evaluated once, so they cannot drift apart per release. Any
379
+ arrangement with that property will do; the one to avoid is two places that
380
+ each decide the id and are expected to agree.
356
381
  2. Point `read` at the id of the **active** release, not the process's cwd.
357
382
  3. Add `release: (data: { buildId: string | null }) => void` to your
358
383
  `ServerToClientEvents` map where you type your socket, so your own `on`
359
- knows the event the binding emits.
384
+ knows the event the binding emits. This is also what makes a **typed**
385
+ `Server<…>` fit `bindReleaseToSocketServer`: its `emit` is narrowed to the
386
+ names in your map, so until `release` is one of them the call fails to
387
+ typecheck — as a long structural mismatch, which reads like a bug in the
388
+ binding and is this line instead.
360
389
  4. Send `SIGUSR2` from the deploy step that activates a frontend without
361
390
  restarting the backend, and name that step in the project's release steps.
362
391
 
@@ -10900,10 +10929,19 @@ A release that breaks a public API leads its `CHANGELOG.md` entry with a
10900
10929
  **`### ⚠️ Breaking changes`** section (exact heading), each item carrying a
10901
10930
  **before → after** snippet. A version with **no** such section is **purely
10902
10931
  additive** — adopting it changes nothing in your code. (See
10903
- [`AGENTS.md` → Breaking changes](../../AGENTS.md).) `bun scripts/upgrade-plan.ts
10904
- <installed> <target>` prints every breaking section your range crosses, oldest
10905
- first, each with its **Who must act** line — the list this guide's migrations
10906
- expand on.
10932
+ [`AGENTS.md` → Breaking changes](../../AGENTS.md).) So the list is recoverable
10933
+ mechanically, and the package recovers it for you:
10934
+
10935
+ ```bash
10936
+ bunx stitchkit@latest upgrade # or: npx stitchkit@latest upgrade
10937
+ ```
10938
+
10939
+ Run it inside the project that depends on stitchkit. It reads your installed
10940
+ version out of `node_modules`, reads the changelog **shipped inside the
10941
+ package**, and prints every breaking section your range crosses, oldest first,
10942
+ each with its **Who must act** line — the list this guide's migrations expand
10943
+ on. Nothing to clone, nothing to be told: `--from` / `--to` override either end
10944
+ of the range if you want a different one.
10907
10945
 
10908
10946
  So upgrading is: read the `### ⚠️ Breaking changes` of every version *above* your
10909
10947
  current one *up to* your target, and apply each snippet.
@@ -10932,6 +10970,31 @@ makes one thing your job rather than the resolver's:
10932
10970
  The mechanical part is identical either way. Only the *noticing* differs, and an
10933
10971
  exact pin moves it onto you.
10934
10972
 
10973
+ ## Released migration: 0.82.0
10974
+
10975
+ One thing, and only if you mount MCP or agent tools from a `createTrackingContract` contract.
10976
+
10977
+ ```bash
10978
+ rg -n "createTrackingContract"
10979
+ ```
10980
+
10981
+ The event ingest (`track`, `POST /events`) is now `expose: ['HTTP']`, which is
10982
+ what its sibling `bootstrap` always declared. Before, it declared nothing — and
10983
+ an endpoint with no `expose` is a tool on MCP and AGENT by default, so the
10984
+ mounted surface carried a `track_<prefix>` tool. Since this contract is built
10985
+ inside the framework, an application that had made every tool opt-in for the
10986
+ endpoints *it* authors could not have opted this one out.
10987
+
10988
+ **If you pin a tool-surface digest or count**, it moves by one; re-record it.
10989
+ **If you assert on tool names**, `track_<prefix>` is gone. **If nothing in your
10990
+ project mounts tools from this contract**, there is nothing to do — the HTTP
10991
+ routes, the client and the schemas are unchanged.
10992
+
10993
+ Un-declaring the tool was never the intent, so there is no option to keep it. If
10994
+ an agent in your system genuinely needs to record something, give it your own
10995
+ endpoint with your own schema: the browser ingest trusts a client-minted event
10996
+ id and a visit lease, and an agent holds neither.
10997
+
10935
10998
  ## Released migration: 0.81.0
10936
10999
 
10937
11000
  **Path literals own string params.**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.81.0",
3
+ "version": "0.82.0",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",
@@ -34,6 +34,9 @@
34
34
  "bun": ">=1.2.0",
35
35
  "node": ">=22"
36
36
  },
37
+ "bin": {
38
+ "stitchkit": "./dist/upgrade-cli.js"
39
+ },
37
40
  "main": "./dist/index.js",
38
41
  "types": "./dist/index.d.ts",
39
42
  "files": [
@@ -42,6 +45,7 @@
42
45
  "llms.txt",
43
46
  "llms-full.txt",
44
47
  "README.md",
48
+ "CHANGELOG.md",
45
49
  "LICENSE"
46
50
  ],
47
51
  "exports": {
@@ -186,7 +190,7 @@
186
190
  "check": "bun x tsc --noEmit",
187
191
  "build:js": "bun run build:entrypoints && bun scripts/preserve-webpack-ignore.mjs",
188
192
  "build:types": "bun x tsc -p tsconfig.build.json --emitDeclarationOnly && bun scripts/rewrite-declaration-specifiers.mjs",
189
- "build": "rm -rf dist && bun run build:js && bun run build:types && bun scripts/check-browser-clean.mjs && bun scripts/check-browser-executes.mjs && bun scripts/check-env-live.mjs && bun scripts/check-public-types.mjs && bun scripts/check-declarations-strict.mjs",
193
+ "build": "rm -rf dist && cp ../../CHANGELOG.md ./CHANGELOG.md && bun run build:js && chmod +x dist/upgrade-cli.js && bun run build:types && bun scripts/check-browser-clean.mjs && bun scripts/check-browser-executes.mjs && bun scripts/check-env-live.mjs && bun scripts/check-public-types.mjs && bun scripts/check-declarations-strict.mjs",
190
194
  "dev": "bun run build:entrypoints -- --watch",
191
195
  "prepack": "cp ../../README.md ./README.md && bun ../../scripts/gen-llms.ts && bun run build",
192
196
  "test": "bun test",