@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
|
@@ -1,30 +1,42 @@
|
|
|
1
|
-
// The
|
|
1
|
+
// The review-feedback bindings (the `/address` review loop). Classify-then-act: the flow-scoped
|
|
2
2
|
// `classify_review_feedback` tool runs perk's read-only `perk.review-classifier` child through
|
|
3
3
|
// the report-wave module (ONE lane, engine-validated report schema, the configured
|
|
4
4
|
// `[models.subagents] review-classifier` model read at execute time), so the verbose GitHub
|
|
5
5
|
// JSON never enters this session and nothing schema-shaped is model-transcribed; the PARENT
|
|
6
6
|
// applies fixes (judgment + edits stay here) and finishes through one terminating
|
|
7
|
-
// `finalize_address` tool.
|
|
7
|
+
// `finalize_address` tool adapting the Pi-free finalization operation in `delivery/address.ts`.
|
|
8
8
|
//
|
|
9
|
-
// Finalization is deliberately submit-then-resolve: committed fixes first flow through the
|
|
10
|
-
//
|
|
11
|
-
// reply to and resolve review threads.
|
|
12
|
-
//
|
|
9
|
+
// Finalization is deliberately submit-then-resolve: committed fixes first flow through the
|
|
10
|
+
// normal publish operation (including a stacked suffix cascade), and only a successful
|
|
11
|
+
// publication may reply to and resolve review threads. Both external effects stay canonical in
|
|
12
|
+
// Python (`perk pr submit` / `perk pr resolve-threads`); this adapter owns the wire vocabulary
|
|
13
|
+
// (params decode, rows decode, the Result projection) and the report loudness of each arm.
|
|
13
14
|
|
|
14
15
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
15
|
-
import {
|
|
16
|
-
|
|
16
|
+
import {
|
|
17
|
+
type AddressFinalization,
|
|
18
|
+
type FinalizeAddressDeps,
|
|
19
|
+
finalizeAddress,
|
|
20
|
+
type ResolveThreads,
|
|
21
|
+
type ThreadInput,
|
|
22
|
+
type ThreadResultRow,
|
|
23
|
+
} from "../../../delivery/address.ts";
|
|
24
|
+
import type { PublishedChange } from "../../../delivery/submit.ts";
|
|
25
|
+
import { openBranchWorkflowSession } from "../../../session/branchWorkflowSession.ts";
|
|
26
|
+
import { planningStageRefusal } from "../../../session/lifecycleGates.ts";
|
|
27
|
+
import { bindingSuffix } from "../../../substrate/bindingDelivery.ts";
|
|
28
|
+
import type { PlanRef } from "../../../substrate/cache.ts";
|
|
17
29
|
import {
|
|
18
30
|
booleanField,
|
|
19
31
|
type ColdJson,
|
|
20
32
|
nullableStringField,
|
|
21
33
|
runColdDoor,
|
|
22
34
|
stringField,
|
|
23
|
-
} from "
|
|
24
|
-
import { registerPerkCommand } from "
|
|
25
|
-
import { subagentModel } from "
|
|
26
|
-
import { render } from "
|
|
27
|
-
import { failFor, ok, type Result } from "
|
|
35
|
+
} from "../../../substrate/coldDoor.ts";
|
|
36
|
+
import { registerPerkCommand } from "../../../substrate/command.ts";
|
|
37
|
+
import { subagentModel } from "../../../substrate/config.ts";
|
|
38
|
+
import { render } from "../../../substrate/prompts.ts";
|
|
39
|
+
import { failFor, ok, type Result } from "../../../substrate/result.ts";
|
|
28
40
|
import {
|
|
29
41
|
arrayParam,
|
|
30
42
|
numberParam,
|
|
@@ -32,50 +44,31 @@ import {
|
|
|
32
44
|
paramsOf,
|
|
33
45
|
stringParam,
|
|
34
46
|
type ToolParams,
|
|
35
|
-
} from "
|
|
36
|
-
import {
|
|
37
|
-
import { type ReportTarget, report } from "
|
|
47
|
+
} from "../../../substrate/toolParams.ts";
|
|
48
|
+
import { activePlanRef } from "../../../substrate/workflowState.ts";
|
|
49
|
+
import { type ReportTarget, report } from "../../../surfaces/report.ts";
|
|
38
50
|
import {
|
|
51
|
+
type ReportWave,
|
|
52
|
+
type ReportWaveAttemptReceipt,
|
|
39
53
|
toAttemptReceipt,
|
|
40
|
-
|
|
41
|
-
type WaveAttemptReceipt,
|
|
42
|
-
} from "../waves/reportWave.ts";
|
|
54
|
+
} from "../../../waves/reportWave.ts";
|
|
43
55
|
import {
|
|
44
|
-
|
|
56
|
+
CLASSIFY_ASSIGNMENT_KEY,
|
|
45
57
|
REVIEW_CLASSIFIER_FLOW,
|
|
46
58
|
runReviewClassifierWave,
|
|
47
|
-
} from "
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
50
|
-
import { driveConflictResolution, type SubmitOk, submitPr } from "./submit.ts";
|
|
51
|
-
|
|
52
|
-
export interface ThreadInput {
|
|
53
|
-
thread_id: string;
|
|
54
|
-
comment?: string;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
interface ResolveCounts {
|
|
58
|
-
actionable?: number;
|
|
59
|
-
informational?: number;
|
|
60
|
-
praise?: number;
|
|
61
|
-
question?: number;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
interface ResolveParams {
|
|
65
|
-
threads: ThreadInput[];
|
|
66
|
-
pr?: number;
|
|
67
|
-
counts?: ResolveCounts;
|
|
68
|
-
}
|
|
59
|
+
} from "../../../waves/reviewClassifierWave.ts";
|
|
60
|
+
import { driveConflictFollowUp, publishDepsFor, renderPublishedMessage } from "./submit.ts";
|
|
61
|
+
import type { SubmitConflictController } from "./submitConflict.ts";
|
|
69
62
|
|
|
70
63
|
/** The four known `counts` keys (recorded into workflow-state — strict-decoded). */
|
|
71
64
|
const COUNT_KEYS = ["actionable", "informational", "praise", "question"] as const;
|
|
72
65
|
|
|
73
66
|
/** Decode the optional `counts` object; null = present-but-mistyped (a key or the object). */
|
|
74
|
-
function decodeCounts(p: ToolParams):
|
|
67
|
+
function decodeCounts(p: ToolParams): AddressFinalization["counts"] | null {
|
|
75
68
|
const raw = objectParam(p, "counts");
|
|
76
69
|
if (raw === undefined) return undefined;
|
|
77
70
|
if (raw === null) return null;
|
|
78
|
-
const counts:
|
|
71
|
+
const counts: NonNullable<AddressFinalization["counts"]> = {};
|
|
79
72
|
for (const key of COUNT_KEYS) {
|
|
80
73
|
const value = numberParam(raw, key);
|
|
81
74
|
if (value === null) return null;
|
|
@@ -85,12 +78,12 @@ function decodeCounts(p: ToolParams): ResolveCounts | undefined | null {
|
|
|
85
78
|
}
|
|
86
79
|
|
|
87
80
|
/**
|
|
88
|
-
* Decode unknown tool-call params into `
|
|
89
|
-
* `threads` absent or non-array decodes to `[]` (so the
|
|
90
|
-
*
|
|
91
|
-
*
|
|
81
|
+
* Decode unknown tool-call params into `AddressFinalization` (the tool-boundary seam).
|
|
82
|
+
* `threads` absent or non-array decodes to `[]` (so the feature's empty-batch refusal fires);
|
|
83
|
+
* any malformed ROW → null — whole-batch refusal, since resolving a guessed subset of threads
|
|
84
|
+
* is a durable GitHub mutation. `pr`/`counts` mistyped → null (recorded state).
|
|
92
85
|
*/
|
|
93
|
-
export function decodeResolveParams(params: unknown):
|
|
86
|
+
export function decodeResolveParams(params: unknown): AddressFinalization | null {
|
|
94
87
|
const p = paramsOf(params);
|
|
95
88
|
if (p === null) return null;
|
|
96
89
|
const rawThreads = arrayParam(p, "threads");
|
|
@@ -112,41 +105,6 @@ export function decodeResolveParams(params: unknown): ResolveParams | null {
|
|
|
112
105
|
return { threads, pr, counts };
|
|
113
106
|
}
|
|
114
107
|
|
|
115
|
-
/** One per-thread outcome row from the cold door's batch result. */
|
|
116
|
-
export interface ThreadResultRow {
|
|
117
|
-
thread_id: string;
|
|
118
|
-
success: boolean;
|
|
119
|
-
comment_added: boolean;
|
|
120
|
-
error?: string | null;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/** The ok-arm fields. */
|
|
124
|
-
export interface ResolveOk {
|
|
125
|
-
results: ThreadResultRow[];
|
|
126
|
-
resolved_thread_ids: string[];
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/** The partial-failure branch carries the per-thread detail on the fail arm too. */
|
|
130
|
-
export interface ResolveFailExtras {
|
|
131
|
-
results?: ThreadResultRow[];
|
|
132
|
-
resolved_thread_ids?: string[];
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export type ResolveResult = Result<ResolveOk, ResolveFailExtras>;
|
|
136
|
-
|
|
137
|
-
/** The full-success payload returned by the terminating model-facing finalizer. */
|
|
138
|
-
export interface FinalizeAddressOk extends ResolveOk {
|
|
139
|
-
submit: SubmitOk;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/** A resolve failure after publication carries successful submit facts and safe retry input. */
|
|
143
|
-
export interface FinalizeAddressFailExtras extends ResolveFailExtras {
|
|
144
|
-
submit?: SubmitOk;
|
|
145
|
-
retry_threads?: ThreadInput[];
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export type FinalizeAddressResult = Result<FinalizeAddressOk, FinalizeAddressFailExtras>;
|
|
149
|
-
|
|
150
108
|
/**
|
|
151
109
|
* Narrow the cold door's `results` array to per-thread rows. Strict per row on `thread_id`,
|
|
152
110
|
* `success`, `comment_added`; lenient on the report-only `error` (wrong-typed coerces to null).
|
|
@@ -174,175 +132,115 @@ function decodeRows(payload: ColdJson): ThreadResultRow[] | null {
|
|
|
174
132
|
}
|
|
175
133
|
|
|
176
134
|
/**
|
|
177
|
-
*
|
|
178
|
-
*
|
|
179
|
-
*
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const byId = new Map(rows.map((row) => [row.thread_id, row]));
|
|
183
|
-
const seen = new Set<string>();
|
|
184
|
-
const retry: ThreadInput[] = [];
|
|
185
|
-
for (const input of params.threads) {
|
|
186
|
-
if (seen.has(input.thread_id)) continue;
|
|
187
|
-
seen.add(input.thread_id);
|
|
188
|
-
const row = byId.get(input.thread_id);
|
|
189
|
-
if (row?.success === true) continue;
|
|
190
|
-
if (row?.comment_added === false && input.comment !== undefined) {
|
|
191
|
-
retry.push({ thread_id: input.thread_id, comment: input.comment });
|
|
192
|
-
} else {
|
|
193
|
-
retry.push({ thread_id: input.thread_id });
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
return retry;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Resolve a batch of review threads (the parent's mechanical resolve step). Delegates to the Python
|
|
201
|
-
* cold door; returns a soft result (never throws). On success, records `last_review_batch`.
|
|
135
|
+
* The production `ResolveThreads` port: `perk pr resolve-threads --json --batch` through the
|
|
136
|
+
* cold-door seam (the temp-file stdin channel). A failure envelope whose payload re-narrows
|
|
137
|
+
* with the SAME rows decode is a `partial` report (the per-thread detail is trustworthy); an
|
|
138
|
+
* absent/malformed payload drops the partial detail — `failed` (uncertainty ⇒ no half-rendered
|
|
139
|
+
* partial table).
|
|
202
140
|
*/
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
const r = await runColdDoor<ThreadResultRow[]>(pi, ctx, ["pr", "resolve-threads", "--json"], {
|
|
217
|
-
label: "perk pr resolve-threads",
|
|
218
|
-
decode: decodeRows,
|
|
219
|
-
stdin: {
|
|
220
|
-
flag: "--batch",
|
|
221
|
-
content: `${JSON.stringify(batch, null, 2)}\n`,
|
|
222
|
-
filename: `resolve-batch-${Date.now()}.json`,
|
|
223
|
-
},
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
if (!r.ok) {
|
|
227
|
-
// A partial/failed batch is loud-but-soft: surface the per-thread detail, do not throw. The
|
|
228
|
-
// detail rides the failure envelope's payload; absent/malformed rows ⇒ plain fail (advisory
|
|
229
|
-
// drop — never a half-rendered partial table).
|
|
141
|
+
function createThreadResolver(pi: ExtensionAPI, ctx: ExtensionContext): ResolveThreads {
|
|
142
|
+
return async (threads) => {
|
|
143
|
+
const batch = threads.map((t) => ({ thread_id: t.thread_id, comment: t.comment ?? null }));
|
|
144
|
+
const r = await runColdDoor<ThreadResultRow[]>(pi, ctx, ["pr", "resolve-threads", "--json"], {
|
|
145
|
+
label: "perk pr resolve-threads",
|
|
146
|
+
decode: decodeRows,
|
|
147
|
+
stdin: {
|
|
148
|
+
flag: "--batch",
|
|
149
|
+
content: `${JSON.stringify(batch, null, 2)}\n`,
|
|
150
|
+
filename: `resolve-batch-${Date.now()}.json`,
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
if (r.ok) return { ok: true, rows: r.data };
|
|
230
154
|
const rows = r.payload !== undefined ? decodeRows(r.payload) : null;
|
|
231
|
-
if (r.payload === undefined || rows === null)
|
|
232
|
-
|
|
155
|
+
if (r.payload === undefined || rows === null) {
|
|
156
|
+
return { ok: false, kind: "failed", message: r.message, errorType: r.errorType };
|
|
157
|
+
}
|
|
233
158
|
const failed = rows.filter((row) => !row.success).length;
|
|
234
|
-
const error = stringField(r.payload, "message") ?? `${failed} thread(s) did not resolve`;
|
|
235
|
-
report(ctx, "address", "error", error, { alsoLog: true });
|
|
236
159
|
return {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
],
|
|
243
|
-
details: {
|
|
244
|
-
ok: false,
|
|
245
|
-
error,
|
|
246
|
-
error_type: stringField(r.payload, "error_type") ?? "partial_failure",
|
|
247
|
-
results: rows,
|
|
248
|
-
resolved_thread_ids: resolvedIds,
|
|
249
|
-
},
|
|
160
|
+
ok: false,
|
|
161
|
+
kind: "partial",
|
|
162
|
+
rows,
|
|
163
|
+
message: stringField(r.payload, "message") ?? `${failed} thread(s) did not resolve`,
|
|
164
|
+
errorType: stringField(r.payload, "error_type") ?? "partial_failure",
|
|
250
165
|
};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const results = r.data;
|
|
254
|
-
const resolvedIds = results.filter((row) => row.success).map((row) => row.thread_id);
|
|
255
|
-
|
|
256
|
-
// Record the batch (tier-3, best-effort-with-logging, idempotent, headless-safe). Strict
|
|
257
|
-
// read-back via rebuild — loud-but-non-fatal, the resolve already succeeded.
|
|
258
|
-
const recordedBatch = {
|
|
259
|
-
pr: params.pr ?? null,
|
|
260
|
-
counts: params.counts ?? null,
|
|
261
|
-
resolved_thread_ids: resolvedIds,
|
|
262
|
-
at: new Date().toISOString(),
|
|
263
166
|
};
|
|
264
|
-
|
|
265
|
-
data: { last_review_batch: recordedBatch },
|
|
266
|
-
field: "last_review_batch",
|
|
267
|
-
expected: recordedBatch,
|
|
268
|
-
scope: "address",
|
|
269
|
-
failure: "last_review_batch read-back failed",
|
|
270
|
-
});
|
|
167
|
+
}
|
|
271
168
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
169
|
+
/** The full-success payload returned by the terminating model-facing finalizer. */
|
|
170
|
+
interface FinalizeAddressOk {
|
|
171
|
+
submit: PublishedChange;
|
|
172
|
+
results: ThreadResultRow[];
|
|
173
|
+
resolved_thread_ids: string[];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** A resolve failure after publication carries successful submit facts and safe retry input. */
|
|
177
|
+
interface FinalizeAddressFailExtras {
|
|
178
|
+
submit?: PublishedChange;
|
|
179
|
+
results?: ThreadResultRow[];
|
|
180
|
+
resolved_thread_ids?: string[];
|
|
181
|
+
retry_threads?: ThreadInput[];
|
|
276
182
|
}
|
|
277
183
|
|
|
184
|
+
type FinalizeAddressResult = Result<FinalizeAddressOk, FinalizeAddressFailExtras>;
|
|
185
|
+
|
|
278
186
|
/**
|
|
279
|
-
*
|
|
280
|
-
*
|
|
187
|
+
* The finalize execute core: planning refusal first (a positioned stacked planning session's
|
|
188
|
+
* cwd binding is the PREDECESSOR), then the feature op over the one production composition —
|
|
189
|
+
* `publishDepsFor` extended with the resolve port and the branch session — then the outcome →
|
|
190
|
+
* Result projection. Publish notes were already reported by the shared publisher at publish
|
|
191
|
+
* time (pre-resolve order preserved on EVERY published arm).
|
|
281
192
|
*/
|
|
282
|
-
|
|
193
|
+
async function executeFinalizeAddress(
|
|
283
194
|
pi: ExtensionAPI,
|
|
284
195
|
ctx: ExtensionContext,
|
|
285
|
-
|
|
196
|
+
input: AddressFinalization,
|
|
197
|
+
controller: SubmitConflictController,
|
|
286
198
|
): Promise<FinalizeAddressResult> {
|
|
287
199
|
const fail = failFor<FinalizeAddressFailExtras>(ctx, "address", "finalize_address");
|
|
288
|
-
// Planning sessions never legitimately publish review fixes — the first check (a positioned
|
|
289
|
-
// stacked planning session's cwd binding is the PREDECESSOR).
|
|
290
200
|
const planningRefusal = planningStageRefusal(ctx, "address");
|
|
291
201
|
if (planningRefusal !== null) return fail(planningRefusal, "planning_session");
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
const submitted = await submitPr(pi, ctx);
|
|
296
|
-
if (!submitted.details.ok) {
|
|
297
|
-
return fail(
|
|
298
|
-
`propagation failed; threads were NOT resolved — ${submitted.details.error}. ` +
|
|
299
|
-
"Fix the publication failure, then re-run finalize_address.",
|
|
300
|
-
submitted.details.error_type,
|
|
301
|
-
);
|
|
302
|
-
}
|
|
202
|
+
// Preserve the feature's empty-batch refusal before counter/run-id reads.
|
|
203
|
+
if (input.threads.length > 0) controller.clear();
|
|
303
204
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
205
|
+
const deps: FinalizeAddressDeps = {
|
|
206
|
+
...publishDepsFor(pi, ctx),
|
|
207
|
+
resolve: createThreadResolver(pi, ctx),
|
|
208
|
+
session: openBranchWorkflowSession(pi, ctx),
|
|
209
|
+
};
|
|
210
|
+
const outcome = await finalizeAddress(deps, input);
|
|
211
|
+
switch (outcome.kind) {
|
|
212
|
+
case "empty_batch":
|
|
213
|
+
return fail(outcome.message, "bad_input");
|
|
214
|
+
case "not_published":
|
|
215
|
+
// Today's two-report publish failure: the inner submit-scope report (the raw publisher
|
|
216
|
+
// failure), then the address-scope finalizer failure.
|
|
217
|
+
report(ctx, "submit", "error", outcome.publishMessage, { alsoLog: true });
|
|
218
|
+
return fail(outcome.message, outcome.errorType);
|
|
219
|
+
case "published_partial":
|
|
220
|
+
report(ctx, "address", "error", outcome.resolveMessage, { alsoLog: true });
|
|
221
|
+
return fail(outcome.message, outcome.errorType, {
|
|
222
|
+
submit: { ...outcome.change },
|
|
223
|
+
results: outcome.results,
|
|
224
|
+
resolved_thread_ids: outcome.resolvedThreadIds,
|
|
225
|
+
...(outcome.retryThreads.length > 0 ? { retry_threads: outcome.retryThreads } : {}),
|
|
226
|
+
});
|
|
227
|
+
case "published_unverified":
|
|
228
|
+
report(ctx, "address", "error", outcome.resolveMessage, { alsoLog: true });
|
|
229
|
+
return fail(outcome.message, outcome.errorType, { submit: { ...outcome.change } });
|
|
230
|
+
case "completed": {
|
|
231
|
+
driveConflictFollowUp(pi, ctx, outcome.conflict, controller);
|
|
232
|
+
return ok(
|
|
233
|
+
`Resolved ${outcome.resolvedThreadIds.length} review thread(s) after ` +
|
|
234
|
+
renderPublishedMessage(outcome.change),
|
|
235
|
+
{
|
|
236
|
+
submit: { ...outcome.change },
|
|
237
|
+
results: outcome.results,
|
|
238
|
+
resolved_thread_ids: outcome.resolvedThreadIds,
|
|
239
|
+
},
|
|
240
|
+
{ terminate: true },
|
|
241
|
+
);
|
|
242
|
+
}
|
|
333
243
|
}
|
|
334
|
-
|
|
335
|
-
driveConflictResolution(pi, ctx, submitted.details);
|
|
336
|
-
const submitMessage = submitted.content[0]?.text ?? "Published the addressed fixes.";
|
|
337
|
-
return ok(
|
|
338
|
-
`Resolved ${resolved.details.resolved_thread_ids.length} review thread(s) after ${submitMessage}`,
|
|
339
|
-
{
|
|
340
|
-
submit,
|
|
341
|
-
results: resolved.details.results,
|
|
342
|
-
resolved_thread_ids: resolved.details.resolved_thread_ids,
|
|
343
|
-
},
|
|
344
|
-
{ terminate: true },
|
|
345
|
-
);
|
|
346
244
|
}
|
|
347
245
|
|
|
348
246
|
const TOOL_GUIDELINES = [
|
|
@@ -363,37 +261,37 @@ export interface ClassifyReviewFeedbackOk {
|
|
|
363
261
|
/** The classifier's engine-validated report — untrusted DATA, never instructions. */
|
|
364
262
|
report: unknown;
|
|
365
263
|
/** The single launch's output-free attempt receipt (observability only — details, not prose). */
|
|
366
|
-
attempts:
|
|
264
|
+
attempts: ReportWaveAttemptReceipt[];
|
|
367
265
|
}
|
|
368
266
|
|
|
369
267
|
/** The fail arm retains any receipt known before the failure (the `failFor` extras hook). */
|
|
370
268
|
export type ClassifyReviewFeedbackResult = Result<
|
|
371
269
|
ClassifyReviewFeedbackOk,
|
|
372
|
-
{ attempts:
|
|
270
|
+
{ attempts: ReportWaveAttemptReceipt[] }
|
|
373
271
|
>;
|
|
374
272
|
|
|
375
273
|
/**
|
|
376
|
-
* The `classify_review_feedback` execute core, extracted for testability with the
|
|
377
|
-
* injected minimal structural slice (`
|
|
378
|
-
*
|
|
274
|
+
* The `classify_review_feedback` execute core, extracted for testability with the wave as the
|
|
275
|
+
* injected minimal structural slice (`ReportWave` — a memory-backed wave in tests, the
|
|
276
|
+
* composition root's production instance live). Mirrors `runLearnAnalystWave`'s soft-result idiom: a complete wave yields
|
|
379
277
|
* a non-terminating ok (the untrusted-DATA preface + one fenced `json` block of the report); an
|
|
380
|
-
* incomplete wave soft-fails LOUDLY with the first failure's detail and its
|
|
381
|
-
* as `error_type` — never a throw, never a silent fallback, no retry
|
|
382
|
-
* "surface the error and stop").
|
|
278
|
+
* incomplete wave soft-fails LOUDLY with the first failure's detail and its
|
|
279
|
+
* `ReportWaveFailureReason` as `error_type` — never a throw, never a silent fallback, no retry
|
|
280
|
+
* (the flow's posture is "surface the error and stop").
|
|
383
281
|
*/
|
|
384
282
|
export async function executeClassifyReviewFeedback(
|
|
385
|
-
|
|
283
|
+
wave: ReportWave,
|
|
386
284
|
target: ReportTarget,
|
|
387
285
|
opts: { model?: string; signal?: AbortSignal } = {},
|
|
388
286
|
): Promise<ClassifyReviewFeedbackResult> {
|
|
389
|
-
const fail = failFor<{ attempts:
|
|
287
|
+
const fail = failFor<{ attempts: ReportWaveAttemptReceipt[] }>(
|
|
390
288
|
target,
|
|
391
289
|
"address",
|
|
392
290
|
"classify_review_feedback",
|
|
393
291
|
);
|
|
394
|
-
const result = await runReviewClassifierWave(
|
|
292
|
+
const result = await runReviewClassifierWave(wave, opts);
|
|
395
293
|
const attempts = [
|
|
396
|
-
toAttemptReceipt(REVIEW_CLASSIFIER_FLOW, 1, [
|
|
294
|
+
toAttemptReceipt(REVIEW_CLASSIFIER_FLOW, 1, [CLASSIFY_ASSIGNMENT_KEY], result.receipt),
|
|
397
295
|
];
|
|
398
296
|
if (!result.complete) {
|
|
399
297
|
const failure = result.failures[0];
|
|
@@ -410,28 +308,13 @@ export async function executeClassifyReviewFeedback(
|
|
|
410
308
|
return ok(text, { report: laneReport, attempts });
|
|
411
309
|
}
|
|
412
310
|
|
|
413
|
-
/** Resolve the active plan-ref (worktree first, then the rebuilt workflow-state). The converged
|
|
414
|
-
* address body carries the PR identity, so the warm door must resolve a ref — and `/address`
|
|
415
|
-
* cannot function without one regardless (the classifier child's `perk pr feedback` hard-errors
|
|
416
|
-
* `no_plan_ref`). Mirrors `doors/learn.ts`'s helper. */
|
|
417
|
-
function activePlanRef(ctx: ExtensionContext): PlanRef | null {
|
|
418
|
-
const fromWorktree = readPlanRef(ctx.cwd);
|
|
419
|
-
if (fromWorktree) return fromWorktree;
|
|
420
|
-
try {
|
|
421
|
-
const branch = branchOf(ctx);
|
|
422
|
-
return (rebuildWorkflowState(branch).active_plan_ref as PlanRef | null) ?? null;
|
|
423
|
-
} catch {
|
|
424
|
-
return null;
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
311
|
/** Inject the address-workflow guidance the model follows (the perk-address skill pointer is
|
|
429
312
|
* delivered by the skill-binding suffix — not hardcoded here). The classify step is ONE
|
|
430
313
|
* `classify_review_feedback` call — the tool owns the wave mechanics, the report schema, and
|
|
431
314
|
* reads the configured `[models.subagents] review-classifier` model at execute time.
|
|
432
315
|
*
|
|
433
316
|
* The wording lives in the shared canonical templates `prompts/stages/address/*` rendered via the
|
|
434
|
-
* cross-plane render seam (contracts.md §8.31) — the warm
|
|
317
|
+
* cross-plane render seam (contracts.md §8.31) — the warm surface converges onto the SAME two
|
|
435
318
|
* templates as the cold `_address_prompt` and the worker `initialPromptFor("address")`. Branching
|
|
436
319
|
* stays in code: preview/action selects the template. */
|
|
437
320
|
export function addressGuidance(ref: PlanRef, preview: boolean): string {
|
|
@@ -443,9 +326,13 @@ export function addressGuidance(ref: PlanRef, preview: boolean): string {
|
|
|
443
326
|
return render(preview ? "stages/address/preview.md" : "stages/address/action.md", variables);
|
|
444
327
|
}
|
|
445
328
|
|
|
446
|
-
/**
|
|
447
|
-
* tools and the `/address` command. */
|
|
448
|
-
export function
|
|
329
|
+
/** Install the review-feedback bindings: the `classify_review_feedback` + terminating
|
|
330
|
+
* `finalize_address` tools and the `/address` command. */
|
|
331
|
+
export function installAddressBindings(
|
|
332
|
+
pi: ExtensionAPI,
|
|
333
|
+
wave: ReportWave,
|
|
334
|
+
controller: SubmitConflictController,
|
|
335
|
+
): void {
|
|
449
336
|
pi.registerTool({
|
|
450
337
|
name: "classify_review_feedback",
|
|
451
338
|
label: "Classify review feedback",
|
|
@@ -468,7 +355,7 @@ export function registerAddress(pi: ExtensionAPI): void {
|
|
|
468
355
|
// gitignored `.perk/local.toml` overlay to the MAIN checkout, so a per-user override
|
|
469
356
|
// survives the cold worktree launch (worktrees never materialize local.toml).
|
|
470
357
|
const model = subagentModel(ctx.cwd, "review-classifier");
|
|
471
|
-
return executeClassifyReviewFeedback(
|
|
358
|
+
return executeClassifyReviewFeedback(wave, ctx, {
|
|
472
359
|
...(model !== undefined ? { model } : {}),
|
|
473
360
|
...(signal !== undefined ? { signal } : {}),
|
|
474
361
|
});
|
|
@@ -525,7 +412,7 @@ export function registerAddress(pi: ExtensionAPI): void {
|
|
|
525
412
|
"finalize_address",
|
|
526
413
|
)("finalize_address needs { threads: [{thread_id, comment?}] }", "bad_input");
|
|
527
414
|
}
|
|
528
|
-
return
|
|
415
|
+
return executeFinalizeAddress(pi, ctx, decoded, controller);
|
|
529
416
|
},
|
|
530
417
|
});
|
|
531
418
|
|