@mgiles/perk 3.1.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 +369 -359
- 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 -310
- package/extension/{doors/prReviewBrowser.ts → pi/v1/codeReview/browser.ts} +125 -57
- package/extension/{doors/hunkHandoff.ts → pi/v1/codeReview/checkout.ts} +12 -8
- package/extension/{doors/reviewWaveTools.ts → pi/v1/codeReview/reviewWave.ts} +169 -116
- package/extension/pi/v1/codeReview/stack.ts +606 -0
- 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 +163 -265
- 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 +128 -60
- 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/{doors → pi/v1}/lifecycleGates.ts +14 -59
- 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/pi/v1/objectivePlanning.ts +776 -0
- 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 +152 -34
- 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 -19
- package/extension/substrate/git.ts +88 -6
- package/extension/substrate/modelVisible.ts +53 -0
- package/extension/substrate/paths.ts +2 -7
- package/extension/substrate/prompts.ts +22 -0
- package/extension/substrate/registry.ts +2 -0
- package/extension/substrate/resolverLease.ts +364 -0
- package/extension/substrate/sessionData.ts +85 -152
- package/extension/substrate/toolGating.ts +279 -84
- package/extension/substrate/unifiedDiff.ts +1 -1
- package/extension/substrate/workflowState.ts +191 -166
- package/extension/substrate/worktreeResolverLock.ts +261 -0
- package/extension/surfaces/surfaces.ts +79 -27
- package/extension/waves/adversarialReviewWave.ts +103 -48
- 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 +98 -10
- 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/adapters/tombell-plan.md +4 -0
- package/prompts/contexts/objective-refinement.md +17 -0
- package/prompts/contexts/plan-authoring.md +6 -5
- package/prompts/contexts/read-only.md +1 -1
- package/prompts/stages/conflict-resolution-continuation.md +9 -0
- package/prompts/stages/conflict-resolution.md +4 -4
- package/prompts/stages/objective-author/adopt.md +1 -1
- package/prompts/stages/objective-author/file.md +1 -1
- package/prompts/stages/objective-author/seed.md +1 -1
- 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 +7 -0
- 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/cold.md +1 -0
- package/prompts/stages/stack-review-browser/stack.md +22 -0
- package/shared/README.md +8 -3
- package/shared/bindings.yaml +6 -3
- package/shared/contracts.md +4499 -2147
- package/shared/fixtures/issues-table.json +130 -0
- package/shared/registry.yaml +29 -1
- package/shared/schemas/outputs/objective-node-engagement.schema.json +318 -0
- package/shared/schemas/outputs/objective-stack-status.schema.json +178 -2
- package/shared/schemas/outputs/pr-ready.schema.json +110 -2
- 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 -475
- package/extension/doors/learn.ts +0 -655
- package/extension/doors/objectiveStack.ts +0 -1143
- package/extension/doors/prReviewDynamic.ts +0 -276
- package/extension/doors/ready.ts +0 -87
- package/extension/doors/submit.ts +0 -347
- package/extension/doors/submitPrReview.ts +0 -408
- 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/objectivePlan.ts +0 -975
- package/extension/factories/objectiveSave.ts +0 -363
- 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
- package/shared/contracts-history.md +0 -605
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
// The warm `/submit` door. The in-session twin of the Python cold door
|
|
2
|
-
// (`perk pr submit`): a deterministic, terminating tool + command that DELEGATE the GitHub write —
|
|
3
|
-
// they do NOT reimplement it (GitHub mutations are canonical in the Python gateway). Mirrors
|
|
4
|
-
// `planSave.ts`: write nothing, delegate to `perk pr submit --json` via `pi.exec`, surface the
|
|
5
|
-
// structured result, never throw (failures are loud-but-non-fatal via `details.ok = false`).
|
|
6
|
-
//
|
|
7
|
-
// Mergeability gate: `perk pr submit` probes the PR's mergeability against the base branch
|
|
8
|
-
// (a deterministic local `git merge-tree` probe). When it reports a definitively-unmergeable PR
|
|
9
|
-
// (`mergeable === false` + conflicts), the warm door drives a fresh-context, write-capable
|
|
10
|
-
// `perk.conflict-resolver` subagent (mirroring `/pr-review`'s dispatch and `/land`'s reconcile
|
|
11
|
-
// drive) to rebase + resolve + push, then asks the model to re-`/submit` to confirm. Bounded by
|
|
12
|
-
// `CONFLICT_RESOLUTION_ATTEMPT_CAP` via the `conflict_resolution_attempts` workflow-state field.
|
|
13
|
-
|
|
14
|
-
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
15
|
-
import { bindingSuffix } from "../substrate/bindingDelivery.ts";
|
|
16
|
-
import {
|
|
17
|
-
booleanField,
|
|
18
|
-
type ColdJson,
|
|
19
|
-
numberField,
|
|
20
|
-
objectField,
|
|
21
|
-
runColdDoor,
|
|
22
|
-
stringField,
|
|
23
|
-
} from "../substrate/coldDoor.ts";
|
|
24
|
-
import { registerPerkCommand } from "../substrate/command.ts";
|
|
25
|
-
import { subagentModel } from "../substrate/config.ts";
|
|
26
|
-
import { render } from "../substrate/prompts.ts";
|
|
27
|
-
import { failFor, type OkDetails, ok, type Result } from "../substrate/result.ts";
|
|
28
|
-
import { captureSessionPointer } from "../substrate/sessionPointers.ts";
|
|
29
|
-
import { appendWorkflowState, branchOf, rebuildWorkflowState } from "../substrate/workflowState.ts";
|
|
30
|
-
import { report } from "../surfaces/report.ts";
|
|
31
|
-
|
|
32
|
-
/** The bounded conflict-resolution re-drive cap: drive the resolver at most this many times. */
|
|
33
|
-
export const CONFLICT_RESOLUTION_ATTEMPT_CAP = 2;
|
|
34
|
-
|
|
35
|
-
/** The ok-arm fields — the structured `details` surface doubles as branch-safe persisted state. */
|
|
36
|
-
export interface SubmitOk {
|
|
37
|
-
pr: { number: number; url: string; is_draft: boolean; existed: boolean };
|
|
38
|
-
branch?: string;
|
|
39
|
-
issue?: number;
|
|
40
|
-
plan_embedded?: boolean;
|
|
41
|
-
/** The target branch the PR merges into (the conflict-resolver rebases onto it). */
|
|
42
|
-
base?: string;
|
|
43
|
-
/** Tri-state mergeability from the Python `git merge-tree` probe: true/false/null/absent. */
|
|
44
|
-
mergeable?: boolean | null;
|
|
45
|
-
/** The conflicted paths when `mergeable === false`; `[]` otherwise (advisory). */
|
|
46
|
-
conflicts?: string[];
|
|
47
|
-
/** `"stacked"` when the submit routed through the delivery publish operation (§8.47). */
|
|
48
|
-
delivery?: string;
|
|
49
|
-
/** The native-stack facts of a stacked submit (absent for the bottom layer). */
|
|
50
|
-
stack?: { number: number; size: number; position: number };
|
|
51
|
-
/** Lenient summary of an automatic suffix synchronization. */
|
|
52
|
-
operation?: {
|
|
53
|
-
kind: string;
|
|
54
|
-
operation_id: string | null;
|
|
55
|
-
no_op: boolean;
|
|
56
|
-
affected_count: number;
|
|
57
|
-
notes: string[];
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export type SubmitResult = Result<SubmitOk>;
|
|
62
|
-
export type SubmitDetails = SubmitResult["details"];
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* A tri-state read of the advisory `mergeable` field: `true`/`false`/`null` pass through;
|
|
66
|
-
* anything else (absent, mistyped) → `undefined`. Kept lenient so a malformed value never sinks
|
|
67
|
-
* an otherwise-successful submit decode.
|
|
68
|
-
*/
|
|
69
|
-
function mergeableField(payload: ColdJson): boolean | null | undefined {
|
|
70
|
-
const value = payload.mergeable;
|
|
71
|
-
if (value === true || value === false || value === null) return value;
|
|
72
|
-
return undefined;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** A lenient string-array read for the advisory `conflicts` field; malformed → `[]`. */
|
|
76
|
-
function conflictsField(payload: ColdJson): string[] {
|
|
77
|
-
const value = payload.conflicts;
|
|
78
|
-
if (Array.isArray(value) && value.every((p) => typeof p === "string")) return value as string[];
|
|
79
|
-
return [];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* A lenient read of the advisory stacked `stack` facts: all three numbers or nothing — a
|
|
84
|
-
* malformed value must NOT sink a successful submit decode (it just drops the suffix).
|
|
85
|
-
*/
|
|
86
|
-
function stackField(payload: ColdJson): SubmitOk["stack"] {
|
|
87
|
-
const value = objectField(payload, "stack");
|
|
88
|
-
if (value === undefined) return undefined;
|
|
89
|
-
const number = numberField(value, "number");
|
|
90
|
-
const size = numberField(value, "size");
|
|
91
|
-
const position = numberField(value, "position");
|
|
92
|
-
if (number === undefined || size === undefined || position === undefined) return undefined;
|
|
93
|
-
return { number, size, position };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Lenient all-or-nothing decode of the cascade operation block. The full affected rows remain a
|
|
98
|
-
* Python/CLI detail; the warm door needs only their count plus the recovery notes.
|
|
99
|
-
*/
|
|
100
|
-
function operationField(payload: ColdJson): SubmitOk["operation"] {
|
|
101
|
-
const value = objectField(payload, "operation");
|
|
102
|
-
if (value === undefined) return undefined;
|
|
103
|
-
const kind = stringField(value, "kind");
|
|
104
|
-
const operationId = value.operation_id;
|
|
105
|
-
const noOp = booleanField(value, "no_op");
|
|
106
|
-
const affected = value.affected;
|
|
107
|
-
const notes = value.notes;
|
|
108
|
-
if (
|
|
109
|
-
kind === undefined ||
|
|
110
|
-
(typeof operationId !== "string" && operationId !== null) ||
|
|
111
|
-
noOp === undefined ||
|
|
112
|
-
!Array.isArray(affected) ||
|
|
113
|
-
!Array.isArray(notes) ||
|
|
114
|
-
!notes.every((note) => typeof note === "string")
|
|
115
|
-
) {
|
|
116
|
-
return undefined;
|
|
117
|
-
}
|
|
118
|
-
return {
|
|
119
|
-
kind,
|
|
120
|
-
operation_id: operationId,
|
|
121
|
-
no_op: noOp,
|
|
122
|
-
affected_count: affected.length,
|
|
123
|
-
notes: notes as string[],
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Narrow the `perk pr submit --json` success payload; strict on `pr`, lenient on the rest. The
|
|
129
|
-
* `base`/`mergeable`/`conflicts` mergeability fields are advisory (mirror land.ts's lenient
|
|
130
|
-
* sub-fields): a malformed value must NOT make a successful submit decode to `null`.
|
|
131
|
-
*/
|
|
132
|
-
function decodeSubmit(payload: ColdJson): SubmitOk | null {
|
|
133
|
-
const pr = objectField(payload, "pr");
|
|
134
|
-
if (pr === undefined) return null;
|
|
135
|
-
const number = numberField(pr, "number");
|
|
136
|
-
const url = stringField(pr, "url");
|
|
137
|
-
const isDraft = booleanField(pr, "is_draft");
|
|
138
|
-
const existed = booleanField(pr, "existed");
|
|
139
|
-
if (number === undefined || url === undefined || isDraft === undefined || existed === undefined) {
|
|
140
|
-
return null;
|
|
141
|
-
}
|
|
142
|
-
return {
|
|
143
|
-
pr: { number, url, is_draft: isDraft, existed },
|
|
144
|
-
branch: stringField(payload, "branch"),
|
|
145
|
-
issue: numberField(payload, "issue"),
|
|
146
|
-
plan_embedded: booleanField(payload, "plan_embedded"),
|
|
147
|
-
base: stringField(payload, "base"),
|
|
148
|
-
mergeable: mergeableField(payload),
|
|
149
|
-
conflicts: conflictsField(payload),
|
|
150
|
-
delivery: stringField(payload, "delivery"),
|
|
151
|
-
stack: stackField(payload),
|
|
152
|
-
operation: operationField(payload),
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** A definitively-unmergeable submit; parsed conflict paths are advisory only. */
|
|
157
|
-
function isUnmergeable(details: SubmitDetails): details is OkDetails<SubmitOk> {
|
|
158
|
-
return details.ok && details.mergeable === false;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* The single submit implementation both surfaces call. Delegates to the Python cold door; returns a
|
|
163
|
-
* soft result (never throws) — failures set `details.ok = false`. On a clean submit
|
|
164
|
-
* (`mergeable !== false`) it resets the bounded conflict-resolution counter so a later independent
|
|
165
|
-
* conflict starts fresh; on conflicts the success message reflects the in-flight resolution.
|
|
166
|
-
*/
|
|
167
|
-
export async function submitPr(pi: ExtensionAPI, ctx: ExtensionContext): Promise<SubmitResult> {
|
|
168
|
-
const fail = failFor(ctx, "submit");
|
|
169
|
-
|
|
170
|
-
// Stamp this implement run id into the plan-header `impl_run_ids` linkage (contracts.md
|
|
171
|
-
// §8.35) so a later/other session can resolve the implement session pointers cross-run.
|
|
172
|
-
// Mirrors planSave's `--run-id` thread; covers the interactive /submit AND the headless worker
|
|
173
|
-
// drive (both flow through this warm door). Absent run_id ⇒ omit (bare-stamp untouched).
|
|
174
|
-
const runId = rebuildWorkflowState(branchOf(ctx)).run_id ?? "";
|
|
175
|
-
const args = ["pr", "submit", "--json"];
|
|
176
|
-
if (runId) args.push("--run-id", runId);
|
|
177
|
-
const r = await runColdDoor<SubmitOk>(pi, ctx, args, {
|
|
178
|
-
label: "perk pr submit",
|
|
179
|
-
decode: decodeSubmit,
|
|
180
|
-
});
|
|
181
|
-
if (!r.ok) return fail(r.message, r.errorType);
|
|
182
|
-
|
|
183
|
-
// Capture `implementation/main` at the moment the run id enters `impl_run_ids` (contracts.md
|
|
184
|
-
// §8.35): any run id stamped into the linkage gets its pointer captured in the same gesture.
|
|
185
|
-
// This covers address/warm sessions that submit — which the stage-gated `session_start` capture
|
|
186
|
-
// never sees — so a submitted run resolves `found` instead of `missing`. For the implement
|
|
187
|
-
// session's own /submit it is an idempotent same-session refresh; `preserveForeign` guarantees
|
|
188
|
-
// it can never clobber a different session's pointer. Best-effort + non-fatal like every
|
|
189
|
-
// capture site (a successful submit must stand).
|
|
190
|
-
if (runId) {
|
|
191
|
-
captureSessionPointer({
|
|
192
|
-
cwd: ctx.cwd,
|
|
193
|
-
runId,
|
|
194
|
-
klass: "implementation",
|
|
195
|
-
site: "main",
|
|
196
|
-
// Optional-chained: best-effort, and some side-session fakes have no getSessionFile.
|
|
197
|
-
sessionFile: ctx.sessionManager.getSessionFile?.(),
|
|
198
|
-
preserveForeign: true,
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const verb = r.data.pr.existed ? "Found existing" : "Opened draft";
|
|
203
|
-
const conflicted = r.data.mergeable === false;
|
|
204
|
-
// Reset the counter on every clean (or undetermined) submit — idempotent; keeps a later
|
|
205
|
-
// independent conflict bounded fresh.
|
|
206
|
-
if (r.data.mergeable !== false) resetConflictAttempts(pi, ctx);
|
|
207
|
-
// Automatic-cascade facts supersede the generic stacked suffix. A malformed operation block was
|
|
208
|
-
// dropped by the lenient decoder, so it falls back to the pre-existing stack wording.
|
|
209
|
-
const deliverySuffix =
|
|
210
|
-
r.data.operation?.kind === "sync"
|
|
211
|
-
? r.data.operation.no_op
|
|
212
|
-
? " (suffix already in sync)"
|
|
213
|
-
: ` (cascaded ${r.data.operation.affected_count} layer(s))`
|
|
214
|
-
: r.data.stack
|
|
215
|
-
? ` (stack #${r.data.stack.number}, layer ${r.data.stack.position}/${r.data.stack.size})`
|
|
216
|
-
: r.data.delivery === "stacked"
|
|
217
|
-
? " (stacked layer)"
|
|
218
|
-
: "";
|
|
219
|
-
for (const note of r.data.operation?.notes ?? []) {
|
|
220
|
-
report(ctx, "submit", "warning", note);
|
|
221
|
-
}
|
|
222
|
-
const message = conflicted
|
|
223
|
-
? `${verb} PR #${r.data.pr.number} → ${r.data.pr.url} — merge conflicts detected; resolving${deliverySuffix}`
|
|
224
|
-
: `${verb} PR #${r.data.pr.number} → ${r.data.pr.url} (${
|
|
225
|
-
r.data.plan_embedded ? "plan embedded" : "no plan embed"
|
|
226
|
-
})${deliverySuffix}`;
|
|
227
|
-
return ok(message, r.data, { terminate: true });
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/** Reset `conflict_resolution_attempts` to 0 (idempotent: a no-op when already 0/absent). */
|
|
231
|
-
function resetConflictAttempts(pi: ExtensionAPI, ctx: ExtensionContext): void {
|
|
232
|
-
const attempts = rebuildWorkflowState(branchOf(ctx)).conflict_resolution_attempts ?? 0;
|
|
233
|
-
if (attempts === 0) return;
|
|
234
|
-
appendWorkflowState(pi, ctx, {
|
|
235
|
-
data: { conflict_resolution_attempts: 0 },
|
|
236
|
-
field: "conflict_resolution_attempts",
|
|
237
|
-
expected: 0,
|
|
238
|
-
scope: "submit",
|
|
239
|
-
failure: "conflict_resolution_attempts reset read-back failed (expected 0)",
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/**
|
|
244
|
-
* The follow-up guidance the warm `/submit` injects to dispatch the conflict-resolver (modeled on
|
|
245
|
-
* `prReviewGuidance`). Pure + exported for offline tests. When `model` is set, the ONE
|
|
246
|
-
* workflowScript call carries a workflow-level `model` default; otherwise the agent's default
|
|
247
|
-
* model is used.
|
|
248
|
-
*/
|
|
249
|
-
export function conflictResolutionGuidance(
|
|
250
|
-
base: string,
|
|
251
|
-
attempt: number,
|
|
252
|
-
cap: number,
|
|
253
|
-
model?: string,
|
|
254
|
-
): string {
|
|
255
|
-
return render("stages/conflict-resolution.md", {
|
|
256
|
-
base,
|
|
257
|
-
attempt: String(attempt),
|
|
258
|
-
cap: String(cap),
|
|
259
|
-
model: model ?? "",
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* After a submit that opened a PR with detected merge conflicts, drive the session into the
|
|
265
|
-
* conflict-resolution pass by injecting `conflictResolutionGuidance` (warm-door driving pattern,
|
|
266
|
-
* modeled on `driveReconcileAfterLand`). The terminating `submit` tool stays terminating — a
|
|
267
|
-
* `followUp` user message is a separate deliberate new turn. Short-circuits (sends nothing) unless
|
|
268
|
-
* the submit succeeded with a definitively-unmergeable PR. Bounded by
|
|
269
|
-
* `CONFLICT_RESOLUTION_ATTEMPT_CAP` via the `conflict_resolution_attempts` field: past the cap it
|
|
270
|
-
* surfaces the unresolved conflict loudly instead of looping.
|
|
271
|
-
*/
|
|
272
|
-
export function driveConflictResolution(
|
|
273
|
-
pi: ExtensionAPI,
|
|
274
|
-
ctx: ExtensionContext,
|
|
275
|
-
details: SubmitDetails,
|
|
276
|
-
): void {
|
|
277
|
-
if (!isUnmergeable(details)) return;
|
|
278
|
-
const base = details.base ?? "";
|
|
279
|
-
const attempts = rebuildWorkflowState(branchOf(ctx)).conflict_resolution_attempts ?? 0;
|
|
280
|
-
if (attempts >= CONFLICT_RESOLUTION_ATTEMPT_CAP) {
|
|
281
|
-
report(
|
|
282
|
-
ctx,
|
|
283
|
-
"submit",
|
|
284
|
-
"error",
|
|
285
|
-
`merge conflicts persist after ${attempts} resolution attempt(s) — resolve manually ` +
|
|
286
|
-
`(rebase onto \`${base}\` and push), then re-run /submit.`,
|
|
287
|
-
{ alsoLog: true },
|
|
288
|
-
);
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
const next = attempts + 1;
|
|
292
|
-
appendWorkflowState(pi, ctx, {
|
|
293
|
-
data: { conflict_resolution_attempts: next },
|
|
294
|
-
field: "conflict_resolution_attempts",
|
|
295
|
-
expected: next,
|
|
296
|
-
scope: "submit",
|
|
297
|
-
failure: `conflict_resolution_attempts read-back failed (expected ${next})`,
|
|
298
|
-
});
|
|
299
|
-
const model = subagentModel(ctx.cwd, "conflict-resolver");
|
|
300
|
-
const message =
|
|
301
|
-
conflictResolutionGuidance(base, next, CONFLICT_RESOLUTION_ATTEMPT_CAP, model) +
|
|
302
|
-
bindingSuffix(ctx.cwd, "command:submit");
|
|
303
|
-
if (ctx.isIdle()) {
|
|
304
|
-
// The `/submit` command path (idle): inject an immediate turn.
|
|
305
|
-
pi.sendUserMessage(message);
|
|
306
|
-
} else {
|
|
307
|
-
// The `submit` tool path (streaming): deliver after the terminating submit batch.
|
|
308
|
-
pi.sendUserMessage(message, { deliverAs: "followUp" });
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const TOOL_GUIDELINES = [
|
|
313
|
-
"Call submit only after the implementation is committed in this worktree; it pushes the branch and opens the draft PR, then ends the turn.",
|
|
314
|
-
"submit operates on the active plan's worktree — it takes no arguments; the branch and plan come from the local plan-ref.",
|
|
315
|
-
];
|
|
316
|
-
|
|
317
|
-
/** Register the warm door: the `submit` terminating tool (canonical) + the `/submit` command twin. */
|
|
318
|
-
export function registerSubmit(pi: ExtensionAPI): void {
|
|
319
|
-
pi.registerTool({
|
|
320
|
-
name: "submit",
|
|
321
|
-
label: "Submit PR",
|
|
322
|
-
description:
|
|
323
|
-
"Push the current plan's branch and open a draft pull request linking the plan. " +
|
|
324
|
-
"Terminating: ends the turn on submit. Call only after the implementation is committed.",
|
|
325
|
-
promptSnippet: "Open the draft PR for the committed implementation (terminates the turn)",
|
|
326
|
-
promptGuidelines: TOOL_GUIDELINES,
|
|
327
|
-
executionMode: "sequential",
|
|
328
|
-
parameters: { type: "object", additionalProperties: false, properties: {} },
|
|
329
|
-
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
330
|
-
const result = await submitPr(pi, ctx);
|
|
331
|
-
driveConflictResolution(pi, ctx, result.details);
|
|
332
|
-
return result;
|
|
333
|
-
},
|
|
334
|
-
});
|
|
335
|
-
|
|
336
|
-
registerPerkCommand(pi, "submit", {
|
|
337
|
-
description: "Push the branch and open a draft PR for the active plan (implement → submit).",
|
|
338
|
-
handler: async (_args, ctx) => {
|
|
339
|
-
const result = await submitPr(pi, ctx);
|
|
340
|
-
// Failure already reported loudly via failFor (the single error surface) — success only.
|
|
341
|
-
if (result.details.ok) {
|
|
342
|
-
report(ctx, "submit", "info", result.content[0]?.text ?? "submit done");
|
|
343
|
-
}
|
|
344
|
-
driveConflictResolution(pi, ctx, result.details);
|
|
345
|
-
},
|
|
346
|
-
});
|
|
347
|
-
}
|