@mandujs/core 0.54.32 → 0.55.0-beta.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 +654 -654
- package/package.json +106 -210
- package/scripts/postinstall-lock.ts +153 -153
- package/src/a11y/__tests__/run-audit.test.ts +333 -333
- package/src/a11y/fix-hints.ts +76 -76
- package/src/a11y/index.ts +18 -18
- package/src/a11y/run-audit.ts +15 -15
- package/src/a11y/types.ts +125 -125
- package/src/agent/__tests__/apply.test.ts +224 -0
- package/src/agent/__tests__/context.test.ts +97 -98
- package/src/agent/apply.ts +1031 -0
- package/src/agent/context.ts +552 -552
- package/src/agent/index.ts +4 -3
- package/src/agent/plan.ts +161 -243
- package/src/agent/repair.ts +239 -162
- package/src/agent/sync.ts +197 -198
- package/src/agent/types.ts +242 -94
- package/src/agent/verify.ts +55 -55
- package/src/auth/__tests__/login.test.ts +1 -1
- package/src/auth/__tests__/password.test.ts +15 -5
- package/src/auth/__tests__/reset.test.ts +2 -2
- package/src/auth/__tests__/tokens.test.ts +274 -274
- package/src/auth/__tests__/verification.test.ts +274 -274
- package/src/auth/index.ts +76 -76
- package/src/auth/login.ts +225 -225
- package/src/auth/password.ts +14 -2
- package/src/auth/reset.ts +243 -243
- package/src/auth/tokens.ts +612 -612
- package/src/auth/verification.ts +253 -253
- package/src/brain/__tests__/redactor.test.ts +94 -94
- package/src/brain/adapters/__tests__/_helpers.ts +83 -83
- package/src/brain/adapters/__tests__/anthropic-oauth.test.ts +196 -196
- package/src/brain/adapters/__tests__/chatgpt-auth.test.ts +193 -193
- package/src/brain/adapters/__tests__/openai-oauth.test.ts +209 -209
- package/src/brain/adapters/__tests__/resolver.test.ts +143 -143
- package/src/brain/adapters/anthropic-oauth.ts +1 -1
- package/src/brain/adapters/chatgpt-auth.ts +300 -300
- package/src/brain/adapters/index.ts +319 -319
- package/src/brain/adapters/oauth-flow.ts +439 -439
- package/src/brain/consent.ts +240 -240
- package/src/brain/credentials.ts +396 -396
- package/src/brain/doctor/analyzer.ts +7 -7
- package/src/bundler/__snapshots__/build.test.ts.snap +5 -5
- package/src/bundler/__tests__/build-runner.ts +166 -166
- package/src/bundler/__tests__/client-boundary-transform.test.ts +524 -524
- package/src/bundler/__tests__/cold-start.test.ts +60 -60
- package/src/bundler/__tests__/css.test.ts +49 -20
- package/src/bundler/__tests__/dev-reliability.test.ts +619 -619
- package/src/bundler/__tests__/extended-watch.test.ts +711 -711
- package/src/bundler/__tests__/fast-refresh.test.ts +24 -24
- package/src/bundler/__tests__/generation.test.ts +447 -0
- package/src/bundler/__tests__/hdr.test.ts +24 -18
- package/src/bundler/__tests__/hmr-client.test.ts +62 -24
- package/src/bundler/__tests__/jsx-runtime-shim.test.ts +179 -140
- package/src/bundler/__tests__/manifest-schema.test.ts +305 -266
- package/src/bundler/__tests__/prod-smoke.test.ts +138 -138
- package/src/bundler/__tests__/reverse-import-graph.test.ts +42 -42
- package/src/bundler/__tests__/slot-dispatch.test.ts +573 -573
- package/src/bundler/__tests__/url-cap-and-slot-regex.test.ts +286 -286
- package/src/bundler/__tests__/vendor-cache.test.ts +455 -455
- package/src/bundler/analyzer.ts +15 -15
- package/src/bundler/budget.ts +404 -404
- package/src/bundler/build.test.ts +898 -915
- package/src/bundler/build.ts +113 -24
- package/src/bundler/client-boundary-transform.ts +977 -977
- package/src/bundler/css.ts +65 -44
- package/src/bundler/dev.ts +105 -73
- package/src/bundler/fast-refresh-preamble.ts +47 -47
- package/src/bundler/generation.ts +602 -0
- package/src/bundler/index.ts +3 -3
- package/src/bundler/manifest-schema.ts +55 -40
- package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +13 -13
- package/src/bundler/plugins/__tests__/react-compiler-config.test.ts +83 -83
- package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -110
- package/src/bundler/plugins/block-generated-imports.ts +16 -15
- package/src/bundler/plugins/index.ts +83 -83
- package/src/bundler/plugins/react-compiler-config.ts +108 -108
- package/src/bundler/plugins/react-compiler-lint.ts +253 -253
- package/src/bundler/plugins/react-compiler.ts +162 -162
- package/src/bundler/prerender.ts +29 -9
- package/src/bundler/reverse-import-graph.ts +339 -339
- package/src/bundler/safe-build.test.ts +1 -1
- package/src/bundler/safe-build.ts +103 -103
- package/src/bundler/scenario-matrix.ts +229 -229
- package/src/bundler/types.ts +58 -56
- package/src/bundler/vendor-cache-types.ts +130 -130
- package/src/bundler/vendor-cache.ts +526 -526
- package/src/change/snapshot.ts +18 -18
- package/src/client/Form.tsx +105 -105
- package/src/client/Link.tsx +9 -9
- package/src/client/__tests__/props-serialization.test.ts +37 -37
- package/src/client/__tests__/use-sse.test.ts +153 -153
- package/src/client/globals.ts +1 -1
- package/src/client/hooks.ts +362 -362
- package/src/client/hydrate.ts +2 -2
- package/src/client/index.ts +1 -1
- package/src/client/island.ts +79 -79
- package/src/client/prefetch-helper.ts +55 -55
- package/src/client/props-serialization.ts +233 -233
- package/src/client/runtime-entry.ts +598 -598
- package/src/client/runtime.ts +1 -1
- package/src/client/serialize.ts +50 -50
- package/src/client/use-fetch.ts +6 -6
- package/src/client/use-head.ts +197 -197
- package/src/client/use-sse.ts +378 -378
- package/src/client/window-state.ts +101 -101
- package/src/components/Image-compat.ts +3 -0
- package/src/components/Image.tsx +162 -162
- package/src/config/validate.ts +1 -1
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/collection.ts +9 -9
- package/src/content/content-layer.ts +7 -7
- package/src/content/data-store.ts +245 -245
- package/src/content/frontmatter.ts +189 -189
- package/src/content/generate-types.ts +1 -1
- package/src/content/index.ts +2 -2
- package/src/content/loader-context.ts +171 -171
- package/src/content/loaders/api.ts +216 -216
- package/src/content/loaders/file.ts +172 -172
- package/src/content/loaders/glob.ts +253 -253
- package/src/content/loaders/index.ts +34 -34
- package/src/content/loaders/types.ts +137 -137
- package/src/content/meta-store.ts +209 -209
- package/src/content/prebuild.test.ts +571 -571
- package/src/content/prebuild.ts +636 -636
- package/src/content/schema.ts +20 -20
- package/src/content/sidebar.ts +630 -630
- package/src/content/slug.ts +110 -110
- package/src/content/types.ts +282 -282
- package/src/content/watcher.ts +135 -135
- package/src/contract/client-safe.test.ts +42 -42
- package/src/contract/client-safe.ts +114 -114
- package/src/contract/define.ts +11 -11
- package/src/contract/index.ts +1 -1
- package/src/contract/normalize.test.ts +276 -276
- package/src/contract/normalize.ts +410 -410
- package/src/contract/registry.test.ts +206 -206
- package/src/contract/route-helpers.ts +1 -1
- package/src/contract/rpc.ts +443 -443
- package/src/contract/types.ts +58 -58
- package/src/db/__tests__/db.test.ts +9 -9
- package/src/db/index.ts +2 -2
- package/src/db/migrations/history-table.ts +345 -345
- package/src/db/migrations/index.ts +3 -3
- package/src/db/migrations/lock.ts +324 -324
- package/src/db/migrations/runner.ts +650 -650
- package/src/deploy/cache.ts +140 -140
- package/src/deploy/compile/vercel.ts +344 -344
- package/src/deploy/index.ts +87 -87
- package/src/deploy/inference/brain.ts +268 -268
- package/src/deploy/inference/context.ts +82 -82
- package/src/deploy/inference/filling-extract.ts +245 -245
- package/src/deploy/inference/heuristic.ts +182 -182
- package/src/deploy/intent.ts +173 -173
- package/src/deploy/plan.ts +178 -178
- package/src/design/__tests__/agents-link.test.ts +109 -109
- package/src/design/__tests__/extract-patch-diff.test.ts +265 -265
- package/src/design/__tests__/lint.test.ts +110 -110
- package/src/design/__tests__/parser.test.ts +195 -195
- package/src/design/__tests__/tailwind-theme.test.ts +229 -229
- package/src/design/agents-link.ts +165 -165
- package/src/design/diff.ts +138 -138
- package/src/design/extract.ts +285 -285
- package/src/design/index.ts +102 -102
- package/src/design/lint.ts +209 -209
- package/src/design/parser.ts +555 -555
- package/src/design/patch.ts +241 -241
- package/src/design/scaffold.ts +147 -147
- package/src/design/tailwind-theme.ts +441 -441
- package/src/design/types.ts +210 -210
- package/src/desktop/__tests__/smoke.test.ts +2 -2
- package/src/desktop/__tests__/webview-fallback.test.ts +12 -32
- package/src/desktop/__tests__/window.test.ts +20 -99
- package/src/desktop/__tests__/worker.test.ts +266 -266
- package/src/desktop/index.ts +43 -43
- package/src/desktop/types.ts +158 -158
- package/src/desktop/webview-fallback.ts +17 -4
- package/src/desktop/window.ts +17 -8
- package/src/desktop/worker.ts +180 -180
- package/src/dev-error-overlay/__tests__/overlay-injector.test.ts +241 -241
- package/src/dev-error-overlay/index.ts +30 -30
- package/src/dev-error-overlay/overlay-injector.ts +243 -243
- package/src/dev-error-overlay/overlay-styles.ts +52 -52
- package/src/dev-error-overlay/types.ts +66 -66
- package/src/devtools/ai/context-builder.ts +375 -375
- package/src/devtools/ai/index.ts +25 -25
- package/src/devtools/ai/mcp-connector.ts +25 -25
- package/src/devtools/client/catchers/error-catcher.ts +344 -344
- package/src/devtools/client/catchers/index.ts +18 -18
- package/src/devtools/client/components/index.ts +39 -39
- package/src/devtools/client/components/mandu-character.tsx +331 -331
- package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
- package/src/devtools/client/components/panel/guard-panel.tsx +30 -30
- package/src/devtools/client/components/panel/islands-panel.tsx +16 -16
- package/src/devtools/client/components/panel/network-panel.tsx +291 -291
- package/src/devtools/client/components/panel/panel-container.tsx +1 -1
- package/src/devtools/client/components/panel/preview-panel.tsx +46 -46
- package/src/devtools/client/filters/context-filters.ts +282 -282
- package/src/devtools/client/filters/index.ts +16 -16
- package/src/devtools/client/index.ts +63 -63
- package/src/devtools/hook/create-hook.ts +207 -207
- package/src/devtools/hook/index.ts +13 -13
- package/src/devtools/index.ts +439 -439
- package/src/devtools/init.ts +265 -265
- package/src/devtools/protocol.ts +237 -237
- package/src/devtools/server/index.ts +17 -17
- package/src/devtools/types.ts +35 -35
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +233 -233
- package/src/diagnose/__tests__/checks.test.ts +136 -136
- package/src/diagnose/checks.ts +185 -185
- package/src/diagnose/index.ts +17 -17
- package/src/diagnose/run.ts +10 -10
- package/src/diagnose/types.ts +53 -53
- package/src/email/__tests__/email.test.ts +355 -355
- package/src/email/index.ts +282 -282
- package/src/email/smtp.ts +64 -64
- package/src/error/domains.ts +265 -265
- package/src/error/types.ts +6 -6
- package/src/errors/extractor.ts +409 -409
- package/src/errors/index.ts +19 -19
- package/src/filling/__tests__/session-sqlite.test.ts +5 -1
- package/src/filling/auth.ts +308 -308
- package/src/filling/body-parse.test.ts +60 -60
- package/src/filling/cookie-codec.ts +5 -3
- package/src/filling/deps.ts +265 -265
- package/src/filling/head-method.test.ts +154 -154
- package/src/filling/session-sqlite.ts +617 -617
- package/src/filling/sse.ts +5 -5
- package/src/filling/ws.ts +78 -78
- package/src/generator/generate.ts +30 -30
- package/src/generator/index.ts +3 -3
- package/src/generator/templates.test.ts +48 -48
- package/src/generator/templates.ts +219 -219
- package/src/guard/__tests__/design-inline-class.test.ts +219 -219
- package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -347
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/auto-correct.ts +1 -1
- package/src/guard/check.ts +39 -24
- package/src/guard/config-guard.ts +13 -13
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/define-rule.ts +243 -243
- package/src/guard/design-inline-class.ts +393 -393
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/fs-routes-policy.ts +51 -51
- package/src/guard/graph.ts +1 -1
- package/src/guard/healing.ts +36 -36
- package/src/guard/index.ts +11 -11
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/reporter.ts +442 -442
- package/src/guard/rule-presets.ts +379 -379
- package/src/guard/semantic-slots.ts +1 -1
- package/src/guard/suggestions.ts +358 -358
- package/src/guard/tsgolint-bridge.ts +512 -512
- package/src/guard/types.ts +348 -348
- package/src/guard/watcher.ts +405 -405
- package/src/i18n/define.ts +126 -126
- package/src/i18n/index.ts +52 -52
- package/src/i18n/message-registry.ts +173 -173
- package/src/i18n/types.ts +112 -112
- package/src/id/__tests__/id.test.ts +3 -3
- package/src/id/index.ts +105 -105
- package/src/index.ts +9 -48
- package/src/internal/client-boundary.ts +266 -266
- package/src/internal/index.ts +2 -2
- package/src/kitchen/api/agent-devtools-api.ts +779 -779
- package/src/kitchen/api/bundle-inspector.ts +179 -0
- package/src/kitchen/api/errors-grouping.ts +126 -126
- package/src/kitchen/api/file-api.ts +11 -11
- package/src/kitchen/kitchen-handler.ts +36 -0
- package/src/kitchen/kitchen-ui.ts +456 -0
- package/src/logging/index.ts +22 -22
- package/src/logging/transports.ts +365 -365
- package/src/middleware/bridge.ts +147 -147
- package/src/middleware/compose.ts +134 -134
- package/src/middleware/compress.ts +62 -62
- package/src/middleware/cors.ts +47 -47
- package/src/middleware/csrf.ts +328 -328
- package/src/middleware/define.ts +132 -132
- package/src/middleware/jwt.ts +134 -134
- package/src/middleware/logger.ts +58 -58
- package/src/middleware/oauth/__tests__/oauth.test.ts +1 -1
- package/src/middleware/oauth/index.ts +505 -505
- package/src/middleware/oauth/providers.ts +115 -115
- package/src/middleware/rate-limit/index.ts +522 -522
- package/src/middleware/rate-limit/sqlite-store.ts +382 -382
- package/src/middleware/scheduler-cron.ts +96 -96
- package/src/middleware/secure/__tests__/secure.test.ts +360 -360
- package/src/middleware/secure/csp.ts +193 -193
- package/src/middleware/session.ts +174 -174
- package/src/middleware/timeout.ts +55 -55
- package/src/observability/logger-adapter.ts +36 -36
- package/src/observability/sqlite-store.ts +254 -254
- package/src/openapi/generator.ts +1 -1
- package/src/openapi/openapi.test.ts +43 -43
- package/src/perf/__tests__/user-marks.test.ts +354 -354
- package/src/perf/index.ts +133 -133
- package/src/perf/user-marks.ts +1 -1
- package/src/plugins/__tests__/lifecycle-integration.test.ts +272 -272
- package/src/plugins/__tests__/runner.test.ts +409 -409
- package/src/plugins/define.ts +124 -124
- package/src/plugins/examples/dep-check-plugin.ts +80 -80
- package/src/plugins/examples/prerender-cache-plugin.ts +111 -111
- package/src/plugins/examples/sitemap-plugin.ts +65 -65
- package/src/plugins/runner.ts +361 -361
- package/src/plugins/types.ts +368 -368
- package/src/report/index.ts +1 -1
- package/src/resource/__tests__/generator.test.ts +7 -7
- package/src/resource/__tests__/schema.test.ts +14 -14
- package/src/resource/ddl/__tests__/diff.test.ts +639 -639
- package/src/resource/ddl/__tests__/emit.test.ts +165 -165
- package/src/resource/ddl/__tests__/snapshot.test.ts +499 -499
- package/src/resource/ddl/emit.ts +146 -146
- package/src/resource/ddl/persistence-types.ts +218 -218
- package/src/resource/ddl/type-map.ts +223 -223
- package/src/resource/ddl/types.ts +232 -232
- package/src/resource/generator-repo.ts +630 -630
- package/src/resource/generator-schema.ts +11 -11
- package/src/resource/generators/slot.ts +72 -72
- package/src/resource/schema.ts +21 -21
- package/src/router/client-entry.test.ts +227 -227
- package/src/router/client-entry.ts +218 -218
- package/src/router/fs-patterns.test.ts +96 -96
- package/src/router/fs-routes.test.ts +532 -532
- package/src/router/fs-routes.ts +53 -53
- package/src/router/fs-scanner.ts +216 -216
- package/src/router/fs-types.ts +19 -19
- package/src/router/route-source-analyzer.ts +521 -521
- package/src/routes/index.ts +74 -74
- package/src/routes/metadata-routes.ts +427 -427
- package/src/routes/types.ts +341 -341
- package/src/runtime/__tests__/devtools-adapter.test.ts +68 -68
- package/src/runtime/__tests__/error-boundary-redaction.test.ts +141 -141
- package/src/runtime/__tests__/hdr-client.test.ts +223 -223
- package/src/runtime/__tests__/http-errors.test.ts +117 -117
- package/src/runtime/__tests__/inline-client-hydration.test.ts +237 -237
- package/src/runtime/__tests__/not-found.test.ts +152 -152
- package/src/runtime/__tests__/observability-lifecycle.test.ts +103 -103
- package/src/runtime/__tests__/page-render-response.test.ts +517 -517
- package/src/runtime/__tests__/request-middleware.test.ts +70 -70
- package/src/runtime/__tests__/searchparams-page-props.test.ts +81 -81
- package/src/runtime/adapter.ts +47 -47
- package/src/runtime/boundary.tsx +252 -252
- package/src/runtime/cache.ts +494 -494
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/devtools-adapter.ts +68 -68
- package/src/runtime/escape.ts +34 -34
- package/src/runtime/fast-refresh-runtime.ts +322 -322
- package/src/runtime/fast-refresh-types.ts +1 -1
- package/src/runtime/handler.ts +65 -65
- package/src/runtime/handlers.ts +30 -11
- package/src/runtime/http-errors.ts +113 -113
- package/src/runtime/image-handler.ts +1 -1
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/logger.test.ts +345 -345
- package/src/runtime/middleware.ts +264 -264
- package/src/runtime/not-found.ts +93 -93
- package/src/runtime/observability-lifecycle.ts +290 -290
- package/src/runtime/openapi-endpoint.ts +236 -236
- package/src/runtime/page-render-response.ts +287 -274
- package/src/runtime/ppr.ts +74 -74
- package/src/runtime/rate-limit.ts +1 -1
- package/src/runtime/redirect.ts +1 -1
- package/src/runtime/registry.ts +171 -171
- package/src/runtime/request-middleware.ts +31 -31
- package/src/runtime/router.test.ts +4 -4
- package/src/runtime/router.ts +10 -10
- package/src/runtime/server.ts +127 -211
- package/src/runtime/shims.ts +48 -48
- package/src/runtime/ssr.ts +43 -17
- package/src/runtime/static-files.ts +369 -289
- package/src/runtime/streaming-ssr.ts +219 -180
- package/src/runtime/trace.ts +144 -144
- package/src/scheduler/__tests__/scheduler.test.ts +12 -11
- package/src/scheduler/index.ts +11 -5
- package/src/scheduler/validate.ts +169 -169
- package/src/seo/index.ts +219 -219
- package/src/seo/integration/ssr.ts +306 -306
- package/src/seo/render/basic.ts +435 -435
- package/src/seo/render/index.ts +143 -143
- package/src/seo/render/jsonld.ts +539 -539
- package/src/seo/render/opengraph.ts +197 -197
- package/src/seo/render/robots.ts +116 -116
- package/src/seo/render/sitemap.ts +137 -137
- package/src/seo/render/twitter.ts +127 -127
- package/src/seo/resolve/opengraph.ts +143 -143
- package/src/seo/resolve/robots.ts +73 -73
- package/src/seo/resolve/title.ts +94 -94
- package/src/seo/resolve/twitter.ts +73 -73
- package/src/seo/resolve/url.ts +104 -104
- package/src/seo/routes/index.ts +290 -290
- package/src/seo/types.ts +588 -588
- package/src/slot/validator.ts +39 -39
- package/src/spec/schema.ts +35 -35
- package/src/storage/s3/__tests__/s3.test.ts +479 -479
- package/src/storage/s3/index.ts +412 -412
- package/src/testing/__tests__/assertions.test.ts +632 -632
- package/src/testing/__tests__/reporter.test.ts +454 -454
- package/src/testing/assertions.ts +986 -986
- package/src/testing/db.ts +157 -157
- package/src/testing/index.ts +9 -3
- package/src/testing/lcov.ts +192 -0
- package/src/testing/mocks.ts +203 -203
- package/src/testing/session.ts +190 -190
- package/src/types/branded.ts +56 -56
- package/src/types/index.ts +1 -1
- package/src/utils/safe-io.ts +188 -188
- package/src/utils/string-safe.ts +298 -298
- package/src/watcher/__tests__/watcher.test.ts +59 -59
- package/src/watcher/watcher.ts +61 -61
package/src/deploy/plan.ts
CHANGED
|
@@ -1,178 +1,178 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plan a deploy — compute the next `DeployIntentCache` from a
|
|
3
|
-
* manifest and the previous cache.
|
|
4
|
-
*
|
|
5
|
-
* Issue #250 — Phase 1.
|
|
6
|
-
*
|
|
7
|
-
* The plan step is pure: it takes the manifest + previous cache +
|
|
8
|
-
* inferer and returns the new cache + a diff. No filesystem writes
|
|
9
|
-
* here; that's the CLI's job. Keeping this pure makes the diff easy
|
|
10
|
-
* to render in tests and lets future surfaces (kitchen UI, MCP tool)
|
|
11
|
-
* reuse the same plan logic.
|
|
12
|
-
*
|
|
13
|
-
* Override hierarchy (highest wins):
|
|
14
|
-
*
|
|
15
|
-
* 1. **Explicit** — entry where `source === "explicit"` in the
|
|
16
|
-
* previous cache. Never overwritten by inference. Re-keyed by
|
|
17
|
-
* `sourceHash` only so users can hand-edit the file and have
|
|
18
|
-
* their edits stick.
|
|
19
|
-
* 2. **Cached, source unchanged** — same `sourceHash` ⇒ reuse the
|
|
20
|
-
* stored intent. This is the cost cap on brain calls.
|
|
21
|
-
* 3. **Inferred** — call the inferer. Default is heuristic; brain
|
|
22
|
-
* can swap in via the `infer` parameter.
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import type { RoutesManifest } from "../spec/schema";
|
|
26
|
-
import {
|
|
27
|
-
emptyDeployIntentCache,
|
|
28
|
-
type DeployIntentCache,
|
|
29
|
-
type DeployIntentCacheEntry,
|
|
30
|
-
} from "./cache";
|
|
31
|
-
import { buildDeployInferenceContext, type DeployInferenceContext } from "./inference/context";
|
|
32
|
-
import {
|
|
33
|
-
inferDeployIntentHeuristic,
|
|
34
|
-
type InferenceResult,
|
|
35
|
-
} from "./inference/heuristic";
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Per-route diff entry surfaced to the CLI / tests.
|
|
39
|
-
*
|
|
40
|
-
* - `added` — no prior entry, new intent inferred.
|
|
41
|
-
* - `unchanged` — same source hash; entry kept verbatim.
|
|
42
|
-
* - `changed` — source hash differs OR explicit override updated;
|
|
43
|
-
* the `previous` field carries the old entry for
|
|
44
|
-
* diff rendering.
|
|
45
|
-
* - `removed` — route exists in cache but not in manifest. Pruned.
|
|
46
|
-
* - `pinned` — explicit entry; inference skipped.
|
|
47
|
-
*/
|
|
48
|
-
export type PlanDiffEntryKind =
|
|
49
|
-
| "added"
|
|
50
|
-
| "unchanged"
|
|
51
|
-
| "changed"
|
|
52
|
-
| "removed"
|
|
53
|
-
| "pinned";
|
|
54
|
-
|
|
55
|
-
export interface PlanDiffEntry {
|
|
56
|
-
routeId: string;
|
|
57
|
-
pattern: string;
|
|
58
|
-
kind: PlanDiffEntryKind;
|
|
59
|
-
next?: DeployIntentCacheEntry;
|
|
60
|
-
previous?: DeployIntentCacheEntry;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface PlanResult {
|
|
64
|
-
cache: DeployIntentCache;
|
|
65
|
-
diff: PlanDiffEntry[];
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export interface PlanDeployOptions {
|
|
69
|
-
rootDir: string;
|
|
70
|
-
manifest: RoutesManifest;
|
|
71
|
-
previous?: DeployIntentCache;
|
|
72
|
-
/**
|
|
73
|
-
* Override the inferer. Defaults to the offline heuristic. The
|
|
74
|
-
* brain inferer (M4) plugs in here without changing the plan flow.
|
|
75
|
-
*/
|
|
76
|
-
infer?: (ctx: DeployInferenceContext) => Promise<InferenceResult> | InferenceResult;
|
|
77
|
-
/** Identifier written into `cache.brainModel`. Defaults to `"heuristic"`. */
|
|
78
|
-
brainModel?: string;
|
|
79
|
-
/** ISO timestamp override — primarily for test determinism. */
|
|
80
|
-
now?: () => string;
|
|
81
|
-
/** Force re-inference even when the source hash matches. */
|
|
82
|
-
reinfer?: boolean;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export async function planDeploy(opts: PlanDeployOptions): Promise<PlanResult> {
|
|
86
|
-
const previous = opts.previous ?? emptyDeployIntentCache();
|
|
87
|
-
const infer =
|
|
88
|
-
opts.infer ?? ((ctx: DeployInferenceContext) => inferDeployIntentHeuristic(ctx));
|
|
89
|
-
const now = opts.now ?? (() => new Date().toISOString());
|
|
90
|
-
const brainModel = opts.brainModel ?? "heuristic";
|
|
91
|
-
const reinfer = opts.reinfer === true;
|
|
92
|
-
|
|
93
|
-
const nextIntents: Record<string, DeployIntentCacheEntry> = {};
|
|
94
|
-
const diff: PlanDiffEntry[] = [];
|
|
95
|
-
|
|
96
|
-
for (const route of opts.manifest.routes) {
|
|
97
|
-
const ctx = await buildDeployInferenceContext(opts.rootDir, route);
|
|
98
|
-
const prevEntry = previous.intents[route.id];
|
|
99
|
-
|
|
100
|
-
// 1. Explicit override — never re-infer; only refresh sourceHash.
|
|
101
|
-
if (prevEntry && prevEntry.source === "explicit") {
|
|
102
|
-
const refreshed: DeployIntentCacheEntry = {
|
|
103
|
-
...prevEntry,
|
|
104
|
-
sourceHash: ctx.sourceHash,
|
|
105
|
-
};
|
|
106
|
-
nextIntents[route.id] = refreshed;
|
|
107
|
-
diff.push({
|
|
108
|
-
routeId: route.id,
|
|
109
|
-
pattern: route.pattern,
|
|
110
|
-
kind: "pinned",
|
|
111
|
-
next: refreshed,
|
|
112
|
-
previous: prevEntry,
|
|
113
|
-
});
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// 2. Cached, source unchanged.
|
|
118
|
-
if (
|
|
119
|
-
!reinfer &&
|
|
120
|
-
prevEntry &&
|
|
121
|
-
prevEntry.sourceHash === ctx.sourceHash
|
|
122
|
-
) {
|
|
123
|
-
nextIntents[route.id] = prevEntry;
|
|
124
|
-
diff.push({
|
|
125
|
-
routeId: route.id,
|
|
126
|
-
pattern: route.pattern,
|
|
127
|
-
kind: "unchanged",
|
|
128
|
-
next: prevEntry,
|
|
129
|
-
previous: prevEntry,
|
|
130
|
-
});
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 3. Infer.
|
|
135
|
-
const result = await infer(ctx);
|
|
136
|
-
const entry: DeployIntentCacheEntry = {
|
|
137
|
-
intent: result.intent,
|
|
138
|
-
source: "inferred",
|
|
139
|
-
rationale: result.rationale,
|
|
140
|
-
sourceHash: ctx.sourceHash,
|
|
141
|
-
inferredAt: now(),
|
|
142
|
-
};
|
|
143
|
-
nextIntents[route.id] = entry;
|
|
144
|
-
diff.push({
|
|
145
|
-
routeId: route.id,
|
|
146
|
-
pattern: route.pattern,
|
|
147
|
-
kind: prevEntry ? "changed" : "added",
|
|
148
|
-
next: entry,
|
|
149
|
-
previous: prevEntry,
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// 4. Detect removed entries (cache had them, manifest doesn't).
|
|
154
|
-
const manifestIds = new Set(opts.manifest.routes.map((r) => r.id));
|
|
155
|
-
for (const [routeId, entry] of Object.entries(previous.intents)) {
|
|
156
|
-
if (manifestIds.has(routeId)) continue;
|
|
157
|
-
diff.push({
|
|
158
|
-
routeId,
|
|
159
|
-
pattern: "(removed from manifest)",
|
|
160
|
-
kind: "removed",
|
|
161
|
-
previous: entry,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const cache: DeployIntentCache = {
|
|
166
|
-
version: 1,
|
|
167
|
-
generatedAt: now(),
|
|
168
|
-
brainModel,
|
|
169
|
-
intents: nextIntents,
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
return { cache, diff };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/** True if the diff contains any non-`unchanged` entry. */
|
|
176
|
-
export function planHasChanges(diff: readonly PlanDiffEntry[]): boolean {
|
|
177
|
-
return diff.some((d) => d.kind !== "unchanged" && d.kind !== "pinned");
|
|
178
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Plan a deploy — compute the next `DeployIntentCache` from a
|
|
3
|
+
* manifest and the previous cache.
|
|
4
|
+
*
|
|
5
|
+
* Issue #250 — Phase 1.
|
|
6
|
+
*
|
|
7
|
+
* The plan step is pure: it takes the manifest + previous cache +
|
|
8
|
+
* inferer and returns the new cache + a diff. No filesystem writes
|
|
9
|
+
* here; that's the CLI's job. Keeping this pure makes the diff easy
|
|
10
|
+
* to render in tests and lets future surfaces (kitchen UI, MCP tool)
|
|
11
|
+
* reuse the same plan logic.
|
|
12
|
+
*
|
|
13
|
+
* Override hierarchy (highest wins):
|
|
14
|
+
*
|
|
15
|
+
* 1. **Explicit** — entry where `source === "explicit"` in the
|
|
16
|
+
* previous cache. Never overwritten by inference. Re-keyed by
|
|
17
|
+
* `sourceHash` only so users can hand-edit the file and have
|
|
18
|
+
* their edits stick.
|
|
19
|
+
* 2. **Cached, source unchanged** — same `sourceHash` ⇒ reuse the
|
|
20
|
+
* stored intent. This is the cost cap on brain calls.
|
|
21
|
+
* 3. **Inferred** — call the inferer. Default is heuristic; brain
|
|
22
|
+
* can swap in via the `infer` parameter.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type { RoutesManifest } from "../spec/schema";
|
|
26
|
+
import {
|
|
27
|
+
emptyDeployIntentCache,
|
|
28
|
+
type DeployIntentCache,
|
|
29
|
+
type DeployIntentCacheEntry,
|
|
30
|
+
} from "./cache";
|
|
31
|
+
import { buildDeployInferenceContext, type DeployInferenceContext } from "./inference/context";
|
|
32
|
+
import {
|
|
33
|
+
inferDeployIntentHeuristic,
|
|
34
|
+
type InferenceResult,
|
|
35
|
+
} from "./inference/heuristic";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Per-route diff entry surfaced to the CLI / tests.
|
|
39
|
+
*
|
|
40
|
+
* - `added` — no prior entry, new intent inferred.
|
|
41
|
+
* - `unchanged` — same source hash; entry kept verbatim.
|
|
42
|
+
* - `changed` — source hash differs OR explicit override updated;
|
|
43
|
+
* the `previous` field carries the old entry for
|
|
44
|
+
* diff rendering.
|
|
45
|
+
* - `removed` — route exists in cache but not in manifest. Pruned.
|
|
46
|
+
* - `pinned` — explicit entry; inference skipped.
|
|
47
|
+
*/
|
|
48
|
+
export type PlanDiffEntryKind =
|
|
49
|
+
| "added"
|
|
50
|
+
| "unchanged"
|
|
51
|
+
| "changed"
|
|
52
|
+
| "removed"
|
|
53
|
+
| "pinned";
|
|
54
|
+
|
|
55
|
+
export interface PlanDiffEntry {
|
|
56
|
+
routeId: string;
|
|
57
|
+
pattern: string;
|
|
58
|
+
kind: PlanDiffEntryKind;
|
|
59
|
+
next?: DeployIntentCacheEntry;
|
|
60
|
+
previous?: DeployIntentCacheEntry;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface PlanResult {
|
|
64
|
+
cache: DeployIntentCache;
|
|
65
|
+
diff: PlanDiffEntry[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface PlanDeployOptions {
|
|
69
|
+
rootDir: string;
|
|
70
|
+
manifest: RoutesManifest;
|
|
71
|
+
previous?: DeployIntentCache;
|
|
72
|
+
/**
|
|
73
|
+
* Override the inferer. Defaults to the offline heuristic. The
|
|
74
|
+
* brain inferer (M4) plugs in here without changing the plan flow.
|
|
75
|
+
*/
|
|
76
|
+
infer?: (ctx: DeployInferenceContext) => Promise<InferenceResult> | InferenceResult;
|
|
77
|
+
/** Identifier written into `cache.brainModel`. Defaults to `"heuristic"`. */
|
|
78
|
+
brainModel?: string;
|
|
79
|
+
/** ISO timestamp override — primarily for test determinism. */
|
|
80
|
+
now?: () => string;
|
|
81
|
+
/** Force re-inference even when the source hash matches. */
|
|
82
|
+
reinfer?: boolean;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function planDeploy(opts: PlanDeployOptions): Promise<PlanResult> {
|
|
86
|
+
const previous = opts.previous ?? emptyDeployIntentCache();
|
|
87
|
+
const infer =
|
|
88
|
+
opts.infer ?? ((ctx: DeployInferenceContext) => inferDeployIntentHeuristic(ctx));
|
|
89
|
+
const now = opts.now ?? (() => new Date().toISOString());
|
|
90
|
+
const brainModel = opts.brainModel ?? "heuristic";
|
|
91
|
+
const reinfer = opts.reinfer === true;
|
|
92
|
+
|
|
93
|
+
const nextIntents: Record<string, DeployIntentCacheEntry> = {};
|
|
94
|
+
const diff: PlanDiffEntry[] = [];
|
|
95
|
+
|
|
96
|
+
for (const route of opts.manifest.routes) {
|
|
97
|
+
const ctx = await buildDeployInferenceContext(opts.rootDir, route);
|
|
98
|
+
const prevEntry = previous.intents[route.id];
|
|
99
|
+
|
|
100
|
+
// 1. Explicit override — never re-infer; only refresh sourceHash.
|
|
101
|
+
if (prevEntry && prevEntry.source === "explicit") {
|
|
102
|
+
const refreshed: DeployIntentCacheEntry = {
|
|
103
|
+
...prevEntry,
|
|
104
|
+
sourceHash: ctx.sourceHash,
|
|
105
|
+
};
|
|
106
|
+
nextIntents[route.id] = refreshed;
|
|
107
|
+
diff.push({
|
|
108
|
+
routeId: route.id,
|
|
109
|
+
pattern: route.pattern,
|
|
110
|
+
kind: "pinned",
|
|
111
|
+
next: refreshed,
|
|
112
|
+
previous: prevEntry,
|
|
113
|
+
});
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 2. Cached, source unchanged.
|
|
118
|
+
if (
|
|
119
|
+
!reinfer &&
|
|
120
|
+
prevEntry &&
|
|
121
|
+
prevEntry.sourceHash === ctx.sourceHash
|
|
122
|
+
) {
|
|
123
|
+
nextIntents[route.id] = prevEntry;
|
|
124
|
+
diff.push({
|
|
125
|
+
routeId: route.id,
|
|
126
|
+
pattern: route.pattern,
|
|
127
|
+
kind: "unchanged",
|
|
128
|
+
next: prevEntry,
|
|
129
|
+
previous: prevEntry,
|
|
130
|
+
});
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 3. Infer.
|
|
135
|
+
const result = await infer(ctx);
|
|
136
|
+
const entry: DeployIntentCacheEntry = {
|
|
137
|
+
intent: result.intent,
|
|
138
|
+
source: "inferred",
|
|
139
|
+
rationale: result.rationale,
|
|
140
|
+
sourceHash: ctx.sourceHash,
|
|
141
|
+
inferredAt: now(),
|
|
142
|
+
};
|
|
143
|
+
nextIntents[route.id] = entry;
|
|
144
|
+
diff.push({
|
|
145
|
+
routeId: route.id,
|
|
146
|
+
pattern: route.pattern,
|
|
147
|
+
kind: prevEntry ? "changed" : "added",
|
|
148
|
+
next: entry,
|
|
149
|
+
previous: prevEntry,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 4. Detect removed entries (cache had them, manifest doesn't).
|
|
154
|
+
const manifestIds = new Set(opts.manifest.routes.map((r) => r.id));
|
|
155
|
+
for (const [routeId, entry] of Object.entries(previous.intents)) {
|
|
156
|
+
if (manifestIds.has(routeId)) continue;
|
|
157
|
+
diff.push({
|
|
158
|
+
routeId,
|
|
159
|
+
pattern: "(removed from manifest)",
|
|
160
|
+
kind: "removed",
|
|
161
|
+
previous: entry,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const cache: DeployIntentCache = {
|
|
166
|
+
version: 1,
|
|
167
|
+
generatedAt: now(),
|
|
168
|
+
brainModel,
|
|
169
|
+
intents: nextIntents,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
return { cache, diff };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** True if the diff contains any non-`unchanged` entry. */
|
|
176
|
+
export function planHasChanges(diff: readonly PlanDiffEntry[]): boolean {
|
|
177
|
+
return diff.some((d) => d.kind !== "unchanged" && d.kind !== "pinned");
|
|
178
|
+
}
|
|
@@ -1,109 +1,109 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Issue #245 M5 — AGENTS.md / CLAUDE.md linker tests.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
6
|
-
import { promises as fs } from "node:fs";
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
import os from "node:os";
|
|
9
|
-
import {
|
|
10
|
-
buildAgentsDesignBlock,
|
|
11
|
-
DESIGN_LINK_MARKER_END,
|
|
12
|
-
DESIGN_LINK_MARKER_START,
|
|
13
|
-
linkAgentsToDesignMd,
|
|
14
|
-
} from "../agents-link";
|
|
15
|
-
|
|
16
|
-
describe("buildAgentsDesignBlock", () => {
|
|
17
|
-
it("contains markers, the canonical heading, and all 8 MCP tools", () => {
|
|
18
|
-
const block = buildAgentsDesignBlock();
|
|
19
|
-
expect(block).toContain(DESIGN_LINK_MARKER_START);
|
|
20
|
-
expect(block).toContain(DESIGN_LINK_MARKER_END);
|
|
21
|
-
expect(block).toContain("## Design System");
|
|
22
|
-
for (const tool of [
|
|
23
|
-
"mandu.design.get",
|
|
24
|
-
"mandu.design.prompt",
|
|
25
|
-
"mandu.component.list",
|
|
26
|
-
"mandu.design.check",
|
|
27
|
-
"mandu.design.extract",
|
|
28
|
-
"mandu.design.propose",
|
|
29
|
-
"mandu.design.patch",
|
|
30
|
-
"mandu.design.diff_upstream",
|
|
31
|
-
]) {
|
|
32
|
-
expect(block).toContain(tool);
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("respects custom DESIGN.md filename", () => {
|
|
37
|
-
expect(buildAgentsDesignBlock("Stripe-DESIGN.md")).toContain("Stripe-DESIGN.md");
|
|
38
|
-
});
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
describe("linkAgentsToDesignMd", () => {
|
|
42
|
-
let TEST_DIR: string;
|
|
43
|
-
beforeEach(async () => {
|
|
44
|
-
TEST_DIR = await fs.mkdtemp(path.join(os.tmpdir(), "mandu-design-link-"));
|
|
45
|
-
});
|
|
46
|
-
afterEach(async () => {
|
|
47
|
-
await fs.rm(TEST_DIR, { recursive: true, force: true });
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("inserts the block at end of an existing AGENTS.md", async () => {
|
|
51
|
-
await fs.writeFile(path.join(TEST_DIR, "AGENTS.md"), "# Existing\n\nSome notes.\n");
|
|
52
|
-
const result = await linkAgentsToDesignMd({ rootDir: TEST_DIR });
|
|
53
|
-
expect(result.changed).toBe(true);
|
|
54
|
-
expect(result.files.find((f) => f.path.endsWith("AGENTS.md"))?.action).toBe("inserted");
|
|
55
|
-
const after = await fs.readFile(path.join(TEST_DIR, "AGENTS.md"), "utf8");
|
|
56
|
-
expect(after).toContain("# Existing");
|
|
57
|
-
expect(after).toContain("Some notes.");
|
|
58
|
-
expect(after).toContain("## Design System");
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("is idempotent — running twice produces no diff on the second run", async () => {
|
|
62
|
-
await fs.writeFile(path.join(TEST_DIR, "AGENTS.md"), "# X\n");
|
|
63
|
-
await linkAgentsToDesignMd({ rootDir: TEST_DIR });
|
|
64
|
-
const after1 = await fs.readFile(path.join(TEST_DIR, "AGENTS.md"), "utf8");
|
|
65
|
-
const second = await linkAgentsToDesignMd({ rootDir: TEST_DIR });
|
|
66
|
-
const after2 = await fs.readFile(path.join(TEST_DIR, "AGENTS.md"), "utf8");
|
|
67
|
-
expect(after1).toBe(after2);
|
|
68
|
-
expect(second.files.find((f) => f.path.endsWith("AGENTS.md"))?.action).toBe("unchanged");
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("replaces an existing markered block when the body changed", async () => {
|
|
72
|
-
await fs.writeFile(
|
|
73
|
-
path.join(TEST_DIR, "AGENTS.md"),
|
|
74
|
-
`# X\n\n${DESIGN_LINK_MARKER_START}\n\n## Old block\n\n${DESIGN_LINK_MARKER_END}\n`,
|
|
75
|
-
);
|
|
76
|
-
await linkAgentsToDesignMd({ rootDir: TEST_DIR });
|
|
77
|
-
const after = await fs.readFile(path.join(TEST_DIR, "AGENTS.md"), "utf8");
|
|
78
|
-
expect(after).not.toContain("## Old block");
|
|
79
|
-
expect(after).toContain("## Design System");
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("touches CLAUDE.md too when both exist", async () => {
|
|
83
|
-
await fs.writeFile(path.join(TEST_DIR, "AGENTS.md"), "# A\n");
|
|
84
|
-
await fs.writeFile(path.join(TEST_DIR, "CLAUDE.md"), "# C\n");
|
|
85
|
-
const result = await linkAgentsToDesignMd({ rootDir: TEST_DIR });
|
|
86
|
-
expect(result.files.filter((f) => f.action === "inserted")).toHaveLength(2);
|
|
87
|
-
expect(await fs.readFile(path.join(TEST_DIR, "CLAUDE.md"), "utf8")).toContain(
|
|
88
|
-
"## Design System",
|
|
89
|
-
);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("creates AGENTS.md when neither file exists and createIfMissing is true", async () => {
|
|
93
|
-
const result = await linkAgentsToDesignMd({
|
|
94
|
-
rootDir: TEST_DIR,
|
|
95
|
-
createIfMissing: true,
|
|
96
|
-
});
|
|
97
|
-
expect(result.changed).toBe(true);
|
|
98
|
-
expect(result.files.find((f) => f.action === "created")).toBeDefined();
|
|
99
|
-
expect(await fs.readFile(path.join(TEST_DIR, "AGENTS.md"), "utf8")).toContain(
|
|
100
|
-
"## Design System",
|
|
101
|
-
);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("is a no-op when neither file exists and createIfMissing is false", async () => {
|
|
105
|
-
const result = await linkAgentsToDesignMd({ rootDir: TEST_DIR });
|
|
106
|
-
expect(result.changed).toBe(false);
|
|
107
|
-
await expect(fs.access(path.join(TEST_DIR, "AGENTS.md"))).rejects.toThrow();
|
|
108
|
-
});
|
|
109
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* Issue #245 M5 — AGENTS.md / CLAUDE.md linker tests.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
|
6
|
+
import { promises as fs } from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import os from "node:os";
|
|
9
|
+
import {
|
|
10
|
+
buildAgentsDesignBlock,
|
|
11
|
+
DESIGN_LINK_MARKER_END,
|
|
12
|
+
DESIGN_LINK_MARKER_START,
|
|
13
|
+
linkAgentsToDesignMd,
|
|
14
|
+
} from "../agents-link";
|
|
15
|
+
|
|
16
|
+
describe("buildAgentsDesignBlock", () => {
|
|
17
|
+
it("contains markers, the canonical heading, and all 8 MCP tools", () => {
|
|
18
|
+
const block = buildAgentsDesignBlock();
|
|
19
|
+
expect(block).toContain(DESIGN_LINK_MARKER_START);
|
|
20
|
+
expect(block).toContain(DESIGN_LINK_MARKER_END);
|
|
21
|
+
expect(block).toContain("## Design System");
|
|
22
|
+
for (const tool of [
|
|
23
|
+
"mandu.design.get",
|
|
24
|
+
"mandu.design.prompt",
|
|
25
|
+
"mandu.component.list",
|
|
26
|
+
"mandu.design.check",
|
|
27
|
+
"mandu.design.extract",
|
|
28
|
+
"mandu.design.propose",
|
|
29
|
+
"mandu.design.patch",
|
|
30
|
+
"mandu.design.diff_upstream",
|
|
31
|
+
]) {
|
|
32
|
+
expect(block).toContain(tool);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("respects custom DESIGN.md filename", () => {
|
|
37
|
+
expect(buildAgentsDesignBlock("Stripe-DESIGN.md")).toContain("Stripe-DESIGN.md");
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("linkAgentsToDesignMd", () => {
|
|
42
|
+
let TEST_DIR: string;
|
|
43
|
+
beforeEach(async () => {
|
|
44
|
+
TEST_DIR = await fs.mkdtemp(path.join(os.tmpdir(), "mandu-design-link-"));
|
|
45
|
+
});
|
|
46
|
+
afterEach(async () => {
|
|
47
|
+
await fs.rm(TEST_DIR, { recursive: true, force: true });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("inserts the block at end of an existing AGENTS.md", async () => {
|
|
51
|
+
await fs.writeFile(path.join(TEST_DIR, "AGENTS.md"), "# Existing\n\nSome notes.\n");
|
|
52
|
+
const result = await linkAgentsToDesignMd({ rootDir: TEST_DIR });
|
|
53
|
+
expect(result.changed).toBe(true);
|
|
54
|
+
expect(result.files.find((f) => f.path.endsWith("AGENTS.md"))?.action).toBe("inserted");
|
|
55
|
+
const after = await fs.readFile(path.join(TEST_DIR, "AGENTS.md"), "utf8");
|
|
56
|
+
expect(after).toContain("# Existing");
|
|
57
|
+
expect(after).toContain("Some notes.");
|
|
58
|
+
expect(after).toContain("## Design System");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("is idempotent — running twice produces no diff on the second run", async () => {
|
|
62
|
+
await fs.writeFile(path.join(TEST_DIR, "AGENTS.md"), "# X\n");
|
|
63
|
+
await linkAgentsToDesignMd({ rootDir: TEST_DIR });
|
|
64
|
+
const after1 = await fs.readFile(path.join(TEST_DIR, "AGENTS.md"), "utf8");
|
|
65
|
+
const second = await linkAgentsToDesignMd({ rootDir: TEST_DIR });
|
|
66
|
+
const after2 = await fs.readFile(path.join(TEST_DIR, "AGENTS.md"), "utf8");
|
|
67
|
+
expect(after1).toBe(after2);
|
|
68
|
+
expect(second.files.find((f) => f.path.endsWith("AGENTS.md"))?.action).toBe("unchanged");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("replaces an existing markered block when the body changed", async () => {
|
|
72
|
+
await fs.writeFile(
|
|
73
|
+
path.join(TEST_DIR, "AGENTS.md"),
|
|
74
|
+
`# X\n\n${DESIGN_LINK_MARKER_START}\n\n## Old block\n\n${DESIGN_LINK_MARKER_END}\n`,
|
|
75
|
+
);
|
|
76
|
+
await linkAgentsToDesignMd({ rootDir: TEST_DIR });
|
|
77
|
+
const after = await fs.readFile(path.join(TEST_DIR, "AGENTS.md"), "utf8");
|
|
78
|
+
expect(after).not.toContain("## Old block");
|
|
79
|
+
expect(after).toContain("## Design System");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("touches CLAUDE.md too when both exist", async () => {
|
|
83
|
+
await fs.writeFile(path.join(TEST_DIR, "AGENTS.md"), "# A\n");
|
|
84
|
+
await fs.writeFile(path.join(TEST_DIR, "CLAUDE.md"), "# C\n");
|
|
85
|
+
const result = await linkAgentsToDesignMd({ rootDir: TEST_DIR });
|
|
86
|
+
expect(result.files.filter((f) => f.action === "inserted")).toHaveLength(2);
|
|
87
|
+
expect(await fs.readFile(path.join(TEST_DIR, "CLAUDE.md"), "utf8")).toContain(
|
|
88
|
+
"## Design System",
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("creates AGENTS.md when neither file exists and createIfMissing is true", async () => {
|
|
93
|
+
const result = await linkAgentsToDesignMd({
|
|
94
|
+
rootDir: TEST_DIR,
|
|
95
|
+
createIfMissing: true,
|
|
96
|
+
});
|
|
97
|
+
expect(result.changed).toBe(true);
|
|
98
|
+
expect(result.files.find((f) => f.action === "created")).toBeDefined();
|
|
99
|
+
expect(await fs.readFile(path.join(TEST_DIR, "AGENTS.md"), "utf8")).toContain(
|
|
100
|
+
"## Design System",
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("is a no-op when neither file exists and createIfMissing is false", async () => {
|
|
105
|
+
const result = await linkAgentsToDesignMd({ rootDir: TEST_DIR });
|
|
106
|
+
expect(result.changed).toBe(false);
|
|
107
|
+
await expect(fs.access(path.join(TEST_DIR, "AGENTS.md"))).rejects.toThrow();
|
|
108
|
+
});
|
|
109
|
+
});
|