@mgiles/perk 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/extension/authoring/gist/draft.ts +198 -0
- package/extension/authoring/gist/prose.ts +46 -0
- package/extension/authoring/gist/review.ts +133 -0
- package/extension/authoring/gist/save.ts +118 -0
- package/extension/authoring/objective/draft.ts +345 -0
- package/extension/{factories/objectiveDreamReport.ts → authoring/objective/dreamReportGate.ts} +74 -131
- package/extension/authoring/objective/planning.ts +124 -0
- package/extension/authoring/objective/prose.ts +103 -0
- package/extension/authoring/objective/review.ts +128 -0
- package/extension/authoring/objective/save.ts +224 -0
- package/extension/authoring/plan/draft.ts +84 -0
- package/extension/authoring/plan/prose.ts +41 -0
- package/extension/authoring/plan/review.ts +269 -0
- package/extension/authoring/plan/save.ts +256 -0
- package/extension/authoring/plan/source.ts +82 -0
- package/extension/authoring/refinement/context.ts +468 -0
- package/extension/authoring/refinement/draft.ts +261 -0
- package/extension/authoring/refinement/prose.ts +79 -0
- package/extension/authoring/refinement/review.ts +111 -0
- package/extension/authoring/refinement/save.ts +119 -0
- package/extension/authoring/review/approvalGate.ts +34 -0
- package/extension/authoring/review/draftContext.ts +68 -0
- package/extension/codeReview/automated.ts +352 -0
- package/extension/codeReview/submission.ts +229 -0
- package/extension/delivery/address.ts +295 -0
- package/extension/delivery/ci.ts +355 -0
- package/extension/delivery/commitCompact.ts +93 -0
- package/extension/delivery/conflictResolution.ts +247 -0
- package/extension/delivery/ready.ts +193 -0
- package/extension/delivery/stackConflict.ts +361 -0
- package/extension/delivery/stackObjective.ts +16 -0
- package/extension/delivery/stackReconcile.ts +165 -0
- package/extension/delivery/submit.ts +171 -0
- package/extension/index.ts +365 -380
- package/extension/learning/analystWave.ts +324 -0
- package/extension/learning/audit.ts +667 -0
- package/extension/learning/capture.ts +92 -0
- package/extension/learning/containment.ts +104 -0
- package/extension/{waves/dreamWave.ts → learning/dream.ts} +112 -94
- package/extension/learning/dreamAnalysis.ts +435 -0
- package/extension/{waves/dreamReducerWave.ts → learning/dreamReducer.ts} +46 -41
- package/extension/{waves → learning}/dreamReport.ts +35 -31
- package/extension/learning/harvest.ts +491 -0
- package/extension/learning/prose.ts +66 -0
- package/extension/learning/routing.ts +79 -0
- package/extension/pi/v1/bashScanTimeout.ts +64 -0
- package/extension/{doors/prReview.ts → pi/v1/codeReview/automated.ts} +215 -311
- package/extension/{doors/prReviewBrowser.ts → pi/v1/codeReview/browser.ts} +53 -33
- package/extension/{doors/hunkHandoff.ts → pi/v1/codeReview/checkout.ts} +12 -8
- package/extension/{doors/reviewWaveTools.ts → pi/v1/codeReview/reviewWave.ts} +146 -114
- package/extension/{doors/stackReviewBrowser.ts → pi/v1/codeReview/stack.ts} +62 -29
- package/extension/pi/v1/codeReview/submit.ts +354 -0
- package/extension/{doors/prReviewTerminal.ts → pi/v1/codeReview/terminal.ts} +32 -27
- package/extension/pi/v1/contextEvidence.ts +80 -0
- package/extension/pi/v1/contextInjection.ts +207 -0
- package/extension/{doors → pi/v1/delivery}/address.ts +154 -267
- package/extension/pi/v1/delivery/ci.ts +570 -0
- package/extension/pi/v1/delivery/commitCompact.ts +201 -0
- package/extension/pi/v1/delivery/conflictResolverEngine.ts +425 -0
- package/extension/{doors → pi/v1/delivery}/land.ts +123 -61
- package/extension/pi/v1/delivery/ready.ts +322 -0
- package/extension/pi/v1/delivery/stackConflictResolver.ts +172 -0
- package/extension/pi/v1/delivery/stackDrive.ts +120 -0
- package/extension/pi/v1/delivery/stackLand.ts +223 -0
- package/extension/pi/v1/delivery/stackRecover.ts +265 -0
- package/extension/pi/v1/delivery/stackStatus.ts +237 -0
- package/extension/pi/v1/delivery/stackSync.ts +658 -0
- package/extension/pi/v1/delivery/submit.ts +389 -0
- package/extension/pi/v1/delivery/submitConflict.ts +186 -0
- package/extension/pi/v1/draftReview.ts +431 -0
- package/extension/{doors → pi/v1}/draftReviewWaveTools.ts +141 -151
- package/extension/pi/v1/gist.ts +794 -0
- package/extension/pi/v1/learning/audit.ts +186 -0
- package/extension/pi/v1/learning/dream.ts +207 -0
- package/extension/{doors/learnFactory.ts → pi/v1/learning/factory.ts} +18 -65
- package/extension/{doors/harvestWaveTools.ts → pi/v1/learning/harvest.ts} +46 -100
- package/extension/pi/v1/learning/learn.ts +585 -0
- package/extension/pi/v1/lifecycleGates.ts +127 -0
- package/extension/{factories → pi/v1}/objective.ts +53 -33
- package/extension/pi/v1/objectiveAuthoring.ts +672 -0
- package/extension/pi/v1/objectiveDreamGate.ts +160 -0
- package/extension/{factories/objectivePlan.ts → pi/v1/objectivePlanning.ts} +328 -533
- package/extension/pi/v1/objectiveRefinement.ts +1320 -0
- package/extension/pi/v1/objectiveReview.ts +451 -0
- package/extension/{doors → pi/v1}/objectiveReviewBrowser.ts +259 -172
- package/extension/pi/v1/plan.ts +812 -0
- package/extension/pi/v1/planReview.ts +820 -0
- package/extension/{doors → pi/v1}/planReviewBrowser.ts +228 -152
- package/extension/{doors/annotationPush.ts → pi/v1/providers/annotations.ts} +158 -89
- package/extension/pi/v1/providers/plannotator.ts +487 -0
- package/extension/{doors → pi/v1/providers}/plannotatorHandoff.ts +73 -27
- package/extension/pi/v1/providers/selection.ts +43 -0
- package/extension/{adapters/planAdapterTombell.ts → pi/v1/providers/tombell.ts} +43 -72
- package/extension/pi/v1/review.ts +538 -0
- package/extension/pi/v1/reviewOutcome.ts +9 -0
- package/extension/pi/v1/scoutWave.ts +318 -0
- package/extension/{doors → pi/v1}/selfcheck.ts +4 -4
- package/extension/session/branchWorkflowSession.ts +60 -0
- package/extension/session/lifecycle.ts +644 -0
- package/extension/session/lifecycleGates.ts +64 -0
- package/extension/session/saveDestination.ts +87 -0
- package/extension/session/workflowSession.ts +971 -0
- package/extension/substrate/agentScratch.ts +27 -54
- package/extension/substrate/bashScanTimeout.ts +181 -0
- package/extension/substrate/bindingDelivery.ts +38 -30
- package/extension/substrate/bindings.ts +4 -5
- package/extension/substrate/cache.ts +64 -12
- package/extension/substrate/childRestrictions.ts +39 -0
- package/extension/substrate/coldDoor.ts +17 -1
- package/extension/substrate/config.ts +157 -21
- package/extension/substrate/git.ts +88 -6
- package/extension/substrate/modelVisible.ts +53 -0
- package/extension/substrate/prompts.ts +22 -0
- package/extension/substrate/registry.ts +2 -0
- package/extension/substrate/resolverLease.ts +5 -4
- package/extension/substrate/sessionData.ts +85 -152
- package/extension/substrate/toolGating.ts +263 -84
- package/extension/substrate/unifiedDiff.ts +1 -1
- package/extension/substrate/workflowState.ts +178 -163
- package/extension/substrate/worktreeResolverLock.ts +261 -0
- package/extension/surfaces/surfaces.ts +79 -27
- package/extension/waves/adversarialReviewWave.ts +87 -46
- package/extension/waves/blockedReports.ts +59 -0
- package/extension/waves/draftReviewWave.ts +42 -42
- package/extension/waves/laneIdentity.ts +77 -0
- package/extension/waves/objectiveExplorerWave.ts +24 -24
- package/extension/waves/prReviewWave.ts +89 -77
- package/extension/waves/reportWave.ts +438 -578
- package/extension/waves/reviewClassifierWave.ts +22 -22
- package/extension/waves/rpcAdapter.ts +100 -15
- package/extension/waves/scoutWave.ts +192 -0
- package/extension/waves/transport.ts +480 -0
- package/extension/worker/sdkAdapter.ts +494 -0
- package/extension/worker/stageExecution.ts +679 -0
- package/extension/workerMain.ts +18 -19
- package/package.json +6 -4
- package/prompts/_fixtures/live.yaml +43 -18
- package/prompts/contexts/adapters/plannotator-gist.md +6 -0
- package/prompts/contexts/adapters/plannotator-objective.md +6 -0
- package/prompts/contexts/adapters/plannotator-plan.md +8 -1
- package/prompts/contexts/adapters/plannotator-refinement.md +22 -0
- package/prompts/contexts/objective-refinement.md +17 -0
- package/prompts/contexts/read-only.md +1 -1
- package/prompts/stages/conflict-resolution-continuation.md +9 -6
- package/prompts/stages/conflict-resolution.md +4 -4
- package/prompts/stages/objective-plan/guidance.md +2 -2
- package/prompts/stages/objective-plan/seed.md +9 -1
- package/prompts/stages/objective-reconcile-ready.md +1 -1
- package/prompts/stages/objective-reconcile.md +1 -1
- package/prompts/stages/objective-refine/seed.md +18 -0
- package/prompts/stages/objective-review-browser.md +4 -4
- package/prompts/stages/objective-sync.md +1 -1
- package/prompts/stages/plan-review-browser.md +4 -4
- package/prompts/stages/pr-review-browser/active.md +3 -4
- package/prompts/stages/pr-review-browser/foreign.md +3 -4
- package/prompts/stages/pr-review-terminal/active.md +3 -3
- package/prompts/stages/pr-review-terminal/foreign.md +3 -3
- package/prompts/stages/pr-review.md +3 -3
- package/prompts/stages/stack-review-browser/stack.md +5 -6
- package/shared/README.md +8 -0
- package/shared/bindings.yaml +3 -3
- package/shared/contracts.md +2601 -506
- package/shared/fixtures/issues-table.json +130 -0
- package/shared/registry.yaml +13 -0
- package/shared/schemas/outputs/objective-node-engagement.schema.json +318 -0
- package/shared/schemas/outputs/objective-stack-status.schema.json +6 -1
- package/shared/schemas/outputs/pr-review-context.schema.json +54 -9
- package/shared/schemas/outputs/pr-review-stack-context.schema.json +196 -0
- package/extension/adapters/planAdapterPlannotator.ts +0 -362
- package/extension/doors/auditWaveTools.ts +0 -352
- package/extension/doors/ciExecutor.ts +0 -756
- package/extension/doors/commitCompact.ts +0 -251
- package/extension/doors/dreamWaveTools.ts +0 -489
- package/extension/doors/learn.ts +0 -668
- package/extension/doors/lifecycleGates.ts +0 -207
- package/extension/doors/objectiveStack.ts +0 -1543
- package/extension/doors/prReviewDynamic.ts +0 -276
- package/extension/doors/ready.ts +0 -279
- package/extension/doors/submit.ts +0 -373
- package/extension/doors/submitPrReview.ts +0 -505
- package/extension/factories/gistAuthor.ts +0 -94
- package/extension/factories/gistDraft.ts +0 -265
- package/extension/factories/gistSave.ts +0 -251
- package/extension/factories/implementHere.ts +0 -116
- package/extension/factories/objectiveAuthor.ts +0 -98
- package/extension/factories/objectiveDraft.ts +0 -466
- package/extension/factories/objectiveSave.ts +0 -366
- package/extension/factories/planDraft.ts +0 -140
- package/extension/factories/planMode.ts +0 -205
- package/extension/factories/planReview.ts +0 -1237
- package/extension/factories/planSave.ts +0 -604
- package/extension/factories/planTitle.ts +0 -141
- package/extension/substrate/structuredOutput.ts +0 -202
- package/extension/waves/auditWave.ts +0 -312
- package/extension/waves/harvestWave.ts +0 -399
- package/extension/waves/learnWave.ts +0 -155
- package/extension/waves/memoryAdapter.ts +0 -139
- package/extension/waves/prReviewDynamicWave.ts +0 -777
- package/extension/worker/readOnlySession.ts +0 -294
- package/extension/worker/worker.ts +0 -899
- package/prompts/stages/pr-review-dynamic.md +0 -7
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Session-lifecycle gates (the interior safety primitive), the Pi registration half. One
|
|
2
|
+
// reusable dirty-repo guard on session_before_switch / session_before_fork that returns
|
|
3
|
+
// { cancel: true } when an *active perk workflow* has uncommitted changes — so a stage
|
|
4
|
+
// transition never silently orphans work. Plus the guard-only `/implement` command that
|
|
5
|
+
// *enforces* the implement stage's `warm: false` legality (the plan→implement jump requires
|
|
6
|
+
// fresh context; that is the cold door `perk implement`). The Pi-free policy these hooks apply
|
|
7
|
+
// (`implementHandoffPrompt`, `planningStageRefusal`) lives in `session/lifecycleGates.ts`.
|
|
8
|
+
//
|
|
9
|
+
// pi.on("session_before_fork"/"...switch") handlers are fired by extensionRunner.emit({type,...})
|
|
10
|
+
// and their { cancel } result round-trips; the handler's
|
|
11
|
+
// pi.exec("git",["status","--porcelain"],{cwd}) resolves the session cwd.
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
ExtensionAPI,
|
|
15
|
+
ExtensionCommandContext,
|
|
16
|
+
ExtensionContext,
|
|
17
|
+
} from "@earendil-works/pi-coding-agent";
|
|
18
|
+
import { implementHandoffPrompt } from "../../session/lifecycleGates.ts";
|
|
19
|
+
import type { PlanRef } from "../../substrate/cache.ts";
|
|
20
|
+
import { registerPerkCommand } from "../../substrate/command.ts";
|
|
21
|
+
import { branchOf, rebuildWorkflowState } from "../../substrate/workflowState.ts";
|
|
22
|
+
import { report } from "../../surfaces/report.ts";
|
|
23
|
+
|
|
24
|
+
const DIRTY_MESSAGE = "uncommitted changes — commit or stash before switching/forking this stage.";
|
|
25
|
+
const HANDOFF_DIRTY_MESSAGE =
|
|
26
|
+
"uncommitted changes — commit before a fresh-context /implement handoff (the plan is the " +
|
|
27
|
+
"only artifact that crosses the boundary).";
|
|
28
|
+
|
|
29
|
+
/** True when this session is linked to a plan (a perk workflow is in progress). */
|
|
30
|
+
function workflowActive(ctx: ExtensionContext): boolean {
|
|
31
|
+
return rebuildWorkflowState(branchOf(ctx)).active_plan_ref != null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The shared dirty-repo guard for switch/fork. Allows (returns undefined) outside a workflow or on
|
|
36
|
+
* a clean tree; cancels with a loud, fail-safe-headless message on a dirty tree in a workflow. If
|
|
37
|
+
* `git status` itself fails (e.g. not a repo) we allow — this is a hygiene guard, not a validator.
|
|
38
|
+
*/
|
|
39
|
+
async function guardTransition(
|
|
40
|
+
pi: ExtensionAPI,
|
|
41
|
+
ctx: ExtensionContext,
|
|
42
|
+
): Promise<{ cancel: true } | undefined> {
|
|
43
|
+
if (!workflowActive(ctx)) return undefined; // perk does not interfere with non-perk transitions
|
|
44
|
+
const res = await pi.exec("git", ["status", "--porcelain"], { cwd: ctx.cwd });
|
|
45
|
+
const dirty = res.code === 0 && res.stdout.trim().length > 0;
|
|
46
|
+
if (!dirty) return undefined;
|
|
47
|
+
report(ctx, "lifecycle", "warning", DIRTY_MESSAGE); // fail-safe-headless: loud, still cancels
|
|
48
|
+
return { cancel: true };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The warm `/implement`. It still NEVER performs the cross-worktree plan→implement
|
|
53
|
+
* transition (that is structurally the Python cold door's job — no extension session API can change
|
|
54
|
+
* cwd). Outside an impl context it refuses and points at `perk implement`. INSIDE an active
|
|
55
|
+
* impl worktree (read-write + a linked plan-ref) it offers the in-process twin of the cold door: a
|
|
56
|
+
* lossless `ctx.newSession` fresh-context handoff seeded with the plan-read priming (the plan, and
|
|
57
|
+
* the worktree's materialized plan-ref, are the durable state — model-visible output stays capped).
|
|
58
|
+
* Dirty-tree hygiene is gated manually here (belt-and-suspenders: `newSession` is a session-replace,
|
|
59
|
+
* not a fork/switch, so it may bypass the `session_before_*` gate — we refuse on a dirty tree
|
|
60
|
+
* either way), fail-safe-headless.
|
|
61
|
+
*/
|
|
62
|
+
function registerImplementGuard(pi: ExtensionAPI): void {
|
|
63
|
+
registerPerkCommand(pi, "implement", {
|
|
64
|
+
description:
|
|
65
|
+
"Refresh implement context (in-worktree handoff); cross-worktree is `perk implement`.",
|
|
66
|
+
handler: async (_args, ctx) => {
|
|
67
|
+
const state = rebuildWorkflowState(branchOf(ctx));
|
|
68
|
+
const ref = state.active_plan_ref;
|
|
69
|
+
const inImpl = state.mode === "read-write" && ref != null;
|
|
70
|
+
if (!inImpl) {
|
|
71
|
+
report(
|
|
72
|
+
ctx,
|
|
73
|
+
"implement",
|
|
74
|
+
"warning",
|
|
75
|
+
"/implement is cold-only here — run `perk implement` from a shell for fresh context.",
|
|
76
|
+
);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Dirty-tree gate (manual; see the doc comment). Refuse the handoff with uncommitted work.
|
|
81
|
+
const status = await pi.exec("git", ["status", "--porcelain"], { cwd: ctx.cwd });
|
|
82
|
+
if (status.code === 0 && status.stdout.trim().length > 0) {
|
|
83
|
+
report(ctx, "implement", "warning", HANDOFF_DIRTY_MESSAGE);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const commandCtx = ctx as ExtensionCommandContext;
|
|
88
|
+
if (typeof commandCtx.newSession !== "function") {
|
|
89
|
+
report(
|
|
90
|
+
ctx,
|
|
91
|
+
"implement",
|
|
92
|
+
"warning",
|
|
93
|
+
"a fresh-context /implement handoff needs an interactive session.",
|
|
94
|
+
);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const prompt = implementHandoffPrompt(ref as PlanRef);
|
|
99
|
+
const parentSession = ctx.sessionManager.getSessionFile() ?? undefined;
|
|
100
|
+
const result = await commandCtx.newSession({
|
|
101
|
+
parentSession,
|
|
102
|
+
withSession: async (fresh) => {
|
|
103
|
+
await fresh.sendUserMessage(prompt);
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Verify + cap model-visible output (the full state lives in the worktree + the plan issue).
|
|
108
|
+
if (result.cancelled) {
|
|
109
|
+
report(ctx, "implement", "info", "/implement handoff cancelled — staying in this session.");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
report(
|
|
113
|
+
ctx,
|
|
114
|
+
"implement",
|
|
115
|
+
"info",
|
|
116
|
+
`fresh implement session started for plan #${(ref as PlanRef).pr_id} — the plan is carried forward, not summarized.`,
|
|
117
|
+
);
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Register the dirty-repo lifecycle gate (switch + fork) and the guard-only `/implement`. */
|
|
123
|
+
export function registerLifecycleGates(pi: ExtensionAPI): void {
|
|
124
|
+
pi.on("session_before_fork", async (_event, ctx) => guardTransition(pi, ctx));
|
|
125
|
+
pi.on("session_before_switch", async (_event, ctx) => guardTransition(pi, ctx));
|
|
126
|
+
registerImplementGuard(pi);
|
|
127
|
+
}
|
|
@@ -22,9 +22,11 @@
|
|
|
22
22
|
// best-effort and never throw (logged-not-thrown).
|
|
23
23
|
|
|
24
24
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
25
|
+
import { openBranchWorkflowSession } from "../../session/branchWorkflowSession.ts";
|
|
26
|
+
import { type ColdJson, objectField, runColdDoor, stringField } from "../../substrate/coldDoor.ts";
|
|
27
|
+
import { registerPerkCommand } from "../../substrate/command.ts";
|
|
28
|
+
import { loadPerkConfig } from "../../substrate/config.ts";
|
|
29
|
+
import { type BranchEntry, branchOf, WORKFLOW_STATE_TYPE } from "../../substrate/workflowState.ts";
|
|
28
30
|
import {
|
|
29
31
|
formatBudgetLine,
|
|
30
32
|
MARK_OBJECTIVE,
|
|
@@ -32,7 +34,7 @@ import {
|
|
|
32
34
|
type PerkStatusHandle,
|
|
33
35
|
registerTranscriptRenderer,
|
|
34
36
|
report,
|
|
35
|
-
} from "
|
|
37
|
+
} from "../../surfaces/surfaces.ts";
|
|
36
38
|
|
|
37
39
|
/** The dedicated budget/activation session entry type (kept off `perk:workflow-state`). */
|
|
38
40
|
export const OBJECTIVE_BUDGET_TYPE = "perk:objective-budget";
|
|
@@ -41,10 +43,7 @@ export const OBJECTIVE_BUDGET_TYPE = "perk:objective-budget";
|
|
|
41
43
|
export const DEFAULT_COMPACT_THRESHOLD = 0.8;
|
|
42
44
|
|
|
43
45
|
/** A branch entry as seen by the budget scan: a custom marker OR an assistant message. */
|
|
44
|
-
interface ScanEntry {
|
|
45
|
-
type: string;
|
|
46
|
-
customType?: string;
|
|
47
|
-
data?: { objective_id?: string; activated_at?: string };
|
|
46
|
+
interface ScanEntry extends BranchEntry {
|
|
48
47
|
message?: { role?: string; usage?: { input?: number; output?: number } };
|
|
49
48
|
}
|
|
50
49
|
|
|
@@ -108,7 +107,7 @@ export function rebuildBudget(branch: readonly ScanEntry[], now: number): Budget
|
|
|
108
107
|
|
|
109
108
|
/** True when context usage has crossed the compaction threshold (pure; tolerant of unknowns). */
|
|
110
109
|
export function shouldCompact(
|
|
111
|
-
usage: { percent: number | null
|
|
110
|
+
usage: { percent: number | null } | undefined,
|
|
112
111
|
threshold: number,
|
|
113
112
|
): boolean {
|
|
114
113
|
if (!usage || usage.percent === null) return false;
|
|
@@ -118,16 +117,14 @@ export function shouldCompact(
|
|
|
118
117
|
// --- the controller -----------------------------------------------------------------------------
|
|
119
118
|
|
|
120
119
|
function scanBranchOf(ctx: ExtensionContext): ScanEntry[] {
|
|
121
|
-
|
|
120
|
+
// `branchOf` owns the typed accessor; the scan only ADDS the optional assistant-message
|
|
121
|
+
// shape, so no round-trip through `unknown` is needed (field presence is checked locally).
|
|
122
|
+
return branchOf(ctx) as ScanEntry[];
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return state.active_objective ?? null;
|
|
128
|
-
} catch {
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
125
|
+
/** The rebuilt `active_objective`, read through the session seam (fail-open null). */
|
|
126
|
+
function activeObjective(pi: ExtensionAPI, ctx: ExtensionContext): string | null {
|
|
127
|
+
return openBranchWorkflowSession(pi, ctx).activeObjective();
|
|
131
128
|
}
|
|
132
129
|
|
|
133
130
|
/**
|
|
@@ -135,10 +132,10 @@ function activeObjective(ctx: ExtensionContext): string | null {
|
|
|
135
132
|
* widget is retired; the value carries id + tokens + elapsed). Headless-safe:
|
|
136
133
|
* the handle no-ops without UI.
|
|
137
134
|
*/
|
|
138
|
-
function renderStatus(ctx: ExtensionContext, status: PerkStatusHandle): void {
|
|
135
|
+
function renderStatus(pi: ExtensionAPI, ctx: ExtensionContext, status: PerkStatusHandle): void {
|
|
139
136
|
if (!ctx.hasUI) return;
|
|
140
137
|
try {
|
|
141
|
-
const active = activeObjective(ctx);
|
|
138
|
+
const active = activeObjective(pi, ctx);
|
|
142
139
|
if (active === null) {
|
|
143
140
|
status.set(ctx, undefined);
|
|
144
141
|
return;
|
|
@@ -164,7 +161,7 @@ function objectiveCommand(
|
|
|
164
161
|
const arg = args.trim();
|
|
165
162
|
try {
|
|
166
163
|
if (arg === "") {
|
|
167
|
-
const active = activeObjective(ctx);
|
|
164
|
+
const active = activeObjective(pi, ctx);
|
|
168
165
|
const budget = rebuildBudget(scanBranchOf(ctx), Date.now());
|
|
169
166
|
const message =
|
|
170
167
|
active === null
|
|
@@ -176,18 +173,20 @@ function objectiveCommand(
|
|
|
176
173
|
|
|
177
174
|
if (arg === "clear") {
|
|
178
175
|
pi.appendEntry(WORKFLOW_STATE_TYPE, { active_objective: null });
|
|
179
|
-
renderStatus(ctx, status);
|
|
176
|
+
renderStatus(pi, ctx, status);
|
|
180
177
|
report(ctx, "objective", "info", "cleared the active objective.");
|
|
181
178
|
return;
|
|
182
179
|
}
|
|
183
180
|
|
|
184
|
-
// Activate: set the LWW field + seed a fresh budget activation marker.
|
|
181
|
+
// Activate: set the LWW field + seed a fresh budget activation marker. This command path's
|
|
182
|
+
// `active_objective` write is a raw LWW append WITHOUT the session seam's strict read-back
|
|
183
|
+
// (the seam path in `saveObjective` verifies; this one stays best-effort — stated honestly).
|
|
185
184
|
pi.appendEntry(WORKFLOW_STATE_TYPE, { active_objective: arg });
|
|
186
185
|
pi.appendEntry(OBJECTIVE_BUDGET_TYPE, {
|
|
187
186
|
objective_id: arg,
|
|
188
187
|
activated_at: new Date().toISOString(),
|
|
189
188
|
});
|
|
190
|
-
renderStatus(ctx, status);
|
|
189
|
+
renderStatus(pi, ctx, status);
|
|
191
190
|
report(ctx, "objective", "info", `activated objective ${arg} (budget tracking started).`);
|
|
192
191
|
} catch (error) {
|
|
193
192
|
reportError(ctx, `command failed: ${error}`);
|
|
@@ -195,37 +194,58 @@ function objectiveCommand(
|
|
|
195
194
|
}
|
|
196
195
|
|
|
197
196
|
/**
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
197
|
+
* Fetch the objective's URL via `perk objective show <id> --json` (reading `objective.url`).
|
|
198
|
+
* Lenient: returns "" on any failure / missing url — `runColdDoor` is a soft-result seam and
|
|
199
|
+
* never throws (the seed prompt's step-1 `perk objective show <id>` step surfaces the URL
|
|
200
|
+
* anyway). Only called for the linear backend (github needs no clause → no fetch). Exported for
|
|
201
|
+
* the warm drives that compose the same backend-aware read clause (the ready-time reconcile
|
|
202
|
+
* drive in `pi/v1/delivery/ready.ts`).
|
|
203
|
+
*/
|
|
204
|
+
export async function fetchObjectiveUrl(
|
|
205
|
+
pi: ExtensionAPI,
|
|
206
|
+
ctx: ExtensionContext,
|
|
207
|
+
objectiveId: string,
|
|
208
|
+
): Promise<string> {
|
|
209
|
+
const r = await runColdDoor<string>(pi, ctx, ["objective", "show", objectiveId, "--json"], {
|
|
210
|
+
label: "perk objective show",
|
|
211
|
+
decode: (payload: ColdJson) =>
|
|
212
|
+
stringField(objectField(payload, "objective") ?? {}, "url") ?? "",
|
|
213
|
+
});
|
|
214
|
+
return r.ok ? r.data : "";
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Install the objective substrate bindings: `/objective` command, budget accounting
|
|
219
|
+
* (session_start / session_tree / agent_settled), and threshold compaction (turn_end). All
|
|
220
|
+
* inert when no objective is active; never throws.
|
|
201
221
|
*/
|
|
202
|
-
export function
|
|
222
|
+
export function installObjectiveBindings(pi: ExtensionAPI, status: PerkStatusHandle): void {
|
|
203
223
|
// Transcript marker for `perk:objective-budget` activations (audit §2.3): renderer body in
|
|
204
224
|
// surfaces.ts, registration = wiring, feature-detect inside the seam (pre-0.80.4 hosts stay
|
|
205
225
|
// inert). Also covers objectiveSave.ts's appends — registration is per entry TYPE.
|
|
206
226
|
registerTranscriptRenderer(pi, OBJECTIVE_BUDGET_TYPE, objectiveBudgetEntryRenderer);
|
|
207
227
|
|
|
208
228
|
pi.on("session_start", async (_event, ctx) => {
|
|
209
|
-
renderStatus(ctx, status);
|
|
229
|
+
renderStatus(pi, ctx, status);
|
|
210
230
|
});
|
|
211
231
|
|
|
212
232
|
pi.on("session_tree", async (_event, ctx) => {
|
|
213
|
-
renderStatus(ctx, status);
|
|
233
|
+
renderStatus(pi, ctx, status);
|
|
214
234
|
});
|
|
215
235
|
|
|
216
236
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
217
237
|
// Recompute the budget after each settled run (stateless rebuild from the branch).
|
|
218
|
-
renderStatus(ctx, status);
|
|
238
|
+
renderStatus(pi, ctx, status);
|
|
219
239
|
});
|
|
220
240
|
|
|
221
241
|
pi.on("turn_end", async (_event, ctx) => {
|
|
222
242
|
try {
|
|
223
|
-
if (activeObjective(ctx) === null) return; // inert unless an objective is active
|
|
243
|
+
if (activeObjective(pi, ctx) === null) return; // inert unless an objective is active
|
|
224
244
|
const threshold =
|
|
225
245
|
loadPerkConfig(ctx.cwd).objectiveCompactThreshold ?? DEFAULT_COMPACT_THRESHOLD;
|
|
226
246
|
const usage = ctx.getContextUsage();
|
|
227
247
|
if (!shouldCompact(usage, threshold)) return;
|
|
228
|
-
const active = activeObjective(ctx);
|
|
248
|
+
const active = activeObjective(pi, ctx);
|
|
229
249
|
ctx.compact({
|
|
230
250
|
customInstructions:
|
|
231
251
|
`Preserve the active perk objective (${active}) and its budget context. ` +
|
|
@@ -234,7 +254,7 @@ export function registerObjective(pi: ExtensionAPI, status: PerkStatusHandle): v
|
|
|
234
254
|
console.error(`perk: objective compaction failed — ${error}`);
|
|
235
255
|
},
|
|
236
256
|
onComplete: () => {
|
|
237
|
-
renderStatus(ctx, status);
|
|
257
|
+
renderStatus(pi, ctx, status);
|
|
238
258
|
},
|
|
239
259
|
});
|
|
240
260
|
} catch (error) {
|