@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,408 +0,0 @@
|
|
|
1
|
-
// The warm `submit_pr_review` tool — the agent-driven curated-posting surface shared by the two
|
|
2
|
-
// PR-review doors (`/pr-review-terminal`, `/pr-review-browser`).
|
|
3
|
-
//
|
|
4
|
-
// `submit_pr_review` implements the per-door posting contract (contracts §8.4): nothing perk-
|
|
5
|
-
// driven reaches GitHub before the human triage; ALL perk-side posting flows through this tool
|
|
6
|
-
// on every door (`gh` mutations and direct `perk pr review-submit` calls are forbidden); the
|
|
7
|
-
// verdict lands last, atomically with the comments. On the terminal door this tool is the sole
|
|
8
|
-
// posting path. On the browser door the human platform-posts from the plannotator UI — that
|
|
9
|
-
// native posting IS the GitHub path; perk composes nothing by default and posts only what the
|
|
10
|
-
// human explicitly hands it (typically a request-changes verdict, which the UI cannot post). It
|
|
11
|
-
// delegates to the Python cold door (`perk pr review-submit` — mutations canonical in Python)
|
|
12
|
-
// via `runColdDoor` (the batch rides the run-scratch stdin channel), then appends `last_review`
|
|
13
|
-
// to `perk:workflow-state`. The human gate splits: explicit conversational go-ahead ALWAYS
|
|
14
|
-
// (pinned in the guidelines/skill); formal events (`approve`/`request-changes`) additionally get
|
|
15
|
-
// the structural gate — headless refuses, interactive raises a blocking `ctx.ui.confirm`.
|
|
16
|
-
// `dry_run` is the anchor-repair loop: no gates, no record, nothing posted.
|
|
17
|
-
|
|
18
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
19
|
-
import {
|
|
20
|
-
booleanField,
|
|
21
|
-
type ColdDoorCtx,
|
|
22
|
-
type ColdJson,
|
|
23
|
-
type ExecHost,
|
|
24
|
-
numberField,
|
|
25
|
-
runColdDoor,
|
|
26
|
-
stringField,
|
|
27
|
-
} from "../substrate/coldDoor.ts";
|
|
28
|
-
import { failFor, ok, type Result } from "../substrate/result.ts";
|
|
29
|
-
import {
|
|
30
|
-
arrayParam,
|
|
31
|
-
booleanParam,
|
|
32
|
-
numberParam,
|
|
33
|
-
paramsOf,
|
|
34
|
-
stringParam,
|
|
35
|
-
type ToolParams,
|
|
36
|
-
} from "../substrate/toolParams.ts";
|
|
37
|
-
import { appendWorkflowState, type EntrySink } from "../substrate/workflowState.ts";
|
|
38
|
-
import type { Severity } from "../surfaces/report.ts";
|
|
39
|
-
|
|
40
|
-
// ------------------------------------------------------------------------ params
|
|
41
|
-
|
|
42
|
-
export type ReviewEvent = "approve" | "request-changes" | "comment";
|
|
43
|
-
|
|
44
|
-
/** One curated inline comment (the exact `review-submit --batch` `comments[]` row). */
|
|
45
|
-
export interface SubmitComment {
|
|
46
|
-
path: string;
|
|
47
|
-
line: number;
|
|
48
|
-
side?: "LEFT" | "RIGHT";
|
|
49
|
-
body: string;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface SubmitParams {
|
|
53
|
-
pr: number;
|
|
54
|
-
event: ReviewEvent;
|
|
55
|
-
body: string;
|
|
56
|
-
comments?: SubmitComment[];
|
|
57
|
-
dry_run?: boolean;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Decode the optional `comments` array; null = present-but-malformed (whole-batch refusal). */
|
|
61
|
-
function decodeSubmitComments(p: ToolParams): SubmitComment[] | undefined | null {
|
|
62
|
-
const raw = arrayParam(p, "comments");
|
|
63
|
-
if (raw === undefined) return undefined;
|
|
64
|
-
if (raw === null) return null;
|
|
65
|
-
const comments: SubmitComment[] = [];
|
|
66
|
-
for (const item of raw) {
|
|
67
|
-
const row = paramsOf(item);
|
|
68
|
-
if (row === null) return null;
|
|
69
|
-
const path = stringParam(row, "path");
|
|
70
|
-
const line = numberParam(row, "line");
|
|
71
|
-
const side = stringParam(row, "side");
|
|
72
|
-
const body = stringParam(row, "body");
|
|
73
|
-
if (typeof path !== "string" || path.length === 0) return null;
|
|
74
|
-
if (typeof line !== "number" || !Number.isInteger(line)) return null;
|
|
75
|
-
if (side !== undefined && side !== "LEFT" && side !== "RIGHT") return null;
|
|
76
|
-
if (typeof body !== "string" || body.length === 0) return null;
|
|
77
|
-
const comment: SubmitComment = { path, line, body };
|
|
78
|
-
if (side !== undefined) comment.side = side;
|
|
79
|
-
comments.push(comment);
|
|
80
|
-
}
|
|
81
|
-
return comments;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Strict-decode unknown tool-call params into `SubmitParams` (the tool-boundary seam). Mirrors
|
|
86
|
-
* `decodePostParams`: submitting a guessed/partial review is a durable GitHub mutation, so ANY
|
|
87
|
-
* malformed field ⇒ null (whole-batch refusal). `pr` must be an int; `event` exactly one of the
|
|
88
|
-
* three flag spellings; `body` a string (EMPTY ALLOWED — the cold door owns the event-conditioned
|
|
89
|
-
* body rule and reports `bad_batch`); each `comments` row strict on
|
|
90
|
-
* path/line(int)/side(LEFT|RIGHT)/body; `dry_run` a boolean.
|
|
91
|
-
*/
|
|
92
|
-
export function decodeSubmitParams(params: unknown): SubmitParams | null {
|
|
93
|
-
const p = paramsOf(params);
|
|
94
|
-
if (p === null) return null;
|
|
95
|
-
const pr = numberParam(p, "pr");
|
|
96
|
-
if (typeof pr !== "number" || !Number.isInteger(pr)) return null;
|
|
97
|
-
const event = stringParam(p, "event");
|
|
98
|
-
if (event !== "approve" && event !== "request-changes" && event !== "comment") return null;
|
|
99
|
-
const body = stringParam(p, "body");
|
|
100
|
-
if (typeof body !== "string") return null;
|
|
101
|
-
const comments = decodeSubmitComments(p);
|
|
102
|
-
if (comments === null) return null;
|
|
103
|
-
const dryRun = booleanParam(p, "dry_run");
|
|
104
|
-
if (dryRun === null) return null;
|
|
105
|
-
const result: SubmitParams = { pr, event, body };
|
|
106
|
-
if (comments !== undefined) result.comments = comments;
|
|
107
|
-
if (dryRun !== undefined) result.dry_run = dryRun;
|
|
108
|
-
return result;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// ------------------------------------------------------------------------ the tool core
|
|
112
|
-
|
|
113
|
-
/** The cold door's ok-arm fields (the `review-submit --json` surface; render-only → lenient). */
|
|
114
|
-
export interface SubmitOk {
|
|
115
|
-
dry_run?: boolean;
|
|
116
|
-
pr?: number;
|
|
117
|
-
event?: string;
|
|
118
|
-
mode?: string;
|
|
119
|
-
comment_count?: number;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export type SubmitResult = Result<SubmitOk>;
|
|
123
|
-
|
|
124
|
-
/** Narrow the cold door's `review-submit --json` payload to the fields the tool reports. */
|
|
125
|
-
function decodeSubmitResult(payload: ColdJson): SubmitOk {
|
|
126
|
-
return {
|
|
127
|
-
dry_run: booleanField(payload, "dry_run"),
|
|
128
|
-
pr: numberField(payload, "pr"),
|
|
129
|
-
event: stringField(payload, "event"),
|
|
130
|
-
mode: stringField(payload, "mode"),
|
|
131
|
-
comment_count: numberField(payload, "comment_count"),
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/** One `bad_anchors` `invalid[]` row (the cold door's per-comment repair detail). */
|
|
136
|
-
interface InvalidAnchor {
|
|
137
|
-
index: number;
|
|
138
|
-
path: string;
|
|
139
|
-
line: number;
|
|
140
|
-
side: string;
|
|
141
|
-
reason: string;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Strict re-narrow of the `bad_anchors` fail payload's `invalid[]` rows. Null on ANY drift —
|
|
146
|
-
* uncertainty renders as a plain fail, never a half table.
|
|
147
|
-
*/
|
|
148
|
-
function decodeInvalidAnchors(payload: ColdJson): InvalidAnchor[] | null {
|
|
149
|
-
const raw = payload.invalid;
|
|
150
|
-
if (!Array.isArray(raw)) return null;
|
|
151
|
-
const rows: InvalidAnchor[] = [];
|
|
152
|
-
for (const item of raw) {
|
|
153
|
-
const row = paramsOf(item);
|
|
154
|
-
if (row === null) return null;
|
|
155
|
-
const index = row.index;
|
|
156
|
-
const path = row.path;
|
|
157
|
-
const line = row.line;
|
|
158
|
-
const side = row.side;
|
|
159
|
-
const reason = row.reason;
|
|
160
|
-
if (typeof index !== "number" || !Number.isInteger(index)) return null;
|
|
161
|
-
if (typeof path !== "string") return null;
|
|
162
|
-
if (typeof line !== "number" || !Number.isInteger(line)) return null;
|
|
163
|
-
if (typeof side !== "string") return null;
|
|
164
|
-
if (typeof reason !== "string") return null;
|
|
165
|
-
rows.push({ index, path, line, side, reason });
|
|
166
|
-
}
|
|
167
|
-
return rows;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/** Flag spelling → the REST wire spelling shown in the human confirm. */
|
|
171
|
-
const WIRE_EVENT: Record<ReviewEvent, string> = {
|
|
172
|
-
approve: "APPROVE",
|
|
173
|
-
"request-changes": "REQUEST_CHANGES",
|
|
174
|
-
comment: "COMMENT",
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
/** The body's first line, truncated for the confirm-dialog summary. */
|
|
178
|
-
function bodyFirstLine(body: string): string {
|
|
179
|
-
const line = body.split("\n", 1)[0] ?? "";
|
|
180
|
-
return line.length > 120 ? `${line.slice(0, 117)}…` : line;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* The minimal ctx slice `submitPrReview` needs — `ExtensionContext` satisfies it (compile-checked
|
|
185
|
-
* in the test); tests fake it. The dialog method is called ONLY behind the `hasUI` guard.
|
|
186
|
-
*/
|
|
187
|
-
export interface SubmitCtx extends ColdDoorCtx {
|
|
188
|
-
hasUI: boolean;
|
|
189
|
-
ui: {
|
|
190
|
-
notify(message: string, type?: Severity): void;
|
|
191
|
-
confirm(title: string, message: string): Promise<boolean>;
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Submit the human-curated review batch to the foreign PR (the terminal door's sole posting
|
|
197
|
-
* surface; the browser door's request-changes / explicit-request path). Delegates to the Python
|
|
198
|
-
* cold door; returns a soft result (never throws). Gate ladder (skipped when `dry_run`): formal
|
|
199
|
-
* events refuse headless (`headless_formal_event`) and otherwise require a blocking confirm
|
|
200
|
-
* (`user_declined` on decline, nothing executed); `comment` posts on the conversational
|
|
201
|
-
* go-ahead alone. On a real success, records `last_review`.
|
|
202
|
-
*/
|
|
203
|
-
export async function submitPrReview(
|
|
204
|
-
pi: ExecHost & EntrySink,
|
|
205
|
-
ctx: SubmitCtx,
|
|
206
|
-
params: SubmitParams,
|
|
207
|
-
): Promise<SubmitResult> {
|
|
208
|
-
const fail = failFor(ctx, "review", "submit_pr_review");
|
|
209
|
-
const dryRun = params.dry_run === true;
|
|
210
|
-
const commentCount = params.comments?.length ?? 0;
|
|
211
|
-
|
|
212
|
-
if (!dryRun && params.event !== "comment") {
|
|
213
|
-
if (!ctx.hasUI) {
|
|
214
|
-
return fail(
|
|
215
|
-
"headless sessions cannot post formal review verdicts — re-run interactively or use " +
|
|
216
|
-
"event: comment",
|
|
217
|
-
"headless_formal_event",
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
const wire = WIRE_EVENT[params.event];
|
|
221
|
-
const firstLine = bodyFirstLine(params.body);
|
|
222
|
-
const summary =
|
|
223
|
-
`event: ${wire} · ${commentCount} inline comment(s)` +
|
|
224
|
-
(firstLine.length > 0 ? `\nbody: ${firstLine}` : "");
|
|
225
|
-
const yes = await ctx.ui.confirm(`Post ${wire} review to PR #${params.pr}?`, summary);
|
|
226
|
-
if (!yes) {
|
|
227
|
-
return fail(
|
|
228
|
-
`user declined the ${params.event} review — nothing was submitted`,
|
|
229
|
-
"user_declined",
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// The exact `perk pr review-submit --batch` shape ({body, comments?} — the event rides the flag).
|
|
235
|
-
const batch: Record<string, unknown> = { body: params.body };
|
|
236
|
-
if (params.comments !== undefined) batch.comments = params.comments;
|
|
237
|
-
|
|
238
|
-
const r = await runColdDoor<SubmitOk>(
|
|
239
|
-
pi,
|
|
240
|
-
ctx,
|
|
241
|
-
[
|
|
242
|
-
"pr",
|
|
243
|
-
"review-submit",
|
|
244
|
-
"--pr",
|
|
245
|
-
String(params.pr),
|
|
246
|
-
"--event",
|
|
247
|
-
params.event,
|
|
248
|
-
...(dryRun ? ["--dry-run"] : []),
|
|
249
|
-
"--json",
|
|
250
|
-
],
|
|
251
|
-
{
|
|
252
|
-
label: "perk pr review-submit",
|
|
253
|
-
decode: decodeSubmitResult,
|
|
254
|
-
stdin: {
|
|
255
|
-
flag: "--batch",
|
|
256
|
-
content: `${JSON.stringify(batch, null, 2)}\n`,
|
|
257
|
-
filename: `review-submit-${Date.now()}.json`,
|
|
258
|
-
},
|
|
259
|
-
},
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
if (!r.ok) {
|
|
263
|
-
// The repair-loop arm: render the per-comment invalid[] detail when it decodes cleanly.
|
|
264
|
-
if (r.errorType === "bad_anchors" && r.payload !== undefined) {
|
|
265
|
-
const rows = decodeInvalidAnchors(r.payload);
|
|
266
|
-
if (rows !== null && rows.length > 0) {
|
|
267
|
-
const table = rows
|
|
268
|
-
.map(
|
|
269
|
-
(row) =>
|
|
270
|
-
` comment[${row.index}] ${row.path}:${row.line} (${row.side}) — ${row.reason}`,
|
|
271
|
-
)
|
|
272
|
-
.join("\n");
|
|
273
|
-
return fail(
|
|
274
|
-
`${r.message}\n${table}\nrepair these anchors and re-run with dry_run: true`,
|
|
275
|
-
r.errorType,
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
return fail(r.message, r.errorType);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const data = r.data;
|
|
283
|
-
if (dryRun) {
|
|
284
|
-
const n = data.comment_count ?? commentCount;
|
|
285
|
-
return ok(
|
|
286
|
-
`validated — ${n} inline comment(s), event ${params.event}; the batch is submittable`,
|
|
287
|
-
{ ...data },
|
|
288
|
-
);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Record the outcome (tier-3, best-effort-with-logging, headless-safe). Strict read-back via
|
|
292
|
-
// rebuild — loud-but-non-fatal, the submission already succeeded.
|
|
293
|
-
const record = {
|
|
294
|
-
pr: data.pr ?? params.pr,
|
|
295
|
-
event: params.event,
|
|
296
|
-
comment_count: data.comment_count ?? null,
|
|
297
|
-
mode: data.mode ?? null,
|
|
298
|
-
at: new Date().toISOString(),
|
|
299
|
-
};
|
|
300
|
-
appendWorkflowState(pi, ctx, {
|
|
301
|
-
data: { last_review: record },
|
|
302
|
-
field: "last_review",
|
|
303
|
-
expected: record,
|
|
304
|
-
scope: "review",
|
|
305
|
-
failure: "last_review read-back failed",
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
let text =
|
|
309
|
-
`submitted ${params.event} review to PR #${record.pr} ` +
|
|
310
|
-
`(${data.comment_count ?? commentCount} inline comment(s))`;
|
|
311
|
-
if (data.mode === "review_folded") {
|
|
312
|
-
text +=
|
|
313
|
-
" — note: inline anchors rejected by GitHub; comments folded into the review body, " +
|
|
314
|
-
"event preserved";
|
|
315
|
-
} else if (data.mode === "comment_fallback") {
|
|
316
|
-
text += " — note: degraded to a discussion comment";
|
|
317
|
-
}
|
|
318
|
-
return ok(`${text}.`, { ...data });
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const TOOL_GUIDELINES = [
|
|
322
|
-
"Call submit_pr_review only after the human triage has settled the batch AND the human has explicitly approved posting — nothing reaches GitHub before triage.",
|
|
323
|
-
"Validate first with dry_run: true and repair any reported anchors until validation passes; a dry-run never posts, never gates, and records nothing.",
|
|
324
|
-
"Make ONE real call: comments + body + event land atomically in a single review — the verdict never lands before the comments.",
|
|
325
|
-
"Formal events (approve / request-changes) additionally raise a blocking in-TUI confirm; headless sessions refuse them (use event: comment or re-run interactively).",
|
|
326
|
-
"All perk-side GitHub posting flows through this tool on both review doors — never post via gh or bash (direct perk pr review-submit calls are forbidden). On /pr-review-terminal this tool is the sole posting path; on /pr-review-browser the plannotator UI's native platform-posting is the human's own GitHub path, and perk posts only what the human explicitly hands it (typically a request-changes verdict).",
|
|
327
|
-
];
|
|
328
|
-
|
|
329
|
-
// ------------------------------------------------------------------------ registration
|
|
330
|
-
|
|
331
|
-
/** Register the `submit_pr_review` tool (the two review doors register no tools of their own). */
|
|
332
|
-
export function registerSubmitPrReview(pi: ExtensionAPI): void {
|
|
333
|
-
pi.registerTool({
|
|
334
|
-
name: "submit_pr_review",
|
|
335
|
-
label: "Submit PR review",
|
|
336
|
-
description:
|
|
337
|
-
"Submit the human-curated review-door outcome to the foreign PR as ONE atomic review " +
|
|
338
|
-
"(comments + body + event) via the perk cold door. dry_run validates the anchors without " +
|
|
339
|
-
"posting (the repair loop); a real submission records last_review in workflow-state.",
|
|
340
|
-
promptSnippet: "Submit the curated review batch to the PR",
|
|
341
|
-
promptGuidelines: TOOL_GUIDELINES,
|
|
342
|
-
executionMode: "sequential",
|
|
343
|
-
parameters: {
|
|
344
|
-
type: "object",
|
|
345
|
-
additionalProperties: false,
|
|
346
|
-
required: ["pr", "event", "body"],
|
|
347
|
-
properties: {
|
|
348
|
-
pr: { type: "number", description: "The foreign PR number being reviewed." },
|
|
349
|
-
event: {
|
|
350
|
-
type: "string",
|
|
351
|
-
enum: ["approve", "request-changes", "comment"],
|
|
352
|
-
description:
|
|
353
|
-
"The review event, settled with the human during triage. Formal events " +
|
|
354
|
-
"(approve/request-changes) additionally raise a blocking confirm dialog.",
|
|
355
|
-
},
|
|
356
|
-
body: {
|
|
357
|
-
type: "string",
|
|
358
|
-
description:
|
|
359
|
-
"The overall review body (markdown). comment/request-changes require a non-empty " +
|
|
360
|
-
"body; unanchorable findings fold in here.",
|
|
361
|
-
},
|
|
362
|
-
comments: {
|
|
363
|
-
type: "array",
|
|
364
|
-
description:
|
|
365
|
-
"The curated inline comments — human-authored or human-approved only, each anchored " +
|
|
366
|
-
"to a line in the PR diff (never re-anchor a child's finding).",
|
|
367
|
-
items: {
|
|
368
|
-
type: "object",
|
|
369
|
-
additionalProperties: false,
|
|
370
|
-
required: ["path", "line", "body"],
|
|
371
|
-
properties: {
|
|
372
|
-
path: { type: "string", description: "The changed file path." },
|
|
373
|
-
line: { type: "number", description: "A line present in the PR diff." },
|
|
374
|
-
side: {
|
|
375
|
-
type: "string",
|
|
376
|
-
enum: ["LEFT", "RIGHT"],
|
|
377
|
-
description: "The diff side the line anchors to (default RIGHT).",
|
|
378
|
-
},
|
|
379
|
-
body: { type: "string", description: "The comment (markdown)." },
|
|
380
|
-
},
|
|
381
|
-
},
|
|
382
|
-
},
|
|
383
|
-
dry_run: {
|
|
384
|
-
type: "boolean",
|
|
385
|
-
description:
|
|
386
|
-
"Validate the batch + anchors without posting (the anchor-repair loop). No gates, " +
|
|
387
|
-
"no last_review record.",
|
|
388
|
-
},
|
|
389
|
-
},
|
|
390
|
-
},
|
|
391
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
392
|
-
const decoded = decodeSubmitParams(params);
|
|
393
|
-
if (decoded === null) {
|
|
394
|
-
return failFor(
|
|
395
|
-
ctx,
|
|
396
|
-
"review",
|
|
397
|
-
"submit_pr_review",
|
|
398
|
-
)(
|
|
399
|
-
"submit_pr_review needs { pr: int, event: 'approve'|'request-changes'|'comment', " +
|
|
400
|
-
"body: string, comments?: [{path, line: int, side?: 'LEFT'|'RIGHT', body}], " +
|
|
401
|
-
"dry_run?: bool }",
|
|
402
|
-
"bad_input",
|
|
403
|
-
);
|
|
404
|
-
}
|
|
405
|
-
return submitPrReview(pi, ctx, decoded);
|
|
406
|
-
},
|
|
407
|
-
});
|
|
408
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
// Gist-authoring context injection (the gist mirror of objectiveAuthor.ts). A `perk gist
|
|
2
|
-
// author` cold launch opens a READ-ONLY session whose handoff `stage` is `gist-author`; this
|
|
3
|
-
// module injects the gist-authoring contract under its own `perk:gist-author-context` customType
|
|
4
|
-
// (once-only: branch-scan dedup'd on the marker), keyed off (read-only gate AND stage ===
|
|
5
|
-
// gist-author), optionally extended by the same `[workflow] plan_authoring` addendum the
|
|
6
|
-
// plan-authoring injection consumes (verbatim reuse, read per-event via loadPerkConfig).
|
|
7
|
-
// planMode.ts defers when the stage is gist-author, so exactly one authoring context is injected.
|
|
8
|
-
//
|
|
9
|
-
// The `gist_save` warm door (the tool + `/gist-save` command) lives in gistSave.ts, the mirror
|
|
10
|
-
// of objectiveSave.ts.
|
|
11
|
-
|
|
12
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
13
|
-
import { loadPerkConfig } from "../substrate/config.ts";
|
|
14
|
-
import { render } from "../substrate/prompts.ts";
|
|
15
|
-
import type { ToolGating } from "../substrate/toolGating.ts";
|
|
16
|
-
import {
|
|
17
|
-
type BranchEntry,
|
|
18
|
-
branchCarries,
|
|
19
|
-
branchOf,
|
|
20
|
-
rebuildWorkflowState,
|
|
21
|
-
} from "../substrate/workflowState.ts";
|
|
22
|
-
|
|
23
|
-
/** The registry stage id of the gist-authoring session (shared with planMode's defer check). */
|
|
24
|
-
export const GIST_AUTHOR_STAGE = "gist-author";
|
|
25
|
-
|
|
26
|
-
/** The gist-authoring context customType (distinct from planMode's `perk:plan-context`). */
|
|
27
|
-
export const GIST_AUTHOR_CONTEXT_TYPE = "perk:gist-author-context";
|
|
28
|
-
const GIST_AUTHOR_MARKER = "[GIST AUTHORING]";
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* The gist-authoring session context: live state + pointers only (contracts.md §8.57 — the flow
|
|
32
|
-
* is stated by the launch statement, the detail by the `perk-gist-author` skill). It names the
|
|
33
|
-
* working-draft artifact (`gist_draft`), the review tool (`plan_review`), and the bound skill;
|
|
34
|
-
* it never restates the flow. Prompting, NOT enforcement (the tool gate is the enforcement).
|
|
35
|
-
*/
|
|
36
|
-
export const GIST_AUTHORING_CONTEXT = render("contexts/gist-authoring.md", {
|
|
37
|
-
marker: GIST_AUTHOR_MARKER,
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
/** Build the full gist-authoring injection, appending the project config addendum when present. */
|
|
41
|
-
export function gistAuthoringContextContent(cwd: string): string {
|
|
42
|
-
const addendum = loadPerkConfig(cwd).planAuthoring;
|
|
43
|
-
return addendum ? `${GIST_AUTHORING_CONTEXT}\n\n${addendum.trim()}` : GIST_AUTHORING_CONTEXT;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Whether the current branch is a gist-author session (read-only gate AND stage match). */
|
|
47
|
-
function isGistAuthoring(gating: ToolGating, branch: readonly BranchEntry[]): boolean {
|
|
48
|
-
return gating.isActive() && rebuildWorkflowState(branch).stage === GIST_AUTHOR_STAGE;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Register the gist-authoring context injection (display:false), the gist mirror of
|
|
53
|
-
* objectiveAuthor's injection. Inert outside a gist-author session; never throws.
|
|
54
|
-
*/
|
|
55
|
-
export function registerGistAuthor(pi: ExtensionAPI, gating: ToolGating): void {
|
|
56
|
-
pi.on("before_agent_start", async (_event, ctx) => {
|
|
57
|
-
const branch = branchOf(ctx);
|
|
58
|
-
if (!isGistAuthoring(gating, branch)) return;
|
|
59
|
-
// Once-only: injected customs persist to the branch, so a live copy suppresses re-injection;
|
|
60
|
-
// compaction dropping it makes the scan come up clean and the next turn re-injects.
|
|
61
|
-
if (branchCarries(branch, GIST_AUTHOR_MARKER)) return;
|
|
62
|
-
return {
|
|
63
|
-
message: {
|
|
64
|
-
customType: GIST_AUTHOR_CONTEXT_TYPE,
|
|
65
|
-
content: gistAuthoringContextContent(ctx.cwd),
|
|
66
|
-
display: false,
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
// Strip the stale gist-authoring marker from context once the session is no longer authoring
|
|
72
|
-
// (gate off, or the stage moved on) so it never lingers — the same hygiene planMode applies.
|
|
73
|
-
pi.on("context", async (event, ctx) => {
|
|
74
|
-
const branch = branchOf(ctx);
|
|
75
|
-
if (isGistAuthoring(gating, branch)) return;
|
|
76
|
-
return {
|
|
77
|
-
messages: event.messages.filter((m) => {
|
|
78
|
-
const msg = m as { customType?: string; role?: string; content?: unknown };
|
|
79
|
-
if (msg.customType === GIST_AUTHOR_CONTEXT_TYPE) return false;
|
|
80
|
-
if (msg.role !== "user") return true;
|
|
81
|
-
const content = msg.content;
|
|
82
|
-
if (typeof content === "string") return !content.includes(GIST_AUTHOR_MARKER);
|
|
83
|
-
if (Array.isArray(content)) {
|
|
84
|
-
return !content.some(
|
|
85
|
-
(c) =>
|
|
86
|
-
(c as { type?: string; text?: string }).type === "text" &&
|
|
87
|
-
((c as { text?: string }).text ?? "").includes(GIST_AUTHOR_MARKER),
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
return true;
|
|
91
|
-
}),
|
|
92
|
-
};
|
|
93
|
-
});
|
|
94
|
-
}
|