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
|
@@ -80,10 +80,10 @@ function standaloneLauncher(t: test.TestContext) {
|
|
|
80
80
|
|
|
81
81
|
test("a genuinely absent adjacent peer falls back to PATH", (t) => {
|
|
82
82
|
const f = standaloneLauncher(t);
|
|
83
|
-
writePathPiScript(f.root, "
|
|
83
|
+
writePathPiScript(f.root, "1.2.1");
|
|
84
84
|
const result = spawnSync(process.execPath, [f.launcher, "--version"], { env: f.env, encoding: "utf8" });
|
|
85
85
|
assert.equal(result.status, 0, result.stderr);
|
|
86
|
-
assert.match(result.stdout, /pi
|
|
86
|
+
assert.match(result.stdout, /pi 1\.2\.1/);
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
test("malformed adjacent metadata never silently falls back to PATH; env override still wins", (t) => {
|
|
@@ -91,8 +91,8 @@ test("malformed adjacent metadata never silently falls back to PATH; env overrid
|
|
|
91
91
|
const peer = join(f.root, "node_modules", "@earendil-works", "pi-coding-agent");
|
|
92
92
|
mkdirSync(join(peer, "dist", "bundle"), { recursive: true });
|
|
93
93
|
writeFileSync(join(peer, "dist", "index.js"), "");
|
|
94
|
-
writePiScript(join(peer, "dist", "bundle", "cli.js"), "
|
|
95
|
-
const fallback = writePathPiScript(f.root, "
|
|
94
|
+
writePiScript(join(peer, "dist", "bundle", "cli.js"), "1.2.0");
|
|
95
|
+
const fallback = writePathPiScript(f.root, "1.2.1");
|
|
96
96
|
const base = { name: "@earendil-works/pi-coding-agent", type: "module", exports: { ".": { import: "./dist/index.js" } }, bin: { pi: "dist/bundle/cli.js" } };
|
|
97
97
|
for (const metadata of [{ ...base, name: "impostor" }, { ...base, bin: { pi: "../outside.js" } }, { ...base, bin: {} }, "malformed"]) {
|
|
98
98
|
writeFileSync(join(peer, "package.json"), metadata === "malformed" ? "{" : JSON.stringify(metadata));
|
|
@@ -103,7 +103,7 @@ test("malformed adjacent metadata never silently falls back to PATH; env overrid
|
|
|
103
103
|
writeFileSync(join(peer, "package.json"), JSON.stringify({ ...base, name: "impostor" }));
|
|
104
104
|
const override = spawnSync(process.execPath, [f.launcher, "--version"], { env: { ...f.env, GENTLE_SHELL_PI: fallback }, encoding: "utf8" });
|
|
105
105
|
assert.equal(override.status, 0, override.stderr);
|
|
106
|
-
assert.match(override.stdout, /pi
|
|
106
|
+
assert.match(override.stdout, /pi 1\.2\.1/);
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
// The launcher's own gentle-pi version, exactly as `ownPackageVersion()` in
|
|
@@ -127,7 +127,7 @@ function fixture(t: test.TestContext) {
|
|
|
127
127
|
const home = join(root, "home");
|
|
128
128
|
mkdirSync(home, { recursive: true });
|
|
129
129
|
const piScript = join(root, "fake-pi.mjs");
|
|
130
|
-
writePiScript(piScript, "
|
|
130
|
+
writePiScript(piScript, "1.2.0");
|
|
131
131
|
const gentleShellHome = join(root, "gentle-shell-home");
|
|
132
132
|
const env: NodeJS.ProcessEnv = {
|
|
133
133
|
...process.env,
|
|
@@ -496,7 +496,7 @@ function writeGentleAiScriptDeclaringGentlePi(path: string, extraSources: string
|
|
|
496
496
|
// the invocation), this models what a real `pi remove` does to settings.json
|
|
497
497
|
// closely enough to prove a later plain launch sees the declaration gone and
|
|
498
498
|
// resumes the launcher's own injection.
|
|
499
|
-
function writePiScriptEditingSettingsOnRemove(path: string, version = "
|
|
499
|
+
function writePiScriptEditingSettingsOnRemove(path: string, version = "1.2.0") {
|
|
500
500
|
writeFileSync(
|
|
501
501
|
path,
|
|
502
502
|
[
|
|
@@ -661,6 +661,123 @@ test("first isolated run bootstraps the home, writes fullscreen, and prints the
|
|
|
661
661
|
assert.doesNotMatch(second.stderr, /using a separate home at/);
|
|
662
662
|
});
|
|
663
663
|
|
|
664
|
+
// --- home import-pi ----------------------------------------------------------
|
|
665
|
+
|
|
666
|
+
function importPiFixture(t: test.TestContext) {
|
|
667
|
+
const f = fixture(t);
|
|
668
|
+
const env = { ...f.env, PI_CODING_AGENT_DIR: undefined };
|
|
669
|
+
const piHome = join(f.home, ".pi", "agent");
|
|
670
|
+
mkdirSync(piHome, { recursive: true });
|
|
671
|
+
writeFileSync(join(piHome, "auth.json"), JSON.stringify({ anthropic: { type: "oauth", refresh: "dummy-refresh" } }), { mode: 0o600 });
|
|
672
|
+
writeFileSync(join(piHome, "mcp.json"), JSON.stringify({ mcpServers: {} }));
|
|
673
|
+
writeFileSync(join(piHome, "settings.json"), JSON.stringify({ defaultProvider: "anthropic", defaultModel: "m", packages: ["npm:foreign"], extensions: ["./x.ts"] }));
|
|
674
|
+
writeFileSync(join(piHome, "mcp-cache.json"), "{}");
|
|
675
|
+
return { ...f, env, piHome };
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
test("first isolated run without a pi home keeps the plain --link hint", (t) => {
|
|
679
|
+
const f = fixture(t);
|
|
680
|
+
const first = run({ ...f.env, PI_CODING_AGENT_DIR: undefined }, []);
|
|
681
|
+
assert.equal(first.status, 0, first.stderr);
|
|
682
|
+
assert.match(first.stderr, /Run 'gentle-shell --link' to reuse your pi sign-ins and chats\.\n/);
|
|
683
|
+
assert.doesNotMatch(first.stderr, /import-pi/);
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
test("first isolated run with a pi sign-in also mentions home import-pi", (t) => {
|
|
687
|
+
const f = importPiFixture(t);
|
|
688
|
+
const first = run(f.env, []);
|
|
689
|
+
assert.equal(first.status, 0, first.stderr);
|
|
690
|
+
assert.match(first.stderr, /'gentle-shell --link' to reuse your pi sign-ins and chats, or 'gentle-shell home import-pi'/);
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
test("--help lists home import-pi", (t) => {
|
|
694
|
+
const f = fixture(t);
|
|
695
|
+
const result = run(f.env, ["--help"]);
|
|
696
|
+
assert.match(result.stdout, /gentle-shell \[home selectors\] home import-pi \[--dry-run\]/);
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
test("home import-pi copies sign-ins, MCP servers and default model into the isolated home only", (t) => {
|
|
700
|
+
const f = importPiFixture(t);
|
|
701
|
+
const result = run(f.env, ["home", "import-pi"]);
|
|
702
|
+
assert.equal(result.status, 0, result.stderr);
|
|
703
|
+
assert.match(result.stdout, /auth\.json: copied \(0600\)/);
|
|
704
|
+
assert.match(result.stdout, /settings\.json defaultProvider: copied/);
|
|
705
|
+
assert.doesNotMatch(result.stdout + result.stderr, /dummy-refresh/);
|
|
706
|
+
assert.ok(existsSync(join(f.gentleShellHome, "auth.json")));
|
|
707
|
+
assert.equal(existsSync(join(f.gentleShellHome, "mcp-cache.json")), false);
|
|
708
|
+
if (process.platform !== "win32") assert.equal(statSync(join(f.gentleShellHome, "auth.json")).mode & 0o777, 0o600);
|
|
709
|
+
const settings = JSON.parse(readFileSync(join(f.gentleShellHome, "settings.json"), "utf8"));
|
|
710
|
+
assert.equal(settings.defaultProvider, "anthropic");
|
|
711
|
+
assert.equal(settings.tuiMode, "fullscreen", "the home is bootstrapped as a normal first launch would");
|
|
712
|
+
assert.equal(settings.packages, undefined);
|
|
713
|
+
assert.equal(settings.extensions, undefined);
|
|
714
|
+
assert.ok(existsSync(join(f.gentleShellHome, ".gentle-shell-home")));
|
|
715
|
+
|
|
716
|
+
const again = run(f.env, ["home", "import-pi"]);
|
|
717
|
+
assert.equal(again.status, 0, again.stderr);
|
|
718
|
+
assert.match(again.stdout, /auth\.json: already present, kept/);
|
|
719
|
+
assert.match(again.stdout, /Nothing to import/);
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
test("home import-pi --dry-run prints the plan and creates nothing", (t) => {
|
|
723
|
+
const f = importPiFixture(t);
|
|
724
|
+
const result = run(f.env, ["home", "import-pi", "--dry-run"]);
|
|
725
|
+
assert.equal(result.status, 0, result.stderr);
|
|
726
|
+
assert.match(result.stdout, /auth\.json: would copy/);
|
|
727
|
+
assert.equal(existsSync(f.gentleShellHome), false);
|
|
728
|
+
assert.equal(existsSync(join(f.home, ".gentle-shell")), false);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
test("home import-pi refuses link mode and a target that is the pi home itself", (t) => {
|
|
732
|
+
const f = importPiFixture(t);
|
|
733
|
+
const before = readFileSync(join(f.piHome, "settings.json"), "utf8");
|
|
734
|
+
for (const argv of [["--link", "home", "import-pi"], ["--home", f.piHome, "home", "import-pi"]]) {
|
|
735
|
+
const result = run(f.env, argv);
|
|
736
|
+
assert.equal(result.status, 1, argv.join(" "));
|
|
737
|
+
assert.match(result.stderr, /gentle-shell home import-pi: .*nothing to import/);
|
|
738
|
+
}
|
|
739
|
+
assert.equal(run(f.env, ["home", "link"]).status, 0);
|
|
740
|
+
assert.equal(run(f.env, ["home", "import-pi"]).status, 1);
|
|
741
|
+
assert.equal(readFileSync(join(f.piHome, "settings.json"), "utf8"), before);
|
|
742
|
+
assert.equal(run(f.env, ["home", "import-pi", "--force"]).status, 2);
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
test("a --home filled by home import-pi is still auto-provisioned as gentle-shell's own", (t) => {
|
|
746
|
+
const f = importPiFixture(t);
|
|
747
|
+
const fresh = join(f.root, "fresh-home");
|
|
748
|
+
const empty = join(f.root, "empty-home");
|
|
749
|
+
mkdirSync(empty);
|
|
750
|
+
const gentleAiScript = join(f.root, "fake-gentle-ai.mjs");
|
|
751
|
+
const counterPath = join(f.root, "gentle-ai-runs.log");
|
|
752
|
+
writeGentleAiScriptCountingRuns(gentleAiScript, counterPath);
|
|
753
|
+
const env = enableAutoProvision({ ...f.env, GENTLE_SHELL_GENTLE_AI_BIN: gentleAiScript, GENTLE_SHELL_GENTLE_AI_PIN: "3.6.0" });
|
|
754
|
+
for (const [index, target] of [fresh, empty].entries()) {
|
|
755
|
+
const imported = run(f.env, ["--home", target, "home", "import-pi"]);
|
|
756
|
+
assert.equal(imported.status, 0, imported.stderr);
|
|
757
|
+
assert.match(imported.stdout, /auth\.json: copied/);
|
|
758
|
+
const launch = run(env, ["--home", target, "--mode", "rpc"]);
|
|
759
|
+
assert.equal(launch.status, 0, launch.stderr);
|
|
760
|
+
assert.doesNotMatch(launch.stderr, /already has content and was not set up by gentle-shell/);
|
|
761
|
+
assert.equal(readFileSync(counterPath, "utf8").trim().split("\n").length, index + 1, `auto-provisioning ran for ${target}`);
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
test("home import-pi exits 1 and names the item when a write fails", { skip: process.platform === "win32" }, (t) => {
|
|
766
|
+
const f = importPiFixture(t);
|
|
767
|
+
mkdirSync(f.gentleShellHome, { recursive: true });
|
|
768
|
+
writeFileSync(join(f.gentleShellHome, "settings.json"), "{}");
|
|
769
|
+
writeFileSync(join(f.gentleShellHome, ".gentle-shell-home"), "{}");
|
|
770
|
+
chmodSync(f.gentleShellHome, 0o500);
|
|
771
|
+
let result;
|
|
772
|
+
try {
|
|
773
|
+
result = run(f.env, ["home", "import-pi"]);
|
|
774
|
+
} finally {
|
|
775
|
+
chmodSync(f.gentleShellHome, 0o700); // before the fixture's own cleanup
|
|
776
|
+
}
|
|
777
|
+
assert.equal(result.status, 1);
|
|
778
|
+
assert.match(result.stdout, /auth\.json: failed/);
|
|
779
|
+
});
|
|
780
|
+
|
|
664
781
|
test("forwarded args reach pi after the injected extension flags, in order", (t) => {
|
|
665
782
|
const f = fixture(t);
|
|
666
783
|
const result = run(f.env, ["--mode", "rpc", "-p", "hi"]);
|
|
@@ -815,13 +932,24 @@ test("gentle-shell install npm:<pkg> forwards the subcommand and its argument ve
|
|
|
815
932
|
assert.equal(payload.PI_CODING_AGENT_DIR, f.gentleShellHome);
|
|
816
933
|
});
|
|
817
934
|
|
|
935
|
+
// #2140: pi dispatches `mcp` on argv[0] only, so an injected -e ahead of it
|
|
936
|
+
// turned `gentle-shell mcp list` into an interactive prompt.
|
|
937
|
+
test("gentle-shell mcp add <server> -- <cmd> forwards to pi's own mcp verbatim, with no injected extension flags", (t) => {
|
|
938
|
+
const f = fixture(t);
|
|
939
|
+
const result = run(f.env, ["mcp", "add", "demo", "--", "npx", "-y", "demo-server"]);
|
|
940
|
+
assert.equal(result.status, 0, result.stderr);
|
|
941
|
+
const payload = JSON.parse(result.stdout);
|
|
942
|
+
assert.deepEqual(payload.args, ["mcp", "add", "demo", "--", "npx", "-y", "demo-server"]);
|
|
943
|
+
assert.equal(payload.PI_CODING_AGENT_DIR, f.gentleShellHome);
|
|
944
|
+
});
|
|
945
|
+
|
|
818
946
|
test("a too-old pi exits 1 naming both versions", (t) => {
|
|
819
947
|
const f = fixture(t);
|
|
820
948
|
writePiScript(f.piScript, "0.80.0");
|
|
821
949
|
const result = run(f.env, []);
|
|
822
950
|
assert.equal(result.status, 1);
|
|
823
951
|
assert.match(result.stderr, /0\.80\.0/);
|
|
824
|
-
assert.match(result.stderr, /
|
|
952
|
+
assert.match(result.stderr, /1\.2\.0/);
|
|
825
953
|
});
|
|
826
954
|
|
|
827
955
|
test("--version prints three lines", (t) => {
|
|
@@ -831,7 +959,7 @@ test("--version prints three lines", (t) => {
|
|
|
831
959
|
const lines = result.stdout.trim().split("\n");
|
|
832
960
|
assert.equal(lines.length, 3);
|
|
833
961
|
assert.match(lines[0], /^gentle-shell /);
|
|
834
|
-
assert.match(lines[1], /^pi
|
|
962
|
+
assert.match(lines[1], /^pi 1\.2\.0$/);
|
|
835
963
|
assert.match(lines[2], /^home isolated /);
|
|
836
964
|
});
|
|
837
965
|
|
|
@@ -1278,7 +1406,7 @@ test("gentle-shell setup propagates a non-zero pi remove exit code with an actio
|
|
|
1278
1406
|
const gentleAiScript = join(f.root, "fake-gentle-ai.mjs");
|
|
1279
1407
|
writeGentleAiScriptDeclaringConflict(gentleAiScript);
|
|
1280
1408
|
const failingPiScript = join(f.root, "fake-pi-remove-fails.mjs");
|
|
1281
|
-
writePiScript(failingPiScript, "
|
|
1409
|
+
writePiScript(failingPiScript, "1.2.0", 7);
|
|
1282
1410
|
const env = { ...f.env, GENTLE_SHELL_GENTLE_AI_BIN: gentleAiScript, GENTLE_SHELL_PI: failingPiScript };
|
|
1283
1411
|
|
|
1284
1412
|
const result = run(env, ["setup"]);
|
|
@@ -1299,7 +1427,7 @@ test("gentle-shell setup --home <dir> includes --home <dir> in the failing-remov
|
|
|
1299
1427
|
const gentleAiScript = join(f.root, "fake-gentle-ai.mjs");
|
|
1300
1428
|
writeGentleAiScriptDeclaringConflict(gentleAiScript);
|
|
1301
1429
|
const failingPiScript = join(f.root, "fake-pi-remove-fails.mjs");
|
|
1302
|
-
writePiScript(failingPiScript, "
|
|
1430
|
+
writePiScript(failingPiScript, "1.2.0", 7);
|
|
1303
1431
|
const env = { ...f.env, GENTLE_SHELL_GENTLE_AI_BIN: gentleAiScript, GENTLE_SHELL_PI: failingPiScript };
|
|
1304
1432
|
|
|
1305
1433
|
const result = run(env, ["--home", target, "setup"]);
|
|
@@ -1323,7 +1451,7 @@ test("gentle-shell setup shell-quotes a --home path containing a space in the fa
|
|
|
1323
1451
|
const gentleAiScript = join(f.root, "fake-gentle-ai.mjs");
|
|
1324
1452
|
writeGentleAiScriptDeclaringConflict(gentleAiScript);
|
|
1325
1453
|
const failingPiScript = join(f.root, "fake-pi-remove-fails.mjs");
|
|
1326
|
-
writePiScript(failingPiScript, "
|
|
1454
|
+
writePiScript(failingPiScript, "1.2.0", 7);
|
|
1327
1455
|
const env = { ...f.env, GENTLE_SHELL_GENTLE_AI_BIN: gentleAiScript, GENTLE_SHELL_PI: failingPiScript };
|
|
1328
1456
|
|
|
1329
1457
|
const result = run(env, ["--home", target, "setup"]);
|
|
@@ -1865,7 +1993,7 @@ test("setup gives Gentle AI a `pi` on its PATH that runs the same Pi gentle-shel
|
|
|
1865
1993
|
|
|
1866
1994
|
const result = run(env, ["setup"]);
|
|
1867
1995
|
assert.equal(result.status, 0, result.stderr);
|
|
1868
|
-
assert.deepEqual(JSON.parse(readFileSync(probe, "utf8")), { status: 0, error: null, stdout: "
|
|
1996
|
+
assert.deepEqual(JSON.parse(readFileSync(probe, "utf8")), { status: 0, error: null, stdout: "1.2.0\n" });
|
|
1869
1997
|
// The launcher is temporary: nothing is left behind in the temp folder it used.
|
|
1870
1998
|
assert.deepEqual(readdirSync(temp), []);
|
|
1871
1999
|
});
|
|
@@ -2171,7 +2299,7 @@ test("a hung 'pi remove' during post-install cleanup is killed after the timeout
|
|
|
2171
2299
|
[
|
|
2172
2300
|
"#!/usr/bin/env node",
|
|
2173
2301
|
"const args = process.argv.slice(2);",
|
|
2174
|
-
'if (args.includes("--version")) { console.log("
|
|
2302
|
+
'if (args.includes("--version")) { console.log("1.2.0"); process.exit(0); }',
|
|
2175
2303
|
"if (args[0] === 'remove') { await new Promise((resolve) => setTimeout(resolve, 60000)); process.exit(0); }",
|
|
2176
2304
|
"console.log(JSON.stringify({ args, PI_CODING_AGENT_DIR: process.env.PI_CODING_AGENT_DIR }));",
|
|
2177
2305
|
"process.exit(0);",
|
|
@@ -2603,6 +2731,13 @@ test("--help mentions the setup subcommand", (t) => {
|
|
|
2603
2731
|
assert.match(result.stdout, /\bsetup\b/);
|
|
2604
2732
|
});
|
|
2605
2733
|
|
|
2734
|
+
test("--help lists the mcp subcommand", (t) => {
|
|
2735
|
+
const f = fixture(t);
|
|
2736
|
+
const result = run(f.env, ["--help"]);
|
|
2737
|
+
assert.equal(result.status, 0, result.stderr);
|
|
2738
|
+
assert.match(result.stdout, /gentle-shell mcp /);
|
|
2739
|
+
});
|
|
2740
|
+
|
|
2606
2741
|
// --- --link takeover of a conflicting path package -----------------------
|
|
2607
2742
|
//
|
|
2608
2743
|
// Regression coverage for the settings.json shape that triggered the tool
|
|
@@ -3186,7 +3321,7 @@ function writeHandoffPiScript(path: string, exitCode = 0) {
|
|
|
3186
3321
|
"#!/usr/bin/env node",
|
|
3187
3322
|
"const { writeFileSync } = require('node:fs');",
|
|
3188
3323
|
"const args = process.argv.slice(2);",
|
|
3189
|
-
"if (args.includes('--version')) { console.log('
|
|
3324
|
+
"if (args.includes('--version')) { console.log('1.2.0'); process.exit(0); }",
|
|
3190
3325
|
"const handoff = process.env.GENTLE_SHELL_RESUME_HANDOFF;",
|
|
3191
3326
|
"if (handoff) writeFileSync(handoff, JSON.stringify({ sessionId: 'abc' }));",
|
|
3192
3327
|
"if (process.env.PI_STUB_PRINT_ENV) console.log(JSON.stringify({ args, handoff }));",
|
|
@@ -3287,7 +3422,7 @@ function writeWaitingPiScript(path: string) {
|
|
|
3287
3422
|
[
|
|
3288
3423
|
"#!/usr/bin/env node",
|
|
3289
3424
|
"const { writeFileSync } = require('node:fs');",
|
|
3290
|
-
"if (process.argv.includes('--version')) { console.log('
|
|
3425
|
+
"if (process.argv.includes('--version')) { console.log('1.2.0'); process.exit(0); }",
|
|
3291
3426
|
"writeFileSync(process.env.GENTLE_SHELL_RESUME_HANDOFF, JSON.stringify({ sessionId: 'abc' }));",
|
|
3292
3427
|
"const quit = () => { process.stdout.write('To resume this session: pi --session abc\\n'); process.exit(0); };",
|
|
3293
3428
|
"process.on('SIGHUP', quit);",
|
|
@@ -3329,7 +3464,7 @@ test("on a TTY a cross-project session resumes by its session file", { skip: !ha
|
|
|
3329
3464
|
[
|
|
3330
3465
|
"#!/usr/bin/env node",
|
|
3331
3466
|
"const { writeFileSync } = require('node:fs');",
|
|
3332
|
-
"if (process.argv.includes('--version')) { console.log('
|
|
3467
|
+
"if (process.argv.includes('--version')) { console.log('1.2.0'); process.exit(0); }",
|
|
3333
3468
|
`writeFileSync(process.env.GENTLE_SHELL_RESUME_HANDOFF, JSON.stringify({ sessionId: 'abc', sessionFile: ${JSON.stringify(sessionFile)} }));`,
|
|
3334
3469
|
"process.stdout.write('To resume this session: pi --session abc\\n');",
|
|
3335
3470
|
"",
|
|
@@ -3350,3 +3485,31 @@ test("on a TTY without colors the gentle-shell line has no ANSI styling", { skip
|
|
|
3350
3485
|
const out = runInPty({ ...f.env, GENTLE_SHELL_PI: piScript, PTY_NO_COLOR: "1" }, ["--link"]);
|
|
3351
3486
|
assert.ok(out.endsWith(`${PI_HINT}To resume in gentle-shell: gentle-shell --link --session abc\r\n`), JSON.stringify(out));
|
|
3352
3487
|
});
|
|
3488
|
+
|
|
3489
|
+
// #2143: a Gentle AI that honors GENTLE_AI_PI_EXCLUDE_PACKAGES (gentle-ai#5503)
|
|
3490
|
+
// never declares npm:gentle-pi, so setup neither runs `pi remove` nor prints
|
|
3491
|
+
// the contradictory "removing npm:gentle-pi" line.
|
|
3492
|
+
test("gentle-shell setup asks Gentle AI to skip npm:gentle-pi, so nothing is installed only to be removed", (t) => {
|
|
3493
|
+
const f = fixture(t);
|
|
3494
|
+
const gentleAiScript = join(f.root, "fake-gentle-ai.mjs");
|
|
3495
|
+
writeFileSync(gentleAiScript, [
|
|
3496
|
+
"#!/usr/bin/env node",
|
|
3497
|
+
"import { readFileSync, writeFileSync } from 'node:fs';",
|
|
3498
|
+
"import { join } from 'node:path';",
|
|
3499
|
+
"const excluded = (process.env.GENTLE_AI_PI_EXCLUDE_PACKAGES ?? '').split(',').map((entry) => entry.trim());",
|
|
3500
|
+
"const settingsPath = join(process.env.PI_CODING_AGENT_DIR, 'settings.json');",
|
|
3501
|
+
"const settings = JSON.parse(readFileSync(settingsPath, 'utf8'));",
|
|
3502
|
+
"settings.packages = ['npm:gentle-pi', 'npm:pi-btw'].filter((source) => !excluded.includes(source));",
|
|
3503
|
+
"writeFileSync(settingsPath, JSON.stringify(settings));",
|
|
3504
|
+
"process.stdout.write(JSON.stringify({ args: process.argv.slice(2), excluded: process.env.GENTLE_AI_PI_EXCLUDE_PACKAGES ?? null }) + '\\n');",
|
|
3505
|
+
"",
|
|
3506
|
+
].join("\n"));
|
|
3507
|
+
chmodSync(gentleAiScript, 0o755);
|
|
3508
|
+
const result = run({ ...f.env, GENTLE_SHELL_GENTLE_AI_BIN: gentleAiScript }, ["setup"]);
|
|
3509
|
+
assert.equal(result.status, 0, result.stderr);
|
|
3510
|
+
assert.doesNotMatch(result.stderr, /removing npm:gentle-pi/);
|
|
3511
|
+
const lines = result.stdout.trim().split("\n").filter((line) => line.length > 0);
|
|
3512
|
+
assert.equal(lines.length, 1, "no pi remove ran: " + result.stdout);
|
|
3513
|
+
assert.deepEqual(JSON.parse(lines[0]), { args: ["install", "--agent", "pi", "--scope", "global"], excluded: "npm:gentle-pi" });
|
|
3514
|
+
});
|
|
3515
|
+
|