gentle-pi 2.3.0 → 2.5.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 +195 -11
- package/assets/agents/gentle-ai-worker.md +9 -0
- package/assets/agents/sdd-explore.md +1 -0
- package/assets/orchestrator-delegation.md +21 -10
- package/assets/orchestrator.md +8 -12
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
- package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/README.md +10 -0
- package/contracts/review-provider-contract-mirror/v1.2.0/bundle/manifest.json +74 -0
- package/contracts/review-provider-contract-mirror/v1.2.0/bundle/orchestration/pi.md +53 -0
- package/contracts/review-provider-contract-mirror/v1.2.0/bundle/schemas/targeted-validator.schema.json +1 -0
- package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-capabilities.baseline.json +9 -2
- package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/generated/provider-roles.baseline.json +2 -2
- package/docs/delegated-verification.md +25 -0
- package/docs/review-integration.md +1 -1
- package/docs/telemetry.md +38 -0
- package/extensions/ask-user-choice.ts +26 -20
- package/extensions/codegraph-tools.ts +94 -5
- package/extensions/gentle-agents.ts +588 -0
- package/extensions/gentle-ai.ts +1421 -143
- package/extensions/gentle-shell.ts +547 -0
- package/extensions/gentle-todo.ts +199 -0
- package/extensions/quiet-tools.ts +1 -1
- package/lib/agent-home.ts +8 -0
- package/lib/agents-config.ts +318 -0
- package/lib/agents-history.ts +80 -0
- package/lib/agents-protocol.ts +429 -0
- package/lib/agents-runner.ts +490 -0
- package/lib/agents-transcript.ts +87 -0
- package/lib/agents-view.ts +557 -0
- package/lib/agents-widget.ts +222 -0
- package/lib/gentle-ai-renderer.ts +142 -26
- package/lib/native-choice-list.ts +194 -0
- package/lib/native-fullscreen-interaction.ts +47 -0
- package/lib/native-pointer-region.ts +164 -0
- package/lib/native-review-cli.ts +103 -12
- package/lib/provider-contract-bundle.ts +88 -6
- package/lib/review-candidate-view-owner.ts +177 -0
- package/lib/review-candidate-view.ts +127 -35
- package/lib/review-consent-ui.ts +65 -0
- package/lib/review-host-relay.ts +146 -60
- package/lib/review-integration-v2.ts +92 -13
- package/lib/review-last-event-controller.ts +1 -0
- package/lib/review-relay-contract.ts +11 -0
- package/lib/review-repository.ts +2 -2
- package/lib/review-risk-assessment.ts +339 -0
- package/lib/review-session-standing-permission-ipc.ts +309 -0
- package/lib/review-session-standing-permission.ts +219 -0
- package/lib/sdd-preflight.ts +2 -2
- package/lib/shell-bar.ts +138 -0
- package/lib/shell-card.ts +136 -0
- package/lib/shell-changes-view.ts +205 -0
- package/lib/shell-changes.ts +210 -0
- package/lib/shell-gauge.ts +40 -0
- package/lib/shell-prompt.ts +119 -0
- package/lib/shell-todo.ts +280 -0
- package/lib/shell-usage-view.ts +76 -0
- package/lib/shell-usage.ts +246 -0
- package/lib/telemetry-trigger.ts +151 -0
- package/package.json +4 -4
- package/runtime/native-review-cli.mjs +102 -11
- package/runtime/review-integration-v2.mjs +92 -13
- package/runtime/review-relay-contract.mjs +11 -0
- package/runtime/review-risk-assessment.mjs +340 -0
- package/runtime/telemetry-trigger.mjs +152 -0
- package/scripts/build-runtime-modules.mjs +2 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/test-packed-runner.mjs +22 -0
- package/scripts/verify-package-files.mjs +18 -13
- package/skills/_shared/review-ledger-contract.md +9 -1
- package/skills/issue-creation/SKILL.md +53 -93
- package/tests/agents-config.test.ts +143 -0
- package/tests/agents-fake-child.ts +52 -0
- package/tests/agents-history.test.ts +54 -0
- package/tests/agents-protocol.test.ts +153 -0
- package/tests/agents-runner-process.test.ts +111 -0
- package/tests/agents-runner.test.ts +402 -0
- package/tests/agents-transcript.test.ts +30 -0
- package/tests/agents-view.test.ts +274 -0
- package/tests/agents-widget.test.ts +111 -0
- package/tests/ask-user-choice.test.ts +157 -3
- package/tests/codegraph-tools.test.ts +110 -1
- package/tests/devbinary/native-review-parity.devtest.ts +108 -0
- package/tests/fixtures/agents-process-child.mjs +23 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/README.md +22 -0
- package/{contracts/review-provider-contract-mirror/v1.1.0/bundle → tests/fixtures/provider-contract-bundle/v1.2.0}/manifest.json +11 -2
- package/tests/fixtures/provider-contract-bundle/v1.2.0/orchestration/pi.md +97 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/lens.schema.json +16 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/schemas/refuter.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/lens.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/refuter.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.2.0/vectors/targeted-validator.json +1 -0
- package/tests/gentle-agents.test.ts +741 -0
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-installer.test.ts +47 -47
- package/tests/gentle-ai-renderer.test.ts +65 -0
- package/tests/gentle-ai.test.ts +31 -14
- package/tests/gentle-card-text.ts +35 -0
- package/tests/gentle-shell.test.ts +527 -0
- package/tests/gentle-todo.test.ts +182 -0
- package/tests/issue-creation-skill.test.ts +103 -0
- package/tests/native-choice-list.test.ts +202 -0
- package/tests/native-fullscreen-interaction.test.ts +125 -0
- package/tests/native-pointer-region.test.ts +245 -0
- package/tests/native-review-capability-contract.test.ts +33 -1
- package/tests/native-review-cli.test.ts +40 -0
- package/tests/native-review-consent.test.ts +91 -0
- package/tests/native-review-parity-runtime.test.ts +8 -2
- package/tests/native-review-parity.test.ts +29 -22
- package/tests/orchestrator-budget.test.ts +71 -2
- package/tests/orchestrator-rdd-ownership.test.ts +10 -1
- package/tests/package-manifest.test.ts +134 -9
- package/tests/provider-contract-bundle.test.ts +76 -0
- package/tests/provider-contract-mirror.test.ts +19 -0
- package/tests/quiet-tool-rendering.test.ts +96 -37
- package/tests/rdd-aware-verification-contract.test.ts +216 -0
- package/tests/rdd-status-line.test.ts +286 -0
- package/tests/review-agent-end-preflight.test.ts +408 -0
- package/tests/review-candidate-view.test.ts +452 -6
- package/tests/review-contract-prompt.test.ts +142 -0
- package/tests/review-controller-native-recovery.test.ts +29 -4
- package/tests/review-controller-native-routing.test.ts +321 -4
- package/tests/review-controller-workspace-root.test.ts +45 -2
- package/tests/review-controller.test.ts +26 -1
- package/tests/review-host-relay-routing.test.ts +229 -11
- package/tests/review-host-relay.test.ts +195 -7
- package/tests/review-integration-v2-forward.test.ts +47 -0
- package/tests/review-integration-v2.test.ts +112 -0
- package/tests/review-last-event-closure.test.ts +7 -2
- package/tests/review-ledger-contract.test.ts +1 -1
- package/tests/review-relay-contract.test.ts +26 -0
- package/tests/review-repository.test.ts +28 -1
- package/tests/review-risk-assessment.test.ts +626 -0
- package/tests/review-session-standing-permission-controller.test.ts +608 -0
- package/tests/review-session-standing-permission-ipc.test.ts +233 -0
- package/tests/review-session-standing-permission-runtime.test.ts +212 -0
- package/tests/review-session-standing-permission.test.ts +126 -0
- package/tests/runtime-harness.mjs +1 -0
- package/tests/shell-bar.test.ts +176 -0
- package/tests/shell-card.test.ts +118 -0
- package/tests/shell-changes-view.test.ts +146 -0
- package/tests/shell-changes.test.ts +182 -0
- package/tests/shell-prompt.test.ts +118 -0
- package/tests/shell-todo.test.ts +170 -0
- package/tests/shell-usage-view.test.ts +62 -0
- package/tests/shell-usage.test.ts +197 -0
- package/tests/telemetry-trigger.test.ts +349 -0
- package/tests/writer-edit-surface-scope.test.ts +153 -17
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.1.0 → v1.2.0}/bundle/vectors/targeted-validator.json +0 -0
- /package/{contracts/review-provider-contract-mirror/v1.1.0/bundle → tests/fixtures/provider-contract-bundle/v1.2.0}/schemas/targeted-validator.schema.json +0 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema": "gentle-ai.review-provider-contract-bundle/v1",
|
|
3
|
+
"contract_semver": "1.2.0",
|
|
4
|
+
"transport_capability": "gentle-ai.provider-transport/v1",
|
|
5
|
+
"runtimes": [
|
|
6
|
+
"claude-code",
|
|
7
|
+
"codex",
|
|
8
|
+
"opencode",
|
|
9
|
+
"pi"
|
|
10
|
+
],
|
|
11
|
+
"readme": {
|
|
12
|
+
"path": "README.md",
|
|
13
|
+
"sha256": "18baab5ee79aefd0bc62a28da0dadcf2162544f57a940c5b859cd6bc4932a085"
|
|
14
|
+
},
|
|
15
|
+
"roles": [
|
|
16
|
+
{
|
|
17
|
+
"id": "lens",
|
|
18
|
+
"request_schema_id": "gentle-ai.review-lens-context/v1",
|
|
19
|
+
"result_schema_id": "https://gentle-ai.dev/schema/review/reviewer/v1",
|
|
20
|
+
"required_capabilities": [
|
|
21
|
+
"gentle-ai.provider-transport/v1"
|
|
22
|
+
],
|
|
23
|
+
"schema": {
|
|
24
|
+
"path": "schemas/lens.schema.json",
|
|
25
|
+
"sha256": "ffef79cfb763333282285ff5c795a32b5a88d840b4a8d125ce012188b04cffe1"
|
|
26
|
+
},
|
|
27
|
+
"vector": {
|
|
28
|
+
"path": "vectors/lens.json",
|
|
29
|
+
"sha256": "7ef0b08404645eeb5dba92aba5f8e4780667b6ec775f43ed8547ff9f2a7148c8"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "refuter",
|
|
34
|
+
"request_schema_id": "gentle-ai.review-provider-refuter-request/v1",
|
|
35
|
+
"result_schema_id": "https://gentle-ai.dev/schema/review/refuter/v1",
|
|
36
|
+
"required_capabilities": [
|
|
37
|
+
"gentle-ai.provider-transport/v1"
|
|
38
|
+
],
|
|
39
|
+
"schema": {
|
|
40
|
+
"path": "schemas/refuter.schema.json",
|
|
41
|
+
"sha256": "36f8267c6c3f04600b8f1cb77df70d90858611af822cc81922efd6b3502fb646"
|
|
42
|
+
},
|
|
43
|
+
"vector": {
|
|
44
|
+
"path": "vectors/refuter.json",
|
|
45
|
+
"sha256": "f42d71906e49c4191660b2da7e70f01f1778b23d32089005bd2d7ae9cbb73039"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "targeted-validator",
|
|
50
|
+
"request_schema_id": "gentle-ai.review-targeted-validation-request/v1",
|
|
51
|
+
"result_schema_id": "https://gentle-ai.dev/schema/review/validator/v1",
|
|
52
|
+
"required_capabilities": [
|
|
53
|
+
"gentle-ai.provider-transport/v1"
|
|
54
|
+
],
|
|
55
|
+
"schema": {
|
|
56
|
+
"path": "schemas/targeted-validator.schema.json",
|
|
57
|
+
"sha256": "6a8d7b331ec6f000366017e39e0476511c3b032649c42abfe865c1e9c956510e"
|
|
58
|
+
},
|
|
59
|
+
"vector": {
|
|
60
|
+
"path": "vectors/targeted-validator.json",
|
|
61
|
+
"sha256": "7b5a5165e3a913863fb98d2ba5387811860c21333b5c37e6ed3b4e0d121cf2fa"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"orchestration": [
|
|
66
|
+
{
|
|
67
|
+
"runtime": "pi",
|
|
68
|
+
"file": {
|
|
69
|
+
"path": "orchestration/pi.md",
|
|
70
|
+
"sha256": "69c51944081ccff09d1cee5e24eb3a14b7b911a2e734cef8f8ea94a15b9f9d4c"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Native Compact Review Orchestration
|
|
2
|
+
|
|
3
|
+
Pi uses the compact gentle-pi facade for this lifecycle: `gentle_review` for inspect, START, consent, bound STATUS, and acknowledgement; `gentle_review_capture` and `gentle_review_capture_group` for collection. Provider-issued authority and every opaque binding are authoritative; prompt prose never creates authority or decides delivery.
|
|
4
|
+
|
|
5
|
+
## Entry rule
|
|
6
|
+
|
|
7
|
+
After authorized source-mutating implementation is complete and normalized, and before reporting it complete, call `gentle_review` with {"operation":"inspect"}. Do this once per candidate whenever the user-owned review switch is enabled (`gentle-ai review mode status` reads it without changing it). The facade returns the only offered START route; do not infer, reconstruct, or replace it. Never skip the preflight because the user did not ask for a review: the START consent envelope lets the human decide this candidate. Skip it only for a trivial passive documentation-only edit, when the user explicitly left this candidate unreviewed, or while a transaction is already bound to it.
|
|
8
|
+
|
|
9
|
+
## Atomic lifecycle
|
|
10
|
+
|
|
11
|
+
1. **Inspect before START.** Call `gentle_review` with {"operation":"inspect"} before START. Retain only the provider-issued authority and opaque bindings returned by the facade.
|
|
12
|
+
2. **Freeze once.** Invoke only the START route inspect offered: `gentle_review` with operation `start`, a fresh `idempotencyKey`, and the `input` the facade documents (ordinary START is {"mode":"ordinary"}; an explicit `baseRef` requires `committedOnly: true`). Retain the returned `lineageId`, revision, target, and `workspaceRoot` as opaque values. An exact replay of an active START may return `replayed`; a genuinely new START is independent. Do not start another lineage, reuse burned authority, or perform ambient recovery.
|
|
13
|
+
3. **Stay bound.** For later routing, call `gentle_review` with operation `status`, the exact retained `lineageId`, and `workspaceRoot` only when needed. Never run raw shell STATUS. Route only from the returned transition; for `execute`, invoke its offered facade operation with its exact opaque binding; for `collect`, satisfy only the named slots through the capture tools below; for `stop`, run no lifecycle operation.
|
|
14
|
+
4. **Collect exactly.** Use `gentle_review_capture` for one current returned slot or `gentle_review_capture_group` for the complete current reviewer group. Submit only the returned opaque binding and result. After collection, use bound facade STATUS again only when the returned transition requires it.
|
|
15
|
+
5. **Acknowledge exactly.** Only the exact provider-issued acknowledgement continuation burns approved authority. Report the burn from its returned envelope, never from a later STATUS.
|
|
16
|
+
|
|
17
|
+
Pi never reconstructs lineage, target, revision, repository context, lens, order, or commands. It never appends, removes, parses, or rebuilds provider-issued opaque bindings. Go owns repository binding, frozen evidence, provider context, validation, admission, correction scope, and closure.
|
|
18
|
+
|
|
19
|
+
## Consent and forecast
|
|
20
|
+
|
|
21
|
+
If START returns the typed `gentle-ai.review-integration.consent/v3` envelope, relay it as a Lossless Blocking Prompt. Global RDD enabled permits review; it never grants consent for this candidate. For medium/high candidates, faithfully translate the headline, reason, `value`, risk evidence, choice labels, every choice `effect`, and the off-path note while preserving original groups/order, selection mode, allowed-answer domain, answer tokens, and the `consentBinding`. Project `value` as benefits and every `effect` as consequences. Do not translate machine answer tokens (`granted`, `declined`). Submit exactly the human's choice through `gentle_review` with operation `answer-consent` and the exact `consentBinding`; a decline is candidate-scoped and is not the kill switch.
|
|
22
|
+
|
|
23
|
+
A four-lens review is long work. The first capture of a materialize slot or group returns a `forecast` and runs nothing: relay it losslessly in the user's language, preserving every step's order and fields (`step`, `kind`, `reason_code`, `description`) and the horizon, then resubmit the same exact binding with `reviewerRunAcknowledged: true`. Forecast is informational; route only from the returned transition.
|
|
24
|
+
|
|
25
|
+
## Capture and correction
|
|
26
|
+
|
|
27
|
+
Reviewers inspect only the provider-bound immutable trees, and the gentle-pi relay owns the reviewer prompt. Never hand candidate bytes through `/tmp`, an external file, a repository scratch file, or `GENTLE_AI_FROZEN_CANDIDATE_CONTEXT`, and never substitute the live worktree, index, or `HEAD`.
|
|
28
|
+
|
|
29
|
+
Only candidate-caused severe findings block. Pre-existing/base-only findings are follow-ups; unknown causality escalates. A deterministic blocker needs no refuter; inferential blockers share one read-only refuter batch. The final reviewer, refuter, or targeted-validator capture owns closure.
|
|
30
|
+
|
|
31
|
+
A malformed, incomplete, or unavailable capture never reaches acknowledgement. Use bound facade STATUS once, and relaunch only when it reoffers the same bound slot. An approved capture awaits acknowledgement; it is not burned. On `approved`, use bound facade STATUS to obtain or replay the exact provider-issued `acknowledge-approved` continuation, then execute it unchanged. Only its successful returned envelope burns authority; do not issue STATUS after that burn. On `correction_required`, continue only through exact bound facade STATUS and the provider-issued correction route. Native Go maps edits only to corroborated frozen findings and permits at most one bounded correction. A validator that cannot inspect the immutable trees produced no verdict: surface one blocked human decision and submit nothing.
|
|
32
|
+
|
|
33
|
+
### Cross-repository lifecycle root
|
|
34
|
+
|
|
35
|
+
A session in repository A may review an explicitly selected nested target in unrelated repository B only after explicit user authorization. Pass the selected path as `workspaceRoot`; Go resolves it to the canonical B worktree root, and the facade retains it. Keep that `workspaceRoot` on every later facade call for the lineage, from inspect through acknowledgement, and do not fall back to A. The same lineage text in A and B is independent; exact acknowledgement burns B only.
|
|
36
|
+
|
|
37
|
+
### Continue after a stop reason code
|
|
38
|
+
|
|
39
|
+
A `stop` ends its transition, never approves delivery. `D` means the human disables the review switch for this clone with `gentle-ai review mode disable --scope clone`; ordinary policy then decides delivery. `S` means re-query bound facade STATUS with the retained `lineageId` and `workspaceRoot`.
|
|
40
|
+
|
|
41
|
+
| Reason codes | Continuation |
|
|
42
|
+
| --- | --- |
|
|
43
|
+
| `captured_artifacts_unverifiable`, `captured_result_selection_unavailable`, `missing_authority_binding`, `corrupted_or_unverifiable_authority`, `manual_intervention_required`, `native_stop_required` | Terminal: the maintainer inspects authority/lineage, or `D`. |
|
|
44
|
+
| `empty_base_diff_bootstrap_required` | Terminal: authorized empty-root bootstrap for a new target, or `D`. |
|
|
45
|
+
| `lens_context_budget_exceeded` | Terminal: reduce the candidate scope and start a new transaction, or `D`. |
|
|
46
|
+
| `staged_workspace_overlay_recovery_unavailable` | Call facade `recover` with the retained `lineageId`, or start a fresh transaction; otherwise `D`. |
|
|
47
|
+
| `corrected_candidate_unavailable` | Change the correction candidate, then `S`; do not reuse the pre-correction target. |
|
|
48
|
+
| `recovery_scope_unchanged` | Change the target identity, then retry the facade `recover` route the stop returned. |
|
|
49
|
+
| `rdd_disabled` | `--scope clone` only clears a clone-local off; the human runs `gentle-ai review mode enable --scope global`, then `S`. |
|
|
50
|
+
|
|
51
|
+
## Delivery follows ordinary repository policy
|
|
52
|
+
|
|
53
|
+
After exact acknowledgement burns terminal `approved` authority, the review lifecycle stops. Commit, push, PR, and release remain separate human decisions under ordinary repository policy. A review outcome is informational and never authorizes delivery.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://gentle-ai.dev/schema/review/validator/v1","title":"Gentle AI targeted validator result","type":"object","additionalProperties":false,"required":["targeted_validation_request_hash","correction_target_identity","original_criteria","correction_regression","follow_ups"],"properties":{"targeted_validation_request_hash":{"$ref":"#/$defs/sha256"},"correction_target_identity":{"$ref":"#/$defs/sha256"},"original_criteria":{"$ref":"#/$defs/check"},"correction_regression":{"$ref":"#/$defs/check"},"follow_ups":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["observation","proof_refs"],"properties":{"observation":{"type":"string"},"proof_refs":{"type":"array","minItems":1,"items":{"type":"string","pattern":"\\S"}}}}}},"$defs":{"sha256":{"type":"string","pattern":"^sha256:[0-9a-f]{64}$"},"check":{"type":"object","additionalProperties":false,"required":["passed","evidence"],"properties":{"passed":{"type":"boolean","description":"true means the named check passed; false means the named check failed."},"evidence":{"type":"array","minItems":1,"items":{"type":"string"}}}}},"examples":[{"targeted_validation_request_hash":"sha256:0000000000000000000000000000000000000000000000000000000000000000","correction_target_identity":"sha256:1111111111111111111111111111111111111111111111111111111111111111","original_criteria":{"passed":true,"evidence":["acceptance test passed"]},"correction_regression":{"passed":true,"evidence":["regression test passed"]},"follow_ups":[]}]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "gentle-pi.provider-contract-capabilities-baseline/v1",
|
|
3
|
-
"contract_semver": "1.
|
|
3
|
+
"contract_semver": "1.2.0",
|
|
4
4
|
"transport_capability": "gentle-ai.provider-transport/v1",
|
|
5
5
|
"mandatory_capabilities": [
|
|
6
6
|
"gentle-ai.provider-transport/v1"
|
|
@@ -11,5 +11,12 @@
|
|
|
11
11
|
"opencode",
|
|
12
12
|
"pi"
|
|
13
13
|
],
|
|
14
|
-
"pi_registered": true
|
|
14
|
+
"pi_registered": true,
|
|
15
|
+
"orchestration": [
|
|
16
|
+
{
|
|
17
|
+
"runtime": "pi",
|
|
18
|
+
"path": "orchestration/pi.md",
|
|
19
|
+
"sha256": "69c51944081ccff09d1cee5e24eb3a14b7b911a2e734cef8f8ea94a15b9f9d4c"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
15
22
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "gentle-pi.provider-contract-roles-baseline/v1",
|
|
3
|
-
"contract_semver": "1.
|
|
3
|
+
"contract_semver": "1.2.0",
|
|
4
4
|
"roles": [
|
|
5
5
|
{
|
|
6
6
|
"id": "lens",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"gentle-ai.provider-transport/v1"
|
|
35
35
|
],
|
|
36
36
|
"schema_path": "schemas/targeted-validator.schema.json",
|
|
37
|
-
"schema_sha256": "
|
|
37
|
+
"schema_sha256": "6a8d7b331ec6f000366017e39e0476511c3b032649c42abfe865c1e9c956510e",
|
|
38
38
|
"vector_path": "vectors/targeted-validator.json",
|
|
39
39
|
"vector_sha256": "7b5a5165e3a913863fb98d2ba5387811860c21333b5c37e6ed3b4e0d121cf2fa"
|
|
40
40
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Delegated verification
|
|
2
|
+
|
|
3
|
+
How the Gentle Pi orchestrator decides who verifies a bounded writer's work. The always-on parent prompt renders a `Receipt-driven development: on|off|unknown` line; the delegation overlay (`assets/orchestrator-delegation.md`, trigger 5) keys the verification rule on it. This page is package-owned; `docs/review-integration.md` mirrors the Gentle AI contract and must stay byte-identical to it.
|
|
4
|
+
|
|
5
|
+
## Receipt-driven development on
|
|
6
|
+
|
|
7
|
+
The bounded writer runs the exact commands the parent lists under `## Verification`, in the foreground, and reports each as `<command>: <observed result>`. That report is the verification of record and the native review is the independent check. `gentle-ai-verify` is on-demand: a `partial` or `blocked` writer, an expensive or external check the parent wants on a cheaper profile, or a parent spot check.
|
|
8
|
+
|
|
9
|
+
This `on` path holds only while the native review actually reaches a terminal outcome for the current candidate (gentle-pi#668). A human decline of the consent envelope for this candidate (candidate-scoped, never the RDD kill switch), a clone-local RDD disable discovered mid-flow, or a refused START/STATUS all mean the review never ran, so the parent falls back to the exact risk-gated path below, as if RDD were `off` -- declining a review never lowers the bar below the RDD-off path. `gentle_review`'s `assess` operation accepts an optional `nativeReviewOutcome` (`closed`, `declined`, `unavailable`, or `unknown`) so the caller can state this directly. `closed` is never auto-derived: only a caller that itself just acknowledged the approved review for this exact candidate may pass it, right after that acknowledgement. When `nativeReviewOutcome` is omitted, `assess` only ever tries to auto-derive `declined`/`unavailable`, and only for the exact candidate the event was bound to -- keyed by that candidate's own target identity, never by repository alone, so one candidate's recorded outcome can never leak into a different candidate's `assess` call in the same clone. A missing or mismatched identity fails closed to `unknown`, verified exactly like `off`. The result's `outcome_source` (`explicit`, `derived`, or `unknown`) states which of these produced the value, so a stale or missing derivation is visible rather than silently indistinguishable from a real `unknown`.
|
|
10
|
+
|
|
11
|
+
## Receipt-driven development off or unknown (gentle-pi#662)
|
|
12
|
+
|
|
13
|
+
The host exposes one read-only native operation: `gentle-ai review assess --cwd <repo> [--base-ref <ref> --committed-only] --json` (gentle-ai#4295). It is decoded by `lib/review-risk-assessment.ts` and wired through `lib/native-review-cli.ts` exactly like the existing `reviewMode` STATUS reader -- a bounded subprocess with a typed decode, never a mutation. A non-zero exit, a failure envelope, or an older binary without the verb all fail closed to `high` risk.
|
|
14
|
+
|
|
15
|
+
The `gentle_review` tool's `assess` operation (`extensions/gentle-ai.ts`) combines that assessment with the rendered `Receipt-driven development:` line to decide whether a delegated writer's change needs a separate `gentle-ai-verify` run, following this tier table:
|
|
16
|
+
|
|
17
|
+
| Native risk tier | Verification when RDD is `off`/`unknown` |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| passive | structural readback by the parent; no separate verifier, no tests |
|
|
20
|
+
| medium | writer self-verification stands; a separate `gentle-ai-verify` run is added only when the writer profile is a small model (mini or low effort) |
|
|
21
|
+
| high | writer self-verification plus a separate `gentle-ai-verify` run, always |
|
|
22
|
+
| unknown / assess failed | treated as high |
|
|
23
|
+
|
|
24
|
+
When RDD is `on` and the native review closed for this candidate, the writer's own self-verification is the record and the closed native review is the independent check, except a passive-risk change, which still gets a structural readback instead; any other `nativeReviewOutcome` under `on` follows this same tier table instead (gentle-pi#668). The small-model bias raises the medium tier to high for verification purposes only; an unknown RDD line never lowers a tier below `off`. The parent's own spot check (re-running one reported command before delivery) stays required in every tier.
|
|
25
|
+
|
|
@@ -25,7 +25,7 @@ A typed Pi transport refusal fails closed. The coordinator reports the refusal w
|
|
|
25
25
|
|
|
26
26
|
## Dynamic contract delivery
|
|
27
27
|
|
|
28
|
-
Package static assets intentionally omit lifecycle instructions, candidate routing, recovery procedures, receipt semantics, and any delivery-gate or delivery-authorization behavior. Gentle AI
|
|
28
|
+
Package static assets intentionally omit lifecycle instructions, candidate routing, recovery procedures, receipt semantics, and any delivery-gate or delivery-authorization behavior. Since Gentle AI stopped generating Pi APPEND_SYSTEM composition, Gentle Pi mirrors the provider contract bundle's `orchestration/pi.md` review execution contract locally (`contracts/review-provider-contract-mirror/`) and injects that verified, mirrored text into the primary session's system prompt at session start. Gentle AI writes nothing into the Pi system prompt; the host follows only that mirrored contract. When the mirrored contract is absent or unreadable, Gentle Pi does not invent a fallback; delivery remains ordinary repository policy.
|
|
29
29
|
|
|
30
30
|
## Integration constraints
|
|
31
31
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Telemetry
|
|
2
|
+
|
|
3
|
+
`gentle-pi` does not collect anything itself. [`gentle-ai`](https://github.com/Gentleman-Programming/gentle-ai) (issue [#4309](https://github.com/Gentleman-Programming/gentle-ai/issues/4309)) owns anonymous usage telemetry end to end: install and heartbeat events, the exact fields sent, rate limiting, and every opt-out. See gentle-ai's own README/docs for that contract. Gentle Pi's only involvement is a best-effort nudge that asks the local binary to act.
|
|
4
|
+
|
|
5
|
+
## What Gentle Pi does
|
|
6
|
+
|
|
7
|
+
On activation of a primary session (never for a named agent or an SDD phase executor), Gentle Pi resolves the package-local `gentle-ai` binary (honoring a registered dev-binary override, same as every other native call) and spawns:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
gentle-ai telemetry trigger --json
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- detached, with stdout/stderr discarded (`stdio: "ignore"`);
|
|
14
|
+
- a 3 s deadline: a runaway process is killed, but Gentle Pi never waits for it to exit;
|
|
15
|
+
- at most once per process, regardless of how many sessions or sub-agents run afterward.
|
|
16
|
+
|
|
17
|
+
Rate limiting, enrollment, and every opt-out live entirely in `gentle-ai`; calling the trigger once per session start is safe by construction. A missing binary, an older binary without the `telemetry` verb (which prints `unknown telemetry command` and exits non-zero), or a spawn failure are all treated as "nothing to do" and never affect activation or surface an error to the user.
|
|
18
|
+
|
|
19
|
+
Install counts for `gentle-pi` and `gentle-engram` come from npm download statistics; neither package emits an install event of its own.
|
|
20
|
+
|
|
21
|
+
## The trigger contract
|
|
22
|
+
|
|
23
|
+
`gentle-ai telemetry trigger --json` always exits `0` and prints one line of JSON:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{"schema":"gentle-ai.telemetry-trigger/v1","decision":"enrolled|sent_install|sent_heartbeat|rate_limited|backoff|disabled","source":"<deciding source>"}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`gentle-ai telemetry status|enable|disable|preview [--json]` exist for the opt-out flow; `status --json` prints `gentle-ai.telemetry-status/v1`. Gentle Pi's `/gentle:telemetry` slash command runs these in the foreground (bounded to 5 s) through the same binary resolver and relays the result.
|
|
30
|
+
|
|
31
|
+
## Opting out
|
|
32
|
+
|
|
33
|
+
Any of the following disables the nudge or the underlying telemetry:
|
|
34
|
+
|
|
35
|
+
- `/gentle:telemetry disable` — asks the local `gentle-ai` binary to disable telemetry. `/gentle:telemetry status` and `/gentle:telemetry preview` inspect it without leaving Pi.
|
|
36
|
+
- `DO_NOT_TRACK=1` — Gentle Pi does not spawn the trigger at all; `gentle-ai` also honors this standard independently.
|
|
37
|
+
- `GENTLE_AI_TELEMETRY=0` — same effect, `gentle-ai`'s own environment switch.
|
|
38
|
+
- `CI=true` — Gentle Pi does not spawn the trigger in automated/CI runs, since they are not a real usage signal.
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { DynamicBorder } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
import {
|
|
3
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
4
4
|
import { type Static, Type } from "typebox";
|
|
5
|
+
import { NativeChoiceList } from "../lib/native-choice-list.ts";
|
|
6
|
+
import { createNativeFullscreenInteraction } from "../lib/native-fullscreen-interaction.ts";
|
|
5
7
|
|
|
6
8
|
const CHOICE_TOOL_NAME = "ask_user_choice";
|
|
7
9
|
const ASK_USER_CHOICE_BLOCKED_EVENT = "gentle-pi:ask-user-choice:blocked";
|
|
@@ -72,41 +74,45 @@ export default function askUserChoice(pi: ExtensionAPI): void {
|
|
|
72
74
|
throw new Error("ask_user_choice is unavailable outside the interactive TUI");
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
const items
|
|
76
|
-
|
|
77
|
+
const items = params.options.map((option, index) => ({
|
|
78
|
+
id: `choice-${index}`,
|
|
77
79
|
label: option.label,
|
|
78
80
|
description: option.description,
|
|
79
81
|
}));
|
|
80
82
|
let selection: ChoiceSelection | undefined;
|
|
81
83
|
try {
|
|
82
84
|
pi.events.emit(ASK_USER_CHOICE_BLOCKED_EVENT, { active: true });
|
|
83
|
-
selection = await ctx.ui.custom<ChoiceSelection | undefined>((tui, theme,
|
|
84
|
-
const
|
|
85
|
-
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
86
|
-
container.addChild(new Text(theme.fg("accent", theme.bold(params.question)), 1, 0));
|
|
87
|
-
const list = new SelectList(items, items.length, {
|
|
85
|
+
selection = await ctx.ui.custom<ChoiceSelection | undefined>((tui, theme, keybindings, done) => {
|
|
86
|
+
const list = new NativeChoiceList(items, {
|
|
88
87
|
selectedPrefix: (text) => theme.fg("accent", text),
|
|
89
88
|
selectedText: (text) => theme.fg("accent", text),
|
|
90
89
|
description: (text) => theme.fg("muted", text),
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
hoverBackground: (text) => theme.bg("toolPendingBg", text),
|
|
91
|
+
}, keybindings);
|
|
92
|
+
const container = createNativeFullscreenInteraction({
|
|
93
|
+
keyboardTarget: list,
|
|
94
|
+
requestRender: () => tui.requestRender(),
|
|
95
|
+
mouseObserver: list.createMouseObserver(() => tui.requestRender()),
|
|
93
96
|
});
|
|
97
|
+
let completed = false;
|
|
98
|
+
const finish = (result: ChoiceSelection | undefined) => {
|
|
99
|
+
if (completed) return;
|
|
100
|
+
completed = true;
|
|
101
|
+
list.setDisabled(true);
|
|
102
|
+
done(result);
|
|
103
|
+
};
|
|
94
104
|
list.onSelect = (item) => {
|
|
95
105
|
const index = items.indexOf(item);
|
|
96
|
-
|
|
106
|
+
const option = params.options[index];
|
|
107
|
+
if (option) finish({ value: option.value, label: option.label, index: index + 1 });
|
|
97
108
|
};
|
|
98
|
-
list.onCancel = () =>
|
|
109
|
+
list.onCancel = () => finish(undefined);
|
|
110
|
+
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
111
|
+
container.addChild(new Text(theme.fg("accent", theme.bold(params.question)), 1, 0));
|
|
99
112
|
container.addChild(list);
|
|
100
113
|
container.addChild(new Text(theme.fg("dim", "↑↓ navigate • Enter select • Esc cancel"), 1, 0));
|
|
101
114
|
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
102
|
-
return
|
|
103
|
-
render: (width) => container.render(width),
|
|
104
|
-
invalidate: () => container.invalidate(),
|
|
105
|
-
handleInput: (data) => {
|
|
106
|
-
list.handleInput(data);
|
|
107
|
-
tui.requestRender();
|
|
108
|
-
},
|
|
109
|
-
};
|
|
115
|
+
return container;
|
|
110
116
|
});
|
|
111
117
|
} finally {
|
|
112
118
|
pi.events.emit(ASK_USER_CHOICE_BLOCKED_EVENT, { active: false });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execFile, execFileSync } from "node:child_process";
|
|
2
|
-
import { lstatSync, realpathSync } from "node:fs";
|
|
2
|
+
import { existsSync, lstatSync, readFileSync, realpathSync, statSync } from "node:fs";
|
|
3
3
|
import { homedir, tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
7
7
|
|
|
@@ -177,13 +177,102 @@ function codeGraphFailureMessage(status: CodeGraphStatus): string {
|
|
|
177
177
|
: `CodeGraph failed to run. ${FALLBACK_INSTRUCTIONS}`;
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
function isEnoent(error: unknown): boolean {
|
|
181
|
+
return (
|
|
182
|
+
typeof error === "object" &&
|
|
183
|
+
error !== null &&
|
|
184
|
+
"code" in error &&
|
|
185
|
+
(error as { code?: unknown }).code === "ENOENT"
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Find `codegraph.cmd` shims in Windows PATH order. */
|
|
190
|
+
function* codeGraphCmdPathsOnPath(): Iterable<string> {
|
|
191
|
+
const pathEnv = process.env.Path ?? process.env.PATH ?? "";
|
|
192
|
+
for (const entry of pathEnv.split(";")) {
|
|
193
|
+
const dir = entry.length >= 2 && entry.startsWith('"') && entry.endsWith('"')
|
|
194
|
+
? entry.slice(1, -1)
|
|
195
|
+
: entry;
|
|
196
|
+
if (!dir) continue;
|
|
197
|
+
const candidate = join(dir, "codegraph.cmd");
|
|
198
|
+
if (existsSync(candidate)) yield candidate;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Resolve the actual JS entry of the `@colbymchenry/codegraph` package from the
|
|
204
|
+
* npm global bin dir (sibling of the `codegraph.cmd` shim) and run it with the
|
|
205
|
+
* current Node executable. No shell involvement, so args are never interpreted.
|
|
206
|
+
*/
|
|
207
|
+
export function codeGraphNodeScript(cmdPath: string): string | undefined {
|
|
208
|
+
const binDir = dirname(cmdPath);
|
|
209
|
+
const pkgRoot = join(binDir, "node_modules", "@colbymchenry", "codegraph");
|
|
210
|
+
const pkgJsonPath = join(pkgRoot, "package.json");
|
|
211
|
+
if (!existsSync(pkgJsonPath)) return undefined;
|
|
212
|
+
try {
|
|
213
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf8")) as { bin?: unknown };
|
|
214
|
+
const bin = pkg.bin;
|
|
215
|
+
const target =
|
|
216
|
+
typeof bin === "string"
|
|
217
|
+
? bin
|
|
218
|
+
: bin &&
|
|
219
|
+
typeof bin === "object" &&
|
|
220
|
+
"codegraph" in bin &&
|
|
221
|
+
typeof (bin as Record<string, unknown>).codegraph === "string"
|
|
222
|
+
? (bin as Record<string, string>).codegraph
|
|
223
|
+
: undefined;
|
|
224
|
+
if (typeof target !== "string" || !target) return undefined;
|
|
225
|
+
const script = join(pkgRoot, target);
|
|
226
|
+
return statSync(script).isFile() ? script : undefined;
|
|
227
|
+
} catch {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function* codeGraphNodeScriptsOnPath(): Iterable<string> {
|
|
233
|
+
for (const cmdPath of codeGraphCmdPathsOnPath()) {
|
|
234
|
+
const script = codeGraphNodeScript(cmdPath);
|
|
235
|
+
if (script) yield script;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Resolve the first valid CodeGraph npm entry point in Windows PATH order. */
|
|
240
|
+
export function findCodeGraphNodeScriptOnPath(): string | undefined {
|
|
241
|
+
return codeGraphNodeScriptsOnPath().next().value;
|
|
242
|
+
}
|
|
243
|
+
|
|
180
244
|
const runCodeGraphCommand: CodeGraphRunner = async (args, options) => {
|
|
181
|
-
const
|
|
245
|
+
const runOptions = {
|
|
182
246
|
cwd: options.cwd,
|
|
183
247
|
signal: options.signal,
|
|
184
248
|
maxBuffer: options.maxBuffer,
|
|
185
|
-
}
|
|
186
|
-
|
|
249
|
+
};
|
|
250
|
+
let unavailableError: unknown;
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
const result = await execFileAsync("codegraph", [...args], runOptions);
|
|
254
|
+
return { stdout: result.stdout, stderr: result.stderr };
|
|
255
|
+
} catch (error) {
|
|
256
|
+
// Windows npm installs only shims (codegraph.cmd/.ps1 and a shell script)
|
|
257
|
+
// with no codegraph.exe, so plain execFile (CreateProcess, no shell)
|
|
258
|
+
// always fails with ENOENT even when the shim is on PATH.
|
|
259
|
+
if (process.platform !== "win32" || !isEnoent(error)) throw error;
|
|
260
|
+
unavailableError = error;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Resolve the real package script from each npm global bin directory in PATH
|
|
264
|
+
// order and run it through the current Node executable. This remains
|
|
265
|
+
// shell-free, so argument boundaries are never interpreted by a shell.
|
|
266
|
+
for (const script of codeGraphNodeScriptsOnPath()) {
|
|
267
|
+
try {
|
|
268
|
+
const result = await execFileAsync(process.execPath, [script, ...args], runOptions);
|
|
269
|
+
return { stdout: result.stdout, stderr: result.stderr };
|
|
270
|
+
} catch (innerError) {
|
|
271
|
+
if (!isEnoent(innerError)) throw innerError;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
throw unavailableError;
|
|
187
276
|
};
|
|
188
277
|
|
|
189
278
|
export function createCodeGraphTool(runner: CodeGraphRunner = runCodeGraphCommand) {
|