gentle-pi 4.2.0 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/README.md +336 -137
  2. package/assets/agents/gentle-ai-explore.md +1 -0
  3. package/assets/agents/gentle-ai-verify.md +6 -4
  4. package/assets/agents/gentle-ai-worker.md +7 -1
  5. package/assets/orchestrator-delegation-reference.md +163 -0
  6. package/assets/orchestrator-delegation.md +25 -176
  7. package/assets/orchestrator-tracking.md +1 -1
  8. package/assets/orchestrator-verification.md +14 -8
  9. package/assets/orchestrator-writer.md +5 -1
  10. package/assets/orchestrator.md +9 -9
  11. package/bin/gentle-shell.mjs +170 -22
  12. package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
  13. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/README.md +7 -0
  14. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/manifest.json +11 -2
  15. package/contracts/review-provider-contract-mirror/v1.4.0/bundle/routing/pi.md +92 -0
  16. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-capabilities.baseline.json +8 -1
  17. package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-roles.baseline.json +1 -1
  18. package/docs/assets/diagrams/agent-orchestration.svg +54 -15
  19. package/docs/assets/diagrams/gentleman-workflow.svg +69 -11
  20. package/docs/assets/diagrams/measured-routing.svg +49 -0
  21. package/docs/assets/diagrams/native-review.svg +52 -12
  22. package/docs/assets/diagrams/odd-workflow.svg +128 -66
  23. package/docs/assets/diagrams/permission-modes.svg +51 -0
  24. package/docs/assets/features/audio-notifications.png +0 -0
  25. package/docs/assets/features/command-palette.png +0 -0
  26. package/docs/bundled-install.md +33 -3
  27. package/docs/delegated-verification.md +10 -8
  28. package/docs/gentle-agents-activity.md +11 -5
  29. package/docs/gentle-shell-uninstall.md +8 -1
  30. package/docs/gentle-shell.md +5 -4
  31. package/docs/install-wizard.md +8 -1
  32. package/docs/permission-modes.md +1 -1
  33. package/docs/readme-reference.md +107 -19
  34. package/docs/yolo-mode.md +21 -8
  35. package/extensions/codegraph-tools.ts +68 -6
  36. package/extensions/gentle-agents.ts +99 -12
  37. package/extensions/gentle-ai.ts +284 -52
  38. package/extensions/gentle-herdr-activity.ts +2 -3
  39. package/extensions/gentle-shell.ts +27 -12
  40. package/extensions/gentle-todo.ts +43 -9
  41. package/extensions/quiet-tools.ts +1 -1
  42. package/extensions/session-scratch.ts +365 -0
  43. package/extensions/startup-banner.ts +2 -3
  44. package/lib/agent-home.ts +8 -3
  45. package/lib/agents-protocol.ts +3 -0
  46. package/lib/agents-runner.ts +55 -6
  47. package/lib/agents-view.ts +3 -1
  48. package/lib/bounded-writer-admission.ts +51 -0
  49. package/lib/command-palette-catalog.ts +0 -4
  50. package/lib/commit-slice-signal.ts +100 -0
  51. package/lib/context-backstop.ts +84 -0
  52. package/lib/delivered-test-lint.ts +80 -0
  53. package/lib/destructive-command-guard.ts +120 -9
  54. package/lib/gauge-theme.ts +146 -0
  55. package/lib/gentle-ai-binary.ts +28 -7
  56. package/lib/gentle-shell-bundled.ts +39 -3
  57. package/lib/gentle-shell-import-pi.ts +233 -0
  58. package/lib/gentle-shell-launcher.ts +145 -11
  59. package/lib/gentle-shell-uninstall.ts +18 -1
  60. package/lib/herdr-activity.ts +24 -0
  61. package/lib/native-review-cli.ts +9 -1
  62. package/lib/orchestrator-discovery.ts +5 -1
  63. package/lib/orchestrator-presence.ts +9 -0
  64. package/lib/orchestrator-scope.ts +21 -3
  65. package/lib/provider-contract-bundle.ts +99 -53
  66. package/lib/review-candidate-view-owner.ts +29 -19
  67. package/lib/review-candidate-view.ts +36 -2
  68. package/lib/review-risk-assessment.ts +43 -38
  69. package/lib/review-session-standing-permission.ts +3 -1
  70. package/lib/session-profile-freeze.ts +30 -1
  71. package/lib/session-scratch.ts +474 -0
  72. package/lib/shell-bar.ts +25 -25
  73. package/lib/shell-changes-view.ts +80 -13
  74. package/lib/shell-gauge.ts +49 -6
  75. package/lib/shell-todo.ts +23 -4
  76. package/lib/shell-usage-view.ts +5 -1
  77. package/lib/shell-usage.ts +7 -7
  78. package/lib/verifier-admission.ts +29 -0
  79. package/lib/yolo-temp-scratch.ts +69 -0
  80. package/package.json +1 -2
  81. package/runtime/gentle-ai-binary.mjs +28 -7
  82. package/runtime/gentle-shell-bundled.mjs +38 -2
  83. package/runtime/gentle-shell-import-pi.mjs +234 -0
  84. package/runtime/gentle-shell-launcher.mjs +143 -9
  85. package/runtime/gentle-shell-uninstall.mjs +18 -1
  86. package/runtime/native-review-cli.mjs +8 -0
  87. package/runtime/review-risk-assessment.mjs +43 -38
  88. package/scripts/build-runtime-modules.mjs +1 -0
  89. package/scripts/bundled-engram-migration.mjs +125 -0
  90. package/scripts/gentle-ai-installer.mjs +10 -10
  91. package/scripts/installer-server.mjs +14 -4
  92. package/scripts/main-channel.mjs +3 -2
  93. package/scripts/run-test-suite.mjs +5 -0
  94. package/scripts/verify-package-files.mjs +19 -13
  95. package/skills/gentle-ai/SKILL.md +3 -3
  96. package/skills/work-unit-commits/SKILL.md +5 -3
  97. package/tests/agent-risk-escalation.test.ts +37 -10
  98. package/tests/agents-runner.test.ts +260 -3
  99. package/tests/autonomous-guard.test.ts +122 -0
  100. package/tests/bounded-writer-admission.test.ts +11 -3
  101. package/tests/card-content-policy.test.ts +19 -9
  102. package/tests/child-safety.test.ts +39 -0
  103. package/tests/codegraph-tools.test.ts +123 -2
  104. package/tests/command-palette.test.ts +14 -4
  105. package/tests/commit-slice-signal.test.ts +162 -0
  106. package/tests/context-backstop.test.ts +159 -0
  107. package/tests/delegate-for-reason-contract.test.ts +91 -24
  108. package/tests/delivered-test-lint.test.ts +105 -0
  109. package/tests/destructive-command-guard.test.ts +85 -1
  110. package/tests/engram-safety.test.ts +1 -1
  111. package/tests/fixtures/primary-session-prompt/gentleman.golden.md +143 -0
  112. package/tests/fixtures/provider-contract-bundle/v1.4.0/README.md +29 -0
  113. package/tests/fixtures/provider-contract-bundle/v1.4.0/manifest.json +83 -0
  114. package/tests/fixtures/provider-contract-bundle/v1.4.0/orchestration/pi.md +70 -0
  115. package/tests/fixtures/provider-contract-bundle/v1.4.0/routing/pi.md +92 -0
  116. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/lens.schema.json +16 -0
  117. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/refuter.schema.json +1 -0
  118. package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/targeted-validator.schema.json +1 -0
  119. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/lens.json +1 -0
  120. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/refuter.json +1 -0
  121. package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/targeted-validator.json +1 -0
  122. package/tests/gauge-theme.test.ts +120 -0
  123. package/tests/gentle-agents.test.ts +282 -3
  124. package/tests/gentle-ai-binary.test.ts +1 -1
  125. package/tests/gentle-ai-child-guards.test.ts +61 -0
  126. package/tests/gentle-ai-doctor-herdr.test.ts +94 -0
  127. package/tests/gentle-ai-installer.test.ts +51 -51
  128. package/tests/gentle-shell-bin.test.ts +163 -0
  129. package/tests/gentle-shell-bundled.test.ts +169 -6
  130. package/tests/gentle-shell-import-pi.test.ts +262 -0
  131. package/tests/gentle-shell-launcher.test.ts +79 -0
  132. package/tests/gentle-shell-uninstall.test.ts +30 -0
  133. package/tests/gentle-shell.test.ts +42 -9
  134. package/tests/gentle-todo.test.ts +179 -0
  135. package/tests/headless-delivery-question.test.ts +58 -0
  136. package/tests/herdr-activity.test.ts +34 -1
  137. package/tests/installer-server.test.ts +10 -2
  138. package/tests/installer-windows-bootstrap.test.ts +33 -4
  139. package/tests/native-review-capability-contract.test.ts +7 -1
  140. package/tests/native-review-parity.test.ts +22 -1
  141. package/tests/no-self-pause-contract.test.ts +5 -2
  142. package/tests/odd-integration.test.ts +5 -1
  143. package/tests/odd-routing-canonical-ratchet.test.ts +127 -162
  144. package/tests/odd-routing-contract.test.ts +115 -30
  145. package/tests/orchestrator-budget.test.ts +19 -8
  146. package/tests/orchestrator-discovery.test.ts +29 -0
  147. package/tests/orchestrator-rdd-ownership.test.ts +3 -1
  148. package/tests/orchestrator-scope.test.ts +66 -0
  149. package/tests/orchestrator-status-publication-contract.test.ts +12 -8
  150. package/tests/package-manifest.test.ts +24 -20
  151. package/tests/pi-update-launcher.test.ts +21 -2
  152. package/tests/primary-session-prompt.test.ts +101 -0
  153. package/tests/provider-contract-bundle.test.ts +118 -0
  154. package/tests/provider-contract-mirror.test.ts +24 -0
  155. package/tests/quiet-tool-rendering.test.ts +23 -0
  156. package/tests/rdd-aware-verification-contract.test.ts +29 -11
  157. package/tests/rdd-status-line.test.ts +79 -0
  158. package/tests/review-agent-end-preflight.test.ts +6 -0
  159. package/tests/review-candidate-view.test.ts +49 -0
  160. package/tests/review-contract-prompt.test.ts +5 -2
  161. package/tests/review-controller-native-routing.test.ts +14 -1
  162. package/tests/review-controller.test.ts +1 -1
  163. package/tests/review-host-relay-routing.test.ts +59 -2
  164. package/tests/review-loadout-rdd-off-sdk.test.ts +237 -0
  165. package/tests/review-owner-identity.test.ts +132 -0
  166. package/tests/review-risk-assessment.test.ts +163 -76
  167. package/tests/review-session-standing-permission-controller.test.ts +14 -1
  168. package/tests/review-session-standing-permission.test.ts +27 -1
  169. package/tests/run-test-suite.test.ts +15 -0
  170. package/tests/runtime-harness.mjs +16 -6
  171. package/tests/session-changes-shell.test.ts +1 -1
  172. package/tests/session-profile-freeze.test.ts +23 -0
  173. package/tests/session-scratch.test.ts +945 -0
  174. package/tests/session-subject-routing-contract.test.ts +2 -1
  175. package/tests/shell-bar.test.ts +7 -0
  176. package/tests/shell-changes-view.test.ts +137 -1
  177. package/tests/shell-gauge.test.ts +42 -0
  178. package/tests/small-task-inline-contract.test.ts +37 -0
  179. package/tests/standalone-config-home.test.ts +324 -0
  180. package/tests/startup-banner.test.ts +5 -4
  181. package/tests/startup-home-repo.test.ts +34 -0
  182. package/tests/support/orchestrator-modules.ts +1 -0
  183. package/tests/task-size-routing-contract.test.ts +69 -9
  184. package/tests/test-scratch-contract.test.ts +102 -0
  185. package/tests/test-strength-contract.test.ts +36 -1
  186. package/tests/tool-batching-contract.test.ts +64 -0
  187. package/tests/verification-proportionality.test.ts +46 -0
  188. package/tests/verifier-admission.test.ts +137 -0
  189. package/tests/verify-agent-contract.test.ts +8 -8
  190. package/tests/windows-hidden-processes.test.ts +5 -0
  191. package/tests/writer-edit-surface-scope.test.ts +3 -0
  192. package/tests/writer-feature-document-admission.test.ts +156 -0
  193. package/tests/yolo-mode.test.ts +18 -0
  194. package/tests/yolo-temp-scratch.test.ts +71 -0
  195. package/scripts/mirror-odd-routing.mjs +0 -242
  196. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/orchestration/pi.md +0 -0
  197. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/lens.schema.json +0 -0
  198. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/refuter.schema.json +0 -0
  199. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/targeted-validator.schema.json +0 -0
  200. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/lens.json +0 -0
  201. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/refuter.json +0 -0
  202. /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/targeted-validator.json +0 -0
@@ -268,8 +268,9 @@ export const VERIFICATION_TIER = {
268
268
  // ---------------------------------------------------------------------------
269
269
  // Agent-raised risk (gentle-shell#1494). The agent that made the change may
270
270
  // raise its risk to high by citing one item of the high-risk list in the
271
- // always-on Task Size section; it can never lower a tier. Lowering stays with
272
- // the deterministic native assessment.
271
+ // always-on Task Size section; it can never lower a tier. That escalation is
272
+ // the only way a plan gets an independent verifier: a native high or a failed
273
+ // assessment informs but never adds one by itself.
273
274
  // ---------------------------------------------------------------------------
274
275
 
275
276
  export const HIGH_RISK_ITEMS = Object.freeze({
@@ -306,10 +307,17 @@ export function decodeAgentRiskEscalation(value ) {
306
307
  return Object.freeze({ item: item , reason });
307
308
  }
308
309
 
309
- /** Raises passive or medium to high when the agent escalated; never lowers. */
310
- export function escalatedRisk(risk , escalation ) {
311
- if (escalation === undefined) return risk;
312
- return risk === VERIFICATION_TIER.PASSIVE || risk === VERIFICATION_TIER.MEDIUM ? VERIFICATION_TIER.HIGH : risk;
310
+ /**
311
+ * The risk tier the verification plan follows. The independent verifier is
312
+ * for concrete occasions only: the plan is high only when the agent escalated
313
+ * a named Task Size high-risk item. Without an escalation a native passive
314
+ * stays passive, and every other native tier -- medium, high, or unassessable
315
+ * (a failed or unavailable assessment) -- is medium: it informs, but adds no
316
+ * verifier by itself. The native tier stays visible to callers as `nativeRisk`.
317
+ */
318
+ export function verificationRisk(nativeRisk , escalation ) {
319
+ if (escalation !== undefined) return VERIFICATION_TIER.HIGH;
320
+ return nativeRisk === VERIFICATION_TIER.PASSIVE ? VERIFICATION_TIER.PASSIVE : VERIFICATION_TIER.MEDIUM;
313
321
  }
314
322
 
315
323
  export const RDD_LINE = {
@@ -357,6 +365,8 @@ export const NATIVE_REVIEW_OUTCOME = {
357
365
 
358
366
 
359
367
 
368
+
369
+
360
370
 
361
371
 
362
372
 
@@ -367,7 +377,7 @@ export const NATIVE_REVIEW_OUTCOME = {
367
377
  * naming which branch fired, up to and including the word "and"; the
368
378
  * tier-specific clause is appended after it.
369
379
  */
370
- function riskGatedPlan(gatePrefix , risk , writerProfile ) {
380
+ function riskGatedPlan(gatePrefix , risk ) {
371
381
  if (risk === VERIFICATION_TIER.PASSIVE) {
372
382
  return Object.freeze({
373
383
  writerSelfVerification: false,
@@ -377,26 +387,22 @@ function riskGatedPlan(gatePrefix , risk , writerProfile
377
387
  });
378
388
  }
379
389
 
380
- if (risk === VERIFICATION_TIER.MEDIUM) {
381
- const smallModelBias = writerProfile === WRITER_PROFILE.SMALL;
390
+ if (risk === VERIFICATION_TIER.HIGH) {
382
391
  return Object.freeze({
383
392
  writerSelfVerification: true,
384
393
  structuralReadbackOnly: false,
385
- independentVerifier: smallModelBias,
386
- reason: smallModelBias
387
- ? `${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.`
388
- : `${gatePrefix} the native assessment reports medium risk with a large writer profile: the writer's self-verification stands and no separate verifier is required.`,
394
+ independentVerifier: true,
395
+ reason: `${gatePrefix} an agent escalation names a Task Size high-risk item: the writer self-verifies and a separate independent verifier runs.`,
389
396
  });
390
397
  }
391
398
 
392
- // high or unassessable.
399
+ // medium, or an unassessable tier that did not go through verificationRisk:
400
+ // both inform only.
393
401
  return Object.freeze({
394
402
  writerSelfVerification: true,
395
403
  structuralReadbackOnly: false,
396
- independentVerifier: true,
397
- reason: risk === VERIFICATION_TIER.UNASSESSABLE
398
- ? `${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.`
399
- : `${gatePrefix} the native assessment reports high risk: the writer self-verifies and a separate independent verifier always runs.`,
404
+ independentVerifier: false,
405
+ 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.`,
400
406
  });
401
407
  }
402
408
 
@@ -417,24 +423,23 @@ function nonClosedOutcomeClause(outcome ) {
417
423
 
418
424
  /**
419
425
  * Computes who verifies a delegated writer's change. Verification follows the
420
- * native risk tier whatever the RDD line and native review outcome
426
+ * plan risk tier whatever the RDD line and native review outcome
421
427
  * (verify-always-rdd-high S1, superseding the gentle-pi#662/#668 on-path): the
422
428
  * native review is an additional outside view and never replaces or skips the
423
429
  * tier's verification. `nativeReviewOutcome` only shapes the reason text.
424
430
  *
431
+ * `risk` is the plan risk from `verificationRisk`; `writerProfile` is never
432
+ * consulted, so the model or effort that wrote the change never adds a
433
+ * verifier.
434
+ *
425
435
  * - `passive`: structural readback by the parent only; no verifier, no tests.
426
- * - `medium`: the writer self-verifies; a separate independent verifier runs
427
- * only when `writerProfile` is `"small"` -- the small-model bias raises the
428
- * tier by one for verification purposes (medium -> high). An unknown or
429
- * omitted writer profile resolves to `"small"` before it reaches this
430
- * function (see `resolveWriterProfile`), so an unrecognized profile never
431
- * lowers a tier below what a known small model would get.
432
- * - `high` or `unassessable` (a failed or unrecognized native assessment is
433
- * always treated as high): the writer self-verifies and an independent
434
- * verifier always runs.
436
+ * - `medium` (also an `unassessable` tier passed directly): the writer
437
+ * self-verifies; no separate verifier.
438
+ * - `high` (only through an agent escalation naming a Task Size high-risk
439
+ * item): the writer self-verifies and an independent verifier runs.
435
440
  */
436
441
  export function verificationPlan(input ) {
437
- const { rddLine, risk, writerProfile } = input;
442
+ const { rddLine, risk } = input;
438
443
 
439
444
  if (rddLine === RDD_LINE.ON) {
440
445
  const outcome = input.nativeReviewOutcome ?? NATIVE_REVIEW_OUTCOME.UNKNOWN;
@@ -444,14 +449,13 @@ export function verificationPlan(input )
444
449
  return riskGatedPlan(
445
450
  `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`,
446
451
  risk,
447
- writerProfile,
448
452
  );
449
453
  }
450
454
 
451
455
  // rddLine is "off" or "unknown" here -- both gate on the native risk
452
456
  // assessment identically, so an unknown RDD line never lowers a tier, and
453
457
  // neither line ever consults nativeReviewOutcome.
454
- return riskGatedPlan(`receipt-driven development is ${rddLine} and`, risk, writerProfile);
458
+ return riskGatedPlan(`receipt-driven development is ${rddLine} and`, risk);
455
459
  }
456
460
 
457
461
  // ---------------------------------------------------------------------------
@@ -476,15 +480,16 @@ export function verificationPlan(input )
476
480
  const SMALL_MODEL_ID_TOKEN = /(?:^|[-_./:\s])mini(?:$|[-_./:\s])/i;
477
481
 
478
482
  /**
479
- * A writer profile counts as a "small model" for the medium-risk bias when:
483
+ * A writer profile counts as a "small model" (reported by ASSESS as
484
+ * `writerProfile`; it never changes the verification plan) when:
480
485
  * - its resolved effort is `low`, or
481
486
  * - its resolved model id carries `mini` as a whole token (see
482
487
  * `SMALL_MODEL_ID_TOKEN`), or
483
488
  * - it carries NO signal at all -- no profile object, or a profile with
484
489
  * neither a model id nor a resolved effort. An unknown or omitted writer
485
- * profile fails closed to small rather than defaulting to large, so a
486
- * caller that cannot resolve the writer's profile never gets a weaker
487
- * verification plan than a known small model would (gentle-pi#662).
490
+ * profile fails closed to small rather than defaulting to large. The
491
+ * result is reported in the assessment only: the verification plan never
492
+ * reads the writer profile, so it cannot add or remove a verifier.
488
493
  *
489
494
  * A profile that carries a model id or an effort, just not a matching one
490
495
  * (for example `{model: {id: "claude-sonnet-5"}, thinking: "high"}`), is
@@ -502,9 +507,9 @@ export function isSmallWriterProfile(profile
502
507
  }
503
508
 
504
509
  /**
505
- * Maps a resolved writer profile to the `writerProfile` input `verificationPlan`
506
- * expects. An unknown or omitted profile never lowers a tier: it resolves to
507
- * `"small"`, exactly like `isSmallWriterProfile` (gentle-pi#662).
510
+ * Maps a resolved writer profile to the reported `writerProfile`. An unknown
511
+ * or omitted profile resolves to `"small"`, exactly like
512
+ * `isSmallWriterProfile` (gentle-pi#662).
508
513
  */
509
514
  export function resolveWriterProfile(profile ) {
510
515
  return isSmallWriterProfile(profile) ? WRITER_PROFILE.SMALL : WRITER_PROFILE.LARGE;
@@ -22,6 +22,7 @@ const sources = [
22
22
  "gentle-shell-startup-flow",
23
23
  "gentle-shell-resume-hint",
24
24
  "gentle-shell-uninstall",
25
+ "gentle-shell-import-pi",
25
26
  ];
26
27
  const header = "// Generated by scripts/build-runtime-modules.mjs. Do not edit.\n";
27
28
 
@@ -0,0 +1,125 @@
1
+ // Explicit bundled-store recovery. The provider owns catalog validation and
2
+ // migration; Shell owns promotion and its core-bound provenance. Never called
3
+ // from ordinary startup or upgrade.
4
+ import { createHash, randomUUID } from "node:crypto";
5
+ import { closeSync, fsyncSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
6
+ import { dirname, join } from "node:path";
7
+ import { tmpdir } from "node:os";
8
+ import { spawnSync } from "node:child_process";
9
+ import { engramProbeAllowed, engramProvenancePath, parseEngramProvenance } from "../runtime/gentle-shell-bundled.mjs";
10
+
11
+ export const MIGRATION_USAGE = "Usage: gentle-shell memory migrate --confirm-quiescent\nStop every Engram client before running this command.\n";
12
+ export const MIGRATION_PENDING_NOTICE = "Engram memory disabled: an incomplete memory migration needs recovery. Keep every Engram client stopped, retain the original backup, and follow docs/bundled-install.md#recovering-an-interrupted-migration. Do not remove the migration marker or create a new store before recovery.";
13
+ export const migrationMarkerPath = (prefix) => join(prefix, "config", "engram-migration.json");
14
+ export function migrationPending(prefix) {
15
+ try { lstatSync(migrationMarkerPath(prefix)); return true; }
16
+ catch (error) { return error.code !== "ENOENT"; }
17
+ }
18
+ function regularText(path, limit) {
19
+ const info = lstatSync(path);
20
+ if (!info.isFile() || info.size > limit) throw new Error("unsafe migration metadata");
21
+ return readFileSync(path, "utf8");
22
+ }
23
+ const digest = (path) => createHash("sha256").update(readFileSync(path)).digest("hex");
24
+ // Durably record a file or a directory entry change. Windows cannot open a
25
+ // directory for fsync; there NTFS metadata journaling covers the renames.
26
+ function syncPath(path, directory = false) {
27
+ if (directory && process.platform === "win32") return;
28
+ const fd = openSync(path, "r");
29
+ try { fsyncSync(fd); } finally { closeSync(fd); }
30
+ }
31
+ function coreHelp(core, env) {
32
+ // Older cores route unknown commands through legacy startup. Inspect their
33
+ // config-free help in an isolated home, never try an unknown data command.
34
+ const scratch = mkdtempSync(join(tmpdir(), "gentle-engram-capability-"));
35
+ try {
36
+ const probe = spawnSync(core, ["--help"], {
37
+ cwd: scratch, env: { ...env, HOME: scratch, USERPROFILE: scratch, ENGRAM_DATA_DIR: join(scratch, "store") },
38
+ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 5000, maxBuffer: 65536, windowsHide: true,
39
+ });
40
+ return !probe.error && !probe.signal && probe.status === 0 && probe.stderr === "" ? probe.stdout.split("\n").map((line) => line.trim()) : [];
41
+ } finally { rmSync(scratch, { recursive: true, force: true }); }
42
+ }
43
+ function supportsMigration(core, env) {
44
+ return coreHelp(core, env).includes("migrate-copy --data-dir <absolute-source> --output-dir <new-absolute-destination> --confirm-quiescent --json");
45
+ }
46
+ export function supportsBundledSessionOwned(core, env) {
47
+ if (!core) return false;
48
+ const clean = Object.fromEntries(Object.entries(env).filter(([key]) => !key.startsWith("ENGRAM_") && key !== "GH_TOKEN" && key !== "GITHUB_TOKEN"));
49
+ try { return coreHelp(core, { ...clean, ENGRAM_NO_UPDATE_CHECK: "1" }).some((line) => /^serve --session-owned(?:\s|$)/.test(line)); }
50
+ catch { return false; }
51
+ }
52
+
53
+ export function migrateBundledEngram({ prefix, core, home, env }) {
54
+ let markerOwned = false;
55
+ let promotionStarted = false;
56
+ let output;
57
+ let backup;
58
+ try {
59
+ if (!core) throw new Error("the verified bundled core is unavailable; run gentle-shell setup first");
60
+ if (!engramProbeAllowed(env)) throw new Error("explicit Engram selections or a disabled startup policy must be resolved before bundled migration");
61
+ if (migrationPending(prefix)) throw new Error(MIGRATION_PENDING_NOTICE);
62
+ const source = join(realpathSync(home), ".engram");
63
+ if (!lstatSync(source).isDirectory() || realpathSync(source) !== source) throw new Error("the source must be a regular default store directory");
64
+ const provenancePath = engramProvenancePath(prefix);
65
+ const original = regularText(provenancePath, 4096);
66
+ const record = parseEngramProvenance(original);
67
+ const identity = regularText(join(source, ".instance-id"), 128).trim();
68
+ if (!record || record.dataDir !== source || record.instanceId !== identity) throw new Error("the store does not match Shell's recorded identity; it cannot be adopted");
69
+ const coreHash = digest(core);
70
+ const childEnv = Object.fromEntries(Object.entries(env).filter(([key]) => !key.startsWith("ENGRAM_") && key !== "GH_TOKEN" && key !== "GITHUB_TOKEN"));
71
+ childEnv.ENGRAM_NO_UPDATE_CHECK = "1";
72
+ if (!supportsMigration(core, childEnv)) throw new Error("the bundled core does not support migrate-copy; wait for a supporting published bundle. The source and provenance are unchanged");
73
+ const suffix = randomUUID();
74
+ output = `${source}.migrating-${suffix}`;
75
+ backup = `${source}.before-${suffix}`;
76
+ const marker = migrationMarkerPath(prefix);
77
+ mkdirSync(dirname(marker), { recursive: true, mode: 0o700 });
78
+ // A recorded refusal precedes copying/promotion. A killed launcher must not
79
+ // mistake the rename gap for a fresh install and provision an empty store.
80
+ writeFileSync(marker, `${JSON.stringify({ source, output, backup, originalProvenance: record })}\n`, { flag: "wx", mode: 0o600 });
81
+ markerOwned = true;
82
+ syncPath(marker);
83
+ syncPath(dirname(marker), true);
84
+ const result = spawnSync(core, ["migrate-copy", "--data-dir", source, "--output-dir", output, "--confirm-quiescent", "--json"], {
85
+ env: childEnv, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 120000, maxBuffer: 16384, windowsHide: true,
86
+ });
87
+ if (result.error || result.signal) throw new Error("the provider failed or timed out; no source promotion was attempted");
88
+ if (result.status !== 0) throw new Error(`the provider rejected migration (exit ${result.status}): ${result.stderr.trim().slice(0, 2048) || "no diagnostic"}`);
89
+ if (result.stderr !== "") throw new Error("unexpected provider diagnostic with success evidence");
90
+ let proof;
91
+ try { proof = JSON.parse(result.stdout); } catch { throw new Error("invalid provider migration evidence"); }
92
+ if (!proof || proof.migration_completed !== true || proof.source_dir !== source || proof.output_dir !== output || proof.instance_id !== identity) {
93
+ throw new Error("provider migration evidence does not match the requested store and copy");
94
+ }
95
+ if (!lstatSync(output).isDirectory() || realpathSync(output) !== output ||
96
+ regularText(join(output, ".instance-id"), 128).trim() !== identity ||
97
+ regularText(join(source, ".instance-id"), 128).trim() !== identity ||
98
+ regularText(provenancePath, 4096) !== original || digest(core) !== coreHash) {
99
+ throw new Error("store, provenance or bundled core changed during migration");
100
+ }
101
+ // The original is retained, not deleted. From this point any failure keeps
102
+ // the marker: recovery must reconcile both paths before another launch.
103
+ promotionStarted = true;
104
+ renameSync(source, backup);
105
+ renameSync(output, source);
106
+ syncPath(dirname(source), true);
107
+ const temporary = `${provenancePath}.${suffix}.tmp`;
108
+ writeFileSync(temporary, `${JSON.stringify({ schema: 1, dataDir: source, instanceId: identity, coreSha256: coreHash })}\n`, { flag: "wx", mode: 0o600 });
109
+ syncPath(temporary);
110
+ renameSync(temporary, provenancePath);
111
+ syncPath(dirname(provenancePath), true);
112
+ // Only a durable promotion and provenance may release the fail-closed marker.
113
+ rmSync(marker);
114
+ // The migration is complete; a failed final sync must not report failure.
115
+ try { syncPath(dirname(marker), true); } catch { /* Best effort. */ }
116
+ return { code: 0, stdout: `${JSON.stringify({ status: "migrated", instance_id: identity, backup_dir: backup })}\n`, stderr: "" };
117
+ } catch (error) {
118
+ if (markerOwned && !promotionStarted) {
119
+ try { rmSync(migrationMarkerPath(prefix)); } catch { /* Keep the fail-closed marker. */ }
120
+ }
121
+ const retained = output ? ` Any partial copy at ${output} is retained; do not use it.` : "";
122
+ const recovery = promotionStarted ? ` Promotion may be incomplete; original backup: ${backup}. ${MIGRATION_PENDING_NOTICE}` : "";
123
+ return { code: 1, stdout: "", stderr: `gentle-shell: memory migration failed: ${error.message}.${retained}${recovery}\n` };
124
+ }
125
+ }
@@ -37,7 +37,7 @@ const WINDOWS_SYSTEM_ROOT = "C:\\Windows";
37
37
  // version check below) derives from this constant instead of repeating the
38
38
  // literal, so a pin bump cannot leave a stale copy behind. See
39
39
  // scripts/install-gentle-ai.mjs for the incident that motivated this.
40
- export const INSTALLER_VERSION = "4.1.1";
40
+ export const INSTALLER_VERSION = "4.2.0";
41
41
  export const RELEASE_BASE_URL = `https://github.com/Gentleman-Programming/gentle-ai/releases/download/v${INSTALLER_VERSION}/`;
42
42
  export const GENTLE_AI_INSTALL_METHOD = Object.freeze({
43
43
  SIGNED_RELEASE_ASSET: "signed-release-asset",
@@ -46,11 +46,11 @@ export const GENTLE_AI_INSTALL_METHOD = Object.freeze({
46
46
  export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH = "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai";
47
47
  export const GENTLE_AI_WINDOWS_SOURCE_MODULE = "github.com/gentleman-programming/gentle-ai/v4";
48
48
  export const GENTLE_AI_WINDOWS_SOURCE_TAG = `v${INSTALLER_VERSION}`;
49
- // `go mod download -json github.com/gentleman-programming/gentle-ai/v4@v4.1.1`
49
+ // `go mod download -json github.com/gentleman-programming/gentle-ai/v4@v4.2.0`
50
50
  // with GOSUMDB=sum.golang.org reports this exact module SumDB checksum, and the
51
- // tag resolves to commit 898d3976e8049a7238eaa7de60bff325c30d2b3d, the published v4.1.1 release head.
51
+ // tag resolves to commit 4acaf4241beae1e0c977c92dfe406a270f8d40de, the published v4.2.0 release head.
52
52
  // v4.0.0 moved the Go module path to the /v4 major-version suffix.
53
- export const GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:+WmZ2IvIOiJ8qtHa7wsjnYKNasem+PByeOZ6HIjoAL8=";
53
+ export const GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:J8EluS7QrfM+m3vmQlF26B/shZRjD6rBfsKtrABL5mc=";
54
54
  export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE = `${GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH}@${GENTLE_AI_WINDOWS_SOURCE_TAG}`;
55
55
  export const GENTLE_AI_WINDOWS_MINIMUM_GO_VERSION = "1.25.10";
56
56
  export const GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE_CODE = "GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE";
@@ -69,7 +69,7 @@ export class GentleAiInstallerError extends Error {
69
69
  // Sentinel used while a re-pinned gentle-ai release is not yet published. A
70
70
  // sentinel digest can never match a real SHA-256, so installation fails closed,
71
71
  // and verify-package-files.mjs refuses to pack/publish while any digest below
72
- // still holds it. The v4.1.1 digests are pinned from the published release:
72
+ // still holds it. The v4.2.0 digests are pinned from the published release:
73
73
  // archive sha256 values verified against the minisign-signed checksums.txt and
74
74
  // freshly computed hashes; binary sha256 values computed from the extracted
75
75
  // executables.
@@ -111,15 +111,15 @@ async function downloadPinnedGentleAiAsset(asset, destination, options) {
111
111
  }
112
112
 
113
113
  // Windows is absent from signed release archives on purpose. gentle-ai stopped
114
- // distributing unsigned Windows builds in c4b764d0, so v4.1.1 publishes signed
114
+ // distributing unsigned Windows builds in c4b764d0, so v4.2.0 publishes signed
115
115
  // Darwin/Linux archives only. Windows x64/arm64 uses the separately verified
116
116
  // exact-tag Go SumDB source-build path below; restore archive rows only when
117
117
  // upstream ships signed Windows assets.
118
118
  export const GENTLE_AI_RELEASE_ASSETS = Object.freeze({
119
- "darwin/amd64": asset("gentle-ai_4.1.1_darwin_amd64.tar.gz", "f2e9aebfba3056e88a8d7c7193e4e8c04282dc126d5eaa2216bc807deadecffd", "82351dd86bf7323c6a5b9e9e68b3cc2893a65a4df04fbdf1ef1801cabf5d2338", "gentle-ai"),
120
- "darwin/arm64": asset("gentle-ai_4.1.1_darwin_arm64.tar.gz", "8093dced37130cb26534d81663013f81d9039342e33f00b8c85aa5319aa7eea9", "6362e3641b87ae2b41a8dd7695dad4cf47457ee86c9f63792e474476bfdb6b6a", "gentle-ai"),
121
- "linux/amd64": asset("gentle-ai_4.1.1_linux_amd64.tar.gz", "57869ebcdba5ba9942493ab413bbc15d146e2451c4dca3eaafcf163fcbd646b4", "7c638c8467efc7036e0ec6b8a1d9080a5e9a64f7ae6cbc6074fce577f789b7fe", "gentle-ai"),
122
- "linux/arm64": asset("gentle-ai_4.1.1_linux_arm64.tar.gz", "5f8fa38573eea0f2110902f0996aa0b008aac58dc0f9a6abbdf301dcbc93f4eb", "76a36a73d6905a16221496ffe9a193bd79e17951463d711cd03abf370349ef55", "gentle-ai"),
119
+ "darwin/amd64": asset("gentle-ai_4.2.0_darwin_amd64.tar.gz", "8f6a91e0f99cac3aea138705d7d222ea342f53a47602d48cb01488952a1c12f8", "784855e99f1bc45579f01420c923b7dc1dc6d63492cdc0174a9a32e611849e57", "gentle-ai"),
120
+ "darwin/arm64": asset("gentle-ai_4.2.0_darwin_arm64.tar.gz", "d5e9a6521de5f08a393635f083f64dc7afc1b8e97884f7aab5ae5eb1f17c7f37", "8ea490287ef796848b0d305cf2b26265e360d3f400efc016972844498fb87097", "gentle-ai"),
121
+ "linux/amd64": asset("gentle-ai_4.2.0_linux_amd64.tar.gz", "552c1642fd10f5d96cfe9bb1cc3b66b30eb47d3074cc7ca0559b598eb086defc", "9212c7b16ea9abfe93e3a16b29d8390e7762b5c07730cf05df1f3e7892ff96a4", "gentle-ai"),
122
+ "linux/arm64": asset("gentle-ai_4.2.0_linux_arm64.tar.gz", "2bebe91b8808ce48d86c4d3700d098e37cb6b0658f196c666d9deeeb7c7c1a7a", "e1b88bd3dcdc8a15ddbcaf8731fd44561f05b06fbcb59d9385bbe82c4b96b5c4", "gentle-ai"),
123
123
  });
124
124
 
125
125
  // A pinned asset is either a signed archive or, for a prerelease pin only,
@@ -170,6 +170,9 @@ export const guidance = Object.freeze({
170
170
  setupRateLimit: "`gentle-shell setup` could not finish because GitHub's limit for anonymous API requests was reached on this network. Wait up to an hour, then run the installer again.",
171
171
  // A failed acquire-go whose detail names a Go folder the installer did not publish.
172
172
  goDestinationConflict: `A Go folder from an earlier, interrupted installer run is in the way, and the installer never replaces a folder it cannot prove it published. Remove that folder (shown below), then run the installer again. Nothing was installed.`,
173
+ // A failed install whose detail shows Windows refused to start the gentle-ai.exe gentle-pi's postinstall built.
174
+ codeIntegrityBlocked: "Windows refused to run the Gentle AI program (gentle-ai.exe) that the installation builds on this computer, most likely because Smart App Control or another code integrity policy blocks unsigned programs. " +
175
+ "Windows offers no exception for a single program. Keep using Gentle Shell in WSL, or turn Smart App Control off in Windows Security and run the installer again; on Windows 11 it cannot be turned back on without reinstalling Windows.",
173
176
  // A failed persist-path whose detail shows pnpm could not tell the shell (POSIX SHELL missing or unsupported).
174
177
  persistPathShell: "`pnpm setup` could not tell which shell profile to edit because the SHELL environment variable is missing or names an unsupported shell. Open a regular terminal and run the installer again, or add `$PNPM_HOME/bin` to your PATH yourself.",
175
178
  fallback: "The installation stopped for an unexpected reason. Nothing else will run; check the terminal and run the installer again.",
@@ -186,16 +189,20 @@ function setupDetail(value) {
186
189
  const rateLimited = (detail) => /rate limit/i.test(detail) || (/GitHub API/i.test(detail) && /\b403\b/.test(detail));
187
190
  const unknownShell = (detail) => /ERR_PNPM_(?:UNKNOWN|UNSUPPORTED)_SHELL/.test(detail);
188
191
  const goConflict = (detail) => detail.startsWith("Conflicting Go destination: ");
192
+ // libuv reports a CreateProcess refusal it has no code for, such as a Windows code integrity block
193
+ // (Smart App Control, event 3077), as spawn UNKNOWN.
194
+ const codeIntegrityBlocked = (detail) => /\bspawn UNKNOWN\b/.test(detail);
189
195
  // The main channel's typed error when GitHub refused the latest main commit with HTTP 403.
190
196
  const githubLimited = (detail) => rateLimited(detail) || (/\bmain-commit-unavailable\b/.test(detail) && /\bHTTP 403\b/.test(detail));
191
197
  // Fixed steps whose sanitized last error line may reach the browser.
192
198
  // The bundled steps send a detail only for a migration (its error message); a new install's send none.
193
199
  const bundledDetail = Object.fromEntries(["claim-prefix", "copy-npm-settings", "install-runtime", "activate-version", "write-launcher", "verify-launcher", "bundled-path"]
194
200
  .map((step) => [step, () => false]));
195
- const detailSteps = Object.freeze({ ...bundledDetail, "shell-setup": rateLimited, "install-version": () => false, "persist-path": unknownShell, "acquire-go": goConflict,
196
- "install-global": githubLimited, "install-shell-main": githubLimited, "build-gentle-ai-main": githubLimited, "update-shell": githubLimited });
201
+ const detailSteps = Object.freeze({ ...bundledDetail, "shell-setup": rateLimited, "install-version": codeIntegrityBlocked, "persist-path": unknownShell, "acquire-go": goConflict,
202
+ "install-global": (detail) => githubLimited(detail) || codeIntegrityBlocked(detail), "install-shell-main": githubLimited, "build-gentle-ai-main": githubLimited, "update-shell": githubLimited });
203
+ // A step whose detail can match more than one pattern names its guidance by detail.
197
204
  const detailGuidance = Object.freeze({ "shell-setup": "setupRateLimit", "persist-path": "persistPathShell", "acquire-go": "goDestinationConflict",
198
- "install-global": "githubRateLimit", "install-shell-main": "githubRateLimit", "build-gentle-ai-main": "githubRateLimit", "update-shell": "githubRateLimit" });
205
+ "install-version": "codeIntegrityBlocked", "install-global": (detail) => (githubLimited(detail) ? "githubRateLimit" : "codeIntegrityBlocked"), "install-shell-main": "githubRateLimit", "build-gentle-ai-main": "githubRateLimit", "update-shell": "githubRateLimit" });
199
206
 
200
207
  const ID = /^[a-z][a-z0-9-]{0,63}$/;
201
208
  function identifier(value, fallback = "unknown") {
@@ -543,7 +550,10 @@ function outcomeView(result) {
543
550
  const detail = step ? setupDetail(result.detail) : null;
544
551
  if (detail) {
545
552
  view.detail = detail;
546
- if (detailSteps[step](detail)) view.guidance = guidance[detailGuidance[step]];
553
+ if (detailSteps[step](detail)) {
554
+ const key = detailGuidance[step];
555
+ view.guidance = guidance[typeof key === "function" ? key(detail) : key];
556
+ }
547
557
  }
548
558
  } else {
549
559
  view.guidance = guidance.outcomes[result.outcome];
@@ -35,9 +35,10 @@ export class MainChannelError extends Error {
35
35
  }
36
36
  }
37
37
 
38
- /** Same config home as the dev-binary override: GENTLE_PI_CONFIG_HOME or ~/.pi/gentle-ai. */
38
+ /** Same config home as the dev-binary override: a nonempty GENTLE_PI_CONFIG_HOME or ~/.pi/gentle-ai.
39
+ * Install-level state, so never the standalone launcher's per-home marker (#2201). */
39
40
  export function configHome({ env, home }) {
40
- return env.GENTLE_PI_CONFIG_HOME ?? join(home, ".pi", "gentle-ai");
41
+ return env.GENTLE_PI_CONFIG_HOME || join(home, ".pi", "gentle-ai");
41
42
  }
42
43
  export function channelStatePath(ctx) {
43
44
  return join(configHome(ctx), "channel.json");
@@ -30,6 +30,10 @@ export async function runStage(stage) {
30
30
  return !normalizedKey.startsWith("GIT_") && !normalizedKey.startsWith("GENTLE_PI_AGENTS_") && normalizedKey !== "GENTLE_PI_CONFIG_HOME";
31
31
  })) : process.env;
32
32
  if (configHome) env.GENTLE_PI_CONFIG_HOME = configHome;
33
+ // Unit fixtures also get a private temp dir, removed with the stage, so a
34
+ // fixture that leaves a subagent unfinished cannot leave its TMPDIR behind.
35
+ const unitTmp = configHome ? mkdtempSync(join(tmpdir(), "gentle-pi-unit-tmp-")) : undefined;
36
+ if (unitTmp) env.TMPDIR = unitTmp;
33
37
  try {
34
38
  return await new Promise((resolve) => {
35
39
  const child = spawn(stage.command, { shell: true, stdio: "inherit", env });
@@ -41,6 +45,7 @@ export async function runStage(stage) {
41
45
  });
42
46
  } finally {
43
47
  if (configHome) rmSync(configHome, { recursive: true, force: true });
48
+ if (unitTmp) rmSync(unitTmp, { recursive: true, force: true });
44
49
  }
45
50
  }
46
51
 
@@ -53,6 +53,7 @@ export const requiredPaths = [
53
53
  "scripts/npm-pack-result.mjs",
54
54
  "assets/orchestrator.md",
55
55
  "assets/orchestrator-delegation.md",
56
+ "assets/orchestrator-delegation-reference.md",
56
57
  "assets/orchestrator-tracking.md",
57
58
  "assets/orchestrator-verification.md",
58
59
  "assets/orchestrator-writer.md",
@@ -81,6 +82,7 @@ export const requiredPaths = [
81
82
  "docs/review-integration.md",
82
83
  "extensions/gentle-ai.ts",
83
84
  "extensions/resume-hint.ts",
85
+ "extensions/session-scratch.ts",
84
86
  "extensions/skill-registry.ts",
85
87
  "lib/child-package-injection.ts",
86
88
  "lib/gentle-ai-binary.ts",
@@ -90,6 +92,7 @@ export const requiredPaths = [
90
92
  "lib/gentle-shell-update-panel.ts",
91
93
  "lib/gentle-shell-startup-flow.ts",
92
94
  "lib/gentle-shell-resume-hint.ts",
95
+ "lib/gentle-shell-import-pi.ts",
93
96
  "lib/native-review-cli.ts",
94
97
  "lib/status-timing-diagnostics.ts",
95
98
  "lib/provider-contract-bundle.ts",
@@ -98,6 +101,7 @@ export const requiredPaths = [
98
101
  "lib/review-relay-contract.ts",
99
102
  "lib/agent-assets.ts",
100
103
  "lib/telemetry-trigger.ts",
104
+ "lib/session-scratch.ts",
101
105
  "runtime/child-package-injection.mjs",
102
106
  "runtime/gentle-ai-binary.mjs",
103
107
  "runtime/gentle-shell-launcher.mjs",
@@ -107,6 +111,7 @@ export const requiredPaths = [
107
111
  "runtime/gentle-shell-startup-flow.mjs",
108
112
  "runtime/gentle-shell-resume-hint.mjs",
109
113
  "runtime/gentle-shell-uninstall.mjs",
114
+ "runtime/gentle-shell-import-pi.mjs",
110
115
  "runtime/native-review-cli.mjs",
111
116
  "runtime/status-timing-diagnostics.mjs",
112
117
  "runtime/review-integration-v2.mjs",
@@ -131,17 +136,18 @@ export const requiredPaths = [
131
136
  // exact bytes are pinned by the lock-driven scripts/check-provider-contract.mjs
132
137
  // drift check, which runs in the same pnpm test flow.
133
138
  "contracts/review-provider-contract-mirror/provider-contract.lock.json",
134
- "contracts/review-provider-contract-mirror/v1.3.0/bundle/README.md",
135
- "contracts/review-provider-contract-mirror/v1.3.0/bundle/manifest.json",
136
- "contracts/review-provider-contract-mirror/v1.3.0/bundle/orchestration/pi.md",
137
- "contracts/review-provider-contract-mirror/v1.3.0/bundle/schemas/lens.schema.json",
138
- "contracts/review-provider-contract-mirror/v1.3.0/bundle/schemas/refuter.schema.json",
139
- "contracts/review-provider-contract-mirror/v1.3.0/bundle/schemas/targeted-validator.schema.json",
140
- "contracts/review-provider-contract-mirror/v1.3.0/bundle/vectors/lens.json",
141
- "contracts/review-provider-contract-mirror/v1.3.0/bundle/vectors/refuter.json",
142
- "contracts/review-provider-contract-mirror/v1.3.0/bundle/vectors/targeted-validator.json",
143
- "contracts/review-provider-contract-mirror/v1.3.0/generated/provider-capabilities.baseline.json",
144
- "contracts/review-provider-contract-mirror/v1.3.0/generated/provider-roles.baseline.json",
139
+ "contracts/review-provider-contract-mirror/v1.4.0/bundle/README.md",
140
+ "contracts/review-provider-contract-mirror/v1.4.0/bundle/manifest.json",
141
+ "contracts/review-provider-contract-mirror/v1.4.0/bundle/orchestration/pi.md",
142
+ "contracts/review-provider-contract-mirror/v1.4.0/bundle/routing/pi.md",
143
+ "contracts/review-provider-contract-mirror/v1.4.0/bundle/schemas/lens.schema.json",
144
+ "contracts/review-provider-contract-mirror/v1.4.0/bundle/schemas/refuter.schema.json",
145
+ "contracts/review-provider-contract-mirror/v1.4.0/bundle/schemas/targeted-validator.schema.json",
146
+ "contracts/review-provider-contract-mirror/v1.4.0/bundle/vectors/lens.json",
147
+ "contracts/review-provider-contract-mirror/v1.4.0/bundle/vectors/refuter.json",
148
+ "contracts/review-provider-contract-mirror/v1.4.0/bundle/vectors/targeted-validator.json",
149
+ "contracts/review-provider-contract-mirror/v1.4.0/generated/provider-capabilities.baseline.json",
150
+ "contracts/review-provider-contract-mirror/v1.4.0/generated/provider-roles.baseline.json",
145
151
  "prompts/skill-creation.md",
146
152
  "skills/_shared/review-ledger-contract.md",
147
153
  "skills/branch-pr/SKILL.md",
@@ -395,7 +401,7 @@ async function main() {
395
401
  });
396
402
 
397
403
  if (driftedContracts.length > 0) {
398
- console.error("gentle-pi packaged review-integration/v1 and review-integration/v2 contract bytes drifted from the pinned v4.1.1 runtime's vendored Gentle AI contract artifacts:");
404
+ console.error("gentle-pi packaged review-integration/v1 and review-integration/v2 contract bytes drifted from the pinned v4.2.0 runtime's vendored Gentle AI contract artifacts:");
399
405
  for (const drift of driftedContracts) console.error(`- ${drift.relativePath}: expected ${drift.expected}, got ${drift.actual}`);
400
406
  process.exit(1);
401
407
  }
@@ -440,7 +446,7 @@ async function main() {
440
446
  process.exit(1);
441
447
  }
442
448
 
443
- console.log(`gentle-pi package resource check passed (${requiredPaths.length} files; ${Object.keys(contractHashes).length} exact byte-pinned contract artifacts for the v4.1.1 runtime).`);
449
+ console.log(`gentle-pi package resource check passed (${requiredPaths.length} files; ${Object.keys(contractHashes).length} exact byte-pinned contract artifacts for the v4.2.0 runtime).`);
444
450
  }
445
451
 
446
452
  const isMainModule = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
@@ -17,7 +17,7 @@ When asked who or what you are, answer as el Gentleman: a Pi-specific coding-age
17
17
  - Size every task by the orchestrator's Task Size section: understood, contained risk, and resumable from the original request plus `git diff`. Counts of files, commands, tests, or fixes never decide it. Track only large work in its task document and mirror.
18
18
  - For behavior changes with applicable runnable deterministic tests and a clear expected outcome, use test-first by default: observe RED, GREEN, relevant alternate cases, then REFACTOR and record evidence. Test presence alone does not establish applicability. For passive documentation, non-testable changes, an unavailable runner, or no meaningful RED, state why and run proportionate ordinary functional or structural verification. Never invent RED/GREEN, skip checks, or require a chat/TUI toggle.
19
19
  - Keep one parent session responsible for orchestration; child subagents should receive concrete phase work and must not spawn more subagents.
20
- - Parent-only delegation triggers fire one mechanism at a time: an open decision (ask), understanding beyond the evidence budget (explore), high risk (independent verifier), a large task (track), a writer reason (parallel units or context), tooling/worktree incidents, or a parent context past the context backstop.
20
+ - Parent-only delegation triggers fire one mechanism at a time: an open decision (ask), understanding beyond the evidence budget (explore), a named high-risk item (independent verifier, through assess `escalate`), a large task (track), a writer reason (parallel units or context), tooling/worktree incidents, or a parent context past the context backstop.
21
21
  - Parallel writers only with disjoint Allowed edit surfaces (runtime-enforced) or isolated worktrees.
22
22
  - Forecast review workload before large changes; ask before producing oversized or multi-area diffs.
23
23
  - Keep dangerous-command safety independent and authoritative.
@@ -45,9 +45,9 @@ Hard delegation triggers:
45
45
 
46
46
  - **Evidence-budget rule**: read inline only when the evidence fits one parallel batch of at most 3 calls, ~10k tokens (grep and line ranges, never whole large files). When understanding needs more reading or more than ~5 sequential lookups, delegate one explorer that returns a handoff of at most ~2k tokens with `path:line` evidence. Never force delegation for a small targeted question; do not re-read what the handoff covered beyond one spot check.
47
47
  - **Writer rule**: a writer only for a reason (parallel units launched together, or context); size and file count never fire it.
48
- - **Verification rule**: a high-risk change gets an independent verifier; otherwise the change's own focused test and suite run inline.
48
+ - **Verification rule**: a change that touches a named high-risk item gets an independent verifier (assess `escalate`); otherwise the change's own focused test and suite run inline.
49
49
  - **Incident rule**: after wrong cwd, accidental worktree/repo mutation, merge recovery, confusing test command, or environment workaround, diagnose separately.
50
- - **Context backstop**: when the parent context passes ~150k tokens, pause and delegate the next bounded unit of work to a non-review subagent. Keep command output bounded (counts, `--stat`, `tail`).
50
+ - **Context backstop**: when the runtime signals the context backstop (measured parent context past the smaller of an absolute cap and a fraction of the model's context window), pause and delegate the next bounded unit that needs several steps to a non-review subagent; small edits stay inline. Keep command output bounded (counts, `--stat`, `tail`).
51
51
 
52
52
  ## Review Lens Selection
53
53
 
@@ -18,7 +18,7 @@ Use it for:
18
18
  - Turning a large change into chained or stacked PRs.
19
19
  - Keeping reviewer cognitive load healthy.
20
20
  - Applying SDD tasks without accidentally producing a PR above 400 changed lines.
21
- - Closing an ODD task with a work-unit commit.
21
+ - Committing ODD work in reviewable slices of about 400 authored changed lines.
22
22
 
23
23
  ## Critical Rules
24
24
 
@@ -80,11 +80,13 @@ Each SDD work unit should map cleanly to a commit or PR with:
80
80
 
81
81
  ## ODD Relationship
82
82
 
83
- Every ODD task closes with at least one work-unit commit:
83
+ ODD commits work units in reviewable slices of about 400 authored changed lines, not one commit per task:
84
84
 
85
+ - A slice holds one or more whole tasks with their tests and docs; the last slice closes the feature.
86
+ - A unit whose Risk line names a high-risk item gets its own commit, so native review takes it alone.
85
87
  - The native review candidate is that commit, or the PR slice it belongs to when review is deferred, evaluated against the previous reviewed boundary.
86
88
  - The running authored line count from work-unit commits feeds the same delivery-strategy vocabulary as SDD.
87
- - The feature document records the commit identity and, once a delivery strategy applies, the slice boundaries.
89
+ - The feature document records the commit that holds each task and, once a delivery strategy applies, the slice boundaries.
88
90
 
89
91
  ## Commands
90
92