@agent-plan/core 0.3.0 → 0.3.1
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/dist/guard-decision.d.ts +53 -43
- package/dist/guard-decision.d.ts.map +1 -1
- package/dist/guard-decision.js +134 -68
- package/dist/guard-state.d.ts +14 -0
- package/dist/guard-state.d.ts.map +1 -0
- package/dist/guard-state.js +35 -0
- package/dist/handoff-reply.d.ts +5 -0
- package/dist/handoff-reply.d.ts.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/load-reply.d.ts +63 -0
- package/dist/load-reply.d.ts.map +1 -0
- package/dist/load-reply.js +64 -0
- package/dist/on-hold.d.ts +55 -0
- package/dist/on-hold.d.ts.map +1 -0
- package/dist/on-hold.js +123 -0
- package/dist/package-version.d.ts +13 -0
- package/dist/package-version.d.ts.map +1 -1
- package/dist/package-version.js +32 -0
- package/dist/project-context.d.ts +6 -4
- package/dist/project-context.d.ts.map +1 -1
- package/dist/project-context.js +2 -3
- package/dist/recap.d.ts.map +1 -1
- package/dist/recap.js +6 -0
- package/dist/recommend-reply.d.ts.map +1 -1
- package/dist/recommend-reply.js +10 -1
- package/dist/task-context.d.ts.map +1 -1
- package/dist/task-context.js +23 -13
- package/dist/task-selection.d.ts +4 -1
- package/dist/task-selection.d.ts.map +1 -1
- package/dist/task-selection.js +22 -2
- package/package.json +1 -1
package/dist/guard-decision.d.ts
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Harness-agnostic
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Harness-agnostic logic for the "no task in progress" warning. The guard
|
|
3
|
+
* never blocks and never asks: every tool call proceeds, and a call that
|
|
4
|
+
* changes project code while no task is in progress only earns the agent a
|
|
5
|
+
* warning. Planner operations never need a task. Any adapter (Claude Code
|
|
6
|
+
* PreToolUse hook, Pi's tool_call handler, a future harness) feeds it a
|
|
7
|
+
* parsed tool-use event plus already-fetched planner state and gets back the
|
|
8
|
+
* same warning, or none. This never talks to the plan store
|
|
6
9
|
* or stdin/stdout — callers own that I/O. It does read the filesystem in one
|
|
7
10
|
* narrow way (see {@link canonicalize}): resolving symlinks so a path
|
|
8
11
|
* comparison isn't fooled by one, which is cheap (a handful of stat calls,
|
|
9
12
|
* not a plan load) and safe to call on paths that don't exist yet.
|
|
10
13
|
*/
|
|
11
|
-
export type GuardPermissionDecision = "allow" | "ask" | "deny";
|
|
12
14
|
/** The tool-use fields the decision needs, already pulled out of whatever
|
|
13
15
|
* shape the harness's raw event uses (Claude Code's `tool_input.file_path`,
|
|
14
16
|
* Pi's `event.input.path`, etc.). */
|
|
15
17
|
export interface GuardEventInput {
|
|
18
|
+
/** Tool name in any case: Claude Code's `Edit`/`Bash` and Pi's `edit`/`bash` are the same tool. */
|
|
16
19
|
toolName: string;
|
|
17
20
|
/** Absolute (or resolvable) working directory used to resolve relative paths. */
|
|
18
21
|
cwd: string;
|
|
19
22
|
/** Absolute planner root, e.g. `join(cwd, ".planner")`. */
|
|
20
23
|
plannerRoot: string;
|
|
21
|
-
/** Edit / Write target path. */
|
|
24
|
+
/** Edit / Write target path (Pi: `path`). */
|
|
22
25
|
filePath?: string;
|
|
23
26
|
/** NotebookEdit target path. */
|
|
24
27
|
notebookPath?: string;
|
|
@@ -31,36 +34,39 @@ export interface GuardStateInput {
|
|
|
31
34
|
totalTasks: number;
|
|
32
35
|
hasInProgressTask: boolean;
|
|
33
36
|
guardBypassed: boolean;
|
|
34
|
-
/**
|
|
35
|
-
|
|
37
|
+
/** The task the warning suggests starting, when there is an obvious one. */
|
|
38
|
+
focusTask?: {
|
|
39
|
+
id: string;
|
|
40
|
+
title: string;
|
|
41
|
+
};
|
|
36
42
|
}
|
|
37
43
|
export interface GuardClassification {
|
|
38
|
-
/** Whether this tool call
|
|
44
|
+
/** Whether this tool call may write project code, so the guard must look at task state. */
|
|
39
45
|
guarded: boolean;
|
|
40
|
-
/**
|
|
46
|
+
/** The project paths outside `.planner/` the call would write to; empty when the target is unknown. */
|
|
41
47
|
paths: string[];
|
|
42
48
|
}
|
|
43
|
-
export interface
|
|
44
|
-
|
|
45
|
-
|
|
49
|
+
export interface NoTaskWarning {
|
|
50
|
+
/** Text for the agent, present only when it should be warned. */
|
|
51
|
+
warning?: string;
|
|
46
52
|
}
|
|
47
53
|
/**
|
|
48
54
|
* Whether a Bash command line contains a write-shaped operation: output
|
|
49
55
|
* redirection, `tee`, in-place `sed`/`perl`, a file-creating `cp`/`mv`,
|
|
50
56
|
* `rm`, or a `git` operation that rewrites tracked files. Deliberately
|
|
51
57
|
* over-inclusive: `git`, build/test runners, `ls`, `cat`, `grep`, `find`
|
|
52
|
-
* and friends must stay unmatched, but when in doubt this returns true —
|
|
53
|
-
*
|
|
54
|
-
*
|
|
58
|
+
* and friends must stay unmatched, but when in doubt this returns true — a
|
|
59
|
+
* false positive costs one warning to the agent, never a blocked call or a
|
|
60
|
+
* prompt, so under-matching (missing a real edit) is the worse failure.
|
|
55
61
|
*/
|
|
56
62
|
export declare function isWriteShapedBashCommand(command: string): boolean;
|
|
57
63
|
/**
|
|
58
64
|
* Best-effort extraction of the file path(s) a write-shaped Bash segment
|
|
59
|
-
* targets, so a command that only touches `.planner/`
|
|
60
|
-
*
|
|
61
|
-
* computed (a variable, command substitution, etc.) rather than written
|
|
62
|
-
* literally in the command line — that case
|
|
63
|
-
*
|
|
65
|
+
* targets, so a command that only touches `.planner/` or files outside the
|
|
66
|
+
* project earns no warning. This cannot see through a heredoc whose target
|
|
67
|
+
* is computed (a variable, command substitution, etc.) rather than written
|
|
68
|
+
* literally in the command line — that case still reaches the in-progress /
|
|
69
|
+
* bypass checks and can warn, rather than being silently treated as safe.
|
|
64
70
|
*
|
|
65
71
|
* A wider gap of the same kind: a command that writes through an
|
|
66
72
|
* interpreter (`python3 - <<PY` then `open(path, "w")`, `node -e`, a script
|
|
@@ -87,32 +93,36 @@ export declare function isWriteShapedBashCommand(command: string): boolean;
|
|
|
87
93
|
export declare function extractBashWriteTargets(command: string): string[];
|
|
88
94
|
/** Whether `candidate` resolves to a path inside `plannerRoot` (or is it exactly). */
|
|
89
95
|
export declare function isPathInsidePlannerRoot(candidate: string, plannerRoot: string, cwd: string): boolean;
|
|
90
|
-
/**
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
|
|
94
|
-
* without duplicating the rule. */
|
|
95
|
-
export declare function isEntirelyInsidePlannerRoot(paths: string[], plannerRoot: string, cwd: string): boolean;
|
|
96
|
+
/** Whether writing `candidate` changes the project's code: it is inside the
|
|
97
|
+
* project (the directory that owns `.planner/`) and not inside `.planner/`.
|
|
98
|
+
* `/dev/null`, `~/.claude/...`, temp dirs and other repositories are not. */
|
|
99
|
+
export declare function isProjectCodePath(candidate: string, plannerRoot: string, cwd: string): boolean;
|
|
96
100
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
+
* Whether a tool call may change the project's code, and so needs the guard
|
|
102
|
+
* to look at task state. This is the one place that rule lives; the hook's
|
|
103
|
+
* cheap early exit and {@link noTaskWarning} both read `guarded`.
|
|
104
|
+
*
|
|
105
|
+
* - Tools that never write (Read, Grep, MCP tools, read-only Bash, ...) are
|
|
106
|
+
* not guarded.
|
|
107
|
+
* - A write whose targets are all known and none is project code (inside
|
|
108
|
+
* `.planner/`, `/dev/null`, `~/.claude/...`, a temp dir, another repo) is
|
|
109
|
+
* not guarded: updating the planner or the user's own config never needs a
|
|
110
|
+
* task.
|
|
111
|
+
* - A write with at least one project-code target is guarded, with `paths`
|
|
112
|
+
* narrowed to those targets.
|
|
113
|
+
* - A write whose target is unknown (a computed heredoc, an Edit with no
|
|
114
|
+
* path) is guarded with empty `paths` — never silently allowed.
|
|
101
115
|
*/
|
|
102
116
|
export declare function classifyGuardedTool(event: GuardEventInput): GuardClassification;
|
|
103
117
|
/**
|
|
104
|
-
* The
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* calls — the hook harness (or a Pi equivalent) does the reading and
|
|
109
|
-
* printing around this; the only filesystem access is the cheap symlink
|
|
110
|
-
* canonicalization in {@link isPathInsidePlannerRoot}.
|
|
118
|
+
* The warning for a tool call that changes project code while no task is in
|
|
119
|
+
* progress, or none. Never a block or a prompt: the adapter lets the call
|
|
120
|
+
* proceed either way and only passes the text to the agent. Pure apart from
|
|
121
|
+
* the cheap symlink canonicalization in {@link isPathInsidePlannerRoot}.
|
|
111
122
|
*
|
|
112
|
-
* Order
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* real candidate for blocking reaches the state-based checks.
|
|
123
|
+
* Order mirrors the cost of finding out: anything that cannot change project
|
|
124
|
+
* code (see {@link classifyGuardedTool}) is settled from the event alone, so
|
|
125
|
+
* adapters can skip loading the plan for it.
|
|
116
126
|
*/
|
|
117
|
-
export declare function
|
|
127
|
+
export declare function noTaskWarning(event: GuardEventInput, state: GuardStateInput): NoTaskWarning;
|
|
118
128
|
//# sourceMappingURL=guard-decision.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guard-decision.d.ts","sourceRoot":"","sources":["../src/guard-decision.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"guard-decision.d.ts","sourceRoot":"","sources":["../src/guard-decision.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH;;qCAEqC;AACrC,MAAM,WAAW,eAAe;IAC9B,mGAAmG;IACnG,QAAQ,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gCAAgC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kDAAkD;IAClD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,OAAO,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,4EAA4E;IAC5E,SAAS,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3C;AAED,MAAM,WAAW,mBAAmB;IAClC,2FAA2F;IAC3F,OAAO,EAAE,OAAO,CAAC;IACjB,uGAAuG;IACvG,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAiFD;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAGjE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CA2BjE;AAuCD,sFAAsF;AACtF,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpG;AAED;;6EAE6E;AAC7E,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAE9F;AAoBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,eAAe,GAAG,mBAAmB,CAM/E;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,aAAa,CAa3F"}
|
package/dist/guard-decision.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { realpathSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
2
3
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
|
-
const
|
|
4
|
+
const FILE_WRITE_TOOLS = new Set(["edit", "write"]);
|
|
4
5
|
/** Strip fd-duplication idioms (`2>&1`, `>&2`, `1>&2`) before segmenting or
|
|
5
6
|
* matching — they duplicate a stream, not a file, are extremely common in
|
|
6
7
|
* otherwise read-only commands, and their bare `&` would otherwise be
|
|
@@ -8,11 +9,53 @@ const GUARDED_FILE_TOOLS = new Set(["Edit", "Write"]);
|
|
|
8
9
|
function stripFdDuplication(command) {
|
|
9
10
|
return command.replace(/\d*>&\d+/g, " ");
|
|
10
11
|
}
|
|
12
|
+
/** Blank out shell operators (`>`, `<`, `|`, `;`, `&`, newlines) that sit
|
|
13
|
+
* inside single or double quotes. They are literal text there — a commit
|
|
14
|
+
* message's `<name@example.com>`, a `grep "a|b"` pattern — not redirects or
|
|
15
|
+
* separators. Quoted paths keep their characters, so `> "/tmp/x"` still
|
|
16
|
+
* reads as a redirect to `/tmp/x`. */
|
|
17
|
+
function maskQuotedOperators(command) {
|
|
18
|
+
let out = "";
|
|
19
|
+
let quote = null;
|
|
20
|
+
for (let i = 0; i < command.length; i += 1) {
|
|
21
|
+
const ch = command[i];
|
|
22
|
+
if (quote === null) {
|
|
23
|
+
if (ch === "\\") {
|
|
24
|
+
out += ch + (command[i + 1] ?? "");
|
|
25
|
+
i += 1;
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (ch === "'" || ch === '"')
|
|
29
|
+
quote = ch;
|
|
30
|
+
out += ch;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (quote === '"' && ch === "\\") {
|
|
34
|
+
out += "__";
|
|
35
|
+
i += 1;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (ch === quote) {
|
|
39
|
+
quote = null;
|
|
40
|
+
out += ch;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
out += /[<>|;&\n]/.test(ch) ? "_" : ch;
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
/** The command line as write-detection reads it: quoted operators masked,
|
|
48
|
+
* then fd-duplication removed. Both detection and target extraction use
|
|
49
|
+
* this, so they always see the same text. */
|
|
50
|
+
function normalizeBashCommand(command) {
|
|
51
|
+
return stripFdDuplication(maskQuotedOperators(command));
|
|
52
|
+
}
|
|
11
53
|
/** Split a Bash command line into pipe/list segments so write-detection on
|
|
12
54
|
* one segment (e.g. `sed -i` on the left of a pipe) doesn't get confused by
|
|
13
55
|
* an unrelated flag on the other side (e.g. `grep -i` on the right). Best
|
|
14
|
-
* effort only —
|
|
15
|
-
*
|
|
56
|
+
* effort only — nested subshells are not parsed. Callers must pass the
|
|
57
|
+
* command through {@link normalizeBashCommand} first, or a bare `2>&1`
|
|
58
|
+
* splits on its `&` and a quoted `|` splits a string in two. */
|
|
16
59
|
function bashSegments(command) {
|
|
17
60
|
return command
|
|
18
61
|
.split(/(?:&&|\|\||[;&|\n])+/)
|
|
@@ -42,22 +85,22 @@ function isWriteShapedBashSegment(segment) {
|
|
|
42
85
|
* redirection, `tee`, in-place `sed`/`perl`, a file-creating `cp`/`mv`,
|
|
43
86
|
* `rm`, or a `git` operation that rewrites tracked files. Deliberately
|
|
44
87
|
* over-inclusive: `git`, build/test runners, `ls`, `cat`, `grep`, `find`
|
|
45
|
-
* and friends must stay unmatched, but when in doubt this returns true —
|
|
46
|
-
*
|
|
47
|
-
*
|
|
88
|
+
* and friends must stay unmatched, but when in doubt this returns true — a
|
|
89
|
+
* false positive costs one warning to the agent, never a blocked call or a
|
|
90
|
+
* prompt, so under-matching (missing a real edit) is the worse failure.
|
|
48
91
|
*/
|
|
49
92
|
export function isWriteShapedBashCommand(command) {
|
|
50
93
|
if (!command || !command.trim())
|
|
51
94
|
return false;
|
|
52
|
-
return bashSegments(
|
|
95
|
+
return bashSegments(normalizeBashCommand(command)).some(isWriteShapedBashSegment);
|
|
53
96
|
}
|
|
54
97
|
/**
|
|
55
98
|
* Best-effort extraction of the file path(s) a write-shaped Bash segment
|
|
56
|
-
* targets, so a command that only touches `.planner/`
|
|
57
|
-
*
|
|
58
|
-
* computed (a variable, command substitution, etc.) rather than written
|
|
59
|
-
* literally in the command line — that case
|
|
60
|
-
*
|
|
99
|
+
* targets, so a command that only touches `.planner/` or files outside the
|
|
100
|
+
* project earns no warning. This cannot see through a heredoc whose target
|
|
101
|
+
* is computed (a variable, command substitution, etc.) rather than written
|
|
102
|
+
* literally in the command line — that case still reaches the in-progress /
|
|
103
|
+
* bypass checks and can warn, rather than being silently treated as safe.
|
|
61
104
|
*
|
|
62
105
|
* A wider gap of the same kind: a command that writes through an
|
|
63
106
|
* interpreter (`python3 - <<PY` then `open(path, "w")`, `node -e`, a script
|
|
@@ -83,7 +126,7 @@ export function isWriteShapedBashCommand(command) {
|
|
|
83
126
|
*/
|
|
84
127
|
export function extractBashWriteTargets(command) {
|
|
85
128
|
const targets = [];
|
|
86
|
-
for (const segment of bashSegments(
|
|
129
|
+
for (const segment of bashSegments(normalizeBashCommand(command))) {
|
|
87
130
|
const redirect = segment.match(/>{1,2}\s*([^\s;&|>]+)/);
|
|
88
131
|
if (redirect?.[1])
|
|
89
132
|
targets.push(redirect[1]);
|
|
@@ -130,74 +173,97 @@ function canonicalize(path) {
|
|
|
130
173
|
return join(canonicalize(parent), basename(path));
|
|
131
174
|
}
|
|
132
175
|
}
|
|
133
|
-
/**
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
176
|
+
/** Turn a path as written in a command line into one `resolve` understands:
|
|
177
|
+
* drop surrounding quotes and expand a leading `~` or `$HOME`. Anything else
|
|
178
|
+
* the shell would expand (other variables, command substitution) is left
|
|
179
|
+
* as-is, so it resolves inside the project and the agent is still warned. */
|
|
180
|
+
function shellPathToFsPath(candidate) {
|
|
181
|
+
const unquoted = candidate.replace(/^['"]|['"]$/g, "");
|
|
182
|
+
return unquoted.replace(/^(?:~|\$HOME|\$\{HOME\})(?=\/|$)/, homedir());
|
|
183
|
+
}
|
|
184
|
+
/** Whether `candidate` resolves to `root` itself or a path inside it. */
|
|
185
|
+
function isPathInside(candidate, root, cwd) {
|
|
186
|
+
const resolvedRoot = canonicalize(resolve(root));
|
|
187
|
+
const resolvedCandidate = canonicalize(resolve(cwd, shellPathToFsPath(candidate)));
|
|
137
188
|
const rel = relative(resolvedRoot, resolvedCandidate);
|
|
138
189
|
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
139
190
|
}
|
|
140
|
-
/**
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
* a hook harness can use the exact same check for its cheap early-allow path
|
|
144
|
-
* without duplicating the rule. */
|
|
145
|
-
export function isEntirelyInsidePlannerRoot(paths, plannerRoot, cwd) {
|
|
146
|
-
return paths.length > 0 && paths.every((path) => isPathInsidePlannerRoot(path, plannerRoot, cwd));
|
|
191
|
+
/** Whether `candidate` resolves to a path inside `plannerRoot` (or is it exactly). */
|
|
192
|
+
export function isPathInsidePlannerRoot(candidate, plannerRoot, cwd) {
|
|
193
|
+
return isPathInside(candidate, plannerRoot, cwd);
|
|
147
194
|
}
|
|
148
|
-
/**
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
195
|
+
/** Whether writing `candidate` changes the project's code: it is inside the
|
|
196
|
+
* project (the directory that owns `.planner/`) and not inside `.planner/`.
|
|
197
|
+
* `/dev/null`, `~/.claude/...`, temp dirs and other repositories are not. */
|
|
198
|
+
export function isProjectCodePath(candidate, plannerRoot, cwd) {
|
|
199
|
+
return isPathInside(candidate, dirname(plannerRoot), cwd) && !isPathInside(candidate, plannerRoot, cwd);
|
|
200
|
+
}
|
|
201
|
+
/** Which paths a covered tool call would write to, before any filtering;
|
|
202
|
+
* empty when the target cannot be read from the event. */
|
|
203
|
+
function rawWriteTargets(event) {
|
|
204
|
+
const tool = event.toolName.toLowerCase();
|
|
205
|
+
if (FILE_WRITE_TOOLS.has(tool)) {
|
|
206
|
+
return { covered: true, paths: event.filePath ? [event.filePath] : [] };
|
|
157
207
|
}
|
|
158
|
-
if (
|
|
159
|
-
return {
|
|
208
|
+
if (tool === "notebookedit") {
|
|
209
|
+
return { covered: true, paths: event.notebookPath ? [event.notebookPath] : [] };
|
|
160
210
|
}
|
|
161
|
-
if (
|
|
211
|
+
if (tool === "bash") {
|
|
162
212
|
const command = event.command ?? "";
|
|
163
213
|
if (!isWriteShapedBashCommand(command))
|
|
164
|
-
return {
|
|
165
|
-
return {
|
|
214
|
+
return { covered: false, paths: [] };
|
|
215
|
+
return { covered: true, paths: extractBashWriteTargets(command) };
|
|
166
216
|
}
|
|
167
|
-
return {
|
|
217
|
+
return { covered: false, paths: [] };
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Whether a tool call may change the project's code, and so needs the guard
|
|
221
|
+
* to look at task state. This is the one place that rule lives; the hook's
|
|
222
|
+
* cheap early exit and {@link noTaskWarning} both read `guarded`.
|
|
223
|
+
*
|
|
224
|
+
* - Tools that never write (Read, Grep, MCP tools, read-only Bash, ...) are
|
|
225
|
+
* not guarded.
|
|
226
|
+
* - A write whose targets are all known and none is project code (inside
|
|
227
|
+
* `.planner/`, `/dev/null`, `~/.claude/...`, a temp dir, another repo) is
|
|
228
|
+
* not guarded: updating the planner or the user's own config never needs a
|
|
229
|
+
* task.
|
|
230
|
+
* - A write with at least one project-code target is guarded, with `paths`
|
|
231
|
+
* narrowed to those targets.
|
|
232
|
+
* - A write whose target is unknown (a computed heredoc, an Edit with no
|
|
233
|
+
* path) is guarded with empty `paths` — never silently allowed.
|
|
234
|
+
*/
|
|
235
|
+
export function classifyGuardedTool(event) {
|
|
236
|
+
const { covered, paths } = rawWriteTargets(event);
|
|
237
|
+
if (!covered)
|
|
238
|
+
return { guarded: false, paths: [] };
|
|
239
|
+
if (paths.length === 0)
|
|
240
|
+
return { guarded: true, paths: [] };
|
|
241
|
+
const projectPaths = paths.filter((path) => isProjectCodePath(path, event.plannerRoot, event.cwd));
|
|
242
|
+
return { guarded: projectPaths.length > 0, paths: projectPaths };
|
|
168
243
|
}
|
|
169
244
|
/**
|
|
170
|
-
* The
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
* calls — the hook harness (or a Pi equivalent) does the reading and
|
|
175
|
-
* printing around this; the only filesystem access is the cheap symlink
|
|
176
|
-
* canonicalization in {@link isPathInsidePlannerRoot}.
|
|
245
|
+
* The warning for a tool call that changes project code while no task is in
|
|
246
|
+
* progress, or none. Never a block or a prompt: the adapter lets the call
|
|
247
|
+
* proceed either way and only passes the text to the agent. Pure apart from
|
|
248
|
+
* the cheap symlink canonicalization in {@link isPathInsidePlannerRoot}.
|
|
177
249
|
*
|
|
178
|
-
* Order
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
* real candidate for blocking reaches the state-based checks.
|
|
250
|
+
* Order mirrors the cost of finding out: anything that cannot change project
|
|
251
|
+
* code (see {@link classifyGuardedTool}) is settled from the event alone, so
|
|
252
|
+
* adapters can skip loading the plan for it.
|
|
182
253
|
*/
|
|
183
|
-
export function
|
|
254
|
+
export function noTaskWarning(event, state) {
|
|
184
255
|
const classification = classifyGuardedTool(event);
|
|
185
256
|
if (!classification.guarded)
|
|
186
|
-
return {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
return { decision: "allow" };
|
|
199
|
-
if (state.guardBypassed)
|
|
200
|
-
return { decision: "allow" };
|
|
201
|
-
const reason = `Agent Plan guard: no task is in-progress, and this ${event.toolName} touches a file outside .planner/.${state.startHint} Or authorize a temporary bypass so this and further edits this session don't ask again (run /planner bypass, or call planner-authorize-bypass) — same bypass window an in-progress task already skips.`;
|
|
202
|
-
return { decision: "ask", reason };
|
|
257
|
+
return {};
|
|
258
|
+
if (!state.hasPlannerDir || state.totalTasks === 0)
|
|
259
|
+
return {};
|
|
260
|
+
if (state.hasInProgressTask || state.guardBypassed)
|
|
261
|
+
return {};
|
|
262
|
+
const target = classification.paths.length > 0 ? classification.paths.join(", ") : "a project file";
|
|
263
|
+
const start = state.focusTask
|
|
264
|
+
? `start the task that covers it (/planner task start ${state.focusTask.id} — ${state.focusTask.title}, or another one)`
|
|
265
|
+
: "start the task that covers it (/planner task start <task>)";
|
|
266
|
+
return {
|
|
267
|
+
warning: `Agent Plan: no task is in progress, and this call changes project code (${target}). It was not blocked. If this is planned work, ${start} so the plan stays accurate. Planner changes never need a task.`,
|
|
268
|
+
};
|
|
203
269
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PlanStore } from "./plan-store.js";
|
|
2
|
+
import type { GuardStateInput } from "./guard-decision.js";
|
|
3
|
+
export interface NoTaskGuardState extends GuardStateInput {
|
|
4
|
+
inProgressTaskIds: string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Read the planner state the no-task warning needs, once, for any adapter.
|
|
8
|
+
* The suggested task is the one the plan is already pointing at: the
|
|
9
|
+
* resume file's in-progress task, then an open task in the current phase,
|
|
10
|
+
* then a paused task, then the first open task. Never throws for a missing
|
|
11
|
+
* planner; it reports `hasPlannerDir: false` instead.
|
|
12
|
+
*/
|
|
13
|
+
export declare function loadNoTaskGuardState(st: PlanStore): Promise<NoTaskGuardState>;
|
|
14
|
+
//# sourceMappingURL=guard-state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard-state.d.ts","sourceRoot":"","sources":["../src/guard-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B;AAID;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,SAAS,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA4BnF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const CLOSED_STATUSES = new Set(["done", "canceled", "rejected"]);
|
|
2
|
+
/**
|
|
3
|
+
* Read the planner state the no-task warning needs, once, for any adapter.
|
|
4
|
+
* The suggested task is the one the plan is already pointing at: the
|
|
5
|
+
* resume file's in-progress task, then an open task in the current phase,
|
|
6
|
+
* then a paused task, then the first open task. Never throws for a missing
|
|
7
|
+
* planner; it reports `hasPlannerDir: false` instead.
|
|
8
|
+
*/
|
|
9
|
+
export async function loadNoTaskGuardState(st) {
|
|
10
|
+
const hasPlannerDir = await st.exists().catch(() => false);
|
|
11
|
+
if (!hasPlannerDir) {
|
|
12
|
+
return { hasPlannerDir, totalTasks: 0, hasInProgressTask: false, inProgressTaskIds: [], guardBypassed: false };
|
|
13
|
+
}
|
|
14
|
+
const [workspace, resume, guardBypassed] = await Promise.all([
|
|
15
|
+
st.loadAll(),
|
|
16
|
+
st.loadResume().catch(() => null),
|
|
17
|
+
st.isGuardBypassed().catch(() => false),
|
|
18
|
+
]);
|
|
19
|
+
const allTasks = workspace.phases.flatMap((phase) => phase.tasks.map((task) => ({ phase, task })));
|
|
20
|
+
const inProgressTaskIds = allTasks.filter(({ task }) => task.status === "in-progress").map(({ task }) => task.id);
|
|
21
|
+
const isOpen = (status) => !CLOSED_STATUSES.has(status);
|
|
22
|
+
const focus = (resume?.inProgressTaskIds?.[0] ? allTasks.find(({ task }) => task.id === resume.inProgressTaskIds[0]) : undefined)
|
|
23
|
+
?? (resume?.currentPhaseId ? allTasks.find(({ phase, task }) => phase.id === resume.currentPhaseId && isOpen(task.status)) : undefined)
|
|
24
|
+
?? allTasks.find(({ task }) => task.pauseSnapshot)
|
|
25
|
+
?? allTasks.find(({ task }) => task.status === "planned" || task.status === "blocked" || task.status === "waiting")
|
|
26
|
+
?? allTasks.find(({ task }) => isOpen(task.status));
|
|
27
|
+
return {
|
|
28
|
+
hasPlannerDir,
|
|
29
|
+
totalTasks: allTasks.length,
|
|
30
|
+
hasInProgressTask: inProgressTaskIds.length > 0,
|
|
31
|
+
inProgressTaskIds,
|
|
32
|
+
guardBypassed,
|
|
33
|
+
...(focus ? { focusTask: { id: focus.task.id, title: focus.task.title } } : {}),
|
|
34
|
+
};
|
|
35
|
+
}
|
package/dist/handoff-reply.d.ts
CHANGED
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
* per-field comments) rather than by a single blanket rule — see AGENTS.md
|
|
12
12
|
* rule 4: this placement decision belongs in the core exactly once, not
|
|
13
13
|
* once per adapter.
|
|
14
|
+
*
|
|
15
|
+
* Transport note: Claude Code shows the agent only structuredContent when a
|
|
16
|
+
* result has one, so the MCP server folds `text` into the structured part
|
|
17
|
+
* for every tool (foldTextIntoStructured in plan-mcp). The rule above still
|
|
18
|
+
* holds for what these builders return; the fold is the MCP transport's job.
|
|
14
19
|
*/
|
|
15
20
|
import type { HandoffCompletenessAudit, Phase } from "./schema.js";
|
|
16
21
|
import type { PhaseWorkMap } from "./task-context.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handoff-reply.d.ts","sourceRoot":"","sources":["../src/handoff-reply.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"handoff-reply.d.ts","sourceRoot":"","sources":["../src/handoff-reply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,wBAAwB,EAA6B,KAAK,EAAE,MAAM,aAAa,CAAC;AAC9F,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAGL,KAAK,oBAAoB,EAEzB,KAAK,iBAAiB,EACvB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,oBAAoB,EAAE,MAAM,GAAE,SAAS,GAAG,aAAyB,GAAG,aAAa,CAMrI;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;oBAMoB;AACpB,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,CAKjF;AA8CD;6CAC6C;AAC7C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAmCD,MAAM,MAAM,qBAAqB,GAC7B;IACE,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,wBAAwB,GAAG,IAAI,CAAC;CAC/C,GACD;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,wBAAwB,GAAG,IAAI,CAAC;CAC/C,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;IACb,YAAY,EAAE,YAAY,CAAC;CAC5B,CAAC;AAEN;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,qBAAqB,GAAG,YAAY,CAsEhF;AAED;wDACwD;AACxD,eAAO,MAAM,iCAAiC,0IAC2F,CAAC;AAE1I,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,iBAAiB,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,wBAAwB,GAAG,YAAY,CAkFtF"}
|
package/dist/index.d.ts
CHANGED
|
@@ -27,11 +27,14 @@ export * from "./feature-delete.js";
|
|
|
27
27
|
export * from "./accepted-decision-guard.js";
|
|
28
28
|
export * from "./project-context-migration.js";
|
|
29
29
|
export * from "./project-context.js";
|
|
30
|
+
export * from "./load-reply.js";
|
|
31
|
+
export * from "./on-hold.js";
|
|
30
32
|
export * from "./requirement-macro-tasks.js";
|
|
31
33
|
export * from "./package-version.js";
|
|
32
34
|
export * from "./runtime-diagnostics.js";
|
|
33
35
|
export * from "./write-coordination.js";
|
|
34
36
|
export * from "./guard-decision.js";
|
|
37
|
+
export * from "./guard-state.js";
|
|
35
38
|
export { PlanStore, PlanStoreError, PlanStaleWriteError, PlanUnsupportedAllocationKindError, assertPlannerRevision, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock, nowISO, type IdeaCreateInput, type IdeaUpdateInput, type IdeaPromotionTargetInput, type AcceptedDecisionOwner, type AcceptedDecisionCreateInput, type AcceptedDecisionUpdateInput, type RecordProjectContextReadInput, type RecordProjectContextReadResult, type PhaseHandoffSummary, type OrphanPhaseSummary } from "./plan-store.js";
|
|
36
39
|
export { PlanRenderer } from "./renderer.js";
|
|
37
40
|
export { ExportService } from "./export-service.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sBAAsB,CAAC;AACrC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,mBAAmB,EAAE,kCAAkC,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,cAAc,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,KAAK,wBAAwB,EAAE,KAAK,qBAAqB,EAAE,KAAK,2BAA2B,EAAE,KAAK,2BAA2B,EAAE,KAAK,6BAA6B,EAAE,KAAK,8BAA8B,EAAE,KAAK,mBAAmB,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC3hB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,mBAAmB,CAAC;AAClC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,gCAAgC,CAAC;AAC/C,cAAc,sBAAsB,CAAC;AACrC,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC;AAC7B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,mBAAmB,EAAE,kCAAkC,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,cAAc,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,KAAK,wBAAwB,EAAE,KAAK,qBAAqB,EAAE,KAAK,2BAA2B,EAAE,KAAK,2BAA2B,EAAE,KAAK,6BAA6B,EAAE,KAAK,8BAA8B,EAAE,KAAK,mBAAmB,EAAE,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC3hB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -27,11 +27,14 @@ export * from "./feature-delete.js";
|
|
|
27
27
|
export * from "./accepted-decision-guard.js";
|
|
28
28
|
export * from "./project-context-migration.js";
|
|
29
29
|
export * from "./project-context.js";
|
|
30
|
+
export * from "./load-reply.js";
|
|
31
|
+
export * from "./on-hold.js";
|
|
30
32
|
export * from "./requirement-macro-tasks.js";
|
|
31
33
|
export * from "./package-version.js";
|
|
32
34
|
export * from "./runtime-diagnostics.js";
|
|
33
35
|
export * from "./write-coordination.js";
|
|
34
36
|
export * from "./guard-decision.js";
|
|
37
|
+
export * from "./guard-state.js";
|
|
35
38
|
export { PlanStore, PlanStoreError, PlanStaleWriteError, PlanUnsupportedAllocationKindError, assertPlannerRevision, setWriteBusyHook, setWriteNotifyHook, migrateToUuids, migrateToGlobalSequence, withFeatureLock, nowISO } from "./plan-store.js";
|
|
36
39
|
export { PlanRenderer } from "./renderer.js";
|
|
37
40
|
export { ExportService } from "./export-service.js";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { ProjectContextReply } from "./project-context.js";
|
|
2
|
+
import type { PlanWorkspace } from "./schema.js";
|
|
3
|
+
/**
|
|
4
|
+
* Reply shaping for an explicit planner load, shared by every adapter
|
|
5
|
+
* (MCP planner-load, Pi's planner-load tool and /planner load command).
|
|
6
|
+
*
|
|
7
|
+
* Every piece is sent once, in one text the agent reads top to bottom: the
|
|
8
|
+
* recap to show the user, then the agent-only context. `structured` carries
|
|
9
|
+
* only status fields; hosts that show the agent only structured content
|
|
10
|
+
* still get the text because the MCP server folds it in.
|
|
11
|
+
*/
|
|
12
|
+
/** How the planner operating guide reaches the agent on this load. */
|
|
13
|
+
export type PlannerGuideDelivery =
|
|
14
|
+
/** The guide body is included (a host with no other way to deliver it). */
|
|
15
|
+
{
|
|
16
|
+
mode: "include";
|
|
17
|
+
content: string;
|
|
18
|
+
customized: boolean;
|
|
19
|
+
message: string;
|
|
20
|
+
}
|
|
21
|
+
/** The host already delivers the guide (e.g. Claude Code's /planner
|
|
22
|
+
* command); send only where it lives, never the body again. */
|
|
23
|
+
| {
|
|
24
|
+
mode: "pointer";
|
|
25
|
+
path: string;
|
|
26
|
+
customized: boolean;
|
|
27
|
+
message: string;
|
|
28
|
+
};
|
|
29
|
+
export interface PlannerLoadReplyInput {
|
|
30
|
+
recap: string;
|
|
31
|
+
projectContext: ProjectContextReply;
|
|
32
|
+
/** Accepted Decisions below project scope (feature/phase/task). Project
|
|
33
|
+
* decisions are already inside the project context. */
|
|
34
|
+
scopedDecisions?: {
|
|
35
|
+
content: string;
|
|
36
|
+
total: number;
|
|
37
|
+
truncated: boolean;
|
|
38
|
+
};
|
|
39
|
+
plannerGuide: PlannerGuideDelivery;
|
|
40
|
+
}
|
|
41
|
+
export interface PlannerLoadReply {
|
|
42
|
+
text: string;
|
|
43
|
+
structured: Record<string, unknown>;
|
|
44
|
+
}
|
|
45
|
+
export declare const PLANNER_LOAD_RECAP_HEADING = "## Recap \u2014 show this to the user verbatim";
|
|
46
|
+
export declare const PLANNER_LOAD_AGENT_HEADING = "## Agent-only context \u2014 read and keep it; never quote it in the recap";
|
|
47
|
+
/** Split a load reply's text back into the recap the user sees and the
|
|
48
|
+
* agent-only context. Any client (or test) that must show only the recap
|
|
49
|
+
* uses this instead of guessing at the layout. */
|
|
50
|
+
export declare function splitPlannerLoadText(text: string): {
|
|
51
|
+
recap: string;
|
|
52
|
+
agentContext: string;
|
|
53
|
+
};
|
|
54
|
+
export declare function buildPlannerLoadReply(input: PlannerLoadReplyInput): PlannerLoadReply;
|
|
55
|
+
/** Accepted Decisions owned by features, phases and tasks, bounded for a
|
|
56
|
+
* load reply. Project decisions are left out: the project context already
|
|
57
|
+
* carries them. */
|
|
58
|
+
export declare function buildScopedDecisionContext(plan: PlanWorkspace): {
|
|
59
|
+
content: string;
|
|
60
|
+
total: number;
|
|
61
|
+
truncated: boolean;
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=load-reply.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-reply.d.ts","sourceRoot":"","sources":["../src/load-reply.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD;;;;;;;;GAQG;AAEH,sEAAsE;AACtE,MAAM,MAAM,oBAAoB;AAC9B,2EAA2E;AACzE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AAC5E;+DAC+D;GAC7D;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5E,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,mBAAmB,CAAC;IACpC;2DACuD;IACvD,eAAe,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IACzE,YAAY,EAAE,oBAAoB,CAAC;CACpC;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED,eAAO,MAAM,0BAA0B,mDAA8C,CAAC;AACtF,eAAO,MAAM,0BAA0B,+EAA0E,CAAC;AAElH;;kDAEkD;AAClD,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAK1F;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,qBAAqB,GAAG,gBAAgB,CAmCpF;AAED;;mBAEmB;AACnB,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,aAAa,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAYtH"}
|