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
|
@@ -208,19 +208,19 @@ function enforcePrivateWindowsDacl(path: string, identity: WindowsAclIdentity =
|
|
|
208
208
|
assertPrivateWindowsDacl(path, "directory", true, identity);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
-
function privateWindowsDacl(path: string, kind: WindowsObjectKind, protectedDacl: boolean, enforce = false): void {
|
|
211
|
+
function privateWindowsDacl(path: string, kind: WindowsObjectKind, protectedDacl: boolean, enforce = false, windowsIdentity?: WindowsAclIdentity): void {
|
|
212
212
|
if (testingWindowsAclAuthority !== undefined) return testingWindowsAclAuthority(path);
|
|
213
|
-
if (enforce) enforcePrivateWindowsDacl(path);
|
|
214
|
-
else assertPrivateWindowsDacl(path, kind, protectedDacl);
|
|
213
|
+
if (enforce) enforcePrivateWindowsDacl(path, windowsIdentity);
|
|
214
|
+
else assertPrivateWindowsDacl(path, kind, protectedDacl, windowsIdentity);
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
function privateWindowsCandidateOwnerBoundary(commonDir: string, enforce = false): void {
|
|
217
|
+
function privateWindowsCandidateOwnerBoundary(commonDir: string, enforce = false, windowsIdentity?: WindowsAclIdentity): void {
|
|
218
218
|
const boundary = [commonDir, join(commonDir, "gentle-ai"), join(commonDir, "gentle-ai", "candidate-views")];
|
|
219
219
|
if (testingWindowsAclAuthority !== undefined) {
|
|
220
220
|
for (const path of boundary) testingWindowsAclAuthority(path);
|
|
221
221
|
return;
|
|
222
222
|
}
|
|
223
|
-
const identity = windowsAclIdentity();
|
|
223
|
+
const identity = windowsIdentity ?? windowsAclIdentity();
|
|
224
224
|
if (enforce) {
|
|
225
225
|
for (const path of boundary) validatePrivateWindowsOwner(windowsOwnerSid(path, "directory"), identity.user);
|
|
226
226
|
for (const path of boundary) enforcePrivateWindowsDacl(path, identity);
|
|
@@ -304,16 +304,23 @@ function directory(path: string, privateMode = false, platform: NodeJS.Platform
|
|
|
304
304
|
return `${stat.dev}:${stat.ino}`;
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
-
|
|
307
|
+
function checkedCandidateOwnerParent(commonDir: string, platform: NodeJS.Platform): { parent: string; windowsIdentity?: WindowsAclIdentity } {
|
|
308
308
|
directory(commonDir, false, platform);
|
|
309
309
|
const control = join(commonDir, "gentle-ai");
|
|
310
310
|
directory(control, false, platform);
|
|
311
311
|
const parent = join(commonDir, "gentle-ai", "candidate-views");
|
|
312
312
|
if (platform === "win32") {
|
|
313
313
|
directory(parent, false, platform);
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
314
|
+
const windowsIdentity = testingWindowsAclAuthority === undefined ? windowsAclIdentity() : undefined;
|
|
315
|
+
privateWindowsCandidateOwnerBoundary(commonDir, false, windowsIdentity);
|
|
316
|
+
return { parent, windowsIdentity };
|
|
317
|
+
}
|
|
318
|
+
directory(parent, true, platform, true);
|
|
319
|
+
return { parent };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export function assertCandidateOwnerParent(commonDir: string, platform: NodeJS.Platform = process.platform): string {
|
|
323
|
+
return checkedCandidateOwnerParent(commonDir, platform).parent;
|
|
317
324
|
}
|
|
318
325
|
|
|
319
326
|
export function prepareCandidateOwnerParent(commonDir: string, platform: NodeJS.Platform = process.platform): string {
|
|
@@ -329,13 +336,13 @@ export function prepareCandidateOwnerParent(commonDir: string, platform: NodeJS.
|
|
|
329
336
|
return assertCandidateOwnerParent(commonDir, platform);
|
|
330
337
|
}
|
|
331
338
|
|
|
332
|
-
function regular(path: string, privateMode = false, platform: NodeJS.Platform = process.platform): string {
|
|
339
|
+
function regular(path: string, privateMode = false, platform: NodeJS.Platform = process.platform, windowsIdentity?: WindowsAclIdentity): string {
|
|
333
340
|
// Keep 64-bit identity exact without a second stat lookup racing the privacy checks.
|
|
334
341
|
const stat = lstatSync(path, { bigint: true });
|
|
335
342
|
const uid = process.getuid?.();
|
|
336
343
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1n || !samePath(realpathSync(path), path, platform) || stat.size > 16384n ||
|
|
337
344
|
(privateMode && platform !== "win32" && (uid === undefined || stat.uid !== BigInt(uid) || (stat.mode & 0o777n) !== 0o600n))) throw new Error("Unsafe candidate owner file");
|
|
338
|
-
if (privateMode && platform === "win32") privateWindowsDacl(path, "file", false);
|
|
345
|
+
if (privateMode && platform === "win32") privateWindowsDacl(path, "file", false, false, windowsIdentity);
|
|
339
346
|
return `${stat.dev}:${stat.ino}`;
|
|
340
347
|
}
|
|
341
348
|
|
|
@@ -347,27 +354,27 @@ function syncDirectory(path: string): void {
|
|
|
347
354
|
try { fsyncSync(fd); } finally { closeSync(fd); }
|
|
348
355
|
}
|
|
349
356
|
|
|
350
|
-
function removeExactPrivateFile(path: string, identity: string, platform: NodeJS.Platform): void {
|
|
357
|
+
function removeExactPrivateFile(path: string, identity: string, platform: NodeJS.Platform, windowsIdentity?: WindowsAclIdentity): void {
|
|
351
358
|
try {
|
|
352
|
-
if (regular(path, true, platform) !== identity) return;
|
|
359
|
+
if (regular(path, true, platform, windowsIdentity) !== identity) return;
|
|
353
360
|
unlinkSync(path);
|
|
354
361
|
syncDirectory(dirname(path));
|
|
355
362
|
} catch { /* An unproven or replaced file survives. */ }
|
|
356
363
|
}
|
|
357
364
|
|
|
358
|
-
function exclusiveFile(path: string, content: string, platform: NodeJS.Platform = process.platform, rollbackOnFailure = false): string {
|
|
365
|
+
function exclusiveFile(path: string, content: string, platform: NodeJS.Platform = process.platform, rollbackOnFailure = false, windowsIdentity?: WindowsAclIdentity): string {
|
|
359
366
|
let identity: string | undefined;
|
|
360
367
|
try {
|
|
361
368
|
const fd = openSync(path, "wx", 0o600);
|
|
362
369
|
try {
|
|
363
|
-
identity = regular(path, true, platform);
|
|
370
|
+
identity = regular(path, true, platform, windowsIdentity);
|
|
364
371
|
writeFileSync(fd, content);
|
|
365
372
|
fsyncSync(fd);
|
|
366
373
|
} finally { closeSync(fd); }
|
|
367
374
|
syncDirectory(dirname(path));
|
|
368
375
|
return identity;
|
|
369
376
|
} catch (error) {
|
|
370
|
-
if (rollbackOnFailure && identity !== undefined) removeExactPrivateFile(path, identity, platform);
|
|
377
|
+
if (rollbackOnFailure && identity !== undefined) removeExactPrivateFile(path, identity, platform, windowsIdentity);
|
|
371
378
|
throw error;
|
|
372
379
|
}
|
|
373
380
|
}
|
|
@@ -375,7 +382,10 @@ function exclusiveFile(path: string, content: string, platform: NodeJS.Platform
|
|
|
375
382
|
function markerPath(root: string): string { return `${root}.owner.json`; }
|
|
376
383
|
|
|
377
384
|
export function createCandidateOwner(commonDir: string, root: string, platform: NodeJS.Platform = process.platform): CandidateViewOwner {
|
|
378
|
-
|
|
385
|
+
// Reuse only identity within this module-controlled synchronous creation.
|
|
386
|
+
// Owner/DACL observations stay fresh, including rollback. Nothing is retained
|
|
387
|
+
// across calls or passed into callback-based removal/sweeping operations.
|
|
388
|
+
const { parent, windowsIdentity } = checkedCandidateOwnerParent(commonDir, platform);
|
|
379
389
|
const uuid = basename(root);
|
|
380
390
|
if (!UUID.test(uuid) || root !== join(parent, uuid) || lstatSync(root, { throwIfNoEntry: false })) throw new Error("Unsafe candidate owner root");
|
|
381
391
|
const owner: CandidateViewOwner = { version: 1, uuid, token: randomUUID(), pid: process.pid, host: localHost(), root, commonDir };
|
|
@@ -383,11 +393,11 @@ export function createCandidateOwner(commonDir: string, root: string, platform:
|
|
|
383
393
|
const marker = markerPath(root);
|
|
384
394
|
let markerIdentity: string | undefined;
|
|
385
395
|
try {
|
|
386
|
-
markerIdentity = exclusiveFile(marker, JSON.stringify(owner), platform, true);
|
|
396
|
+
markerIdentity = exclusiveFile(marker, JSON.stringify(owner), platform, true, windowsIdentity);
|
|
387
397
|
syncDirectory(dirname(parent));
|
|
388
398
|
syncDirectory(commonDir);
|
|
389
399
|
} catch (error) {
|
|
390
|
-
if (markerIdentity !== undefined) removeExactPrivateFile(marker, markerIdentity, platform);
|
|
400
|
+
if (markerIdentity !== undefined) removeExactPrivateFile(marker, markerIdentity, platform, windowsIdentity);
|
|
391
401
|
throw error;
|
|
392
402
|
}
|
|
393
403
|
return Object.freeze(owner);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFileSync, type ExecFileSyncOptions } from "node:child_process";
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
-
import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, symlinkSync, utimesSync } from "node:fs";
|
|
3
|
+
import { chmodSync, copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, symlinkSync, utimesSync, type Stats } from "node:fs";
|
|
4
4
|
import { mkdtempSync } from "node:fs";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
@@ -650,6 +650,32 @@ function makeReadonly(root: string, entries: readonly CandidateViewEntry[]): voi
|
|
|
650
650
|
for (const directory of candidateDirectories(root, entries)) chmodSync(directory, 0o555);
|
|
651
651
|
}
|
|
652
652
|
|
|
653
|
+
// macOS File Provider (iCloud-synced Desktop/Documents) asynchronously resets
|
|
654
|
+
// directory modes to exactly 0700 while leaving file modes intact, so a view
|
|
655
|
+
// made read-only at creation later reads as writable. Only that exact shape on
|
|
656
|
+
// a directory this user owns is re-tightened; any group or other bit still
|
|
657
|
+
// refuses. Owner-only write grants nothing a same-user process could not do by
|
|
658
|
+
// chmod anyway, and the caller then proves the directory holds exactly the
|
|
659
|
+
// frozen children, closing the ignored-file injection the untracked scan misses.
|
|
660
|
+
function repairFileProviderDirectoryMode(directory: string, metadata: Stats, platform: NodeJS.Platform): boolean {
|
|
661
|
+
if (platform === "win32" || !metadata.isDirectory() || metadata.isSymbolicLink() || (metadata.mode & 0o7777) !== 0o700) return false;
|
|
662
|
+
if (typeof process.getuid !== "function" || metadata.uid !== process.getuid()) return false;
|
|
663
|
+
chmodSync(directory, 0o555);
|
|
664
|
+
return true;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
function expectedDirectoryChildren(root: string, entries: readonly CandidateViewEntry[]): ReadonlyMap<string, ReadonlySet<string>> {
|
|
668
|
+
const children = new Map<string, Set<string>>([[root, new Set([".git"])]]);
|
|
669
|
+
for (const entry of entries) {
|
|
670
|
+
for (let path = join(root, entry.path); path !== root && isWithin(root, path); path = dirname(path)) {
|
|
671
|
+
const parent = dirname(path);
|
|
672
|
+
if (!children.has(parent)) children.set(parent, new Set());
|
|
673
|
+
children.get(parent)!.add(basename(path));
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return children;
|
|
677
|
+
}
|
|
678
|
+
|
|
653
679
|
function makeWritableForCleanup(path: string): void {
|
|
654
680
|
const entry = lstatSync(path, { throwIfNoEntry: false });
|
|
655
681
|
if (!entry || entry.isSymbolicLink()) return;
|
|
@@ -1050,8 +1076,16 @@ function assertRecordSafe(record: CandidateViewRecord, platform: NodeJS.Platform
|
|
|
1050
1076
|
if (!isWithin(record.parent, root) || !existsSync(root)) throw new CandidateViewError("candidate view is missing or moved");
|
|
1051
1077
|
const rootStat = lstatSync(root);
|
|
1052
1078
|
if (!rootStat.isDirectory() || rootStat.isSymbolicLink() || realpathSync(root) !== root) throw new CandidateViewError("candidate view root is unsafe");
|
|
1079
|
+
let expectedChildren: ReadonlyMap<string, ReadonlySet<string>> | undefined;
|
|
1053
1080
|
for (const directory of candidateDirectories(root, record.entries)) {
|
|
1054
|
-
|
|
1081
|
+
let metadata = lstatSync(directory);
|
|
1082
|
+
if (repairFileProviderDirectoryMode(directory, metadata, platform)) {
|
|
1083
|
+
expectedChildren ??= expectedDirectoryChildren(root, record.entries);
|
|
1084
|
+
const expected = expectedChildren.get(directory) ?? new Set<string>();
|
|
1085
|
+
const actual = readdirSync(directory);
|
|
1086
|
+
if (actual.length !== expected.size || actual.some((name) => !expected.has(name))) throw new CandidateViewError("candidate view directory contains unexpected entries");
|
|
1087
|
+
metadata = lstatSync(directory);
|
|
1088
|
+
}
|
|
1055
1089
|
if (!metadata.isDirectory() || metadata.isSymbolicLink() || (metadata.mode & 0o222) !== 0) throw new CandidateViewError("candidate view directory is unsafe or writable");
|
|
1056
1090
|
}
|
|
1057
1091
|
const gitFile = lstatSync(join(root, ".git"));
|
package/lib/review-host-relay.ts
CHANGED
|
@@ -630,8 +630,15 @@ function snapshotReviewHostRelayRequest(request: ReviewHostRelayRequest): Review
|
|
|
630
630
|
// are TypeErrors — a programmer mistake, never a typed relay refusal — and
|
|
631
631
|
// are checked before the business-level reviewer selection below.
|
|
632
632
|
assertTokens("capture", request.captureArgumentTokens);
|
|
633
|
-
|
|
634
|
-
|
|
633
|
+
// A caller-supplied executable path is input, validated like every other
|
|
634
|
+
// input before anything else. Only the environment-dependent resolution of
|
|
635
|
+
// an unspecified executable is deferred below: the executable supply is
|
|
636
|
+
// environment state (a missing pinned binary, an invalid dev override, a
|
|
637
|
+
// transient filesystem error under load), and letting its untyped errors
|
|
638
|
+
// escape ahead of the config check turned the typed configuration refusal
|
|
639
|
+
// into an environment-dependent unknown failure (gentle-shell#2256).
|
|
640
|
+
const suppliedExecutable = request.gentleAiExecutable;
|
|
641
|
+
if (suppliedExecutable !== undefined && !isAbsolute(suppliedExecutable)) throw new TypeError("Pi host relay requires an absolute gentle-ai executable path");
|
|
635
642
|
// Caller-owned reviewer selection is validated before any process launches:
|
|
636
643
|
// a broken configuration is a typed refusal, never a mid-review transport
|
|
637
644
|
// failure (gentle-pi#311 P2).
|
|
@@ -640,6 +647,8 @@ function snapshotReviewHostRelayRequest(request: ReviewHostRelayRequest): Review
|
|
|
640
647
|
// slot without a provider-owned submission is a typed contract mismatch,
|
|
641
648
|
// never a synthesized invocation.
|
|
642
649
|
resolveReviewHostRelaySubmission(request.submission);
|
|
650
|
+
const gentleAiExecutable = suppliedExecutable ?? resolveGentleAiBinary();
|
|
651
|
+
if (!isAbsolute(gentleAiExecutable)) throw new TypeError("Pi host relay requires an absolute gentle-ai executable path");
|
|
643
652
|
const environment = Object.freeze({ ...(request.environment ?? process.env) }) as NodeJS.ProcessEnv;
|
|
644
653
|
const submission = request.submission === undefined ? undefined : Object.freeze({
|
|
645
654
|
operationToken: request.submission.operationToken,
|
|
@@ -6,6 +6,9 @@ export const REVIEW_REMINDER_RECEIPT = "gentle-pi.review-reminder-receipt/v1";
|
|
|
6
6
|
export interface ReceiptSession {
|
|
7
7
|
getSessionId(): string;
|
|
8
8
|
getBranch(): readonly { type: string; customType?: string; data?: unknown }[];
|
|
9
|
+
/** Append-only identity for memoizing repeated scans; hosts without it stay uncached. */
|
|
10
|
+
getLeafId?(): string | null;
|
|
11
|
+
getEntryCount?(): number;
|
|
9
12
|
}
|
|
10
13
|
interface ReceiptHost { appendEntry(type: string, data: unknown): void }
|
|
11
14
|
export interface MutationEvidence {
|
|
@@ -59,7 +62,14 @@ function receipts(session: ReceiptSession, root: string): Receipt[] {
|
|
|
59
62
|
|
|
60
63
|
// The first receipt per mutation id, in branch order, plus the index of the
|
|
61
64
|
// last consumed one (-1 when nothing was consumed).
|
|
62
|
-
|
|
65
|
+
type Ledger = { mutations: Mutation[]; consumed: number };
|
|
66
|
+
// Entries are append-only, so an unchanged (leafId, entryCount) identity means
|
|
67
|
+
// the scan result is unchanged: each (session, root) ledger is memoized until
|
|
68
|
+
// the next append (which moves the leaf) invalidates it. Hosts without leaf
|
|
69
|
+
// identity stay uncached, the same way sessionStats must.
|
|
70
|
+
const ledgerMemo = new WeakMap<object, Map<string, Ledger>>();
|
|
71
|
+
|
|
72
|
+
function scanLedger(session: ReceiptSession, root: string): Ledger {
|
|
63
73
|
const mutations: Mutation[] = [];
|
|
64
74
|
let consumed = -1;
|
|
65
75
|
for (const receipt of receipts(session, root)) {
|
|
@@ -74,6 +84,19 @@ function mutationLedger(session: ReceiptSession, root: string): { mutations: Mut
|
|
|
74
84
|
return { mutations, consumed };
|
|
75
85
|
}
|
|
76
86
|
|
|
87
|
+
function mutationLedger(session: ReceiptSession, root: string): Ledger {
|
|
88
|
+
const leafId = typeof session.getLeafId === "function" ? session.getLeafId() : null;
|
|
89
|
+
if (leafId === null) return scanLedger(session, root);
|
|
90
|
+
const key = JSON.stringify([session.getSessionId(), leafId, session.getEntryCount?.(), root] as const);
|
|
91
|
+
let byKey = ledgerMemo.get(session);
|
|
92
|
+
const cached = byKey?.get(key);
|
|
93
|
+
if (cached) return cached;
|
|
94
|
+
const ledger = scanLedger(session, root);
|
|
95
|
+
if (!byKey) ledgerMemo.set(session, (byKey = new Map()));
|
|
96
|
+
byKey.set(key, ledger);
|
|
97
|
+
return ledger;
|
|
98
|
+
}
|
|
99
|
+
|
|
77
100
|
export function pendingReviewMutation(session: ReceiptSession, root: string, captured?: string): string | undefined {
|
|
78
101
|
const { mutations, consumed } = mutationLedger(session, root);
|
|
79
102
|
if (captured !== undefined) return mutations.findIndex((mutation) => mutation.id === captured) > consumed ? captured : undefined;
|
|
@@ -267,8 +267,9 @@ export type VerificationTier = (typeof VERIFICATION_TIER)[keyof typeof VERIFICAT
|
|
|
267
267
|
// ---------------------------------------------------------------------------
|
|
268
268
|
// Agent-raised risk (gentle-shell#1494). The agent that made the change may
|
|
269
269
|
// raise its risk to high by citing one item of the high-risk list in the
|
|
270
|
-
// always-on Task Size section; it can never lower a tier.
|
|
271
|
-
// the
|
|
270
|
+
// always-on Task Size section; it can never lower a tier. That escalation is
|
|
271
|
+
// the only way a plan gets an independent verifier: a native high or a failed
|
|
272
|
+
// assessment informs but never adds one by itself.
|
|
272
273
|
// ---------------------------------------------------------------------------
|
|
273
274
|
|
|
274
275
|
export const HIGH_RISK_ITEMS = Object.freeze({
|
|
@@ -305,10 +306,17 @@ export function decodeAgentRiskEscalation(value: unknown): AgentRiskEscalation {
|
|
|
305
306
|
return Object.freeze({ item: item as HighRiskItem, reason });
|
|
306
307
|
}
|
|
307
308
|
|
|
308
|
-
/**
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
309
|
+
/**
|
|
310
|
+
* The risk tier the verification plan follows. The independent verifier is
|
|
311
|
+
* for concrete occasions only: the plan is high only when the agent escalated
|
|
312
|
+
* a named Task Size high-risk item. Without an escalation a native passive
|
|
313
|
+
* stays passive, and every other native tier -- medium, high, or unassessable
|
|
314
|
+
* (a failed or unavailable assessment) -- is medium: it informs, but adds no
|
|
315
|
+
* verifier by itself. The native tier stays visible to callers as `nativeRisk`.
|
|
316
|
+
*/
|
|
317
|
+
export function verificationRisk(nativeRisk: VerificationTier, escalation: AgentRiskEscalation | undefined): VerificationTier {
|
|
318
|
+
if (escalation !== undefined) return VERIFICATION_TIER.HIGH;
|
|
319
|
+
return nativeRisk === VERIFICATION_TIER.PASSIVE ? VERIFICATION_TIER.PASSIVE : VERIFICATION_TIER.MEDIUM;
|
|
312
320
|
}
|
|
313
321
|
|
|
314
322
|
export const RDD_LINE = {
|
|
@@ -342,7 +350,9 @@ export type NativeReviewOutcome = (typeof NATIVE_REVIEW_OUTCOME)[keyof typeof NA
|
|
|
342
350
|
|
|
343
351
|
export interface VerificationPlanInput {
|
|
344
352
|
readonly rddLine: RddLine;
|
|
353
|
+
/** The plan risk from `verificationRisk`, not the raw native tier. */
|
|
345
354
|
readonly risk: VerificationTier;
|
|
355
|
+
/** Reported context only: the writer's model or effort never changes the plan. */
|
|
346
356
|
readonly writerProfile: WriterProfile;
|
|
347
357
|
/**
|
|
348
358
|
* Native review outcome for the current candidate. Only consulted when
|
|
@@ -366,7 +376,7 @@ export interface VerificationPlan {
|
|
|
366
376
|
* naming which branch fired, up to and including the word "and"; the
|
|
367
377
|
* tier-specific clause is appended after it.
|
|
368
378
|
*/
|
|
369
|
-
function riskGatedPlan(gatePrefix: string, risk: VerificationTier
|
|
379
|
+
function riskGatedPlan(gatePrefix: string, risk: VerificationTier): VerificationPlan {
|
|
370
380
|
if (risk === VERIFICATION_TIER.PASSIVE) {
|
|
371
381
|
return Object.freeze({
|
|
372
382
|
writerSelfVerification: false,
|
|
@@ -376,26 +386,22 @@ function riskGatedPlan(gatePrefix: string, risk: VerificationTier, writerProfile
|
|
|
376
386
|
});
|
|
377
387
|
}
|
|
378
388
|
|
|
379
|
-
if (risk === VERIFICATION_TIER.
|
|
380
|
-
const smallModelBias = writerProfile === WRITER_PROFILE.SMALL;
|
|
389
|
+
if (risk === VERIFICATION_TIER.HIGH) {
|
|
381
390
|
return Object.freeze({
|
|
382
391
|
writerSelfVerification: true,
|
|
383
392
|
structuralReadbackOnly: false,
|
|
384
|
-
independentVerifier:
|
|
385
|
-
reason:
|
|
386
|
-
? `${gatePrefix} the native assessment reports medium risk, but the small-model bias raises it one tier to high for verification purposes: the writer self-verifies and a separate independent verifier also runs.`
|
|
387
|
-
: `${gatePrefix} the native assessment reports medium risk with a large writer profile: the writer's self-verification stands and no separate verifier is required.`,
|
|
393
|
+
independentVerifier: true,
|
|
394
|
+
reason: `${gatePrefix} an agent escalation names a Task Size high-risk item: the writer self-verifies and a separate independent verifier runs.`,
|
|
388
395
|
});
|
|
389
396
|
}
|
|
390
397
|
|
|
391
|
-
//
|
|
398
|
+
// medium, or an unassessable tier that did not go through verificationRisk:
|
|
399
|
+
// both inform only.
|
|
392
400
|
return Object.freeze({
|
|
393
401
|
writerSelfVerification: true,
|
|
394
402
|
structuralReadbackOnly: false,
|
|
395
|
-
independentVerifier:
|
|
396
|
-
reason: risk
|
|
397
|
-
? `${gatePrefix} the native risk assessment could not be produced, so the candidate is treated as high risk: the writer self-verifies and a separate independent verifier always runs.`
|
|
398
|
-
: `${gatePrefix} the native assessment reports high risk: the writer self-verifies and a separate independent verifier always runs.`,
|
|
403
|
+
independentVerifier: false,
|
|
404
|
+
reason: `${gatePrefix} no Task Size high-risk item was escalated: the writer's self-verification stands and no separate verifier runs, whatever the native tier or the model that wrote the change.`,
|
|
399
405
|
});
|
|
400
406
|
}
|
|
401
407
|
|
|
@@ -416,24 +422,23 @@ function nonClosedOutcomeClause(outcome: NativeReviewOutcome): string {
|
|
|
416
422
|
|
|
417
423
|
/**
|
|
418
424
|
* Computes who verifies a delegated writer's change. Verification follows the
|
|
419
|
-
*
|
|
425
|
+
* plan risk tier whatever the RDD line and native review outcome
|
|
420
426
|
* (verify-always-rdd-high S1, superseding the gentle-pi#662/#668 on-path): the
|
|
421
427
|
* native review is an additional outside view and never replaces or skips the
|
|
422
428
|
* tier's verification. `nativeReviewOutcome` only shapes the reason text.
|
|
423
429
|
*
|
|
430
|
+
* `risk` is the plan risk from `verificationRisk`; `writerProfile` is never
|
|
431
|
+
* consulted, so the model or effort that wrote the change never adds a
|
|
432
|
+
* verifier.
|
|
433
|
+
*
|
|
424
434
|
* - `passive`: structural readback by the parent only; no verifier, no tests.
|
|
425
|
-
* - `medium
|
|
426
|
-
*
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
* function (see `resolveWriterProfile`), so an unrecognized profile never
|
|
430
|
-
* lowers a tier below what a known small model would get.
|
|
431
|
-
* - `high` or `unassessable` (a failed or unrecognized native assessment is
|
|
432
|
-
* always treated as high): the writer self-verifies and an independent
|
|
433
|
-
* verifier always runs.
|
|
435
|
+
* - `medium` (also an `unassessable` tier passed directly): the writer
|
|
436
|
+
* self-verifies; no separate verifier.
|
|
437
|
+
* - `high` (only through an agent escalation naming a Task Size high-risk
|
|
438
|
+
* item): the writer self-verifies and an independent verifier runs.
|
|
434
439
|
*/
|
|
435
440
|
export function verificationPlan(input: VerificationPlanInput): VerificationPlan {
|
|
436
|
-
const { rddLine, risk
|
|
441
|
+
const { rddLine, risk } = input;
|
|
437
442
|
|
|
438
443
|
if (rddLine === RDD_LINE.ON) {
|
|
439
444
|
const outcome = input.nativeReviewOutcome ?? NATIVE_REVIEW_OUTCOME.UNKNOWN;
|
|
@@ -443,14 +448,13 @@ export function verificationPlan(input: VerificationPlanInput): VerificationPlan
|
|
|
443
448
|
return riskGatedPlan(
|
|
444
449
|
`receipt-driven development is on and ${clause}; the native review is an additional outside view that never replaces verification, so the risk-gated path applies exactly as receipt-driven development off, and`,
|
|
445
450
|
risk,
|
|
446
|
-
writerProfile,
|
|
447
451
|
);
|
|
448
452
|
}
|
|
449
453
|
|
|
450
454
|
// rddLine is "off" or "unknown" here -- both gate on the native risk
|
|
451
455
|
// assessment identically, so an unknown RDD line never lowers a tier, and
|
|
452
456
|
// neither line ever consults nativeReviewOutcome.
|
|
453
|
-
return riskGatedPlan(`receipt-driven development is ${rddLine} and`, risk
|
|
457
|
+
return riskGatedPlan(`receipt-driven development is ${rddLine} and`, risk);
|
|
454
458
|
}
|
|
455
459
|
|
|
456
460
|
// ---------------------------------------------------------------------------
|
|
@@ -475,15 +479,16 @@ export interface WriterModelProfileLike {
|
|
|
475
479
|
const SMALL_MODEL_ID_TOKEN = /(?:^|[-_./:\s])mini(?:$|[-_./:\s])/i;
|
|
476
480
|
|
|
477
481
|
/**
|
|
478
|
-
* A writer profile counts as a "small model"
|
|
482
|
+
* A writer profile counts as a "small model" (reported by ASSESS as
|
|
483
|
+
* `writerProfile`; it never changes the verification plan) when:
|
|
479
484
|
* - its resolved effort is `low`, or
|
|
480
485
|
* - its resolved model id carries `mini` as a whole token (see
|
|
481
486
|
* `SMALL_MODEL_ID_TOKEN`), or
|
|
482
487
|
* - it carries NO signal at all -- no profile object, or a profile with
|
|
483
488
|
* neither a model id nor a resolved effort. An unknown or omitted writer
|
|
484
|
-
* profile fails closed to small rather than defaulting to large
|
|
485
|
-
*
|
|
486
|
-
*
|
|
489
|
+
* profile fails closed to small rather than defaulting to large. The
|
|
490
|
+
* result is reported in the assessment only: the verification plan never
|
|
491
|
+
* reads the writer profile, so it cannot add or remove a verifier.
|
|
487
492
|
*
|
|
488
493
|
* A profile that carries a model id or an effort, just not a matching one
|
|
489
494
|
* (for example `{model: {id: "claude-sonnet-5"}, thinking: "high"}`), is
|
|
@@ -501,9 +506,9 @@ export function isSmallWriterProfile(profile: WriterModelProfileLike | undefined
|
|
|
501
506
|
}
|
|
502
507
|
|
|
503
508
|
/**
|
|
504
|
-
* Maps a resolved writer profile to the `writerProfile
|
|
505
|
-
*
|
|
506
|
-
* `
|
|
509
|
+
* Maps a resolved writer profile to the reported `writerProfile`. An unknown
|
|
510
|
+
* or omitted profile resolves to `"small"`, exactly like
|
|
511
|
+
* `isSmallWriterProfile` (gentle-pi#662).
|
|
507
512
|
*/
|
|
508
513
|
export function resolveWriterProfile(profile: WriterModelProfileLike | undefined): WriterProfile {
|
|
509
514
|
return isSmallWriterProfile(profile) ? WRITER_PROFILE.SMALL : WRITER_PROFILE.LARGE;
|
|
@@ -15,6 +15,7 @@ export interface GitCommandOptions {
|
|
|
15
15
|
timeout: number;
|
|
16
16
|
maxBuffer: number;
|
|
17
17
|
windowsHide?: boolean;
|
|
18
|
+
stdio?: ["ignore", "pipe", "pipe"];
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export type GitAsyncRunner = (command: string, args: readonly string[], options: GitCommandOptions) => Promise<{ stdout: string }>;
|
|
@@ -133,7 +134,8 @@ export function resolveCanonicalGitRepositoryIdentitySync(
|
|
|
133
134
|
run: GitSyncRunner = execFileSync as unknown as GitSyncRunner,
|
|
134
135
|
): string | undefined {
|
|
135
136
|
try {
|
|
136
|
-
|
|
137
|
+
// execFileSync otherwise echoes stderr on failure, even when caught below.
|
|
138
|
+
const output = run("git", ["-C", cwd, "rev-parse", "--git-common-dir"], { encoding: "utf8", timeout: 5_000, maxBuffer: 64 * 1024, windowsHide: true, stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
137
139
|
if (output.length === 0 || output.includes("\n") || output.includes("\r")) return undefined;
|
|
138
140
|
return canonicalRepositoryIdentity(realpathSync(resolve(cwd, output)));
|
|
139
141
|
} catch {
|