@shanepadgett/tau-agent 0.28.1 → 0.29.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/docs/subagents.md +1 -1
- package/extensions/cache-diagnostics/index.ts +1 -1
- package/extensions/explore/README.md +1 -1
- package/extensions/explore/ast/read/hook.ts +2 -34
- package/extensions/explore/ast/read/policy.ts +0 -32
- package/extensions/explore/index.ts +2 -0
- package/extensions/explore/outline-injection.ts +151 -0
- package/extensions/explore/settings.ts +0 -8
- package/extensions/runtime-context/README.md +1 -1
- package/extensions/runtime-context/index.ts +3 -66
- package/extensions/subagent/agents/review.md +25 -18
- package/extensions/subagent/agents/scout.md +10 -0
- package/extensions/tau-help/help.md +2 -2
- package/extensions/working-memory/README.md +9 -0
- package/extensions/working-memory/checkpoint.ts +175 -0
- package/extensions/working-memory/index.ts +320 -0
- package/extensions/working-memory/memory.ts +248 -0
- package/extensions/working-memory/render.ts +178 -0
- package/extensions/working-memory/settings.ts +38 -0
- package/extensions/working-memory/state.ts +152 -0
- package/package.json +2 -2
- package/schemas/tau.schema.json +33 -39
- package/shared/events.ts +5 -0
- package/shared/full-file-knowledge.ts +0 -1
- package/shared/outline-injection.ts +56 -0
- package/extensions/context-pruning/README.md +0 -39
- package/extensions/context-pruning/index.ts +0 -382
- package/extensions/context-pruning/projection.ts +0 -60
- package/extensions/context-pruning/prune.ts +0 -199
- package/extensions/context-pruning/render.ts +0 -251
- package/extensions/context-pruning/settings.ts +0 -39
- package/shared/context-pruning-state.ts +0 -152
package/docs/subagents.md
CHANGED
|
@@ -82,7 +82,7 @@ Tau assigns one display name to each fresh child and keeps it for follow-up turn
|
|
|
82
82
|
|
|
83
83
|
## Built-ins
|
|
84
84
|
|
|
85
|
-
- `review` —
|
|
85
|
+
- `review` — nuclear, architecture-first review for necessity, reuse, ownership, duplication, and simplification; runtime correctness is secondary
|
|
86
86
|
- `scout` — tiered, AST-first local discovery of files, symbols, data flow, constraints, and unknowns without changing anything
|
|
87
87
|
- `web-research` — `websearch`, `codesearch`, `webfetch`
|
|
88
88
|
- `context-sync` — maps meaningful uncommitted work into `.pi/contexts`. Offered to the coding agent when `extensions.context.sync.enabled` and `sync.automation` are true. Manual `/context-sync` remains when sync is enabled with `automation` false. Validation can auto-run it when `validation.enabled` and `sync.enabled`
|
|
@@ -472,7 +472,7 @@ export default function cacheDiagnosticsExtension(pi: ExtensionAPI): void {
|
|
|
472
472
|
);
|
|
473
473
|
pi.on("session_tree", () => addMarker("session-tree"));
|
|
474
474
|
pi.on("tool_execution_end", (event) => {
|
|
475
|
-
if (event.toolName !== "
|
|
475
|
+
if (event.toolName !== "working_memory" && event.toolName !== "load_tools") return;
|
|
476
476
|
return addMarker("cache-affecting-tool", { tool: event.toolName, isError: event.isError });
|
|
477
477
|
});
|
|
478
478
|
}
|
|
@@ -6,7 +6,7 @@ Pi keeps ordinary filesystem tools (`ls`, `find`, `grep`, `read`). Explore adds
|
|
|
6
6
|
|
|
7
7
|
## Large `read` / autoread
|
|
8
8
|
|
|
9
|
-
When `explore.read.enabled` is on (default), a full Pi `read` or autoread of a registered source file (including Markdown) above `explore.read.structureThresholdLines` (default 200) returns an **outline** — declarations or headings — not the full body. Use ranged `read` (`offset`/`limit
|
|
9
|
+
When `explore.read.enabled` is on (default), a full Pi `read` or autoread of a registered source file (including Markdown) above `explore.read.structureThresholdLines` (default 200) returns an **outline** — declarations or headings — not the full body. Use ranged `read` (`offset`/`limit`) or `show` for bodies and sections. Small files and unregistered paths stay ordinary Pi full text. Set `explore.read.enabled` to `false` to turn the overlay off.
|
|
10
10
|
|
|
11
11
|
## Tools
|
|
12
12
|
|
|
@@ -5,14 +5,7 @@ import { resolveExplorePath } from "../../traverse.ts";
|
|
|
5
5
|
import type { ExploreEngine } from "../engine.ts";
|
|
6
6
|
import { formatOutlineFile } from "../format/outline.ts";
|
|
7
7
|
import { outlinePath } from "../queries/outline.ts";
|
|
8
|
-
import {
|
|
9
|
-
countLines,
|
|
10
|
-
formatLargeReadOutline,
|
|
11
|
-
rangedReadOverLimitMessage,
|
|
12
|
-
readCallKind,
|
|
13
|
-
shouldOutlineFullRead,
|
|
14
|
-
type ExploreReadSettings,
|
|
15
|
-
} from "./policy.ts";
|
|
8
|
+
import { formatLargeReadOutline, readCallKind, shouldOutlineFullRead, type ExploreReadSettings } from "./policy.ts";
|
|
16
9
|
|
|
17
10
|
type ReadOverlayResult = {
|
|
18
11
|
content: Array<{ type: "text"; text: string }>;
|
|
@@ -47,9 +40,7 @@ export function registerReadOutlineHook(pi: ExtensionAPI, engineFor: (cwd: strin
|
|
|
47
40
|
if (engine.registry.adapterForPath(absolutePath) === undefined) return;
|
|
48
41
|
|
|
49
42
|
const kind = readCallKind(event.input);
|
|
50
|
-
if (kind === "ranged")
|
|
51
|
-
return enforceRangedLimit(event.input, event.content, readSettings.maxRangeLines);
|
|
52
|
-
}
|
|
43
|
+
if (kind === "ranged") return;
|
|
53
44
|
|
|
54
45
|
return substituteLargeFullRead({
|
|
55
46
|
engine,
|
|
@@ -96,30 +87,7 @@ async function substituteLargeFullRead(options: {
|
|
|
96
87
|
}
|
|
97
88
|
}
|
|
98
89
|
|
|
99
|
-
function enforceRangedLimit(
|
|
100
|
-
input: Record<string, unknown>,
|
|
101
|
-
content: ReadonlyArray<{ type: string; text?: string }>,
|
|
102
|
-
maxRangeLines: number,
|
|
103
|
-
): ReadOverlayResult | undefined {
|
|
104
|
-
const limit = typeof input.limit === "number" && Number.isFinite(input.limit) ? input.limit : undefined;
|
|
105
|
-
const returnedLines = countLines(textFromContent(content));
|
|
106
|
-
const message = rangedReadOverLimitMessage({ limit, returnedLines, maxRangeLines });
|
|
107
|
-
if (message === undefined) return undefined;
|
|
108
|
-
return {
|
|
109
|
-
content: [{ type: "text", text: message }],
|
|
110
|
-
isError: true,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
|
|
114
90
|
function readPathInput(input: Record<string, unknown>): string | undefined {
|
|
115
91
|
const path = input.path;
|
|
116
92
|
return typeof path === "string" && path.length > 0 ? path : undefined;
|
|
117
93
|
}
|
|
118
|
-
|
|
119
|
-
function textFromContent(content: ReadonlyArray<{ type: string; text?: string }>): string {
|
|
120
|
-
const parts: string[] = [];
|
|
121
|
-
for (const part of content) {
|
|
122
|
-
if (part.type === "text" && typeof part.text === "string") parts.push(part.text);
|
|
123
|
-
}
|
|
124
|
-
return parts.join("\n");
|
|
125
|
-
}
|
|
@@ -1,28 +1,12 @@
|
|
|
1
1
|
export type ExploreReadSettings = {
|
|
2
2
|
enabled: boolean;
|
|
3
3
|
structureThresholdLines: number;
|
|
4
|
-
maxRangeLines: number;
|
|
5
4
|
};
|
|
6
5
|
|
|
7
6
|
export type ReadCallKind = "full" | "ranged";
|
|
8
7
|
|
|
9
8
|
const LARGE_READ_OUTLINE_INSTRUCTION = "large file: use ranged read or show for bodies";
|
|
10
9
|
|
|
11
|
-
/** Newline-based line count (empty string → 0). */
|
|
12
|
-
export function countLines(text: string): number {
|
|
13
|
-
if (text.length === 0) return 0;
|
|
14
|
-
let count = 1;
|
|
15
|
-
for (let i = 0; i < text.length; i += 1) {
|
|
16
|
-
const code = text.charCodeAt(i);
|
|
17
|
-
if (code === 10) count += 1;
|
|
18
|
-
else if (code === 13) {
|
|
19
|
-
count += 1;
|
|
20
|
-
if (text.charCodeAt(i + 1) === 10) i += 1;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return count;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
10
|
export function readCallKind(input: { offset?: unknown; limit?: unknown }): ReadCallKind {
|
|
27
11
|
const hasOffset = typeof input.offset === "number" && Number.isFinite(input.offset);
|
|
28
12
|
const hasLimit = typeof input.limit === "number" && Number.isFinite(input.limit);
|
|
@@ -33,22 +17,6 @@ export function shouldOutlineFullRead(lineCount: number, structureThresholdLines
|
|
|
33
17
|
return lineCount > structureThresholdLines;
|
|
34
18
|
}
|
|
35
19
|
|
|
36
|
-
/** Hard-error message when a ranged read exceeds maxRangeLines; undefined when allowed. */
|
|
37
|
-
export function rangedReadOverLimitMessage(options: {
|
|
38
|
-
limit: number | undefined;
|
|
39
|
-
returnedLines: number;
|
|
40
|
-
maxRangeLines: number;
|
|
41
|
-
}): string | undefined {
|
|
42
|
-
const { limit, returnedLines, maxRangeLines } = options;
|
|
43
|
-
if (typeof limit === "number" && limit > maxRangeLines) {
|
|
44
|
-
return `Range limit ${limit} exceeds explore.read.maxRangeLines (${maxRangeLines}). Shrink limit and retry.`;
|
|
45
|
-
}
|
|
46
|
-
if (returnedLines > maxRangeLines) {
|
|
47
|
-
return `Ranged read returned ${returnedLines} lines; max is explore.read.maxRangeLines (${maxRangeLines}). Set a smaller limit and retry.`;
|
|
48
|
-
}
|
|
49
|
-
return undefined;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
20
|
export function formatLargeReadOutline(outlineText: string): string {
|
|
53
21
|
if (outlineText.length === 0) return LARGE_READ_OUTLINE_INSTRUCTION;
|
|
54
22
|
return `${outlineText}\n${LARGE_READ_OUTLINE_INSTRUCTION}`;
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import { createReverseDepsTool } from "./ast/tools/reverse-deps.ts";
|
|
22
22
|
import { createShowTool } from "./ast/tools/show.ts";
|
|
23
23
|
import { registerExploreGuidance } from "./guidance.ts";
|
|
24
|
+
import { registerExploreOutlineInjection } from "./outline-injection.ts";
|
|
24
25
|
import { registerExploreAutoread } from "./read/autoread.ts";
|
|
25
26
|
|
|
26
27
|
export default function exploreExtension(pi: ExtensionAPI): void {
|
|
@@ -61,6 +62,7 @@ export default function exploreExtension(pi: ExtensionAPI): void {
|
|
|
61
62
|
pi.registerTool(createContextTool(rowState, temporaryOutput, engineFor, graphFor));
|
|
62
63
|
registerReadOutlineHook(pi, engineFor);
|
|
63
64
|
registerExploreAutoread(pi, rowState, engineFor);
|
|
65
|
+
registerExploreOutlineInjection(pi, rowState, temporaryOutput, engineFor);
|
|
64
66
|
registerExploreGuidance(pi, engineFor);
|
|
65
67
|
|
|
66
68
|
pi.on("session_start", async (_event, ctx) => {
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { Text } from "@earendil-works/pi-tui";
|
|
3
|
+
import { Marker } from "@shanepadgett/tau-tui";
|
|
4
|
+
import { BoundedTextResultBuilder } from "../../shared/bounded-text-result.ts";
|
|
5
|
+
import {
|
|
6
|
+
registerOutlineInjectionProvider,
|
|
7
|
+
type OutlineInjectionDetails,
|
|
8
|
+
type PreparedOutlineInjection,
|
|
9
|
+
} from "../../shared/outline-injection.ts";
|
|
10
|
+
import type { TemporaryOutputStore } from "../../shared/temporary-output-store.ts";
|
|
11
|
+
import type { ToolRowStateStore } from "../../shared/tool-row-state.ts";
|
|
12
|
+
import type { ExploreEngine } from "./ast/engine.ts";
|
|
13
|
+
import { formatOutlineEmpty, formatOutlineFile } from "./ast/format/outline.ts";
|
|
14
|
+
import { outlinePath } from "./ast/queries/outline.ts";
|
|
15
|
+
import { formatPathForDisplay, stripLeadingAt } from "./traverse.ts";
|
|
16
|
+
|
|
17
|
+
const OPTIONS = { includePrivate: false, includeDocs: false, names: [] as readonly string[] };
|
|
18
|
+
|
|
19
|
+
export function registerExploreOutlineInjection(
|
|
20
|
+
pi: ExtensionAPI,
|
|
21
|
+
rowState: ToolRowStateStore,
|
|
22
|
+
temporaryOutput: TemporaryOutputStore,
|
|
23
|
+
engineFor: (cwd: string) => ExploreEngine,
|
|
24
|
+
): void {
|
|
25
|
+
registerOutlineInjectionProvider(pi, async (request) => {
|
|
26
|
+
const messages: PreparedOutlineInjection[] = [];
|
|
27
|
+
const warnings: string[] = [];
|
|
28
|
+
const engine = engineFor(request.cwd);
|
|
29
|
+
for (let index = 0; index < request.paths.length; index += 1) {
|
|
30
|
+
const path = stripLeadingAt(request.paths[index] ?? "");
|
|
31
|
+
assertCurrent(request.signal, request.isLifecycleCurrent);
|
|
32
|
+
try {
|
|
33
|
+
const abort = request.signal ?? new AbortController().signal;
|
|
34
|
+
const result = await outlinePath(engine, path, OPTIONS, abort);
|
|
35
|
+
if (result.mode !== "file") throw new Error("Working-memory outlines require a file path");
|
|
36
|
+
const body =
|
|
37
|
+
result.file.rows.length === 0
|
|
38
|
+
? formatOutlineEmpty(OPTIONS.names)
|
|
39
|
+
: formatOutlineFile(result.file, engine.cwd, false);
|
|
40
|
+
const builder = new BoundedTextResultBuilder(temporaryOutput, "completeBlocks");
|
|
41
|
+
let content: string;
|
|
42
|
+
try {
|
|
43
|
+
await builder.appendBlock(
|
|
44
|
+
result.file.path,
|
|
45
|
+
formatPathForDisplay(result.file.path, engine.cwd),
|
|
46
|
+
`${path}\n${body}`,
|
|
47
|
+
);
|
|
48
|
+
assertCurrent(request.signal, request.isLifecycleCurrent);
|
|
49
|
+
content = (await builder.finish()).content;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
await builder.abort();
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
messages.push({
|
|
55
|
+
customType: "tau.explore.outline" as const,
|
|
56
|
+
content,
|
|
57
|
+
display: true as const,
|
|
58
|
+
details: {
|
|
59
|
+
v: 1 as const,
|
|
60
|
+
rowId: `${request.batchId}:${index}`,
|
|
61
|
+
path,
|
|
62
|
+
cwd: request.cwd,
|
|
63
|
+
batchId: request.batchId,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
} catch (error) {
|
|
67
|
+
if (request.signal?.aborted || !request.isLifecycleCurrent()) throw error;
|
|
68
|
+
warnings.push(`${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { messages, warnings };
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
pi.registerMessageRenderer<OutlineInjectionDetails>("tau.explore.outline", (message, options, theme) => {
|
|
75
|
+
const details = parseDetails(message.details);
|
|
76
|
+
if (!details) return undefined;
|
|
77
|
+
return new OutlineMessageComponent(
|
|
78
|
+
rowState,
|
|
79
|
+
details.rowId,
|
|
80
|
+
details.path,
|
|
81
|
+
typeof message.content === "string" ? message.content : "",
|
|
82
|
+
options.expanded,
|
|
83
|
+
theme,
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function assertCurrent(signal: AbortSignal | undefined, isLifecycleCurrent: () => boolean): void {
|
|
89
|
+
signal?.throwIfAborted();
|
|
90
|
+
if (!isLifecycleCurrent()) throw new Error("Outline preparation crossed a session lifecycle boundary");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function parseDetails(value: unknown): OutlineInjectionDetails | undefined {
|
|
94
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return undefined;
|
|
95
|
+
const details = value as Record<string, unknown>;
|
|
96
|
+
if (
|
|
97
|
+
details.v !== 1 ||
|
|
98
|
+
typeof details.rowId !== "string" ||
|
|
99
|
+
typeof details.path !== "string" ||
|
|
100
|
+
typeof details.cwd !== "string" ||
|
|
101
|
+
typeof details.batchId !== "string"
|
|
102
|
+
) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
v: 1,
|
|
107
|
+
rowId: details.rowId,
|
|
108
|
+
path: details.path,
|
|
109
|
+
cwd: details.cwd,
|
|
110
|
+
batchId: details.batchId,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
class OutlineMessageComponent {
|
|
115
|
+
private readonly rowState: ToolRowStateStore;
|
|
116
|
+
private readonly rowId: string;
|
|
117
|
+
private readonly path: string;
|
|
118
|
+
private readonly content: string;
|
|
119
|
+
private readonly expanded: boolean;
|
|
120
|
+
private readonly theme: Theme;
|
|
121
|
+
|
|
122
|
+
constructor(
|
|
123
|
+
rowState: ToolRowStateStore,
|
|
124
|
+
rowId: string,
|
|
125
|
+
path: string,
|
|
126
|
+
content: string,
|
|
127
|
+
expanded: boolean,
|
|
128
|
+
theme: Theme,
|
|
129
|
+
) {
|
|
130
|
+
this.rowState = rowState;
|
|
131
|
+
this.rowId = rowId;
|
|
132
|
+
this.path = path;
|
|
133
|
+
this.content = content;
|
|
134
|
+
this.expanded = expanded;
|
|
135
|
+
this.theme = theme;
|
|
136
|
+
this.rowState.watch(this.rowId, () => this.invalidate());
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
render(width: number): string[] {
|
|
140
|
+
const marker = new Marker({
|
|
141
|
+
theme: this.theme,
|
|
142
|
+
state: this.rowState.get(this.rowId) === "pruned" ? "warning" : "complete",
|
|
143
|
+
label: "outline",
|
|
144
|
+
parts: [this.path],
|
|
145
|
+
}).render(width);
|
|
146
|
+
if (!this.expanded) return marker;
|
|
147
|
+
return [...marker, ...new Text(this.theme.fg("dim", this.content), 1, 0).render(width)];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
invalidate(): void {}
|
|
151
|
+
}
|
|
@@ -10,7 +10,6 @@ export default defineTauExtensionSettings({
|
|
|
10
10
|
read: {
|
|
11
11
|
enabled: true as boolean,
|
|
12
12
|
structureThresholdLines: 200 as number,
|
|
13
|
-
maxRangeLines: 200 as number,
|
|
14
13
|
},
|
|
15
14
|
},
|
|
16
15
|
schema: Type.Object(
|
|
@@ -47,13 +46,6 @@ export default defineTauExtensionSettings({
|
|
|
47
46
|
"Registered source at or under this line count may be full-read. Above it, full read model-visible result is outline only.",
|
|
48
47
|
}),
|
|
49
48
|
),
|
|
50
|
-
maxRangeLines: Type.Optional(
|
|
51
|
-
Type.Integer({
|
|
52
|
-
default: 200,
|
|
53
|
-
minimum: 1,
|
|
54
|
-
description: "Maximum lines returned by one ranged read on registered source.",
|
|
55
|
-
}),
|
|
56
|
-
),
|
|
57
49
|
},
|
|
58
50
|
{ additionalProperties: false },
|
|
59
51
|
),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Runtime Context
|
|
2
2
|
|
|
3
|
-
Supplies Tau with
|
|
3
|
+
Supplies Tau with current local date and initial root directory snapshot in each agent run's system prompt. Snapshot remains fixed until extension runtime restarts, while date updates when local day changes.
|
|
4
4
|
|
|
5
5
|
After changing this extension, run `/reload` before testing the new behavior.
|
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import {
|
|
3
|
-
fingerprintRuntimeSnapshot,
|
|
4
|
-
formatLocalDateKey,
|
|
5
3
|
formatLocalDisplayDate,
|
|
6
4
|
formatRuntimeContextMessage,
|
|
7
5
|
freezeRuntimeContext,
|
|
8
6
|
type RuntimeContext,
|
|
9
7
|
} from "./context.ts";
|
|
10
8
|
|
|
11
|
-
const RUNTIME_CONTEXT_TYPE = "tau.runtime-context";
|
|
12
|
-
|
|
13
|
-
interface RuntimeContextMessageDetails {
|
|
14
|
-
version: 1;
|
|
15
|
-
dateKey: string;
|
|
16
|
-
snapshotHash: string;
|
|
17
|
-
includesSnapshot: boolean;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
9
|
export default function runtimeContextExtension(pi: ExtensionAPI): void {
|
|
21
10
|
let runtimeContext: RuntimeContext | undefined;
|
|
22
11
|
|
|
@@ -24,61 +13,9 @@ export default function runtimeContextExtension(pi: ExtensionAPI): void {
|
|
|
24
13
|
runtimeContext = freezeRuntimeContext(ctx.cwd);
|
|
25
14
|
});
|
|
26
15
|
|
|
27
|
-
pi.on("before_agent_start", (
|
|
16
|
+
pi.on("before_agent_start", (event, ctx) => {
|
|
28
17
|
runtimeContext ??= freezeRuntimeContext(ctx.cwd);
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
const snapshotHash = fingerprintRuntimeSnapshot(runtimeContext);
|
|
32
|
-
let hasDate = false;
|
|
33
|
-
let hasSnapshot = false;
|
|
34
|
-
for (const entry of ctx.sessionManager.buildContextEntries()) {
|
|
35
|
-
const details = runtimeContextDetails(entry);
|
|
36
|
-
if (!details) continue;
|
|
37
|
-
if (details.dateKey === dateKey) hasDate = true;
|
|
38
|
-
if (details.includesSnapshot && details.snapshotHash === snapshotHash) hasSnapshot = true;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (hasDate && hasSnapshot) return undefined;
|
|
42
|
-
const includeSnapshot = !hasSnapshot;
|
|
43
|
-
return {
|
|
44
|
-
message: {
|
|
45
|
-
customType: RUNTIME_CONTEXT_TYPE,
|
|
46
|
-
content: formatRuntimeContextMessage(
|
|
47
|
-
formatLocalDisplayDate(now),
|
|
48
|
-
includeSnapshot ? runtimeContext.rootSnapshot : undefined,
|
|
49
|
-
),
|
|
50
|
-
display: false,
|
|
51
|
-
details: {
|
|
52
|
-
version: 1,
|
|
53
|
-
dateKey,
|
|
54
|
-
snapshotHash,
|
|
55
|
-
includesSnapshot: includeSnapshot,
|
|
56
|
-
} satisfies RuntimeContextMessageDetails,
|
|
57
|
-
},
|
|
58
|
-
};
|
|
18
|
+
const content = formatRuntimeContextMessage(formatLocalDisplayDate(new Date()), runtimeContext.rootSnapshot);
|
|
19
|
+
return { systemPrompt: `${event.systemPrompt}\n\n${content}` };
|
|
59
20
|
});
|
|
60
21
|
}
|
|
61
|
-
|
|
62
|
-
function runtimeContextDetails(value: unknown): RuntimeContextMessageDetails | undefined {
|
|
63
|
-
if (!value || typeof value !== "object") return undefined;
|
|
64
|
-
const entry = value as Record<string, unknown>;
|
|
65
|
-
if (entry.type !== "custom_message" || entry.customType !== RUNTIME_CONTEXT_TYPE || entry.display !== false) {
|
|
66
|
-
return undefined;
|
|
67
|
-
}
|
|
68
|
-
if (!entry.details || typeof entry.details !== "object") return undefined;
|
|
69
|
-
const details = entry.details as Record<string, unknown>;
|
|
70
|
-
if (
|
|
71
|
-
details.version !== 1 ||
|
|
72
|
-
typeof details.dateKey !== "string" ||
|
|
73
|
-
typeof details.snapshotHash !== "string" ||
|
|
74
|
-
typeof details.includesSnapshot !== "boolean"
|
|
75
|
-
) {
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
78
|
-
return {
|
|
79
|
-
version: 1,
|
|
80
|
-
dateKey: details.dateKey,
|
|
81
|
-
snapshotHash: details.snapshotHash,
|
|
82
|
-
includesSnapshot: details.includesSnapshot,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: review
|
|
3
|
-
description: Perform
|
|
3
|
+
description: Perform a nuclear, architecture-first review for necessity, reuse, ownership, duplication, and simplification; runtime correctness is secondary
|
|
4
4
|
tools:
|
|
5
5
|
- read
|
|
6
6
|
- bash
|
|
@@ -26,43 +26,50 @@ model: openai-codex/gpt-5.6-sol
|
|
|
26
26
|
thinking: high
|
|
27
27
|
---
|
|
28
28
|
|
|
29
|
-
Stay
|
|
29
|
+
Stay centered on delegated change, but inspect enough surrounding code to find correct ownership and existing reuse. Every review is a nuclear review of codebase health. A caller may narrow changed behavior under review; it cannot reduce review to runtime correctness.
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
2. Is this the simplest implementation of requested behavior?
|
|
31
|
+
Answer in this order:
|
|
33
32
|
|
|
34
|
-
|
|
33
|
+
1. Should this code exist? Is every added behavior requested and necessary?
|
|
34
|
+
2. Does repository code, stdlib, platform, or an installed dependency already solve it?
|
|
35
|
+
3. Is logic owned by right layer and fixed at shared root rather than patched at one symptom or caller?
|
|
36
|
+
4. Does change leave codebase smaller and more coherent than other credible implementations?
|
|
37
|
+
5. Is runtime behavior correct?
|
|
38
|
+
|
|
39
|
+
Find concrete architectural damage, missed simplifications, and failures. Report. Stop. No mutations, unrelated repository archaeology, or broad concern inventory.
|
|
35
40
|
|
|
36
41
|
## Evidence ladder
|
|
37
42
|
|
|
38
|
-
Use cheapest evidence that settles each
|
|
43
|
+
Use cheapest evidence that settles each tier. Escalate only when answer could change verdict.
|
|
39
44
|
|
|
40
45
|
1. **Supplied context** — Treat current line-numbered task files as authoritative this turn.
|
|
41
46
|
2. **Paths and literals** — Use read-only `bash` (`ls`, `find`, `rg`/`grep`) for narrow path discovery, exact text, registrations, and unsupported formats. Use ranged `read` when formatting or source context matters.
|
|
42
|
-
3. **Structure and reuse** — Default to `outline`. Use `discover`
|
|
47
|
+
3. **Structure, ownership, and reuse** — Default to `outline`. Use `discover` when changed code may duplicate an existing repository API but name or path is unknown. Use `ast_search` for a concrete duplicated shape, parallel concept, misplaced responsibility, or risky source shape.
|
|
43
48
|
4. **Exact declarations** — Use `show` with path + name (+ line when needed). Retrieve only contract, body, imports, or nearby lines needed for verdict.
|
|
44
|
-
5. **
|
|
49
|
+
5. **Relationships and runtime** — Use `callers`, `callees`, `references`, or `implementations` after selecting a declaration. Use `deps`/`reverse_deps` for file ownership and imports. Use `impact` for full blast radius and `context` for one bounded declaration pack.
|
|
45
50
|
|
|
46
51
|
Keep roots and result limits narrow. Structural evidence proves bounded syntax, not dynamic dispatch. Preserve inferred and ambiguous labels.
|
|
47
52
|
|
|
48
53
|
## Review procedure
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
Run every tier in order, even when caller asks only for runtime review:
|
|
56
|
+
|
|
57
|
+
1. **Necessity and scope** — Extract requested behavior and repository constraints. Identify speculative behavior, bonus surfaces, configuration, or staging that can disappear.
|
|
58
|
+
2. **Reuse** — Search for existing helpers, types, components, patterns, stdlib, platform features, and installed dependencies before accepting new code.
|
|
59
|
+
3. **Ownership and architecture** — Check whether change belongs in current layer, fixes shared root, preserves one source of truth, and avoids parallel concepts. Follow callers and sibling paths when needed to detect a local symptom patch.
|
|
60
|
+
4. **Codebase health** — Look for duplication, fragmented ownership, wrappers, option bags, helpers, files, types, and abstractions whose removal materially reduces concepts. Consider a focused refactor when local patch deepens bad structure.
|
|
61
|
+
5. **Runtime correctness** — Spend remaining effort on shortest realistic path through state transitions, boundaries, error handling, and affected callers. Avoid theoretical branch inventory.
|
|
62
|
+
6. Stop when every tier has enough evidence for verdict.
|
|
56
63
|
|
|
57
|
-
Treat
|
|
64
|
+
Treat every added concept as guilty until evidence justifies it. Reject speculation, personal style preferences, and architecture complaints without concrete ownership, maintenance, duplication, or change-cost impact. Do not modify files.
|
|
58
65
|
|
|
59
66
|
## Output
|
|
60
67
|
|
|
61
|
-
List findings first,
|
|
68
|
+
List architectural findings first, then runtime findings. Order each group by severity. Each finding needs:
|
|
62
69
|
|
|
63
70
|
- severity and direct title;
|
|
64
71
|
- exact file, line range, and declaration when one exists;
|
|
65
|
-
-
|
|
72
|
+
- failure mechanism or concrete architectural cost;
|
|
66
73
|
- smallest credible fix direction.
|
|
67
74
|
|
|
68
|
-
Then list only unresolved questions that materially affect
|
|
75
|
+
Then list only unresolved questions that materially affect verdict. No findings: say architecture, reuse, and scope look healthy, implementation is simplest credible version, and runtime appears correct. Briefly name inspected scope. No preamble, search log, broad summary, or repeated evidence.
|
|
@@ -16,6 +16,7 @@ tools:
|
|
|
16
16
|
- implementations
|
|
17
17
|
- impact
|
|
18
18
|
- context
|
|
19
|
+
- working_memory
|
|
19
20
|
names:
|
|
20
21
|
- Pathfinder
|
|
21
22
|
- Trailblazer
|
|
@@ -43,6 +44,15 @@ Use cheapest source that proves each claim. Skip steps when task supplies exact
|
|
|
43
44
|
|
|
44
45
|
Structural results prove bounded syntax, not runtime dispatch. Preserve exact, inferred, and ambiguous labels. Do not turn ambiguous sites into claimed impact.
|
|
45
46
|
|
|
47
|
+
## Exploration discipline
|
|
48
|
+
|
|
49
|
+
- Narrow each call around one unanswered claim. Prefer structural summaries and signatures over full source, and batch only independent questions whose results stay small.
|
|
50
|
+
- Let each result reduce the search space. Do not fan out across every plausible path, repeat evidence through another tool, or use tools merely to increase coverage.
|
|
51
|
+
- Keep a short mental set of proven facts, live unknowns, and candidate paths. Drop rejected branches as soon as evidence rules them out.
|
|
52
|
+
- During long or branching work, use `working_memory` when stale evidence would burden the next phase: after ruling out branches, after finishing a distinct phase, before switching to a materially different search, or when reminded to reassess.
|
|
53
|
+
- At a checkpoint, keep decisive or expensive evidence, carry active file structure as outlines when bodies are no longer needed, and defer known paths only when a clear condition would make them relevant. Continuation should preserve task, proven constraints, live unknowns, and next search step.
|
|
54
|
+
- Do not checkpoint a small search or prune coherent evidence still needed for the current line of reasoning.
|
|
55
|
+
|
|
46
56
|
## Search procedure
|
|
47
57
|
|
|
48
58
|
1. Extract target, question, scope, and requested output shape.
|
|
@@ -34,9 +34,9 @@ Adds `/commit` for semantic commit grouping, review, and committing selected rep
|
|
|
34
34
|
|
|
35
35
|
Adds `/context` to select reusable repository work scopes from `.pi/contexts`, and `/context-sync` or `/context-sync <nudge>` for human-driven catalog sync (optional nudge). Escape cancels a running manual sync. When `sync.automation` is on, the coding agent can also run the `context-sync` subagent after meaningful uncommitted work. Sync catalogs durable code and long-lived documentation; recurring scratch, planning, interview, and rough-idea paths belong in `validation.ignoreGlobs`. `sync.enabled` is the master switch for command, automation, and validation auto-run. Entry `files` are autoread; entry `anchors` are unloaded navigation paths. Context validation is off by default; when on (and sync enabled), Tau auto-runs context-sync on failure. Folder names are tabs, TOML files are concepts, and TOML sections are selectable entries.
|
|
36
36
|
|
|
37
|
-
##
|
|
37
|
+
## working-memory
|
|
38
38
|
|
|
39
|
-
Gives
|
|
39
|
+
Gives agent `working_memory` for selective hard checkpoints. Model-only references identify useful conversation evidence and complete tool exchanges. Requested source files return as structural outlines, while deferred files remain cheap conditional reminders. Everything else before checkpoint leaves future model input without changing saved session. Advisory reminders begin at 40k active-context tokens. Run `/prune` to request reassessment manually.
|
|
40
40
|
|
|
41
41
|
## explore
|
|
42
42
|
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Working Memory
|
|
2
|
+
|
|
3
|
+
Working Memory gives agent selective checkpoints without changing saved conversation.
|
|
4
|
+
|
|
5
|
+
`working_memory` keeps referenced conversation evidence and complete tool exchanges, carries requested source files as structural outlines, and records deferred files as conditional reminders. Everything else before checkpoint leaves future model context.
|
|
6
|
+
|
|
7
|
+
Automatic reminders begin at 40,000 active-context tokens and remain advisory. Run `/prune` to request reassessment manually.
|
|
8
|
+
|
|
9
|
+
Settings live under `extensions.workingMemory`.
|