Skip to content

Commit 3db7129

Browse files
committed
Refactor ledger module with factory pattern, stale guards, and named constants
1 parent 4cb0e8b commit 3db7129

2 files changed

Lines changed: 75 additions & 47 deletions

File tree

ledger/store.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,21 @@ export function getEntryNames(state: AgenticodingState): string[] {
2828
return Array.from(state.ledger.keys()).sort();
2929
}
3030

31+
const PREVIEW_MAX_CHARS = 80;
32+
const ELLIPSIS_LENGTH = 3;
33+
3134
export function formatEntryList(state: AgenticodingState): string {
3235
const names = getEntryNames(state);
33-
if (names.length === 0) return "(empty)";
36+
if (names.length === 0) return "";
3437

3538
return names
3639
.map((name) => {
3740
const content = state.ledger.get(name)!;
3841
const firstLine = content.split("\n")[0] ?? "";
3942
const preview =
40-
firstLine.length > 80 ? firstLine.slice(0, 77) + "..." : firstLine;
43+
firstLine.length > PREVIEW_MAX_CHARS
44+
? firstLine.slice(0, PREVIEW_MAX_CHARS - ELLIPSIS_LENGTH) + "..."
45+
: firstLine;
4146
return ` ${name}: ${preview}`;
4247
})
4348
.join("\n");
@@ -48,9 +53,11 @@ export async function saveLedgerEntry(
4853
state: AgenticodingState,
4954
name: string,
5055
content: string,
56+
assertWritable?: () => void,
5157
): Promise<string[]> {
5258
const release = await acquireWriteLock();
5359
try {
60+
assertWritable?.();
5461
const truncated = truncateHead(content, {
5562
maxLines: DEFAULT_MAX_LINES,
5663
maxBytes: DEFAULT_MAX_BYTES,

ledger/tools.ts

Lines changed: 66 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,86 +6,94 @@
66
* list of entry names in both result text and details.
77
*/
88

9-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9+
import type { ExtensionAPI, ToolDefinition } from "@earendil-works/pi-coding-agent";
1010
import { Type } from "typebox";
1111
import type { AgenticodingState } from "../state.js";
1212
import { formatEntryList, getEntryNames, saveLedgerEntry } from "./store.js";
1313

14-
// ── Registration ──────────────────────────────────────────────────────
14+
// ── Factory ───────────────────────────────────────────────────────────
1515

16-
export function registerLedgerTools(
16+
/**
17+
* Creates ledger tool definitions (ledger_add, ledger_get, ledger_list).
18+
*
19+
* Shared by parent registration (withPromptHints=true) and child spawn
20+
* sessions (withPromptHints=false). The prompt hints (snippet, guidelines)
21+
* are only included for the parent — child agents don't need them.
22+
*/
23+
export function createLedgerToolDefinitions(
1724
pi: ExtensionAPI,
1825
state: AgenticodingState,
19-
): void {
20-
// ── ledger_add ──────────────────────────────────────────────────
21-
pi.registerTool({
26+
options?: { withPromptHints?: boolean; isStale?: () => boolean },
27+
): ToolDefinition[] {
28+
const withHints = options?.withPromptHints ?? false;
29+
const assertFresh = () => {
30+
if (options?.isStale?.()) {
31+
throw new Error("Spawn invalidated by reset.");
32+
}
33+
};
34+
35+
const ledgerAdd: ToolDefinition = {
2236
name: "ledger_add",
2337
label: "Ledger Add",
2438
description:
2539
"Save or refine a compact continuity entry. " +
2640
"Same name overwrites the previous entry (refinement). " +
2741
"Writes are serialized via a process-local lock; same-name writes overwrite in completion order. " +
2842
"Always returns the current list of up to date entries.",
29-
30-
promptSnippet: "Save or refine a compact continuity entry",
31-
promptGuidelines: [
32-
"Continuously maintain the ledger while you work. After meaningful reads, research, analysis, decisions, or milestones, either refine an existing entry, create a compact reusable entry, or consciously skip because nothing reusable was learned.",
33-
"Prefer refining existing entries over creating many tiny ones. Do not try to make the ledger complete.",
34-
],
35-
43+
...(withHints
44+
? {
45+
promptSnippet: "Save or refine a compact continuity entry",
46+
promptGuidelines: [
47+
"Continuously maintain the ledger while you work. After meaningful reads, research, analysis, decisions, or milestones, either refine an existing entry, create a compact reusable entry, or consciously skip because nothing reusable was learned.",
48+
"Prefer refining existing entries over creating many tiny ones. Do not try to make the ledger complete.",
49+
],
50+
}
51+
: {}),
3652
executionMode: "sequential",
37-
3853
parameters: Type.Object({
3954
name: Type.String({
4055
description:
4156
"Kebab-case entry identifier. Using an existing name overwrites that entry (refinement).",
4257
}),
4358
content: Type.String({
4459
description:
45-
"Compact markdown. Prefer one reusable item per bullet. " +
46-
"Capture only stable facts, decisions, constraints, progress, and expensive discoveries " +
47-
"that future work should build on. Truncated at 50KB / 2000 lines.",
60+
"Compact markdown. Capture only reusable facts, decisions, " +
61+
"constraints, progress, and expensive discoveries. " +
62+
"Truncated at 50KB / 2000 lines.",
4863
}),
4964
}),
50-
51-
async execute(
52-
_toolCallId,
53-
params,
54-
_signal,
55-
_onUpdate,
56-
_ctx,
57-
) {
58-
const names = await saveLedgerEntry(pi, state, params.name, params.content);
59-
65+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
66+
assertFresh();
67+
const names = await saveLedgerEntry(pi, state, params.name, params.content, assertFresh);
6068
return {
6169
content: [
6270
{
6371
type: "text",
6472
text: `Saved ledger entry "${params.name}".` +
65-
`\n\nEntries:\n${formatEntryList(state)}`,
73+
`\n\nEntries:\n${formatEntryList(state) || "(empty)"}`,
6674
},
6775
],
6876
details: { entries: names },
6977
};
7078
},
71-
});
79+
};
7280

73-
// ── ledger_get ──────────────────────────────────────────────────
74-
pi.registerTool({
81+
const ledgerGet: ToolDefinition = {
7582
name: "ledger_get",
7683
label: "Ledger Get",
7784
description:
7885
"Retrieve a ledger entry's full body by name. " +
7986
"Always returns the current list of entry names.",
80-
81-
promptSnippet: "Fetch a ledger entry by name",
87+
...(withHints
88+
? { promptSnippet: "Fetch a ledger entry by name" }
89+
: {}),
8290
parameters: Type.Object({
8391
name: Type.String({
8492
description: "Entry name to retrieve.",
8593
}),
8694
}),
87-
8895
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
96+
assertFresh();
8997
const content = state.ledger.get(params.name);
9098
const names = getEntryNames(state);
9199

@@ -96,7 +104,7 @@ export function registerLedgerTools(
96104
type: "text",
97105
text:
98106
`Entry "${params.name}" not found.` +
99-
`\n\nEntries:\n${formatEntryList(state)}`,
107+
`\n\nEntries:\n${formatEntryList(state) || "(empty)"}`,
100108
},
101109
],
102110
details: { entries: names, found: false },
@@ -109,37 +117,50 @@ export function registerLedgerTools(
109117
type: "text",
110118
text:
111119
`--- ${params.name} ---\n${content}\n` +
112-
`---\nEntries:\n${formatEntryList(state)}`,
120+
`---\nEntries:\n${formatEntryList(state) || "(empty)"}`,
113121
},
114122
],
115123
details: { entries: names, found: true },
116124
};
117125
},
118-
});
126+
};
119127

120-
// ── ledger_list ─────────────────────────────────────────────────
121-
pi.registerTool({
128+
const ledgerList: ToolDefinition = {
122129
name: "ledger_list",
123130
label: "Ledger List",
124131
description:
125132
"List all ledger entries as name + first-line preview. " +
126133
"Always returns the current list of entry names.",
127-
128-
promptSnippet: "List all ledger entries",
134+
...(withHints
135+
? { promptSnippet: "List all ledger entries" }
136+
: {}),
129137
parameters: Type.Object({}),
130-
131138
async execute() {
139+
assertFresh();
132140
const names = getEntryNames(state);
133-
134141
return {
135142
content: [
136143
{
137144
type: "text",
138-
text: `Entries:\n${formatEntryList(state)}`,
145+
text: `Entries:\n${formatEntryList(state) || "(empty)"}`,
139146
},
140147
],
141148
details: { entries: names },
142149
};
143150
},
144-
});
151+
};
152+
153+
return [ledgerAdd, ledgerGet, ledgerList];
154+
}
155+
156+
// ── Registration ──────────────────────────────────────────────────────
157+
158+
export function registerLedgerTools(
159+
pi: ExtensionAPI,
160+
state: AgenticodingState,
161+
): void {
162+
const tools = createLedgerToolDefinitions(pi, state, { withPromptHints: true });
163+
for (const tool of tools) {
164+
pi.registerTool(tool);
165+
}
145166
}

0 commit comments

Comments
 (0)