gentle-pi 4.2.0 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +340 -138
- package/assets/agents/gentle-ai-explore.md +1 -0
- package/assets/agents/gentle-ai-verify.md +6 -4
- package/assets/agents/gentle-ai-worker.md +7 -1
- package/assets/design-canvas/canvas.css +648 -0
- package/assets/design-canvas/canvas.js +1497 -0
- package/assets/design-canvas/index.html +88 -0
- package/assets/design-canvas/inspector.js +290 -0
- package/assets/orchestrator-delegation-reference.md +163 -0
- package/assets/orchestrator-delegation.md +25 -176
- package/assets/orchestrator-tracking.md +1 -1
- package/assets/orchestrator-verification.md +14 -8
- package/assets/orchestrator-writer.md +5 -1
- package/assets/orchestrator.md +11 -11
- package/bin/engram-guard +1 -1
- package/bin/gentle-shell.mjs +237 -139
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/README.md +7 -0
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/manifest.json +11 -2
- package/contracts/review-provider-contract-mirror/v1.4.0/bundle/routing/pi.md +92 -0
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-capabilities.baseline.json +8 -1
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-roles.baseline.json +1 -1
- package/docs/assets/diagrams/agent-orchestration.svg +54 -15
- package/docs/assets/diagrams/gentleman-workflow.svg +69 -11
- package/docs/assets/diagrams/measured-routing.svg +49 -0
- package/docs/assets/diagrams/native-review.svg +52 -12
- package/docs/assets/diagrams/odd-workflow.svg +128 -66
- package/docs/assets/diagrams/permission-modes.svg +51 -0
- package/docs/assets/features/audio-notifications.png +0 -0
- package/docs/assets/features/command-palette.png +0 -0
- package/docs/bundled-install.md +65 -30
- package/docs/delegated-verification.md +16 -8
- package/docs/gentle-agents-activity.md +16 -6
- package/docs/gentle-music.md +61 -0
- package/docs/gentle-shell-uninstall.md +8 -1
- package/docs/gentle-shell.md +80 -9
- package/docs/install-wizard.md +37 -6
- package/docs/permission-modes.md +8 -3
- package/docs/readme-reference.md +120 -28
- package/docs/session-profile-format.md +26 -0
- package/docs/voice.md +259 -0
- package/docs/yolo-mode.md +38 -10
- package/extensions/ask-user-choice.ts +4 -1
- package/extensions/codegraph-tools.ts +68 -6
- package/extensions/gentle-agents.ts +270 -48
- package/extensions/gentle-ai.ts +490 -113
- package/extensions/gentle-design.ts +158 -0
- package/extensions/gentle-herdr-activity.ts +2 -3
- package/extensions/gentle-music.ts +289 -0
- package/extensions/gentle-shell.ts +359 -85
- package/extensions/gentle-todo.ts +43 -9
- package/extensions/gentle-voice.ts +11 -0
- package/extensions/quiet-tools.ts +1 -1
- package/extensions/session-scratch.ts +384 -0
- package/extensions/skill-registry.ts +63 -8
- package/extensions/startup-banner.ts +2 -3
- package/lib/agent-home.ts +8 -3
- package/lib/agent-profiles.ts +26 -0
- package/lib/agents-config.ts +35 -0
- package/lib/agents-protocol.ts +60 -1
- package/lib/agents-runner.ts +107 -9
- package/lib/agents-session-transport.ts +101 -6
- package/lib/agents-view.ts +3 -1
- package/lib/agents-widget.ts +14 -1
- package/lib/auto-permission-memory.ts +48 -18
- package/lib/auto-permission-runtime.ts +21 -6
- package/lib/bounded-writer-admission.ts +51 -0
- package/lib/clipboard-image.ts +147 -0
- package/lib/command-palette-catalog.ts +2 -4
- package/lib/commit-slice-signal.ts +100 -0
- package/lib/context-backstop.ts +120 -0
- package/lib/delivered-test-lint.ts +80 -0
- package/lib/design-canvas-server.ts +947 -0
- package/lib/design-comments.ts +437 -0
- package/lib/design-handoffs.ts +313 -0
- package/lib/design-open-browser.ts +58 -0
- package/lib/design-sources.ts +120 -0
- package/lib/destructive-command-guard.ts +120 -9
- package/lib/external-editor.ts +31 -0
- package/lib/gauge-theme.ts +146 -0
- package/lib/gentle-ai-binary.ts +28 -7
- package/lib/gentle-design.ts +513 -0
- package/lib/gentle-shell-bundled.ts +146 -36
- package/lib/gentle-shell-import-pi.ts +233 -0
- package/lib/gentle-shell-launcher.ts +154 -15
- package/lib/gentle-shell-startup-update.ts +61 -9
- package/lib/gentle-shell-uninstall.ts +51 -2
- package/lib/herdr-activity.ts +24 -0
- package/lib/image-attachments.ts +150 -0
- package/lib/inprocess-reviewer.ts +33 -2
- package/lib/nan-provider.ts +3 -0
- package/lib/native-review-cli.ts +20 -5
- package/lib/notification-audio-native.ts +149 -13
- package/lib/notification-audio.ts +4 -0
- package/lib/notification-pulse-worker.ts +82 -18
- package/lib/notification-service.ts +5 -1
- package/lib/orchestrator-discovery.ts +5 -1
- package/lib/orchestrator-presence.ts +87 -8
- package/lib/orchestrator-scope.ts +31 -3
- package/lib/orchestrator-state.ts +11 -3
- package/lib/permission-mode-policy.ts +2 -2
- package/lib/provider-contract-bundle.ts +99 -53
- package/lib/review-candidate-view-owner.ts +29 -19
- package/lib/review-candidate-view.ts +36 -2
- package/lib/review-host-relay.ts +11 -2
- package/lib/review-reminder-receipt.ts +24 -1
- package/lib/review-risk-assessment.ts +43 -38
- package/lib/review-session-standing-permission.ts +3 -1
- package/lib/session-profile-append-controller.ts +713 -0
- package/lib/session-profile-disk-reader.ts +182 -0
- package/lib/session-profile-freeze.ts +30 -1
- package/lib/session-profile-persistence.ts +15 -7
- package/lib/session-scratch.ts +523 -0
- package/lib/session-worktree-registry.ts +7 -0
- package/lib/shell-bar.ts +29 -28
- package/lib/shell-changes-view.ts +80 -13
- package/lib/shell-changes.ts +81 -5
- package/lib/shell-gauge.ts +49 -6
- package/lib/shell-prompt.ts +4 -4
- package/lib/shell-sidebar-layout.ts +26 -6
- package/lib/shell-todo.ts +24 -5
- package/lib/shell-usage-view.ts +5 -1
- package/lib/shell-usage.ts +7 -7
- package/lib/verifier-admission.ts +30 -0
- package/lib/vim-editor-adapter.ts +11 -8
- package/lib/voice-artifacts.ts +188 -0
- package/lib/voice-command.ts +576 -0
- package/lib/voice-install.ts +455 -0
- package/lib/voice-platform.ts +99 -0
- package/lib/voice-push-to-talk.ts +262 -0
- package/lib/voice-session.ts +460 -0
- package/lib/voice-settings.ts +50 -0
- package/lib/voice-ui.ts +158 -0
- package/lib/yolo-session-policy.ts +58 -22
- package/lib/yolo-temp-scratch.ts +69 -0
- package/package.json +1 -2
- package/runtime/gentle-ai-binary.mjs +28 -7
- package/runtime/gentle-shell-bundled.mjs +145 -35
- package/runtime/gentle-shell-import-pi.mjs +234 -0
- package/runtime/gentle-shell-launcher.mjs +152 -13
- package/runtime/gentle-shell-startup-update.mjs +61 -9
- package/runtime/gentle-shell-uninstall.mjs +51 -2
- package/runtime/native-review-cli.mjs +19 -4
- package/runtime/review-risk-assessment.mjs +43 -38
- package/scripts/build-runtime-modules.mjs +1 -0
- package/scripts/bundled-engram-migration.mjs +119 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/installer-downloads.mjs +7 -7
- package/scripts/installer-preflight.mjs +5 -3
- package/scripts/installer-server.mjs +14 -4
- package/scripts/installer-windows.mjs +37 -7
- package/scripts/main-channel.mjs +94 -19
- package/scripts/pi-update.mjs +34 -6
- package/scripts/run-test-suite.mjs +5 -0
- package/scripts/verify-package-files.mjs +43 -13
- package/scripts/voice-worker.cjs +476 -0
- package/skills/design/SKILL.md +110 -0
- package/skills/gentle-ai/SKILL.md +3 -3
- package/skills/work-unit-commits/SKILL.md +5 -3
- package/tests/agent-profiles.test.ts +24 -0
- package/tests/agent-risk-escalation.test.ts +37 -10
- package/tests/agents-config.test.ts +61 -0
- package/tests/agents-fake-child.ts +8 -1
- package/tests/agents-protocol.test.ts +32 -0
- package/tests/agents-runner.test.ts +485 -7
- package/tests/agents-session-transport.test.ts +53 -3
- package/tests/ask-user-choice.test.ts +30 -0
- package/tests/auto-mode.test.ts +17 -0
- package/tests/auto-permission-memory-diagnostics.test.ts +56 -0
- package/tests/auto-permission-runtime.test.ts +85 -1
- package/tests/autonomous-guard.test.ts +142 -0
- package/tests/bounded-writer-admission.test.ts +11 -3
- package/tests/bundled-install.test.ts +3 -3
- package/tests/card-content-policy.test.ts +19 -9
- package/tests/child-safety.test.ts +39 -0
- package/tests/clipboard-image.test.ts +114 -0
- package/tests/codegraph-tools.test.ts +123 -2
- package/tests/command-palette.test.ts +15 -5
- package/tests/commit-slice-signal.test.ts +162 -0
- package/tests/context-backstop.test.ts +225 -0
- package/tests/delegate-for-reason-contract.test.ts +92 -24
- package/tests/delivered-test-lint.test.ts +105 -0
- package/tests/design-canvas-assets.test.ts +710 -0
- package/tests/design-canvas-server.test.ts +1096 -0
- package/tests/design-comments.test.ts +201 -0
- package/tests/design-handoffs.test.ts +282 -0
- package/tests/design-open-browser.test.ts +70 -0
- package/tests/design-sources.test.ts +132 -0
- package/tests/destructive-command-guard.test.ts +85 -1
- package/tests/engram-safety.test.ts +132 -57
- package/tests/external-editor.test.ts +84 -0
- package/tests/fixtures/primary-session-prompt/gentleman.golden.md +143 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/README.md +29 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/manifest.json +83 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/orchestration/pi.md +70 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/routing/pi.md +92 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/lens.schema.json +16 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/refuter.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/targeted-validator.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/lens.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/refuter.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/targeted-validator.json +1 -0
- package/tests/gauge-theme.test.ts +120 -0
- package/tests/gentle-agents.test.ts +645 -21
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-child-guards.test.ts +61 -0
- package/tests/gentle-ai-doctor-herdr.test.ts +94 -0
- package/tests/gentle-ai-installer.test.ts +51 -51
- package/tests/gentle-ai.test.ts +292 -13
- package/tests/gentle-design.test.ts +1313 -0
- package/tests/gentle-music.test.ts +335 -0
- package/tests/gentle-shell-bin.test.ts +163 -0
- package/tests/gentle-shell-bundled.test.ts +392 -176
- package/tests/gentle-shell-import-pi.test.ts +262 -0
- package/tests/gentle-shell-launcher.test.ts +107 -2
- package/tests/gentle-shell-startup-update.test.ts +77 -1
- package/tests/gentle-shell-uninstall.test.ts +89 -0
- package/tests/gentle-shell.test.ts +225 -17
- package/tests/gentle-todo.test.ts +190 -0
- package/tests/headless-delivery-question.test.ts +58 -0
- package/tests/herdr-activity.test.ts +34 -1
- package/tests/image-attachments.test.ts +119 -0
- package/tests/image-paste-chips.test.ts +236 -0
- package/tests/inprocess-reviewer.test.ts +55 -2
- package/tests/installer-bundles.test.ts +12 -6
- package/tests/installer-posix-bootstrap.test.ts +8 -8
- package/tests/installer-preflight.test.ts +16 -0
- package/tests/installer-server.test.ts +10 -2
- package/tests/installer-windows-bootstrap.test.ts +94 -7
- package/tests/installer-windows-diagnostics.test.ts +111 -0
- package/tests/main-channel-upgrade.test.ts +232 -2
- package/tests/main-channel.test.ts +140 -21
- package/tests/nan-provider.test.ts +36 -0
- package/tests/native-review-capability-contract.test.ts +17 -4
- package/tests/native-review-parity.test.ts +23 -2
- package/tests/no-self-pause-contract.test.ts +5 -2
- package/tests/notification-audio-native.test.ts +128 -9
- package/tests/notification-customize.test.ts +17 -5
- package/tests/odd-integration.test.ts +5 -1
- package/tests/odd-routing-canonical-ratchet.test.ts +128 -162
- package/tests/odd-routing-contract.test.ts +115 -30
- package/tests/orchestrator-budget.test.ts +22 -8
- package/tests/orchestrator-discovery.test.ts +29 -0
- package/tests/orchestrator-presence.test.ts +100 -2
- package/tests/orchestrator-rdd-ownership.test.ts +3 -1
- package/tests/orchestrator-scope.test.ts +80 -0
- package/tests/orchestrator-state.test.ts +29 -0
- package/tests/orchestrator-status-publication-contract.test.ts +12 -8
- package/tests/package-files.test.ts +34 -0
- package/tests/package-manifest.test.ts +24 -20
- package/tests/permission-mode-policy.test.ts +10 -1
- package/tests/pi-update-launcher.test.ts +21 -2
- package/tests/pi-update.test.ts +99 -4
- package/tests/primary-session-prompt.test.ts +101 -0
- package/tests/provider-contract-bundle.test.ts +118 -0
- package/tests/provider-contract-mirror.test.ts +24 -0
- package/tests/quiet-tool-rendering.test.ts +23 -0
- package/tests/rdd-aware-verification-contract.test.ts +29 -11
- package/tests/rdd-status-line.test.ts +79 -0
- package/tests/review-agent-end-preflight.test.ts +6 -0
- package/tests/review-candidate-view.test.ts +49 -0
- package/tests/review-contract-prompt.test.ts +5 -2
- package/tests/review-controller-native-routing.test.ts +14 -1
- package/tests/review-controller.test.ts +1 -1
- package/tests/review-host-relay-routing.test.ts +147 -3
- package/tests/review-host-relay.test.ts +9 -2
- package/tests/review-json-arguments.test.ts +20 -3
- package/tests/review-loadout-rdd-off-sdk.test.ts +258 -0
- package/tests/review-owner-identity.test.ts +132 -0
- package/tests/review-relay-transport-agent.test.ts +26 -0
- package/tests/review-reminder-receipt.test.ts +31 -1
- package/tests/review-risk-assessment.test.ts +163 -76
- package/tests/review-session-standing-permission-controller.test.ts +14 -1
- package/tests/review-session-standing-permission.test.ts +27 -1
- package/tests/run-test-suite.test.ts +15 -0
- package/tests/runtime-harness.mjs +16 -6
- package/tests/session-changes-shell.test.ts +1 -1
- package/tests/session-changes.test.ts +2 -1
- package/tests/session-profile-append-controller-fork.test.ts +458 -0
- package/tests/session-profile-append-controller-pi.test.ts +290 -0
- package/tests/session-profile-append-controller.test.ts +731 -0
- package/tests/session-profile-disk-reader-pi.test.ts +183 -0
- package/tests/session-profile-disk-reader.test.ts +311 -0
- package/tests/session-profile-freeze.test.ts +23 -0
- package/tests/session-scratch.test.ts +1011 -0
- package/tests/session-subject-routing-contract.test.ts +2 -1
- package/tests/session-worktree-registry.test.ts +13 -1
- package/tests/shell-bar.test.ts +21 -2
- package/tests/shell-changes-view.test.ts +137 -1
- package/tests/shell-changes.test.ts +148 -1
- package/tests/shell-gauge.test.ts +42 -0
- package/tests/shell-prompt.test.ts +3 -3
- package/tests/shell-render-cache.test.ts +134 -1
- package/tests/shell-sidebar-layout.test.ts +50 -0
- package/tests/short-check-routing-contract.test.ts +1 -1
- package/tests/skill-collision-prefixes.test.ts +3 -1
- package/tests/skill-registry-watcher.test.ts +17 -5
- package/tests/skill-registry.test.ts +47 -3
- package/tests/small-task-inline-contract.test.ts +37 -0
- package/tests/standalone-config-home.test.ts +324 -0
- package/tests/startup-banner.test.ts +5 -4
- package/tests/startup-home-repo.test.ts +34 -0
- package/tests/support/orchestrator-modules.ts +1 -0
- package/tests/task-size-routing-contract.test.ts +70 -10
- package/tests/test-scratch-contract.test.ts +102 -0
- package/tests/test-strength-contract.test.ts +36 -1
- package/tests/tool-batching-contract.test.ts +64 -0
- package/tests/verification-proportionality.test.ts +46 -0
- package/tests/verifier-admission.test.ts +154 -0
- package/tests/verify-agent-contract.test.ts +8 -8
- package/tests/voice-command.test.ts +773 -0
- package/tests/voice-install.test.ts +323 -0
- package/tests/voice-platform.test.ts +176 -0
- package/tests/voice-push-to-talk.test.ts +444 -0
- package/tests/voice-session.test.ts +434 -0
- package/tests/voice-settings.test.ts +49 -0
- package/tests/voice-ui.test.ts +97 -0
- package/tests/voice-worker.test.ts +445 -0
- package/tests/weak-model-economy-prompt.test.ts +32 -0
- package/tests/windows-hidden-processes.test.ts +30 -2
- package/tests/writer-edit-surface-scope.test.ts +3 -0
- package/tests/writer-feature-document-admission.test.ts +156 -0
- package/tests/yolo-customize.test.ts +5 -1
- package/tests/yolo-mode.test.ts +19 -1
- package/tests/yolo-session-policy-capture.test.ts +104 -0
- package/tests/yolo-temp-scratch.test.ts +71 -0
- package/scripts/mirror-odd-routing.mjs +0 -242
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/orchestration/pi.md +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/targeted-validator.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/targeted-validator.json +0 -0
|
@@ -10,6 +10,15 @@ import { basename } from "node:path";
|
|
|
10
10
|
|
|
11
11
|
const tick = () => new Promise<void>((resolve) => setImmediate(resolve));
|
|
12
12
|
|
|
13
|
+
/** UI chains settle across a load-dependent number of turns: wait on the
|
|
14
|
+
* observable state instead of assuming a fixed tick count landed the async
|
|
15
|
+
* step (gentle-shell#2498). The deadline keeps a broken chain failing fast. */
|
|
16
|
+
const settleUntil = async (predicate: () => boolean, what: string): Promise<void> => {
|
|
17
|
+
const deadline = Date.now() + 5000;
|
|
18
|
+
while (!predicate() && Date.now() < deadline) await tick();
|
|
19
|
+
assert.ok(predicate(), `timed out waiting for ${what}`);
|
|
20
|
+
};
|
|
21
|
+
|
|
13
22
|
/** Real packaged WAVs so a valid path uses an existing fixture, never a synthetic name. */
|
|
14
23
|
const WAV_FIXTURE = fileURLToPath(new URL("../assets/sounds/success.wav", import.meta.url));
|
|
15
24
|
const WAV_FIXTURES = {
|
|
@@ -223,7 +232,7 @@ test("an in-flight local sound validation keeps busy, blocking a second field an
|
|
|
223
232
|
assert.match(view.render(160).join("\n"), /Local audio path/);
|
|
224
233
|
view.handleInput(WAV_FIXTURE);
|
|
225
234
|
view.handleInput("\r");
|
|
226
|
-
await
|
|
235
|
+
await settleUntil(() => validated.length === 1, "the submitted value to start validating");
|
|
227
236
|
assert.deepEqual(validated, [`file:${WAV_FIXTURE}`], "the submitted value is validated exactly once");
|
|
228
237
|
// Validation is still pending: `f` must not open a second field and Enter must not write.
|
|
229
238
|
view.handleInput("f");
|
|
@@ -233,11 +242,11 @@ test("an in-flight local sound validation keeps busy, blocking a second field an
|
|
|
233
242
|
assert.equal(h.writes, 0, "no write before the deferred validation resolves");
|
|
234
243
|
// Resolve: a single write, then busy releases for the next edit.
|
|
235
244
|
resolveValidation(true);
|
|
236
|
-
await
|
|
245
|
+
await settleUntil(() => h.writes === 1, "the resolved validation to write exactly once");
|
|
237
246
|
assert.equal(h.writes, 1, "the resolved validation writes exactly once");
|
|
238
247
|
assert.equal(getNotificationService()!.getState().settings.audio.events["agent.failed"], `file:${WAV_FIXTURE}`);
|
|
239
248
|
view.handleInput("f");
|
|
240
|
-
|
|
249
|
+
await settleUntil(() => view.render(160).join("\n").includes("Local audio path"), "busy released so the next edit opens a fresh field");
|
|
241
250
|
} finally { h.owner.retire(); }
|
|
242
251
|
});
|
|
243
252
|
|
|
@@ -262,14 +271,17 @@ test("disposing the card while validation is in flight never writes the stale ch
|
|
|
262
271
|
const h = harness(); try {
|
|
263
272
|
const rows = h.rows();
|
|
264
273
|
let resolveValidation!: (value: boolean) => void;
|
|
265
|
-
|
|
274
|
+
let validationStarted = false;
|
|
275
|
+
getNotificationService()!.validateFile = () => { validationStarted = true; return new Promise<boolean>(resolve => { resolveValidation = resolve; }); };
|
|
266
276
|
const view = customizeView(rows);
|
|
267
277
|
await expandAndSelect(view, "agent.failed:");
|
|
268
278
|
view.handleInput("f");
|
|
269
279
|
view.render(160);
|
|
270
280
|
view.handleInput(WAV_FIXTURE);
|
|
271
281
|
view.handleInput("\r");
|
|
272
|
-
|
|
282
|
+
// Disposing before the validation even starts would never observe the
|
|
283
|
+
// stale-choice guard, so wait until the deferred validation is pending.
|
|
284
|
+
await settleUntil(() => validationStarted, "the in-flight validation to start");
|
|
273
285
|
view.dispose();
|
|
274
286
|
resolveValidation(true);
|
|
275
287
|
await tick(); await tick();
|
|
@@ -48,7 +48,11 @@ test("applicability, fallback and honest evidence flow through ODD actors", () =
|
|
|
48
48
|
test("retired SDD routes and assets are absent while ODD entry and generic workers remain", () => {
|
|
49
49
|
const core = read("assets/orchestrator.md");
|
|
50
50
|
const delegation = readDelegationDetail();
|
|
51
|
-
|
|
51
|
+
// S11 T3: deduplicated. The core's "ODD (Default Workflow, harness section
|
|
52
|
+
// above) is mandatory on every request" restated the harness; the mandatory
|
|
53
|
+
// ODD entry is stated once, in the harness that renders above the core.
|
|
54
|
+
assert.match(read("extensions/gentle-ai.ts"), /Default workflow: Organic Driven Development \(MANDATORY\)\nOrganic Driven Development \(ODD\) is the predefined workflow of this orchestrator\. Every request enters it/);
|
|
55
|
+
assert.doesNotMatch(core, /ODD \(Default Workflow, harness section above\)/);
|
|
52
56
|
assert.match(delegation, /generic writer chain is unavailable/);
|
|
53
57
|
for (const path of [
|
|
54
58
|
"assets/agents/gentle-ai-worker.md",
|
|
@@ -1,46 +1,36 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import {
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import test from "node:test";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
ODD_ROUTING_SOURCE_PATH,
|
|
9
|
-
ODD_ROUTING_SOURCE_REPO,
|
|
10
|
-
assertCleanGentleAiCheckout,
|
|
11
|
-
parseOddRoutingFixture,
|
|
12
|
-
renderOddRoutingFixture,
|
|
13
|
-
resolveOddRoutingProvenance,
|
|
14
|
-
sha256Hex,
|
|
15
|
-
} from "../scripts/mirror-odd-routing.mjs";
|
|
5
|
+
import { __testing } from "../extensions/gentle-ai.ts";
|
|
6
|
+
import { PI_RUNTIME_IDENTITY, ROUTING_REQUIRED_SINCE_MINOR, verifyProviderContractBundleTree } from "../lib/provider-contract-bundle.ts";
|
|
7
|
+
import { PROVIDER_CONTRACT_LOCK_FILE, PROVIDER_CONTRACT_MIRROR_ROOT } from "../scripts/mirror-provider-contract.mjs";
|
|
16
8
|
|
|
17
9
|
// ---------------------------------------------------------------------------
|
|
18
10
|
// ODD routing drift ratchet (gentle-pi#1147 follow-up)
|
|
19
11
|
//
|
|
20
|
-
// gentle-
|
|
21
|
-
// `internal/components/agentguidance/routing.go`
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// `
|
|
25
|
-
//
|
|
26
|
-
// either from the canonical
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
// assets, so drift stays visible as a deliberate diff.
|
|
12
|
+
// gentle-ai renders the always-on ODD routing block for Pi
|
|
13
|
+
// (`internal/components/agentguidance/routing.go`) and publishes it in the
|
|
14
|
+
// provider contract bundle as `routing/pi.md` (contract 1.4.0). The mirrored
|
|
15
|
+
// bundle (`contracts/review-provider-contract-mirror/`, refreshed with
|
|
16
|
+
// `scripts/mirror-provider-contract.mjs`) is the canonical reference: the
|
|
17
|
+
// harness ODD steps are rendered from it, and this ratchet fails when a
|
|
18
|
+
// mandatory-delegation clause is dropped either from the canonical block or
|
|
19
|
+
// from a Pi surface that restates it (the `assets/orchestrator*.md` modules
|
|
20
|
+
// stay Pi's own and keep their anchors).
|
|
30
21
|
// ---------------------------------------------------------------------------
|
|
31
22
|
|
|
32
23
|
const REPO_ROOT = join(import.meta.dirname, "..");
|
|
33
|
-
const
|
|
24
|
+
const MIRROR_ROOT = join(REPO_ROOT, ...PROVIDER_CONTRACT_MIRROR_ROOT.split("/"));
|
|
34
25
|
const DELEGATION_PATH = join(REPO_ROOT, "assets", "orchestrator-delegation.md");
|
|
35
26
|
const CORE_PATH = join(REPO_ROOT, "assets", "orchestrator.md");
|
|
36
|
-
const EXTENSION_PATH = join(REPO_ROOT, "extensions", "gentle-ai.ts");
|
|
37
27
|
|
|
38
28
|
const readRepo = (absolutePath: string): string => readFileSync(absolutePath, "utf8");
|
|
39
29
|
|
|
40
30
|
// A mandatory-delegation semantic anchor: the clause the canonical block must
|
|
41
31
|
// still carry, plus the wording each pi mirror surface is expected to carry.
|
|
42
|
-
// A canonical change that drops `canonical` is caught here
|
|
43
|
-
// because the
|
|
32
|
+
// A canonical change that drops `canonical` is caught here once the bundle is re-mirrored
|
|
33
|
+
// because the mirrored routing block no longer carries it.
|
|
44
34
|
interface RoutingAnchor {
|
|
45
35
|
label: string;
|
|
46
36
|
canonical: string;
|
|
@@ -49,23 +39,33 @@ interface RoutingAnchor {
|
|
|
49
39
|
|
|
50
40
|
const DELEGATION = "assets/orchestrator-delegation.md";
|
|
51
41
|
const CORE = "assets/orchestrator.md";
|
|
52
|
-
|
|
42
|
+
// The harness prompt rendered by extensions/gentle-ai.ts, including the ODD
|
|
43
|
+
// steps it takes from the mirrored routing/pi.md.
|
|
44
|
+
const EXTENSION = "rendered harness prompt";
|
|
53
45
|
|
|
54
46
|
const ANCHORS: readonly RoutingAnchor[] = [
|
|
55
47
|
{
|
|
56
|
-
|
|
57
|
-
|
|
48
|
+
// S9 canon change (gentle-ai 87a3b2eb): the heading dropped "Mandatory";
|
|
49
|
+
// a trigger is mandatory only once its condition truly holds.
|
|
50
|
+
label: "delegation triggers heading",
|
|
51
|
+
canonical: "### Delegation Triggers",
|
|
58
52
|
mirrors: [
|
|
59
|
-
{ surface: DELEGATION, includes: "####
|
|
60
|
-
|
|
53
|
+
{ surface: DELEGATION, includes: "#### Delegation Triggers" },
|
|
54
|
+
// Writer-rule fix: the core names them "Delegation Triggers" and says
|
|
55
|
+
// each holds only when its condition truly holds; the lazy port keeps
|
|
56
|
+
// the canonical heading.
|
|
57
|
+
{ surface: CORE, includes: "Delegation Triggers — each mechanism turns on only when its own trigger condition truly holds" },
|
|
61
58
|
],
|
|
62
59
|
},
|
|
63
60
|
{
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
// S9 canon change: "These triggers are mandatory, not advisory." read as a
|
|
62
|
+
// default to delegate (L8); a trigger now holds only when its condition
|
|
63
|
+
// truly holds, none by default, inline when in doubt.
|
|
64
|
+
label: "triggers hold only when their condition truly holds",
|
|
65
|
+
canonical: "Each trigger holds only when its condition truly holds, and only then is it mandatory, not advisory; no trigger holds by default, and when in doubt work inline.",
|
|
66
66
|
mirrors: [
|
|
67
|
-
{ surface: DELEGATION, includes: "
|
|
68
|
-
{ surface: EXTENSION, includes: "
|
|
67
|
+
{ surface: DELEGATION, includes: "Each trigger holds only when its condition truly holds, and only then is it mandatory, not advisory; no trigger holds by default, and when in doubt work inline." },
|
|
68
|
+
{ surface: EXTENSION, includes: "No trigger holds by default and, when in doubt, work inline" },
|
|
69
69
|
],
|
|
70
70
|
},
|
|
71
71
|
{
|
|
@@ -94,17 +94,65 @@ const ANCHORS: readonly RoutingAnchor[] = [
|
|
|
94
94
|
],
|
|
95
95
|
},
|
|
96
96
|
{
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
97
|
+
// S9 canon change: exploration never reads files an inline write reads anyway.
|
|
98
|
+
label: "never explore to read files an inline write reads anyway",
|
|
99
|
+
canonical: "Never explore to read files you will read anyway before writing inline.",
|
|
100
|
+
mirrors: [
|
|
101
|
+
{ surface: DELEGATION, includes: "Never explore to read files you will read anyway before writing inline." },
|
|
102
|
+
{ surface: CORE, includes: "never for reading before an inline write" },
|
|
103
|
+
],
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
label: "writer trigger fires only for a named reason, never by size or file count",
|
|
107
|
+
// S9 canon change (gentle-ai 87a3b2eb): the canon caught up with Gentle
|
|
108
|
+
// Shell (gentle-shell#1731): the writer fires on named reasons
|
|
109
|
+
// (parallelism, context), never on size alone or a price ratio.
|
|
110
|
+
canonical: "**Writer trigger:** a writer is delegated only for a named reason, one bounded writer per unit",
|
|
103
111
|
mirrors: [
|
|
104
112
|
{ surface: DELEGATION, includes: "**Writer trigger (Writer rule):** a large task alone never delegates, and file count never fires this trigger" },
|
|
105
113
|
{ surface: CORE, includes: "**Writer rule** — never by file count or a large task alone" },
|
|
106
114
|
],
|
|
107
115
|
},
|
|
116
|
+
{
|
|
117
|
+
// S9 canon change: a writer unit is one independent behavior with its
|
|
118
|
+
// code, tests, and docs together, never split across writers.
|
|
119
|
+
label: "a writer unit is one independent behavior",
|
|
120
|
+
canonical: "A unit is one independent piece of behavior with its code, tests, and docs together",
|
|
121
|
+
mirrors: [
|
|
122
|
+
{ surface: DELEGATION, includes: "A unit is one independent piece of behavior with its code, tests, and docs together" },
|
|
123
|
+
{ surface: CORE, includes: "Tests, docs, and code of the same behavior are one unit, never separate writers" },
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
// S9 canon change: a unit qualifies only with several minutes of work,
|
|
128
|
+
// because a subagent spends one to two minutes reading before it writes.
|
|
129
|
+
label: "a writer unit needs several minutes of work",
|
|
130
|
+
canonical: "several files or roughly 100+ changed lines",
|
|
131
|
+
mirrors: [
|
|
132
|
+
{ surface: DELEGATION, includes: "several files or ~100+ changed lines; a subagent spends 1-2 minutes reading before it writes" },
|
|
133
|
+
{ surface: CORE, includes: "several files or roughly 100+ changed lines" },
|
|
134
|
+
],
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
// S9 canon change (gentle-ai c3763aa7, C3): whoever writes reuses the
|
|
138
|
+
// existing shared-state helper and keeps its invariants.
|
|
139
|
+
label: "writers reuse the existing shared-state function",
|
|
140
|
+
canonical: "reuses the existing function for shared state",
|
|
141
|
+
mirrors: [
|
|
142
|
+
{ surface: DELEGATION, includes: "reuses the existing function for shared state and keeps its invariants (ordering, uniqueness, validation, normalization)" },
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
// S9 canon change (gentle-ai c3763aa7, C8): PRESERVE diffs each existing
|
|
147
|
+
// command that shares the changed code against the previous code.
|
|
148
|
+
label: "differential check against the previous code",
|
|
149
|
+
canonical: "run a differential check",
|
|
150
|
+
mirrors: [
|
|
151
|
+
{ surface: DELEGATION, includes: "**Differential check (PRESERVE):**" },
|
|
152
|
+
// weak-model-economy S2: the harness gates the check on shared code an unnamed command uses.
|
|
153
|
+
{ surface: EXTENSION, includes: "run one differential check" },
|
|
154
|
+
],
|
|
155
|
+
},
|
|
108
156
|
{
|
|
109
157
|
label: "task size decides the route (gentle-shell#1494)",
|
|
110
158
|
canonical: "### Task Size",
|
|
@@ -114,11 +162,14 @@ const ANCHORS: readonly RoutingAnchor[] = [
|
|
|
114
162
|
],
|
|
115
163
|
},
|
|
116
164
|
{
|
|
117
|
-
label: "verification trigger is reserved for high
|
|
118
|
-
|
|
165
|
+
label: "verification trigger is reserved for a named high-risk item",
|
|
166
|
+
// S9 canon change (gentle-ai 6b9a71ef): only a change whose Risk line
|
|
167
|
+
// names a high-risk item gets the independent verifier.
|
|
168
|
+
canonical: "**Verification trigger:** only a change whose Risk line names a high-risk item gets an independent verifier",
|
|
119
169
|
mirrors: [
|
|
120
|
-
|
|
121
|
-
{ surface:
|
|
170
|
+
// Verification by named risk: the core row names the escalate-only verifier.
|
|
171
|
+
{ surface: CORE, includes: "**Verification rule** — only a named high-risk item (assess `escalate`) → independent `gentle-ai-verify`" },
|
|
172
|
+
{ surface: DELEGATION, includes: "only a change whose Risk line names a high-risk item (Task Size, assess `escalate`) gets an independent `gentle-ai-verify` run" },
|
|
122
173
|
],
|
|
123
174
|
},
|
|
124
175
|
{
|
|
@@ -138,10 +189,12 @@ const ANCHORS: readonly RoutingAnchor[] = [
|
|
|
138
189
|
label: "long-session backstop",
|
|
139
190
|
canonical: "**Long-session backstop:**",
|
|
140
191
|
// Gentle Shell intentionally leads the canon here: a parent-context token
|
|
141
|
-
// backstop replaces the tool-call count (tracked by gentle-ai#5139)
|
|
192
|
+
// backstop replaces the tool-call count (tracked by gentle-ai#5139), and the
|
|
193
|
+
// runtime measures it and signals it instead of the canon's advisory ~150k
|
|
194
|
+
// estimate (harness roadmap R1), so the Pi anchor names the runtime signal.
|
|
142
195
|
mirrors: [
|
|
143
|
-
{ surface: DELEGATION, includes: "**Context backstop:** when the
|
|
144
|
-
{ surface: CORE, includes: "**Context backstop** —
|
|
196
|
+
{ surface: DELEGATION, includes: "**Context backstop:** when the runtime signals it" },
|
|
197
|
+
{ surface: CORE, includes: "**Context backstop** — when the runtime signals it" },
|
|
145
198
|
],
|
|
146
199
|
},
|
|
147
200
|
{
|
|
@@ -173,58 +226,52 @@ const ANCHORS: readonly RoutingAnchor[] = [
|
|
|
173
226
|
],
|
|
174
227
|
},
|
|
175
228
|
{
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
229
|
+
// S9 canon change: step 6 honors its triggers only when their conditions truly hold.
|
|
230
|
+
label: "ODD step 6 honors its delegation triggers when their conditions hold",
|
|
231
|
+
canonical: "honoring its delegation triggers when their conditions truly hold",
|
|
232
|
+
mirrors: [{ surface: EXTENSION, includes: "honoring its delegation triggers when their conditions truly hold" }],
|
|
179
233
|
},
|
|
180
234
|
];
|
|
181
235
|
|
|
182
236
|
// Condensed trigger rows that live only in the always-on core prompt; the
|
|
183
237
|
// canonical RenderRouting block does not carry the incident or verification
|
|
184
|
-
// rows, so they are mirror-only and not
|
|
238
|
+
// rows, so they are mirror-only and not anchors of the canonical routing block.
|
|
185
239
|
const CORE_ONLY_TRIGGERS = [
|
|
186
240
|
"**Incident rule** — diagnose wrong cwd/worktree/git/tooling incidents separately",
|
|
187
|
-
"**Verification rule** — high
|
|
241
|
+
"**Verification rule** — only a named high-risk item (assess `escalate`) → independent `gentle-ai-verify`",
|
|
188
242
|
] as const;
|
|
189
243
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
);
|
|
195
|
-
|
|
244
|
+
// The verified mirrored routing/pi.md: the bundle tree must pass the strict
|
|
245
|
+
// decoder, so a hand edit to the canonical block fails here as well as in
|
|
246
|
+
// `pnpm run check:provider-contract`.
|
|
247
|
+
function canonicalRoutingBlock(): string {
|
|
248
|
+
const lock = JSON.parse(readFileSync(join(MIRROR_ROOT, PROVIDER_CONTRACT_LOCK_FILE), "utf8")) as { contract_semver: string };
|
|
249
|
+
const bundle = verifyProviderContractBundleTree(join(MIRROR_ROOT, `v${lock.contract_semver}`, "bundle"));
|
|
250
|
+
const block = bundle.routing.get(PI_RUNTIME_IDENTITY);
|
|
251
|
+
assert.ok(block !== undefined, `the mirrored provider contract ${bundle.contractSemver} carries no routing/${PI_RUNTIME_IDENTITY}.md`);
|
|
252
|
+
return block;
|
|
196
253
|
}
|
|
197
254
|
|
|
198
255
|
// ---------------------------------------------------------------------------
|
|
199
|
-
// 1 —
|
|
256
|
+
// 1 — The canonical reference is the verified mirrored bundle
|
|
200
257
|
// ---------------------------------------------------------------------------
|
|
201
258
|
|
|
202
|
-
test("the canonical routing
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
assert.equal(header.source_repo, ODD_ROUTING_SOURCE_REPO);
|
|
209
|
-
assert.equal(header.source_path, ODD_ROUTING_SOURCE_PATH);
|
|
210
|
-
assert.match(header.source_commit ?? "", /^[0-9a-f]{40}$/, "fixture must record the gentle-ai source commit");
|
|
211
|
-
assert.match(header.generated_at ?? "", /^\d{4}-\d{2}-\d{2}T/, "fixture must record a generation date");
|
|
212
|
-
assert.match(header.block_sha256 ?? "", /^[0-9a-f]{64}$/, "fixture must record the block body digest");
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
test("the fixture digest matches its block body byte-for-byte", () => {
|
|
216
|
-
const { header, body } = parseOddRoutingFixture(readRepo(FIXTURE_PATH));
|
|
217
|
-
assert.equal(header.block_sha256, sha256Hex(body), "the fixture body was hand-edited without regenerating its digest");
|
|
259
|
+
test("the canonical routing reference is the verified mirrored routing/pi.md (contract >= 1.4.0)", () => {
|
|
260
|
+
const lock = JSON.parse(readFileSync(join(MIRROR_ROOT, PROVIDER_CONTRACT_LOCK_FILE), "utf8")) as { contract_semver: string; entries: Record<string, string> };
|
|
261
|
+
const [major, minor] = lock.contract_semver.split(".").map(Number);
|
|
262
|
+
assert.ok(major === 1 && (minor ?? 0) >= ROUTING_REQUIRED_SINCE_MINOR, `mirrored contract ${lock.contract_semver} predates routing`);
|
|
263
|
+
assert.match(lock.entries[`routing/${PI_RUNTIME_IDENTITY}.md`] ?? "", /^[0-9a-f]{64}$/);
|
|
264
|
+
assert.ok(canonicalRoutingBlock().startsWith("## Implementation Routing\n"));
|
|
218
265
|
});
|
|
219
266
|
|
|
220
267
|
// ---------------------------------------------------------------------------
|
|
221
|
-
// 2 —
|
|
268
|
+
// 2 — The canonical block carries every mandatory anchor
|
|
222
269
|
// ---------------------------------------------------------------------------
|
|
223
270
|
|
|
224
|
-
test("the canonical
|
|
225
|
-
const body =
|
|
271
|
+
test("the canonical routing block carries every mandatory-delegation anchor", () => {
|
|
272
|
+
const body = canonicalRoutingBlock();
|
|
226
273
|
for (const anchor of ANCHORS) {
|
|
227
|
-
assert.ok(body.includes(anchor.canonical), `canonical
|
|
274
|
+
assert.ok(body.includes(anchor.canonical), `canonical routing block dropped anchor: ${anchor.label}`);
|
|
228
275
|
}
|
|
229
276
|
});
|
|
230
277
|
|
|
@@ -236,7 +283,7 @@ test("the pi mirror surfaces carry every mapped mandatory-delegation anchor", ()
|
|
|
236
283
|
const surfaces: Record<string, string> = {
|
|
237
284
|
[DELEGATION]: readRepo(DELEGATION_PATH),
|
|
238
285
|
[CORE]: readRepo(CORE_PATH),
|
|
239
|
-
[EXTENSION]:
|
|
286
|
+
[EXTENSION]: __testing.buildGentlePrompt("gentleman"),
|
|
240
287
|
};
|
|
241
288
|
for (const anchor of ANCHORS) {
|
|
242
289
|
for (const mirror of anchor.mirrors) {
|
|
@@ -256,84 +303,3 @@ test("the always-on core prompt carries the condensed incident and verification
|
|
|
256
303
|
assert.ok(core.includes(row), `assets/orchestrator.md is missing condensed trigger row: ${JSON.stringify(row)}`);
|
|
257
304
|
}
|
|
258
305
|
});
|
|
259
|
-
|
|
260
|
-
// ---------------------------------------------------------------------------
|
|
261
|
-
// 4 — Regeneration provenance: idempotent and fail-closed
|
|
262
|
-
// ---------------------------------------------------------------------------
|
|
263
|
-
|
|
264
|
-
// The fixture header claims provenance from the source commit, so regeneration
|
|
265
|
-
// must be byte-reproducible: the same gentle-ai commit must not produce a
|
|
266
|
-
// date-only diff. `generated_at` therefore tracks the source commit's committer
|
|
267
|
-
// date instead of wall-clock time.
|
|
268
|
-
test("fixture rendering is idempotent and derives generated_at from the source commit", () => {
|
|
269
|
-
const sourceCommit = "e7729359fd9d6cb691ed2a88e8f72b1372f7c92e";
|
|
270
|
-
const committerDate = "2026-09-18T13:49:03+02:00";
|
|
271
|
-
const fakeGit = (args: readonly string[]): string => {
|
|
272
|
-
switch (args[0]) {
|
|
273
|
-
case "status":
|
|
274
|
-
return "";
|
|
275
|
-
case "rev-parse":
|
|
276
|
-
return `${sourceCommit}\n`;
|
|
277
|
-
case "show":
|
|
278
|
-
return `${committerDate}\n`;
|
|
279
|
-
default:
|
|
280
|
-
throw new Error(`unexpected git invocation: ${args.join(" ")}`);
|
|
281
|
-
}
|
|
282
|
-
};
|
|
283
|
-
const block = `${ODD_ROUTING_BLOCK_PREFIX}\n\n- a canonical clause\n`;
|
|
284
|
-
const first = renderOddRoutingFixture(block, resolveOddRoutingProvenance("/fake/gentle-ai", fakeGit));
|
|
285
|
-
const second = renderOddRoutingFixture(block, resolveOddRoutingProvenance("/fake/gentle-ai", fakeGit));
|
|
286
|
-
assert.equal(first, second, "rendering the same source commit twice must be byte-identical");
|
|
287
|
-
const { header } = parseOddRoutingFixture(first);
|
|
288
|
-
assert.equal(header.source_commit, sourceCommit);
|
|
289
|
-
assert.equal(
|
|
290
|
-
header.generated_at,
|
|
291
|
-
committerDate,
|
|
292
|
-
"generated_at must track the source commit's committer date, not wall-clock time",
|
|
293
|
-
);
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
// A dirty checkout would render uncommitted content under the committed
|
|
297
|
-
// provenance claim, so the mirror fails closed before rendering and names the
|
|
298
|
-
// tracked paths. Untracked files (the odd/tasks notes) must never block.
|
|
299
|
-
test("the mirror fails closed on a dirty source checkout and names the tracked paths", () => {
|
|
300
|
-
const recordedCalls: string[][] = [];
|
|
301
|
-
const cleanGit = (args: readonly string[]): string => {
|
|
302
|
-
recordedCalls.push([...args]);
|
|
303
|
-
switch (args[0]) {
|
|
304
|
-
case "status":
|
|
305
|
-
return "";
|
|
306
|
-
case "rev-parse":
|
|
307
|
-
return `${"a".repeat(40)}\n`;
|
|
308
|
-
case "show":
|
|
309
|
-
return "2026-09-18T13:49:03+02:00\n";
|
|
310
|
-
default:
|
|
311
|
-
throw new Error(`unexpected git invocation: ${args.join(" ")}`);
|
|
312
|
-
}
|
|
313
|
-
};
|
|
314
|
-
resolveOddRoutingProvenance("/fake/gentle-ai", cleanGit);
|
|
315
|
-
assert.deepEqual(
|
|
316
|
-
recordedCalls.find((args) => args[0] === "status"),
|
|
317
|
-
["status", "--porcelain", "--untracked-files=no"],
|
|
318
|
-
"the dirty guard must ignore untracked files",
|
|
319
|
-
);
|
|
320
|
-
|
|
321
|
-
assert.doesNotThrow(() => assertCleanGentleAiCheckout(""));
|
|
322
|
-
const dirtyGit = (args: readonly string[]): string => {
|
|
323
|
-
if (args[0] === "status") {
|
|
324
|
-
return " M internal/components/agentguidance/routing.go\n M scripts/other.mjs\n";
|
|
325
|
-
}
|
|
326
|
-
throw new Error(`unexpected git invocation: ${args.join(" ")}`);
|
|
327
|
-
};
|
|
328
|
-
assert.throws(
|
|
329
|
-
() => resolveOddRoutingProvenance("/fake/gentle-ai", dirtyGit),
|
|
330
|
-
(error: unknown) => {
|
|
331
|
-
assert.ok(error instanceof Error);
|
|
332
|
-
assert.match(
|
|
333
|
-
error.message,
|
|
334
|
-
/gentle-ai checkout is dirty; provenance would be unverifiable: internal\/components\/agentguidance\/routing\.go, scripts\/other\.mjs\. Commit or stash first\./,
|
|
335
|
-
);
|
|
336
|
-
return true;
|
|
337
|
-
},
|
|
338
|
-
);
|
|
339
|
-
});
|