gentle-pi 4.0.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -18
- package/assets/agents/gentle-ai-explore.md +11 -4
- package/assets/agents/gentle-ai-verify.md +23 -4
- package/assets/agents/gentle-ai-worker.md +6 -6
- package/assets/install-wizard/index.html +47 -0
- package/assets/install-wizard/wizard.css +1279 -0
- package/assets/install-wizard/wizard.js +951 -0
- package/assets/orchestrator-delegation.md +70 -163
- package/assets/orchestrator-memory.md +11 -4
- package/assets/orchestrator-prompts.md +48 -0
- package/assets/orchestrator-tracking.md +53 -0
- package/assets/orchestrator-verification.md +28 -0
- package/assets/orchestrator-writer.md +38 -0
- package/assets/orchestrator.md +29 -25
- package/assets/sounds/LICENSE.md +52 -0
- package/assets/sounds/attention.wav +0 -0
- package/assets/sounds/error.wav +0 -0
- package/assets/sounds/success.wav +0 -0
- package/bin/engram-guard +9 -0
- package/bin/gentle-shell-install.mjs +302 -0
- package/bin/gentle-shell.mjs +708 -22
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +7 -7
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/manifest.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/orchestration/pi.md +16 -4
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-capabilities.baseline.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-roles.baseline.json +1 -1
- package/docs/auto-permission-answers.md +25 -0
- package/docs/auto-permission-evidence.md +39 -0
- package/docs/bundled-install.md +399 -0
- package/docs/delegated-verification.md +3 -5
- package/docs/gentle-agents-activity.md +513 -0
- package/docs/gentle-shell-uninstall.md +136 -0
- package/docs/gentle-shell.md +90 -33
- package/docs/install-wizard.md +2040 -0
- package/docs/native-authority-architecture.md +1 -1
- package/docs/permission-modes.md +127 -0
- package/docs/prompt-history.md +1 -1
- package/docs/readme-reference.md +310 -47
- package/docs/render-cache-performance.md +28 -0
- package/docs/session-profile-format.md +39 -0
- package/docs/sound-notifications-proposal.md +191 -0
- package/docs/sound-notifications.md +114 -0
- package/docs/yolo-mode.md +11 -10
- package/extensions/ask-user-choice.ts +4 -0
- package/extensions/ask-user-question.ts +6 -1
- package/extensions/child-context.ts +11 -2
- package/extensions/child-safety.ts +4 -2
- package/extensions/codegraph-tools.ts +18 -5
- package/extensions/gentle-agents.ts +718 -87
- package/extensions/gentle-ai.ts +991 -263
- package/extensions/gentle-herdr-activity.ts +81 -0
- package/extensions/gentle-notifications.ts +104 -0
- package/extensions/gentle-shell.ts +271 -80
- package/extensions/gentle-todo.ts +123 -18
- package/extensions/history/index.ts +11 -6
- package/extensions/pi-pretty.ts +39 -3
- package/extensions/quiet-tools.ts +91 -21
- package/extensions/skill-registry.ts +175 -46
- package/extensions/startup-banner.ts +11 -2
- package/lib/agent-home.ts +10 -1
- package/lib/agent-model-resolution.ts +80 -0
- package/lib/agent-profiles.ts +13 -3
- package/lib/agents-completion-delivery.ts +11 -3
- package/lib/agents-messaging.ts +1 -1
- package/lib/agents-protocol.ts +40 -1
- package/lib/agents-runner.ts +90 -15
- package/lib/auto-permission-answers.ts +133 -0
- package/lib/auto-permission-classifier.ts +459 -0
- package/lib/auto-permission-evidence.ts +88 -0
- package/lib/auto-permission-memory.ts +69 -0
- package/lib/auto-permission-runtime.ts +484 -0
- package/lib/auto-permission-tool-context.ts +61 -0
- package/lib/background-jobs-tools.ts +202 -0
- package/lib/background-jobs.ts +253 -0
- package/lib/background-monitor.ts +154 -0
- package/lib/bounded-writer-admission.ts +83 -10
- package/lib/builtin-codemode-optout.ts +166 -0
- package/lib/card-content-policy.ts +85 -0
- package/lib/child-package-injection.ts +62 -0
- package/lib/codemode-renderer.ts +14 -4
- package/lib/command-palette-catalog.ts +3 -2
- package/lib/context-threshold.ts +26 -0
- package/lib/destructive-command-guard.ts +104 -28
- package/lib/gentle-ai-binary.ts +109 -4
- package/lib/gentle-shell-bundled.ts +535 -0
- package/lib/gentle-shell-launcher.ts +75 -24
- package/lib/gentle-shell-startup-flow.ts +54 -0
- package/lib/gentle-shell-startup-update.ts +92 -0
- package/lib/gentle-shell-uninstall.ts +840 -0
- package/lib/gentle-shell-update-panel.ts +231 -0
- package/lib/herdr-activity.ts +183 -0
- package/lib/inprocess-reviewer.ts +3 -0
- package/lib/jobs-sidebar-state.ts +20 -0
- package/lib/jobs-view.ts +161 -0
- package/lib/model-price-ratio.ts +83 -0
- package/lib/model-routing-authority.ts +6 -1
- package/lib/nan-provider.ts +33 -11
- package/lib/native-review-cli.ts +194 -73
- package/lib/notification-audio-native.ts +162 -0
- package/lib/notification-audio-windows.ts +399 -0
- package/lib/notification-audio.ts +332 -0
- package/lib/notification-customize.ts +241 -0
- package/lib/notification-events.ts +113 -0
- package/lib/notification-policy.ts +148 -0
- package/lib/notification-pulse-client.ts +367 -0
- package/lib/notification-pulse-protocol.ts +305 -0
- package/lib/notification-pulse-stream.ts +213 -0
- package/lib/notification-pulse-worker.ts +66 -0
- package/lib/notification-scheduler.ts +173 -0
- package/lib/notification-service.ts +202 -0
- package/lib/notification-ui.ts +87 -0
- package/lib/odd-phase.ts +1 -1
- package/lib/orchestrator-alias.ts +20 -0
- package/lib/orchestrator-catalog.ts +79 -0
- package/lib/orchestrator-consultation.ts +61 -0
- package/lib/orchestrator-discovery.ts +49 -0
- package/lib/orchestrator-helper-consent.ts +122 -0
- package/lib/orchestrator-helper.ts +167 -0
- package/lib/orchestrator-message-card.ts +105 -0
- package/lib/orchestrator-presence.ts +115 -14
- package/lib/orchestrator-scope.ts +80 -0
- package/lib/orchestrator-state.ts +107 -0
- package/lib/orchestrator-tool-card.ts +280 -0
- package/lib/orchestrator-work-search.ts +218 -0
- package/lib/orchestrator-work.ts +79 -0
- package/lib/permission-mode-policy.ts +59 -0
- package/lib/review-candidate-view-owner.ts +8 -6
- package/lib/review-candidate-view.ts +3 -2
- package/lib/review-compact-contract.ts +2 -2
- package/lib/review-host-relay.ts +7 -2
- package/lib/review-integration-v2.ts +100 -22
- package/lib/review-publication-gate.ts +1 -1
- package/lib/review-risk-assessment.ts +77 -51
- package/lib/review-sidebar-state.ts +2 -1
- package/lib/review-transaction.ts +72 -3
- package/lib/runtime-metrics-children.ts +1 -1
- package/lib/runtime-metrics-native.ts +3 -2
- package/lib/selection-engine.ts +22 -7
- package/lib/session-change-capture.ts +5 -4
- package/lib/session-changes.ts +27 -22
- package/lib/session-profile-binding.ts +83 -0
- package/lib/session-profile-freeze.ts +261 -0
- package/lib/session-profile-persistence.ts +187 -0
- package/lib/session-worktree-registry.ts +74 -8
- package/lib/shell-bar.ts +41 -11
- package/lib/shell-changes-view.ts +40 -13
- package/lib/shell-changes.ts +88 -71
- package/lib/shell-prompt.ts +6 -4
- package/lib/shell-sidebar-layout.ts +5 -1
- package/lib/shell-todo.ts +81 -26
- package/lib/shell-usage.ts +4 -3
- package/lib/status-timing-diagnostics.ts +201 -0
- package/lib/telemetry-trigger.ts +3 -1
- package/lib/vim-editor-adapter.ts +27 -5
- package/lib/visual-customization-policy.ts +20 -9
- package/lib/visual-customize-view.ts +171 -20
- package/lib/writer-surfaces.ts +162 -0
- package/lib/yolo-session-policy.ts +198 -121
- package/package.json +5 -5
- package/runtime/child-package-injection.mjs +63 -0
- package/runtime/gentle-ai-binary.mjs +109 -4
- package/runtime/gentle-shell-bundled.mjs +536 -0
- package/runtime/gentle-shell-launcher.mjs +74 -23
- package/runtime/gentle-shell-startup-flow.mjs +55 -0
- package/runtime/gentle-shell-startup-update.mjs +93 -0
- package/runtime/gentle-shell-uninstall.mjs +841 -0
- package/runtime/gentle-shell-update-panel.mjs +232 -0
- package/runtime/native-review-cli.mjs +191 -70
- package/runtime/review-integration-v2.mjs +100 -22
- package/runtime/review-risk-assessment.mjs +77 -51
- package/runtime/status-timing-diagnostics.mjs +202 -0
- package/runtime/telemetry-trigger.mjs +3 -1
- package/scripts/bootstrap.cmd +255 -0
- package/scripts/bootstrap.sh +168 -0
- package/scripts/build-distribution.mjs +157 -0
- package/scripts/build-installer-bundles.mjs +136 -0
- package/scripts/build-runtime-modules.mjs +7 -0
- package/scripts/bundled-install.mjs +1308 -0
- package/scripts/bundled-wizard.mjs +399 -0
- package/scripts/check-types.mjs +4 -6
- package/scripts/gentle-ai-installer.mjs +79 -32
- package/scripts/install-gentle-ai.mjs +48 -1
- package/scripts/install-tui-mode-setting.mjs +10 -1
- package/scripts/install-wizard-preview.mjs +154 -0
- package/scripts/installer-downloads.mjs +648 -0
- package/scripts/installer-preflight.mjs +387 -0
- package/scripts/installer-probes.mjs +564 -0
- package/scripts/installer-runner.mjs +1102 -0
- package/scripts/installer-server.mjs +881 -0
- package/scripts/installer-windows-artifacts.json +15 -0
- package/scripts/installer-windows.mjs +752 -0
- package/scripts/main-channel.mjs +452 -0
- package/scripts/npm-pack-result.mjs +31 -0
- package/scripts/pi-update.mjs +223 -0
- package/scripts/render-cache-performance-worker.mjs +205 -0
- package/scripts/render-cache-performance.mjs +115 -0
- package/scripts/run-test-suite.mjs +22 -8
- package/scripts/test-installer-acceptance.sh +161 -0
- package/scripts/test-packed-runner.mjs +23 -5
- package/scripts/types-baseline.json +2 -92
- package/scripts/verify-package-files.mjs +82 -18
- package/skills/gentle-ai/SKILL.md +12 -11
- package/tests/agent-home.test.ts +56 -3
- package/tests/agent-model-resolution.test.ts +113 -0
- package/tests/agent-profiles.test.ts +12 -0
- package/tests/agent-risk-escalation.test.ts +123 -0
- package/tests/agents-completion-delivery.test.ts +17 -0
- package/tests/agents-protocol.test.ts +57 -0
- package/tests/agents-queries.test.ts +9 -9
- package/tests/agents-runner-process.test.ts +31 -0
- package/tests/agents-runner.test.ts +89 -4
- package/tests/append-system-prompt-route.test.ts +18 -1
- package/tests/auto-mode-human-answers.test.ts +68 -0
- package/tests/auto-mode-invariants.test.ts +276 -0
- package/tests/auto-mode-sdk.test.ts +84 -0
- package/tests/auto-mode.test.ts +49 -0
- package/tests/auto-permission-answers.test.ts +170 -0
- package/tests/auto-permission-classifier.test.ts +634 -0
- package/tests/auto-permission-compound.test.ts +24 -0
- package/tests/auto-permission-evidence.test.ts +88 -0
- package/tests/auto-permission-memory.test.ts +46 -0
- package/tests/auto-permission-public-regressions.test.ts +111 -0
- package/tests/auto-permission-runtime.test.ts +454 -0
- package/tests/autonomous-guard.test.ts +86 -16
- package/tests/background-cache-warming.test.ts +2 -1
- package/tests/background-jobs.test.ts +227 -0
- package/tests/background-monitor.test.ts +195 -0
- package/tests/background-subagents-default-mode.test.ts +28 -5
- package/tests/bounded-stop-contract.test.ts +85 -0
- package/tests/bounded-writer-admission.test.ts +52 -1
- package/tests/bridge-wake-lifecycle.test.ts +129 -0
- package/tests/bridge-wake-rendering.test.ts +56 -0
- package/tests/build-distribution.test.ts +269 -0
- package/tests/builtin-codemode-optout.test.ts +242 -0
- package/tests/bundled-install.test.ts +1319 -0
- package/tests/bundled-wizard.test.ts +505 -0
- package/tests/card-content-policy.test.ts +68 -0
- package/tests/card-style-policy.test.ts +34 -2
- package/tests/child-codegraph-bootstrap.test.ts +74 -0
- package/tests/child-package-entrypoints.test.ts +65 -0
- package/tests/child-package-injection.test.ts +74 -0
- package/tests/child-safety.test.ts +12 -0
- package/tests/child-web-package.test.ts +224 -0
- package/tests/codegraph-tools.test.ts +35 -0
- package/tests/codemode-rendering.test.ts +107 -2
- package/tests/command-palette.test.ts +5 -5
- package/tests/context-threshold.test.ts +31 -0
- package/tests/delegate-for-reason-contract.test.ts +327 -0
- package/tests/delegated-key-learnings-contract.test.ts +3 -2
- package/tests/devbinary/native-review-parity.devtest.ts +1 -1
- package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
- package/tests/engram-safety.test.ts +316 -0
- package/tests/generic-agent-tools.test.ts +16 -1
- package/tests/gentle-agents.test.ts +1895 -76
- package/tests/gentle-ai-binary-isolation.test.ts +135 -0
- package/tests/gentle-ai-binary.test.ts +19 -2
- package/tests/gentle-ai-child-guards.test.ts +202 -0
- package/tests/gentle-ai-dev-binary-surfacing.test.ts +125 -15
- package/tests/gentle-ai-installer.test.ts +548 -58
- package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
- package/tests/gentle-ai-renderer.test.ts +6 -6
- package/tests/gentle-ai.test.ts +999 -31
- package/tests/gentle-herdr-activity.test.ts +100 -0
- package/tests/gentle-notifications.test.ts +427 -0
- package/tests/gentle-shell-bin.test.ts +263 -19
- package/tests/gentle-shell-bundled.test.ts +1036 -0
- package/tests/gentle-shell-launcher.test.ts +251 -25
- package/tests/gentle-shell-startup-flow.test.ts +75 -0
- package/tests/gentle-shell-startup-update.test.ts +85 -0
- package/tests/gentle-shell-uninstall.test.ts +978 -0
- package/tests/gentle-shell-update-panel.test.ts +158 -0
- package/tests/gentle-shell.test.ts +611 -100
- package/tests/gentle-todo.test.ts +317 -14
- package/tests/herdr-activity.test.ts +243 -0
- package/tests/history-agent-home.test.ts +81 -0
- package/tests/history-child-guard.test.ts +70 -0
- package/tests/inprocess-reviewer.test.ts +20 -0
- package/tests/install-gentle-ai-failure.test.ts +69 -0
- package/tests/install-tui-mode-setting.test.ts +15 -0
- package/tests/install-wizard.test.ts +1223 -0
- package/tests/installer-bundles.test.ts +122 -0
- package/tests/installer-posix-bootstrap.test.ts +1201 -0
- package/tests/installer-preflight.test.ts +750 -0
- package/tests/installer-probes.test.ts +1106 -0
- package/tests/installer-runner.test.ts +2159 -0
- package/tests/installer-server.test.ts +1567 -0
- package/tests/installer-windows-bootstrap.test.ts +2410 -0
- package/tests/jobs-view.test.ts +160 -0
- package/tests/main-channel-upgrade.test.ts +395 -0
- package/tests/main-channel.test.ts +344 -0
- package/tests/maintainer/provider-relay.maintest.ts +13 -12
- package/tests/model-price-ratio.test.ts +116 -0
- package/tests/model-profiles-preservation.test.ts +153 -0
- package/tests/model-routing-package.test.ts +10 -0
- package/tests/nan-provider-runtime.test.ts +156 -0
- package/tests/nan-provider.test.ts +28 -18
- package/tests/native-binary-gate.test.ts +2 -0
- package/tests/native-review-capability-contract.test.ts +7 -1
- package/tests/native-review-cli.test.ts +236 -1
- package/tests/native-review-consent.test.ts +2 -2
- package/tests/native-review-parity-runtime.test.ts +1 -1
- package/tests/native-review-parity.test.ts +12 -48
- package/tests/native-risk-evidence-parity.test.ts +21 -0
- package/tests/no-self-pause-contract.test.ts +87 -0
- package/tests/notification-audio-native.test.ts +281 -0
- package/tests/notification-audio-windows.test.ts +581 -0
- package/tests/notification-audio.test.ts +548 -0
- package/tests/notification-customize.test.ts +655 -0
- package/tests/notification-events.test.ts +115 -0
- package/tests/notification-policy.test.ts +201 -0
- package/tests/notification-pulse-client.test.ts +356 -0
- package/tests/notification-pulse-protocol.test.ts +213 -0
- package/tests/notification-pulse-stream.test.ts +320 -0
- package/tests/notification-scheduler.test.ts +258 -0
- package/tests/notification-ui.test.ts +157 -0
- package/tests/npm-pack-result.test.ts +41 -0
- package/tests/odd-integration.test.ts +14 -5
- package/tests/odd-routing-canonical-ratchet.test.ts +53 -12
- package/tests/odd-routing-contract.test.ts +80 -23
- package/tests/odd-runtime-delegation-gate.test.ts +4 -1
- package/tests/orchestrator-budget.test.ts +29 -6
- package/tests/orchestrator-catalog.test.ts +146 -0
- package/tests/orchestrator-consultation-sdk.test.ts +614 -0
- package/tests/orchestrator-consultation.test.ts +111 -0
- package/tests/orchestrator-discovery.test.ts +241 -0
- package/tests/orchestrator-helper-consent.test.ts +171 -0
- package/tests/orchestrator-helper.test.ts +297 -0
- package/tests/orchestrator-message-card.test.ts +190 -0
- package/tests/orchestrator-presence.test.ts +23 -0
- package/tests/orchestrator-rdd-ownership.test.ts +6 -5
- package/tests/orchestrator-scope.test.ts +107 -0
- package/tests/orchestrator-state.test.ts +114 -0
- package/tests/orchestrator-status-publication-contract.test.ts +34 -0
- package/tests/orchestrator-tool-card.test.ts +398 -0
- package/tests/orchestrator-work-search.test.ts +318 -0
- package/tests/orchestrator-work.test.ts +147 -0
- package/tests/package-manifest.test.ts +105 -21
- package/tests/packed-runner-owned-path.test.ts +35 -1
- package/tests/parallel-reason-contract.test.ts +21 -0
- package/tests/parallel-writer-admission.test.ts +210 -0
- package/tests/permission-mode-policy.test.ts +194 -0
- package/tests/persona-single-channel.test.ts +4 -12
- package/tests/pi-pretty.test.ts +56 -0
- package/tests/pi-update-launcher.test.ts +87 -0
- package/tests/pi-update.test.ts +186 -0
- package/tests/provider-defect-handoff.test.ts +4 -3
- package/tests/quiet-tool-rendering.test.ts +160 -3
- package/tests/rdd-aware-verification-contract.test.ts +29 -41
- package/tests/rdd-status-line.test.ts +52 -0
- package/tests/render-cache-performance.test.ts +48 -0
- package/tests/review-agent-end-preflight.test.ts +62 -10
- package/tests/review-candidate-view.test.ts +101 -12
- package/tests/review-contract-prompt.test.ts +33 -4
- package/tests/review-controller-native-recovery.test.ts +327 -17
- package/tests/review-controller-native-routing.test.ts +255 -1
- package/tests/review-controller-workspace-root.test.ts +43 -2
- package/tests/review-controller.test.ts +2 -2
- package/tests/review-correction-lifecycle.test.ts +2 -1
- package/tests/review-gate.test.ts +7 -4
- package/tests/review-graph-schema.test.ts +2 -2
- package/tests/review-host-relay-routing.test.ts +5 -3
- package/tests/review-host-relay.test.ts +49 -2
- package/tests/review-integration-v2-forward.test.ts +21 -1
- package/tests/review-json-arguments.test.ts +167 -0
- package/tests/review-last-event-closure.test.ts +157 -8
- package/tests/review-ledger-contract.test.ts +5 -4
- package/tests/review-policy-judgment-day.test.ts +1 -1
- package/tests/review-policy-ordinary.test.ts +2 -2
- package/tests/review-relay-transport-agent.test.ts +66 -1
- package/tests/review-risk-assessment.test.ts +109 -33
- package/tests/review-session-standing-permission-controller.test.ts +16 -17
- package/tests/review-session-standing-permission-ipc.test.ts +6 -5
- package/tests/review-test-fixtures.ts +7 -1
- package/tests/review-transaction.test.ts +18 -1
- package/tests/run-test-suite.test.ts +83 -5
- package/tests/runtime-harness.mjs +42 -15
- package/tests/runtime-metrics-extension.test.ts +1 -1
- package/tests/runtime-metrics-native.test.ts +31 -1
- package/tests/selection-engine.test.ts +65 -5
- package/tests/session-change-capture.test.ts +10 -10
- package/tests/session-changes-shell.test.ts +242 -48
- package/tests/session-changes.test.ts +17 -24
- package/tests/session-profile-binding.test.ts +163 -0
- package/tests/session-profile-freeze.test.ts +394 -0
- package/tests/session-profile-persistence.test.ts +334 -0
- package/tests/session-subject-routing-contract.test.ts +34 -0
- package/tests/session-worktree-registry.test.ts +57 -2
- package/tests/shell-bar.test.ts +40 -0
- package/tests/shell-changes-view.test.ts +85 -34
- package/tests/shell-changes.test.ts +79 -62
- package/tests/shell-render-cache-runtime.test.ts +69 -0
- package/tests/shell-render-cache.test.ts +129 -0
- package/tests/shell-sidebar-layout.test.ts +27 -2
- package/tests/shell-todo.test.ts +132 -11
- package/tests/shell-usage-view.test.ts +1 -1
- package/tests/shell-usage.test.ts +35 -0
- package/tests/short-check-routing-contract.test.ts +49 -0
- package/tests/skill-registry-watcher.test.ts +119 -0
- package/tests/skill-registry.test.ts +311 -5
- package/tests/startup-banner.test.ts +117 -3
- package/tests/startup-home-repo.test.ts +170 -0
- package/tests/status-timing-diagnostics.test.ts +283 -0
- package/tests/support/orchestrator-modules.ts +19 -0
- package/tests/task-size-routing-contract.test.ts +241 -0
- package/tests/telemetry-trigger.test.ts +27 -1
- package/tests/test-strength-contract.test.ts +124 -0
- package/tests/verify-agent-contract.test.ts +90 -0
- package/tests/verify-first-launch-contract.test.ts +139 -0
- package/tests/verify-package-files.test.ts +56 -0
- package/tests/vim-editor-adapter.test.ts +47 -21
- package/tests/visual-customization-policy.test.ts +32 -3
- package/tests/visual-customize-view.test.ts +288 -10
- package/tests/writer-edit-surface-scope.test.ts +59 -2
- package/tests/writer-surfaces.test.ts +199 -0
- package/tests/yolo-customize.test.ts +18 -3
- package/tests/yolo-mode-runtime.test.ts +37 -10
- package/tests/yolo-mode.test.ts +38 -5
- package/tests/yolo-session-policy.test.ts +1 -1
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/README.md +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/targeted-validator.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/targeted-validator.json +0 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { execFileSync } from "node:child_process";
|
|
3
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import { worktreeGitEnvironment } from "../lib/session-worktree-registry.ts";
|
|
8
|
+
import { canonicalWriterRoot, normalizeSurfaceEntry, overlappingSurfaces, surfacesOverlap, WriterSurfaceRegistry, writerSurfaceConflictMessage } from "../lib/writer-surfaces.ts";
|
|
9
|
+
|
|
10
|
+
// gentle-shell#1731 T4 (S2, AC6): concurrent writers are admitted only when
|
|
11
|
+
// their `## Allowed edit surfaces` are disjoint. Overlap is conservative: two
|
|
12
|
+
// entries overlap when some path could be covered by both, and an entry also
|
|
13
|
+
// covers everything under it, so uncertainty always reads as overlap.
|
|
14
|
+
|
|
15
|
+
test("normalizeSurfaceEntry strips list markers, backticks, ./ and trailing slashes", () => {
|
|
16
|
+
assert.equal(normalizeSurfaceEntry("lib/a.ts"), "lib/a.ts");
|
|
17
|
+
assert.equal(normalizeSurfaceEntry("- `docs/with space.md`"), "docs/with space.md");
|
|
18
|
+
assert.equal(normalizeSurfaceEntry("`./lib//a.ts`"), "lib/a.ts");
|
|
19
|
+
assert.equal(normalizeSurfaceEntry("lib\\nested\\a.ts"), "lib\\nested\\a.ts", "backslashes are kept for the fail-safe check");
|
|
20
|
+
assert.equal(normalizeSurfaceEntry("src/cafe\u0301.ts"), "src/caf\u00e9.ts", "entries compare in NFC");
|
|
21
|
+
assert.equal(normalizeSurfaceEntry("lib/./dir/"), "lib/dir");
|
|
22
|
+
assert.equal(normalizeSurfaceEntry(" 1. tests/x.test.ts "), "tests/x.test.ts");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("normalizeSurfaceEntry refuses entries it cannot scope", () => {
|
|
26
|
+
for (const entry of ["", " ", ".", "./", "/abs/a.ts", "~/a.ts", "C:/a.ts", "lib/../a.ts", "``"]) {
|
|
27
|
+
assert.equal(normalizeSurfaceEntry(entry), undefined, entry);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("disjoint files do not overlap", () => {
|
|
32
|
+
assert.equal(surfacesOverlap("lib/a.ts", "lib/b.ts"), false);
|
|
33
|
+
assert.equal(surfacesOverlap("lib/a.ts", "tests/a.ts"), false);
|
|
34
|
+
assert.equal(surfacesOverlap("lib/a.ts", "lib/a.ts.bak"), false);
|
|
35
|
+
assert.equal(surfacesOverlap("lib/a/b.ts", "lib/b/b.ts"), false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("the same file overlaps, whatever its spelling", () => {
|
|
39
|
+
assert.equal(surfacesOverlap("lib/a.ts", "lib/a.ts"), true);
|
|
40
|
+
assert.equal(surfacesOverlap("`lib/a.ts`", "./lib/a.ts"), true);
|
|
41
|
+
assert.equal(surfacesOverlap("- lib/a.ts", "lib//a.ts"), true);
|
|
42
|
+
assert.equal(surfacesOverlap("lib/A.ts", "lib/a.ts"), true, "case-insensitive filesystems make these one file");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("a glob covering a file overlaps it", () => {
|
|
46
|
+
assert.equal(surfacesOverlap("lib/*.ts", "lib/a.ts"), true);
|
|
47
|
+
assert.equal(surfacesOverlap("lib/a.ts", "lib/*.ts"), true);
|
|
48
|
+
assert.equal(surfacesOverlap("tests/*.test.ts", "tests/writer-surfaces.test.ts"), true);
|
|
49
|
+
assert.equal(surfacesOverlap("lib/?.ts", "lib/a.ts"), true);
|
|
50
|
+
assert.equal(surfacesOverlap("lib/[ab].ts", "lib/b.ts"), true);
|
|
51
|
+
assert.equal(surfacesOverlap("lib/[!a].ts", "lib/b.ts"), true);
|
|
52
|
+
assert.equal(surfacesOverlap("lib/{a,b}.ts", "lib/b.ts"), true);
|
|
53
|
+
assert.equal(surfacesOverlap("lib/*.ts", "lib/a.md"), false);
|
|
54
|
+
assert.equal(surfacesOverlap("lib/{a,b}.ts", "lib/c.ts"), false);
|
|
55
|
+
assert.equal(surfacesOverlap("lib/*.ts", "tests/a.ts"), false);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("a directory glob or bare directory covers anything under it", () => {
|
|
59
|
+
assert.equal(surfacesOverlap("lib/**", "lib/a.ts"), true);
|
|
60
|
+
assert.equal(surfacesOverlap("lib/**", "lib/deep/nested/a.ts"), true);
|
|
61
|
+
assert.equal(surfacesOverlap("lib/a.ts", "lib/**"), true);
|
|
62
|
+
assert.equal(surfacesOverlap("lib/**/*.ts", "lib/x/y.md"), true, "after ** the rest may sit under the other entry");
|
|
63
|
+
assert.equal(surfacesOverlap("lib", "lib/a.ts"), true);
|
|
64
|
+
assert.equal(surfacesOverlap("lib/", "lib/a.ts"), true);
|
|
65
|
+
assert.equal(surfacesOverlap("**/*.ts", "docs/a.md"), true);
|
|
66
|
+
assert.equal(surfacesOverlap("lib/**", "tests/a.ts"), false);
|
|
67
|
+
assert.equal(surfacesOverlap("lib/**", "libs/a.ts"), false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("two globs overlap unless their literal affixes prove them disjoint", () => {
|
|
71
|
+
assert.equal(surfacesOverlap("lib/*.ts", "lib/*.md"), false);
|
|
72
|
+
assert.equal(surfacesOverlap("lib/a*.ts", "lib/b*.ts"), false);
|
|
73
|
+
assert.equal(surfacesOverlap("lib/a*", "lib/*b"), true);
|
|
74
|
+
assert.equal(surfacesOverlap("lib/*.ts", "lib/x*"), true);
|
|
75
|
+
assert.equal(surfacesOverlap("lib/**", "lib/*.md"), true);
|
|
76
|
+
assert.equal(surfacesOverlap("lib/*/a.ts", "lib/x/*.ts"), true);
|
|
77
|
+
assert.equal(surfacesOverlap("lib/*/a.ts", "tests/*/a.ts"), false);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("uncertain entries are treated as overlapping", () => {
|
|
81
|
+
assert.equal(surfacesOverlap("lib/../tests/a.ts", "docs/b.md"), true, "unscopable entry");
|
|
82
|
+
assert.equal(surfacesOverlap(".", "docs/b.md"), true, "repository root");
|
|
83
|
+
assert.equal(surfacesOverlap("lib/@(a|b).ts", "lib/c.ts"), true, "extglob syntax is not interpreted");
|
|
84
|
+
assert.equal(surfacesOverlap("lib/x[y", "lib/xzzy"), true, "an unclosed class falls back to affixes");
|
|
85
|
+
assert.equal(surfacesOverlap("lib/{a,{b,c}}.ts", "lib/d.ts"), true, "nested braces are not interpreted");
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Verify B1: a brace group spanning `/` must never be split into segments.
|
|
89
|
+
test("a brace group spanning a slash overlaps everything", () => {
|
|
90
|
+
assert.equal(surfacesOverlap("lib/{foo.ts,bar/baz.ts}", "lib/bar/baz.ts"), true);
|
|
91
|
+
assert.equal(surfacesOverlap("src/{a,b/c}.ts", "src/b/c.ts"), true);
|
|
92
|
+
assert.equal(surfacesOverlap("src/b/c.ts", "src/{a,b/c}.ts"), true);
|
|
93
|
+
assert.equal(surfacesOverlap("lib/{x,y/z}/w.ts", "docs/a.md"), true, "fail safe even across unrelated roots");
|
|
94
|
+
assert.equal(surfacesOverlap("lib/a}.ts", "lib/b.ts"), true, "an unbalanced brace is not interpreted");
|
|
95
|
+
const registry = new WriterSurfaceRegistry();
|
|
96
|
+
registry.claim("task-a", "/repo", ["lib/{foo.ts,bar/baz.ts}"]);
|
|
97
|
+
assert.deepEqual(registry.conflicts("/repo", ["lib/bar/baz.ts"]).map(conflict => conflict.taskId), ["task-a"]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// Verify M1: lowercasing and class syntax (negation, POSIX classes) are never
|
|
101
|
+
// interpreted; any bracket in an entry overlaps everything.
|
|
102
|
+
test("any bracket class overlaps everything", () => {
|
|
103
|
+
assert.equal(surfacesOverlap("src/[^a].ts", "src/A.ts"), true);
|
|
104
|
+
assert.equal(surfacesOverlap("src/[!a-z].ts", "src/B.ts"), true);
|
|
105
|
+
assert.equal(surfacesOverlap("src/[[:alpha:]].ts", "src/b.ts"), true);
|
|
106
|
+
assert.equal(surfacesOverlap("lib/[ab].ts", "lib/c.ts"), true);
|
|
107
|
+
assert.equal(surfacesOverlap("lib/[a/b].ts", "docs/x.md"), true);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Second verify (A1 fail-safe additions): a backslash may be an escape or a
|
|
111
|
+
// separator, so the entry overlaps everything.
|
|
112
|
+
test("any backslash overlaps everything", () => {
|
|
113
|
+
assert.equal(surfacesOverlap("src/foo\\*", "src/foo?"), true);
|
|
114
|
+
assert.equal(surfacesOverlap("lib\\a.ts", "docs/x.md"), true);
|
|
115
|
+
assert.equal(surfacesOverlap("docs/x.md", "lib\\a.ts"), true);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("Unicode spellings compare in NFC and non-ASCII never meets a wildcard regex", () => {
|
|
119
|
+
assert.equal(surfacesOverlap("src/caf\u00e9.ts", "src/cafe\u0301.ts"), true, "NFC vs NFD is one file");
|
|
120
|
+
assert.equal(surfacesOverlap("src/?.ts", "src/\u{1F600}.ts"), true, "an astral character is one path character");
|
|
121
|
+
assert.equal(surfacesOverlap("src/\u{1F600}*.ts", "src/\u{1F600}x.ts"), true);
|
|
122
|
+
assert.equal(surfacesOverlap("src/caf\u00e9.ts", "src/cafe.ts"), false, "distinct literals stay disjoint");
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Second verify A1: the registry key is the canonical worktree root, so a
|
|
126
|
+
// subdirectory cwd, the root itself and a symlinked spelling share one key.
|
|
127
|
+
test("canonicalWriterRoot keys one worktree once and separate worktrees apart", (t) => {
|
|
128
|
+
const base = realpathSync(mkdtempSync(join(tmpdir(), "writer-root-")));
|
|
129
|
+
t.after(() => rmSync(base, { recursive: true, force: true }));
|
|
130
|
+
const repo = join(base, "repo");
|
|
131
|
+
mkdirSync(join(repo, "sub"), { recursive: true });
|
|
132
|
+
const git = (...args: string[]) => execFileSync("git", ["-C", repo, ...args], { env: worktreeGitEnvironment(), stdio: "ignore" });
|
|
133
|
+
git("init", "-q");
|
|
134
|
+
writeFileSync(join(repo, "sub", "a.ts"), "");
|
|
135
|
+
git("add", ".");
|
|
136
|
+
git("-c", "user.name=t", "-c", "user.email=t@example.invalid", "commit", "-q", "-m", "init");
|
|
137
|
+
const feature = join(base, "feature");
|
|
138
|
+
git("worktree", "add", "-q", feature);
|
|
139
|
+
const link = join(base, "link");
|
|
140
|
+
symlinkSync(repo, link);
|
|
141
|
+
assert.equal(canonicalWriterRoot(join(repo, "sub")), repo, "a subdirectory cwd keys to its worktree root");
|
|
142
|
+
assert.equal(canonicalWriterRoot(repo), repo);
|
|
143
|
+
assert.equal(canonicalWriterRoot(join(link, "sub")), repo, "a symlinked spelling keys to the real root");
|
|
144
|
+
assert.equal(canonicalWriterRoot(feature), feature, "a linked worktree keys to itself");
|
|
145
|
+
const registry = new WriterSurfaceRegistry();
|
|
146
|
+
registry.claim("task-a", canonicalWriterRoot(join(repo, "sub")), ["lib/a.ts"]);
|
|
147
|
+
assert.deepEqual(registry.conflicts(canonicalWriterRoot(link), ["lib/a.ts"]).map(conflict => conflict.taskId), ["task-a"]);
|
|
148
|
+
assert.deepEqual(registry.conflicts(canonicalWriterRoot(feature), ["lib/a.ts"]), [], "different worktrees never conflict");
|
|
149
|
+
const plain = join(base, "plain");
|
|
150
|
+
mkdirSync(plain);
|
|
151
|
+
symlinkSync(plain, join(base, "plain-link"));
|
|
152
|
+
assert.equal(canonicalWriterRoot(join(base, "plain-link"), () => undefined), plain, "without Git the key is the realpath");
|
|
153
|
+
assert.equal(canonicalWriterRoot(join(base, "missing"), () => undefined), join(base, "missing"), "an unresolvable path keeps its resolved spelling");
|
|
154
|
+
assert.equal(canonicalWriterRoot("/repo/sub", () => ({ root: "/repo", commonDir: "/repo/.git" })), "/repo", "an injected resolver decides the root");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Like minimatch, `**` is a globstar only as a whole segment; inside a segment
|
|
158
|
+
// (`src/**.ts`) it matches within that one segment.
|
|
159
|
+
test("`**` inside a segment stays single-segment", () => {
|
|
160
|
+
assert.equal(surfacesOverlap("src/**.ts", "src/a.ts"), true);
|
|
161
|
+
assert.equal(surfacesOverlap("src/**.ts", "src/a/b.ts"), false);
|
|
162
|
+
assert.equal(surfacesOverlap("src/**", "src/a/b.ts"), true);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("overlappingSurfaces lists every overlapping pair; an empty list claims everything", () => {
|
|
166
|
+
assert.deepEqual(overlappingSurfaces(["lib/a.ts", "docs/x.md"], ["lib/**", "tests/a.ts"]), [["lib/a.ts", "lib/**"]]);
|
|
167
|
+
assert.deepEqual(overlappingSurfaces(["lib/a.ts"], ["lib/b.ts", "tests/b.ts"]), []);
|
|
168
|
+
assert.deepEqual(overlappingSurfaces([], ["lib/b.ts"]), [[".", "lib/b.ts"]]);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("the registry reports live overlapping writers in the same root only and releases them", () => {
|
|
172
|
+
const registry = new WriterSurfaceRegistry();
|
|
173
|
+
registry.claim("task-a", "/repo", ["lib/a.ts", "tests/a.test.ts"]);
|
|
174
|
+
registry.claim("task-b", "/repo", ["lib/b.ts"]);
|
|
175
|
+
registry.claim("task-c", "/repo-worktrees/feature", ["lib/a.ts"]);
|
|
176
|
+
assert.deepEqual(registry.conflicts("/repo", ["docs/x.md"]), []);
|
|
177
|
+
assert.deepEqual(registry.conflicts("/repo", ["lib/*.ts"]), [
|
|
178
|
+
{ taskId: "task-a", root: "/repo", pairs: [["lib/*.ts", "lib/a.ts"]] },
|
|
179
|
+
{ taskId: "task-b", root: "/repo", pairs: [["lib/*.ts", "lib/b.ts"]] },
|
|
180
|
+
]);
|
|
181
|
+
assert.deepEqual(registry.conflicts("/elsewhere", ["lib/a.ts"]), [], "different worktree roots never conflict");
|
|
182
|
+
assert.deepEqual(registry.conflicts("/repo/", ["lib/b.ts"]).map(conflict => conflict.taskId), ["task-b"], "roots compare resolved");
|
|
183
|
+
assert.equal(registry.release("task-b"), true);
|
|
184
|
+
assert.equal(registry.release("task-b"), false, "release is idempotent");
|
|
185
|
+
assert.deepEqual(registry.conflicts("/repo", ["lib/b.ts"]), []);
|
|
186
|
+
assert.equal(registry.size, 2);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("the conflict message names the live task, the overlapping entries and the ways forward", () => {
|
|
190
|
+
const message = writerSurfaceConflictMessage([{ taskId: "task-a", root: "/repo", pairs: [["lib/*.ts", "lib/a.ts"]] }]);
|
|
191
|
+
assert.match(message, /task task-a/);
|
|
192
|
+
assert.match(message, /`lib\/\*\.ts` overlaps `lib\/a\.ts`/);
|
|
193
|
+
assert.match(message, /\/repo/);
|
|
194
|
+
assert.match(message, /wait for/i);
|
|
195
|
+
assert.match(message, /narrow `## Allowed edit surfaces`/);
|
|
196
|
+
assert.match(message, /isolated worktree/);
|
|
197
|
+
const many = writerSurfaceConflictMessage([{ taskId: "t", root: "/repo", pairs: Array.from({ length: 8 }, (_, index) => [`a${index}`, `a${index}`] as [string, string]) }]);
|
|
198
|
+
assert.match(many, /and 3 more/, "long overlap lists stay bounded");
|
|
199
|
+
});
|
|
@@ -68,7 +68,7 @@ function fixture(t: test.TestContext, order = "owner-first", env: NodeJS.Process
|
|
|
68
68
|
},
|
|
69
69
|
} as unknown as ExtensionContext;
|
|
70
70
|
let controller: yolo.YoloSessionController | undefined;
|
|
71
|
-
const owner = () => { controller = yolo.registerYoloSessionPolicy(pi, env); };
|
|
71
|
+
const owner = () => { controller = yolo.registerYoloSessionPolicy(pi, { ...env, GENTLE_PI_CONFIG_HOME: home }); };
|
|
72
72
|
const shell = () => gentleShell(pi, { ...env, GENTLE_PI_CONFIG_HOME: home, GENTLE_PI_SHELL_CHANGES_WATCH_MS: "off" }, {
|
|
73
73
|
fetch: async () => { assert.fail("network disabled"); }, activeProfile: () => undefined, devBinary: () => undefined,
|
|
74
74
|
});
|
|
@@ -111,6 +111,21 @@ async function settle(check: () => boolean) {
|
|
|
111
111
|
}
|
|
112
112
|
function text(view: View) { return view.render(140).map(stripAnsi).join("\n"); }
|
|
113
113
|
|
|
114
|
+
test("Customize selects Auto for this session and saves either elevated default without changing current mode", async t => {
|
|
115
|
+
const h=fixture(t,"owner-first",{},true);const open=await h.open();const view=h.view();selectYolo(view);
|
|
116
|
+
view.handleInput("j");view.handleInput("j");view.render(140);
|
|
117
|
+
assert.match(text(view),/Session permissions: AUTO/);
|
|
118
|
+
await h.apply("\r");assert.equal(await h.controller().mode(h.ctx),"auto");
|
|
119
|
+
view.handleInput("j");view.handleInput("j");view.handleInput("j");view.render(140);
|
|
120
|
+
assert.match(text(view),/Default permissions: AUTO/);
|
|
121
|
+
await h.apply("\r");assert.equal(h.controller().defaultMode(),"auto");assert.equal(await h.controller().mode(h.ctx),"auto");
|
|
122
|
+
view.handleInput("j");view.render(140);await h.apply("\r");
|
|
123
|
+
assert.equal(h.controller().defaultMode(),"yolo");assert.equal(await h.controller().mode(h.ctx),"auto");
|
|
124
|
+
assert.equal(JSON.parse(readFileSync(join(h.home,"permission-mode.json"),"utf8")).mode,"yolo");
|
|
125
|
+
for(const width of [1,20,60,140])assert.ok(view.render(width).every(line=>visibleWidth(line)<=width));
|
|
126
|
+
open.close();await open.result;
|
|
127
|
+
});
|
|
128
|
+
|
|
114
129
|
for (const order of ["owner-first", "shell-first"]) test(`Editor YOLO menu and slash share one owner (${order})`, async t => {
|
|
115
130
|
const h = fixture(t, order, {}, true);
|
|
116
131
|
for (const name of ["vim-policy.json", "visual-settings.json", "visual-profiles.json", "runtime-guardrails.json", "history-capture-policy.json", "agent-profiles.json"]) writeFileSync(join(h.home, name), "{}\n");
|
|
@@ -187,11 +202,11 @@ test("off wins over pending menu on; child/headless/RPC cannot acquire adapter a
|
|
|
187
202
|
assert.equal(await h.controller().active(h.ctx), false);
|
|
188
203
|
open.close(); await open.result;
|
|
189
204
|
assert.equal(typeof yolo.discoverYoloUiAdapter, "function");
|
|
190
|
-
for (const variant of ["child", "headless", "rpc", "missing-
|
|
205
|
+
for (const variant of ["child", "headless", "rpc", "missing-workspace"]) {
|
|
191
206
|
const f = fixture(t, "owner-first", variant === "child" ? { GENTLE_PI_AGENTS_CHILD: "1" } : {});
|
|
192
207
|
if (variant === "headless") Object.assign(f.ctx, { hasUI: false });
|
|
193
208
|
if (variant === "rpc") Object.assign(f.ctx, { mode: "rpc" });
|
|
194
|
-
if (variant === "missing-
|
|
209
|
+
if (variant === "missing-workspace") Object.assign(f.ctx, { cwd: "/nonexistent-gentle-workspace" });
|
|
195
210
|
const adapter = await yolo.discoverYoloUiAdapter(f.pi, f.ctx);
|
|
196
211
|
if (adapter) { assert.equal(await adapter.read(), "UNAVAILABLE"); await adapter.toggle(); adapter.dispose(); }
|
|
197
212
|
assert.equal(await f.controller().active(f.ctx), false, variant);
|
|
@@ -12,10 +12,17 @@ import { loadExtensionFromFactory } from "../node_modules/@earendil-works/pi-cod
|
|
|
12
12
|
import { discoverYoloUiAdapter, registerYoloSessionPolicy, YOLO_STATUS_KEY, YOLO_STATUS_TEXT } from "../lib/yolo-session-policy.ts";
|
|
13
13
|
import { createGentleAiExtension } from "../extensions/gentle-ai.ts";
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
// An empty env would resolve the developer's real ~/.pi/gentle-ai and inherit its persisted mode default.
|
|
16
|
+
function isolatedEnv(t: test.TestContext): NodeJS.ProcessEnv {
|
|
17
|
+
const home = mkdtempSync(join(tmpdir(), "yolo-runtime-config-"));
|
|
18
|
+
t.after(() => rmSync(home, { recursive: true, force: true }));
|
|
19
|
+
return { GENTLE_PI_CONFIG_HOME: home };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test("complete Gentle AI extension registers and executes YOLO through the actual SDK loader", async t => {
|
|
16
23
|
const cwd = process.cwd();
|
|
17
24
|
const runtime = createExtensionRuntime();
|
|
18
|
-
const extension = await loadExtensionFromFactory(createGentleAiExtension({ nativeReviewCli: null, candidateViews: null, processEnv:
|
|
25
|
+
const extension = await loadExtensionFromFactory(createGentleAiExtension({ nativeReviewCli: null, candidateViews: null, processEnv: isolatedEnv(t) }), cwd, createEventBus(), runtime);
|
|
19
26
|
assert.equal(extension.handlers.get("session_start")?.length, 1, "original startup hook remains the sole handler");
|
|
20
27
|
assert.equal(extension.handlers.get("session_shutdown")?.length, 1, "original shutdown hook remains the sole handler");
|
|
21
28
|
const runner = new ExtensionRunner([extension], runtime, cwd, SessionManager.inMemory(cwd), {} as never);
|
|
@@ -40,8 +47,9 @@ test("complete Gentle AI extension registers and executes YOLO through the actua
|
|
|
40
47
|
// Actual SDK factory loader, context/command runner and in-memory SessionManager.
|
|
41
48
|
// Lifecycle events are dispatched through the real runner; the terminal UI is stubbed.
|
|
42
49
|
// No providers, persisted sessions, global assets or real tool execution are needed.
|
|
43
|
-
test("SDK-loaded YOLO command resets across actual runner lifecycle dispatch and extension replacement", async
|
|
50
|
+
test("SDK-loaded YOLO command resets across actual runner lifecycle dispatch and extension replacement", async t => {
|
|
44
51
|
const cwd = process.cwd();
|
|
52
|
+
const env = isolatedEnv(t);
|
|
45
53
|
const manager = SessionManager.inMemory(cwd);
|
|
46
54
|
const statuses = new Map<string, string | undefined>();
|
|
47
55
|
const widgets = new Map<string, unknown>();
|
|
@@ -53,7 +61,7 @@ test("SDK-loaded YOLO command resets across actual runner lifecycle dispatch and
|
|
|
53
61
|
const load = async () => {
|
|
54
62
|
const runtime = createExtensionRuntime();
|
|
55
63
|
const extension = await loadExtensionFromFactory((pi) => {
|
|
56
|
-
const yolo = registerYoloSessionPolicy(pi,
|
|
64
|
+
const yolo = registerYoloSessionPolicy(pi, env);
|
|
57
65
|
// Match production ownership: the host's existing hooks reset the controller.
|
|
58
66
|
pi.on("session_start", (_event, ctx) => yolo.reset(ctx));
|
|
59
67
|
pi.on("session_shutdown", (_event, ctx) => yolo.reset(ctx));
|
|
@@ -111,12 +119,32 @@ for (const shellFirst of [false, true]) test(`SDK loads both extensions on one b
|
|
|
111
119
|
const statuses = new Map<string, string | undefined>();
|
|
112
120
|
const notices: string[] = [];
|
|
113
121
|
let renders = 0;
|
|
122
|
+
const renderWaiters = new Set<() => void>();
|
|
123
|
+
const text = () => view!.render(140).map(stripAnsi).join("\n");
|
|
124
|
+
// Footer publication precedes the asynchronous authority snapshot refresh.
|
|
125
|
+
// Observe the requested menu render, not elapsed time after setStatus.
|
|
126
|
+
const rendered = (expected: RegExp) => new Promise<void>((resolve, reject) => {
|
|
127
|
+
const check = () => {
|
|
128
|
+
if (!view || !expected.test(text())) return;
|
|
129
|
+
clearTimeout(deadline);
|
|
130
|
+
renderWaiters.delete(check);
|
|
131
|
+
resolve();
|
|
132
|
+
};
|
|
133
|
+
const deadline = setTimeout(() => {
|
|
134
|
+
renderWaiters.delete(check);
|
|
135
|
+
reject(new Error(`Menu did not request a render matching ${expected}:\n${text()}`));
|
|
136
|
+
}, 1_000);
|
|
137
|
+
renderWaiters.add(check);
|
|
138
|
+
});
|
|
114
139
|
runner.setUIContext({
|
|
115
140
|
theme, getAllThemes: () => [{ name: "dark" }], getTheme: () => theme,
|
|
116
141
|
notify: (text: string) => notices.push(text), setStatus: (key: string, text?: string) => statuses.set(key, text), setWidget() {},
|
|
117
142
|
custom(factory: (tui: unknown, theme: unknown, keys: unknown, done: () => void) => Component) {
|
|
118
143
|
return new Promise<void>(resolve => {
|
|
119
|
-
view = factory({ terminal: { rows: 30 }, requestRender: () => {
|
|
144
|
+
view = factory({ terminal: { rows: 30 }, requestRender: () => {
|
|
145
|
+
renders++;
|
|
146
|
+
for (const check of [...renderWaiters]) check();
|
|
147
|
+
} }, theme, {}, resolve);
|
|
120
148
|
opened();
|
|
121
149
|
});
|
|
122
150
|
},
|
|
@@ -132,15 +160,14 @@ for (const shellFirst of [false, true]) test(`SDK loads both extensions on one b
|
|
|
132
160
|
await ready; assert.ok(view);
|
|
133
161
|
for (let i = 0; i < 3; i++) view.handleInput?.("j");
|
|
134
162
|
view.handleInput?.("\x1b[C"); view.handleInput?.("j"); view.handleInput?.("j");
|
|
135
|
-
const text = () => view!.render(140).map(stripAnsi).join("\n");
|
|
136
163
|
assert.match(text(), /YOLO: OFF · session only/);
|
|
164
|
+
const onRendered = rendered(/YOLO: ON/);
|
|
137
165
|
view.handleInput?.("\r");
|
|
138
|
-
|
|
166
|
+
await onRendered;
|
|
139
167
|
assert.equal(statuses.get(YOLO_STATUS_KEY), YOLO_STATUS_TEXT);
|
|
140
|
-
await new Promise(resolve => setTimeout(resolve, 30));
|
|
141
168
|
assert.match(text(), /YOLO: ON/);
|
|
142
|
-
await command.handler("disable", ctx);
|
|
143
|
-
|
|
169
|
+
await Promise.all([rendered(/YOLO: OFF/), command.handler("disable", ctx)]);
|
|
170
|
+
assert.equal(statuses.get(YOLO_STATUS_KEY), undefined);
|
|
144
171
|
assert.match(text(), /YOLO: OFF/);
|
|
145
172
|
for (const width of [1, 20, 45, 60, 140]) assert.ok(view.render(width).every(line => visibleWidth(line) <= width));
|
|
146
173
|
const adapter = await discoverYoloUiAdapter({
|
package/tests/yolo-mode.test.ts
CHANGED
|
@@ -13,7 +13,17 @@ const USAGE = "Use /gentle:yolo enable|disable|status. State unchanged.";
|
|
|
13
13
|
const STALE_MENU = "YOLO unchanged — the session changed while the menu was open.";
|
|
14
14
|
const menuTitle = (state: "ON" | "OFF") => `🚀 Gentle YOLO 🔥 — full speed, destructive actions still ask (current: ${state})`;
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
// An empty env would resolve the developer's real ~/.pi/gentle-ai and inherit its persisted mode default.
|
|
17
|
+
const isolatedConfigHomes: string[] = [];
|
|
18
|
+
process.once("exit", () => { for (const home of isolatedConfigHomes) rmSync(home, { recursive: true, force: true }); });
|
|
19
|
+
function isolatedConfigHome(): string {
|
|
20
|
+
const home = mkdtempSync(join(tmpdir(), "yolo-config-"));
|
|
21
|
+
isolatedConfigHomes.push(home);
|
|
22
|
+
return home;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function harness(overrides: NodeJS.ProcessEnv = {}) {
|
|
26
|
+
const env: NodeJS.ProcessEnv = { GENTLE_PI_CONFIG_HOME: isolatedConfigHome(), ...overrides };
|
|
17
27
|
const commands = new Map<string, { handler: (args: string, ctx: ExtensionContext) => Promise<void> }>();
|
|
18
28
|
const handlers = new Map<string, Array<(event: unknown, ctx: ExtensionContext) => unknown>>();
|
|
19
29
|
const tools: string[] = [];
|
|
@@ -132,12 +142,12 @@ test("revocation wins over an activation still awaiting Git identity", async ()
|
|
|
132
142
|
}
|
|
133
143
|
});
|
|
134
144
|
|
|
135
|
-
test("child, headless, RPC and
|
|
136
|
-
for (const variant of ["child", "headless", "rpc", "no-
|
|
145
|
+
test("child, headless, RPC and nonexistent workspace cannot activate", async () => {
|
|
146
|
+
for (const variant of ["child", "headless", "rpc", "no-workspace"]) {
|
|
137
147
|
const h = harness(variant === "child" ? { GENTLE_PI_AGENTS_CHILD: "1" } : {});
|
|
138
148
|
if (variant === "headless") Object.assign(h.ctx, { hasUI: false });
|
|
139
149
|
if (variant === "rpc") Object.assign(h.ctx, { mode: "rpc" });
|
|
140
|
-
if (variant === "no-
|
|
150
|
+
if (variant === "no-workspace") Object.assign(h.ctx, { cwd: "/nonexistent-gentle-workspace" });
|
|
141
151
|
await h.command("enable"); assert.notEqual(h.statuses.get("gentle:yolo"), ON_TEXT);
|
|
142
152
|
h.setSelect(async () => "enable");
|
|
143
153
|
await h.command(""); assert.notEqual(h.statuses.get("gentle:yolo"), ON_TEXT, `${variant} menu enable fails closed`);
|
|
@@ -182,7 +192,12 @@ test("active YOLO waives only ordinary push after full destructive/guard evaluat
|
|
|
182
192
|
await h.command("enable");
|
|
183
193
|
assert.equal(await call("git push origin main"), undefined);
|
|
184
194
|
assert.equal(h.confirmations(), 1);
|
|
185
|
-
|
|
195
|
+
// gentle-shell#2172: reversible compound delivery is waived too.
|
|
196
|
+
for (const command of ["git commit -m 'fix: x' && git push -u origin feature/x", "git push origin main && gh pr create --fill", "git -C /other push origin main", "git rebase origin/main && git push"]) {
|
|
197
|
+
assert.equal(await call(command), undefined, command);
|
|
198
|
+
}
|
|
199
|
+
assert.equal(h.confirmations(), 1, "waived commands never reach the confirmation dialog");
|
|
200
|
+
for (const command of ["git push origin main && rm -rf ./data", `sh -c "psql -c 'DROP TABLE users'"`, "git push origin main && git rebase -i main", "git push --force origin main", "git push origin main && npm publish", "git push origin :main", "sh -c 'git push origin main'"]) {
|
|
186
201
|
assert.ok(await call(command), command);
|
|
187
202
|
}
|
|
188
203
|
await h.command("disable"); assert.ok(await call("git push origin main"));
|
|
@@ -259,3 +274,21 @@ test("a session replaced while the menu is open cannot receive the enable choice
|
|
|
259
274
|
await h.command("");
|
|
260
275
|
assert.equal(h.notices.at(-1), "YOLO OFF");
|
|
261
276
|
});
|
|
277
|
+
|
|
278
|
+
test("bash_background goes through the same command confirmation and YOLO waiver as bash", async () => {
|
|
279
|
+
const h = harness();
|
|
280
|
+
const call = (command: string) => h.emit("tool_call", { toolName: "bash_background", input: { command } });
|
|
281
|
+
assert.deepEqual(await call("git push origin main"), { block: true, reason: "Gentle AI safety policy blocked the command because it was not confirmed." });
|
|
282
|
+
await h.command("enable");
|
|
283
|
+
assert.equal(await call("git push origin main"), undefined);
|
|
284
|
+
assert.ok(await call("git push origin main && rm -rf ./data"), "YOLO never waives a destructive command");
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test("monitor goes through the same command confirmation and YOLO waiver as bash", async () => {
|
|
288
|
+
const h = harness();
|
|
289
|
+
const call = (command: string) => h.emit("tool_call", { toolName: "monitor", input: { command, timeout_seconds: 60 } });
|
|
290
|
+
assert.deepEqual(await call("git push origin main"), { block: true, reason: "Gentle AI safety policy blocked the command because it was not confirmed." });
|
|
291
|
+
await h.command("enable");
|
|
292
|
+
assert.equal(await call("git push origin main"), undefined);
|
|
293
|
+
assert.ok(await call("git push origin main && rm -rf ./data"), "YOLO never waives a destructive command");
|
|
294
|
+
});
|
|
@@ -42,7 +42,7 @@ test("legacy hosts without an event bus retain command registration but no menu
|
|
|
42
42
|
const commands: string[] = [];
|
|
43
43
|
const pi = { registerCommand: (name: string) => commands.push(name) } as unknown as ExtensionAPI;
|
|
44
44
|
assert.ok(registerYoloSessionPolicy(pi, {}));
|
|
45
|
-
assert.deepEqual(commands, ["gentle:yolo"], "bare
|
|
45
|
+
assert.deepEqual(commands, ["gentle:mode", "gentle:yolo"], "bare commands are not registered");
|
|
46
46
|
assert.equal(await discoverYoloUiAdapter(pi, {} as ExtensionContext), undefined);
|
|
47
47
|
});
|
|
48
48
|
|
|
File without changes
|
/package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/lens.schema.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
/package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/lens.json
RENAMED
|
File without changes
|
/package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/refuter.json
RENAMED
|
File without changes
|
|
File without changes
|