gentle-pi 4.2.0 → 4.4.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 +340 -138
- package/assets/agents/gentle-ai-explore.md +1 -0
- package/assets/agents/gentle-ai-verify.md +6 -4
- package/assets/agents/gentle-ai-worker.md +7 -1
- package/assets/design-canvas/canvas.css +648 -0
- package/assets/design-canvas/canvas.js +1497 -0
- package/assets/design-canvas/index.html +88 -0
- package/assets/design-canvas/inspector.js +290 -0
- package/assets/orchestrator-delegation-reference.md +163 -0
- package/assets/orchestrator-delegation.md +25 -176
- package/assets/orchestrator-tracking.md +1 -1
- package/assets/orchestrator-verification.md +14 -8
- package/assets/orchestrator-writer.md +5 -1
- package/assets/orchestrator.md +11 -11
- package/bin/engram-guard +1 -1
- package/bin/gentle-shell.mjs +237 -139
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/README.md +7 -0
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/manifest.json +11 -2
- package/contracts/review-provider-contract-mirror/v1.4.0/bundle/routing/pi.md +92 -0
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-capabilities.baseline.json +8 -1
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-roles.baseline.json +1 -1
- package/docs/assets/diagrams/agent-orchestration.svg +54 -15
- package/docs/assets/diagrams/gentleman-workflow.svg +69 -11
- package/docs/assets/diagrams/measured-routing.svg +49 -0
- package/docs/assets/diagrams/native-review.svg +52 -12
- package/docs/assets/diagrams/odd-workflow.svg +128 -66
- package/docs/assets/diagrams/permission-modes.svg +51 -0
- package/docs/assets/features/audio-notifications.png +0 -0
- package/docs/assets/features/command-palette.png +0 -0
- package/docs/bundled-install.md +65 -30
- package/docs/delegated-verification.md +16 -8
- package/docs/gentle-agents-activity.md +16 -6
- package/docs/gentle-music.md +61 -0
- package/docs/gentle-shell-uninstall.md +8 -1
- package/docs/gentle-shell.md +80 -9
- package/docs/install-wizard.md +37 -6
- package/docs/permission-modes.md +8 -3
- package/docs/readme-reference.md +120 -28
- package/docs/session-profile-format.md +26 -0
- package/docs/voice.md +259 -0
- package/docs/yolo-mode.md +38 -10
- package/extensions/ask-user-choice.ts +4 -1
- package/extensions/codegraph-tools.ts +68 -6
- package/extensions/gentle-agents.ts +270 -48
- package/extensions/gentle-ai.ts +490 -113
- package/extensions/gentle-design.ts +158 -0
- package/extensions/gentle-herdr-activity.ts +2 -3
- package/extensions/gentle-music.ts +289 -0
- package/extensions/gentle-shell.ts +359 -85
- package/extensions/gentle-todo.ts +43 -9
- package/extensions/gentle-voice.ts +11 -0
- package/extensions/quiet-tools.ts +1 -1
- package/extensions/session-scratch.ts +384 -0
- package/extensions/skill-registry.ts +63 -8
- package/extensions/startup-banner.ts +2 -3
- package/lib/agent-home.ts +8 -3
- package/lib/agent-profiles.ts +26 -0
- package/lib/agents-config.ts +35 -0
- package/lib/agents-protocol.ts +60 -1
- package/lib/agents-runner.ts +107 -9
- package/lib/agents-session-transport.ts +101 -6
- package/lib/agents-view.ts +3 -1
- package/lib/agents-widget.ts +14 -1
- package/lib/auto-permission-memory.ts +48 -18
- package/lib/auto-permission-runtime.ts +21 -6
- package/lib/bounded-writer-admission.ts +51 -0
- package/lib/clipboard-image.ts +147 -0
- package/lib/command-palette-catalog.ts +2 -4
- package/lib/commit-slice-signal.ts +100 -0
- package/lib/context-backstop.ts +120 -0
- package/lib/delivered-test-lint.ts +80 -0
- package/lib/design-canvas-server.ts +947 -0
- package/lib/design-comments.ts +437 -0
- package/lib/design-handoffs.ts +313 -0
- package/lib/design-open-browser.ts +58 -0
- package/lib/design-sources.ts +120 -0
- package/lib/destructive-command-guard.ts +120 -9
- package/lib/external-editor.ts +31 -0
- package/lib/gauge-theme.ts +146 -0
- package/lib/gentle-ai-binary.ts +28 -7
- package/lib/gentle-design.ts +513 -0
- package/lib/gentle-shell-bundled.ts +146 -36
- package/lib/gentle-shell-import-pi.ts +233 -0
- package/lib/gentle-shell-launcher.ts +154 -15
- package/lib/gentle-shell-startup-update.ts +61 -9
- package/lib/gentle-shell-uninstall.ts +51 -2
- package/lib/herdr-activity.ts +24 -0
- package/lib/image-attachments.ts +150 -0
- package/lib/inprocess-reviewer.ts +33 -2
- package/lib/nan-provider.ts +3 -0
- package/lib/native-review-cli.ts +20 -5
- package/lib/notification-audio-native.ts +149 -13
- package/lib/notification-audio.ts +4 -0
- package/lib/notification-pulse-worker.ts +82 -18
- package/lib/notification-service.ts +5 -1
- package/lib/orchestrator-discovery.ts +5 -1
- package/lib/orchestrator-presence.ts +87 -8
- package/lib/orchestrator-scope.ts +31 -3
- package/lib/orchestrator-state.ts +11 -3
- package/lib/permission-mode-policy.ts +2 -2
- package/lib/provider-contract-bundle.ts +99 -53
- package/lib/review-candidate-view-owner.ts +29 -19
- package/lib/review-candidate-view.ts +36 -2
- package/lib/review-host-relay.ts +11 -2
- package/lib/review-reminder-receipt.ts +24 -1
- package/lib/review-risk-assessment.ts +43 -38
- package/lib/review-session-standing-permission.ts +3 -1
- package/lib/session-profile-append-controller.ts +713 -0
- package/lib/session-profile-disk-reader.ts +182 -0
- package/lib/session-profile-freeze.ts +30 -1
- package/lib/session-profile-persistence.ts +15 -7
- package/lib/session-scratch.ts +523 -0
- package/lib/session-worktree-registry.ts +7 -0
- package/lib/shell-bar.ts +29 -28
- package/lib/shell-changes-view.ts +80 -13
- package/lib/shell-changes.ts +81 -5
- package/lib/shell-gauge.ts +49 -6
- package/lib/shell-prompt.ts +4 -4
- package/lib/shell-sidebar-layout.ts +26 -6
- package/lib/shell-todo.ts +24 -5
- package/lib/shell-usage-view.ts +5 -1
- package/lib/shell-usage.ts +7 -7
- package/lib/verifier-admission.ts +30 -0
- package/lib/vim-editor-adapter.ts +11 -8
- package/lib/voice-artifacts.ts +188 -0
- package/lib/voice-command.ts +576 -0
- package/lib/voice-install.ts +455 -0
- package/lib/voice-platform.ts +99 -0
- package/lib/voice-push-to-talk.ts +262 -0
- package/lib/voice-session.ts +460 -0
- package/lib/voice-settings.ts +50 -0
- package/lib/voice-ui.ts +158 -0
- package/lib/yolo-session-policy.ts +58 -22
- package/lib/yolo-temp-scratch.ts +69 -0
- package/package.json +1 -2
- package/runtime/gentle-ai-binary.mjs +28 -7
- package/runtime/gentle-shell-bundled.mjs +145 -35
- package/runtime/gentle-shell-import-pi.mjs +234 -0
- package/runtime/gentle-shell-launcher.mjs +152 -13
- package/runtime/gentle-shell-startup-update.mjs +61 -9
- package/runtime/gentle-shell-uninstall.mjs +51 -2
- package/runtime/native-review-cli.mjs +19 -4
- package/runtime/review-risk-assessment.mjs +43 -38
- package/scripts/build-runtime-modules.mjs +1 -0
- package/scripts/bundled-engram-migration.mjs +119 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/installer-downloads.mjs +7 -7
- package/scripts/installer-preflight.mjs +5 -3
- package/scripts/installer-server.mjs +14 -4
- package/scripts/installer-windows.mjs +37 -7
- package/scripts/main-channel.mjs +94 -19
- package/scripts/pi-update.mjs +34 -6
- package/scripts/run-test-suite.mjs +5 -0
- package/scripts/verify-package-files.mjs +43 -13
- package/scripts/voice-worker.cjs +476 -0
- package/skills/design/SKILL.md +110 -0
- package/skills/gentle-ai/SKILL.md +3 -3
- package/skills/work-unit-commits/SKILL.md +5 -3
- package/tests/agent-profiles.test.ts +24 -0
- package/tests/agent-risk-escalation.test.ts +37 -10
- package/tests/agents-config.test.ts +61 -0
- package/tests/agents-fake-child.ts +8 -1
- package/tests/agents-protocol.test.ts +32 -0
- package/tests/agents-runner.test.ts +485 -7
- package/tests/agents-session-transport.test.ts +53 -3
- package/tests/ask-user-choice.test.ts +30 -0
- package/tests/auto-mode.test.ts +17 -0
- package/tests/auto-permission-memory-diagnostics.test.ts +56 -0
- package/tests/auto-permission-runtime.test.ts +85 -1
- package/tests/autonomous-guard.test.ts +142 -0
- package/tests/bounded-writer-admission.test.ts +11 -3
- package/tests/bundled-install.test.ts +3 -3
- package/tests/card-content-policy.test.ts +19 -9
- package/tests/child-safety.test.ts +39 -0
- package/tests/clipboard-image.test.ts +114 -0
- package/tests/codegraph-tools.test.ts +123 -2
- package/tests/command-palette.test.ts +15 -5
- package/tests/commit-slice-signal.test.ts +162 -0
- package/tests/context-backstop.test.ts +225 -0
- package/tests/delegate-for-reason-contract.test.ts +92 -24
- package/tests/delivered-test-lint.test.ts +105 -0
- package/tests/design-canvas-assets.test.ts +710 -0
- package/tests/design-canvas-server.test.ts +1096 -0
- package/tests/design-comments.test.ts +201 -0
- package/tests/design-handoffs.test.ts +282 -0
- package/tests/design-open-browser.test.ts +70 -0
- package/tests/design-sources.test.ts +132 -0
- package/tests/destructive-command-guard.test.ts +85 -1
- package/tests/engram-safety.test.ts +132 -57
- package/tests/external-editor.test.ts +84 -0
- package/tests/fixtures/primary-session-prompt/gentleman.golden.md +143 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/README.md +29 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/manifest.json +83 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/orchestration/pi.md +70 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/routing/pi.md +92 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/lens.schema.json +16 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/refuter.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/targeted-validator.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/lens.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/refuter.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/targeted-validator.json +1 -0
- package/tests/gauge-theme.test.ts +120 -0
- package/tests/gentle-agents.test.ts +645 -21
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-child-guards.test.ts +61 -0
- package/tests/gentle-ai-doctor-herdr.test.ts +94 -0
- package/tests/gentle-ai-installer.test.ts +51 -51
- package/tests/gentle-ai.test.ts +292 -13
- package/tests/gentle-design.test.ts +1313 -0
- package/tests/gentle-music.test.ts +335 -0
- package/tests/gentle-shell-bin.test.ts +163 -0
- package/tests/gentle-shell-bundled.test.ts +392 -176
- package/tests/gentle-shell-import-pi.test.ts +262 -0
- package/tests/gentle-shell-launcher.test.ts +107 -2
- package/tests/gentle-shell-startup-update.test.ts +77 -1
- package/tests/gentle-shell-uninstall.test.ts +89 -0
- package/tests/gentle-shell.test.ts +225 -17
- package/tests/gentle-todo.test.ts +190 -0
- package/tests/headless-delivery-question.test.ts +58 -0
- package/tests/herdr-activity.test.ts +34 -1
- package/tests/image-attachments.test.ts +119 -0
- package/tests/image-paste-chips.test.ts +236 -0
- package/tests/inprocess-reviewer.test.ts +55 -2
- package/tests/installer-bundles.test.ts +12 -6
- package/tests/installer-posix-bootstrap.test.ts +8 -8
- package/tests/installer-preflight.test.ts +16 -0
- package/tests/installer-server.test.ts +10 -2
- package/tests/installer-windows-bootstrap.test.ts +94 -7
- package/tests/installer-windows-diagnostics.test.ts +111 -0
- package/tests/main-channel-upgrade.test.ts +232 -2
- package/tests/main-channel.test.ts +140 -21
- package/tests/nan-provider.test.ts +36 -0
- package/tests/native-review-capability-contract.test.ts +17 -4
- package/tests/native-review-parity.test.ts +23 -2
- package/tests/no-self-pause-contract.test.ts +5 -2
- package/tests/notification-audio-native.test.ts +128 -9
- package/tests/notification-customize.test.ts +17 -5
- package/tests/odd-integration.test.ts +5 -1
- package/tests/odd-routing-canonical-ratchet.test.ts +128 -162
- package/tests/odd-routing-contract.test.ts +115 -30
- package/tests/orchestrator-budget.test.ts +22 -8
- package/tests/orchestrator-discovery.test.ts +29 -0
- package/tests/orchestrator-presence.test.ts +100 -2
- package/tests/orchestrator-rdd-ownership.test.ts +3 -1
- package/tests/orchestrator-scope.test.ts +80 -0
- package/tests/orchestrator-state.test.ts +29 -0
- package/tests/orchestrator-status-publication-contract.test.ts +12 -8
- package/tests/package-files.test.ts +34 -0
- package/tests/package-manifest.test.ts +24 -20
- package/tests/permission-mode-policy.test.ts +10 -1
- package/tests/pi-update-launcher.test.ts +21 -2
- package/tests/pi-update.test.ts +99 -4
- package/tests/primary-session-prompt.test.ts +101 -0
- package/tests/provider-contract-bundle.test.ts +118 -0
- package/tests/provider-contract-mirror.test.ts +24 -0
- package/tests/quiet-tool-rendering.test.ts +23 -0
- package/tests/rdd-aware-verification-contract.test.ts +29 -11
- package/tests/rdd-status-line.test.ts +79 -0
- package/tests/review-agent-end-preflight.test.ts +6 -0
- package/tests/review-candidate-view.test.ts +49 -0
- package/tests/review-contract-prompt.test.ts +5 -2
- package/tests/review-controller-native-routing.test.ts +14 -1
- package/tests/review-controller.test.ts +1 -1
- package/tests/review-host-relay-routing.test.ts +147 -3
- package/tests/review-host-relay.test.ts +9 -2
- package/tests/review-json-arguments.test.ts +20 -3
- package/tests/review-loadout-rdd-off-sdk.test.ts +258 -0
- package/tests/review-owner-identity.test.ts +132 -0
- package/tests/review-relay-transport-agent.test.ts +26 -0
- package/tests/review-reminder-receipt.test.ts +31 -1
- package/tests/review-risk-assessment.test.ts +163 -76
- package/tests/review-session-standing-permission-controller.test.ts +14 -1
- package/tests/review-session-standing-permission.test.ts +27 -1
- package/tests/run-test-suite.test.ts +15 -0
- package/tests/runtime-harness.mjs +16 -6
- package/tests/session-changes-shell.test.ts +1 -1
- package/tests/session-changes.test.ts +2 -1
- package/tests/session-profile-append-controller-fork.test.ts +458 -0
- package/tests/session-profile-append-controller-pi.test.ts +290 -0
- package/tests/session-profile-append-controller.test.ts +731 -0
- package/tests/session-profile-disk-reader-pi.test.ts +183 -0
- package/tests/session-profile-disk-reader.test.ts +311 -0
- package/tests/session-profile-freeze.test.ts +23 -0
- package/tests/session-scratch.test.ts +1011 -0
- package/tests/session-subject-routing-contract.test.ts +2 -1
- package/tests/session-worktree-registry.test.ts +13 -1
- package/tests/shell-bar.test.ts +21 -2
- package/tests/shell-changes-view.test.ts +137 -1
- package/tests/shell-changes.test.ts +148 -1
- package/tests/shell-gauge.test.ts +42 -0
- package/tests/shell-prompt.test.ts +3 -3
- package/tests/shell-render-cache.test.ts +134 -1
- package/tests/shell-sidebar-layout.test.ts +50 -0
- package/tests/short-check-routing-contract.test.ts +1 -1
- package/tests/skill-collision-prefixes.test.ts +3 -1
- package/tests/skill-registry-watcher.test.ts +17 -5
- package/tests/skill-registry.test.ts +47 -3
- package/tests/small-task-inline-contract.test.ts +37 -0
- package/tests/standalone-config-home.test.ts +324 -0
- package/tests/startup-banner.test.ts +5 -4
- package/tests/startup-home-repo.test.ts +34 -0
- package/tests/support/orchestrator-modules.ts +1 -0
- package/tests/task-size-routing-contract.test.ts +70 -10
- package/tests/test-scratch-contract.test.ts +102 -0
- package/tests/test-strength-contract.test.ts +36 -1
- package/tests/tool-batching-contract.test.ts +64 -0
- package/tests/verification-proportionality.test.ts +46 -0
- package/tests/verifier-admission.test.ts +154 -0
- package/tests/verify-agent-contract.test.ts +8 -8
- package/tests/voice-command.test.ts +773 -0
- package/tests/voice-install.test.ts +323 -0
- package/tests/voice-platform.test.ts +176 -0
- package/tests/voice-push-to-talk.test.ts +444 -0
- package/tests/voice-session.test.ts +434 -0
- package/tests/voice-settings.test.ts +49 -0
- package/tests/voice-ui.test.ts +97 -0
- package/tests/voice-worker.test.ts +445 -0
- package/tests/weak-model-economy-prompt.test.ts +32 -0
- package/tests/windows-hidden-processes.test.ts +30 -2
- package/tests/writer-edit-surface-scope.test.ts +3 -0
- package/tests/writer-feature-document-admission.test.ts +156 -0
- package/tests/yolo-customize.test.ts +5 -1
- package/tests/yolo-mode.test.ts +19 -1
- package/tests/yolo-session-policy-capture.test.ts +104 -0
- package/tests/yolo-temp-scratch.test.ts +71 -0
- package/scripts/mirror-odd-routing.mjs +0 -242
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/orchestration/pi.md +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/targeted-validator.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/targeted-validator.json +0 -0
|
@@ -45,6 +45,26 @@ function fixture120Entries(): Map<string, Buffer> {
|
|
|
45
45
|
return new Map(FIXTURE_1_2_0_PATHS.map((path) => [path, readFileSync(join(FIXTURE_1_2_0_ROOT, path))]));
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// Fixture provenance: the gentle-ai provider contract bundle 1.4.0 archive
|
|
49
|
+
// built by goreleaser (archives `review-provider-contract`) from gentle-ai
|
|
50
|
+
// 9e6f24f6, extracted. Adds `routing` to the manifest: Pi's ODD routing steps.
|
|
51
|
+
const FIXTURE_1_4_0_ROOT = join(import.meta.dirname, "fixtures", "provider-contract-bundle", "v1.4.0");
|
|
52
|
+
const FIXTURE_1_4_0_PATHS = [...FIXTURE_1_2_0_PATHS, "routing/pi.md"] as const;
|
|
53
|
+
|
|
54
|
+
function fixture140Entries(): Map<string, Buffer> {
|
|
55
|
+
return new Map(FIXTURE_1_4_0_PATHS.map((path) => [path, readFileSync(join(FIXTURE_1_4_0_ROOT, path))]));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// A valid 1.3.0-shaped bundle: the 1.4.0 fixture without the routing field and file.
|
|
59
|
+
function contractOneThreeEntries(): Map<string, Buffer> {
|
|
60
|
+
const entries = withManifest(fixture140Entries(), (manifest) => {
|
|
61
|
+
manifest.contract_semver = "1.3.0";
|
|
62
|
+
delete manifest.routing;
|
|
63
|
+
});
|
|
64
|
+
entries.delete("routing/pi.md");
|
|
65
|
+
return entries;
|
|
66
|
+
}
|
|
67
|
+
|
|
48
68
|
function sha256Hex(bytes: Buffer): string {
|
|
49
69
|
return createHash("sha256").update(bytes).digest("hex");
|
|
50
70
|
}
|
|
@@ -391,6 +411,104 @@ test("rejects an orchestration array on a 1.1.0 manifest", () => {
|
|
|
391
411
|
);
|
|
392
412
|
});
|
|
393
413
|
|
|
414
|
+
// --- routing (contract 1.4.0) ----------------------------------------------------------
|
|
415
|
+
|
|
416
|
+
test("verifies the 1.4.0 fixture tree and exposes the mirrored pi routing text", () => {
|
|
417
|
+
const bundle = verifyProviderContractBundleTree(FIXTURE_1_4_0_ROOT);
|
|
418
|
+
assert.equal(bundle.contractSemver, "1.4.0");
|
|
419
|
+
assert.equal(bundle.entries.size, 10);
|
|
420
|
+
assert.deepEqual([...bundle.orchestration.keys()], [PI_RUNTIME_IDENTITY]);
|
|
421
|
+
assert.deepEqual([...bundle.routing.keys()], [PI_RUNTIME_IDENTITY]);
|
|
422
|
+
assert.equal(bundle.routing.get(PI_RUNTIME_IDENTITY), readFileSync(join(FIXTURE_1_4_0_ROOT, "routing/pi.md"), "utf8"));
|
|
423
|
+
assert.equal(bundle.entrySha256.get("routing/pi.md"), sha256Hex(readFileSync(join(FIXTURE_1_4_0_ROOT, "routing/pi.md"))));
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test("verifies the 1.4.0 release archive in memory", () => {
|
|
427
|
+
withTemporaryDirectory((directory) => {
|
|
428
|
+
const archivePath = join(directory, "gentle-ai-review-provider-contract-1.4.0.tar.gz");
|
|
429
|
+
writeFileSync(archivePath, buildTarGz([...fixture140Entries()].map(([name, bytes]) => ({ name, bytes }))));
|
|
430
|
+
const { bundle } = verifyProviderContractBundleArchive(archivePath);
|
|
431
|
+
assert.equal(bundle.contractSemver, "1.4.0");
|
|
432
|
+
assert.deepEqual([...bundle.routing.keys()], [PI_RUNTIME_IDENTITY]);
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
test("a 1.3.0 bundle without routing still verifies with an empty routing inventory", () => {
|
|
437
|
+
const bundle = verifyProviderContractBundleEntries(contractOneThreeEntries());
|
|
438
|
+
assert.equal(bundle.contractSemver, "1.3.0");
|
|
439
|
+
assert.equal(bundle.entries.size, 9);
|
|
440
|
+
assert.equal(bundle.routing.size, 0);
|
|
441
|
+
assert.deepEqual([...bundle.orchestration.keys()], [PI_RUNTIME_IDENTITY]);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
test("rejects a 1.4.0 manifest without the required routing array", () => {
|
|
445
|
+
const entries = withManifest(fixture140Entries(), (manifest) => {
|
|
446
|
+
delete manifest.routing;
|
|
447
|
+
});
|
|
448
|
+
assertRejects(entries, "manifest.routing is required from contract 1.4.0");
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
test("rejects a routing array on a 1.3.0 manifest", () => {
|
|
452
|
+
const entries = withManifest(fixture140Entries(), (manifest) => {
|
|
453
|
+
manifest.contract_semver = "1.3.0";
|
|
454
|
+
});
|
|
455
|
+
assertRejects(entries, "manifest.routing is not part of contract 1.3.0");
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test("rejects a 1.4.0 routing entry whose runtime is not registered in manifest.runtimes", () => {
|
|
459
|
+
assertRejects(
|
|
460
|
+
withManifest(fixture140Entries(), (manifest) => {
|
|
461
|
+
const routing = manifest.routing as { runtime: string; file: { path: string } }[];
|
|
462
|
+
routing[0]!.runtime = "aider";
|
|
463
|
+
routing[0]!.file.path = "routing/aider.md";
|
|
464
|
+
}),
|
|
465
|
+
'manifest.routing[0].runtime "aider" is not registered in manifest.runtimes',
|
|
466
|
+
);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
test("rejects a 1.4.0 routing entry that points outside routing/<runtime>.md", () => {
|
|
470
|
+
assertRejects(
|
|
471
|
+
withManifest(fixture140Entries(), (manifest) => {
|
|
472
|
+
(manifest.routing as { file: { path: string } }[])[0]!.file.path = "orchestration/pi.md";
|
|
473
|
+
}),
|
|
474
|
+
'manifest.routing[0].file.path must be "routing/pi.md"',
|
|
475
|
+
);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
test("rejects a 1.4.0 routing entry whose file bytes do not match its manifest SHA-256", () => {
|
|
479
|
+
const entries = fixture140Entries();
|
|
480
|
+
const tampered = Buffer.from(entries.get("routing/pi.md") as Buffer);
|
|
481
|
+
tampered[tampered.length - 2] = 0x20;
|
|
482
|
+
entries.set("routing/pi.md", tampered);
|
|
483
|
+
assertRejects(entries, "does not match its manifest SHA-256");
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test("rejects a 1.4.0 bundle missing its routing file", () => {
|
|
487
|
+
const entries = fixture140Entries();
|
|
488
|
+
entries.delete("routing/pi.md");
|
|
489
|
+
assertRejects(entries, "bundle inventory has 9 files; exactly 10 are expected");
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
test("the raw-entry bound leaves room for one routing file per runtime before the exact count decides", () => {
|
|
493
|
+
const entries = fixture140Entries();
|
|
494
|
+
for (let index = 0; index < 70; index += 1) entries.set(`extra/${index}.md`, Buffer.from("x\n"));
|
|
495
|
+
assertRejects(entries, "bundle inventory has 80 files; exactly 10 are expected");
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
test("generated 1.4.0 baselines record the mirrored routing entry", () => {
|
|
499
|
+
const bundle = verifyProviderContractBundleTree(FIXTURE_1_4_0_ROOT);
|
|
500
|
+
const capabilities = JSON.parse(
|
|
501
|
+
generateProviderContractBaselines(bundle).get("provider-capabilities.baseline.json") as string,
|
|
502
|
+
) as { routing: { runtime: string; path: string; sha256: string }[] };
|
|
503
|
+
assert.deepEqual(capabilities.routing, [
|
|
504
|
+
{ runtime: PI_RUNTIME_IDENTITY, path: "routing/pi.md", sha256: bundle.entrySha256.get("routing/pi.md") },
|
|
505
|
+
]);
|
|
506
|
+
const older = JSON.parse(
|
|
507
|
+
generateProviderContractBaselines(verifyProviderContractBundleTree(FIXTURE_1_2_0_ROOT)).get("provider-capabilities.baseline.json") as string,
|
|
508
|
+
) as { routing: unknown[] };
|
|
509
|
+
assert.deepEqual(older.routing, []);
|
|
510
|
+
});
|
|
511
|
+
|
|
394
512
|
// --- archive safety ------------------------------------------------------------------
|
|
395
513
|
|
|
396
514
|
test("rejects a tar entry that escapes the bundle root", () => {
|
|
@@ -31,6 +31,10 @@ const FIXTURE_PATHS = [
|
|
|
31
31
|
// release archive, extracted. gentle-pi#560 / gentle-ai#4056, #4057.
|
|
32
32
|
const FIXTURE_1_2_0_ROOT = join(import.meta.dirname, "fixtures", "provider-contract-bundle", "v1.2.0");
|
|
33
33
|
|
|
34
|
+
// Fixture provenance: the gentle-ai provider contract bundle 1.4.0 archive
|
|
35
|
+
// built by goreleaser, extracted. Adds Pi's ODD routing block as routing/pi.md.
|
|
36
|
+
const FIXTURE_1_4_0_ROOT = join(import.meta.dirname, "fixtures", "provider-contract-bundle", "v1.4.0");
|
|
37
|
+
|
|
34
38
|
interface LockRecord {
|
|
35
39
|
schema: string;
|
|
36
40
|
acquisition: string;
|
|
@@ -124,6 +128,26 @@ test("mirrors a verified local 1.2.0 bundle tree, including its orchestration en
|
|
|
124
128
|
});
|
|
125
129
|
});
|
|
126
130
|
|
|
131
|
+
test("mirrors a verified local 1.4.0 bundle tree, including its routing entry, and the offline drift check passes", () => {
|
|
132
|
+
withTemporaryRoot((packageRoot) => {
|
|
133
|
+
mirrorProviderContractBundle(FIXTURE_1_4_0_ROOT, packageRoot);
|
|
134
|
+
const lock = JSON.parse(
|
|
135
|
+
readFileSync(join(packageRoot, PROVIDER_CONTRACT_MIRROR_ROOT, PROVIDER_CONTRACT_LOCK_FILE), "utf8"),
|
|
136
|
+
) as LockRecord;
|
|
137
|
+
assert.equal(lock.contract_semver, "1.4.0");
|
|
138
|
+
assert.equal(Object.keys(lock.entries).length, 10);
|
|
139
|
+
assert.ok("routing/pi.md" in lock.entries, "the mirrored lock must record routing/pi.md");
|
|
140
|
+
const mirrored = join(packageRoot, PROVIDER_CONTRACT_MIRROR_ROOT, "v1.4.0", "bundle", "routing", "pi.md");
|
|
141
|
+
assert.deepEqual(readFileSync(mirrored), readFileSync(join(FIXTURE_1_4_0_ROOT, "routing", "pi.md")));
|
|
142
|
+
assert.deepEqual(checkProviderContractMirror(packageRoot), []);
|
|
143
|
+
|
|
144
|
+
writeFileSync(mirrored, `${readFileSync(mirrored, "utf8")}tampered\n`);
|
|
145
|
+
const problems = checkProviderContractMirror(packageRoot);
|
|
146
|
+
assert.equal(problems.length, 1);
|
|
147
|
+
assert.match(problems[0] ?? "", /mirrored bundle failed verification: .*"routing\/pi\.md" does not match its manifest SHA-256/);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
127
151
|
test("mirrors a verified local archive and records the archive digest in the lock", () => {
|
|
128
152
|
withTemporaryRoot((packageRoot) => {
|
|
129
153
|
const archivePath = join(packageRoot, "gentle-ai-review-provider-contract-1.1.0.tar.gz");
|
|
@@ -1232,6 +1232,29 @@ test("quiet tool rendering preserves complete expanded text and delegates saniti
|
|
|
1232
1232
|
assert.doesNotMatch(image, /\x1b/);
|
|
1233
1233
|
});
|
|
1234
1234
|
|
|
1235
|
+
test("quiet tool rendering re-renders expanded read-with-image results without crashing when lastComponent is passed (#1620)", () => {
|
|
1236
|
+
const tools = registeredQuietTools();
|
|
1237
|
+
const readTool = tools.get("read");
|
|
1238
|
+
const result = {
|
|
1239
|
+
content: [{ type: "image", data: "iVBORw0KGgo=", mimeType: "image/png" }],
|
|
1240
|
+
details: { path: "image.png" },
|
|
1241
|
+
};
|
|
1242
|
+
const context = { args: { path: "image.png" }, cwd: "/repo", showImages: false, isError: false };
|
|
1243
|
+
|
|
1244
|
+
const initialComponent = readTool.renderResult(result, { expanded: true, isPartial: false }, passthroughTheme, context);
|
|
1245
|
+
const initialOutput = renderToString(initialComponent);
|
|
1246
|
+
assert.ok(initialOutput.includes(imageFallback("image/png")));
|
|
1247
|
+
|
|
1248
|
+
const reRenderComponent = readTool.renderResult(
|
|
1249
|
+
result,
|
|
1250
|
+
{ expanded: true, isPartial: false },
|
|
1251
|
+
passthroughTheme,
|
|
1252
|
+
{ ...context, lastComponent: initialComponent },
|
|
1253
|
+
);
|
|
1254
|
+
const reRenderOutput = renderToString(reRenderComponent);
|
|
1255
|
+
assert.ok(reRenderOutput.includes(imageFallback("image/png")));
|
|
1256
|
+
});
|
|
1257
|
+
|
|
1235
1258
|
test("quiet tool rendering preserves directional visual preview rows at narrow widths", () => {
|
|
1236
1259
|
const tools = registeredQuietTools();
|
|
1237
1260
|
const bash = tools.get("bash");
|
|
@@ -48,13 +48,18 @@ const ROUTING_SENTENCE =
|
|
|
48
48
|
'After the writer returns, call `gentle_review` with `{"operation":"assess"}` over the writer\'s diff and follow the returned plan, whatever the `Receipt-driven development:` line reads (`on`, `off`, or `unknown`), instead of judging non-triviality from the task description: the operation resolves the native risk tier and states exactly who verifies next.';
|
|
49
49
|
const RDD_ON_SENTENCE =
|
|
50
50
|
"When the line reads `on`, the native review is an additional outside view of the change; it never replaces or skips the tier's verification, and the tier's verifier runs before review.";
|
|
51
|
-
|
|
51
|
+
// Verification by named risk changed the tier table and replaced the
|
|
52
|
+
// small-model bias sentence: the independent verifier runs only through an
|
|
53
|
+
// `escalate` naming a Task Size high-risk item; a native high, a failed
|
|
54
|
+
// assessment, and the writer's model or effort inform only.
|
|
55
|
+
const TIER_TABLE_HEADER = "| Tier | Verification (any RDD state) |";
|
|
52
56
|
const PASSIVE_TIER_ROW = "| passive | structural readback by the parent; no separate verifier, no tests |";
|
|
53
|
-
const MEDIUM_TIER_ROW = "| medium | writer self-verification stands;
|
|
54
|
-
const HIGH_TIER_ROW = "| high | writer self-verification plus
|
|
55
|
-
const UNASSESSABLE_TIER_ROW = "| unknown
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
const MEDIUM_TIER_ROW = "| medium | writer self-verification stands; no separate verifier, whatever model wrote the change |";
|
|
58
|
+
const HIGH_TIER_ROW = "| high | only through `escalate` naming a Task Size high-risk item: writer self-verification plus one separate `gentle-ai-verify` run |";
|
|
59
|
+
const UNASSESSABLE_TIER_ROW = "| native high, unknown, or assess failed | informs only: writer self-verification stands; escalate when a named high-risk item applies |";
|
|
60
|
+
// odd/tasks/verifier-admission.md S6: the sentence names the runtime admission of the verifier launch.
|
|
61
|
+
const NAMED_RISK_SENTENCE =
|
|
62
|
+
"The independent verifier runs only when the change touches a named Task Size high-risk item, passed as `escalate` (runtime admits its launch on a `Risk: item N` line or sentence, a feature-document task line, or past the context backstop); never for size, a native tier alone, a failed assessment, or the model or effort that wrote the change. An unknown `Receipt-driven development:` line never lowers a tier below `off`.";
|
|
58
63
|
const SPOT_CHECK_SENTENCE =
|
|
59
64
|
"The parent spot check (re-running one reported command before delivery) stays required in every tier.";
|
|
60
65
|
test("trigger 5 (Verification rule) routes every RDD line through assess and the risk tier", () => {
|
|
@@ -74,8 +79,9 @@ test("trigger 5 states the native risk tier table exactly once, with all four ro
|
|
|
74
79
|
}
|
|
75
80
|
});
|
|
76
81
|
|
|
77
|
-
test("trigger 5 states the
|
|
78
|
-
assert.ok(delegation.includes(
|
|
82
|
+
test("trigger 5 states the named-risk verifier rule and the unknown-never-lowers-a-tier rule", () => {
|
|
83
|
+
assert.ok(delegation.includes(NAMED_RISK_SENTENCE), "trigger 5 is missing the named-risk verifier sentence");
|
|
84
|
+
assert.ok(!delegation.includes("small-model bias"), "the retired small-model bias must not survive in any orchestrator module");
|
|
79
85
|
});
|
|
80
86
|
|
|
81
87
|
test("trigger 5 keeps the parent spot check requirement in every tier", () => {
|
|
@@ -99,10 +105,11 @@ test("trigger 5 never restates the retired #661 off/unknown non-trivial judgment
|
|
|
99
105
|
assert.doesNotMatch(delegation, /purely passive documentation with no behavior to verify/);
|
|
100
106
|
});
|
|
101
107
|
|
|
108
|
+
// S9 canon change (gentle-ai 87a3b2eb): the trigger section heading dropped "Mandatory".
|
|
102
109
|
test("the Simple Delegation paragraph references trigger 3 instead of restating the on/off/unknown routing", () => {
|
|
103
110
|
assert.match(
|
|
104
111
|
delegation,
|
|
105
|
-
/per the RDD-aware Verification rule \(trigger 3 under
|
|
112
|
+
/per the RDD-aware Verification rule \(trigger 3 under Delegation Triggers, gentle-pi#661\)/,
|
|
106
113
|
);
|
|
107
114
|
assert.match(delegation, /the normative on\/off\/unknown routing lives in `orchestrator-verification.md`, not here/);
|
|
108
115
|
});
|
|
@@ -223,7 +230,9 @@ test("worker asset keeps the existing Return contract fields", () => {
|
|
|
223
230
|
// delegation asset, and docs/delegated-verification.md -- and they must say
|
|
224
231
|
// the same thing about the behavior ASSESS implements: closure only from the
|
|
225
232
|
// native `candidate.consumed` fact for this candidate, the runtime writer
|
|
226
|
-
// profile with a caller fallback,
|
|
233
|
+
// profile with a caller fallback, a writer profile that never adds a verifier
|
|
234
|
+
// (verification by named risk retired the small-model bias), the escalate-only
|
|
235
|
+
// independent verifier, the
|
|
227
236
|
// declined/unavailable/unknown fallback, and functional checks that a native
|
|
228
237
|
// review never replaces. Assertions use short stable phrases, normalized for
|
|
229
238
|
// case, quoting, and line wrapping, not whole paragraphs.
|
|
@@ -267,7 +276,8 @@ const SHARED_POLICY_PHRASES: ReadonlyArray<readonly [string, string]> = [
|
|
|
267
276
|
["caller closure claim is not authority", "a caller-supplied closed is not authority"],
|
|
268
277
|
["runtime writer profile", "runtime-recorded model and effort of the pending mutations"],
|
|
269
278
|
["caller profile is only a fallback", "only a fallback when no runtime evidence exists"],
|
|
270
|
-
["
|
|
279
|
+
["writer profile never adds a verifier", "the writer profile is reported but never adds a verifier"],
|
|
280
|
+
["independent verifier only through escalate", "only when escalate names a task size high-risk item"],
|
|
271
281
|
["native review never replaces verification (verify-always-rdd-high S1)", "native review is an additional outside view"],
|
|
272
282
|
["functional checks still run", "a native code review is not a substitute for applicable functional checks"],
|
|
273
283
|
];
|
|
@@ -280,6 +290,14 @@ test("the three verification policy surfaces carry the same ASSESS closure, prof
|
|
|
280
290
|
}
|
|
281
291
|
});
|
|
282
292
|
|
|
293
|
+
test("no verification policy surface still carries the retired small-model bias or unassessable-as-high rule", () => {
|
|
294
|
+
for (const [surface, text] of POLICY_SURFACES) {
|
|
295
|
+
for (const stale of ["small-model bias", "treated as high", "verified exactly like high", "fail closed to high"]) {
|
|
296
|
+
assert.ok(!text.includes(stale), `${surface} still carries the retired wording: "${stale}"`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
283
301
|
test("no verification policy surface still tells callers to pass closed explicitly (gentle-pi#1175)", () => {
|
|
284
302
|
for (const [surface, text] of POLICY_SURFACES) {
|
|
285
303
|
for (const stale of ["closed is never derived", "closed is never auto-derived", "closed is never inferred", "pass it explicitly", "pass it only right after"]) {
|
|
@@ -2,7 +2,10 @@ import assert from "node:assert/strict";
|
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import { __testing } from "../extensions/gentle-ai.ts";
|
|
4
4
|
import {
|
|
5
|
+
NATIVE_REVIEW_ERROR_CODE,
|
|
5
6
|
NATIVE_REVIEW_MODE_OPERATION,
|
|
7
|
+
NATIVE_REVIEW_OPERATION,
|
|
8
|
+
NativeReviewCliError,
|
|
6
9
|
NATIVE_REVIEW_MODE_SOURCE,
|
|
7
10
|
type NativeReviewCli,
|
|
8
11
|
type NativeReviewModeRequest,
|
|
@@ -31,6 +34,7 @@ import {
|
|
|
31
34
|
|
|
32
35
|
const {
|
|
33
36
|
renderRddStatusLine,
|
|
37
|
+
pinRddStatusLine,
|
|
34
38
|
resolveRddModeStatus,
|
|
35
39
|
resolveRddStatusLine,
|
|
36
40
|
getOrchestratorPrompt,
|
|
@@ -358,3 +362,78 @@ test("resolveRddModeStatus stays unknown when no status was ever read", async ()
|
|
|
358
362
|
const cli = fakeReviewMode(() => { throw new Error("never available"); });
|
|
359
363
|
assert.equal(await resolveRddModeStatus(cli, "/repo-never-known"), undefined);
|
|
360
364
|
});
|
|
365
|
+
|
|
366
|
+
// gentle-shell#2250: the one-shot unavailable notice must name the cause so a
|
|
367
|
+
// user on a slow or foreign-owned filesystem can tell a timeout from a native
|
|
368
|
+
// refusal instead of reading a generic "absent, timed out, or failed".
|
|
369
|
+
function captureNotices(): { ctx: { hasUI: true; ui: { notify(message: string, level: string): void } }; messages: string[] } {
|
|
370
|
+
const messages: string[] = [];
|
|
371
|
+
return { ctx: { hasUI: true, ui: { notify(message: string) { messages.push(message); } } }, messages };
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
test("the unavailable notice names the native failure and its stderr", async () => {
|
|
375
|
+
clearRddStatusMemoForTesting();
|
|
376
|
+
const { ctx, messages } = captureNotices();
|
|
377
|
+
const cli = fakeReviewMode(() => {
|
|
378
|
+
throw new NativeReviewCliError(NATIVE_REVIEW_ERROR_CODE.NON_ZERO, NATIVE_REVIEW_OPERATION.MODE, true, false, "native process failed", {
|
|
379
|
+
operation: NATIVE_REVIEW_OPERATION.MODE,
|
|
380
|
+
error_code: NATIVE_REVIEW_ERROR_CODE.NON_ZERO,
|
|
381
|
+
exit_code: 1,
|
|
382
|
+
timed_out: false,
|
|
383
|
+
output_limit_exceeded: false,
|
|
384
|
+
stderr: "fatal: detected dubious ownership in repository",
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
assert.equal(await resolveRddModeStatus(cli, "/repo-native-failure", undefined, Date.now, ctx as never), undefined);
|
|
388
|
+
assert.equal(messages.length, 1);
|
|
389
|
+
assert.match(messages[0], /status is unavailable: native `gentle-ai review mode status` failed \(non-zero, exit 1\): fatal: detected dubious ownership in repository\./);
|
|
390
|
+
assert.match(messages[0], /keeps the last known status/);
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test("the unavailable notice reports a timeout distinctly", async () => {
|
|
394
|
+
clearRddStatusMemoForTesting();
|
|
395
|
+
const { ctx, messages } = captureNotices();
|
|
396
|
+
const keepalive = setTimeout(() => {}, 2000);
|
|
397
|
+
try {
|
|
398
|
+
await resolveRddModeStatus(fakeReviewMode(() => new Promise<never>(() => {})), "/repo-timeout", AbortSignal.timeout(50), Date.now, ctx as never);
|
|
399
|
+
} finally {
|
|
400
|
+
clearTimeout(keepalive);
|
|
401
|
+
}
|
|
402
|
+
assert.equal(messages.length, 1);
|
|
403
|
+
assert.match(messages[0], new RegExp(`status is unavailable: native \`gentle-ai review mode status\` did not answer within ${RDD_STATUS_TIMEOUT_MS} ms\.`));
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test("the unavailable notice reports an absent native review CLI", async () => {
|
|
407
|
+
clearRddStatusMemoForTesting();
|
|
408
|
+
const { ctx, messages } = captureNotices();
|
|
409
|
+
await resolveRddModeStatus(null, "/repo-absent", undefined, Date.now, ctx as never);
|
|
410
|
+
assert.equal(messages.length, 1);
|
|
411
|
+
assert.match(messages[0], /status is unavailable: the native gentle-ai review CLI is not available\./);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
test("the unavailable notice still fires only once per session", async () => {
|
|
415
|
+
clearRddStatusMemoForTesting();
|
|
416
|
+
const { ctx, messages } = captureNotices();
|
|
417
|
+
const cli = fakeReviewMode(() => { throw new Error("boom"); });
|
|
418
|
+
await resolveRddModeStatus(cli, "/repo-once-a", undefined, Date.now, ctx as never);
|
|
419
|
+
await resolveRddModeStatus(cli, "/repo-once-b", undefined, Date.now, ctx as never);
|
|
420
|
+
assert.equal(messages.length, 1);
|
|
421
|
+
assert.match(messages[0], /status is unavailable: boom\./);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
test("pinRddStatusLine keeps the session's first line until the off state changes (#2195)", () => {
|
|
425
|
+
const unknown = "Receipt-driven development: unknown (native status unavailable)";
|
|
426
|
+
const onGlobal = "Receipt-driven development: on (decided by global)";
|
|
427
|
+
const onClone = "Receipt-driven development: on (decided by clone)";
|
|
428
|
+
const off = "Receipt-driven development: off (decided by clone)";
|
|
429
|
+
const offGlobal = "Receipt-driven development: off (decided by global)";
|
|
430
|
+
assert.equal(pinRddStatusLine(undefined, onGlobal), onGlobal, "the first render of a session is the resolved line");
|
|
431
|
+
assert.equal(pinRddStatusLine(unknown, onGlobal), unknown, "unknown to on keeps the cached prompt prefix");
|
|
432
|
+
assert.equal(pinRddStatusLine(onGlobal, unknown), onGlobal, "on to unknown keeps the cached prompt prefix");
|
|
433
|
+
assert.equal(pinRddStatusLine(onGlobal, onClone), onGlobal, "a deciding-source change alone keeps the prefix");
|
|
434
|
+
assert.equal(pinRddStatusLine(off, offGlobal), off, "a source change while off keeps the prefix");
|
|
435
|
+
assert.equal(pinRddStatusLine(onGlobal, off), off, "turning RDD off re-renders, since it drops the review contract");
|
|
436
|
+
assert.equal(pinRddStatusLine(unknown, off), off, "unknown to off re-renders");
|
|
437
|
+
assert.equal(pinRddStatusLine(off, onGlobal), onGlobal, "turning RDD back on re-renders");
|
|
438
|
+
assert.equal(pinRddStatusLine(off, unknown), unknown, "off to unknown re-renders, since unknown keeps the review contract");
|
|
439
|
+
});
|
|
@@ -128,13 +128,18 @@ function toolContext(context: ExtensionContext): ExtensionToolContext {
|
|
|
128
128
|
async function withSessionStartEnv<T>(callback: (cwd: string) => Promise<T>, initializeGit = true): Promise<T> {
|
|
129
129
|
const previousAgentHome = process.env.GENTLE_PI_AGENT_HOME;
|
|
130
130
|
const previousConfigHome = process.env.GENTLE_PI_CONFIG_HOME;
|
|
131
|
+
// Each call made three temp dirs and never removed them (87 left behind per suite run).
|
|
132
|
+
const created: string[] = [];
|
|
131
133
|
process.env.GENTLE_PI_AGENT_HOME = await mkdtemp(join(tmpdir(), "gentle-pi-session-baseline-agent-home-"));
|
|
134
|
+
created.push(process.env.GENTLE_PI_AGENT_HOME);
|
|
132
135
|
// Isolates both the model-config sweep and the dev-binary registration
|
|
133
136
|
// lookup from this machine's real ~/.pi/gentle-ai, so `session_start`'s
|
|
134
137
|
// unrelated notifications never leak into these assertions.
|
|
135
138
|
process.env.GENTLE_PI_CONFIG_HOME = await mkdtemp(join(tmpdir(), "gentle-pi-session-baseline-config-home-"));
|
|
139
|
+
created.push(process.env.GENTLE_PI_CONFIG_HOME);
|
|
136
140
|
try {
|
|
137
141
|
const cwd = await mkdtemp(join(tmpdir(), "gentle-pi-session-baseline-cwd-"));
|
|
142
|
+
created.push(cwd);
|
|
138
143
|
if (initializeGit) childProcess.execFileSync("git", ["init", "--quiet", cwd]);
|
|
139
144
|
await mkdir(join(cwd, "src"));
|
|
140
145
|
await writeFile(join(cwd, "src/example.ts"), "export const value = 1;");
|
|
@@ -144,6 +149,7 @@ async function withSessionStartEnv<T>(callback: (cwd: string) => Promise<T>, ini
|
|
|
144
149
|
else process.env.GENTLE_PI_AGENT_HOME = previousAgentHome;
|
|
145
150
|
if (previousConfigHome === undefined) delete process.env.GENTLE_PI_CONFIG_HOME;
|
|
146
151
|
else process.env.GENTLE_PI_CONFIG_HOME = previousConfigHome;
|
|
152
|
+
for (const dir of created) await rm(dir, { recursive: true, force: true });
|
|
147
153
|
}
|
|
148
154
|
}
|
|
149
155
|
|
|
@@ -1326,6 +1326,55 @@ test("candidate view recursively protects nested content and worktree metadata,
|
|
|
1326
1326
|
}
|
|
1327
1327
|
});
|
|
1328
1328
|
|
|
1329
|
+
// macOS File Provider (iCloud-synced Desktop/Documents) asynchronously resets
|
|
1330
|
+
// directory modes to exactly 0700 while leaving file modes intact.
|
|
1331
|
+
function simulateFileProviderDirectoryReset(root: string, directories: readonly string[]): void {
|
|
1332
|
+
for (const directory of directories) chmodSync(join(root, directory), 0o700);
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
test("candidate view repairs owner-only directories reset by macOS File Provider and stays verifiable", { skip: process.platform === "win32" }, (t) => {
|
|
1336
|
+
const contributorRoot = repository(t);
|
|
1337
|
+
mkdirSync(join(contributorRoot, "nested", "deeper"), { recursive: true });
|
|
1338
|
+
writeFileSync(join(contributorRoot, "nested", "deeper", "candidate.txt"), "candidate\n");
|
|
1339
|
+
const view = createCandidateView({ contributorRoot });
|
|
1340
|
+
try {
|
|
1341
|
+
simulateFileProviderDirectoryReset(view.root, ["nested/deeper", "nested", "."]);
|
|
1342
|
+
view.verify();
|
|
1343
|
+
for (const directory of [".", "nested", "nested/deeper"]) assert.equal(lstatSync(join(view.root, directory)).mode & 0o777, 0o555, directory);
|
|
1344
|
+
assert.equal(lstatSync(join(view.root, "nested", "deeper", "candidate.txt")).mode & 0o777, 0o444);
|
|
1345
|
+
} finally {
|
|
1346
|
+
view.cleanup();
|
|
1347
|
+
}
|
|
1348
|
+
});
|
|
1349
|
+
|
|
1350
|
+
test("candidate view does not repair owner-only directories carrying special mode bits", { skip: process.platform === "win32" }, (t) => {
|
|
1351
|
+
const contributorRoot = repository(t);
|
|
1352
|
+
mkdirSync(join(contributorRoot, "nested"), { recursive: true });
|
|
1353
|
+
writeFileSync(join(contributorRoot, "nested", "candidate.txt"), "candidate\n");
|
|
1354
|
+
const view = createCandidateView({ contributorRoot });
|
|
1355
|
+
try {
|
|
1356
|
+
chmodSync(join(view.root, "nested"), 0o1700);
|
|
1357
|
+
assert.throws(() => view.verify(), /candidate view directory is unsafe or writable/);
|
|
1358
|
+
} finally {
|
|
1359
|
+
view.cleanup();
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
test("candidate view refuses an ignored entry injected into an owner-only directory it would repair", { skip: process.platform === "win32" }, (t) => {
|
|
1364
|
+
const contributorRoot = repository(t);
|
|
1365
|
+
mkdirSync(join(contributorRoot, "nested"), { recursive: true });
|
|
1366
|
+
writeFileSync(join(contributorRoot, ".gitignore"), "*.log\n");
|
|
1367
|
+
writeFileSync(join(contributorRoot, "nested", "candidate.txt"), "candidate\n");
|
|
1368
|
+
const view = createCandidateView({ contributorRoot });
|
|
1369
|
+
try {
|
|
1370
|
+
simulateFileProviderDirectoryReset(view.root, ["nested"]);
|
|
1371
|
+
writeFileSync(join(view.root, "nested", "injected.log"), "injected\n");
|
|
1372
|
+
assert.throws(() => view.verify(), /candidate view directory contains unexpected entries/);
|
|
1373
|
+
} finally {
|
|
1374
|
+
view.cleanup();
|
|
1375
|
+
}
|
|
1376
|
+
});
|
|
1377
|
+
|
|
1329
1378
|
test("candidate view registry rejects unsafe, moved, writable, stale, and unselected lens contexts before dispatch", (t) => {
|
|
1330
1379
|
const contributorRoot = repository(t);
|
|
1331
1380
|
writeFileSync(join(contributorRoot, "tracked.txt"), "candidate\n");
|
|
@@ -109,9 +109,12 @@ test("before_agent_start injects the mirrored review execution contract for the
|
|
|
109
109
|
assert.match(appended, /no meaningful RED, explain why and run proportionate ordinary functional or structural verification/);
|
|
110
110
|
assert.match(appended, /Never invent lifecycle evidence or skip checks/);
|
|
111
111
|
assert.doesNotMatch(appended, /If tests exist, use strict TDD/);
|
|
112
|
-
|
|
112
|
+
// S11 T3: deduplicated. The core pointer sentence restated the harness; the
|
|
113
|
+
// mandatory ODD entry is injected once, from the harness steps' header.
|
|
114
|
+
assert.match(appended, /Organic Driven Development \(ODD\) is the predefined workflow of this orchestrator\. Every request enters it/);
|
|
115
|
+
assert.doesNotMatch(appended, /ODD \(Default Workflow, harness section above\)/);
|
|
113
116
|
assert.doesNotMatch(appended, /Prefer SDD\/OpenSpec artifacts/);
|
|
114
|
-
assert.match(appended, /## Gentle AI review execution contract \(mirrored provider bundle 1\.
|
|
117
|
+
assert.match(appended, /## Gentle AI review execution contract \(mirrored provider bundle 1\.4\.0\)/);
|
|
115
118
|
assert.ok(appended.includes(expected), "the mirrored orchestration/pi.md text must appear verbatim");
|
|
116
119
|
assert.match(appended, /call `gentle_review` with {"operation":"inspect"}/);
|
|
117
120
|
assert.match(appended, /call `gentle_review` with operation `status`, the exact retained `lineageId`, and `workspaceRoot`/);
|
|
@@ -6,13 +6,24 @@ import fsp from "node:fs/promises";
|
|
|
6
6
|
import os, { tmpdir } from "node:os";
|
|
7
7
|
import { syncBuiltinESMExports } from "node:module";
|
|
8
8
|
import { dirname, join, resolve, sep } from "node:path";
|
|
9
|
-
import test from "node:test";
|
|
9
|
+
import test, { after, before } from "node:test";
|
|
10
|
+
import { setWindowsAclAuthorityForTesting } from "../lib/review-candidate-view-owner.ts";
|
|
10
11
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
11
12
|
import { __testing, createGentleAiExtension, PendingReviewConsentRegistry } from "../extensions/gentle-ai.ts";
|
|
12
13
|
import { CandidateViewError, CandidateViewRegistry } from "../lib/review-candidate-view.ts";
|
|
13
14
|
import { NATIVE_REVIEW_ERROR_CODE, NativeReviewCliError, NativeReviewConsentRequiredError, type NativeReviewCli } from "../lib/native-review-cli.ts";
|
|
14
15
|
import { decodeReviewLastEventClosureV1, decodeReviewConsentV3, decodeReviewStatusV3, type ReviewCollectInputV3, type ReviewStatusV3 } from "../lib/review-integration-v2.ts";
|
|
15
16
|
|
|
17
|
+
// Coordination tests use fixture authority; real Windows ACL coverage lives
|
|
18
|
+
// in review-candidate-view.test.ts, in a separate test process.
|
|
19
|
+
let fixtureAclChecks = 0;
|
|
20
|
+
before(() => {
|
|
21
|
+
if (process.platform === "win32") {
|
|
22
|
+
setWindowsAclAuthorityForTesting(() => { fixtureAclChecks++; });
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
after(() => setWindowsAclAuthorityForTesting());
|
|
26
|
+
|
|
16
27
|
const SHA = `sha256:${"a".repeat(64)}`;
|
|
17
28
|
const TREE = "b".repeat(40);
|
|
18
29
|
|
|
@@ -1076,7 +1087,9 @@ function reviewContext(cwd: string): ExtensionContext {
|
|
|
1076
1087
|
|
|
1077
1088
|
function startStatus(cwd: string, baseRef?: string, intendedUntracked: readonly string[] = []): ReviewStatusV3 {
|
|
1078
1089
|
const candidateViews = new CandidateViewRegistry();
|
|
1090
|
+
const checksBefore = fixtureAclChecks;
|
|
1079
1091
|
const view = candidateViews.create({ contributorRoot: cwd, intendedUntracked, ...(baseRef === undefined ? {} : { baseRef, committedOnly: true }) });
|
|
1092
|
+
if (process.platform === "win32") assert.ok(fixtureAclChecks > checksBefore, "coordination candidates must use the fixture ACL authority");
|
|
1080
1093
|
try {
|
|
1081
1094
|
return {
|
|
1082
1095
|
contract: "gentle-ai.review-integration/v2",
|
|
@@ -406,7 +406,7 @@ test("shipped controller fails closed while static prompts defer RDD lifecycle o
|
|
|
406
406
|
const core = readFileSync("assets/orchestrator.md", "utf8");
|
|
407
407
|
assert.match(core, new RegExp(boundary.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
|
|
408
408
|
|
|
409
|
-
for (const path of ["assets/orchestrator-delegation.md", "skills/gentle-ai/SKILL.md"]) {
|
|
409
|
+
for (const path of ["assets/orchestrator-delegation.md", "assets/orchestrator-delegation-reference.md", "skills/gentle-ai/SKILL.md"]) {
|
|
410
410
|
const contract = readFileSync(path, "utf8");
|
|
411
411
|
assert.doesNotMatch(contract, /INSPECT before START|start -> finalize -> validate|next_transition|review\.capture-result/is, path);
|
|
412
412
|
}
|