create-ailk 0.25.1 → 0.26.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.
Files changed (60) hide show
  1. package/README.md +12 -3
  2. package/dist/lib/fetch-template-snapshot.d.ts +2 -1
  3. package/dist/lib/fetch-template-snapshot.js +6 -5
  4. package/dist/lib/module-license-gate.js +2 -3
  5. package/dist/lib/module-tier.d.ts +2 -8
  6. package/dist/lib/module-tier.js +13 -12
  7. package/dist/module-architecture.js +12 -2
  8. package/dist/starter-grid.d.ts +6 -6
  9. package/dist/starter-grid.js +12 -5
  10. package/dist/surfaces.js +29 -0
  11. package/dist/template-record.d.ts +11 -3
  12. package/dist/template-record.js +20 -7
  13. package/dist/topology.js +23 -4
  14. package/dist/update.d.ts +1 -1
  15. package/dist/update.js +188 -18
  16. package/package.json +3 -3
  17. package/release-scope.json +1 -1
  18. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +8 -2
  19. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +2 -2
  20. package/site-templates/marketing-site/ailk-template.json +7 -1
  21. package/site-templates/marketing-site/provenance.json +7 -1
  22. package/site-templates/saas-product/ailk-template.json +2 -1
  23. package/site-templates/saas-product/provenance.json +1 -0
  24. package/templates/.env.example +15 -0
  25. package/templates/apps/api/.env.example +21 -0
  26. package/templates/apps/api/CLAUDE.md +18 -8
  27. package/templates/apps/api/src/lib/__mocks__/prisma-client.js +16 -0
  28. package/templates/apps/mcp/CLAUDE.md +27 -10
  29. package/templates/apps/web/CLAUDE.md +22 -1
  30. package/templates/apps/web/app/[locale]/(auth)/__tests__/auth-pages.test.tsx +43 -4
  31. package/templates/apps/web/app/[locale]/(auth)/legal-footnote.tsx +35 -0
  32. package/templates/apps/web/app/[locale]/(auth)/sign-in/__tests__/page.test.tsx +117 -8
  33. package/templates/apps/web/app/[locale]/(auth)/sign-in/page.tsx +14 -37
  34. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +6 -6
  35. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-text.ts +20 -35
  36. package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/page.test.tsx +108 -0
  37. package/templates/apps/web/app/[locale]/(auth)/sign-up/__tests__/sign-up-card.test.tsx +111 -5
  38. package/templates/apps/web/app/[locale]/(auth)/sign-up/page.tsx +78 -16
  39. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +132 -12
  40. package/templates/apps/web/app/[locale]/(authed)/__tests__/gate.test.tsx +53 -1
  41. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/billing.test.tsx +38 -0
  42. package/templates/apps/web/app/[locale]/(authed)/account/billing/page.tsx +4 -2
  43. package/templates/apps/web/app/[locale]/(authed)/sign-in-gate.tsx +47 -24
  44. package/templates/apps/web/app/[locale]/(authed)/signed-out-gate.generated.tsx +23 -4
  45. package/templates/apps/web/app/[locale]/layout.tsx +33 -13
  46. package/templates/apps/web/app/[locale]/meet/confirmed/BookingHandOff.tsx +128 -0
  47. package/templates/apps/web/app/[locale]/meet/confirmed/__tests__/BookingHandOff.test.tsx +126 -0
  48. package/templates/apps/web/auth.ts +8 -4
  49. package/templates/apps/web/i18n/request.ts +49 -1
  50. package/templates/apps/web/lib/__tests__/account-config.test.ts +7 -0
  51. package/templates/apps/web/lib/__tests__/auth-email.test.ts +276 -0
  52. package/templates/apps/web/lib/auth-email.ts +102 -20
  53. package/templates/apps/web/lib/hub-session/__tests__/sign-in-gate.test.tsx +72 -23
  54. package/templates/apps/web/lib/hub-session/sign-in-gate.tsx +94 -70
  55. package/templates/apps/web/lib/prisma-for-auth.ts +5 -2
  56. package/templates/apps/web/lib/sign-up-agreements.ts +291 -28
  57. package/templates/apps/web/lib/site-contributions.ts +18 -0
  58. package/templates/apps/web/messages/en.json +186 -20
  59. package/templates/apps/web/messages/site/en.json +1 -0
  60. package/versions.json +48 -48
package/dist/update.js CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * `create-ailk update [<target-dir>]` — bring template fixes into a site's
3
- * already-copied `apps/web`, `apps/api` and `apps/mcp` code (#8522).
3
+ * already-copied `apps/web`, `apps/api` and `apps/mcp` code (#8522), and
4
+ * the root `tools/` files their jest configs require (#9654).
4
5
  *
5
6
  * Reads `.ailk-template-files.json` (`template-record.ts`) to tell an
6
7
  * untouched copy from an owner's edit. An untouched file whose template
@@ -44,7 +45,7 @@ import { ParseError } from "./parse-args.js";
44
45
  import { SITE_ROSTER_PATH } from "./site-roster.js";
45
46
  import { SPOKE_CHECKOUT_TEMPLATE_PATHS } from "./spoke-checkout.js";
46
47
  import { excludedRoutePaths } from "./surfaces.js";
47
- import { DATABASE_BOOT_FILES, TEMPLATE_RECORD_PATH, isRecordable, parseTemplateRecord, serializeTemplateRecord, sha256Hex, } from "./template-record.js";
48
+ import { DATABASE_BOOT_FILES, ROOT_TOOL_FILES, TEMPLATE_RECORD_PATH, isRecordable, parseTemplateRecord, serializeTemplateRecord, sha256Hex, } from "./template-record.js";
48
49
  import { MCP_SEAM_PATH, MCP_SERVER_HUB, TopologyError, isSpokeExcluded, readSpokeShape, spokeExclusions, } from "./topology.js";
49
50
  /** A refusal the CLI exits 2 on, thrown before anything is written. */
50
51
  export class UpdateRefusedError extends Error {
@@ -77,12 +78,12 @@ function versionOlder(a, b) {
77
78
  const DATABASE_PACKAGE_GUARD = "database/ is still a package here — convert it to the thin consumer first";
78
79
  /**
79
80
  * Every file under `templatesDir`'s three app trees, plus each of the
80
- * `DATABASE_BOOT_FILES` present there (#9569 DD8), raw bytes keyed by
81
- * destination path (D3).
81
+ * `DATABASE_BOOT_FILES` (#9569 DD8) and `ROOT_TOOL_FILES` (#9654) present
82
+ * there, raw bytes keyed by destination path (D3).
82
83
  */
83
84
  function walkLocalTemplates(templatesDir) {
84
85
  const out = new Map();
85
- for (const boot of DATABASE_BOOT_FILES) {
86
+ for (const boot of [...DATABASE_BOOT_FILES, ...ROOT_TOOL_FILES]) {
86
87
  const abs = join(templatesDir, ...boot.split("/"));
87
88
  if (existsSync(abs))
88
89
  out.set(boot, readFileSync(abs));
@@ -236,6 +237,155 @@ const BUNDLED_MCP_TOOLS = new Set([
236
237
  "proposals",
237
238
  "submit_flow_step",
238
239
  ].map((name) => `${MCP_SRC}/tools/${name}.ts`));
240
+ /**
241
+ * The sha256 of every `apps/mcp/src` file a create-ailk template shipped
242
+ * before #9460 — the published tarballs 0.1.0 to 0.24.0, each one a blob in
243
+ * git history before the #9460 merge (#9654). A file under `apps/mcp/src`
244
+ * whose bytes hash into this set is a template copy, never a site edit, so
245
+ * the move removes it even where the record names another copy or none.
246
+ * Frozen as of #9460; never extend: no template ships a server file under
247
+ * `apps/mcp/src` any more.
248
+ */
249
+ const PRIOR_MCP_TEMPLATE_HASHES = new Set([
250
+ "02789f25139dd78952a77c2d6f7a7057d73d18cdf45845913ad4d35964ad0e16",
251
+ "035a4f5eda5d7a00046f6f3446100dd1e34f85f894f000b04a931e094799c03e",
252
+ "06fe9518a954091284571ec253abea979c738ee3556b73f8993c759607eb3c4b",
253
+ "0702b6d5012dab15d4030be01923ed8da8b0a237028e6c88053c8fdcee63e155",
254
+ "09fedce92b189911964733f6a2535d0d3b0f0104ac18a1ff76abf5fd96261922",
255
+ "0d42d4c17a35867245fc86c48efa5b6dd2c0f89a03c564f68b7661cde98dddf4",
256
+ "0dc43a72c6d2fd1a5f36f037d9b01abcb9a11d1653a04fb033b5b6b223fcfbd8",
257
+ "0fb7cd858e7cd3c1f8757824184519f2494f9c12ec49290d7b97ee7656a7f405",
258
+ "115ac6b648a45e5e1b22193097e92a8ee4da844be5853ca202cb624651ef4c56",
259
+ "16cefcfebbcadb2505b8d8253c853b2d2098209fa4bac84fe39dc7f6b38c02c3",
260
+ "16eaefbceb5edcc3a8039d8ef55776d1a1e317521ecca17386673b87852c4099",
261
+ "17c6e5115ba947d53dcbb632990f6b66d570ea1657d022c51bf62e09c0b58194",
262
+ "17cc5d3eed8849a33f2eebd0d12ffa72c9ffebcccf5a4818aaf8f94fca05aa0f",
263
+ "18185d6f4e4777a7b0856a971c509ce0e094f8ccb8206eb6a1e78d38030c3763",
264
+ "1c5f0f0cdd304b3aff3934e6f0878adb18fc6555be030cb81e3d4a0764d65c43",
265
+ "1d328a242845e31426f28c33d58bf5be7be76f526e066858f77916ef2b28af41",
266
+ "1d5810272869e6a2f66941f862ec7c39a54af82e13ff33d4bff4a19e0d52f1a4",
267
+ "1ddc5b3a79b4a6453a0ac1f6e60ef3463b58221fb5ee313d52c57e9928ea8cdc",
268
+ "2131309a986b964e99a9a9589a0c236d5d830b14d66653e2008289eae2e2cf94",
269
+ "21cd9825d4b7ae057c10b5ca7d01c83387be20be66bf8c38814e7db592a7517e",
270
+ "21d28634fc19f15389ec99a3e6005490faff1c1d364bc7bd3a1f387edab09434",
271
+ "22c245c92b973d86f425b934bd65229c0017801e738a80db19aa0a174d7142c0",
272
+ "23d1318bb8b9aaf489c62ce667d8ba56110f48c8b7c401713a3ac4849c0978bb",
273
+ "2d901fec3ca30024d39274b9dc27156b4d366c86a5c3fb2897a8ca9b97b6d2d9",
274
+ "2dfe47a0e1d6904b5b183fc4aeb067e492127f3ee39058677895a9cc5e270b9e",
275
+ "2eeb3c272e60e1dbf87f868fecac8526f265a4f5327afbeb5ee4c3ecebb848db",
276
+ "3110e5b85f764e39e0e9c7164290cd62a1be51b90e49ed38611a004143a6b130",
277
+ "31afce85a1982d17c9e1d8069e54913b0bdb295010104541ea04b061084b314e",
278
+ "36bc7bea57167851ae81b4baa57daee2fad4f2db9b96b730a72e2ca0c69d75b7",
279
+ "38fab15821f26449c149c12851ff7a857db89d55c4722f797b15274e31a4a4bf",
280
+ "39ef3340a53926a8e8dd77f78924d1975fa5284ab7ae2b99bb8602b20bb7e7a8",
281
+ "3bb46c8ea91349d7551a66c13462f99910ead5b813f0aa51a1b7ab1671f3576a",
282
+ "3cc84368f3eaaf7e875a145fabf7ac644ee4ef1fa785cf548c9639a69985661e",
283
+ "40a538c6a84a95690dc810a5249b74e67c234fc5cf4f6cc6476d7961f67ecbdd",
284
+ "426e33baaeed2bb51a720fce286dba4da00d94418d3554c628c87b3d14e03116",
285
+ "428c570d20f07c9069578c8729c2dcf7eb6eab3b956f8c20eccbabae2f518272",
286
+ "436fc6ed239cf5b4612ee4dbf102debaf5384a3872a30a834d1042e521e318f3",
287
+ "443a31b8af758901d5685134d5046cf4880b534268a9e9cc2ea85185cb5f54ee",
288
+ "443c60fd527c7703b5f3af9ad9b6efd49c359fdde717228254961ea9e7815117",
289
+ "4b28c6e6a6799eb9548720a9f16bafd93a15baa04cacbec950c23e2e5be0f1aa",
290
+ "4b8e654da8220df69a4c4457c1527c2bea9f5aed73d67458c4ce2a14f1b87e8f",
291
+ "4d01e1fcdda1f941789894a6a54d2940f47096c39f37609a5e908c0aa84789db",
292
+ "52583caf9f8946c36133949278449dceeb0ac8f74360534ca91b96938d80446c",
293
+ "5364243db73f8687c1c0ec9cc2516e95efb43a756e295ae8b6469fd55f940649",
294
+ "578dd3161d2dc283011a069a4d426037ba665f0f787a317779115b570a74269f",
295
+ "59882d160c82e5be765102c13dd3c05fbd7e48673408f89c194c8e021ae29797",
296
+ "5ae7a51f8d439d331c5a02229b4109daa5299de6c12d821b78bbc14feda8413f",
297
+ "5ed07e4eed1e6cc7d19185669a14b8df974ea640d283676f33f4db8566fb8cdc",
298
+ "5f60e2bd4fb529c02e195b1aef3bf2ebfa55db05212ae9888e265f3f2f9fade8",
299
+ "6022594cc246c8f3453935781607fa3750b7c8e5b29fab9c841f13364cc9d08a",
300
+ "60b1ed4e37ecc2e045726f2fd93ef90cc9dea33718532a90e365d6d64d803047",
301
+ "659248c6e0c85075142c6a86395fb2b6b2bdf36895dfb2aeb5ab5e044528f11b",
302
+ "66d95e135f59ff1ccdcf3480bd22eac81a559ef0a4439d9a34ce6c25a6885ada",
303
+ "68976a1295acc8ed025a8a3ab30d78492c7e40e4320ac5acecc53f9e7f2aa04f",
304
+ "69cebf99ae494a0fd06b4b9408587f230def919dceb4fbcf3ef950f243bbf486",
305
+ "6b919aa697e9785f175201193e498b17b6c64ffe08c34a9a3ff728d660aa68cf",
306
+ "6da43872f0bee03f3028183795d41ea257c6102928175efe162ffb0fd37849c8",
307
+ "6e86925727a045268249248e9dffd7e8869f2604af43fda27c8acf75982988de",
308
+ "6e8f01a9c1f20f580e2dec7fe8a4c811ffd88621612aac03604aedeaeb9b7c90",
309
+ "715ed89d03445103ca05c0686c8f6fc7cef0c61b18767c285777bb65122fa81a",
310
+ "74a2c97adba8e0390a281fddbc5dce3ec7f9e97577b2fcb9abcf0bdcee01b63c",
311
+ "75abc78c74f28259d817cf159b3969c548395131de6255978b9df4d275aa1544",
312
+ "77d1aacb454904eded6d9a043de7f95922d53bae9693df48b106a74de7405e98",
313
+ "78826164442493682a624ba492f6e39b3479208d93a7aa2fdfca70005333255f",
314
+ "792e6b2cf89272fe9860d585a23687e14881533f3644dd5b1e8e30ea89e6f4a2",
315
+ "7afb50314471ab4e8f31099db49e29a96e56efd83ba960a42732e177654c874d",
316
+ "7b7d5053aaa6358eb66042cfca9e2fe6280d354e23a303c129f08a413bf94c4c",
317
+ "7ed1787f0d71fceb92fb687a8d4e6a5ffc49896ee586303b4311035ba60ba2d2",
318
+ "81dd15a6b1479896458106b946edc0ac76f6c0acf4d525307b092ed11d2f4596",
319
+ "83eaa71438882dde8edf0d7978644a93c802a08a50804f5b2c62d01eaeb4edc9",
320
+ "8441a52d587584cccd6f1fbe3ba5204c9c49a9c6d452d9f844d34dcc0448ad76",
321
+ "847d648346ea4bd0d2f349399527f5a907e05367c6764696b724f3382090089e",
322
+ "85df33660e2291ffe1e77b7103e4705c31b93dd7a294872b6c0fca52a3bd3079",
323
+ "8a9eea9c23a94ff9df729a4aa423e62b02c0632e398bf01524f1caeed61944b9",
324
+ "8e7d112d48976c7ae66203b6c45e8c20708f60a4486411a30af7857e214b8d27",
325
+ "91945c6e325758d09d7f70586f63bda0758f187247e4a8cfab05d9ed45c1e25b",
326
+ "960723d00c54a88af4142cde66b77c5e70cd3b39f59dce462fdf8d153846a9cf",
327
+ "9623a2f691777ea1fea280782240837b24b8202082bf98dbaa2ea48c670774b8",
328
+ "9ebfb38044ad35af8156803be6bb975a7c6b1624fa511037d56beb7b9e0d3579",
329
+ "9ede34ab75c67dc0fe134d7bf64d040e250cec85111ef9fb11be745af5e90277",
330
+ "a03fd2d78e6e4982209e193ad27f0732035c2c2eb5edfcdd8e3093a42e5334a4",
331
+ "a14cf80e8cfc09105cd856bc5723a4b1a231f6ad8f08e5a443360c480a6b483b",
332
+ "a17f89416b31fb883dc9a5c389b021332b2b40e5052fe344e1aec96872475d5f",
333
+ "a20b034a648c2b9daaf3ca817a2229ce7e0136b0fc7487c7fed5fe0cdf1e9e7b",
334
+ "a4495346b79124d1b17f6082f0c1f1f99d3845f4f98cfde83543f2eadf600666",
335
+ "a6d6d6be76cff255bbf77d529dee8775817dae75725bf24964febbbc7cf8d508",
336
+ "a7421e1949c4f3671cfb0d758fecb3b87aab3df03d0feaed0259443ebb2a4353",
337
+ "ac7cd310467561330d3cc2914fd29c9a16ecd27d7f8413dd1120e28ce9ee3ad7",
338
+ "add424a2fbb1027d67270dca6e5e1e0e384dd37a729ba5c091022bc30b3d85c1",
339
+ "af5c4f3113285a137019f7bcbb905c04f0de3b6917d709a0fc8df3f0f39beda9",
340
+ "b0c8bc973e3b9f7fdaf95cb02ca111cb685db2ac98559c859071b4f26f600bf4",
341
+ "b455cd835c1769af463b4ebd91fa6716effbad6f8210df12d47637977b34c6e2",
342
+ "b7140dd15cac5f55309991e7ce1b438a9514a4b289c9ef807f75ed576246fc26",
343
+ "b7198546881c491e233018dadd3fbdb6378694e8a15b762b56c17a1c61d10c91",
344
+ "b7d1e33111a13249d6bc4d0a9334074430bc94f1a0c79d94f5c6f5907b76ae30",
345
+ "b9bd1f23c0e84f91cf7d46db46cab4cb1232574b0f6c9d6f5926fa0bab110570",
346
+ "ba7e5b4db4fd020b0aadace2083aacfb9ffafa63465d2026e4a79804eb8cd493",
347
+ "bb321afe40c6d7495676f9157bc80bbab9d5241fa84340133770b1ba3006d166",
348
+ "bced6c526cd757d87b4d725e247497b6e1286192b758421786c645719c3b4dfd",
349
+ "bf398a05bdbdb85a8034ecfd9f4d865587b7d25352a06f3c9f3ca1c0deed6787",
350
+ "bf4a2222b34a4fdabccd696341f6168104d3d7d74df036187347650ecf350d32",
351
+ "c1513e2317857959145d66659c3b147cc885ca46f4c239ed85f52a833d058971",
352
+ "c371bdde5f7ace2448c46a0e009d799bdb90d3351d42131234181d1efd2b486f",
353
+ "c57ccbaddda8c7d538d103c2b2d96889a815cbcc4469f5f6780b0883d99b4006",
354
+ "c5a448c17e6bba3379ee938b17309c02eb851cbf5fb54dcd6ec490503b70b121",
355
+ "c5d35895a261227c6cf537daeac4b24a98d91ea65e8322d7ca5c47d05c83570c",
356
+ "c64f4b1a9dba4d1fa76f8aea664c81e35961dca2e3f44f63a6783f5164951560",
357
+ "c676077b914411e737376f9604087aab67ba2f300aedb99e76a5839f1798827b",
358
+ "ca076f76130bd3e35488bfe23d7f55f1829164a4e09035459f3af2aee1d91ace",
359
+ "ca842e6ec1748a84fa30adde6f93a84e7b98b6211afbb3e7907e9b6b9b65fed2",
360
+ "ce7ada5214b23c595422399fbd10a8e95bb1b9b30a345c2117bf1954cc2163a6",
361
+ "d2b460eee83c40d26db73bded9ad30666fe970eb991c925727eed5e45d1a5cb4",
362
+ "d4cba3c71603c40bf0a8b6e1643a157e2028d3743b238fb932e059381a17deb9",
363
+ "d5fcd1015582870231c52f490b8daac7a89dbaa2d387f285e118fff98bf7ada2",
364
+ "d96ff716399aca50bd6f5c43bb37721e2d4cbb43793a453119f6e15e6eeca34b",
365
+ "da75ba145c8142544d6211b4eaba8485549511766e2c0d248ac2308b502cf986",
366
+ "dfbcfe0f30c6079153b5ab51096ef106716a713a7d6169b2ec00840423640560",
367
+ "e1945e3ff2b6d806fc459c90d0253ca71f2adac5bedc6c578ca21506f9dc094a",
368
+ "e43789a019a1c487b42cc785175f0b08b36a7e4f5ae72e40a6c1835f9d02b2d6",
369
+ "e482750dfb358f78b1142a8707cb007cc7a5e5c863614b05d0289f4843d10b6f",
370
+ "e4948f0dadc17dcae583e2d8732a1e95c8268d4e5bf68bc38e97e05a251b9fcb",
371
+ "e4aff38f30ca76f6da6ec4bf98c71a51d8fc41877ddcf791aeb61fcf34bb44ea",
372
+ "e59294cc6a47cabd5df6ab635dd3748f42610e1691b248d24b8962fc3661df43",
373
+ "e7c7711f3512fdd51bdd175098ffe97c1c4483b4369b2627bbf00eafd65c00af",
374
+ "e7feca03ec0681bee9bba97b9ee5024215899f95aad72cca08c0902b969bee5c",
375
+ "e97b05627ca65f8656167b5ffefb8236cce711173a98430d7ac7f063dc598223",
376
+ "e9ba0a0e497e5d12662a473b81e0cb09f81a094c2763266769e08f5556718f15",
377
+ "e9c9f8d30528a30084e58d41d93d7e4d58d07a4bd8f1e2c1d132c2525669e8a5",
378
+ "ec28dcc626ae59a3c5208f342ba8090c3e8640c6e4930a4b518dfdaa4ccdb773",
379
+ "ef7f59acd7f5de7287681786bb96f1907ac72a9cca7d49732c33320359725ef9",
380
+ "f37c56da121f8cba9c0a55bc69ef701e3d81c63a15f1a9ff354efc4fed58ff39",
381
+ "f562f262c2b26f3e613a50aea737bf7b021d8c4ea9e769ce6b4728675fd68b9e",
382
+ "f6f9e117ab5d1e68b7b99baa30ab822c0126467b8941195a187bf3af20f7faa5",
383
+ "f8d26912ffbc7c1a7b73a0916111aa5c969d98a60b211ff7ca75d8e7828d86c1",
384
+ "f984b95c8a9759cdd988722b7de5be6654c69e0ceb12abc4847115f964087fc0",
385
+ "fc43fb068d3816a65a00144f32ebd3fb077f474090de8c1c99dd77f542c35c43",
386
+ "fdbdd75dc3b798c548ddf28a3c730fa61f64dc1b48d30ff65e190bbc7bb097ba",
387
+ "ffe3db23d21ce93dbb8b832a04318cf23aaa8b1d6db11185ebcf30a634e86e6d",
388
+ ]);
239
389
  /** The old hub seam's two relative imports; each is now `MCP_SERVER_HUB` (DD7). */
240
390
  const OLD_HUB_SEAM_IMPORTS = ['"./public-workspace.js"', '"./license-key-store.js"'];
241
391
  /** Every file under `root`, as `/`-separated paths relative to `targetDir`. */
@@ -280,18 +430,26 @@ function resolveMoveVersions(app, pkg, packages, versions) {
280
430
  * what it lists for review: a `bundled` file (bundle-delivered, never
281
431
  * recorded) is removed even at a boot-set path (#9566: the #9564→#9565
282
432
  * window's in-app module objects sit where the template's re-exports land);
283
- * any other boot-set file is left to the record; the rest is reviewed with
284
- * `reason`, never deleted.
433
+ * any other boot-set file is left to the record; a file whose bytes hash
434
+ * into `templateCopies` is a prior template copy and is removed (#9654); the
435
+ * rest is reviewed with `reason`, never deleted.
285
436
  */
286
- function classifyUnrecorded(targetDir, src, bootSet, recorded, bundled, reason) {
437
+ function classifyUnrecorded(targetDir, src, bootSet, recorded, bundled, reason, templateCopies = new Set()) {
287
438
  const deletes = [];
288
439
  const review = [];
289
440
  for (const path of filesUnder(targetDir, src)) {
290
441
  if (recorded.has(path))
291
442
  continue;
292
- if (bundled.has(path))
443
+ if (bundled.has(path)) {
444
+ deletes.push(path);
445
+ continue;
446
+ }
447
+ if (bootSet.has(path))
448
+ continue;
449
+ const hash = sha256Hex(readFileSync(join(targetDir, ...path.split("/"))));
450
+ if (templateCopies.has(hash))
293
451
  deletes.push(path);
294
- else if (!bootSet.has(path))
452
+ else
295
453
  review.push({ path, reason });
296
454
  }
297
455
  return { deletes, review };
@@ -335,7 +493,7 @@ function planMcpMove(targetDir, modules, hub, recorded, versions) {
335
493
  if (!existsSync(join(targetDir, ...MCP_SRC.split("/")))) {
336
494
  throw new UpdateRefusedError(`${MCP_PACKAGE_JSON} is present but ${MCP_SRC}/ is not, so apps/mcp cannot be moved to ${MCP_SERVER_PACKAGE}`);
337
495
  }
338
- const unrecorded = classifyUnrecorded(targetDir, MCP_SRC, MCP_BOOT_SET, recorded, BUNDLED_MCP_TOOLS, `not in the boot set apps/mcp/src is since ${MCP_SERVER_PACKAGE} (#9460); move it into a package or delete it`);
496
+ const unrecorded = classifyUnrecorded(targetDir, MCP_SRC, MCP_BOOT_SET, recorded, BUNDLED_MCP_TOOLS, `not in the boot set apps/mcp/src is since ${MCP_SERVER_PACKAGE} (#9460); move it into a package or delete it`, PRIOR_MCP_TEMPLATE_HASHES);
339
497
  move.deletes = unrecorded.deletes;
340
498
  move.review = unrecorded.review;
341
499
  return move;
@@ -807,6 +965,15 @@ export async function updateSite(opts) {
807
965
  }
808
966
  // Edited.
809
967
  if (newBytes === undefined) {
968
+ // #9654 — the bytes are a pre-#9460 template copy, just not the one
969
+ // the row names: not an edit, so it is retired as an untouched copy
970
+ // is, on the move's run or any later one.
971
+ if (path.startsWith(`${MCP_SRC}/`) && PRIOR_MCP_TEMPLATE_HASHES.has(diskHash)) {
972
+ deletes.push(path);
973
+ delete nextFiles[path];
974
+ removed.push(path);
975
+ continue;
976
+ }
810
977
  review.push({ path, reason: `edited here, removed from the template in ${version}` });
811
978
  continue;
812
979
  }
@@ -898,11 +1065,13 @@ export async function updateSite(opts) {
898
1065
  if (sha256Hex(diskBytes) !== newHash) {
899
1066
  skipped.push({ path, reason: "exists, not in the record" });
900
1067
  }
901
- else if (DATABASE_BOOT_FILES.includes(path)) {
1068
+ else if (DATABASE_BOOT_FILES.includes(path) || ROOT_TOOL_FILES.includes(path)) {
902
1069
  // Adoption (#9569 DD8): a thin site converted by hand, or scaffolded
903
1070
  // before the boot files were recorded, gains the row for a copy that
904
- // already equals the running template's. App paths keep today's
905
- // behaviour: an unrecorded, identical copy stays unrecorded.
1071
+ // already equals the running template's — and so (#9654) does a root
1072
+ // tools/ file a site scaffolded before tools/ was recorded, or copied
1073
+ // by hand. App paths keep today's behaviour: an unrecorded, identical
1074
+ // copy stays unrecorded.
906
1075
  nextFiles[path] = { sha256: newHash, version };
907
1076
  current.push(path);
908
1077
  }
@@ -1106,10 +1275,11 @@ Options:
1106
1275
 
1107
1276
  Reads .ailk-template-files.json (written beside .ailk-site-manifest.json by
1108
1277
  every template scaffold) to tell an untouched copy from an edit. An
1109
- untouched apps/web, apps/api or apps/mcp file whose template copy changed is
1110
- overwritten. An edited file whose template copy also changed is three-way
1111
- merged with \`git\` (git must be on PATH); a conflict leaves the file alone
1112
- and is listed under "Needs review". Every template test that imports a file
1278
+ untouched apps/web, apps/api or apps/mcp file, or a root tools/jest file the
1279
+ app jest configs require, whose template copy changed is overwritten. An
1280
+ edited file whose template copy also changed is three-way merged with
1281
+ \`git\` (git must be on PATH); a conflict leaves the file alone and is
1282
+ listed under "Needs review". Every template test that imports a file
1113
1283
  left under "Needs review" or "Skipped" (by a relative import) is held at the
1114
1284
  site's version with it, its record entry is not advanced, and it is listed
1115
1285
  under "Held back with <file>" — reconcile the file, then run update again to
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ailk",
3
- "version": "0.25.1",
3
+ "version": "0.26.0",
4
4
  "description": "Scaffolder for AI Launch Kit — `pnpm create ailk@latest <dir>` produces a starter repo consuming @working-theory/* from npm (per ADR 0007).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -28,8 +28,8 @@
28
28
  "tar": "^7.5.21"
29
29
  },
30
30
  "devDependencies": {
31
- "@working-theory/schema": "0.46.0",
32
- "@working-theory/templates": "0.46.0"
31
+ "@working-theory/schema": "0.48.0",
32
+ "@working-theory/templates": "0.48.0"
33
33
  },
34
34
  "repository": {
35
35
  "type": "git",
@@ -17,7 +17,7 @@
17
17
  "docs-blog": "released",
18
18
  "scheduling": "released",
19
19
  "signal-collectors": "released",
20
- "workspace": "gated",
20
+ "workspace": "released",
21
21
  "multi-tenant": "gated",
22
22
  "commerce": "gated"
23
23
  },
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "template": "marketing-site",
3
- "requires": ["marketing", "docs-blog", "scheduling", "signal-collectors"],
3
+ "requires": [
4
+ "marketing",
5
+ "docs-blog",
6
+ "scheduling",
7
+ "signal-collectors",
8
+ "workspace"
9
+ ],
4
10
  "categories": [
5
11
  "accounting",
6
12
  "legal",
@@ -17,7 +23,7 @@
17
23
  ],
18
24
  "shape": "organization-product",
19
25
  "design": "standard",
20
- "generatedHash": "f4a8f11aba42a071794585c4563015bd1c99cbe6917a36ee0629d37c6f5f476c",
26
+ "generatedHash": "d58492d416674a21b1358858d285b7050d14618f5e2bed2f0ae20bdbc5267d5a",
21
27
  "layout": {
22
28
  "nav": "full",
23
29
  "footer": "columns",
@@ -6,7 +6,7 @@
6
6
  | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
7
7
  | Template | `marketing-site` |
8
8
  | Level | Pro |
9
- | `requires` | `[marketing, docs-blog, scheduling, signal-collectors]` (spec 5462 D12/D15, settled by Sam 2026-09-14) |
9
+ | `requires` | `[marketing, docs-blog, scheduling, signal-collectors, workspace]` (spec 5462 D12/D15, settled by Sam 2026-09-14; `workspace` since #9696) |
10
10
  | Delivery | licence-checked module bundle (#5441); never in the Free tarball. Composed base → modules → template (#5618): `/pricing`, `/privacy`, `/terms` arrive from the `_base` layer it `extends`, every module sample from the docs-blog and signal-collectors bundles its `requires` names, and this tree ships one copy override (blog post 05 onto `launching-ailk.mdx`) |
11
11
  | Sample content posture | every sample report, post and flow in the demo is **public** |
12
12
 
@@ -513,7 +513,7 @@ Everything in the Free manifest § 2a applies. Pro adds:
513
513
 
514
514
  ```
515
515
  marketing-site:
516
- requires: [marketing, docs-blog, scheduling, signal-collectors]
516
+ requires: [marketing, docs-blog, scheduling, signal-collectors, workspace]
517
517
  categories: [accounting, legal, consulting, hvac, roofing, plumbing, electrical, landscaping, auto-repair, real-estate, technology, medical-specialty]
518
518
  shape: organization-product
519
519
  design: standard
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "name": "marketing-site",
3
- "requires": ["marketing", "docs-blog", "scheduling", "signal-collectors"],
3
+ "requires": [
4
+ "marketing",
5
+ "docs-blog",
6
+ "scheduling",
7
+ "signal-collectors",
8
+ "workspace"
9
+ ],
4
10
  "extends": "_base",
5
11
  "$comment": "Pro template (#5464). installMap: bundle-relative source -> scaffold-relative destination; the stager reads only installMap/postInstall/patches. After #5618 D6 this tree holds compositions and copy overrides only: the base pages (/pricing, /privacy, /terms) arrive from the _base layer it extends, and every module sample (the blog listing and posts 01-04, the three docs pages, the projects, thank-you, flow, waitlist and report samples) ships in the docs-blog and signal-collectors bundles its requires names. Its one content file, post 05, is deliberately mapped ONTO apps/web/content/en/blog/launching-ailk.mdx (D6.2a): the template lands last and can overwrite but never remove the module's own seed post, so this is Rule B's copy-override clause with a real instance (a template-owned replacement of a seed row is a docs-blog gap on #5467, spec 5618 s7 finding 9). Unmapped content/en/** would land at the scaffold root's content/, exactly as marketing-site-free's does; today nothing is unmapped.",
6
12
  "installMap": {
@@ -1,4 +1,10 @@
1
1
  {
2
2
  "template": "marketing-site",
3
- "requires": ["marketing", "docs-blog", "scheduling", "signal-collectors"]
3
+ "requires": [
4
+ "marketing",
5
+ "docs-blog",
6
+ "scheduling",
7
+ "signal-collectors",
8
+ "workspace"
9
+ ]
4
10
  }
@@ -5,6 +5,7 @@
5
5
  "docs-blog",
6
6
  "scheduling",
7
7
  "signal-collectors",
8
+ "workspace",
8
9
  "multi-tenant"
9
10
  ],
10
11
  "extends": "_base",
@@ -12,5 +13,5 @@
12
13
  "installMap": {
13
14
  "provenance.json": ".ailk-template.json"
14
15
  },
15
- "postInstall": "Template saas-product installed. Its Pro Plus modules - docs-blog, scheduling, signal-collectors and multi-tenant (the authed apps/admin app) - are delivered by their own module bundles. This template's own page compositions, copy and demo data are pending under #5470; until they land it contributes the provenance record and its requires cross-check only, and the scaffold renders each module's own seed content."
16
+ "postInstall": "Template saas-product installed. Its Pro Plus modules - docs-blog, scheduling, signal-collectors, workspace (the /admin settings pages) and multi-tenant (the authed apps/admin app) - are delivered by their own module bundles. This template's own page compositions, copy and demo data are pending under #5470; until they land it contributes the provenance record and its requires cross-check only, and the scaffold renders each module's own seed content."
16
17
  }
@@ -5,6 +5,7 @@
5
5
  "docs-blog",
6
6
  "scheduling",
7
7
  "signal-collectors",
8
+ "workspace",
8
9
  "multi-tenant"
9
10
  ]
10
11
  }
@@ -188,6 +188,13 @@ CONTENT_ADAPTER=mdx
188
188
  # config declares a `followUps` block.
189
189
  # AILK_FOLLOWUP_EMAIL_FROM=
190
190
  #
191
+ # The inbox's sender (#9690): an Admin reply emailed to the client, and the
192
+ # Invite to sign up email. Optional — falls back to
193
+ # AILK_LEAD_NOTIFICATION_FROM when unset; with neither, a reply is still saved
194
+ # (its email is `not_configured`) and an invitation is refused. The links
195
+ # start at FRONTEND_URL (https only outside development).
196
+ # AILK_INBOX_FROM=
197
+ #
191
198
  # The Resend Contacts audience the #4732 follow-up sweep checks before
192
199
  # sending — a lead found unsubscribed in this audience is suppressed.
193
200
  # Optional: unset degrades to "cannot verify — proceed" (never to "block all
@@ -446,6 +453,14 @@ CONTENT_ADAPTER=mdx
446
453
  # that database. Rotation and the rekey bin: apps/api/.env.example.
447
454
  # STRIPE_CONNECTION_KEY=
448
455
  # STRIPE_CONNECTION_KEY_PREVIOUS=
456
+ # The key each Organization's Slack connection is encrypted under (#9748),
457
+ # and the key it replaced during a rotation: 32 random bytes each, `openssl
458
+ # rand -base64 32`, distinct from each other and from SCHEDULING_CREDENTIAL_KEY
459
+ # and both Stripe connection keys. Unset = a connection cannot be written and
460
+ # reads as not connected, but can be removed; any other shape stops the API.
461
+ # No rekey walk: rotation and the leaked-key rule in apps/api/.env.example.
462
+ # SLACK_CONNECTION_KEY=
463
+ # SLACK_CONNECTION_KEY_PREVIOUS=
449
464
  # The Organization that owns STRIPE_SECRET_KEY (#9285): with the multi-tenant
450
465
  # module only the Organization whose id is named here sells through it; unset
451
466
  # means nobody. Without the module the single Organization owns it.
@@ -125,6 +125,27 @@ STRIPE_WEBHOOK_SECRET=whsec_xxx
125
125
  # STRIPE_CONNECTION_KEY=
126
126
  # STRIPE_CONNECTION_KEY_PREVIOUS=
127
127
  #
128
+ # An Organization's Slack connection (#9748) — the AES-256-GCM key each
129
+ # Organization's Slack bot token and signing secret are encrypted under. The
130
+ # same shape rule: exactly 32 random bytes in canonical standard base64
131
+ # (`openssl rand -base64 32`), and never the same bytes as
132
+ # SCHEDULING_CREDENTIAL_KEY, STRIPE_CONNECTION_KEY or
133
+ # STRIPE_CONNECTION_KEY_PREVIOUS. Any other value stops the API at startup.
134
+ # Unset means connect answers 503 credential_key_not_configured, every
135
+ # connection reads as not connected, and disconnect still works.
136
+ #
137
+ # ROTATION. Set SLACK_CONNECTION_KEY_PREVIOUS to the old key and
138
+ # SLACK_CONNECTION_KEY to a new one, and redeploy. Every connect writes under
139
+ # the new key; a connection written under the old one reads as connected
140
+ # while _PREVIOUS is set, and as not connected once it is unset, until its
141
+ # owner reconnects. There is no rekey walk. A rotation made because the old
142
+ # key LEAKED is not complete until each connected owner rotates the bot token
143
+ # and signing secret in Slack and reconnects: the old key still opens every
144
+ # row written under it. _PREVIOUS without a current key, or equal to it,
145
+ # stops the API.
146
+ # SLACK_CONNECTION_KEY=
147
+ # SLACK_CONNECTION_KEY_PREVIOUS=
148
+ #
128
149
  # The Organization that owns STRIPE_SECRET_KEY (#9285). On a deployment with
129
150
  # the multi-tenant module, only the Organization whose id is named here sees
130
151
  # that key as its Stripe connection and sells its Offerings through it; unset,