create-ailk 0.30.0 → 0.31.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 (67) hide show
  1. package/README.md +71 -3
  2. package/component-catalog.json +524 -8
  3. package/component-catalog.md +8 -5
  4. package/dist/cli.js +99 -0
  5. package/dist/cutover.d.ts +109 -0
  6. package/dist/cutover.js +494 -0
  7. package/dist/design-presets.js +20 -4
  8. package/dist/export-content.d.ts +55 -0
  9. package/dist/export-content.js +303 -0
  10. package/dist/inventory.d.ts +216 -0
  11. package/dist/inventory.js +833 -0
  12. package/dist/language-import.d.ts +114 -0
  13. package/dist/language-import.js +435 -0
  14. package/dist/lib/env-catalog.js +1 -1
  15. package/dist/lib/import-mdx.d.ts +38 -0
  16. package/dist/lib/import-mdx.js +411 -64
  17. package/dist/lib/render-comparison.d.ts +200 -0
  18. package/dist/lib/render-comparison.js +374 -0
  19. package/dist/migrate-content.d.ts +6 -2
  20. package/dist/migrate-content.js +60 -14
  21. package/dist/seed-workspace.d.ts +3 -1
  22. package/dist/seed-workspace.js +45 -16
  23. package/dist/topology.d.ts +12 -0
  24. package/dist/topology.js +24 -10
  25. package/package.json +3 -3
  26. package/templates/.claude/cloudparity-repo-hooks.json +5 -0
  27. package/templates/.claude/hooks/content-source-guard.sh +75 -0
  28. package/templates/.claude/settings.json +9 -0
  29. package/templates/.github/workflows/ci.yml +2 -0
  30. package/templates/CLAUDE.md +5 -0
  31. package/templates/apps/mcp/CLAUDE.md +8 -4
  32. package/templates/apps/web/.env.example +4 -2
  33. package/templates/apps/web/CLAUDE.md +16 -5
  34. package/templates/apps/web/app/[locale]/[slug]/[detail]/__tests__/page.test.tsx +153 -4
  35. package/templates/apps/web/app/[locale]/[slug]/[detail]/page.tsx +45 -2
  36. package/templates/apps/web/app/[locale]/contact/__tests__/site-contact-lines.test.ts +49 -0
  37. package/templates/apps/web/app/[locale]/contact/site-contact-lines.ts +6 -4
  38. package/templates/apps/web/app/[locale]/events/[slug]/page.tsx +13 -1
  39. package/templates/apps/web/app/[locale]/events/__tests__/program-event.test.tsx +120 -0
  40. package/templates/apps/web/app/[locale]/events/page.tsx +11 -0
  41. package/templates/apps/web/app/[locale]/flows/[slug]/FlowStepperClient.tsx +44 -5
  42. package/templates/apps/web/app/[locale]/flows/[slug]/__tests__/FlowStepperClient.thanks-by-tier.test.tsx +306 -0
  43. package/templates/apps/web/app/[locale]/layout.tsx +122 -27
  44. package/templates/apps/web/app/[locale]/thanks/[slug]/__tests__/page.test.tsx +21 -2
  45. package/templates/apps/web/app/[locale]/thanks/[slug]/page.tsx +1 -0
  46. package/templates/apps/web/app/llms.txt/route.ts +16 -9
  47. package/templates/apps/web/app/sitemap.xml/route.ts +3 -0
  48. package/templates/apps/web/jest.config.cjs +46 -1
  49. package/templates/apps/web/lib/__tests__/bot-check.test.ts +247 -4
  50. package/templates/apps/web/lib/__tests__/in-page-magnet-self-request.test.ts +92 -0
  51. package/templates/apps/web/lib/__tests__/page-chrome.test.ts +90 -0
  52. package/templates/apps/web/lib/__tests__/relay.test.ts +33 -8
  53. package/templates/apps/web/lib/__tests__/site-identity-feature-list.test.ts +196 -0
  54. package/templates/apps/web/lib/__tests__/structured-data.test.tsx +11 -1
  55. package/templates/apps/web/lib/bot-check.ts +113 -13
  56. package/templates/apps/web/lib/in-page-magnet.ts +4 -1
  57. package/templates/apps/web/lib/page-chrome.ts +65 -5
  58. package/templates/apps/web/lib/relay.ts +22 -3
  59. package/templates/apps/web/lib/site-identity.ts +75 -24
  60. package/templates/apps/web/messages/en.json +5 -1
  61. package/templates/apps/web/next.config.mjs +15 -1
  62. package/templates/apps/web/package.json +1 -1
  63. package/templates/apps/web/scripts/assert-content-bundled.ts +36 -8
  64. package/templates/apps/web/scripts/content-snapshot.ts +33 -0
  65. package/templates/package.json +2 -1
  66. package/templates/tools/check-content-source.ts +108 -0
  67. package/versions.json +48 -48
@@ -15,13 +15,13 @@ The component inventory of `@working-theory/ui` and the shells of `@working-theo
15
15
  3. Check the Primitives, Panels and Shells tables too — a low-level control, a collapsible layout container, or a route-level frame lives there, not under Sections or Blocks.
16
16
  4. Only then build. If you do build, you are declaring the capability is genuinely absent.
17
17
 
18
- Inventory: **321 component files** across 16 tiers — 57 sections, 107 blocks, 19 chrome, 79 primitives, 6 panels, 8 shells, 5 docs-blog-blocks, 1 docs-blog-chrome, 2 docs-blog-shells, 2 scheduling-sections, 6 scheduling-blocks, 5 signal-collectors-sections, 9 signal-collectors-blocks, 2 commerce-sections, 12 commerce-blocks, 1 commerce-panels. 318 distinct names: `SchedulerEmbed`, `Separator`, `Tabs` exist in more than one tier as genuinely different components, listed once per tier with its own source path.
18
+ Inventory: **324 component files** across 16 tiers — 60 sections, 107 blocks, 19 chrome, 79 primitives, 6 panels, 8 shells, 5 docs-blog-blocks, 1 docs-blog-chrome, 2 docs-blog-shells, 2 scheduling-sections, 6 scheduling-blocks, 5 signal-collectors-sections, 9 signal-collectors-blocks, 2 commerce-sections, 12 commerce-blocks, 1 commerce-panels. 321 distinct names: `SchedulerEmbed`, `Separator`, `Tabs` exist in more than one tier as genuinely different components, listed once per tier with its own source path.
19
19
 
20
- Description coverage: **317/321** components carry a capability description derived from source — 44 from the section registry's hand-authored when-to-use text, 238 from the component's own doc-comment header, 35 from its tier barrel's roster line. **4** have none; they are listed under [Undescribed components](#undescribed-components) rather than given a sentence invented from their name.
20
+ Description coverage: **320/324** components carry a capability description derived from source — 45 from the section registry's hand-authored when-to-use text, 240 from the component's own doc-comment header, 35 from its tier barrel's roster line. **4** have none; they are listed under [Undescribed components](#undescribed-components) rather than given a sentence invented from their name.
21
21
 
22
- Of those, **283** are *strong* — a capability sentence you could match a requirement against — and **34** are *weak*: either one short clause, or a long one that spends itself on wiring (client boundaries, class names, which primitives it composes) instead of on what the thing is for. Weak rows are marked `(weak)` and listed in full under [Weak descriptions](#weak-descriptions). Coverage is 99%, but **findable-without-luck coverage is 88%**.
22
+ Of those, **286** are *strong* — a capability sentence you could match a requirement against — and **34** are *weak*: either one short clause, or a long one that spends itself on wiring (client boundaries, class names, which primitives it composes) instead of on what the thing is for. Weak rows are marked `(weak)` and listed in full under [Weak descriptions](#weak-descriptions). Coverage is 99%, but **findable-without-luck coverage is 88%**.
23
23
 
24
- Per tier, strong out of total: **Sections** 56/57 · **Blocks** 99/107 · **Chrome** 10/19 · **Primitives** 64/79 (4 undescribed) · **Panels** 5/6 · **Shells** 7/8 · **Docs + Blog Blocks** 5/5 · **Docs + Blog Chrome** 1/1 · **Docs + Blog Shells** 0/2 · **Scheduling Sections** 2/2 · **Scheduling Blocks** 6/6 · **Signal-Collectors Sections** 5/5 · **Signal-Collectors Blocks** 8/9 · **Commerce Sections** 2/2 · **Commerce Blocks** 12/12 · **Commerce Panels** 1/1. A tier low on this line is a tier where a capability search is most likely to come up empty on something that exists.
24
+ Per tier, strong out of total: **Sections** 59/60 · **Blocks** 99/107 · **Chrome** 10/19 · **Primitives** 64/79 (4 undescribed) · **Panels** 5/6 · **Shells** 7/8 · **Docs + Blog Blocks** 5/5 · **Docs + Blog Chrome** 1/1 · **Docs + Blog Shells** 0/2 · **Scheduling Sections** 2/2 · **Scheduling Blocks** 6/6 · **Signal-Collectors Sections** 5/5 · **Signal-Collectors Blocks** 8/9 · **Commerce Sections** 2/2 · **Commerce Blocks** 12/12 · **Commerce Panels** 1/1. A tier low on this line is a tier where a capability search is most likely to come up empty on something that exists.
25
25
 
26
26
  That grade is **mechanical**. It counts what it can measure — how much a description adds beyond the component's own name, how much of it is spent on wiring, whether the extraction came out damaged — and it cannot tell a fluent paragraph about the wrong subject from a useful one. A person reading these tables will find more rows unusable than the weak count admits. Treat it as a floor on the problem, not a measurement of it, and read the source before concluding a capability is absent.
27
27
 
@@ -53,7 +53,7 @@ The left column is search vocabulary — how a person phrases the need. It is ha
53
53
 
54
54
  ---
55
55
 
56
- ## Sections (57)
56
+ ## Sections (60)
57
57
 
58
58
  Page-level content bands. Most are MDX-driven (authored by `type` in page frontmatter and dispatched by `SectionRenderer`); a few are composed directly in a route.
59
59
 
@@ -61,6 +61,7 @@ Import: `import { X } from "@working-theory/ui/sections";` (or from the `@workin
61
61
 
62
62
  | Component | What it does | How to use it |
63
63
  | --- | --- | --- |
64
+ | `AddToCalendar` | An Event page's "Add to calendar" control. A Server Component of three plain links built by `eventCalendarLinks` in `@working-theory/schema`: Google Calendar and Outlook open in a new tab; Apple Calendar downloads the `.ics`. Rendered directly by `renderPage` for every Event page with a start date (the `BlogPost` precedent of a section a caller renders with typed props), never authored in `sections[]`. | `import { AddToCalendar } from "@working-theory/ui/sections"` |
64
65
  | `AlertSection` | A compact, dismissible in-page alert strip — use for a confirmation or status notice (e.g. a delivery-page 'your download is on its way' message), typically the first section on the page. Bounded to success/info/warning tone; dismissal persists per-visitor. | MDX `type: alert` |
65
66
  | `AlertSectionDismissIsland` | Dismiss state + × button for AlertSection. Mirrors the chrome Banner's dismiss-persistence precedent (`packages/ui/src/chrome/BannerDismissIsland.tsx`): a localStorage-backed dismissed flag, read on mount and written on dismiss, with graceful degradation when localStorage is unavailable (SSR, iframe, quota, etc.). | internal — compose from `packages/ui/src/sections/AlertSectionDismissIsland.tsx` |
66
67
  | `AskAi` | Use above the footer — hands the visitor to a third-party AI seeded with the entity question. Bounded to five measured providers (ChatGPT/Claude/Google AI/Grok/Perplexity), each deep-linked with an authored prompt. The `ticker` variant renders a compact full-width band instead of the default headline/description/provider-row layout. | MDX `type: ask-ai` |
@@ -92,6 +93,7 @@ Import: `import { X } from "@working-theory/ui/sections";` (or from the `@workin
92
93
  | `HeroIntakeContext` | The bounded seam through which a consumer page mounts its own conversational intake as the FIRST surface INSIDE one of Hero's existing gate surfaces (the `lightboxCta` FormLightbox, or the panel variant's form-slot card), with the existing multi-step StepFlow as the in-gate fallback (correcting 's whole-page swap). | `import { HeroIntakeContext } from "@working-theory/ui/sections"` |
93
94
  | `HeroPromptCapture` | The Hero `promptCapture` opt-in's wiring. Composes the two new blocks — `PromptCaptureCard` (the chat-style card) and `SuggestionChipCarousel` (the one-expanded-slot chip row) — and owns the ONE piece of state they share: the typed text. A chip click writes into it; the card renders and edits it; submit carries it onward. | `import { HeroPromptCapture } from "@working-theory/ui/sections"` |
94
95
  | `HeroQuickSearch` | Surfaces the existing ⌘K quick-search as an inline trigger inside the hero CTA cluster (Chassis 2). It does NOT re-roll a search input: it composes the SearchInputTrigger primitive (an input-style control at CTA height) plus the Command palette primitive, so there is ONE search mechanism. The nav's compact 24px ⌘K pill stays as-is — the hero reads as a search INPUT, matching the 44px "Get Started" CTA. | internal — compose from `packages/ui/src/sections/HeroQuickSearch.tsx` |
96
+ | `HeroTypedHeadline` | The centred Hero's `typing` opt-in. The headline, then each subheadline sentence, types on its own line with a caret: solid while letters appear, blinking at every pause and after the last line. THE FULL TEXT IS ALWAYS IN THE DOM: each row renders its typed part and its untyped rest, and only the split point moves. | internal — compose from `packages/ui/src/sections/HeroTypedHeadline.tsx` |
95
97
  | `Items` | A general-purpose card/items grid — use for services, use cases, or any homogeneous list of cards. | MDX `type: items` · `import { Items } from "@working-theory/ui/sections"` |
96
98
  | `ItemsFilterIsland` | A search box and facet selects over an items section's cards. Items (a Server Component) renders every card on the server and wraps each in an `ItemsFilterSlot`; the island only decides which slots show. So every card is in the server HTML, and before hydration (or with no JavaScript) every slot is visible. | internal — compose from `packages/ui/src/sections/ItemsFilterIsland.tsx` |
97
99
  | `ItemsTileLinkIsland` | Makes a whole linked tile card its link's click target. | internal — compose from `packages/ui/src/sections/ItemsTileLinkIsland.tsx` |
@@ -102,6 +104,7 @@ Import: `import { X } from "@working-theory/ui/sections";` (or from the `@workin
102
104
  | `Newsletter` | An email signup form — use for a low-commitment conversion ask. | MDX `type: newsletter` · `import { Newsletter } from "@working-theory/ui/sections"` |
103
105
  | `Objection` | Objection-and-response pairs — use for you-might-think rhetoric that isn't phrased as a question. | MDX `type: objection` |
104
106
  | `Pricing` | A plan comparison — the page's pricing decision surface; six variants cover tables, toggles, single-price offers, and add-on grids. | MDX `type: pricing` · `import { Pricing } from "@working-theory/ui/sections"` |
107
+ | `ProjectListings` | The site's public project listings — each a name linking to /projects/<slug>, newest decided first, at most `limit` (default 6), no logos. Use it on the home to show what the site lists; the rows are filled at render from the site's public project listings, never authored, and with none (or the API unreachable) the section is removed from the page. Author only the eyebrow, headline, description and `limit`. | MDX `type: project-listings` |
105
108
  | `ReportHeader` | The tiny header the chrome-off report reader puts above the document — a back arrow and the report's title, nothing else. Use it ONLY with `document-viewer` on a /reports/<slug> page; it is not a hero and has no description, CTA or media slot. `backHref` is optional: a reader who arrived from an emailed link has nowhere to go back to, and the header renders the title alone rather than a dead control. | MDX `type: report-header` |
106
109
  | `ReportsList` | A listing of reports, each linking to /reports/<slug>. Two variants over one data shape: `cards` (default) is a titled grid with cover, date and a Read link — the home or resources listing; `list` is centered titles with dates and no covers, for a full-bleed slide (set `tone: "on-dark"` on a dark surface). Not for blog posts — that is `blog-list`. | MDX `type: reports-list` |
107
110
  | `SchedulerEmbed` | A declarative third-party booking iframe (e.g. Google Calendar Appointment Schedules) — use for a booking/scheduling moment; sets the provider's cookies on load, so a site classifying it as non-essential may wrap it in a consent gate. | MDX `type: scheduler-embed` · `import { SchedulerEmbed } from "@working-theory/ui/sections"` · `packages/ui/src/sections/SchedulerEmbed.tsx` |
package/dist/cli.js CHANGED
@@ -23,6 +23,14 @@
23
23
  * agent key from AILK_AGENT_KEY, print one line per page and the count
24
24
  * line, exit 1 if any failed (2 on a refusal it names, nothing sent)
25
25
  * (#10207)
26
+ * on an `inventory` subcommand: freeze a site's URLs into
27
+ * .ailk-cutover/inventory.json once and audit each page against its
28
+ * workspace, exit 1 if any prose page fails (2 on a refusal) (#10648)
29
+ * on an `export-content` subcommand: write every published page back to
30
+ * its content file, sections recombined, exit 1 if any failed (#10648)
31
+ * on a `cutover` subcommand: plan — or, with --apply, run — the eight
32
+ * cutover steps, stopping at the first failure, exit 1 (2 on a refusal)
33
+ * (#10648)
26
34
  * on a `sync-routes` subcommand: parse its own argv, run syncRoutesSync(),
27
35
  * print the report, exit (1 iff routes are missing in check mode)
28
36
  * on a `license set` subcommand: write the key into the scaffold, push it
@@ -70,6 +78,9 @@ import { ADOPT_HELP, AdoptRefusedError, adoptSite, formatAdoptReport, parseAdopt
70
78
  import { APPLY_DESIGN_HELP, applyDesign, formatApplyDesignReport, parseApplyDesignArgs, } from "./apply-design.js";
71
79
  import { CLAUDE_MD_CHECK_PATH } from "./claude-md.js";
72
80
  import { buildScaffoldMarkers } from "./copy.js";
81
+ import { CUTOVER_HELP, CutoverRefusedError, cutover, parseCutoverArgs, } from "./cutover.js";
82
+ import { EXPORT_CONTENT_HELP, ExportContentRefusedError, exportContent, parseExportContentArgs, } from "./export-content.js";
83
+ import { INVENTORY_HELP, InventoryRefusedError, inventory, parseInventoryArgs, } from "./inventory.js";
73
84
  import { ImportMdxRefusedError } from "./lib/import-mdx.js";
74
85
  import { assertKeyedOrFreeReleased, assertReleased } from "./lib/module-license-gate.js";
75
86
  import { withNpmTokenUserConfig } from "./lib/npm-auth.js";
@@ -351,6 +362,94 @@ async function main() {
351
362
  process.exit(2);
352
363
  }
353
364
  }
365
+ // `inventory [dir]` (#10648): freeze a site's URL set and audit each page
366
+ // against its workspace.
367
+ if (argv[0] === "inventory") {
368
+ try {
369
+ const args = parseInventoryArgs(argv.slice(1));
370
+ if (args.help) {
371
+ process.stdout.write(INVENTORY_HELP);
372
+ process.exit(0);
373
+ }
374
+ const { exitCode } = await inventory({
375
+ targetDir: args.target ?? process.cwd(),
376
+ origin: args.origin,
377
+ refreeze: args.refreeze,
378
+ docsPath: args.docsPath,
379
+ apiUrl: args.apiUrl,
380
+ workspaceId: args.workspaceId,
381
+ onLine: (line) => process.stdout.write(`${line}\n`),
382
+ });
383
+ process.exit(exitCode);
384
+ }
385
+ catch (err) {
386
+ if (!(err instanceof ParseError) && !(err instanceof InventoryRefusedError)) {
387
+ throw err;
388
+ }
389
+ process.stderr.write(`[create-ailk] ${err.message}\n`);
390
+ process.exit(2);
391
+ }
392
+ }
393
+ // `export-content [dir]` (#10648): the cutover's rollback — every published
394
+ // page back to its content file.
395
+ if (argv[0] === "export-content") {
396
+ try {
397
+ const args = parseExportContentArgs(argv.slice(1));
398
+ if (args.help) {
399
+ process.stdout.write(EXPORT_CONTENT_HELP);
400
+ process.exit(0);
401
+ }
402
+ const { exitCode } = await exportContent({
403
+ targetDir: args.target ?? process.cwd(),
404
+ dryRun: args.dryRun,
405
+ apiUrl: args.apiUrl,
406
+ workspaceId: args.workspaceId,
407
+ onLine: (line) => process.stdout.write(`${line}\n`),
408
+ });
409
+ process.exit(exitCode);
410
+ }
411
+ catch (err) {
412
+ if (!(err instanceof ParseError) && !(err instanceof ExportContentRefusedError)) {
413
+ throw err;
414
+ }
415
+ process.stderr.write(`[create-ailk] ${err.message}\n`);
416
+ process.exit(2);
417
+ }
418
+ }
419
+ // `cutover [dir]` (#10648): the eight cutover steps, a dry run by default.
420
+ if (argv[0] === "cutover") {
421
+ try {
422
+ const args = parseCutoverArgs(argv.slice(1));
423
+ if (args.help) {
424
+ process.stdout.write(CUTOVER_HELP);
425
+ process.exit(0);
426
+ }
427
+ const { exitCode } = await cutover({
428
+ targetDir: args.target ?? process.cwd(),
429
+ before: args.before,
430
+ after: args.after,
431
+ apply: args.apply,
432
+ ...(args.envTarget !== undefined && { envTarget: args.envTarget }),
433
+ docsPath: args.docsPath,
434
+ ...(args.pixelTolerance !== undefined && { pixelTolerance: args.pixelTolerance }),
435
+ apiUrl: args.apiUrl,
436
+ workspaceId: args.workspaceId,
437
+ onLine: (line) => process.stdout.write(`${line}\n`),
438
+ });
439
+ process.exit(exitCode);
440
+ }
441
+ catch (err) {
442
+ if (!(err instanceof ParseError) &&
443
+ !(err instanceof CutoverRefusedError) &&
444
+ !(err instanceof InventoryRefusedError) &&
445
+ !(err instanceof MigrateContentRefusedError) &&
446
+ !(err instanceof ImportMdxRefusedError)) {
447
+ throw err;
448
+ }
449
+ process.stderr.write(`[create-ailk] ${err.message}\n`);
450
+ process.exit(2);
451
+ }
452
+ }
354
453
  // Subcommand dispatch, ahead of the flag parser: `sync-routes` has its own
355
454
  // argv shape (#4986). Check mode exits 1 iff a template route is missing;
356
455
  // locally modified routes are reported but do not fail; --apply exits 0.
@@ -0,0 +1,109 @@
1
+ import { type WordsCodec, type WorkspaceRow } from "./inventory.js";
2
+ import { type LanguageImportResult } from "./language-import.js";
3
+ import { type ImportMdxOutcome, type ImportMdxResult, type MdxAdapterModule } from "./lib/import-mdx.js";
4
+ export declare const CUTOVER_STEPS: readonly ["import", "publish", "inventory", "comparison", "set-env", "deploy", "verify", "remove-files"];
5
+ export type CutoverStep = (typeof CUTOVER_STEPS)[number];
6
+ export declare const ENV_TARGETS: readonly ["preview", "production"];
7
+ export type EnvTarget = (typeof ENV_TARGETS)[number];
8
+ /** A refusal the CLI exits 2 on, raised before step 1. */
9
+ export declare class CutoverRefusedError extends Error {
10
+ readonly exitCode = 2;
11
+ constructor(message: string);
12
+ }
13
+ /** A spawned command, no shell: its status and captured stdout. */
14
+ export type Runner = (command: string, args: string[], options: {
15
+ cwd: string;
16
+ input?: string;
17
+ }) => {
18
+ status: number | null;
19
+ stdout: string;
20
+ error?: string;
21
+ };
22
+ /** The default runner: `spawnSync` with no shell, stdout piped, stderr shown. */
23
+ export declare const spawnRunner: Runner;
24
+ /** Every step's collaborator; a test injects fakes, the CLI takes the defaults. */
25
+ export interface CutoverDeps {
26
+ migrate: (o: {
27
+ apply: boolean;
28
+ }) => Promise<{
29
+ result: ImportMdxResult;
30
+ exitCode: 0 | 1;
31
+ language?: LanguageImportResult;
32
+ }>;
33
+ list: () => Promise<WorkspaceRow[]>;
34
+ inventory: () => Promise<{
35
+ exitCode: 0 | 1;
36
+ summary: string;
37
+ }>;
38
+ /** The frozen page paths compared, and every frozen path swept. */
39
+ frozen: () => {
40
+ pages: string[];
41
+ sweep: string[];
42
+ } | undefined;
43
+ compare: (o: {
44
+ after: string;
45
+ pages: string[];
46
+ sweep: string[];
47
+ screenshots: boolean;
48
+ dir: string;
49
+ }) => Promise<{
50
+ pass: boolean;
51
+ summary: string;
52
+ }>;
53
+ run: Runner;
54
+ git: Runner;
55
+ }
56
+ export interface CutoverOptions {
57
+ targetDir: string;
58
+ /** The site served from files. */
59
+ before?: string | undefined;
60
+ /** A deployment reading the database. */
61
+ after?: string | undefined;
62
+ apply?: boolean;
63
+ envTarget?: EnvTarget;
64
+ docsPath?: string | undefined;
65
+ /** The share of pixels that may differ; default 0. */
66
+ pixelTolerance?: number;
67
+ apiUrl?: string | undefined;
68
+ workspaceId?: string | undefined;
69
+ env?: NodeJS.ProcessEnv;
70
+ fetch?: typeof globalThis.fetch;
71
+ /** Injectable for tests; defaults to the site's installed adapter. */
72
+ adapters?: MdxAdapterModule;
73
+ /** Injectable for tests; defaults to the site's installed codec. */
74
+ codec?: WordsCodec;
75
+ /** Injectable for tests: names the run's directory. */
76
+ now?: () => Date;
77
+ deps?: Partial<CutoverDeps>;
78
+ onLine?: (line: string) => void;
79
+ }
80
+ export interface StepRecord {
81
+ n: number;
82
+ name: CutoverStep;
83
+ status: "passed" | "failed" | "not-run" | "planned";
84
+ command: string;
85
+ evidence: string[];
86
+ }
87
+ /**
88
+ * Step 8's paths: each imported file under the root it came from, repo-relative
89
+ * (D8). An outcome's path is relative to the content root the importer walked.
90
+ */
91
+ export declare function removablePaths(pages: readonly ImportMdxOutcome[]): string[];
92
+ export declare function cutover(opts: CutoverOptions): Promise<{
93
+ exitCode: 0 | 1;
94
+ steps: StepRecord[];
95
+ }>;
96
+ export interface CutoverArgs {
97
+ target: string | undefined;
98
+ before: string | undefined;
99
+ after: string | undefined;
100
+ apply: boolean;
101
+ envTarget: EnvTarget | undefined;
102
+ docsPath: string | undefined;
103
+ pixelTolerance: number | undefined;
104
+ apiUrl: string | undefined;
105
+ workspaceId: string | undefined;
106
+ help: boolean;
107
+ }
108
+ export declare const CUTOVER_HELP = "create-ailk cutover \u2014 move a site from its page files to its database, one checked step at a time.\n\nUsage:\n AILK_AGENT_KEY=<key> create-ailk cutover [<dir>] --before <origin> --after <origin> [--apply] [--env-target preview|production] [--docs-path <path>] [--pixel-tolerance <ratio>] [--api-url <origin>] [--workspace <wksp_id>]\n\nArguments:\n <dir> The site. Defaults to the current dir.\n\nOptions:\n --before <origin> The site served from its files.\n --after <origin> A deployment reading the database (a preview\n with CONTENT_ADAPTER=database).\n --apply Run the steps. Without it the command plans\n them: it sends only reads, spawns nothing and\n writes nothing.\n --env-target <target> preview (the default) or production \u2014 where\n step 5 sets CONTENT_ADAPTER and step 6 deploys.\n --docs-path <path> Where the site serves its docs. Default /docs.\n --pixel-tolerance <ratio> The share of a screenshot's pixels that may\n differ. Default 0.\n --api-url <origin> The API origin that holds the workspace \u2014 on a\n spoke, its hub's. Else AILK_API_URL.\n --workspace <wksp_id> The workspace. Else AILK_WORKSPACE_ID.\n -h, --help Show this help.\n\nSteps, in this order; the first that fails stops the run:\n 1 import create-ailk migrate-content <dir> --apply; stops, before\n writing, when the import would change a page's render\n 2 publish every imported page is published (publish in Admin)\n 3 inventory create-ailk inventory <dir> --origin <before>; audit passes\n 4 comparison text, JSON-LD and screenshots per frozen page, before and\n after, at 1440 and 390 wide; every frozen URL answers 200\n 5 set env vercel env add CONTENT_ADAPTER <target> = database (apps/web)\n 6 deploy vercel deploy [--prod] (apps/web)\n 7 verify text and JSON-LD per frozen page on the deployment; 200s\n 8 remove files git rm --quiet -- <the imported files> (staged, not committed)\n\nEach run writes .ailk-cutover/runs/<UTC time>/run.json and the comparison's\nevidence beside it (gitignored). The comparison needs Playwright's Chromium:\npnpm add -D @playwright/test && pnpm exec playwright install chromium. When VERCEL_AUTOMATION_BYPASS_SECRET is set,\nrequests to --after and the deployment carry it. The agent key needs the\ncontent-edit grant; on a spoke, AILK_SPOKE_KEY is needed too. The rollback\nis create-ailk export-content (docs/internal/runbooks/site-cutover.md).\nExit 0 when every step passed (a dry run: when the import plan has no\nfailed page), 1 when one failed, 2 on a refusal.\n";
109
+ export declare function parseCutoverArgs(argv: string[]): CutoverArgs;