@shanepadgett/tau-agent 0.5.1 → 0.7.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 CHANGED
@@ -9,7 +9,7 @@ Tau's `subagent` tool delegates one focused task to an isolated child Pi session
9
9
  | **User (global)** | `~/.pi/agent/tau/agents/*.md` | You want the agent in every project |
10
10
  | **Project** | nearest trusted `.pi/tau/agents/*.md` | Repo-specific helpers |
11
11
 
12
- Precedence: **project overrides user**, which overrides Tau's built-ins (`scout`, `context-maintenance`, `web-research`). Duplicate names in one scope are invalid.
12
+ Precedence: **project overrides user**, which overrides Tau's built-ins (`scout`, `web-research`). Duplicate names in one scope are invalid.
13
13
 
14
14
  ## Definition format
15
15
 
@@ -53,7 +53,6 @@ Named tools and configured models must exist in the normally loaded child Pi env
53
53
  ## Built-ins
54
54
 
55
55
  - `scout` — local exploration with `read`, `grep`, `find`, `ls`
56
- - `context-maintenance` — repository context reconciliation with interactive user approval
57
56
  - `web-research` — `websearch`, `codesearch`, `webfetch`
58
57
 
59
58
  Ask Tau to delegate, or let it call `subagent` with an agent name and task.
@@ -87,7 +87,7 @@ async function runAutoName(
87
87
  ): Promise<void> {
88
88
  const ui = ctx.ui;
89
89
  try {
90
- const candidates = await resolveCandidates(ctx, AUTO_NAME_MODELS);
90
+ const candidates = await resolveCandidates(ctx, AUTO_NAME_MODELS, true);
91
91
  const result = await generateToolValidated(
92
92
  { ui, signal: controller.signal },
93
93
  candidates,
@@ -55,7 +55,7 @@ export async function generatePlan(
55
55
  const prompt = buildPlanPrompt(evidence, previousPlan, regenerationNote);
56
56
  return generateToolValidated(
57
57
  ctx,
58
- await resolveCandidates(ctx, COMMIT_MODELS),
58
+ await resolveCandidates(ctx, COMMIT_MODELS, true),
59
59
  prompt,
60
60
  COMMIT_PLAN_TOOL,
61
61
  (input) => commitGroupsFromToolInput(input, evidence.files),
@@ -83,10 +83,17 @@ export async function regenerateMessage(
83
83
  ): Promise<string> {
84
84
  const selected = evidence.files.filter((file) => files.includes(file.path));
85
85
  const prompt = buildMessagePrompt(evidence, selected, previousPlan, selectedGroupId, regenerationNote);
86
- return generateValidated(ctx, await resolveCandidates(ctx, COMMIT_MODELS), prompt, requireCommitMessage, undefined, {
87
- statusKey: "commit",
88
- notifyOnFallback: true,
89
- });
86
+ return generateValidated(
87
+ ctx,
88
+ await resolveCandidates(ctx, COMMIT_MODELS, true),
89
+ prompt,
90
+ requireCommitMessage,
91
+ undefined,
92
+ {
93
+ statusKey: "commit",
94
+ notifyOnFallback: true,
95
+ },
96
+ );
90
97
  }
91
98
 
92
99
  export function requireCommitMessage(rawMessage: string): string {
@@ -2,7 +2,7 @@
2
2
 
3
3
  Context stores reusable repository work scopes in `.pi/contexts`. Folder names become selector tabs, TOML files become concepts, and TOML sections become selectable entries.
4
4
 
5
- Use `/context` to select entries and inject their files through Tau autoread. Use `/context-manage <idea>` to start an isolated maintenance agent that researches the catalog, presents proposed operations for approval, accepts feedback, and applies only selected operations.
5
+ Use `/context` to select entries and inject their files through Tau autoread. Use `/context-sync` to reconcile affected scopes from the current Git changes. Sync is automatic, constrained to the changed scope, and may report `no-change` when existing mappings already fit.
6
6
 
7
7
  ```toml
8
8
  name = "Player"
@@ -1,8 +1,7 @@
1
- import { access, mkdir, readFile, readdir, rename, stat, writeFile } from "node:fs/promises";
1
+ import { access, readFile, readdir, stat } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
4
- import { parse, stringify } from "smol-toml";
5
- import { withFileMutationQueue } from "@earendil-works/pi-coding-agent";
4
+ import { parse } from "smol-toml";
6
5
 
7
6
  export interface ContextEntry {
8
7
  id: string;
@@ -16,38 +15,6 @@ export interface ContextEntry {
16
15
  path: string;
17
16
  }
18
17
 
19
- export interface ContextProposal {
20
- tab: string;
21
- concept: string;
22
- conceptName: string;
23
- conceptDescription: string;
24
- entry: string;
25
- description: string;
26
- files: string[];
27
- }
28
-
29
- export type ContextOperation =
30
- | (ContextProposal & { id: string; kind: "create"; reason: string })
31
- | {
32
- id: string;
33
- kind: "add-files" | "remove-files";
34
- tab: string;
35
- concept: string;
36
- entry: string;
37
- files: string[];
38
- reason: string;
39
- }
40
- | {
41
- id: string;
42
- kind: "replace-file";
43
- tab: string;
44
- concept: string;
45
- entry: string;
46
- from: string;
47
- to: string;
48
- reason: string;
49
- };
50
-
51
18
  export async function pathExists(path: string): Promise<boolean> {
52
19
  try {
53
20
  await access(path);
@@ -149,110 +116,3 @@ export async function loadContextEntries(root: string): Promise<ContextEntry[]>
149
116
  }
150
117
  return result;
151
118
  }
152
-
153
- export async function writeContextEntry(root: string, proposal: ContextProposal, replace: boolean): Promise<void> {
154
- const tab = validSlug(proposal.tab, "Context tab");
155
- const concept = validSlug(proposal.concept, "Context concept");
156
- const entry = validSlug(proposal.entry, "Context entry");
157
- const conceptName = proposal.conceptName.trim();
158
- const description = proposal.description.trim();
159
- if (!conceptName) throw new Error("Context concept name is required");
160
- if (!description) throw new Error("Context entry description is required");
161
- const files = await requireFiles(root, proposal.files);
162
- const path = join(root, ".pi", "contexts", tab, `${concept}.toml`);
163
- await withFileMutationQueue(path, async () => {
164
- let raw: Record<string, unknown> = {};
165
- if (await pathExists(path)) raw = parse(await readFile(path, "utf8")) as Record<string, unknown>;
166
- if (!replace && raw[entry] !== undefined)
167
- throw new Error(`Context entry already exists: ${tab}/${concept}/${entry}`);
168
- if (raw.name === undefined) raw.name = conceptName;
169
- if (raw.description === undefined) raw.description = proposal.conceptDescription.trim();
170
- raw[entry] = { description, files };
171
- await mkdir(dirname(path), { recursive: true });
172
- const temporary = `${path}.${process.pid}.${Date.now()}.tmp`;
173
- await writeFile(temporary, stringify(raw), "utf8");
174
- await rename(temporary, path);
175
- });
176
- }
177
-
178
- export async function updateContextFiles(
179
- root: string,
180
- tabInput: string,
181
- conceptInput: string,
182
- entryInput: string,
183
- paths: readonly string[],
184
- action: "add" | "remove",
185
- ): Promise<string[]> {
186
- const tab = validSlug(tabInput, "Context tab");
187
- const concept = validSlug(conceptInput, "Context concept");
188
- const entry = validSlug(entryInput, "Context entry");
189
- const current = (await loadContextEntries(root)).find((item) => item.id === `${tab}/${concept}/${entry}`);
190
- if (!current) throw new Error(`Unknown context entry: ${tab}/${concept}/${entry}`);
191
- const changed =
192
- action === "add" ? await requireFiles(root, paths) : paths.map((path) => normalizeProjectPath(root, path));
193
- const changedSet = new Set(changed);
194
- const files =
195
- action === "add" ? [...current.files, ...changed] : current.files.filter((file) => !changedSet.has(file));
196
- if (files.length === 0) throw new Error("Context entries must contain at least one file");
197
- await writeContextEntry(
198
- root,
199
- {
200
- tab,
201
- concept,
202
- conceptName: current.conceptName,
203
- conceptDescription: current.conceptDescription,
204
- entry,
205
- description: current.description,
206
- files,
207
- },
208
- true,
209
- );
210
- return sortedUnique(changed);
211
- }
212
-
213
- export async function replaceContextFile(
214
- root: string,
215
- tab: string,
216
- concept: string,
217
- entry: string,
218
- fromInput: string,
219
- toInput: string,
220
- ): Promise<void> {
221
- const from = normalizeProjectPath(root, fromInput);
222
- const [to] = await requireFiles(root, [toInput]);
223
- const current = (await loadContextEntries(root)).find((item) => item.id === `${tab}/${concept}/${entry}`);
224
- if (!current) throw new Error(`Unknown context entry: ${tab}/${concept}/${entry}`);
225
- if (!current.files.includes(from)) throw new Error(`Context entry does not contain: ${from}`);
226
- await writeContextEntry(
227
- root,
228
- {
229
- tab: current.tab,
230
- concept: current.concept,
231
- conceptName: current.conceptName,
232
- conceptDescription: current.conceptDescription,
233
- entry: current.name,
234
- description: current.description,
235
- files: current.files.map((file) => (file === from ? to : file)),
236
- },
237
- true,
238
- );
239
- }
240
-
241
- export async function applyContextOperation(root: string, operation: ContextOperation): Promise<void> {
242
- if (operation.kind === "create") {
243
- await writeContextEntry(root, operation, false);
244
- return;
245
- }
246
- if (operation.kind === "replace-file") {
247
- await replaceContextFile(root, operation.tab, operation.concept, operation.entry, operation.from, operation.to);
248
- return;
249
- }
250
- await updateContextFiles(
251
- root,
252
- operation.tab,
253
- operation.concept,
254
- operation.entry,
255
- operation.files,
256
- operation.kind === "add-files" ? "add" : "remove",
257
- );
258
- }