@mgiles/perk 1.0.1 → 2.0.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 +67 -57
- package/extension/adapters/planAdapterPlannotator.ts +27 -41
- package/extension/adapters/planAdapterTombell.ts +15 -28
- package/extension/adapters/todoAdapterJuicesharp.ts +10 -13
- package/extension/checkpoints/checkpoints.ts +21 -13
- package/extension/doors/address.ts +6 -5
- package/extension/doors/askUser.ts +12 -8
- package/extension/doors/ciExecutor.ts +23 -15
- package/extension/doors/hunkHandoff.ts +202 -0
- package/extension/doors/land.ts +33 -10
- package/extension/doors/learn.ts +240 -34
- package/extension/doors/learnFactory.ts +144 -0
- package/extension/doors/lifecycleGates.ts +2 -1
- package/extension/doors/plannotatorHandoff.ts +509 -0
- package/extension/doors/prReview.ts +22 -37
- package/extension/doors/prReviewBrowser.ts +341 -0
- package/extension/doors/prReviewTerminal.ts +267 -0
- package/extension/doors/ready.ts +2 -1
- package/extension/doors/selfcheck.ts +240 -6
- package/extension/doors/submit.ts +37 -19
- package/extension/doors/submitPrReview.ts +408 -0
- package/extension/factories/implementHere.ts +116 -0
- package/extension/factories/objective.ts +17 -6
- package/extension/factories/objectiveAuthor.ts +15 -32
- package/extension/factories/objectiveDraft.ts +1 -1
- package/extension/factories/objectivePlan.ts +16 -34
- package/extension/factories/objectiveSave.ts +7 -17
- package/extension/factories/planMode.ts +24 -38
- package/extension/factories/planReview.ts +291 -177
- package/extension/factories/planSave.ts +23 -8
- package/extension/index.ts +117 -20
- package/extension/substrate/bindingDelivery.ts +32 -10
- package/extension/substrate/bindings.ts +4 -2
- package/extension/substrate/cache.ts +36 -9
- package/extension/substrate/clipboard.ts +81 -0
- package/extension/substrate/command.ts +25 -0
- package/extension/substrate/config.ts +98 -76
- package/extension/substrate/consoleCapture.ts +90 -0
- package/extension/substrate/git.ts +77 -0
- package/extension/substrate/miniJinja.ts +480 -0
- package/extension/substrate/paths.ts +38 -0
- package/extension/substrate/prompts.ts +15 -24
- package/extension/substrate/providers.ts +62 -8
- package/extension/substrate/sessionData.ts +1 -1
- package/extension/substrate/sessionPointers.ts +184 -0
- package/extension/substrate/structuredOutput.ts +3 -1
- package/extension/substrate/terminalLaunch.ts +178 -0
- package/extension/substrate/toolGating.ts +332 -73
- package/extension/substrate/toolParams.ts +7 -0
- package/extension/substrate/workflowState.ts +54 -2
- package/extension/surfaces/footerProvider.ts +8 -4
- package/extension/surfaces/surfaces.ts +338 -13
- package/extension/vendor/btw/btw.ts +10 -0
- package/extension/worker/readOnlySession.ts +19 -6
- package/extension/worker/worker.ts +187 -31
- package/extension/workerMain.ts +14 -13
- package/package.json +3 -7
- package/prompts/README.md +56 -5
- package/prompts/_fixtures/cases.yaml +52 -131
- package/prompts/_fixtures/golden/cond_elif-a.txt +3 -0
- package/prompts/_fixtures/golden/cond_elif-b.txt +3 -0
- package/prompts/_fixtures/golden/cond_elif-c.txt +3 -0
- package/prompts/_fixtures/golden/cond_if-false.txt +3 -0
- package/prompts/_fixtures/golden/cond_if-true.txt +3 -0
- package/prompts/_fixtures/golden/cond_ops-1.txt +3 -0
- package/prompts/_fixtures/golden/cond_ops-2.txt +3 -0
- package/prompts/_fixtures/golden/no_trailing_nl.txt +2 -0
- package/prompts/_fixtures/golden/trailing_nl.txt +2 -0
- package/prompts/_fixtures/golden/trim_block.txt +3 -0
- package/prompts/_fixtures/golden/trim_inline.txt +1 -0
- package/prompts/_fixtures/live.yaml +396 -0
- package/prompts/_fixtures/templates/cond_elif.md +9 -0
- package/prompts/_fixtures/templates/cond_if.md +7 -0
- package/prompts/_fixtures/templates/cond_ops.md +3 -0
- package/prompts/_fixtures/templates/no_trailing_nl.md +2 -0
- package/prompts/_fixtures/templates/trailing_nl.md +2 -0
- package/prompts/_fixtures/templates/trim_block.md +5 -0
- package/prompts/_fixtures/templates/trim_inline.md +1 -0
- package/prompts/contexts/adapters/juicesharp-todo.md +7 -0
- package/prompts/contexts/adapters/plannotator-objective.md +7 -0
- package/prompts/contexts/adapters/plannotator-plan.md +6 -0
- package/prompts/contexts/adapters/tombell-plan.md +17 -0
- package/prompts/contexts/objective-authoring.md +20 -0
- package/prompts/contexts/plan-authoring.md +24 -0
- package/prompts/contexts/read-only.md +10 -0
- package/prompts/stages/conflict-resolution.md +4 -0
- package/prompts/stages/learn-code.md +8 -0
- package/prompts/stages/learn-docs.md +7 -6
- package/prompts/stages/learn-orchestrate.md +6 -0
- package/prompts/stages/learn.md +1 -1
- package/prompts/stages/objective-author/adopt.md +12 -0
- package/prompts/stages/objective-author/file.md +9 -0
- package/prompts/stages/objective-author/seed.md +9 -0
- package/prompts/stages/objective-plan/guidance.md +1 -1
- package/prompts/stages/objective-plan/seed.md +3 -2
- package/prompts/stages/objective-reconcile.md +7 -0
- package/prompts/stages/objective-replan.md +14 -0
- package/prompts/stages/objective-save.md +9 -0
- package/prompts/stages/plan-from/adopt.md +10 -0
- package/prompts/stages/plan-from/file.md +9 -0
- package/prompts/stages/pr-review-browser/active.md +11 -0
- package/prompts/stages/pr-review-browser/foreign.md +11 -0
- package/prompts/stages/pr-review-terminal/active.md +12 -0
- package/prompts/stages/pr-review-terminal/foreign.md +13 -0
- package/prompts/stages/pr-review-terminal/local.md +4 -0
- package/prompts/stages/pr-review.md +6 -0
- package/prompts/stages/replan.md +13 -0
- package/prompts/stages/skills/create-from.md +15 -0
- package/prompts/stages/skills/create.md +9 -0
- package/prompts/stages/skills/refine.md +9 -0
- package/shared/README.md +23 -13
- package/shared/bindings.yaml +22 -2
- package/shared/contracts-history.md +191 -0
- package/shared/contracts.md +2258 -1638
- package/shared/providers.yaml +8 -1
- package/shared/registry.yaml +10 -11
- package/shared/schemas/contracts/bindings.schema.json +38 -0
- package/shared/schemas/contracts/providers.schema.json +89 -0
- package/shared/schemas/contracts/registry.schema.json +98 -0
- package/shared/schemas/inputs/handoff-arg.schema.json +6 -0
- package/shared/schemas/inputs/resolve-threads-batch.schema.json +37 -0
- package/shared/schemas/inputs/review-post-batch.schema.json +84 -0
- package/shared/schemas/inputs/review-submit-batch.schema.json +66 -0
- package/shared/schemas/inputs/structured-roadmap-node.schema.json +102 -0
- package/shared/schemas/outputs/doctor-report.schema.json +236 -0
- package/shared/schemas/outputs/init-report.schema.json +419 -0
- package/shared/schemas/outputs/learn-capture.schema.json +90 -0
- package/shared/schemas/outputs/learn-skip.schema.json +59 -0
- package/shared/schemas/outputs/plan-save.schema.json +209 -0
- package/shared/schemas/outputs/pr-feedback.schema.json +334 -0
- package/shared/schemas/outputs/pr-land.schema.json +187 -0
- package/shared/schemas/outputs/pr-ready.schema.json +75 -0
- package/shared/schemas/outputs/pr-review-checkout.schema.json +69 -0
- package/shared/schemas/outputs/pr-review-cleanup.schema.json +54 -0
- package/shared/schemas/outputs/pr-review-context.schema.json +86 -0
- package/shared/schemas/outputs/pr-review-submit.schema.json +64 -0
- package/shared/schemas/outputs/pr-submit.schema.json +147 -0
- package/extension/doors/learnDocs.ts +0 -99
- package/prompts/_fixtures/golden/address-action-model.txt +0 -10
- package/prompts/_fixtures/golden/address-action.txt +0 -10
- package/prompts/_fixtures/golden/address-preview-model.txt +0 -6
- package/prompts/_fixtures/golden/address-preview.txt +0 -6
- package/prompts/_fixtures/golden/implement-github.txt +0 -8
- package/prompts/_fixtures/golden/learn-docs.txt +0 -8
- package/prompts/_fixtures/golden/learn-github.txt +0 -11
- package/prompts/_fixtures/golden/learn-linear.txt +0 -11
- package/prompts/_fixtures/golden/learn-no-ref.txt +0 -8
- package/prompts/_fixtures/golden/learn-other.txt +0 -8
- package/prompts/_fixtures/golden/objective-plan-guidance-linear.txt +0 -8
- package/prompts/_fixtures/golden/objective-plan-guidance.txt +0 -8
- package/prompts/_fixtures/golden/objective-plan-seed-linear.txt +0 -20
- package/prompts/_fixtures/golden/objective-plan-seed.txt +0 -15
- package/prompts/_fixtures/golden/objective-read-linear-nourl.txt +0 -1
- package/prompts/_fixtures/golden/objective-read-linear.txt +0 -1
- package/prompts/_fixtures/golden/plan-read-github.txt +0 -1
- package/prompts/_fixtures/golden/plan-read-linear.txt +0 -1
- package/prompts/_fixtures/golden/plan-read-other.txt +0 -1
|
@@ -13,12 +13,26 @@
|
|
|
13
13
|
// the converged content actually *reached* the prompt. That closes perk's two-plane blind spot —
|
|
14
14
|
// doctor checks disk, selfcheck checks the prompt.
|
|
15
15
|
//
|
|
16
|
+
// The same report additionally carries a per-surface payload CENSUS — derived counts/chars for
|
|
17
|
+
// `appendSystemPrompt`, `contextFiles`, the skills catalog section, the active tool definitions,
|
|
18
|
+
// and perk-injected `custom_message` branch context. Report-only: the census never affects the
|
|
19
|
+
// ok/level verdict (contracts.md §8.7).
|
|
20
|
+
//
|
|
16
21
|
// Sensitivity: `getSystemPromptOptions()` exposes the full system-prompt construction inputs. This
|
|
17
|
-
// module logs ONLY derived
|
|
22
|
+
// module logs ONLY derived output — identifiers (paths, skill/tool names, source strings,
|
|
23
|
+
// customTypes) and counts/chars; never prompt/content text. See docs/learned/pi/.
|
|
18
24
|
|
|
19
25
|
import { existsSync, readFileSync } from "node:fs";
|
|
20
26
|
import { join } from "node:path";
|
|
21
|
-
import
|
|
27
|
+
import {
|
|
28
|
+
type ExtensionAPI,
|
|
29
|
+
formatSkillsForPrompt,
|
|
30
|
+
type Skill,
|
|
31
|
+
type ToolInfo,
|
|
32
|
+
} from "@earendil-works/pi-coding-agent";
|
|
33
|
+
import { BINDING_HEADER } from "../substrate/bindingDelivery.ts";
|
|
34
|
+
import { registerPerkCommand } from "../substrate/command.ts";
|
|
35
|
+
import { branchOf } from "../substrate/workflowState.ts";
|
|
22
36
|
import { report as reportTo } from "../surfaces/report.ts";
|
|
23
37
|
|
|
24
38
|
/** Project-scoped ambient routing index, relative to the repo root. */
|
|
@@ -27,10 +41,15 @@ export const AMBIENT_INDEX_REL_PATH = join(".pi", "APPEND_SYSTEM.md");
|
|
|
27
41
|
/** The managed `AGENTS.md` block marker (cross-plane: written by `perk init`, read here). */
|
|
28
42
|
export const MANAGED_AGENTS_MARKER = "<!-- BEGIN perk managed -->";
|
|
29
43
|
|
|
30
|
-
/** The
|
|
44
|
+
/** The slice of `BuildSystemPromptOptions` the verifier + census probe (all optional). */
|
|
31
45
|
export interface SystemPromptProbeInput {
|
|
46
|
+
customPrompt?: string;
|
|
32
47
|
appendSystemPrompt?: string;
|
|
33
48
|
contextFiles?: { path: string; content: string }[];
|
|
49
|
+
skills?: Skill[];
|
|
50
|
+
selectedTools?: string[];
|
|
51
|
+
toolSnippets?: Record<string, string>;
|
|
52
|
+
promptGuidelines?: string[];
|
|
34
53
|
}
|
|
35
54
|
|
|
36
55
|
/** Read the on-disk ambient routing index (`.pi/APPEND_SYSTEM.md`), or `null` if absent. */
|
|
@@ -129,6 +148,214 @@ export function buildSelfcheckReport(input: {
|
|
|
129
148
|
return { version, sharedOk, ambient, agents, ok, summary, level: ok ? "info" : "warning" };
|
|
130
149
|
}
|
|
131
150
|
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// The per-surface payload census (contracts.md §8.7). Pure builders over the same
|
|
153
|
+
// `getSystemPromptOptions()` slice plus the tool registry and the session branch. All counts are
|
|
154
|
+
// `string.length` char counts (rendered with a `c` suffix). Identifiers (paths, skill/tool names,
|
|
155
|
+
// source strings, customTypes) and counts/chars only — never prompt/content text.
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
/** Derived per-surface counts for the system-prompt construction inputs. */
|
|
159
|
+
export interface PromptCensus {
|
|
160
|
+
/** `customPrompt` length; `null` = pi's built-in default is in use (not measurable here). */
|
|
161
|
+
basePromptChars: number | null;
|
|
162
|
+
appendChars: number;
|
|
163
|
+
contextFiles: { count: number; totalChars: number; files: { path: string; chars: number }[] };
|
|
164
|
+
/** `hidden` = skills with `disableModelInvocation`; `promptSectionChars` is the exact
|
|
165
|
+
* system-prompt contribution (pi's own exported `formatSkillsForPrompt`). */
|
|
166
|
+
skills: { visible: number; hidden: number; promptSectionChars: number };
|
|
167
|
+
toolGuidelineChars: number;
|
|
168
|
+
toolSnippetChars: number;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Census the system-prompt surfaces from the probed options (undefined → zeros). */
|
|
172
|
+
export function promptCensus(options: SystemPromptProbeInput | undefined): PromptCensus {
|
|
173
|
+
const files = (options?.contextFiles ?? []).map((f) => ({
|
|
174
|
+
path: f.path,
|
|
175
|
+
chars: f.content.length,
|
|
176
|
+
}));
|
|
177
|
+
const skills = options?.skills ?? [];
|
|
178
|
+
const hidden = skills.filter((s) => s.disableModelInvocation).length;
|
|
179
|
+
let guidelineChars = 0;
|
|
180
|
+
for (const g of options?.promptGuidelines ?? []) guidelineChars += g.length;
|
|
181
|
+
let snippetChars = 0;
|
|
182
|
+
for (const s of Object.values(options?.toolSnippets ?? {})) snippetChars += s.length;
|
|
183
|
+
return {
|
|
184
|
+
basePromptChars: options?.customPrompt === undefined ? null : options.customPrompt.length,
|
|
185
|
+
appendChars: (options?.appendSystemPrompt ?? "").length,
|
|
186
|
+
contextFiles: {
|
|
187
|
+
count: files.length,
|
|
188
|
+
totalChars: files.reduce((sum, f) => sum + f.chars, 0),
|
|
189
|
+
files,
|
|
190
|
+
},
|
|
191
|
+
skills: {
|
|
192
|
+
visible: skills.length - hidden,
|
|
193
|
+
hidden,
|
|
194
|
+
// formatSkillsForPrompt filters disableModelInvocation skills itself, so passing the full
|
|
195
|
+
// list measures exactly the visible skills' prompt-section contribution.
|
|
196
|
+
promptSectionChars: formatSkillsForPrompt(skills).length,
|
|
197
|
+
},
|
|
198
|
+
toolGuidelineChars: guidelineChars,
|
|
199
|
+
toolSnippetChars: snippetChars,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Derived counts for the tool-definitions payload (active tools = the per-request payload). */
|
|
204
|
+
export interface ToolsCensus {
|
|
205
|
+
active: number;
|
|
206
|
+
all: number;
|
|
207
|
+
/** Sum of `JSON.stringify({name, description, parameters})` over ACTIVE tools. */
|
|
208
|
+
schemaChars: number;
|
|
209
|
+
/** Active tools grouped by `sourceInfo.source`, sorted by source string (stable output). */
|
|
210
|
+
bySource: { source: string; active: number; schemaChars: number }[];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Census the tool registry: active vs registered counts + per-source schema chars. */
|
|
214
|
+
export function toolsCensus(allTools: ToolInfo[], activeNames: string[]): ToolsCensus {
|
|
215
|
+
const activeSet = new Set(activeNames);
|
|
216
|
+
const bySource = new Map<string, { active: number; schemaChars: number }>();
|
|
217
|
+
let schemaChars = 0;
|
|
218
|
+
for (const tool of allTools) {
|
|
219
|
+
if (!activeSet.has(tool.name)) continue;
|
|
220
|
+
const chars = JSON.stringify({
|
|
221
|
+
name: tool.name,
|
|
222
|
+
description: tool.description,
|
|
223
|
+
parameters: tool.parameters,
|
|
224
|
+
}).length;
|
|
225
|
+
schemaChars += chars;
|
|
226
|
+
const row = bySource.get(tool.sourceInfo.source) ?? { active: 0, schemaChars: 0 };
|
|
227
|
+
row.active += 1;
|
|
228
|
+
row.schemaChars += chars;
|
|
229
|
+
bySource.set(tool.sourceInfo.source, row);
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
active: activeNames.length,
|
|
233
|
+
all: allTools.length,
|
|
234
|
+
schemaChars,
|
|
235
|
+
bySource: [...bySource.entries()]
|
|
236
|
+
.map(([source, row]) => ({ source, ...row }))
|
|
237
|
+
.sort((a, b) => (a.source < b.source ? -1 : a.source > b.source ? 1 : 0)),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* The structural slice of a session entry the branch census reads. Injected contexts persist as
|
|
243
|
+
* `type: "custom_message"` entries carrying `content` — distinct from `type: "custom"` state
|
|
244
|
+
* entries (workflow state, checkpoints), which the census must NOT count as context.
|
|
245
|
+
*/
|
|
246
|
+
export interface CensusBranchEntry {
|
|
247
|
+
type: string;
|
|
248
|
+
customType?: string;
|
|
249
|
+
content?: unknown;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Derived counts for perk-injected branch context (perk's customTypes share the `perk:` prefix). */
|
|
253
|
+
export interface BranchContextCensus {
|
|
254
|
+
entries: number;
|
|
255
|
+
/** `custom_message` entries whose customType starts with `perk:`, sorted by customType. */
|
|
256
|
+
perkContexts: { customType: string; copies: number; totalChars: number }[];
|
|
257
|
+
/** `custom_message` entries without the `perk:` prefix (borrowed packages). */
|
|
258
|
+
otherCustomMessages: { copies: number; totalChars: number };
|
|
259
|
+
/** Entries (any type) whose JSON form carries `BINDING_HEADER` — the cold↔warm dedup marker. */
|
|
260
|
+
bindingHeaderCopies: number;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Char count of a `custom_message` entry's content — string, `text`-part array, or JSON size. */
|
|
264
|
+
function contentChars(content: unknown): number {
|
|
265
|
+
if (content === undefined) return 0;
|
|
266
|
+
if (typeof content === "string") return content.length;
|
|
267
|
+
if (Array.isArray(content)) {
|
|
268
|
+
let sum = 0;
|
|
269
|
+
for (const part of content) {
|
|
270
|
+
if (typeof part !== "object" || part === null || Array.isArray(part)) continue;
|
|
271
|
+
const text = (part as { text?: unknown }).text;
|
|
272
|
+
if (typeof text === "string") sum += text.length;
|
|
273
|
+
}
|
|
274
|
+
return sum;
|
|
275
|
+
}
|
|
276
|
+
return JSON.stringify(content).length;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Census the session branch's injected context (counts/chars only — never message content). */
|
|
280
|
+
export function branchContextCensus(entries: readonly CensusBranchEntry[]): BranchContextCensus {
|
|
281
|
+
const perk = new Map<string, { copies: number; totalChars: number }>();
|
|
282
|
+
const other = { copies: 0, totalChars: 0 };
|
|
283
|
+
let bindingHeaderCopies = 0;
|
|
284
|
+
for (const entry of entries) {
|
|
285
|
+
if (JSON.stringify(entry).includes(BINDING_HEADER)) bindingHeaderCopies += 1;
|
|
286
|
+
if (entry.type !== "custom_message") continue;
|
|
287
|
+
const chars = contentChars(entry.content);
|
|
288
|
+
const customType = entry.customType ?? "";
|
|
289
|
+
if (customType.startsWith("perk:")) {
|
|
290
|
+
const row = perk.get(customType) ?? { copies: 0, totalChars: 0 };
|
|
291
|
+
row.copies += 1;
|
|
292
|
+
row.totalChars += chars;
|
|
293
|
+
perk.set(customType, row);
|
|
294
|
+
} else {
|
|
295
|
+
other.copies += 1;
|
|
296
|
+
other.totalChars += chars;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
entries: entries.length,
|
|
301
|
+
perkContexts: [...perk.entries()]
|
|
302
|
+
.map(([customType, row]) => ({ customType, ...row }))
|
|
303
|
+
.sort((a, b) => (a.customType < b.customType ? -1 : a.customType > b.customType ? 1 : 0)),
|
|
304
|
+
otherCustomMessages: other,
|
|
305
|
+
bindingHeaderCopies,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Render the census as a fixed multi-line block. The line grammar (the `census:` /
|
|
311
|
+
* `append-system-prompt:` / `context-files:` / `skills:` / `tools:` / `per source:` / `branch:` /
|
|
312
|
+
* `perk contexts:` keys) is stable — the closing audit diffs against these exact keys.
|
|
313
|
+
*/
|
|
314
|
+
export function renderCensus(
|
|
315
|
+
prompt: PromptCensus,
|
|
316
|
+
tools: ToolsCensus,
|
|
317
|
+
branch: BranchContextCensus,
|
|
318
|
+
): string {
|
|
319
|
+
const lines: string[] = ["census:"];
|
|
320
|
+
lines.push(
|
|
321
|
+
prompt.basePromptChars === null
|
|
322
|
+
? " base-prompt: pi-default (not measured)"
|
|
323
|
+
: ` base-prompt: custom ${prompt.basePromptChars}c`,
|
|
324
|
+
);
|
|
325
|
+
lines.push(` append-system-prompt: ${prompt.appendChars}c`);
|
|
326
|
+
const fileList = prompt.contextFiles.files.map((f) => `${f.path}=${f.chars}c`).join(", ");
|
|
327
|
+
lines.push(
|
|
328
|
+
` context-files: ${prompt.contextFiles.count} file(s), ${prompt.contextFiles.totalChars}c` +
|
|
329
|
+
(fileList.length > 0 ? ` — ${fileList}` : ""),
|
|
330
|
+
);
|
|
331
|
+
lines.push(
|
|
332
|
+
` skills: ${prompt.skills.visible} visible + ${prompt.skills.hidden} hidden; ` +
|
|
333
|
+
`prompt-section=${prompt.skills.promptSectionChars}c`,
|
|
334
|
+
);
|
|
335
|
+
lines.push(
|
|
336
|
+
` tools: ${tools.active} active / ${tools.all} registered; schemas=${tools.schemaChars}c; ` +
|
|
337
|
+
`guidelines=${prompt.toolGuidelineChars}c; snippets=${prompt.toolSnippetChars}c`,
|
|
338
|
+
);
|
|
339
|
+
if (tools.bySource.length > 0) {
|
|
340
|
+
const rows = tools.bySource
|
|
341
|
+
.map((r) => `${r.source}=${r.active} (${r.schemaChars}c)`)
|
|
342
|
+
.join("; ");
|
|
343
|
+
lines.push(` per source: ${rows}`);
|
|
344
|
+
}
|
|
345
|
+
lines.push(
|
|
346
|
+
` branch: ${branch.entries} entries; binding-header-copies=${branch.bindingHeaderCopies}`,
|
|
347
|
+
);
|
|
348
|
+
const perkRows = branch.perkContexts.map(
|
|
349
|
+
(r) => `${r.customType} ×${r.copies} (${r.totalChars}c)`,
|
|
350
|
+
);
|
|
351
|
+
const perkSegment = perkRows.length > 0 ? perkRows.join("; ") : "none";
|
|
352
|
+
lines.push(
|
|
353
|
+
` perk contexts: ${perkSegment}; other custom_message ` +
|
|
354
|
+
`×${branch.otherCustomMessages.copies} (${branch.otherCustomMessages.totalChars}c)`,
|
|
355
|
+
);
|
|
356
|
+
return lines.join("\n");
|
|
357
|
+
}
|
|
358
|
+
|
|
132
359
|
/**
|
|
133
360
|
* Register `/perk-selfcheck`: the session-wiring verifier. Runs on a command context (the only
|
|
134
361
|
* context exposing `getSystemPromptOptions()`), so it sees the live splice — not just disk.
|
|
@@ -137,7 +364,7 @@ export function registerSelfcheck(
|
|
|
137
364
|
pi: ExtensionAPI,
|
|
138
365
|
opts: { version: string; sharedOk: boolean },
|
|
139
366
|
): void {
|
|
140
|
-
pi
|
|
367
|
+
registerPerkCommand(pi, "perk-selfcheck", {
|
|
141
368
|
description:
|
|
142
369
|
"Verify perk's session wiring: the ambient index + managed AGENTS block reached the prompt.",
|
|
143
370
|
handler: async (_args, ctx) => {
|
|
@@ -148,8 +375,15 @@ export function registerSelfcheck(
|
|
|
148
375
|
onDiskIndex: readAmbientIndex(ctx.cwd),
|
|
149
376
|
options,
|
|
150
377
|
});
|
|
151
|
-
//
|
|
152
|
-
|
|
378
|
+
// The census is report-only: it rides the same report but never affects ok/level. A command
|
|
379
|
+
// doesn't fire `before_agent_start`, so the branch reads as of the last completed turn.
|
|
380
|
+
const census = renderCensus(
|
|
381
|
+
promptCensus(options),
|
|
382
|
+
toolsCensus(pi.getAllTools(), pi.getActiveTools()),
|
|
383
|
+
branchContextCensus(branchOf(ctx)),
|
|
384
|
+
);
|
|
385
|
+
// Headless-safe: report() surfaces the derived counts/identifiers (never raw prompt content).
|
|
386
|
+
reportTo(ctx, "selfcheck", report.level, `${report.summary}\n${census}`);
|
|
153
387
|
},
|
|
154
388
|
});
|
|
155
389
|
}
|
|
@@ -21,8 +21,11 @@ import {
|
|
|
21
21
|
runColdDoor,
|
|
22
22
|
stringField,
|
|
23
23
|
} from "../substrate/coldDoor.ts";
|
|
24
|
+
import { registerPerkCommand } from "../substrate/command.ts";
|
|
24
25
|
import { loadPerkConfig } from "../substrate/config.ts";
|
|
26
|
+
import { render } from "../substrate/prompts.ts";
|
|
25
27
|
import { failFor, type OkDetails, ok, type Result } from "../substrate/result.ts";
|
|
28
|
+
import { captureSessionPointer } from "../substrate/sessionPointers.ts";
|
|
26
29
|
import { appendWorkflowState, branchOf, rebuildWorkflowState } from "../substrate/workflowState.ts";
|
|
27
30
|
import { report } from "../surfaces/report.ts";
|
|
28
31
|
|
|
@@ -104,12 +107,38 @@ function isUnmergeable(details: SubmitDetails): details is OkDetails<SubmitOk> {
|
|
|
104
107
|
export async function submitPr(pi: ExtensionAPI, ctx: ExtensionContext): Promise<SubmitResult> {
|
|
105
108
|
const fail = failFor(ctx, "submit");
|
|
106
109
|
|
|
107
|
-
|
|
110
|
+
// Stamp this implement run id into the plan-header `impl_run_ids` linkage (contracts.md
|
|
111
|
+
// §8.35) so a later/other session can resolve the implement session pointers cross-run.
|
|
112
|
+
// Mirrors planSave's `--run-id` thread; covers the interactive /submit AND the headless worker
|
|
113
|
+
// drive (both flow through this warm door). Absent run_id ⇒ omit (bare-stamp untouched).
|
|
114
|
+
const runId = rebuildWorkflowState(branchOf(ctx)).run_id ?? "";
|
|
115
|
+
const args = ["pr", "submit", "--json"];
|
|
116
|
+
if (runId) args.push("--run-id", runId);
|
|
117
|
+
const r = await runColdDoor<SubmitOk>(pi, ctx, args, {
|
|
108
118
|
label: "perk pr submit",
|
|
109
119
|
decode: decodeSubmit,
|
|
110
120
|
});
|
|
111
121
|
if (!r.ok) return fail(r.message, r.errorType);
|
|
112
122
|
|
|
123
|
+
// Capture `implementation/main` at the moment the run id enters `impl_run_ids` (contracts.md
|
|
124
|
+
// §8.35): any run id stamped into the linkage gets its pointer captured in the same gesture.
|
|
125
|
+
// This covers address/warm sessions that submit — which the stage-gated `session_start` capture
|
|
126
|
+
// never sees — so a submitted run resolves `found` instead of `missing`. For the implement
|
|
127
|
+
// session's own /submit it is an idempotent same-session refresh; `preserveForeign` guarantees
|
|
128
|
+
// it can never clobber a different session's pointer. Best-effort + non-fatal like every
|
|
129
|
+
// capture site (a successful submit must stand).
|
|
130
|
+
if (runId) {
|
|
131
|
+
captureSessionPointer({
|
|
132
|
+
cwd: ctx.cwd,
|
|
133
|
+
runId,
|
|
134
|
+
klass: "implementation",
|
|
135
|
+
site: "main",
|
|
136
|
+
// Optional-chained: best-effort, and some side-session fakes have no getSessionFile.
|
|
137
|
+
sessionFile: ctx.sessionManager.getSessionFile?.(),
|
|
138
|
+
preserveForeign: true,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
113
142
|
const verb = r.data.pr.existed ? "Found existing" : "Opened draft";
|
|
114
143
|
const conflicted = r.data.mergeable === false && (r.data.conflicts?.length ?? 0) > 0;
|
|
115
144
|
// Reset the counter on every clean (or undetermined) submit — idempotent; keeps a later
|
|
@@ -147,23 +176,12 @@ export function conflictResolutionGuidance(
|
|
|
147
176
|
cap: number,
|
|
148
177
|
model?: string,
|
|
149
178
|
): string {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
:
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
`${attempt} of ${cap}.`,
|
|
157
|
-
`1. Spawn the \`perk.conflict-resolver\` agent via the \`subagent\` tool with \`context: "fresh"\`${modelClause}. ` +
|
|
158
|
-
"A fresh context keeps this implementation session's history from biasing the resolution.",
|
|
159
|
-
`2. Tell it: rebase the PR branch onto \`${base}\` and **carefully** resolve all merge ` +
|
|
160
|
-
"conflicts so the resulting diff is **clean** (no stray markers, no unrelated churn) and " +
|
|
161
|
-
"**correct** (preserve the change's intent on both sides). The child reads its own plan + PR " +
|
|
162
|
-
"diff context first (it runs `perk pr review-context`) so it resolves with the change's " +
|
|
163
|
-
"intent in hand, verifies, and force-pushes — the raw diff never enters this session.",
|
|
164
|
-
"3. After the child reports success, call `/submit` again to re-verify mergeability. Do NOT " +
|
|
165
|
-
"edit or resolve conflicts yourself here — the child owns the rebase/resolve/push.",
|
|
166
|
-
].join("\n");
|
|
179
|
+
return render("stages/conflict-resolution.md", {
|
|
180
|
+
base,
|
|
181
|
+
attempt: String(attempt),
|
|
182
|
+
cap: String(cap),
|
|
183
|
+
model: model ?? "",
|
|
184
|
+
});
|
|
167
185
|
}
|
|
168
186
|
|
|
169
187
|
/**
|
|
@@ -239,7 +257,7 @@ export function registerSubmit(pi: ExtensionAPI): void {
|
|
|
239
257
|
},
|
|
240
258
|
});
|
|
241
259
|
|
|
242
|
-
pi
|
|
260
|
+
registerPerkCommand(pi, "submit", {
|
|
243
261
|
description: "Push the branch and open a draft PR for the active plan (implement → submit).",
|
|
244
262
|
handler: async (_args, ctx) => {
|
|
245
263
|
const result = await submitPr(pi, ctx);
|