@1agh/maude 1.0.9 → 1.2.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/apps/studio/acp/bridge.ts +18 -13
- package/apps/studio/acp/probe.ts +268 -16
- package/apps/studio/acp/transcript-io.ts +394 -0
- package/apps/studio/acp/transcript.ts +103 -38
- package/apps/studio/api.ts +139 -26
- package/apps/studio/bin/_import-figma.mjs +57 -11
- package/apps/studio/bin/_seed-fixture.mjs +163 -0
- package/apps/studio/canvas-imports.ts +89 -0
- package/apps/studio/client/app.jsx +257 -7
- package/apps/studio/client/export-center.jsx +15 -7
- package/apps/studio/client/panels/ReadinessList.jsx +7 -1
- package/apps/studio/client/panels/SyncPanel.jsx +125 -4
- package/apps/studio/client/report-bug.jsx +19 -1
- package/apps/studio/client/styles/3-shell-maude.css +34 -0
- package/apps/studio/client/styles/4-components.css +26 -0
- package/apps/studio/collab/persistence.ts +132 -16
- package/apps/studio/collab/registry.ts +10 -18
- package/apps/studio/debug-bundle.ts +13 -0
- package/apps/studio/dist/client.bundle.js +1128 -1128
- package/apps/studio/dist/styles.css +1 -1
- package/apps/studio/export-dialog.tsx +54 -0
- package/apps/studio/exporters/degraded.ts +19 -3
- package/apps/studio/exporters/ghostscript.ts +194 -0
- package/apps/studio/exporters/pdf-fonts.ts +261 -0
- package/apps/studio/exporters/pdf.ts +152 -3
- package/apps/studio/exporters/remote.ts +36 -1
- package/apps/studio/fs-watch.ts +45 -1
- package/apps/studio/http.ts +7 -1
- package/apps/studio/mem.ts +45 -9
- package/apps/studio/readiness.ts +42 -7
- package/apps/studio/server.ts +11 -4
- package/apps/studio/sync/agent.ts +136 -29
- package/apps/studio/sync/asset-push.ts +1 -47
- package/apps/studio/sync/codec.ts +122 -31
- package/apps/studio/sync/cold-start-apply.ts +7 -0
- package/apps/studio/sync/cold-start.ts +118 -22
- package/apps/studio/sync/comment-identity.ts +68 -0
- package/apps/studio/sync/connection-state.ts +15 -1
- package/apps/studio/sync/file-ledger.ts +117 -0
- package/apps/studio/sync/file-plane.ts +623 -27
- package/apps/studio/sync/index.ts +711 -20
- package/apps/studio/sync/journal-client.ts +13 -1
- package/apps/studio/sync/migrate-seed.ts +40 -3
- package/apps/studio/sync/presentation.ts +26 -3
- package/apps/studio/sync/retry-after.ts +169 -0
- package/apps/studio/sync/seed-progress.ts +218 -0
- package/apps/studio/sync/status.ts +122 -5
- package/apps/studio/sync/transport-error.ts +111 -0
- package/apps/studio/whats-new.json +91 -0
- package/cli/bin/maude.mjs +2 -0
- package/cli/commands/codex.mjs +191 -0
- package/cli/commands/harness.mjs +1051 -0
- package/cli/commands/help.mjs +13 -0
- package/cli/lib/design-link.mjs +190 -2
- package/cli/lib/harness/capabilities.mjs +302 -0
- package/cli/lib/harness/codex-runtime.mjs +889 -0
- package/cli/lib/harness/compatibility.mjs +27 -0
- package/cli/lib/harness/discover-claude.mjs +1393 -0
- package/cli/lib/harness/managed-state.mjs +1296 -0
- package/cli/lib/harness/model.mjs +86 -0
- package/cli/lib/harness/secrets.mjs +390 -0
- package/cli/lib/harness/targets/codex.mjs +1141 -0
- package/cli/lib/harness/targets/opencode.mjs +807 -0
- package/cli/lib/harness/transaction.mjs +1099 -0
- package/cli/lib/hubs-config.mjs +35 -8
- package/cli/templates/harness/opencode/maude-projector.ts +58 -0
- package/docs/harness-capability-matrix.md +125 -0
- package/docs/harness-environment-projection.md +95 -0
- package/package.json +32 -9
- package/plugins/flow/.claude-plugin/config.schema.json +8 -0
- package/apps/studio/.ai/cache/_stats.json +0 -7
- package/apps/studio/bin/_agent-browser-safe.test.mjs +0 -165
- package/apps/studio/bin/_curl-local.test.mjs +0 -280
- package/apps/studio/bin/_pw-launch.test.mjs +0 -90
- package/apps/studio/bin/_smart-frames.test.mjs +0 -195
- package/apps/studio/bin/_transcribe.test.mjs +0 -80
- package/apps/studio/bun.lock +0 -928
- package/apps/studio/draw/test/animate.test.ts +0 -172
- package/apps/studio/draw/test/brush.test.ts +0 -213
- package/apps/studio/draw/test/composition.test.ts +0 -141
- package/apps/studio/draw/test/geometry.test.ts +0 -199
- package/apps/studio/draw/test/gradient.test.ts +0 -167
- package/apps/studio/draw/test/layout.test.ts +0 -120
- package/apps/studio/draw/test/morph.test.ts +0 -107
- package/apps/studio/draw/test/optimize.test.ts +0 -40
- package/apps/studio/draw/test/palette.test.ts +0 -123
- package/apps/studio/draw/test/primitives.test.ts +0 -129
- package/apps/studio/draw/test/serialize-animate.test.ts +0 -142
- package/apps/studio/draw/test/serialize.test.ts +0 -105
- package/apps/studio/figma/assets.test.ts +0 -464
- package/apps/studio/figma/client.test.ts +0 -395
- package/apps/studio/figma/codegen-client.test.ts +0 -276
- package/apps/studio/figma/codegen-fonts.test.ts +0 -103
- package/apps/studio/figma/codegen-values.test.ts +0 -179
- package/apps/studio/figma/comments-to-strokes.test.ts +0 -194
- package/apps/studio/figma/fig-decode.test.ts +0 -788
- package/apps/studio/figma/fig-differential.test.ts +0 -182
- package/apps/studio/figma/fig-translator.test.ts +0 -192
- package/apps/studio/figma/fig-vector.test.ts +0 -113
- package/apps/studio/figma/from-codegen.test.ts +0 -408
- package/apps/studio/figma/sanitize.test.ts +0 -325
- package/apps/studio/figma/tailwind-map.test.ts +0 -142
- package/apps/studio/figma/to-artboard.test.ts +0 -808
- package/apps/studio/figma/to-render.test.ts +0 -180
- package/apps/studio/figma/to-strokes-roundtrip.test.ts +0 -152
- package/apps/studio/figma/to-strokes.test.ts +0 -705
- package/apps/studio/figma/to-tokens.test.ts +0 -321
- package/apps/studio/figma/url.test.ts +0 -167
- package/apps/studio/footage/schema.test.ts +0 -287
- package/apps/studio/generation/adapters/groq.test.ts +0 -80
- package/apps/studio/generation/audio-library.test.ts +0 -95
- package/apps/studio/generation/captions.test.ts +0 -78
- package/apps/studio/generation/elevenlabs.test.ts +0 -223
- package/apps/studio/generation/gemini.test.ts +0 -350
- package/apps/studio/generation/jobs.test.ts +0 -128
- package/apps/studio/generation/keys.test.ts +0 -75
- package/apps/studio/generation/prefs.test.ts +0 -95
- package/apps/studio/generation/types.test.ts +0 -123
- package/apps/studio/generation/whisper-models.test.ts +0 -179
- package/apps/studio/test/_helpers.ts +0 -108
- package/apps/studio/test/acp-activity-endpoint.test.ts +0 -183
- package/apps/studio/test/acp-activity.test.ts +0 -154
- package/apps/studio/test/acp-ai-activity.test.ts +0 -182
- package/apps/studio/test/acp-attachment-serve.test.ts +0 -74
- package/apps/studio/test/acp-bootstrap-brief.test.ts +0 -178
- package/apps/studio/test/acp-branch-guard.test.ts +0 -123
- package/apps/studio/test/acp-bridge-lifetime.test.ts +0 -475
- package/apps/studio/test/acp-bridge.test.ts +0 -301
- package/apps/studio/test/acp-capabilities.test.ts +0 -123
- package/apps/studio/test/acp-caps-bridge.test.ts +0 -279
- package/apps/studio/test/acp-commands.test.ts +0 -119
- package/apps/studio/test/acp-elicitation-bridge.test.ts +0 -480
- package/apps/studio/test/acp-elicitation.test.ts +0 -251
- package/apps/studio/test/acp-env.test.ts +0 -95
- package/apps/studio/test/acp-mode-banner.test.ts +0 -45
- package/apps/studio/test/acp-origin-gate.test.ts +0 -160
- package/apps/studio/test/acp-permission-prompt.test.ts +0 -251
- package/apps/studio/test/acp-permission.test.ts +0 -278
- package/apps/studio/test/acp-plugin-bootstrap.test.ts +0 -89
- package/apps/studio/test/acp-session-allowed-tools.test.ts +0 -235
- package/apps/studio/test/acp-session-plugins.test.ts +0 -209
- package/apps/studio/test/acp-toolgroup.test.ts +0 -76
- package/apps/studio/test/acp-transcript-view.test.ts +0 -71
- package/apps/studio/test/acp-transcript.test.ts +0 -238
- package/apps/studio/test/acp-usage-bridge.test.ts +0 -141
- package/apps/studio/test/acp-usage.test.ts +0 -143
- package/apps/studio/test/acp-write-gate.test.ts +0 -323
- package/apps/studio/test/acp-write-scope.test.ts +0 -533
- package/apps/studio/test/active-state.test.ts +0 -186
- package/apps/studio/test/activity.test.ts +0 -189
- package/apps/studio/test/ai-activity.test.ts +0 -113
- package/apps/studio/test/annotate-write.test.ts +0 -1074
- package/apps/studio/test/annotation-strokes-command.test.ts +0 -166
- package/apps/studio/test/annotations-align.test.ts +0 -88
- package/apps/studio/test/annotations-api.test.ts +0 -145
- package/apps/studio/test/annotations-bindings.test.ts +0 -262
- package/apps/studio/test/annotations-draw-modifiers.test.ts +0 -206
- package/apps/studio/test/annotations-groups.test.ts +0 -231
- package/apps/studio/test/annotations-layer.test.ts +0 -1011
- package/apps/studio/test/annotations-roundtrip.test.ts +0 -663
- package/apps/studio/test/annotations-snap.test.ts +0 -135
- package/apps/studio/test/annotations-text-format.test.ts +0 -396
- package/apps/studio/test/artboard-activity-overlay.test.tsx +0 -56
- package/apps/studio/test/artboard-guides-overlay.test.tsx +0 -152
- package/apps/studio/test/artboard-hug-commit.test.ts +0 -36
- package/apps/studio/test/artboard-kinds.test.tsx +0 -83
- package/apps/studio/test/artboard-selection-attrs.test.ts +0 -69
- package/apps/studio/test/asset-api.test.ts +0 -149
- package/apps/studio/test/assets-s3.test.ts +0 -249
- package/apps/studio/test/binary-smoke.test.ts +0 -47
- package/apps/studio/test/boot-self-heal.test.ts +0 -87
- package/apps/studio/test/browse-posture.test.tsx +0 -107
- package/apps/studio/test/bundle-smoke.test.ts +0 -46
- package/apps/studio/test/camera-reveal.test.tsx +0 -173
- package/apps/studio/test/canvas-artifacts.test.ts +0 -125
- package/apps/studio/test/canvas-build-sandbox.test.ts +0 -69
- package/apps/studio/test/canvas-build.test.ts +0 -141
- package/apps/studio/test/canvas-create-api.test.ts +0 -676
- package/apps/studio/test/canvas-cursors.test.ts +0 -164
- package/apps/studio/test/canvas-edit.test.ts +0 -470
- package/apps/studio/test/canvas-freshness-deps.test.ts +0 -64
- package/apps/studio/test/canvas-header.test.ts +0 -127
- package/apps/studio/test/canvas-hide-chrome.test.ts +0 -88
- package/apps/studio/test/canvas-hmr-runtime.test.tsx +0 -114
- package/apps/studio/test/canvas-lib-inline.test.ts +0 -146
- package/apps/studio/test/canvas-lib-motion.test.ts +0 -131
- package/apps/studio/test/canvas-lib-resolver.test.ts +0 -111
- package/apps/studio/test/canvas-list-watch.test.ts +0 -388
- package/apps/studio/test/canvas-media-drop.test.ts +0 -187
- package/apps/studio/test/canvas-meta-api.test.ts +0 -707
- package/apps/studio/test/canvas-move-api.test.ts +0 -587
- package/apps/studio/test/canvas-origin-gate.test.ts +0 -417
- package/apps/studio/test/canvas-pipeline.test.ts +0 -180
- package/apps/studio/test/canvas-rects.test.ts +0 -198
- package/apps/studio/test/canvas-reorder.test.ts +0 -211
- package/apps/studio/test/canvas-route.test.ts +0 -179
- package/apps/studio/test/canvas-shell-base.test.ts +0 -107
- package/apps/studio/test/canvas-shell-build-error.test.ts +0 -52
- package/apps/studio/test/canvas-url.test.ts +0 -138
- package/apps/studio/test/canvas-wheel-delta.test.ts +0 -42
- package/apps/studio/test/canvas-zoom-floor.test.ts +0 -73
- package/apps/studio/test/capture-core.test.ts +0 -32
- package/apps/studio/test/capture-determinism-shape.test.ts +0 -135
- package/apps/studio/test/chat-attachments.test.ts +0 -117
- package/apps/studio/test/chat-context.test.ts +0 -129
- package/apps/studio/test/chat-markdown.test.tsx +0 -58
- package/apps/studio/test/clip-addressing.test.ts +0 -738
- package/apps/studio/test/clip-ops.test.ts +0 -922
- package/apps/studio/test/cloud-build.test.ts +0 -95
- package/apps/studio/test/cloud-connect-note.test.ts +0 -135
- package/apps/studio/test/cloud-endpoints.test.ts +0 -590
- package/apps/studio/test/cloud-history-hardening.test.ts +0 -165
- package/apps/studio/test/cloud-history-posture.test.ts +0 -230
- package/apps/studio/test/cloud-managed-save-surfaces.test.ts +0 -90
- package/apps/studio/test/cloud-renew.test.ts +0 -205
- package/apps/studio/test/cloud-session-role.test.ts +0 -161
- package/apps/studio/test/cloud-shell-surfaces.test.ts +0 -170
- package/apps/studio/test/cold-start-apply.test.ts +0 -303
- package/apps/studio/test/collab-annotations-bridge.test.ts +0 -55
- package/apps/studio/test/collab-awareness-bridge.test.ts +0 -223
- package/apps/studio/test/collab-bridge.test.ts +0 -79
- package/apps/studio/test/collab-loopback.test.ts +0 -63
- package/apps/studio/test/collab-origin-gate.test.ts +0 -323
- package/apps/studio/test/collab-protocol.test.ts +0 -146
- package/apps/studio/test/collab-readonly-gate.test.ts +0 -194
- package/apps/studio/test/collab-reseed-guard.test.ts +0 -78
- package/apps/studio/test/collab-room.test.ts +0 -188
- package/apps/studio/test/collab-session-survival.test.tsx +0 -176
- package/apps/studio/test/collab-stress.test.ts +0 -129
- package/apps/studio/test/comment-mount.test.ts +0 -97
- package/apps/studio/test/comment-relay-origin-gate.test.ts +0 -117
- package/apps/studio/test/comments-api.test.ts +0 -277
- package/apps/studio/test/comments-fs-rebroadcast.test.ts +0 -155
- package/apps/studio/test/comments-overlay.test.ts +0 -117
- package/apps/studio/test/compile-entry.test.ts +0 -133
- package/apps/studio/test/component-map.test.ts +0 -48
- package/apps/studio/test/config-projection.test.ts +0 -104
- package/apps/studio/test/config-reload.test.ts +0 -230
- package/apps/studio/test/config-version.test.ts +0 -88
- package/apps/studio/test/context-resolve-tokens.test.ts +0 -97
- package/apps/studio/test/convert-to-absolute.test.ts +0 -333
- package/apps/studio/test/credential-grammar.test.ts +0 -107
- package/apps/studio/test/csp-canvas-shell.test.ts +0 -62
- package/apps/studio/test/csrf-write-guard.test.ts +0 -141
- package/apps/studio/test/debug-bundle.test.ts +0 -105
- package/apps/studio/test/design-setup-readiness.test.ts +0 -155
- package/apps/studio/test/detach-component.test.ts +0 -94
- package/apps/studio/test/dns-rebinding-guard.test.ts +0 -101
- package/apps/studio/test/dom-selection.test.ts +0 -198
- package/apps/studio/test/dynamic-text-edit.test.ts +0 -162
- package/apps/studio/test/edit-css-occurrence.test.ts +0 -81
- package/apps/studio/test/edit-persistence.test.ts +0 -91
- package/apps/studio/test/edit-scope-api.test.ts +0 -119
- package/apps/studio/test/edit-source-command.test.ts +0 -142
- package/apps/studio/test/edit-suppress.test.ts +0 -170
- package/apps/studio/test/editing-presence.test.ts +0 -103
- package/apps/studio/test/element-resize.test.ts +0 -136
- package/apps/studio/test/element-structural-api.test.ts +0 -434
- package/apps/studio/test/element-structural-edit.test.ts +0 -750
- package/apps/studio/test/ensure-browser.test.ts +0 -92
- package/apps/studio/test/equal-spacing-detector.test.ts +0 -257
- package/apps/studio/test/export-assemble.test.ts +0 -53
- package/apps/studio/test/export-browser-lane.test.ts +0 -126
- package/apps/studio/test/export-capture-fidelity.test.ts +0 -146
- package/apps/studio/test/export-capture-hygiene.test.ts +0 -675
- package/apps/studio/test/export-center.test.tsx +0 -287
- package/apps/studio/test/export-e2e-lanes.test.ts +0 -857
- package/apps/studio/test/export-format-scope-coherence.test.ts +0 -89
- package/apps/studio/test/export-lane.test.ts +0 -245
- package/apps/studio/test/export-shim-multi-capture.test.ts +0 -83
- package/apps/studio/test/exporters/canva.test.ts +0 -63
- package/apps/studio/test/exporters/degraded-propagation.test.ts +0 -123
- package/apps/studio/test/exporters/endpoint.test.ts +0 -121
- package/apps/studio/test/exporters/history.test.ts +0 -107
- package/apps/studio/test/exporters/html.test.ts +0 -26
- package/apps/studio/test/exporters/jobs.test.ts +0 -342
- package/apps/studio/test/exporters/pdf.test.ts +0 -85
- package/apps/studio/test/exporters/png.test.ts +0 -103
- package/apps/studio/test/exporters/pptx-deck.test.ts +0 -112
- package/apps/studio/test/exporters/pptx.test.ts +0 -31
- package/apps/studio/test/exporters/pw-launch.test.ts +0 -49
- package/apps/studio/test/exporters/runtime.test.ts +0 -59
- package/apps/studio/test/exporters/scope.test.ts +0 -292
- package/apps/studio/test/exporters/svg.test.ts +0 -29
- package/apps/studio/test/exporters/unsupported-media.test.ts +0 -123
- package/apps/studio/test/exporters/zip.test.ts +0 -104
- package/apps/studio/test/fetch-asset-gate.test.ts +0 -189
- package/apps/studio/test/figjam-v3-model.test.ts +0 -342
- package/apps/studio/test/figma-explode.test.ts +0 -438
- package/apps/studio/test/figma-provenance.test.ts +0 -108
- package/apps/studio/test/figma-routes.test.ts +0 -294
- package/apps/studio/test/file-lock.test.ts +0 -84
- package/apps/studio/test/fixtures/fake-claude-auth.mjs +0 -13
- package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +0 -1
- package/apps/studio/test/fixtures/mock-acp-agent-caps.mjs +0 -158
- package/apps/studio/test/fixtures/mock-acp-agent-commands.mjs +0 -44
- package/apps/studio/test/fixtures/mock-acp-agent-elicit-flood.mjs +0 -46
- package/apps/studio/test/fixtures/mock-acp-agent-elicit-url.mjs +0 -42
- package/apps/studio/test/fixtures/mock-acp-agent-elicit.mjs +0 -63
- package/apps/studio/test/fixtures/mock-acp-agent-permission-flood.mjs +0 -51
- package/apps/studio/test/fixtures/mock-acp-agent-permission.mjs +0 -50
- package/apps/studio/test/fixtures/mock-acp-agent-slow.mjs +0 -45
- package/apps/studio/test/fixtures/mock-acp-agent-usage.mjs +0 -56
- package/apps/studio/test/fixtures/mock-acp-agent-wedged.mjs +0 -37
- package/apps/studio/test/fixtures/mock-acp-agent-write.mjs +0 -118
- package/apps/studio/test/fixtures/mock-acp-agent.mjs +0 -69
- package/apps/studio/test/fixtures/perf-canvas.mjs +0 -201
- package/apps/studio/test/fixtures/phase-20-annotations.svg +0 -1
- package/apps/studio/test/fixtures/phase-21-annotations.svg +0 -1
- package/apps/studio/test/fixtures/tiny-subset.woff2 +0 -0
- package/apps/studio/test/fixtures/video-comp-fixture.tsx +0 -82
- package/apps/studio/test/footage-store.test.ts +0 -100
- package/apps/studio/test/fs-mkdir-api.test.ts +0 -248
- package/apps/studio/test/fs-watch.test.ts +0 -41
- package/apps/studio/test/gemma-models.test.ts +0 -245
- package/apps/studio/test/generate-route.test.ts +0 -106
- package/apps/studio/test/git-api.test.ts +0 -0
- package/apps/studio/test/git-branches.test.ts +0 -341
- package/apps/studio/test/git-cloud-posture.test.ts +0 -59
- package/apps/studio/test/git-grouping.test.ts +0 -106
- package/apps/studio/test/git-lifecycle.test.ts +0 -100
- package/apps/studio/test/git-log-format.test.ts +0 -95
- package/apps/studio/test/git-safe-rel.test.ts +0 -132
- package/apps/studio/test/git-system-engine.test.ts +0 -135
- package/apps/studio/test/git-watch.test.ts +0 -97
- package/apps/studio/test/github-api.test.ts +0 -577
- package/apps/studio/test/grid-track-handles.test.ts +0 -160
- package/apps/studio/test/handoff-static-frames.test.ts +0 -215
- package/apps/studio/test/handoff.test.ts +0 -329
- package/apps/studio/test/history-rollback.test.ts +0 -88
- package/apps/studio/test/hmr-broadcast.test.ts +0 -236
- package/apps/studio/test/hmr-classify.test.ts +0 -87
- package/apps/studio/test/hub-link.test.ts +0 -80
- package/apps/studio/test/identity-cache.test.ts +0 -83
- package/apps/studio/test/import-asset-browser.test.ts +0 -64
- package/apps/studio/test/import-asset-route.test.ts +0 -71
- package/apps/studio/test/import-asset.test.ts +0 -427
- package/apps/studio/test/import-brand-browser.test.ts +0 -88
- package/apps/studio/test/import-brand-route.test.ts +0 -120
- package/apps/studio/test/import-brand.test.ts +0 -206
- package/apps/studio/test/import-figma.test.ts +0 -667
- package/apps/studio/test/import-tokens.test.ts +0 -722
- package/apps/studio/test/index-data-canvas-ds.test.ts +0 -97
- package/apps/studio/test/input-router.test.ts +0 -757
- package/apps/studio/test/inspect-script-syntax.test.ts +0 -53
- package/apps/studio/test/inspect-selections.test.ts +0 -219
- package/apps/studio/test/knob-props-authored.test.ts +0 -87
- package/apps/studio/test/layers-synthetic-groups.test.ts +0 -96
- package/apps/studio/test/locator.test.ts +0 -214
- package/apps/studio/test/marquee-overlay.test.ts +0 -94
- package/apps/studio/test/media-commit-chain.test.ts +0 -210
- package/apps/studio/test/mode-toggle.test.tsx +0 -226
- package/apps/studio/test/motion-sample.test.ts +0 -35
- package/apps/studio/test/move-artboards-command.test.ts +0 -108
- package/apps/studio/test/participants-chrome.test.ts +0 -65
- package/apps/studio/test/paths.test.ts +0 -88
- package/apps/studio/test/pdf-print-boxes.test.ts +0 -359
- package/apps/studio/test/peer-selection-follows-camera.test.tsx +0 -131
- package/apps/studio/test/perf-harness.ts +0 -193
- package/apps/studio/test/phase-3.6-smoke.test.ts +0 -79
- package/apps/studio/test/photo-bg-remove-validation.test.ts +0 -71
- package/apps/studio/test/photo-canvas-bundle.test.ts +0 -79
- package/apps/studio/test/photo-edit-api.test.ts +0 -201
- package/apps/studio/test/photo-filters.test.ts +0 -149
- package/apps/studio/test/photo-pipeline.test.ts +0 -106
- package/apps/studio/test/photo-taxonomy.test.ts +0 -96
- package/apps/studio/test/print-marks.test.ts +0 -113
- package/apps/studio/test/print-units.test.ts +0 -173
- package/apps/studio/test/read-annotations.test.ts +0 -727
- package/apps/studio/test/read-only-gate.test.ts +0 -188
- package/apps/studio/test/read-only-role.test.ts +0 -95
- package/apps/studio/test/readiness.test.ts +0 -226
- package/apps/studio/test/remote-ahead-behind-cache.test.ts +0 -89
- package/apps/studio/test/reorder-api.test.ts +0 -210
- package/apps/studio/test/repo-concurrency.test.ts +0 -146
- package/apps/studio/test/repo-lock.test.ts +0 -350
- package/apps/studio/test/report-proxy.test.ts +0 -125
- package/apps/studio/test/ripple.test.ts +0 -197
- package/apps/studio/test/runtime-bundle-error-mapping.test.ts +0 -43
- package/apps/studio/test/runtime-bundle.test.ts +0 -69
- package/apps/studio/test/sanitize-annotation-svg.test.ts +0 -238
- package/apps/studio/test/scaffold-design.test.ts +0 -122
- package/apps/studio/test/server-lifecycle.test.ts +0 -38
- package/apps/studio/test/session-runtime-state.test.ts +0 -226
- package/apps/studio/test/session-scope.test.ts +0 -100
- package/apps/studio/test/shared-doc-cell-pairing.test.ts +0 -642
- package/apps/studio/test/shared-doc-convergence.test.ts +0 -265
- package/apps/studio/test/shared-doc-foundation.test.ts +0 -63
- package/apps/studio/test/shared-doc-migrate.test.ts +0 -369
- package/apps/studio/test/shared-doc-projection.test.ts +0 -238
- package/apps/studio/test/shell-importmap.test.ts +0 -56
- package/apps/studio/test/sizing-mode.test.ts +0 -102
- package/apps/studio/test/slash-commands.test.ts +0 -117
- package/apps/studio/test/snap-distance-pill.test.ts +0 -68
- package/apps/studio/test/spacing-handles.test.ts +0 -138
- package/apps/studio/test/specimen-select.test.ts +0 -88
- package/apps/studio/test/sync-agent.test.ts +0 -660
- package/apps/studio/test/sync-annotations-cold-start.test.ts +0 -302
- package/apps/studio/test/sync-asset-push.test.ts +0 -931
- package/apps/studio/test/sync-atomic-write.test.ts +0 -62
- package/apps/studio/test/sync-attach-incremental.test.ts +0 -527
- package/apps/studio/test/sync-autocommit.test.ts +0 -537
- package/apps/studio/test/sync-canvas-path.test.ts +0 -200
- package/apps/studio/test/sync-cell-write-nudge.test.ts +0 -346
- package/apps/studio/test/sync-codec.test.ts +0 -230
- package/apps/studio/test/sync-cold-seed-dedup.test.ts +0 -187
- package/apps/studio/test/sync-cold-start.test.ts +0 -304
- package/apps/studio/test/sync-connect-honesty.test.ts +0 -114
- package/apps/studio/test/sync-connection-state.test.ts +0 -337
- package/apps/studio/test/sync-ctl-channel.test.ts +0 -508
- package/apps/studio/test/sync-decide-file.test.ts +0 -420
- package/apps/studio/test/sync-doc-name.test.ts +0 -281
- package/apps/studio/test/sync-echo-guard.test.ts +0 -96
- package/apps/studio/test/sync-file-ledger.test.ts +0 -334
- package/apps/studio/test/sync-file-membership.test.ts +0 -347
- package/apps/studio/test/sync-file-plane.test.ts +0 -976
- package/apps/studio/test/sync-file-pull.test.ts +0 -333
- package/apps/studio/test/sync-fresh-link-parity.test.ts +0 -267
- package/apps/studio/test/sync-fs-mirror.test.ts +0 -181
- package/apps/studio/test/sync-hardening.test.ts +0 -586
- package/apps/studio/test/sync-hub-listing.test.ts +0 -46
- package/apps/studio/test/sync-hubs-config.test.ts +0 -133
- package/apps/studio/test/sync-incident-replay.test.ts +0 -211
- package/apps/studio/test/sync-journal.test.ts +0 -176
- package/apps/studio/test/sync-meta-codec.test.ts +0 -199
- package/apps/studio/test/sync-migrate-flat-fallback.test.ts +0 -98
- package/apps/studio/test/sync-move-retirement.test.ts +0 -231
- package/apps/studio/test/sync-panel-surface.test.ts +0 -153
- package/apps/studio/test/sync-path-pull.test.ts +0 -531
- package/apps/studio/test/sync-presentation.test.ts +0 -262
- package/apps/studio/test/sync-pull-budget.test.ts +0 -169
- package/apps/studio/test/sync-remote-docs.test.ts +0 -333
- package/apps/studio/test/sync-resync-routes.test.ts +0 -168
- package/apps/studio/test/sync-runtime.test.ts +0 -1675
- package/apps/studio/test/sync-seed-defers-to-hub.test.ts +0 -83
- package/apps/studio/test/sync-settings-routes.test.ts +0 -195
- package/apps/studio/test/sync-settings.test.ts +0 -151
- package/apps/studio/test/sync-status.test.ts +0 -261
- package/apps/studio/test/sync-supervisor.test.ts +0 -262
- package/apps/studio/test/sync-tombstone-apply.test.ts +0 -111
- package/apps/studio/test/sync-trash.test.ts +0 -132
- package/apps/studio/test/sync-two-peer-discovery.test.ts +0 -343
- package/apps/studio/test/sync-untrusted.test.ts +0 -103
- package/apps/studio/test/system-endpoint.test.ts +0 -144
- package/apps/studio/test/text-editability-stamp.test.ts +0 -131
- package/apps/studio/test/timeline-comp-target.test.ts +0 -139
- package/apps/studio/test/timeline-media-cache.test.ts +0 -41
- package/apps/studio/test/timeline-parse.test.ts +0 -184
- package/apps/studio/test/timeline-scale.test.ts +0 -65
- package/apps/studio/test/timeline-snap.test.ts +0 -85
- package/apps/studio/test/tool-palette-insert-anchor.test.ts +0 -86
- package/apps/studio/test/tool-palette.test.tsx +0 -55
- package/apps/studio/test/tour-overlay.test.tsx +0 -90
- package/apps/studio/test/trusted-request-host.test.ts +0 -66
- package/apps/studio/test/undo-sequence-byte-compare.test.ts +0 -211
- package/apps/studio/test/undo-stack.test.ts +0 -244
- package/apps/studio/test/use-agent-presence.test.tsx +0 -117
- package/apps/studio/test/use-annotation-resize.test.ts +0 -402
- package/apps/studio/test/use-annotation-selection.test.tsx +0 -77
- package/apps/studio/test/use-artboard-drag.test.ts +0 -324
- package/apps/studio/test/use-canvas-activity.test.tsx +0 -206
- package/apps/studio/test/use-collab.test.ts +0 -0
- package/apps/studio/test/use-cursor-modifiers.test.ts +0 -59
- package/apps/studio/test/use-keyboard-discipline.test.ts +0 -27
- package/apps/studio/test/use-selection-set.test.tsx +0 -166
- package/apps/studio/test/use-snap-guides.test.ts +0 -271
- package/apps/studio/test/use-tool-mode.test.tsx +0 -186
- package/apps/studio/test/use-undo-stack.test.tsx +0 -223
- package/apps/studio/test/video-asset.test.ts +0 -163
- package/apps/studio/test/video-comp-fixture.test.ts +0 -50
- package/apps/studio/test/video-comp.test.ts +0 -270
- package/apps/studio/test/video-encode-lib.test.ts +0 -63
- package/apps/studio/test/video-render-bridge.test.ts +0 -171
- package/apps/studio/test/whats-new.test.ts +0 -113
- package/apps/studio/test/whisper-setup.test.ts +0 -97
- package/apps/studio/test/workspace-containment.test.ts +0 -382
- package/apps/studio/test/workspace-signin.test.ts +0 -292
- package/apps/studio/test/ws-handshake.test.ts +0 -33
- package/cli/commands/cache.test.mjs +0 -166
- package/cli/commands/design-link.test.mjs +0 -419
- package/cli/commands/design.test.mjs +0 -240
- package/cli/commands/doctor.test.mjs +0 -237
- package/cli/commands/hub-workspace.test.mjs +0 -275
- package/cli/commands/hub.test.mjs +0 -225
- package/cli/commands/kg.test.mjs +0 -142
- package/cli/commands/preflight.test.mjs +0 -46
- package/cli/lib/argv.test.mjs +0 -46
- package/cli/lib/cache.test.mjs +0 -303
- package/cli/lib/cell-plan.test.mjs +0 -225
- package/cli/lib/config-lint.test.mjs +0 -117
- package/cli/lib/ddr-to-kgai.test.mjs +0 -118
- package/cli/lib/design-ownership.test.mjs +0 -329
- package/cli/lib/fetch-asset.test.mjs +0 -213
- package/cli/lib/figma-codegen-reachability.test.mjs +0 -104
- package/cli/lib/figma-import-controls.test.mjs +0 -70
- package/cli/lib/flow-design-integration.test.mjs +0 -165
- package/cli/lib/gitignore-block.test.mjs +0 -136
- package/cli/lib/gitignore-drift.test.mjs +0 -156
- package/cli/lib/hubs-config.test.mjs +0 -165
- package/cli/lib/pkg-root.test.mjs +0 -111
- package/cli/lib/plugin-cli-reachability.test.mjs +0 -57
- package/cli/lib/plugin-name-namespace.test.mjs +0 -71
- package/cli/lib/reconstruct-toolset.test.mjs +0 -194
- package/cli/lib/stack-detect.test.mjs +0 -121
- package/cli/lib/update-check.test.mjs +0 -32
- package/cli/lib/workspace-plan.test.mjs +0 -699
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
// DDR-185 — the loopback-only curl gate behind `maude design curl-local`
|
|
2
|
-
// (apps/studio/bin/_curl-local.mjs). This suite locks the SECURITY CORE: the
|
|
3
|
-
// strict-loopback address classifier (accepts ONLY 127.0.0.0/8 / ::1 / the
|
|
4
|
-
// IPv4-mapped form — unlike `_fetch-asset.mjs`'s `classifyAddress`, an
|
|
5
|
-
// ordinary private-LAN address like 192.168.1.1 is REJECTED here, not
|
|
6
|
-
// allowed), the every-record DNS check (closes the multi-A-record rebinding
|
|
7
|
-
// gap), and — round 2 of the security addendum — the Maude-owned request
|
|
8
|
-
// parser (`parseRequestArgv`) that REPLACED the raw-curl-argv allowlist a
|
|
9
|
-
// live security fan-out found still bypassable via curl's own concatenated
|
|
10
|
-
// short-flag syntax (`-K<path>`, `-x<url>`, `-T<path>` all slipped past the
|
|
11
|
-
// `a.split('=')[0]` check) and a missing `--noproxy` reject entry. This file
|
|
12
|
-
// no longer recognizes ANY curl flag at all — only its own small vocabulary
|
|
13
|
-
// — so there is no curl-syntax bypass class left to enumerate against.
|
|
14
|
-
|
|
15
|
-
import { describe, expect, test } from 'bun:test';
|
|
16
|
-
import {
|
|
17
|
-
buildCurlArgs,
|
|
18
|
-
CurlLocalArgvError,
|
|
19
|
-
CurlLocalError,
|
|
20
|
-
classifyRecords,
|
|
21
|
-
isLoopbackAddress,
|
|
22
|
-
parseRequestArgv,
|
|
23
|
-
resolveLoopbackIp,
|
|
24
|
-
} from './_curl-local.mjs';
|
|
25
|
-
|
|
26
|
-
describe('isLoopbackAddress', () => {
|
|
27
|
-
test('accepts IPv4 loopback (127.0.0.0/8, not just 127.0.0.1)', () => {
|
|
28
|
-
expect(isLoopbackAddress('127.0.0.1')).toBe(true);
|
|
29
|
-
expect(isLoopbackAddress('127.9.9.9')).toBe(true);
|
|
30
|
-
expect(isLoopbackAddress('127.255.255.255')).toBe(true);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('accepts IPv6 ::1 and the IPv4-mapped loopback form', () => {
|
|
34
|
-
expect(isLoopbackAddress('::1')).toBe(true);
|
|
35
|
-
expect(isLoopbackAddress('::ffff:127.0.0.1')).toBe(true);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("rejects ordinary private-LAN addresses — this is STRICTER than fetch-asset's classifier", () => {
|
|
39
|
-
// fetch-asset's classifyAddress blocks these too (as "not a safe egress
|
|
40
|
-
// target"), but for a different reason (SSRF from an external URL). Here
|
|
41
|
-
// the bar is "is this literally my own machine" — a private-LAN IP is a
|
|
42
|
-
// real, different host, not loopback, and must still prompt.
|
|
43
|
-
expect(isLoopbackAddress('192.168.1.1')).toBe(false);
|
|
44
|
-
expect(isLoopbackAddress('10.0.0.1')).toBe(false);
|
|
45
|
-
expect(isLoopbackAddress('172.16.0.1')).toBe(false);
|
|
46
|
-
expect(isLoopbackAddress('169.254.169.254')).toBe(false); // cloud IMDS
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test('rejects public addresses and link-local/multicast/reserved ranges', () => {
|
|
50
|
-
for (const ip of ['8.8.8.8', '1.1.1.1', '224.0.0.1', '240.0.0.1', 'fe80::1']) {
|
|
51
|
-
expect(isLoopbackAddress(ip)).toBe(false);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
test('rejects a non-IP string', () => {
|
|
56
|
-
expect(isLoopbackAddress('not-an-ip')).toBe(false);
|
|
57
|
-
expect(isLoopbackAddress('localhost')).toBe(false); // a hostname, not a literal — resolved separately
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
describe('classifyRecords — the every-record check (multi-A-record DNS rebinding)', () => {
|
|
62
|
-
test('all-loopback records pass', () => {
|
|
63
|
-
expect(
|
|
64
|
-
classifyRecords('localhost', [{ address: '127.0.0.1' }, { address: '127.0.0.2' }])
|
|
65
|
-
).toBeNull();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('a single non-loopback record among several loopback ones still fails the whole host', () => {
|
|
69
|
-
const reason = classifyRecords('rebind.example', [
|
|
70
|
-
{ address: '127.0.0.1' },
|
|
71
|
-
{ address: '93.184.216.34' },
|
|
72
|
-
]);
|
|
73
|
-
expect(reason).toContain('93.184.216.34');
|
|
74
|
-
expect(reason).toContain('non-loopback');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
test('empty record set fails', () => {
|
|
78
|
-
expect(classifyRecords('empty.example', [])).toContain('no DNS records');
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
describe('resolveLoopbackIp', () => {
|
|
83
|
-
test('an IP-literal host skips DNS entirely — loopback passes, returns itself', async () => {
|
|
84
|
-
await expect(resolveLoopbackIp('127.0.0.1')).resolves.toBe('127.0.0.1');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test('an IP-literal host that is not loopback throws CurlLocalError(3)', async () => {
|
|
88
|
-
await expect(resolveLoopbackIp('8.8.8.8')).rejects.toThrow(CurlLocalError);
|
|
89
|
-
try {
|
|
90
|
-
await resolveLoopbackIp('8.8.8.8');
|
|
91
|
-
throw new Error('should have thrown');
|
|
92
|
-
} catch (err) {
|
|
93
|
-
expect(err.code).toBe(3);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test('a hostname resolving to all-loopback records (injected lookup) passes, returns the pin IP', async () => {
|
|
98
|
-
const lookupFn = async () => [
|
|
99
|
-
{ address: '127.0.0.1', family: 4 },
|
|
100
|
-
{ address: '::1', family: 6 },
|
|
101
|
-
];
|
|
102
|
-
await expect(resolveLoopbackIp('localhost', { lookupFn })).resolves.toBe('127.0.0.1');
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
test('a hostname with ONE non-loopback record among several is rejected (DNS-rebinding defense)', async () => {
|
|
106
|
-
const lookupFn = async () => [
|
|
107
|
-
{ address: '127.0.0.1', family: 4 },
|
|
108
|
-
{ address: '203.0.113.5', family: 4 }, // TEST-NET-3 — a real "other" address
|
|
109
|
-
];
|
|
110
|
-
await expect(resolveLoopbackIp('rebind.example', { lookupFn })).rejects.toThrow(CurlLocalError);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
test('DNS resolution failure is a rejection, not a silent pass', async () => {
|
|
114
|
-
const lookupFn = async () => {
|
|
115
|
-
const err = new Error('getaddrinfo ENOTFOUND nope.invalid');
|
|
116
|
-
err.code = 'ENOTFOUND';
|
|
117
|
-
throw err;
|
|
118
|
-
};
|
|
119
|
-
await expect(resolveLoopbackIp('nope.invalid', { lookupFn })).rejects.toThrow(CurlLocalError);
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
describe('parseRequestArgv — the Maude-owned flag vocabulary (never raw curl syntax)', () => {
|
|
124
|
-
test('a bare URL parses as a GET with no headers/body', () => {
|
|
125
|
-
const req = parseRequestArgv(['http://localhost:3000/api']);
|
|
126
|
-
expect(req.url).toBe('http://localhost:3000/api');
|
|
127
|
-
expect(req.method).toBe('GET');
|
|
128
|
-
expect(req.headers).toEqual([]);
|
|
129
|
-
expect(req.data).toBeNull();
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test('--method, repeated --header, and --data all parse', () => {
|
|
133
|
-
const req = parseRequestArgv([
|
|
134
|
-
'http://localhost:3000/api',
|
|
135
|
-
'--method',
|
|
136
|
-
'post',
|
|
137
|
-
'--header',
|
|
138
|
-
'Content-Type: application/json',
|
|
139
|
-
'--header',
|
|
140
|
-
'X-Test: 1',
|
|
141
|
-
'--data',
|
|
142
|
-
'{"a":1}',
|
|
143
|
-
]);
|
|
144
|
-
expect(req.method).toBe('POST'); // uppercased
|
|
145
|
-
expect(req.headers).toEqual(['Content-Type: application/json', 'X-Test: 1']);
|
|
146
|
-
expect(req.data).toBe('{"a":1}');
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
test('boolean flags (--insecure/--include/--verbose) parse with no value consumed', () => {
|
|
150
|
-
const req = parseRequestArgv([
|
|
151
|
-
'http://localhost:3000/',
|
|
152
|
-
'--insecure',
|
|
153
|
-
'--include',
|
|
154
|
-
'--verbose',
|
|
155
|
-
]);
|
|
156
|
-
expect(req.insecure).toBe(true);
|
|
157
|
-
expect(req.include).toBe(true);
|
|
158
|
-
expect(req.verbose).toBe(true);
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test('rejects an unrecognized method', () => {
|
|
162
|
-
expect(() => parseRequestArgv(['http://localhost:3000/', '--method', 'TRACE'])).toThrow(
|
|
163
|
-
CurlLocalArgvError
|
|
164
|
-
);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
test('rejects a malformed header (must look like "Name: value")', () => {
|
|
168
|
-
expect(() => parseRequestArgv(['http://localhost:3000/', '--header', 'not-a-header'])).toThrow(
|
|
169
|
-
CurlLocalArgvError
|
|
170
|
-
);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test('rejects a non-positive --max-time', () => {
|
|
174
|
-
expect(() => parseRequestArgv(['http://localhost:3000/', '--max-time', '0'])).toThrow(
|
|
175
|
-
CurlLocalArgvError
|
|
176
|
-
);
|
|
177
|
-
expect(() => parseRequestArgv(['http://localhost:3000/', '--max-time', 'nope'])).toThrow(
|
|
178
|
-
CurlLocalArgvError
|
|
179
|
-
);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
test('rejects a non-http(s) URL scheme', () => {
|
|
183
|
-
expect(() => parseRequestArgv(['file:///etc/passwd'])).toThrow(CurlLocalArgvError);
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
test('rejects a value-taking flag with no value', () => {
|
|
187
|
-
expect(() => parseRequestArgv(['http://localhost:3000/', '--header'])).toThrow(
|
|
188
|
-
CurlLocalArgvError
|
|
189
|
-
);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test('rejects more than one positional argument', () => {
|
|
193
|
-
expect(() => parseRequestArgv(['http://localhost:3000/', 'http://localhost:4000/'])).toThrow(
|
|
194
|
-
CurlLocalArgvError
|
|
195
|
-
);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
test('rejects when no URL is given at all', () => {
|
|
199
|
-
expect(() => parseRequestArgv(['--method', 'GET'])).toThrow(CurlLocalArgvError);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// ── Round-2 security addendum: these are exactly the argv forms that
|
|
203
|
-
// bypassed the PREVIOUS raw-curl-argv allowlist. None of them are
|
|
204
|
-
// "rejected flags" here — they're simply not part of this file's flag
|
|
205
|
-
// vocabulary at all, so they fall through to the generic
|
|
206
|
-
// "unrecognized flag" / "unexpected extra argument" path.
|
|
207
|
-
test('rejects the concatenated -K<path> config-file-smuggling PoC verbatim (round-2 finding #1)', () => {
|
|
208
|
-
expect(() => parseRequestArgv(['http://127.0.0.1:1/', '-Kevil.conf'])).toThrow(
|
|
209
|
-
CurlLocalArgvError
|
|
210
|
-
);
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
test('rejects a caller-supplied --noproxy override verbatim (round-2 finding #2)', () => {
|
|
214
|
-
expect(() => parseRequestArgv(['http://127.0.0.1:1/', '--noproxy', ''])).toThrow(
|
|
215
|
-
CurlLocalArgvError
|
|
216
|
-
);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test('rejects the concatenated -x<url>/-T<path> short forms (round-2 finding #3)', () => {
|
|
220
|
-
expect(() => parseRequestArgv(['-xhttp://evil.example', 'http://127.0.0.1:1/'])).toThrow(
|
|
221
|
-
CurlLocalArgvError
|
|
222
|
-
);
|
|
223
|
-
expect(() => parseRequestArgv(['http://127.0.0.1:1/', '-T/etc/passwd'])).toThrow(
|
|
224
|
-
CurlLocalArgvError
|
|
225
|
-
);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
test('rejects the original --resolve/--connect-to target-override PoC verbatim', () => {
|
|
229
|
-
expect(() =>
|
|
230
|
-
parseRequestArgv(['--resolve', 'localhost:80:203.0.113.5', 'http://localhost/'])
|
|
231
|
-
).toThrow(CurlLocalArgvError);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
test('a value containing curl-flag-shaped text is inert data, not a flag — no injection via --data', () => {
|
|
235
|
-
// "@/etc/passwd" as a --data value must never be treated as curl's
|
|
236
|
-
// "read a file" shorthand (that's -d/--data's behavior, not
|
|
237
|
-
// --data-raw's, which buildCurlArgs always uses — see the live
|
|
238
|
-
// end-to-end test below for the full round-trip proof).
|
|
239
|
-
const req = parseRequestArgv([
|
|
240
|
-
'http://localhost:3000/',
|
|
241
|
-
'--data',
|
|
242
|
-
'@/etc/passwd; -K evil.conf',
|
|
243
|
-
]);
|
|
244
|
-
expect(req.data).toBe('@/etc/passwd; -K evil.conf');
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
describe("buildCurlArgs — every flag name is one of THIS file's own hardcoded literals", () => {
|
|
249
|
-
test('a bare GET pins the connection and forces the safety flags', () => {
|
|
250
|
-
const req = parseRequestArgv(['http://localhost:3000/']);
|
|
251
|
-
const args = buildCurlArgs(req, '127.0.0.1', 3000);
|
|
252
|
-
expect(args).toContain('--resolve');
|
|
253
|
-
expect(args[args.indexOf('--resolve') + 1]).toBe('localhost:3000:127.0.0.1');
|
|
254
|
-
expect(args).toContain('--noproxy');
|
|
255
|
-
expect(args).toContain('-q'); // curlrc disabled
|
|
256
|
-
expect(args).toEqual(expect.arrayContaining(['--max-redirs', '0']));
|
|
257
|
-
expect(args.at(-1)).toBe('http://localhost:3000/'); // the URL, always last, after `--`
|
|
258
|
-
expect(args.at(-2)).toBe('--');
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
test('POST + headers + data map to -X/-H/--data-raw (never -d, which treats a leading @ as a file path)', () => {
|
|
262
|
-
const req = parseRequestArgv([
|
|
263
|
-
'http://localhost:3000/api',
|
|
264
|
-
'--method',
|
|
265
|
-
'POST',
|
|
266
|
-
'--header',
|
|
267
|
-
'Content-Type: application/json',
|
|
268
|
-
'--data',
|
|
269
|
-
'@/etc/passwd',
|
|
270
|
-
]);
|
|
271
|
-
const args = buildCurlArgs(req, '127.0.0.1', 3000);
|
|
272
|
-
expect(args).toContain('-X');
|
|
273
|
-
expect(args[args.indexOf('-X') + 1]).toBe('POST');
|
|
274
|
-
expect(args).toContain('-H');
|
|
275
|
-
expect(args[args.indexOf('-H') + 1]).toBe('Content-Type: application/json');
|
|
276
|
-
expect(args).toContain('--data-raw');
|
|
277
|
-
expect(args).not.toContain('-d');
|
|
278
|
-
expect(args[args.indexOf('--data-raw') + 1]).toBe('@/etc/passwd');
|
|
279
|
-
});
|
|
280
|
-
});
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
// _pw-launch.test.mjs — the pure, security-relevant helpers shared by the
|
|
2
|
-
// multi-artboard export shims (_png/_pdf-playwright.mjs). Regression coverage
|
|
3
|
-
// for the arbitrary-file-write finding fixed in the print-artboards dogfood
|
|
4
|
-
// pass (DDR-182 addendum): a `data-dc-screen` id is DDR-054 untrusted canvas
|
|
5
|
-
// content, and used to be trusted verbatim as a `path.join` segment.
|
|
6
|
-
|
|
7
|
-
import { describe, expect, test } from 'bun:test';
|
|
8
|
-
import { assertRenderOutputSizeOk, safeArtboardFilename } from './_pw-launch.mjs';
|
|
9
|
-
|
|
10
|
-
describe('safeArtboardFilename', () => {
|
|
11
|
-
test('passes a well-formed id through unchanged', () => {
|
|
12
|
-
expect(safeArtboardFilename('ak-alpha', 0, 'pdf')).toBe('ak-alpha.pdf');
|
|
13
|
-
expect(safeArtboardFilename('Beta_2', 3, 'png')).toBe('Beta_2.png');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test('falls back to the positional name for a path-traversal id', () => {
|
|
17
|
-
expect(safeArtboardFilename('../../../../tmp/pwned', 2, 'pdf')).toBe('artboard-3.pdf');
|
|
18
|
-
expect(safeArtboardFilename('/etc/passwd', 0, 'png')).toBe('artboard-1.png');
|
|
19
|
-
expect(safeArtboardFilename('..', 1, 'pdf')).toBe('artboard-2.pdf');
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
test('falls back for null/undefined/empty/non-string ids', () => {
|
|
23
|
-
expect(safeArtboardFilename(null, 0, 'pdf')).toBe('artboard-1.pdf');
|
|
24
|
-
expect(safeArtboardFilename(undefined, 4, 'png')).toBe('artboard-5.png');
|
|
25
|
-
expect(safeArtboardFilename('', 1, 'pdf')).toBe('artboard-2.pdf');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test('falls back for an id starting with a digit or containing unsafe chars', () => {
|
|
29
|
-
expect(safeArtboardFilename('1abc', 0, 'pdf')).toBe('artboard-1.pdf');
|
|
30
|
-
expect(safeArtboardFilename('a/b', 0, 'pdf')).toBe('artboard-1.pdf');
|
|
31
|
-
expect(safeArtboardFilename('a b', 0, 'pdf')).toBe('artboard-1.pdf');
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
test('falls back for an id over the 64-char cap', () => {
|
|
35
|
-
expect(safeArtboardFilename(`a${'x'.repeat(64)}`, 0, 'pdf')).toBe('artboard-1.pdf');
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
describe('assertRenderOutputSizeOk', () => {
|
|
40
|
-
test('does not exit for a reasonable A4-at-300dpi request', () => {
|
|
41
|
-
const origExit = process.exit;
|
|
42
|
-
let exited = false;
|
|
43
|
-
process.exit = () => {
|
|
44
|
-
exited = true;
|
|
45
|
-
};
|
|
46
|
-
try {
|
|
47
|
-
assertRenderOutputSizeOk(818, 1146, 3.125, '_test');
|
|
48
|
-
} finally {
|
|
49
|
-
process.exit = origExit;
|
|
50
|
-
}
|
|
51
|
-
expect(exited).toBe(false);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('exits when the requested output exceeds the max side guard', () => {
|
|
55
|
-
const origExit = process.exit;
|
|
56
|
-
const origError = console.error;
|
|
57
|
-
let exitCode;
|
|
58
|
-
process.exit = (code) => {
|
|
59
|
-
exitCode = code;
|
|
60
|
-
throw new Error('exit'); // stop the (test-only) function from continuing past exit
|
|
61
|
-
};
|
|
62
|
-
console.error = () => {};
|
|
63
|
-
try {
|
|
64
|
-
expect(() => assertRenderOutputSizeOk(20000, 20000, 1, '_test')).toThrow();
|
|
65
|
-
} finally {
|
|
66
|
-
process.exit = origExit;
|
|
67
|
-
console.error = origError;
|
|
68
|
-
}
|
|
69
|
-
expect(exitCode).toBe(1);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test('exits when the requested output exceeds the byte-size guard even under the side cap', () => {
|
|
73
|
-
const origExit = process.exit;
|
|
74
|
-
const origError = console.error;
|
|
75
|
-
let exitCode;
|
|
76
|
-
process.exit = (code) => {
|
|
77
|
-
exitCode = code;
|
|
78
|
-
throw new Error('exit');
|
|
79
|
-
};
|
|
80
|
-
console.error = () => {};
|
|
81
|
-
try {
|
|
82
|
-
// 15000×15000 stays under the 16000px side cap but blows the ~600MB estimate.
|
|
83
|
-
expect(() => assertRenderOutputSizeOk(15000, 15000, 1, '_test')).toThrow();
|
|
84
|
-
} finally {
|
|
85
|
-
process.exit = origExit;
|
|
86
|
-
console.error = origError;
|
|
87
|
-
}
|
|
88
|
-
expect(exitCode).toBe(1);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
// _smart-frames.test.mjs — the pure timestamp/tier logic behind
|
|
2
|
-
// `maude design smart-frames` (feature-scene-aware-keyframes). The ffmpeg/gemma
|
|
3
|
-
// spawns + Chromium delegate are soft-dep live gates covered by manual runs; this
|
|
4
|
-
// covers the deterministic math that decides WHICH frames get pulled.
|
|
5
|
-
|
|
6
|
-
import { describe, expect, test } from 'bun:test';
|
|
7
|
-
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
|
|
8
|
-
import { tmpdir } from 'node:os';
|
|
9
|
-
import { join } from 'node:path';
|
|
10
|
-
import {
|
|
11
|
-
clipCandidates,
|
|
12
|
-
mergeTimestamps,
|
|
13
|
-
ollamaHost,
|
|
14
|
-
ollamaScoutPrompt,
|
|
15
|
-
parseBeats,
|
|
16
|
-
parseSceneCuts,
|
|
17
|
-
pickOllamaGemmaTag,
|
|
18
|
-
readEnginePref,
|
|
19
|
-
selectTier,
|
|
20
|
-
} from './_smart-frames.mjs';
|
|
21
|
-
|
|
22
|
-
describe('parseSceneCuts', () => {
|
|
23
|
-
test('extracts pts_time values from ffmpeg showinfo stderr', () => {
|
|
24
|
-
const stderr =
|
|
25
|
-
'frame:0 pts_time:0.233333 ...\nframe:1 pts_time:6.533333 ...\nnoise pts_time:bad';
|
|
26
|
-
expect(parseSceneCuts(stderr)).toEqual([0.233333, 6.533333]);
|
|
27
|
-
});
|
|
28
|
-
test('empty on no cuts', () => {
|
|
29
|
-
expect(parseSceneCuts('nothing here')).toEqual([]);
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
describe('parseBeats', () => {
|
|
34
|
-
test('parses the TIME=<sec> format', () => {
|
|
35
|
-
const t = 'TIME=0.5 | opener\nTIME=6.9 | run play\n';
|
|
36
|
-
expect(parseBeats(t, 8).map((b) => b.t)).toEqual([0.5, 6.9]);
|
|
37
|
-
});
|
|
38
|
-
test('parses the M:SS format the small model tends to emit', () => {
|
|
39
|
-
const t = '0:00 | game action\n0:06 | interview\n1:03 | reveal\n';
|
|
40
|
-
const beats = parseBeats(t, 90);
|
|
41
|
-
expect(beats.map((b) => b.t)).toEqual([0, 6, 63]);
|
|
42
|
-
expect(beats[1].what).toBe('interview');
|
|
43
|
-
});
|
|
44
|
-
test('drops out-of-range beats', () => {
|
|
45
|
-
expect(parseBeats('TIME=99 | too late', 8)).toEqual([]);
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
describe('mergeTimestamps', () => {
|
|
50
|
-
test('always includes the true first and last frame', () => {
|
|
51
|
-
const ts = mergeTimestamps({ durationSec: 8, cuts: [], beats: [], maxFrames: 12 });
|
|
52
|
-
expect(ts[0]).toBe(0);
|
|
53
|
-
expect(ts[ts.length - 1]).toBeCloseTo(7.95, 2);
|
|
54
|
-
});
|
|
55
|
-
test('puts a frame just inside a shot at a cut (when not deduped against an endpoint)', () => {
|
|
56
|
-
const ts = mergeTimestamps({ durationSec: 8, cuts: [0.233, 6.533], beats: [], maxFrames: 20 });
|
|
57
|
-
// 6.583 = just after the 6.533 cut — clearly separated, survives dedup.
|
|
58
|
-
expect(ts.some((t) => Math.abs(t - 6.583) < 0.01)).toBe(true);
|
|
59
|
-
// 0.283 (just after the 0.233 cut) is within 0.4s of t=0, so dedup folds it into
|
|
60
|
-
// the opening frame — same moment, correct behavior.
|
|
61
|
-
expect(ts.filter((t) => t < 0.4)).toEqual([0]);
|
|
62
|
-
});
|
|
63
|
-
test('samples long shots more densely', () => {
|
|
64
|
-
// one 8s shot, no cuts → midpoints added (gap>3 → floor(8/3)=2 extra)
|
|
65
|
-
const ts = mergeTimestamps({ durationSec: 8, cuts: [], beats: [], maxFrames: 20 });
|
|
66
|
-
expect(ts.length).toBeGreaterThanOrEqual(4);
|
|
67
|
-
});
|
|
68
|
-
test('dedups within 0.4s', () => {
|
|
69
|
-
const ts = mergeTimestamps({
|
|
70
|
-
durationSec: 8,
|
|
71
|
-
cuts: [],
|
|
72
|
-
beats: [
|
|
73
|
-
{ t: 0.1, what: '' },
|
|
74
|
-
{ t: 0.2, what: '' },
|
|
75
|
-
],
|
|
76
|
-
maxFrames: 20,
|
|
77
|
-
});
|
|
78
|
-
// 0, 0.05(inside), 0.1, 0.2 all within 0.4 → collapse toward the earliest kept
|
|
79
|
-
const near0 = ts.filter((t) => t < 0.4);
|
|
80
|
-
expect(near0.length).toBe(1);
|
|
81
|
-
});
|
|
82
|
-
test('caps to maxFrames with an even spread', () => {
|
|
83
|
-
const beats = Array.from({ length: 40 }, (_, i) => ({ t: (i * 8) / 40, what: '' }));
|
|
84
|
-
const ts = mergeTimestamps({ durationSec: 8, cuts: [], beats, maxFrames: 6 });
|
|
85
|
-
expect(ts.length).toBeLessThanOrEqual(6);
|
|
86
|
-
expect(ts[0]).toBe(0);
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
describe('selectTier', () => {
|
|
91
|
-
const gemmaReady = { ffmpeg: true, mlxPython: 'python3', ollama: null };
|
|
92
|
-
const ollamaReady = {
|
|
93
|
-
ffmpeg: true,
|
|
94
|
-
mlxPython: null,
|
|
95
|
-
ollama: { host: 'http://127.0.0.1:11434', model: 'gemma3:4b' },
|
|
96
|
-
};
|
|
97
|
-
const ffmpegOnly = { ffmpeg: true, mlxPython: null, ollama: null };
|
|
98
|
-
const bare = { ffmpeg: false, mlxPython: null, ollama: null };
|
|
99
|
-
|
|
100
|
-
test('auto degrades gemma → ffmpeg → blind', () => {
|
|
101
|
-
expect(selectTier('auto', gemmaReady)).toBe('gemma');
|
|
102
|
-
expect(selectTier('auto', ffmpegOnly)).toBe('ffmpeg');
|
|
103
|
-
expect(selectTier('auto', bare)).toBe('blind');
|
|
104
|
-
});
|
|
105
|
-
test('ollama alone unlocks the gemma tier (no mlx-vlm needed)', () => {
|
|
106
|
-
expect(selectTier('auto', ollamaReady)).toBe('gemma');
|
|
107
|
-
expect(selectTier('gemma', ollamaReady)).toBe('gemma');
|
|
108
|
-
});
|
|
109
|
-
test('explicit engine errors when its deps are missing (no silent downgrade)', () => {
|
|
110
|
-
expect(() => selectTier('gemma', ffmpegOnly)).toThrow(/mlx-vlm|Ollama/);
|
|
111
|
-
expect(() => selectTier('ffmpeg', bare)).toThrow(/ffmpeg/);
|
|
112
|
-
});
|
|
113
|
-
test('blind is always allowed', () => {
|
|
114
|
-
expect(selectTier('blind', bare)).toBe('blind');
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
describe('ollama runtime helpers', () => {
|
|
119
|
-
test('pickOllamaGemmaTag picks a vision-capable gemma3 tag', () => {
|
|
120
|
-
expect(pickOllamaGemmaTag(['llama3:8b', 'gemma3:4b'], {})).toBe('gemma3:4b');
|
|
121
|
-
expect(pickOllamaGemmaTag(['gemma3:latest'], {})).toBe('gemma3:latest');
|
|
122
|
-
expect(pickOllamaGemmaTag(['gemma3:12b'], {})).toBe('gemma3:12b');
|
|
123
|
-
});
|
|
124
|
-
test('pickOllamaGemmaTag excludes text-only tags', () => {
|
|
125
|
-
expect(pickOllamaGemmaTag(['gemma3:1b'], {})).toBe(null);
|
|
126
|
-
expect(pickOllamaGemmaTag(['gemma3n:e4b'], {})).toBe(null);
|
|
127
|
-
expect(pickOllamaGemmaTag(['llama3:8b'], {})).toBe(null);
|
|
128
|
-
});
|
|
129
|
-
test('$MAUDE_OLLAMA_MODEL wins verbatim', () => {
|
|
130
|
-
expect(pickOllamaGemmaTag(['gemma3:4b'], { MAUDE_OLLAMA_MODEL: 'gemma3:27b' })).toBe(
|
|
131
|
-
'gemma3:27b'
|
|
132
|
-
);
|
|
133
|
-
});
|
|
134
|
-
test('ollamaHost normalizes $OLLAMA_HOST and pins it to loopback', () => {
|
|
135
|
-
expect(ollamaHost({})).toBe('http://127.0.0.1:11434');
|
|
136
|
-
expect(ollamaHost({ OLLAMA_HOST: 'http://localhost:11434/' })).toBe('http://localhost:11434');
|
|
137
|
-
expect(ollamaHost({ OLLAMA_HOST: '127.0.0.1:11434' })).toBe('http://127.0.0.1:11434');
|
|
138
|
-
expect(ollamaHost({ OLLAMA_HOST: '[::1]:11434' })).toBe('http://[::1]:11434');
|
|
139
|
-
// non-loopback = refused (frames must never leave this machine — DDR-183)
|
|
140
|
-
expect(ollamaHost({ OLLAMA_HOST: '0.0.0.0:11434' })).toBe(null);
|
|
141
|
-
expect(ollamaHost({ OLLAMA_HOST: '192.168.1.20:11434' })).toBe(null);
|
|
142
|
-
expect(ollamaHost({ OLLAMA_HOST: 'http://ollama.internal:11434' })).toBe(null);
|
|
143
|
-
expect(ollamaHost({ OLLAMA_HOST: 'http://169.254.169.254' })).toBe(null);
|
|
144
|
-
});
|
|
145
|
-
test('parseBeats sanitizes model-authored labels (control chars stripped, capped)', () => {
|
|
146
|
-
const evil = `0:05 | a\x00b\x1bc ${'x'.repeat(300)}`;
|
|
147
|
-
const beats = parseBeats(evil, 10);
|
|
148
|
-
expect(beats).toHaveLength(1);
|
|
149
|
-
expect(beats[0].what.length).toBeLessThanOrEqual(120);
|
|
150
|
-
// biome-ignore lint/suspicious/noControlCharactersInRegex: asserting that control chars were stripped is the point.
|
|
151
|
-
expect(beats[0].what).not.toMatch(/[\x00-\x1f]/);
|
|
152
|
-
});
|
|
153
|
-
test('ollamaScoutPrompt maps frames to timestamps', () => {
|
|
154
|
-
const p = ollamaScoutPrompt(10, [0.05, 5, 9.95]);
|
|
155
|
-
expect(p).toContain('frame 1 = t=0.05s');
|
|
156
|
-
expect(p).toContain('frame 3 = t=9.95s');
|
|
157
|
-
expect(p).toContain('TIME=');
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
describe('clipCandidates', () => {
|
|
162
|
-
test('tries raw, root-relative, and designRoot/assets locations', () => {
|
|
163
|
-
const c = clipCandidates('assets/abc12345.mp4', '/repo', '.design');
|
|
164
|
-
expect(c).toContain('assets/abc12345.mp4');
|
|
165
|
-
expect(c).toContain('/repo/assets/abc12345.mp4');
|
|
166
|
-
expect(c).toContain('/repo/.design/assets/abc12345.mp4');
|
|
167
|
-
});
|
|
168
|
-
test('bare filename resolves under designRoot/assets', () => {
|
|
169
|
-
const c = clipCandidates('abc12345.mp4', '/repo', '.design');
|
|
170
|
-
expect(c).toContain('/repo/.design/assets/abc12345.mp4');
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
describe('readEnginePref', () => {
|
|
175
|
-
function repoWith(config) {
|
|
176
|
-
const root = mkdtempSync(join(tmpdir(), 'maude-sf-'));
|
|
177
|
-
mkdirSync(join(root, '.design'), { recursive: true });
|
|
178
|
-
if (config !== undefined)
|
|
179
|
-
writeFileSync(join(root, '.design', 'config.json'), JSON.stringify(config));
|
|
180
|
-
return root;
|
|
181
|
-
}
|
|
182
|
-
test('reads generation.keyframes.engine when present', () => {
|
|
183
|
-
const root = repoWith({ generation: { keyframes: { engine: 'ffmpeg' } } });
|
|
184
|
-
expect(readEnginePref(root)).toBe('ffmpeg');
|
|
185
|
-
});
|
|
186
|
-
test('null when absent / no config / no root (terminal self-detect path)', () => {
|
|
187
|
-
expect(readEnginePref(repoWith({}))).toBeNull();
|
|
188
|
-
expect(readEnginePref(repoWith(undefined))).toBeNull();
|
|
189
|
-
expect(readEnginePref(null)).toBeNull();
|
|
190
|
-
});
|
|
191
|
-
test('null on an invalid engine value', () => {
|
|
192
|
-
const root = repoWith({ generation: { keyframes: { engine: 'nonsense' } } });
|
|
193
|
-
expect(readEnginePref(root)).toBeNull();
|
|
194
|
-
});
|
|
195
|
-
});
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
// _transcribe.test.mjs — the whisper.cpp JSON → word-timings parser (Phase 2).
|
|
2
|
-
// The spawn/model resolution is a soft-dep live gate; this covers the pure,
|
|
3
|
-
// deterministic parsing that turns whisper.cpp `-oj` output into CaptionWords
|
|
4
|
-
// (then captions.ts renders the SRT — covered in captions.test.ts).
|
|
5
|
-
|
|
6
|
-
import { describe, expect, test } from 'bun:test';
|
|
7
|
-
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
|
|
8
|
-
import { tmpdir } from 'node:os';
|
|
9
|
-
import { join } from 'node:path';
|
|
10
|
-
import { wordsToSrt } from '../generation/captions.ts';
|
|
11
|
-
import { resolveProvider, whisperJsonToWords } from './_transcribe.mjs';
|
|
12
|
-
|
|
13
|
-
function tmpRepo(config) {
|
|
14
|
-
const root = mkdtempSync(join(tmpdir(), 'maude-transcribe-'));
|
|
15
|
-
mkdirSync(join(root, '.design'), { recursive: true });
|
|
16
|
-
if (config !== undefined)
|
|
17
|
-
writeFileSync(join(root, '.design', 'config.json'), JSON.stringify(config));
|
|
18
|
-
return root;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const WHISPER_JSON = JSON.stringify({
|
|
22
|
-
transcription: [
|
|
23
|
-
{ offsets: { from: 0, to: 480 }, text: ' Hello' },
|
|
24
|
-
{ offsets: { from: 480, to: 900 }, text: ' there.' },
|
|
25
|
-
{ offsets: { from: 1200, to: 1800 }, text: ' Maude' },
|
|
26
|
-
{ offsets: { from: 0, to: 0 }, text: ' ' }, // blank → dropped
|
|
27
|
-
],
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe('whisperJsonToWords', () => {
|
|
31
|
-
test('maps offsets (ms) to seconds and trims text', () => {
|
|
32
|
-
const words = whisperJsonToWords(WHISPER_JSON);
|
|
33
|
-
expect(words).toHaveLength(3);
|
|
34
|
-
expect(words[0]).toEqual({ text: 'Hello', start: 0, end: 0.48 });
|
|
35
|
-
expect(words[1]).toEqual({ text: 'there.', start: 0.48, end: 0.9 });
|
|
36
|
-
expect(words[2].text).toBe('Maude');
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('feeds captions.ts to produce a valid SRT', () => {
|
|
40
|
-
const srt = wordsToSrt(whisperJsonToWords(WHISPER_JSON));
|
|
41
|
-
expect(srt).toContain('00:00:00,000 --> 00:00:00,900');
|
|
42
|
-
expect(srt).toContain('Hello there.');
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test('malformed / empty JSON yields no words (never throws)', () => {
|
|
46
|
-
expect(whisperJsonToWords('not json')).toEqual([]);
|
|
47
|
-
expect(whisperJsonToWords('{}')).toEqual([]);
|
|
48
|
-
expect(whisperJsonToWords(JSON.stringify({ transcription: [] }))).toEqual([]);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test('tolerates missing offsets', () => {
|
|
52
|
-
const words = whisperJsonToWords(JSON.stringify({ transcription: [{ text: 'x' }] }));
|
|
53
|
-
expect(words).toEqual([{ text: 'x', start: 0, end: 0 }]);
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe('resolveProvider (Task 2.6 — explicit engine choice, no silent switch)', () => {
|
|
58
|
-
test('an explicit valid flag wins over config', () => {
|
|
59
|
-
const repo = tmpRepo({ generation: { transcription: { provider: 'groq' } } });
|
|
60
|
-
expect(resolveProvider('elevenlabs', repo)).toEqual({
|
|
61
|
-
provider: 'elevenlabs',
|
|
62
|
-
defaulted: false,
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
test('falls back to the project config default', () => {
|
|
67
|
-
const repo = tmpRepo({ generation: { transcription: { provider: 'groq' } } });
|
|
68
|
-
expect(resolveProvider(undefined, repo)).toEqual({ provider: 'groq', defaulted: false });
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
test('defaults to local whisper (marked defaulted) when nothing is set', () => {
|
|
72
|
-
const repo = tmpRepo(); // no config
|
|
73
|
-
expect(resolveProvider(undefined, repo)).toEqual({ provider: 'whisper', defaulted: true });
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test('a malformed config provider is ignored → whisper default', () => {
|
|
77
|
-
const repo = tmpRepo({ generation: { transcription: { provider: 'bogus' } } });
|
|
78
|
-
expect(resolveProvider(undefined, repo)).toEqual({ provider: 'whisper', defaulted: true });
|
|
79
|
-
});
|
|
80
|
-
});
|