@1agh/maude 1.0.11 → 1.3.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 +3 -1
- package/apps/studio/acp/bridge.ts +18 -13
- package/apps/studio/acp/index.ts +43 -7
- package/apps/studio/acp/transcript-io.ts +394 -0
- package/apps/studio/acp/transcript.ts +103 -38
- package/apps/studio/annotations-layer.tsx +191 -116
- package/apps/studio/annotations-model.ts +39 -0
- package/apps/studio/annotations-sync.ts +50 -0
- package/apps/studio/api.ts +821 -55
- package/apps/studio/bin/_import-figma.mjs +57 -11
- package/apps/studio/bin/_seed-fixture.mjs +163 -0
- package/apps/studio/bin/annotate.mjs +3 -1
- package/apps/studio/bin/server-up.sh +17 -2
- package/apps/studio/canvas-edit.ts +391 -7
- package/apps/studio/canvas-imports.ts +89 -0
- package/apps/studio/canvas-lib.tsx +82 -18
- package/apps/studio/canvas-notice-message.ts +16 -0
- package/apps/studio/canvas-notifications.tsx +19 -0
- package/apps/studio/canvas-shell.tsx +57 -6
- package/apps/studio/client/app.jsx +943 -248
- package/apps/studio/client/apply-edit-request.ts +55 -0
- package/apps/studio/client/export-center.jsx +125 -67
- package/apps/studio/client/github.js +38 -1
- package/apps/studio/client/index-loader.ts +76 -0
- package/apps/studio/client/panels/CloudBar.jsx +52 -6
- package/apps/studio/client/panels/GitPanel.jsx +103 -0
- package/apps/studio/client/panels/OnboardingWizard.jsx +36 -7
- package/apps/studio/client/panels/RepoBranchSwitcher.jsx +92 -27
- package/apps/studio/client/panels/SourceConflictPanel.jsx +166 -0
- package/apps/studio/client/panels/SyncPanel.jsx +238 -4
- package/apps/studio/client/panels/TeamProjects.jsx +376 -0
- package/apps/studio/client/panels/file-deep-link-dialog.jsx +54 -0
- package/apps/studio/client/photo-knobs.jsx +3 -0
- package/apps/studio/client/report-bug.jsx +19 -1
- package/apps/studio/client/share-dialog.jsx +91 -0
- package/apps/studio/client/share-link.js +94 -0
- package/apps/studio/client/styles/3-shell-maude.css +69 -18
- package/apps/studio/client/styles/4-components.css +44 -1
- package/apps/studio/client/tree-row-menu.jsx +48 -3
- package/apps/studio/client/whats-new.jsx +26 -32
- package/apps/studio/cloud/endpoints.ts +132 -0
- package/apps/studio/collab/index.ts +4 -0
- package/apps/studio/collab/persistence.ts +137 -17
- package/apps/studio/collab/registry.ts +19 -21
- package/apps/studio/collab/room.ts +13 -2
- package/apps/studio/context.ts +59 -0
- package/apps/studio/debug-bundle.ts +13 -0
- package/apps/studio/dist/client.bundle.js +1911 -1879
- package/apps/studio/dist/runtime/.min-sizes.json +1 -0
- package/apps/studio/dist/runtime/sonner.js +1 -0
- 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 +296 -19
- package/apps/studio/inspect.ts +19 -0
- package/apps/studio/managed-projects.ts +150 -0
- package/apps/studio/mem.ts +45 -9
- package/apps/studio/notifications.tsx +292 -0
- package/apps/studio/runtime-bundle.ts +2 -0
- package/apps/studio/server.ts +69 -6
- package/apps/studio/sync/accepted-cold-start.ts +225 -0
- package/apps/studio/sync/accepted-link.ts +320 -0
- package/apps/studio/sync/action-stage.ts +343 -0
- package/apps/studio/sync/agent.ts +104 -45
- package/apps/studio/sync/asset-push.ts +1 -47
- package/apps/studio/sync/cell-file-events.ts +3 -0
- package/apps/studio/sync/codec.ts +192 -34
- 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/ctl-provider.ts +15 -2
- package/apps/studio/sync/document-discovery.ts +81 -0
- package/apps/studio/sync/file-ledger.ts +117 -0
- package/apps/studio/sync/file-membership.ts +10 -2
- package/apps/studio/sync/file-plane.ts +926 -47
- package/apps/studio/sync/index.ts +1786 -36
- package/apps/studio/sync/journal-client.ts +13 -1
- package/apps/studio/sync/migrate-seed.ts +150 -22
- package/apps/studio/sync/poke.ts +4 -2
- package/apps/studio/sync/presentation.ts +311 -5
- package/apps/studio/sync/projection.ts +820 -23
- package/apps/studio/sync/repeated-module.ts +73 -0
- package/apps/studio/sync/retry-after.ts +169 -0
- package/apps/studio/sync/revision-barrier.ts +129 -0
- package/apps/studio/sync/seed-progress.ts +218 -0
- package/apps/studio/sync/seed-repair.ts +46 -0
- package/apps/studio/sync/source-merge.ts +100 -0
- package/apps/studio/sync/source-ops.ts +289 -0
- package/apps/studio/sync/source-recovery.ts +70 -0
- package/apps/studio/sync/source-validation.ts +56 -0
- package/apps/studio/sync/status.ts +201 -6
- package/apps/studio/sync/transaction-client.ts +460 -0
- package/apps/studio/sync/transport-error.ts +111 -0
- package/apps/studio/sync/writer-registry.ts +236 -0
- package/apps/studio/text-caret.ts +35 -0
- package/apps/studio/undo-hud.tsx +9 -87
- package/apps/studio/use-canvas-media-drop.tsx +4 -39
- package/apps/studio/whats-new.json +173 -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 +892 -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 +33 -9
- package/plugins/design/dependencies.json +3 -3
- package/plugins/design/templates/_shell.html +47 -0
- package/plugins/flow/.claude-plugin/config.schema.json +8 -0
- package/plugins/flow/dependencies.json +3 -3
- 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 -545
- 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/backgrounding-claude-auth.sh +0 -12
- package/apps/studio/test/fixtures/fake-claude-auth.mjs +0 -23
- 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/noisy-claude-auth.mjs +0 -27
- 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/fixtures/wedged-claude-auth.sh +0 -11
- 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 -337
- 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
|
@@ -0,0 +1,1051 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
|
3
|
+
import { lstat, mkdir, readFile, stat } from 'node:fs/promises';
|
|
4
|
+
import { homedir, tmpdir } from 'node:os';
|
|
5
|
+
import { delimiter, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
6
|
+
import { createInterface } from 'node:readline/promises';
|
|
7
|
+
|
|
8
|
+
import { parse as parseToml } from '@decimalturn/toml-patch';
|
|
9
|
+
|
|
10
|
+
import { parseArgs } from '../lib/argv.mjs';
|
|
11
|
+
import { assertTargetVersion } from '../lib/harness/compatibility.mjs';
|
|
12
|
+
import { discoverClaude } from '../lib/harness/discover-claude.mjs';
|
|
13
|
+
import {
|
|
14
|
+
adoptManagedPath,
|
|
15
|
+
assertAllowedPath,
|
|
16
|
+
createManifest,
|
|
17
|
+
hashFile,
|
|
18
|
+
manifestPaths,
|
|
19
|
+
RemovalInterruptedError,
|
|
20
|
+
readFileNoFollow,
|
|
21
|
+
readManifest,
|
|
22
|
+
writeManifest,
|
|
23
|
+
} from '../lib/harness/managed-state.mjs';
|
|
24
|
+
import { createEnvironmentIR, sha256 } from '../lib/harness/model.mjs';
|
|
25
|
+
import { containsRejectedLiteral, sanitizeUntrustedValue } from '../lib/harness/secrets.mjs';
|
|
26
|
+
import { CODEX_TARGET_VERSION, lowerCodex } from '../lib/harness/targets/codex.mjs';
|
|
27
|
+
import {
|
|
28
|
+
lowerOpenCode,
|
|
29
|
+
OPENCODE_TARGET_VERSION,
|
|
30
|
+
smokeOpenCodeConfig,
|
|
31
|
+
} from '../lib/harness/targets/opencode.mjs';
|
|
32
|
+
import {
|
|
33
|
+
LockError,
|
|
34
|
+
runManagedTransaction,
|
|
35
|
+
TransactionInterruptedError,
|
|
36
|
+
} from '../lib/harness/transaction.mjs';
|
|
37
|
+
|
|
38
|
+
export const EXIT_CODES = Object.freeze({
|
|
39
|
+
clean: 0,
|
|
40
|
+
drift: 1,
|
|
41
|
+
usage: 2,
|
|
42
|
+
strict: 3,
|
|
43
|
+
ownership: 4,
|
|
44
|
+
source: 5,
|
|
45
|
+
target: 6,
|
|
46
|
+
interrupted: 7,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const TARGETS = new Set(['opencode', 'codex']);
|
|
50
|
+
const VERBS = new Set(['migrate', 'sync', 'check', 'diff', 'status', 'adopt', 'remove']);
|
|
51
|
+
const SAFE_OPENCODE_DEFAULTS = {
|
|
52
|
+
permission: {
|
|
53
|
+
bash: 'ask',
|
|
54
|
+
edit: 'ask',
|
|
55
|
+
external_directory: 'ask',
|
|
56
|
+
webfetch: 'ask',
|
|
57
|
+
},
|
|
58
|
+
tools: { task: false, websearch: false },
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export async function run({ args, io = defaultIo() }) {
|
|
62
|
+
try {
|
|
63
|
+
return await runHarness(args, io);
|
|
64
|
+
} catch (error) {
|
|
65
|
+
const code = classifyError(error);
|
|
66
|
+
write(io.stderr, `maude harness: ${error.message}\n`);
|
|
67
|
+
process.exitCode = code;
|
|
68
|
+
return { error: error.message, exitCode: code };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function runHarness(args, io) {
|
|
73
|
+
const { flags, positional } = parseArgs(args, {
|
|
74
|
+
booleans: ['global', 'help', 'json', 'strict', 'yes'],
|
|
75
|
+
});
|
|
76
|
+
const verb = positional[0];
|
|
77
|
+
if (!verb || verb === 'help' || flags.help) {
|
|
78
|
+
write(io.stdout, usage());
|
|
79
|
+
return result(EXIT_CODES.clean);
|
|
80
|
+
}
|
|
81
|
+
if (!VERBS.has(verb)) throw usageError(`unknown verb "${verb}"`);
|
|
82
|
+
if (positional.length > 1) throw usageError(`unexpected argument "${positional[1]}"`);
|
|
83
|
+
|
|
84
|
+
const scope = resolveScope(flags);
|
|
85
|
+
const selectedTargets = resolveTargets(verb, flags);
|
|
86
|
+
assertDiagnosticOverrides(verb, scope);
|
|
87
|
+
if (verb === 'migrate' && flags.from !== 'claude') {
|
|
88
|
+
throw sourceError('migrate requires --from claude');
|
|
89
|
+
}
|
|
90
|
+
rejectUnknownFlags(flags, verb);
|
|
91
|
+
|
|
92
|
+
const context = await createContext({ flags, scope, selectedTargets });
|
|
93
|
+
if (verb === 'status') return await status(context, flags, io);
|
|
94
|
+
if (verb === 'adopt') return await adopt(context, flags, io);
|
|
95
|
+
if (verb === 'remove') return await remove(context, flags, io);
|
|
96
|
+
|
|
97
|
+
const projection = await project(context);
|
|
98
|
+
const preview = await buildPreview(context, projection);
|
|
99
|
+
const strictFailure = flags.strict && hasStrictFailure(projection.reports);
|
|
100
|
+
const securityFailure = hasSecurityFailure(projection.reports);
|
|
101
|
+
|
|
102
|
+
if (verb === 'check' || verb === 'diff') {
|
|
103
|
+
printPreview(preview, projection, flags, io);
|
|
104
|
+
return finish(strictFailure ? EXIT_CODES.strict : preview.drift ? EXIT_CODES.drift : 0);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
printPreview(preview, projection, flags, io);
|
|
108
|
+
if (securityFailure) {
|
|
109
|
+
throw strictError('unsupported or unproven security capability blocks mutation');
|
|
110
|
+
}
|
|
111
|
+
if (preview.conflicts.length > 0) throw ownershipError(preview.conflicts[0].reason);
|
|
112
|
+
if (!preview.drift) return finish(EXIT_CODES.clean);
|
|
113
|
+
|
|
114
|
+
if (verb === 'migrate' && !flags.yes && !(await confirmMutation(io, 'Apply this migration?'))) {
|
|
115
|
+
return finish(EXIT_CODES.drift);
|
|
116
|
+
}
|
|
117
|
+
await ensureTargetRoots(context.targetLayouts);
|
|
118
|
+
await commitProjection(context, projection);
|
|
119
|
+
if (!flags.json) write(io.stdout, 'Harness projection committed.\n');
|
|
120
|
+
return finish(EXIT_CODES.clean);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function resolveScope(flags) {
|
|
124
|
+
const global = flags.global === true;
|
|
125
|
+
const project = typeof flags.project === 'string' && flags.project.length > 0;
|
|
126
|
+
if (global === project)
|
|
127
|
+
throw usageError('select exactly one scope: --global or --project <root>');
|
|
128
|
+
return project
|
|
129
|
+
? { kind: 'project', projectRoot: resolve(flags.project) }
|
|
130
|
+
: { kind: 'global', projectRoot: process.cwd() };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function resolveTargets(verb, flags) {
|
|
134
|
+
if (verb === 'status') {
|
|
135
|
+
if (flags.targets || flags.target) throw usageError('status reads all owned targets');
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
if (verb === 'adopt' || verb === 'remove') {
|
|
139
|
+
if (!flags.target || flags.targets) throw usageError(`${verb} requires exactly one --target`);
|
|
140
|
+
validateTarget(flags.target);
|
|
141
|
+
return [flags.target];
|
|
142
|
+
}
|
|
143
|
+
if (!flags.targets || flags.target) throw usageError(`${verb} requires --targets <list>`);
|
|
144
|
+
const targets = [
|
|
145
|
+
...new Set(
|
|
146
|
+
String(flags.targets)
|
|
147
|
+
.split(',')
|
|
148
|
+
.map((value) => value.trim())
|
|
149
|
+
.filter(Boolean)
|
|
150
|
+
),
|
|
151
|
+
];
|
|
152
|
+
if (targets.length === 0) throw targetError('target list is empty');
|
|
153
|
+
for (const target of targets) validateTarget(target);
|
|
154
|
+
return targets.sort();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function validateTarget(target) {
|
|
158
|
+
if (!TARGETS.has(target)) throw targetError(`invalid target "${target}"`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function rejectUnknownFlags(flags, verb) {
|
|
162
|
+
const common = new Set(['global', 'help', 'json', 'project']);
|
|
163
|
+
const byVerb = {
|
|
164
|
+
adopt: ['path', 'target', 'yes'],
|
|
165
|
+
check: ['strict', 'targets'],
|
|
166
|
+
diff: ['targets'],
|
|
167
|
+
migrate: ['from', 'targets', 'yes'],
|
|
168
|
+
remove: ['target', 'yes'],
|
|
169
|
+
status: [],
|
|
170
|
+
sync: ['targets'],
|
|
171
|
+
};
|
|
172
|
+
const allowed = new Set([...common, ...byVerb[verb]]);
|
|
173
|
+
const unknown = Object.keys(flags).find((flag) => !allowed.has(flag));
|
|
174
|
+
if (unknown) throw usageError(`unsupported option --${unknown} for ${verb}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function createContext({ flags, scope, selectedTargets }) {
|
|
178
|
+
await assertDirectory(scope.projectRoot, 'project root');
|
|
179
|
+
const home = resolve(process.env.HOME || homedir());
|
|
180
|
+
await assertDirectory(home, 'home');
|
|
181
|
+
const sourceHome = resolve(process.env.MAUDE_HARNESS_SOURCE_HOME || home);
|
|
182
|
+
await assertDirectory(sourceHome, 'Claude source home');
|
|
183
|
+
const allowedPluginRoots = String(process.env.MAUDE_HARNESS_ALLOWED_PLUGIN_ROOTS || '')
|
|
184
|
+
.split(delimiter)
|
|
185
|
+
.filter(Boolean)
|
|
186
|
+
.map((path) => resolve(path));
|
|
187
|
+
const stateRoot = resolve(
|
|
188
|
+
process.env.MAUDE_HARNESS_STATE_ROOT || join(home, '.config', 'maude', 'harness')
|
|
189
|
+
);
|
|
190
|
+
const paths = await manifestPaths({
|
|
191
|
+
projectRoot: scope.projectRoot,
|
|
192
|
+
scope: scope.kind,
|
|
193
|
+
stateRoot,
|
|
194
|
+
});
|
|
195
|
+
const manifest = await readManifest(paths);
|
|
196
|
+
const targetLayouts = Object.fromEntries(
|
|
197
|
+
selectedTargets.map((target) => [target, targetLayout(target, scope, home)])
|
|
198
|
+
);
|
|
199
|
+
return {
|
|
200
|
+
allowedPluginRoots,
|
|
201
|
+
flags,
|
|
202
|
+
home,
|
|
203
|
+
manifest,
|
|
204
|
+
paths,
|
|
205
|
+
scope,
|
|
206
|
+
selectedTargets,
|
|
207
|
+
sourceHome,
|
|
208
|
+
targetLayouts,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function targetLayout(target, scope, home) {
|
|
213
|
+
const sourceProjectRoot = scope.projectRoot;
|
|
214
|
+
const targetProjectRoot =
|
|
215
|
+
scope.kind === 'project'
|
|
216
|
+
? resolve(process.env.MAUDE_HARNESS_PROJECT_TARGET_ROOT || sourceProjectRoot)
|
|
217
|
+
: sourceProjectRoot;
|
|
218
|
+
if (target === 'opencode') {
|
|
219
|
+
const root =
|
|
220
|
+
scope.kind === 'global'
|
|
221
|
+
? join(resolve(process.env.XDG_CONFIG_HOME || join(home, '.config')), 'opencode')
|
|
222
|
+
: targetProjectRoot;
|
|
223
|
+
return {
|
|
224
|
+
allowRoots: scope.kind === 'global' ? [root] : [targetProjectRoot],
|
|
225
|
+
configPath: join(root, 'opencode.json'),
|
|
226
|
+
outputRoot: scope.kind === 'global' ? root : join(targetProjectRoot, '.opencode'),
|
|
227
|
+
roots:
|
|
228
|
+
scope.kind === 'global'
|
|
229
|
+
? [root]
|
|
230
|
+
: [targetProjectRoot, join(targetProjectRoot, '.opencode')],
|
|
231
|
+
target,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
const codexHome = resolve(process.env.CODEX_HOME || join(home, '.codex'));
|
|
235
|
+
const projectConfigRoot = join(targetProjectRoot, '.codex');
|
|
236
|
+
const userSkillsRoot = join(home, '.agents', 'skills');
|
|
237
|
+
const projectSkillsRoot = join(targetProjectRoot, '.agents', 'skills');
|
|
238
|
+
const outputRoot =
|
|
239
|
+
scope.kind === 'global' ? join(codexHome, 'maude') : join(projectConfigRoot, 'maude');
|
|
240
|
+
const allowRoots =
|
|
241
|
+
scope.kind === 'global' ? [codexHome, join(home, '.agents')] : [targetProjectRoot];
|
|
242
|
+
return {
|
|
243
|
+
allowRoots,
|
|
244
|
+
outputRoot,
|
|
245
|
+
projectConfigPath: join(projectConfigRoot, 'config.toml'),
|
|
246
|
+
projectRoot: sourceProjectRoot,
|
|
247
|
+
projectSkillsRoot,
|
|
248
|
+
roots:
|
|
249
|
+
scope.kind === 'global'
|
|
250
|
+
? [codexHome, outputRoot, userSkillsRoot]
|
|
251
|
+
: [projectConfigRoot, outputRoot, projectSkillsRoot],
|
|
252
|
+
target,
|
|
253
|
+
userConfigPath: join(codexHome, 'config.toml'),
|
|
254
|
+
userSkillsRoot,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function project(context) {
|
|
259
|
+
let discovered;
|
|
260
|
+
try {
|
|
261
|
+
discovered = await discoverClaude({
|
|
262
|
+
allowedPluginRoots: context.allowedPluginRoots,
|
|
263
|
+
home: context.sourceHome,
|
|
264
|
+
projectRoot: context.scope.projectRoot,
|
|
265
|
+
});
|
|
266
|
+
} catch (error) {
|
|
267
|
+
throw sourceError(error.message, error);
|
|
268
|
+
}
|
|
269
|
+
const baseIr = createEnvironmentIR(
|
|
270
|
+
discovered.items.filter((item) => item.scope === context.scope.kind)
|
|
271
|
+
);
|
|
272
|
+
const targetOverrides = Object.fromEntries(
|
|
273
|
+
await Promise.all(
|
|
274
|
+
context.selectedTargets.map(async (target) => [
|
|
275
|
+
target,
|
|
276
|
+
await readTargetOverride(context, target),
|
|
277
|
+
])
|
|
278
|
+
)
|
|
279
|
+
);
|
|
280
|
+
const overrideHashes = Object.fromEntries(
|
|
281
|
+
Object.entries(targetOverrides).map(([target, override]) => [
|
|
282
|
+
`target-override:${target}`,
|
|
283
|
+
sha256(JSON.stringify(override)),
|
|
284
|
+
])
|
|
285
|
+
);
|
|
286
|
+
const ir = {
|
|
287
|
+
...baseIr,
|
|
288
|
+
generationHash: sha256(`${baseIr.serialized}\n${JSON.stringify(overrideHashes)}`),
|
|
289
|
+
};
|
|
290
|
+
const outputs = [];
|
|
291
|
+
const preservedPaths = [];
|
|
292
|
+
const reports = [];
|
|
293
|
+
const observedVersions = {};
|
|
294
|
+
for (const target of context.selectedTargets) {
|
|
295
|
+
const layout = context.targetLayouts[target];
|
|
296
|
+
const observed = observeTargetVersion(target);
|
|
297
|
+
observedVersions[target] = observed;
|
|
298
|
+
try {
|
|
299
|
+
assertTargetVersion(target, observed);
|
|
300
|
+
} catch (error) {
|
|
301
|
+
throw targetError(error.message, error);
|
|
302
|
+
}
|
|
303
|
+
const lowered =
|
|
304
|
+
target === 'opencode'
|
|
305
|
+
? await lowerOpenCode(ir, {
|
|
306
|
+
configPath: layout.configPath,
|
|
307
|
+
defaultConfig: await openCodeDefaults(context),
|
|
308
|
+
existingConfig: await readJsonIfPresent(layout.configPath),
|
|
309
|
+
observedTargetVersion: observed || OPENCODE_TARGET_VERSION,
|
|
310
|
+
outputRoot: layout.outputRoot,
|
|
311
|
+
overrides: targetOverrides.opencode,
|
|
312
|
+
})
|
|
313
|
+
: await lowerCodex(ir, {
|
|
314
|
+
existingProjectToml: await readTextIfPresent(layout.projectConfigPath),
|
|
315
|
+
existingUserToml: await readTextIfPresent(layout.userConfigPath),
|
|
316
|
+
observedTargetVersion: observed || CODEX_TARGET_VERSION,
|
|
317
|
+
outputRoot: layout.outputRoot,
|
|
318
|
+
projectConfigPath: layout.projectConfigPath,
|
|
319
|
+
projectRoot: layout.projectRoot,
|
|
320
|
+
projectSkillsRoot: layout.projectSkillsRoot,
|
|
321
|
+
projectTrusted:
|
|
322
|
+
context.scope.kind === 'project' &&
|
|
323
|
+
codexProjectTrusted(
|
|
324
|
+
await readTextIfPresent(layout.userConfigPath),
|
|
325
|
+
layout.projectRoot
|
|
326
|
+
),
|
|
327
|
+
overrides: targetOverrides.codex,
|
|
328
|
+
trustedHookHashes: targetOverrides.codex?.trusted_hook_hashes ?? [],
|
|
329
|
+
userConfigPath: layout.userConfigPath,
|
|
330
|
+
userSkillsRoot: layout.userSkillsRoot,
|
|
331
|
+
});
|
|
332
|
+
const scopedOutputs = selectScopedOutputs(target, context.scope.kind, lowered.outputs, layout);
|
|
333
|
+
await validateOutputs(scopedOutputs);
|
|
334
|
+
outputs.push(...scopedOutputs);
|
|
335
|
+
preservedPaths.push(...(lowered.preservedPaths ?? []));
|
|
336
|
+
reports.push(lowered.report);
|
|
337
|
+
}
|
|
338
|
+
return {
|
|
339
|
+
ir,
|
|
340
|
+
observedVersions,
|
|
341
|
+
outputs: outputs.sort(byPath),
|
|
342
|
+
overrideHashes,
|
|
343
|
+
preservedPaths: [...new Set(preservedPaths)].sort(),
|
|
344
|
+
reports,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function readTargetOverride(context, target) {
|
|
349
|
+
if (context.scope.kind !== 'project') return {};
|
|
350
|
+
const path = join(
|
|
351
|
+
context.scope.projectRoot,
|
|
352
|
+
'.maude',
|
|
353
|
+
'targets',
|
|
354
|
+
target === 'opencode' ? 'opencode.json' : 'codex.toml'
|
|
355
|
+
);
|
|
356
|
+
const text = await readTextIfPresent(path);
|
|
357
|
+
if (!text) return {};
|
|
358
|
+
let value;
|
|
359
|
+
try {
|
|
360
|
+
value = target === 'opencode' ? JSON.parse(text) : parseToml(text);
|
|
361
|
+
} catch (error) {
|
|
362
|
+
throw sourceError(`invalid ${target} target override: ${error.message}`, error);
|
|
363
|
+
}
|
|
364
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
365
|
+
throw sourceError(`${target} target override must be an object`);
|
|
366
|
+
}
|
|
367
|
+
const allowed =
|
|
368
|
+
target === 'opencode'
|
|
369
|
+
? new Set(['acknowledge', 'exclude', 'permission'])
|
|
370
|
+
: new Set([
|
|
371
|
+
'acknowledge',
|
|
372
|
+
'approval_policy',
|
|
373
|
+
'exclude',
|
|
374
|
+
'sandbox_mode',
|
|
375
|
+
'trusted_hook_hashes',
|
|
376
|
+
]);
|
|
377
|
+
const unknown = Object.keys(value).find((key) => !allowed.has(key));
|
|
378
|
+
if (unknown) throw sourceError(`unsupported ${target} target override key ${unknown}`);
|
|
379
|
+
for (const key of ['acknowledge', 'exclude', 'trusted_hook_hashes']) {
|
|
380
|
+
if (value[key] !== undefined && !isStringArray(value[key])) {
|
|
381
|
+
throw sourceError(`${target} target override ${key} must be an array of strings`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
const sanitized = sanitizeUntrustedValue(value);
|
|
385
|
+
if (containsRejectedLiteral(sanitized.value)) {
|
|
386
|
+
throw sourceError(`${target} target override contains a literal credential`);
|
|
387
|
+
}
|
|
388
|
+
return sanitized.value;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function codexProjectTrusted(userToml, projectRoot) {
|
|
392
|
+
if (!userToml) return false;
|
|
393
|
+
let config;
|
|
394
|
+
try {
|
|
395
|
+
config = parseToml(userToml);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
throw targetError(`invalid Codex user config: ${error.message}`, error);
|
|
398
|
+
}
|
|
399
|
+
return config.projects?.[resolve(projectRoot)]?.trust_level === 'trusted';
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
async function openCodeDefaults(context) {
|
|
403
|
+
if (process.env.MAUDE_HARNESS_SKIP_EXECUTABLE_PROOF === '1') return SAFE_OPENCODE_DEFAULTS;
|
|
404
|
+
const smoke = smokeOpenCodeConfig({
|
|
405
|
+
cwd: context.scope.projectRoot,
|
|
406
|
+
env: targetEnvironment(context),
|
|
407
|
+
pure: true,
|
|
408
|
+
});
|
|
409
|
+
return smoke.available ? smoke.config : SAFE_OPENCODE_DEFAULTS;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function selectScopedOutputs(target, scope, outputs, layout) {
|
|
413
|
+
if (target !== 'codex') return outputs;
|
|
414
|
+
const excludedConfig = scope === 'global' ? layout.projectConfigPath : layout.userConfigPath;
|
|
415
|
+
return outputs.filter((output) => output.path !== excludedConfig);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async function validateOutputs(outputs) {
|
|
419
|
+
const paths = new Set();
|
|
420
|
+
for (const output of outputs) {
|
|
421
|
+
if (paths.has(output.path)) throw targetError(`duplicate target output ${output.path}`);
|
|
422
|
+
paths.add(output.path);
|
|
423
|
+
try {
|
|
424
|
+
if (output.metadata.kind === 'plugin') {
|
|
425
|
+
const checked = spawnSync(process.execPath, ['--input-type=module', '--check'], {
|
|
426
|
+
encoding: 'utf8',
|
|
427
|
+
input: output.contents,
|
|
428
|
+
});
|
|
429
|
+
if (checked.status !== 0) {
|
|
430
|
+
throw new Error(checked.stderr || checked.stdout || 'module syntax check failed');
|
|
431
|
+
}
|
|
432
|
+
} else {
|
|
433
|
+
await output.validate?.(output.contents, output.path);
|
|
434
|
+
}
|
|
435
|
+
} catch (error) {
|
|
436
|
+
throw targetError(`validation failed for ${output.path}: ${error.message}`, error);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function buildPreview(context, projection) {
|
|
442
|
+
const owned = new Map((context.manifest?.outputs ?? []).map((output) => [output.path, output]));
|
|
443
|
+
const changed = [];
|
|
444
|
+
const conflicts = [];
|
|
445
|
+
for (const output of projection.outputs) {
|
|
446
|
+
await assertAllowedPath(output.path, context.targetLayouts[output.metadata.target].allowRoots, {
|
|
447
|
+
allowMissingRoots: true,
|
|
448
|
+
});
|
|
449
|
+
const prior = owned.get(output.path);
|
|
450
|
+
const currentHash = await optionalHash(output.path);
|
|
451
|
+
const desiredHash = sha256(output.contents);
|
|
452
|
+
if (prior && currentHash !== prior.hash) {
|
|
453
|
+
conflicts.push({
|
|
454
|
+
path: output.path,
|
|
455
|
+
reason: `managed output was externally modified: ${output.path}`,
|
|
456
|
+
});
|
|
457
|
+
} else if (!prior && currentHash && currentHash !== desiredHash) {
|
|
458
|
+
conflicts.push({
|
|
459
|
+
path: output.path,
|
|
460
|
+
reason: `unmanaged target collision requires adoption: ${output.path}`,
|
|
461
|
+
});
|
|
462
|
+
} else if (currentHash !== desiredHash) {
|
|
463
|
+
changed.push({
|
|
464
|
+
action: currentHash ? 'update' : 'create',
|
|
465
|
+
path: output.path,
|
|
466
|
+
target: output.metadata.target,
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
owned.delete(output.path);
|
|
470
|
+
}
|
|
471
|
+
for (const path of projection.preservedPaths ?? []) {
|
|
472
|
+
const prior = owned.get(path);
|
|
473
|
+
if (!prior) continue;
|
|
474
|
+
const currentHash = await optionalHash(path);
|
|
475
|
+
if (currentHash !== prior.hash) {
|
|
476
|
+
conflicts.push({
|
|
477
|
+
path,
|
|
478
|
+
reason: `managed output became a native source after external modification: ${path}`,
|
|
479
|
+
});
|
|
480
|
+
} else {
|
|
481
|
+
changed.push({ action: 'release-ownership', path, target: prior.target });
|
|
482
|
+
}
|
|
483
|
+
owned.delete(path);
|
|
484
|
+
}
|
|
485
|
+
for (const stale of owned.values()) {
|
|
486
|
+
if (context.selectedTargets.includes(stale.target)) {
|
|
487
|
+
const currentHash = await optionalHash(stale.path);
|
|
488
|
+
if (currentHash !== stale.hash) {
|
|
489
|
+
conflicts.push({
|
|
490
|
+
path: stale.path,
|
|
491
|
+
reason: `stale managed output was externally modified: ${stale.path}`,
|
|
492
|
+
});
|
|
493
|
+
} else {
|
|
494
|
+
changed.push({ action: 'remove-stale', path: stale.path, target: stale.target });
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
return { changed, conflicts, drift: changed.length > 0 || conflicts.length > 0 };
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async function commitProjection(context, projection) {
|
|
502
|
+
const priorOutputs = new Map(
|
|
503
|
+
(context.manifest?.outputs ?? []).map((output) => [output.path, output])
|
|
504
|
+
);
|
|
505
|
+
const requested = new Set(context.selectedTargets);
|
|
506
|
+
const transactionOutputs = [];
|
|
507
|
+
for (const output of projection.outputs) {
|
|
508
|
+
const prior = priorOutputs.get(output.path);
|
|
509
|
+
if (!prior && (await optionalHash(output.path)) === sha256(output.contents)) continue;
|
|
510
|
+
transactionOutputs.push({
|
|
511
|
+
...output,
|
|
512
|
+
expectedHash: prior?.hash,
|
|
513
|
+
metadata: {
|
|
514
|
+
...output.metadata,
|
|
515
|
+
...(prior?.ownership === 'adopted'
|
|
516
|
+
? {
|
|
517
|
+
backupHash: prior.backupHash,
|
|
518
|
+
backupPath: prior.backupPath,
|
|
519
|
+
originalMode: prior.originalMode,
|
|
520
|
+
ownership: 'adopted',
|
|
521
|
+
}
|
|
522
|
+
: { ownership: 'generated' }),
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
for (const stale of priorOutputs.values()) {
|
|
527
|
+
if (
|
|
528
|
+
!requested.has(stale.target) ||
|
|
529
|
+
projection.outputs.some((output) => output.path === stale.path)
|
|
530
|
+
) {
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
if ((projection.preservedPaths ?? []).includes(stale.path)) continue;
|
|
534
|
+
transactionOutputs.push(
|
|
535
|
+
stale.ownership === 'adopted'
|
|
536
|
+
? {
|
|
537
|
+
contents: await readAdoptedBackup(stale, context.paths.backupDir),
|
|
538
|
+
expectedHash: stale.hash,
|
|
539
|
+
operation: 'restore',
|
|
540
|
+
path: stale.path,
|
|
541
|
+
restoreMode: stale.originalMode ?? 0o600,
|
|
542
|
+
}
|
|
543
|
+
: { expectedHash: stale.hash, operation: 'delete', path: stale.path }
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
const preserved = (context.manifest?.outputs ?? []).filter(
|
|
547
|
+
(output) => !requested.has(output.target)
|
|
548
|
+
);
|
|
549
|
+
const generationId = projection.ir.generationHash.slice(7);
|
|
550
|
+
const allowRoots = context.selectedTargets.flatMap(
|
|
551
|
+
(target) => context.targetLayouts[target].allowRoots
|
|
552
|
+
);
|
|
553
|
+
await runManagedTransaction({
|
|
554
|
+
allowRoots,
|
|
555
|
+
expectedGenerationId: context.manifest?.generationId ?? null,
|
|
556
|
+
generationId,
|
|
557
|
+
isGenerationCommitted: async (candidate) =>
|
|
558
|
+
(await readManifest(context.paths))?.generationId === candidate,
|
|
559
|
+
outputs: transactionOutputs,
|
|
560
|
+
readGenerationId: async () => (await readManifest(context.paths))?.generationId ?? null,
|
|
561
|
+
recheckSources: async () => {
|
|
562
|
+
const current = await discoverClaude({
|
|
563
|
+
allowedPluginRoots: context.allowedPluginRoots,
|
|
564
|
+
home: context.sourceHome,
|
|
565
|
+
projectRoot: context.scope.projectRoot,
|
|
566
|
+
});
|
|
567
|
+
const currentIr = createEnvironmentIR(
|
|
568
|
+
current.items.filter((item) => item.scope === context.scope.kind)
|
|
569
|
+
);
|
|
570
|
+
const currentOverrides = Object.fromEntries(
|
|
571
|
+
await Promise.all(
|
|
572
|
+
context.selectedTargets.map(async (target) => [
|
|
573
|
+
`target-override:${target}`,
|
|
574
|
+
sha256(JSON.stringify(await readTargetOverride(context, target))),
|
|
575
|
+
])
|
|
576
|
+
)
|
|
577
|
+
);
|
|
578
|
+
return (
|
|
579
|
+
sha256(`${currentIr.serialized}\n${JSON.stringify(currentOverrides)}`) ===
|
|
580
|
+
projection.ir.generationHash
|
|
581
|
+
);
|
|
582
|
+
},
|
|
583
|
+
scopeDir: context.paths.scopeDir,
|
|
584
|
+
commitManifest: async (committed) => {
|
|
585
|
+
const manifest = createManifest({
|
|
586
|
+
capabilitySummary: combineSummaries(projection.reports),
|
|
587
|
+
generationId,
|
|
588
|
+
outputs: [...preserved, ...committed],
|
|
589
|
+
rootHash: context.paths.rootHash,
|
|
590
|
+
sourceHashes: Object.fromEntries([
|
|
591
|
+
...projection.ir.items.map((item) => [item.id, item.sourceHash]),
|
|
592
|
+
...Object.entries(projection.overrideHashes ?? {}),
|
|
593
|
+
]),
|
|
594
|
+
targetVersions: {
|
|
595
|
+
...(context.manifest?.targetVersions ?? {}),
|
|
596
|
+
...projection.observedVersions,
|
|
597
|
+
},
|
|
598
|
+
});
|
|
599
|
+
manifest.lastValidation = new Date().toISOString();
|
|
600
|
+
await writeManifest(context.paths, manifest);
|
|
601
|
+
},
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
async function adopt(context, flags, io) {
|
|
606
|
+
if (!flags.path) throw usageError('adopt requires --path <owned-path>');
|
|
607
|
+
const target = context.selectedTargets[0];
|
|
608
|
+
const layout = context.targetLayouts[target];
|
|
609
|
+
const path = resolve(flags.path);
|
|
610
|
+
if ((context.manifest?.outputs ?? []).some((output) => output.path === path)) {
|
|
611
|
+
throw ownershipError(`path is already managed: ${path}`);
|
|
612
|
+
}
|
|
613
|
+
let preview;
|
|
614
|
+
try {
|
|
615
|
+
preview = await adoptManagedPath({
|
|
616
|
+
allowRoots: layout.allowRoots,
|
|
617
|
+
path,
|
|
618
|
+
paths: context.paths,
|
|
619
|
+
target,
|
|
620
|
+
});
|
|
621
|
+
} catch (error) {
|
|
622
|
+
throw targetError(error.message, error);
|
|
623
|
+
}
|
|
624
|
+
printObject(flags, io, { action: 'adopt', path: preview.path, target });
|
|
625
|
+
if (!flags.yes && !(await confirmMutation(io, 'Adopt this file and retain a private backup?'))) {
|
|
626
|
+
return finish(EXIT_CODES.drift);
|
|
627
|
+
}
|
|
628
|
+
const existing = context.manifest?.outputs ?? [];
|
|
629
|
+
const generationId = `adopt-${sha256(`${context.manifest?.generationId ?? 'none'}:${path}:${preview.hash}`).slice(7, 39)}`;
|
|
630
|
+
let adopted;
|
|
631
|
+
try {
|
|
632
|
+
await runManagedTransaction({
|
|
633
|
+
allowRoots: layout.allowRoots,
|
|
634
|
+
commitManifest: async (committed) => {
|
|
635
|
+
const manifest = createManifest({
|
|
636
|
+
capabilitySummary: context.manifest?.capabilitySummary,
|
|
637
|
+
generationId,
|
|
638
|
+
outputs: [...existing, ...committed],
|
|
639
|
+
rootHash: context.paths.rootHash,
|
|
640
|
+
sourceHashes: context.manifest?.sourceHashes,
|
|
641
|
+
targetVersions: context.manifest?.targetVersions,
|
|
642
|
+
});
|
|
643
|
+
if (context.manifest?.lastValidation)
|
|
644
|
+
manifest.lastValidation = context.manifest.lastValidation;
|
|
645
|
+
await writeManifest(context.paths, manifest);
|
|
646
|
+
},
|
|
647
|
+
expectedGenerationId: context.manifest?.generationId ?? null,
|
|
648
|
+
generationId,
|
|
649
|
+
isGenerationCommitted: async (candidate) =>
|
|
650
|
+
(await readManifest(context.paths))?.generationId === candidate,
|
|
651
|
+
outputs: async () => {
|
|
652
|
+
adopted = await adoptManagedPath({
|
|
653
|
+
allowRoots: layout.allowRoots,
|
|
654
|
+
confirm: true,
|
|
655
|
+
path,
|
|
656
|
+
paths: context.paths,
|
|
657
|
+
target,
|
|
658
|
+
});
|
|
659
|
+
return [
|
|
660
|
+
{
|
|
661
|
+
contents: await readFile(path),
|
|
662
|
+
expectedHash: adopted.hash,
|
|
663
|
+
metadata: adopted,
|
|
664
|
+
mode: adopted.originalMode,
|
|
665
|
+
path,
|
|
666
|
+
},
|
|
667
|
+
];
|
|
668
|
+
},
|
|
669
|
+
readGenerationId: async () => (await readManifest(context.paths))?.generationId ?? null,
|
|
670
|
+
scopeDir: context.paths.scopeDir,
|
|
671
|
+
});
|
|
672
|
+
} catch (error) {
|
|
673
|
+
throw targetError(error.message, error);
|
|
674
|
+
}
|
|
675
|
+
write(
|
|
676
|
+
io.stdout,
|
|
677
|
+
flags.json
|
|
678
|
+
? `${JSON.stringify({ ok: true, adopted: adopted.path })}\n`
|
|
679
|
+
: `Adopted ${adopted.path}.\n`
|
|
680
|
+
);
|
|
681
|
+
return finish(EXIT_CODES.clean);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
async function remove(context, flags, io) {
|
|
685
|
+
const target = context.selectedTargets[0];
|
|
686
|
+
const outputs = (context.manifest?.outputs ?? []).filter((output) => output.target === target);
|
|
687
|
+
printObject(flags, io, {
|
|
688
|
+
action: 'remove',
|
|
689
|
+
outputs: outputs.map((output) => output.path),
|
|
690
|
+
target,
|
|
691
|
+
});
|
|
692
|
+
if (outputs.length === 0) return finish(EXIT_CODES.clean);
|
|
693
|
+
if (!flags.yes && !(await confirmMutation(io, 'Remove these managed outputs?'))) {
|
|
694
|
+
return finish(EXIT_CODES.drift);
|
|
695
|
+
}
|
|
696
|
+
const layout = context.targetLayouts[target];
|
|
697
|
+
const generationId = `remove-${sha256(`${context.manifest.generationId}:${target}`).slice(7, 39)}`;
|
|
698
|
+
await runManagedTransaction({
|
|
699
|
+
allowRoots: layout.allowRoots,
|
|
700
|
+
commitManifest: async () => {
|
|
701
|
+
const manifest = createManifest({
|
|
702
|
+
capabilitySummary: context.manifest.capabilitySummary,
|
|
703
|
+
generationId,
|
|
704
|
+
outputs: context.manifest.outputs.filter((output) => output.target !== target),
|
|
705
|
+
rootHash: context.paths.rootHash,
|
|
706
|
+
sourceHashes: context.manifest.sourceHashes,
|
|
707
|
+
targetVersions: Object.fromEntries(
|
|
708
|
+
Object.entries(context.manifest.targetVersions ?? {}).filter(([name]) => name !== target)
|
|
709
|
+
),
|
|
710
|
+
});
|
|
711
|
+
if (context.manifest.lastValidation)
|
|
712
|
+
manifest.lastValidation = context.manifest.lastValidation;
|
|
713
|
+
await writeManifest(context.paths, manifest);
|
|
714
|
+
},
|
|
715
|
+
expectedGenerationId: context.manifest.generationId,
|
|
716
|
+
generationId,
|
|
717
|
+
isGenerationCommitted: async (candidate) =>
|
|
718
|
+
(await readManifest(context.paths))?.generationId === candidate,
|
|
719
|
+
outputs: await Promise.all(
|
|
720
|
+
outputs.map(async (output) =>
|
|
721
|
+
output.ownership === 'adopted'
|
|
722
|
+
? {
|
|
723
|
+
contents: await readAdoptedBackup(output, context.paths.backupDir),
|
|
724
|
+
expectedHash: output.hash,
|
|
725
|
+
operation: 'restore',
|
|
726
|
+
path: output.path,
|
|
727
|
+
restoreMode: output.originalMode ?? 0o600,
|
|
728
|
+
}
|
|
729
|
+
: { expectedHash: output.hash, operation: 'delete', path: output.path }
|
|
730
|
+
)
|
|
731
|
+
),
|
|
732
|
+
readGenerationId: async () => (await readManifest(context.paths))?.generationId ?? null,
|
|
733
|
+
scopeDir: context.paths.scopeDir,
|
|
734
|
+
});
|
|
735
|
+
return finish(EXIT_CODES.clean);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
async function readAdoptedBackup(output, backupDir) {
|
|
739
|
+
if (!output.backupPath || !output.backupHash) {
|
|
740
|
+
throw ownershipError(`adopted output has no valid backup metadata: ${output.path}`);
|
|
741
|
+
}
|
|
742
|
+
await assertAllowedPath(output.backupPath, [backupDir], { mustExist: true });
|
|
743
|
+
const bytes = await readFileNoFollow(output.backupPath);
|
|
744
|
+
if (sha256(bytes) !== output.backupHash) {
|
|
745
|
+
throw ownershipError(`adoption backup was modified: ${output.backupPath}`);
|
|
746
|
+
}
|
|
747
|
+
return bytes;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function assertDiagnosticOverrides(verb, _scope) {
|
|
751
|
+
const overrides = [
|
|
752
|
+
process.env.MAUDE_HARNESS_SKIP_EXECUTABLE_PROOF === '1' &&
|
|
753
|
+
'MAUDE_HARNESS_SKIP_EXECUTABLE_PROOF',
|
|
754
|
+
process.env.MAUDE_HARNESS_PROJECT_TARGET_ROOT && 'MAUDE_HARNESS_PROJECT_TARGET_ROOT',
|
|
755
|
+
].filter(Boolean);
|
|
756
|
+
if (overrides.length === 0 || ['check', 'diff', 'status'].includes(verb)) return;
|
|
757
|
+
const roots = [
|
|
758
|
+
process.env.HOME,
|
|
759
|
+
process.env.MAUDE_HARNESS_STATE_ROOT,
|
|
760
|
+
process.env.MAUDE_HARNESS_PROJECT_TARGET_ROOT,
|
|
761
|
+
].filter(Boolean);
|
|
762
|
+
const conformance =
|
|
763
|
+
process.env.MAUDE_HARNESS_CONFORMANCE === '1' &&
|
|
764
|
+
roots.every((path) => isWithin(realpathSync(tmpdir()), path));
|
|
765
|
+
if (!conformance) {
|
|
766
|
+
throw usageError(
|
|
767
|
+
`${overrides.join(', ')} may not be used by mutating harness verbs outside explicit temporary-root conformance mode`
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function isWithin(root, path) {
|
|
773
|
+
const nested = relative(resolve(root), resolve(path));
|
|
774
|
+
return (
|
|
775
|
+
nested === '' || (!nested.startsWith(`..${sep}`) && nested !== '..' && !isAbsolute(nested))
|
|
776
|
+
);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async function status(context, flags, io) {
|
|
780
|
+
const manifest = context.manifest;
|
|
781
|
+
const ownedTargets = [
|
|
782
|
+
...new Set((manifest?.outputs ?? []).map((output) => output.target)),
|
|
783
|
+
].sort();
|
|
784
|
+
const drift = [];
|
|
785
|
+
for (const output of manifest?.outputs ?? []) {
|
|
786
|
+
if ((await optionalHash(output.path)) !== output.hash) drift.push(output.path);
|
|
787
|
+
}
|
|
788
|
+
const value = {
|
|
789
|
+
generation: manifest?.generationId ?? null,
|
|
790
|
+
ownedTargets,
|
|
791
|
+
drift: drift.length > 0,
|
|
792
|
+
driftCount: drift.length,
|
|
793
|
+
counts: manifest?.capabilitySummary ?? {
|
|
794
|
+
degraded: 0,
|
|
795
|
+
native: 0,
|
|
796
|
+
securityFailures: 0,
|
|
797
|
+
unsupported: 0,
|
|
798
|
+
},
|
|
799
|
+
lastValidation: manifest?.lastValidation ?? null,
|
|
800
|
+
observedTargetVersions: manifest?.targetVersions ?? {},
|
|
801
|
+
rollbackAvailable: (manifest?.outputs ?? []).some(
|
|
802
|
+
(output) => output.ownership === 'adopted' || output.backupPath
|
|
803
|
+
),
|
|
804
|
+
};
|
|
805
|
+
printObject(flags, io, value);
|
|
806
|
+
return finish(value.drift ? EXIT_CODES.drift : EXIT_CODES.clean);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function printPreview(preview, projection, flags, io) {
|
|
810
|
+
const value = {
|
|
811
|
+
generation: projection.ir.generationHash,
|
|
812
|
+
drift: preview.drift,
|
|
813
|
+
changed: preview.changed,
|
|
814
|
+
conflicts: preview.conflicts,
|
|
815
|
+
counts: combineSummaries(projection.reports),
|
|
816
|
+
observedTargetVersions: projection.observedVersions,
|
|
817
|
+
capabilities: projection.reports.flatMap((report) =>
|
|
818
|
+
report.records.map(
|
|
819
|
+
({ acknowledged, enabled, reason, securityRelevant, sourceId, status, target }) => ({
|
|
820
|
+
acknowledged,
|
|
821
|
+
enabled,
|
|
822
|
+
reason,
|
|
823
|
+
securityRelevant,
|
|
824
|
+
sourceId,
|
|
825
|
+
status,
|
|
826
|
+
target,
|
|
827
|
+
})
|
|
828
|
+
)
|
|
829
|
+
),
|
|
830
|
+
};
|
|
831
|
+
printObject(flags, io, value);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function printObject(flags, io, value) {
|
|
835
|
+
if (flags.json) {
|
|
836
|
+
write(io.stdout, `${JSON.stringify(value)}\n`);
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
if (value.generation) write(io.stdout, `generation: ${value.generation}\n`);
|
|
840
|
+
if (value.target) write(io.stdout, `${value.action}: ${value.target}\n`);
|
|
841
|
+
for (const item of value.changed ?? []) write(io.stdout, ` ${item.action} ${item.path}\n`);
|
|
842
|
+
for (const conflict of value.conflicts ?? []) write(io.stdout, ` conflict ${conflict.path}\n`);
|
|
843
|
+
for (const capability of value.capabilities ?? []) {
|
|
844
|
+
if (!capability.enabled) {
|
|
845
|
+
write(
|
|
846
|
+
io.stdout,
|
|
847
|
+
` ${capability.target} ${capability.status} ${capability.sourceId}: ${capability.reason}\n`
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
for (const path of value.outputs ?? []) write(io.stdout, ` remove ${path}\n`);
|
|
852
|
+
if (value.drift !== undefined) write(io.stdout, `drift: ${value.drift ? 'yes' : 'no'}\n`);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
async function confirmMutation(io, question) {
|
|
856
|
+
if (typeof io.confirm === 'function') return Boolean(await io.confirm(question));
|
|
857
|
+
const interactive = io.isTTY ?? (io.stdin?.isTTY && io.stdout?.isTTY);
|
|
858
|
+
if (!interactive) return false;
|
|
859
|
+
const readline = createInterface({ input: io.stdin, output: io.stdout });
|
|
860
|
+
try {
|
|
861
|
+
const answer = await readline.question(`${question} [y/N] `);
|
|
862
|
+
return /^(?:y|yes)$/i.test(answer.trim());
|
|
863
|
+
} finally {
|
|
864
|
+
readline.close();
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
async function ensureTargetRoots(layouts) {
|
|
869
|
+
for (const root of new Set(Object.values(layouts).flatMap((layout) => layout.roots))) {
|
|
870
|
+
await mkdir(root, { mode: 0o700, recursive: true });
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
export function observeTargetVersion(target) {
|
|
875
|
+
if (process.env.MAUDE_HARNESS_SKIP_EXECUTABLE_PROOF === '1') return null;
|
|
876
|
+
const name = target === 'opencode' ? 'opencode' : 'codex';
|
|
877
|
+
const candidates = [
|
|
878
|
+
...(target === 'codex' && process.env.MAUDE_CODEX_REAL ? [process.env.MAUDE_CODEX_REAL] : []),
|
|
879
|
+
...String(process.env.PATH || '')
|
|
880
|
+
.split(delimiter)
|
|
881
|
+
.filter(Boolean)
|
|
882
|
+
.map((root) => join(root, name)),
|
|
883
|
+
];
|
|
884
|
+
for (const candidate of new Set(candidates)) {
|
|
885
|
+
let executable;
|
|
886
|
+
try {
|
|
887
|
+
executable = realpathSync(candidate);
|
|
888
|
+
if (target === 'codex' && isMaudeCodexWrapper(executable)) continue;
|
|
889
|
+
} catch (error) {
|
|
890
|
+
if (error.code === 'ENOENT') continue;
|
|
891
|
+
throw error;
|
|
892
|
+
}
|
|
893
|
+
const result = spawnSync(executable, ['--version'], { encoding: 'utf8', timeout: 10_000 });
|
|
894
|
+
if (result.error?.code === 'ENOENT') continue;
|
|
895
|
+
if (result.status !== 0) throw targetError(`${target} --version failed`);
|
|
896
|
+
return /\d+\.\d+\.\d+/.exec(`${result.stdout}\n${result.stderr}`)?.[0] ?? null;
|
|
897
|
+
}
|
|
898
|
+
return null;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
function isMaudeCodexWrapper(path) {
|
|
902
|
+
try {
|
|
903
|
+
if (statSync(path).size > 8192) return false;
|
|
904
|
+
return /\bexec\s+maude\s+codex\b/.test(readFileSync(path, 'utf8'));
|
|
905
|
+
} catch {
|
|
906
|
+
return false;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function targetEnvironment(context) {
|
|
911
|
+
return {
|
|
912
|
+
...process.env,
|
|
913
|
+
HOME: context.home,
|
|
914
|
+
XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME || join(context.home, '.config'),
|
|
915
|
+
CODEX_HOME: process.env.CODEX_HOME || join(context.home, '.codex'),
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
async function readJsonIfPresent(path) {
|
|
920
|
+
const text = await readTextIfPresent(path);
|
|
921
|
+
if (!text) return {};
|
|
922
|
+
try {
|
|
923
|
+
const value = JSON.parse(text);
|
|
924
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
925
|
+
throw new Error('must be an object');
|
|
926
|
+
return value;
|
|
927
|
+
} catch (error) {
|
|
928
|
+
throw targetError(`invalid target JSON ${path}: ${error.message}`, error);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
async function readTextIfPresent(path) {
|
|
933
|
+
try {
|
|
934
|
+
const info = await lstat(path);
|
|
935
|
+
if (info.isSymbolicLink() || !info.isFile())
|
|
936
|
+
throw targetError(`target is not a regular file: ${path}`);
|
|
937
|
+
return await readFile(path, 'utf8');
|
|
938
|
+
} catch (error) {
|
|
939
|
+
if (error.code === 'ENOENT') return '';
|
|
940
|
+
throw error;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
async function optionalHash(path) {
|
|
945
|
+
try {
|
|
946
|
+
return await hashFile(path);
|
|
947
|
+
} catch (error) {
|
|
948
|
+
if (error.code === 'ENOENT') return null;
|
|
949
|
+
throw error;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
async function assertDirectory(path, label) {
|
|
954
|
+
const info = await stat(path);
|
|
955
|
+
if (!info.isDirectory()) throw sourceError(`${label} is not a directory: ${path}`);
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
function hasSecurityFailure(reports) {
|
|
959
|
+
return reports.some((report) => report.summary.securityFailures > 0);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
function hasStrictFailure(reports) {
|
|
963
|
+
return reports.some(
|
|
964
|
+
(report) => report.summary.unsupported > 0 || report.summary.securityFailures > 0
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function combineSummaries(reports) {
|
|
969
|
+
return reports.reduce(
|
|
970
|
+
(summary, report) => {
|
|
971
|
+
for (const key of Object.keys(summary)) summary[key] += report.summary[key] ?? 0;
|
|
972
|
+
return summary;
|
|
973
|
+
},
|
|
974
|
+
{ degraded: 0, native: 0, securityFailures: 0, unsupported: 0 }
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function classifyError(error) {
|
|
979
|
+
if (error.exitCode) return error.exitCode;
|
|
980
|
+
if (
|
|
981
|
+
error instanceof TransactionInterruptedError ||
|
|
982
|
+
error instanceof RemovalInterruptedError ||
|
|
983
|
+
error instanceof LockError ||
|
|
984
|
+
/interrupted transaction|transaction generation is quarantined|rollback was incomplete/i.test(
|
|
985
|
+
error.message
|
|
986
|
+
)
|
|
987
|
+
) {
|
|
988
|
+
return EXIT_CODES.interrupted;
|
|
989
|
+
}
|
|
990
|
+
if (
|
|
991
|
+
/externally modified|collision|requires adoption|already managed|ownership/i.test(error.message)
|
|
992
|
+
) {
|
|
993
|
+
return EXIT_CODES.ownership;
|
|
994
|
+
}
|
|
995
|
+
return EXIT_CODES.source;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
function codedError(message, exitCode, cause) {
|
|
999
|
+
return Object.assign(new Error(message, cause ? { cause } : undefined), { exitCode });
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
const usageError = (message) => codedError(message, EXIT_CODES.usage);
|
|
1003
|
+
const strictError = (message) => codedError(message, EXIT_CODES.strict);
|
|
1004
|
+
const ownershipError = (message) => codedError(message, EXIT_CODES.ownership);
|
|
1005
|
+
const sourceError = (message, cause) => codedError(message, EXIT_CODES.source, cause);
|
|
1006
|
+
const targetError = (message, cause) => codedError(message, EXIT_CODES.target, cause);
|
|
1007
|
+
|
|
1008
|
+
function finish(exitCode) {
|
|
1009
|
+
process.exitCode = exitCode;
|
|
1010
|
+
return result(exitCode);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function result(exitCode) {
|
|
1014
|
+
return { exitCode, ok: exitCode === 0 };
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
function byPath(left, right) {
|
|
1018
|
+
return left.path.localeCompare(right.path);
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
function isStringArray(value) {
|
|
1022
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
function write(stream, value) {
|
|
1026
|
+
stream.write(value);
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
function defaultIo() {
|
|
1030
|
+
return { stdin: process.stdin, stdout: process.stdout, stderr: process.stderr };
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
function usage() {
|
|
1034
|
+
return `maude harness <verb> [options]
|
|
1035
|
+
|
|
1036
|
+
migrate --from claude --targets opencode,codex (--global | --project ROOT) [--yes] [--json]
|
|
1037
|
+
sync --targets opencode,codex (--global | --project ROOT) [--json]
|
|
1038
|
+
check --targets opencode,codex (--global | --project ROOT) [--strict] [--json]
|
|
1039
|
+
diff --targets opencode,codex (--global | --project ROOT) [--json]
|
|
1040
|
+
status (--global | --project ROOT) [--json]
|
|
1041
|
+
adopt --target opencode|codex --path PATH (--global | --project ROOT) [--yes] [--json]
|
|
1042
|
+
remove --target opencode|codex (--global | --project ROOT) [--yes] [--json]
|
|
1043
|
+
|
|
1044
|
+
Exactly one scope is mandatory. migrate and adopt preview in non-interactive use;
|
|
1045
|
+
--yes applies only a fully validated, conflict-free diff. diff is always read-only.
|
|
1046
|
+
|
|
1047
|
+
Exit codes: 0 clean/success, 1 drift/preview declined, 2 usage, 3 strict or
|
|
1048
|
+
security capability failure, 4 ownership conflict, 5 invalid source, 6 invalid
|
|
1049
|
+
target/config/version, 7 interrupted transaction or recovery required.
|
|
1050
|
+
`;
|
|
1051
|
+
}
|