gentle-pi 2.2.0 → 2.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.
- package/README.md +51 -80
- 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-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 +54 -248
- package/assets/orchestrator-memory.md +2 -0
- package/assets/orchestrator.md +22 -38
- 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/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 +1778 -3673
- package/extensions/quiet-tools.ts +515 -32
- package/extensions/sdd-init.ts +4 -8
- package/lib/gentle-ai-renderer.ts +70 -0
- package/lib/model-routing-authority.ts +133 -0
- package/lib/native-review-cli.ts +372 -940
- package/lib/opaque-pi-reviewer-adapter.ts +284 -0
- package/lib/review-candidate-view.ts +341 -132
- package/lib/review-host-relay.ts +210 -68
- package/lib/review-integration-v2.ts +839 -307
- package/lib/review-last-event-controller.ts +35 -0
- package/lib/sdd-preflight.ts +177 -66
- 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 +322 -890
- package/runtime/review-integration-v2.mjs +789 -257
- 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 +12 -13
- package/skills/_shared/review-ledger-contract.md +8 -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/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/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 +820 -131
- package/tests/gentle-theme.test.ts +133 -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 +28 -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 +37 -35
- package/tests/provider-defect-handoff.test.ts +15 -118
- package/tests/quiet-tool-rendering.test.ts +1055 -28
- 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-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 +117 -82
- package/tests/review-host-relay.test.ts +242 -8
- package/tests/review-integration-v2-forward.test.ts +576 -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 +361 -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 +230 -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
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { NativeReviewCli } from "./native-review-cli.ts";
|
|
2
|
+
import type { ReviewLastEventClosureBinding, ReviewStatusV3 } from "./review-integration-v2.ts";
|
|
3
|
+
|
|
4
|
+
export interface ReviewLastEventCaptureSelector {
|
|
5
|
+
readonly baseRef?: string;
|
|
6
|
+
readonly committedOnly?: true;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Reconcile exactly one ambiguous native capture outcome. Successful captures
|
|
11
|
+
* return their native artifact or closure directly; this helper is never a
|
|
12
|
+
* post-success lifecycle step and never replays a capture.
|
|
13
|
+
*/
|
|
14
|
+
export async function reconcileUnknownReviewLastEventCapture(
|
|
15
|
+
nativeReviewCli: NativeReviewCli,
|
|
16
|
+
cwd: string,
|
|
17
|
+
binding: ReviewLastEventClosureBinding,
|
|
18
|
+
selector?: ReviewLastEventCaptureSelector,
|
|
19
|
+
): Promise<ReviewStatusV3> {
|
|
20
|
+
if (nativeReviewCli.targetStatus === undefined) {
|
|
21
|
+
throw new TypeError("native target-scoped STATUS is required to reconcile an ambiguous capture outcome");
|
|
22
|
+
}
|
|
23
|
+
const status = await nativeReviewCli.targetStatus({
|
|
24
|
+
cwd,
|
|
25
|
+
lineageId: binding.lineageId,
|
|
26
|
+
...(selector === undefined ? {} : selector),
|
|
27
|
+
});
|
|
28
|
+
if (binding.targetIdentity !== undefined && status.targetIdentity !== binding.targetIdentity) {
|
|
29
|
+
throw new TypeError("capture reconciliation returned a different target");
|
|
30
|
+
}
|
|
31
|
+
if (status.authority?.lineageId !== undefined && status.authority.lineageId !== binding.lineageId) {
|
|
32
|
+
throw new TypeError("capture reconciliation returned a different lineage");
|
|
33
|
+
}
|
|
34
|
+
return status;
|
|
35
|
+
}
|
package/lib/sdd-preflight.ts
CHANGED
|
@@ -23,19 +23,36 @@ function gentlePiAgentHome(): string {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export type SddExecutionMode = "interactive" | "auto";
|
|
26
|
+
export type SddDeliveryStrategy =
|
|
27
|
+
| "ask-on-risk"
|
|
28
|
+
| "auto-chain"
|
|
29
|
+
| "single-pr"
|
|
30
|
+
| "exception-ok";
|
|
31
|
+
/** @deprecated Use SddDeliveryStrategy; legacy values normalize at persistence boundaries. */
|
|
26
32
|
export type SddChainedPrStrategy =
|
|
33
|
+
| SddDeliveryStrategy
|
|
27
34
|
| "auto-forecast"
|
|
28
35
|
| "ask-always"
|
|
29
36
|
| "single-pr-default"
|
|
30
37
|
| "force-chained";
|
|
38
|
+
export type SddPreflightField = "executionMode" | "artifactStore" | "chainedPrStrategy" | "reviewBudgetLines";
|
|
39
|
+
export const SDD_PREFLIGHT_FIELDS = ["executionMode", "artifactStore", "chainedPrStrategy", "reviewBudgetLines"] as const;
|
|
31
40
|
|
|
32
41
|
export interface SddPreflightPreferences {
|
|
33
42
|
executionMode: SddExecutionMode;
|
|
34
43
|
artifactStore: SddArtifactStore;
|
|
44
|
+
/** Runtime values are canonical; legacy assignments normalize at persistence/render boundaries. */
|
|
35
45
|
chainedPrStrategy: SddChainedPrStrategy;
|
|
36
46
|
reviewBudgetLines: number;
|
|
37
47
|
engramAvailable: boolean;
|
|
38
48
|
prompted: boolean;
|
|
49
|
+
sizeExceptionAccepted?: true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface SddPreflightResolutionOptions {
|
|
53
|
+
persisted?: Partial<SddPreflightPreferences>;
|
|
54
|
+
promptFields?: readonly SddPreflightField[];
|
|
55
|
+
acceptSizeException?: true;
|
|
39
56
|
}
|
|
40
57
|
|
|
41
58
|
interface SddPreflightCallbacks {
|
|
@@ -69,14 +86,14 @@ interface LegacyManagedAssetsManifest extends ManagedAssetsManifest {
|
|
|
69
86
|
packageVersion: string;
|
|
70
87
|
}
|
|
71
88
|
|
|
72
|
-
const DEFAULT_SDD_PREFLIGHT: SddPreflightPreferences = {
|
|
73
|
-
executionMode: "
|
|
89
|
+
export const DEFAULT_SDD_PREFLIGHT: SddPreflightPreferences = Object.freeze({
|
|
90
|
+
executionMode: "auto",
|
|
74
91
|
artifactStore: "openspec",
|
|
75
|
-
chainedPrStrategy: "
|
|
92
|
+
chainedPrStrategy: "ask-on-risk",
|
|
76
93
|
reviewBudgetLines: 400,
|
|
77
94
|
engramAvailable: false,
|
|
78
95
|
prompted: false,
|
|
79
|
-
};
|
|
96
|
+
});
|
|
80
97
|
|
|
81
98
|
const sddPreflightBySession = new Map<string, SddPreflightPreferences>();
|
|
82
99
|
const sddPreflightInFlight = new Map<string, Promise<SddPreflightPreferences>>();
|
|
@@ -85,6 +102,69 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
85
102
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
86
103
|
}
|
|
87
104
|
|
|
105
|
+
function isSddExecutionMode(value: unknown): value is SddExecutionMode {
|
|
106
|
+
return value === "interactive" || value === "auto";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function isSddArtifactStore(value: unknown): value is SddArtifactStore {
|
|
110
|
+
return value === "openspec" || value === "engram" || value === "hybrid" || value === "none";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// normalizeSddArtifactStore accepts the canonical names and the legacy "both"
|
|
114
|
+
// spelling of the dual-store mode. Operator preflight files written before the
|
|
115
|
+
// rename carry "both" on disk; rejecting it would silently drop the operator's
|
|
116
|
+
// choice back to the default, so it maps forward instead.
|
|
117
|
+
export function normalizeSddArtifactStore(value: unknown): SddArtifactStore | undefined {
|
|
118
|
+
if (value === "both") return "hybrid";
|
|
119
|
+
return isSddArtifactStore(value) ? value : undefined;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function normalizeReviewBudgetValue(value: unknown): number | undefined {
|
|
123
|
+
const parsed = typeof value === "number"
|
|
124
|
+
? value
|
|
125
|
+
: typeof value === "string"
|
|
126
|
+
? Number.parseInt(value.trim(), 10)
|
|
127
|
+
: Number.NaN;
|
|
128
|
+
return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function normalizeSddStrategySelection(
|
|
132
|
+
value: unknown,
|
|
133
|
+
allowExceptionOk: boolean,
|
|
134
|
+
): SddDeliveryStrategy | undefined {
|
|
135
|
+
if (value === "exception-ok") return allowExceptionOk ? value : undefined;
|
|
136
|
+
if (value === "auto-forecast" || value === "ask-always") return "ask-on-risk";
|
|
137
|
+
if (value === "single-pr-default") return "single-pr";
|
|
138
|
+
if (value === "force-chained") return "auto-chain";
|
|
139
|
+
return value === "ask-on-risk" || value === "auto-chain" || value === "single-pr"
|
|
140
|
+
? value
|
|
141
|
+
: undefined;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function normalizeSddChainedPrStrategy(
|
|
145
|
+
value: unknown,
|
|
146
|
+
allowExceptionOk = false,
|
|
147
|
+
): SddDeliveryStrategy {
|
|
148
|
+
return normalizeSddStrategySelection(value, allowExceptionOk) ?? "ask-on-risk";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function normalizedSelections(
|
|
152
|
+
value: Partial<Record<SddPreflightField, unknown>> | undefined,
|
|
153
|
+
engramAvailable: boolean,
|
|
154
|
+
allowExceptionOk: boolean,
|
|
155
|
+
): Partial<Record<SddPreflightField, unknown>> {
|
|
156
|
+
if (!value) return {};
|
|
157
|
+
const result: Partial<Record<SddPreflightField, unknown>> = {};
|
|
158
|
+
if (isSddExecutionMode(value.executionMode)) result.executionMode = value.executionMode;
|
|
159
|
+
const artifactStore = normalizeSddArtifactStore(value.artifactStore);
|
|
160
|
+
if (artifactStore !== undefined && (engramAvailable || artifactStore === "openspec" || artifactStore === "none")) result.artifactStore = artifactStore;
|
|
161
|
+
const strategy = normalizeSddStrategySelection(value.chainedPrStrategy, allowExceptionOk);
|
|
162
|
+
if (strategy) result.chainedPrStrategy = strategy;
|
|
163
|
+
const reviewBudgetLines = normalizeReviewBudgetValue(value.reviewBudgetLines);
|
|
164
|
+
if (reviewBudgetLines !== undefined) result.reviewBudgetLines = reviewBudgetLines;
|
|
165
|
+
return result;
|
|
166
|
+
}
|
|
167
|
+
|
|
88
168
|
function emptyManagedAssetsManifest(): ManagedAssetsManifest {
|
|
89
169
|
return {
|
|
90
170
|
schemaVersion: MANAGED_ASSETS_SCHEMA_VERSION,
|
|
@@ -273,28 +353,28 @@ export function readSddPreflightFromDisk(cwd: string): SddPreflightPreferences |
|
|
|
273
353
|
try {
|
|
274
354
|
const parsed: unknown = JSON.parse(readFileSync(path, "utf8"));
|
|
275
355
|
if (!isRecord(parsed)) return undefined;
|
|
276
|
-
// Validate required fields to guard against stale/corrupt writes
|
|
356
|
+
// Validate required fields to guard against stale/corrupt writes.
|
|
277
357
|
const { executionMode, artifactStore, chainedPrStrategy, reviewBudgetLines, engramAvailable, prompted } = parsed;
|
|
358
|
+
// Normalize before validating: a preflight file written before the
|
|
359
|
+
// dual-store rename carries "both", and discarding it would silently
|
|
360
|
+
// drop the operator's choice back to the default.
|
|
361
|
+
const canonicalArtifactStore = normalizeSddArtifactStore(artifactStore);
|
|
278
362
|
if (
|
|
279
|
-
(executionMode
|
|
280
|
-
|
|
363
|
+
!isSddExecutionMode(executionMode) ||
|
|
364
|
+
canonicalArtifactStore === undefined ||
|
|
281
365
|
typeof reviewBudgetLines !== "number" ||
|
|
366
|
+
!Number.isFinite(reviewBudgetLines) ||
|
|
367
|
+
reviewBudgetLines <= 0 ||
|
|
282
368
|
typeof engramAvailable !== "boolean" ||
|
|
283
369
|
typeof prompted !== "boolean"
|
|
284
370
|
) {
|
|
285
371
|
return undefined;
|
|
286
372
|
}
|
|
287
|
-
const normalizedChain: SddChainedPrStrategy =
|
|
288
|
-
chainedPrStrategy === "ask-always" ||
|
|
289
|
-
chainedPrStrategy === "single-pr-default" ||
|
|
290
|
-
chainedPrStrategy === "force-chained"
|
|
291
|
-
? (chainedPrStrategy as SddChainedPrStrategy)
|
|
292
|
-
: "auto-forecast";
|
|
293
373
|
return {
|
|
294
374
|
executionMode,
|
|
295
|
-
artifactStore,
|
|
296
|
-
chainedPrStrategy:
|
|
297
|
-
reviewBudgetLines,
|
|
375
|
+
artifactStore: canonicalArtifactStore,
|
|
376
|
+
chainedPrStrategy: normalizeSddChainedPrStrategy(chainedPrStrategy),
|
|
377
|
+
reviewBudgetLines: normalizeReviewBudgetValue(reviewBudgetLines) ?? DEFAULT_SDD_PREFLIGHT.reviewBudgetLines,
|
|
298
378
|
engramAvailable,
|
|
299
379
|
prompted,
|
|
300
380
|
};
|
|
@@ -306,8 +386,19 @@ export function readSddPreflightFromDisk(cwd: string): SddPreflightPreferences |
|
|
|
306
386
|
export function writeSddPreflightToDisk(cwd: string, prefs: SddPreflightPreferences): void {
|
|
307
387
|
try {
|
|
308
388
|
const path = sddPreflightDiskPath(cwd);
|
|
389
|
+
const prompted = prefs.prompted === true;
|
|
390
|
+
const canonical: SddPreflightPreferences = {
|
|
391
|
+
executionMode: isSddExecutionMode(prefs.executionMode) ? prefs.executionMode : DEFAULT_SDD_PREFLIGHT.executionMode,
|
|
392
|
+
artifactStore: normalizeSddArtifactStore(prefs.artifactStore) ?? DEFAULT_SDD_PREFLIGHT.artifactStore,
|
|
393
|
+
chainedPrStrategy: normalizeSddChainedPrStrategy(prefs.chainedPrStrategy),
|
|
394
|
+
reviewBudgetLines:
|
|
395
|
+
normalizeReviewBudgetValue(prefs.reviewBudgetLines) ??
|
|
396
|
+
DEFAULT_SDD_PREFLIGHT.reviewBudgetLines,
|
|
397
|
+
engramAvailable: prefs.engramAvailable === true,
|
|
398
|
+
prompted,
|
|
399
|
+
};
|
|
309
400
|
mkdirSync(dirname(path), { recursive: true });
|
|
310
|
-
writeFileSync(path, JSON.stringify(
|
|
401
|
+
writeFileSync(path, JSON.stringify(canonical, null, 2));
|
|
311
402
|
} catch {
|
|
312
403
|
// Disk write failures are non-fatal; in-memory cache is the primary store
|
|
313
404
|
}
|
|
@@ -495,7 +586,7 @@ export function installSddAssets(
|
|
|
495
586
|
|
|
496
587
|
export function isSddPreflightTrigger(text: string): boolean {
|
|
497
588
|
const trimmed = text.trim();
|
|
498
|
-
if (/^\/sdd(?:[-:][^\s]*)?(?:\s|$)/i.test(trimmed)) return true;
|
|
589
|
+
if (/^\/(?:gentle-)?sdd(?:[-:][^\s]*)?(?:\s|$)/i.test(trimmed)) return true;
|
|
499
590
|
if (/[??]\s*$/.test(trimmed)) return false;
|
|
500
591
|
if (
|
|
501
592
|
/\b(?:don't|do\s+not|not\s+use|never\s+use|without\s+using|sin\s+usar|no\s+(?:quiero|queremos|vamos\s+a)?\s*usar)\s+sdd\b/i.test(
|
|
@@ -550,56 +641,67 @@ function hasWritableEngramTool(pi: ExtensionAPI): boolean {
|
|
|
550
641
|
}
|
|
551
642
|
}
|
|
552
643
|
|
|
553
|
-
function
|
|
554
|
-
const parsed = Number.parseInt(value.trim(), 10);
|
|
555
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : 400;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
async function collectSddPreflightPreferences(
|
|
644
|
+
export async function collectSddPreflightPreferences(
|
|
559
645
|
ctx: ExtensionContext,
|
|
560
646
|
engramAvailable: boolean,
|
|
647
|
+
options: SddPreflightResolutionOptions = {},
|
|
561
648
|
): Promise<SddPreflightPreferences> {
|
|
562
|
-
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
649
|
+
const persistedPrompted = options.persisted?.prompted === true;
|
|
650
|
+
const allowExceptionOk = options.acceptSizeException === true;
|
|
651
|
+
const persisted = normalizedSelections(options.persisted, engramAvailable, allowExceptionOk);
|
|
652
|
+
const resolved: Partial<Record<SddPreflightField, unknown>> = { ...persisted };
|
|
653
|
+
let prompted = persistedPrompted;
|
|
654
|
+
let sizeExceptionAccepted = allowExceptionOk && persisted.chainedPrStrategy === "exception-ok";
|
|
655
|
+
const promptFields = new Set(options.promptFields ?? []);
|
|
656
|
+
|
|
657
|
+
const usePromptedValue = (
|
|
658
|
+
field: SddPreflightField,
|
|
659
|
+
value: unknown,
|
|
660
|
+
): void => {
|
|
661
|
+
const candidate = normalizedSelections({ [field]: value }, engramAvailable, allowExceptionOk);
|
|
662
|
+
if (candidate[field] !== undefined) {
|
|
663
|
+
resolved[field] = candidate[field];
|
|
664
|
+
if (field === "chainedPrStrategy" && candidate[field] === "exception-ok") sizeExceptionAccepted = true;
|
|
665
|
+
prompted = true;
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
const promptField = async (
|
|
670
|
+
field: SddPreflightField,
|
|
671
|
+
read: () => Promise<unknown>,
|
|
672
|
+
enabled = true,
|
|
673
|
+
): Promise<void> => {
|
|
674
|
+
if (ctx.hasUI && enabled && promptFields.has(field)) {
|
|
675
|
+
usePromptedValue(field, await read());
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
const artifactOptions = engramAvailable ? ["openspec", "engram", "hybrid"] : ["openspec"];
|
|
679
|
+
await promptField("executionMode", () => ctx.ui.select("SDD execution mode", ["interactive", "auto"]));
|
|
680
|
+
await promptField("artifactStore", () => ctx.ui.select("SDD artifact store", artifactOptions), artifactOptions.length > 1);
|
|
681
|
+
await promptField("chainedPrStrategy", () => ctx.ui.select("SDD delivery strategy", ["ask-on-risk", "auto-chain", "single-pr"]));
|
|
682
|
+
await promptField("reviewBudgetLines", () => ctx.ui.input("SDD review budget lines", String(DEFAULT_SDD_PREFLIGHT.reviewBudgetLines)));
|
|
683
|
+
|
|
684
|
+
const resolvedValue = <T>(field: SddPreflightField, fallback: T): T =>
|
|
685
|
+
(resolved[field] as T | undefined) ?? fallback;
|
|
580
686
|
return {
|
|
581
|
-
executionMode:
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
? artifactStore
|
|
586
|
-
: DEFAULT_SDD_PREFLIGHT.artifactStore,
|
|
587
|
-
chainedPrStrategy:
|
|
588
|
-
chainedPrStrategy === "ask-always" ||
|
|
589
|
-
chainedPrStrategy === "single-pr-default" ||
|
|
590
|
-
chainedPrStrategy === "force-chained"
|
|
591
|
-
? chainedPrStrategy
|
|
592
|
-
: DEFAULT_SDD_PREFLIGHT.chainedPrStrategy,
|
|
593
|
-
reviewBudgetLines,
|
|
687
|
+
executionMode: resolvedValue("executionMode", DEFAULT_SDD_PREFLIGHT.executionMode),
|
|
688
|
+
artifactStore: resolvedValue("artifactStore", DEFAULT_SDD_PREFLIGHT.artifactStore),
|
|
689
|
+
chainedPrStrategy: resolvedValue("chainedPrStrategy", DEFAULT_SDD_PREFLIGHT.chainedPrStrategy),
|
|
690
|
+
reviewBudgetLines: resolvedValue("reviewBudgetLines", DEFAULT_SDD_PREFLIGHT.reviewBudgetLines),
|
|
594
691
|
engramAvailable,
|
|
595
|
-
prompted
|
|
692
|
+
prompted,
|
|
693
|
+
...(sizeExceptionAccepted ? { sizeExceptionAccepted: true as const } : {}),
|
|
596
694
|
};
|
|
597
695
|
}
|
|
598
696
|
|
|
599
697
|
export function renderSddPreflightPrompt(prefs: SddPreflightPreferences): string {
|
|
698
|
+
const deliveryStrategy = normalizeSddChainedPrStrategy(
|
|
699
|
+
prefs.chainedPrStrategy,
|
|
700
|
+
prefs.sizeExceptionAccepted === true,
|
|
701
|
+
);
|
|
600
702
|
const sourceLine = prefs.prompted
|
|
601
|
-
? "
|
|
602
|
-
: "
|
|
703
|
+
? "These SDD preferences are explicit current-session choices. Reuse them unless the user explicitly changes them."
|
|
704
|
+
: "These SDD preferences are canonical defaults or persisted choices. Treat them as authoritative; do not revisit dependent decisions unless a genuine human-control gate is reached.";
|
|
603
705
|
const interactiveRules =
|
|
604
706
|
prefs.executionMode === "interactive"
|
|
605
707
|
? [
|
|
@@ -615,25 +717,34 @@ export function renderSddPreflightPrompt(prefs: SddPreflightPreferences): string
|
|
|
615
717
|
sourceLine,
|
|
616
718
|
`- Execution mode: ${prefs.executionMode}`,
|
|
617
719
|
`- Artifact store: ${prefs.artifactStore}${prefs.engramAvailable ? "" : " (Engram unavailable in this session)"}`,
|
|
618
|
-
`-
|
|
619
|
-
|
|
720
|
+
`- Delivery strategy: ${deliveryStrategy}`,
|
|
721
|
+
"- Delivery strategy domain: `ask-on-risk` | `auto-chain` | `single-pr` | `exception-ok`",
|
|
722
|
+
`- Review budget: ${prefs.reviewBudgetLines} changed lines (400 is the canonical threshold unless explicitly changed)`,
|
|
723
|
+
"- Chain strategy: deferred until chaining is selected.",
|
|
724
|
+
"- `exception-ok` is never inferred; it requires explicit acceptance of `size:exception`.",
|
|
620
725
|
...interactiveRules,
|
|
621
|
-
"-
|
|
726
|
+
"- Preserve human-controlled consent, authorization, security, destructive/publishing, ambiguous-scope, and `size:exception` gates.",
|
|
727
|
+
"- When review-budget risk requires a delivery decision, use `ask-on-risk` to pause and ask; do not invent a chain strategy or an exception.",
|
|
622
728
|
].join("\n");
|
|
623
729
|
}
|
|
624
730
|
|
|
625
731
|
export async function ensureSddPreflight(
|
|
626
732
|
ctx: ExtensionContext,
|
|
627
733
|
callbacks: SddPreflightCallbacks,
|
|
734
|
+
resolutionOptions: SddPreflightResolutionOptions = {},
|
|
628
735
|
): Promise<SddPreflightPreferences> {
|
|
629
736
|
const sessionKey = sddPreflightSessionKey(ctx);
|
|
630
737
|
const existing = sddPreflightBySession.get(sessionKey);
|
|
631
|
-
if (existing) return existing;
|
|
738
|
+
if (existing && !(resolutionOptions.promptFields?.length ?? 0)) return existing;
|
|
632
739
|
const inFlight = sddPreflightInFlight.get(sessionKey);
|
|
633
|
-
if (inFlight) return inFlight;
|
|
740
|
+
if (inFlight && !(resolutionOptions.promptFields?.length ?? 0)) return inFlight;
|
|
634
741
|
const promise = (async () => {
|
|
635
742
|
const engramAvailable = hasWritableEngramTool(callbacks.pi);
|
|
636
|
-
const
|
|
743
|
+
const persisted = resolutionOptions.persisted ?? readSddPreflightFromDisk(ctx.cwd);
|
|
744
|
+
const prefs = await collectSddPreflightPreferences(ctx, engramAvailable, {
|
|
745
|
+
...resolutionOptions,
|
|
746
|
+
persisted,
|
|
747
|
+
});
|
|
637
748
|
const result =
|
|
638
749
|
(await callbacks.installAssets?.(ctx.cwd)) ??
|
|
639
750
|
installSddAssets(ctx.cwd, false);
|
|
@@ -650,9 +761,9 @@ export async function ensureSddPreflight(
|
|
|
650
761
|
"Gentle AI SDD preflight complete.",
|
|
651
762
|
`Mode: ${prefs.executionMode}`,
|
|
652
763
|
`Artifacts: ${prefs.artifactStore}`,
|
|
653
|
-
`
|
|
764
|
+
`Delivery strategy: ${prefs.chainedPrStrategy}`,
|
|
654
765
|
`Review budget: ${prefs.reviewBudgetLines} changed lines`,
|
|
655
|
-
`Preference source: ${prefs.prompted ? "
|
|
766
|
+
`Preference source: ${prefs.prompted ? "explicit session choice" : "canonical default or persisted preference"}`,
|
|
656
767
|
`Global SDD assets ready: ${result.agents} agent(s), ${result.chains} chain(s), ${result.support} support file(s), ${result.skipped} already present.`,
|
|
657
768
|
modelRoutingLine,
|
|
658
769
|
].join("\n"),
|