gentle-pi 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -79
- package/assets/agents/review-readability.md +0 -2
- package/assets/agents/review-reliability.md +0 -2
- package/assets/agents/review-resilience.md +0 -2
- package/assets/agents/review-risk.md +0 -2
- package/assets/agents/sdd-apply.md +5 -3
- package/assets/agents/sdd-explore.md +1 -0
- package/assets/agents/sdd-proposal.md +2 -0
- package/assets/agents/sdd-research.md +54 -0
- package/assets/agents/sdd-status.md +4 -4
- package/assets/agents/sdd-tasks.md +2 -3
- package/assets/agents/sdd-verify.md +21 -1
- package/assets/chains/sdd-full.chain.md +1 -1
- package/assets/chains/sdd-verify.chain.md +1 -1
- package/assets/orchestrator-delegation.md +56 -249
- package/assets/orchestrator-memory.md +2 -0
- package/assets/orchestrator.md +20 -40
- package/assets/sdd-orchestrator-workflow.md +30 -37
- package/assets/support/sdd-status-contract.md +7 -7
- package/contracts/review-integration/v1/schemas/transition-execution.schema.json +42 -0
- package/contracts/review-integration/v2/schemas/last-event-closure.schema.json +66 -0
- package/contracts/review-integration/v2/schemas/opencode-provider-role.schema.json +14 -0
- 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/native-authority-architecture.md +9 -11
- package/docs/review-integration.md +27 -373
- package/extensions/ask-user-choice.ts +151 -0
- package/extensions/gentle-ai.ts +2349 -3785
- package/extensions/quiet-tools.ts +515 -32
- package/extensions/sdd-init.ts +4 -8
- package/lib/agent-home.ts +8 -0
- package/lib/gentle-ai-renderer.ts +70 -0
- package/lib/model-routing-authority.ts +133 -0
- package/lib/native-review-cli.ts +387 -940
- package/lib/opaque-pi-reviewer-adapter.ts +284 -0
- package/lib/provider-contract-bundle.ts +88 -6
- package/lib/review-candidate-view.ts +341 -132
- package/lib/review-host-relay.ts +324 -96
- package/lib/review-integration-v2.ts +868 -307
- package/lib/review-last-event-controller.ts +35 -0
- package/lib/sdd-preflight.ts +179 -68
- package/lib/sdd-status.ts +66 -111
- package/lib/terminal-theme.ts +1 -1
- package/package.json +83 -82
- package/runtime/gentle-ai-binary.mjs +1 -1
- package/runtime/native-review-cli.mjs +337 -890
- package/runtime/review-integration-v2.mjs +816 -255
- package/runtime/review-relay-contract.mjs +1 -1
- package/scripts/{build-git-commit-transaction-runner.mjs → build-runtime-modules.mjs} +4 -5
- package/scripts/gentle-ai-installer.mjs +75 -22
- package/scripts/maintainer/provider-relay-matrix.mjs +195 -11
- package/scripts/test-packed-runner.mjs +4 -7
- package/scripts/verify-package-files.mjs +23 -23
- package/skills/_shared/review-ledger-contract.md +14 -14
- package/skills/chained-pr/SKILL.md +3 -0
- package/skills/cognitive-doc-design/SKILL.md +1 -1
- package/skills/comment-writer/SKILL.md +1 -1
- package/skills/gentle-ai/SKILL.md +7 -74
- package/skills/issue-creation/SKILL.md +53 -93
- package/skills/judgment-day/SKILL.md +5 -7
- package/skills/rdd-defect-workflow/SKILL.md +3 -3
- package/skills/release/SKILL.md +3 -3
- package/skills/skill-registry/SKILL.md +1 -1
- package/skills/work-unit-commits/SKILL.md +3 -1
- package/tests/artifact-language.test.ts +24 -11
- package/tests/ask-user-choice.test.ts +264 -0
- package/tests/codegraph-tools.test.ts +3 -3
- package/tests/crosslane/cross-lane.mjs +15 -1168
- package/tests/delegated-key-learnings-contract.test.ts +8 -6
- package/tests/devbinary/native-review-parity.devtest.ts +167 -250
- package/tests/devbinary/pi-host-relay.devtest.ts +867 -0
- package/tests/fixtures/devbinary/last-event-capture-correction-plan.captured.json +10 -0
- package/tests/fixtures/devbinary/last-event-capture-refuter-approved.captured.json +20 -0
- package/tests/fixtures/devbinary/last-event-capture-refuter-correction-required.captured.json +26 -0
- package/tests/fixtures/devbinary/last-event-capture-result-approved.captured.json +8 -0
- package/tests/fixtures/devbinary/last-event-capture-result-correction-required.captured.json +26 -0
- package/tests/fixtures/devbinary/last-event-capture-validation-approved.captured.json +8 -0
- package/tests/fixtures/devbinary/last-event-closure.provenance.md +13 -0
- package/tests/fixtures/devbinary/review-acknowledged-v1.captured.json +9 -0
- package/tests/fixtures/devbinary/review-acknowledged.provenance.md +31 -0
- package/tests/fixtures/devbinary/start-v3-zero-lens-closed.captured.json +21 -0
- package/tests/fixtures/native-review-cli/v2.5.0-rc.1/PROVENANCE.txt +15 -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-ai-binary.test.ts +83 -13
- package/tests/gentle-ai-dev-binary.test.ts +51 -228
- package/tests/gentle-ai-installer.test.ts +137 -47
- package/tests/gentle-ai.test.ts +821 -131
- package/tests/gentle-theme.test.ts +133 -0
- package/tests/issue-creation-skill.test.ts +103 -0
- package/tests/maintainer/provider-relay.maintest.ts +340 -4
- package/tests/model-routing-authority.test.ts +257 -0
- package/tests/native-review-capability-contract.test.ts +45 -1
- package/tests/native-review-cli.test.ts +586 -962
- package/tests/native-review-consent.test.ts +150 -66
- package/tests/native-review-parity-runtime.test.ts +80 -359
- package/tests/native-review-parity.test.ts +674 -925
- package/tests/opaque-pi-reviewer-adapter.test.ts +266 -0
- package/tests/orchestrator-budget.test.ts +132 -88
- package/tests/orchestrator-rdd-ownership.test.ts +103 -0
- package/tests/package-manifest.test.ts +152 -36
- package/tests/provider-contract-bundle.test.ts +76 -0
- package/tests/provider-contract-mirror.test.ts +19 -0
- package/tests/provider-defect-handoff.test.ts +15 -118
- package/tests/quiet-tool-rendering.test.ts +1055 -28
- package/tests/review-agent-end-preflight.test.ts +408 -0
- package/tests/review-authority-recovery-docs.test.ts +1 -2
- package/tests/review-candidate-view.test.ts +409 -3
- package/tests/review-compact-contract.test.ts +4 -30
- package/tests/review-contract-prompt.test.ts +139 -0
- package/tests/review-controller-lock-status.test.ts +2 -2
- package/tests/review-controller-native-recovery.test.ts +363 -1450
- package/tests/review-controller-native-routing.test.ts +1251 -5642
- package/tests/review-controller-retired-ops.test.ts +1 -1
- package/tests/review-controller-workspace-root.test.ts +222 -71
- package/tests/review-controller.test.ts +26 -816
- package/tests/review-corrected-finalize-binding.test.ts +112 -153
- package/tests/review-dispatch-hydration-gap.test.ts +1 -53
- package/tests/review-gate.test.ts +0 -45
- package/tests/review-host-relay-restart-parity.test.ts +360 -0
- package/tests/review-host-relay-routing.test.ts +328 -91
- package/tests/review-host-relay.test.ts +436 -14
- package/tests/review-integration-v2-forward.test.ts +623 -553
- package/tests/review-integration-v2.test.ts +239 -165
- package/tests/review-last-event-closure.test.ts +408 -0
- package/tests/review-ledger-contract.test.ts +97 -35
- package/tests/review-recovered-lineage-routing.test.ts +0 -47
- package/tests/review-relay-transport-agent.test.ts +119 -46
- package/tests/review-snapshot.test.ts +3 -2
- package/tests/runtime-harness.mjs +362 -196
- package/tests/sdd-agent-tools.test.ts +36 -0
- package/tests/sdd-preflight.test.ts +81 -15
- package/tests/sdd-status.test.ts +109 -110
- package/tests/skill-collision-prefixes.test.ts +5 -8
- package/tests/writer-edit-surface-scope.test.ts +366 -0
- package/themes/Gentleman-Cute.json +94 -0
- package/themes/Gentleman-Sexy.json +92 -0
- package/lib/git-commit-transaction.ts +0 -861
- package/runtime/git-commit-transaction.mjs +0 -862
- package/scripts/run-git-commit-transaction.mjs +0 -35
- package/tests/fixtures/native-review-cli/v2.1.3/sdd-status-engram.json +0 -139
- package/tests/fixtures/native-review-cli/v2.1.3/sdd-status.json +0 -200
- package/tests/git-commit-transaction.test.ts +0 -530
- /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
|
@@ -13,7 +13,7 @@ function scratchDir(prefix: string): string {
|
|
|
13
13
|
|
|
14
14
|
async function runRetiredOperation(parameters: Record<string, unknown>): Promise<Record<string, unknown>> {
|
|
15
15
|
const cwd = scratchDir("gentle-pi-retired-ops-");
|
|
16
|
-
return await __testing.executeReviewControllerOperation(parameters, cwd,
|
|
16
|
+
return await __testing.executeReviewControllerOperation(parameters, cwd, null);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function assertRetiredEnvelope(details: Record<string, unknown>, operation: string): void {
|
|
@@ -5,7 +5,7 @@ import { tmpdir } from "node:os";
|
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import test from "node:test";
|
|
7
7
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
8
|
-
import { createGentleAiExtension } from "../extensions/gentle-ai.ts";
|
|
8
|
+
import { __testing, createGentleAiExtension } from "../extensions/gentle-ai.ts";
|
|
9
9
|
import type { NativeReviewCli } from "../lib/native-review-cli.ts";
|
|
10
10
|
import { CandidateViewRegistry } from "../lib/review-candidate-view.ts";
|
|
11
11
|
import type { AuthorityRepairAssessmentV1, ReviewStatusV3 } from "../lib/review-integration-v2.ts";
|
|
@@ -55,8 +55,13 @@ function context(cwd: string): ExtensionContext {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
function repository(t: test.TestContext, prefix = "gentle-pi-workspace-root-"): string {
|
|
58
|
-
const cwd = mkdtempSync(join(tmpdir(), prefix));
|
|
59
|
-
t.after(() =>
|
|
58
|
+
const cwd = realpathSync(mkdtempSync(join(tmpdir(), prefix)));
|
|
59
|
+
t.after(() => {
|
|
60
|
+
if (process.platform !== "win32") {
|
|
61
|
+
try { execFileSync("chmod", ["-R", "u+w", cwd], { stdio: "ignore" }); } catch { /* best effort */ }
|
|
62
|
+
}
|
|
63
|
+
rmSync(cwd, { recursive: true, force: true });
|
|
64
|
+
});
|
|
60
65
|
execFileSync("git", ["init", "-b", "main"], { cwd });
|
|
61
66
|
writeFileSync(join(cwd, "app.ts"), "export const value = 1;\n");
|
|
62
67
|
execFileSync("git", ["add", "."], { cwd });
|
|
@@ -65,7 +70,7 @@ function repository(t: test.TestContext, prefix = "gentle-pi-workspace-root-"):
|
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
function addWorktree(t: test.TestContext, cwd: string, branch: string): string {
|
|
68
|
-
const parent = mkdtempSync(join(tmpdir(), "gentle-pi-workspace-worktrees-"));
|
|
73
|
+
const parent = realpathSync(mkdtempSync(join(tmpdir(), "gentle-pi-workspace-worktrees-")));
|
|
69
74
|
t.after(() => {
|
|
70
75
|
try { execFileSync("git", ["worktree", "remove", "--force", join(parent, branch)], { cwd }); } catch {}
|
|
71
76
|
rmSync(parent, { recursive: true, force: true });
|
|
@@ -78,14 +83,11 @@ function addWorktree(t: test.TestContext, cwd: string, branch: string): string {
|
|
|
78
83
|
function fakeNative(overrides: Partial<NativeReviewCli> = {}): NativeReviewCli {
|
|
79
84
|
return {
|
|
80
85
|
start: async () => ({ lineageId: "native-lineage", state: "reviewing", riskLevel: "medium", selectedLenses: ["review-reliability"], changedFiles: 2, changedLines: 7, correctionBudget: 4, action: "created", lensesRequired: true }),
|
|
81
|
-
finalize: async () => ({ lineageId: "native-lineage", state: "approved", action: "approved", storeRevision: "r1", receiptPath: "/opaque/receipt" }),
|
|
82
|
-
validate: async () => { throw new Error("validate is not expected in workspace-root tests"); },
|
|
83
|
-
bindSdd: async () => { throw new Error("bind-sdd is not expected in workspace-root tests"); },
|
|
84
86
|
sddStatus: async () => ({ ready: false }),
|
|
85
87
|
reviewStatus: async () => ({ schema: "gentle-ai.review-authority-status/v1", repository: "/repo", complete: true, authoritative: true, status: "clean", entries: [], locks: [], diagnostics: [], raw: { schema: "gentle-ai.review-authority-status/v1", operation: "review/status", repository: "/repo", complete: true, authoritative: true, status: "clean", entries: [], locks: [], diagnostics: [] } }),
|
|
86
88
|
targetStatus: async (request) => request.lineageId === undefined
|
|
87
89
|
? candidateStartTargetStatus(request)
|
|
88
|
-
:
|
|
90
|
+
: candidateTargetStatus(request, request.lineageId),
|
|
89
91
|
...overrides,
|
|
90
92
|
};
|
|
91
93
|
}
|
|
@@ -102,17 +104,20 @@ function targetStatusFixture(options: {
|
|
|
102
104
|
applicability?: "current_target" | "unrelated";
|
|
103
105
|
action?: ReviewStatusV3["action"];
|
|
104
106
|
lineageId?: string;
|
|
107
|
+
authorityState?: NonNullable<ReviewStatusV3["authority"]>["state"];
|
|
105
108
|
baseTree?: string;
|
|
106
109
|
currentCandidateTree?: string;
|
|
107
110
|
paths?: readonly string[];
|
|
111
|
+
intendedUntracked?: readonly string[];
|
|
108
112
|
} = {}): ReviewStatusV3 {
|
|
109
113
|
const applicability = options.applicability ?? "current_target";
|
|
110
|
-
const action = options.action ?? (applicability === "current_target" ? "
|
|
111
|
-
const
|
|
114
|
+
const action = options.action ?? (applicability === "current_target" ? "stop" : "start"); const lineageId = options.lineageId ?? "native-lineage";
|
|
115
|
+
const authorityState = options.authorityState ?? "reviewing";
|
|
112
116
|
const sha = `sha256:${"a".repeat(64)}`;
|
|
113
117
|
const tree = options.currentCandidateTree ?? "b".repeat(40);
|
|
114
118
|
const baseTree = options.baseTree ?? tree;
|
|
115
119
|
const paths = options.paths ?? ["app.ts"];
|
|
120
|
+
const intendedUntracked = options.intendedUntracked ?? [];
|
|
116
121
|
const projection = {
|
|
117
122
|
schema: "gentle-ai.review-integration.projection/v1" as const,
|
|
118
123
|
kind: "current-changes" as const,
|
|
@@ -122,7 +127,7 @@ function targetStatusFixture(options: {
|
|
|
122
127
|
currentCandidateTree: tree,
|
|
123
128
|
pathsDigest: sha,
|
|
124
129
|
paths,
|
|
125
|
-
intendedUntracked
|
|
130
|
+
intendedUntracked,
|
|
126
131
|
intendedUntrackedProof: sha,
|
|
127
132
|
initialSnapshotIdentity: sha,
|
|
128
133
|
currentSnapshotIdentity: sha,
|
|
@@ -156,7 +161,7 @@ function targetStatusFixture(options: {
|
|
|
156
161
|
current_candidate_tree: tree,
|
|
157
162
|
paths_digest: sha,
|
|
158
163
|
paths,
|
|
159
|
-
intended_untracked:
|
|
164
|
+
intended_untracked: intendedUntracked,
|
|
160
165
|
intended_untracked_proof: sha,
|
|
161
166
|
initial_snapshot_identity: sha,
|
|
162
167
|
current_snapshot_identity: sha,
|
|
@@ -164,13 +169,13 @@ function targetStatusFixture(options: {
|
|
|
164
169
|
candidates: [],
|
|
165
170
|
};
|
|
166
171
|
if (applicability === "current_target") {
|
|
167
|
-
raw.authority = { version: "compact-v2", lineage_id: lineageId, state:
|
|
172
|
+
raw.authority = { version: "compact-v2", lineage_id: lineageId, state: authorityState, generation: 1, revision: sha };
|
|
168
173
|
raw.frozen = { tier: "medium", original_changed_lines: 2, correction_budget: 1 };
|
|
169
174
|
}
|
|
170
175
|
return {
|
|
171
176
|
contract: "gentle-ai.review-integration/v2",
|
|
172
177
|
applicability,
|
|
173
|
-
...(applicability === "current_target" ? { authority: { version: "compact-v2" as const, lineageId, state:
|
|
178
|
+
...(applicability === "current_target" ? { authority: { version: "compact-v2" as const, lineageId, state: authorityState, generation: 1, revision: sha } } : {}),
|
|
174
179
|
receipt: { status: applicability === "current_target" ? "expected_missing" : "not_applicable" },
|
|
175
180
|
action,
|
|
176
181
|
replayability: "not_replayable",
|
|
@@ -189,6 +194,7 @@ function candidateStartTargetStatus(request: Parameters<NonNullable<NativeReview
|
|
|
189
194
|
candidate = new CandidateViewRegistry().create({
|
|
190
195
|
contributorRoot: request.cwd,
|
|
191
196
|
...(request.baseRef === undefined ? {} : { baseRef: request.baseRef, committedOnly: true }),
|
|
197
|
+
...(request.intendedUntracked === undefined ? {} : { intendedUntracked: request.intendedUntracked }),
|
|
192
198
|
});
|
|
193
199
|
return targetStatusFixture({
|
|
194
200
|
applicability: "unrelated",
|
|
@@ -196,13 +202,14 @@ function candidateStartTargetStatus(request: Parameters<NonNullable<NativeReview
|
|
|
196
202
|
baseTree: candidate.baseTree,
|
|
197
203
|
currentCandidateTree: candidate.candidateTree,
|
|
198
204
|
paths: candidate.paths,
|
|
205
|
+
intendedUntracked: request.intendedUntracked,
|
|
199
206
|
});
|
|
200
207
|
} finally {
|
|
201
208
|
candidate?.cleanup();
|
|
202
209
|
}
|
|
203
210
|
}
|
|
204
211
|
|
|
205
|
-
function
|
|
212
|
+
function candidateTargetStatus(request: Parameters<NonNullable<NativeReviewCli["targetStatus"]>>[0], lineageId: string): ReviewStatusV3 {
|
|
206
213
|
let candidate: ReturnType<CandidateViewRegistry["create"]> | undefined;
|
|
207
214
|
try {
|
|
208
215
|
candidate = new CandidateViewRegistry().create({ contributorRoot: request.cwd });
|
|
@@ -270,39 +277,164 @@ test("START freezes the candidate from the explicit workspace root and returns t
|
|
|
270
277
|
candidateViews.cleanup(view.token);
|
|
271
278
|
});
|
|
272
279
|
|
|
273
|
-
test("
|
|
274
|
-
const
|
|
275
|
-
|
|
280
|
+
test("an omitted workspaceRoot canonicalizes a nested same-worktree session before supported operations", async (t) => {
|
|
281
|
+
const root = repository(t);
|
|
282
|
+
const sessionCwd = join(root, "nested");
|
|
283
|
+
mkdirSync(sessionCwd);
|
|
284
|
+
writeFileSync(join(root, "app.ts"), "export const value = 3;\n");
|
|
276
285
|
const candidateViews = new CandidateViewRegistry();
|
|
286
|
+
const targetStatusCwds: string[] = [];
|
|
277
287
|
const startRequests: Parameters<NativeReviewCli["start"]>[0][] = [];
|
|
278
288
|
const { controller } = runtime(fakeNative({
|
|
289
|
+
targetStatus: async (request) => { targetStatusCwds.push(request.cwd); return request.lineageId === undefined ? candidateStartTargetStatus(request) : candidateTargetStatus(request, request.lineageId); },
|
|
279
290
|
start: async (request) => {
|
|
280
291
|
startRequests.push(request);
|
|
281
292
|
return { lineageId: "session-lineage", state: "reviewing", riskLevel: "medium", selectedLenses: ["review-reliability"], changedFiles: 1, changedLines: 1, correctionBudget: 1, action: "created", lensesRequired: true };
|
|
282
293
|
},
|
|
283
294
|
}), candidateViews);
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
};
|
|
289
|
-
assert.equal(details.workspace_root,
|
|
290
|
-
assert.equal(details.actor_binding.workspace_root,
|
|
295
|
+
const ctx = context(sessionCwd);
|
|
296
|
+
await controller.execute("inspect-session", { operation: "inspect" }, undefined, undefined, ctx);
|
|
297
|
+
const started = await controller.execute("start-session", { operation: "start", input: JSON.stringify({ mode: "ordinary" }) }, undefined, undefined, ctx);
|
|
298
|
+
const status = await controller.execute("status-session", { operation: "status", lineageId: "session-lineage" }, undefined, undefined, ctx);
|
|
299
|
+
const details = started.details as { workspace_root: string; actor_binding: { workspace_root: string; candidate_root: string; candidate_paths: readonly string[] } };
|
|
300
|
+
assert.equal(details.workspace_root, root);
|
|
301
|
+
assert.equal(details.actor_binding.workspace_root, root);
|
|
302
|
+
assert.equal((status.details as { workspace_root: string }).workspace_root, root);
|
|
291
303
|
assert.deepEqual(details.actor_binding.candidate_paths, ["app.ts"]);
|
|
292
|
-
assert.deepEqual(startRequests, [{ cwd:
|
|
304
|
+
assert.deepEqual(startRequests, [{ cwd: root, targetIdentity: `sha256:${"a".repeat(64)}`, projection: "workspace" }]);
|
|
293
305
|
const view = candidateViews.resolveForLens("session-lineage", "review-reliability");
|
|
294
306
|
assert.equal(details.actor_binding.candidate_root, view.root);
|
|
307
|
+
assert.ok(targetStatusCwds.every((cwd) => cwd === root), JSON.stringify(targetStatusCwds));
|
|
295
308
|
candidateViews.cleanup(view.token);
|
|
296
309
|
});
|
|
297
310
|
|
|
298
|
-
test("
|
|
311
|
+
test("an explicit nested foreign workspace root canonically owns inspect, status, and START", async (t) => {
|
|
312
|
+
const sessionCwd = repository(t, "gentle-pi-session-a-");
|
|
313
|
+
const target = repository(t, "gentle-pi-target-b-");
|
|
314
|
+
const nested = join(target, "nested");
|
|
315
|
+
mkdirSync(nested);
|
|
316
|
+
writeFileSync(join(target, "app.ts"), "export const value = 2; // target B\n");
|
|
317
|
+
const root = realpathSync(target);
|
|
318
|
+
const targetStatusCwds: string[] = [];
|
|
319
|
+
const targetStatusRequests: Parameters<NonNullable<NativeReviewCli["targetStatus"]>>[0][] = [];
|
|
320
|
+
const startCwds: string[] = [];
|
|
321
|
+
const candidateViews = new CandidateViewRegistry();
|
|
322
|
+
let initialCrossRootStatus = true;
|
|
323
|
+
const { controller } = runtime(fakeNative({
|
|
324
|
+
targetStatus: async (request) => {
|
|
325
|
+
targetStatusCwds.push(request.cwd);
|
|
326
|
+
targetStatusRequests.push(request);
|
|
327
|
+
if (request.lineageId === "cross-root-lineage" && initialCrossRootStatus) {
|
|
328
|
+
initialCrossRootStatus = false;
|
|
329
|
+
return request.untrackedScope === "select"
|
|
330
|
+
? targetStatusFixture({ lineageId: request.lineageId, authorityState: "correction_required" })
|
|
331
|
+
: targetStatusFixture({ applicability: "unrelated", action: "start" });
|
|
332
|
+
}
|
|
333
|
+
return request.lineageId === undefined
|
|
334
|
+
? candidateStartTargetStatus(request)
|
|
335
|
+
: candidateTargetStatus(request, request.lineageId);
|
|
336
|
+
},
|
|
337
|
+
start: async (request) => {
|
|
338
|
+
startCwds.push(request.cwd);
|
|
339
|
+
return { lineageId: "cross-root-lineage", state: "reviewing", riskLevel: "medium", selectedLenses: ["review-reliability"], changedFiles: 1, changedLines: 1, correctionBudget: 1, action: "created", lensesRequired: true };
|
|
340
|
+
},
|
|
341
|
+
}), candidateViews);
|
|
342
|
+
|
|
343
|
+
const digest = `sha256:${"b".repeat(64)}`;
|
|
344
|
+
const intendedUntracked = ["selected-b.ts", "selected-a.ts"];
|
|
345
|
+
const inspect = await controller.execute("inspect-target-b", { operation: "inspect", workspaceRoot: nested }, undefined, undefined, context(sessionCwd));
|
|
346
|
+
const status = await controller.execute("status-target-b", { operation: "status", lineageId: "cross-root-lineage", workspaceRoot: nested, input: JSON.stringify({ untrackedScope: "select", expectedUntrackedInventory: digest, intendedUntracked }) }, undefined, undefined, context(sessionCwd));
|
|
347
|
+
const started = await controller.execute("start-target-b", { operation: "start", workspaceRoot: nested, input: JSON.stringify({ mode: "ordinary" }) }, undefined, undefined, context(sessionCwd));
|
|
348
|
+
|
|
349
|
+
for (const result of [inspect, status, started]) {
|
|
350
|
+
assert.equal((result.details as { workspace_root?: string }).workspace_root, root);
|
|
351
|
+
}
|
|
352
|
+
const statusDetails = status.details as { status?: string; result?: { authority?: { state?: string } } };
|
|
353
|
+
assert.equal(statusDetails.status, "blocked");
|
|
354
|
+
assert.equal(statusDetails.result?.authority?.state, "correction_required");
|
|
355
|
+
assert.deepEqual(targetStatusRequests[1], { cwd: root, lineageId: "cross-root-lineage", untrackedScope: "select", expectedUntrackedInventory: digest, intendedUntracked, agent: "pi" });
|
|
356
|
+
assert.ok(targetStatusCwds.every((cwd) => cwd === root), JSON.stringify(targetStatusCwds));
|
|
357
|
+
assert.deepEqual(startCwds, [root]);
|
|
358
|
+
candidateViews.cleanup(candidateViews.resolveForLens("cross-root-lineage", "review-reliability").token);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
test("STATUS relays exclude selection and rejects malformed input before native status", async (t) => {
|
|
363
|
+
const cwd = repository(t);
|
|
364
|
+
const requests: Parameters<NonNullable<NativeReviewCli["targetStatus"]>>[0][] = [];
|
|
365
|
+
const { controller } = runtime(fakeNative({
|
|
366
|
+
targetStatus: async (request) => {
|
|
367
|
+
requests.push(request);
|
|
368
|
+
return targetStatusFixture({ lineageId: request.lineageId });
|
|
369
|
+
},
|
|
370
|
+
}));
|
|
371
|
+
const digest = `sha256:${"c".repeat(64)}`;
|
|
372
|
+
const excluded = await controller.execute("status-exclude", { operation: "status", lineageId: "excluded-lineage", input: JSON.stringify({ untrackedScope: "exclude", expectedUntrackedInventory: digest, intendedUntracked: [] }) }, undefined, undefined, context(cwd));
|
|
373
|
+
assert.equal((excluded.details as { status?: string }).status, "blocked");
|
|
374
|
+
assert.deepEqual(requests, [{ cwd, lineageId: "excluded-lineage", untrackedScope: "exclude", expectedUntrackedInventory: digest, intendedUntracked: [], agent: "pi" }]);
|
|
375
|
+
|
|
376
|
+
for (const [input, reason] of [
|
|
377
|
+
[{}, "untracked-selection-invalid"],
|
|
378
|
+
[{ untrackedScope: "select", expectedUntrackedInventory: digest, intendedUntracked: [] }, "untracked-selection-invalid"],
|
|
379
|
+
[{ untrackedScope: "select" }, "untracked-selection-invalid"],
|
|
380
|
+
[{ untrackedScope: "exclude", expectedUntrackedInventory: digest, intendedUntracked: [], unexpected: true }, "unknown-field"],
|
|
381
|
+
] as const) {
|
|
382
|
+
const rejected = await controller.execute("status-invalid", { operation: "status", input: JSON.stringify(input) }, undefined, undefined, context(cwd));
|
|
383
|
+
assert.equal((rejected.details as { reason?: string }).reason, reason);
|
|
384
|
+
}
|
|
385
|
+
assert.equal(requests.length, 1);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
test("an explicit foreign workspace root works when the Pi session cwd is not a Git repository", async (t) => {
|
|
389
|
+
const target = repository(t, "gentle-pi-target-b-non-git-session-");
|
|
390
|
+
const nested = join(target, "nested");
|
|
391
|
+
mkdirSync(nested);
|
|
392
|
+
const nonGit = realpathSync(mkdtempSync(join(tmpdir(), "gentle-pi-non-git-session-")));
|
|
393
|
+
t.after(() => rmSync(nonGit, { recursive: true, force: true }));
|
|
394
|
+
const observed: string[] = [];
|
|
395
|
+
const { controller } = runtime(fakeNative({
|
|
396
|
+
targetStatus: async (request) => {
|
|
397
|
+
observed.push(request.cwd);
|
|
398
|
+
return targetStatusFixture();
|
|
399
|
+
},
|
|
400
|
+
}));
|
|
401
|
+
const result = await controller.execute("inspect-target-from-non-git", { operation: "inspect", workspaceRoot: nested }, undefined, undefined, context(nonGit));
|
|
402
|
+
assert.equal((result.details as { workspace_root?: string }).workspace_root, realpathSync(target));
|
|
403
|
+
assert.deepEqual(observed, [realpathSync(target)]);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test("omitted workspaceRoot fails closed for the same lineage bound to two target repositories", async (t) => {
|
|
407
|
+
const rootA = repository(t, "gentle-pi-ambiguous-a-");
|
|
408
|
+
const rootB = repository(t, "gentle-pi-ambiguous-b-");
|
|
409
|
+
const candidateViews = new CandidateViewRegistry();
|
|
410
|
+
const viewA = candidateViews.create({ contributorRoot: rootA });
|
|
411
|
+
const viewB = candidateViews.create({ contributorRoot: rootB });
|
|
412
|
+
candidateViews.bindCurrent({ token: viewA.token, lineageId: "shared-lineage", selectedLenses: ["review-reliability"] });
|
|
413
|
+
candidateViews.bindCurrent({ token: viewB.token, lineageId: "shared-lineage", selectedLenses: ["review-reliability"] });
|
|
414
|
+
let nativeCalls = 0;
|
|
415
|
+
const { controller } = runtime(fakeNative({
|
|
416
|
+
targetStatus: async (request) => {
|
|
417
|
+
nativeCalls += 1;
|
|
418
|
+
return targetStatusFixture({ lineageId: request.lineageId });
|
|
419
|
+
},
|
|
420
|
+
}), candidateViews);
|
|
421
|
+
await assert.rejects(
|
|
422
|
+
controller.execute("ambiguous-omission", { operation: "status", lineageId: "shared-lineage" }, undefined, undefined, context(rootA)),
|
|
423
|
+
/workspaceRoot/,
|
|
424
|
+
);
|
|
425
|
+
assert.equal(nativeCalls, 0);
|
|
426
|
+
const explicit = await controller.execute("explicit-b", { operation: "status", lineageId: "shared-lineage", workspaceRoot: rootB }, undefined, undefined, context(rootA));
|
|
427
|
+
assert.equal((explicit.details as { workspace_root?: string }).workspace_root, realpathSync(rootB));
|
|
428
|
+
assert.equal(nativeCalls, 1);
|
|
429
|
+
candidateViews.cleanupTerminal("shared-lineage", "approved", rootA);
|
|
430
|
+
candidateViews.cleanupTerminal("shared-lineage", "approved", rootB);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test("workspaceRoot fails closed before any native call for invalid target paths", async (t) => {
|
|
299
434
|
const sessionCwd = repository(t);
|
|
300
435
|
const worktree = addWorktree(t, sessionCwd, "feat-guard");
|
|
301
|
-
const
|
|
302
|
-
const nonGit = mkdtempSync(join(tmpdir(), "gentle-pi-non-git-"));
|
|
436
|
+
const nonGit = realpathSync(mkdtempSync(join(tmpdir(), "gentle-pi-non-git-")));
|
|
303
437
|
t.after(() => rmSync(nonGit, { recursive: true, force: true }));
|
|
304
|
-
const nested = join(worktree, "nested");
|
|
305
|
-
mkdirSync(nested);
|
|
306
438
|
const filePath = join(worktree, "app.ts");
|
|
307
439
|
let nativeCalls = 0;
|
|
308
440
|
const counting = fakeNative({
|
|
@@ -311,16 +443,13 @@ test("workspaceRoot fails closed before any native call for non-worktree and for
|
|
|
311
443
|
reviewStatus: async () => { nativeCalls += 1; throw new Error("native review status must not run"); },
|
|
312
444
|
});
|
|
313
445
|
const { controller } = runtime(counting);
|
|
314
|
-
const rejected: Array<{ label: string; workspaceRoot: string
|
|
315
|
-
{ label: "non-git directory", workspaceRoot: nonGit
|
|
316
|
-
{ label: "missing directory", workspaceRoot: join(nonGit, "missing")
|
|
317
|
-
{ label: "file path", workspaceRoot: filePath
|
|
318
|
-
{ label: "relative path", workspaceRoot: "relative/worktree"
|
|
319
|
-
{ label: "worktree subdirectory", workspaceRoot: nested, ctx: sessionCwd },
|
|
320
|
-
{ label: "foreign repository", workspaceRoot: foreign, ctx: sessionCwd },
|
|
321
|
-
{ label: "session outside a repository", workspaceRoot: worktree, ctx: nonGit },
|
|
446
|
+
const rejected: Array<{ label: string; workspaceRoot: string }> = [
|
|
447
|
+
{ label: "non-git directory", workspaceRoot: nonGit },
|
|
448
|
+
{ label: "missing directory", workspaceRoot: join(nonGit, "missing") },
|
|
449
|
+
{ label: "file path", workspaceRoot: filePath },
|
|
450
|
+
{ label: "relative path", workspaceRoot: "relative/worktree" },
|
|
322
451
|
];
|
|
323
|
-
for (const { label, workspaceRoot
|
|
452
|
+
for (const { label, workspaceRoot } of rejected) {
|
|
324
453
|
for (const operation of ["inspect", "start", "status"] as const) {
|
|
325
454
|
await assert.rejects(
|
|
326
455
|
controller.execute(`${operation}-${label}`, {
|
|
@@ -328,7 +457,7 @@ test("workspaceRoot fails closed before any native call for non-worktree and for
|
|
|
328
457
|
...(operation === "start" ? { input: JSON.stringify({ mode: "ordinary" }) } : {}),
|
|
329
458
|
...(operation === "status" ? { lineageId: "native-lineage" } : {}),
|
|
330
459
|
workspaceRoot,
|
|
331
|
-
}, undefined, undefined, context(
|
|
460
|
+
}, undefined, undefined, context(sessionCwd)),
|
|
332
461
|
/workspaceRoot/,
|
|
333
462
|
`${operation} must fail closed for ${label}`,
|
|
334
463
|
);
|
|
@@ -337,40 +466,62 @@ test("workspaceRoot fails closed before any native call for non-worktree and for
|
|
|
337
466
|
assert.equal(nativeCalls, 0);
|
|
338
467
|
});
|
|
339
468
|
|
|
340
|
-
|
|
469
|
+
function providerRoleBinding(lineageId: string) {
|
|
470
|
+
return {
|
|
471
|
+
name: "provider_targeted_validator",
|
|
472
|
+
schema: "https://gentle-ai.dev/schema/review/targeted-validator/v1",
|
|
473
|
+
captureOperation: "review.capture-validation",
|
|
474
|
+
arguments: [
|
|
475
|
+
{ name: "lineage", value: lineageId, token: `--lineage=${lineageId}` },
|
|
476
|
+
{ name: "target", value: `sha256:${"a".repeat(64)}`, token: `--target=sha256:${"a".repeat(64)}` },
|
|
477
|
+
{ name: "agent", value: "pi", token: "--agent=pi" },
|
|
478
|
+
{ name: "execute", value: "true", token: "--execute=true" },
|
|
479
|
+
],
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
test("same-session START binding migrates to one validation capture without a FINALIZE operation", async (t) => {
|
|
341
484
|
const sessionCwd = repository(t);
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
485
|
+
const worktree = addWorktree(t, sessionCwd, "feat-last-event");
|
|
486
|
+
writeFileSync(join(worktree, "app.ts"), "export const value = 2;\n");
|
|
487
|
+
const candidateViews = new CandidateViewRegistry();
|
|
488
|
+
const lineageId = "workspace-last-event";
|
|
489
|
+
const binding = providerRoleBinding(lineageId);
|
|
490
|
+
let captureCalls = 0;
|
|
491
|
+
const native = fakeNative({
|
|
492
|
+
targetStatus: async (request) => request.lineageId === undefined
|
|
493
|
+
? candidateStartTargetStatus(request)
|
|
494
|
+
: ({ ...candidateTargetStatus(request, lineageId), nextTransition: { kind: "collect", reasonCode: "provider_role_required", collect: { inputs: [binding] } } } as unknown as ReviewStatusV3),
|
|
495
|
+
start: async () => ({ lineageId, state: "reviewing", riskLevel: "medium", selectedLenses: ["review-reliability"], changedFiles: 1, changedLines: 1, correctionBudget: 1, action: "created", lensesRequired: true }),
|
|
496
|
+
captureProviderRole: async (request) => {
|
|
497
|
+
captureCalls += 1;
|
|
498
|
+
assert.equal(request.captureOperation, "review.capture-validation");
|
|
499
|
+
return { schema: "gentle-ai.review-last-event-closure/v1", operation: "review.capture-validation", lineageId, state: "approved", storeRevision: `sha256:${"a".repeat(64)}` };
|
|
351
500
|
},
|
|
352
|
-
);
|
|
501
|
+
});
|
|
502
|
+
const { controller } = runtime(native, candidateViews);
|
|
503
|
+
await controller.execute("start", { operation: "start", workspaceRoot: worktree, input: JSON.stringify({ mode: "ordinary" }) }, undefined, undefined, context(sessionCwd));
|
|
504
|
+
const captured = await __testing.executeReviewCaptureOperation({ lineageId, workspaceRoot: worktree, collectBinding: JSON.stringify(binding) }, sessionCwd, native, undefined, candidateViews);
|
|
505
|
+
assert.equal(captured.outcome, "native-last-event-closure");
|
|
506
|
+
assert.equal(captured.status, "closed");
|
|
507
|
+
assert.equal(captureCalls, 1);
|
|
508
|
+
candidateViews.cleanupTerminal(lineageId, "approved", realpathSync(worktree));
|
|
353
509
|
});
|
|
354
510
|
|
|
355
|
-
test("
|
|
511
|
+
test("an explicit linked-worktree root owns STATUS collect capture over the session candidate root", async (t) => {
|
|
356
512
|
const sessionCwd = repository(t);
|
|
357
|
-
const worktree = addWorktree(t, sessionCwd, "feat-
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
return { lineageId: "finalize-lineage", state: "approved", action: "approved", storeRevision: "r1" };
|
|
513
|
+
const worktree = addWorktree(t, sessionCwd, "feat-explicit-capture");
|
|
514
|
+
const lineageId = "explicit-worktree-capture";
|
|
515
|
+
const binding = providerRoleBinding(lineageId);
|
|
516
|
+
const observedRoots: string[] = [];
|
|
517
|
+
const native = {
|
|
518
|
+
targetStatus: async (request: { cwd: string }) => {
|
|
519
|
+
observedRoots.push(request.cwd);
|
|
520
|
+
return { ...targetStatusFixture({ lineageId }), nextTransition: { kind: "collect", reasonCode: "provider_role_required", collect: { inputs: [binding] } } } as unknown as ReviewStatusV3;
|
|
366
521
|
},
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
assert.
|
|
372
|
-
assert.equal(finalizes, 0);
|
|
373
|
-
const matched = await controller.execute("finalize-right-root", { operation: "finalize", lineageId: "finalize-lineage", workspaceRoot: worktree, input }, undefined, undefined, context(sessionCwd));
|
|
374
|
-
assert.equal((matched.details as { result?: { state?: string } }).result?.state, "approved");
|
|
375
|
-
assert.equal(finalizes, 1);
|
|
522
|
+
captureProviderRole: async () => ({ schema: "gentle-ai.review-last-event-closure/v1", operation: "review.capture-validation", lineageId, state: "approved", storeRevision: `sha256:${"a".repeat(64)}` }),
|
|
523
|
+
} as unknown as NativeReviewCli;
|
|
524
|
+
const result = await __testing.executeReviewCaptureOperation({ lineageId, workspaceRoot: worktree, collectBinding: JSON.stringify(binding) }, sessionCwd, native);
|
|
525
|
+
assert.equal(result.outcome, "native-last-event-closure");
|
|
526
|
+
assert.deepEqual(observedRoots, [realpathSync(worktree)]);
|
|
376
527
|
});
|