create-ailk 0.6.1 → 0.7.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 (232) hide show
  1. package/README.md +43 -4
  2. package/component-catalog.json +146 -21
  3. package/component-catalog.md +9 -6
  4. package/dist/add-module-core.d.ts +22 -0
  5. package/dist/add-module-core.js +50 -0
  6. package/dist/add-module.d.ts +89 -0
  7. package/dist/add-module.js +334 -0
  8. package/dist/category-profile.d.ts +126 -0
  9. package/dist/category-profile.js +262 -0
  10. package/dist/cli.js +73 -11
  11. package/dist/index.d.ts +4 -2
  12. package/dist/index.js +6 -1
  13. package/dist/lib/extract-module-bundle.d.ts +56 -1
  14. package/dist/lib/extract-module-bundle.js +165 -13
  15. package/dist/lib/license-key-files.d.ts +15 -0
  16. package/dist/lib/license-key-files.js +102 -0
  17. package/dist/lib/pro-callout.d.ts +16 -10
  18. package/dist/lib/pro-callout.js +26 -3
  19. package/dist/license-set.d.ts +34 -0
  20. package/dist/license-set.js +194 -0
  21. package/dist/module-architecture.d.ts +20 -0
  22. package/dist/module-architecture.js +77 -19
  23. package/dist/modules/commerce/0.6.1.tar.gz +0 -0
  24. package/dist/modules/docs-blog/0.6.1.tar.gz +0 -0
  25. package/dist/modules/marketing-site/0.6.1.tar.gz +0 -0
  26. package/dist/modules/minimalism-site/0.6.1.tar.gz +0 -0
  27. package/dist/modules/multi-tenant/0.6.1.tar.gz +0 -0
  28. package/dist/modules/saas-product/0.6.1.tar.gz +0 -0
  29. package/dist/modules/scheduling/0.6.1.tar.gz +0 -0
  30. package/dist/modules/signal-collectors/0.6.1.tar.gz +0 -0
  31. package/dist/modules/simple-service/0.6.1.tar.gz +0 -0
  32. package/dist/modules/workspace/0.6.1.tar.gz +0 -0
  33. package/dist/parse-args.d.ts +7 -0
  34. package/dist/parse-args.js +23 -1
  35. package/dist/programmatic.d.ts +46 -0
  36. package/dist/programmatic.js +130 -9
  37. package/dist/site-chrome.d.ts +18 -7
  38. package/dist/site-chrome.js +32 -14
  39. package/dist/site-roster.d.ts +127 -0
  40. package/dist/site-roster.js +494 -0
  41. package/dist/surfaces.d.ts +20 -7
  42. package/dist/surfaces.js +149 -28
  43. package/dist/sync-routes.d.ts +9 -0
  44. package/dist/sync-routes.js +2 -2
  45. package/dist/template-manifest.d.ts +58 -2
  46. package/dist/template-manifest.js +196 -69
  47. package/package.json +6 -4
  48. package/site-templates/_base/content/en/BookCall/book-call.mdx +53 -0
  49. package/site-templates/_base/content/en/GlossaryList/glossary.mdx +66 -0
  50. package/site-templates/_base/content/en/NewsletterSignup/newsletter.mdx +68 -0
  51. package/site-templates/_base/content/en/Pricing/pricing.mdx +71 -62
  52. package/site-templates/_base/content/en/PrivacyPage/privacy.mdx +2 -3
  53. package/site-templates/_base/content/en/TermsPage/terms.mdx +2 -3
  54. package/site-templates/_base/content/en/UseCase/first-use-case.mdx +23 -0
  55. package/site-templates/_base/content/en/UseCase/second-use-case.mdx +23 -0
  56. package/site-templates/_base/content/en/UseCase/third-use-case.mdx +23 -0
  57. package/site-templates/designs/minimalism/DESIGN.md +162 -0
  58. package/site-templates/designs/standard/DESIGN.md +162 -0
  59. package/site-templates/marketing-site/DESIGN.md +55 -41
  60. package/site-templates/marketing-site/TEMPLATE-MANIFEST.json +312 -57
  61. package/site-templates/marketing-site/TEMPLATE-MANIFEST.md +209 -90
  62. package/site-templates/marketing-site/ailk-template.json +1 -0
  63. package/site-templates/marketing-site-free/DESIGN.md +55 -41
  64. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.json +260 -66
  65. package/site-templates/marketing-site-free/TEMPLATE-MANIFEST.md +294 -114
  66. package/site-templates/marketing-site-free/ailk-template.json +3 -2
  67. package/site-templates/minimalism-site/DESIGN.md +54 -40
  68. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.json +246 -19
  69. package/site-templates/minimalism-site/TEMPLATE-MANIFEST.md +185 -51
  70. package/site-templates/minimalism-site/ailk-template.json +3 -2
  71. package/site-templates/minimalism-site/content/en/BookCall/book-call.mdx +53 -0
  72. package/site-templates/minimalism-site/content/en/ContactPage/contact.mdx +3 -37
  73. package/site-templates/minimalism-site/content/en/PurchasePolicyPage/purchase-policy.mdx +124 -0
  74. package/site-templates/saas-product/DESIGN.md +55 -41
  75. package/site-templates/simple-service/DESIGN.md +55 -41
  76. package/site-templates/simple-service/TEMPLATE-MANIFEST.json +135 -26
  77. package/site-templates/simple-service/TEMPLATE-MANIFEST.md +114 -59
  78. package/site-templates/simple-service/ailk-template.json +4 -3
  79. package/site-templates/simple-service/content/en/BookCall/book-call.mdx +53 -0
  80. package/site-templates/simple-service/content/en/ContactPage/contact.mdx +1 -36
  81. package/site-templates/simple-service/content/en/PurchasePolicyPage/purchase-policy.mdx +124 -0
  82. package/templates/.claude/settings.json +15 -0
  83. package/templates/.claude/skills/README.md +8 -8
  84. package/templates/.claude/skills/{scaffold-auth → add-auth}/SKILL.md +7 -7
  85. package/templates/.claude/skills/{scaffold-auth → add-auth}/references/env-vars.md +2 -2
  86. package/templates/.claude/skills/{scaffold-auth → add-auth}/references/oauth-app-setup.md +3 -3
  87. package/templates/.claude/skills/{scaffold-page → add-page}/SKILL.md +76 -62
  88. package/templates/.claude/skills/add-page/references/page-types.md +111 -0
  89. package/templates/.claude/skills/{scaffold-page → add-page}/templates/content.mdx.tmpl +4 -4
  90. package/templates/.claude/skills/{scaffold-page → add-page}/templates/route.tsx.tmpl +1 -1
  91. package/templates/.claude/skills/{audit-site → check-site}/SKILL.md +16 -17
  92. package/templates/.claude/skills/{audit-site → check-site}/references/common-errors.md +3 -3
  93. package/templates/.claude/skills/{audit-site → check-site}/references/findings-glossary.md +16 -14
  94. package/templates/.claude/skills/{audit-site → check-site}/references/severity-levels.md +1 -1
  95. package/templates/.claude/skills/fill-page/SKILL.md +180 -0
  96. package/templates/.claude/skills/fill-page/profiles/plumbing.yaml +80 -0
  97. package/templates/.claude/skills/fill-page/references/section-sources.md +41 -0
  98. package/templates/.claude/skills/{provision-config → setup-deploy}/SKILL.md +8 -8
  99. package/templates/.claude/skills/{provision-config → setup-deploy}/references/env-vars-template.md +2 -2
  100. package/templates/.claude/skills/{provision-config → setup-deploy}/references/runbook-template.md +8 -8
  101. package/templates/.claude/skills/{provision-config → setup-deploy}/references/vercel-json-template.md +1 -1
  102. package/templates/.claude/skills/upgrade-plan/SKILL.md +117 -0
  103. package/templates/.claude/skills/upgrade-plan/references/options.md +24 -0
  104. package/templates/.claude/skills/upgrade-plan/scripts/upgrade-plan.ts +527 -0
  105. package/templates/.github/workflows/ci.yml +5 -0
  106. package/templates/CLAUDE.md +8 -2
  107. package/templates/DESIGN.md +52 -45
  108. package/templates/README.md +108 -46
  109. package/templates/apps/api/.env.example +2 -1
  110. package/templates/apps/api/package.json +1 -1
  111. package/templates/apps/api/src/config/__tests__/index.test.ts +37 -2
  112. package/templates/apps/api/src/config/index.ts +19 -6
  113. package/templates/apps/api/src/lib/license-key-store.ts +45 -0
  114. package/templates/apps/api/src/lib/site.ts +5 -3
  115. package/templates/apps/api/src/middleware/__tests__/auth.database-not-configured.test.ts +97 -0
  116. package/templates/apps/api/src/middleware/auth.ts +9 -0
  117. package/templates/apps/api/src/routes/content/__tests__/get-page.test.ts +127 -5
  118. package/templates/apps/api/src/routes/content/get-page.ts +86 -2
  119. package/templates/apps/api/src/server.ts +6 -0
  120. package/templates/apps/mcp/__tests__/audit-single-page.test.ts +75 -1
  121. package/templates/apps/mcp/__tests__/lib/audit-single-page-readiness-scan.test.ts +57 -9
  122. package/templates/apps/mcp/package.json +2 -1
  123. package/templates/apps/mcp/src/index.ts +7 -0
  124. package/templates/apps/mcp/src/lib/audit-single-page.ts +36 -10
  125. package/templates/apps/mcp/src/lib/license-key-store.ts +45 -0
  126. package/templates/apps/web/__tests__/setup.ts +18 -0
  127. package/templates/apps/web/app/[locale]/(auth)/forgot-password/forgot-password-card.tsx +10 -2
  128. package/templates/apps/web/app/[locale]/(auth)/layout.tsx +2 -2
  129. package/templates/apps/web/app/[locale]/(auth)/reset-password/reset-password-card.tsx +10 -4
  130. package/templates/apps/web/app/[locale]/(auth)/sign-in/sign-in-card.tsx +22 -14
  131. package/templates/apps/web/app/[locale]/(auth)/sign-up/sign-up-card.tsx +26 -18
  132. package/templates/apps/web/app/[locale]/(authed)/account/page.tsx +12 -4
  133. package/templates/apps/web/app/[locale]/[slug]/__tests__/page.test.tsx +35 -4
  134. package/templates/apps/web/app/[locale]/[slug]/page.tsx +49 -9
  135. package/templates/apps/web/app/[locale]/book-a-call/page.tsx +50 -0
  136. package/templates/apps/web/app/[locale]/careers/__tests__/page-renderer-migration.test.tsx +3 -3
  137. package/templates/apps/web/app/[locale]/case-studies/__tests__/page-renderer-migration.test.tsx +3 -3
  138. package/templates/apps/web/app/[locale]/clients/__tests__/page-renderer-migration.test.tsx +3 -4
  139. package/templates/apps/web/app/[locale]/contact/ContactForm.tsx +94 -33
  140. package/templates/apps/web/app/[locale]/contact/ConversationalIntakeWrapper.tsx +53 -23
  141. package/templates/apps/web/app/[locale]/contact/__tests__/ContactForm.test.tsx +96 -0
  142. package/templates/apps/web/app/[locale]/contact/__tests__/ConversationalIntakeWrapper.test.tsx +84 -2
  143. package/templates/apps/web/app/[locale]/contact/__tests__/page-renderer-migration.test.tsx +5 -6
  144. package/templates/apps/web/app/[locale]/contact/page.tsx +25 -7
  145. package/templates/apps/web/app/[locale]/courses/__tests__/page-renderer-migration.test.tsx +3 -3
  146. package/templates/apps/web/app/[locale]/downloads/__tests__/page-renderer-migration.test.tsx +3 -4
  147. package/templates/apps/web/app/[locale]/events/__tests__/page-renderer-migration.test.tsx +4 -5
  148. package/templates/apps/web/app/[locale]/glossary/page.tsx +50 -0
  149. package/templates/apps/web/app/[locale]/how-to/__tests__/page-renderer-migration.test.tsx +1 -1
  150. package/templates/apps/web/app/[locale]/how-to/page.tsx +25 -15
  151. package/templates/apps/web/app/[locale]/integrations/__tests__/page-renderer-migration.test.tsx +3 -3
  152. package/templates/apps/web/app/[locale]/layout.tsx +113 -51
  153. package/templates/apps/web/app/[locale]/newsletter/page.tsx +50 -0
  154. package/templates/apps/web/app/[locale]/partners/__tests__/page-renderer-migration.test.tsx +3 -3
  155. package/templates/apps/web/app/[locale]/products/__tests__/page-renderer-migration.test.tsx +3 -3
  156. package/templates/apps/web/app/[locale]/projects/__tests__/page-renderer-migration.test.tsx +4 -5
  157. package/templates/apps/web/app/[locale]/{shipping → purchase-policy}/page.tsx +15 -10
  158. package/templates/apps/web/app/[locale]/resources/__tests__/page-renderer-migration.test.tsx +4 -5
  159. package/templates/apps/web/app/[locale]/services/__tests__/page-renderer-migration.test.tsx +3 -3
  160. package/templates/apps/web/app/[locale]/software/[slug]/page.tsx +14 -1
  161. package/templates/apps/web/app/[locale]/software/__tests__/page-renderer-migration.test.tsx +1 -1
  162. package/templates/apps/web/app/[locale]/software/page.tsx +25 -15
  163. package/templates/apps/web/app/[locale]/tech-articles/__tests__/page-renderer-migration.test.tsx +1 -1
  164. package/templates/apps/web/app/[locale]/tech-articles/page.tsx +25 -15
  165. package/templates/apps/web/app/[locale]/testimonials/__tests__/page-renderer-migration.test.tsx +3 -4
  166. package/templates/apps/web/app/[locale]/use-cases/[slug]/page.tsx +48 -0
  167. package/templates/apps/web/app/[locale]/use-cases/__tests__/page-renderer-migration.test.tsx +3 -4
  168. package/templates/apps/web/app/[locale]/use-cases/page.tsx +15 -2
  169. package/templates/apps/web/app/[locale]/whitepapers/__tests__/page-renderer-migration.test.tsx +1 -1
  170. package/templates/apps/web/app/[locale]/whitepapers/page.tsx +25 -15
  171. package/templates/apps/web/app/error-fallback.tsx +17 -45
  172. package/templates/apps/web/app/not-found.tsx +14 -38
  173. package/templates/apps/web/app/og/route.tsx +6 -0
  174. package/templates/apps/web/eslint.config.mjs +3 -0
  175. package/templates/apps/web/instrumentation.ts +14 -0
  176. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +38 -0
  177. package/templates/apps/web/lib/license-key-store.ts +52 -0
  178. package/templates/apps/web/lib/nav-config.ts +64 -12
  179. package/templates/apps/web/lib/prisma-for-auth.ts +9 -6
  180. package/templates/apps/web/lib/site-brand.tsx +1 -1
  181. package/templates/apps/web/lib/site-chrome.ts +3 -3
  182. package/templates/apps/web/lib/site-identity.ts +134 -0
  183. package/templates/apps/web/lib/site-theme.ts +11 -0
  184. package/templates/apps/web/messages/en.json +52 -37
  185. package/templates/apps/web/public/android-chrome-192x192.png +0 -0
  186. package/templates/apps/web/public/android-chrome-512x512.png +0 -0
  187. package/templates/apps/web/public/apple-touch-icon.png +0 -0
  188. package/templates/apps/web/public/favicon-16x16.png +0 -0
  189. package/templates/apps/web/public/favicon-32x32.png +0 -0
  190. package/templates/apps/web/public/favicon.ico +0 -0
  191. package/templates/apps/web/public/favicon.svg +3 -3
  192. package/templates/apps/web/public/logomark.svg +6 -3
  193. package/templates/apps/web/public/logos/ai-launch-kit-light.svg +4 -0
  194. package/templates/apps/web/public/logos/ai-launch-kit.svg +4 -0
  195. package/templates/apps/web/public/logos/atomic-agent-light.svg +4 -0
  196. package/templates/apps/web/public/logos/atomic-agent.svg +4 -0
  197. package/templates/apps/web/public/logos/salessmyth-light.svg +4 -0
  198. package/templates/apps/web/public/logos/salessmyth.svg +4 -0
  199. package/templates/apps/web/public/site.webmanifest +4 -4
  200. package/templates/apps/web/public/wordmark-light.svg +4 -0
  201. package/templates/apps/web/public/wordmark.svg +3 -3
  202. package/templates/apps/web/security-headers.config.ts +5 -1
  203. package/templates/content/_site.mdx +23 -0
  204. package/templates/content/en/AboutPage/about.mdx +150 -144
  205. package/templates/content/en/BlogList/blog.mdx +105 -0
  206. package/templates/content/en/ContactPage/contact.mdx +44 -85
  207. package/templates/content/en/FAQPage/faq.mdx +86 -57
  208. package/templates/content/en/HomePage/home.mdx +276 -217
  209. package/templates/content/en/PurchasePolicyPage/purchase-policy.mdx +182 -0
  210. package/templates/content/en/SoftwareProduct/product.mdx +175 -0
  211. package/templates/content/en/UseCasesList/use-cases.mdx +170 -0
  212. package/templates/database/CHANGELOG.md +7 -0
  213. package/templates/database/package.json +1 -1
  214. package/templates/package.json +3 -1
  215. package/templates/tools/lint-architecture.ts +315 -0
  216. package/templates/tools/tsconfig.json +14 -0
  217. package/versions.json +33 -33
  218. package/templates/.claude/skills/add-schema/SKILL.md +0 -277
  219. package/templates/.claude/skills/add-schema/references/conflict-handling.md +0 -200
  220. package/templates/.claude/skills/add-schema/references/file-finding.md +0 -123
  221. package/templates/.claude/skills/add-schema/references/schema-types.md +0 -234
  222. package/templates/.claude/skills/scaffold-page/references/page-types.md +0 -169
  223. package/templates/.claude/skills/suggest-site-pages/SKILL.md +0 -242
  224. package/templates/.claude/skills/suggest-site-pages/references/categories.md +0 -287
  225. package/templates/apps/web/app/[locale]/cancellation/page.tsx +0 -44
  226. package/templates/apps/web/app/[locale]/returns/page.tsx +0 -44
  227. package/templates/content/en/CancellationPolicyPage/cancellation.mdx +0 -108
  228. package/templates/content/en/LandingPage/blog.mdx +0 -83
  229. package/templates/content/en/LandingPage/features.mdx +0 -103
  230. package/templates/content/en/LandingPage/how-it-works.mdx +0 -143
  231. package/templates/content/en/ReturnsPolicyPage/returns.mdx +0 -108
  232. package/templates/content/en/ShippingPolicyPage/shipping.mdx +0 -108
@@ -0,0 +1,315 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * Architecture Boundary Linter — enforces this site's `architecture.yaml`.
4
+ *
5
+ * `architecture.yaml` is generated by create-ailk from the selected module
6
+ * set; this file is what makes it more than prose. It walks `apps/` and
7
+ * `database/` for .ts/.tsx files and reports:
8
+ *
9
+ * - apps do not import apps — any file under `apps/<a>/` (or `database/`)
10
+ * importing `apps/<b>/`, by package name or by relative path, whether or
11
+ * not architecture.yaml declares either app;
12
+ * - `may_not_import_from` / `may_import_from` per declared app;
13
+ * - the global and per-app `forbidden_patterns` (with `paths_excluded`);
14
+ * - the `namespaces` boundaries inside `database/`, when declared:
15
+ * cross-namespace relative imports and cross-namespace Prisma `@relation`
16
+ * directives.
17
+ *
18
+ * An import target is checked only when it is a workspace present in this
19
+ * repo (`apps/*`, `database`); `@working-theory/*` libraries install from npm
20
+ * and are not source here.
21
+ *
22
+ * Usage:
23
+ * pnpm architecture # or: tsx tools/lint-architecture.ts
24
+ *
25
+ * Exit codes:
26
+ * 0 — no violations
27
+ * 1 — violations found (each names the file, the line and the rule)
28
+ */
29
+
30
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
31
+ import { dirname, join, relative, resolve, sep } from "node:path";
32
+
33
+ import { parse as parseYaml } from "yaml";
34
+
35
+ interface ForbiddenPattern {
36
+ pattern: string;
37
+ reason: string;
38
+ paths_excluded?: string[];
39
+ }
40
+
41
+ interface AppRule {
42
+ path: string;
43
+ may_import_from?: string[];
44
+ may_not_import_from?: string[];
45
+ forbidden_patterns?: ForbiddenPattern[];
46
+ }
47
+
48
+ interface ArchConfig {
49
+ apps?: Record<string, AppRule>;
50
+ forbidden_patterns?: ForbiddenPattern[];
51
+ namespaces?: { members?: Record<string, { path: string }> };
52
+ }
53
+
54
+ interface Violation {
55
+ file: string;
56
+ line: number;
57
+ rule: string;
58
+ reason: string;
59
+ }
60
+
61
+ const ROOT = resolve(dirname(process.argv[1] ?? "."), "..");
62
+ const SKIP_DIRS = new Set([
63
+ "node_modules",
64
+ "dist",
65
+ ".next",
66
+ ".turbo",
67
+ ".source",
68
+ "coverage",
69
+ "generated",
70
+ ]);
71
+ const IMPORT_RE =
72
+ /(?:^|[\n;])\s*(?:import\s+(?:type\s+)?|export\s+(?:type\s+)?(?=[*{]))[^;'"]*?\bfrom\s+['"]([^'"]+)['"]/g;
73
+ const SIDE_EFFECT_IMPORT_RE = /(?:^|[\n;])\s*import\s+['"]([^'"]+)['"]/g;
74
+ const DYNAMIC_IMPORT_RE = /\b(?:import|require)\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
75
+
76
+ function toPosix(p: string): string {
77
+ return p.split(sep).join("/");
78
+ }
79
+
80
+ function rel(abs: string): string {
81
+ return toPosix(relative(ROOT, abs));
82
+ }
83
+
84
+ function walk(dir: string, match: RegExp, out: string[] = []): string[] {
85
+ if (!existsSync(dir)) return out;
86
+ for (const entry of readdirSync(dir)) {
87
+ if (SKIP_DIRS.has(entry)) continue;
88
+ const p = join(dir, entry);
89
+ if (statSync(p).isDirectory()) walk(p, match, out);
90
+ else if (match.test(entry) && !entry.endsWith(".d.ts")) out.push(p);
91
+ }
92
+ return out;
93
+ }
94
+
95
+ /** Every workspace in this repo: `apps/<name>` for each app dir, plus `database`. */
96
+ function workspacesOnDisk(): string[] {
97
+ const out: string[] = [];
98
+ const appsDir = join(ROOT, "apps");
99
+ if (existsSync(appsDir)) {
100
+ for (const entry of readdirSync(appsDir).sort()) {
101
+ if (statSync(join(appsDir, entry)).isDirectory()) out.push(`apps/${entry}`);
102
+ }
103
+ }
104
+ if (existsSync(join(ROOT, "database"))) out.push("database");
105
+ return out;
106
+ }
107
+
108
+ /** Package name → workspace, read from each workspace's own package.json. */
109
+ function packageNames(workspaces: string[]): Map<string, string> {
110
+ const names = new Map<string, string>();
111
+ for (const ws of workspaces) {
112
+ const pkgPath = join(ROOT, ws, "package.json");
113
+ if (!existsSync(pkgPath)) continue;
114
+ const name = (JSON.parse(readFileSync(pkgPath, "utf8")) as { name?: unknown })
115
+ .name;
116
+ if (typeof name === "string") names.set(name, ws);
117
+ }
118
+ return names;
119
+ }
120
+
121
+ function workspaceOfPath(relPath: string, workspaces: string[]): string | null {
122
+ for (const ws of workspaces) {
123
+ if (relPath === ws || relPath.startsWith(`${ws}/`)) return ws;
124
+ }
125
+ return null;
126
+ }
127
+
128
+ function extractImports(content: string): { specifier: string; line: number }[] {
129
+ const lineOf = (offset: number) => content.slice(0, offset).split("\n").length;
130
+ const out: { specifier: string; line: number }[] = [];
131
+ for (const re of [IMPORT_RE, SIDE_EFFECT_IMPORT_RE, DYNAMIC_IMPORT_RE]) {
132
+ for (const m of content.matchAll(re)) {
133
+ // The newline/semicolon (and blank lines) the static regexes anchor on
134
+ // belong to earlier lines; count from the import keyword itself.
135
+ const lead = Math.max(0, m[0].search(/[^\s;]/));
136
+ if (m[1]) out.push({ specifier: m[1], line: lineOf((m.index ?? 0) + lead) });
137
+ }
138
+ }
139
+ return out;
140
+ }
141
+
142
+ function resolveTarget(
143
+ file: string,
144
+ specifier: string,
145
+ workspaces: string[],
146
+ names: Map<string, string>,
147
+ ): string | null {
148
+ if (specifier.startsWith(".")) {
149
+ return workspaceOfPath(rel(resolve(dirname(file), specifier)), workspaces);
150
+ }
151
+ for (const [name, ws] of names) {
152
+ if (specifier === name || specifier.startsWith(`${name}/`)) return ws;
153
+ }
154
+ return null;
155
+ }
156
+
157
+ function checkImport(
158
+ fileWs: string,
159
+ targetWs: string,
160
+ cfg: ArchConfig,
161
+ ): { rule: string; reason: string } | null {
162
+ if (targetWs.startsWith("apps/")) {
163
+ return fileWs.startsWith("apps/")
164
+ ? {
165
+ rule: `${fileWs} may not import ${targetWs} — apps do not import apps`,
166
+ reason:
167
+ "Apps talk over HTTP (web → api) and MCP (mcp → api); shared code belongs in a package.",
168
+ }
169
+ : {
170
+ rule: `${fileWs} may not import ${targetWs} — the data tier imports no app`,
171
+ reason: "database/ sits below every app; apps import it, never the reverse.",
172
+ };
173
+ }
174
+ const rule = fileWs.startsWith("apps/")
175
+ ? cfg.apps?.[fileWs.slice("apps/".length)]
176
+ : undefined;
177
+ if (!rule) return null;
178
+ if (rule.may_not_import_from?.includes(targetWs)) {
179
+ return {
180
+ rule: `${fileWs} may_not_import_from ${targetWs}`,
181
+ reason: "Forbidden by architecture.yaml",
182
+ };
183
+ }
184
+ if (rule.may_import_from?.length && !rule.may_import_from.includes(targetWs)) {
185
+ return {
186
+ rule: `${fileWs} may_import_from does not include ${targetWs}`,
187
+ reason: `Not on ${fileWs}'s allowlist in architecture.yaml`,
188
+ };
189
+ }
190
+ return null;
191
+ }
192
+
193
+ function isExcluded(relPath: string, globs: string[] | undefined): boolean {
194
+ return (globs ?? []).some((glob) =>
195
+ glob.endsWith("/**") ? relPath.startsWith(glob.slice(0, -2)) : relPath === glob,
196
+ );
197
+ }
198
+
199
+ function checkForbiddenPattern(
200
+ file: string,
201
+ content: string,
202
+ fp: ForbiddenPattern,
203
+ ): Violation[] {
204
+ if (isExcluded(rel(file), fp.paths_excluded)) return [];
205
+ const re = new RegExp(fp.pattern);
206
+ const out: Violation[] = [];
207
+ content.split("\n").forEach((text, i) => {
208
+ if (re.test(text)) {
209
+ out.push({ file, line: i + 1, rule: `forbidden_pattern: ${fp.pattern}`, reason: fp.reason });
210
+ }
211
+ });
212
+ return out;
213
+ }
214
+
215
+ /** Namespace boundaries inside database/ — imports and Prisma @relation. */
216
+ function checkNamespaces(cfg: ArchConfig): Violation[] {
217
+ const members = Object.values(cfg.namespaces?.members ?? {}).map((m) => m.path);
218
+ if (members.length === 0) return [];
219
+ const nsOf = (relPath: string) => workspaceOfPath(relPath, members);
220
+ const dbDir = join(ROOT, "database");
221
+ const out: Violation[] = [];
222
+
223
+ for (const file of walk(dbDir, /\.tsx?$/)) {
224
+ const fromNs = nsOf(rel(file));
225
+ if (!fromNs) continue;
226
+ for (const imp of extractImports(readFileSync(file, "utf8"))) {
227
+ if (!imp.specifier.startsWith(".")) continue;
228
+ const toNs = nsOf(rel(resolve(dirname(file), imp.specifier)));
229
+ if (!toNs || toNs === fromNs) continue;
230
+ out.push({
231
+ file,
232
+ line: imp.line,
233
+ rule: `cross-namespace-import: ${fromNs} may not import ${toNs}`,
234
+ reason: "Cross-namespace data assembly routes through apps/api.",
235
+ });
236
+ }
237
+ }
238
+
239
+ const prismaFiles = walk(dbDir, /\.prisma$/);
240
+ const modelNs = new Map<string, string>();
241
+ for (const file of prismaFiles) {
242
+ const ns = nsOf(rel(file));
243
+ if (!ns) continue;
244
+ for (const m of readFileSync(file, "utf8").matchAll(/^\s*model\s+(\w+)\s*\{/gm)) {
245
+ if (m[1] && !modelNs.has(m[1])) modelNs.set(m[1], ns);
246
+ }
247
+ }
248
+ const relationLine = /^\s*\w+\s+(\w+)(\?|\[\])?\s+@relation\s*\(/;
249
+ for (const file of prismaFiles) {
250
+ const fromNs = nsOf(rel(file));
251
+ if (!fromNs) continue;
252
+ readFileSync(file, "utf8")
253
+ .split("\n")
254
+ .forEach((text, i) => {
255
+ const model = relationLine.exec(text)?.[1];
256
+ const toNs = model ? modelNs.get(model) : undefined;
257
+ if (!toNs || toNs === fromNs) return;
258
+ out.push({
259
+ file,
260
+ line: i + 1,
261
+ rule: `cross-namespace-prisma-relation: ${fromNs} may not relate to ${toNs}`,
262
+ reason: `@relation to model '${model}' crosses a namespace boundary.`,
263
+ });
264
+ });
265
+ }
266
+ return out;
267
+ }
268
+
269
+ function main(): void {
270
+ const yamlPath = join(ROOT, "architecture.yaml");
271
+ if (!existsSync(yamlPath)) {
272
+ console.error(`architecture: no architecture.yaml at ${ROOT}`);
273
+ process.exitCode = 1;
274
+ return;
275
+ }
276
+ const cfg = (parseYaml(readFileSync(yamlPath, "utf8")) ?? {}) as ArchConfig;
277
+ const workspaces = workspacesOnDisk();
278
+ const names = packageNames(workspaces);
279
+ const violations: Violation[] = [];
280
+
281
+ for (const ws of workspaces) {
282
+ for (const file of walk(join(ROOT, ws), /\.tsx?$/)) {
283
+ const content = readFileSync(file, "utf8");
284
+ for (const imp of extractImports(content)) {
285
+ const targetWs = resolveTarget(file, imp.specifier, workspaces, names);
286
+ if (!targetWs || targetWs === ws) continue;
287
+ const v = checkImport(ws, targetWs, cfg);
288
+ if (v) violations.push({ file, line: imp.line, ...v });
289
+ }
290
+ for (const fp of cfg.forbidden_patterns ?? []) {
291
+ violations.push(...checkForbiddenPattern(file, content, fp));
292
+ }
293
+ const app = ws.startsWith("apps/") ? cfg.apps?.[ws.slice("apps/".length)] : undefined;
294
+ for (const fp of app?.forbidden_patterns ?? []) {
295
+ violations.push(...checkForbiddenPattern(file, content, fp));
296
+ }
297
+ }
298
+ }
299
+ violations.push(...checkNamespaces(cfg));
300
+
301
+ if (violations.length === 0) {
302
+ console.log("architecture: OK");
303
+ return;
304
+ }
305
+ console.error(`architecture: ${violations.length} violation(s)`);
306
+ for (const v of violations) {
307
+ console.error(` ${rel(v.file)}:${v.line} ${v.rule}`);
308
+ console.error(` ${v.reason}`);
309
+ }
310
+ // exitCode, not exit(): stderr to a pipe is asynchronous on macOS, and the
311
+ // hook reads this report through one.
312
+ process.exitCode = 1;
313
+ }
314
+
315
+ main();
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "strict": true,
8
+ "noUncheckedIndexedAccess": true,
9
+ "skipLibCheck": true,
10
+ "noEmit": true,
11
+ "types": ["node"]
12
+ },
13
+ "include": ["*.ts"]
14
+ }
package/versions.json CHANGED
@@ -1,39 +1,39 @@
1
1
  {
2
- "@working-theory/admin-ui": "0.26.0",
3
- "@working-theory/aeo": "0.26.0",
4
- "@working-theory/agent-context": "0.26.0",
2
+ "@working-theory/admin-ui": "0.27.0",
3
+ "@working-theory/aeo": "0.27.0",
4
+ "@working-theory/agent-context": "0.27.0",
5
5
  "@working-theory/agent-contract": "0.1.0",
6
6
  "@working-theory/agent-memory": "0.0.0",
7
7
  "@working-theory/ai": "0.1.0",
8
- "@working-theory/analytics": "0.26.0",
9
- "@working-theory/api-client": "0.26.0",
8
+ "@working-theory/analytics": "0.27.0",
9
+ "@working-theory/api-client": "0.27.0",
10
10
  "@working-theory/cda-filesystem": "0.0.0",
11
- "@working-theory/cli": "0.26.0",
12
- "@working-theory/common": "0.26.0",
11
+ "@working-theory/cli": "0.27.0",
12
+ "@working-theory/common": "0.27.0",
13
13
  "@working-theory/consent": "0.1.0",
14
- "@working-theory/content-adapters": "0.26.0",
15
- "@working-theory/content-projection": "0.26.0",
16
- "@working-theory/email": "0.26.0",
14
+ "@working-theory/content-adapters": "0.27.0",
15
+ "@working-theory/content-projection": "0.27.0",
16
+ "@working-theory/email": "0.27.0",
17
17
  "@working-theory/env": "0.0.0",
18
- "@working-theory/eslint-config": "0.26.0",
19
- "@working-theory/fumadocs-schema": "0.26.0",
18
+ "@working-theory/eslint-config": "0.27.0",
19
+ "@working-theory/fumadocs-schema": "0.27.0",
20
20
  "@working-theory/ids": "0.8.0",
21
- "@working-theory/jsonld": "0.26.0",
22
- "@working-theory/license": "0.26.0",
23
- "@working-theory/metadata": "0.26.0",
24
- "@working-theory/observability": "0.26.0",
25
- "@working-theory/page-renderer": "0.26.0",
26
- "@working-theory/schema": "0.26.0",
27
- "@working-theory/schema-pack-registry": "0.26.0",
28
- "@working-theory/schema-validator": "0.26.0",
29
- "@working-theory/sections-registry": "0.26.0",
30
- "@working-theory/site-config": "0.26.0",
31
- "@working-theory/stripe-client": "0.26.0",
32
- "@working-theory/templates": "0.26.0",
33
- "@working-theory/theme": "0.26.0",
34
- "@working-theory/ui": "0.26.0",
35
- "@working-theory/validation": "0.26.0",
36
- "@working-theory/web-i18n": "0.26.0",
21
+ "@working-theory/jsonld": "0.27.0",
22
+ "@working-theory/license": "0.27.0",
23
+ "@working-theory/metadata": "0.27.0",
24
+ "@working-theory/observability": "0.27.0",
25
+ "@working-theory/page-renderer": "0.27.0",
26
+ "@working-theory/schema": "0.27.0",
27
+ "@working-theory/schema-pack-registry": "0.27.0",
28
+ "@working-theory/schema-validator": "0.27.0",
29
+ "@working-theory/sections-registry": "0.27.0",
30
+ "@working-theory/site-config": "0.27.0",
31
+ "@working-theory/stripe-client": "0.27.0",
32
+ "@working-theory/templates": "0.27.0",
33
+ "@working-theory/theme": "0.27.0",
34
+ "@working-theory/ui": "0.27.0",
35
+ "@working-theory/validation": "0.27.0",
36
+ "@working-theory/web-i18n": "0.27.0",
37
37
  "@working-theory/billing-client": "0.0.0",
38
38
  "@working-theory/branding": "0.0.0",
39
39
  "@working-theory/license-client": "0.0.0",
@@ -41,12 +41,12 @@
41
41
  "@working-theory/marketing-api-client": "0.0.0",
42
42
  "@working-theory/partner-session": "0.0.0",
43
43
  "@working-theory/preview-protocol": "0.0.0",
44
- "@working-theory/ui-commerce": "0.23.3",
45
- "@working-theory/ui-docs-blog": "0.23.3",
46
- "@working-theory/ui-scheduling": "0.23.3",
47
- "@working-theory/ui-signal-collectors": "0.24.1",
44
+ "@working-theory/ui-commerce": "0.23.4",
45
+ "@working-theory/ui-docs-blog": "0.23.4",
46
+ "@working-theory/ui-scheduling": "0.23.4",
47
+ "@working-theory/ui-signal-collectors": "0.24.2",
48
48
  "@working-theory/web": "0.0.0",
49
49
  "@working-theory/api": "1.0.0",
50
50
  "@working-theory/mcp": "0.0.0",
51
- "@working-theory/database": "0.26.0"
51
+ "@working-theory/database": "0.27.0"
52
52
  }
@@ -1,277 +0,0 @@
1
- ---
2
- name: add-schema
3
- description: Add or replace schema.org JSON-LD on an existing page in an AILK consumer site. Use when the user says "add Product schema to /pricing", "add FAQ to /contact", "put Service schema on this page", or otherwise wants schema attached to a page that already exists. Does NOT scaffold new pages — that's `scaffold-page` (#362). Type-aware conflict handling: replaces existing schema of the same `@type`, appends supplementary schema, asks on ambiguity.
4
- ---
5
-
6
- # add-schema
7
-
8
- Inject schema.org JSON-LD into an existing page without re-scaffolding
9
- the route. Pairs with `scaffold-page` (#362) — that skill creates new
10
- pages, this one modifies existing ones. Both share the same `<JsonLd>`
11
- attachment pattern from `@working-theory/jsonld`.
12
-
13
- ## When to use
14
-
15
- The consumer wants to add or update structured data on an existing
16
- page. Triggers:
17
-
18
- - "add Product schema to /pricing"
19
- - "add FAQ to /contact" / "add an FAQ section schema to about"
20
- - "put Service schema on this page"
21
- - "swap out the Product schema on /pricing for a new one"
22
-
23
- Skip this skill when:
24
-
25
- - The consumer wants a **new page** — that's `scaffold-page` (#362).
26
- This skill refuses on no-match and points at #362 explicitly.
27
- - The consumer wants to **audit** schema coverage / validity —
28
- that's `audit-site` (#365). Never invoked from here.
29
- - The schema type isn't in `references/schema-types.md`. Ask the
30
- consumer rather than guessing.
31
-
32
- ## What this skill does
33
-
34
- 1. Parse the consumer's input: extract the **schema type** (Product,
35
- FAQ, Service, etc.) and the **target slug** (`/pricing`,
36
- `/contact`).
37
- 2. **Resolve the file.** Slug-pattern lookup against:
38
- - App Router: `apps/web/app/<slug>/page.tsx` (or
39
- `app/<slug>/page.tsx` depending on consumer layout).
40
- - MDX content: `content/<Type>/<slug>.mdx` for any of the page
41
- types in `@working-theory/schema`.
42
- 3. **Refuse on zero matches.** Point at `scaffold-page` (#362).
43
- 4. **Ask on multiple matches.** List route vs MDX (or multiple
44
- MDX matches) and ask which to modify.
45
- 5. **Detect existing `<JsonLd>` attachments** in the resolved file.
46
- Use `references/conflict-handling.md` to decide:
47
- - Same `@type` already present → **replace** (after confirming).
48
- - Different `@type` (supplementary) already present → **append**.
49
- - Existing JSON-LD is malformed / unparseable → **ask** for
50
- clarification before writing.
51
- 6. **Show the diff** in a single confirmation step. After approval,
52
- write.
53
- 7. **Validate** the modified file still parses cleanly (TS or MDX
54
- syntax check). Surface failures with the literal parse error.
55
-
56
- ## What this skill does NOT do
57
-
58
- - Create new pages. Refuses + points at `scaffold-page` (#362).
59
- - Validate schema.org rich-results against Google's Rich Results
60
- Test or schema.org's own validator. The typed builder from
61
- `@working-theory/schema` is the type-correctness contract; rich-results
62
- validation is `audit-site` (#365)'s lane.
63
- - Modify `packages/schema/` (read-only at v1). If a builder is
64
- missing, surface and stop.
65
- - Use a `<script>` tag, `metadata.other`, or hand-coded JSON
66
- literals. The canonical attachment is always
67
- `<JsonLd schema={builder(...)} />` from `@working-theory/jsonld` +
68
- `@working-theory/schema`. This matches `scaffold-page` (#362) — the two
69
- skills share one attachment pattern.
70
- - Auto-invoke other skills.
71
-
72
- ## The mechanic
73
-
74
- ### Step 1 — Parse the input
75
-
76
- Extract two things:
77
-
78
- - **Schema type.** Match the consumer's words to a `@type` from
79
- `references/schema-types.md`. "Product" → `Product`, "FAQ" →
80
- `FAQPage`, "how-to" → `HowTo`, "Service" → `Service`. If
81
- ambiguous (e.g., "review" could be `Review` or
82
- `AggregateRating`), ask.
83
- - **Target slug.** From phrases like "/pricing", "to /contact", "on
84
- the about page". Drop the leading `/`. If the consumer says
85
- "this page" without specifying, ask which page.
86
-
87
- ### Step 2 — File resolution
88
-
89
- Look in two places:
90
-
91
- 1. **App Router route:** `apps/web/app/<slug>/page.tsx` (also try
92
- `app/<slug>/page.tsx` for non-monorepo layouts).
93
- 2. **MDX content:** scan `content/**/<slug>.mdx`. The frontmatter
94
- `slug:` field is authoritative; filename is a fallback.
95
-
96
- **Zero matches** — refuse:
97
-
98
- > "I don't see a page at `/<slug>`. To **create** the page first,
99
- > run `scaffold-page` (#362). Once it exists, come back here to add
100
- > additional schema."
101
-
102
- Stop. No writes.
103
-
104
- **One match** — proceed to Step 3.
105
-
106
- **Multiple matches** — list and ask:
107
-
108
- > "Found multiple files for `/<slug>`:
109
- >
110
- > - `apps/web/app/<slug>/page.tsx` (App Router route)
111
- > - `content/Product/<slug>.mdx` (MDX content)
112
- >
113
- > Which one should I modify?"
114
-
115
- Wait for the consumer's choice.
116
-
117
- ### Step 3 — Detect existing attachments
118
-
119
- In the resolved file, look for `<JsonLd schema={...} />` JSX
120
- elements. The canonical pattern emits one of these per attached
121
- schema. Two states matter:
122
-
123
- - **Same `@type` already present.** Example: consumer wants to add
124
- `Product` schema to `/pricing`, and the file already has
125
- `<JsonLd schema={buildProduct(...)} />`. This is a **replace**
126
- case (Step 4).
127
- - **Different `@type` already present.** Example: file has
128
- `<JsonLd schema={buildOrganization(...)} />` and consumer wants
129
- to add `FAQPage`. This is an **append** case (Step 5).
130
-
131
- If existing JSON-LD is **malformed or unrecognized** (raw `<script
132
- type="application/ld+json">` block, hand-coded literal not
133
- matching a builder, malformed JSX), do not silently rewrite. Ask:
134
-
135
- > "I see existing structured data on this page that wasn't created
136
- > by the canonical `<JsonLd schema={builder(...)} />` pattern. I
137
- > can:
138
- >
139
- > - Add my new schema alongside (no conflict — different
140
- > attachment style).
141
- > - Replace what's there with the canonical pattern + my new
142
- > schema (clean migration).
143
- > - Cancel — leave the file alone.
144
- >
145
- > Which?"
146
-
147
- ### Step 4 — Conflict handling: replace (same `@type`)
148
-
149
- When the same `@type` is already attached, the default is
150
- **replace**. Confirm:
151
-
152
- > "`<Type>` schema is already attached to `/<slug>`. I'll replace
153
- > it with the new one — confirm or cancel."
154
-
155
- After approval, locate the existing `<JsonLd>` JSX, swap the
156
- `schema={...}` expression. Keep surrounding code (imports, other
157
- JSX, exports) untouched.
158
-
159
- ### Step 5 — Conflict handling: append (different `@type`)
160
-
161
- When the new schema is a different `@type` from existing
162
- attachments, the default is **append**. Show the diff:
163
-
164
- > "Your page already has `<Organization>` schema. I'll add an
165
- > `<FAQPage>` block next to it — confirm or cancel."
166
-
167
- After approval, add a new `<JsonLd schema={buildFAQPage(...)} />`
168
- JSX element next to the existing one(s). Add the import for the
169
- new builder if not already imported.
170
-
171
- ### Step 6 — Type-aware classification
172
-
173
- `references/schema-types.md` tags the general schema.org vocabulary
174
- it curates as **primary** or **supplementary**; the AILK page-type
175
- registry (60 keys, all builder-backed in `@working-theory/schema`) is
176
- listed separately in that file's "AILK page types" section and is
177
- treated as **primary** for conflict handling — a registry page type
178
- names what the whole page IS about, and replacing on same-type
179
- conflict is the usual intent:
180
-
181
- - **Primary** types are typically one-per-page: `HomePage`,
182
- `AboutPage`, `BlogPosting`, `Product`, `Service`. A page rarely
183
- has two `Product` schemas.
184
- - **Supplementary** types stack naturally: `FAQPage`, `HowTo`,
185
- `BreadcrumbList`, `Review`, `AggregateRating`. A single product
186
- page might have `Product` + `FAQPage` + `Review` + `BreadcrumbList`.
187
-
188
- Default behavior:
189
-
190
- - Adding a **primary** type → if same type already present, replace.
191
- - Adding a **supplementary** type → always append; never replace
192
- (you'd lose information).
193
-
194
- The classification is read-only at v1 — the skill knows the table,
195
- but does not write to `packages/schema/`. Future PR may move
196
- classification into the package.
197
-
198
- ### Step 7 — Show the diff + confirm
199
-
200
- Single confirmation message:
201
-
202
- > "Adding `<Type>` schema to `<filepath>`:
203
- >
204
- > ```diff
205
- > + import { buildType } from "@working-theory/schema";
206
- > ...
207
- > + <JsonLd schema={buildType({ ... })} />
208
- > ```
209
- >
210
- > Confirm to write, or tell me what to adjust."
211
-
212
- Wait for approval. Do not write per-step.
213
-
214
- ### Step 8 — Write + validate
215
-
216
- Write the modified file atomically. After write, validate the file
217
- still parses:
218
-
219
- - For `.tsx` routes: run a TS parse check on the modified file
220
- (parse only — full type-check is the consumer's `pnpm
221
- type-check` job).
222
- - For `.mdx` content: run an MDX parse check.
223
-
224
- If parse fails, surface the literal error and revert the write:
225
-
226
- > "After writing, the file no longer parses cleanly:
227
- >
228
- > ```
229
- > <verbatim parse error>
230
- > ```
231
- >
232
- > I've reverted the change. Try a different approach, or hand-edit
233
- > the file."
234
-
235
- If parse succeeds, print a closing summary:
236
-
237
- > "Added `<Type>` schema to `<filepath>`. Run `pnpm dev` to verify
238
- > the page renders, or `audit-site` (#365) to check
239
- > citation-readiness."
240
-
241
- ## Type-by-name recognition
242
-
243
- The skill recognizes all 60 AILK page types in `@working-theory/schema` by
244
- name, plus a smaller general schema.org vocabulary for non-registry types.
245
- Common aliases (e.g., "FAQ" → `FAQPage`, "how-to" → `HowTo`,
246
- "review" → `Review`) are handled in `references/schema-types.md`.
247
- If the consumer asks for a type not in the catalog, surface and
248
- ask:
249
-
250
- > "I don't see `<requested type>` in the v1 schema catalog. The
251
- > recognized types are listed in `references/schema-types.md` —
252
- > closest matches are `<X>` and `<Y>`. Use one of those, or hand-
253
- > author the JSON-LD inline if you need a less-common type."
254
-
255
- ## References
256
-
257
- - `references/schema-types.md` — the 60 registry-backed AILK page types
258
- from `@working-theory/schema`, plus a smaller general schema.org
259
- vocabulary tagged primary or supplementary, with human-friendly
260
- aliases.
261
- - `references/file-finding.md` — slug-to-file resolution rules
262
- (App Router + MDX).
263
- - `references/conflict-handling.md` — full decision tree for
264
- replace / append / ask.
265
-
266
- ## Coordination
267
-
268
- - **`scaffold-page` (#362)** — creates new pages. This skill
269
- modifies existing ones. Same `<JsonLd>` attachment pattern.
270
- - **`audit-site` (#365)** — citation-readiness check. Never
271
- invoked from inside this skill; the audit reports, this fix
272
- tool fixes.
273
- - **`@working-theory/jsonld` (#389)** — the React component for JSON-LD
274
- rendering. Single source of truth for the attachment pattern.
275
- - **`@working-theory/schema`** — typed builders + `JsonLdObject` output.
276
- Read-only at v1; `references/schema-types.md` is the skill's
277
- copy of the catalog.