@mandujs/core 0.53.2 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +654 -654
- package/package.json +2 -2
- 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 +394 -394
- package/src/a11y/types.ts +125 -125
- package/src/auth/__tests__/login.test.ts +1 -1
- package/src/auth/__tests__/password.test.ts +122 -122
- 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 +120 -120
- 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/bundler/__tests__/build-runner.ts +113 -113
- 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 +10 -10
- package/src/bundler/__tests__/hdr.test.ts +353 -353
- package/src/bundler/__tests__/hmr-client.test.ts +532 -532
- package/src/bundler/__tests__/manifest-schema.test.ts +266 -266
- package/src/bundler/__tests__/prod-smoke.test.ts +138 -138
- package/src/bundler/__tests__/reverse-import-graph.test.ts +519 -519
- 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/budget.ts +404 -404
- package/src/bundler/build.test.ts +179 -179
- package/src/bundler/build.ts +55 -55
- package/src/bundler/dev.ts +42 -42
- package/src/bundler/manifest-schema.ts +301 -301
- package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +263 -263
- 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 +155 -155
- 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/reverse-import-graph.ts +339 -339
- package/src/bundler/safe-build.test.ts +201 -201
- package/src/bundler/safe-build.ts +103 -103
- package/src/bundler/scenario-matrix.ts +229 -229
- package/src/bundler/types.ts +10 -10
- 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__/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 +340 -340
- package/src/client/prefetch-helper.ts +55 -55
- package/src/client/router.ts +11 -11
- package/src/client/runtime.ts +47 -47
- package/src/client/serialize.ts +404 -404
- 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.tsx +162 -162
- package/src/config/validate.ts +3 -3
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/collection.ts +8 -8
- 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/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/schema.ts +48 -48
- package/src/contract/types.ts +58 -58
- package/src/db/__tests__/db.test.ts +482 -482
- package/src/db/index.ts +138 -138
- 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 +182 -182
- 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__/webview-fallback.test.ts +254 -254
- package/src/desktop/__tests__/window.test.ts +248 -248
- 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/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/overlay.tsx +368 -368
- 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 +320 -320
- package/src/devtools/client/components/panel/network-panel.tsx +291 -291
- package/src/devtools/client/components/panel/panel-container.tsx +500 -500
- 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/client/persistence.ts +335 -335
- 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/server/source-context.ts +450 -450
- 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/devtools/worker/worker-manager.ts +410 -410
- package/src/diagnose/__tests__/checks.test.ts +451 -380
- package/src/diagnose/checks.ts +832 -720
- package/src/diagnose/index.ts +17 -16
- package/src/diagnose/run.ts +93 -91
- 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/result.ts +60 -60
- 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 +454 -454
- package/src/filling/auth.ts +308 -308
- package/src/filling/cookie-codec.ts +299 -299
- package/src/filling/deps.ts +265 -265
- 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/index.ts +3 -3
- 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/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/graph.ts +1 -1
- package/src/guard/healing.ts +36 -36
- 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/index.ts +105 -105
- package/src/index.ts +2 -2
- package/src/kitchen/api/agent-devtools-api.ts +779 -544
- package/src/kitchen/api/errors-grouping.ts +126 -0
- package/src/kitchen/kitchen-handler.ts +192 -63
- package/src/kitchen/kitchen-ui.ts +842 -464
- 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/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 +32 -32
- package/src/resource/ddl/__tests__/diff.test.ts +639 -639
- package/src/resource/ddl/__tests__/emit.test.ts +823 -823
- package/src/resource/ddl/__tests__/snapshot.test.ts +499 -499
- package/src/resource/ddl/emit.ts +559 -559
- 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/schema.ts +1 -1
- package/src/router/fs-patterns.test.ts +96 -96
- package/src/router/fs-routes.ts +6 -6
- 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__/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__/not-found.test.ts +152 -152
- 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/fast-refresh-runtime.ts +322 -322
- package/src/runtime/handler.ts +65 -65
- package/src/runtime/handlers.ts +300 -300
- 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/openapi-endpoint.ts +236 -236
- package/src/runtime/ppr.ts +74 -74
- package/src/runtime/registry.ts +171 -171
- package/src/runtime/router.ts +105 -105
- package/src/runtime/server.ts +67 -67
- package/src/runtime/shims.ts +48 -48
- package/src/runtime/ssr.ts +24 -31
- package/src/runtime/streaming-ssr.ts +32 -37
- package/src/runtime/trace.ts +144 -144
- 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/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/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/watcher.ts +18 -18
package/src/deploy/cache.ts
CHANGED
|
@@ -1,140 +1,140 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Deploy intent cache — `.mandu/deploy.intent.json`.
|
|
3
|
-
*
|
|
4
|
-
* Issue #250 — Phase 1.
|
|
5
|
-
*
|
|
6
|
-
* The cache is the persistence layer between `mandu deploy:plan`
|
|
7
|
-
* (writer) and `mandu deploy --target=...` (reader). It MUST be
|
|
8
|
-
* checked into the repo so deploys are deterministic, brain-free, and
|
|
9
|
-
* reproducible across CI runs.
|
|
10
|
-
*
|
|
11
|
-
* Per-entry shape:
|
|
12
|
-
*
|
|
13
|
-
* - `intent` — fully validated `DeployIntent`.
|
|
14
|
-
* - `source` — `"explicit"` (user wrote `.deploy()` on the
|
|
15
|
-
* route) or `"inferred"` (heuristic / brain).
|
|
16
|
-
* Explicit entries are never overwritten by
|
|
17
|
-
* `deploy:plan` — the inferer treats them as
|
|
18
|
-
* pinned ground truth.
|
|
19
|
-
* - `rationale` — short, human-readable reason. The plan command
|
|
20
|
-
* surfaces this in the diff so reviewers can audit
|
|
21
|
-
* why a route landed on a given runtime.
|
|
22
|
-
* - `sourceHash` — content hash of the route source the intent was
|
|
23
|
-
* inferred from. The next plan call skips
|
|
24
|
-
* re-inference when the hash matches — that's the
|
|
25
|
-
* cost cap on brain calls.
|
|
26
|
-
* - `inferredAt` — ISO timestamp; left unset on `explicit` entries.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import { z } from "zod";
|
|
30
|
-
import { promises as fs } from "fs";
|
|
31
|
-
import path from "path";
|
|
32
|
-
import { DeployIntent } from "./intent";
|
|
33
|
-
|
|
34
|
-
export const DeployIntentSource = z.enum(["explicit", "inferred"]);
|
|
35
|
-
export type DeployIntentSource = z.infer<typeof DeployIntentSource>;
|
|
36
|
-
|
|
37
|
-
export const DeployIntentCacheEntry = z.object({
|
|
38
|
-
intent: DeployIntent,
|
|
39
|
-
source: DeployIntentSource,
|
|
40
|
-
rationale: z.string().min(1),
|
|
41
|
-
sourceHash: z.string().min(1),
|
|
42
|
-
inferredAt: z.string().datetime().optional(),
|
|
43
|
-
});
|
|
44
|
-
export type DeployIntentCacheEntry = z.infer<typeof DeployIntentCacheEntry>;
|
|
45
|
-
|
|
46
|
-
export const DeployIntentCache = z.object({
|
|
47
|
-
/** Format version — bump on any breaking schema change. */
|
|
48
|
-
version: z.literal(1),
|
|
49
|
-
/** ISO timestamp of the most recent `deploy:plan` write. */
|
|
50
|
-
generatedAt: z.string().datetime(),
|
|
51
|
-
/**
|
|
52
|
-
* Identifier of the inferer used for the most recent write. Examples:
|
|
53
|
-
* - `"heuristic"` — rule-tree only (no brain).
|
|
54
|
-
* - `"openai:gpt-4.1-mini"` — brain-validated.
|
|
55
|
-
* - `"manual"` — user hand-edited entries.
|
|
56
|
-
*/
|
|
57
|
-
brainModel: z.string().min(1).default("heuristic"),
|
|
58
|
-
/** Map of route id → cache entry. */
|
|
59
|
-
intents: z.record(z.string(), DeployIntentCacheEntry),
|
|
60
|
-
});
|
|
61
|
-
export type DeployIntentCache = z.infer<typeof DeployIntentCache>;
|
|
62
|
-
|
|
63
|
-
/** Filename relative to the project root. */
|
|
64
|
-
export const DEPLOY_INTENT_CACHE_FILE = ".mandu/deploy.intent.json";
|
|
65
|
-
|
|
66
|
-
/** Resolve the absolute path of the cache file for a given project root. */
|
|
67
|
-
export function resolveDeployIntentCachePath(rootDir: string): string {
|
|
68
|
-
return path.join(rootDir, DEPLOY_INTENT_CACHE_FILE);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* An empty (but valid) cache. Returned by `loadDeployIntentCache`
|
|
73
|
-
* when the file is missing — the caller treats that as "every route
|
|
74
|
-
* needs inference".
|
|
75
|
-
*/
|
|
76
|
-
export function emptyDeployIntentCache(): DeployIntentCache {
|
|
77
|
-
return {
|
|
78
|
-
version: 1,
|
|
79
|
-
generatedAt: new Date().toISOString(),
|
|
80
|
-
brainModel: "heuristic",
|
|
81
|
-
intents: {},
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Read + validate the cache file. A missing file resolves to an empty
|
|
87
|
-
* cache so first-time runs work without ceremony. A malformed file
|
|
88
|
-
* rejects — silently swallowing JSON errors would let a corrupted
|
|
89
|
-
* cache silently produce wrong deploy configs.
|
|
90
|
-
*/
|
|
91
|
-
export async function loadDeployIntentCache(
|
|
92
|
-
rootDir: string,
|
|
93
|
-
): Promise<DeployIntentCache> {
|
|
94
|
-
const file = resolveDeployIntentCachePath(rootDir);
|
|
95
|
-
let raw: string;
|
|
96
|
-
try {
|
|
97
|
-
raw = await fs.readFile(file, "utf8");
|
|
98
|
-
} catch (err) {
|
|
99
|
-
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
100
|
-
return emptyDeployIntentCache();
|
|
101
|
-
}
|
|
102
|
-
throw err;
|
|
103
|
-
}
|
|
104
|
-
let parsed: unknown;
|
|
105
|
-
try {
|
|
106
|
-
parsed = JSON.parse(raw);
|
|
107
|
-
} catch (err) {
|
|
108
|
-
throw new Error(
|
|
109
|
-
`Deploy intent cache is not valid JSON (${file}): ${(err as Error).message}`,
|
|
110
|
-
{ cause: err },
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
return DeployIntentCache.parse(parsed);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Atomically write the cache. The intermediate `.tmp` file + rename
|
|
118
|
-
* dance prevents a half-written cache from being read by a concurrent
|
|
119
|
-
* deploy.
|
|
120
|
-
*/
|
|
121
|
-
export async function saveDeployIntentCache(
|
|
122
|
-
rootDir: string,
|
|
123
|
-
cache: DeployIntentCache,
|
|
124
|
-
): Promise<void> {
|
|
125
|
-
const validated = DeployIntentCache.parse(cache);
|
|
126
|
-
const file = resolveDeployIntentCachePath(rootDir);
|
|
127
|
-
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
128
|
-
const tmp = `${file}.tmp`;
|
|
129
|
-
// Stable key order so the committed file stays diff-friendly.
|
|
130
|
-
const ordered: DeployIntentCache = {
|
|
131
|
-
...validated,
|
|
132
|
-
intents: Object.fromEntries(
|
|
133
|
-
Object.keys(validated.intents)
|
|
134
|
-
.sort()
|
|
135
|
-
.map((id) => [id, validated.intents[id]!]),
|
|
136
|
-
),
|
|
137
|
-
};
|
|
138
|
-
await fs.writeFile(tmp, JSON.stringify(ordered, null, 2) + "\n", "utf8");
|
|
139
|
-
await fs.rename(tmp, file);
|
|
140
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Deploy intent cache — `.mandu/deploy.intent.json`.
|
|
3
|
+
*
|
|
4
|
+
* Issue #250 — Phase 1.
|
|
5
|
+
*
|
|
6
|
+
* The cache is the persistence layer between `mandu deploy:plan`
|
|
7
|
+
* (writer) and `mandu deploy --target=...` (reader). It MUST be
|
|
8
|
+
* checked into the repo so deploys are deterministic, brain-free, and
|
|
9
|
+
* reproducible across CI runs.
|
|
10
|
+
*
|
|
11
|
+
* Per-entry shape:
|
|
12
|
+
*
|
|
13
|
+
* - `intent` — fully validated `DeployIntent`.
|
|
14
|
+
* - `source` — `"explicit"` (user wrote `.deploy()` on the
|
|
15
|
+
* route) or `"inferred"` (heuristic / brain).
|
|
16
|
+
* Explicit entries are never overwritten by
|
|
17
|
+
* `deploy:plan` — the inferer treats them as
|
|
18
|
+
* pinned ground truth.
|
|
19
|
+
* - `rationale` — short, human-readable reason. The plan command
|
|
20
|
+
* surfaces this in the diff so reviewers can audit
|
|
21
|
+
* why a route landed on a given runtime.
|
|
22
|
+
* - `sourceHash` — content hash of the route source the intent was
|
|
23
|
+
* inferred from. The next plan call skips
|
|
24
|
+
* re-inference when the hash matches — that's the
|
|
25
|
+
* cost cap on brain calls.
|
|
26
|
+
* - `inferredAt` — ISO timestamp; left unset on `explicit` entries.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { z } from "zod";
|
|
30
|
+
import { promises as fs } from "fs";
|
|
31
|
+
import path from "path";
|
|
32
|
+
import { DeployIntent } from "./intent";
|
|
33
|
+
|
|
34
|
+
export const DeployIntentSource = z.enum(["explicit", "inferred"]);
|
|
35
|
+
export type DeployIntentSource = z.infer<typeof DeployIntentSource>;
|
|
36
|
+
|
|
37
|
+
export const DeployIntentCacheEntry = z.object({
|
|
38
|
+
intent: DeployIntent,
|
|
39
|
+
source: DeployIntentSource,
|
|
40
|
+
rationale: z.string().min(1),
|
|
41
|
+
sourceHash: z.string().min(1),
|
|
42
|
+
inferredAt: z.string().datetime().optional(),
|
|
43
|
+
});
|
|
44
|
+
export type DeployIntentCacheEntry = z.infer<typeof DeployIntentCacheEntry>;
|
|
45
|
+
|
|
46
|
+
export const DeployIntentCache = z.object({
|
|
47
|
+
/** Format version — bump on any breaking schema change. */
|
|
48
|
+
version: z.literal(1),
|
|
49
|
+
/** ISO timestamp of the most recent `deploy:plan` write. */
|
|
50
|
+
generatedAt: z.string().datetime(),
|
|
51
|
+
/**
|
|
52
|
+
* Identifier of the inferer used for the most recent write. Examples:
|
|
53
|
+
* - `"heuristic"` — rule-tree only (no brain).
|
|
54
|
+
* - `"openai:gpt-4.1-mini"` — brain-validated.
|
|
55
|
+
* - `"manual"` — user hand-edited entries.
|
|
56
|
+
*/
|
|
57
|
+
brainModel: z.string().min(1).default("heuristic"),
|
|
58
|
+
/** Map of route id → cache entry. */
|
|
59
|
+
intents: z.record(z.string(), DeployIntentCacheEntry),
|
|
60
|
+
});
|
|
61
|
+
export type DeployIntentCache = z.infer<typeof DeployIntentCache>;
|
|
62
|
+
|
|
63
|
+
/** Filename relative to the project root. */
|
|
64
|
+
export const DEPLOY_INTENT_CACHE_FILE = ".mandu/deploy.intent.json";
|
|
65
|
+
|
|
66
|
+
/** Resolve the absolute path of the cache file for a given project root. */
|
|
67
|
+
export function resolveDeployIntentCachePath(rootDir: string): string {
|
|
68
|
+
return path.join(rootDir, DEPLOY_INTENT_CACHE_FILE);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* An empty (but valid) cache. Returned by `loadDeployIntentCache`
|
|
73
|
+
* when the file is missing — the caller treats that as "every route
|
|
74
|
+
* needs inference".
|
|
75
|
+
*/
|
|
76
|
+
export function emptyDeployIntentCache(): DeployIntentCache {
|
|
77
|
+
return {
|
|
78
|
+
version: 1,
|
|
79
|
+
generatedAt: new Date().toISOString(),
|
|
80
|
+
brainModel: "heuristic",
|
|
81
|
+
intents: {},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Read + validate the cache file. A missing file resolves to an empty
|
|
87
|
+
* cache so first-time runs work without ceremony. A malformed file
|
|
88
|
+
* rejects — silently swallowing JSON errors would let a corrupted
|
|
89
|
+
* cache silently produce wrong deploy configs.
|
|
90
|
+
*/
|
|
91
|
+
export async function loadDeployIntentCache(
|
|
92
|
+
rootDir: string,
|
|
93
|
+
): Promise<DeployIntentCache> {
|
|
94
|
+
const file = resolveDeployIntentCachePath(rootDir);
|
|
95
|
+
let raw: string;
|
|
96
|
+
try {
|
|
97
|
+
raw = await fs.readFile(file, "utf8");
|
|
98
|
+
} catch (err) {
|
|
99
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
100
|
+
return emptyDeployIntentCache();
|
|
101
|
+
}
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
let parsed: unknown;
|
|
105
|
+
try {
|
|
106
|
+
parsed = JSON.parse(raw);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Deploy intent cache is not valid JSON (${file}): ${(err as Error).message}`,
|
|
110
|
+
{ cause: err },
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return DeployIntentCache.parse(parsed);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Atomically write the cache. The intermediate `.tmp` file + rename
|
|
118
|
+
* dance prevents a half-written cache from being read by a concurrent
|
|
119
|
+
* deploy.
|
|
120
|
+
*/
|
|
121
|
+
export async function saveDeployIntentCache(
|
|
122
|
+
rootDir: string,
|
|
123
|
+
cache: DeployIntentCache,
|
|
124
|
+
): Promise<void> {
|
|
125
|
+
const validated = DeployIntentCache.parse(cache);
|
|
126
|
+
const file = resolveDeployIntentCachePath(rootDir);
|
|
127
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
128
|
+
const tmp = `${file}.tmp`;
|
|
129
|
+
// Stable key order so the committed file stays diff-friendly.
|
|
130
|
+
const ordered: DeployIntentCache = {
|
|
131
|
+
...validated,
|
|
132
|
+
intents: Object.fromEntries(
|
|
133
|
+
Object.keys(validated.intents)
|
|
134
|
+
.sort()
|
|
135
|
+
.map((id) => [id, validated.intents[id]!]),
|
|
136
|
+
),
|
|
137
|
+
};
|
|
138
|
+
await fs.writeFile(tmp, JSON.stringify(ordered, null, 2) + "\n", "utf8");
|
|
139
|
+
await fs.rename(tmp, file);
|
|
140
|
+
}
|