@mgiles/perk 3.2.0 → 3.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 +5 -0
- package/extension/authoring/gist/draft.ts +198 -0
- package/extension/authoring/gist/prose.ts +46 -0
- package/extension/authoring/gist/review.ts +133 -0
- package/extension/authoring/gist/save.ts +118 -0
- package/extension/authoring/objective/draft.ts +345 -0
- package/extension/{factories/objectiveDreamReport.ts → authoring/objective/dreamReportGate.ts} +74 -131
- package/extension/authoring/objective/planning.ts +124 -0
- package/extension/authoring/objective/prose.ts +103 -0
- package/extension/authoring/objective/review.ts +128 -0
- package/extension/authoring/objective/save.ts +224 -0
- package/extension/authoring/plan/draft.ts +84 -0
- package/extension/authoring/plan/prose.ts +41 -0
- package/extension/authoring/plan/review.ts +269 -0
- package/extension/authoring/plan/save.ts +256 -0
- package/extension/authoring/plan/source.ts +82 -0
- package/extension/authoring/refinement/context.ts +468 -0
- package/extension/authoring/refinement/draft.ts +261 -0
- package/extension/authoring/refinement/prose.ts +79 -0
- package/extension/authoring/refinement/review.ts +111 -0
- package/extension/authoring/refinement/save.ts +119 -0
- package/extension/authoring/review/approvalGate.ts +34 -0
- package/extension/authoring/review/draftContext.ts +68 -0
- package/extension/codeReview/automated.ts +352 -0
- package/extension/codeReview/submission.ts +229 -0
- package/extension/delivery/address.ts +295 -0
- package/extension/delivery/ci.ts +355 -0
- package/extension/delivery/commitCompact.ts +93 -0
- package/extension/delivery/conflictResolution.ts +247 -0
- package/extension/delivery/ready.ts +193 -0
- package/extension/delivery/stackConflict.ts +361 -0
- package/extension/delivery/stackObjective.ts +16 -0
- package/extension/delivery/stackReconcile.ts +165 -0
- package/extension/delivery/submit.ts +171 -0
- package/extension/index.ts +365 -380
- package/extension/learning/analystWave.ts +324 -0
- package/extension/learning/audit.ts +667 -0
- package/extension/learning/capture.ts +92 -0
- package/extension/learning/containment.ts +104 -0
- package/extension/{waves/dreamWave.ts → learning/dream.ts} +112 -94
- package/extension/learning/dreamAnalysis.ts +435 -0
- package/extension/{waves/dreamReducerWave.ts → learning/dreamReducer.ts} +46 -41
- package/extension/{waves → learning}/dreamReport.ts +35 -31
- package/extension/learning/harvest.ts +491 -0
- package/extension/learning/prose.ts +66 -0
- package/extension/learning/routing.ts +79 -0
- package/extension/pi/v1/bashScanTimeout.ts +64 -0
- package/extension/{doors/prReview.ts → pi/v1/codeReview/automated.ts} +215 -311
- package/extension/{doors/prReviewBrowser.ts → pi/v1/codeReview/browser.ts} +53 -33
- package/extension/{doors/hunkHandoff.ts → pi/v1/codeReview/checkout.ts} +12 -8
- package/extension/{doors/reviewWaveTools.ts → pi/v1/codeReview/reviewWave.ts} +146 -114
- package/extension/{doors/stackReviewBrowser.ts → pi/v1/codeReview/stack.ts} +62 -29
- package/extension/pi/v1/codeReview/submit.ts +354 -0
- package/extension/{doors/prReviewTerminal.ts → pi/v1/codeReview/terminal.ts} +32 -27
- package/extension/pi/v1/contextEvidence.ts +80 -0
- package/extension/pi/v1/contextInjection.ts +207 -0
- package/extension/{doors → pi/v1/delivery}/address.ts +154 -267
- package/extension/pi/v1/delivery/ci.ts +570 -0
- package/extension/pi/v1/delivery/commitCompact.ts +201 -0
- package/extension/pi/v1/delivery/conflictResolverEngine.ts +425 -0
- package/extension/{doors → pi/v1/delivery}/land.ts +123 -61
- package/extension/pi/v1/delivery/ready.ts +322 -0
- package/extension/pi/v1/delivery/stackConflictResolver.ts +172 -0
- package/extension/pi/v1/delivery/stackDrive.ts +120 -0
- package/extension/pi/v1/delivery/stackLand.ts +223 -0
- package/extension/pi/v1/delivery/stackRecover.ts +265 -0
- package/extension/pi/v1/delivery/stackStatus.ts +237 -0
- package/extension/pi/v1/delivery/stackSync.ts +658 -0
- package/extension/pi/v1/delivery/submit.ts +389 -0
- package/extension/pi/v1/delivery/submitConflict.ts +186 -0
- package/extension/pi/v1/draftReview.ts +431 -0
- package/extension/{doors → pi/v1}/draftReviewWaveTools.ts +141 -151
- package/extension/pi/v1/gist.ts +794 -0
- package/extension/pi/v1/learning/audit.ts +186 -0
- package/extension/pi/v1/learning/dream.ts +207 -0
- package/extension/{doors/learnFactory.ts → pi/v1/learning/factory.ts} +18 -65
- package/extension/{doors/harvestWaveTools.ts → pi/v1/learning/harvest.ts} +46 -100
- package/extension/pi/v1/learning/learn.ts +585 -0
- package/extension/pi/v1/lifecycleGates.ts +127 -0
- package/extension/{factories → pi/v1}/objective.ts +53 -33
- package/extension/pi/v1/objectiveAuthoring.ts +672 -0
- package/extension/pi/v1/objectiveDreamGate.ts +160 -0
- package/extension/{factories/objectivePlan.ts → pi/v1/objectivePlanning.ts} +328 -533
- package/extension/pi/v1/objectiveRefinement.ts +1320 -0
- package/extension/pi/v1/objectiveReview.ts +451 -0
- package/extension/{doors → pi/v1}/objectiveReviewBrowser.ts +259 -172
- package/extension/pi/v1/plan.ts +812 -0
- package/extension/pi/v1/planReview.ts +820 -0
- package/extension/{doors → pi/v1}/planReviewBrowser.ts +228 -152
- package/extension/{doors/annotationPush.ts → pi/v1/providers/annotations.ts} +158 -89
- package/extension/pi/v1/providers/plannotator.ts +487 -0
- package/extension/{doors → pi/v1/providers}/plannotatorHandoff.ts +73 -27
- package/extension/pi/v1/providers/selection.ts +43 -0
- package/extension/{adapters/planAdapterTombell.ts → pi/v1/providers/tombell.ts} +43 -72
- package/extension/pi/v1/review.ts +538 -0
- package/extension/pi/v1/reviewOutcome.ts +9 -0
- package/extension/pi/v1/scoutWave.ts +318 -0
- package/extension/{doors → pi/v1}/selfcheck.ts +4 -4
- package/extension/session/branchWorkflowSession.ts +60 -0
- package/extension/session/lifecycle.ts +644 -0
- package/extension/session/lifecycleGates.ts +64 -0
- package/extension/session/saveDestination.ts +87 -0
- package/extension/session/workflowSession.ts +971 -0
- package/extension/substrate/agentScratch.ts +27 -54
- package/extension/substrate/bashScanTimeout.ts +181 -0
- package/extension/substrate/bindingDelivery.ts +38 -30
- package/extension/substrate/bindings.ts +4 -5
- package/extension/substrate/cache.ts +64 -12
- package/extension/substrate/childRestrictions.ts +39 -0
- package/extension/substrate/coldDoor.ts +17 -1
- package/extension/substrate/config.ts +157 -21
- package/extension/substrate/git.ts +88 -6
- package/extension/substrate/modelVisible.ts +53 -0
- package/extension/substrate/prompts.ts +22 -0
- package/extension/substrate/registry.ts +2 -0
- package/extension/substrate/resolverLease.ts +5 -4
- package/extension/substrate/sessionData.ts +85 -152
- package/extension/substrate/toolGating.ts +263 -84
- package/extension/substrate/unifiedDiff.ts +1 -1
- package/extension/substrate/workflowState.ts +178 -163
- package/extension/substrate/worktreeResolverLock.ts +261 -0
- package/extension/surfaces/surfaces.ts +79 -27
- package/extension/waves/adversarialReviewWave.ts +87 -46
- package/extension/waves/blockedReports.ts +59 -0
- package/extension/waves/draftReviewWave.ts +42 -42
- package/extension/waves/laneIdentity.ts +77 -0
- package/extension/waves/objectiveExplorerWave.ts +24 -24
- package/extension/waves/prReviewWave.ts +89 -77
- package/extension/waves/reportWave.ts +438 -578
- package/extension/waves/reviewClassifierWave.ts +22 -22
- package/extension/waves/rpcAdapter.ts +100 -15
- package/extension/waves/scoutWave.ts +192 -0
- package/extension/waves/transport.ts +480 -0
- package/extension/worker/sdkAdapter.ts +494 -0
- package/extension/worker/stageExecution.ts +679 -0
- package/extension/workerMain.ts +18 -19
- package/package.json +6 -4
- package/prompts/_fixtures/live.yaml +43 -18
- package/prompts/contexts/adapters/plannotator-gist.md +6 -0
- package/prompts/contexts/adapters/plannotator-objective.md +6 -0
- package/prompts/contexts/adapters/plannotator-plan.md +8 -1
- package/prompts/contexts/adapters/plannotator-refinement.md +22 -0
- package/prompts/contexts/objective-refinement.md +17 -0
- package/prompts/contexts/read-only.md +1 -1
- package/prompts/stages/conflict-resolution-continuation.md +9 -6
- package/prompts/stages/conflict-resolution.md +4 -4
- package/prompts/stages/objective-plan/guidance.md +2 -2
- package/prompts/stages/objective-plan/seed.md +9 -1
- package/prompts/stages/objective-reconcile-ready.md +1 -1
- package/prompts/stages/objective-reconcile.md +1 -1
- package/prompts/stages/objective-refine/seed.md +18 -0
- package/prompts/stages/objective-review-browser.md +4 -4
- package/prompts/stages/objective-sync.md +1 -1
- package/prompts/stages/plan-review-browser.md +4 -4
- package/prompts/stages/pr-review-browser/active.md +3 -4
- package/prompts/stages/pr-review-browser/foreign.md +3 -4
- package/prompts/stages/pr-review-terminal/active.md +3 -3
- package/prompts/stages/pr-review-terminal/foreign.md +3 -3
- package/prompts/stages/pr-review.md +3 -3
- package/prompts/stages/stack-review-browser/stack.md +5 -6
- package/shared/README.md +8 -0
- package/shared/bindings.yaml +3 -3
- package/shared/contracts.md +2601 -506
- package/shared/fixtures/issues-table.json +130 -0
- package/shared/registry.yaml +13 -0
- package/shared/schemas/outputs/objective-node-engagement.schema.json +318 -0
- package/shared/schemas/outputs/objective-stack-status.schema.json +6 -1
- package/shared/schemas/outputs/pr-review-context.schema.json +54 -9
- package/shared/schemas/outputs/pr-review-stack-context.schema.json +196 -0
- package/extension/adapters/planAdapterPlannotator.ts +0 -362
- package/extension/doors/auditWaveTools.ts +0 -352
- package/extension/doors/ciExecutor.ts +0 -756
- package/extension/doors/commitCompact.ts +0 -251
- package/extension/doors/dreamWaveTools.ts +0 -489
- package/extension/doors/learn.ts +0 -668
- package/extension/doors/lifecycleGates.ts +0 -207
- package/extension/doors/objectiveStack.ts +0 -1543
- package/extension/doors/prReviewDynamic.ts +0 -276
- package/extension/doors/ready.ts +0 -279
- package/extension/doors/submit.ts +0 -373
- package/extension/doors/submitPrReview.ts +0 -505
- package/extension/factories/gistAuthor.ts +0 -94
- package/extension/factories/gistDraft.ts +0 -265
- package/extension/factories/gistSave.ts +0 -251
- package/extension/factories/implementHere.ts +0 -116
- package/extension/factories/objectiveAuthor.ts +0 -98
- package/extension/factories/objectiveDraft.ts +0 -466
- package/extension/factories/objectiveSave.ts +0 -366
- package/extension/factories/planDraft.ts +0 -140
- package/extension/factories/planMode.ts +0 -205
- package/extension/factories/planReview.ts +0 -1237
- package/extension/factories/planSave.ts +0 -604
- package/extension/factories/planTitle.ts +0 -141
- package/extension/substrate/structuredOutput.ts +0 -202
- package/extension/waves/auditWave.ts +0 -312
- package/extension/waves/harvestWave.ts +0 -399
- package/extension/waves/learnWave.ts +0 -155
- package/extension/waves/memoryAdapter.ts +0 -139
- package/extension/waves/prReviewDynamicWave.ts +0 -777
- package/extension/worker/readOnlySession.ts +0 -294
- package/extension/worker/worker.ts +0 -899
- package/prompts/stages/pr-review-dynamic.md +0 -7
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
// The objective review arm of the `plan_review` door — a thin ADAPTER over the feature's
|
|
2
|
+
// `reviewObjectiveDraft` (authoring/objective/review.ts). This module owns what is genuinely
|
|
3
|
+
// Pi/provider-shaped: the headless skip, the wave arm's raw-baseline read ordering, the launch
|
|
4
|
+
// chooser, the two reviewer constructions (plannotator bridge / first-party view-only editor —
|
|
5
|
+
// provider vocabulary is translated INTO `ObjectiveReviewOutcome` here, the `# Direct Edits`
|
|
6
|
+
// heading check included), the composed `objectiveApprovalSaveV1` binding, and the rendered
|
|
7
|
+
// Result envelopes (every text/details shape byte-stable, `details.subject: "objective"`
|
|
8
|
+
// included).
|
|
9
|
+
//
|
|
10
|
+
// THE OBJECTIVE ARM: an objective-authoring session (read-only, stage `objective-author` or
|
|
11
|
+
// `objective-save` — the two stages whose working draft IS the objective draft; neither carries
|
|
12
|
+
// `plan_draft`) routes through `executeObjectiveReview` instead of the plan path — the
|
|
13
|
+
// reviewed bytes are the RENDERED objective draft (the feature's resume+render — never raw
|
|
14
|
+
// JSON, never the `plan` param, never the transcript; no draft soft-skips with
|
|
15
|
+
// `reason: "no_objective_draft"`). First-party reviews run VIEW-ONLY (edits are never written
|
|
16
|
+
// back; deny+feedback is the change channel). An APPROVED outcome wires into the approval→save
|
|
17
|
+
// seam: re-read the STRUCTURED artifact → `saveObjective` → D1a gate exit → a TERMINATING
|
|
18
|
+
// result; an unconfirmed save is non-terminating, leaves the gate read-only, and latches
|
|
19
|
+
// automatic saves off for the activation (`draftReview.ts`) — `/objective-save` is the
|
|
20
|
+
// deliberate retry. Every arm opens the current-review slot at entry and runs the decision
|
|
21
|
+
// ladder on a completed verdict before anything is saved.
|
|
22
|
+
|
|
23
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
24
|
+
import { renderObjectiveDraft, resumeObjectiveDraft } from "../../authoring/objective/draft.ts";
|
|
25
|
+
import {
|
|
26
|
+
completeObjectiveReview,
|
|
27
|
+
type ObjectiveReviewOutcome,
|
|
28
|
+
type ReviewObjectiveDraftResult,
|
|
29
|
+
} from "../../authoring/objective/review.ts";
|
|
30
|
+
import { objectiveApprovalSave } from "../../authoring/objective/save.ts";
|
|
31
|
+
import { openBranchWorkflowSession } from "../../session/branchWorkflowSession.ts";
|
|
32
|
+
import type { ToolGating } from "../../substrate/toolGating.ts";
|
|
33
|
+
import {
|
|
34
|
+
checkDraftReviewDecision,
|
|
35
|
+
type DecisionCheck,
|
|
36
|
+
type DraftReviewSlot,
|
|
37
|
+
destinationChangedResult,
|
|
38
|
+
type OpenDraftReview,
|
|
39
|
+
openRefusedResult,
|
|
40
|
+
recordSaveOutcome,
|
|
41
|
+
saveUnconfirmedResult,
|
|
42
|
+
staleApprovalResult,
|
|
43
|
+
supersededReviewResult,
|
|
44
|
+
withDraftChangedNote,
|
|
45
|
+
} from "./draftReview.ts";
|
|
46
|
+
import {
|
|
47
|
+
type ObjectiveApprovalSaveV1Outcome,
|
|
48
|
+
objectiveSaveDepsFor,
|
|
49
|
+
renderObjectiveApprovalSave,
|
|
50
|
+
} from "./objectiveAuthoring.ts";
|
|
51
|
+
import { hasDirectEditsHeading } from "./providers/plannotator.ts";
|
|
52
|
+
import { isPlannotatorPlanSelected } from "./providers/selection.ts";
|
|
53
|
+
import {
|
|
54
|
+
approvedSubjectSaveResult,
|
|
55
|
+
chooseReviewLaunch,
|
|
56
|
+
type DraftReviewBridge,
|
|
57
|
+
type ReviewOutcome,
|
|
58
|
+
type ReviewSubject,
|
|
59
|
+
runFirstPartyReview,
|
|
60
|
+
skipResult,
|
|
61
|
+
subjectReviewOutcomeResult,
|
|
62
|
+
type ToolResult,
|
|
63
|
+
untrustedReviewFeedback,
|
|
64
|
+
verdictsFor,
|
|
65
|
+
type WaveLaunch,
|
|
66
|
+
waveLaunchedResult,
|
|
67
|
+
} from "./review.ts";
|
|
68
|
+
|
|
69
|
+
/** The objective-arm descriptor for the shared renderer cores. */
|
|
70
|
+
export const OBJECTIVE_SUBJECT: ReviewSubject = {
|
|
71
|
+
noun: "objective",
|
|
72
|
+
present: "the complete objective + structured roadmap to the user",
|
|
73
|
+
presentUnavailable: "the complete objective + structured roadmap to the user",
|
|
74
|
+
implementHereWhere: "on the objective path",
|
|
75
|
+
draftTool: "objective_draft",
|
|
76
|
+
failsafeCmd: "/objective-save",
|
|
77
|
+
detailsExtra: { subject: "objective" },
|
|
78
|
+
noSourceError: "no objective draft resolved",
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export const OBJECTIVE_REVIEW_EDITOR_TITLE =
|
|
82
|
+
"Objective review (view only — edits are not saved) — Enter: continue to verdict · Esc: skip · " +
|
|
83
|
+
"Ctrl+G: $EDITOR";
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Map a non-approved objective review outcome into the model-facing tool result (exported for
|
|
87
|
+
* the offline tests) — the objective-flavored sibling of `reviewOutcomeResult`, delegating to
|
|
88
|
+
* `subjectReviewOutcomeResult` with `OBJECTIVE_SUBJECT`. Every arm carries
|
|
89
|
+
* `details.subject: "objective"`; the texts redirect to `objective_draft` / `/objective-save`.
|
|
90
|
+
* The execute path routes approved outcomes to `approvedObjectiveSaveResult` first, so
|
|
91
|
+
* `completed` renders DENIED here.
|
|
92
|
+
*/
|
|
93
|
+
export function objectiveReviewOutcomeResult(outcome: ReviewOutcome): ToolResult {
|
|
94
|
+
return subjectReviewOutcomeResult(OBJECTIVE_SUBJECT, outcome);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Map an APPROVED objective review outcome + the approval-save outcome into the model-facing
|
|
99
|
+
* tool result (exported for the offline tests) — the objective sibling of `approvedSaveResult`,
|
|
100
|
+
* delegating to `approvedSubjectSaveResult` with `OBJECTIVE_SUBJECT` and no opts (the objective
|
|
101
|
+
* path reviews only the rendered draft, view-only — no `paramMismatch`/`edited`).
|
|
102
|
+
*/
|
|
103
|
+
export function approvedObjectiveSaveResult(
|
|
104
|
+
outcome: Extract<ReviewOutcome, { status: "completed" }>,
|
|
105
|
+
save: ObjectiveApprovalSaveV1Outcome,
|
|
106
|
+
): ToolResult {
|
|
107
|
+
return approvedSubjectSaveResult(
|
|
108
|
+
OBJECTIVE_SUBJECT,
|
|
109
|
+
outcome,
|
|
110
|
+
save.status === "no-draft"
|
|
111
|
+
? { status: "no-source" }
|
|
112
|
+
: save.status === "refused-draft"
|
|
113
|
+
? { status: "refused-draft", problem: save.problem }
|
|
114
|
+
: save,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The no-draft soft skip (byte-stable redirect to objective_draft). */
|
|
119
|
+
function noObjectiveDraftResult(): ToolResult {
|
|
120
|
+
return {
|
|
121
|
+
content: [
|
|
122
|
+
{
|
|
123
|
+
type: "text",
|
|
124
|
+
text:
|
|
125
|
+
"no objective draft to review — write the working objective with objective_draft " +
|
|
126
|
+
"(prose + the structured roadmap), then call plan_review again.",
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
details: {
|
|
130
|
+
ok: false,
|
|
131
|
+
error: "no objective draft to review — write it with objective_draft first",
|
|
132
|
+
error_type: "no_objective_draft",
|
|
133
|
+
status: "skipped",
|
|
134
|
+
reason: "no_objective_draft",
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* The Direct-Edits revise round (plannotator only, contracts §8.23's objective arm): the save
|
|
141
|
+
* seam re-reads the STRUCTURED artifact, so rendered-markdown edits — roadmap-table rows
|
|
142
|
+
* included — cannot be folded back without model judgment. Nothing was saved, the gate stays
|
|
143
|
+
* read-only; the model folds the diff into `objective_draft`, then re-reviews to confirm.
|
|
144
|
+
*/
|
|
145
|
+
function directEditsReviseResult(feedback: string, reviewId: string | undefined): ToolResult {
|
|
146
|
+
return {
|
|
147
|
+
content: [
|
|
148
|
+
{
|
|
149
|
+
type: "text",
|
|
150
|
+
text:
|
|
151
|
+
"objective APPROVED with direct browser edits — these cannot be auto-applied to " +
|
|
152
|
+
"the structured draft, so nothing was saved. Fold the Direct Edits diff below into " +
|
|
153
|
+
"the working draft with objective_draft (prose hunks → the prose; roadmap-table " +
|
|
154
|
+
"hunks → the matching node fields), then call plan_review again to confirm.\n\n" +
|
|
155
|
+
`Reviewer feedback:\n${untrustedReviewFeedback(feedback)}`,
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
details: {
|
|
159
|
+
ok: true,
|
|
160
|
+
status: "revise",
|
|
161
|
+
reason: "direct_edits",
|
|
162
|
+
approved: true,
|
|
163
|
+
feedback,
|
|
164
|
+
reviewId,
|
|
165
|
+
subject: "objective",
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Rebuild the door's completed outcome from the feature arm (the shared mappers consume it). */
|
|
171
|
+
function completedOutcome(
|
|
172
|
+
approved: boolean,
|
|
173
|
+
carried: { feedback?: string; reviewId?: string },
|
|
174
|
+
): Extract<ReviewOutcome, { status: "completed" }> {
|
|
175
|
+
return {
|
|
176
|
+
status: "completed",
|
|
177
|
+
approved,
|
|
178
|
+
...(carried.feedback !== undefined ? { feedback: carried.feedback } : {}),
|
|
179
|
+
reviewId: carried.reviewId ?? "",
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Translate a review-door outcome (`ReviewOutcome`) into the feature's
|
|
185
|
+
* `ObjectiveReviewOutcome`. Plannotator's vocabulary is translated here: an approval whose
|
|
186
|
+
* feedback OPENS with the `# Direct Edits` heading becomes the `approvedDirectEdits` variant —
|
|
187
|
+
* the heading check suffices (extraction success is irrelevant: the diff goes to the model
|
|
188
|
+
* verbatim either way; the variant requires the feedback, so the edits can never be dropped).
|
|
189
|
+
* The `implement-here` arm is unreachable on the objective path (neither reviewer offers it)
|
|
190
|
+
* and maps defensively to `dismissed`.
|
|
191
|
+
*/
|
|
192
|
+
function objectiveOutcomeOf(outcome: ReviewOutcome): ObjectiveReviewOutcome {
|
|
193
|
+
switch (outcome.status) {
|
|
194
|
+
case "completed": {
|
|
195
|
+
const carried = {
|
|
196
|
+
...(outcome.feedback !== undefined ? { feedback: outcome.feedback } : {}),
|
|
197
|
+
reviewId: outcome.reviewId,
|
|
198
|
+
};
|
|
199
|
+
if (outcome.approved) {
|
|
200
|
+
if (outcome.feedback !== undefined && hasDirectEditsHeading(outcome.feedback)) {
|
|
201
|
+
return {
|
|
202
|
+
status: "approvedDirectEdits",
|
|
203
|
+
rawFeedback: outcome.feedback,
|
|
204
|
+
reviewId: outcome.reviewId,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return { status: "approved", ...carried };
|
|
208
|
+
}
|
|
209
|
+
return { status: "denied", ...carried };
|
|
210
|
+
}
|
|
211
|
+
case "unavailable":
|
|
212
|
+
return { status: "unavailable", warning: outcome.warning };
|
|
213
|
+
case "aborted":
|
|
214
|
+
return { status: "aborted" };
|
|
215
|
+
case "dismissed":
|
|
216
|
+
return { status: "dismissed" };
|
|
217
|
+
case "implement-here":
|
|
218
|
+
return { status: "dismissed" };
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* The first-party reviewer: the in-TUI editor review, VIEW-ONLY (3 verdicts). Returns the door
|
|
224
|
+
* vocabulary (`ReviewOutcome`) so both arms share one ladder + completion path.
|
|
225
|
+
*/
|
|
226
|
+
async function firstPartyObjectiveReview(
|
|
227
|
+
ctx: ExtensionContext,
|
|
228
|
+
rendered: string,
|
|
229
|
+
signal: AbortSignal | undefined,
|
|
230
|
+
): Promise<ReviewOutcome> {
|
|
231
|
+
const fp = await runFirstPartyReview({
|
|
232
|
+
ui: ctx.ui,
|
|
233
|
+
plan: rendered,
|
|
234
|
+
writeDraft: () => true, // unreachable under viewOnly — the branch is skipped
|
|
235
|
+
signal,
|
|
236
|
+
editorTitle: OBJECTIVE_REVIEW_EDITOR_TITLE,
|
|
237
|
+
verdicts: verdictsFor(OBJECTIVE_SUBJECT),
|
|
238
|
+
viewOnly: true,
|
|
239
|
+
});
|
|
240
|
+
return fp.outcome;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Whether a completed bridge outcome's effect is a save (an approval without Direct Edits). */
|
|
244
|
+
export function objectiveEffectOf(
|
|
245
|
+
outcome: Extract<ReviewOutcome, { status: "completed" }>,
|
|
246
|
+
): "save" | "revision" {
|
|
247
|
+
return outcome.approved &&
|
|
248
|
+
!(outcome.feedback !== undefined && hasDirectEditsHeading(outcome.feedback))
|
|
249
|
+
? "save"
|
|
250
|
+
: "revision";
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Render a non-`proceed` ladder verdict as the objective arm's tool result (nothing saved; the
|
|
255
|
+
* gate untouched). Shared by the Plannotator arm, the first-party arm and the browser door.
|
|
256
|
+
*/
|
|
257
|
+
export function objectiveGuardResult(
|
|
258
|
+
check: Exclude<DecisionCheck, { kind: "proceed" }>,
|
|
259
|
+
review: OpenDraftReview,
|
|
260
|
+
feedback: string | undefined,
|
|
261
|
+
): ToolResult {
|
|
262
|
+
switch (check.kind) {
|
|
263
|
+
case "superseded":
|
|
264
|
+
return supersededReviewResult("objective");
|
|
265
|
+
case "save-unconfirmed":
|
|
266
|
+
return saveUnconfirmedResult("objective", review.runId, check.detail, feedback);
|
|
267
|
+
case "stale-approval":
|
|
268
|
+
return staleApprovalResult("objective", check.reviewedDigest, feedback);
|
|
269
|
+
case "destination-changed":
|
|
270
|
+
return destinationChangedResult("objective", check.changed, feedback);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Report a completed objective review's save result into the unconfirmed-save latch. */
|
|
275
|
+
function recordObjectiveSaveOutcome(
|
|
276
|
+
slot: DraftReviewSlot,
|
|
277
|
+
result: ReviewObjectiveDraftResult<ObjectiveApprovalSaveV1Outcome>,
|
|
278
|
+
): void {
|
|
279
|
+
if (result.status !== "approvedSave") return;
|
|
280
|
+
const save = result.save;
|
|
281
|
+
switch (save.status) {
|
|
282
|
+
case "saved":
|
|
283
|
+
recordSaveOutcome(slot, "objective", { confirmed: true });
|
|
284
|
+
return;
|
|
285
|
+
case "save-failed":
|
|
286
|
+
recordSaveOutcome(slot, "objective", {
|
|
287
|
+
confirmed: false,
|
|
288
|
+
detail: save.result.details.ok ? undefined : save.result.details.error,
|
|
289
|
+
});
|
|
290
|
+
return;
|
|
291
|
+
case "no-draft":
|
|
292
|
+
case "refused-draft":
|
|
293
|
+
// Nothing reached the backend — no attempt to confirm.
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* The objective review arm, mirroring the plan arm's shape: the headless skip, the wave arm's
|
|
300
|
+
* launch chooser, the slot open (both arms — the resume's raw artifact bytes are the reviewed
|
|
301
|
+
* bytes, the rendered markdown what the reviewer saw; ONE validated read serves both), the
|
|
302
|
+
* review, the decision ladder on a completed verdict, then the subject completion with the
|
|
303
|
+
* save reported into the latch.
|
|
304
|
+
*/
|
|
305
|
+
export async function executeObjectiveReview(
|
|
306
|
+
pi: ExtensionAPI,
|
|
307
|
+
ctx: ExtensionContext,
|
|
308
|
+
gating: ToolGating,
|
|
309
|
+
bridge: DraftReviewBridge,
|
|
310
|
+
slot: DraftReviewSlot,
|
|
311
|
+
signal?: AbortSignal,
|
|
312
|
+
wave?: WaveLaunch,
|
|
313
|
+
): Promise<ToolResult> {
|
|
314
|
+
// 1. Headless → soft skip (fail-open; never wedges CI/supervisor runs on an interactive UI).
|
|
315
|
+
if (!ctx.hasUI) return skipResult();
|
|
316
|
+
const sig = signal ?? ctx.signal;
|
|
317
|
+
// 2. ONE validated read serves both the rendering and the reviewed-bytes baseline (the
|
|
318
|
+
// resume carries the exact bytes it decoded), so the baseline can never be newer than
|
|
319
|
+
// what the human saw — a second read could straddle a concurrent objective_draft write
|
|
320
|
+
// and let an approval save bytes nobody reviewed. Raw artifact bytes as the baseline on
|
|
321
|
+
// purpose: the save-authoritative surface catches render-invisible changes.
|
|
322
|
+
const session = openBranchWorkflowSession(pi, ctx);
|
|
323
|
+
// 3. Backend dispatch (mirrors the plan path): plannotator-selected → the bridge; ANY other
|
|
324
|
+
// selection → the first-party editor, view-only.
|
|
325
|
+
const plannotator = isPlannotatorPlanSelected(ctx.cwd);
|
|
326
|
+
if (plannotator && wave?.present()) {
|
|
327
|
+
// The launch chooser (contracts.md §8.23): every eligible round the human picks with/without
|
|
328
|
+
// the streamed reviewer wave BEFORE anything launches. Eligibility is drafts-only — the wave
|
|
329
|
+
// door stale-guards the raw artifact bytes, so an absent draft keeps the plain path
|
|
330
|
+
// (silently: there is no forced mode to warn about). A `refused` resume (raw bytes
|
|
331
|
+
// present but refused) also skips the wave arm — the plain review below renders the
|
|
332
|
+
// refused-draft skip exactly once.
|
|
333
|
+
const resumed = resumeObjectiveDraft(session);
|
|
334
|
+
if (resumed.kind === "valid") {
|
|
335
|
+
const choice = await chooseReviewLaunch(ctx.ui, "Objective", sig);
|
|
336
|
+
if (choice.launch === "aborted") return objectiveReviewOutcomeResult({ status: "aborted" });
|
|
337
|
+
if (choice.launch === "wave") {
|
|
338
|
+
const guidance = await wave.objective(ctx, {
|
|
339
|
+
// The reviewed bytes are the RENDERED markdown (prose + roadmap table) — never raw
|
|
340
|
+
// JSON; the stale-guard baseline is the SAME read's raw bytes.
|
|
341
|
+
rendered: renderObjectiveDraft(resumed.draft),
|
|
342
|
+
artifactRaw: resumed.raw,
|
|
343
|
+
...(choice.custom !== undefined ? { custom: choice.custom } : {}),
|
|
344
|
+
});
|
|
345
|
+
// Abort outranks the opener result too: a turn interrupted during the awaited open must
|
|
346
|
+
// never report a successful launch (the door's own bridge abort handling settles the
|
|
347
|
+
// background tasks and clears the primed surfaces).
|
|
348
|
+
if (sig?.aborted) return objectiveReviewOutcomeResult({ status: "aborted" });
|
|
349
|
+
if (guidance !== null) return waveLaunchedResult(OBJECTIVE_SUBJECT, guidance);
|
|
350
|
+
// null = the synchronous port-pick failure (already loudly reported inside the core) —
|
|
351
|
+
// fall open to the plain blocking review in the same call: the review never wedges.
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (sig?.aborted) return objectiveReviewOutcomeResult({ status: "aborted" });
|
|
357
|
+
// 4. Resolve the reviewed bytes: a fresh validated resume (the chooser above was a human
|
|
358
|
+
// wait) — its draft for the render, its raw bytes for the guard (never the rendered bytes
|
|
359
|
+
// — the save re-reads the STRUCTURED artifact).
|
|
360
|
+
const resumed = resumeObjectiveDraft(session);
|
|
361
|
+
if (resumed.kind === "absent") return noObjectiveDraftResult();
|
|
362
|
+
if (resumed.kind === "refused")
|
|
363
|
+
return renderObjectiveReviewResult({ status: "refusedDraft", problem: resumed.problem });
|
|
364
|
+
const rendered = renderObjectiveDraft(resumed.draft);
|
|
365
|
+
const opened = slot.open(ctx, {
|
|
366
|
+
subject: "objective",
|
|
367
|
+
source: plannotator ? "artifact" : "editor",
|
|
368
|
+
raw: resumed.raw,
|
|
369
|
+
markdown: rendered,
|
|
370
|
+
});
|
|
371
|
+
if (!opened.ok) return openRefusedResult(opened);
|
|
372
|
+
const review = opened.review;
|
|
373
|
+
|
|
374
|
+
// 5. The review itself, then the ladder, then the completion.
|
|
375
|
+
const outcome = plannotator
|
|
376
|
+
? await bridge.review(rendered, sig)
|
|
377
|
+
: await firstPartyObjectiveReview(ctx, rendered, sig);
|
|
378
|
+
if (sig?.aborted) return objectiveReviewOutcomeResult({ status: "aborted" });
|
|
379
|
+
if (outcome.status !== "completed") return objectiveReviewOutcomeResult(outcome);
|
|
380
|
+
const check = checkDraftReviewDecision(slot, ctx, review, objectiveEffectOf(outcome));
|
|
381
|
+
if (check.kind !== "proceed") return objectiveGuardResult(check, review, outcome.feedback);
|
|
382
|
+
return withDraftChangedNote(
|
|
383
|
+
await completeObjectiveReviewV1(pi, ctx, gating, slot, outcome),
|
|
384
|
+
check.draftChanged,
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function renderObjectiveReviewResult(
|
|
389
|
+
result: ReviewObjectiveDraftResult<ObjectiveApprovalSaveV1Outcome>,
|
|
390
|
+
): ToolResult {
|
|
391
|
+
switch (result.status) {
|
|
392
|
+
case "noDraft":
|
|
393
|
+
return noObjectiveDraftResult();
|
|
394
|
+
case "refusedDraft":
|
|
395
|
+
// Fail-closed soft skip (the `noObjectiveDraftResult` shape): an invalid artifact is
|
|
396
|
+
// never reviewed — rewrite, then re-review. Gate untouched.
|
|
397
|
+
return {
|
|
398
|
+
content: [
|
|
399
|
+
{
|
|
400
|
+
type: "text",
|
|
401
|
+
text:
|
|
402
|
+
`the working objective draft is invalid: ${result.problem} — rewrite it with ` +
|
|
403
|
+
"objective_draft, then call plan_review again.",
|
|
404
|
+
},
|
|
405
|
+
],
|
|
406
|
+
details: {
|
|
407
|
+
ok: false,
|
|
408
|
+
error: result.problem,
|
|
409
|
+
error_type: "bad_state",
|
|
410
|
+
status: "skipped",
|
|
411
|
+
reason: "objective_draft_refused",
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
case "approvedDirectEdits":
|
|
415
|
+
return directEditsReviseResult(result.rawFeedback, result.reviewId);
|
|
416
|
+
case "approvedSave":
|
|
417
|
+
return approvedObjectiveSaveResult(completedOutcome(true, result), result.save);
|
|
418
|
+
case "denied":
|
|
419
|
+
return objectiveReviewOutcomeResult(completedOutcome(false, result));
|
|
420
|
+
case "dismissed":
|
|
421
|
+
return objectiveReviewOutcomeResult({ status: "dismissed" });
|
|
422
|
+
case "aborted":
|
|
423
|
+
return objectiveReviewOutcomeResult({ status: "aborted" });
|
|
424
|
+
case "unavailable":
|
|
425
|
+
return objectiveReviewOutcomeResult({ status: "unavailable", warning: result.warning });
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* The objective completion shared by both tool arms and the browser door: a completed outcome
|
|
431
|
+
* → the feature completion (an approval re-reads the STRUCTURED artifact and saves through the
|
|
432
|
+
* production deps; Direct Edits is the no-save revise round) → the latch record → the rendered
|
|
433
|
+
* tool result. The caller has already run the decision ladder.
|
|
434
|
+
*/
|
|
435
|
+
export async function completeObjectiveReviewV1(
|
|
436
|
+
pi: ExtensionAPI,
|
|
437
|
+
ctx: ExtensionContext,
|
|
438
|
+
gating: ToolGating,
|
|
439
|
+
slot: DraftReviewSlot,
|
|
440
|
+
outcome: Extract<ReviewOutcome, { status: "completed" }>,
|
|
441
|
+
): Promise<ToolResult> {
|
|
442
|
+
const result = await completeObjectiveReview(objectiveOutcomeOf(outcome), async () =>
|
|
443
|
+
renderObjectiveApprovalSave(
|
|
444
|
+
pi,
|
|
445
|
+
ctx,
|
|
446
|
+
await objectiveApprovalSave(objectiveSaveDepsFor(pi, ctx, gating)),
|
|
447
|
+
),
|
|
448
|
+
);
|
|
449
|
+
recordObjectiveSaveOutcome(slot, result);
|
|
450
|
+
return renderObjectiveReviewResult(result);
|
|
451
|
+
}
|