create-ailk 0.12.1 → 0.14.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 (163) hide show
  1. package/README.md +12 -4
  2. package/component-catalog.json +247 -33
  3. package/component-catalog.md +10 -6
  4. package/dist/apply-design.d.ts +21 -2
  5. package/dist/apply-design.js +120 -19
  6. package/dist/cli.js +1 -0
  7. package/dist/design-compile.d.ts +70 -2
  8. package/dist/design-compile.js +97 -1
  9. package/dist/design-home.d.ts +74 -0
  10. package/dist/design-home.js +256 -0
  11. package/dist/design-package.d.ts +39 -2
  12. package/dist/design-package.js +223 -9
  13. package/dist/design-presets.d.ts +17 -1
  14. package/dist/design-presets.js +17 -5
  15. package/dist/design-scaffold.d.ts +6 -25
  16. package/dist/design-scaffold.js +26 -74
  17. package/dist/index.d.ts +1 -0
  18. package/dist/lib/design-checks.d.ts +12 -1
  19. package/dist/lib/design-checks.js +38 -2
  20. package/dist/lib/module-license-gate.d.ts +9 -0
  21. package/dist/lib/module-license-gate.js +18 -0
  22. package/dist/module-architecture.js +27 -0
  23. package/dist/programmatic.js +70 -26
  24. package/dist/site-chrome.d.ts +38 -0
  25. package/dist/site-chrome.js +109 -2
  26. package/dist/site-fonts.d.ts +21 -0
  27. package/dist/site-fonts.js +54 -0
  28. package/dist/site-roster.d.ts +16 -0
  29. package/dist/site-roster.js +81 -0
  30. package/dist/starter-grid.js +15 -0
  31. package/dist/surfaces.js +12 -0
  32. package/dist/team-profiles.d.ts +29 -0
  33. package/dist/team-profiles.js +43 -0
  34. package/package.json +3 -3
  35. package/release-scope.json +1 -1
  36. package/site-templates/_base/content/en/TeamList/team.mdx +68 -0
  37. package/site-templates/_base/content/en/TeamMember/first-member.mdx +22 -0
  38. package/site-templates/_base/content/en/TeamMember/second-member.mdx +22 -0
  39. package/site-templates/_base/content/en/TeamMember/third-member.mdx +22 -0
  40. package/site-templates/designs/minimalism/DESIGN.md +164 -34
  41. package/site-templates/designs/minimalism/home/local-business-service.mdx +119 -0
  42. package/site-templates/designs/minimalism/home/organization-product.mdx +132 -0
  43. package/site-templates/designs/minimalism/home/organization-service.mdx +131 -0
  44. package/site-templates/designs/minimalism/home/person-service.mdx +101 -0
  45. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +34 -1
  46. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +20 -0
  47. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +34 -1
  48. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +22 -1
  49. package/site-templates/marketing-site-free/ailk-template.json +1 -1
  50. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.json +34 -1
  51. package/site-templates/shapes/local-business-service/TEMPLATE-MANIFEST.md +26 -7
  52. package/site-templates/shapes/local-business-service/ailk-template.json +1 -1
  53. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.json +34 -1
  54. package/site-templates/shapes/organization-service/TEMPLATE-MANIFEST.md +26 -7
  55. package/site-templates/shapes/organization-service/ailk-template.json +1 -1
  56. package/templates/.env.example +23 -9
  57. package/templates/README.md +2 -0
  58. package/templates/apps/api/CLAUDE.md +24 -17
  59. package/templates/apps/api/src/__tests__/factories/agent-caller.ts +98 -0
  60. package/templates/apps/api/src/__tests__/factories/in-memory-prisma.ts +321 -0
  61. package/templates/apps/api/src/__tests__/schedule-forwarded-rate-limit.test.ts +117 -0
  62. package/templates/apps/api/src/config/__tests__/hub-require-spoke-key.test.ts +39 -14
  63. package/templates/apps/api/src/config/index.ts +35 -20
  64. package/templates/apps/api/src/lib/__mocks__/prisma-client.js +3 -0
  65. package/templates/apps/api/src/lib/__mocks__/prisma.ts +14 -0
  66. package/templates/apps/api/src/lib/__tests__/agent-store.test.ts +468 -0
  67. package/templates/apps/api/src/lib/__tests__/spoke-store.test.ts +32 -2
  68. package/templates/apps/api/src/lib/agent-store.ts +521 -0
  69. package/templates/apps/api/src/lib/spoke-store.ts +13 -2
  70. package/templates/apps/api/src/middleware/__tests__/auth.bearer-path.test.ts +384 -1
  71. package/templates/apps/api/src/middleware/__tests__/auth.cookie-path.test.ts +61 -0
  72. package/templates/apps/api/src/middleware/__tests__/default-tenant.test.ts +176 -0
  73. package/templates/apps/api/src/middleware/__tests__/rate-limit.test.ts +76 -0
  74. package/templates/apps/api/src/middleware/auth.ts +219 -5
  75. package/templates/apps/api/src/middleware/default-tenant.ts +29 -8
  76. package/templates/apps/api/src/middleware/rate-limit.ts +35 -13
  77. package/templates/apps/api/src/routes/leads/__tests__/spoke.test.ts +76 -15
  78. package/templates/apps/api/src/routes/leads/index.ts +14 -6
  79. package/templates/apps/api/src/routes/project-listings/__tests__/me.test.ts +5 -6
  80. package/templates/apps/api/src/routes/project-listings/index.ts +14 -9
  81. package/templates/apps/api/src/routes/project-listings/me.ts +4 -3
  82. package/templates/apps/api/src/routes/project-listings/patch-copy.ts +7 -5
  83. package/templates/apps/api/src/routes/workspaces/__tests__/inbox-wire-schemas.test.ts +198 -0
  84. package/templates/apps/api/src/server.ts +27 -0
  85. package/templates/apps/api/src/services/__tests__/audit-log.test.ts +374 -57
  86. package/templates/apps/api/src/services/__tests__/leads-inbox.test.ts +25 -2
  87. package/templates/apps/api/src/services/__tests__/spoke-resolution.test.ts +303 -17
  88. package/templates/apps/api/src/services/audit-log.ts +278 -47
  89. package/templates/apps/api/src/services/default-tenant.ts +55 -5
  90. package/templates/apps/api/src/services/leads-inbox.ts +12 -21
  91. package/templates/apps/api/src/services/project-listings.ts +1 -21
  92. package/templates/apps/api/src/services/public-site-resolver.ts +103 -13
  93. package/templates/apps/mcp/CLAUDE.md +44 -5
  94. package/templates/apps/mcp/__tests__/parity.test.ts +53 -1
  95. package/templates/apps/mcp/__tests__/workspace-surface.test.ts +364 -0
  96. package/templates/apps/mcp/package.json +1 -1
  97. package/templates/apps/mcp/src/http-transport.ts +107 -1
  98. package/templates/apps/mcp/src/workspace-server.ts +193 -0
  99. package/templates/apps/web/.env.example +64 -37
  100. package/templates/apps/web/CLAUDE.md +11 -0
  101. package/templates/apps/web/app/[locale]/(authed)/account/__tests__/settings.test.tsx +10 -0
  102. package/templates/apps/web/app/[locale]/(authed)/account/settings/__tests__/theme-modes.test.ts +42 -0
  103. package/templates/apps/web/app/[locale]/(authed)/account/settings/theme-modes.ts +8 -0
  104. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +2 -2
  105. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +2 -2
  106. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +14 -1
  107. package/templates/apps/web/app/[locale]/contact/__tests__/site-contact-lines.test.ts +69 -0
  108. package/templates/apps/web/app/[locale]/contact/site-contact-lines.ts +64 -0
  109. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +4 -4
  110. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +15 -3
  111. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +17 -6
  112. package/templates/apps/web/app/[locale]/for/__tests__/page-renderer-migration.test.tsx +14 -1
  113. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +2 -2
  114. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +4 -6
  115. package/templates/apps/web/app/[locale]/layout.tsx +156 -53
  116. package/templates/apps/web/app/[locale]/page.tsx +37 -0
  117. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +2 -2
  118. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +4 -6
  119. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +15 -2
  120. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +43 -6
  121. package/templates/apps/web/app/[locale]/services/page.tsx +14 -2
  122. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +2 -2
  123. package/templates/apps/web/app/[locale]/team/[slug]/page.tsx +45 -0
  124. package/templates/apps/web/app/[locale]/team/__tests__/team-routes.test.tsx +155 -0
  125. package/templates/apps/web/app/[locale]/team/page.tsx +49 -0
  126. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +2 -2
  127. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +14 -1
  128. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +2 -2
  129. package/templates/apps/web/app/not-found.tsx +47 -0
  130. package/templates/apps/web/app/v1/[...path]/route.ts +16 -0
  131. package/templates/apps/web/lib/__tests__/profile-back-link.test.ts +95 -0
  132. package/templates/apps/web/lib/__tests__/relay.test.ts +515 -0
  133. package/templates/apps/web/lib/__tests__/site-theme-head.test.ts +70 -0
  134. package/templates/apps/web/lib/__tests__/site-theme.test.ts +64 -1
  135. package/templates/apps/web/lib/profile-back-link.ts +51 -0
  136. package/templates/apps/web/lib/relay.ts +279 -0
  137. package/templates/apps/web/lib/site-chrome.ts +12 -0
  138. package/templates/apps/web/lib/site-fonts.ts +15 -0
  139. package/templates/apps/web/lib/site-logo.tsx +39 -1
  140. package/templates/apps/web/lib/site-theme-head.ts +33 -0
  141. package/templates/apps/web/lib/site-theme.ts +13 -1
  142. package/templates/apps/web/messages/en.json +7 -1
  143. package/templates/apps/web/middleware.ts +5 -4
  144. package/templates/apps/web/next.config.mjs +6 -0
  145. package/templates/database/CHANGELOG.md +22 -0
  146. package/templates/database/__tests__/agents.db.test.ts +235 -0
  147. package/templates/database/auth/schema.prisma +61 -0
  148. package/templates/database/migrations/20261005120000_agents/migration.sql +84 -0
  149. package/templates/database/package.json +1 -1
  150. package/versions.json +35 -35
  151. package/site-templates/designs/minimalism/sample/README.md +0 -48
  152. package/site-templates/designs/minimalism/sample/content/en/HomePage/home.mdx +0 -244
  153. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-1.svg +0 -7
  154. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-2.svg +0 -7
  155. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-3.svg +0 -7
  156. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-4.svg +0 -7
  157. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-5.svg +0 -7
  158. package/site-templates/designs/minimalism/sample/public/avatar-placeholder-6.svg +0 -7
  159. package/site-templates/designs/minimalism/sample/public/hero-backdrop.svg +0 -17
  160. package/site-templates/designs/minimalism/sample/public/logo-placeholder-1.svg +0 -6
  161. package/site-templates/designs/minimalism/sample/public/logo-placeholder-2.svg +0 -6
  162. package/site-templates/designs/minimalism/sample/public/logo-placeholder-3.svg +0 -6
  163. package/site-templates/designs/minimalism/sample/public/wordmark-placeholder.svg +0 -28
@@ -2,6 +2,8 @@ import { type DesignSections } from "./design-package.js";
2
2
  export interface SectionLookCatalogEntry {
3
3
  /** The look group, in registry order (D2). */
4
4
  readonly look: readonly string[];
5
+ /** The type's rendering component (#7743 D6 — what the site manifest records), when the catalog names one. */
6
+ readonly component: string | undefined;
5
7
  /** The type's `data` JSON Schema `properties` (the catalog's `schema.properties`). */
6
8
  readonly properties: Readonly<Record<string, unknown>>;
7
9
  }
@@ -37,6 +39,9 @@ export interface LocatedDataEntry {
37
39
  export interface LocatedSection {
38
40
  readonly type: string;
39
41
  readonly form: "flow" | "block";
42
+ /** #7743 D3 — the element's `[start, end)` span: flow, its `{ … }`; block, from its `- ` to its last line's end. */
43
+ readonly start: number;
44
+ readonly end: number;
40
45
  /** Flow: the index of `data`'s `{`. Block: the end of the `data:` line (before its newline). */
41
46
  readonly dataOpen: number;
42
47
  /** Flow: the index of `data`'s `}`. Block: `dataOpen`. */
@@ -45,6 +50,15 @@ export interface LocatedSection {
45
50
  readonly entryColumn: number;
46
51
  readonly entries: readonly LocatedDataEntry[];
47
52
  }
53
+ /**
54
+ * A page's located sections, with the `[valueStart, valueEnd)` span of the
55
+ * whole `sections:` value (#7743 D3): from just after `sections:` (and the
56
+ * spaces after it) to the flow form's `]`, or the block form's last line end.
57
+ */
58
+ export type LocatedSections = LocatedSection[] & {
59
+ readonly valueStart: number;
60
+ readonly valueEnd: number;
61
+ };
48
62
  /**
49
63
  * Locate a page's `sections[]` in its frontmatter — the prettier-formatted
50
64
  * flow form (`sections:\n [ { "id": …, "type": …, "data": { … } }, … ]`)
@@ -52,7 +66,7 @@ export interface LocatedSection {
52
66
  * Undefined when the page has no frontmatter or no column-0 `sections:` key.
53
67
  * Any other shape throws `TemplateError` naming the element (D5).
54
68
  */
55
- export declare function locatePageSections(text: string, file: string): LocatedSection[] | undefined;
69
+ export declare function locatePageSections(text: string, file: string): LocatedSections | undefined;
56
70
  export interface PresetPage {
57
71
  readonly path: string;
58
72
  readonly text: string;
@@ -64,6 +78,8 @@ export interface PresetApplication {
64
78
  readonly sectionType: string;
65
79
  readonly field: string;
66
80
  }
81
+ /** The frontmatter's column-0 `type:` value, or undefined when it has none. */
82
+ export declare function pageTypeOf(text: string, file: string): string | undefined;
67
83
  /**
68
84
  * Set one look field on section `index` of a page, splicing only its value
69
85
  * span (D5): an existing value is replaced; an absent field is inserted as
@@ -44,6 +44,7 @@ export function readSectionLookCatalog(catalogPath) {
44
44
  const properties = entry.schema["properties"];
45
45
  out[type] = {
46
46
  look: entry.look.map(String),
47
+ component: typeof entry.component === "string" ? entry.component : undefined,
47
48
  properties: isPlainObject(properties) ? properties : {},
48
49
  };
49
50
  }
@@ -397,6 +398,8 @@ function locateFlow(text, open, end, file) {
397
398
  out.push({
398
399
  type,
399
400
  form: "flow",
401
+ start: element.open,
402
+ end: element.close + 1,
400
403
  dataOpen: data.open,
401
404
  dataClose: data.close,
402
405
  entryColumn: -1,
@@ -410,7 +413,7 @@ function locateFlow(text, open, end, file) {
410
413
  }
411
414
  if (text[i] !== "]")
412
415
  cannotLocate(file, undefined, "the closing ]");
413
- return out;
416
+ return { sections: out, close: i };
414
417
  }
415
418
  function splitLines(text, start, end) {
416
419
  const lines = [];
@@ -510,9 +513,12 @@ function locateBlock(text, lines, file) {
510
513
  lastDeep = l.end;
511
514
  }
512
515
  close();
516
+ const last = item.filter((l) => !l.blank).at(-1);
513
517
  return {
514
518
  type,
515
519
  form: "block",
520
+ start: item[0].start + itemIndent,
521
+ end: last.end,
516
522
  dataOpen: data.end,
517
523
  dataClose: data.end,
518
524
  entryColumn,
@@ -545,20 +551,26 @@ export function locatePageSections(text, file) {
545
551
  const nextKey = /\n(?=[^\s#-])/.exec(text.slice(afterKey, fmEnd));
546
552
  const valueEnd = nextKey ? afterKey + nextKey.index : fmEnd;
547
553
  const first = skipSpace(text, afterKey, valueEnd);
548
- if (text[first] === "[")
549
- return locateFlow(text, first, valueEnd, file);
554
+ if (text[first] === "[") {
555
+ const { sections, close } = locateFlow(text, first, valueEnd, file);
556
+ return Object.assign(sections, { valueStart: afterKey, valueEnd: close + 1 });
557
+ }
550
558
  const lineEnd = text.indexOf("\n", afterKey);
551
559
  if (lineEnd >= 0 && text.slice(afterKey, lineEnd).trim() === "") {
552
560
  const lines = splitLines(text, lineEnd + 1, valueEnd);
553
561
  const firstLine = lines.find((l) => !l.blank);
554
562
  if (firstLine === undefined || text.startsWith("- ", firstLine.start + firstLine.indent)) {
555
- return locateBlock(text, lines, file);
563
+ const lastLine = lines.filter((l) => !l.blank).at(-1);
564
+ return Object.assign(locateBlock(text, lines, file), {
565
+ valueStart: afterKey,
566
+ valueEnd: lastLine?.end ?? afterKey,
567
+ });
556
568
  }
557
569
  }
558
570
  return cannotLocate(file, undefined, "a flow or block sequence");
559
571
  }
560
572
  /** The frontmatter's column-0 `type:` value, or undefined when it has none. */
561
- function pageTypeOf(text, file) {
573
+ export function pageTypeOf(text, file) {
562
574
  const fm = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*\r?$/m.exec(text);
563
575
  if (!fm || fm.index !== 0)
564
576
  return undefined;
@@ -1,6 +1,5 @@
1
1
  import { type CompiledDesignTheme } from "./design-compile.js";
2
2
  import { type DesignPackage } from "./design-package.js";
3
- import type { LayerOwnership } from "./lib/extract-module-bundle.js";
4
3
  /** One resolved `--design` reference (D2). */
5
4
  export interface ResolvedDesign {
6
5
  /** "name" when the reference was a registry name, "path" when a directory. */
@@ -38,9 +37,12 @@ export declare function resolveDesign(ref: string, opts: {
38
37
  }): ResolvedDesign;
39
38
  /**
40
39
  * Render a compiled theme's `theme:` frontmatter block (D5) — `theme:`, each
41
- * treatment present as a bare `key: value`, then `colors:` with `light:`/
42
- * `dark:` sub-blocks, each colour slot as `slot: <JSON.stringify(value)>` in
43
- * `DESIGN_COLOR_MAP`'s order. Ends with a trailing newline.
40
+ * treatment present as a bare `key: value` (#7739's `darkInk`/`figures` after
41
+ * `checkmarks`), the `typography:` group when compiled — every role and field
42
+ * in the compile's order, role keys unquoted, sizes as plain scalars (#7720
43
+ * D2, #7739 D2.5) — then `colors:` with `light:`/`dark:` sub-blocks, each
44
+ * colour slot as `slot: <JSON.stringify(value)>` in `DESIGN_COLOR_MAP`'s
45
+ * order. Ends with a trailing newline.
44
46
  */
45
47
  export declare function renderThemeBlock(theme: CompiledDesignTheme): string;
46
48
  /**
@@ -53,24 +55,3 @@ export declare function renderThemeBlock(theme: CompiledDesignTheme): string;
53
55
  * @throws TemplateError when `text` carries no frontmatter to write into.
54
56
  */
55
57
  export declare function spliceThemeBlock(text: string, theme: CompiledDesignTheme, file: string): string;
56
- /**
57
- * Install a design's `sample/` directory onto a scaffold (spec 6892 D4) — a
58
- * FIXED, undeclared convention, not a design-package field: `sample/content/**`
59
- * lands at the scaffold's `content/**`, `sample/public/**` at
60
- * `apps/web/public/**`. A design with no `sample/` directory (e.g. `standard`)
61
- * installs nothing.
62
- *
63
- * Refuses — before its first write, so a refused sample leaves `targetDir`
64
- * untouched — any file outside those two roots, or any `.ts`/`.tsx`/`.js`/
65
- * `.mjs` file (content only, the template §5.2 rule this design package
66
- * amendment carries over). Every installed file's destination is recorded in
67
- * `ownership` as `design:<name>` (`readDesignPackage(designDir).name`), the
68
- * same call-scoped registry `installBundleTree` writes into.
69
- *
70
- * @throws Error on a file outside `content/`/`public/`, or a forbidden extension.
71
- */
72
- export declare function installDesignSample(opts: {
73
- designDir: string;
74
- targetDir: string;
75
- ownership: LayerOwnership;
76
- }): void;
@@ -10,8 +10,8 @@
10
10
  * directory it reads is supplied by the caller — so it stays importable from
11
11
  * Jest's CJS transform, unlike `programmatic.ts`.
12
12
  */
13
- import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync, } from "node:fs";
14
- import { join, relative, resolve, sep } from "node:path";
13
+ import { statSync } from "node:fs";
14
+ import { join, resolve } from "node:path";
15
15
  import { isDeepStrictEqual } from "node:util";
16
16
  import { DESIGN_COLOR_MAP } from "./design-compile.js";
17
17
  import { readDesignPackage } from "./design-package.js";
@@ -82,9 +82,12 @@ export function resolveDesign(ref, opts) {
82
82
  const RUNTIME_COLOR_SLOT_ORDER = DESIGN_COLOR_MAP.filter((row) => row.mode === "light").map((row) => row.runtime);
83
83
  /**
84
84
  * Render a compiled theme's `theme:` frontmatter block (D5) — `theme:`, each
85
- * treatment present as a bare `key: value`, then `colors:` with `light:`/
86
- * `dark:` sub-blocks, each colour slot as `slot: <JSON.stringify(value)>` in
87
- * `DESIGN_COLOR_MAP`'s order. Ends with a trailing newline.
85
+ * treatment present as a bare `key: value` (#7739's `darkInk`/`figures` after
86
+ * `checkmarks`), the `typography:` group when compiled — every role and field
87
+ * in the compile's order, role keys unquoted, sizes as plain scalars (#7720
88
+ * D2, #7739 D2.5) — then `colors:` with `light:`/`dark:` sub-blocks, each
89
+ * colour slot as `slot: <JSON.stringify(value)>` in `DESIGN_COLOR_MAP`'s
90
+ * order. Ends with a trailing newline.
88
91
  */
89
92
  export function renderThemeBlock(theme) {
90
93
  const lines = ["theme:"];
@@ -96,6 +99,24 @@ export function renderThemeBlock(theme) {
96
99
  lines.push(` placeholders: ${theme.placeholders}`);
97
100
  if (theme.checkmarks !== undefined)
98
101
  lines.push(` checkmarks: ${theme.checkmarks}`);
102
+ if (theme.buttonShape !== undefined)
103
+ lines.push(` buttonShape: ${theme.buttonShape}`);
104
+ if (theme.darkInk !== undefined)
105
+ lines.push(` darkInk: ${theme.darkInk}`);
106
+ if (theme.figures !== undefined)
107
+ lines.push(` figures: ${theme.figures}`);
108
+ if (theme.typography !== undefined) {
109
+ lines.push(" typography:");
110
+ for (const [role, fields] of Object.entries(theme.typography)) {
111
+ lines.push(` ${role}:`);
112
+ for (const [field, value] of Object.entries(fields)) {
113
+ lines.push(` ${field}: ${String(value)}`);
114
+ }
115
+ }
116
+ }
117
+ // #7719 D6 — present only for a design that uses `solid-dark`, or (#7739 D2.3) sets `darkInk: accent`.
118
+ if (theme.darkBands !== undefined)
119
+ lines.push(` darkBands: ${theme.darkBands}`);
99
120
  lines.push(" colors:");
100
121
  for (const mode of ["light", "dark"]) {
101
122
  lines.push(` ${mode}:`);
@@ -149,72 +170,3 @@ export function spliceThemeBlock(text, theme, file) {
149
170
  : [...lines.slice(0, themeStart), ...rendered, ...lines.slice(themeEnd)];
150
171
  return next.join("\n");
151
172
  }
152
- /** Forbidden extensions for a design sample's files (spec 6892 D4) — content only, never code. */
153
- const SAMPLE_FORBIDDEN_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs"];
154
- function hasSampleForbiddenExtension(p) {
155
- return SAMPLE_FORBIDDEN_EXTENSIONS.some((ext) => p.toLowerCase().endsWith(ext));
156
- }
157
- /** Every file under `dir`, as `/`-separated paths relative to `dir`. */
158
- function listFiles(dir) {
159
- const out = [];
160
- const stack = [dir];
161
- for (let cur = stack.pop(); cur !== undefined; cur = stack.pop()) {
162
- for (const entry of readdirSync(cur, { withFileTypes: true })) {
163
- const abs = join(cur, entry.name);
164
- if (entry.isDirectory())
165
- stack.push(abs);
166
- else if (entry.isFile())
167
- out.push(relative(dir, abs).split(sep).join("/"));
168
- }
169
- }
170
- return out;
171
- }
172
- /** Where one `sample/` file lands on a scaffold (spec 6892 D4's fixed convention). */
173
- function sampleDestination(rel) {
174
- if (rel.startsWith("content/"))
175
- return rel;
176
- return `apps/web/public/${rel.slice("public/".length)}`;
177
- }
178
- /**
179
- * Install a design's `sample/` directory onto a scaffold (spec 6892 D4) — a
180
- * FIXED, undeclared convention, not a design-package field: `sample/content/**`
181
- * lands at the scaffold's `content/**`, `sample/public/**` at
182
- * `apps/web/public/**`. A design with no `sample/` directory (e.g. `standard`)
183
- * installs nothing.
184
- *
185
- * Refuses — before its first write, so a refused sample leaves `targetDir`
186
- * untouched — any file outside those two roots, or any `.ts`/`.tsx`/`.js`/
187
- * `.mjs` file (content only, the template §5.2 rule this design package
188
- * amendment carries over). Every installed file's destination is recorded in
189
- * `ownership` as `design:<name>` (`readDesignPackage(designDir).name`), the
190
- * same call-scoped registry `installBundleTree` writes into.
191
- *
192
- * @throws Error on a file outside `content/`/`public/`, or a forbidden extension.
193
- */
194
- export function installDesignSample(opts) {
195
- const sampleDir = join(opts.designDir, "sample");
196
- if (!existsSync(sampleDir))
197
- return;
198
- const name = readDesignPackage(opts.designDir).name;
199
- const subject = `design:${name}`;
200
- // README.md at the sample root is the ONE documented exception (spec 6892
201
- // D5: "the narrative sections move to designs/<name>/sample/README.md,
202
- // which is not installed") — every other file is real content and is
203
- // validated against the two roots below.
204
- const files = listFiles(sampleDir).filter((rel) => rel !== "README.md");
205
- for (const rel of files) {
206
- if (!rel.startsWith("content/") && !rel.startsWith("public/")) {
207
- throw new Error(`${subject}: sample file "${rel}" is outside content/ and public/ (spec 6892 D4)`);
208
- }
209
- if (hasSampleForbiddenExtension(rel)) {
210
- throw new Error(`${subject}: sample file "${rel}" is a code file — a design sample is content only (spec 6892 D4)`);
211
- }
212
- }
213
- for (const rel of files) {
214
- const destinationRel = sampleDestination(rel);
215
- const destinationAbs = resolve(opts.targetDir, destinationRel);
216
- mkdirSync(join(destinationAbs, ".."), { recursive: true });
217
- copyFileSync(join(sampleDir, rel), destinationAbs);
218
- opts.ownership.set(destinationRel, subject);
219
- }
220
- }
package/dist/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export { DEFAULT_AILK_MODULE_REGISTRY_URL, FetchModuleError, fetchModule, type F
10
10
  export { extractModuleBundle, type ExistingFilePolicy, type ExtractModuleBundleOptions, type InstallReportRow, } from "./lib/extract-module-bundle.js";
11
11
  export { ADD_MODULE_HELP, AddModuleRefusedError, applyModuleToScaffold, formatAddModuleReport, parseAddModuleArgs, type AddModuleReportRow, type AddModuleResult, type ApplyModuleOptions, } from "./add-module.js";
12
12
  export { APPLY_DESIGN_HELP, applyDesign, formatApplyDesignReport, parseApplyDesignArgs, type ApplyDesignArgs, type ApplyDesignChange, type ApplyDesignOptions, type ApplyDesignResult, } from "./apply-design.js";
13
+ export type { HomeSeedOutcome } from "./design-home.js";
13
14
  export { SITE_ROSTER_PATH, readSiteRoster, siteRosterProblems, } from "./site-roster.js";
14
15
  export { SYNC_ROUTES_HELP, formatSyncRoutesReport, listTemplateRouteDirs, parseSyncRoutesArgs, syncRoutes, type RouteReport, type SyncRoutesOptions, type SyncRoutesResult, } from "./sync-routes.js";
15
16
  export { deriveArchitecture } from "./derive-architecture.js";
@@ -46,7 +46,18 @@ export type JsonLdDriftVerdict = {
46
46
  changed: DriftNode[];
47
47
  reason: string;
48
48
  };
49
- /** Decide whether `apply-design <design>` left every page's JSON-LD unchanged (logo excluded). */
49
+ /**
50
+ * `after` with the standard home put back when the seeded home's JSON-LD is
51
+ * exactly the standard home's node followed by one `Person` per
52
+ * `TEAM_SEED_MEMBERS` member, in order (the people slide's `team` section,
53
+ * #7744 D8). Any other home, or a home the people slide did not change, is
54
+ * returned as it is.
55
+ */
56
+ export declare function admitSeededPeopleSlide(before: ComposedPages, after: ComposedPages): ComposedPages;
57
+ /**
58
+ * Decide whether `apply-design <design>` left every page's JSON-LD unchanged
59
+ * (logo excluded), the seeded people slide's Persons admitted.
60
+ */
50
61
  export declare function assessJsonLdDrift(design: string, before: ComposedPages, after: ComposedPages): JsonLdDriftVerdict;
51
62
  export interface MeasureApplyDesignDriftOptions {
52
63
  /** create-ailk's built `dist/cli.js`. */
@@ -16,10 +16,17 @@
16
16
  * touches the filesystem and processes only through the paths it is handed,
17
17
  * so the driver and `__tests__/verify-scaffold-checks.test.ts` run the same
18
18
  * procedure. Nothing here imports `@working-theory/theme`.
19
+ *
20
+ * One change is content, not drift (#7744 D8): a design that seeds an
21
+ * organization home's people slide adds one `Person` per seeded team member to
22
+ * the home's graph. `admitSeededPeopleSlide` puts the standard home back only
23
+ * when the seeded home is exactly that, so any other change still counts.
19
24
  */
20
25
  import { execFileSync } from "node:child_process";
21
26
  import { writeFileSync } from "node:fs";
22
27
  import { join } from "node:path";
28
+ import { isDeepStrictEqual } from "node:util";
29
+ import { TEAM_SEED_MEMBERS } from "../team-profiles.js";
23
30
  function describePair(row) {
24
31
  const measured = row.status === "unmeasurable" || row.ratio === null
25
32
  ? "unmeasurable"
@@ -107,15 +114,44 @@ export function jsonLdDrift(before, after) {
107
114
  const cmp = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
108
115
  return nodes.sort((x, y) => cmp(x.page, y.page) || cmp(x.path, y.path));
109
116
  }
117
+ const HOME_PAGE = "content/en/HomePage/home.mdx";
118
+ /**
119
+ * `after` with the standard home put back when the seeded home's JSON-LD is
120
+ * exactly the standard home's node followed by one `Person` per
121
+ * `TEAM_SEED_MEMBERS` member, in order (the people slide's `team` section,
122
+ * #7744 D8). Any other home, or a home the people slide did not change, is
123
+ * returned as it is.
124
+ */
125
+ export function admitSeededPeopleSlide(before, after) {
126
+ const standard = before[HOME_PAGE];
127
+ const seeded = after[HOME_PAGE];
128
+ if (standard === undefined || seeded === undefined || !seeded.sectionsValid)
129
+ return after;
130
+ const graph = JSON.parse(seeded.jsonld)["@graph"];
131
+ if (!Array.isArray(graph))
132
+ return after;
133
+ const [page, ...people] = graph;
134
+ const expected = TEAM_SEED_MEMBERS.map((m) => ({
135
+ "@context": "https://schema.org",
136
+ "@type": "Person",
137
+ name: m.name,
138
+ jobTitle: m.role,
139
+ }));
140
+ const isPeopleSlide = isDeepStrictEqual(page, JSON.parse(standard.jsonld)) && isDeepStrictEqual(people, expected);
141
+ return isPeopleSlide ? { ...after, [HOME_PAGE]: standard } : after;
142
+ }
110
143
  /** How many changed nodes a drift reason lists before it stops. */
111
144
  const REASON_NODE_LIMIT = 10;
112
- /** Decide whether `apply-design <design>` left every page's JSON-LD unchanged (logo excluded). */
145
+ /**
146
+ * Decide whether `apply-design <design>` left every page's JSON-LD unchanged
147
+ * (logo excluded), the seeded people slide's Persons admitted.
148
+ */
113
149
  export function assessJsonLdDrift(design, before, after) {
114
150
  const pages = Object.keys(before).length;
115
151
  if (pages === 0) {
116
152
  return { ok: false, pages, changed: [], reason: `design ${design}: no page was composed` };
117
153
  }
118
- const changed = jsonLdDrift(before, after);
154
+ const changed = jsonLdDrift(before, admitSeededPeopleSlide(before, after));
119
155
  if (changed.length === 0)
120
156
  return { ok: true, pages };
121
157
  const listed = changed
@@ -139,6 +139,15 @@ export declare function deriveLevel(options: GateOptions): Promise<{
139
139
  level: Level;
140
140
  bundles: Map<ModuleName, Uint8Array>;
141
141
  }>;
142
+ /**
143
+ * The level a `--modules`-keep run's chosen non-Free design is gated on
144
+ * (#7010). `resolveModuleSet`'s keep branch composes no level (NFR-2), so
145
+ * `scaffold()` derives one here through `deriveLevel`, as the addition branch
146
+ * does. A key the registry refuses for a Pro module — lapsed, revoked, or one
147
+ * that does not cover Pro — reads as `"free"`, so `assertDesignAllowed`
148
+ * refuses with its own message (fail closed); any other failure propagates.
149
+ */
150
+ export declare function deriveDesignLevel(options: GateOptions): Promise<Level>;
142
151
  /**
143
152
  * The one entry point that turns `--modules` + the key into the effective
144
153
  * module set and its resolved bundles (D5) — `programmatic.ts`'s `scaffold()`
@@ -294,6 +294,24 @@ export async function deriveLevel(options) {
294
294
  throw err;
295
295
  }
296
296
  }
297
+ /**
298
+ * The level a `--modules`-keep run's chosen non-Free design is gated on
299
+ * (#7010). `resolveModuleSet`'s keep branch composes no level (NFR-2), so
300
+ * `scaffold()` derives one here through `deriveLevel`, as the addition branch
301
+ * does. A key the registry refuses for a Pro module — lapsed, revoked, or one
302
+ * that does not cover Pro — reads as `"free"`, so `assertDesignAllowed`
303
+ * refuses with its own message (fail closed); any other failure propagates.
304
+ */
305
+ export async function deriveDesignLevel(options) {
306
+ try {
307
+ return (await deriveLevel(options)).level;
308
+ }
309
+ catch (err) {
310
+ if (err instanceof LicenseRequiredError)
311
+ return "free";
312
+ throw err;
313
+ }
314
+ }
297
315
  /**
298
316
  * The one entry point that turns `--modules` + the key into the effective
299
317
  * module set and its resolved bundles (D5) — `programmatic.ts`'s `scaffold()`
@@ -806,6 +806,19 @@ export const MODULE_SLICES = {
806
806
  // `__tests__/tenancy-slice-resolution.test.ts` holds the whole gate set.
807
807
  "apps/api/src/routes/workspaces/general.ts",
808
808
  "apps/api/src/routes/workspaces/members.ts",
809
+ // #3591 PR-B — the agent seat's routes, a sibling of `members.ts` under
810
+ // the same `workspace || multi-tenant` gate, so co-owned with
811
+ // `workspace` for the #7399 reason above.
812
+ "apps/api/src/routes/workspaces/agents.ts",
813
+ // #3591 PR-E — the signed-in workspace MCP tools that twin the agent
814
+ // context, message and submission routes above. Each registers when
815
+ // its twin route does (`workspace || multi-tenant`), so co-owned with
816
+ // `workspace` for the same reason; the application tools' file is
817
+ // co-owned with `signal-collectors` instead, its routes' gate.
818
+ "apps/mcp/src/tools/agent_context.ts",
819
+ "apps/mcp/src/tools/inbox_messages.ts",
820
+ "apps/mcp/src/tools/inbox_submissions.ts",
821
+ "apps/mcp/src/tools/inbox_applications.ts",
809
822
  "apps/api/src/lib/tenant-db.ts",
810
823
  "apps/api/src/lib/ws-token.ts",
811
824
  // #6157 D16 — the magnet Admin authoring surface, co-owned with
@@ -910,6 +923,16 @@ export const MODULE_SLICES = {
910
923
  // register under the workspace||multi-tenant gate.
911
924
  "apps/api/src/routes/workspaces/general.ts",
912
925
  "apps/api/src/routes/workspaces/members.ts",
926
+ // Co-owned with `multi-tenant` (#3591 PR-B), for the reason the two
927
+ // lines above carry.
928
+ "apps/api/src/routes/workspaces/agents.ts",
929
+ // Co-owned with `multi-tenant` (#3591 PR-E): the signed-in MCP tools
930
+ // that twin the agent context, message and submission routes register
931
+ // under the same `workspace || multi-tenant` disjunction as those
932
+ // routes.
933
+ "apps/mcp/src/tools/agent_context.ts",
934
+ "apps/mcp/src/tools/inbox_messages.ts",
935
+ "apps/mcp/src/tools/inbox_submissions.ts",
913
936
  // Co-owned with `multi-tenant` (#6216) — see that slice's entry: the
914
937
  // route registers under the SAME workspace||multi-tenant gate as the
915
938
  // two lines above, and its service function imports `lib/tenant-db.ts`
@@ -1370,6 +1393,10 @@ export const MODULE_SLICES = {
1370
1393
  "apps/api/src/bin/seed-waitlist-experiments.ts",
1371
1394
  "apps/mcp/src/tools/get_flow.ts",
1372
1395
  "apps/mcp/src/tools/submit_flow_step.ts",
1396
+ // #3591 PR-E — the signed-in MCP tools that twin the tenant-scoped
1397
+ // project-listing routes, CO-OWNED with multi-tenant: they register
1398
+ // under the routes' own `multi-tenant || signal-collectors` gate.
1399
+ "apps/mcp/src/tools/inbox_applications.ts",
1373
1400
  // ── The review surface (#5677) — CO-OWNED with multi-tenant ──────────
1374
1401
  //
1375
1402
  // Every path below is ALSO a multi-tenant `ownedFiles` entry and stays