gentle-pi 4.1.0 → 4.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +352 -128
- 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/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 +9 -9
- package/bin/engram-guard +1 -1
- package/bin/gentle-shell.mjs +188 -35
- 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/auto-permission-answers.md +25 -0
- package/docs/auto-permission-evidence.md +39 -0
- package/docs/bundled-install.md +40 -8
- package/docs/delegated-verification.md +10 -8
- package/docs/gentle-agents-activity.md +11 -5
- package/docs/gentle-shell-uninstall.md +8 -1
- package/docs/gentle-shell.md +20 -12
- package/docs/install-wizard.md +17 -10
- package/docs/permission-modes.md +51 -6
- package/docs/readme-reference.md +155 -31
- package/docs/session-profile-format.md +39 -0
- package/docs/yolo-mode.md +21 -7
- package/extensions/ask-user-choice.ts +4 -0
- package/extensions/ask-user-question.ts +6 -1
- package/extensions/codegraph-tools.ts +68 -6
- package/extensions/gentle-agents.ts +146 -28
- package/extensions/gentle-ai.ts +380 -68
- package/extensions/gentle-herdr-activity.ts +2 -3
- package/extensions/gentle-shell.ts +53 -43
- package/extensions/gentle-todo.ts +62 -18
- package/extensions/quiet-tools.ts +1 -1
- package/extensions/session-scratch.ts +365 -0
- package/extensions/startup-banner.ts +2 -3
- package/lib/agent-home.ts +8 -3
- package/lib/agents-protocol.ts +3 -0
- package/lib/agents-runner.ts +55 -6
- package/lib/agents-view.ts +3 -1
- package/lib/auto-permission-answers.ts +133 -0
- package/lib/auto-permission-classifier.ts +215 -36
- package/lib/auto-permission-evidence.ts +88 -0
- package/lib/auto-permission-memory.ts +69 -0
- package/lib/auto-permission-runtime.ts +251 -35
- package/lib/auto-permission-tool-context.ts +61 -0
- package/lib/bounded-writer-admission.ts +51 -0
- package/lib/command-palette-catalog.ts +0 -4
- package/lib/commit-slice-signal.ts +100 -0
- package/lib/context-backstop.ts +84 -0
- package/lib/delivered-test-lint.ts +80 -0
- package/lib/destructive-command-guard.ts +215 -28
- package/lib/gauge-theme.ts +146 -0
- package/lib/gentle-ai-binary.ts +28 -7
- package/lib/gentle-shell-bundled.ts +62 -8
- package/lib/gentle-shell-import-pi.ts +233 -0
- package/lib/gentle-shell-launcher.ts +146 -12
- package/lib/gentle-shell-uninstall.ts +18 -1
- package/lib/herdr-activity.ts +24 -0
- package/lib/model-routing-authority.ts +6 -1
- package/lib/native-review-cli.ts +9 -1
- package/lib/orchestrator-discovery.ts +5 -1
- package/lib/orchestrator-presence.ts +9 -0
- package/lib/orchestrator-scope.ts +21 -3
- 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-risk-assessment.ts +43 -38
- package/lib/review-session-standing-permission.ts +3 -1
- package/lib/selection-engine.ts +4 -1
- package/lib/session-profile-freeze.ts +40 -16
- package/lib/session-profile-persistence.ts +187 -0
- package/lib/session-scratch.ts +474 -0
- package/lib/shell-bar.ts +25 -25
- package/lib/shell-changes-view.ts +80 -13
- package/lib/shell-changes.ts +2 -0
- package/lib/shell-gauge.ts +49 -6
- package/lib/shell-prompt.ts +6 -4
- package/lib/shell-todo.ts +39 -8
- package/lib/shell-usage-view.ts +5 -1
- package/lib/shell-usage.ts +7 -7
- package/lib/verifier-admission.ts +29 -0
- package/lib/vim-editor-adapter.ts +22 -4
- package/lib/yolo-temp-scratch.ts +69 -0
- package/package.json +5 -6
- package/runtime/gentle-ai-binary.mjs +28 -7
- package/runtime/gentle-shell-bundled.mjs +61 -7
- package/runtime/gentle-shell-import-pi.mjs +234 -0
- package/runtime/gentle-shell-launcher.mjs +144 -10
- package/runtime/gentle-shell-uninstall.mjs +18 -1
- package/runtime/native-review-cli.mjs +8 -0
- package/runtime/review-risk-assessment.mjs +43 -38
- package/scripts/build-runtime-modules.mjs +1 -0
- package/scripts/bundled-engram-migration.mjs +125 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/install-wizard-preview.mjs +2 -2
- package/scripts/installer-downloads.mjs +7 -7
- package/scripts/installer-preflight.mjs +1 -1
- package/scripts/installer-server.mjs +14 -4
- package/scripts/main-channel.mjs +3 -2
- package/scripts/pi-update.mjs +223 -0
- package/scripts/run-test-suite.mjs +5 -0
- package/scripts/verify-package-files.mjs +19 -13
- package/skills/gentle-ai/SKILL.md +3 -3
- package/skills/work-unit-commits/SKILL.md +5 -3
- package/tests/agent-risk-escalation.test.ts +37 -10
- package/tests/agents-runner.test.ts +260 -3
- 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 +23 -5
- package/tests/auto-mode-sdk.test.ts +29 -2
- package/tests/auto-permission-answers.test.ts +170 -0
- package/tests/auto-permission-classifier.test.ts +353 -2
- 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 +270 -12
- package/tests/autonomous-guard.test.ts +208 -16
- 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/codegraph-tools.test.ts +123 -2
- package/tests/command-palette.test.ts +14 -4
- package/tests/commit-slice-signal.test.ts +162 -0
- package/tests/context-backstop.test.ts +159 -0
- package/tests/delegate-for-reason-contract.test.ts +91 -24
- package/tests/delivered-test-lint.test.ts +105 -0
- package/tests/destructive-command-guard.test.ts +85 -1
- package/tests/engram-safety.test.ts +35 -7
- 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 +443 -8
- 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-shell-bin.test.ts +180 -17
- package/tests/gentle-shell-bundled.test.ts +235 -38
- package/tests/gentle-shell-import-pi.test.ts +262 -0
- package/tests/gentle-shell-launcher.test.ts +97 -13
- package/tests/gentle-shell-uninstall.test.ts +30 -0
- package/tests/gentle-shell.test.ts +113 -14
- package/tests/gentle-todo.test.ts +258 -27
- package/tests/headless-delivery-question.test.ts +58 -0
- package/tests/herdr-activity.test.ts +34 -1
- package/tests/install-wizard.test.ts +2 -2
- package/tests/installer-posix-bootstrap.test.ts +8 -8
- package/tests/installer-preflight.test.ts +3 -3
- package/tests/installer-probes.test.ts +8 -8
- package/tests/installer-runner.test.ts +14 -13
- package/tests/installer-server.test.ts +13 -5
- package/tests/installer-windows-bootstrap.test.ts +33 -4
- package/tests/main-channel-upgrade.test.ts +7 -1
- package/tests/native-review-capability-contract.test.ts +7 -1
- package/tests/native-review-parity.test.ts +22 -1
- package/tests/no-self-pause-contract.test.ts +5 -2
- package/tests/odd-integration.test.ts +5 -1
- package/tests/odd-routing-canonical-ratchet.test.ts +127 -162
- package/tests/odd-routing-contract.test.ts +115 -30
- package/tests/odd-runtime-delegation-gate.test.ts +4 -1
- package/tests/orchestrator-budget.test.ts +19 -8
- package/tests/orchestrator-discovery.test.ts +29 -0
- package/tests/orchestrator-rdd-ownership.test.ts +3 -1
- package/tests/orchestrator-scope.test.ts +66 -0
- package/tests/orchestrator-status-publication-contract.test.ts +12 -8
- package/tests/package-manifest.test.ts +28 -24
- package/tests/pi-update-launcher.test.ts +106 -0
- package/tests/pi-update.test.ts +186 -0
- 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 +131 -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 +59 -2
- package/tests/review-loadout-rdd-off-sdk.test.ts +237 -0
- package/tests/review-owner-identity.test.ts +132 -0
- 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/selection-engine.test.ts +35 -5
- package/tests/session-changes-shell.test.ts +1 -1
- package/tests/session-profile-freeze.test.ts +37 -1
- package/tests/session-profile-persistence.test.ts +334 -0
- package/tests/session-scratch.test.ts +945 -0
- package/tests/session-subject-routing-contract.test.ts +2 -1
- package/tests/shell-bar.test.ts +7 -0
- package/tests/shell-changes-view.test.ts +137 -1
- package/tests/shell-changes.test.ts +18 -0
- package/tests/shell-gauge.test.ts +42 -0
- package/tests/shell-todo.test.ts +15 -12
- 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 +69 -9
- 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 +137 -0
- package/tests/verify-agent-contract.test.ts +8 -8
- package/tests/vim-editor-adapter.test.ts +41 -15
- package/tests/windows-hidden-processes.test.ts +5 -0
- package/tests/writer-edit-surface-scope.test.ts +3 -0
- package/tests/writer-feature-document-admission.test.ts +156 -0
- package/tests/yolo-mode.test.ts +24 -1
- 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
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { __testing } from "../extensions/gentle-ai.ts";
|
|
4
|
+
import { INTERACTIVE_HOST_ENV } from "../lib/rpc-host.ts";
|
|
5
|
+
|
|
6
|
+
// Bench gate-p53-r1-BS40 x6 run 3 (headless RPC): the ask-on-risk delivery
|
|
7
|
+
// question ran before any code was written; ask_user_question answered
|
|
8
|
+
// "unavailable outside the interactive TUI", the orchestrator printed the menu
|
|
9
|
+
// and stopped, and the round ended with "No code changed yet". When no human
|
|
10
|
+
// can answer, the delivery question resolves to the recommended chain instead.
|
|
11
|
+
|
|
12
|
+
const HEADLESS_DELIVERY = "No human can answer questions in this session";
|
|
13
|
+
|
|
14
|
+
function promptFor(mode: string | undefined): string {
|
|
15
|
+
return __testing.buildGentlePrompt("neutral", process.cwd(), undefined, undefined, mode);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function withInteractiveHost<T>(value: string | undefined, run: () => T): T {
|
|
19
|
+
const previous = process.env[INTERACTIVE_HOST_ENV];
|
|
20
|
+
if (value === undefined) delete process.env[INTERACTIVE_HOST_ENV];
|
|
21
|
+
else process.env[INTERACTIVE_HOST_ENV] = value;
|
|
22
|
+
try {
|
|
23
|
+
return run();
|
|
24
|
+
} finally {
|
|
25
|
+
if (previous === undefined) delete process.env[INTERACTIVE_HOST_ENV];
|
|
26
|
+
else process.env[INTERACTIVE_HOST_ENV] = previous;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test("headless host modes resolve the ask-on-risk delivery question to the feature-branch chain without asking", () => {
|
|
31
|
+
withInteractiveHost(undefined, () => {
|
|
32
|
+
for (const mode of ["rpc", "print", "json"]) {
|
|
33
|
+
const prompt = promptFor(mode);
|
|
34
|
+
assert.ok(prompt.includes(HEADLESS_DELIVERY), `${mode} must say no human can answer`);
|
|
35
|
+
assert.match(prompt, /ask-on-risk/, `${mode} must name the ask-on-risk delivery question`);
|
|
36
|
+
assert.match(prompt, /`feature-branch-chain`/, `${mode} must name the chain it resolves to`);
|
|
37
|
+
assert.match(prompt, /feature document/, `${mode} must record the choice in the feature document`);
|
|
38
|
+
assert.match(prompt, /consent/, `${mode} must keep consent prompts out of the default`);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("interactive hosts keep asking the delivery question", () => {
|
|
44
|
+
withInteractiveHost(undefined, () => {
|
|
45
|
+
for (const mode of ["tui", undefined]) assert.ok(!promptFor(mode).includes(HEADLESS_DELIVERY), `${String(mode)} keeps the question`);
|
|
46
|
+
});
|
|
47
|
+
withInteractiveHost("1", () => {
|
|
48
|
+
assert.ok(!promptFor("rpc").includes(HEADLESS_DELIVERY), "an interactive RPC host keeps the question");
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("the reference documents the headless delivery default", async () => {
|
|
53
|
+
const { readFileSync } = await import("node:fs");
|
|
54
|
+
const reference = readFileSync(new URL("../docs/readme-reference.md", import.meta.url), "utf8");
|
|
55
|
+
for (const term of ["no human can answer questions", "`GENTLE_SHELL_INTERACTIVE_HOST=1`", "`ask-on-risk` does not ask", "`feature-branch-chain`"]) {
|
|
56
|
+
assert.ok(reference.includes(term), `docs/readme-reference.md is missing ${term}`);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
@@ -4,7 +4,7 @@ import childProcess from "node:child_process";
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import { syncBuiltinESMExports } from "node:module";
|
|
6
6
|
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
7
|
-
import { activitySummary, ActivityPublisher, createActivityColumns, metadataArgs, metadataTransport } from "../lib/herdr-activity.ts";
|
|
7
|
+
import { activitySummary, ActivityPublisher, createActivityColumns, herdrDetected, herdrSetupHint, metadataArgs, metadataTransport } from "../lib/herdr-activity.ts";
|
|
8
8
|
|
|
9
9
|
const tasks = [{ title: "Revisar integración Herdr", status: "in_progress" }];
|
|
10
10
|
const project = (title: string, columns = 30) => activitySummary([{ title, status: "in_progress" }], columns);
|
|
@@ -241,3 +241,36 @@ test("geometry is cached, local/named, bounded and fail-safe without reading use
|
|
|
241
241
|
assert.equal(paths.length, before, "relative paths must not read arbitrary snapshots");
|
|
242
242
|
} finally { for (const stub of stubs) stub.mock.restore(); syncBuiltinESMExports(); }
|
|
243
243
|
});
|
|
244
|
+
|
|
245
|
+
const herdrEnv = { HERDR_ENV: "1", HERDR_PANE_ID: "pane", HERDR_SOCKET_PATH: "/herdr.sock" };
|
|
246
|
+
|
|
247
|
+
test("Herdr detection uses the activity gate: HERDR_ENV=1 plus nonblank pane and socket", () => {
|
|
248
|
+
assert.equal(herdrDetected(herdrEnv), true);
|
|
249
|
+
for (const env of [{}, { ...herdrEnv, HERDR_ENV: "0" }, { ...herdrEnv, HERDR_PANE_ID: " " }, { ...herdrEnv, HERDR_SOCKET_PATH: "" }]) {
|
|
250
|
+
assert.equal(herdrDetected(env), false);
|
|
251
|
+
assert.deepEqual(herdrSetupHint(env, { interactive: true, isSocket: () => true }), []);
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
test("setup hint states active publishing and gives read-only sidebar guidance", () => {
|
|
256
|
+
const sockets: string[] = [];
|
|
257
|
+
const lines = herdrSetupHint(herdrEnv, { interactive: true, isSocket: (path) => { sockets.push(path); return true; } });
|
|
258
|
+
assert.deepEqual(sockets, ["/herdr.sock"]);
|
|
259
|
+
assert.deepEqual(lines, [
|
|
260
|
+
"Herdr",
|
|
261
|
+
"pass: Herdr task-summary publishing active",
|
|
262
|
+
"info: Herdr's default sidebar does not show task titles; add these two rows to the Agents sidebar rows in your Herdr config: ['$summary'], ['$summary2']",
|
|
263
|
+
"info: Sidebar rows belong to the config of the Herdr client that displays the session, including when Pi runs over SSH",
|
|
264
|
+
"info: After editing, reload or restart Herdr; Gentle Shell never changes Herdr config",
|
|
265
|
+
"info: Details: https://github.com/Gentleman-Programming/gentle-shell/blob/main/docs/readme-reference.md#herdr-active-work-summary",
|
|
266
|
+
]);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("setup hint names why publishing is inactive and keeps the guidance", () => {
|
|
270
|
+
const reason = (env: NodeJS.ProcessEnv, interactive: boolean, socket: boolean) =>
|
|
271
|
+
herdrSetupHint(env, { interactive, isSocket: () => socket })[1];
|
|
272
|
+
assert.equal(reason({ ...herdrEnv, GENTLE_PI_AGENTS_CHILD: "1" }, true, true), "warn: Herdr task-summary publishing inactive: Gentle Agents child session");
|
|
273
|
+
assert.equal(reason(herdrEnv, false, true), "warn: Herdr task-summary publishing inactive: requires the interactive Pi TUI");
|
|
274
|
+
assert.equal(reason(herdrEnv, true, false), "warn: Herdr task-summary publishing inactive: HERDR_SOCKET_PATH is not a Unix socket");
|
|
275
|
+
assert.equal(herdrSetupHint(herdrEnv, { interactive: false, isSocket: () => true }).length, 6);
|
|
276
|
+
});
|
|
@@ -399,7 +399,7 @@ test("the review screen shows the notice about reused tools in folders another a
|
|
|
399
399
|
});
|
|
400
400
|
|
|
401
401
|
test("a recoverable stack is reviewed as completing setup, not as a reinstall", async () => {
|
|
402
|
-
const view = await serverPlanView({ pi: { available: true, version: "1.
|
|
402
|
+
const view = await serverPlanView({ pi: { available: true, version: "1.2.0", usable: true },
|
|
403
403
|
shell: { available: true, version: requirements.shell, usable: true, global: true },
|
|
404
404
|
gentleAi: { available: true, version: requirements.gentleAi, usable: true, compatible: true },
|
|
405
405
|
setup: { available: true, recoverable: true } });
|
|
@@ -563,7 +563,7 @@ test("expectedSteps checks, updates and verifies an older Pi before any Gentle S
|
|
|
563
563
|
test("a plan that only updates an older Pi is reviewed as a Pi update, with an Update Pi button", () => {
|
|
564
564
|
const document = new FakeDocument();
|
|
565
565
|
const model = wizard.planModel({ planId: "p", ready: false, channel: "release", blockers: [],
|
|
566
|
-
actions: [{ id: "update-pi", description: "Update Pi 0.87.1 to 1.
|
|
566
|
+
actions: [{ id: "update-pi", description: "Update Pi 0.87.1 to 1.2.0 with npm." }, { id: "verify-readiness", description: "Verify." }] });
|
|
567
567
|
assert.equal(model.kind, "update-pi");
|
|
568
568
|
const view = wizard.renderPlan(document, model, { install: () => {}, reload: () => {}, close: () => {} });
|
|
569
569
|
assert.ok(headings(view)[0].textContent.includes("Update Pi"));
|
|
@@ -934,19 +934,19 @@ test("Go has no descriptor for other targets", () => {
|
|
|
934
934
|
// The Engram release the bundled install ships (S9), from its checksums.txt and
|
|
935
935
|
// the GitHub asset digests; every archive was downloaded and re-hashed.
|
|
936
936
|
const engramPins = {
|
|
937
|
-
"darwin-arm64": ["darwin_arm64.tar.gz", "
|
|
938
|
-
"darwin-x64": ["darwin_amd64.tar.gz", "
|
|
939
|
-
"linux-x64": ["linux_amd64.tar.gz", "
|
|
940
|
-
"linux-arm64": ["linux_arm64.tar.gz", "
|
|
941
|
-
"win32-x64": ["windows_amd64.zip", "
|
|
942
|
-
"win32-arm64": ["windows_arm64.zip", "
|
|
937
|
+
"darwin-arm64": ["darwin_arm64.tar.gz", "d0aa59c9a83d17ae3fc7f5dd31fdab6a26fd8c00fc5e692195753cd0719294f7", 7470202],
|
|
938
|
+
"darwin-x64": ["darwin_amd64.tar.gz", "3c5bdc73d226af5629cf4f4a781e223142e37c1ebb443c4b596f2e284352e381", 7961674],
|
|
939
|
+
"linux-x64": ["linux_amd64.tar.gz", "715fda625556e46d65ddb4526bb7f88a3fee5339ca3c44ce44cc652f17fa57f1", 7821191],
|
|
940
|
+
"linux-arm64": ["linux_arm64.tar.gz", "3309e9c0bbb539d183fd5aa9f9a13cd7aa0e81ec2512b5bedefb8b2bdd35b3e5", 7176726],
|
|
941
|
+
"win32-x64": ["windows_amd64.zip", "3065736ec7ff8137eb3fc607275f542ea13bb743a5157318c8a8770f11ce3289", 8021225],
|
|
942
|
+
"win32-arm64": ["windows_arm64.zip", "7463872a85a5e5434cf958bdffc71dda1926bced30d3b6b38c80fec9bb98fbba", 7231641],
|
|
943
943
|
} as const;
|
|
944
944
|
test("fixed Engram descriptors are the GitHub release archives and checksums, and nothing else", () => {
|
|
945
945
|
for (const [target, [suffix, sha256, size]] of Object.entries(engramPins)) {
|
|
946
946
|
const [platform, arch] = target.split("-");
|
|
947
947
|
const artifact = artifactFor("engram", platform, arch);
|
|
948
|
-
assert.equal(artifact.version, "3.
|
|
949
|
-
assert.equal(artifact.url, `https://github.com/Gentleman-Programming/engram/releases/download/v3.
|
|
948
|
+
assert.equal(artifact.version, "3.4.0");
|
|
949
|
+
assert.equal(artifact.url, `https://github.com/Gentleman-Programming/engram/releases/download/v3.4.0/engram_3.4.0_${suffix}`);
|
|
950
950
|
assert.equal(artifact.integrity, `sha256-${sha256}`);
|
|
951
951
|
assert.deepEqual([artifact.size, artifact.maxBytes], [size, size]);
|
|
952
952
|
// GitHub serves release assets from its asset hosts.
|
|
@@ -253,7 +253,7 @@ test("a missing Pi is installed before an existing Gentle Shell is updated", ()
|
|
|
253
253
|
});
|
|
254
254
|
|
|
255
255
|
test("an existing compatible Pi from any installation is reused and only Gentle Shell is installed", () => {
|
|
256
|
-
for (const pi of [tool("1.2.0"), { ...tool("1.
|
|
256
|
+
for (const pi of [tool("1.2.0"), { ...tool("1.2.4"), external: true }]) {
|
|
257
257
|
const plan = planPreflight({ ...installed(), pi, shell: absent, gentleAi: absent, setup: false });
|
|
258
258
|
assert.equal(plan.tools.pi.status, "reusable");
|
|
259
259
|
assert.deepEqual(updateIds(plan), ["install-shell", "setup-shell", "verify-readiness"]);
|
|
@@ -261,7 +261,7 @@ test("an existing compatible Pi from any installation is reused and only Gentle
|
|
|
261
261
|
});
|
|
262
262
|
|
|
263
263
|
test("a pnpm Pi newer than the installer's pin never blocks: Gentle Shell is added or updated beside it", () => {
|
|
264
|
-
const newer = { ...tool("1.1
|
|
264
|
+
const newer = { ...tool("1.2.1") };
|
|
265
265
|
const install = planPreflight({ ...installed(), pi: newer, shell: absent, gentleAi: absent, setup: false });
|
|
266
266
|
assert.deepEqual([install.blockers, updateIds(install)], [[], ["install-shell", "setup-shell", "verify-readiness"]]);
|
|
267
267
|
const update = planPreflight({ ...installed(), pi: newer, shell: owned("3.9.0"), gentleAi: unchecked, setup: unchecked });
|
|
@@ -363,7 +363,7 @@ test("missing Windows Go is acquired before reprovisioning an existing Shell", (
|
|
|
363
363
|
// A pinned stack this pnpm installed whose setup did not finish (setup probe: recoverable).
|
|
364
364
|
const recoverable = { available: true, recoverable: true };
|
|
365
365
|
function partial(change: object = {}) {
|
|
366
|
-
return { ...installed(), pi: tool("1.
|
|
366
|
+
return { ...installed(), pi: tool("1.2.0"), shell: { ...tool(requirements.shell), global: true }, setup: recoverable, ...change };
|
|
367
367
|
}
|
|
368
368
|
|
|
369
369
|
test("a recoverable stack only reruns setup, with PATH setup when the global bin is off PATH", () => {
|
|
@@ -657,11 +657,11 @@ test("clean global list: Pi, Shell and Gentle AI absent and setup needed, from o
|
|
|
657
657
|
});
|
|
658
658
|
|
|
659
659
|
test("pnpm-global Pi and Shell report versions; verified package-native Gentle AI is compatible", async () => {
|
|
660
|
-
const stdout = listing({ "@earendil-works/pi-coding-agent": { version:
|
|
660
|
+
const stdout = listing({ "@earendil-works/pi-coding-agent": { version: PI_INSTALL_VERSION, path: `${PNPM_HOME}/global/v11/def/node_modules/pi` },
|
|
661
661
|
"gentle-pi": { version: requirements.shell, path: SHELL_ROOT } });
|
|
662
662
|
const files = [TOOLS_PNPM, `${BIN}/gentle-shell`, gentleAiBinaryPath(SHELL_ROOT, "linux")];
|
|
663
663
|
const h = probes({ files, dirs: [HOME, PNPM_HOME, SHELL_ROOT], texts: besidePi(PI_INSTALL_VERSION), results: { [`${TOOLS_PNPM} ${LIST}`]: { code: 0, stdout } } });
|
|
664
|
-
assert.deepEqual(await h.probes.pi(), { available: true, version:
|
|
664
|
+
assert.deepEqual(await h.probes.pi(), { available: true, version: PI_INSTALL_VERSION, usable: true });
|
|
665
665
|
assert.deepEqual(await h.probes.shell(), { available: true, version: requirements.shell, usable: true, global: true, owner: "pnpm" });
|
|
666
666
|
assert.deepEqual(await h.probes.gentleAi(), { available: true, version: requirements.gentleAi, usable: true, compatible: true });
|
|
667
667
|
assert.deepEqual(h.integrityCalls, [{ packageRoot: SHELL_ROOT, platform: "linux", env: bootstrapEnv, home: HOME }]);
|
|
@@ -684,7 +684,7 @@ test("setup is recoverable only for the pinned Pi and Shell owned by one pnpm pr
|
|
|
684
684
|
const persisted = { dependencies: { npm: { version: "11.19.0" }, pnpm: { version: "11.1.1" } } };
|
|
685
685
|
assert.deepEqual(await setup(JSON.stringify([persisted, { dependencies: { [PI]: pi, "gentle-pi": shell } }])), recoverable);
|
|
686
686
|
const foreign = [
|
|
687
|
-
listing({ [PI]: { ...pi, version: "1.2.
|
|
687
|
+
listing({ [PI]: { ...pi, version: "1.2.1" }, "gentle-pi": shell }),
|
|
688
688
|
listing({ [PI]: pi, "gentle-pi": { ...shell, version: "3.0.0" } }),
|
|
689
689
|
listing({ "gentle-pi": shell }),
|
|
690
690
|
JSON.stringify([{ dependencies: { [PI]: pi, "gentle-pi": shell } }, { dependencies: { [PI]: pi } }]),
|
|
@@ -698,7 +698,7 @@ test("setup is recoverable only for the pinned Pi and Shell owned by one pnpm pr
|
|
|
698
698
|
assert.deepEqual(await setup(listing({ [PI]: pi, "gentle-pi": shell }), { realpaths: { [SHELL_ROOT]: "/elsewhere/gentle-pi" } }),
|
|
699
699
|
{ available: null });
|
|
700
700
|
// The Pi beside gentle-pi, which the launcher runs, may be pnpm's newer peer, but never below the minimum, unreadable or missing.
|
|
701
|
-
assert.deepEqual(await setup(listing({ [PI]: pi, "gentle-pi": shell }), { texts: besidePi("1.1
|
|
701
|
+
assert.deepEqual(await setup(listing({ [PI]: pi, "gentle-pi": shell }), { texts: besidePi("1.2.1") }), recoverable);
|
|
702
702
|
for (const texts of [besidePi("0.99.0"), { [BESIDE_PI]: "not json" }, { [`${PNPM_HOME}/elsewhere`]: "" }]) {
|
|
703
703
|
assert.deepEqual(await setup(listing({ [PI]: pi, "gentle-pi": shell }), { texts }), { available: null }, JSON.stringify(texts));
|
|
704
704
|
}
|
|
@@ -741,7 +741,7 @@ test("a pnpm-global Shell from the main channel is present with its main version
|
|
|
741
741
|
|
|
742
742
|
const NPM_ROOT = "/usr/local/lib/node_modules";
|
|
743
743
|
const NPM_SHELL = `${NPM_ROOT}/gentle-pi`;
|
|
744
|
-
const outside = (shellVersion: string, { linked = false, piOutput = "1.
|
|
744
|
+
const outside = (shellVersion: string, { linked = false, piOutput = "1.2.4\n" } = {}) => probes({
|
|
745
745
|
env: { HOME, PATH: `${TOOLS}/pnpm/bin:/usr/local/bin:/usr/bin` },
|
|
746
746
|
files: [TOOLS_PNPM, "/usr/local/bin/pi", "/usr/local/bin/gentle-shell", "/usr/local/bin/npm"],
|
|
747
747
|
dirs: [HOME, NPM_ROOT, "/home/u/work/gentle-pi"],
|
|
@@ -752,7 +752,7 @@ const outside = (shellVersion: string, { linked = false, piOutput = "1.0.4\n" }
|
|
|
752
752
|
});
|
|
753
753
|
|
|
754
754
|
test("a Pi on PATH that pnpm does not manage is reused with the version it reports", async () => {
|
|
755
|
-
assert.deepEqual(await outside("3.9.0").probes.pi(), { available: true, version: "1.
|
|
755
|
+
assert.deepEqual(await outside("3.9.0").probes.pi(), { available: true, version: "1.2.4", usable: true, external: true });
|
|
756
756
|
for (const piOutput of ["", "pi dev build\n"]) {
|
|
757
757
|
assert.deepEqual(await outside("3.9.0", { piOutput }).probes.pi(), { available: null, outsidePnpm: true }, piOutput);
|
|
758
758
|
}
|
|
@@ -791,13 +791,13 @@ test("an older pnpm-global Pi reports pnpm as its owner, and locatePi finds its
|
|
|
791
791
|
});
|
|
792
792
|
|
|
793
793
|
test("a pnpm-global Pi newer than the installer's pin reports no owner: it is reused as it is", async () => {
|
|
794
|
-
for (const version of ["1.
|
|
794
|
+
for (const version of ["1.2.1", "1.3.0", "2.0.0"]) {
|
|
795
795
|
const newer = probes({ files: [TOOLS_PNPM], dirs: [HOME, PI_ROOT], results: { [`${TOOLS_PNPM} ${LIST}`]: { code: 0,
|
|
796
796
|
stdout: listing({ [PI_PACKAGE]: { version, path: PI_ROOT } }) } } });
|
|
797
797
|
assert.deepEqual(await newer.probes.pi(), { available: true, version, usable: true }, version);
|
|
798
798
|
}
|
|
799
799
|
// A newer Pi from elsewhere is reused as it is: pnpm does not own it.
|
|
800
|
-
assert.deepEqual(await outsidePi("1.1
|
|
800
|
+
assert.deepEqual(await outsidePi("1.2.1").probes.pi(), { available: true, version: "1.2.1", usable: true, external: true });
|
|
801
801
|
});
|
|
802
802
|
|
|
803
803
|
test("an older Pi in npm's global root reports npm as its owner; one elsewhere has no owner", async () => {
|
|
@@ -724,7 +724,7 @@ test("install nonzero, signal or deadline fails and reports completed steps", as
|
|
|
724
724
|
test("an existing global Pi or gentle-pi blocks before installation regardless of the plan", async () => {
|
|
725
725
|
const existing = [
|
|
726
726
|
listing(),
|
|
727
|
-
JSON.stringify([{ dependencies: { "@earendil-works/pi-coding-agent": { version: "1.2.
|
|
727
|
+
JSON.stringify([{ dependencies: { "@earendil-works/pi-coding-agent": { version: "1.2.1" } } }]),
|
|
728
728
|
JSON.stringify([{ dependencies: {} }, { dependencies: { "gentle-pi": { version: "3.0.0" } } }]),
|
|
729
729
|
JSON.stringify([{ optionalDependencies: { "gentle-pi": { version: "4.0.0" } } }]),
|
|
730
730
|
];
|
|
@@ -1390,7 +1390,7 @@ test("setup recovery blocks when the installed stack changed between plan and ru
|
|
|
1390
1390
|
const stack = JSON.parse(listing());
|
|
1391
1391
|
const changed = [
|
|
1392
1392
|
"[]",
|
|
1393
|
-
listing("1.2.
|
|
1393
|
+
listing("1.2.1"),
|
|
1394
1394
|
listing(PI_INSTALL_VERSION, "3.0.0"),
|
|
1395
1395
|
listing(PI_INSTALL_VERSION, requirements.shell, "/elsewhere/gentle-pi"),
|
|
1396
1396
|
JSON.stringify([...stack, { dependencies: { "gentle-pi": { version: requirements.shell, path: PACKAGE_ROOT } } }]),
|
|
@@ -1640,7 +1640,7 @@ const NPM_SHELL_ROOT = "/usr/local/lib/node_modules/gentle-pi";
|
|
|
1640
1640
|
|
|
1641
1641
|
test("with a compatible Pi already installed, only Gentle Shell is added and Pi is left as it is", async () => {
|
|
1642
1642
|
// A pnpm Pi newer than PI_INSTALL_VERSION is kept too: gentle-pi gets its own pnpm group, nothing is replaced.
|
|
1643
|
-
for (const version of ["1.2.
|
|
1643
|
+
for (const version of ["1.2.1", PI_INSTALL_VERSION]) {
|
|
1644
1644
|
const h = harness({ results: { [LIST]: [piOnlyList(version), { code: 0, stdout: listing(version) }], [SHELL_ONLY_ADD]: { code: 0 } } });
|
|
1645
1645
|
const result = await runStandardInstall({ plan: existingPlan({ pi: tool(version) }), consent: true }, h.adapters);
|
|
1646
1646
|
assert.equal(result.outcome, "ready", version);
|
|
@@ -1652,7 +1652,7 @@ test("with a compatible Pi already installed, only Gentle Shell is added and Pi
|
|
|
1652
1652
|
});
|
|
1653
1653
|
|
|
1654
1654
|
test("adding only Gentle Shell still refuses a gentle-pi that pnpm already lists", async () => {
|
|
1655
|
-
const h = harness({ results: { [LIST]: { code: 0, stdout: listing("1.2.
|
|
1655
|
+
const h = harness({ results: { [LIST]: { code: 0, stdout: listing("1.2.1") } } });
|
|
1656
1656
|
const result = await runStandardInstall({ plan: existingPlan({}), consent: true }, h.adapters);
|
|
1657
1657
|
assert.deepEqual([result.outcome, result.reason], ["blocked", "existing-stack"]);
|
|
1658
1658
|
assert.equal(h.calls.some((call) => call.args[1] === "add"), false);
|
|
@@ -1674,19 +1674,20 @@ test("Pi and gentle-pi are added as separate pnpm groups, so a later `pi update`
|
|
|
1674
1674
|
|
|
1675
1675
|
test("verify-global-list accepts the Pi the launcher runs beside gentle-pi at the minimum or newer, and reports one other than the pin", async () => {
|
|
1676
1676
|
// pnpm's latest peer beside gentle-pi (or nested in it): Gentle Shell runs it, and the outcome says which.
|
|
1677
|
-
for (const texts of [{ [besidePi(PACKAGE_ROOT)]: piPackage("1.1
|
|
1677
|
+
for (const texts of [{ [besidePi(PACKAGE_ROOT)]: piPackage("1.2.1") }, { [nestedPi(PACKAGE_ROOT)]: piPackage("1.2.1") },
|
|
1678
1678
|
{ [besidePi(PACKAGE_ROOT)]: piPackage(requirements.pi) }]) {
|
|
1679
1679
|
const h = harness({ texts });
|
|
1680
1680
|
const result = await runStandardInstall({ plan: plan(), consent: true }, h.adapters);
|
|
1681
1681
|
assert.equal(result.outcome, "ready", JSON.stringify(texts));
|
|
1682
|
-
|
|
1682
|
+
const selectedVersion = JSON.parse(Object.values(texts)[0]).version;
|
|
1683
|
+
assert.equal(result.piVersion, selectedVersion === PI_INSTALL_VERSION ? undefined : selectedVersion);
|
|
1683
1684
|
}
|
|
1684
1685
|
// The pin itself adds nothing to the outcome.
|
|
1685
1686
|
assert.equal("piVersion" in (await runStandardInstall({ plan: plan(), consent: true }, harness().adapters)), false);
|
|
1686
1687
|
const wrong = [
|
|
1687
1688
|
{ [besidePi(PACKAGE_ROOT)]: piPackage("0.99.0") },
|
|
1688
1689
|
// A Pi nested in gentle-pi resolves first, even below the minimum.
|
|
1689
|
-
{ [nestedPi(PACKAGE_ROOT)]: piPackage("0.99.0"), [besidePi(PACKAGE_ROOT)]: piPackage("1.1
|
|
1690
|
+
{ [nestedPi(PACKAGE_ROOT)]: piPackage("0.99.0"), [besidePi(PACKAGE_ROOT)]: piPackage("1.2.1") },
|
|
1690
1691
|
{ [besidePi(PACKAGE_ROOT)]: piPackage("1.2.0-beta.1") },
|
|
1691
1692
|
{ [besidePi(PACKAGE_ROOT)]: JSON.stringify({ name: "not-pi", version: PI_INSTALL_VERSION }) },
|
|
1692
1693
|
{ [besidePi(PACKAGE_ROOT)]: "not json" },
|
|
@@ -1706,10 +1707,10 @@ test("verify-global-list accepts the Pi the launcher runs beside gentle-pi at th
|
|
|
1706
1707
|
});
|
|
1707
1708
|
|
|
1708
1709
|
test("the main Shell is added alone and its adjacent Pi is verified like the release one", async () => {
|
|
1709
|
-
const h = mainHarness(undefined, { texts: { [besidePi(MAIN_ROOT)]: piPackage("1.1
|
|
1710
|
+
const h = mainHarness(undefined, { texts: { [besidePi(MAIN_ROOT)]: piPackage("1.2.1") } });
|
|
1710
1711
|
const result = await runStandardInstall({ plan: mainPlan(), consent: true }, h.adapters);
|
|
1711
1712
|
assert.equal(result.outcome, "ready");
|
|
1712
|
-
assert.equal(result.piVersion, "1.1
|
|
1713
|
+
assert.equal(result.piVersion, "1.2.1");
|
|
1713
1714
|
assert.deepEqual(h.calls.filter((call) => call.args[1] === "add").map((call) => call.args.slice(1)),
|
|
1714
1715
|
[INSTALL.split(" "), MAIN_ADD.split(" ")]);
|
|
1715
1716
|
const below = mainHarness(undefined, { texts: { [besidePi(MAIN_ROOT)]: piPackage("0.99.0") } });
|
|
@@ -1722,10 +1723,10 @@ test("a pnpm-owned Gentle Shell update is verified with a Pi at the minimum or n
|
|
|
1722
1723
|
const fixed = existingPlan({ shell: { available: true, version: "3.9.0", usable: true, global: true, owner: "pnpm" },
|
|
1723
1724
|
gentleAi: { available: null }, setup: { available: null } });
|
|
1724
1725
|
const located = [{ root: OLD_ROOT, version: "3.9.0", owner: "pnpm" }, { root: PACKAGE_ROOT, version: requirements.shell, owner: "pnpm" }];
|
|
1725
|
-
const h = updateHarness({ located, texts: { [besidePi(PACKAGE_ROOT)]: piPackage("1.1
|
|
1726
|
+
const h = updateHarness({ located, texts: { [besidePi(PACKAGE_ROOT)]: piPackage("1.2.1") } });
|
|
1726
1727
|
const result = await runStandardInstall({ plan: fixed, consent: true }, h.adapters);
|
|
1727
1728
|
assert.equal(result.outcome, "ready");
|
|
1728
|
-
assert.equal(result.piVersion, "1.1
|
|
1729
|
+
assert.equal(result.piVersion, "1.2.1");
|
|
1729
1730
|
assert.deepEqual(result.completed.slice(3, 5), ["update-shell", "verify-updated-shell"]);
|
|
1730
1731
|
for (const texts of [{ [besidePi(PACKAGE_ROOT)]: piPackage("0.99.0") }, { [nestedPi(PACKAGE_ROOT)]: piPackage("0.99.0") }]) {
|
|
1731
1732
|
const wrong = updateHarness({ located, texts });
|
|
@@ -1740,7 +1741,7 @@ test("a pnpm-owned Gentle Shell update is verified with a Pi at the minimum or n
|
|
|
1740
1741
|
});
|
|
1741
1742
|
|
|
1742
1743
|
test("a setup recovery accepts a newer Pi beside gentle-pi but not one below the minimum", async () => {
|
|
1743
|
-
const newer = harness({ results: installedList, texts: { [besidePi(PACKAGE_ROOT)]: piPackage("1.1
|
|
1744
|
+
const newer = harness({ results: installedList, texts: { [besidePi(PACKAGE_ROOT)]: piPackage("1.2.1") } });
|
|
1744
1745
|
assert.equal((await runStandardInstall({ plan: recoveryPlan(), consent: true }, newer.adapters)).outcome, "ready");
|
|
1745
1746
|
const h = harness({ results: installedList, texts: { [besidePi(PACKAGE_ROOT)]: piPackage("0.99.0") } });
|
|
1746
1747
|
const result = await runStandardInstall({ plan: recoveryPlan(), consent: true }, h.adapters);
|
|
@@ -1942,7 +1943,7 @@ test("a Pi update is refused before any change when the installed Pi is not the
|
|
|
1942
1943
|
const plan = existingPlan({ pi: olderPi("pnpm") });
|
|
1943
1944
|
// Gone, unattributed, another version, another owner, or already current: never reinstalled, never downgraded.
|
|
1944
1945
|
for (const before of [null, { ...piBefore, owner: null }, { ...piBefore, version: "0.90.0" }, { ...piBefore, owner: "npm" },
|
|
1945
|
-
{ ...piBefore, version: "1.2.
|
|
1946
|
+
{ ...piBefore, version: "1.2.1" }, { ...piBefore, version: PI_INSTALL_VERSION }, { ...piBefore, root: "relative/pi" }]) {
|
|
1946
1947
|
const h = locatingPi(harness({ results: { [LIST]: piOnlyList("0.87.1") } }), [before]);
|
|
1947
1948
|
const result = await runStandardInstall({ plan, consent: true }, h.adapters);
|
|
1948
1949
|
assert.deepEqual([result.outcome, result.reason], ["blocked", "existing-stack-unverified"], JSON.stringify(before));
|
|
@@ -298,7 +298,7 @@ test("/api/plan reports blockers with guidance and no profile change when alread
|
|
|
298
298
|
|
|
299
299
|
test("/api/plan names the found and required version of an older Gentle Shell and how to resolve it", async () => {
|
|
300
300
|
const { host, port, login } = await start({ collect: async () => collected({
|
|
301
|
-
pi: { available: true, version: "1.
|
|
301
|
+
pi: { available: true, version: "1.2.4", usable: true },
|
|
302
302
|
shell: { available: true, version: "3.4.0", usable: false, global: true },
|
|
303
303
|
gentleAi: { available: null }, setup: { available: null },
|
|
304
304
|
}) });
|
|
@@ -306,8 +306,8 @@ test("/api/plan names the found and required version of an older Gentle Shell an
|
|
|
306
306
|
const view = await plan(port, await login());
|
|
307
307
|
assert.deepEqual(view.blockers.map((blocker: { code: string; tool: string }) => [blocker.code, blocker.tool]), [["incompatible-tool", "shell"]]);
|
|
308
308
|
assert.equal(view.blockers[0].guidance,
|
|
309
|
-
"Gentle Shell 3.4.0 is installed globally with pnpm, but this installer needs 4.
|
|
310
|
-
"Update it with `pnpm add -g gentle-pi@4.
|
|
309
|
+
"Gentle Shell 3.4.0 is installed globally with pnpm, but this installer needs 4.3.0 or newer. Nothing was replaced. " +
|
|
310
|
+
"Update it with `pnpm add -g gentle-pi@4.3.0`, or remove it with `pnpm remove -g gentle-pi`, then select Check again.");
|
|
311
311
|
assert.deepEqual(view.actions, []);
|
|
312
312
|
} finally {
|
|
313
313
|
await host.close("test");
|
|
@@ -449,7 +449,7 @@ test("/api/plan explains a pnpm in $PNPM_HOME/bin that the installer will not re
|
|
|
449
449
|
|
|
450
450
|
test("/api/plan explains a Gentle Shell that neither pnpm nor npm manages", async () => {
|
|
451
451
|
const { host, port, login } = await start({ collect: async () => collected({
|
|
452
|
-
pi: { available: true, version: "1.
|
|
452
|
+
pi: { available: true, version: "1.2.4", usable: true },
|
|
453
453
|
shell: { available: null, outsidePnpm: true }, gentleAi: { available: null }, setup: { available: null },
|
|
454
454
|
}) });
|
|
455
455
|
try {
|
|
@@ -514,7 +514,7 @@ test("the outcome says which Pi Gentle Shell runs only when it is not the instal
|
|
|
514
514
|
|
|
515
515
|
test("/api/plan describes updating an existing Gentle Shell on either channel", async () => {
|
|
516
516
|
const shell = { available: true, version: "3.9.0", usable: true, global: true, owner: "npm" };
|
|
517
|
-
const { host, port, login } = await start({ collect: async (channel) => collectedFor(channel, { pi: { available: true, version: "1.
|
|
517
|
+
const { host, port, login } = await start({ collect: async (channel) => collectedFor(channel, { pi: { available: true, version: "1.2.4", usable: true },
|
|
518
518
|
shell, go: { available: true, version: "1.26.0", usable: true }, gentleAi: { available: null }, setup: { available: null } }) });
|
|
519
519
|
try {
|
|
520
520
|
const cookie = await login();
|
|
@@ -934,6 +934,10 @@ test("a failed setup passes only its bounded detail through, with rate-limit gui
|
|
|
934
934
|
for (const [step, detail, expected] of [
|
|
935
935
|
["install-global", ".../gentle-pi postinstall: root cause Error: go: open ~/x: The directory name is invalid.", guidance.failed["install-global"]],
|
|
936
936
|
["install-global", rateLimited, guidance.githubRateLimit],
|
|
937
|
+
// Windows code integrity (Smart App Control) refused the gentle-ai.exe the postinstall built.
|
|
938
|
+
["install-global", ".../node_modules/gentle-pi postinstall: caused by Error: spawn UNKNOWN (code UNKNOWN)", guidance.codeIntegrityBlocked],
|
|
939
|
+
["install-version", ".../node_modules/gentle-pi postinstall: caused by Error: spawn UNKNOWN (code UNKNOWN)", guidance.codeIntegrityBlocked],
|
|
940
|
+
["install-version", "ERR_PNPM_FETCH_401 GET https://npm.acme.dev/x: Unauthorized", guidance.failed["install-version"]],
|
|
937
941
|
["install-shell-main", mainLimited, guidance.githubRateLimit],
|
|
938
942
|
["install-shell-main", "main-commit-unavailable: GitHub did not return the latest main commit of Gentleman-Programming/gentle-shell (HTTP 500)", guidance.failed["install-shell-main"]],
|
|
939
943
|
["build-gentle-ai-main", "main-commit-unavailable: GitHub did not return the latest main commit of Gentleman-Programming/gentle-ai (HTTP 403)", guidance.githubRateLimit],
|
|
@@ -957,6 +961,10 @@ test("a failed setup passes only its bounded detail through, with rate-limit gui
|
|
|
957
961
|
assert.match(guidance.githubRateLimit, /GitHub/);
|
|
958
962
|
assert.match(guidance.githubRateLimit, /hour/);
|
|
959
963
|
assert.doesNotMatch(guidance.githubRateLimit, /gentle-shell setup|token|credential|password|log in|sign in/i);
|
|
964
|
+
assert.match(guidance.codeIntegrityBlocked, /Smart App Control/);
|
|
965
|
+
assert.match(guidance.codeIntegrityBlocked, /WSL/);
|
|
966
|
+
assert.match(guidance.codeIntegrityBlocked, /cannot be turned back on/);
|
|
967
|
+
assert.doesNotMatch(guidance.codeIntegrityBlocked, /network|registry/);
|
|
960
968
|
assert.match(guidance.goDestinationConflict, /earlier/);
|
|
961
969
|
assert.match(guidance.goDestinationConflict, /Remove that folder/);
|
|
962
970
|
assert.doesNotMatch(guidance.goDestinationConflict, /network/);
|
|
@@ -653,13 +653,14 @@ test("native Windows entry: spaces, Unicode, CMD metacharacters and early missin
|
|
|
653
653
|
const scripts = join(f.root, "scripts"); mkdirSync(scripts);
|
|
654
654
|
// Observe the copied production entry's first PowerShell handle so the
|
|
655
655
|
// existing ownership-checked guard can reap it if the fixture times out.
|
|
656
|
-
writeFileSync(join(scripts, "bootstrap.cmd"), observeStage(readFileSync(new URL("../scripts/bootstrap.cmd", import.meta.url), "utf8")));
|
|
656
|
+
writeFileSync(join(scripts, "bootstrap.cmd"), observeStage(readFileSync(new URL("../scripts/bootstrap.cmd", import.meta.url), "utf8"), true));
|
|
657
657
|
const local = join(f.root, "home"); mkdirSync(local);
|
|
658
658
|
const env = { ...nativeEnv(f.root), LOCALAPPDATA: local };
|
|
659
659
|
// The same outer bound as every other production fragment (nativeCmd): a cold
|
|
660
660
|
// cmd.exe plus Windows PowerShell 5.1 start can exceed 5 s while the CI step runs
|
|
661
661
|
// the installer suites in parallel. The bundle stage itself has no deadline.
|
|
662
662
|
console.error("WINDOWS_ENTRY_PROBE_START timeoutMs=14000");
|
|
663
|
+
const startedAt = Date.now();
|
|
663
664
|
const started = performance.now();
|
|
664
665
|
const result = await nativeCmdFile(f.root, "scripts\\bootstrap.cmd", env, 14000);
|
|
665
666
|
console.error("WINDOWS_ENTRY_PROBE_RESULT", JSON.stringify({ elapsedMs: performance.now() - started, status: result.status, guardKilled: result.guardKilled, expectedDiagnostic: /No acquisition attempted/.test(result.stderr) }));
|
|
@@ -668,6 +669,8 @@ test("native Windows entry: spaces, Unicode, CMD metacharacters and early missin
|
|
|
668
669
|
assertNative(result, 1);
|
|
669
670
|
assert.match(result.stderr, /No acquisition attempted/);
|
|
670
671
|
assert.match(readFileSync(join(f.root, "process-records"), "utf8").trim(), /^[1-9][0-9]*\|[1-9][0-9]*$/, "the copied entry must expose its first PowerShell handle to fixture cleanup");
|
|
672
|
+
const readyMs = Number(readFileSync(join(f.root, "powershell-ready-ms"), "utf8"));
|
|
673
|
+
assert.ok(Number.isSafeInteger(readyMs) && readyMs >= startedAt && readyMs <= Date.now(), "the fixture must timestamp PowerShell executing its body");
|
|
671
674
|
assert.deepEqual(readdirSync(local), []);
|
|
672
675
|
} finally {
|
|
673
676
|
try { cleanNativeProcesses(f.root); } finally { f.cleanup(); }
|
|
@@ -1199,11 +1202,14 @@ test("production and fixture PowerShell never depend on autoloading Microsoft.Po
|
|
|
1199
1202
|
}
|
|
1200
1203
|
});
|
|
1201
1204
|
const psRecordLine = ' "$record = [Diagnostics.Process]::GetCurrentProcess(); [IO.File]::AppendAllText($env:GENTLE_FIXTURE_RECORDS,([string]$record.Id + [char]124 + [string]$record.StartTime.ToUniversalTime().Ticks + [Environment]::NewLine));" ^';
|
|
1202
|
-
|
|
1205
|
+
// Entry-only telemetry: this timestamps execution of the PowerShell body, not
|
|
1206
|
+
// merely process creation. Keep it separate from the guard's PID/ticks records.
|
|
1207
|
+
const psReadyLine = ' "[IO.File]::WriteAllText([IO.Path]::Combine($env:GENTLE_FIXTURE_ROOT,\'powershell-ready-ms\'),[string][DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds());" ^';
|
|
1208
|
+
function observeStage(stage: string, entryReadiness = false) {
|
|
1203
1209
|
const lines = stage.split("\n");
|
|
1204
1210
|
const index = lines.findIndex((line) => line.includes("LanguageMode"));
|
|
1205
1211
|
assert.ok(index >= 0);
|
|
1206
|
-
lines.splice(index + 1, 0, psRecordLine);
|
|
1212
|
+
lines.splice(index + 1, 0, psRecordLine, ...(entryReadiness ? [psReadyLine] : []));
|
|
1207
1213
|
return lines.join("\n");
|
|
1208
1214
|
}
|
|
1209
1215
|
function cmdComposition(stages: string[]) {
|
|
@@ -1234,6 +1240,16 @@ test("native entry observation preserves the complete production batch", () => {
|
|
|
1234
1240
|
assert.equal(observed.filter((line) => line !== psRecordLine).join("\n"), source);
|
|
1235
1241
|
});
|
|
1236
1242
|
|
|
1243
|
+
test("native entry readiness observation is opt-in and preserves production and ownership records", () => {
|
|
1244
|
+
const source = readFileSync(new URL("../scripts/bootstrap.cmd", import.meta.url), "utf8");
|
|
1245
|
+
const observed = observeStage(source, true).split("\n");
|
|
1246
|
+
assert.equal(observed.filter((line) => line.includes("powershell-ready-ms")).length, 1);
|
|
1247
|
+
assert.equal(observed.filter((line) => line === psRecordLine).length, 1);
|
|
1248
|
+
assert.equal(observed.filter((line) => line !== psRecordLine && !line.includes("powershell-ready-ms")).join("\n"), source);
|
|
1249
|
+
assert.doesNotMatch(observeStage(source), /powershell-ready-ms/);
|
|
1250
|
+
assert.ok(observed.findIndex((line) => line.includes("powershell-ready-ms")) > observed.indexOf(psRecordLine));
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1237
1253
|
test("native fixture compositions retain exact production fragments and no transport command", () => {
|
|
1238
1254
|
for (const marker of Object.values(stageMarkers)) {
|
|
1239
1255
|
const original = cmdStage(marker);
|
|
@@ -1314,10 +1330,22 @@ async function nativeCmd(root: string, stages: string[], env: NodeJS.ProcessEnv
|
|
|
1314
1330
|
return nativeCmdFile(root, "fixture.cmd", env, limit);
|
|
1315
1331
|
}
|
|
1316
1332
|
async function nativeCmdFile(root: string, file: string, env: NodeJS.ProcessEnv, limit = 5000): Promise<NativeResult> {
|
|
1333
|
+
const startedAt = Date.now();
|
|
1317
1334
|
const started = performance.now();
|
|
1318
1335
|
const trace = (phase: string, detail: Record<string, unknown> = {}) => {
|
|
1319
1336
|
if (file === "scripts\\bootstrap.cmd") console.error("WINDOWS_ENTRY_PHASE", JSON.stringify({ phase, elapsedMs: performance.now() - started, limit, ...detail }));
|
|
1320
1337
|
};
|
|
1338
|
+
// Wall-clock timestamps share an epoch with PowerShell. These measurements
|
|
1339
|
+
// are diagnostic only; the monotonic outer deadline is never reset. Emit
|
|
1340
|
+
// the split before synchronous guard cleanup can inflate execution time.
|
|
1341
|
+
const traceEntryTiming = () => {
|
|
1342
|
+
if (file !== "scripts\\bootstrap.cmd") return;
|
|
1343
|
+
const readyPath = join(root, "powershell-ready-ms");
|
|
1344
|
+
const finishedAt = Date.now();
|
|
1345
|
+
const readyMs = existsSync(readyPath) ? Number(readFileSync(readyPath, "utf8")) : NaN;
|
|
1346
|
+
const valid = Number.isSafeInteger(readyMs) && readyMs >= startedAt && readyMs <= finishedAt;
|
|
1347
|
+
trace("entry-timing", { readyObserved: valid, startupMs: valid ? readyMs - startedAt : null, executionMs: valid ? finishedAt - readyMs : null });
|
|
1348
|
+
};
|
|
1321
1349
|
trace("spawn-request");
|
|
1322
1350
|
const child = spawn(join(process.env.SystemRoot!, "System32/cmd.exe"), ["/d", "/c", file], { cwd: root, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
1323
1351
|
let stdout = ""; let stderr = ""; let guardKilled = false;
|
|
@@ -1327,6 +1355,7 @@ async function nativeCmdFile(root: string, file: string, env: NodeJS.ProcessEnv,
|
|
|
1327
1355
|
const result = await new Promise<NativeResult>((resolveChild, reject) => {
|
|
1328
1356
|
const timer = setTimeout(() => {
|
|
1329
1357
|
guardKilled = true;
|
|
1358
|
+
traceEntryTiming();
|
|
1330
1359
|
trace("guard-fired", { recordExists: existsSync(join(root, "process-records")), diagnosticSeen: /No acquisition attempted/.test(stderr) });
|
|
1331
1360
|
try { cleanNativeProcesses(root); } catch (error) { guardError = error; }
|
|
1332
1361
|
child.kill("SIGKILL");
|
|
@@ -1341,7 +1370,7 @@ async function nativeCmdFile(root: string, file: string, env: NodeJS.ProcessEnv,
|
|
|
1341
1370
|
if (!/No acquisition attempted/.test(before) && /No acquisition attempted/.test(stderr)) trace("missing-bundle-diagnostic");
|
|
1342
1371
|
if (stderr.length > 1048576) { guardError = new Error("Fixture output limit"); child.kill("SIGKILL"); } });
|
|
1343
1372
|
child.once("error", (error) => { clearTimeout(timer); reject(error); });
|
|
1344
|
-
child.once("close", (status) => { trace("cmd-close", { status, guardKilled }); clearTimeout(timer); resolveChild({ status, stdout, stderr, guardKilled }); });
|
|
1373
|
+
child.once("close", (status) => { if (!guardKilled) traceEntryTiming(); trace("cmd-close", { status, guardKilled }); clearTimeout(timer); resolveChild({ status, stdout, stderr, guardKilled }); });
|
|
1345
1374
|
});
|
|
1346
1375
|
const residualReaped = cleanNativeProcesses(root);
|
|
1347
1376
|
assert.equal(residualReaped, false, "production must reap its own recorded children; fixture cleanup cannot mask a failure");
|
|
@@ -5,6 +5,7 @@ import { tmpdir } from "node:os";
|
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import test from "node:test";
|
|
7
7
|
import { CHANNEL_SCHEMA, MainChannelError, ownerManager, readChannel, removeGlobalShell, runUpgrade, writeChannel } from "../scripts/main-channel.mjs";
|
|
8
|
+
import { requirements } from "../scripts/installer-preflight.mjs";
|
|
8
9
|
|
|
9
10
|
const AI_SHA = "1f9d5e6423e37f7d2316859045f379ba9b5d8c3a";
|
|
10
11
|
const SHELL_SHA = "6e7e3a18f794223396527a54c7c36d19c7d236c6";
|
|
@@ -53,6 +54,9 @@ function world({ owner = "pnpm", latest = "4.1.0", commits = { ai: AI_SHA, shell
|
|
|
53
54
|
for (const name of ["gentle-ai", "gentle-ai.exe"]) writeFileSync(join(options.env!.GOBIN, name), "#!/bin/sh\n", { mode: 0o755 });
|
|
54
55
|
return { code: 0, stdout: "" };
|
|
55
56
|
}
|
|
57
|
+
if (argv[0] === "version" && command === `${bin}/go`) {
|
|
58
|
+
return { code: 0, stdout: `go version go${requirements.go} ${platform}/amd64\n` };
|
|
59
|
+
}
|
|
56
60
|
if (argv[0] === "version") {
|
|
57
61
|
const sha = command.split("/").at(-2) ?? "";
|
|
58
62
|
return { code: 0, stdout: `gentle-ai 4.0.1-0.20261008202137-${sha.slice(0, 12)}\n` };
|
|
@@ -69,7 +73,9 @@ function world({ owner = "pnpm", latest = "4.1.0", commits = { ai: AI_SHA, shell
|
|
|
69
73
|
};
|
|
70
74
|
const which = async (name: string) => (tools.includes(name) ? `${bin}/${name}${name === "go" ? "" : suffix}` : null);
|
|
71
75
|
const upgrade = (args: string[], currentVersion = "4.0.0") => runUpgrade({ args, ctx, platform, packageRoot, currentVersion,
|
|
72
|
-
adapters: { fetch, run, fs, which,
|
|
76
|
+
adapters: { fetch, run, fs, which,
|
|
77
|
+
acquireGo: async () => { throw new Error("Unexpected Go acquisition in sandboxed upgrade fixture"); },
|
|
78
|
+
...(invocation ? { invocation } : {}), ...(migrate ? { migrate } : {}) }, out: (line: string) => lines.push(line) });
|
|
73
79
|
const installs = () => calls.filter((call) => call.argv.some((arg) => ["add", "install"].includes(arg)) && !call.argv.some((arg) => arg.includes("cmd/gentle-ai@")))
|
|
74
80
|
.map((call) => `${call.command} ${call.argv.join(" ")}`);
|
|
75
81
|
const manager = (command?: string) => ownerManager({ ctx, platform, packageRoot, adapters: { fs, run, which, ...(invocation ? { invocation } : {}) },
|
|
@@ -379,11 +379,17 @@ test("4.1.1 explicitly repeats 4.0.0 because capabilities/v2.6 is unchanged desp
|
|
|
379
379
|
assert.deepEqual(contract, NATIVE_CLI_CONTRACTS["4.0.0"] as Record<string, boolean>);
|
|
380
380
|
});
|
|
381
381
|
|
|
382
|
+
test("4.2.0 explicitly repeats 4.1.1 because capabilities/v2.6 is unchanged despite provider contract 1.4.0", () => {
|
|
383
|
+
const contract = NATIVE_CLI_CONTRACTS["4.2.0"] as Record<string, boolean>;
|
|
384
|
+
assert.ok(contract);
|
|
385
|
+
assert.deepEqual(contract, NATIVE_CLI_CONTRACTS["4.1.1"] as Record<string, boolean>);
|
|
386
|
+
});
|
|
387
|
+
|
|
382
388
|
test("no shipped version key was added beyond the pin bump", () => {
|
|
383
389
|
// Rows are promises to consumers, so a new key only ever appears in a
|
|
384
390
|
// dedicated commit alongside a pin bump, never as a side effect. v2.2.4 and
|
|
385
391
|
// v2.3.0 shipped upstream while Pi stayed on 2.2.3 and were never pinned,
|
|
386
392
|
// so they get no row: a row asserts ground truth measured against a binary
|
|
387
393
|
// Pi actually ran, and the table only has to be ascending, not gapless.
|
|
388
|
-
assert.deepEqual(Object.keys(NATIVE_CLI_CONTRACTS), [...DARK_VERSIONS, "2.2.0", "2.2.1", "2.2.2", "2.2.3", "2.4.0", "2.5.0-rc.3", "2.5.0", "2.6.0", "2.7.0", "2.8.0", "2.8.1", "2.8.2", "2.9.0", "2.9.1", "3.0.0", "3.0.1", "3.1.0", "3.2.1", "3.4.0", "3.5.0", "3.6.0", "3.6.1", "3.7.0", "4.0.0", "4.1.1"]);
|
|
394
|
+
assert.deepEqual(Object.keys(NATIVE_CLI_CONTRACTS), [...DARK_VERSIONS, "2.2.0", "2.2.1", "2.2.2", "2.2.3", "2.4.0", "2.5.0-rc.3", "2.5.0", "2.6.0", "2.7.0", "2.8.0", "2.8.1", "2.8.2", "2.9.0", "2.9.1", "3.0.0", "3.0.1", "3.1.0", "3.2.1", "3.4.0", "3.5.0", "3.6.0", "3.6.1", "3.7.0", "4.0.0", "4.1.1", "4.2.0"]);
|
|
389
395
|
});
|
|
@@ -3,7 +3,8 @@ import { execFileSync } from "node:child_process";
|
|
|
3
3
|
import { existsSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { join } from "node:path";
|
|
6
|
-
import test from "node:test";
|
|
6
|
+
import test, { after, before } from "node:test";
|
|
7
|
+
import { setWindowsAclAuthorityForTesting } from "../lib/review-candidate-view-owner.ts";
|
|
7
8
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
8
9
|
import { __testing, createGentleAiExtension, PendingReviewConsentRegistry } from "../extensions/gentle-ai.ts";
|
|
9
10
|
import { CandidateViewRegistry } from "../lib/review-candidate-view.ts";
|
|
@@ -28,6 +29,24 @@ import {
|
|
|
28
29
|
type ReviewStatusV3,
|
|
29
30
|
} from "../lib/review-integration-v2.ts";
|
|
30
31
|
|
|
32
|
+
// Coordination tests use fixture authority; real Windows ACL coverage lives
|
|
33
|
+
// in review-candidate-view.test.ts, in a separate test process.
|
|
34
|
+
let fixtureAclChecks = 0;
|
|
35
|
+
before(() => {
|
|
36
|
+
if (process.platform === "win32") {
|
|
37
|
+
setWindowsAclAuthorityForTesting(() => { fixtureAclChecks++; });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
after(() => setWindowsAclAuthorityForTesting());
|
|
41
|
+
|
|
42
|
+
test("coordination suites isolate Windows ACL authority with teardown", () => {
|
|
43
|
+
for (const file of ["native-review-parity", "review-controller-native-routing", "review-session-standing-permission-controller"]) {
|
|
44
|
+
const source = readFileSync(new URL(`./${file}.test.ts`, import.meta.url), "utf8");
|
|
45
|
+
assert.match(source, /import \{ setWindowsAclAuthorityForTesting \} from/, file);
|
|
46
|
+
assert.match(source, /after\(\(\) => setWindowsAclAuthorityForTesting\(\)\)/, file);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
31
50
|
const CAPTURED_FIXTURES = join(process.cwd(), "tests", "fixtures", "devbinary");
|
|
32
51
|
|
|
33
52
|
function captured(name: string): unknown {
|
|
@@ -218,7 +237,9 @@ function repository(t: test.TestContext): string {
|
|
|
218
237
|
|
|
219
238
|
function startStatus(cwd: string): ReviewStatusV3 {
|
|
220
239
|
const candidates = new CandidateViewRegistry();
|
|
240
|
+
const checksBefore = fixtureAclChecks;
|
|
221
241
|
const candidate = candidates.create({ contributorRoot: cwd });
|
|
242
|
+
if (process.platform === "win32") assert.ok(fixtureAclChecks > checksBefore, "coordination candidates must use the fixture ACL authority");
|
|
222
243
|
try {
|
|
223
244
|
return {
|
|
224
245
|
contract: "gentle-ai.review-integration/v2",
|