@gavin-hu/my-pi-agent 0.11.4 → 0.13.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 +38 -26
- package/README.zh-CN.md +33 -23
- package/extensions/bytefly/README.md +381 -225
- package/extensions/bytefly/chrome.ts +7 -2
- package/extensions/bytefly/doctor/register.ts +6 -0
- package/extensions/bytefly/doctor/runtime.ts +52 -6
- package/extensions/bytefly/doctor/tools.ts +1 -3
- package/extensions/bytefly/footer/collect/snapshot.ts +12 -2
- package/extensions/bytefly/footer/config.ts +7 -1
- package/extensions/bytefly/footer/delegation.ts +100 -0
- package/extensions/bytefly/footer/price/config.ts +54 -15
- package/extensions/bytefly/footer/price/price.ts +20 -7
- package/extensions/bytefly/footer/price/types.ts +13 -7
- package/extensions/bytefly/footer/register.ts +13 -2
- package/extensions/bytefly/footer/render/format.ts +3 -2
- package/extensions/bytefly/footer/render/lines.ts +2 -0
- package/extensions/bytefly/footer/render/segments.ts +30 -2
- package/extensions/bytefly/footer/types.ts +12 -0
- package/extensions/bytefly/header/component.ts +21 -6
- package/extensions/bytefly/header/config.ts +3 -0
- package/extensions/bytefly/header/register.ts +34 -3
- package/extensions/bytefly/index.ts +15 -4
- package/extensions/bytefly/notify/config.ts +2 -0
- package/extensions/bytefly/notify/format.ts +19 -1
- package/extensions/bytefly/notify/register.ts +37 -13
- package/extensions/bytefly/notify/types.ts +2 -0
- package/extensions/bytefly/panel/commands.ts +34 -13
- package/extensions/bytefly/panel/settings-runtime.ts +71 -28
- package/extensions/bytefly/panel/settings.ts +293 -48
- package/extensions/bytefly/panel/tui/entries.ts +58 -0
- package/extensions/bytefly/panel/tui/extensions-view.ts +86 -0
- package/extensions/bytefly/panel/tui/frame.ts +71 -0
- package/extensions/bytefly/panel/tui/nav.ts +75 -0
- package/extensions/bytefly/panel/tui/settings-view.ts +115 -0
- package/extensions/bytefly/panel/tui.ts +205 -319
- package/extensions/bytefly/picker/runtime.ts +61 -2
- package/extensions/bytefly/picker/toggle.ts +56 -0
- package/extensions/bytefly/update/cache.ts +73 -0
- package/extensions/bytefly/update/check.ts +59 -0
- package/extensions/bytefly/update/format.ts +87 -0
- package/extensions/bytefly/update/register.ts +158 -0
- package/extensions/bytefly/update/state.ts +40 -0
- package/extensions/bytefly/update/version.ts +83 -0
- package/extensions/document/README.md +24 -14
- package/extensions/document/config.ts +4 -0
- package/extensions/document/deadline.ts +61 -0
- package/extensions/document/tool.ts +14 -5
- package/extensions/file-browse/README.md +12 -8
- package/extensions/file-browse/client.ts +6 -7
- package/extensions/file-browse/files.ts +0 -4
- package/extensions/file-browse/git.ts +9 -10
- package/extensions/file-browse/html.ts +17 -7
- package/extensions/file-browse/router.ts +24 -15
- package/extensions/file-open/README.md +30 -15
- package/extensions/file-open/index.ts +4 -3
- package/extensions/file-open/open.ts +52 -21
- package/extensions/goal/README.md +45 -13
- package/extensions/goal/clarify-prompt.ts +5 -36
- package/extensions/goal/clarify.ts +55 -86
- package/extensions/goal/commands.ts +41 -35
- package/extensions/goal/format.ts +12 -4
- package/extensions/goal/schema.ts +6 -2
- package/extensions/goal/tools.ts +28 -8
- package/extensions/goal/tui.ts +1 -1
- package/extensions/goal/types.ts +6 -2
- package/extensions/job/README.md +16 -12
- package/extensions/job/runtime.ts +23 -6
- package/extensions/job/status.ts +4 -2
- package/extensions/memory/README.md +56 -14
- package/extensions/memory/commands.ts +20 -2
- package/extensions/memory/doctor.ts +137 -0
- package/extensions/memory/format.ts +24 -7
- package/extensions/memory/runtime.ts +49 -0
- package/extensions/memory/store.ts +31 -0
- package/extensions/memory/tui.ts +136 -12
- package/extensions/plan/README.md +83 -36
- package/extensions/plan/blocks.ts +1 -35
- package/extensions/plan/commands.ts +84 -19
- package/extensions/plan/index.ts +35 -11
- package/extensions/plan/plan-index.ts +234 -0
- package/extensions/plan/plan-text.ts +95 -0
- package/extensions/plan/plans.ts +91 -2
- package/extensions/plan/policy.ts +4 -3
- package/extensions/plan/review.ts +246 -0
- package/extensions/plan/runtime.ts +73 -24
- package/extensions/plan/steps.ts +60 -39
- package/extensions/plan/todos.ts +29 -60
- package/extensions/plan/tools.ts +188 -244
- package/extensions/plan/tui.ts +18 -31
- package/extensions/plan/types.ts +12 -0
- package/extensions/plan/web-client.ts +51 -18
- package/extensions/plan/web-page.ts +7 -2
- package/extensions/plan/web-router.ts +11 -3
- package/extensions/plan/web.ts +106 -41
- package/extensions/question/README.md +1 -1
- package/extensions/question/tools.ts +1 -1
- package/extensions/rewind/README.md +28 -8
- package/extensions/rewind/commands.ts +1 -1
- package/extensions/rewind/format.ts +4 -4
- package/extensions/rewind/git.ts +56 -18
- package/extensions/rewind/index.ts +3 -5
- package/extensions/rewind/path-picker.ts +198 -0
- package/extensions/rewind/paths.ts +128 -0
- package/extensions/rewind/restore.ts +69 -28
- package/extensions/rewind/rewind.ts +80 -3
- package/extensions/rewind/runtime.ts +13 -5
- package/extensions/rewind/snapshot-run.ts +5 -2
- package/extensions/rewind/types.ts +2 -0
- package/extensions/subagent/README.md +7 -1
- package/extensions/subagent/accounting.ts +34 -0
- package/extensions/subagent/index.ts +23 -3
- package/extensions/subagent/stream.ts +2 -0
- package/extensions/subagent/types.ts +2 -0
- package/extensions/todo/README.md +19 -11
- package/extensions/todo/index.ts +5 -3
- package/extensions/todo/nudge.ts +58 -9
- package/extensions/todo/runtime.ts +28 -17
- package/extensions/watch/README.md +8 -3
- package/extensions/watch/index.ts +19 -0
- package/extensions/web-access/README.md +46 -16
- package/extensions/web-access/charset.ts +58 -0
- package/extensions/web-access/fetch/find.ts +11 -7
- package/extensions/web-access/fetch/format.ts +64 -18
- package/extensions/web-access/fetch/page.ts +5 -1
- package/extensions/web-access/fetch/tool.ts +20 -21
- package/extensions/web-access/http.ts +49 -8
- package/extensions/web-access/search/facts.ts +38 -0
- package/extensions/web-access/search/tool.ts +14 -3
- package/extensions/web-access/text.ts +50 -11
- package/extensions/wechat/README.md +5 -4
- package/extensions/wechat/tools.ts +0 -2
- package/extensions/worktree/README.md +74 -15
- package/extensions/worktree/config.ts +11 -0
- package/extensions/worktree/exit.ts +35 -3
- package/extensions/worktree/gaps.ts +74 -0
- package/extensions/worktree/guard.ts +50 -4
- package/extensions/worktree/handoff.ts +55 -0
- package/extensions/worktree/index.ts +12 -2
- package/extensions/worktree/runtime.ts +20 -0
- package/extensions/worktree/status.ts +15 -2
- package/extensions/worktree/tools.ts +3 -2
- package/lib/README.md +12 -9
- package/lib/config.ts +43 -1
- package/lib/env.ts +12 -0
- package/lib/events.ts +86 -1
- package/lib/goal-state.ts +95 -0
- package/lib/requirements.ts +340 -46
- package/lib/todo-state.ts +62 -0
- package/lib/tui/README.md +91 -0
- package/lib/tui/glyphs.ts +2 -0
- package/lib/web/layout.ts +12 -2
- package/package.json +5 -3
- package/themes/bytefly-dark.json +1 -1
- package/assets/bytefly.schema.json +0 -785
- package/assets/chrome.png +0 -0
- package/assets/file-browse.png +0 -0
- package/assets/job.png +0 -0
- package/assets/plan-review.png +0 -0
- package/assets/question.png +0 -0
- package/assets/rewind.png +0 -0
- package/assets/todo.png +0 -0
- package/assets/tool-renderings.png +0 -0
- package/assets/tools/ask_user_question-cancelled.png +0 -0
- package/assets/tools/ask_user_question.png +0 -0
- package/assets/tools/bash.png +0 -0
- package/assets/tools/edit.png +0 -0
- package/assets/tools/enter_plan_mode.png +0 -0
- package/assets/tools/enter_worktree.png +0 -0
- package/assets/tools/exit_plan_mode.png +0 -0
- package/assets/tools/exit_worktree.png +0 -0
- package/assets/tools/find.png +0 -0
- package/assets/tools/goal.png +0 -0
- package/assets/tools/grep.png +0 -0
- package/assets/tools/job.png +0 -0
- package/assets/tools/list_worktrees.png +0 -0
- package/assets/tools/ls.png +0 -0
- package/assets/tools/memory-long-subject.png +0 -0
- package/assets/tools/memory.png +0 -0
- package/assets/tools/notice.png +0 -0
- package/assets/tools/open_path.png +0 -0
- package/assets/tools/prune_worktrees.png +0 -0
- package/assets/tools/read.png +0 -0
- package/assets/tools/read_doc-running.png +0 -0
- package/assets/tools/read_doc.png +0 -0
- package/assets/tools/run_doctor.png +0 -0
- package/assets/tools/send_wechat.png +0 -0
- package/assets/tools/set_plan_steps.png +0 -0
- package/assets/tools/subagent.png +0 -0
- package/assets/tools/todo.png +0 -0
- package/assets/tools/turn-rule.png +0 -0
- package/assets/tools/user-message.png +0 -0
- package/assets/tools/watch.png +0 -0
- package/assets/tools/web_fetch-rejected.png +0 -0
- package/assets/tools/web_fetch.png +0 -0
- package/assets/tools/web_search.png +0 -0
- package/assets/tools/write.png +0 -0
- package/assets/tools/write_plan.png +0 -0
package/extensions/plan/steps.ts
CHANGED
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* guided to write, the top-level steps that seed `todo` and drive the review
|
|
6
6
|
* gauge, the heading sections the review screen navigates, and the in-place
|
|
7
7
|
* checkbox edit that keeps the file current after approval. All of it is pure so
|
|
8
|
-
* the tools, the store, and the renderer agree on one parser.
|
|
8
|
+
* the tools, the store, and the renderer agree on one parser. The review's
|
|
9
|
+
* section comparison lives in `plan-text.ts`, which owns the text normalization
|
|
10
|
+
* the digest is computed over.
|
|
9
11
|
*
|
|
10
12
|
* Only top-level list items become steps: nested sub-bullets are notes, and the
|
|
11
13
|
* shallowest list level is treated as the plan. When the plan has a `Steps`
|
|
@@ -28,7 +30,7 @@ export const PLAN_OUTLINE = [
|
|
|
28
30
|
"# <Title>",
|
|
29
31
|
"",
|
|
30
32
|
"## Goal",
|
|
31
|
-
"What this change achieves,
|
|
33
|
+
"What this change achieves, and what it deliberately does not.",
|
|
32
34
|
"",
|
|
33
35
|
"## Context",
|
|
34
36
|
"The relevant code, constraints, and prior art.",
|
|
@@ -37,8 +39,8 @@ export const PLAN_OUTLINE = [
|
|
|
37
39
|
"The chosen approach and why; alternatives considered.",
|
|
38
40
|
"",
|
|
39
41
|
"## Steps",
|
|
40
|
-
"- [ ]
|
|
41
|
-
"- [ ]
|
|
42
|
+
"- [ ] Add `parseConfig` in `src/config.ts`, with a test for a missing key",
|
|
43
|
+
"- [ ] Wire it into `load()` and document the failure in the README",
|
|
42
44
|
"",
|
|
43
45
|
"## Risks",
|
|
44
46
|
"What could go wrong and how it is mitigated.",
|
|
@@ -61,6 +63,10 @@ export interface PlanSection {
|
|
|
61
63
|
title: string;
|
|
62
64
|
/** The section's markdown, including its heading line. */
|
|
63
65
|
text: string;
|
|
66
|
+
/** 0-based source line of the section's heading (0 for the preamble). */
|
|
67
|
+
line: number;
|
|
68
|
+
/** Heading level, or 0 for the preamble before the first heading. */
|
|
69
|
+
level: number;
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
/** A top-level list item located in the source. */
|
|
@@ -78,10 +84,19 @@ interface PlanListItem {
|
|
|
78
84
|
/** A top-level numbered or bulleted list item, capturing its parts. */
|
|
79
85
|
const LIST_ITEM = /^([ \t]*)((?:\d+[.)]|[-*+])\s+)(.*\S)\s*$/;
|
|
80
86
|
const CHECKBOX = /^\[([ xX])\]\s*(.*)$/;
|
|
81
|
-
/**
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
/**
|
|
88
|
+
* A markdown heading outside a fence, capturing its level and text: up to three
|
|
89
|
+
* leading spaces, a space after the hashes, and a closing hash run only when a
|
|
90
|
+
* space separates it. Shared with `blocks.ts`, so the section split and the
|
|
91
|
+
* browser outline read a heading line the same way, and `## C#` keeps its title.
|
|
92
|
+
*/
|
|
93
|
+
export const HEADING = /^ {0,3}(#{1,6})\s+(.*?)(?:\s+#+)?\s*$/;
|
|
94
|
+
/**
|
|
95
|
+
* A markdown code-fence opener or closer (backticks or tildes). Shared with
|
|
96
|
+
* `blocks.ts`: a tab-indented fence is prose for both parsers, never code for
|
|
97
|
+
* one and prose for the other.
|
|
98
|
+
*/
|
|
99
|
+
export const FENCE = /^ {0,3}(`{3,}|~{3,})/;
|
|
85
100
|
const DONE_MARKER = /\[DONE:\d+\]/gi;
|
|
86
101
|
|
|
87
102
|
/**
|
|
@@ -211,6 +226,38 @@ function elide(content: string): string {
|
|
|
211
226
|
return content.length > MAX_STEP_LENGTH ? `${content.slice(0, MAX_STEP_LENGTH - 1).trimEnd()}…` : content;
|
|
212
227
|
}
|
|
213
228
|
|
|
229
|
+
/** A list marker with an optional checkbox, at any indent. */
|
|
230
|
+
const LIST_PREFIX = /^\s*(?:[-*+]|\d+[.)])\s+(?:\[[ xX]\]\s*)?/;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* The plan's `Verification` section as one trailing checklist item, or
|
|
234
|
+
* `undefined` when the plan has none.
|
|
235
|
+
*
|
|
236
|
+
* The section is prose ("commands and checks"), and a todo item is one line, so
|
|
237
|
+
* the lines are stripped of list markers and inline emphasis and joined with
|
|
238
|
+
* `; `, prefixed with `Verify:` and elided like a long step. The item is
|
|
239
|
+
* deliberately *not* a plan step: it is not in the `Steps` section, so it must
|
|
240
|
+
* not count against the step cap or appear in `set_plan_steps` numbering.
|
|
241
|
+
*
|
|
242
|
+
* A plan with no `Steps` heading feeds its shallowest list as steps (the older
|
|
243
|
+
* rule), so that list already covers the verification bullets; appending a
|
|
244
|
+
* summary of them would duplicate them, and there is nothing to carry.
|
|
245
|
+
*/
|
|
246
|
+
export function verificationItem(plan: string): PlanStep | undefined {
|
|
247
|
+
if (stepsSection(plan) === undefined) return undefined;
|
|
248
|
+
const section = extractPlanSections(plan).find(
|
|
249
|
+
(candidate) => candidate.level > 0 && candidate.title.toLowerCase() === "verification",
|
|
250
|
+
);
|
|
251
|
+
if (!section) return undefined;
|
|
252
|
+
const items = section.text
|
|
253
|
+
.split("\n")
|
|
254
|
+
.slice(1)
|
|
255
|
+
.map((line) => plainText(line.replace(LIST_PREFIX, "")))
|
|
256
|
+
.filter(Boolean);
|
|
257
|
+
if (items.length === 0) return undefined;
|
|
258
|
+
return { content: elide(`Verify: ${items.join("; ")}`), status: "pending" };
|
|
259
|
+
}
|
|
260
|
+
|
|
214
261
|
/** Top-level steps from a plan, in order. */
|
|
215
262
|
export function extractPlanSteps(plan: string): PlanStep[] {
|
|
216
263
|
return topLevelItems(plan).map((item) => ({ content: elide(item.step.content), status: item.step.status }));
|
|
@@ -281,32 +328,6 @@ export function setStepStatuses(plan: string, updates: StepUpdate[]): string | u
|
|
|
281
328
|
return lines.join("\n");
|
|
282
329
|
}
|
|
283
330
|
|
|
284
|
-
/**
|
|
285
|
-
* Titles (normalized) whose section text differs between two plan revisions, or
|
|
286
|
-
* that are new in `current`. Revision snapshots are the caller's to provide;
|
|
287
|
-
* strip stored feedback first so a review comment does not look like an edit.
|
|
288
|
-
* Duplicate headings are compared by occurrence, and a changed title marks every
|
|
289
|
-
* section with that title — plans following the canonical outline have none.
|
|
290
|
-
*/
|
|
291
|
-
export function changedSections(previous: string, current: string): Set<string> {
|
|
292
|
-
const before = new Map<string, string[]>();
|
|
293
|
-
for (const section of extractPlanSections(previous)) {
|
|
294
|
-
const texts = before.get(section.title);
|
|
295
|
-
if (texts) texts.push(section.text.trim());
|
|
296
|
-
else before.set(section.title, [section.text.trim()]);
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const seen = new Map<string, number>();
|
|
300
|
-
const changed = new Set<string>();
|
|
301
|
-
for (const section of extractPlanSections(current)) {
|
|
302
|
-
const index = seen.get(section.title) ?? 0;
|
|
303
|
-
seen.set(section.title, index + 1);
|
|
304
|
-
const prior = before.get(section.title)?.[index];
|
|
305
|
-
if (prior === undefined || prior !== section.text.trim()) changed.add(section.title);
|
|
306
|
-
}
|
|
307
|
-
return changed;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
331
|
/**
|
|
311
332
|
* Sections of a plan, split at heading lines outside code fences. The content
|
|
312
333
|
* before the first heading is a section titled `""`. Pi's `Markdown` renderer
|
|
@@ -323,7 +344,7 @@ export function extractPlanSections(plan: string): PlanSection[] {
|
|
|
323
344
|
current = undefined;
|
|
324
345
|
};
|
|
325
346
|
|
|
326
|
-
|
|
347
|
+
plan.split("\n").forEach((line, index) => {
|
|
327
348
|
const fenceMatch = line.match(FENCE);
|
|
328
349
|
if (fenceMatch) {
|
|
329
350
|
if (fence === undefined) fence = fenceMatch[1][0];
|
|
@@ -332,13 +353,13 @@ export function extractPlanSections(plan: string): PlanSection[] {
|
|
|
332
353
|
const heading = line.match(HEADING);
|
|
333
354
|
if (heading) {
|
|
334
355
|
flush();
|
|
335
|
-
current = { title: plainText(heading[2]), text: line };
|
|
336
|
-
|
|
356
|
+
current = { title: plainText(heading[2]), text: line, line: index, level: heading[1].length };
|
|
357
|
+
return;
|
|
337
358
|
}
|
|
338
359
|
}
|
|
339
|
-
if (current === undefined) current = { title: "", text: line };
|
|
360
|
+
if (current === undefined) current = { title: "", text: line, line: 0, level: 0 };
|
|
340
361
|
else current.text += `\n${line}`;
|
|
341
|
-
}
|
|
362
|
+
});
|
|
342
363
|
|
|
343
364
|
flush();
|
|
344
365
|
return sections;
|
package/extensions/plan/todos.ts
CHANGED
|
@@ -3,25 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* `todo` is whole-list replacement, so seeding an approved plan straight over
|
|
5
5
|
* the list would discard whatever the user was already tracking. This module
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* skipped, and the tool's item cap is respected without dropping anything the
|
|
11
|
-
* user had.
|
|
6
|
+
* merges the plan's steps into the list `lib/todo-state.ts` replays from the
|
|
7
|
+
* session branch: existing items keep their place and status, a step whose text
|
|
8
|
+
* is already present is skipped, and the tool's item cap is respected without
|
|
9
|
+
* dropping anything the user had.
|
|
12
10
|
*/
|
|
13
11
|
|
|
14
|
-
import {
|
|
12
|
+
import type { ExistingTodo } from "../../lib/todo-state.ts";
|
|
15
13
|
import type { PlanStep } from "./steps.ts";
|
|
16
14
|
|
|
17
|
-
/** One item in the existing todo list, as the todo tool stores it. */
|
|
18
|
-
export interface ExistingTodo {
|
|
19
|
-
content: string;
|
|
20
|
-
status: "pending" | "in_progress" | "completed";
|
|
21
|
-
/** Present-continuous label the todo tool requires for an in-progress item. */
|
|
22
|
-
activeForm?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
15
|
/** The merged list plus the counts the approval message reports. */
|
|
26
16
|
export interface TodoMerge {
|
|
27
17
|
todos: ExistingTodo[];
|
|
@@ -33,54 +23,16 @@ export interface TodoMerge {
|
|
|
33
23
|
kept: number;
|
|
34
24
|
/** Plan steps left out because the list reached its cap. */
|
|
35
25
|
deferred: number;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
message?: {
|
|
42
|
-
role?: string;
|
|
43
|
-
toolName?: string;
|
|
44
|
-
details?: unknown;
|
|
45
|
-
};
|
|
26
|
+
/**
|
|
27
|
+
* What happened to the trailing checklist item (the plan's verification
|
|
28
|
+
* summary): appended after the steps, already present, or left out by the cap.
|
|
29
|
+
*/
|
|
30
|
+
trailing?: "added" | "skipped" | "deferred";
|
|
46
31
|
}
|
|
47
32
|
|
|
48
33
|
/** Fallback list cap when the registered todo tool does not advertise one. */
|
|
49
34
|
export const DEFAULT_TODO_MAX = 50;
|
|
50
35
|
|
|
51
|
-
function isStatus(value: unknown): value is ExistingTodo["status"] {
|
|
52
|
-
return value === "pending" || value === "in_progress" || value === "completed";
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* The last valid todo list written on the branch, or an empty list. A rejected
|
|
57
|
-
* call carries the unchanged list and is not a state write, so it is skipped;
|
|
58
|
-
* an entry that does not re-validate is ignored rather than wiping a good list.
|
|
59
|
-
*/
|
|
60
|
-
export function lastTodos(entries: Iterable<unknown>): ExistingTodo[] {
|
|
61
|
-
let todos: ExistingTodo[] = [];
|
|
62
|
-
for (const raw of entries) {
|
|
63
|
-
const entry = raw as BranchEntryLike;
|
|
64
|
-
if (entry?.type !== "message") continue;
|
|
65
|
-
const message = entry.message;
|
|
66
|
-
if (message?.role !== "toolResult" || message.toolName !== TODO_TOOL) continue;
|
|
67
|
-
const details = message.details as { todos?: unknown; error?: unknown } | undefined;
|
|
68
|
-
if (!details || details.error) continue;
|
|
69
|
-
if (!Array.isArray(details.todos)) continue;
|
|
70
|
-
todos = details.todos.flatMap((item): ExistingTodo[] => {
|
|
71
|
-
if (typeof item !== "object" || item === null) return [];
|
|
72
|
-
const value = item as { content?: unknown; status?: unknown; activeForm?: unknown };
|
|
73
|
-
if (typeof value.content !== "string" || !isStatus(value.status)) return [];
|
|
74
|
-
const content = value.content.trim();
|
|
75
|
-
if (!content) return [];
|
|
76
|
-
const activeForm =
|
|
77
|
-
typeof value.activeForm === "string" && value.activeForm.trim() ? value.activeForm.trim() : undefined;
|
|
78
|
-
return [activeForm ? { content, status: value.status, activeForm } : { content, status: value.status }];
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
return todos;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
36
|
/** Case- and whitespace-insensitive key for recognizing a repeated item. */
|
|
85
37
|
function contentKey(content: string): string {
|
|
86
38
|
return content.trim().replace(/\s+/g, " ").toLowerCase();
|
|
@@ -91,8 +43,12 @@ function contentKey(content: string): string {
|
|
|
91
43
|
* keep their status, a legacy in-progress item without an `activeForm` gets its
|
|
92
44
|
* content as one so the todo tool accepts it, and the appended steps stop at
|
|
93
45
|
* `max` items — the rest are counted as deferred, never dropped.
|
|
46
|
+
*
|
|
47
|
+
* `trailing` is the plan's verification summary: it is appended after every step
|
|
48
|
+
* (still subject to the same duplicate and cap rules) but is not counted as a
|
|
49
|
+
* step, because it is not one.
|
|
94
50
|
*/
|
|
95
|
-
export function mergeTodos(existing: ExistingTodo[], steps: PlanStep[], max: number): TodoMerge {
|
|
51
|
+
export function mergeTodos(existing: ExistingTodo[], steps: PlanStep[], max: number, trailing?: PlanStep): TodoMerge {
|
|
96
52
|
const limit = Number.isInteger(max) && max > 0 ? max : DEFAULT_TODO_MAX;
|
|
97
53
|
const todos: ExistingTodo[] = existing.map((todo) =>
|
|
98
54
|
todo.status === "in_progress" && !todo.activeForm ? { ...todo, activeForm: todo.content } : todo,
|
|
@@ -116,5 +72,18 @@ export function mergeTodos(existing: ExistingTodo[], steps: PlanStep[], max: num
|
|
|
116
72
|
todos.push({ content: step.content, status: step.status });
|
|
117
73
|
added += 1;
|
|
118
74
|
}
|
|
119
|
-
|
|
75
|
+
|
|
76
|
+
let trailingOutcome: TodoMerge["trailing"];
|
|
77
|
+
if (trailing) {
|
|
78
|
+
const key = contentKey(trailing.content);
|
|
79
|
+
if (!key) trailingOutcome = undefined;
|
|
80
|
+
else if (seen.has(key)) trailingOutcome = "skipped";
|
|
81
|
+
else if (todos.length >= limit) trailingOutcome = "deferred";
|
|
82
|
+
else {
|
|
83
|
+
todos.push({ content: trailing.content, status: trailing.status });
|
|
84
|
+
trailingOutcome = "added";
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { todos, added, skipped, kept: existing.length, deferred, trailing: trailingOutcome };
|
|
120
89
|
}
|