zz-meridian 0.36.0 → 0.37.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -4
- package/dist/adopt.js +39 -27
- package/dist/cli.js +14 -2
- package/dist/create.js +1 -1
- package/dist/files.js +22 -5
- package/dist/migrations.js +104 -32
- package/dist/reconcile.js +5 -0
- package/dist/session.js +2 -2
- package/dist/update-session.js +11 -4
- package/package.json +1 -1
- package/payload/CHANGELOG.md +127 -0
- package/payload/app/(dashboard)/(home)/README.md +2 -0
- package/payload/app/(dashboard)/(home)/page.tsx +1 -1
- package/payload/app/(dashboard)/business/page.tsx +1 -1
- package/payload/app/(dashboard)/error.tsx +4 -3
- package/payload/app/(dashboard)/layout.tsx +4 -4
- package/payload/app/(dashboard)/operations-tickets/README.md +1 -0
- package/payload/app/(dashboard)/requests/[id]/README.md +9 -1
- package/payload/app/(dashboard)/requests/[id]/actions.ts +7 -2
- package/payload/app/(dashboard)/settings/README.md +2 -0
- package/payload/app/(dashboard)/settings/actions.ts +1 -1
- package/payload/app/(dashboard)/usage/page.tsx +1 -1
- package/payload/app/api/export/requests/route.ts +2 -2
- package/payload/app/embed/now/README.md +1 -0
- package/payload/app/embed/now/view.tsx +3 -2
- package/payload/app/embed/requests/README.md +1 -1
- package/payload/app/embed/requests/view.tsx +1 -0
- package/payload/app/layout.tsx +5 -1
- package/payload/app/sign-in/README.md +4 -1
- package/payload/app/sign-in/actions.ts +1 -1
- package/payload/app/sign-in/panel.tsx +15 -1
- package/payload/app/system/(atlas)/[section]/[id]/page.tsx +8 -2
- package/payload/app/system/(atlas)/_article.tsx +28 -3
- package/payload/app/system/preview/[section]/[card]/page.tsx +2 -1
- package/payload/app/system/preview/[section]/[card]/stage.tsx +3 -2
- package/payload/docs/assistant.md +17 -4
- package/payload/docs/atlas-scenario-coverage.md +8 -8
- package/payload/docs/data-layers.md +21 -11
- package/payload/docs/distribution.md +17 -8
- package/payload/docs/start-a-dashboard.md +2 -0
- package/payload/docs/surfaces.md +5 -1
- package/payload/docs/testing.md +24 -0
- package/payload/journeys/workspaces.ts +7 -7
- package/payload/package.json +1 -1
- package/payload/scripts/audit.ts +7 -6
- package/payload/scripts/brand.ts +23 -14
- package/payload/scripts/contrast.ts +1 -1
- package/payload/scripts/interactions.ts +6 -2
- package/payload/scripts/keyboard.ts +8 -5
- package/payload/scripts/lib/answered.ts +1 -3
- package/payload/scripts/lib/chrome.ts +15 -12
- package/payload/scripts/lib/coverage.ts +19 -1
- package/payload/scripts/lib/data-url.ts +60 -42
- package/payload/scripts/lib/effective-manifest.ts +19 -0
- package/payload/scripts/lib/journey.ts +6 -13
- package/payload/scripts/lib/layer-rules.ts +17 -4
- package/payload/scripts/lib/routes.ts +4 -1
- package/payload/scripts/lib/update-session.ts +9 -4
- package/payload/scripts/live.ts +24 -13
- package/payload/scripts/navigate.ts +1 -1
- package/payload/scripts/shot.ts +1 -1
- package/payload/scripts/tokens.ts +23 -8
- package/payload/scripts/verify.ts +9 -5
- package/payload/skills/zz-meridian/references/cache.md +19 -13
- package/payload/skills/zz-meridian/references/customize.md +13 -8
- package/payload/skills/zz-meridian/references/data-layers.md +21 -11
- package/payload/skills/zz-meridian/references/live.md +6 -2
- package/payload/skills/zz-meridian/references/update.md +7 -3
- package/payload/skills/zz-meridian/references/validation.md +3 -1
- package/payload/src/components/base/providers.tsx +5 -2
- package/payload/src/components/base/shell/README.md +3 -1
- package/payload/src/components/base/shell/index.tsx +9 -4
- package/payload/src/components/base/shell/leave-question.tsx +1 -1
- package/payload/src/components/base/shell/preview.tsx +4 -2
- package/payload/src/components/base/surface/index.tsx +14 -5
- package/payload/src/components/charts/meridian/README.md +2 -1
- package/payload/src/components/charts/meridian/index.tsx +15 -3
- package/payload/src/components/patterns/activity-feed/README.md +1 -1
- package/payload/src/components/patterns/activity-feed/index.tsx +2 -1
- package/payload/src/components/patterns/appearance-menu/README.md +4 -4
- package/payload/src/components/patterns/appearance-menu/index.tsx +7 -17
- package/payload/src/components/patterns/appearance-menu/preview.tsx +10 -9
- package/payload/src/components/patterns/ask-about/index.tsx +5 -4
- package/payload/src/components/patterns/assistant/index.tsx +8 -5
- package/payload/src/components/patterns/assistant/preview.tsx +4 -3
- package/payload/src/components/patterns/command-palette/README.md +7 -1
- package/payload/src/components/patterns/command-palette/index.tsx +4 -19
- package/payload/src/components/patterns/command-palette/open.ts +36 -0
- package/payload/src/components/patterns/data-table/README.md +1 -0
- package/payload/src/components/patterns/data-table/index.tsx +11 -7
- package/payload/src/components/patterns/export-button/README.md +7 -5
- package/payload/src/components/patterns/export-button/index.tsx +10 -5
- package/payload/src/components/patterns/export-button/preview.tsx +4 -3
- package/payload/src/components/patterns/form-section/README.md +4 -4
- package/payload/src/components/patterns/form-section/index.tsx +4 -4
- package/payload/src/components/patterns/proposal/README.md +6 -5
- package/payload/src/components/patterns/proposal/index.tsx +8 -7
- package/payload/src/components/patterns/rail/README.md +2 -2
- package/payload/src/components/patterns/rail/index.tsx +6 -4
- package/payload/src/components/patterns/shell-tools/index.tsx +1 -1
- package/payload/src/components/ui/avatar/README.md +2 -0
- package/payload/src/components/ui/avatar/index.tsx +9 -5
- package/payload/src/components/ui/button/README.md +3 -0
- package/payload/src/components/ui/button/index.tsx +31 -15
- package/payload/src/components/ui/copy-field/README.md +2 -0
- package/payload/src/components/ui/copy-field/index.tsx +28 -18
- package/payload/src/components/ui/dialog/index.tsx +1 -1
- package/payload/src/components/ui/disclosure/README.md +1 -1
- package/payload/src/components/ui/key-value/README.md +1 -1
- package/payload/src/components/ui/menu/README.md +1 -0
- package/payload/src/components/ui/menu/index.tsx +1 -1
- package/payload/src/components/ui/pagination/README.md +2 -2
- package/payload/src/components/ui/pagination/index.tsx +3 -2
- package/payload/src/components/ui/progress/README.md +1 -1
- package/payload/src/components/ui/progress/index.tsx +2 -2
- package/payload/src/components/ui/search-input/README.md +2 -0
- package/payload/src/components/ui/search-input/index.tsx +3 -2
- package/payload/src/components/ui/segmented/README.md +1 -1
- package/payload/src/components/ui/segmented/index.tsx +1 -1
- package/payload/src/data/README.md +2 -0
- package/payload/src/data/access.ts +1 -2
- package/payload/src/data/derived.ts +15 -13
- package/payload/src/data/read.ts +8 -5
- package/payload/src/lib/assistant/prompt.ts +10 -7
- package/payload/src/lib/assistant/tools.ts +35 -19
- package/payload/src/lib/collection.ts +1 -1
- package/payload/src/lib/csv.ts +8 -2
- package/payload/src/lib/live.ts +2 -1
- package/payload/src/logic/home-context.ts +3 -3
- package/payload/src/logic/home-parts.ts +21 -20
- package/payload/src/logic/request-context.ts +2 -2
- package/payload/src/logic/revenue-context.ts +9 -5
- package/payload/src/logic/revenue-parts.ts +10 -8
- package/payload/src/styles/base.css +20 -4
- package/payload/src/styles/fonts/Geist-Variable.woff2 +0 -0
- package/payload/src/styles/fonts/GeistMono-Variable.woff2 +0 -0
- package/payload/src/styles/tokens.css +35 -5
- package/payload/src/system/atlas-shell.tsx +4 -3
- package/payload/src/system/card-stage.tsx +2 -1
- package/payload/src/system/fixtures/sample-records.ts +6 -6
- package/payload/src/system/preview-loading.tsx +7 -0
- package/payload/src/system/sample-cells.tsx +13 -3
- package/payload/src/views/console-chrome.tsx +6 -1
- package/payload/src/views/console-live.tsx +3 -2
- package/payload/src/views/home.tsx +1 -1
- package/payload/src/views/members.tsx +16 -10
- package/payload/src/views/not-found-address.tsx +1 -1
- package/payload/src/views/period-actions.tsx +1 -1
- package/payload/src/views/request.tsx +17 -12
- package/payload/src/views/revenue.tsx +8 -8
- package/payload/src/views/settings.tsx +3 -3
- package/payload/src/views/tickets.tsx +17 -3
- package/payload/tests/activity-feed-link.test.tsx +1 -2
- package/payload/tests/agent-page-spec.test.ts +2 -3
- package/payload/tests/agents.test.tsx +0 -11
- package/payload/tests/appearance-menu-keyboard.test.tsx +29 -0
- package/payload/tests/assistant-access.test.ts +0 -14
- package/payload/tests/assistant-column-draft.test.tsx +8 -1
- package/payload/tests/assistant-explore-tool.test.ts +3 -19
- package/payload/tests/assistant-guard.test.ts +114 -1
- package/payload/tests/assistant-prompt-truth.test.ts +5 -11
- package/payload/tests/assistant-proposal.test.tsx +39 -4
- package/payload/tests/assistant-questions.test.tsx +0 -1
- package/payload/tests/assistant-safety.test.ts +80 -2
- package/payload/tests/assistant-thread.test.tsx +0 -1
- package/payload/tests/assistant-tools.test.ts +2 -4
- package/payload/tests/assistant-withheld.test.ts +1 -10
- package/payload/tests/atlas-layers.test.ts +1 -40
- package/payload/tests/atlas-product-slice.test.tsx +1 -26
- package/payload/tests/atlas-shell-early-press.test.tsx +24 -0
- package/payload/tests/atlas-template-contracts.test.tsx +2 -69
- package/payload/tests/avatar-initials.test.tsx +11 -0
- package/payload/tests/banner-preview.test.tsx +0 -13
- package/payload/tests/bar-list-forms.test.tsx +2 -8
- package/payload/tests/brand-config.test.tsx +1 -4
- package/payload/tests/button-busy.test.tsx +45 -0
- package/payload/tests/card-header-title.test.tsx +0 -14
- package/payload/tests/card-stage-loading.test.tsx +40 -0
- package/payload/tests/check-nav-under.test.ts +0 -7
- package/payload/tests/chrome-navigation.test.tsx +1 -7
- package/payload/tests/client-imports.test.ts +0 -8
- package/payload/tests/column-chart-crowded.test.tsx +0 -7
- package/payload/tests/column-chart-grouped.test.tsx +4 -20
- package/payload/tests/column-chart-labels.test.tsx +4 -42
- package/payload/tests/column-chart-no-data.test.tsx +0 -4
- package/payload/tests/column-chart-readout.test.tsx +0 -3
- package/payload/tests/column-chart-running.test.tsx +0 -21
- package/payload/tests/column-chart-signed.test.tsx +1 -4
- package/payload/tests/column-chart.test.tsx +2 -8
- package/payload/tests/composition-bar.test.tsx +0 -8
- package/payload/tests/console-live.test.tsx +4 -0
- package/payload/tests/copy-field.test.tsx +131 -1
- package/payload/tests/counts-and-lists.test.tsx +1 -2
- package/payload/tests/csv-bom.test.ts +27 -0
- package/payload/tests/customer-figures.test.ts +0 -13
- package/payload/tests/customers-masthead.test.tsx +0 -1
- package/payload/tests/data-read.test.ts +3 -4
- package/payload/tests/data-table.test.tsx +8 -9
- package/payload/tests/day-in-progress.test.ts +0 -1
- package/payload/tests/derivations.test.ts +0 -27
- package/payload/tests/derived-answer.test.ts +178 -19
- package/payload/tests/disclosure.test.tsx +2 -3
- package/payload/tests/diverging-bars.test.tsx +1 -6
- package/payload/tests/effective-manifest.test.ts +56 -0
- package/payload/tests/embed-context-lifetime.test.tsx +79 -0
- package/payload/tests/embed-page-failure.test.tsx +1 -8
- package/payload/tests/embed-requests-provenance.test.tsx +25 -1
- package/payload/tests/embed-theme.test.tsx +62 -0
- package/payload/tests/engineering-workflows.test.ts +4 -53
- package/payload/tests/explore-answer-length.test.ts +0 -16
- package/payload/tests/export-button.test.tsx +45 -0
- package/payload/tests/facts-engineering.test.ts +1 -12
- package/payload/tests/facts.test.ts +1 -18
- package/payload/tests/feature-access.test.ts +1 -7
- package/payload/tests/feature-definitions.test.ts +1 -21
- package/payload/tests/featured-metric.test.tsx +1 -24
- package/payload/tests/filter-bar-search.test.tsx +0 -18
- package/payload/tests/form-section-actions.test.tsx +0 -3
- package/payload/tests/format-semantics.test.ts +1 -3
- package/payload/tests/heatmap-states.test.tsx +1 -37
- package/payload/tests/honest-controls.test.tsx +0 -4
- package/payload/tests/incident-card-hyphens.test.tsx +3 -18
- package/payload/tests/input-states.test.tsx +0 -4
- package/payload/tests/key-value.test.tsx +2 -36
- package/payload/tests/linked-records.test.ts +1 -2
- package/payload/tests/live-client.test.tsx +65 -0
- package/payload/tests/loading-routes.test.tsx +1 -10
- package/payload/tests/member-mutations.test.ts +2 -5
- package/payload/tests/member-roles.test.ts +0 -11
- package/payload/tests/members-optimistic.test.tsx +13 -23
- package/payload/tests/meridian.test.tsx +25 -2
- package/payload/tests/metric-tile-compact.test.tsx +0 -38
- package/payload/tests/metric-tile-delta-format.test.tsx +1 -2
- package/payload/tests/metric-tile-note.test.tsx +1 -15
- package/payload/tests/metric-tile-title.test.tsx +0 -1
- package/payload/tests/metric-tile.test.tsx +2 -17
- package/payload/tests/no-access.test.tsx +1 -3
- package/payload/tests/page-frame-title.test.tsx +0 -92
- package/payload/tests/page-loading.test.tsx +65 -44
- package/payload/tests/page-server-module.test.ts +10 -1
- package/payload/tests/pagination-sizes.test.tsx +16 -0
- package/payload/tests/palette-hint.test.tsx +0 -8
- package/payload/tests/palette-open.test.ts +24 -0
- package/payload/tests/popover-name.test.tsx +1 -2
- package/payload/tests/progress-values.test.tsx +5 -3
- package/payload/tests/proposal-approval.test.tsx +31 -0
- package/payload/tests/prose-cell-labels.test.tsx +0 -8
- package/payload/tests/rail-branch-feel.test.tsx +3 -71
- package/payload/tests/rail-branch.test.tsx +17 -23
- package/payload/tests/read-all-query.test.ts +57 -7
- package/payload/tests/records-consumers.test.tsx +9 -77
- package/payload/tests/refusal-descriptor.test.ts +0 -11
- package/payload/tests/release-phone-cell.test.ts +0 -3
- package/payload/tests/release-wording.test.ts +2 -7
- package/payload/tests/request-curl.test.ts +97 -16
- package/payload/tests/request-replay.test.ts +12 -1
- package/payload/tests/request-usage.test.tsx +2 -2
- package/payload/tests/request-view-replay.test.tsx +36 -28
- package/payload/tests/review-held-scales.test.tsx +1 -32
- package/payload/tests/roles-context.test.ts +0 -1
- package/payload/tests/roles-matrix.test.tsx +0 -2
- package/payload/tests/script-args.test.ts +8 -0
- package/payload/tests/search-input.test.tsx +54 -0
- package/payload/tests/segmented-measure.test.tsx +23 -0
- package/payload/tests/settings-access.test.tsx +1 -4
- package/payload/tests/settings-agents.test.tsx +0 -18
- package/payload/tests/settings-save.test.tsx +1 -15
- package/payload/tests/shared-context.test.tsx +35 -3
- package/payload/tests/shell-alerts.test.ts +0 -10
- package/payload/tests/shell-promise.test.tsx +1 -8
- package/payload/tests/shell-search-name.test.tsx +0 -13
- package/payload/tests/sign-in-honest.test.tsx +19 -11
- package/payload/tests/sign-in-persona.test.ts +14 -1
- package/payload/tests/table-cut-cell.test.tsx +13 -40
- package/payload/tests/table-grid.test.tsx +2 -57
- package/payload/tests/template-consumers.test.tsx +0 -4
- package/payload/tests/tickets-view.test.tsx +53 -4
- package/payload/tests/tile-row.test.tsx +29 -0
- package/payload/tests/trend-chart-band.test.tsx +1 -3
- package/payload/tests/trend-chart-forms.test.tsx +1 -2
- package/payload/tests/update-session.test.ts +19 -2
- package/payload/tests/uptime-bars-end.test.tsx +1 -1
- package/payload/tests/uptime-edges.test.tsx +0 -7
- package/payload/tests/usage-figures.test.ts +0 -11
- package/payload/tests/verify-coverage.test.ts +10 -0
- package/payload/tests/verify-data-url.test.ts +76 -15
- package/payload/tests/verify-explore-surfaces.test.ts +10 -7
- package/payload/tests/verify-loopback.test.ts +0 -11
- package/payload/tests/verify-personas.test.ts +0 -21
- package/payload/tests/verify-sample-surfaces.test.ts +0 -4
- package/payload/tests/verify-sizes.test.ts +1 -5
- package/payload/tests/verify-suite-outcome.test.ts +12 -1
- package/payload/tests/verify-timing.test.ts +1 -2
- package/payload/tests/view-as.test.ts +1 -6
- package/payload/tests/view-tool-modules.test.ts +1 -2
- package/payload/tests/view-tool-needs.test.ts +0 -11
- package/payload/tests/workspace-access.test.ts +20 -3
- package/payload/tests/workspace-chrome.test.tsx +1 -13
- package/payload/tests/workspace-data-paths.test.ts +0 -13
- package/payload/tests/workspace-home.test.tsx +0 -9
- package/payload/tests/workspaces.test.ts +1 -27
- package/payload/tests/bar-list-axis-words.test.tsx +0 -42
- package/payload/tests/dialog-xl.test.tsx +0 -25
- package/payload/tests/error-retry.test.tsx +0 -26
- package/payload/tests/feel-bar-list.test.tsx +0 -22
- package/payload/tests/filter-bar-view.test.tsx +0 -20
- package/payload/tests/forced-colors-states.test.ts +0 -62
- package/payload/tests/hatch.test.ts +0 -45
- package/payload/tests/heatmap-names-column.test.tsx +0 -30
- package/payload/tests/segmented-fit.test.tsx +0 -22
- package/payload/tests/select-touch.test.tsx +0 -13
- package/payload/tests/table-cut-align.test.tsx +0 -30
- package/payload/tests/table-hide-below-pad.test.tsx +0 -18
- package/payload/tests/tabs-indicator.test.tsx +0 -18
package/README.md
CHANGED
|
@@ -37,17 +37,26 @@ npx zz-meridian@latest skill [--global] # install only the agent skill
|
|
|
37
37
|
Brand flags: `--name "Acme Ops"`, `--hex '#2E6BE4'` (or `--hue 250 --chroma 0.14`, or `--accent indigo|cobalt|jade|graphite`), `--theme dark|light`,
|
|
38
38
|
`--workspace`, `--timezone`, `--currency`, `--user`, `--role`. `--workspace` sets `app.workspace`, the environment named
|
|
39
39
|
in a page's kicker ("Production"); it does not touch the navigation workspaces.
|
|
40
|
+
`--timezone` must be accepted by `Intl.DateTimeFormat`, for example `UTC` or `Europe/London`; an unsupported zone is refused rather than saved.
|
|
41
|
+
Quoted brand strings remain literal values across repeated rebranding; changing a role does not rewrite the person's existing name.
|
|
42
|
+
Every valued flag needs its own value, including with the standalone `pnpm brand`: missing values and a following flag used as a value are refused before any configuration changes.
|
|
43
|
+
Commands reject extra positional arguments before changing files: only `create` takes a folder argument.
|
|
40
44
|
|
|
41
45
|
**adopt** copies Meridian's tokens, styles, components, gates and scripts into `src/`, `tokens/` and `scripts/`;
|
|
42
46
|
merges the dependencies and scripts it needs into your `package.json`; adds the `@meridian/*` alias to your
|
|
43
47
|
`tsconfig.json` (yours is kept as `tsconfig.before.json` when it carries comments or trailing commas, which JSON
|
|
44
48
|
cannot hold); replaces your global stylesheet (yours is kept
|
|
45
|
-
beside it as `*.before.css`);
|
|
49
|
+
beside it as `*.before.css`); preserves string keys and values in your TypeScript configuration, including punctuation;
|
|
50
|
+
brands it; adds Meridian's managed block to your `AGENTS.md` between two markers, keeping your own text there byte
|
|
46
51
|
for byte; writes an empty `docs/brief.md` (your product, users, data, decisions and glossary) if you have none; installs the skill into
|
|
47
52
|
`.agents/skills/` (Codex) and `.claude/skills/` (Claude Code); records every Meridian file it copied in `.meridian/manifest.json`;
|
|
48
|
-
installs and
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
installs and reports route-generation and TypeScript diagnostics. An installation failure exits nonzero; diagnostic
|
|
54
|
+
failures are reported as `types: fails` but adoption still exits zero, because your existing routes may need repairs.
|
|
55
|
+
This is not a clean-typecheck guarantee: fix those diagnostics before running the gate. Your routes, your data layer
|
|
56
|
+
and your own components are not touched. Meridian's files import each other by relative path, so a
|
|
57
|
+
`components/ui/button` of your own is never confused with Meridian's; your code imports Meridian as `@meridian/…`.
|
|
58
|
+
Supported dependency specifications below Meridian's required floor are raised, including within the same major;
|
|
59
|
+
newer versions are kept. Custom specifications are kept with a note to confirm compatibility.
|
|
51
60
|
|
|
52
61
|
It refuses, and writes nothing, when the git tree has uncommitted changes (so its change is one reviewable diff), when
|
|
53
62
|
the project is not Next.js with the App Router, or when a file it would copy already exists with other content.
|
|
@@ -68,10 +77,12 @@ writes nothing; run it first.
|
|
|
68
77
|
- It reports a migration for each interface the release changed where one of your own files still has the old shape,
|
|
69
78
|
such as `cache-components-config` or `nav-needs`. `cli/src/migrations.ts` holds every one; resolve them as the skill's
|
|
70
79
|
`references/update.md` says.
|
|
80
|
+
- Team-owned sample files are checked again on every forward upgrade, even after their migration's introduction, so newer managed tests do not silently outrun the sample they exercise. They remain team-owned: the migration asks for review and never overwrites them.
|
|
71
81
|
- Everything it did and everything left to do is in `.meridian/update/<version>/MERGE.md`. Resolve the items, then run
|
|
72
82
|
the pinned `npx zz-meridian@<version> update --finalize`; `--resume` continues an interrupted run and `--abort` restores
|
|
73
83
|
what it changed. Only finalize records the new version. `--finalize --verify` validates with the project's default
|
|
74
84
|
`verify` instead of the gate and the build, and reports its coverage line.
|
|
85
|
+
- Failure and coverage metadata is recorded literally in the report, including `$` sequences; it is not interpreted as report-template syntax.
|
|
75
86
|
- It refuses, writing nothing, on a dirty git tree (unless `--allow-dirty`) or while another update is open. `--verbose`
|
|
76
87
|
lists every file.
|
|
77
88
|
|
|
@@ -80,6 +91,9 @@ styles) and `src/app.config.ts` for the new flags and records them in the manife
|
|
|
80
91
|
refuses when one of those outputs was edited, while an update is open, or on a dirty tree (unless `--allow-dirty`). Run
|
|
81
92
|
it with the version the manifest records. The project's own `pnpm brand` still works, but its changes count as your edits.
|
|
82
93
|
|
|
94
|
+
**skill** checks both `.agents/skills/zz-meridian` and `.claude/skills/zz-meridian` before writing either. Identical files are retained; different content or symlinks are refused. Move a customized copy aside, install the shipped skill, then reconcile the customization manually rather than losing it to an overwrite.
|
|
95
|
+
An incomplete package without its `SKILL.md` is refused before either destination is written.
|
|
96
|
+
|
|
83
97
|
The standard a project on Meridian is built to is
|
|
84
98
|
[data-layers.md](https://github.com/zhixuan312/zz-meridian/blob/master/docs/data-layers.md): the four layers between a
|
|
85
99
|
database and the screen, the contract a backend answers, and the six rules a project switches on in its own
|
|
@@ -102,6 +116,14 @@ telemetry. It talks to npm only, when `npx` fetches it and when `update` reads r
|
|
|
102
116
|
public, so leave out anything that identifies you, your organisation or your product, and describe the problem with
|
|
103
117
|
Meridian's own template or sample data.
|
|
104
118
|
|
|
119
|
+
## Maintaining this package
|
|
120
|
+
|
|
121
|
+
From the repository root, run `node cli/scripts/build-payload.ts` before `pnpm exec tsc -p cli/tsconfig.json` and packing. The producer reads committed `HEAD` by default; use `--worktree` to try uncommitted tracked changes. It also generates `cli/src/update-session.ts` from the canonical standalone `scripts/lib/update-session.ts`, using the same source provenance as the payload. Edit only the canonical module, never its generated CLI companion.
|
|
122
|
+
The payload excludes `.env` and its variants at every directory depth, even when tracked; deliberate `.env.example`
|
|
123
|
+
files remain available as public configuration templates. Never put credentials in an example file.
|
|
124
|
+
|
|
125
|
+
The template's copied-command regression runs real cURL on loopback and needs a POSIX shell and `curl`, with no external endpoint or credentials.
|
|
126
|
+
|
|
105
127
|
## License
|
|
106
128
|
|
|
107
129
|
MIT: the text is `payload/LICENSE` in the package and `LICENSE` in a project `create` makes.
|
package/dist/adopt.js
CHANGED
|
@@ -12,9 +12,41 @@ import path from 'node:path';
|
|
|
12
12
|
import { VERSION, brandArgs, effectiveBrand, inside, installArgs, installSkill, jsoncExtras, packageManager, payloadBytes, payloadFiles, readJsonc, readPayload, relativeImports, skillPaths, run, runTool, sha256, writeIn, writeManifest, } from './files.js';
|
|
13
13
|
import { BRIEF_TEMPLATE, managedBlock, upsertManagedBlock } from './context.js';
|
|
14
14
|
import { FIXED, adoptSetOf, managedPaths, parseManifest } from './ownership.js';
|
|
15
|
-
import { MERIDIAN_SCRIPTS, TOOLCHAIN, needOf } from './reconcile.js';
|
|
16
|
-
const major = (spec) => Number(/(\d+)/.exec(spec)?.[1] ?? NaN);
|
|
15
|
+
import { MERIDIAN_SCRIPTS, TOOLCHAIN, compareMinimum, needOf } from './reconcile.js';
|
|
17
16
|
const TS = /\.(ts|tsx)$/;
|
|
17
|
+
/** Raise known dependency floors in place; custom specifications stay the team's decision. */
|
|
18
|
+
export function adoptDependencies(pkg, tpl, field, names) {
|
|
19
|
+
const notes = [];
|
|
20
|
+
pkg[field] ??= {};
|
|
21
|
+
for (const n of names) {
|
|
22
|
+
const want = tpl.dependencies?.[n] ?? tpl.devDependencies?.[n];
|
|
23
|
+
if (!want)
|
|
24
|
+
continue;
|
|
25
|
+
const have = pkg.dependencies?.[n] ?? pkg.devDependencies?.[n];
|
|
26
|
+
if (!have) {
|
|
27
|
+
pkg[field][n] = want;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const order = compareMinimum(have, want);
|
|
31
|
+
if (order === null) {
|
|
32
|
+
notes.push(`kept ${n} ${have}, which Meridian cannot compare: confirm it meets ${want} before running the gate`);
|
|
33
|
+
}
|
|
34
|
+
else if (order < 0) {
|
|
35
|
+
const at = pkg.dependencies?.[n] ? 'dependencies' : 'devDependencies';
|
|
36
|
+
pkg[at][n] = want;
|
|
37
|
+
notes.push(`raised ${n} from ${have} to ${want} (Meridian needs this floor; read its upgrade guide)`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return notes;
|
|
41
|
+
}
|
|
42
|
+
/** Adoption is installed even when a team's existing routes need type repairs; report both diagnostic stages. */
|
|
43
|
+
function adoptionTypes(root) {
|
|
44
|
+
const route = runTool(root, 'next/dist/bin/next', ['typegen']);
|
|
45
|
+
const tsc = runTool(root, 'typescript/bin/tsc', ['--noEmit']);
|
|
46
|
+
const failures = [['next typegen', route], ['tsc', tsc]];
|
|
47
|
+
const failed = failures.filter(([, result]) => result.status !== 0);
|
|
48
|
+
return failed.length ? `fails:\n${failed.map(([name, result]) => `${name}:\n${(result.stdout + result.stderr).trim().split('\n').slice(0, 30).join('\n')}`).join('\n')}` : 'passes';
|
|
49
|
+
}
|
|
18
50
|
/** A specifier inside a copied file, pinned to the exact module so a team file of a similar name can never win. */
|
|
19
51
|
function exactImports(rel, source, known) {
|
|
20
52
|
const pin = (spec) => {
|
|
@@ -267,29 +299,11 @@ Rename or move them, then run adopt again.`);
|
|
|
267
299
|
}
|
|
268
300
|
// ── Merged files: the project's own, changed only where Meridian needs it ────────────────────────────
|
|
269
301
|
const tpl = JSON.parse(readPayload('package.json'));
|
|
270
|
-
const merge = (field, names) => {
|
|
271
|
-
pkg[field] ??= {};
|
|
272
|
-
for (const n of names) {
|
|
273
|
-
const want = tpl.dependencies?.[n] ?? tpl.devDependencies?.[n];
|
|
274
|
-
if (!want)
|
|
275
|
-
continue;
|
|
276
|
-
const have = pkg.dependencies?.[n] ?? pkg.devDependencies?.[n];
|
|
277
|
-
if (!have) {
|
|
278
|
-
pkg[field][n] = want;
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
if (major(have) < major(want)) {
|
|
282
|
-
const at = pkg.dependencies?.[n] ? 'dependencies' : 'devDependencies';
|
|
283
|
-
pkg[at][n] = want;
|
|
284
|
-
notes.push(`raised ${n} from ${have} to ${want} (Meridian needs the newer major; read its upgrade guide)`);
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
};
|
|
288
302
|
// The packages the adopted modules import, and the framework: the rule `update` applies to a created project.
|
|
289
303
|
const sources = new Map(set.filter((rel) => TS.test(rel)).map((rel) => [rel, payloadBytes(rel).toString('utf8')]));
|
|
290
304
|
sources.set('src/app.config.ts', config);
|
|
291
|
-
|
|
292
|
-
|
|
305
|
+
notes.push(...adoptDependencies(pkg, tpl, 'dependencies', needOf(tpl, sources).runtime));
|
|
306
|
+
notes.push(...adoptDependencies(pkg, tpl, 'devDependencies', TOOLCHAIN));
|
|
293
307
|
pkg.scripts ??= {};
|
|
294
308
|
for (const s of MERIDIAN_SCRIPTS) {
|
|
295
309
|
const want = tpl.scripts[s];
|
|
@@ -406,16 +420,14 @@ Rename or move them, then run adopt again.`);
|
|
|
406
420
|
files[rel] = sha256(fs.readFileSync(inside(root, rel)));
|
|
407
421
|
installSkill(root, files);
|
|
408
422
|
writeManifest(root, { version: VERSION, route: 'adopt', brand, files });
|
|
409
|
-
// ── Install and
|
|
423
|
+
// ── Install and report type diagnostics (existing team code may still need repairs) ──────────────────
|
|
410
424
|
let typed = 'not run (--no-install)';
|
|
411
425
|
if (o.install) {
|
|
412
426
|
say(`\nInstalling with ${pm}…`);
|
|
413
427
|
if (run(pm, installArgs(pm), root).status !== 0)
|
|
414
428
|
return fail(`${pm} install failed (above). Fix it, then run ${pm} install and ${pm} run typecheck.`);
|
|
415
|
-
// Route types first (LayoutProps, PageProps), as the gate does.
|
|
416
|
-
|
|
417
|
-
const tsc = runTool(root, 'typescript/bin/tsc', ['--noEmit']);
|
|
418
|
-
typed = tsc.status === 0 ? 'passes' : `fails:\n${(tsc.stdout + tsc.stderr).trim().split('\n').slice(0, 30).join('\n')}`;
|
|
429
|
+
// Route types first (LayoutProps, PageProps), as the gate does; neither failure may be reported as passing.
|
|
430
|
+
typed = adoptionTypes(root);
|
|
419
431
|
}
|
|
420
432
|
say(`
|
|
421
433
|
Meridian ${VERSION} is in ${root}.
|
package/dist/cli.js
CHANGED
|
@@ -37,6 +37,7 @@ const HELP = `zz-meridian ${VERSION}
|
|
|
37
37
|
|
|
38
38
|
Brand flags: --name "Acme Ops" --workspace Production --timezone Europe/London --currency EUR
|
|
39
39
|
--user "Ada Park" --role Admin --accent indigo|cobalt|jade|graphite | --hex '#2E6BE4' | --hue 250 --chroma 0.14
|
|
40
|
+
--theme dark|light
|
|
40
41
|
|
|
41
42
|
Then give your coding agent the skill: .agents/skills/zz-meridian/SKILL.md.`;
|
|
42
43
|
const read = readFlags(process.argv.slice(2));
|
|
@@ -69,6 +70,11 @@ function main() {
|
|
|
69
70
|
console.error(`zz-meridian ${command}: --${wrong.flag} belongs to ${wrong.belongsTo}`);
|
|
70
71
|
return 1;
|
|
71
72
|
}
|
|
73
|
+
const extra = positionals[command === 'create' ? 2 : 1];
|
|
74
|
+
if (extra !== undefined) {
|
|
75
|
+
console.error(`zz-meridian ${command}: unexpected argument ${JSON.stringify(extra)}`);
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
72
78
|
if (command === 'adopt')
|
|
73
79
|
return adopt({ root: process.cwd(), brand, allowDirty: Boolean(flags['allow-dirty']), install });
|
|
74
80
|
if (command === 'create') {
|
|
@@ -95,8 +101,14 @@ function main() {
|
|
|
95
101
|
return brandCommand({ root: process.cwd(), flags: brand, allowDirty: Boolean(flags['allow-dirty']) });
|
|
96
102
|
if (command === 'skill') {
|
|
97
103
|
const roots = flags.global ? [os.homedir()] : [process.cwd()];
|
|
98
|
-
|
|
99
|
-
|
|
104
|
+
try {
|
|
105
|
+
for (const r of roots)
|
|
106
|
+
installSkill(r);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
console.error(`zz-meridian skill: ${error instanceof Error ? error.message : String(error)}`);
|
|
110
|
+
return 1;
|
|
111
|
+
}
|
|
100
112
|
const skill = path.join(roots[0], '.claude/skills/zz-meridian/SKILL.md');
|
|
101
113
|
console.log(`The zz-meridian skill is in ${flags.global ? path.join(os.homedir(), '.agents/skills') + ' and ' + path.join(os.homedir(), '.claude/skills') : '.agents/skills and .claude/skills'}. Codex may need a restart to list it.`);
|
|
102
114
|
console.log(`To follow it now, read ${skill} and the references beside it: it says how to build what was asked and when it is done.`);
|
package/dist/create.js
CHANGED
|
@@ -73,7 +73,7 @@ export function create(o) {
|
|
|
73
73
|
if (hasCommand('git'))
|
|
74
74
|
run('git', ['init', '-q'], root);
|
|
75
75
|
if (o.install && run(install[0], install[1], root).status !== 0)
|
|
76
|
-
return fail(`${install.join(' ')} failed (above); run it again in ${root}`);
|
|
76
|
+
return fail(`${install[0]} ${install[1].join(' ')} failed (above); run it again in ${root}`);
|
|
77
77
|
console.log(`
|
|
78
78
|
${name} is a new Meridian ${VERSION} dashboard in ${root}.
|
|
79
79
|
skill: .agents/skills/zz-meridian (Codex) and .claude/skills/zz-meridian (Claude Code)
|
package/dist/files.js
CHANGED
|
@@ -140,7 +140,8 @@ function strippedJsonc(text) {
|
|
|
140
140
|
}
|
|
141
141
|
out += c;
|
|
142
142
|
}
|
|
143
|
-
|
|
143
|
+
// Consume strings whole before removing trailing commas: their keys and values may themselves contain ",]" or ",}".
|
|
144
|
+
const json = out.replace(/"(?:\\.|[^"\\])*"|,(\s*[}\]])/g, (match, end) => end ?? match);
|
|
144
145
|
return { json, dropped: dropped || json.length !== out.length };
|
|
145
146
|
}
|
|
146
147
|
/** JSON with the comments and trailing commas a tsconfig may carry. */
|
|
@@ -184,13 +185,29 @@ export function writeManifest(root, m) {
|
|
|
184
185
|
*/
|
|
185
186
|
export function skillPaths() {
|
|
186
187
|
const files = payloadFiles('skills/zz-meridian');
|
|
188
|
+
if (!files.includes('skills/zz-meridian/SKILL.md'))
|
|
189
|
+
throw new Error('skills/zz-meridian/SKILL.md is not in cli/payload/: rebuild the payload or reinstall a complete package before installing the skill.');
|
|
187
190
|
return ['.agents/skills/zz-meridian', '.claude/skills/zz-meridian'].flatMap((base) => files.map((f) => ({ rel: `${base}/${f.slice('skills/zz-meridian/'.length)}`, payload: f })));
|
|
188
191
|
}
|
|
189
|
-
/**
|
|
192
|
+
/** Refuse a different existing skill before writing either agent's copy. Identical copies need no write. */
|
|
190
193
|
export function installSkill(root, record) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
+
const contents = new Map();
|
|
195
|
+
const plan = skillPaths().map(({ rel, payload }) => {
|
|
196
|
+
const abs = inside(root, rel);
|
|
197
|
+
let content = contents.get(payload);
|
|
198
|
+
if (!content) {
|
|
199
|
+
content = payloadBytes(payload);
|
|
200
|
+
contents.set(payload, content);
|
|
201
|
+
}
|
|
202
|
+
const existing = fs.lstatSync(abs, { throwIfNoEntry: false });
|
|
203
|
+
if (existing && (!existing.isFile() || !fs.readFileSync(abs).equals(content))) {
|
|
204
|
+
throw new Error(`refusing to overwrite ${rel}: it differs from the packaged skill. Move your copy aside before installing again; no skill files were written.`);
|
|
205
|
+
}
|
|
206
|
+
return { rel, content, exists: Boolean(existing) };
|
|
207
|
+
});
|
|
208
|
+
for (const { rel, content, exists } of plan) {
|
|
209
|
+
if (!exists)
|
|
210
|
+
writeIn(root, rel, content);
|
|
194
211
|
if (record)
|
|
195
212
|
record[rel] = sha256(content);
|
|
196
213
|
}
|
package/dist/migrations.js
CHANGED
|
@@ -106,8 +106,16 @@ function removedVerifyFlags(root) {
|
|
|
106
106
|
const hits = [...new Set(files)].filter((rel) => removed.test(text(root, rel) ?? ''));
|
|
107
107
|
return hits.length > 0 ? hits : null;
|
|
108
108
|
}
|
|
109
|
+
const APP_DIRS = ['app', 'src/app'];
|
|
110
|
+
/** The route's path within either supported App Router folder, or null outside one. */
|
|
111
|
+
const appPath = (rel) => rel.startsWith('app/') ? rel.slice(4) : rel.startsWith('src/app/') ? rel.slice(8) : null;
|
|
112
|
+
const appFiles = (root, rel) => APP_DIRS.flatMap((dir) => {
|
|
113
|
+
const file = `${dir}/${rel}`;
|
|
114
|
+
const src = text(root, file);
|
|
115
|
+
return src === null ? [] : [{ rel: file, src }];
|
|
116
|
+
});
|
|
109
117
|
const base = (rel) => rel.slice(rel.lastIndexOf('/') + 1);
|
|
110
|
-
const isPageOrLayout = (rel) => rel
|
|
118
|
+
const isPageOrLayout = (rel) => appPath(rel) !== null && /^(?:page|layout)\.tsx?$/.test(base(rel));
|
|
111
119
|
const isActionsFile = (rel) => /^actions\.tsx?$/.test(base(rel));
|
|
112
120
|
const isServerFile = (src) => /^\s*(['"])use server\1/m.test(src);
|
|
113
121
|
/** The tag `<name …>` bodies in a file, with `=>` inside an attribute not ending the tag. */
|
|
@@ -434,7 +442,7 @@ export const RELEASE_MIGRATIONS = [
|
|
|
434
442
|
{
|
|
435
443
|
id: 'authorized-read',
|
|
436
444
|
since: '0.5.0',
|
|
437
|
-
applies: (root) => matching(root, (rel, src) => rel
|
|
445
|
+
applies: (root) => matching(root, (rel, src) => appPath(rel) !== null && /^page\.tsx?$/.test(base(rel)) && /\.query\(/.test(src)),
|
|
438
446
|
summary: 'A page reads a collection through the authorized, cached read(), not by querying it directly.',
|
|
439
447
|
instructions: 'Read "Authorized, scoped reads" in references/cache.md. Replace `collection.query(q)` in the page with `read(name, q)` from `src/data/read.ts`, format any freshness against the returned `observedAt`, and add `src/data/access.ts` and `src/data/read.ts` from the reference, bound to your own sign-in and database predicates.',
|
|
440
448
|
checks: GATE_AND_BUILD,
|
|
@@ -452,13 +460,12 @@ export const RELEASE_MIGRATIONS = [
|
|
|
452
460
|
since: '0.5.0',
|
|
453
461
|
applies: (root) => {
|
|
454
462
|
const out = [];
|
|
455
|
-
const layout = 'app/(dashboard)/layout.tsx';
|
|
456
463
|
const collections = 'src/data/collections.ts';
|
|
457
464
|
if (text(root, collections) !== null && text(root, 'src/data/live-actions.ts') === null)
|
|
458
465
|
out.push(collections);
|
|
459
|
-
const src
|
|
460
|
-
|
|
461
|
-
|
|
466
|
+
for (const { rel, src } of appFiles(root, '(dashboard)/layout.tsx'))
|
|
467
|
+
if (!/\b(?:LiveProvider|ConsoleLive)\b/.test(src))
|
|
468
|
+
out.push(rel);
|
|
462
469
|
return out.length > 0 ? out : null;
|
|
463
470
|
},
|
|
464
471
|
summary: 'The console has a live route, a refresh action and a provider.',
|
|
@@ -468,7 +475,7 @@ export const RELEASE_MIGRATIONS = [
|
|
|
468
475
|
{
|
|
469
476
|
id: 'authorized-endpoints',
|
|
470
477
|
since: '0.5.0',
|
|
471
|
-
applies: (root) => matching(root, (rel, src) => (rel === '
|
|
478
|
+
applies: (root) => matching(root, (rel, src) => (appPath(rel) === 'api/assistant/route.ts' || (isActionsFile(rel) && isServerFile(src))) && !/\bresolveAccess\(/.test(src)),
|
|
472
479
|
summary: 'The assistant route and every actions file resolve the caller before they act.',
|
|
473
480
|
instructions: 'Read "Writes authorize, then invalidate" and "The assistant route" in references/cache.md. Call `resolveAccess()` first in the route and in each Server Action, answer 401 or an error result when there is no session, and hand the assistant only the collections the caller may read. The route passes a guard that authorizes each approved change again when it runs, so `src/lib/assistant/tools.ts` and `src/lib/assistant/respond.ts` take that guard too: bring them over from the release with the route.',
|
|
474
481
|
checks: GATE_AND_BUILD,
|
|
@@ -485,13 +492,13 @@ export const RELEASE_MIGRATIONS = [
|
|
|
485
492
|
id: 'assistant-view-tools',
|
|
486
493
|
since: '0.10.0',
|
|
487
494
|
applies: (root) => {
|
|
488
|
-
const
|
|
495
|
+
const routes = appFiles(root, 'api/assistant/route.ts');
|
|
489
496
|
const tools = text(root, 'src/lib/assistant/tools.ts');
|
|
490
497
|
const respond = text(root, 'src/lib/assistant/respond.ts');
|
|
491
|
-
if (
|
|
498
|
+
if (routes.length === 0 || tools === null || respond === null)
|
|
492
499
|
return null;
|
|
493
500
|
// The 0.9.0 assistant takes the views and tells the model its limits; one kept from before does neither.
|
|
494
|
-
const stale = [...(/\bViewTool\b/.test(tools) ? [] : ['src/lib/assistant/tools.ts']), ...(/\blimitsOf\b/.test(respond) ? [] : ['src/lib/assistant/respond.ts']), ...(
|
|
501
|
+
const stale = [...(/\bViewTool\b/.test(tools) ? [] : ['src/lib/assistant/tools.ts']), ...(/\blimitsOf\b/.test(respond) ? [] : ['src/lib/assistant/respond.ts']), ...routes.filter(({ src }) => !/\bviews\s*:/.test(src)).map(({ rel }) => rel)];
|
|
495
502
|
return stale.length > 0 ? stale : null;
|
|
496
503
|
},
|
|
497
504
|
summary: "The assistant's route-side code predates view tools and its limits.",
|
|
@@ -573,7 +580,10 @@ export const RELEASE_MIGRATIONS = [
|
|
|
573
580
|
{
|
|
574
581
|
id: 'view-page-matched-as-next',
|
|
575
582
|
since: '0.16.3',
|
|
576
|
-
applies: (root) =>
|
|
583
|
+
applies: (root) => {
|
|
584
|
+
const paths = appFiles(root, 'api/assistant/route.ts').filter(({ src }) => /\bfunction onPage\s*\(/.test(src)).map(({ rel }) => rel);
|
|
585
|
+
return paths.length > 0 ? paths : null;
|
|
586
|
+
},
|
|
577
587
|
summary: 'The assistant route finds the view of the page a person is on with `viewAt` from `src/lib/shared-context.ts`, which matches a catch-all `page` as Next.js does.',
|
|
578
588
|
instructions: 'In `app/api/assistant/route.ts`, delete the local `onPage` and find the view with `viewAt` from `@/lib/shared-context` (`@meridian/lib/shared-context` in an adopted project): `const at = viewAt(viewTools, path); const view = at && views.includes(at) ? at : undefined;`, where `views` are the view tools the person may use. `onPage` matched a `[...name]` segment as one segment and a `[[...name]]` never, so a view whose `page` is a catch-all (`/docs/[...path]`) was handed its prepared query at one depth only. `viewAt` matches as Next.js does — `[...name]` one or more segments, `[[...name]]` zero or more — and where two views match one address it takes the one Next.js renders, before asking whether the person may use it, so a less specific view never stands in for the page.',
|
|
579
589
|
checks: ['gate'],
|
|
@@ -589,7 +599,7 @@ export const RELEASE_MIGRATIONS = [
|
|
|
589
599
|
{
|
|
590
600
|
id: 'today-in-timezone',
|
|
591
601
|
since: '0.16.3',
|
|
592
|
-
applies: (root) => matching(root, (rel, src) => (rel === '
|
|
602
|
+
applies: (root) => matching(root, (rel, src) => (appPath(rel) === 'api/assistant/route.ts' || rel === 'src/data/metrics.ts') && /\bconst today = clock\(\)\.toISOString\(\)\.slice\(0, 10\)|\btoday: clock\(\)\.toISOString\(\)\.slice\(0, 10\)/.test(src)),
|
|
593
603
|
summary: "Today is the day in `app.timezone` wherever a day is cut: `dayOf(clock())` from `src/lib/format-date.ts`, not the UTC date.",
|
|
594
604
|
instructions: "Replace `clock().toISOString().slice(0, 10)` with `dayOf(clock())` (`dayOf` in `src/lib/format-date.ts`) in each file listed: the prepared query's `today` in `app/api/assistant/route.ts`, and `readDays`'s today in `src/data/metrics.ts`. The prompt has told the model today in `app.timezone` all along, while the prepared query and the page cut their periods at the UTC date, so east of UTC after local midnight (west of UTC in the evening) the model's today and its `7d` window's end were a day apart. A day row is kept under its day in `app.timezone`, so a file of your own that compares an instant's day with one — `now.slice(0, 10)`, `at.slice(0, 10)` — asks `dayOf` too. A product whose `app.timezone` is UTC sees no change. A project that kept the sample's views brings `src/views/now-parts.ts`, `src/views/revenue-parts.ts`, `src/views/customer-context.ts`, `src/views/customer.tsx` and `src/views/members.tsx` over from the release.",
|
|
595
605
|
checks: ['gate'],
|
|
@@ -615,7 +625,7 @@ export const RELEASE_MIGRATIONS = [
|
|
|
615
625
|
since: '0.26.0',
|
|
616
626
|
applies: (root) => matching(root, (rel, src) => importsOf(rel, src).some(({ from }) => from === 'src/components/ui/table' || from === 'src/components/patterns/data-table') && (/@min-\[672px\]:min-w-64/.test(src) || /<TableCell\b[^>]*\bcolSpan\s*=/.test(src))),
|
|
617
627
|
summary: 'A table is a grid since 0.26.0: its spare width sits in the gaps, a `grow` column\'s floor is 160px at every width, and a cell spans no columns.',
|
|
618
|
-
instructions: 'Bring `tests/table-cut-cell.test.tsx` over from the release as it has it. Since 0.26.0 `src/components/ui/table/index.tsx` lays a table out as a grid whose rows are subgrids: every column is as wide as its content, the spare is shared equally between the gaps, and a `grow` column
|
|
628
|
+
instructions: 'Bring `tests/table-cut-cell.test.tsx` over from the release as it has it. Since 0.26.0 `src/components/ui/table/index.tsx` lays a table out as a grid whose rows are subgrids: every column is as wide as its content, the spare is shared equally between the gaps, and a `grow` column has a 160px floor at every width. Remove tests that pin the old or new utility-class strings rather than repinning them; check truncation, full-value access and the rendered responsive grid. A `TableCell` of your own with a `colSpan` takes one track of the grid, so it draws in the first column only: a row that should run the width of the table, such as an empty state, is drawn outside the table, or as a `DataTable` `empty`.',
|
|
619
629
|
checks: ['gate'],
|
|
620
630
|
},
|
|
621
631
|
{
|
|
@@ -626,12 +636,20 @@ export const RELEASE_MIGRATIONS = [
|
|
|
626
636
|
instructions: `Read "The four layers" and "The path an existing product follows" in references/data-layers.md. Thirty-eight modules moved into the logic layer, and each import of one in your own \`src/views/\` producers and pages names the path it left: ${MOVED_LIST}. Rewrite each import to the path the module has now, or move your own module beside the work it does. Two symbols left a card of Meridian's, and both must be rewritten because the update replaces the card: \`afterStart\`, which the incident card exported, is \`@/logic/after-start\`, and \`summarise\`, which the status list re-exported, is \`@/logic/summarise\` (\`@meridian/logic/…\` in an adopted project, where \`@/\` is your own). Every other path your own files name is still there, so a project that imported neither symbol keeps building as it was. The six layer rules stay off until your own \`scripts/check.local.ts\` exports \`fourLayers = true\`; turn them on once your figures have moved, and the gate and the build are what show the work is done.`,
|
|
627
637
|
checks: GATE_AND_BUILD,
|
|
628
638
|
},
|
|
639
|
+
{
|
|
640
|
+
id: 'env-reset-before-each',
|
|
641
|
+
since: '0.29.0',
|
|
642
|
+
applies: (root) => matching(root, (rel, src) => /\.test\.[cm]?[jt]sx?$/.test(rel) && setsEnvInBeforeAll(src)),
|
|
643
|
+
summary: '`tests/setup.ts` puts `process.env` back before every test, so a value a test file sets in `beforeAll` is gone when its first test runs.',
|
|
644
|
+
instructions: "Since 0.29.0 the shipped `tests/setup.ts` restores the environment before each test, so a stub in one file can no longer reach the file beside it. A test file of your own that sets `process.env` (or calls `vi.stubEnv`) inside `beforeAll` loses the value before its first test, and fails with no reason given. In each file listed, keep the costly start (a fake backend, a server) in `beforeAll` and keep what it returns in a variable, then set the environment from it in a `beforeEach`: `beforeEach(() => { process.env.API_URL = started.base; })`.",
|
|
645
|
+
checks: ['gate'],
|
|
646
|
+
},
|
|
629
647
|
{
|
|
630
648
|
id: 'direct-query-escape',
|
|
631
649
|
since: '0.30.0',
|
|
632
|
-
applies: (root) => matching(root, (rel, src) => /^
|
|
650
|
+
applies: (root) => matching(root, (rel, src) => (appPath(rel) !== null || /^src\/(?:logic|views|lib)\//.test(rel)) && SOURCE.test(rel) && /\.query\s*\(/.test(src.replace(/\/\/[^\n]*/g, '')) && !/allow-no-direct-query:/.test(src)),
|
|
633
651
|
summary: 'A direct `.query(` call is allowed only where the call states its own escape: `// allow-no-direct-query: <why>`, on that line.',
|
|
634
|
-
instructions: 'Since 0.30.0 `node scripts/check.ts` reports every `.query(` call in `app/`, `src/logic/`, `src/views/` and `src/lib/`, and keeps the run passing only where the call\'s own line carries the escape the check prints — `// allow-no-direct-query: read() cannot serve this — <why>`. Before this release the rule allowed five sites of the template\'s by line number, so a file of your own that read a collection directly passed as long as the call sat on one of those lines; that list is gone, because a comment added above a call moved it off the line and turned a passing project red for no reason. Read every file named above. Where the read has to be direct — a server action reading the record it is about to change, a streamed export, the sign-in resolving a person before an `AccessScope` exists — state the reason on that line in the form the check prints. Everywhere else, read through `read()`, `readAll()` or `aggregate()` from `@/data/read`, which ask the caller\'s grant.',
|
|
652
|
+
instructions: 'Since 0.30.0 `node scripts/check.ts` reports every `.query(` call in `app/`, `src/app/`, `src/logic/`, `src/views/` and `src/lib/`, and keeps the run passing only where the call\'s own line carries the escape the check prints — `// allow-no-direct-query: read() cannot serve this — <why>`. Before this release the rule allowed five sites of the template\'s by line number, so a file of your own that read a collection directly passed as long as the call sat on one of those lines; that list is gone, because a comment added above a call moved it off the line and turned a passing project red for no reason. Read every file named above. Where the read has to be direct — a server action reading the record it is about to change, a streamed export, the sign-in resolving a person before an `AccessScope` exists — state the reason on that line in the form the check prints. Everywhere else, read through `read()`, `readAll()` or `aggregate()` from `@/data/read`, which ask the caller\'s grant.',
|
|
635
653
|
checks: ['gate'],
|
|
636
654
|
},
|
|
637
655
|
{
|
|
@@ -658,6 +676,14 @@ export const RELEASE_MIGRATIONS = [
|
|
|
658
676
|
instructions: 'The audit, the presses, the keyboard walk, the vitals and `scripts/shot.ts` now read what a route answered: one that answered an error status or draws the not-found screen (`[data-missing-page]`) was measured as that screen and not as the route, so it fails. In `scripts/verify.config.ts`, which is yours, move every entry of `detailRoutes` that names a record that does not exist (the sample\'s `/requests/req_missing` and `/customers/missing`) into a new `missingRoutes` list; each must be the not-found screen, and one that renders a page fails. A not-found screen of your own carries `data-missing-page` on its root, as `MissingPage` does: inside a streamed shell a missing record has already answered 200.',
|
|
659
677
|
checks: ['verify'],
|
|
660
678
|
},
|
|
679
|
+
{
|
|
680
|
+
id: 'computed-series-slot',
|
|
681
|
+
since: '0.32.0',
|
|
682
|
+
applies: (root) => matching(root, (rel, src) => SOURCE.test(rel) && importsOf(rel, src).some(({ from }) => SLOT_CHARTS.includes(from)) && computesSlot(src)),
|
|
683
|
+
summary: "A chart's `color` is a palette slot, `SeriesSlot` (1 to 6), where it took any number: a computed one fails the type check.",
|
|
684
|
+
instructions: "Since 0.32.0 `ColumnPart`, a Composition bar's `Part`, `Sparkline` and a Trend chart's series take `color` as a `SeriesSlot`, `1 | 2 | 3 | 4 | 5 | 6` from `src/components/charts/scale.ts`, because a slot past six named a colour no theme defines and drew nothing. A file of your own that computes the slot (`color: i + 1`) fails `tsc` at `--finalize`. In each file listed, type the value as a `SeriesSlot` where it is computed, or fold the parts past the palette into one \"Other\" part, as the Composition bar does past six. A file that already types it so has nothing to change.",
|
|
685
|
+
checks: ['gate'],
|
|
686
|
+
},
|
|
661
687
|
{
|
|
662
688
|
id: 'use-size-callback-ref',
|
|
663
689
|
since: '0.33.0',
|
|
@@ -669,7 +695,7 @@ export const RELEASE_MIGRATIONS = [
|
|
|
669
695
|
{
|
|
670
696
|
id: 'self-hosted-fonts',
|
|
671
697
|
since: '0.34.0',
|
|
672
|
-
applies: (root) => matching(root, (rel, src) =>
|
|
698
|
+
applies: (root) => matching(root, (rel, src) => appPath(rel) !== null && SOURCE.test(rel) && /from\s+['"]next\/font\/google['"]/.test(src) && /--font-face-(?:sans|mono)/.test(src)),
|
|
673
699
|
summary: "Meridian's faces ship with the release under `src/styles/fonts/`: a build loads them from there and fetches nothing from Google Fonts.",
|
|
674
700
|
instructions: "Your `app/layout.tsx` is yours, so an update does not touch it, and yours hands `--font-face-sans` and `--font-face-mono` their faces through `next/font/google`, which `next build` fetches from fonts.googleapis.com: with that host unreachable and no build cache, the build fails (`Build error occurred`). The update has delivered the two faces, `src/styles/fonts/Geist-Variable.woff2` and `src/styles/fonts/GeistMono-Variable.woff2`, with their licence. In the layout, replace the `next/font/google` import with `import localFont from 'next/font/local';` and each face's call with `localFont({ src: '../src/styles/fonts/Geist-Variable.woff2', weight: '100 900', variable: '--font-face-sans', display: 'swap' })` and `localFont({ src: '../src/styles/fonts/GeistMono-Variable.woff2', weight: '100 900', variable: '--font-face-mono', display: 'swap' })`, keeping the class names on `<html>` as they are. A layout that gives the tokens faces of its own from Google Fonts is the same case: ship the files and load them with `next/font/local`.",
|
|
675
701
|
checks: GATE_AND_BUILD,
|
|
@@ -693,11 +719,28 @@ export const RELEASE_MIGRATIONS = [
|
|
|
693
719
|
{
|
|
694
720
|
id: 'sample-with-its-tests',
|
|
695
721
|
since: '0.36.0',
|
|
722
|
+
recurring: true,
|
|
696
723
|
applies: sampleBehindItsTests,
|
|
697
|
-
summary: "The
|
|
698
|
-
instructions: "A project `create` made holds the template's sample (its pages, views, data, figures, their helpers and the demo gate in `proxy.ts`) as its own files, which an update never touches, while the shipped tests
|
|
724
|
+
summary: "The release's sample differs from team-owned files, while its shipped tests are managed. Review those differences before finalizing.",
|
|
725
|
+
instructions: "A project `create` made holds the template's sample (its pages, views, data, figures, their helpers and the demo gate in `proxy.ts`) as its own files, which an update never touches, while the shipped tests are Meridian's and arrive with it. The update reports differing sample files on every upgrade, even when a previous release already reported this migration. Each named file differs from the release's copy, or is a sample module introduced after your project's recorded version; the gate may depend on its changes. Write the release's copies into a scratch folder with `npx zz-meridian@<the version you are updating to> create <scratch folder> --no-install`. For a file your team never changed, copy the release's file over it, and copy each module the release added. For a file your team made its own, preserve the intended product behavior while updating its contracts and meaningful behavior and security tests. Keep coverage for functionality the product still exposes; do not delete failures merely to finalize. Retire tests with removed sample features, or when they only pin wording or implementation details, and record intentionally deleted managed tests in `.meridian/keep.json`. A sample file your team deleted after its introduction is not named.",
|
|
699
726
|
checks: GATE_AND_BUILD,
|
|
700
727
|
},
|
|
728
|
+
{
|
|
729
|
+
id: 'proposal-approve-required',
|
|
730
|
+
since: '0.37.0',
|
|
731
|
+
applies: (root) => matching(root, (rel, src) => SOURCE.test(rel) && importsOf(rel, src).some(({ from }) => from === 'src/components/patterns/proposal') && tags(src, 'Proposal').some((t) => !/\bstate\s*=/.test(t) && !/\bonApprove\s*=/.test(t) && !/\{\s*\.\.\./.test(t))),
|
|
732
|
+
summary: 'An uncontrolled `Proposal` requires `onApprove`: without it, Approve claimed Applied for a change nothing ran.',
|
|
733
|
+
instructions: 'Since 0.37.0 `src/components/patterns/proposal/index.tsx` types `onApprove` as required when no `state` is passed, so a `<Proposal>` of your own without either fails `tsc` at `--finalize`. In each file listed, pass the function that runs the change as `onApprove`, or pass `state` and own the card\'s states yourself. A card that only shows a change it cannot run is not a proposal: show it in a Card instead.',
|
|
734
|
+
checks: ['gate'],
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
id: 'open-command-moved',
|
|
738
|
+
since: '0.37.0',
|
|
739
|
+
applies: (root) => matching(root, (rel, src) => SOURCE.test(rel) && importsOf(rel, src).some(({ from, names }) => from === 'src/components/patterns/command-palette' && names.some((n) => n === 'openCommand' || n === 'usePaletteKey'))),
|
|
740
|
+
summary: '`openCommand` and `usePaletteKey` moved to `src/components/patterns/command-palette/open.ts`, so the palette can be a chunk of its own.',
|
|
741
|
+
instructions: 'Since 0.37.0 the Command palette loads with `next/dynamic` and is in no page\'s first load, so the two things a page needs before it opens, `openCommand` and `usePaletteKey`, live in `src/components/patterns/command-palette/open.ts` and the palette\'s own file no longer exports them. In each file listed, import them from `@/components/patterns/command-palette/open`. Mount the palette the way `src/views/console-chrome.tsx` does, through `dynamic()`, or importing it puts it back in every page\'s first load.',
|
|
742
|
+
checks: ['gate'],
|
|
743
|
+
},
|
|
701
744
|
];
|
|
702
745
|
/**
|
|
703
746
|
* The template's sample as `create` writes it: the team's own sources among the pages, views, data, logic and library,
|
|
@@ -707,10 +750,10 @@ const isSample = (rel) => SOURCE.test(rel) && isTeamOwned(rel) && ((rel.startsWi
|
|
|
707
750
|
/** The sample modules 0.36.0 added: a project created before has none, and the release's pages and tests import them. */
|
|
708
751
|
const SAMPLE_ADDED = ['src/lib/totals.ts'];
|
|
709
752
|
/**
|
|
710
|
-
*
|
|
711
|
-
* release
|
|
753
|
+
* In a project `create` made, each sample file that differs from the release's, and each sample module introduced
|
|
754
|
+
* after the source release that the project lacks. A file the team deleted after its introduction is not named.
|
|
712
755
|
*/
|
|
713
|
-
function sampleBehindItsTests(root) {
|
|
756
|
+
function sampleBehindItsTests(root, source) {
|
|
714
757
|
if (!/"route"\s*:\s*"create"/.test(text(root, '.meridian/manifest.json') ?? ''))
|
|
715
758
|
return null;
|
|
716
759
|
const hits = payloadFiles().filter(isSample).filter((rel) => {
|
|
@@ -718,7 +761,7 @@ function sampleBehindItsTests(root) {
|
|
|
718
761
|
return !fs.readFileSync(path.join(root, rel)).equals(payloadBytes(rel));
|
|
719
762
|
}
|
|
720
763
|
catch {
|
|
721
|
-
return SAMPLE_ADDED.includes(rel);
|
|
764
|
+
return SAMPLE_ADDED.includes(rel) && isNewer('0.36.0', source);
|
|
722
765
|
}
|
|
723
766
|
});
|
|
724
767
|
return hits.length > 0 ? hits : null;
|
|
@@ -757,12 +800,14 @@ function navItemWithoutFields(root) {
|
|
|
757
800
|
* A configuration's own `NavItem` as written, comments blanked: its declaration's line, and when that line does not end
|
|
758
801
|
* the statement with a `;`, every line after it that is blank, indented, or opens with a closing bracket or an operator
|
|
759
802
|
* that continues it (`}`, `]`, `)`, `>`, `|`, `&`, `?`, `:`), up to the first that starts at the margin with anything
|
|
760
|
-
* else.
|
|
761
|
-
*
|
|
762
|
-
*
|
|
803
|
+
* else. The type is read whether it is exported or not: one local to the module still types the items of an exported
|
|
804
|
+
* `NavGroup`, so the rail reads its fields all the same (#103). A declaration inside a comment is not the type unless it
|
|
805
|
+
* is the only one. Undefined when there is none. No type is parsed: a shape this cuts short is reported rather than
|
|
806
|
+
* passed, since a migration line with nothing to do costs a look, and a missed one is the type error with no line it
|
|
807
|
+
* exists to prevent.
|
|
763
808
|
*/
|
|
764
809
|
function navItemType(body) {
|
|
765
|
-
const declaration = /^export\s+(?:interface|type)\s+NavItem\b/m;
|
|
810
|
+
const declaration = /^(?:export\s+)?(?:interface|type)\s+NavItem\b/m;
|
|
766
811
|
// A regular expression is not told from a comment here, so one that opens `/*` can blank the type: then the raw text.
|
|
767
812
|
const m = declaration.exec(uncommented(body)) ?? declaration.exec(body);
|
|
768
813
|
if (!m)
|
|
@@ -842,7 +887,7 @@ function renamedSampleFiles(root) {
|
|
|
842
887
|
const modules = new Set([...old.flatMap((n) => [`src/views/${n}`, `src/views/${n}-context`]), `src/data/${old[1]}`]);
|
|
843
888
|
const group = `(${old[0]})`;
|
|
844
889
|
const named = new RegExp(`\\b(?:FEATURES\\.|view_)(?:${old.join('|')})\\b`);
|
|
845
|
-
const isOld = (rel) => modules.has(rel.replace(/\.[mc]?[jt]sx?$/, '')) || (
|
|
890
|
+
const isOld = (rel) => modules.has(rel.replace(/\.[mc]?[jt]sx?$/, '')) || (appPath(rel) !== null && rel.split('/').includes(group));
|
|
846
891
|
const sources = teamSources(root);
|
|
847
892
|
if (!sources.some((f) => isOld(f.rel)))
|
|
848
893
|
return null;
|
|
@@ -854,7 +899,7 @@ function renamedSampleFiles(root) {
|
|
|
854
899
|
// of the old, and are as much the sample's. A file that imports one of the old files, such as a test that renders the
|
|
855
900
|
// page, names it too; a file that only names one, such as the configuration, is not itself the old.
|
|
856
901
|
const oldFiles = new Set(sources.map((f) => f.rel).filter(isOld));
|
|
857
|
-
const pages = sources.filter(({ rel, src }) =>
|
|
902
|
+
const pages = sources.filter(({ rel, src }) => appPath(rel) !== null && /^(?:page|view)\.tsx?$/.test(base(rel)) && (isOld(rel) || importsOf(rel, src).some((i) => modules.has(i.from))));
|
|
858
903
|
for (const { rel } of pages) {
|
|
859
904
|
const dir = path.posix.dirname(rel);
|
|
860
905
|
try {
|
|
@@ -915,6 +960,31 @@ function navItemObjects(nav) {
|
|
|
915
960
|
}
|
|
916
961
|
return found;
|
|
917
962
|
}
|
|
963
|
+
/** The chart cards whose `color` is a `SeriesSlot` since 0.32.0. */
|
|
964
|
+
const SLOT_CHARTS = ['src/components/charts/column-chart', 'src/components/charts/column-chart/grouped', 'src/components/charts/composition-bar', 'src/components/charts/sparkline', 'src/components/charts/trend-chart'];
|
|
965
|
+
/**
|
|
966
|
+
* Whether a file computes a chart colour by arithmetic (`color: i + 1`) without typing it `SeriesSlot`: the shape the
|
|
967
|
+
* 0.32.0 narrowing fails. A slot held in a variable typed `number` is not read; a named colour or a literal slot passes.
|
|
968
|
+
*/
|
|
969
|
+
function computesSlot(src) {
|
|
970
|
+
return [...src.matchAll(/\bcolor\s*(?::|=\s*\{)([^,}\n]*)/g)].some((m) => /[-+*%]\s*\d|\d\s*[-+*%]/.test(m[1]) && !/\bas\s+SeriesSlot\b/.test(m[1]));
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Whether a test file sets the environment inside a `beforeAll`: the hook's own parentheses, read by their nesting, hold
|
|
974
|
+
* an assignment to `process.env` or a `vi.stubEnv` call. Strings are not told from code; a missed shape is a test
|
|
975
|
+
* that fails at `--finalize`, a false one a line to read.
|
|
976
|
+
*/
|
|
977
|
+
function setsEnvInBeforeAll(src) {
|
|
978
|
+
for (const m of src.matchAll(/\bbeforeAll\s*\(/g)) {
|
|
979
|
+
let depth = 1;
|
|
980
|
+
let i = m.index + m[0].length;
|
|
981
|
+
for (; i < src.length && depth > 0; i++)
|
|
982
|
+
depth += src[i] === '(' ? 1 : src[i] === ')' ? -1 : 0;
|
|
983
|
+
if (/\bprocess\.env(?:\.\w+|\[[^\]]+\])\s*=(?!=)|\bvi\.stubEnv\s*\(/.test(src.slice(m.index, i)))
|
|
984
|
+
return true;
|
|
985
|
+
}
|
|
986
|
+
return false;
|
|
987
|
+
}
|
|
918
988
|
/** The project's own nav, with any item still missing the `needs` that 0.13.0 requires of it. */
|
|
919
989
|
function navItemsWithoutNeeds(root) {
|
|
920
990
|
const rel = 'src/app.config.ts';
|
|
@@ -941,7 +1011,7 @@ function navItemsWithoutNeeds(root) {
|
|
|
941
1011
|
*/
|
|
942
1012
|
function ownGroupPagesWithoutAgentReads(root) {
|
|
943
1013
|
const dirs = [];
|
|
944
|
-
for (const app of
|
|
1014
|
+
for (const app of APP_DIRS) {
|
|
945
1015
|
try {
|
|
946
1016
|
dirs.push(...fs.readdirSync(path.join(root, app), { withFileTypes: true }).filter((e) => e.isDirectory() && /^\(.+\)$/.test(e.name) && e.name !== '(dashboard)').map((e) => `${app}/${e.name}`));
|
|
947
1017
|
}
|
|
@@ -951,7 +1021,7 @@ function ownGroupPagesWithoutAgentReads(root) {
|
|
|
951
1021
|
}
|
|
952
1022
|
/** The page specifications under the console and embed folders that lack the part the 0.9.0 check requires. */
|
|
953
1023
|
function pagesWithoutAgentReads(root) {
|
|
954
|
-
return specsWithoutAgentReads(root, [
|
|
1024
|
+
return specsWithoutAgentReads(root, APP_DIRS.flatMap((app) => [`${app}/(dashboard)`, `${app}/embed`]));
|
|
955
1025
|
}
|
|
956
1026
|
/** The page specifications under `dirs` without `## Agents` and its `### What the agent reads`. */
|
|
957
1027
|
function specsWithoutAgentReads(root, dirs) {
|
|
@@ -963,13 +1033,15 @@ function specsWithoutAgentReads(root, dirs) {
|
|
|
963
1033
|
}
|
|
964
1034
|
/** The registry entries that apply to a project moving from `source` to `target`, as journal migrations. */
|
|
965
1035
|
export function releaseMigrations(root, source, target) {
|
|
1036
|
+
if (!isNewer(target, source))
|
|
1037
|
+
return [];
|
|
966
1038
|
const out = [];
|
|
967
1039
|
sources = new Map();
|
|
968
1040
|
try {
|
|
969
1041
|
for (const m of RELEASE_MIGRATIONS) {
|
|
970
|
-
if (
|
|
1042
|
+
if (isNewer(m.since, target) || (!m.recurring && !isNewer(m.since, source)))
|
|
971
1043
|
continue;
|
|
972
|
-
const paths = m.applies(root);
|
|
1044
|
+
const paths = m.applies(root, source);
|
|
973
1045
|
if (paths)
|
|
974
1046
|
out.push({ id: m.id, summary: m.summary, paths, instructions: m.instructions, checks: m.checks });
|
|
975
1047
|
}
|
package/dist/reconcile.js
CHANGED
|
@@ -20,6 +20,11 @@ function range(spec) {
|
|
|
20
20
|
}
|
|
21
21
|
const minimum = (spec) => range(spec)?.v ?? null;
|
|
22
22
|
const compare = (a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
|
|
23
|
+
/** Compare supported dependency floors; null means the specification needs a person's judgment. */
|
|
24
|
+
export function compareMinimum(have, want) {
|
|
25
|
+
const a = minimum(have), b = minimum(want);
|
|
26
|
+
return a && b ? compare(a, b) : null;
|
|
27
|
+
}
|
|
23
28
|
/** Whether `version` satisfies a specification `range` reads. A specification it cannot read is not judged. */
|
|
24
29
|
export function satisfies(spec, version) {
|
|
25
30
|
const want = range(spec);
|
package/dist/session.js
CHANGED
|
@@ -598,9 +598,9 @@ function patchReport(l, outcome, coverage = null) {
|
|
|
598
598
|
.replace(/^- Coverage: .*\n/m, '')
|
|
599
599
|
.replace(/^## Failure\n\n[\s\S]*?\n\n(?=## )/m, '');
|
|
600
600
|
if (coverage)
|
|
601
|
-
text = text.replace(/^(- Outcome: .*)$/m, `$
|
|
601
|
+
text = text.replace(/^(- Outcome: .*)$/m, (line) => `${line}\n- Coverage: ${coverage.replace(/^coverage: /, '')}`);
|
|
602
602
|
if (j.failure)
|
|
603
|
-
text = text.replace(/^## What needs you$/m, `## Failure\n\n${j.failure}\n\n## What needs you`);
|
|
603
|
+
text = text.replace(/^## What needs you$/m, () => `## Failure\n\n${j.failure}\n\n## What needs you`);
|
|
604
604
|
writeAtomic(file, text);
|
|
605
605
|
}
|
|
606
606
|
/** Drops the temporary copies and moves the session to `.meridian/history/<version>/<id>/`. */
|