gentle-pi 2.7.0 → 3.1.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 +24 -6
- package/assets/agents/gentle-ai-worker.md +5 -1
- package/assets/agents/sdd-apply.md +9 -7
- package/assets/agents/sdd-archive.md +42 -23
- package/assets/agents/sdd-proposal.md +2 -2
- package/assets/agents/sdd-remediate.md +4 -4
- package/assets/agents/sdd-research.md +20 -48
- package/assets/agents/sdd-tasks.md +5 -5
- package/assets/agents/sdd-verify.md +6 -28
- package/assets/chains/sdd-full.chain.md +4 -22
- package/assets/chains/sdd-verify.chain.md +3 -12
- package/assets/orchestrator-delegation.md +33 -3
- package/assets/orchestrator-memory.md +20 -7
- package/assets/orchestrator.md +5 -3
- package/assets/sdd-orchestrator-workflow.md +25 -58
- package/assets/support/sdd-status-contract.md +9 -12
- package/docs/gentle-shell.md +14 -4
- package/docs/readme-reference.md +175 -36
- package/extensions/codegraph-tools.ts +2 -0
- package/extensions/gentle-agents.ts +281 -361
- package/extensions/gentle-ai.ts +588 -117
- package/extensions/gentle-shell.ts +74 -30
- package/extensions/pi-pretty.ts +63 -14
- package/extensions/quiet-tools.ts +1 -2
- package/extensions/startup-banner.ts +10 -9
- package/lib/agent-home.ts +8 -0
- package/lib/agent-profile-pin.ts +336 -0
- package/lib/agent-profiles.ts +28 -8
- package/lib/agents-config.ts +24 -2
- package/lib/agents-history.ts +3 -97
- package/lib/agents-keys.ts +27 -0
- package/lib/agents-protocol.ts +2 -15
- package/lib/agents-runner.ts +67 -111
- package/lib/agents-session-transport.ts +691 -0
- package/lib/command-palette-catalog.ts +87 -0
- package/lib/command-palette.ts +346 -0
- package/lib/native-choice-list.ts +5 -0
- package/lib/native-review-cli.ts +28 -97
- package/lib/review-publication-gate.ts +11 -1
- package/lib/review-repository.ts +1 -1
- package/lib/review-snapshot.ts +1 -0
- package/lib/review-transaction.ts +4 -2
- package/lib/sdd-preflight.ts +2 -1
- package/lib/sdd-research-capabilities.ts +18 -152
- package/lib/sdd-status.ts +7 -779
- package/lib/session-change-capture.ts +2 -1
- package/lib/session-changes.ts +8 -1
- package/lib/shell-bar.ts +21 -12
- package/lib/shell-card.ts +8 -12
- package/lib/shell-changes.ts +3 -2
- package/lib/shell-prompt.ts +25 -8
- package/lib/shell-sidebar-banner.ts +2 -2
- package/lib/shell-sidebar-layout.ts +5 -2
- package/lib/windows-session-transport.ts +877 -0
- package/package.json +3 -3
- package/runtime/native-review-cli.mjs +27 -96
- package/runtime/windows-session-transport.ps1 +791 -0
- package/scripts/gentle-ai-installer.mjs +12 -12
- package/scripts/test-packed-runner.mjs +1668 -20
- package/scripts/verify-package-files.mjs +2 -3
- package/tests/agent-home.test.ts +52 -0
- package/tests/agent-profiles.test.ts +30 -1
- package/tests/agents-config.test.ts +44 -0
- package/tests/agents-history.test.ts +12 -24
- package/tests/agents-runner.test.ts +307 -58
- package/tests/agents-session-transport-process.test.ts +249 -0
- package/tests/agents-session-transport.test.ts +823 -0
- package/tests/artifact-language.test.ts +10 -7
- package/tests/command-palette.test.ts +378 -0
- package/tests/delegated-key-learnings-contract.test.ts +2 -2
- package/tests/fixtures/agents-session-transport-process.mjs +108 -0
- package/tests/fixtures/legacy/sdd-research-v2.5.0.md +54 -0
- package/tests/fixtures/windows-session-bootstrap.ps1 +129 -0
- package/tests/fixtures/windows-session-compile.ps1 +110 -0
- package/tests/gentle-agents.test.ts +849 -356
- package/tests/gentle-ai-binary.test.ts +3 -3
- package/tests/gentle-ai-installer.test.ts +51 -51
- package/tests/gentle-ai.test.ts +472 -4
- package/tests/gentle-shell.test.ts +201 -8
- package/tests/native-choice-list.test.ts +13 -0
- package/tests/native-review-capability-contract.test.ts +30 -1
- package/tests/native-review-cli.test.ts +0 -33
- package/tests/odd-routing-contract.test.ts +208 -0
- package/tests/orchestrator-budget.test.ts +17 -2
- package/tests/package-manifest.test.ts +119 -31
- package/tests/persona-single-channel.test.ts +3 -3
- package/tests/pi-pretty.test.ts +45 -0
- package/tests/profile-pin.test.ts +370 -0
- package/tests/quiet-tool-rendering.test.ts +32 -5
- package/tests/review-contract-prompt.test.ts +9 -0
- package/tests/review-controller.test.ts +0 -44
- package/tests/review-session-standing-permission-ipc.test.ts +427 -13
- package/tests/runtime-harness.mjs +4 -4
- package/tests/sdd-agent-tools.test.ts +15 -36
- package/tests/sdd-archive-replay.test.ts +82 -0
- package/tests/sdd-classical-continuation.test.ts +74 -0
- package/tests/sdd-execution-routing-contract.test.ts +18 -2
- package/tests/sdd-managed-runtime-settlement.test.ts +42 -330
- package/tests/sdd-native-managed-uptake.test.ts +11 -21
- package/tests/sdd-no-attempts-contract.test.ts +15 -0
- package/tests/sdd-odd-integration.test.ts +33 -0
- package/tests/sdd-optional-research.test.ts +124 -0
- package/tests/sdd-planning-routing-contract.test.ts +1 -1
- package/tests/sdd-preflight-rpc-input.test.ts +125 -0
- package/tests/sdd-preflight.test.ts +1 -1
- package/tests/sdd-research-capabilities.test.ts +20 -162
- package/tests/sdd-selection-transport.test.ts +180 -88
- package/tests/sdd-status.test.ts +5 -778
- package/tests/sdd-task-truth.test.ts +43 -0
- package/tests/session-change-capture.test.ts +20 -2
- package/tests/session-changes.test.ts +11 -0
- package/tests/shell-bar.test.ts +21 -0
- package/tests/shell-card.test.ts +8 -6
- package/tests/shell-changes.test.ts +8 -0
- package/tests/shell-prompt.test.ts +41 -7
- package/tests/shell-sidebar-banner.test.ts +4 -4
- package/tests/shell-sidebar-layout.test.ts +97 -13
- package/tests/startup-banner.test.ts +55 -2
- package/tests/windows-hidden-processes.test.ts +303 -0
- package/tests/windows-session-bootstrap.test.ts +1772 -0
- package/tests/windows-session-compile.test.ts +170 -0
- package/tests/windows-session-transport.test.ts +754 -0
- package/assets/agents/sdd-sync.md +0 -146
- package/lib/openspec-guardrails.ts +0 -99
- package/tests/native-sdd-attempt-authority.test.ts +0 -240
- package/tests/openspec-guardrails.test.ts +0 -71
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { homedir, tmpdir } from "node:os";
|
|
13
13
|
import { dirname, join } from "node:path";
|
|
14
14
|
import test from "node:test";
|
|
15
|
+
import ts from "typescript";
|
|
15
16
|
import { fileURLToPath } from "node:url";
|
|
16
17
|
import { applyModelConfig } from "../extensions/gentle-ai.ts";
|
|
17
18
|
import { resolveGentlePiAgentHome } from "../lib/agent-home.ts";
|
|
@@ -182,7 +183,7 @@ test("npm publication is bound to the exact package tag and triggering commit",
|
|
|
182
183
|
packageJson.repository,
|
|
183
184
|
{
|
|
184
185
|
type: "git",
|
|
185
|
-
url: "git+https://github.com/Gentleman-Programming/gentle-
|
|
186
|
+
url: "git+https://github.com/Gentleman-Programming/gentle-shell.git",
|
|
186
187
|
},
|
|
187
188
|
"trusted publishing requires the exact case-sensitive npm repository identity",
|
|
188
189
|
);
|
|
@@ -218,11 +219,29 @@ test("generated runtime modules and packed-package checks are deterministic", ()
|
|
|
218
219
|
assert.match(ci, /pnpm run check:runtime-modules/);
|
|
219
220
|
assert.match(ci, /pnpm run test:packed-package/);
|
|
220
221
|
assert.match(generator, /Generated by scripts\/build-runtime-modules\.mjs/);
|
|
221
|
-
|
|
222
|
-
assert.
|
|
223
|
-
assert.
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
const ordinaryPostinstall = readNamedFunction(packedRunner, "testHookedPackedRunner");
|
|
223
|
+
assert.match(ordinaryPostinstall, /\["install"[^\]]*"--ignore-scripts=false"/s, "ordinary packed install must explicitly enable postinstall");
|
|
224
|
+
assert.doesNotMatch(ordinaryPostinstall, /\["install"[^\]]*"--ignore-scripts"(?!\=false)/s);
|
|
225
|
+
for (const name of [
|
|
226
|
+
"testSdkLifecyclePackedSession",
|
|
227
|
+
"testWindowsStartupTimingPackedHelper",
|
|
228
|
+
"testWindowsStartupTimingEnvironmentExperiment",
|
|
229
|
+
"testUnhookedPackedImports",
|
|
230
|
+
]) {
|
|
231
|
+
const isolatedInstall = readNamedFunction(packedRunner, name);
|
|
232
|
+
assert.match(isolatedInstall, /\["install"[^\]]*"--ignore-scripts"(?!\=false)/s, `${name} must isolate installation scripts`);
|
|
233
|
+
assert.doesNotMatch(isolatedInstall, /\["install"[^\]]*"--ignore-scripts=false"/s, `${name} must not enable postinstall`);
|
|
234
|
+
}
|
|
235
|
+
const windowsNpmInvocation = readNamedFunction(packedRunner, "windowsNpmInvocation");
|
|
236
|
+
const runNpmWithEnv = readNamedFunction(packedRunner, "runNpmWithEnv");
|
|
237
|
+
assert.match(windowsNpmInvocation, /execFileSync\("where\.exe", \["npm"\]/);
|
|
238
|
+
assert.match(windowsNpmInvocation, /return \{ file: process\.execPath, prefix: \[installedCli\] \}/);
|
|
239
|
+
assert.match(windowsNpmInvocation, /return \{ file: path, prefix: \[\] \}/);
|
|
240
|
+
assert.match(windowsNpmInvocation, /could not resolve npm-cli\.js without a command shell/);
|
|
241
|
+
assert.doesNotMatch(windowsNpmInvocation, /ComSpec|cmd\.exe/);
|
|
242
|
+
assert.match(runNpmWithEnv, /execFileSync\(invocation\.file, \[\.\.\.invocation\.prefix, \.\.\.arguments_\]/);
|
|
243
|
+
assert.match(runNpmWithEnv, /\.\.\.options, env/);
|
|
244
|
+
assert.doesNotMatch(packedRunner, /shell\s*:\s*true/);
|
|
226
245
|
assert.match(packedRunner, /review", "capabilities", "--contract", "gentle-ai\.review-integration\/v2"/);
|
|
227
246
|
assert.doesNotMatch(packedRunner, /git-commit-transaction|transaction runner/i);
|
|
228
247
|
});
|
|
@@ -247,8 +266,8 @@ test("package manifest installs pi-pretty through a wrapper without bundling nat
|
|
|
247
266
|
|
|
248
267
|
assert.equal(
|
|
249
268
|
packageJson.dependencies?.["@heyhuynhgiabuu/pi-pretty"],
|
|
250
|
-
"0.6.
|
|
251
|
-
"gentle-pi must install the tested pi-pretty version
|
|
269
|
+
"0.6.27",
|
|
270
|
+
"gentle-pi must install the tested pi-pretty version containing the model-visible result integrity fix",
|
|
252
271
|
);
|
|
253
272
|
assert.ok(
|
|
254
273
|
packageJson.pi?.extensions?.includes("./extensions"),
|
|
@@ -260,10 +279,22 @@ test("package manifest installs pi-pretty through a wrapper without bundling nat
|
|
|
260
279
|
),
|
|
261
280
|
"gentle-pi must not reference pnpm-unportable nested node_modules paths",
|
|
262
281
|
);
|
|
282
|
+
const piPrettyWrapperPath = join(PACKAGE_ROOT, "extensions", "pi-pretty.ts");
|
|
263
283
|
assert.ok(
|
|
264
|
-
existsSync(
|
|
284
|
+
existsSync(piPrettyWrapperPath),
|
|
265
285
|
"gentle-pi must expose pi-pretty through a packaged wrapper extension",
|
|
266
286
|
);
|
|
287
|
+
const piPrettyWrapper = readFileSync(piPrettyWrapperPath, "utf8");
|
|
288
|
+
assert.match(
|
|
289
|
+
piPrettyWrapper,
|
|
290
|
+
/import\("@heyhuynhgiabuu\/pi-pretty"\)/,
|
|
291
|
+
"the wrapper must use the compiled-runtime-safe ESM loader",
|
|
292
|
+
);
|
|
293
|
+
assert.doesNotMatch(
|
|
294
|
+
piPrettyWrapper,
|
|
295
|
+
/requireFromRealPackage\("@heyhuynhgiabuu\/pi-pretty"\)/,
|
|
296
|
+
"the wrapper must not resolve the pi-pretty package root through createRequire",
|
|
297
|
+
);
|
|
267
298
|
assert.ok(
|
|
268
299
|
existsSync(join(PACKAGE_ROOT, "extensions", "quiet-tools.ts")),
|
|
269
300
|
"gentle-pi must expose quiet built-in tool rendering through a packaged extension",
|
|
@@ -278,20 +309,20 @@ test("package manifest installs pi-pretty through a wrapper without bundling nat
|
|
|
278
309
|
);
|
|
279
310
|
});
|
|
280
311
|
|
|
281
|
-
test("package verification binds the published Gentle AI
|
|
312
|
+
test("package verification binds the published Gentle AI v3.0.1 runtime pin", () => {
|
|
282
313
|
const installer = readFileSync(join(PACKAGE_ROOT, "scripts", "gentle-ai-installer.mjs"), "utf8");
|
|
283
314
|
const binary = readFileSync(join(PACKAGE_ROOT, "lib", "gentle-ai-binary.ts"), "utf8");
|
|
284
315
|
const verifier = readFileSync(join(PACKAGE_ROOT, "scripts", "verify-package-files.mjs"), "utf8");
|
|
285
316
|
|
|
286
|
-
assert.match(installer, /INSTALLER_VERSION = "
|
|
317
|
+
assert.match(installer, /INSTALLER_VERSION = "3\.0\.1"/);
|
|
287
318
|
assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_PACKAGE.*GENTLE_AI_WINDOWS_SOURCE_MODULE/);
|
|
288
|
-
assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:
|
|
319
|
+
assert.match(installer, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:r2z8yf0CdL0ZPPkMEKI3km6SitjOIC\/DQDvtipc1Igs="/);
|
|
289
320
|
assert.match(installer, /GOTOOLCHAIN: "local"/);
|
|
290
321
|
assert.match(installer, /GOSUMDB: "sum\.golang\.org"/);
|
|
291
322
|
assert.match(binary, /GENTLE_AI_VERSION = INSTALLER_VERSION/);
|
|
292
323
|
assert.match(binary, /GO_SUMDB_SOURCE_BUILD/);
|
|
293
324
|
assert.match(binary, /GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM/);
|
|
294
|
-
assert.match(verifier, /
|
|
325
|
+
assert.match(verifier, /v3\.0\.1/);
|
|
295
326
|
});
|
|
296
327
|
|
|
297
328
|
|
|
@@ -355,6 +386,46 @@ function nestedContractFields(contract: string, parent: string): string[] {
|
|
|
355
386
|
return contractFields(nestedBlock.join("\n"), 2);
|
|
356
387
|
}
|
|
357
388
|
|
|
389
|
+
function readNamedFunction(source: string, name: string): string {
|
|
390
|
+
const fileName = "test-packed-runner.mjs";
|
|
391
|
+
const options: ts.CompilerOptions = {
|
|
392
|
+
allowJs: true,
|
|
393
|
+
noLib: true,
|
|
394
|
+
noResolve: true,
|
|
395
|
+
target: ts.ScriptTarget.Latest,
|
|
396
|
+
};
|
|
397
|
+
const host = ts.createCompilerHost(options, true);
|
|
398
|
+
host.getSourceFile = (requested, languageVersion) =>
|
|
399
|
+
requested === fileName
|
|
400
|
+
? ts.createSourceFile(requested, source, languageVersion, true, ts.ScriptKind.JS)
|
|
401
|
+
: undefined;
|
|
402
|
+
host.fileExists = (requested) => requested === fileName;
|
|
403
|
+
host.readFile = (requested) => requested === fileName ? source : undefined;
|
|
404
|
+
host.getCurrentDirectory = () => ".";
|
|
405
|
+
host.writeFile = () => {};
|
|
406
|
+
const program = ts.createProgram([fileName], options, host);
|
|
407
|
+
const sourceFile = program.getSourceFile(fileName);
|
|
408
|
+
assert.ok(sourceFile, "packed runner source must be available to the in-memory compiler host");
|
|
409
|
+
assert.equal(program.getSyntacticDiagnostics(sourceFile).length, 0, "packed runner source must have no syntactic diagnostics");
|
|
410
|
+
const declarations = sourceFile.statements.filter(
|
|
411
|
+
(statement): statement is ts.FunctionDeclaration =>
|
|
412
|
+
ts.isFunctionDeclaration(statement) && statement.name?.text === name,
|
|
413
|
+
);
|
|
414
|
+
assert.equal(declarations.length, 1, `packed runner must define exactly one top-level ${name}`);
|
|
415
|
+
const declaration = declarations[0];
|
|
416
|
+
assert.ok(declaration.body, `${name} must have a complete function body`);
|
|
417
|
+
assert.ok(declaration.body.end > declaration.body.pos, `${name} must have a non-empty function body`);
|
|
418
|
+
return source.slice(declaration.getStart(sourceFile), declaration.end);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
test("readNamedFunction fails closed for missing, duplicate, and incomplete declarations", () => {
|
|
422
|
+
assert.equal(readNamedFunction("function target() { return 1; }\nfunction neighbor() { return 2; }\n", "target"), "function target() { return 1; }");
|
|
423
|
+
assert.throws(() => readNamedFunction("const target = () => {};\n", "target"), /exactly one top-level target/);
|
|
424
|
+
assert.throws(() => readNamedFunction("function target() {}\nfunction target() {}\n", "target"), /exactly one top-level target/);
|
|
425
|
+
assert.throws(() => readNamedFunction("function target() {\n", "target"), /packed runner source must have no syntactic diagnostics/);
|
|
426
|
+
assert.throws(() => readNamedFunction("function target() {}\nfunction neighbor( {}\n", "target"), /packed runner source must have no syntactic diagnostics/);
|
|
427
|
+
});
|
|
428
|
+
|
|
358
429
|
function readMarkdownSection(source: string, heading: string): string {
|
|
359
430
|
const lines = source.split(/\r?\n/);
|
|
360
431
|
const matches = lines.flatMap((line, index) => {
|
|
@@ -548,7 +619,7 @@ const EXPECTED_OWNER_ASSETS: Record<PackageAssetOwner, readonly string[]> = {
|
|
|
548
619
|
"agents/sdd-apply.md", "agents/sdd-archive.md", "agents/sdd-design.md",
|
|
549
620
|
"agents/sdd-explore.md", "agents/sdd-init.md", "agents/sdd-onboard.md",
|
|
550
621
|
"agents/sdd-proposal.md", "agents/sdd-remediate.md", "agents/sdd-research.md", "agents/sdd-spec.md",
|
|
551
|
-
"agents/sdd-status.md", "agents/sdd-
|
|
622
|
+
"agents/sdd-status.md", "agents/sdd-tasks.md", "agents/sdd-verify.md",
|
|
552
623
|
"chains/sdd-full.chain.md", "chains/sdd-plan.chain.md", "chains/sdd-verify.chain.md",
|
|
553
624
|
"gentle-ai/support/sdd-status-contract.md", "gentle-ai/support/strict-tdd-verify.md",
|
|
554
625
|
"gentle-ai/support/strict-tdd.md",
|
|
@@ -591,10 +662,10 @@ test("legacy all-assets installation covers every packaged file with explicit ow
|
|
|
591
662
|
assert.equal(getPackageAssetOwner(key), undefined, "unknown assets must not default to SDD");
|
|
592
663
|
}
|
|
593
664
|
withIsolatedAssetHome((agentHome) => {
|
|
594
|
-
assert.deepEqual(installSddAssets(agentHome, false), { agents:
|
|
665
|
+
assert.deepEqual(installSddAssets(agentHome, false), { agents: 23, chains: 4, support: 3, skipped: 0 });
|
|
595
666
|
assert.deepEqual(Object.keys(installedAssetManifest(agentHome).assets).sort(), packaged);
|
|
596
|
-
assert.deepEqual(installSddAssets(agentHome, false), { agents: 0, chains: 0, support: 0, skipped:
|
|
597
|
-
assert.deepEqual(installSddAssets(agentHome, true), { agents:
|
|
667
|
+
assert.deepEqual(installSddAssets(agentHome, false), { agents: 0, chains: 0, support: 0, skipped: 30 });
|
|
668
|
+
assert.deepEqual(installSddAssets(agentHome, true), { agents: 23, chains: 4, support: 3, skipped: 0 });
|
|
598
669
|
});
|
|
599
670
|
});
|
|
600
671
|
|
|
@@ -651,14 +722,8 @@ test("selective review migration adopts only untouched legacy copies and preserv
|
|
|
651
722
|
});
|
|
652
723
|
|
|
653
724
|
test("unowned legacy research migrates by exact normalized hash, preserving routing and user edits", () => {
|
|
654
|
-
|
|
655
|
-
const
|
|
656
|
-
const legacy = packaged
|
|
657
|
-
.replace(/## Parent Preflight Transport\n[\s\S]*?(?=## Skill Resolution Contract)/, "")
|
|
658
|
-
.replace(/## Bounded artifact handoff\n[\s\S]*?(?=## Memory Contract)/, "")
|
|
659
|
-
.replace(/ - fetch_content\n - web_search\n - source_check\n - get_search_content\n/, "")
|
|
660
|
-
.replace(/- Use the injected `## SDD Research Capabilities`[\s\S]*?(?=- Admission denial)/, oldAdmission)
|
|
661
|
-
.replace(/Use `done` only when all selected questions[\s\S]*?product decisions remain separately confirmed by the parent\./i, "For this runtime the outcome is `blocked` with an admission denial and no claims.");
|
|
725
|
+
// Historical bytes must not be reconstructed from the evolving current agent.
|
|
726
|
+
const legacy = readFileSync(join(PACKAGE_ROOT, "tests/fixtures/legacy/sdd-research-v2.5.0.md"), "utf8");
|
|
662
727
|
const manifest = JSON.parse(readFileSync(join(PACKAGE_ROOT, "assets", "migrations", "managed-assets-v2.5.0.json"), "utf8"));
|
|
663
728
|
assert.equal(sha256(legacy), manifest.assets["agents/sdd-research.md"], "fixture reconstruction must match observed old package bytes");
|
|
664
729
|
const temporary = mkdtempSync(join(tmpdir(), "gentle-research-migration-"));
|
|
@@ -1496,22 +1561,22 @@ test("orchestrator routes generic roles without static RDD lens routing", () =>
|
|
|
1496
1561
|
assert.match(core, /this package invents no lifecycle instructions/);
|
|
1497
1562
|
});
|
|
1498
1563
|
|
|
1499
|
-
test("pi-pretty wrapper uses
|
|
1564
|
+
test("pi-pretty wrapper uses cached ESM loading for compiled and pnpm symlink installs", () => {
|
|
1500
1565
|
const wrapper = readFileSync(
|
|
1501
1566
|
join(PACKAGE_ROOT, "extensions", "pi-pretty.ts"),
|
|
1502
1567
|
"utf8",
|
|
1503
1568
|
);
|
|
1504
1569
|
|
|
1505
|
-
assert.
|
|
1506
|
-
assert.match(wrapper, /
|
|
1507
|
-
assert.match(wrapper,
|
|
1570
|
+
assert.doesNotMatch(wrapper, /realpathSync|createRequire/);
|
|
1571
|
+
assert.match(wrapper, /piPrettyExtensionPromise/);
|
|
1572
|
+
assert.match(wrapper, /import\("@heyhuynhgiabuu\/pi-pretty"\)/);
|
|
1508
1573
|
assert.match(wrapper, /PI_PRETTY_SUPPRESSED_TOOL_NAMES/);
|
|
1509
1574
|
assert.match(wrapper, /quietToolsEnabled/);
|
|
1510
1575
|
});
|
|
1511
1576
|
|
|
1512
|
-
test("
|
|
1577
|
+
test("v3.1.0 release package and runtime stop before publication", () => {
|
|
1513
1578
|
const packageJson = readPackageJson();
|
|
1514
|
-
assert.equal(packageJson.version, "
|
|
1579
|
+
assert.equal(packageJson.version, "3.1.0", "the release manifest must remain explicitly pinned to v3.1.0");
|
|
1515
1580
|
assert.equal(
|
|
1516
1581
|
packageJson.scripts?.test,
|
|
1517
1582
|
"node --experimental-strip-types --test tests/*.test.ts && pnpm run check:provider-contract && pnpm run test:harness",
|
|
@@ -1559,3 +1624,26 @@ test("technical reference documents dynamic Gentle AI RDD ownership and the inst
|
|
|
1559
1624
|
test("package verification explicitly requires the managed remediation actor", () => {
|
|
1560
1625
|
assert.match(readFileSync(join(PACKAGE_ROOT, "scripts/verify-package-files.mjs"), "utf8"), /assets\/agents\/sdd-remediate\.md/);
|
|
1561
1626
|
});
|
|
1627
|
+
|
|
1628
|
+
test("SDD installation retires owned sync but preserves modified copies and unrelated owners", () => {
|
|
1629
|
+
withIsolatedAssetHome((agentHome) => {
|
|
1630
|
+
installSddAssets(agentHome, false);
|
|
1631
|
+
const path = join(agentHome, "agents/sdd-sync.md");
|
|
1632
|
+
const manifestPath = join(agentHome, "gentle-ai/managed-assets.json");
|
|
1633
|
+
const legacy = "Previously managed sync executor\n";
|
|
1634
|
+
const manifest = installedAssetManifest(agentHome);
|
|
1635
|
+
manifest.assets["agents/sdd-sync.md"] = sha256(legacy);
|
|
1636
|
+
writeFileSync(path, legacy);
|
|
1637
|
+
writeFileSync(manifestPath, JSON.stringify(manifest));
|
|
1638
|
+
installPackageAssets(agentHome, true, ["delegation"]);
|
|
1639
|
+
assert.equal(readFileSync(path, "utf8"), legacy);
|
|
1640
|
+
installPackageAssets(agentHome, true, ["sdd"]);
|
|
1641
|
+
assert.equal(existsSync(path), false);
|
|
1642
|
+
assert.equal(installedAssetManifest(agentHome).assets["agents/sdd-sync.md"], undefined);
|
|
1643
|
+
writeFileSync(path, "User-modified sync instructions\n");
|
|
1644
|
+
writeFileSync(manifestPath, JSON.stringify(manifest));
|
|
1645
|
+
installPackageAssets(agentHome, true, ["sdd"]);
|
|
1646
|
+
assert.equal(readFileSync(path, "utf8"), "User-modified sync instructions\n");
|
|
1647
|
+
assert.equal(installedAssetManifest(agentHome).assets["agents/sdd-sync.md"], undefined);
|
|
1648
|
+
});
|
|
1649
|
+
});
|
|
@@ -262,7 +262,7 @@ test("Table A rule: wrapper :180/:181 + orchestrator :9,:12 self-description MER
|
|
|
262
262
|
);
|
|
263
263
|
assert.match(
|
|
264
264
|
prompt,
|
|
265
|
-
/I am el Gentleman: a Pi-specific coding-agent harness for controlled development, with a senior architect persona\. I
|
|
265
|
+
/I am el Gentleman: a Pi-specific coding-agent harness for controlled development, with a senior architect persona\. I run Organic Driven Development by default and SDD\/OpenSpec when explicitly selected, coordinate subagents, use phase artifacts, run commands, and edit files\. I am not a generic chatbot\./,
|
|
266
266
|
`[${persona}] the richer translated self-description paragraph (orchestrator :9,:12) must survive in the wrapper`,
|
|
267
267
|
);
|
|
268
268
|
}
|
|
@@ -295,7 +295,7 @@ test("Table A rule: SDD/OpenSpec artifacts + subagents core-capabilities bullet
|
|
|
295
295
|
const prompt = __testing.buildGentlePrompt(persona);
|
|
296
296
|
assert.match(
|
|
297
297
|
prompt,
|
|
298
|
-
/Mention SDD\/OpenSpec phase artifacts and subagents as core capabilities\./,
|
|
298
|
+
/Mention ODD as the default workflow, SDD\/OpenSpec phase artifacts, and subagents as core capabilities\./,
|
|
299
299
|
`[${persona}] wrapper :182 rule must survive`,
|
|
300
300
|
);
|
|
301
301
|
}
|
|
@@ -439,7 +439,7 @@ test("dup guard (exact-string): 'Do not claim portability outside the Pi runtime
|
|
|
439
439
|
|
|
440
440
|
test("dup guard (exact-string): identity self-description sentence occurs exactly once", () => {
|
|
441
441
|
const selfDescription =
|
|
442
|
-
"I am el Gentleman: a Pi-specific coding-agent harness for controlled development, with a senior architect persona. I
|
|
442
|
+
"I am el Gentleman: a Pi-specific coding-agent harness for controlled development, with a senior architect persona. I run Organic Driven Development by default and SDD/OpenSpec when explicitly selected, coordinate subagents, use phase artifacts, run commands, and edit files. I am not a generic chatbot.";
|
|
443
443
|
for (const persona of ["gentleman", "neutral"] as const) {
|
|
444
444
|
const prompt = __testing.buildGentlePrompt(persona);
|
|
445
445
|
assert.equal(
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import pretty from "../extensions/pi-pretty.ts";
|
|
4
|
+
|
|
5
|
+
test("bundled pretty cannot replace or restore an editor owned by the host", async () => {
|
|
6
|
+
const handlers = new Map<string, Function[]>();
|
|
7
|
+
const pi = { on(name: string, handler: Function) { handlers.set(name, [...(handlers.get(name) ?? []), handler]); } };
|
|
8
|
+
const owner = () => "custom";
|
|
9
|
+
let editor: unknown = owner;
|
|
10
|
+
let indicator: unknown;
|
|
11
|
+
let message: unknown;
|
|
12
|
+
let visible = true;
|
|
13
|
+
const ctx = { mode: "tui", ui: {
|
|
14
|
+
getEditorComponent: () => editor,
|
|
15
|
+
setEditorComponent: (value: unknown) => { editor = value; },
|
|
16
|
+
setWorkingIndicator: (value: unknown) => { indicator = value; },
|
|
17
|
+
setWorkingMessage: (value: unknown) => { message = value; },
|
|
18
|
+
setWorkingVisible(value: boolean) { visible = value; },
|
|
19
|
+
theme: { fg: (role: string, text: string) => `<${role}>${text}</${role}>`, bold: (text: string) => text },
|
|
20
|
+
} };
|
|
21
|
+
await pretty(pi, undefined, async (api: any) => {
|
|
22
|
+
api.on("session_start", (_event: unknown, context: any) => {
|
|
23
|
+
context.ui.setEditorComponent(() => "pretty");
|
|
24
|
+
context.ui.setWorkingIndicator({ frames: ["foreign"] });
|
|
25
|
+
});
|
|
26
|
+
api.on("session_shutdown", (_event: unknown, context: any) => context.ui.setEditorComponent(undefined));
|
|
27
|
+
}, {});
|
|
28
|
+
for (const handler of handlers.get("session_start") ?? []) await handler({}, ctx);
|
|
29
|
+
assert.equal(editor, owner);
|
|
30
|
+
assert.equal(visible, false, "the prompt frame owns the live working state without a duplicate loader row");
|
|
31
|
+
assert.equal(indicator, undefined);
|
|
32
|
+
assert.equal(message, undefined);
|
|
33
|
+
for (const handler of handlers.get("session_shutdown") ?? []) await handler({}, ctx);
|
|
34
|
+
assert.equal(editor, owner);
|
|
35
|
+
assert.equal(visible, true);
|
|
36
|
+
assert.equal(indicator, undefined);
|
|
37
|
+
assert.equal(message, undefined);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("disabled shell leaves bundled editor behavior untouched", async () => {
|
|
41
|
+
let received: unknown;
|
|
42
|
+
const pi = {};
|
|
43
|
+
await pretty(pi, undefined, async (api: unknown) => { received = api; }, { GENTLE_PI_SHELL: "0" });
|
|
44
|
+
assert.equal(received, pi);
|
|
45
|
+
});
|