@shanepadgett/tau-agent 0.27.0 → 0.28.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/extensions/context/sync.ts +1 -9
- package/extensions/context-pruning/prune.ts +2 -2
- package/extensions/explore/README.md +13 -21
- package/extensions/explore/ast/adapter.ts +97 -0
- package/extensions/explore/ast/engine.ts +470 -0
- package/extensions/explore/ast/format/ast-search.ts +97 -0
- package/extensions/explore/ast/format/composite.ts +26 -0
- package/extensions/explore/ast/format/context.ts +66 -0
- package/extensions/explore/ast/format/deps.ts +86 -0
- package/extensions/explore/ast/format/discover.ts +85 -0
- package/extensions/explore/ast/format/impact.ts +105 -0
- package/extensions/explore/ast/format/outline.ts +66 -0
- package/extensions/explore/ast/format/relationships.ts +81 -0
- package/extensions/explore/ast/format/show.ts +38 -0
- package/extensions/explore/ast/grammars/kotlin.wasm +0 -0
- package/extensions/explore/ast/grammars/manifest.json +41 -0
- package/extensions/explore/ast/grammars/manifest.ts +99 -0
- package/extensions/explore/ast/grammars/odin.wasm +0 -0
- package/extensions/explore/ast/grammars/swift.wasm +0 -0
- package/extensions/explore/ast/graph/file-graph.ts +360 -0
- package/extensions/explore/ast/graph/relationships.ts +597 -0
- package/extensions/explore/ast/identity.ts +119 -0
- package/extensions/explore/ast/ir.ts +130 -0
- package/extensions/explore/ast/languages/csharp-file-deps.ts +189 -0
- package/extensions/explore/ast/languages/csharp.ts +493 -0
- package/extensions/explore/ast/languages/file-dep-util.ts +120 -0
- package/extensions/explore/ast/languages/fixtures/sample.cs +17 -0
- package/extensions/explore/ast/languages/fixtures/sample.java +21 -0
- package/extensions/explore/ast/languages/fixtures/sample.kt +15 -0
- package/extensions/explore/ast/languages/fixtures/sample.odin +28 -0
- package/extensions/explore/ast/languages/fixtures/sample.rs +31 -0
- package/extensions/explore/ast/languages/fixtures/sample.swift +25 -0
- package/extensions/explore/ast/languages/go-file-deps.ts +139 -0
- package/extensions/explore/ast/languages/go.ts +545 -0
- package/extensions/explore/ast/languages/java.ts +412 -0
- package/extensions/explore/ast/languages/jvm-file-deps.ts +217 -0
- package/extensions/explore/ast/languages/kotlin.ts +611 -0
- package/extensions/explore/ast/languages/odin-file-deps.ts +175 -0
- package/extensions/explore/ast/languages/odin.ts +407 -0
- package/extensions/explore/ast/languages/rust-file-deps.ts +159 -0
- package/extensions/explore/ast/languages/rust.ts +506 -0
- package/extensions/explore/ast/languages/swift-file-deps.ts +59 -0
- package/extensions/explore/ast/languages/swift.ts +466 -0
- package/extensions/explore/ast/languages/tree.ts +220 -0
- package/extensions/explore/ast/languages/typescript-file-deps.ts +259 -0
- package/extensions/explore/ast/languages/typescript-package-surface.ts +440 -0
- package/extensions/explore/ast/languages/typescript.ts +716 -0
- package/extensions/explore/ast/markdown.ts +145 -0
- package/extensions/explore/ast/package-surface.ts +42 -0
- package/extensions/explore/ast/queries/ast-search.ts +306 -0
- package/extensions/explore/ast/queries/composite-target.ts +77 -0
- package/extensions/explore/ast/queries/context.ts +406 -0
- package/extensions/explore/ast/queries/discover.ts +427 -0
- package/extensions/explore/ast/queries/impact.ts +250 -0
- package/extensions/explore/ast/queries/outline.ts +203 -0
- package/extensions/explore/ast/queries/show.ts +262 -0
- package/extensions/explore/ast/query.ts +48 -0
- package/extensions/explore/ast/read/hook.ts +125 -0
- package/extensions/explore/ast/read/policy.ts +55 -0
- package/extensions/explore/ast/registry.ts +120 -0
- package/extensions/explore/ast/scan.ts +76 -0
- package/extensions/explore/ast/slice.ts +31 -0
- package/extensions/explore/ast/tools/ast-search.ts +138 -0
- package/extensions/explore/ast/tools/context.ts +126 -0
- package/extensions/explore/ast/tools/deps.ts +72 -0
- package/extensions/explore/ast/tools/discover.ts +208 -0
- package/extensions/explore/ast/tools/impact.ts +135 -0
- package/extensions/explore/ast/tools/outline.ts +200 -0
- package/extensions/explore/ast/tools/relationships.ts +160 -0
- package/extensions/explore/ast/tools/render.ts +121 -0
- package/extensions/explore/ast/tools/reverse-deps.ts +72 -0
- package/extensions/explore/ast/tools/show.ts +116 -0
- package/extensions/explore/guidance.ts +52 -0
- package/extensions/explore/index.ts +82 -92
- package/extensions/explore/read/autoread.ts +118 -0
- package/extensions/explore/settings.ts +49 -31
- package/extensions/explore/traverse.ts +254 -91
- package/extensions/handoff/index.ts +1 -1
- package/extensions/publish/index.ts +18 -85
- package/extensions/publish/publish-ui.ts +0 -4
- package/extensions/soul/README.md +7 -4
- package/extensions/soul/index.ts +11 -5
- package/extensions/soul/prompt.ts +31 -13
- package/extensions/soul/settings.ts +7 -2
- package/extensions/subagent/agents/context-sync.md +6 -10
- package/extensions/subagent/agents/review.md +24 -55
- package/extensions/subagent/agents/scout.md +38 -115
- package/extensions/subagent/run.ts +1 -1
- package/extensions/tau-help/help.md +2 -4
- package/package.json +6 -4
- package/schemas/tau.schema.json +35 -30
- package/{extensions/explore → shared}/autoread.ts +21 -7
- package/shared/events.ts +1 -1
- package/shared/full-file-knowledge.ts +49 -0
- package/extensions/explore/ast-guidance.ts +0 -67
- package/extensions/explore/ast-languages.ts +0 -61
- package/extensions/explore/ast-tools.ts +0 -1994
- package/extensions/explore/ast-worker.ts +0 -1025
- package/extensions/explore/find.ts +0 -147
- package/extensions/explore/full-file-knowledge.ts +0 -234
- package/extensions/explore/grep.ts +0 -749
- package/extensions/explore/limits.ts +0 -22
- package/extensions/explore/ls.ts +0 -113
- package/extensions/explore/orientation-state.ts +0 -280
- package/extensions/explore/path-display.ts +0 -41
- package/extensions/explore/path-tree.ts +0 -150
- package/extensions/explore/read-cache.ts +0 -273
- package/extensions/explore/read-snapshots.ts +0 -57
- package/extensions/explore/read-stats-panel.ts +0 -176
- package/extensions/explore/read-stats.ts +0 -138
- package/extensions/explore/read.ts +0 -372
- package/extensions/explore/result.ts +0 -29
- package/native-bin/darwin-arm64/tau-ast +0 -0
|
@@ -1,40 +1,80 @@
|
|
|
1
1
|
import type { Stats } from "node:fs";
|
|
2
2
|
import { lstat, opendir, readFile } from "node:fs/promises";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import { isAbsolute, join, matchesGlob, relative, resolve, sep } from "node:path";
|
|
4
|
+
|
|
5
|
+
/** Default recursive budgets for structural directory ops (path-conventions.md). */
|
|
6
|
+
export const DEFAULT_TRAVERSAL_BUDGETS = {
|
|
7
|
+
maxFiles: 2000,
|
|
8
|
+
maxSourceBytes: 64 * 1024 * 1024,
|
|
9
|
+
maxDepth: 32,
|
|
10
|
+
maxElapsedMs: 20_000,
|
|
11
|
+
} as const;
|
|
12
|
+
|
|
13
|
+
export type TraversalBudgets = {
|
|
14
|
+
maxFiles: number;
|
|
15
|
+
maxSourceBytes: number;
|
|
16
|
+
maxDepth: number;
|
|
17
|
+
maxElapsedMs: number;
|
|
18
|
+
};
|
|
5
19
|
|
|
6
20
|
export type PathKind = "file" | "dir";
|
|
7
21
|
|
|
8
|
-
export
|
|
22
|
+
export type TraversalHit = {
|
|
9
23
|
absolutePath: string;
|
|
10
24
|
displayPath: string;
|
|
11
25
|
type: PathKind;
|
|
12
26
|
stats: Stats;
|
|
13
27
|
depth: number;
|
|
14
|
-
|
|
15
|
-
}
|
|
28
|
+
};
|
|
16
29
|
|
|
17
|
-
export
|
|
30
|
+
export type TraversalLimit = "maxFiles" | "maxSourceBytes" | "maxDepth" | "maxElapsedMs" | "cancelled";
|
|
31
|
+
|
|
32
|
+
export type TraversalResult = {
|
|
33
|
+
entries: TraversalHit[];
|
|
34
|
+
/** Set when a budget or cancellation stopped the walk before the tree was complete. */
|
|
35
|
+
limit: TraversalLimit | undefined;
|
|
36
|
+
filesVisited: number;
|
|
37
|
+
sourceBytes: number;
|
|
38
|
+
elapsedMs: number;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type WalkOptions = {
|
|
18
42
|
cwd: string;
|
|
19
43
|
root: string;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
includeHidden
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
44
|
+
/** Include the root path itself when it is a file or directory. Default true. */
|
|
45
|
+
includeRoot?: boolean;
|
|
46
|
+
/** Descend into hidden path segments (name starts with `.`). Default false. */
|
|
47
|
+
includeHidden?: boolean;
|
|
48
|
+
/** Include paths matching gitignore-style rules. Default false. */
|
|
49
|
+
includeIgnored?: boolean;
|
|
50
|
+
/** Include common noise directories (node_modules, dist, …). Default false. */
|
|
51
|
+
includeNoise?: boolean;
|
|
52
|
+
/** Only yield files (skip directory entries in the result list). Default true. */
|
|
53
|
+
filesOnly?: boolean;
|
|
54
|
+
budgets?: Partial<TraversalBudgets>;
|
|
55
|
+
signal?: AbortSignal;
|
|
56
|
+
/**
|
|
57
|
+
* Called after each accepted file. Return false to stop early without a budget limit
|
|
58
|
+
* (caller owns the stop reason). Async allowed for cooperative yielding.
|
|
59
|
+
*/
|
|
60
|
+
onFile?: (hit: TraversalHit) => void | boolean | Promise<void | boolean>;
|
|
61
|
+
/**
|
|
62
|
+
* If set, files for which this returns false are skipped without counting toward budgets.
|
|
63
|
+
* Directories are not filtered here.
|
|
64
|
+
*/
|
|
65
|
+
matchFile?: (hit: TraversalHit) => boolean;
|
|
66
|
+
};
|
|
27
67
|
|
|
28
|
-
|
|
68
|
+
type IgnoreRule = {
|
|
29
69
|
baseDir: string;
|
|
30
70
|
pattern: string;
|
|
31
71
|
negated: boolean;
|
|
32
72
|
dirOnly: boolean;
|
|
33
73
|
hasSlash: boolean;
|
|
34
74
|
anchored: boolean;
|
|
35
|
-
}
|
|
75
|
+
};
|
|
36
76
|
|
|
37
|
-
const
|
|
77
|
+
const NOISE_NAMES = new Set([
|
|
38
78
|
".git",
|
|
39
79
|
"node_modules",
|
|
40
80
|
"dist",
|
|
@@ -47,20 +87,65 @@ const NOISE_PATH_NAMES = new Set([
|
|
|
47
87
|
"out",
|
|
48
88
|
]);
|
|
49
89
|
|
|
50
|
-
function
|
|
51
|
-
return
|
|
90
|
+
export function stripLeadingAt(value: string): string {
|
|
91
|
+
return value.startsWith("@") ? value.slice(1) : value;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function toSlashPath(value: string): string {
|
|
95
|
+
return value.replaceAll("\\", "/");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function resolveExplorePath(cwd: string, input: string): string {
|
|
99
|
+
const path = stripLeadingAt(input);
|
|
100
|
+
if (path.length === 0) return resolve(cwd);
|
|
101
|
+
return isAbsolute(path) ? resolve(path) : resolve(cwd, path);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function pathResolutionError(error: unknown, input: string): Error {
|
|
105
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
106
|
+
return new Error(`Path not found: ${stripLeadingAt(input)}`);
|
|
107
|
+
}
|
|
108
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isWithinPath(parent: string, child: string): boolean {
|
|
112
|
+
const rel = relative(resolve(parent), resolve(child));
|
|
113
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function formatPathForDisplay(absolutePath: string, cwd: string): string {
|
|
117
|
+
const resolvedCwd = resolve(cwd);
|
|
118
|
+
const resolvedPath = resolve(absolutePath);
|
|
119
|
+
const rel = relative(resolvedCwd, resolvedPath);
|
|
120
|
+
if (rel === "") return ".";
|
|
121
|
+
if (!rel.startsWith("..") && !isAbsolute(rel)) return toSlashPath(rel);
|
|
122
|
+
return toSlashPath(resolvedPath);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function relativeSlash(from: string, to: string): string {
|
|
126
|
+
const rel = relative(resolve(from), resolve(to));
|
|
127
|
+
return rel === "" ? "." : toSlashPath(rel);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function mergeBudgets(partial: Partial<TraversalBudgets> | undefined): TraversalBudgets {
|
|
131
|
+
return {
|
|
132
|
+
maxFiles: partial?.maxFiles ?? DEFAULT_TRAVERSAL_BUDGETS.maxFiles,
|
|
133
|
+
maxSourceBytes: partial?.maxSourceBytes ?? DEFAULT_TRAVERSAL_BUDGETS.maxSourceBytes,
|
|
134
|
+
maxDepth: partial?.maxDepth ?? DEFAULT_TRAVERSAL_BUDGETS.maxDepth,
|
|
135
|
+
maxElapsedMs: partial?.maxElapsedMs ?? DEFAULT_TRAVERSAL_BUDGETS.maxElapsedMs,
|
|
136
|
+
};
|
|
52
137
|
}
|
|
53
138
|
|
|
54
139
|
function pathSegments(displayPath: string): string[] {
|
|
55
140
|
return displayPath.split("/").filter((segment) => segment.length > 0 && segment !== ".");
|
|
56
141
|
}
|
|
57
142
|
|
|
58
|
-
function
|
|
143
|
+
function hasHiddenSegment(displayPath: string): boolean {
|
|
59
144
|
return pathSegments(displayPath).some((segment) => segment.startsWith("."));
|
|
60
145
|
}
|
|
61
146
|
|
|
62
|
-
function
|
|
63
|
-
return pathSegments(displayPath).some((segment) =>
|
|
147
|
+
function hasNoiseSegment(displayPath: string): boolean {
|
|
148
|
+
return pathSegments(displayPath).some((segment) => NOISE_NAMES.has(segment));
|
|
64
149
|
}
|
|
65
150
|
|
|
66
151
|
function parseIgnoreLine(baseDir: string, rawLine: string): IgnoreRule | undefined {
|
|
@@ -75,7 +160,14 @@ function parseIgnoreLine(baseDir: string, rawLine: string): IgnoreRule | undefin
|
|
|
75
160
|
if (anchored) line = line.slice(1);
|
|
76
161
|
line = toSlashPath(line);
|
|
77
162
|
if (!line) return undefined;
|
|
78
|
-
return {
|
|
163
|
+
return {
|
|
164
|
+
baseDir,
|
|
165
|
+
pattern: line,
|
|
166
|
+
negated,
|
|
167
|
+
dirOnly,
|
|
168
|
+
hasSlash: line.includes("/"),
|
|
169
|
+
anchored,
|
|
170
|
+
};
|
|
79
171
|
}
|
|
80
172
|
|
|
81
173
|
async function readIgnoreRules(baseDir: string): Promise<IgnoreRule[]> {
|
|
@@ -90,27 +182,27 @@ async function readIgnoreRules(baseDir: string): Promise<IgnoreRule[]> {
|
|
|
90
182
|
}
|
|
91
183
|
}
|
|
92
184
|
|
|
93
|
-
async function
|
|
94
|
-
directory
|
|
95
|
-
inherited:
|
|
96
|
-
): Promise<IgnoreRule[]> {
|
|
97
|
-
const rules = await readIgnoreRules(directory);
|
|
98
|
-
return rules.length === 0 ? [...inherited] : [...inherited, ...rules];
|
|
185
|
+
async function appendIgnoreRules(directory: string, inherited: readonly IgnoreRule[]): Promise<IgnoreRule[]> {
|
|
186
|
+
const local = await readIgnoreRules(directory);
|
|
187
|
+
return local.length === 0 ? [...inherited] : [...inherited, ...local];
|
|
99
188
|
}
|
|
100
189
|
|
|
101
190
|
async function collectIgnoreRulesToDirectory(cwd: string, directory: string): Promise<IgnoreRule[]> {
|
|
102
191
|
const resolvedCwd = resolve(cwd);
|
|
103
192
|
const resolvedDirectory = resolve(directory);
|
|
104
|
-
|
|
193
|
+
// Outside the session cwd (absolute foreign roots): load that directory's own ignore only.
|
|
194
|
+
if (!isWithinPath(resolvedCwd, resolvedDirectory)) {
|
|
195
|
+
return appendIgnoreRules(resolvedDirectory, []);
|
|
196
|
+
}
|
|
105
197
|
|
|
106
198
|
let current = resolvedCwd;
|
|
107
|
-
let rules
|
|
199
|
+
let rules = await appendIgnoreRules(current, []);
|
|
108
200
|
const rel = relativeSlash(resolvedCwd, resolvedDirectory);
|
|
109
201
|
if (rel === ".") return rules;
|
|
110
202
|
|
|
111
203
|
for (const segment of rel.split("/")) {
|
|
112
204
|
current = join(current, segment.split("/").join(sep));
|
|
113
|
-
rules = await
|
|
205
|
+
rules = await appendIgnoreRules(current, rules);
|
|
114
206
|
}
|
|
115
207
|
return rules;
|
|
116
208
|
}
|
|
@@ -131,31 +223,43 @@ function isIgnored(absolutePath: string, kind: PathKind, rules: readonly IgnoreR
|
|
|
131
223
|
return ignored;
|
|
132
224
|
}
|
|
133
225
|
|
|
134
|
-
function
|
|
226
|
+
function shouldSkip(
|
|
135
227
|
absolutePath: string,
|
|
136
228
|
kind: PathKind,
|
|
137
229
|
rules: readonly IgnoreRule[],
|
|
138
|
-
options:
|
|
230
|
+
options: {
|
|
231
|
+
walkRoot: string;
|
|
232
|
+
includeHidden: boolean;
|
|
233
|
+
includeIgnored: boolean;
|
|
234
|
+
includeNoise: boolean;
|
|
235
|
+
},
|
|
139
236
|
): boolean {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
237
|
+
// Hidden/noise are relative to the walk root — not the session cwd display path.
|
|
238
|
+
// Otherwise absolute roots under ~/.local (etc.) look entirely "hidden".
|
|
239
|
+
const rel = relativeSlash(options.walkRoot, absolutePath);
|
|
240
|
+
const underRoot = rel === "." || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
241
|
+
const segmentPath = underRoot && rel !== "." ? rel : "";
|
|
242
|
+
if (!options.includeHidden && segmentPath.length > 0 && hasHiddenSegment(segmentPath)) return true;
|
|
243
|
+
if (!options.includeNoise && segmentPath.length > 0 && hasNoiseSegment(segmentPath)) return true;
|
|
143
244
|
return !options.includeIgnored && isIgnored(absolutePath, kind, rules);
|
|
144
245
|
}
|
|
145
246
|
|
|
146
|
-
function
|
|
147
|
-
return
|
|
247
|
+
function entryKind(stats: Stats): PathKind {
|
|
248
|
+
return stats.isDirectory() ? "dir" : "file";
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function sortHits(hits: TraversalHit[]): TraversalHit[] {
|
|
252
|
+
return hits.sort((a, b) => {
|
|
148
253
|
if (a.type !== b.type) return a.type === "dir" ? -1 : 1;
|
|
149
254
|
return a.displayPath.localeCompare(b.displayPath);
|
|
150
255
|
});
|
|
151
256
|
}
|
|
152
257
|
|
|
153
|
-
async function
|
|
258
|
+
async function listNames(directory: string, cwd: string): Promise<string[]> {
|
|
154
259
|
try {
|
|
155
260
|
const names: string[] = [];
|
|
156
261
|
for await (const entry of await opendir(directory)) {
|
|
157
262
|
names.push(entry.name);
|
|
158
|
-
if (limit !== undefined && names.length >= limit) break;
|
|
159
263
|
}
|
|
160
264
|
return names;
|
|
161
265
|
} catch {
|
|
@@ -163,46 +267,109 @@ async function readDirectoryNames(directory: string, cwd: string, limit: number
|
|
|
163
267
|
}
|
|
164
268
|
}
|
|
165
269
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
270
|
+
/**
|
|
271
|
+
* Ignore-aware directory walk for structural scans.
|
|
272
|
+
* Budgets (files / source bytes / depth / elapsed) are separate from model output limits.
|
|
273
|
+
* Hitting a budget sets `result.limit` — not silent complete-tree success.
|
|
274
|
+
*/
|
|
275
|
+
export async function walkPaths(options: WalkOptions): Promise<TraversalResult> {
|
|
276
|
+
const budgets = mergeBudgets(options.budgets);
|
|
277
|
+
const includeRoot = options.includeRoot !== false;
|
|
278
|
+
const includeHidden = options.includeHidden === true;
|
|
279
|
+
const includeIgnored = options.includeIgnored === true;
|
|
280
|
+
const includeNoise = options.includeNoise === true;
|
|
281
|
+
const filesOnly = options.filesOnly !== false;
|
|
282
|
+
const started = Date.now();
|
|
283
|
+
|
|
284
|
+
const entries: TraversalHit[] = [];
|
|
285
|
+
let filesVisited = 0;
|
|
286
|
+
let sourceBytes = 0;
|
|
287
|
+
let limit: TraversalLimit | undefined;
|
|
288
|
+
|
|
289
|
+
const elapsed = () => Date.now() - started;
|
|
290
|
+
|
|
291
|
+
const budgetHit = (): TraversalLimit | undefined => {
|
|
292
|
+
if (options.signal?.aborted) return "cancelled";
|
|
293
|
+
if (elapsed() >= budgets.maxElapsedMs) return "maxElapsedMs";
|
|
294
|
+
if (filesVisited >= budgets.maxFiles) return "maxFiles";
|
|
295
|
+
if (sourceBytes > budgets.maxSourceBytes) return "maxSourceBytes";
|
|
296
|
+
return undefined;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const stopIfNeeded = (): boolean => {
|
|
300
|
+
const hit = budgetHit();
|
|
301
|
+
if (hit === undefined) return false;
|
|
302
|
+
limit = limit ?? hit;
|
|
303
|
+
return true;
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
async function pushFile(hit: TraversalHit): Promise<boolean> {
|
|
307
|
+
if (options.matchFile !== undefined && !options.matchFile(hit)) return true;
|
|
308
|
+
if (stopIfNeeded()) return false;
|
|
309
|
+
if (filesVisited >= budgets.maxFiles) {
|
|
310
|
+
limit = "maxFiles";
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
const nextBytes = sourceBytes + hit.stats.size;
|
|
314
|
+
// Allow the first file even if it alone exceeds the byte budget; report the limit.
|
|
315
|
+
if (filesVisited > 0 && nextBytes > budgets.maxSourceBytes) {
|
|
316
|
+
limit = "maxSourceBytes";
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
filesVisited += 1;
|
|
320
|
+
sourceBytes = nextBytes;
|
|
321
|
+
if (sourceBytes > budgets.maxSourceBytes) limit = "maxSourceBytes";
|
|
322
|
+
entries.push(hit);
|
|
323
|
+
const cont = await options.onFile?.(hit);
|
|
324
|
+
if (cont === false) return false;
|
|
325
|
+
return !stopIfNeeded();
|
|
186
326
|
}
|
|
187
327
|
|
|
188
|
-
|
|
189
|
-
|
|
328
|
+
const root = resolveExplorePath(options.cwd, options.root);
|
|
329
|
+
const rootStats = await lstat(root);
|
|
330
|
+
const rootKind = entryKind(rootStats);
|
|
331
|
+
const rootDir = rootKind === "dir" ? root : resolve(root, "..");
|
|
332
|
+
const walkRoot = rootKind === "dir" ? root : rootDir;
|
|
333
|
+
const skipOpts = { walkRoot, includeHidden, includeIgnored, includeNoise };
|
|
334
|
+
const rootRules = includeIgnored ? [] : await collectIgnoreRulesToDirectory(options.cwd, rootDir);
|
|
190
335
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
336
|
+
if (includeRoot) {
|
|
337
|
+
const rootHit: TraversalHit = {
|
|
338
|
+
absolutePath: root,
|
|
339
|
+
displayPath: formatPathForDisplay(root, options.cwd),
|
|
340
|
+
type: rootKind,
|
|
341
|
+
stats: rootStats,
|
|
342
|
+
depth: 0,
|
|
343
|
+
};
|
|
344
|
+
if (rootKind === "file") {
|
|
345
|
+
await pushFile(rootHit);
|
|
346
|
+
return { entries, limit, filesVisited, sourceBytes, elapsedMs: elapsed() };
|
|
347
|
+
}
|
|
348
|
+
if (!filesOnly) entries.push(rootHit);
|
|
349
|
+
} else if (rootKind === "file") {
|
|
350
|
+
return { entries, limit, filesVisited, sourceBytes, elapsedMs: elapsed() };
|
|
351
|
+
}
|
|
194
352
|
|
|
195
|
-
|
|
196
|
-
|
|
353
|
+
async function walkDir(directory: string, depth: number, rules: readonly IgnoreRule[]): Promise<void> {
|
|
354
|
+
if (stopIfNeeded()) return;
|
|
355
|
+
if (depth >= budgets.maxDepth) {
|
|
356
|
+
limit = limit ?? "maxDepth";
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
197
359
|
|
|
198
|
-
const
|
|
360
|
+
const names = await listNames(directory, options.cwd);
|
|
361
|
+
const children: TraversalHit[] = [];
|
|
199
362
|
for (const name of names) {
|
|
200
|
-
if (
|
|
201
|
-
const childPath =
|
|
202
|
-
|
|
203
|
-
|
|
363
|
+
if (stopIfNeeded()) return;
|
|
364
|
+
const childPath = join(directory, name);
|
|
365
|
+
let stats: Stats;
|
|
366
|
+
try {
|
|
367
|
+
stats = await lstat(childPath);
|
|
368
|
+
} catch {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
204
371
|
const kind = entryKind(stats);
|
|
205
|
-
if (
|
|
372
|
+
if (shouldSkip(childPath, kind, rules, skipOpts)) continue;
|
|
206
373
|
children.push({
|
|
207
374
|
absolutePath: childPath,
|
|
208
375
|
displayPath: formatPathForDisplay(childPath, options.cwd),
|
|
@@ -212,26 +379,22 @@ export async function collectPaths(options: CollectPathOptions): Promise<Travers
|
|
|
212
379
|
});
|
|
213
380
|
}
|
|
214
381
|
|
|
215
|
-
for (const child of
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
? rules
|
|
221
|
-
: await appendIgnoreRulesForDirectory(child.absolutePath, rules);
|
|
222
|
-
const visibleChildren = await walkDirectory(child.absolutePath, child.depth, childRules);
|
|
223
|
-
if (visibleChildren === 0 && (options.maxDepth === undefined || child.depth < options.maxDepth)) {
|
|
224
|
-
child.empty = true;
|
|
382
|
+
for (const child of sortHits(children)) {
|
|
383
|
+
if (stopIfNeeded()) return;
|
|
384
|
+
if (child.type === "file") {
|
|
385
|
+
if (!(await pushFile(child))) return;
|
|
386
|
+
continue;
|
|
225
387
|
}
|
|
388
|
+
if (!filesOnly) entries.push(child);
|
|
389
|
+
if (child.depth >= budgets.maxDepth) {
|
|
390
|
+
limit = limit ?? "maxDepth";
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
const childRules = includeIgnored ? rules : await appendIgnoreRules(child.absolutePath, rules);
|
|
394
|
+
await walkDir(child.absolutePath, child.depth, childRules);
|
|
226
395
|
}
|
|
227
|
-
|
|
228
|
-
return children.length;
|
|
229
396
|
}
|
|
230
397
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (rootEntry && visibleChildren === 0 && (options.maxDepth === undefined || options.maxDepth > 0)) {
|
|
234
|
-
rootEntry.empty = true;
|
|
235
|
-
}
|
|
236
|
-
return entries;
|
|
398
|
+
if (rootKind === "dir") await walkDir(root, 0, rootRules);
|
|
399
|
+
return { entries, limit, filesVisited, sourceBytes, elapsedMs: elapsed() };
|
|
237
400
|
}
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
type ExtensionAPI,
|
|
7
7
|
serializeConversation,
|
|
8
8
|
} from "@earendil-works/pi-coding-agent";
|
|
9
|
-
import { prepareAutoreadMessage, type AutoreadDetails } from "
|
|
9
|
+
import { prepareAutoreadMessage, type AutoreadDetails } from "../../shared/autoread.ts";
|
|
10
10
|
import { generateToolValidated, resolveCandidates } from "../../shared/model-fallback/index.ts";
|
|
11
11
|
import { errorText } from "../../shared/text.ts";
|
|
12
12
|
import { buildHandoffRequest, type HandoffDraft, handoffDraftFromToolInput, HANDOFF_TOOL } from "./model.ts";
|
|
@@ -11,8 +11,6 @@ const WORKFLOW_NAME = "publish.yml";
|
|
|
11
11
|
const WORKFLOW_WAIT_MS = 10 * 60_000;
|
|
12
12
|
const POLL_INTERVAL_MS = 5_000;
|
|
13
13
|
const DEFAULT_COMMAND_TIMEOUT_MS = 120_000;
|
|
14
|
-
const CARGO_BUILD_TIMEOUT_MS = 15 * 60_000;
|
|
15
|
-
const TAU_AST_SCRIPT = "packages/agent/scripts/package-tau-ast.ts";
|
|
16
14
|
|
|
17
15
|
type Bump = "patch" | "minor" | "major";
|
|
18
16
|
|
|
@@ -68,8 +66,6 @@ async function publish(pi: ExtensionAPI, ctx: ExtensionCommandContext): Promise<
|
|
|
68
66
|
if (!repo) throw new Error("No git repository found.");
|
|
69
67
|
if (repo.fileCount > 0)
|
|
70
68
|
throw new Error("Working tree is not clean. Commit or stash every change before publishing.");
|
|
71
|
-
if (process.platform !== "darwin" || process.arch !== "arm64")
|
|
72
|
-
throw new Error(`/publish requires darwin-arm64; this host is ${process.platform}-${process.arch}.`);
|
|
73
69
|
await run(pi, ctx, "gh", ["auth", "status"], repo.root, DEFAULT_COMMAND_TIMEOUT_MS);
|
|
74
70
|
await run(pi, ctx, "npm", ["run", "check:package-sources"], repo.root, DEFAULT_COMMAND_TIMEOUT_MS);
|
|
75
71
|
|
|
@@ -129,87 +125,24 @@ async function completePublish(
|
|
|
129
125
|
progress?.update("write package versions");
|
|
130
126
|
await writeReleaseVersion(root, manifests, version);
|
|
131
127
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
progress?.update("stage tau-ast");
|
|
151
|
-
await run(
|
|
152
|
-
pi,
|
|
153
|
-
ctx,
|
|
154
|
-
process.execPath,
|
|
155
|
-
["--experimental-strip-types", TAU_AST_SCRIPT, "stage"],
|
|
156
|
-
root,
|
|
157
|
-
DEFAULT_COMMAND_TIMEOUT_MS,
|
|
158
|
-
);
|
|
159
|
-
progress?.update("smoke test tau-ast");
|
|
160
|
-
await run(
|
|
161
|
-
pi,
|
|
162
|
-
ctx,
|
|
163
|
-
process.execPath,
|
|
164
|
-
["--experimental-strip-types", TAU_AST_SCRIPT, "smoke"],
|
|
165
|
-
root,
|
|
166
|
-
DEFAULT_COMMAND_TIMEOUT_MS,
|
|
167
|
-
);
|
|
168
|
-
progress?.update("npm pack --dry-run @shanepadgett/tau-tui");
|
|
169
|
-
await run(
|
|
170
|
-
pi,
|
|
171
|
-
ctx,
|
|
172
|
-
"npm",
|
|
173
|
-
["pack", "--dry-run", "--workspace", "@shanepadgett/tau-tui"],
|
|
174
|
-
root,
|
|
175
|
-
DEFAULT_COMMAND_TIMEOUT_MS,
|
|
176
|
-
);
|
|
177
|
-
progress?.update("npm pack --dry-run @shanepadgett/tau-agent");
|
|
178
|
-
await run(
|
|
179
|
-
pi,
|
|
180
|
-
ctx,
|
|
181
|
-
"npm",
|
|
182
|
-
["pack", "--dry-run", "--workspace", "@shanepadgett/tau-agent"],
|
|
183
|
-
root,
|
|
184
|
-
DEFAULT_COMMAND_TIMEOUT_MS,
|
|
185
|
-
);
|
|
186
|
-
progress?.update("verify packed tau-ast");
|
|
187
|
-
await run(
|
|
188
|
-
pi,
|
|
189
|
-
ctx,
|
|
190
|
-
process.execPath,
|
|
191
|
-
["--experimental-strip-types", TAU_AST_SCRIPT, "verify-pack"],
|
|
192
|
-
root,
|
|
193
|
-
DEFAULT_COMMAND_TIMEOUT_MS,
|
|
194
|
-
);
|
|
195
|
-
} finally {
|
|
196
|
-
await run(
|
|
197
|
-
pi,
|
|
198
|
-
ctx,
|
|
199
|
-
process.execPath,
|
|
200
|
-
["--experimental-strip-types", TAU_AST_SCRIPT, "clean"],
|
|
201
|
-
root,
|
|
202
|
-
DEFAULT_COMMAND_TIMEOUT_MS,
|
|
203
|
-
);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const trackedNativeFiles = await git.run(["ls-files", "--", "packages/agent/native-bin"], { cwd: root });
|
|
207
|
-
const stagedNativeFiles = await git.run(["diff", "--cached", "--name-only", "--", "packages/agent/native-bin"], {
|
|
208
|
-
cwd: root,
|
|
209
|
-
});
|
|
210
|
-
if (trackedNativeFiles || stagedNativeFiles)
|
|
211
|
-
throw new Error("Release blocked: packages/agent/native-bin/ contains tracked or staged files.");
|
|
212
|
-
|
|
128
|
+
progress?.update("npm pack --dry-run @shanepadgett/tau-tui");
|
|
129
|
+
await run(
|
|
130
|
+
pi,
|
|
131
|
+
ctx,
|
|
132
|
+
"npm",
|
|
133
|
+
["pack", "--dry-run", "--workspace", "@shanepadgett/tau-tui"],
|
|
134
|
+
root,
|
|
135
|
+
DEFAULT_COMMAND_TIMEOUT_MS,
|
|
136
|
+
);
|
|
137
|
+
progress?.update("npm pack --dry-run @shanepadgett/tau-agent");
|
|
138
|
+
await run(
|
|
139
|
+
pi,
|
|
140
|
+
ctx,
|
|
141
|
+
"npm",
|
|
142
|
+
["pack", "--dry-run", "--workspace", "@shanepadgett/tau-agent"],
|
|
143
|
+
root,
|
|
144
|
+
DEFAULT_COMMAND_TIMEOUT_MS,
|
|
145
|
+
);
|
|
213
146
|
if (tag) {
|
|
214
147
|
progress?.update("git add release files");
|
|
215
148
|
await git.run(["add", ...PACKAGE_PATHS, LOCKFILE_PATH], { cwd: root });
|
|
@@ -4,12 +4,8 @@ import { ToolPanel } from "@shanepadgett/tau-tui";
|
|
|
4
4
|
|
|
5
5
|
const RELEASE_STEPS = [
|
|
6
6
|
"write package versions",
|
|
7
|
-
"build tau-ast darwin-arm64",
|
|
8
|
-
"stage tau-ast",
|
|
9
|
-
"smoke test tau-ast",
|
|
10
7
|
"npm pack --dry-run @shanepadgett/tau-tui",
|
|
11
8
|
"npm pack --dry-run @shanepadgett/tau-agent",
|
|
12
|
-
"verify packed tau-ast",
|
|
13
9
|
"git add release files",
|
|
14
10
|
"git commit",
|
|
15
11
|
"git tag",
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
# Soul
|
|
2
2
|
|
|
3
|
-
Soul
|
|
3
|
+
Soul shapes how Tau works and talks by adding two independent sections to Pi's native assistant prompt:
|
|
4
|
+
|
|
5
|
+
- **ponytail** — a lazy-senior-dev build ethos: do the smallest correct thing, reuse before writing, YAGNI, fix bugs at the root, never cut validation, security, or accessibility.
|
|
6
|
+
- **caveman** — a terse communication style: drop filler, keep technical substance exact, quote the shortest decisive line, spell out anything where brevity would risk safety.
|
|
4
7
|
|
|
5
8
|
Pi continues to own tool guidance, project instructions, skills, documentation paths, custom prompts, and working-directory context.
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
Toggle each section in Tau settings (both on by default):
|
|
8
11
|
|
|
9
12
|
```json
|
|
10
13
|
{
|
|
11
14
|
"extensions": {
|
|
12
|
-
"soul": { "
|
|
15
|
+
"soul": { "ponytail": true, "caveman": false }
|
|
13
16
|
}
|
|
14
17
|
}
|
|
15
18
|
```
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
Settings take effect on session start.
|
|
18
21
|
|
|
19
22
|
After changing this extension, run `/reload` before testing the new behavior.
|
package/extensions/soul/index.ts
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { loadTauExtensionSettings } from "../../shared/settings/load.ts";
|
|
3
|
-
import {
|
|
3
|
+
import { CAVEMAN_STYLE, PONYTAIL_ETHOS } from "./prompt.ts";
|
|
4
4
|
import soulSettings from "./settings.ts";
|
|
5
5
|
|
|
6
6
|
export default function soulExtension(pi: ExtensionAPI): void {
|
|
7
|
-
let
|
|
7
|
+
let ponytail = true;
|
|
8
|
+
let caveman = true;
|
|
8
9
|
|
|
9
10
|
pi.on("session_start", async (_event, ctx) => {
|
|
10
|
-
|
|
11
|
+
const settings = await loadTauExtensionSettings(ctx, soulSettings);
|
|
12
|
+
ponytail = settings.ponytail;
|
|
13
|
+
caveman = settings.caveman;
|
|
11
14
|
});
|
|
12
15
|
|
|
13
16
|
pi.on("before_agent_start", (event) => {
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
const sections: string[] = [];
|
|
18
|
+
if (ponytail) sections.push(PONYTAIL_ETHOS);
|
|
19
|
+
if (caveman) sections.push(CAVEMAN_STYLE);
|
|
20
|
+
if (sections.length === 0) return undefined;
|
|
21
|
+
return { systemPrompt: [event.systemPrompt, ...sections].join("\n\n") };
|
|
16
22
|
});
|
|
17
23
|
}
|