humanish 0.107.0 → 0.108.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/ARCHITECTURE.md +22 -17
- package/CHANGELOG.md +14 -0
- package/CONTEXT.md +51 -44
- package/README.md +145 -172
- package/SECURITY.md +16 -26
- package/TELEMETRY.md +8 -6
- package/dist/actors/affordance.d.ts +4 -4
- package/dist/actors/affordance.js +12 -12
- package/dist/actors/agent-session.d.ts +2 -2
- package/dist/actors/agent-session.js +7 -7
- package/dist/actors/codex/app-server-actor-trace.d.ts +1 -1
- package/dist/actors/codex/app-server-actor-trace.js +1 -1
- package/dist/actors/codex/app-server-ui.js +1 -1
- package/dist/actors/codex/codex-admission.d.ts +91 -0
- package/dist/actors/codex/codex-admission.js +150 -0
- package/dist/actors/codex/restricted-executable.d.ts +4 -4
- package/dist/actors/codex/restricted-executable.js +8 -8
- package/dist/actors/codex/restricted-launch.d.ts +2 -1
- package/dist/actors/codex/restricted-launch.js +1 -1
- package/dist/actors/codex/restricted-participant.js +8 -6
- package/dist/actors/codex/restricted-policy.d.ts +2 -2
- package/dist/actors/codex/restricted-policy.js +2 -2
- package/dist/actors/codex/restricted-session.d.ts +2 -2
- package/dist/actors/codex/restricted-session.js +10 -5
- package/dist/actors/computer-use/actor.d.ts +16 -16
- package/dist/actors/computer-use/actor.js +2 -2
- package/dist/actors/computer-use/admission-limit.d.ts +1 -1
- package/dist/actors/computer-use/admission-limit.js +1 -1
- package/dist/actors/computer-use/loop/actions.d.ts +3 -3
- package/dist/actors/computer-use/loop/actions.js +10 -10
- package/dist/actors/computer-use/loop/backstop.d.ts +3 -3
- package/dist/actors/computer-use/loop/backstop.js +14 -12
- package/dist/actors/computer-use/loop/ending.d.ts +5 -5
- package/dist/actors/computer-use/loop/ending.js +7 -7
- package/dist/actors/computer-use/loop/observation.d.ts +2 -2
- package/dist/actors/computer-use/loop/observation.js +9 -9
- package/dist/actors/computer-use/loop/provider-call.d.ts +1 -1
- package/dist/actors/computer-use/loop/provider-call.js +1 -1
- package/dist/actors/computer-use/loop/session.js +3 -3
- package/dist/actors/computer-use/loop/spend.d.ts +1 -1
- package/dist/actors/computer-use/loop/spend.js +1 -1
- package/dist/actors/computer-use/loop/trace.js +9 -8
- package/dist/actors/computer-use/loop/types.d.ts +25 -23
- package/dist/actors/computer-use/loop/usage.js +2 -2
- package/dist/actors/computer-use/openai-context.d.ts +37 -0
- package/dist/actors/computer-use/openai-context.js +214 -0
- package/dist/actors/computer-use/openai-provider.d.ts +11 -8
- package/dist/actors/computer-use/openai-provider.js +45 -34
- package/dist/actors/computer-use/openai-requests.d.ts +36 -10
- package/dist/actors/computer-use/openai-requests.js +82 -15
- package/dist/actors/computer-use/openai-wire.d.ts +19 -7
- package/dist/actors/computer-use/openai-wire.js +36 -34
- package/dist/actors/contract.d.ts +78 -43
- package/dist/actors/contract.js +29 -23
- package/dist/actors/local-agent/claude-session.d.ts +1 -1
- package/dist/actors/local-agent/claude-session.js +10 -9
- package/dist/actors/local-agent/cli.d.ts +8 -6
- package/dist/actors/local-agent/cli.js +24 -28
- package/dist/actors/local-agent/readiness.d.ts +2 -2
- package/dist/actors/local-agent/readiness.js +4 -4
- package/dist/actors/reasoning-effort.js +6 -6
- package/dist/actors/registry.d.ts +10 -10
- package/dist/actors/registry.js +18 -17
- package/dist/actors/scripted-browser/actor.d.ts +8 -8
- package/dist/actors/scripted-browser/actor.js +16 -16
- package/dist/actors/scripted-browser/browser-command.js +1 -1
- package/dist/actors/scripted-browser/steps.js +1 -1
- package/dist/actors/scripted-browser/types.d.ts +1 -1
- package/dist/actors/stop-conditions.d.ts +1 -1
- package/dist/analysis/codex-config.d.ts +6 -2
- package/dist/analysis/codex-config.js +22 -14
- package/dist/analysis/service.js +5 -5
- package/dist/cli/commands/analyze.js +19 -16
- package/dist/cli/commands/codex.js +9 -9
- package/dist/cli/commands/comms.js +54 -33
- package/dist/cli/commands/feedback.js +20 -21
- package/dist/cli/commands/lab-format.d.ts +5 -4
- package/dist/cli/commands/lab-format.js +35 -23
- package/dist/cli/commands/lab-route-computer-use.js +6 -7
- package/dist/cli/commands/lab-route-preview.js +4 -3
- package/dist/cli/commands/lab-route-shared-world.js +3 -3
- package/dist/cli/commands/lab-run.js +10 -11
- package/dist/cli/commands/lab-scorer.d.ts +2 -2
- package/dist/cli/commands/lab-scorer.js +1 -1
- package/dist/cli/commands/migrate.d.ts +4 -0
- package/dist/cli/commands/migrate.js +65 -0
- package/dist/cli/commands/observe.js +147 -108
- package/dist/cli/commands/run-command.d.ts +10 -0
- package/dist/cli/commands/run-command.js +85 -0
- package/dist/cli/commands/runs.js +137 -138
- package/dist/cli/commands/setup.d.ts +2 -2
- package/dist/cli/commands/setup.js +26 -21
- package/dist/cli/commands/study.d.ts +3 -0
- package/dist/cli/commands/study.js +182 -0
- package/dist/cli/commands/tui.d.ts +4 -4
- package/dist/cli/commands/tui.js +21 -21
- package/dist/cli/commands/watch.js +60 -75
- package/dist/cli/deprecations.d.ts +18 -0
- package/dist/cli/deprecations.js +48 -0
- package/dist/cli/doctor.d.ts +15 -5
- package/dist/cli/doctor.js +103 -48
- package/dist/cli/first-run-path.d.ts +12 -10
- package/dist/cli/first-run-path.js +30 -28
- package/dist/cli/io.d.ts +41 -6
- package/dist/cli/io.js +97 -22
- package/dist/cli/observer-follow.d.ts +2 -2
- package/dist/cli/observer-follow.js +5 -6
- package/dist/cli/orientation.d.ts +6 -3
- package/dist/cli/orientation.js +43 -39
- package/dist/cli/product-sentence.d.ts +6 -0
- package/dist/cli/product-sentence.js +6 -0
- package/dist/cli/program.d.ts +6 -1
- package/dist/cli/program.js +109 -40
- package/dist/cli/telemetry.d.ts +15 -15
- package/dist/cli/telemetry.js +48 -40
- package/dist/cli.js +1 -1
- package/dist/comms/capture-surface.d.ts +13 -12
- package/dist/comms/capture-surface.js +23 -22
- package/dist/comms/catch-host.d.ts +7 -7
- package/dist/comms/catch-host.js +14 -14
- package/dist/comms/email-catch.d.ts +3 -3
- package/dist/comms/email-catch.js +8 -8
- package/dist/comms/external-evidence.js +2 -2
- package/dist/comms/extract.js +2 -2
- package/dist/comms/fake-inbox.d.ts +2 -2
- package/dist/comms/fake-inbox.js +2 -2
- package/dist/comms/receiving-render.d.ts +1 -1
- package/dist/comms/receiving-render.js +2 -2
- package/dist/comms/sandbox-catch-script.d.ts +6 -6
- package/dist/comms/sandbox-catch-script.js +6 -6
- package/dist/comms/sandbox-catch.d.ts +36 -37
- package/dist/comms/sandbox-catch.js +34 -35
- package/dist/comms/setup.js +42 -8
- package/dist/comms/thread-evidence.d.ts +1 -1
- package/dist/comms/thread-evidence.js +6 -6
- package/dist/comms/types.d.ts +6 -6
- package/dist/comms/types.js +3 -3
- package/dist/evidence/frame-signature.d.ts +1 -1
- package/dist/evidence/frame-signature.js +4 -4
- package/dist/evidence/redaction.d.ts +3 -3
- package/dist/evidence/redaction.js +4 -4
- package/dist/feedback/draft.js +36 -78
- package/dist/feedback/export-bundle.js +1 -1
- package/dist/feedback/export.d.ts +5 -1
- package/dist/feedback/export.js +7 -8
- package/dist/feedback/feedback.d.ts +2 -2
- package/dist/feedback/feedback.js +21 -3
- package/dist/guest/chromium-text.js +2 -2
- package/dist/guest/desktop-native.d.ts +1 -1
- package/dist/guest/runtime.js +1 -1
- package/dist/index.d.ts +12 -10
- package/dist/index.js +8 -6
- package/dist/keys/key-resolution.d.ts +5 -5
- package/dist/keys/key-resolution.js +23 -23
- package/dist/lab/adapter-extension.d.ts +7 -7
- package/dist/lab/adapter-extension.js +13 -36
- package/dist/lab/adapter-scorer-loader.d.ts +7 -7
- package/dist/lab/adapter-scorer-loader.js +32 -32
- package/dist/lab/composition-rules.js +42 -42
- package/dist/lab/config.js +65 -25
- package/dist/lab/deprecated-fields.d.ts +10 -0
- package/dist/lab/deprecated-fields.js +24 -0
- package/dist/lab/device-presets.d.ts +8 -8
- package/dist/lab/device-presets.js +11 -11
- package/dist/lab/discover.d.ts +13 -9
- package/dist/lab/discover.js +117 -57
- package/dist/lab/doctor.d.ts +10 -3
- package/dist/lab/doctor.js +47 -9
- package/dist/lab/init-templates.js +147 -147
- package/dist/lab/init.d.ts +1 -1
- package/dist/lab/init.js +41 -9
- package/dist/lab/keys.d.ts +2 -0
- package/dist/lab/keys.js +42 -8
- package/dist/lab/lab-deps.d.ts +7 -12
- package/dist/lab/parse/actors.js +9 -9
- package/dist/lab/parse/comms.js +5 -5
- package/dist/lab/parse/execution.js +13 -13
- package/dist/lab/parse/study-v3.d.ts +23 -0
- package/dist/lab/parse/study-v3.js +259 -0
- package/dist/lab/parse/subject-state.d.ts +7 -7
- package/dist/lab/parse/subject-state.js +16 -16
- package/dist/lab/parse/subject.js +28 -28
- package/dist/lab/parse/values.d.ts +1 -1
- package/dist/lab/parse/values.js +3 -3
- package/dist/lab/persona-resolve.d.ts +4 -4
- package/dist/lab/persona-resolve.js +4 -4
- package/dist/lab/plan-participants.d.ts +2 -2
- package/dist/lab/plan-types.d.ts +4 -4
- package/dist/lab/plan.d.ts +1 -7
- package/dist/lab/plan.js +10 -41
- package/dist/lab/preflight-probes.js +11 -11
- package/dist/lab/preflight-result.d.ts +2 -1
- package/dist/lab/preflight-result.js +5 -4
- package/dist/lab/preflight.d.ts +7 -5
- package/dist/lab/preflight.js +5 -5
- package/dist/lab/routing.d.ts +31 -25
- package/dist/lab/routing.js +60 -29
- package/dist/lab/run-lab-events.d.ts +0 -3
- package/dist/lab/run-lab-events.js +0 -6
- package/dist/lab/run-lab-homes.d.ts +22 -10
- package/dist/lab/run-lab-homes.js +5 -1
- package/dist/lab/run-lab-options.d.ts +1 -1
- package/dist/lab/run-lab-options.js +16 -13
- package/dist/lab/summary.d.ts +7 -6
- package/dist/lab/summary.js +10 -10
- package/dist/lab/tasks.d.ts +15 -15
- package/dist/lab/tasks.js +14 -14
- package/dist/lab/types.d.ts +101 -95
- package/dist/lab/types.js +2 -0
- package/dist/lab/validation.d.ts +13 -13
- package/dist/lab/validation.js +44 -42
- package/dist/lab/warnings.d.ts +10 -0
- package/dist/lab/warnings.js +61 -33
- package/dist/library-aliases.d.ts +56 -0
- package/dist/library-aliases.js +22 -0
- package/dist/observer/artifact.d.ts +1 -1
- package/dist/observer/artifact.js +6 -6
- package/dist/observer/data.d.ts +6 -6
- package/dist/observer/data.js +5 -4
- package/dist/observer/exposure.d.ts +1 -1
- package/dist/observer/exposure.js +11 -11
- package/dist/observer/http.js +1 -1
- package/dist/observer/library.d.ts +1 -1
- package/dist/observer/library.js +4 -2
- package/dist/observer/listen.js +5 -4
- package/dist/observer/render.d.ts +1 -1
- package/dist/observer/render.js +10 -5
- package/dist/observer/run-routes.d.ts +3 -3
- package/dist/observer/run-routes.js +1 -1
- package/dist/observer/serve.js +7 -3
- package/dist/observer/tunnel.js +1 -1
- package/dist/observer-app.html +4 -4
- package/dist/routes/computer-use/bundle-parts.d.ts +9 -9
- package/dist/routes/computer-use/bundle-parts.js +15 -15
- package/dist/routes/computer-use/bundle.js +3 -3
- package/dist/routes/computer-use/e2b-desktop/comms.d.ts +1 -1
- package/dist/routes/computer-use/e2b-desktop/comms.js +14 -14
- package/dist/routes/computer-use/e2b-desktop/desktop.js +4 -4
- package/dist/routes/computer-use/e2b-desktop/fidelity.d.ts +4 -3
- package/dist/routes/computer-use/e2b-desktop/fidelity.js +11 -10
- package/dist/routes/computer-use/e2b-desktop/prepare.js +7 -7
- package/dist/routes/computer-use/e2b-desktop/start.js +9 -8
- package/dist/routes/computer-use/e2b-desktop/teardown.js +5 -4
- package/dist/routes/computer-use/external-comms.d.ts +1 -1
- package/dist/routes/computer-use/external-comms.js +1 -1
- package/dist/routes/computer-use/fanout-bundle.js +18 -18
- package/dist/routes/computer-use/fanout-records.js +10 -11
- package/dist/routes/computer-use/live-flush.d.ts +1 -1
- package/dist/routes/computer-use/live-flush.js +6 -6
- package/dist/routes/computer-use/live-phase.d.ts +4 -2
- package/dist/routes/computer-use/live-phase.js +6 -6
- package/dist/routes/computer-use/local-vm.js +1 -1
- package/dist/routes/computer-use/participant-desktop.d.ts +4 -4
- package/dist/routes/computer-use/participant-desktop.js +4 -4
- package/dist/routes/computer-use/participant-execution.d.ts +4 -4
- package/dist/routes/computer-use/participant-execution.js +9 -9
- package/dist/routes/computer-use/participant-model.d.ts +6 -0
- package/dist/routes/computer-use/participant-model.js +31 -13
- package/dist/routes/computer-use/participant-prompt.d.ts +11 -11
- package/dist/routes/computer-use/participant-prompt.js +6 -6
- package/dist/routes/computer-use/participant-runs.d.ts +7 -7
- package/dist/routes/computer-use/participant-runs.js +13 -13
- package/dist/routes/computer-use/plan.js +22 -22
- package/dist/routes/computer-use/preflight.js +21 -21
- package/dist/routes/computer-use/rerun-selection.js +1 -1
- package/dist/routes/computer-use/result.js +11 -11
- package/dist/routes/computer-use/route.d.ts +2 -1
- package/dist/routes/computer-use/route.js +15 -14
- package/dist/routes/computer-use/self-report.d.ts +7 -7
- package/dist/routes/computer-use/self-report.js +31 -31
- package/dist/routes/computer-use/setup.d.ts +1 -1
- package/dist/routes/computer-use/setup.js +17 -18
- package/dist/routes/computer-use/single-bundle.d.ts +7 -7
- package/dist/routes/computer-use/single-bundle.js +28 -30
- package/dist/routes/computer-use/subject-projection.d.ts +1 -1
- package/dist/routes/computer-use/subject-projection.js +6 -6
- package/dist/routes/computer-use/types.d.ts +40 -42
- package/dist/routes/computer-use/types.js +10 -10
- package/dist/routes/preview.d.ts +8 -2
- package/dist/routes/preview.js +8 -4
- package/dist/routes/scripted/bundle.d.ts +2 -2
- package/dist/routes/scripted/bundle.js +13 -12
- package/dist/routes/scripted/plan.d.ts +1 -1
- package/dist/routes/scripted/plan.js +9 -9
- package/dist/routes/scripted/result.js +5 -5
- package/dist/routes/scripted/route.js +5 -6
- package/dist/routes/scripted/scenario.js +2 -2
- package/dist/routes/scripted/setup.js +7 -7
- package/dist/routes/scripted/subject.js +1 -1
- package/dist/routes/scripted/surface-records.js +3 -3
- package/dist/routes/scripted/types.d.ts +3 -5
- package/dist/routes/scripted/types.js +1 -1
- package/dist/routes/shared-world/bundle-records.js +9 -8
- package/dist/routes/shared-world/bundle.d.ts +5 -5
- package/dist/routes/shared-world/bundle.js +40 -38
- package/dist/routes/shared-world/checkpoints.d.ts +1 -1
- package/dist/routes/shared-world/checkpoints.js +1 -1
- package/dist/routes/shared-world/comms.d.ts +5 -5
- package/dist/routes/shared-world/comms.js +14 -14
- package/dist/routes/shared-world/external-public.d.ts +4 -3
- package/dist/routes/shared-world/external-public.js +16 -12
- package/dist/routes/shared-world/handoff.d.ts +7 -6
- package/dist/routes/shared-world/handoff.js +65 -57
- package/dist/routes/shared-world/lobby-code.d.ts +2 -2
- package/dist/routes/shared-world/lobby-code.js +9 -9
- package/dist/routes/shared-world/participant-specs.d.ts +7 -6
- package/dist/routes/shared-world/participant-specs.js +31 -24
- package/dist/routes/shared-world/plan.js +7 -7
- package/dist/routes/shared-world/provenance.d.ts +2 -2
- package/dist/routes/shared-world/provenance.js +2 -2
- package/dist/routes/shared-world/provisioned.js +14 -13
- package/dist/routes/shared-world/result.js +20 -21
- package/dist/routes/shared-world/route.js +23 -22
- package/dist/routes/shared-world/setup.d.ts +3 -3
- package/dist/routes/shared-world/setup.js +4 -4
- package/dist/routes/shared-world/types.d.ts +30 -28
- package/dist/routes/shared-world/types.js +4 -4
- package/dist/routes/terminal/adapter.d.ts +3 -3
- package/dist/routes/terminal/adapter.js +5 -5
- package/dist/routes/terminal/bundle.d.ts +3 -3
- package/dist/routes/terminal/bundle.js +22 -21
- package/dist/routes/terminal/credentials.d.ts +4 -4
- package/dist/routes/terminal/credentials.js +7 -7
- package/dist/routes/terminal/dry-run.js +4 -4
- package/dist/routes/terminal/encoding.js +2 -2
- package/dist/routes/terminal/ledger.d.ts +3 -3
- package/dist/routes/terminal/ledger.js +14 -14
- package/dist/routes/terminal/live-finish.js +3 -3
- package/dist/routes/terminal/live-sandbox.js +12 -13
- package/dist/routes/terminal/persona.js +1 -1
- package/dist/routes/terminal/plan.js +11 -11
- package/dist/routes/terminal/recorder.js +2 -2
- package/dist/routes/terminal/result.js +8 -8
- package/dist/routes/terminal/route.js +6 -7
- package/dist/routes/terminal/sandbox.d.ts +1 -1
- package/dist/routes/terminal/sandbox.js +1 -1
- package/dist/routes/terminal/session.js +8 -7
- package/dist/routes/terminal/token-usage.d.ts +1 -1
- package/dist/routes/terminal/token-usage.js +7 -8
- package/dist/routes/terminal/trace.d.ts +2 -2
- package/dist/routes/terminal/trace.js +2 -2
- package/dist/routes/terminal/types.d.ts +26 -28
- package/dist/routes/terminal/types.js +3 -3
- package/dist/run/bundle-shape.js +3 -3
- package/dist/run/bundle.d.ts +46 -46
- package/dist/run/bundle.js +2 -1
- package/dist/run/cost-summary.js +3 -3
- package/dist/run/costs.js +7 -1
- package/dist/run/detail.d.ts +6 -6
- package/dist/run/detail.js +4 -4
- package/dist/run/dry-run-inputs.js +1 -1
- package/dist/run/dry-run.js +18 -18
- package/dist/run/judge.d.ts +19 -13
- package/dist/run/judge.js +18 -8
- package/dist/run/paths.d.ts +1 -1
- package/dist/run/paths.js +1 -1
- package/dist/run/pricing.d.ts +11 -6
- package/dist/run/pricing.js +20 -13
- package/dist/run/projection.d.ts +14 -14
- package/dist/run/projection.js +15 -15
- package/dist/run/reclaim.js +2 -2
- package/dist/run/results.d.ts +5 -5
- package/dist/run/run-index.d.ts +8 -6
- package/dist/run/run-index.js +15 -16
- package/dist/run/run-not-found.d.ts +5 -0
- package/dist/run/run-not-found.js +15 -0
- package/dist/run/run.d.ts +5 -4
- package/dist/run/run.js +1 -1
- package/dist/run/sandbox-receipts.d.ts +2 -2
- package/dist/run/sandbox-receipts.js +3 -3
- package/dist/run/shared-world-evidence.d.ts +36 -35
- package/dist/run/shared-world-evidence.js +4 -4
- package/dist/run/shared-world-shape.d.ts +1 -1
- package/dist/run/shared-world-shape.js +6 -6
- package/dist/run/stats.d.ts +12 -6
- package/dist/run/stats.js +116 -58
- package/dist/run/status.d.ts +8 -29
- package/dist/run/status.js +5 -27
- package/dist/run/stored-runs.js +2 -1
- package/dist/run/stream-shape.js +1 -1
- package/dist/run/streams.d.ts +6 -6
- package/dist/run/study-provenance.d.ts +47 -0
- package/dist/run/study-provenance.js +63 -0
- package/dist/run/study-result.d.ts +16 -0
- package/dist/run/study-result.js +9 -0
- package/dist/run/text.d.ts +2 -0
- package/dist/run/text.js +4 -0
- package/dist/run-lab.d.ts +7 -7
- package/dist/run-lab.js +1 -3
- package/dist/study/convert.d.ts +33 -0
- package/dist/study/convert.js +432 -0
- package/dist/study/field-paths.d.ts +12 -0
- package/dist/study/field-paths.js +126 -0
- package/dist/study/files.d.ts +26 -0
- package/dist/study/files.js +43 -0
- package/dist/study/migrate.d.ts +48 -0
- package/dist/study/migrate.js +408 -0
- package/dist/study/owned-files.d.ts +38 -0
- package/dist/study/owned-files.js +182 -0
- package/dist/subject/clone.d.ts +1 -1
- package/dist/subject/desktop-cli.d.ts +1 -1
- package/dist/subject/desktop-cli.js +2 -2
- package/dist/subject/local-tree-archive.js +1 -1
- package/dist/subject/local-tree.d.ts +1 -1
- package/dist/subject/node-bootstrap.js +1 -1
- package/dist/subject/runtime.js +8 -9
- package/dist/subject/serve.d.ts +1 -1
- package/dist/subject/serve.js +3 -3
- package/dist/subject/state.d.ts +1 -1
- package/dist/subject/steps.d.ts +5 -5
- package/dist/subject/steps.js +4 -4
- package/dist/substrates/command-failure.d.ts +3 -3
- package/dist/substrates/command-failure.js +3 -3
- package/dist/substrates/detached.d.ts +6 -6
- package/dist/substrates/detached.js +7 -7
- package/dist/substrates/e2b/cdp-probe.d.ts +9 -9
- package/dist/substrates/e2b/cdp-probe.js +14 -17
- package/dist/substrates/e2b/desktop-browser.d.ts +2 -2
- package/dist/substrates/e2b/desktop-browser.js +8 -8
- package/dist/substrates/e2b/desktop-cdp.d.ts +9 -9
- package/dist/substrates/e2b/desktop-cdp.js +8 -8
- package/dist/substrates/e2b/desktop-executor.d.ts +1 -1
- package/dist/substrates/e2b/desktop-executor.js +1 -1
- package/dist/substrates/e2b/desktop-geometry.d.ts +3 -3
- package/dist/substrates/e2b/desktop-geometry.js +7 -7
- package/dist/substrates/e2b/desktop-media.d.ts +1 -1
- package/dist/substrates/e2b/desktop-media.js +1 -1
- package/dist/substrates/e2b/desktop-screenshot-cleanup.d.ts +3 -3
- package/dist/substrates/e2b/desktop-screenshot-cleanup.js +3 -3
- package/dist/substrates/e2b/sandbox.d.ts +1 -1
- package/dist/substrates/e2b/sandbox.js +1 -1
- package/dist/substrates/e2b/sdk.d.ts +11 -11
- package/dist/substrates/e2b/sdk.js +6 -6
- package/dist/tui/actions.d.ts +1 -1
- package/dist/tui/actions.js +13 -13
- package/dist/tui/contract.d.ts +16 -16
- package/dist/tui/contract.js +10 -10
- package/dist/tui/launch.d.ts +4 -4
- package/dist/tui/launch.js +18 -19
- package/dist/tui/project.js +5 -5
- package/dist/tui-app.js +82 -82
- package/dist/verify/actor.d.ts +8 -8
- package/dist/verify/actor.js +36 -36
- package/dist/verify/artifacts.d.ts +2 -2
- package/dist/verify/artifacts.js +3 -3
- package/dist/verify/context-growth.d.ts +9 -0
- package/dist/verify/context-growth.js +62 -0
- package/dist/verify/costs.d.ts +6 -6
- package/dist/verify/costs.js +8 -8
- package/dist/verify/shared-world-concurrent.d.ts +2 -2
- package/dist/verify/shared-world-concurrent.js +79 -76
- package/dist/verify/shared-world.d.ts +2 -2
- package/dist/verify/shared-world.js +16 -16
- package/dist/verify/subject.d.ts +4 -4
- package/dist/verify/subject.js +18 -18
- package/dist/verify/verify.js +92 -43
- package/docs/README.md +25 -32
- package/docs/architecture/actor-contract.md +56 -87
- package/docs/architecture/desktop-sessions.md +7 -7
- package/docs/architecture/external-public-shared-world.md +61 -59
- package/docs/architecture/github-feedback-loop.md +1 -4
- package/docs/architecture/guest-desktop.md +2 -2
- package/docs/architecture/observer.md +34 -34
- package/docs/architecture/restricted-codex-analysis.md +34 -28
- package/docs/architecture/serve.md +34 -37
- package/docs/architecture/state-driven-executor.md +55 -58
- package/docs/architecture/task-protocol-support.md +5 -5
- package/docs/architecture/terminal-product-route.md +67 -107
- package/docs/contracts/README.md +24 -0
- package/docs/contracts/adapter-admission.md +3 -3
- package/docs/contracts/adapter-fixtures.md +2 -5
- package/docs/contracts/core.md +1 -3
- package/docs/contracts/errors.md +62 -0
- package/docs/contracts/feedback.md +8 -9
- package/docs/contracts/policy.md +7 -12
- package/docs/contracts/run-bundle.md +45 -44
- package/docs/contracts/schemas.md +421 -362
- package/docs/contracts/study-analysis.md +3 -3
- package/docs/contracts/study-costs.md +11 -7
- package/docs/decisions/0001-run-bundle-is-the-source-of-truth.md +1 -1
- package/docs/decisions/0002-observer-is-one-self-contained-file.md +2 -2
- package/docs/decisions/0003-contained-paths-and-cleanup-authority.md +3 -3
- package/docs/principles/actor-fidelity.md +4 -4
- package/docs/principles/engineering.md +3 -3
- package/docs/principles/invariants-and-defaults.md +38 -38
- package/docs/principles/self-driving-harness.md +1 -5
- package/docs/principles/three-roles.md +1 -1
- package/docs/product/automatic-analysis.md +7 -7
- package/examples/participant/README.md +5 -5
- package/examples/participant/run.mjs +11 -10
- package/examples/scorer/README.md +3 -3
- package/examples/scorer/run.mjs +11 -12
- package/package.json +16 -17
- package/skills/humanish/SKILL.md +60 -58
- package/skills/humanish/agents/openai.yaml +1 -1
- package/AGENTS.md +0 -99
- package/CONTRIBUTING.md +0 -148
- package/dist/actors/codex/qualified-versions.d.ts +0 -72
- package/dist/actors/codex/qualified-versions.js +0 -132
- package/dist/cli/commands/lab.d.ts +0 -3
- package/dist/cli/commands/lab.js +0 -199
- package/dist/cli/renamed-options.d.ts +0 -11
- package/dist/cli/renamed-options.js +0 -18
- package/dist/observer/static.d.ts +0 -39
- package/dist/observer/static.js +0 -369
- package/docs/architecture/comms-inbox.md +0 -122
- package/docs/architecture/desktop-recording.md +0 -80
- package/docs/architecture/local-browser-runtime.md +0 -178
- package/docs/architecture/observer-review.md +0 -144
- package/docs/architecture/participant-media.md +0 -90
- package/docs/architecture/project-layout.md +0 -62
- package/docs/architecture/real-email-receiving.md +0 -136
- package/docs/assets/humanish-drawdb-hero.png +0 -0
- package/docs/assets/humanish-observer-hero.png +0 -0
- package/docs/goals/current.md +0 -207
- package/docs/product/lobby-trivia-3player-external-public.md +0 -77
- package/docs/product/open-source-install-experience.md +0 -240
- package/docs/ramp/README.md +0 -295
- package/docs/release/open-source-readiness.md +0 -222
- package/docs/release/public-readiness-standard.md +0 -210
- package/docs/roadmap/world-class-open-source-v0.md +0 -311
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/** One directory discovery reads study files from. */
|
|
2
|
+
export interface StudyDirectory {
|
|
3
|
+
readonly relativeDir: string;
|
|
4
|
+
/** `committed` is the project's own directory; `ignored` ones are gitignored overlays. */
|
|
5
|
+
readonly origin: "committed" | "ignored";
|
|
6
|
+
/** `labs` is the directory a `studies/` one replaces. */
|
|
7
|
+
readonly family: "studies" | "labs";
|
|
8
|
+
}
|
|
9
|
+
/** The six directories, in resolution order. */
|
|
10
|
+
export declare const STUDY_DIRECTORIES: readonly StudyDirectory[];
|
|
11
|
+
/** One path a study named by its stem can have. */
|
|
12
|
+
export interface StudyFileCandidate {
|
|
13
|
+
readonly relativePath: string;
|
|
14
|
+
readonly directory: StudyDirectory;
|
|
15
|
+
}
|
|
16
|
+
/** Every path a study with this stem can have, in resolution order. */
|
|
17
|
+
export declare function studyFileCandidates(stem: string): StudyFileCandidate[];
|
|
18
|
+
/** A file name's stem when it is a study file name, else undefined. */
|
|
19
|
+
export declare function studyFileStem(name: string): string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* The files that already use `stem` in any of the six directories, other than `except`, which is
|
|
22
|
+
* the file the writer is about to replace. `exists` reads through the writer's own contained
|
|
23
|
+
* filesystem checks. A writer skips or refuses when the result is not empty, so no write creates
|
|
24
|
+
* the collision discovery reports.
|
|
25
|
+
*/
|
|
26
|
+
export declare function otherStudyFiles(stem: string, exists: (relativePath: string) => Promise<boolean>, except?: string): Promise<string[]>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Where study files live. Discovery reads the three studies/ directories first, then the three labs/
|
|
2
|
+
// directories they replace. A name may live in one family or the other, never both, so every writer
|
|
3
|
+
// checks a name against all six directories before it writes.
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
/** The six directories, in resolution order. */
|
|
6
|
+
export const STUDY_DIRECTORIES = [
|
|
7
|
+
{ relativeDir: path.join("humanish", "studies"), origin: "committed", family: "studies" },
|
|
8
|
+
{ relativeDir: path.join(".humanish", "studies"), origin: "ignored", family: "studies" },
|
|
9
|
+
{ relativeDir: path.join(".humanish", "local", "studies"), origin: "ignored", family: "studies" },
|
|
10
|
+
{ relativeDir: path.join("humanish", "labs"), origin: "committed", family: "labs" },
|
|
11
|
+
{ relativeDir: path.join(".humanish", "labs"), origin: "ignored", family: "labs" },
|
|
12
|
+
{ relativeDir: path.join(".humanish", "local", "labs"), origin: "ignored", family: "labs" },
|
|
13
|
+
];
|
|
14
|
+
/** A study file's extensions, `.yaml` first. */
|
|
15
|
+
const STUDY_EXTENSIONS = [".yaml", ".yml"];
|
|
16
|
+
/** Every path a study with this stem can have, in resolution order. */
|
|
17
|
+
export function studyFileCandidates(stem) {
|
|
18
|
+
return STUDY_DIRECTORIES.flatMap((directory) => STUDY_EXTENSIONS.map((extension) => ({
|
|
19
|
+
relativePath: path.join(directory.relativeDir, `${stem}${extension}`),
|
|
20
|
+
directory,
|
|
21
|
+
})));
|
|
22
|
+
}
|
|
23
|
+
/** A file name's stem when it is a study file name, else undefined. */
|
|
24
|
+
export function studyFileStem(name) {
|
|
25
|
+
const extension = STUDY_EXTENSIONS.find((candidate) => name.endsWith(candidate));
|
|
26
|
+
return extension === undefined ? undefined : name.slice(0, -extension.length);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The files that already use `stem` in any of the six directories, other than `except`, which is
|
|
30
|
+
* the file the writer is about to replace. `exists` reads through the writer's own contained
|
|
31
|
+
* filesystem checks. A writer skips or refuses when the result is not empty, so no write creates
|
|
32
|
+
* the collision discovery reports.
|
|
33
|
+
*/
|
|
34
|
+
export async function otherStudyFiles(stem, exists, except) {
|
|
35
|
+
const found = [];
|
|
36
|
+
for (const candidate of studyFileCandidates(stem)) {
|
|
37
|
+
if (except !== undefined && path.normalize(candidate.relativePath) === path.normalize(except))
|
|
38
|
+
continue;
|
|
39
|
+
if (await exists(candidate.relativePath))
|
|
40
|
+
found.push(candidate.relativePath);
|
|
41
|
+
}
|
|
42
|
+
return found;
|
|
43
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { StudyRoute } from "../lab/parse/study-v3.js";
|
|
2
|
+
import { type DroppedKey, type MovedKey } from "./convert.js";
|
|
3
|
+
/** The schema of `humanish migrate --json`. */
|
|
4
|
+
declare const MIGRATE_SCHEMA = "humanish.migrate-result.v1";
|
|
5
|
+
/** One file migrate looked at. Paths are relative to the project. */
|
|
6
|
+
export interface MigrateFile {
|
|
7
|
+
readonly source: string;
|
|
8
|
+
/** `move` to a studies/ directory, `rewrite` in place, or `skip` a v3 file. */
|
|
9
|
+
readonly action: "move" | "rewrite" | "skip";
|
|
10
|
+
readonly destination?: string;
|
|
11
|
+
readonly route?: StudyRoute;
|
|
12
|
+
readonly moved: readonly MovedKey[];
|
|
13
|
+
readonly dropped: readonly DroppedKey[];
|
|
14
|
+
}
|
|
15
|
+
/** What migrate did, or would do with `--dry-run`. */
|
|
16
|
+
export interface MigrateResult {
|
|
17
|
+
readonly schema: typeof MIGRATE_SCHEMA;
|
|
18
|
+
readonly ok: boolean;
|
|
19
|
+
readonly cwd: string;
|
|
20
|
+
readonly dryRun: boolean;
|
|
21
|
+
readonly files: readonly MigrateFile[];
|
|
22
|
+
/** Files left behind after a clean-up step found them changed: sources, backups or temp files. */
|
|
23
|
+
readonly unresolved?: readonly string[];
|
|
24
|
+
readonly error?: {
|
|
25
|
+
readonly code: "HUMANISH_MIGRATE_REFUSED" | "HUMANISH_MIGRATE_FAILED";
|
|
26
|
+
readonly phase: "plan" | "stage" | "commit" | "clean-up";
|
|
27
|
+
readonly file?: string;
|
|
28
|
+
readonly message: string;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** How to run migrate. */
|
|
32
|
+
export interface MigrateOptions {
|
|
33
|
+
readonly cwd: string;
|
|
34
|
+
/** Files to convert. Without any, the six study directories are scanned. */
|
|
35
|
+
readonly paths?: readonly string[];
|
|
36
|
+
readonly dryRun?: boolean;
|
|
37
|
+
/** Called with the plan before anything is written, so a caller can print every destination. */
|
|
38
|
+
readonly onPlan?: (files: readonly MigrateFile[]) => void;
|
|
39
|
+
/** Replaces `fs.link`. Tests use it to stand in for a filesystem without hard links. */
|
|
40
|
+
readonly link?: (from: string, to: string) => Promise<void>;
|
|
41
|
+
/** Called between stage and commit, and between commit and clean-up. Tests change files there. */
|
|
42
|
+
readonly onPhase?: (finished: "stage" | "commit") => void;
|
|
43
|
+
/** Called for each file after its last check and before its commit changes anything. Tests race it. */
|
|
44
|
+
readonly onCommit?: (source: string) => Promise<void> | void;
|
|
45
|
+
}
|
|
46
|
+
/** Convert the selected v2 study files to v3. */
|
|
47
|
+
export declare function migrateStudies(options: MigrateOptions): Promise<MigrateResult>;
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
// `humanish migrate`: convert humanish.lab.v2 study files to humanish.study.v3. A file under a
|
|
2
|
+
// labs/ directory moves to the matching studies/ directory; any other file is rewritten in place.
|
|
3
|
+
// It runs in four phases, and a phase that fails leaves every source file where it was:
|
|
4
|
+
// 1. Plan: read, convert and plan-check every file, and record each source's identity. No writes.
|
|
5
|
+
// 2. Stage: write each output to a new temp file beside its destination. A failure removes them.
|
|
6
|
+
// 3. Commit: recheck each source. A rewritten source is first renamed to a random hidden name and
|
|
7
|
+
// checked there. Then the new file is linked into place, which fails if anything holds that
|
|
8
|
+
// name, so no file this run did not write is ever overwritten. A failure undoes the commits.
|
|
9
|
+
// 4. Clean up: delete each moved source and each rewrite's original, once checked (owned-files.ts).
|
|
10
|
+
// A file that changed since the plan is kept and listed; a rewrite's original as <name>.v2.bak.
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
12
|
+
import { link, lstat, readdir, readFile, realpath, rename, rmdir } from "node:fs/promises";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { parseDocument } from "yaml";
|
|
15
|
+
import { isNodeError } from "../run/type-guards.js";
|
|
16
|
+
import { convertStudyText, isStudyV3 } from "./convert.js";
|
|
17
|
+
import { otherStudyFiles, STUDY_DIRECTORIES, studyFileStem } from "./files.js";
|
|
18
|
+
import { assertDirectory, bindDirectory, exists, fileIdentity, NO_HARD_LINKS, removeChecked, removeOwned, restoreName, sameFile, sha256, writeOwned, } from "./owned-files.js";
|
|
19
|
+
/** The schema of `humanish migrate --json`. */
|
|
20
|
+
const MIGRATE_SCHEMA = "humanish.migrate-result.v1";
|
|
21
|
+
class MigrateError extends Error {
|
|
22
|
+
phase;
|
|
23
|
+
file;
|
|
24
|
+
refused;
|
|
25
|
+
unresolved;
|
|
26
|
+
constructor(phase, file, message, refused = false, unresolved = []) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.phase = phase;
|
|
29
|
+
this.file = file;
|
|
30
|
+
this.refused = refused;
|
|
31
|
+
this.unresolved = unresolved;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const LABS_TO_STUDIES = new Map(STUDY_DIRECTORIES.filter((directory) => directory.family === "labs").map((directory) => [
|
|
35
|
+
directory.relativeDir,
|
|
36
|
+
directory.relativeDir.replace(/labs$/, "studies"),
|
|
37
|
+
]));
|
|
38
|
+
async function scanTargets(root) {
|
|
39
|
+
const found = [];
|
|
40
|
+
for (const directory of STUDY_DIRECTORIES) {
|
|
41
|
+
const bound = await bindDirectory(root, directory.relativeDir);
|
|
42
|
+
if (bound === undefined)
|
|
43
|
+
continue;
|
|
44
|
+
for (const name of (await readdir(bound.physicalPath)).sort()) {
|
|
45
|
+
if (studyFileStem(name) !== undefined)
|
|
46
|
+
found.push(path.join(directory.relativeDir, name));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return found;
|
|
50
|
+
}
|
|
51
|
+
async function givenTargets(root, paths) {
|
|
52
|
+
const seen = new Map();
|
|
53
|
+
for (const given of paths) {
|
|
54
|
+
const absolute = path.resolve(root, given);
|
|
55
|
+
const relative = path.relative(root, absolute);
|
|
56
|
+
if (relative.startsWith("..") || path.isAbsolute(relative))
|
|
57
|
+
throw new MigrateError("plan", given, `${given} is outside the project.`, true);
|
|
58
|
+
let real;
|
|
59
|
+
try {
|
|
60
|
+
real = await realpath(absolute);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
throw new MigrateError("plan", given, `${given} does not exist.`, true);
|
|
64
|
+
}
|
|
65
|
+
if (!seen.has(real))
|
|
66
|
+
seen.set(real, relative);
|
|
67
|
+
}
|
|
68
|
+
return [...seen.values()];
|
|
69
|
+
}
|
|
70
|
+
async function planWrites(root, options) {
|
|
71
|
+
const targets = options.paths && options.paths.length > 0
|
|
72
|
+
? await givenTargets(root, options.paths)
|
|
73
|
+
: await scanTargets(root);
|
|
74
|
+
const files = [];
|
|
75
|
+
const writes = [];
|
|
76
|
+
for (const source of targets) {
|
|
77
|
+
const sourcePath = path.join(root, source);
|
|
78
|
+
let sourceDirectory;
|
|
79
|
+
let identity;
|
|
80
|
+
let mode;
|
|
81
|
+
let bytes;
|
|
82
|
+
try {
|
|
83
|
+
sourceDirectory = await bindDirectory(root, path.dirname(source));
|
|
84
|
+
if (sourceDirectory === undefined)
|
|
85
|
+
throw new Error("its directory is missing");
|
|
86
|
+
identity = await fileIdentity(sourcePath);
|
|
87
|
+
mode = Number((await lstat(sourcePath)).mode & 0o777);
|
|
88
|
+
bytes = await readFile(sourcePath);
|
|
89
|
+
if (sha256(bytes) !== identity.sha256)
|
|
90
|
+
throw new Error("it changed while it was read");
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
throw new MigrateError("plan", source, `${source}: ${error.message}.`, true);
|
|
94
|
+
}
|
|
95
|
+
const text = bytes.toString("utf8");
|
|
96
|
+
if (isStudyV3(parseDocument(text).toJS())) {
|
|
97
|
+
files.push({ source, action: "skip", moved: [], dropped: [] });
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const converted = convertStudyText(text, root);
|
|
101
|
+
if (!converted.ok)
|
|
102
|
+
throw new MigrateError("plan", source, `${source} was not converted: ${converted.reason}`, true);
|
|
103
|
+
const studiesDir = LABS_TO_STUDIES.get(path.dirname(source));
|
|
104
|
+
const destination = studiesDir === undefined ? source : path.join(studiesDir, path.basename(source));
|
|
105
|
+
const file = {
|
|
106
|
+
source,
|
|
107
|
+
action: studiesDir === undefined ? "rewrite" : "move",
|
|
108
|
+
destination,
|
|
109
|
+
route: converted.conversion.route,
|
|
110
|
+
moved: converted.conversion.moved,
|
|
111
|
+
dropped: converted.conversion.dropped,
|
|
112
|
+
};
|
|
113
|
+
files.push(file);
|
|
114
|
+
writes.push({
|
|
115
|
+
file,
|
|
116
|
+
sourcePath,
|
|
117
|
+
sourceDirectory: sourceDirectory,
|
|
118
|
+
sourceIdentity: identity,
|
|
119
|
+
sourceMode: mode,
|
|
120
|
+
destinationPath: path.join(root, destination),
|
|
121
|
+
asidePath: path.join(path.dirname(sourcePath), `.${path.basename(sourcePath)}.migrate-${randomUUID()}.orig`),
|
|
122
|
+
backupPath: `${sourcePath}.v2.bak`,
|
|
123
|
+
text: converted.conversion.text,
|
|
124
|
+
expected: sha256(Buffer.from(converted.conversion.text)),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
await refuseCollisions(root, writes);
|
|
128
|
+
return { files, writes };
|
|
129
|
+
}
|
|
130
|
+
// A destination two files share, or a name another file already uses, would leave discovery two
|
|
131
|
+
// files for one study. Files that move together are not collisions with each other.
|
|
132
|
+
async function refuseCollisions(root, writes) {
|
|
133
|
+
const sources = new Set(writes.map((write) => write.file.source));
|
|
134
|
+
const destinations = new Map();
|
|
135
|
+
for (const write of writes) {
|
|
136
|
+
const destination = write.file.destination;
|
|
137
|
+
const stem = studyFileStem(path.basename(destination));
|
|
138
|
+
// A file without a study extension has no stem, so only its exact path can collide.
|
|
139
|
+
const sameStem = stem === undefined
|
|
140
|
+
? undefined
|
|
141
|
+
: [...destinations.entries()].find(([other]) => path.dirname(other) === path.dirname(destination) &&
|
|
142
|
+
studyFileStem(path.basename(other)) === stem);
|
|
143
|
+
if (destinations.has(destination) || sameStem) {
|
|
144
|
+
const other = destinations.get(destination) ?? sameStem[1];
|
|
145
|
+
const clash = destinations.has(destination)
|
|
146
|
+
? `would both become ${destination}`
|
|
147
|
+
: `would share the name ${studyFileStem(path.basename(destination))} in ${path.dirname(destination)}`;
|
|
148
|
+
throw new MigrateError("plan", write.file.source, `${write.file.source} and ${other} ${clash}. Remove one, then migrate.`, true);
|
|
149
|
+
}
|
|
150
|
+
destinations.set(destination, write.file.source);
|
|
151
|
+
const others = await collisions(root, write, sources);
|
|
152
|
+
const blocking = write.file.action === "move" && (await exists(write.destinationPath))
|
|
153
|
+
? [write.file.destination, ...others]
|
|
154
|
+
: others;
|
|
155
|
+
if (blocking.length > 0) {
|
|
156
|
+
throw new MigrateError("plan", write.file.source, `${write.file.source} and ${blocking[0]} share the name ${stem ?? path.basename(destination)}. Keep one, then migrate.`, true);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async function stage(root, writes, created) {
|
|
161
|
+
for (const write of writes) {
|
|
162
|
+
try {
|
|
163
|
+
write.destinationDirectory = await bindDirectory(root, path.dirname(write.file.destination), created);
|
|
164
|
+
write.temp = {
|
|
165
|
+
path: path.join(path.dirname(write.destinationPath), `.${path.basename(write.destinationPath)}.migrate-${randomUUID()}.tmp`),
|
|
166
|
+
};
|
|
167
|
+
await writeOwned(write.temp, write.text, write.sourceMode);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
throw new MigrateError("stage", write.file.source, `${write.file.source}: ${error.message}.`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// After a failed stage or commit: remove each temp file. One this run did not write, or that
|
|
175
|
+
// changed, is kept and listed.
|
|
176
|
+
async function unstage(root, writes, created, linkFile) {
|
|
177
|
+
const kept = [];
|
|
178
|
+
for (const write of writes) {
|
|
179
|
+
if (write.temp === undefined)
|
|
180
|
+
continue;
|
|
181
|
+
const left = await removeOwned(write.temp, linkFile).catch(() => write.temp.path);
|
|
182
|
+
if (left !== undefined)
|
|
183
|
+
kept.push(path.relative(root, left));
|
|
184
|
+
}
|
|
185
|
+
for (const directory of [...created].reverse())
|
|
186
|
+
await rmdir(directory).catch(() => undefined);
|
|
187
|
+
return kept;
|
|
188
|
+
}
|
|
189
|
+
// Put the staged file at its destination without replacing anything there.
|
|
190
|
+
async function placeNew(root, write, linkFile) {
|
|
191
|
+
const taken = () => new Error(`${path.relative(root, write.destinationPath)} appeared while ${write.file.source} was migrated; both are kept`);
|
|
192
|
+
try {
|
|
193
|
+
await linkFile(write.temp.path, write.destinationPath);
|
|
194
|
+
// The temp file's name goes once every file is committed (commit), checked like any removal.
|
|
195
|
+
write.committed = write.temp.identity;
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
if (write.committed !== undefined)
|
|
200
|
+
throw error;
|
|
201
|
+
if (isNodeError(error) && error.code === "EEXIST")
|
|
202
|
+
throw taken();
|
|
203
|
+
if (!(isNodeError(error) && NO_HARD_LINKS.has(error.code ?? "")))
|
|
204
|
+
throw error;
|
|
205
|
+
const copy = { path: write.destinationPath };
|
|
206
|
+
try {
|
|
207
|
+
await writeOwned(copy, write.text, write.sourceMode);
|
|
208
|
+
}
|
|
209
|
+
catch (copyError) {
|
|
210
|
+
if (copy.inode === undefined && isNodeError(copyError) && copyError.code === "EEXIST")
|
|
211
|
+
throw taken();
|
|
212
|
+
// A copy this run created but could not finish or read back is removed, if it is unchanged.
|
|
213
|
+
if (copy.inode !== undefined)
|
|
214
|
+
await removeOwned(copy, linkFile).catch(() => undefined);
|
|
215
|
+
throw copyError;
|
|
216
|
+
}
|
|
217
|
+
write.committed = copy.identity;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Every name that would collide with this write's destination, other than the files this run moves
|
|
221
|
+
// or writes. Checked at plan time and again just before each commit.
|
|
222
|
+
async function collisions(root, write, ours) {
|
|
223
|
+
if (!STUDY_DIRECTORIES.some((directory) => directory.relativeDir === path.dirname(write.file.destination)))
|
|
224
|
+
return [];
|
|
225
|
+
// Discovery reads only .yaml and .yml files, so a file without that extension shares no name.
|
|
226
|
+
const stem = studyFileStem(path.basename(write.file.destination));
|
|
227
|
+
if (stem === undefined)
|
|
228
|
+
return [];
|
|
229
|
+
return (await otherStudyFiles(stem, (candidate) => exists(path.join(root, candidate)), write.file.source)).filter((other) => !ours.has(other));
|
|
230
|
+
}
|
|
231
|
+
// Move a rewrite's source to its aside name, then check that the file moved is the one the plan
|
|
232
|
+
// read. An edit saved in between is given its name back, never overwritten.
|
|
233
|
+
async function moveAside(write, linkFile) {
|
|
234
|
+
await rename(write.sourcePath, write.asidePath);
|
|
235
|
+
write.movedAside = true;
|
|
236
|
+
const moved = await fileIdentity(write.asidePath).catch(() => undefined);
|
|
237
|
+
if (moved !== undefined && sameFile(moved, write.sourceIdentity))
|
|
238
|
+
return;
|
|
239
|
+
if (await restoreName(write.asidePath, write.sourcePath, linkFile))
|
|
240
|
+
write.movedAside = false;
|
|
241
|
+
throw new Error(`${write.file.source} changed after it was planned`);
|
|
242
|
+
}
|
|
243
|
+
// A rewrite's source that cannot get its name back is kept as <name>.v2.bak, or under its aside
|
|
244
|
+
// name when that is taken too. Returns where it is.
|
|
245
|
+
async function keepOriginal(write, linkFile) {
|
|
246
|
+
const kept = (await restoreName(write.asidePath, write.backupPath, linkFile).catch(() => false))
|
|
247
|
+
? write.backupPath
|
|
248
|
+
: write.asidePath;
|
|
249
|
+
write.movedAside = false;
|
|
250
|
+
return kept;
|
|
251
|
+
}
|
|
252
|
+
async function commit(root, writes, linkFile, onCommit) {
|
|
253
|
+
const ours = new Set(writes.flatMap((write) => [write.file.source, write.file.destination]));
|
|
254
|
+
try {
|
|
255
|
+
for (const write of writes) {
|
|
256
|
+
await assertDirectory(write.sourceDirectory, root);
|
|
257
|
+
await assertDirectory(write.destinationDirectory, root);
|
|
258
|
+
if (!sameFile(await fileIdentity(write.temp.path, true), write.temp.identity))
|
|
259
|
+
throw new Error(`the staged copy of ${write.file.source} changed`);
|
|
260
|
+
const other = (await collisions(root, write, ours))[0];
|
|
261
|
+
if (other !== undefined)
|
|
262
|
+
throw new Error(`${other} appeared with the name of ${write.file.destination}`);
|
|
263
|
+
if (!sameFile(await fileIdentity(write.sourcePath), write.sourceIdentity))
|
|
264
|
+
throw new Error(`${write.file.source} changed after it was planned`);
|
|
265
|
+
await onCommit?.(write.file.source);
|
|
266
|
+
if (write.file.action === "rewrite")
|
|
267
|
+
await moveAside(write, linkFile);
|
|
268
|
+
await placeNew(root, write, linkFile);
|
|
269
|
+
}
|
|
270
|
+
for (const write of writes) {
|
|
271
|
+
if (write.temp === undefined)
|
|
272
|
+
continue;
|
|
273
|
+
write.leftover = await removeOwned(write.temp, linkFile).catch(() => write.temp.path);
|
|
274
|
+
write.temp = undefined;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
const unrestored = await undoCommits(root, writes, linkFile);
|
|
279
|
+
throw new MigrateError("commit", undefined, unrestored.length === 0
|
|
280
|
+
? `${error.message}. Every source is where it was.`
|
|
281
|
+
: `${error.message}. These files could not be put back: ${unrestored.join(", ")}.`, false, unrestored);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Undo each commit, last first: remove each new file that is still the one this run wrote, and give
|
|
285
|
+
// each rewritten source its name back. A name another file now holds is left alone, and the source
|
|
286
|
+
// is kept beside it. One failure does not stop the rest. Returns the paths it could not put back.
|
|
287
|
+
async function undoCommits(root, writes, linkFile) {
|
|
288
|
+
const unrestored = [];
|
|
289
|
+
const keep = (file) => unrestored.push(path.relative(root, file));
|
|
290
|
+
for (const write of [...writes].reverse()) {
|
|
291
|
+
try {
|
|
292
|
+
const committed = write.committed;
|
|
293
|
+
if (committed !== undefined) {
|
|
294
|
+
const kept = await removeChecked(write.destinationPath, (current) => sameFile(current, committed), linkFile);
|
|
295
|
+
write.committed = undefined;
|
|
296
|
+
if (kept !== undefined) {
|
|
297
|
+
keep(kept);
|
|
298
|
+
if (write.movedAside === true)
|
|
299
|
+
keep(await keepOriginal(write, linkFile));
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
if (write.movedAside !== true)
|
|
304
|
+
continue;
|
|
305
|
+
if (await restoreName(write.asidePath, write.sourcePath, linkFile))
|
|
306
|
+
write.movedAside = false;
|
|
307
|
+
else
|
|
308
|
+
keep(await keepOriginal(write, linkFile));
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
keep(write.committed !== undefined
|
|
312
|
+
? write.destinationPath
|
|
313
|
+
: write.movedAside === true
|
|
314
|
+
? write.asidePath
|
|
315
|
+
: write.sourcePath);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return unrestored;
|
|
319
|
+
}
|
|
320
|
+
// Delete each moved source and each rewrite's original, but only once the v3 file is in place as
|
|
321
|
+
// written, and only if the old file is still the one the plan read (removeChecked). Anything kept
|
|
322
|
+
// is listed; a rewrite's original is kept as <name>.v2.bak.
|
|
323
|
+
async function cleanUp(root, writes, linkFile) {
|
|
324
|
+
const unresolved = [];
|
|
325
|
+
for (const write of writes) {
|
|
326
|
+
if (write.leftover !== undefined)
|
|
327
|
+
unresolved.push(path.relative(root, write.leftover));
|
|
328
|
+
const old = write.file.action === "move" ? write.sourcePath : write.asidePath;
|
|
329
|
+
let kept = old;
|
|
330
|
+
try {
|
|
331
|
+
const placed = await fileIdentity(write.destinationPath, true);
|
|
332
|
+
if (!sameFile(placed, write.committed))
|
|
333
|
+
throw new Error("the new file changed");
|
|
334
|
+
await assertDirectory(write.sourceDirectory, root);
|
|
335
|
+
// A rename keeps the inode, so a rewrite's original still has its source's identity.
|
|
336
|
+
kept = await removeChecked(old, (current) => sameFile(current, write.sourceIdentity), linkFile);
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
kept = old;
|
|
340
|
+
}
|
|
341
|
+
if (kept === undefined)
|
|
342
|
+
continue;
|
|
343
|
+
if (kept === write.asidePath)
|
|
344
|
+
kept = await keepOriginal(write, linkFile);
|
|
345
|
+
unresolved.push(path.relative(root, kept));
|
|
346
|
+
}
|
|
347
|
+
return unresolved;
|
|
348
|
+
}
|
|
349
|
+
/** Convert the selected v2 study files to v3. */
|
|
350
|
+
export async function migrateStudies(options) {
|
|
351
|
+
const root = await realpath(path.resolve(options.cwd)).catch(() => path.resolve(options.cwd));
|
|
352
|
+
const base = { schema: MIGRATE_SCHEMA, cwd: root, dryRun: options.dryRun === true };
|
|
353
|
+
let files = [];
|
|
354
|
+
const created = [];
|
|
355
|
+
let writes = [];
|
|
356
|
+
const linkFile = options.link ?? link;
|
|
357
|
+
try {
|
|
358
|
+
({ files, writes } = await planWrites(root, options));
|
|
359
|
+
options.onPlan?.(files);
|
|
360
|
+
if (options.dryRun === true || writes.length === 0)
|
|
361
|
+
return { ...base, ok: true, files };
|
|
362
|
+
try {
|
|
363
|
+
await stage(root, writes, created);
|
|
364
|
+
options.onPhase?.("stage");
|
|
365
|
+
await commit(root, writes, linkFile, options.onCommit);
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
const kept = await unstage(root, writes, created, linkFile);
|
|
369
|
+
if (kept.length > 0 && error instanceof MigrateError) {
|
|
370
|
+
throw new MigrateError(error.phase, error.file, `${error.message} Kept for you to check: ${kept.join(", ")}.`, error.refused, [...error.unresolved, ...kept]);
|
|
371
|
+
}
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
374
|
+
options.onPhase?.("commit");
|
|
375
|
+
const unresolved = await cleanUp(root, writes, linkFile);
|
|
376
|
+
if (unresolved.length > 0) {
|
|
377
|
+
return {
|
|
378
|
+
...base,
|
|
379
|
+
ok: false,
|
|
380
|
+
files,
|
|
381
|
+
unresolved,
|
|
382
|
+
error: {
|
|
383
|
+
code: "HUMANISH_MIGRATE_FAILED",
|
|
384
|
+
phase: "clean-up",
|
|
385
|
+
message: `Every study was written, but these files changed since the plan and were kept: ${unresolved.join(", ")}. Check them, then delete them.`,
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
return { ...base, ok: true, files };
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
const failure = error instanceof MigrateError
|
|
393
|
+
? error
|
|
394
|
+
: new MigrateError("plan", undefined, error.message);
|
|
395
|
+
return {
|
|
396
|
+
...base,
|
|
397
|
+
ok: false,
|
|
398
|
+
files,
|
|
399
|
+
...(failure.unresolved.length === 0 ? {} : { unresolved: failure.unresolved }),
|
|
400
|
+
error: {
|
|
401
|
+
code: failure.refused ? "HUMANISH_MIGRATE_REFUSED" : "HUMANISH_MIGRATE_FAILED",
|
|
402
|
+
phase: failure.phase,
|
|
403
|
+
...(failure.file === undefined ? {} : { file: failure.file }),
|
|
404
|
+
message: failure.message,
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export interface FileIdentity {
|
|
2
|
+
readonly dev: bigint;
|
|
3
|
+
readonly ino: bigint;
|
|
4
|
+
readonly size: bigint;
|
|
5
|
+
readonly sha256: string;
|
|
6
|
+
}
|
|
7
|
+
export interface DirectoryIdentity {
|
|
8
|
+
readonly physicalPath: string;
|
|
9
|
+
readonly dev: bigint;
|
|
10
|
+
readonly ino: bigint;
|
|
11
|
+
}
|
|
12
|
+
interface Inode {
|
|
13
|
+
readonly dev: bigint;
|
|
14
|
+
readonly ino: bigint;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A file this run creates. `inode` is set the moment the exclusive create succeeds, so a name that
|
|
18
|
+
* was already taken is never treated as this run's. `identity` is set once the bytes are written
|
|
19
|
+
* and read back through the same descriptor.
|
|
20
|
+
*/
|
|
21
|
+
export interface OwnedFile {
|
|
22
|
+
readonly path: string;
|
|
23
|
+
inode?: Inode | undefined;
|
|
24
|
+
identity?: FileIdentity | undefined;
|
|
25
|
+
}
|
|
26
|
+
export type LinkFile = (from: string, to: string) => Promise<void>;
|
|
27
|
+
export declare function sha256(bytes: Buffer): string;
|
|
28
|
+
export declare function fileIdentity(absolutePath: string, ours?: boolean): Promise<FileIdentity>;
|
|
29
|
+
export declare function sameFile(left: FileIdentity, right: FileIdentity): boolean;
|
|
30
|
+
export declare function bindDirectory(root: string, relativeDir: string, created?: string[]): Promise<DirectoryIdentity | undefined>;
|
|
31
|
+
export declare function assertDirectory(directory: DirectoryIdentity, root: string): Promise<void>;
|
|
32
|
+
export declare function exists(absolutePath: string): Promise<boolean>;
|
|
33
|
+
export declare function writeOwned(owned: OwnedFile, bytes: Buffer | string, mode: number): Promise<void>;
|
|
34
|
+
export declare function removeChecked(file: string, matches: (current: FileIdentity) => boolean, linkFile: LinkFile): Promise<string | undefined>;
|
|
35
|
+
export declare function removeOwned(owned: OwnedFile, linkFile: LinkFile): Promise<string | undefined>;
|
|
36
|
+
export declare const NO_HARD_LINKS: Set<string>;
|
|
37
|
+
export declare function restoreName(from: string, to: string, linkFile: LinkFile): Promise<boolean>;
|
|
38
|
+
export {};
|