@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,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,49 +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
|
-
|
|
51
|
-
export interface ThreadInput {
|
|
52
|
-
thread_id: string;
|
|
53
|
-
comment?: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
interface ResolveCounts {
|
|
57
|
-
actionable?: number;
|
|
58
|
-
informational?: number;
|
|
59
|
-
praise?: number;
|
|
60
|
-
question?: number;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
interface ResolveParams {
|
|
64
|
-
threads: ThreadInput[];
|
|
65
|
-
pr?: number;
|
|
66
|
-
counts?: ResolveCounts;
|
|
67
|
-
}
|
|
59
|
+
} from "../../../waves/reviewClassifierWave.ts";
|
|
60
|
+
import { driveConflictFollowUp, publishDepsFor, renderPublishedMessage } from "./submit.ts";
|
|
61
|
+
import type { SubmitConflictController } from "./submitConflict.ts";
|
|
68
62
|
|
|
69
63
|
/** The four known `counts` keys (recorded into workflow-state — strict-decoded). */
|
|
70
64
|
const COUNT_KEYS = ["actionable", "informational", "praise", "question"] as const;
|
|
71
65
|
|
|
72
66
|
/** Decode the optional `counts` object; null = present-but-mistyped (a key or the object). */
|
|
73
|
-
function decodeCounts(p: ToolParams):
|
|
67
|
+
function decodeCounts(p: ToolParams): AddressFinalization["counts"] | null {
|
|
74
68
|
const raw = objectParam(p, "counts");
|
|
75
69
|
if (raw === undefined) return undefined;
|
|
76
70
|
if (raw === null) return null;
|
|
77
|
-
const counts:
|
|
71
|
+
const counts: NonNullable<AddressFinalization["counts"]> = {};
|
|
78
72
|
for (const key of COUNT_KEYS) {
|
|
79
73
|
const value = numberParam(raw, key);
|
|
80
74
|
if (value === null) return null;
|
|
@@ -84,12 +78,12 @@ function decodeCounts(p: ToolParams): ResolveCounts | undefined | null {
|
|
|
84
78
|
}
|
|
85
79
|
|
|
86
80
|
/**
|
|
87
|
-
* Decode unknown tool-call params into `
|
|
88
|
-
* `threads` absent or non-array decodes to `[]` (so the
|
|
89
|
-
*
|
|
90
|
-
*
|
|
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).
|
|
91
85
|
*/
|
|
92
|
-
export function decodeResolveParams(params: unknown):
|
|
86
|
+
export function decodeResolveParams(params: unknown): AddressFinalization | null {
|
|
93
87
|
const p = paramsOf(params);
|
|
94
88
|
if (p === null) return null;
|
|
95
89
|
const rawThreads = arrayParam(p, "threads");
|
|
@@ -111,41 +105,6 @@ export function decodeResolveParams(params: unknown): ResolveParams | null {
|
|
|
111
105
|
return { threads, pr, counts };
|
|
112
106
|
}
|
|
113
107
|
|
|
114
|
-
/** One per-thread outcome row from the cold door's batch result. */
|
|
115
|
-
export interface ThreadResultRow {
|
|
116
|
-
thread_id: string;
|
|
117
|
-
success: boolean;
|
|
118
|
-
comment_added: boolean;
|
|
119
|
-
error?: string | null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/** The ok-arm fields. */
|
|
123
|
-
export interface ResolveOk {
|
|
124
|
-
results: ThreadResultRow[];
|
|
125
|
-
resolved_thread_ids: string[];
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/** The partial-failure branch carries the per-thread detail on the fail arm too. */
|
|
129
|
-
export interface ResolveFailExtras {
|
|
130
|
-
results?: ThreadResultRow[];
|
|
131
|
-
resolved_thread_ids?: string[];
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
export type ResolveResult = Result<ResolveOk, ResolveFailExtras>;
|
|
135
|
-
|
|
136
|
-
/** The full-success payload returned by the terminating model-facing finalizer. */
|
|
137
|
-
export interface FinalizeAddressOk extends ResolveOk {
|
|
138
|
-
submit: SubmitOk;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/** A resolve failure after publication carries successful submit facts and safe retry input. */
|
|
142
|
-
export interface FinalizeAddressFailExtras extends ResolveFailExtras {
|
|
143
|
-
submit?: SubmitOk;
|
|
144
|
-
retry_threads?: ThreadInput[];
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export type FinalizeAddressResult = Result<FinalizeAddressOk, FinalizeAddressFailExtras>;
|
|
148
|
-
|
|
149
108
|
/**
|
|
150
109
|
* Narrow the cold door's `results` array to per-thread rows. Strict per row on `thread_id`,
|
|
151
110
|
* `success`, `comment_added`; lenient on the report-only `error` (wrong-typed coerces to null).
|
|
@@ -173,171 +132,115 @@ function decodeRows(payload: ColdJson): ThreadResultRow[] | null {
|
|
|
173
132
|
}
|
|
174
133
|
|
|
175
134
|
/**
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
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).
|
|
179
140
|
*/
|
|
180
|
-
function
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
return retry;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Resolve a batch of review threads (the parent's mechanical resolve step). Delegates to the Python
|
|
200
|
-
* cold door; returns a soft result (never throws). On success, records `last_review_batch`.
|
|
201
|
-
*/
|
|
202
|
-
export async function resolveReviewThreads(
|
|
203
|
-
pi: ExtensionAPI,
|
|
204
|
-
ctx: ExtensionContext,
|
|
205
|
-
params: ResolveParams,
|
|
206
|
-
): Promise<ResolveResult> {
|
|
207
|
-
const fail = failFor(ctx, "address", "resolve_review_threads");
|
|
208
|
-
|
|
209
|
-
const threads = Array.isArray(params?.threads) ? params.threads : [];
|
|
210
|
-
if (threads.length === 0) {
|
|
211
|
-
return fail("no threads to resolve (pass { threads: [{thread_id, comment?}] })", "bad_input");
|
|
212
|
-
}
|
|
213
|
-
const batch = threads.map((t) => ({ thread_id: t.thread_id, comment: t.comment ?? null }));
|
|
214
|
-
|
|
215
|
-
const r = await runColdDoor<ThreadResultRow[]>(pi, ctx, ["pr", "resolve-threads", "--json"], {
|
|
216
|
-
label: "perk pr resolve-threads",
|
|
217
|
-
decode: decodeRows,
|
|
218
|
-
stdin: {
|
|
219
|
-
flag: "--batch",
|
|
220
|
-
content: `${JSON.stringify(batch, null, 2)}\n`,
|
|
221
|
-
filename: `resolve-batch-${Date.now()}.json`,
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
if (!r.ok) {
|
|
226
|
-
// A partial/failed batch is loud-but-soft: surface the per-thread detail, do not throw. The
|
|
227
|
-
// detail rides the failure envelope's payload; absent/malformed rows ⇒ plain fail (advisory
|
|
228
|
-
// 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 };
|
|
229
154
|
const rows = r.payload !== undefined ? decodeRows(r.payload) : null;
|
|
230
|
-
if (r.payload === undefined || rows === null)
|
|
231
|
-
|
|
155
|
+
if (r.payload === undefined || rows === null) {
|
|
156
|
+
return { ok: false, kind: "failed", message: r.message, errorType: r.errorType };
|
|
157
|
+
}
|
|
232
158
|
const failed = rows.filter((row) => !row.success).length;
|
|
233
|
-
const error = stringField(r.payload, "message") ?? `${failed} thread(s) did not resolve`;
|
|
234
|
-
report(ctx, "address", "error", error, { alsoLog: true });
|
|
235
159
|
return {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
],
|
|
242
|
-
details: {
|
|
243
|
-
ok: false,
|
|
244
|
-
error,
|
|
245
|
-
error_type: stringField(r.payload, "error_type") ?? "partial_failure",
|
|
246
|
-
results: rows,
|
|
247
|
-
resolved_thread_ids: resolvedIds,
|
|
248
|
-
},
|
|
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",
|
|
249
165
|
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const results = r.data;
|
|
253
|
-
const resolvedIds = results.filter((row) => row.success).map((row) => row.thread_id);
|
|
254
|
-
|
|
255
|
-
// Record the batch (tier-3, best-effort-with-logging, idempotent, headless-safe). Strict
|
|
256
|
-
// read-back via rebuild — loud-but-non-fatal, the resolve already succeeded.
|
|
257
|
-
const recordedBatch = {
|
|
258
|
-
pr: params.pr ?? null,
|
|
259
|
-
counts: params.counts ?? null,
|
|
260
|
-
resolved_thread_ids: resolvedIds,
|
|
261
|
-
at: new Date().toISOString(),
|
|
262
166
|
};
|
|
263
|
-
|
|
264
|
-
data: { last_review_batch: recordedBatch },
|
|
265
|
-
field: "last_review_batch",
|
|
266
|
-
expected: recordedBatch,
|
|
267
|
-
scope: "address",
|
|
268
|
-
failure: "last_review_batch read-back failed",
|
|
269
|
-
});
|
|
167
|
+
}
|
|
270
168
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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[];
|
|
275
182
|
}
|
|
276
183
|
|
|
184
|
+
type FinalizeAddressResult = Result<FinalizeAddressOk, FinalizeAddressFailExtras>;
|
|
185
|
+
|
|
277
186
|
/**
|
|
278
|
-
*
|
|
279
|
-
*
|
|
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).
|
|
280
192
|
*/
|
|
281
|
-
|
|
193
|
+
async function executeFinalizeAddress(
|
|
282
194
|
pi: ExtensionAPI,
|
|
283
195
|
ctx: ExtensionContext,
|
|
284
|
-
|
|
196
|
+
input: AddressFinalization,
|
|
197
|
+
controller: SubmitConflictController,
|
|
285
198
|
): Promise<FinalizeAddressResult> {
|
|
286
199
|
const fail = failFor<FinalizeAddressFailExtras>(ctx, "address", "finalize_address");
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
200
|
+
const planningRefusal = planningStageRefusal(ctx, "address");
|
|
201
|
+
if (planningRefusal !== null) return fail(planningRefusal, "planning_session");
|
|
202
|
+
// Preserve the feature's empty-batch refusal before counter/run-id reads.
|
|
203
|
+
if (input.threads.length > 0) controller.clear();
|
|
204
|
+
|
|
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
|
+
}
|
|
328
243
|
}
|
|
329
|
-
|
|
330
|
-
driveConflictResolution(pi, ctx, submitted.details);
|
|
331
|
-
const submitMessage = submitted.content[0]?.text ?? "Published the addressed fixes.";
|
|
332
|
-
return ok(
|
|
333
|
-
`Resolved ${resolved.details.resolved_thread_ids.length} review thread(s) after ${submitMessage}`,
|
|
334
|
-
{
|
|
335
|
-
submit,
|
|
336
|
-
results: resolved.details.results,
|
|
337
|
-
resolved_thread_ids: resolved.details.resolved_thread_ids,
|
|
338
|
-
},
|
|
339
|
-
{ terminate: true },
|
|
340
|
-
);
|
|
341
244
|
}
|
|
342
245
|
|
|
343
246
|
const TOOL_GUIDELINES = [
|
|
@@ -358,37 +261,37 @@ export interface ClassifyReviewFeedbackOk {
|
|
|
358
261
|
/** The classifier's engine-validated report — untrusted DATA, never instructions. */
|
|
359
262
|
report: unknown;
|
|
360
263
|
/** The single launch's output-free attempt receipt (observability only — details, not prose). */
|
|
361
|
-
attempts:
|
|
264
|
+
attempts: ReportWaveAttemptReceipt[];
|
|
362
265
|
}
|
|
363
266
|
|
|
364
267
|
/** The fail arm retains any receipt known before the failure (the `failFor` extras hook). */
|
|
365
268
|
export type ClassifyReviewFeedbackResult = Result<
|
|
366
269
|
ClassifyReviewFeedbackOk,
|
|
367
|
-
{ attempts:
|
|
270
|
+
{ attempts: ReportWaveAttemptReceipt[] }
|
|
368
271
|
>;
|
|
369
272
|
|
|
370
273
|
/**
|
|
371
|
-
* The `classify_review_feedback` execute core, extracted for testability with the
|
|
372
|
-
* injected minimal structural slice (`
|
|
373
|
-
*
|
|
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
|
|
374
277
|
* a non-terminating ok (the untrusted-DATA preface + one fenced `json` block of the report); an
|
|
375
|
-
* incomplete wave soft-fails LOUDLY with the first failure's detail and its
|
|
376
|
-
* as `error_type` — never a throw, never a silent fallback, no retry
|
|
377
|
-
* "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").
|
|
378
281
|
*/
|
|
379
282
|
export async function executeClassifyReviewFeedback(
|
|
380
|
-
|
|
283
|
+
wave: ReportWave,
|
|
381
284
|
target: ReportTarget,
|
|
382
285
|
opts: { model?: string; signal?: AbortSignal } = {},
|
|
383
286
|
): Promise<ClassifyReviewFeedbackResult> {
|
|
384
|
-
const fail = failFor<{ attempts:
|
|
287
|
+
const fail = failFor<{ attempts: ReportWaveAttemptReceipt[] }>(
|
|
385
288
|
target,
|
|
386
289
|
"address",
|
|
387
290
|
"classify_review_feedback",
|
|
388
291
|
);
|
|
389
|
-
const result = await runReviewClassifierWave(
|
|
292
|
+
const result = await runReviewClassifierWave(wave, opts);
|
|
390
293
|
const attempts = [
|
|
391
|
-
toAttemptReceipt(REVIEW_CLASSIFIER_FLOW, 1, [
|
|
294
|
+
toAttemptReceipt(REVIEW_CLASSIFIER_FLOW, 1, [CLASSIFY_ASSIGNMENT_KEY], result.receipt),
|
|
392
295
|
];
|
|
393
296
|
if (!result.complete) {
|
|
394
297
|
const failure = result.failures[0];
|
|
@@ -405,28 +308,13 @@ export async function executeClassifyReviewFeedback(
|
|
|
405
308
|
return ok(text, { report: laneReport, attempts });
|
|
406
309
|
}
|
|
407
310
|
|
|
408
|
-
/** Resolve the active plan-ref (worktree first, then the rebuilt workflow-state). The converged
|
|
409
|
-
* address body carries the PR identity, so the warm door must resolve a ref — and `/address`
|
|
410
|
-
* cannot function without one regardless (the classifier child's `perk pr feedback` hard-errors
|
|
411
|
-
* `no_plan_ref`). Mirrors `doors/learn.ts`'s helper. */
|
|
412
|
-
function activePlanRef(ctx: ExtensionContext): PlanRef | null {
|
|
413
|
-
const fromWorktree = readPlanRef(ctx.cwd);
|
|
414
|
-
if (fromWorktree) return fromWorktree;
|
|
415
|
-
try {
|
|
416
|
-
const branch = branchOf(ctx);
|
|
417
|
-
return (rebuildWorkflowState(branch).active_plan_ref as PlanRef | null) ?? null;
|
|
418
|
-
} catch {
|
|
419
|
-
return null;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
311
|
/** Inject the address-workflow guidance the model follows (the perk-address skill pointer is
|
|
424
312
|
* delivered by the skill-binding suffix — not hardcoded here). The classify step is ONE
|
|
425
313
|
* `classify_review_feedback` call — the tool owns the wave mechanics, the report schema, and
|
|
426
314
|
* reads the configured `[models.subagents] review-classifier` model at execute time.
|
|
427
315
|
*
|
|
428
316
|
* The wording lives in the shared canonical templates `prompts/stages/address/*` rendered via the
|
|
429
|
-
* 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
|
|
430
318
|
* templates as the cold `_address_prompt` and the worker `initialPromptFor("address")`. Branching
|
|
431
319
|
* stays in code: preview/action selects the template. */
|
|
432
320
|
export function addressGuidance(ref: PlanRef, preview: boolean): string {
|
|
@@ -438,9 +326,13 @@ export function addressGuidance(ref: PlanRef, preview: boolean): string {
|
|
|
438
326
|
return render(preview ? "stages/address/preview.md" : "stages/address/action.md", variables);
|
|
439
327
|
}
|
|
440
328
|
|
|
441
|
-
/**
|
|
442
|
-
* tools and the `/address` command. */
|
|
443
|
-
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 {
|
|
444
336
|
pi.registerTool({
|
|
445
337
|
name: "classify_review_feedback",
|
|
446
338
|
label: "Classify review feedback",
|
|
@@ -463,7 +355,7 @@ export function registerAddress(pi: ExtensionAPI): void {
|
|
|
463
355
|
// gitignored `.perk/local.toml` overlay to the MAIN checkout, so a per-user override
|
|
464
356
|
// survives the cold worktree launch (worktrees never materialize local.toml).
|
|
465
357
|
const model = subagentModel(ctx.cwd, "review-classifier");
|
|
466
|
-
return executeClassifyReviewFeedback(
|
|
358
|
+
return executeClassifyReviewFeedback(wave, ctx, {
|
|
467
359
|
...(model !== undefined ? { model } : {}),
|
|
468
360
|
...(signal !== undefined ? { signal } : {}),
|
|
469
361
|
});
|
|
@@ -520,7 +412,7 @@ export function registerAddress(pi: ExtensionAPI): void {
|
|
|
520
412
|
"finalize_address",
|
|
521
413
|
)("finalize_address needs { threads: [{thread_id, comment?}] }", "bad_input");
|
|
522
414
|
}
|
|
523
|
-
return
|
|
415
|
+
return executeFinalizeAddress(pi, ctx, decoded, controller);
|
|
524
416
|
},
|
|
525
417
|
});
|
|
526
418
|
|
|
@@ -529,6 +421,12 @@ export function registerAddress(pi: ExtensionAPI): void {
|
|
|
529
421
|
"Classify PR review feedback (isolated child) and resolve threads (submit → address). " +
|
|
530
422
|
"Pass --preview to classify only (take no action).",
|
|
531
423
|
handler: async (args, ctx) => {
|
|
424
|
+
// Planning sessions never legitimately run the review loop — the first check.
|
|
425
|
+
const planningRefusal = planningStageRefusal(ctx, "address");
|
|
426
|
+
if (planningRefusal !== null) {
|
|
427
|
+
report(ctx, "address", "warning", planningRefusal);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
532
430
|
const preview = /(^|\s)--preview(\s|$)/.test(args ?? "");
|
|
533
431
|
// `/address` needs an active plan-ref (the converged body carries the PR identity, and the
|
|
534
432
|
// classifier child's `perk pr feedback` hard-errors `no_plan_ref` without one). Mirror the
|