@mgiles/perk 3.2.0 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/extension/authoring/gist/draft.ts +198 -0
- package/extension/authoring/gist/prose.ts +46 -0
- package/extension/authoring/gist/review.ts +133 -0
- package/extension/authoring/gist/save.ts +118 -0
- package/extension/authoring/objective/draft.ts +345 -0
- package/extension/{factories/objectiveDreamReport.ts → authoring/objective/dreamReportGate.ts} +74 -131
- package/extension/authoring/objective/planning.ts +124 -0
- package/extension/authoring/objective/prose.ts +103 -0
- package/extension/authoring/objective/review.ts +128 -0
- package/extension/authoring/objective/save.ts +224 -0
- package/extension/authoring/plan/draft.ts +84 -0
- package/extension/authoring/plan/prose.ts +41 -0
- package/extension/authoring/plan/review.ts +269 -0
- package/extension/authoring/plan/save.ts +256 -0
- package/extension/authoring/plan/source.ts +82 -0
- package/extension/authoring/refinement/context.ts +468 -0
- package/extension/authoring/refinement/draft.ts +261 -0
- package/extension/authoring/refinement/prose.ts +79 -0
- package/extension/authoring/refinement/review.ts +111 -0
- package/extension/authoring/refinement/save.ts +119 -0
- package/extension/authoring/review/approvalGate.ts +34 -0
- package/extension/authoring/review/draftContext.ts +68 -0
- package/extension/codeReview/automated.ts +352 -0
- package/extension/codeReview/submission.ts +229 -0
- package/extension/delivery/address.ts +295 -0
- package/extension/delivery/ci.ts +355 -0
- package/extension/delivery/commitCompact.ts +93 -0
- package/extension/delivery/conflictResolution.ts +247 -0
- package/extension/delivery/ready.ts +193 -0
- package/extension/delivery/stackConflict.ts +361 -0
- package/extension/delivery/stackObjective.ts +16 -0
- package/extension/delivery/stackReconcile.ts +165 -0
- package/extension/delivery/submit.ts +171 -0
- package/extension/index.ts +365 -380
- package/extension/learning/analystWave.ts +324 -0
- package/extension/learning/audit.ts +667 -0
- package/extension/learning/capture.ts +92 -0
- package/extension/learning/containment.ts +104 -0
- package/extension/{waves/dreamWave.ts → learning/dream.ts} +112 -94
- package/extension/learning/dreamAnalysis.ts +435 -0
- package/extension/{waves/dreamReducerWave.ts → learning/dreamReducer.ts} +46 -41
- package/extension/{waves → learning}/dreamReport.ts +35 -31
- package/extension/learning/harvest.ts +491 -0
- package/extension/learning/prose.ts +66 -0
- package/extension/learning/routing.ts +79 -0
- package/extension/pi/v1/bashScanTimeout.ts +64 -0
- package/extension/{doors/prReview.ts → pi/v1/codeReview/automated.ts} +215 -311
- package/extension/{doors/prReviewBrowser.ts → pi/v1/codeReview/browser.ts} +53 -33
- package/extension/{doors/hunkHandoff.ts → pi/v1/codeReview/checkout.ts} +12 -8
- package/extension/{doors/reviewWaveTools.ts → pi/v1/codeReview/reviewWave.ts} +146 -114
- package/extension/{doors/stackReviewBrowser.ts → pi/v1/codeReview/stack.ts} +62 -29
- package/extension/pi/v1/codeReview/submit.ts +354 -0
- package/extension/{doors/prReviewTerminal.ts → pi/v1/codeReview/terminal.ts} +32 -27
- package/extension/pi/v1/contextEvidence.ts +80 -0
- package/extension/pi/v1/contextInjection.ts +207 -0
- package/extension/{doors → pi/v1/delivery}/address.ts +154 -267
- package/extension/pi/v1/delivery/ci.ts +570 -0
- package/extension/pi/v1/delivery/commitCompact.ts +201 -0
- package/extension/pi/v1/delivery/conflictResolverEngine.ts +425 -0
- package/extension/{doors → pi/v1/delivery}/land.ts +123 -61
- package/extension/pi/v1/delivery/ready.ts +322 -0
- package/extension/pi/v1/delivery/stackConflictResolver.ts +172 -0
- package/extension/pi/v1/delivery/stackDrive.ts +120 -0
- package/extension/pi/v1/delivery/stackLand.ts +223 -0
- package/extension/pi/v1/delivery/stackRecover.ts +265 -0
- package/extension/pi/v1/delivery/stackStatus.ts +237 -0
- package/extension/pi/v1/delivery/stackSync.ts +658 -0
- package/extension/pi/v1/delivery/submit.ts +389 -0
- package/extension/pi/v1/delivery/submitConflict.ts +186 -0
- package/extension/pi/v1/draftReview.ts +431 -0
- package/extension/{doors → pi/v1}/draftReviewWaveTools.ts +141 -151
- package/extension/pi/v1/gist.ts +794 -0
- package/extension/pi/v1/learning/audit.ts +186 -0
- package/extension/pi/v1/learning/dream.ts +207 -0
- package/extension/{doors/learnFactory.ts → pi/v1/learning/factory.ts} +18 -65
- package/extension/{doors/harvestWaveTools.ts → pi/v1/learning/harvest.ts} +46 -100
- package/extension/pi/v1/learning/learn.ts +585 -0
- package/extension/pi/v1/lifecycleGates.ts +127 -0
- package/extension/{factories → pi/v1}/objective.ts +53 -33
- package/extension/pi/v1/objectiveAuthoring.ts +672 -0
- package/extension/pi/v1/objectiveDreamGate.ts +160 -0
- package/extension/{factories/objectivePlan.ts → pi/v1/objectivePlanning.ts} +328 -533
- package/extension/pi/v1/objectiveRefinement.ts +1320 -0
- package/extension/pi/v1/objectiveReview.ts +451 -0
- package/extension/{doors → pi/v1}/objectiveReviewBrowser.ts +259 -172
- package/extension/pi/v1/plan.ts +812 -0
- package/extension/pi/v1/planReview.ts +820 -0
- package/extension/{doors → pi/v1}/planReviewBrowser.ts +228 -152
- package/extension/{doors/annotationPush.ts → pi/v1/providers/annotations.ts} +158 -89
- package/extension/pi/v1/providers/plannotator.ts +487 -0
- package/extension/{doors → pi/v1/providers}/plannotatorHandoff.ts +73 -27
- package/extension/pi/v1/providers/selection.ts +43 -0
- package/extension/{adapters/planAdapterTombell.ts → pi/v1/providers/tombell.ts} +43 -72
- package/extension/pi/v1/review.ts +538 -0
- package/extension/pi/v1/reviewOutcome.ts +9 -0
- package/extension/pi/v1/scoutWave.ts +318 -0
- package/extension/{doors → pi/v1}/selfcheck.ts +4 -4
- package/extension/session/branchWorkflowSession.ts +60 -0
- package/extension/session/lifecycle.ts +644 -0
- package/extension/session/lifecycleGates.ts +64 -0
- package/extension/session/saveDestination.ts +87 -0
- package/extension/session/workflowSession.ts +971 -0
- package/extension/substrate/agentScratch.ts +27 -54
- package/extension/substrate/bashScanTimeout.ts +181 -0
- package/extension/substrate/bindingDelivery.ts +38 -30
- package/extension/substrate/bindings.ts +4 -5
- package/extension/substrate/cache.ts +64 -12
- package/extension/substrate/childRestrictions.ts +39 -0
- package/extension/substrate/coldDoor.ts +17 -1
- package/extension/substrate/config.ts +157 -21
- package/extension/substrate/git.ts +88 -6
- package/extension/substrate/modelVisible.ts +53 -0
- package/extension/substrate/prompts.ts +22 -0
- package/extension/substrate/registry.ts +2 -0
- package/extension/substrate/resolverLease.ts +5 -4
- package/extension/substrate/sessionData.ts +85 -152
- package/extension/substrate/toolGating.ts +263 -84
- package/extension/substrate/unifiedDiff.ts +1 -1
- package/extension/substrate/workflowState.ts +178 -163
- package/extension/substrate/worktreeResolverLock.ts +261 -0
- package/extension/surfaces/surfaces.ts +79 -27
- package/extension/waves/adversarialReviewWave.ts +87 -46
- package/extension/waves/blockedReports.ts +59 -0
- package/extension/waves/draftReviewWave.ts +42 -42
- package/extension/waves/laneIdentity.ts +77 -0
- package/extension/waves/objectiveExplorerWave.ts +24 -24
- package/extension/waves/prReviewWave.ts +89 -77
- package/extension/waves/reportWave.ts +438 -578
- package/extension/waves/reviewClassifierWave.ts +22 -22
- package/extension/waves/rpcAdapter.ts +100 -15
- package/extension/waves/scoutWave.ts +192 -0
- package/extension/waves/transport.ts +480 -0
- package/extension/worker/sdkAdapter.ts +494 -0
- package/extension/worker/stageExecution.ts +679 -0
- package/extension/workerMain.ts +18 -19
- package/package.json +6 -4
- package/prompts/_fixtures/live.yaml +43 -18
- package/prompts/contexts/adapters/plannotator-gist.md +6 -0
- package/prompts/contexts/adapters/plannotator-objective.md +6 -0
- package/prompts/contexts/adapters/plannotator-plan.md +8 -1
- package/prompts/contexts/adapters/plannotator-refinement.md +22 -0
- package/prompts/contexts/objective-refinement.md +17 -0
- package/prompts/contexts/read-only.md +1 -1
- package/prompts/stages/conflict-resolution-continuation.md +9 -6
- package/prompts/stages/conflict-resolution.md +4 -4
- package/prompts/stages/objective-plan/guidance.md +2 -2
- package/prompts/stages/objective-plan/seed.md +9 -1
- package/prompts/stages/objective-reconcile-ready.md +1 -1
- package/prompts/stages/objective-reconcile.md +1 -1
- package/prompts/stages/objective-refine/seed.md +18 -0
- package/prompts/stages/objective-review-browser.md +4 -4
- package/prompts/stages/objective-sync.md +1 -1
- package/prompts/stages/plan-review-browser.md +4 -4
- package/prompts/stages/pr-review-browser/active.md +3 -4
- package/prompts/stages/pr-review-browser/foreign.md +3 -4
- package/prompts/stages/pr-review-terminal/active.md +3 -3
- package/prompts/stages/pr-review-terminal/foreign.md +3 -3
- package/prompts/stages/pr-review.md +3 -3
- package/prompts/stages/stack-review-browser/stack.md +5 -6
- package/shared/README.md +8 -0
- package/shared/bindings.yaml +3 -3
- package/shared/contracts.md +2601 -506
- package/shared/fixtures/issues-table.json +130 -0
- package/shared/registry.yaml +13 -0
- package/shared/schemas/outputs/objective-node-engagement.schema.json +318 -0
- package/shared/schemas/outputs/objective-stack-status.schema.json +6 -1
- package/shared/schemas/outputs/pr-review-context.schema.json +54 -9
- package/shared/schemas/outputs/pr-review-stack-context.schema.json +196 -0
- package/extension/adapters/planAdapterPlannotator.ts +0 -362
- package/extension/doors/auditWaveTools.ts +0 -352
- package/extension/doors/ciExecutor.ts +0 -756
- package/extension/doors/commitCompact.ts +0 -251
- package/extension/doors/dreamWaveTools.ts +0 -489
- package/extension/doors/learn.ts +0 -668
- package/extension/doors/lifecycleGates.ts +0 -207
- package/extension/doors/objectiveStack.ts +0 -1543
- package/extension/doors/prReviewDynamic.ts +0 -276
- package/extension/doors/ready.ts +0 -279
- package/extension/doors/submit.ts +0 -373
- package/extension/doors/submitPrReview.ts +0 -505
- package/extension/factories/gistAuthor.ts +0 -94
- package/extension/factories/gistDraft.ts +0 -265
- package/extension/factories/gistSave.ts +0 -251
- package/extension/factories/implementHere.ts +0 -116
- package/extension/factories/objectiveAuthor.ts +0 -98
- package/extension/factories/objectiveDraft.ts +0 -466
- package/extension/factories/objectiveSave.ts +0 -366
- package/extension/factories/planDraft.ts +0 -140
- package/extension/factories/planMode.ts +0 -205
- package/extension/factories/planReview.ts +0 -1237
- package/extension/factories/planSave.ts +0 -604
- package/extension/factories/planTitle.ts +0 -141
- package/extension/substrate/structuredOutput.ts +0 -202
- package/extension/waves/auditWave.ts +0 -312
- package/extension/waves/harvestWave.ts +0 -399
- package/extension/waves/learnWave.ts +0 -155
- package/extension/waves/memoryAdapter.ts +0 -139
- package/extension/waves/prReviewDynamicWave.ts +0 -777
- package/extension/worker/readOnlySession.ts +0 -294
- package/extension/worker/worker.ts +0 -899
- package/prompts/stages/pr-review-dynamic.md +0 -7
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
// Objective-authoring context injection (the objective mirror of planMode's plan-authoring
|
|
2
|
-
// half). A `perk objective author` cold launch opens a READ-ONLY session whose handoff `stage` is
|
|
3
|
-
// `objective-author`; this module injects the objective-authoring contract under its own
|
|
4
|
-
// `perk:objective-author-context` customType (once-only: branch-scan dedup'd on the marker),
|
|
5
|
-
// keyed off (read-only gate AND stage === objective-author), optionally extended by the same `[workflow] plan_authoring` addendum the
|
|
6
|
-
// plan-authoring injection consumes (verbatim reuse, read per-event via loadPerkConfig). planMode.ts defers when the stage is objective-author, so exactly one
|
|
7
|
-
// authoring context is injected — the coupling break: plan-authoring is no longer keyed off
|
|
8
|
-
// the bare read-only gate.
|
|
9
|
-
//
|
|
10
|
-
// The `objective_save` warm door (the tool + `/objective-save` command) lives in objectiveSave.ts,
|
|
11
|
-
// the mirror of planSave.ts.
|
|
12
|
-
|
|
13
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
14
|
-
import { loadPerkConfig } from "../substrate/config.ts";
|
|
15
|
-
import { render } from "../substrate/prompts.ts";
|
|
16
|
-
import type { ToolGating } from "../substrate/toolGating.ts";
|
|
17
|
-
import {
|
|
18
|
-
type BranchEntry,
|
|
19
|
-
branchCarries,
|
|
20
|
-
branchOf,
|
|
21
|
-
rebuildWorkflowState,
|
|
22
|
-
} from "../substrate/workflowState.ts";
|
|
23
|
-
|
|
24
|
-
/** The registry stage id of the objective-authoring session (shared with planMode's defer check). */
|
|
25
|
-
export const OBJECTIVE_AUTHOR_STAGE = "objective-author";
|
|
26
|
-
|
|
27
|
-
/** The objective-authoring context customType (distinct from planMode's `perk:plan-context`). */
|
|
28
|
-
export const OBJECTIVE_AUTHOR_CONTEXT_TYPE = "perk:objective-author-context";
|
|
29
|
-
const OBJECTIVE_AUTHOR_MARKER = "[OBJECTIVE AUTHORING]";
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* The objective-authoring session context: live state + pointers only (contracts.md §8.57 — the
|
|
33
|
-
* flow is stated by the launch statement, the detail by the `perk-objective-author` skill). It
|
|
34
|
-
* names the working-draft artifact (`objective_draft`), the review tool (`plan_review`), and
|
|
35
|
-
* the bound skill; it never restates the flow. Prompting, NOT enforcement (the tool gate is the
|
|
36
|
-
* enforcement).
|
|
37
|
-
*/
|
|
38
|
-
export const OBJECTIVE_AUTHORING_CONTEXT = render("contexts/objective-authoring.md", {
|
|
39
|
-
marker: OBJECTIVE_AUTHOR_MARKER,
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
/** Build the full objective-authoring injection, appending the project config addendum when present. */
|
|
43
|
-
export function objectiveAuthoringContextContent(cwd: string): string {
|
|
44
|
-
const addendum = loadPerkConfig(cwd).planAuthoring;
|
|
45
|
-
return addendum
|
|
46
|
-
? `${OBJECTIVE_AUTHORING_CONTEXT}\n\n${addendum.trim()}`
|
|
47
|
-
: OBJECTIVE_AUTHORING_CONTEXT;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/** Whether the current branch is an objective-author session (read-only gate AND stage match). */
|
|
51
|
-
function isObjectiveAuthoring(gating: ToolGating, branch: readonly BranchEntry[]): boolean {
|
|
52
|
-
return gating.isActive() && rebuildWorkflowState(branch).stage === OBJECTIVE_AUTHOR_STAGE;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Register the objective-authoring context injection (display:false), the mirror of planMode's
|
|
57
|
-
* injection half. Inert outside an objective-author session; never throws.
|
|
58
|
-
*/
|
|
59
|
-
export function registerObjectiveAuthor(pi: ExtensionAPI, gating: ToolGating): void {
|
|
60
|
-
pi.on("before_agent_start", async (_event, ctx) => {
|
|
61
|
-
const branch = branchOf(ctx);
|
|
62
|
-
if (!isObjectiveAuthoring(gating, branch)) return;
|
|
63
|
-
// Once-only: injected customs persist to the branch, so a live copy suppresses re-injection;
|
|
64
|
-
// compaction dropping it makes the scan come up clean and the next turn re-injects.
|
|
65
|
-
if (branchCarries(branch, OBJECTIVE_AUTHOR_MARKER)) return;
|
|
66
|
-
return {
|
|
67
|
-
message: {
|
|
68
|
-
customType: OBJECTIVE_AUTHOR_CONTEXT_TYPE,
|
|
69
|
-
content: objectiveAuthoringContextContent(ctx.cwd),
|
|
70
|
-
display: false,
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// Strip the stale objective-authoring marker from context once the session is no longer authoring
|
|
76
|
-
// (gate off, or the stage moved on) so it never lingers — the same hygiene planMode applies.
|
|
77
|
-
pi.on("context", async (event, ctx) => {
|
|
78
|
-
const branch = branchOf(ctx);
|
|
79
|
-
if (isObjectiveAuthoring(gating, branch)) return;
|
|
80
|
-
return {
|
|
81
|
-
messages: event.messages.filter((m) => {
|
|
82
|
-
const msg = m as { customType?: string; role?: string; content?: unknown };
|
|
83
|
-
if (msg.customType === OBJECTIVE_AUTHOR_CONTEXT_TYPE) return false;
|
|
84
|
-
if (msg.role !== "user") return true;
|
|
85
|
-
const content = msg.content;
|
|
86
|
-
if (typeof content === "string") return !content.includes(OBJECTIVE_AUTHOR_MARKER);
|
|
87
|
-
if (Array.isArray(content)) {
|
|
88
|
-
return !content.some(
|
|
89
|
-
(c) =>
|
|
90
|
-
(c as { type?: string; text?: string }).type === "text" &&
|
|
91
|
-
((c as { text?: string }).text ?? "").includes(OBJECTIVE_AUTHOR_MARKER),
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
return true;
|
|
95
|
-
}),
|
|
96
|
-
};
|
|
97
|
-
});
|
|
98
|
-
}
|
|
@@ -1,466 +0,0 @@
|
|
|
1
|
-
// The `objective_draft` file tool: the objective-flavored twin of the
|
|
2
|
-
// `plan_draft` carve-out (planDraft.ts).
|
|
3
|
-
//
|
|
4
|
-
// Carve-out doctrine: the tool takes NO path/name parameter — the artifact name is the fixed
|
|
5
|
-
// constant `OBJECTIVE_DRAFT_ARTIFACT` and the path is derived exclusively through the session-data
|
|
6
|
-
// accessor seam (`writeSessionArtifact`, sessionData.ts), so the only bytes it can ever write are
|
|
7
|
-
// the one working-objective artifact in the current run's data dir (gitignored scratch).
|
|
8
|
-
// Allowlisting its name in `READ_ONLY_TOOLS` (toolGating.ts) is therefore safe: the read-only
|
|
9
|
-
// invariant (the worktree stays untouched) holds, and the gate's `tool_call` edit/write/bash
|
|
10
|
-
// blocking logic is UNCHANGED. Full rewrite per call, non-terminating; NOT a save —
|
|
11
|
-
// `objective_save`/`/objective-save` still persist the objective to GitHub.
|
|
12
|
-
//
|
|
13
|
-
// Format doctrine: JSON is the storage/transport format, NEVER the human review surface. The
|
|
14
|
-
// artifact carries `{schema_version, title?, prose, roadmap}` (plus, in a perk learn dream
|
|
15
|
-
// session, the tool-written `dream_report` block — contracts §8.63) — the structured roadmap
|
|
16
|
-
// rides verbatim (node-shape validation stays with the Python plane at save time, the
|
|
17
|
-
// `parse_structured_roadmap` path). The review surface reads the draft via
|
|
18
|
-
// `readObjectiveDraft` (over `readSessionArtifact` — digest-validated, fail-open) and renders
|
|
19
|
-
// markdown via `renderObjectiveDraft` (the prose + a roadmap table) — never raw JSON; the
|
|
20
|
-
// approval→`objective_save` orchestration feeds the recovered roadmap
|
|
21
|
-
// back as structured JSON.
|
|
22
|
-
//
|
|
23
|
-
// Vocabulary ownership: this module owns the shared draft/save param vocabulary
|
|
24
|
-
// (`ObjectiveSaveParams`, `decodeObjectiveSaveParams`, `ROADMAP_PARAM_SCHEMA`,
|
|
25
|
-
// `DREAM_REPORT_PARAM_SCHEMA`) — objectiveDraft is the LEAF (mirroring planDraft←planSave's
|
|
26
|
-
// direction); objectiveSave.ts consumes it, so it may value-import `readObjectiveDraft`
|
|
27
|
-
// cycle-free for the approval→save orchestration.
|
|
28
|
-
//
|
|
29
|
-
// Imports stay node builtins + sibling seams (sessionData.ts, result.ts) so the module loads
|
|
30
|
-
// under `node --test`; no manual `scratch`/`runs` path segments (cacheGuard.test.ts).
|
|
31
|
-
|
|
32
|
-
import { relative } from "node:path";
|
|
33
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
34
|
-
import { failFor, ok, type Result } from "../substrate/result.ts";
|
|
35
|
-
import {
|
|
36
|
-
activeSessionRunId,
|
|
37
|
-
digestSessionData,
|
|
38
|
-
readSessionArtifact,
|
|
39
|
-
type SessionDataCtx,
|
|
40
|
-
writeSessionArtifact,
|
|
41
|
-
} from "../substrate/sessionData.ts";
|
|
42
|
-
import { arrayParam, objectParam, paramsOf, stringParam } from "../substrate/toolParams.ts";
|
|
43
|
-
import type { EntrySink } from "../substrate/workflowState.ts";
|
|
44
|
-
import type { ReportTarget } from "../surfaces/report.ts";
|
|
45
|
-
import { DREAM_REPORT_INPUT_SCHEMA } from "../waves/dreamReport.ts";
|
|
46
|
-
import {
|
|
47
|
-
decodeDreamReportBlock,
|
|
48
|
-
type ObjectiveDreamReportBlock,
|
|
49
|
-
resolveDreamReportGate,
|
|
50
|
-
} from "./objectiveDreamReport.ts";
|
|
51
|
-
|
|
52
|
-
/** The reviewed objective delivery choice (contracts §8.45). */
|
|
53
|
-
export type DeliveryChoice = "incremental" | "stacked";
|
|
54
|
-
|
|
55
|
-
/** The decoded `objective_save` tool params (shared with `objective_draft`). */
|
|
56
|
-
export interface ObjectiveSaveParams {
|
|
57
|
-
prose: string;
|
|
58
|
-
title?: string;
|
|
59
|
-
roadmap?: unknown[];
|
|
60
|
-
// The objective's target branch; omitted to use the repo default.
|
|
61
|
-
base?: string;
|
|
62
|
-
// The reviewed delivery choice; omitted ⇒ incremental (the §8.42 absence rule).
|
|
63
|
-
delivery?: DeliveryChoice;
|
|
64
|
-
// The dream-report input (perk learn dream only — §8.63); deep validation is the gate
|
|
65
|
-
// resolver's, so the decode keeps it opaque beyond the plain-object shape.
|
|
66
|
-
dream_report?: unknown;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* The `delivery` enum property, shared between `objective_save` and `objective_draft` so the
|
|
71
|
-
* two tools' delivery contracts cannot drift. The description bakes in the explicit-human-choice
|
|
72
|
-
* discipline: the agent must ASK, with incremental recommended.
|
|
73
|
-
*/
|
|
74
|
-
export const DELIVERY_PARAM_SCHEMA = {
|
|
75
|
-
type: "string",
|
|
76
|
-
enum: ["incremental", "stacked"],
|
|
77
|
-
description:
|
|
78
|
-
"The reviewed delivery choice — ask the human explicitly (incremental is the recommended " +
|
|
79
|
-
"default: each plan lands independently; stacked lands ALL non-skipped roadmap nodes as " +
|
|
80
|
-
"one atomic PR train — capability-checked at save).",
|
|
81
|
-
} as const;
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* The `dream_report` property, shared between `objective_save` and `objective_draft` so the
|
|
85
|
-
* two tools' dream contracts cannot drift: the §8.62 `DREAM_REPORT_INPUT_SCHEMA` embedded by
|
|
86
|
-
* identifier (the `DELIVERY_PARAM_SCHEMA`/`ROADMAP_PARAM_SCHEMA` shared-schema pattern) plus
|
|
87
|
-
* the gate description. Structurally reachable only inside a `perk learn dream` session (the
|
|
88
|
-
* resolver refuses it outside one).
|
|
89
|
-
*/
|
|
90
|
-
export const DREAM_REPORT_PARAM_SCHEMA = {
|
|
91
|
-
...DREAM_REPORT_INPUT_SCHEMA,
|
|
92
|
-
description:
|
|
93
|
-
"The perk learn dream session's final report input (the parent's decisions only) — " +
|
|
94
|
-
"required inside a dream session, refused outside one.",
|
|
95
|
-
} as const;
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* The roadmap-node items JSON schema, shared between `objective_save` and `objective_draft`
|
|
99
|
-
* so the two tools' roadmap contracts cannot drift.
|
|
100
|
-
*/
|
|
101
|
-
export const ROADMAP_PARAM_SCHEMA = {
|
|
102
|
-
type: "object",
|
|
103
|
-
additionalProperties: false,
|
|
104
|
-
required: ["id", "description"],
|
|
105
|
-
properties: {
|
|
106
|
-
id: { type: "string", description: 'A stable node id, e.g. "1.1".' },
|
|
107
|
-
description: { type: "string", description: "What this node delivers." },
|
|
108
|
-
status: {
|
|
109
|
-
type: "string",
|
|
110
|
-
enum: ["pending", "planning", "in_progress", "done", "blocked", "skipped"],
|
|
111
|
-
description: "Optional initial status (defaults to pending).",
|
|
112
|
-
},
|
|
113
|
-
slug: { type: "string", description: "Optional short slug." },
|
|
114
|
-
pr: { type: "string", description: 'Optional plan/PR backlink, e.g. "#42".' },
|
|
115
|
-
depends_on: {
|
|
116
|
-
type: "array",
|
|
117
|
-
items: { type: "string" },
|
|
118
|
-
description: "Optional explicit dependency node ids.",
|
|
119
|
-
},
|
|
120
|
-
comment: { type: "string", description: "Optional note." },
|
|
121
|
-
adopt_issue: {
|
|
122
|
-
type: "string",
|
|
123
|
-
description:
|
|
124
|
-
"Optional: the id/identifier of a pre-existing source issue this node adopts in place " +
|
|
125
|
-
"(objective author --from, Linear only).",
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
} as const;
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Decode unknown `objective_save` tool-call params (the tool-boundary seam). `prose`
|
|
132
|
-
* absent decodes to `""` (so `saveObjective`'s "no objective prose to save" `invalid_input` arm
|
|
133
|
-
* keeps owning that message) but present-but-mistyped → null (strict-fail). `roadmap` stays
|
|
134
|
-
* `unknown[]` — the Python cold door owns node-shape validation.
|
|
135
|
-
*/
|
|
136
|
-
export function decodeObjectiveSaveParams(params: unknown): ObjectiveSaveParams | null {
|
|
137
|
-
const p = paramsOf(params);
|
|
138
|
-
if (p === null) return null;
|
|
139
|
-
const prose = stringParam(p, "prose");
|
|
140
|
-
const title = stringParam(p, "title");
|
|
141
|
-
const roadmap = arrayParam(p, "roadmap");
|
|
142
|
-
const base = stringParam(p, "base");
|
|
143
|
-
const delivery = stringParam(p, "delivery");
|
|
144
|
-
// `dream_report` must be a plain object when present (absent → undefined); deep validation
|
|
145
|
-
// stays with the gate resolver (resolveDreamReportGate).
|
|
146
|
-
const dreamReport = objectParam(p, "dream_report");
|
|
147
|
-
if (
|
|
148
|
-
prose === null ||
|
|
149
|
-
title === null ||
|
|
150
|
-
roadmap === null ||
|
|
151
|
-
base === null ||
|
|
152
|
-
delivery === null ||
|
|
153
|
-
dreamReport === null
|
|
154
|
-
) {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
// The delivery enum is strict beyond `string`: an off-enum value is present-but-mistyped.
|
|
158
|
-
if (delivery !== undefined && delivery !== "incremental" && delivery !== "stacked") return null;
|
|
159
|
-
return { prose: prose ?? "", title, roadmap, base, delivery, dream_report: dreamReport };
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/** The fixed working-objective artifact name (one JSON file: prose + the structured roadmap). */
|
|
163
|
-
export const OBJECTIVE_DRAFT_ARTIFACT = "objective-draft.json";
|
|
164
|
-
|
|
165
|
-
/** The ok-arm details — provenance-consistent with the recorded `session_artifacts` pointer. */
|
|
166
|
-
export interface ObjectiveDraftOk {
|
|
167
|
-
name: string;
|
|
168
|
-
path: string;
|
|
169
|
-
digest: string;
|
|
170
|
-
bytes: number;
|
|
171
|
-
run_id: string;
|
|
172
|
-
roadmap_nodes: number;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
export type ObjectiveDraftResult = Result<ObjectiveDraftOk>;
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* The core both the tool handler and tests call: serialize the working objective (prose + the
|
|
179
|
-
* structured roadmap, verbatim — the draft never validates node shapes) as one JSON artifact and
|
|
180
|
-
* write it through the accessor seam (file + `session_artifacts` provenance pointer). Soft
|
|
181
|
-
* result, never throws — failure taxonomy: empty prose → `invalid_input`; no session run_id →
|
|
182
|
-
* `no_run_id`; a `dream_report` gate refusal → `invalid_input`/`bad_state` (the §8.63 matrix —
|
|
183
|
-
* validated at write time so a report-less dream bundle can never reach review);
|
|
184
|
-
* file-or-pointer write failure → `write_failed` (the seam already warned).
|
|
185
|
-
*/
|
|
186
|
-
export function writeObjectiveDraft(
|
|
187
|
-
sink: EntrySink,
|
|
188
|
-
ctx: SessionDataCtx & ReportTarget,
|
|
189
|
-
opts: {
|
|
190
|
-
prose: string;
|
|
191
|
-
title?: string;
|
|
192
|
-
roadmap?: unknown[];
|
|
193
|
-
base?: string;
|
|
194
|
-
delivery?: DeliveryChoice;
|
|
195
|
-
dream_report?: unknown;
|
|
196
|
-
},
|
|
197
|
-
): ObjectiveDraftResult {
|
|
198
|
-
const fail = failFor(ctx, "objective-draft");
|
|
199
|
-
|
|
200
|
-
if (!opts.prose.trim()) {
|
|
201
|
-
return fail("no objective prose to write (pass the full working draft)", "invalid_input");
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
const runId = activeSessionRunId(ctx);
|
|
205
|
-
if (runId === null) {
|
|
206
|
-
return fail("session has no run_id — cannot write the objective-draft artifact", "no_run_id");
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// The §8.63 gate: validated at draft-write time via buildDreamReport, the ONE stamp stored
|
|
210
|
-
// with the block; `absent` keeps the payload byte-identical (every non-dream path unchanged).
|
|
211
|
-
const gate = resolveDreamReportGate(ctx, opts.dream_report, new Date().toISOString());
|
|
212
|
-
if (gate.kind === "refuse") {
|
|
213
|
-
return fail(gate.detail, gate.errorType);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Deterministic key order via the explicit literal; `title`/`base`/`delivery` are omitted
|
|
217
|
-
// when blank/absent (schema_version stays 1 — an additive optional field, fail-open readers).
|
|
218
|
-
const title = opts.title?.trim();
|
|
219
|
-
const base = opts.base?.trim();
|
|
220
|
-
const delivery = opts.delivery;
|
|
221
|
-
const roadmap = opts.roadmap ?? [];
|
|
222
|
-
const payload = {
|
|
223
|
-
schema_version: 1,
|
|
224
|
-
...(title ? { title } : {}),
|
|
225
|
-
...(base ? { base } : {}),
|
|
226
|
-
...(delivery ? { delivery } : {}),
|
|
227
|
-
...(gate.kind === "block" ? { dream_report: gate.block } : {}),
|
|
228
|
-
prose: opts.prose,
|
|
229
|
-
roadmap,
|
|
230
|
-
};
|
|
231
|
-
const content = `${JSON.stringify(payload, null, 2)}\n`;
|
|
232
|
-
|
|
233
|
-
const written = writeSessionArtifact(sink, ctx, OBJECTIVE_DRAFT_ARTIFACT, content);
|
|
234
|
-
if (written === null) {
|
|
235
|
-
return fail(
|
|
236
|
-
`could not write the ${OBJECTIVE_DRAFT_ARTIFACT} artifact (see warnings)`,
|
|
237
|
-
"write_failed",
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Derive digest/relative path consistently with the pointer the seam recorded.
|
|
242
|
-
const digest = digestSessionData(content);
|
|
243
|
-
const relPath = relative(ctx.cwd, written);
|
|
244
|
-
return ok(`Objective draft written → ${relPath} (${digest}; ${roadmap.length} roadmap nodes)`, {
|
|
245
|
-
name: OBJECTIVE_DRAFT_ARTIFACT,
|
|
246
|
-
path: relPath,
|
|
247
|
-
digest,
|
|
248
|
-
bytes: Buffer.byteLength(content, "utf8"),
|
|
249
|
-
run_id: runId,
|
|
250
|
-
roadmap_nodes: roadmap.length,
|
|
251
|
-
});
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// ------------------------------------------------------------------- the reader + the renderer
|
|
255
|
-
|
|
256
|
-
/** The validated working-objective draft shape consumers receive from `readObjectiveDraft`. */
|
|
257
|
-
export interface ObjectiveDraft {
|
|
258
|
-
title?: string;
|
|
259
|
-
prose: string;
|
|
260
|
-
roadmap: unknown[];
|
|
261
|
-
// The objective's target branch; kept only when a non-blank string in the artifact.
|
|
262
|
-
base?: string;
|
|
263
|
-
// The reviewed delivery choice; kept only when exactly the enum (junk → absent, like `base`).
|
|
264
|
-
delivery?: DeliveryChoice;
|
|
265
|
-
// The dream-report block (§8.63); a present-but-malformed block refuses the WHOLE draft.
|
|
266
|
-
dream_report?: ObjectiveDreamReportBlock;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* Read + validate the working-objective draft artifact. Fail-open `null` everywhere (mirroring
|
|
271
|
-
* `readSessionArtifact`'s loud tier): no pointer/file/digest → `null` (the seam already spoke);
|
|
272
|
-
* malformed JSON, a non-object payload, an unsupported `schema_version`, or blank prose → a
|
|
273
|
-
* stderr warning + `null`. `roadmap` defaults to `[]` when absent/non-array; `title` is kept
|
|
274
|
-
* only when a non-blank string. A present-but-malformed `dream_report` block refuses the WHOLE
|
|
275
|
-
* draft (warn + `null`) — deliberately stricter than the lenient junk→absent handling of
|
|
276
|
-
* `base`/`delivery`, because silently dropping a malformed report is exactly what §8.63
|
|
277
|
-
* forbids. Never throws.
|
|
278
|
-
*/
|
|
279
|
-
export function readObjectiveDraft(ctx: SessionDataCtx): ObjectiveDraft | null {
|
|
280
|
-
const artifact = readSessionArtifact(ctx, OBJECTIVE_DRAFT_ARTIFACT);
|
|
281
|
-
if (artifact === null) return null;
|
|
282
|
-
|
|
283
|
-
const refuse = (why: string): null => {
|
|
284
|
-
console.error(`perk: warning: ${OBJECTIVE_DRAFT_ARTIFACT} ${why} — refusing the draft`);
|
|
285
|
-
return null;
|
|
286
|
-
};
|
|
287
|
-
let parsed: unknown;
|
|
288
|
-
try {
|
|
289
|
-
parsed = JSON.parse(artifact.content);
|
|
290
|
-
} catch {
|
|
291
|
-
return refuse("is not valid JSON");
|
|
292
|
-
}
|
|
293
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
294
|
-
return refuse("is not a JSON object");
|
|
295
|
-
}
|
|
296
|
-
const payload = parsed as Record<string, unknown>;
|
|
297
|
-
if (payload.schema_version !== 1) {
|
|
298
|
-
return refuse(`has an unsupported schema_version (${JSON.stringify(payload.schema_version)})`);
|
|
299
|
-
}
|
|
300
|
-
const prose = payload.prose;
|
|
301
|
-
if (typeof prose !== "string" || !prose.trim()) {
|
|
302
|
-
return refuse("has no prose");
|
|
303
|
-
}
|
|
304
|
-
const roadmap = Array.isArray(payload.roadmap) ? payload.roadmap : [];
|
|
305
|
-
const title =
|
|
306
|
-
typeof payload.title === "string" && payload.title.trim() ? payload.title : undefined;
|
|
307
|
-
const base = typeof payload.base === "string" && payload.base.trim() ? payload.base : undefined;
|
|
308
|
-
const delivery =
|
|
309
|
-
payload.delivery === "incremental" || payload.delivery === "stacked"
|
|
310
|
-
? payload.delivery
|
|
311
|
-
: undefined;
|
|
312
|
-
let dreamReport: ObjectiveDreamReportBlock | undefined;
|
|
313
|
-
if ("dream_report" in payload) {
|
|
314
|
-
const block = decodeDreamReportBlock(payload.dream_report);
|
|
315
|
-
if (block === null) {
|
|
316
|
-
return refuse("carries a malformed dream_report block");
|
|
317
|
-
}
|
|
318
|
-
dreamReport = block;
|
|
319
|
-
}
|
|
320
|
-
return {
|
|
321
|
-
...(title !== undefined ? { title } : {}),
|
|
322
|
-
...(base !== undefined ? { base } : {}),
|
|
323
|
-
...(delivery !== undefined ? { delivery } : {}),
|
|
324
|
-
...(dreamReport !== undefined ? { dream_report: dreamReport } : {}),
|
|
325
|
-
prose,
|
|
326
|
-
roadmap,
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
/** Sanitize a table cell: `|` escaped, newlines collapsed to a single space. */
|
|
331
|
-
function tableCell(value: string): string {
|
|
332
|
-
return value.replace(/\r?\n/g, " ").replace(/\|/g, "\\|");
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/** Read a string field off an unknown-shaped roadmap node (`""` when absent/mistyped). */
|
|
336
|
-
function nodeString(node: unknown, key: string): string {
|
|
337
|
-
if (typeof node !== "object" || node === null) return "";
|
|
338
|
-
const value = (node as Record<string, unknown>)[key];
|
|
339
|
-
return typeof value === "string" ? value : "";
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/** Render a node's `depends_on` as a `", "`-join of its string members; `-` when empty/absent. */
|
|
343
|
-
function nodeDependsOn(node: unknown): string {
|
|
344
|
-
if (typeof node !== "object" || node === null) return "-";
|
|
345
|
-
const value = (node as Record<string, unknown>).depends_on;
|
|
346
|
-
if (!Array.isArray(value)) return "-";
|
|
347
|
-
const deps = value.filter((d): d is string => typeof d === "string");
|
|
348
|
-
return deps.length > 0 ? deps.join(", ") : "-";
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Render the draft as the markdown review surface (JSON is storage/transport only — contracts
|
|
353
|
-
* §8.1): the optional `# title` heading, the prose verbatim, and (when the roadmap is non-empty)
|
|
354
|
-
* a `## Roadmap` section with ONE markdown table. A prominent `**Delivery:**` line renders
|
|
355
|
-
* directly under the title unconditionally (the reviewed choice must be visible either way —
|
|
356
|
-
* contracts §8.45). The `Phase` column appears only when some node carries a non-blank string
|
|
357
|
-
* `phase`. When the draft carries a `dream_report` block, the stored CANONICAL parts append as
|
|
358
|
-
* the final section — the review surface IS the approval bundle: the objective and its report
|
|
359
|
-
* review (and are approved or denied) together (§8.63); the parts carry their own
|
|
360
|
-
* `# Dream report — <run_id>` headers. Pure; never throws.
|
|
361
|
-
*/
|
|
362
|
-
/** The always-present prominent `**Delivery:**` review line (contracts §8.45). */
|
|
363
|
-
function deliveryLine(draft: ObjectiveDraft): string {
|
|
364
|
-
if (draft.delivery === "stacked") {
|
|
365
|
-
return (
|
|
366
|
-
"**Delivery: STACKED** — all non-skipped roadmap nodes land as ONE atomic pull-request " +
|
|
367
|
-
"train (capability-checked at save)"
|
|
368
|
-
);
|
|
369
|
-
}
|
|
370
|
-
return "**Delivery: incremental** (the default — each plan lands independently)";
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
export function renderObjectiveDraft(draft: ObjectiveDraft): string {
|
|
374
|
-
let out = "";
|
|
375
|
-
if (draft.title) out += `# ${draft.title}\n\n`;
|
|
376
|
-
out += `${deliveryLine(draft)}\n\n`;
|
|
377
|
-
out += draft.prose;
|
|
378
|
-
|
|
379
|
-
if (draft.roadmap.length > 0) {
|
|
380
|
-
const withPhase = draft.roadmap.some((node) => nodeString(node, "phase").trim().length > 0);
|
|
381
|
-
const header = withPhase
|
|
382
|
-
? "| Node | Phase | Description | Depends On | Status |\n| --- | --- | --- | --- | --- |"
|
|
383
|
-
: "| Node | Description | Depends On | Status |\n| --- | --- | --- | --- |";
|
|
384
|
-
const rows = draft.roadmap.map((node) => {
|
|
385
|
-
const cells = [
|
|
386
|
-
tableCell(nodeString(node, "id")),
|
|
387
|
-
...(withPhase ? [tableCell(nodeString(node, "phase"))] : []),
|
|
388
|
-
tableCell(nodeString(node, "description")),
|
|
389
|
-
tableCell(nodeDependsOn(node)),
|
|
390
|
-
tableCell(nodeString(node, "status") || "pending"),
|
|
391
|
-
];
|
|
392
|
-
return `| ${cells.join(" | ")} |`;
|
|
393
|
-
});
|
|
394
|
-
out = `${out.trimEnd()}\n\n## Roadmap\n\n${header}\n${rows.join("\n")}\n`;
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (draft.dream_report === undefined) return out;
|
|
398
|
-
// The approval bundle: objective first, then the stored CANONICAL report parts.
|
|
399
|
-
return `${out.trimEnd()}\n\n${draft.dream_report.parts.join("\n\n")}\n`;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
const TOOL_GUIDELINES = [
|
|
403
|
-
"Call objective_draft to persist the current working objective as you author or revise it; pass the FULL prose and the FULL structured roadmap each time (it rewrites the whole draft).",
|
|
404
|
-
"objective_draft never saves to GitHub and never ends the turn — objective_save//objective-save remain the canonical save surface. Never hand-write roadmap YAML — hand the structured roadmap to the tool.",
|
|
405
|
-
"Pass objective_draft's `base` only to target a non-default branch; omit it to use the repo default.",
|
|
406
|
-
];
|
|
407
|
-
|
|
408
|
-
/** Register the `objective_draft` tool (the carve-out producer; interior-only). */
|
|
409
|
-
export function registerObjectiveDraft(pi: ExtensionAPI): void {
|
|
410
|
-
pi.registerTool({
|
|
411
|
-
name: "objective_draft",
|
|
412
|
-
label: "Objective draft",
|
|
413
|
-
description:
|
|
414
|
-
"Write (or overwrite) the working objective draft — prose + the structured roadmap — to " +
|
|
415
|
-
"the session data dir and record its provenance pointer. The only sanctioned write surface " +
|
|
416
|
-
"while read-only. NOT a save — objective_save//objective-save still persist the objective " +
|
|
417
|
-
"to GitHub.",
|
|
418
|
-
promptSnippet:
|
|
419
|
-
"Persist the working objective draft (prose + structured roadmap) to the session data dir (full rewrite)",
|
|
420
|
-
promptGuidelines: TOOL_GUIDELINES,
|
|
421
|
-
executionMode: "sequential",
|
|
422
|
-
parameters: {
|
|
423
|
-
type: "object",
|
|
424
|
-
additionalProperties: false,
|
|
425
|
-
required: ["prose"],
|
|
426
|
-
properties: {
|
|
427
|
-
prose: {
|
|
428
|
-
type: "string",
|
|
429
|
-
description: "The objective prose (the why, the design, the boundaries/non-goals).",
|
|
430
|
-
},
|
|
431
|
-
title: {
|
|
432
|
-
type: "string",
|
|
433
|
-
description: "Optional objective title (defaults to the prose's first heading).",
|
|
434
|
-
},
|
|
435
|
-
base: {
|
|
436
|
-
type: "string",
|
|
437
|
-
description:
|
|
438
|
-
"Optional target branch for this objective's plans (omit to use the repo default).",
|
|
439
|
-
},
|
|
440
|
-
delivery: DELIVERY_PARAM_SCHEMA,
|
|
441
|
-
dream_report: DREAM_REPORT_PARAM_SCHEMA,
|
|
442
|
-
roadmap: {
|
|
443
|
-
type: "array",
|
|
444
|
-
description:
|
|
445
|
-
"The structured roadmap: a JSON array of nodes. Never hand-write roadmap YAML.",
|
|
446
|
-
items: ROADMAP_PARAM_SCHEMA,
|
|
447
|
-
},
|
|
448
|
-
},
|
|
449
|
-
},
|
|
450
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
451
|
-
// The shared param contract: the same decode as `objective_save`, so the two cannot drift.
|
|
452
|
-
const decoded = decodeObjectiveSaveParams(params);
|
|
453
|
-
if (decoded === null) {
|
|
454
|
-
return failFor(
|
|
455
|
-
ctx,
|
|
456
|
-
"objective-draft",
|
|
457
|
-
"objective_draft",
|
|
458
|
-
)(
|
|
459
|
-
"objective_draft needs { prose: string, roadmap?: array } per the tool schema",
|
|
460
|
-
"bad_input",
|
|
461
|
-
);
|
|
462
|
-
}
|
|
463
|
-
return writeObjectiveDraft(pi, ctx, decoded);
|
|
464
|
-
},
|
|
465
|
-
});
|
|
466
|
-
}
|