gentle-pi 2.4.0 → 2.6.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 +292 -25
- package/assets/agents/gentle-ai-worker.md +13 -0
- package/assets/agents/jd-fix-agent.md +18 -0
- package/assets/agents/jd-judge-a.md +1 -1
- package/assets/agents/jd-judge-b.md +1 -1
- package/assets/agents/sdd-apply.md +7 -5
- package/assets/agents/sdd-archive.md +5 -3
- package/assets/agents/sdd-design.md +4 -0
- package/assets/agents/sdd-explore.md +4 -0
- package/assets/agents/sdd-init.md +4 -0
- package/assets/agents/sdd-onboard.md +4 -0
- package/assets/agents/sdd-proposal.md +4 -0
- package/assets/agents/sdd-remediate.md +37 -0
- package/assets/agents/sdd-research.md +26 -3
- package/assets/agents/sdd-spec.md +4 -0
- package/assets/agents/sdd-status.md +9 -75
- package/assets/agents/sdd-sync.md +4 -0
- package/assets/agents/sdd-tasks.md +4 -0
- package/assets/agents/sdd-verify.md +5 -3
- package/assets/chains/sdd-full.chain.md +4 -0
- package/assets/chains/sdd-plan.chain.md +4 -0
- package/assets/chains/sdd-verify.chain.md +4 -0
- package/assets/migrations/managed-assets-v2.5.0.json +7 -0
- package/assets/orchestrator-delegation.md +39 -11
- package/assets/orchestrator.md +5 -5
- package/assets/sdd-orchestrator-workflow.md +54 -21
- package/assets/support/sdd-status-contract.md +34 -90
- package/contracts/telemetry/runtime-aggregate-v1.schema.json +65 -0
- package/docs/delegated-verification.md +25 -0
- package/docs/telemetry.md +94 -0
- package/docs/windows-startup-console-visibility.md +18 -0
- package/extensions/ask-user-choice.ts +159 -25
- package/extensions/codegraph-tools.ts +95 -5
- package/extensions/gentle-agents.ts +1337 -0
- package/extensions/gentle-ai.ts +2916 -386
- package/extensions/gentle-shell.ts +650 -0
- package/extensions/gentle-todo.ts +234 -0
- package/extensions/quiet-tools.ts +2 -1
- package/extensions/runtime-metrics.ts +130 -0
- package/extensions/sdd-init.ts +2 -2
- package/extensions/startup-banner.ts +52 -75
- package/lib/agent-profiles.ts +550 -0
- package/lib/agents-completion-delivery.ts +72 -0
- package/lib/agents-config.ts +315 -0
- package/lib/agents-history.ts +88 -0
- package/lib/agents-messaging.ts +187 -0
- package/lib/agents-protocol.ts +501 -0
- package/lib/agents-runner.ts +1012 -0
- package/lib/agents-thread-view.ts +57 -0
- package/lib/agents-transcript.ts +87 -0
- package/lib/agents-view-layout.ts +40 -0
- package/lib/agents-view.ts +914 -0
- package/lib/agents-widget.ts +241 -0
- package/lib/gentle-ai-binary.ts +3 -1
- package/lib/gentle-ai-renderer.ts +143 -25
- package/lib/native-choice-list.ts +194 -0
- package/lib/native-fullscreen-interaction.ts +47 -0
- package/lib/native-pointer-region.ts +164 -0
- package/lib/native-review-cli.ts +371 -13
- package/lib/orchestrator-presence.ts +337 -0
- package/lib/profiles-orchestrator.ts +203 -0
- package/lib/review-candidate-view-owner.ts +427 -0
- package/lib/review-candidate-view.ts +150 -48
- package/lib/review-consent-component.ts +247 -0
- package/lib/review-consent-ui.ts +110 -0
- package/lib/review-host-relay.ts +28 -0
- package/lib/review-integration-v2.ts +243 -11
- package/lib/review-last-event-controller.ts +8 -4
- package/lib/review-relay-contract.ts +11 -0
- package/lib/review-reminder-receipt.ts +74 -0
- package/lib/review-repository.ts +2 -2
- package/lib/review-risk-assessment.ts +339 -0
- package/lib/review-session-standing-permission-ipc.ts +309 -0
- package/lib/review-session-standing-permission.ts +240 -0
- package/lib/runtime-metrics-children.ts +199 -0
- package/lib/runtime-metrics-delivery.ts +68 -0
- package/lib/runtime-metrics-native.ts +166 -0
- package/lib/runtime-metrics-pi-identity.ts +113 -0
- package/lib/runtime-metrics-policy.ts +51 -0
- package/lib/runtime-metrics.ts +255 -0
- package/lib/sdd-preflight.ts +362 -81
- package/lib/sdd-research-capabilities.ts +228 -0
- package/lib/sdd-status.ts +29 -7
- package/lib/session-worktree-registry.ts +118 -0
- package/lib/shell-bar.ts +184 -0
- package/lib/shell-card.ts +133 -0
- package/lib/shell-changes-view.ts +530 -0
- package/lib/shell-changes.ts +290 -0
- package/lib/shell-gauge.ts +40 -0
- package/lib/shell-prompt.ts +115 -0
- package/lib/shell-sidebar-banner.ts +11 -0
- package/lib/shell-sidebar-layout.ts +213 -0
- package/lib/shell-sidebar.ts +41 -0
- package/lib/shell-todo.ts +297 -0
- package/lib/shell-usage-view.ts +76 -0
- package/lib/shell-usage.ts +246 -0
- package/lib/telemetry-trigger.ts +153 -0
- package/package.json +8 -5
- package/runtime/gentle-ai-binary.mjs +3 -1
- package/runtime/native-review-cli.mjs +370 -12
- package/runtime/review-integration-v2.mjs +243 -11
- package/runtime/review-relay-contract.mjs +11 -0
- package/runtime/review-risk-assessment.mjs +340 -0
- package/runtime/telemetry-trigger.mjs +154 -0
- package/scripts/build-runtime-modules.mjs +11 -1
- package/scripts/check-types.mjs +125 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/install-gentle-ai.mjs +12 -0
- package/scripts/install-tui-mode-setting.mjs +114 -0
- package/scripts/test-packed-runner.mjs +38 -2
- package/scripts/types-baseline.json +99 -0
- package/scripts/verify-package-files.mjs +8 -2
- package/skills/_shared/review-ledger-contract.md +20 -2
- package/skills/issue-creation/SKILL.md +3 -3
- package/skills/judgment-day/SKILL.md +17 -3
- package/skills/judgment-day/references/prompts-and-formats.md +14 -3
- package/tests/agent-profiles.test.ts +722 -0
- package/tests/agents-completion-delivery.test.ts +94 -0
- package/tests/agents-config.test.ts +205 -0
- package/tests/agents-fake-child.ts +66 -0
- package/tests/agents-grouping.test.ts +179 -0
- package/tests/agents-history.test.ts +54 -0
- package/tests/agents-integration.test.ts +100 -0
- package/tests/agents-messaging.test.ts +94 -0
- package/tests/agents-protocol.test.ts +198 -0
- package/tests/agents-queries.test.ts +190 -0
- package/tests/agents-responsive.test.ts +43 -0
- package/tests/agents-runner-process.test.ts +111 -0
- package/tests/agents-runner.test.ts +959 -0
- package/tests/agents-thread-view.test.ts +45 -0
- package/tests/agents-transcript.test.ts +30 -0
- package/tests/agents-view.test.ts +685 -0
- package/tests/agents-widget.test.ts +141 -0
- package/tests/artifact-language.test.ts +25 -2
- package/tests/ask-user-choice.test.ts +325 -5
- package/tests/asset-installation-runtime.test.ts +108 -0
- package/tests/autonomous-guard.test.ts +116 -1
- package/tests/codegraph-tools.test.ts +112 -2
- package/tests/delegated-key-learnings-contract.test.ts +1 -1
- package/tests/devbinary/native-review-parity.devtest.ts +110 -0
- package/tests/feature-request-form.test.ts +67 -0
- package/tests/fixtures/agents-messaging-child.mjs +5 -0
- package/tests/fixtures/agents-process-child.mjs +23 -0
- package/tests/fixtures/runtime-metrics-native-batches.json +6 -0
- package/tests/gentle-agents.test.ts +2168 -0
- package/tests/gentle-ai-binary.test.ts +7 -2
- package/tests/gentle-ai-installer.test.ts +47 -47
- package/tests/gentle-ai-renderer.test.ts +103 -0
- package/tests/gentle-ai.test.ts +971 -15
- package/tests/gentle-card-text.ts +35 -0
- package/tests/gentle-shell.test.ts +818 -0
- package/tests/gentle-todo.test.ts +226 -0
- package/tests/install-tui-mode-setting.test.ts +324 -0
- package/tests/issue-creation-skill.test.ts +22 -0
- package/tests/model-routing-authority.test.ts +12 -0
- package/tests/native-choice-list.test.ts +202 -0
- package/tests/native-fullscreen-interaction.test.ts +125 -0
- package/tests/native-pointer-region.test.ts +245 -0
- package/tests/native-review-capability-contract.test.ts +27 -1
- package/tests/native-review-cli.test.ts +317 -3
- package/tests/native-review-consent.test.ts +91 -0
- package/tests/native-review-parity-runtime.test.ts +8 -2
- package/tests/native-review-parity.test.ts +43 -29
- package/tests/native-sdd-attempt-authority.test.ts +7 -2
- package/tests/orchestrator-budget.test.ts +69 -0
- package/tests/orchestrator-presence.test.ts +389 -0
- package/tests/orchestrator-rdd-ownership.test.ts +9 -0
- package/tests/package-manifest.test.ts +243 -7
- package/tests/profiles-orchestrator.test.ts +208 -0
- package/tests/quiet-tool-rendering.test.ts +97 -37
- package/tests/rdd-aware-verification-contract.test.ts +226 -0
- package/tests/rdd-status-line.test.ts +286 -0
- package/tests/review-agent-end-preflight.test.ts +332 -24
- package/tests/review-candidate-view.test.ts +751 -7
- package/tests/review-consent-ui.test.ts +352 -0
- package/tests/review-contract-prompt.test.ts +17 -0
- package/tests/review-controller-native-recovery.test.ts +29 -4
- package/tests/review-controller-native-routing.test.ts +884 -7
- package/tests/review-controller-workspace-root.test.ts +45 -2
- package/tests/review-controller.test.ts +26 -1
- package/tests/review-host-relay-restart-parity.test.ts +142 -1
- package/tests/review-host-relay-routing.test.ts +384 -8
- package/tests/review-host-relay.test.ts +29 -0
- package/tests/review-integration-v2-forward.test.ts +44 -0
- package/tests/review-integration-v2.test.ts +276 -0
- package/tests/review-last-event-closure.test.ts +112 -3
- package/tests/review-ledger-contract.test.ts +61 -6
- package/tests/review-relay-contract.test.ts +26 -0
- package/tests/review-reminder-receipt.test.ts +62 -0
- package/tests/review-repository.test.ts +28 -1
- package/tests/review-risk-assessment.test.ts +626 -0
- package/tests/review-session-standing-permission-controller.test.ts +656 -0
- package/tests/review-session-standing-permission-ipc.test.ts +233 -0
- package/tests/review-session-standing-permission-runtime.test.ts +212 -0
- package/tests/review-session-standing-permission.test.ts +156 -0
- package/tests/runtime-harness.mjs +447 -39
- package/tests/runtime-metrics-children.test.ts +206 -0
- package/tests/runtime-metrics-delivery.test.ts +85 -0
- package/tests/runtime-metrics-extension.test.ts +187 -0
- package/tests/runtime-metrics-native.test.ts +209 -0
- package/tests/runtime-metrics-pi-identity.test.ts +113 -0
- package/tests/runtime-metrics-policy.test.ts +62 -0
- package/tests/runtime-metrics.test.ts +184 -0
- package/tests/sdd-agent-tools.test.ts +10 -1
- package/tests/sdd-execution-routing-contract.test.ts +28 -0
- package/tests/sdd-managed-runtime-settlement.test.ts +331 -0
- package/tests/sdd-native-managed-uptake.test.ts +253 -0
- package/tests/sdd-planning-routing-contract.test.ts +45 -0
- package/tests/sdd-preflight.test.ts +252 -8
- package/tests/sdd-research-capabilities.test.ts +256 -0
- package/tests/sdd-research-live.test.ts +241 -0
- package/tests/sdd-selection-transport.test.ts +504 -0
- package/tests/sdd-status.test.ts +51 -0
- package/tests/session-worktree-registry.test.ts +135 -0
- package/tests/shell-bar.test.ts +176 -0
- package/tests/shell-card.test.ts +139 -0
- package/tests/shell-changes-view.test.ts +609 -0
- package/tests/shell-changes.test.ts +350 -0
- package/tests/shell-prompt.test.ts +140 -0
- package/tests/shell-sidebar-banner.test.ts +23 -0
- package/tests/shell-sidebar-layout.test.ts +387 -0
- package/tests/shell-sidebar.test.ts +50 -0
- package/tests/shell-todo.test.ts +259 -0
- package/tests/shell-usage-view.test.ts +62 -0
- package/tests/shell-usage.test.ts +197 -0
- package/tests/startup-banner.test.ts +126 -0
- package/tests/telemetry-trigger.test.ts +351 -0
|
@@ -3,6 +3,7 @@ import { realpathSync } from "node:fs";
|
|
|
3
3
|
import test from "node:test";
|
|
4
4
|
import { initTheme, keyHint } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { imageFallback, visibleWidth } from "@earendil-works/pi-tui";
|
|
6
|
+
import { cardBody, cardHint, cardTitle, cardTone } from "./gentle-card-text.ts";
|
|
6
7
|
import piPretty from "../extensions/pi-pretty.ts";
|
|
7
8
|
import quietTools, {
|
|
8
9
|
countNonEmptyLines,
|
|
@@ -172,6 +173,7 @@ test("quiet tool rendering registers noisy built-in tools", () => {
|
|
|
172
173
|
for (const toolName of ["read", "bash", "grep", "find", "ls", "edit", "write"]) {
|
|
173
174
|
const tool = tools.get(toolName);
|
|
174
175
|
assert.ok(tool, `missing quiet renderer for ${toolName}`);
|
|
176
|
+
assert.equal(tool.renderShell, "self", `${toolName} must opt out of Pi's painted Box`);
|
|
175
177
|
assert.equal(typeof tool.execute, "function", `${toolName} must delegate execution`);
|
|
176
178
|
assert.ok(tool.parameters, `${toolName} must preserve built-in parameters`);
|
|
177
179
|
}
|
|
@@ -180,7 +182,16 @@ test("quiet tool rendering registers noisy built-in tools", () => {
|
|
|
180
182
|
|
|
181
183
|
test("quiet tool execution uses the tool-call cwd", async () => {
|
|
182
184
|
const tool = registeredQuietTools().get("bash");
|
|
183
|
-
const
|
|
185
|
+
const context = {
|
|
186
|
+
cwd: "/tmp",
|
|
187
|
+
sessionManager: {
|
|
188
|
+
getSessionId: () => "quiet-tool-test",
|
|
189
|
+
getSessionFile: () => undefined,
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
const output = extractTextContent(
|
|
193
|
+
await tool.execute("tool-call", { command: "pwd" }, new AbortController().signal, undefined, context),
|
|
194
|
+
).trim();
|
|
184
195
|
// pwd prints the physical directory: on macOS /tmp is a symlink to /private/tmp.
|
|
185
196
|
assert.equal(output, realpathSync("/tmp"));
|
|
186
197
|
assert.notEqual(output, process.cwd());
|
|
@@ -472,21 +483,21 @@ test("quiet tool rendering hides every collapsed direct Gentle AI result and pre
|
|
|
472
483
|
const nonText = renderToString(tool.renderResult({ content: [{ type: "image", data: "opaque", mimeType: "image/png" }] }, { expanded: false, isPartial: false }, passthroughTheme, { args: { command } }));
|
|
473
484
|
|
|
474
485
|
assert.equal([...textRose].length, 2);
|
|
475
|
-
assert.equal(call
|
|
476
|
-
assert.
|
|
477
|
-
assert.
|
|
486
|
+
assert.equal(cardTitle(call), `🌹︎ Gentle AI · running · review status`);
|
|
487
|
+
assert.match(cardBody(collapsed), /\d+ lines?\b/);
|
|
488
|
+
assert.match(cardBody(collapsed), /\d+ lines?\b/);
|
|
478
489
|
assert.doesNotMatch(collapsed, /next_transition|stop/);
|
|
479
|
-
assert.equal(expanded.split("\n")[0], '{"next_transition":"stop"}');
|
|
490
|
+
assert.equal(cardBody(expanded).split("\n")[0], '{"next_transition":"stop"}');
|
|
480
491
|
assert.match(expanded, /"next_transition":"stop"/);
|
|
481
492
|
assert.doesNotMatch(expanded, /to expand/);
|
|
482
|
-
assert.
|
|
493
|
+
assert.match(cardBody(failure), /\d+ lines?\b/);
|
|
483
494
|
assert.doesNotMatch(failure, /review status failed|authority unavailable|lineage=secret/);
|
|
484
495
|
assert.match(expandedFailure, /review status failed: authority unavailable/);
|
|
485
496
|
assert.match(expandedFailure, /lineage=secret/);
|
|
486
497
|
assert.doesNotMatch(expandedFailure, /to expand/);
|
|
487
|
-
assert.doesNotMatch(expandedFailure, /\x1b\[/);
|
|
488
|
-
assert.equal(empty, "");
|
|
489
|
-
assert.equal(nonText, "");
|
|
498
|
+
assert.doesNotMatch(cardBody(expandedFailure), /\x1b\[/);
|
|
499
|
+
assert.equal(cardBody(empty), "");
|
|
500
|
+
assert.equal(cardBody(nonText), "");
|
|
490
501
|
});
|
|
491
502
|
|
|
492
503
|
test("quiet tool rendering transitions one Gentle AI header through lifecycle states", () => {
|
|
@@ -528,16 +539,16 @@ test("quiet tool rendering transitions one Gentle AI header through lifecycle st
|
|
|
528
539
|
assert.strictEqual(initial, running);
|
|
529
540
|
assert.strictEqual(running, completed);
|
|
530
541
|
assert.strictEqual(completed, failed);
|
|
531
|
-
assert.equal(initialText, "
|
|
532
|
-
assert.equal(runningText, "
|
|
533
|
-
assert.equal(completedText, "
|
|
534
|
-
assert.equal(failedText, "
|
|
542
|
+
assert.equal(cardTitle(initialText), "🌹︎ Gentle AI · running · review status"); assert.equal(cardTone(initialText), "warning");
|
|
543
|
+
assert.equal(cardTitle(runningText), "🌹︎ Gentle AI · running · review status"); assert.equal(cardTone(runningText), "warning");
|
|
544
|
+
assert.equal(cardTitle(completedText), "🌹︎ Gentle AI · completed · review status"); assert.equal(cardTone(completedText), "success");
|
|
545
|
+
assert.equal(cardTitle(failedText), "🌹︎ Gentle AI · failed · review status"); assert.equal(cardTone(failedText), "error");
|
|
535
546
|
assert.doesNotMatch(failedText, /private-change/);
|
|
536
547
|
});
|
|
537
548
|
|
|
538
549
|
test("quiet Bash keeps direct Gentle AI calls seamless while streaming", () => {
|
|
539
550
|
const tool = registeredQuietTools().get("bash");
|
|
540
|
-
const rose =
|
|
551
|
+
const rose = /🌹︎ Gentle AI/;
|
|
541
552
|
const render = (command: string, state: Record<string, unknown>, overrides: Record<string, unknown> = {}) => {
|
|
542
553
|
const component = tool.renderCall({ command }, passthroughTheme, routineRenderContext({ args: { command }, state, argsComplete: false, ...overrides }));
|
|
543
554
|
return [component, renderToString(component)] as const;
|
|
@@ -545,14 +556,14 @@ test("quiet Bash keeps direct Gentle AI calls seamless while streaming", () => {
|
|
|
545
556
|
const command = "gentle-ai review status --token 'lineage-secret";
|
|
546
557
|
const state = {};
|
|
547
558
|
const [preparing, collapsed] = render(command, state);
|
|
548
|
-
assert.equal(collapsed, "🌹︎ Gentle AI · preparing · review status");
|
|
559
|
+
assert.equal(cardTitle(collapsed), "🌹︎ Gentle AI · preparing · review status");
|
|
549
560
|
assert.doesNotMatch(collapsed, /token|lineage-secret/);
|
|
550
561
|
const expanded = render(command, state, { expanded: true, lastComponent: preparing })[1];
|
|
551
|
-
assert.
|
|
562
|
+
assert.equal(cardTitle(expanded), "🌹︎ Gentle AI · preparing · review status"); assert.equal(cardBody(expanded), "$ gentle-ai review status --token 'lineage-secret");
|
|
552
563
|
|
|
553
564
|
const direct = "gentle-ai review status";
|
|
554
565
|
const [running, runningText] = render(direct, state, { argsComplete: true, executionStarted: true, lastComponent: preparing });
|
|
555
|
-
assert.strictEqual(running, preparing); assert.equal(runningText, "🌹︎ Gentle AI · running · review status");
|
|
566
|
+
assert.strictEqual(running, preparing); assert.equal(cardTitle(runningText), "🌹︎ Gentle AI · running · review status");
|
|
556
567
|
const genericState = {};
|
|
557
568
|
const [generic, genericText] = render("gentle-ai review status | cat", genericState);
|
|
558
569
|
assert.doesNotMatch(genericText, rose); assert.match(genericText, /\| cat/);
|
|
@@ -569,7 +580,7 @@ test("quiet Bash keeps direct Gentle AI calls seamless while streaming", () => {
|
|
|
569
580
|
assert.equal(genericResult.split("\n")[0], "generic result");
|
|
570
581
|
const directState = {}; render(direct, directState, { argsComplete: true });
|
|
571
582
|
const directResult = renderToolResult(tool, textResult("direct result"), { expanded: false, isPartial: false }, { args: { command: direct }, state: directState, argsComplete: true });
|
|
572
|
-
assert.
|
|
583
|
+
assert.match(cardBody(directResult), /\d+ lines?\b/);
|
|
573
584
|
});
|
|
574
585
|
|
|
575
586
|
test("quiet tool rendering displays only finite safe Gentle AI operation paths", () => {
|
|
@@ -635,7 +646,7 @@ test("quiet tool rendering displays only finite safe Gentle AI operation paths",
|
|
|
635
646
|
routineRenderContext({ args: { command } }),
|
|
636
647
|
),
|
|
637
648
|
);
|
|
638
|
-
assert.equal(rendered
|
|
649
|
+
assert.equal(cardTitle(rendered), `🌹︎ Gentle AI · running · ${path}`, command);
|
|
639
650
|
assert.doesNotMatch(rendered, /change-123|private|secret|lineage|sha256|result\.json|incident\.json/);
|
|
640
651
|
}
|
|
641
652
|
});
|
|
@@ -658,7 +669,7 @@ test("quiet tool rendering covers version and future standalone Gentle AI comman
|
|
|
658
669
|
const rendered = renderToString(
|
|
659
670
|
tool.renderCall({ command }, passthroughTheme, routineRenderContext({ args: { command } })),
|
|
660
671
|
);
|
|
661
|
-
assert.equal(rendered
|
|
672
|
+
assert.equal(cardTitle(rendered), `🌹︎ Gentle AI · running · ${path}`, command);
|
|
662
673
|
assert.doesNotMatch(rendered, /authorization-root|secret-change|private|C:\\\\private/);
|
|
663
674
|
}
|
|
664
675
|
});
|
|
@@ -686,7 +697,7 @@ test("quiet tool rendering recognizes exact quoted, escaped, Windows, and comman
|
|
|
686
697
|
|
|
687
698
|
for (const command of routineCommands) {
|
|
688
699
|
const call = renderToString(tool.renderCall({ command }, passthroughTheme, { args: { command } }));
|
|
689
|
-
assert.equal(call
|
|
700
|
+
assert.equal(cardTitle(call), "🌹︎ Gentle AI · running · version", command);
|
|
690
701
|
}
|
|
691
702
|
|
|
692
703
|
for (const command of ["'gentle-ai-copy' version", '"gentle-ai-copy.exe" version', "'gentle-ai' version && echo done"] as const) {
|
|
@@ -713,7 +724,7 @@ test("quiet tool rendering recognizes only the exact resolved dev binary", () =>
|
|
|
713
724
|
] as const;
|
|
714
725
|
for (const [command, operationPath] of cases) {
|
|
715
726
|
const call = renderToString(tool.renderCall({ command }, statusTheme, routineRenderContext({ args: { command } })));
|
|
716
|
-
assert.equal(call
|
|
727
|
+
assert.equal(cardTitle(call), `🌹︎ Gentle AI · running · ${operationPath}`, command); assert.equal(cardTone(call), "warning", command);
|
|
717
728
|
assert.doesNotMatch(call, /gentle-ai-main|private|secret|hidden/);
|
|
718
729
|
}
|
|
719
730
|
const command = `${devPath} review status --prompt hidden-prompt --lineage lineage-secret --body private-body`;
|
|
@@ -728,11 +739,11 @@ test("quiet tool rendering recognizes only the exact resolved dev binary", () =>
|
|
|
728
739
|
const expanded = renderToolResult(tool, textResult(text), { expanded: true, isPartial: false, isError: true }, lifecycleContext);
|
|
729
740
|
const refreshed = renderToolResult(tool, textResult("result-secret"), { expanded: false, isPartial: false }, { args: { command: refreshedCommand } });
|
|
730
741
|
const hint = keyHint("app.tools.expand", "to expand");
|
|
731
|
-
assert.
|
|
732
|
-
assert.equal(collapsed.split(hint).length - 1,
|
|
742
|
+
assert.match(cardBody(collapsed), /\d+ lines?\b/);
|
|
743
|
+
assert.equal(cardBody(collapsed).split(hint).length - 1, 0);
|
|
733
744
|
assert.doesNotMatch(collapsed, /private|lineage|secret|hidden|error/);
|
|
734
745
|
assert.match(expanded, /private failure|lineage=secret body=hidden/);
|
|
735
|
-
assert.doesNotMatch(expanded, /to expand|\x1b\[/);
|
|
746
|
+
assert.doesNotMatch(cardBody(expanded), /to expand|\x1b\[/);
|
|
736
747
|
assert.doesNotMatch(refreshed, /result-secret/);
|
|
737
748
|
assert.ok(resolutions > 1);
|
|
738
749
|
});
|
|
@@ -794,12 +805,12 @@ test("quiet tool rendering hides the routine partial result because the header o
|
|
|
794
805
|
),
|
|
795
806
|
);
|
|
796
807
|
|
|
797
|
-
assert.
|
|
798
|
-
assert.equal(partial.split(expandHint).length - 1,
|
|
808
|
+
assert.match(cardBody(partial), /\d+ lines?\b/);
|
|
809
|
+
assert.equal(cardBody(partial).split(expandHint).length - 1, 0);
|
|
799
810
|
assert.match(partialExpanded, /"status":"running"/);
|
|
800
811
|
assert.doesNotMatch(partialExpanded, /to expand/);
|
|
801
|
-
assert.
|
|
802
|
-
assert.
|
|
812
|
+
assert.match(cardBody(partialFailure), /\d+ lines?\b/);
|
|
813
|
+
assert.match(cardBody(completed), /\d+ lines?\b/);
|
|
803
814
|
|
|
804
815
|
const partialExpandedFailure = renderToString(
|
|
805
816
|
tool.renderResult(
|
|
@@ -810,7 +821,7 @@ test("quiet tool rendering hides the routine partial result because the header o
|
|
|
810
821
|
),
|
|
811
822
|
);
|
|
812
823
|
assert.match(partialExpandedFailure, /review status failed: authority unavailable/);
|
|
813
|
-
assert.doesNotMatch(partialExpandedFailure, /\x1b\[/);
|
|
824
|
+
assert.doesNotMatch(cardBody(partialExpandedFailure), /\x1b\[/);
|
|
814
825
|
});
|
|
815
826
|
|
|
816
827
|
test("quiet tool rendering collapses grant calls to action and authorization-root cardinality", () => {
|
|
@@ -844,8 +855,8 @@ test("quiet tool rendering collapses grant calls to action and authorization-roo
|
|
|
844
855
|
assert.strictEqual(running, completed);
|
|
845
856
|
assert.strictEqual(completed, failed);
|
|
846
857
|
const rendered = renderToString(failed);
|
|
847
|
-
assert.equal(rendered, "
|
|
848
|
-
assert.doesNotMatch(rendered, /authorization-root|secret-change|repo\/root|other\/root|audit:|\x1b\[/);
|
|
858
|
+
assert.equal(cardTitle(rendered), "🌹︎ Gentle AI · failed · sdd attempt grant · 2 roots"); assert.equal(cardTone(rendered), "error");
|
|
859
|
+
assert.doesNotMatch(rendered.split(keyHint("app.tools.expand", "to expand")).join(""), /authorization-root|secret-change|repo\/root|other\/root|audit:|\x1b\[/);
|
|
849
860
|
});
|
|
850
861
|
|
|
851
862
|
test("quiet tool rendering counts grant authorization roots without rendering values", () => {
|
|
@@ -863,7 +874,7 @@ test("quiet tool rendering counts grant authorization roots without rendering va
|
|
|
863
874
|
|
|
864
875
|
for (const [command, expected] of cases) {
|
|
865
876
|
const rendered = renderToString(tool.renderCall({ command }, passthroughTheme, { args: { command } }));
|
|
866
|
-
assert.equal(rendered
|
|
877
|
+
assert.equal(cardTitle(rendered), `🌹︎ Gentle AI · running · ${expected}`, command);
|
|
867
878
|
assert.doesNotMatch(rendered, /authorization-root|\/repo\/root|\/one|\/two|\/three|change/);
|
|
868
879
|
}
|
|
869
880
|
});
|
|
@@ -883,8 +894,8 @@ test("quiet tool rendering hides invocation secrets from collapsed Gentle AI cal
|
|
|
883
894
|
),
|
|
884
895
|
);
|
|
885
896
|
|
|
886
|
-
assert.equal(call
|
|
887
|
-
assert.
|
|
897
|
+
assert.equal(cardTitle(call), "🌹︎ Gentle AI · running · review finalize");
|
|
898
|
+
assert.match(cardBody(collapsed), /\d+ lines?\b/);
|
|
888
899
|
for (const forbidden of ["hidden-prompt", "lineage-secret", "private-body", "/private/root", "audit:"]) {
|
|
889
900
|
assert.doesNotMatch(call, new RegExp(forbidden));
|
|
890
901
|
assert.doesNotMatch(collapsed, new RegExp(forbidden));
|
|
@@ -973,7 +984,7 @@ test("quiet tool rendering sanitizes collapsed output and call rows", () => {
|
|
|
973
984
|
|
|
974
985
|
const carriageReturn = renderToolResult(tools.get("bash"), textResult("prefix\rSECRET\r\nnext"), { expanded: false, isPartial: false }, { args: { command: "printf output" } });
|
|
975
986
|
assert.match(collapsed, /safered/);
|
|
976
|
-
assert.doesNotMatch(collapsed, /\x1b\[31m|\x1b\[0m/);
|
|
987
|
+
assert.doesNotMatch(cardBody(collapsed), /\x1b\[31m|\x1b\[0m/);
|
|
977
988
|
assert.equal(call.trimEnd(), "$ echo red");
|
|
978
989
|
assert.match(carriageReturn, /prefixSECRET\nnext/);
|
|
979
990
|
assert.doesNotMatch(carriageReturn, /\r/);
|
|
@@ -1064,7 +1075,7 @@ test("quiet tool rendering recognizes a quoted exact dev override path containin
|
|
|
1064
1075
|
const command = `"${devPath}" review status "literal \\$|#;"`;
|
|
1065
1076
|
const call = renderToString(tool.renderCall({ command }, passthroughTheme, { args: { command } }));
|
|
1066
1077
|
const collapsed = renderToolResult(tool, textResult("private result"), { expanded: false, isPartial: false }, { args: { command } });
|
|
1067
|
-
assert.equal(call
|
|
1078
|
+
assert.equal(cardTitle(call), "🌹︎ Gentle AI · running · review status");
|
|
1068
1079
|
assert.doesNotMatch(call, /\/opt\/Gentle AI\/gentle-ai|literal/);
|
|
1069
1080
|
assert.doesNotMatch(collapsed, /private result/);
|
|
1070
1081
|
});
|
|
@@ -1274,3 +1285,52 @@ test("quiet tool rendering respects command and env wrapper order", () => {
|
|
|
1274
1285
|
assert.equal(gentleAiRoutineCommand({ command }), undefined);
|
|
1275
1286
|
assertGenericBash(bash, command);
|
|
1276
1287
|
});
|
|
1288
|
+
|
|
1289
|
+
test("quiet tool rendering lets a final result promote a replayed call card to its outcome", async () => {
|
|
1290
|
+
// The promotion invalidates after the render returns (a microtask), never inside it.
|
|
1291
|
+
const flush = () => new Promise((resolve) => queueMicrotask(() => resolve(undefined)));
|
|
1292
|
+
const { pi, tools } = createPi();
|
|
1293
|
+
withEnv({ GENTLE_PI_QUIET_TOOLS: undefined }, () => quietTools(pi as any));
|
|
1294
|
+
const tool = tools.get("bash");
|
|
1295
|
+
const command = "gentle-ai review status";
|
|
1296
|
+
const state = {};
|
|
1297
|
+
let invalidations = 0;
|
|
1298
|
+
const replayed = routineRenderContext({ args: { command }, state, argsComplete: false, executionStarted: false, isPartial: false, invalidate: () => { invalidations += 1; } });
|
|
1299
|
+
|
|
1300
|
+
const call = tool.renderCall({ command }, statusTheme, replayed);
|
|
1301
|
+
assert.equal(cardTitle(renderToString(call)), "🌹︎ Gentle AI · preparing · review status");
|
|
1302
|
+
renderToString(tool.renderResult(textResult("done"), { expanded: false, isPartial: false }, statusTheme, replayed));
|
|
1303
|
+
assert.equal(invalidations, 0, "never reentrant");
|
|
1304
|
+
await flush();
|
|
1305
|
+
assert.equal(invalidations, 1);
|
|
1306
|
+
const promoted = tool.renderCall({ command }, statusTheme, { ...replayed, lastComponent: call });
|
|
1307
|
+
assert.strictEqual(promoted, call);
|
|
1308
|
+
assert.equal(cardTitle(renderToString(promoted)), "🌹︎ Gentle AI · completed · review status");
|
|
1309
|
+
assert.equal(cardTone(renderToString(promoted)), "success");
|
|
1310
|
+
|
|
1311
|
+
renderToString(tool.renderResult(textResult("boom"), { expanded: false, isPartial: false }, statusTheme, { ...replayed, isError: true }));
|
|
1312
|
+
await flush();
|
|
1313
|
+
assert.equal(invalidations, 2);
|
|
1314
|
+
assert.equal(cardTitle(renderToString(tool.renderCall({ command }, statusTheme, { ...replayed, lastComponent: call }))), "🌹︎ Gentle AI · failed · review status");
|
|
1315
|
+
renderToString(tool.renderResult(textResult("boom"), { expanded: false, isPartial: false }, statusTheme, { ...replayed, isError: true }));
|
|
1316
|
+
await flush();
|
|
1317
|
+
assert.equal(invalidations, 2, "an unchanged outcome must not request another render");
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1320
|
+
test("quiet tool rendering puts the expand key in the finished call card's top rule, not in the result", () => {
|
|
1321
|
+
const { pi, tools } = createPi();
|
|
1322
|
+
withEnv({ GENTLE_PI_QUIET_TOOLS: undefined }, () => quietTools(pi as any));
|
|
1323
|
+
const tool = tools.get("bash");
|
|
1324
|
+
const command = "gentle-ai review status";
|
|
1325
|
+
const running = renderToString(tool.renderCall({ command }, passthroughTheme, routineRenderContext({ args: { command }, executionStarted: true, isPartial: true })));
|
|
1326
|
+
assert.equal(cardHint(running), undefined, "a running call has nothing to expand yet");
|
|
1327
|
+
const completed = renderToString(tool.renderCall({ command }, passthroughTheme, routineRenderContext({ args: { command }, executionStarted: true, isPartial: false, expanded: false })));
|
|
1328
|
+
assert.match(cardHint(completed) ?? "", /to expand$/);
|
|
1329
|
+
assert.equal(cardTitle(completed), "🌹︎ Gentle AI · completed · review status");
|
|
1330
|
+
const expanded = renderToString(tool.renderCall({ command }, passthroughTheme, routineRenderContext({ args: { command }, executionStarted: true, isPartial: false, expanded: true })));
|
|
1331
|
+
assert.match(cardHint(expanded) ?? "", /to collapse$/);
|
|
1332
|
+
const collapsedResult = renderToolResult(tool, textResult("{\"next_transition\":\"stop\"}"), { expanded: false, isPartial: false }, { args: { command } });
|
|
1333
|
+
assert.equal(cardBody(collapsedResult), "1 line");
|
|
1334
|
+
assert.equal(cardBody(renderToolResult(tool, textResult("a\nb\nc"), { expanded: false, isPartial: false }, { args: { command } })), "3 lines");
|
|
1335
|
+
assert.match(collapsedResult.split("\n").pop() ?? "", /╰─+╯$/);
|
|
1336
|
+
});
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// gentle-pi#661/#662: RDD-aware verification rule for delegated work.
|
|
8
|
+
//
|
|
9
|
+
// The bounded writer always self-verifies: it runs the parent-authorized
|
|
10
|
+
// `## Verification` commands itself and reports observed output. Whether a
|
|
11
|
+
// SEPARATE `gentle-ai-verify` delegation is also required depends on the
|
|
12
|
+
// rendered `Receipt-driven development:` line, stated normatively exactly
|
|
13
|
+
// once in trigger 5 (Verification rule) and referenced -- not restated --
|
|
14
|
+
// everywhere else in this asset:
|
|
15
|
+
// - `on` -> the writer's own report is the verification of
|
|
16
|
+
// record; `gentle-ai-verify` is on-demand, except
|
|
17
|
+
// passive risk, which gets a structural readback.
|
|
18
|
+
// - `off`/`unknown` -> gentle-pi#662: the parent calls `gentle_review` with
|
|
19
|
+
// `{"operation":"assess"}` over the writer's diff and
|
|
20
|
+
// follows the returned plan by native risk tier
|
|
21
|
+
// (passive/medium/high/unassessable), instead of a
|
|
22
|
+
// blanket non-trivial judgment. An unknown RDD line
|
|
23
|
+
// never lowers a tier below `off`.
|
|
24
|
+
// These tests assert the exact distinctive sentences (not bare words like
|
|
25
|
+
// `off`/`unknown`/`partial`/`blocked`), that the tier table is stated exactly
|
|
26
|
+
// once, that the routing ladder paragraph references trigger 5 rather than
|
|
27
|
+
// restating it, and that `## Known environmental failures` has one canonical
|
|
28
|
+
// definition (owned by the worker asset) that the delegation asset
|
|
29
|
+
// references rather than duplicates.
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
const ROOT = join(import.meta.dirname, "..");
|
|
33
|
+
|
|
34
|
+
function read(relativePath: string): string {
|
|
35
|
+
return readFileSync(join(ROOT, relativePath), "utf8");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function countOccurrences(haystack: string, needle: string): number {
|
|
39
|
+
return haystack.split(needle).length - 1;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const delegation = read("assets/orchestrator-delegation.md");
|
|
43
|
+
const worker = read("assets/agents/gentle-ai-worker.md");
|
|
44
|
+
|
|
45
|
+
const ON_SENTENCE =
|
|
46
|
+
"When the line reads `on`, that writer report is the verification of record, and the native review is the independent check the writer cannot influence";
|
|
47
|
+
const OFF_UNKNOWN_SENTENCE =
|
|
48
|
+
'When the line reads `off` or `unknown`, after the writer returns, call `gentle_review` with `{"operation":"assess"}` over the writer\'s diff and follow the returned plan instead of judging non-triviality from the task description: the operation resolves the native risk tier and states exactly who verifies next.';
|
|
49
|
+
const TIER_TABLE_HEADER = "| Native risk tier | Verification when RDD is `off`/`unknown` |";
|
|
50
|
+
const PASSIVE_TIER_ROW = "| passive | structural readback by the parent; no separate verifier, no tests |";
|
|
51
|
+
const MEDIUM_TIER_ROW = "| medium | writer self-verification stands; a separate `gentle-ai-verify` run is added only when the writer profile is a small model (mini or low effort) |";
|
|
52
|
+
const HIGH_TIER_ROW = "| high | writer self-verification plus a separate `gentle-ai-verify` run, always |";
|
|
53
|
+
const UNASSESSABLE_TIER_ROW = "| unknown / assess failed | treated as high |";
|
|
54
|
+
const SMALL_MODEL_BIAS_SENTENCE =
|
|
55
|
+
"The small-model bias raises the tier by one for verification purposes (medium becomes high); an unknown `Receipt-driven development:` line never lowers a tier below `off`.";
|
|
56
|
+
const SPOT_CHECK_SENTENCE =
|
|
57
|
+
"The parent spot check (re-running one reported command before delivery) stays required in every tier.";
|
|
58
|
+
// gentle-pi#668: the `on` branch of trigger 5 holds only while the native
|
|
59
|
+
// review actually reaches a terminal outcome for this candidate -- a decline,
|
|
60
|
+
// a clone-local disable, or a refused START/STATUS all fall back to the exact
|
|
61
|
+
// same risk-gated path as `off`.
|
|
62
|
+
const ON_BRANCH_FALLBACK_SENTENCE =
|
|
63
|
+
'That `on` branch holds only while the native review actually reaches a terminal outcome for this candidate (gentle-pi#668): a human decline of the consent envelope for this candidate (candidate-scoped, never the RDD kill switch), a clone-local RDD disable discovered mid-flow, or a refused START/STATUS all fall back to the risk-gated path exactly as `off` -- call `gentle_review` with `{"operation":"assess"}` (pass `nativeReviewOutcome` when the parent already knows it; the tool derives it from what it itself observed for the candidate otherwise, failing closed to `unknown` when it cannot) and follow the returned plan.';
|
|
64
|
+
|
|
65
|
+
test("trigger 5 (Verification rule) states the exact on-line routing: writer report is the verification of record", () => {
|
|
66
|
+
assert.ok(delegation.includes(ON_SENTENCE), "trigger 5 is missing the exact on-line sentence");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("trigger 5 states the exact off/unknown-line routing: the parent calls gentle_review's assess operation and follows the returned plan", () => {
|
|
70
|
+
assert.ok(delegation.includes(OFF_UNKNOWN_SENTENCE), "trigger 5 is missing the exact off/unknown-line sentence");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("trigger 5 states the native risk tier table exactly once, with all four rows", () => {
|
|
74
|
+
for (const row of [TIER_TABLE_HEADER, PASSIVE_TIER_ROW, MEDIUM_TIER_ROW, HIGH_TIER_ROW, UNASSESSABLE_TIER_ROW]) {
|
|
75
|
+
assert.equal(countOccurrences(delegation, row), 1, `expected exactly one occurrence of tier table row: ${row}`);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("trigger 5 states the small-model bias and the unknown-never-lowers-a-tier rule", () => {
|
|
80
|
+
assert.ok(delegation.includes(SMALL_MODEL_BIAS_SENTENCE), "trigger 5 is missing the small-model bias sentence");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("trigger 5 keeps the parent spot check requirement in every tier", () => {
|
|
84
|
+
assert.ok(delegation.includes(SPOT_CHECK_SENTENCE), "trigger 5 is missing the parent spot check sentence");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("trigger 5 states that the on branch holds only while the native review closes for this candidate, falling back to the off path exactly once (gentle-pi#668)", () => {
|
|
88
|
+
assert.equal(countOccurrences(delegation, ON_BRANCH_FALLBACK_SENTENCE), 1, "trigger 5 is missing (or duplicates) the on-branch fallback sentence");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("the on-branch fallback sentence names all three non-closed triggers and never introduces a forbidden native RDD marker", () => {
|
|
92
|
+
for (const clause of ["decline", "clone-local", "refused START/STATUS"]) {
|
|
93
|
+
assert.ok(ON_BRANCH_FALLBACK_SENTENCE.includes(clause), `on-branch fallback sentence missing: ${clause}`);
|
|
94
|
+
}
|
|
95
|
+
for (const marker of ["gentle-ai review status", "next_transition", "review.capture-result", "review.validate", "reviewGate.result"]) {
|
|
96
|
+
assert.ok(!delegation.includes(marker), `stale/forbidden RDD marker introduced: ${marker}`);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("the on-line and off/unknown-line routing sentences appear exactly once each (normative statement lives only in trigger 5)", () => {
|
|
101
|
+
for (const sentence of [ON_SENTENCE, OFF_UNKNOWN_SENTENCE]) {
|
|
102
|
+
assert.equal(countOccurrences(delegation, sentence), 1, `expected exactly one occurrence of: ${sentence.slice(0, 60)}...`);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("trigger 5 never restates the retired #661 off/unknown non-trivial judgment", () => {
|
|
107
|
+
assert.doesNotMatch(delegation, /non-trivial change, in addition to the writer's own report/);
|
|
108
|
+
assert.doesNotMatch(delegation, /purely passive documentation with no behavior to verify/);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("the Simple Delegation paragraph references trigger 5 instead of restating the on/off/unknown routing", () => {
|
|
112
|
+
assert.match(
|
|
113
|
+
delegation,
|
|
114
|
+
/per the RDD-aware Verification rule \(trigger 5 under Mandatory Delegation Triggers, gentle-pi#661\)/,
|
|
115
|
+
);
|
|
116
|
+
assert.match(delegation, /the normative on\/off\/unknown routing lives there, not here/);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("delegation overlay's trigger 5 (Verification rule) is RDD-aware", () => {
|
|
120
|
+
assert.match(delegation, /\*\*Verification rule\*\*.*RDD-aware/);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("delegation overlay reserves separate exploration for parent routing decisions", () => {
|
|
124
|
+
assert.match(delegation, /exploration stays reserved for when the parent needs the map to decide or route/i);
|
|
125
|
+
assert.match(delegation, /reading that prepares a write belongs with the writer/i);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("delegation overlay keeps the required headings", () => {
|
|
129
|
+
for (const heading of [
|
|
130
|
+
"### Delegation Rules",
|
|
131
|
+
"#### Background Subagent Policy",
|
|
132
|
+
"#### Allowed edit surfaces (MANDATORY)",
|
|
133
|
+
"### 3. SDD (optional)",
|
|
134
|
+
]) {
|
|
135
|
+
assert.ok(delegation.includes(heading), `delegation overlay lost required heading: ${heading}`);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("worker asset declares the Verification section after Test discipline", () => {
|
|
140
|
+
const testDisciplineIndex = worker.indexOf("## Test discipline");
|
|
141
|
+
const verificationIndex = worker.indexOf("## Verification");
|
|
142
|
+
const interactionIndex = worker.indexOf("## Interaction contract");
|
|
143
|
+
assert.ok(testDisciplineIndex >= 0, "worker asset lost ## Test discipline");
|
|
144
|
+
assert.ok(verificationIndex >= 0, "worker asset is missing ## Verification");
|
|
145
|
+
assert.ok(interactionIndex >= 0, "worker asset lost ## Interaction contract");
|
|
146
|
+
assert.ok(
|
|
147
|
+
testDisciplineIndex < verificationIndex && verificationIndex < interactionIndex,
|
|
148
|
+
"## Verification must sit between ## Test discipline and ## Interaction contract",
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("worker asset requires foreground, one-at-a-time verification with nothing left unreported", () => {
|
|
153
|
+
for (const clause of [
|
|
154
|
+
"in the foreground",
|
|
155
|
+
"one at a time",
|
|
156
|
+
"Never launch a verification command in the background",
|
|
157
|
+
"never end the task with a listed command unreported",
|
|
158
|
+
]) {
|
|
159
|
+
assert.ok(worker.includes(clause), `worker asset is missing: ${clause}`);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("worker asset reports each verification command as <exact command>: <observed result> in validation", () => {
|
|
164
|
+
assert.ok(worker.includes("`<exact command>: <observed result>`"));
|
|
165
|
+
assert.ok(worker.includes("in `validation`"));
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
// Contract consistency (`## Known environmental failures`): defined exactly
|
|
170
|
+
// once, in the worker asset, as "exact test names (or exact command lines)
|
|
171
|
+
// that already fail on the base"; the writer reports those as evidence, but
|
|
172
|
+
// any OTHER failing required command still forces `status: partial`. The
|
|
173
|
+
// delegation asset must reference this same definition, not restate it.
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
test("worker asset owns the canonical Known environmental failures definition", () => {
|
|
177
|
+
assert.ok(worker.includes("## Known environmental failures"));
|
|
178
|
+
assert.match(worker, /this is the canonical definition; other assets reference it, they do not restate it/i);
|
|
179
|
+
assert.match(worker, /lists exact test names or exact command lines that already fail on the base/i);
|
|
180
|
+
assert.match(worker, /Any OTHER required command that fails -- one not named under that heading -- still forces `status: partial`\./);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("delegation asset references the worker's Known environmental failures definition instead of restating it", () => {
|
|
184
|
+
assert.match(
|
|
185
|
+
delegation,
|
|
186
|
+
/`## Known environmental failures` follows the same definition as `gentle-ai-worker`'s Verification contract: exact pre-existing base failures reported as evidence, never blockers -- any other failing required command still forces `status: partial`\./,
|
|
187
|
+
);
|
|
188
|
+
// The full canonical wording ("lists exact test names or exact command
|
|
189
|
+
// lines that already fail on the base") must not be duplicated here.
|
|
190
|
+
assert.doesNotMatch(delegation, /lists exact test names or exact command lines that already fail on the base/i);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("worker asset never claims completion while a required verification command fails under RDD, except a named environmental failure", () => {
|
|
194
|
+
assert.match(worker, /this report is the verification of record/i);
|
|
195
|
+
assert.match(worker, /native review remains the independent check/i);
|
|
196
|
+
assert.match(
|
|
197
|
+
worker,
|
|
198
|
+
/never report `status: completed` while a required command under `## Verification` is failing, unless that exact failure is named under `## Known environmental failures`\./i,
|
|
199
|
+
);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
test("worker keeps candidate review disposition and lifecycle parent-owned", () => {
|
|
203
|
+
for (const clause of [
|
|
204
|
+
"The primary parent owns candidate review disposition and lifecycle, including preflight and any explicit candidate-level opt-out.",
|
|
205
|
+
"Never search for, request, or invoke review tools, including `gentle_review`.",
|
|
206
|
+
"Missing review tools never block this worker's implementation or verification handoff.",
|
|
207
|
+
]) {
|
|
208
|
+
assert.ok(worker.includes(clause), `worker asset is missing: ${clause}`);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("worker asset keeps the existing Return contract fields", () => {
|
|
213
|
+
for (const field of [
|
|
214
|
+
"status: completed | partial | blocked | interaction_required",
|
|
215
|
+
"summary:",
|
|
216
|
+
"files_changed:",
|
|
217
|
+
"tdd_evidence:",
|
|
218
|
+
"validation:",
|
|
219
|
+
"risks:",
|
|
220
|
+
"review_focus:",
|
|
221
|
+
"skill_resolution:",
|
|
222
|
+
"interaction_required:",
|
|
223
|
+
]) {
|
|
224
|
+
assert.ok(worker.includes(field), `worker asset lost Return contract field: ${field}`);
|
|
225
|
+
}
|
|
226
|
+
});
|