@shanepadgett/tau-agent 0.33.1 → 0.35.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/context.md +29 -20
- package/docs/extending-tau-agent.md +63 -24
- package/extensions/appshot/index.ts +112 -111
- package/extensions/aside/README.md +7 -0
- package/extensions/aside/index.ts +168 -0
- package/extensions/aside/panel.ts +83 -0
- package/extensions/bash-approval/README.md +20 -0
- package/extensions/bash-approval/index.ts +235 -0
- package/extensions/bash-approval/settings.ts +24 -0
- package/extensions/branch/index.ts +38 -21
- package/extensions/cache-diagnostics/index.ts +70 -58
- package/extensions/checkpoint/README.md +9 -0
- package/extensions/checkpoint/checkpoint-budget.ts +66 -0
- package/extensions/checkpoint/checkpoint.ts +279 -0
- package/extensions/checkpoint/index.ts +102 -0
- package/extensions/checkpoint/messages.ts +170 -0
- package/extensions/checkpoint/prompt.ts +24 -0
- package/extensions/checkpoint/settings.ts +27 -0
- package/extensions/commit/commit-effort.ts +3 -3
- package/extensions/commit/commit-plan.ts +2 -2
- package/extensions/context/README.md +12 -8
- package/extensions/context/definitions.ts +178 -55
- package/extensions/context/index.ts +192 -160
- package/extensions/context/panel.ts +57 -37
- package/extensions/context/sync.ts +139 -89
- package/extensions/context/validation.ts +37 -25
- package/extensions/effort/README.md +1 -1
- package/extensions/effort/index.ts +3 -3
- package/extensions/effort/state.ts +3 -18
- package/extensions/explore/guidance.ts +6 -47
- package/extensions/explore/index.ts +38 -53
- package/extensions/explore/{ast/read → read}/hook.ts +12 -7
- package/extensions/explore/{ast/tools → tools}/ast-search.ts +15 -19
- package/extensions/explore/{ast/tools → tools}/context.ts +16 -23
- package/extensions/explore/{ast/tools → tools}/deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/discover.ts +15 -17
- package/extensions/explore/{ast/tools → tools}/impact.ts +20 -23
- package/extensions/explore/{ast/tools → tools}/outline.ts +21 -24
- package/extensions/explore/{ast/tools → tools}/relationships.ts +23 -32
- package/extensions/explore/{ast/tools → tools}/render.ts +1 -1
- package/extensions/explore/{ast/tools → tools}/reverse-deps.ts +11 -15
- package/extensions/explore/{ast/tools → tools}/show.ts +16 -19
- package/extensions/explore/tools/target-params.ts +9 -0
- package/extensions/footer/index.ts +75 -56
- package/extensions/handoff/index.ts +8 -30
- package/extensions/image-gen/index.ts +46 -40
- package/extensions/patch/executor.ts +190 -135
- package/extensions/patch/matcher.ts +82 -48
- package/extensions/patch/parser.ts +149 -105
- package/extensions/patch/render.ts +32 -39
- package/extensions/patch/summary.ts +30 -26
- package/extensions/ready/README.md +7 -0
- package/extensions/ready/index.ts +58 -0
- package/extensions/ready/model.ts +128 -0
- package/extensions/ready/packs.ts +272 -0
- package/extensions/ready/render.ts +445 -0
- package/extensions/ready/scan.ts +760 -0
- package/extensions/reference/index.ts +2 -0
- package/extensions/reference/panel.ts +94 -31
- package/extensions/review/README.md +14 -6
- package/extensions/review/index.ts +58 -99
- package/extensions/review/model.ts +13 -33
- package/extensions/review/session.ts +9 -23
- package/extensions/runtime-context/context.ts +30 -26
- package/extensions/script-runner/README.md +2 -2
- package/extensions/script-runner/index.ts +122 -82
- package/extensions/silent-command-runner/index.ts +38 -23
- package/extensions/soul/README.md +2 -2
- package/extensions/soul/index.ts +4 -4
- package/extensions/soul/prompt.ts +6 -10
- package/extensions/soul/settings.ts +6 -3
- package/extensions/subagent/agents/context-sync.md +185 -33
- package/extensions/subagent/agents/scout.md +2 -2
- package/extensions/subagent/agents/web-research.md +2 -2
- package/extensions/subagent/agents.ts +137 -84
- package/extensions/subagent/cmux-dashboard.ts +39 -30
- package/extensions/subagent/index.ts +79 -59
- package/extensions/subagent/resume.ts +1 -1
- package/extensions/subagent/run.ts +308 -193
- package/extensions/subagent/runtime.ts +781 -343
- package/extensions/tau/index.ts +47 -38
- package/extensions/tau-help/help.md +20 -8
- package/extensions/tool-loader/README.md +4 -2
- package/extensions/tool-loader/index.ts +77 -52
- package/extensions/web/index.ts +6 -3
- package/extensions/web/webfetch.ts +114 -86
- package/package.json +3 -3
- package/schemas/tau.schema.json +38 -35
- package/shared/bounded-text-result.ts +1 -0
- package/shared/checkpoint-visibility.ts +9 -0
- package/shared/events.ts +28 -10
- package/shared/isolated-session.ts +72 -30
- package/shared/model-effort.ts +23 -22
- package/shared/model-fallback/index.ts +66 -41
- package/shared/settings/load.ts +45 -38
- package/shared/temporary-output-store.ts +16 -10
- package/{extensions/explore → src}/ast/adapter.ts +11 -2
- package/{extensions/explore → src}/ast/engine.ts +54 -38
- package/{extensions/explore → src}/ast/format/ast-search.ts +15 -6
- package/{extensions/explore → src}/ast/format/context.ts +3 -6
- package/src/ast/format/deps.ts +94 -0
- package/{extensions/explore → src}/ast/format/discover.ts +2 -2
- package/{extensions/explore → src}/ast/format/impact.ts +9 -11
- package/{extensions/explore → src}/ast/format/outline.ts +2 -2
- package/{extensions/explore → src}/ast/format/relationships.ts +11 -7
- package/{extensions/explore → src}/ast/format/show.ts +1 -1
- package/src/ast/format/targeted-result.ts +20 -0
- package/src/ast/grammars/c_sharp.wasm +0 -0
- package/{extensions/explore → src}/ast/grammars/manifest.json +9 -12
- package/{extensions/explore → src}/ast/grammars/manifest.ts +0 -4
- package/{extensions/explore → src}/ast/graph/file-graph.ts +210 -98
- package/{extensions/explore → src}/ast/graph/relationships.ts +285 -72
- package/{extensions/explore → src}/ast/ir.ts +6 -0
- package/src/ast/languages/csharp-file-deps.ts +90 -0
- package/{extensions/explore → src}/ast/languages/csharp.ts +9 -40
- package/src/ast/languages/dotted-index.ts +131 -0
- package/{extensions/explore → src}/ast/languages/file-dep-util.ts +22 -13
- package/src/ast/languages/fixtures/sample.cs +53 -0
- package/src/ast/languages/fixtures/sample.cs.expected.json +156 -0
- package/src/ast/languages/fixtures/sample.go +37 -0
- package/src/ast/languages/fixtures/sample.go.expected.json +96 -0
- package/src/ast/languages/fixtures/sample.java +40 -0
- package/src/ast/languages/fixtures/sample.java.expected.json +126 -0
- package/src/ast/languages/fixtures/sample.kt +31 -0
- package/src/ast/languages/fixtures/sample.kt.expected.json +134 -0
- package/src/ast/languages/fixtures/sample.md +23 -0
- package/src/ast/languages/fixtures/sample.md.expected.json +44 -0
- package/src/ast/languages/fixtures/sample.rs +54 -0
- package/src/ast/languages/fixtures/sample.rs.expected.json +178 -0
- package/src/ast/languages/fixtures/sample.swift +45 -0
- package/src/ast/languages/fixtures/sample.swift.expected.json +147 -0
- package/src/ast/languages/fixtures/sample.ts +45 -0
- package/src/ast/languages/fixtures/sample.ts.expected.json +141 -0
- package/src/ast/languages/fixtures/sample.tsx +36 -0
- package/src/ast/languages/fixtures/sample.tsx.expected.json +59 -0
- package/{extensions/explore → src}/ast/languages/go-file-deps.ts +6 -0
- package/{extensions/explore → src}/ast/languages/go.ts +21 -3
- package/{extensions/explore → src}/ast/languages/java.ts +5 -38
- package/src/ast/languages/jvm-file-deps.ts +135 -0
- package/{extensions/explore → src}/ast/languages/kotlin.ts +5 -39
- package/{extensions/explore → src}/ast/languages/rust-file-deps.ts +3 -3
- package/{extensions/explore → src}/ast/languages/rust.ts +21 -13
- package/{extensions/explore → src}/ast/languages/swift-file-deps.ts +22 -15
- package/{extensions/explore → src}/ast/languages/swift.ts +9 -39
- package/{extensions/explore → src}/ast/languages/tree.ts +47 -0
- package/{extensions/explore → src}/ast/languages/typescript-file-deps.ts +108 -0
- package/{extensions/explore → src}/ast/languages/typescript-package-surface.ts +4 -3
- package/{extensions/explore → src}/ast/languages/typescript.ts +3 -5
- package/{extensions/explore → src}/ast/markdown.ts +72 -47
- package/{extensions/explore → src}/ast/queries/ast-search.ts +164 -146
- package/{extensions/explore → src}/ast/queries/composite-target.ts +1 -1
- package/{extensions/explore → src}/ast/queries/context.ts +116 -80
- package/{extensions/explore → src}/ast/queries/discover.ts +154 -105
- package/src/ast/queries/impact.ts +291 -0
- package/{extensions/explore → src}/ast/queries/outline.ts +4 -2
- package/{extensions/explore → src}/ast/queries/show.ts +46 -42
- package/{extensions/explore → src}/ast/query.ts +1 -1
- package/{extensions/explore → src}/ast/registry.ts +1 -2
- package/{extensions/explore → src}/ast/scan.ts +1 -1
- package/src/ast/session.ts +50 -0
- package/{extensions/explore → src}/ast/slice.ts +8 -0
- package/{extensions/explore → src/ast}/traverse.ts +32 -20
- package/src/file-injection/index.ts +547 -0
- package/src/image-generation/client.ts +50 -31
- package/src/image-generation/index.ts +177 -134
- package/src/index.ts +2 -0
- package/src/tool-loading/index.ts +58 -0
- package/extensions/context/evidence.ts +0 -517
- package/extensions/context/projection.ts +0 -141
- package/extensions/context/state.ts +0 -30
- package/extensions/context/write-scope.ts +0 -109
- package/extensions/explore/ast/format/deps.ts +0 -86
- package/extensions/explore/ast/grammars/odin.wasm +0 -0
- package/extensions/explore/ast/languages/csharp-file-deps.ts +0 -189
- package/extensions/explore/ast/languages/fixtures/sample.cs +0 -17
- package/extensions/explore/ast/languages/fixtures/sample.java +0 -21
- package/extensions/explore/ast/languages/fixtures/sample.kt +0 -15
- package/extensions/explore/ast/languages/fixtures/sample.odin +0 -28
- package/extensions/explore/ast/languages/fixtures/sample.rs +0 -31
- package/extensions/explore/ast/languages/fixtures/sample.swift +0 -25
- package/extensions/explore/ast/languages/jvm-file-deps.ts +0 -217
- package/extensions/explore/ast/languages/odin-file-deps.ts +0 -175
- package/extensions/explore/ast/languages/odin.ts +0 -407
- package/extensions/explore/ast/queries/impact.ts +0 -250
- package/extensions/explore/outline-injection.ts +0 -151
- package/extensions/explore/read/autoread.ts +0 -118
- package/extensions/review/panel.ts +0 -128
- package/extensions/working-memory/README.md +0 -17
- package/extensions/working-memory/checkpoint.ts +0 -242
- package/extensions/working-memory/index.ts +0 -341
- package/extensions/working-memory/memory.ts +0 -235
- package/extensions/working-memory/render.ts +0 -185
- package/extensions/working-memory/settings.ts +0 -38
- package/extensions/working-memory/state.ts +0 -158
- package/shared/autoread.ts +0 -234
- package/shared/context-messages.ts +0 -19
- package/shared/outline-injection.ts +0 -56
- /package/{extensions/explore → src}/ast/format/composite.ts +0 -0
- /package/{extensions/explore → src}/ast/grammars/kotlin.wasm +0 -0
- /package/{extensions/explore → src}/ast/grammars/swift.wasm +0 -0
- /package/{extensions/explore → src}/ast/identity.ts +0 -0
- /package/{extensions/explore → src}/ast/package-surface.ts +0 -0
- /package/{extensions/explore/ast/read/policy.ts → src/ast/read-policy.ts} +0 -0
|
@@ -156,111 +156,144 @@ function parseChunkLine(rawLine: string): { prefix: " " | "+" | "-"; text: strin
|
|
|
156
156
|
return { prefix, text: rawLine.slice(1) };
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
let sawAnyChunk = false;
|
|
169
|
-
let linesAdded = 0;
|
|
170
|
-
let linesRemoved = 0;
|
|
159
|
+
interface UpdateBodyState {
|
|
160
|
+
chunks: UpdateFileChunk[];
|
|
161
|
+
index: number;
|
|
162
|
+
movePath?: string;
|
|
163
|
+
current?: MutableChunk;
|
|
164
|
+
sawAnyChunk: boolean;
|
|
165
|
+
linesAdded: number;
|
|
166
|
+
linesRemoved: number;
|
|
167
|
+
}
|
|
171
168
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
169
|
+
function changeContextFromDirective(directive: string): string | undefined {
|
|
170
|
+
if (directive === CHANGE_CONTEXT_MARKER) return undefined;
|
|
171
|
+
const context = directive.slice(CHANGE_CONTEXT_MARKER.length + 1).trim();
|
|
172
|
+
return context.length > 0 ? context : undefined;
|
|
173
|
+
}
|
|
177
174
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
175
|
+
function openOrReuseChunk(state: UpdateBodyState, operationPath: string): MutableChunk {
|
|
176
|
+
if (state.current) return state.current;
|
|
177
|
+
if (state.sawAnyChunk || state.chunks.length > 0) {
|
|
178
|
+
throw new Error(`Only the first update chunk may omit @@: ${operationPath}`);
|
|
179
|
+
}
|
|
180
|
+
state.current = createChunk();
|
|
181
|
+
state.sawAnyChunk = true;
|
|
182
|
+
return state.current;
|
|
183
|
+
}
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
index += 1;
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
185
|
+
function appendHunkLine(state: UpdateBodyState, current: MutableChunk, line: string): void {
|
|
186
|
+
const parsedLine = parseChunkLine(line);
|
|
187
|
+
current.hasLines = true;
|
|
188
|
+
if (parsedLine.prefix === "+") state.linesAdded += 1;
|
|
189
|
+
if (parsedLine.prefix === "-") state.linesRemoved += 1;
|
|
190
|
+
current.lines.push({ prefix: parsedLine.prefix, text: parsedLine.text });
|
|
191
|
+
state.index += 1;
|
|
192
|
+
}
|
|
196
193
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
current = createChunk();
|
|
202
|
-
sawAnyChunk = true;
|
|
203
|
-
}
|
|
194
|
+
function consumeUpdateLine(state: UpdateBodyState, line: string, operationPath: string): boolean {
|
|
195
|
+
const directive = updateDirective(line);
|
|
196
|
+
if (isUpdateBoundary(line)) return false;
|
|
204
197
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
continue;
|
|
198
|
+
if (directive.startsWith(MOVE_TO_MARKER)) {
|
|
199
|
+
if (state.sawAnyChunk || state.current) {
|
|
200
|
+
throw new Error(`Move to must appear before update chunks: ${operationPath}`);
|
|
209
201
|
}
|
|
202
|
+
state.movePath = requireUpdatePath(line, MOVE_TO_MARKER);
|
|
203
|
+
state.index += 1;
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
210
206
|
|
|
211
|
-
|
|
212
|
-
current.
|
|
207
|
+
if (directive === CHANGE_CONTEXT_MARKER || directive.startsWith(`${CHANGE_CONTEXT_MARKER} `)) {
|
|
208
|
+
finalizeChunk(state.current, state.chunks, operationPath);
|
|
209
|
+
state.current = createChunk(changeContextFromDirective(directive));
|
|
210
|
+
state.sawAnyChunk = true;
|
|
211
|
+
state.index += 1;
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
213
214
|
|
|
214
|
-
|
|
215
|
-
|
|
215
|
+
const current = openOrReuseChunk(state, operationPath);
|
|
216
|
+
if (directive === EOF_MARKER) {
|
|
217
|
+
current.isEndOfFile = true;
|
|
218
|
+
state.index += 1;
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
216
221
|
|
|
217
|
-
|
|
218
|
-
|
|
222
|
+
appendHunkLine(state, current, line);
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function parseUpdateBody(
|
|
227
|
+
lines: string[],
|
|
228
|
+
startIndex: number,
|
|
229
|
+
operationPath: string,
|
|
230
|
+
): { movePath?: string; chunks: UpdateFileChunk[]; linesAdded: number; linesRemoved: number; nextIndex: number } {
|
|
231
|
+
const state: UpdateBodyState = {
|
|
232
|
+
chunks: [],
|
|
233
|
+
index: startIndex,
|
|
234
|
+
sawAnyChunk: false,
|
|
235
|
+
linesAdded: 0,
|
|
236
|
+
linesRemoved: 0,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
while (state.index < lines.length) {
|
|
240
|
+
const line = lines[state.index];
|
|
241
|
+
if (line === undefined || !consumeUpdateLine(state, line, operationPath)) break;
|
|
219
242
|
}
|
|
220
243
|
|
|
221
|
-
finalizeChunk(current, chunks, operationPath);
|
|
222
|
-
if (chunks.length === 0 && !movePath)
|
|
244
|
+
finalizeChunk(state.current, state.chunks, operationPath);
|
|
245
|
+
if (state.chunks.length === 0 && !state.movePath) {
|
|
223
246
|
throw new Error(`Update file patch is missing chunk content: ${operationPath}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
movePath: state.movePath,
|
|
251
|
+
chunks: state.chunks,
|
|
252
|
+
linesAdded: state.linesAdded,
|
|
253
|
+
linesRemoved: state.linesRemoved,
|
|
254
|
+
nextIndex: state.index,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function parseWholeFileSection(header: string, sectionLines: string[], isReplace: boolean): ParsedSection {
|
|
259
|
+
const prefix = isReplace ? REPLACE_FILE_MARKER : ADD_FILE_MARKER;
|
|
260
|
+
const path = requireTopLevelPath(header, prefix);
|
|
261
|
+
const body = parseAddBody(sectionLines, 1);
|
|
262
|
+
if (body.nextIndex !== sectionLines.length) {
|
|
263
|
+
throw new Error(`Malformed ${isReplace ? "Replace" : "Add"} File section: ${path}`);
|
|
264
|
+
}
|
|
265
|
+
return { type: isReplace ? "replace" : "add", path, content: body.content, linesAdded: body.lineCount };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function parseDeleteSection(header: string, sectionLines: string[]): ParsedSection {
|
|
269
|
+
const path = requireTopLevelPath(header, DELETE_FILE_MARKER);
|
|
270
|
+
if (sectionLines.length !== 1) throw new Error(`Malformed Delete File section: ${path}`);
|
|
271
|
+
return { type: "delete", path };
|
|
272
|
+
}
|
|
224
273
|
|
|
225
|
-
|
|
274
|
+
function parseUpdateSection(header: string, sectionLines: string[]): ParsedSection {
|
|
275
|
+
const path = requireTopLevelPath(header, UPDATE_FILE_MARKER);
|
|
276
|
+
const updateBody = parseUpdateBody(sectionLines, 1, path);
|
|
277
|
+
if (updateBody.nextIndex !== sectionLines.length) throw new Error(`Malformed Update File section: ${path}`);
|
|
278
|
+
return {
|
|
279
|
+
type: "update",
|
|
280
|
+
path,
|
|
281
|
+
movePath: updateBody.movePath,
|
|
282
|
+
chunks: updateBody.chunks,
|
|
283
|
+
linesAdded: updateBody.linesAdded,
|
|
284
|
+
linesRemoved: updateBody.linesRemoved,
|
|
285
|
+
};
|
|
226
286
|
}
|
|
227
287
|
|
|
228
288
|
function parseSection(sectionLines: string[]): ParsedSection {
|
|
229
289
|
const header = sectionLines[0] ?? "";
|
|
230
|
-
|
|
231
290
|
const headerDirective = topLevelDirective(header);
|
|
232
291
|
|
|
233
292
|
if (headerDirective.startsWith(ADD_FILE_MARKER) || headerDirective.startsWith(REPLACE_FILE_MARKER)) {
|
|
234
|
-
|
|
235
|
-
const prefix = isReplace ? REPLACE_FILE_MARKER : ADD_FILE_MARKER;
|
|
236
|
-
const path = requireTopLevelPath(header, prefix);
|
|
237
|
-
const body = parseAddBody(sectionLines, 1);
|
|
238
|
-
if (body.nextIndex !== sectionLines.length) {
|
|
239
|
-
throw new Error(`Malformed ${isReplace ? "Replace" : "Add"} File section: ${path}`);
|
|
240
|
-
}
|
|
241
|
-
return { type: isReplace ? "replace" : "add", path, content: body.content, linesAdded: body.lineCount };
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (headerDirective.startsWith(DELETE_FILE_MARKER)) {
|
|
245
|
-
const path = requireTopLevelPath(header, DELETE_FILE_MARKER);
|
|
246
|
-
if (sectionLines.length !== 1) throw new Error(`Malformed Delete File section: ${path}`);
|
|
247
|
-
return { type: "delete", path };
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (headerDirective.startsWith(UPDATE_FILE_MARKER)) {
|
|
251
|
-
const path = requireTopLevelPath(header, UPDATE_FILE_MARKER);
|
|
252
|
-
const updateBody = parseUpdateBody(sectionLines, 1, path);
|
|
253
|
-
if (updateBody.nextIndex !== sectionLines.length) throw new Error(`Malformed Update File section: ${path}`);
|
|
254
|
-
return {
|
|
255
|
-
type: "update",
|
|
256
|
-
path,
|
|
257
|
-
movePath: updateBody.movePath,
|
|
258
|
-
chunks: updateBody.chunks,
|
|
259
|
-
linesAdded: updateBody.linesAdded,
|
|
260
|
-
linesRemoved: updateBody.linesRemoved,
|
|
261
|
-
};
|
|
293
|
+
return parseWholeFileSection(header, sectionLines, headerDirective.startsWith(REPLACE_FILE_MARKER));
|
|
262
294
|
}
|
|
263
|
-
|
|
295
|
+
if (headerDirective.startsWith(DELETE_FILE_MARKER)) return parseDeleteSection(header, sectionLines);
|
|
296
|
+
if (headerDirective.startsWith(UPDATE_FILE_MARKER)) return parseUpdateSection(header, sectionLines);
|
|
264
297
|
throw new Error(`Unexpected patch line: ${header}`);
|
|
265
298
|
}
|
|
266
299
|
|
|
@@ -285,15 +318,41 @@ function envelopeFailure(message: string): ParsedPatch {
|
|
|
285
318
|
return { operations: [], parseFailures: [{ phase: "parse", sectionIndex: 0, message }], totalSections: 1 };
|
|
286
319
|
}
|
|
287
320
|
|
|
288
|
-
|
|
321
|
+
function parseEnvelopeLines(input: string): string[] | ParsedPatch {
|
|
289
322
|
const normalized = input.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim();
|
|
290
323
|
if (!normalized) return envelopeFailure("Patch must not be empty.");
|
|
291
|
-
|
|
292
324
|
const lines = normalized.split("\n");
|
|
293
|
-
if (topLevelDirective(lines[0] ?? "") !== BEGIN_PATCH_MARKER)
|
|
325
|
+
if (topLevelDirective(lines[0] ?? "") !== BEGIN_PATCH_MARKER) {
|
|
294
326
|
return envelopeFailure("Patch must start with *** Begin Patch.");
|
|
295
|
-
|
|
327
|
+
}
|
|
328
|
+
if (topLevelDirective(lines[lines.length - 1] ?? "") !== END_PATCH_MARKER) {
|
|
296
329
|
return envelopeFailure("Patch must end with *** End Patch.");
|
|
330
|
+
}
|
|
331
|
+
return lines;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function pushParsedSection(
|
|
335
|
+
operations: PatchOperation[],
|
|
336
|
+
parseFailures: PatchFailure[],
|
|
337
|
+
sectionIndex: number,
|
|
338
|
+
sectionLines: string[],
|
|
339
|
+
): void {
|
|
340
|
+
try {
|
|
341
|
+
operations.push({ ...parseSection(sectionLines), sectionIndex });
|
|
342
|
+
} catch (error) {
|
|
343
|
+
parseFailures.push({
|
|
344
|
+
phase: "parse",
|
|
345
|
+
sectionIndex,
|
|
346
|
+
...parseHeaderMetadata(sectionLines[0] ?? ""),
|
|
347
|
+
message: error instanceof Error ? error.message : String(error),
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
export function parsePatch(input: string): ParsedPatch {
|
|
353
|
+
const envelope = parseEnvelopeLines(input);
|
|
354
|
+
if (!Array.isArray(envelope)) return envelope;
|
|
355
|
+
const lines = envelope;
|
|
297
356
|
|
|
298
357
|
const operations: PatchOperation[] = [];
|
|
299
358
|
const parseFailures: PatchFailure[] = [];
|
|
@@ -308,30 +367,15 @@ export function parsePatch(input: string): ParsedPatch {
|
|
|
308
367
|
continue;
|
|
309
368
|
}
|
|
310
369
|
|
|
311
|
-
|
|
312
|
-
if (!isTopLevelBoundary(line) || lineDirective === END_PATCH_MARKER) {
|
|
370
|
+
if (!isTopLevelBoundary(line) || topLevelDirective(line) === END_PATCH_MARKER) {
|
|
313
371
|
parseFailures.push({ phase: "parse", sectionIndex, message: `Unexpected patch line: ${line}` });
|
|
314
372
|
index += 1;
|
|
315
373
|
continue;
|
|
316
374
|
}
|
|
317
375
|
|
|
318
376
|
sectionIndex += 1;
|
|
319
|
-
const sectionStart = index;
|
|
320
377
|
const nextBoundary = nextSectionBoundary(lines, index + 1, isUpdateHeader(line));
|
|
321
|
-
|
|
322
|
-
const sectionLines = lines.slice(sectionStart, nextBoundary);
|
|
323
|
-
try {
|
|
324
|
-
operations.push({ ...parseSection(sectionLines), sectionIndex });
|
|
325
|
-
} catch (error) {
|
|
326
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
327
|
-
parseFailures.push({
|
|
328
|
-
phase: "parse",
|
|
329
|
-
sectionIndex,
|
|
330
|
-
...parseHeaderMetadata(sectionLines[0] ?? ""),
|
|
331
|
-
message,
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
|
|
378
|
+
pushParsedSection(operations, parseFailures, sectionIndex, lines.slice(index, nextBoundary));
|
|
335
379
|
index = nextBoundary;
|
|
336
380
|
}
|
|
337
381
|
|
|
@@ -44,6 +44,34 @@ function startPreviewOp(ops: PreviewOp[], kind: PreviewOp["kind"], path: string)
|
|
|
44
44
|
return op;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
function matchPreviewSectionStart(ops: PreviewOp[], value: string): PreviewOp | undefined {
|
|
48
|
+
const addPath = pathAfter(value, ADD_FILE_MARKER);
|
|
49
|
+
if (addPath !== undefined) return startPreviewOp(ops, "add", addPath);
|
|
50
|
+
const replacePath = pathAfter(value, REPLACE_FILE_MARKER);
|
|
51
|
+
if (replacePath !== undefined) return startPreviewOp(ops, "replace", replacePath);
|
|
52
|
+
const deletePath = pathAfter(value, DELETE_FILE_MARKER);
|
|
53
|
+
if (deletePath !== undefined) return startPreviewOp(ops, "delete", deletePath);
|
|
54
|
+
const updatePath = pathAfter(value, UPDATE_FILE_MARKER);
|
|
55
|
+
if (updatePath !== undefined) return startPreviewOp(ops, "update", updatePath);
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function accumulatePreviewBodyLine(current: PreviewOp, line: string): void {
|
|
60
|
+
if (current.kind === "update") {
|
|
61
|
+
const movePath = pathAfter(updateDirective(line), MOVE_TO_MARKER);
|
|
62
|
+
if (movePath !== undefined) {
|
|
63
|
+
current.moveTo = movePath;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (line.startsWith("+")) current.linesAdded += 1;
|
|
67
|
+
else if (line.startsWith("-")) current.linesRemoved += 1;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if ((current.kind === "add" || current.kind === "replace") && line.startsWith("+")) {
|
|
71
|
+
current.linesAdded += 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
47
75
|
// Tolerant preview scan — never throws, display-oriented (partial patches during streaming).
|
|
48
76
|
function scanPreview(input: string | undefined): PreviewOp[] {
|
|
49
77
|
if (typeof input !== "string" || !input) return [];
|
|
@@ -54,47 +82,12 @@ function scanPreview(input: string | undefined): PreviewOp[] {
|
|
|
54
82
|
for (const line of lines) {
|
|
55
83
|
const value = current?.kind === "update" ? updateDirective(line) : topLevelDirective(line);
|
|
56
84
|
if (value === END_PATCH_MARKER) break;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
current = startPreviewOp(ops, "add", addPath);
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const replacePath = pathAfter(value, REPLACE_FILE_MARKER);
|
|
65
|
-
if (replacePath !== undefined) {
|
|
66
|
-
current = startPreviewOp(ops, "replace", replacePath);
|
|
85
|
+
const started = matchPreviewSectionStart(ops, value);
|
|
86
|
+
if (started) {
|
|
87
|
+
current = started;
|
|
67
88
|
continue;
|
|
68
89
|
}
|
|
69
|
-
|
|
70
|
-
const deletePath = pathAfter(value, DELETE_FILE_MARKER);
|
|
71
|
-
if (deletePath !== undefined) {
|
|
72
|
-
current = startPreviewOp(ops, "delete", deletePath);
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const updatePath = pathAfter(value, UPDATE_FILE_MARKER);
|
|
77
|
-
if (updatePath !== undefined) {
|
|
78
|
-
current = startPreviewOp(ops, "update", updatePath);
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (!current) continue;
|
|
83
|
-
|
|
84
|
-
if (current.kind === "update") {
|
|
85
|
-
const movePath = pathAfter(updateDirective(line), MOVE_TO_MARKER);
|
|
86
|
-
if (movePath !== undefined) {
|
|
87
|
-
current.moveTo = movePath;
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
if (line.startsWith("+")) current.linesAdded += 1;
|
|
91
|
-
else if (line.startsWith("-")) current.linesRemoved += 1;
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (current.kind === "add" || current.kind === "replace") {
|
|
96
|
-
if (line.startsWith("+")) current.linesAdded += 1;
|
|
97
|
-
}
|
|
90
|
+
if (current) accumulatePreviewBodyLine(current, line);
|
|
98
91
|
}
|
|
99
92
|
|
|
100
93
|
return ops;
|
|
@@ -1,35 +1,39 @@
|
|
|
1
|
-
import { type ApplyPatchSummary, deriveStats } from "./executor.ts";
|
|
1
|
+
import { type ApplyPatchStats, type ApplyPatchSummary, deriveStats } from "./executor.ts";
|
|
2
|
+
import type { PatchFailure } from "./parser.ts";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
4
|
+
function formatStatusLine(summary: ApplyPatchSummary, stats: ApplyPatchStats): string {
|
|
5
|
+
if (summary.status === "failed") return "No changes applied.";
|
|
6
|
+
const parts: string[] = [];
|
|
7
|
+
if (stats.linesAdded > 0) parts.push(`+${stats.linesAdded}`);
|
|
8
|
+
if (stats.linesRemoved > 0) parts.push(`-${stats.linesRemoved}`);
|
|
9
|
+
const badge = parts.length > 0 ? ` [${parts.join(" ")}]` : "";
|
|
10
|
+
return `Applied ${stats.completedOperations}/${summary.totalSections} sections.${badge}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function formatChangeLines(stats: ApplyPatchStats): string[] {
|
|
5
14
|
const lines: string[] = [];
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
lines.push(`Applied ${s.completedOperations}/${summary.totalSections} sections.${badge}`);
|
|
14
|
-
}
|
|
15
|
+
for (const path of stats.added) lines.push(`A ${path}`);
|
|
16
|
+
for (const path of stats.replaced) lines.push(`M ${path}`);
|
|
17
|
+
for (const path of stats.updated) lines.push(`M ${path}`);
|
|
18
|
+
for (const path of stats.deleted) lines.push(`D ${path}`);
|
|
19
|
+
for (const move of stats.moved) lines.push(`R ${move.from} -> ${move.to}`);
|
|
20
|
+
return lines;
|
|
21
|
+
}
|
|
15
22
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
function formatFailureLine(failure: PatchFailure): string {
|
|
24
|
+
const kind = failure.kind ? `${failure.kind} ` : "";
|
|
25
|
+
const path = failure.path ?? "";
|
|
26
|
+
const chunk = failure.chunkIndex && failure.totalChunks ? ` chunk ${failure.chunkIndex}/${failure.totalChunks}` : "";
|
|
27
|
+
const ctx = failure.contextHint ? ` (context: "${failure.contextHint}")` : "";
|
|
28
|
+
return `- ${kind}${path}${chunk}: ${failure.message}${ctx}`.trim();
|
|
29
|
+
}
|
|
21
30
|
|
|
31
|
+
export function formatPatchSummary(summary: ApplyPatchSummary): string {
|
|
32
|
+
const stats = deriveStats(summary);
|
|
33
|
+
const lines = [formatStatusLine(summary, stats), ...formatChangeLines(stats)];
|
|
22
34
|
if (summary.failures.length > 0) {
|
|
23
35
|
lines.push("Failures:");
|
|
24
|
-
for (const failure of summary.failures)
|
|
25
|
-
const kind = failure.kind ? `${failure.kind} ` : "";
|
|
26
|
-
const path = failure.path ?? "";
|
|
27
|
-
const chunk =
|
|
28
|
-
failure.chunkIndex && failure.totalChunks ? ` chunk ${failure.chunkIndex}/${failure.totalChunks}` : "";
|
|
29
|
-
const ctx = failure.contextHint ? ` (context: "${failure.contextHint}")` : "";
|
|
30
|
-
lines.push(`- ${kind}${path}${chunk}: ${failure.message}${ctx}`.trim());
|
|
31
|
-
}
|
|
36
|
+
for (const failure of summary.failures) lines.push(formatFailureLine(failure));
|
|
32
37
|
}
|
|
33
|
-
|
|
34
38
|
return lines.join("\n");
|
|
35
39
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Ready
|
|
2
|
+
|
|
3
|
+
Adds `/ready` to scan the repository for agent-readiness rails and write one report file.
|
|
4
|
+
|
|
5
|
+
You pick Markdown or HTML. Tau runs a deterministic scan (no model), builds the report in memory, and writes a timestamped file under `.pi/tau/ready/` using your local timezone in the filename. A notification shows the path and status counts.
|
|
6
|
+
|
|
7
|
+
The scan looks for cold-start docs, toolchain pins, task/verify entrypoints, silent command runner config, policy and vocabulary files, standards and context catalogs, and language-specific format/lint/types/entropy tools (TypeScript, Deno, Go, Rust packs in v1). Statuses are pass, weak, missing, n/a, or unknown — no scores.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join, relative } from "node:path";
|
|
3
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
4
|
+
import { resolveProjectRoot } from "../../shared/settings/paths.ts";
|
|
5
|
+
import { errorText } from "../../shared/text.ts";
|
|
6
|
+
import { type ReadyFormat, localTimestampSlug, summarizeCounts } from "./model.ts";
|
|
7
|
+
import { readyFileExtension, renderReadyReport } from "./render.ts";
|
|
8
|
+
import { scanReadyReport } from "./scan.ts";
|
|
9
|
+
|
|
10
|
+
const OUTPUT_DIR = join(".pi", "tau", "ready");
|
|
11
|
+
|
|
12
|
+
export default function readyExtension(pi: ExtensionAPI): void {
|
|
13
|
+
pi.registerCommand("ready", {
|
|
14
|
+
description: "Scan agent readiness and write a markdown or HTML report",
|
|
15
|
+
handler: async (_args, ctx) => {
|
|
16
|
+
try {
|
|
17
|
+
await runReady(ctx);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
ctx.ui.notify(`/ready failed: ${errorText(error)}`, "error");
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function runReady(ctx: ExtensionCommandContext): Promise<void> {
|
|
26
|
+
if (!ctx.isProjectTrusted()) {
|
|
27
|
+
ctx.ui.notify("/ready requires a trusted project", "warning");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const format = await pickFormat(ctx);
|
|
32
|
+
if (!format) return;
|
|
33
|
+
|
|
34
|
+
const root = await resolveProjectRoot(ctx.cwd);
|
|
35
|
+
const report = await scanReadyReport(ctx.cwd);
|
|
36
|
+
const body = renderReadyReport(report, format);
|
|
37
|
+
const stamp = localTimestampSlug(new Date(report.generatedAt));
|
|
38
|
+
const fileName = `ready-${stamp}.${readyFileExtension(format)}`;
|
|
39
|
+
const outDir = join(root, OUTPUT_DIR);
|
|
40
|
+
const outPath = join(outDir, fileName);
|
|
41
|
+
|
|
42
|
+
await mkdir(outDir, { recursive: true });
|
|
43
|
+
await writeFile(outPath, body, "utf8");
|
|
44
|
+
|
|
45
|
+
const displayPath = relative(ctx.cwd, outPath) || outPath;
|
|
46
|
+
ctx.ui.notify(`/ready ${summarizeCounts(report.counts)}\n${displayPath}`, "info");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function pickFormat(ctx: ExtensionCommandContext): Promise<ReadyFormat | undefined> {
|
|
50
|
+
if (ctx.mode !== "tui") {
|
|
51
|
+
ctx.ui.notify("/ready needs the TUI to choose Markdown or HTML", "warning");
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
const selected = await ctx.ui.select("Ready report format", ["Markdown", "HTML"]);
|
|
55
|
+
if (selected === "Markdown") return "markdown";
|
|
56
|
+
if (selected === "HTML") return "html";
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
export type ReadyStatus = "pass" | "weak" | "missing" | "na" | "unknown";
|
|
2
|
+
export type ReadyHow = "scan" | "judgment" | "mixed";
|
|
3
|
+
export type ReadyFormat = "markdown" | "html";
|
|
4
|
+
|
|
5
|
+
export type ReadyAreaId =
|
|
6
|
+
| "cold-start"
|
|
7
|
+
| "toolchain"
|
|
8
|
+
| "verify"
|
|
9
|
+
| "lint-entropy"
|
|
10
|
+
| "policy"
|
|
11
|
+
| "standards"
|
|
12
|
+
| "context"
|
|
13
|
+
| "reuse"
|
|
14
|
+
| "markers"
|
|
15
|
+
| "harness"
|
|
16
|
+
| "side-effect-verbs";
|
|
17
|
+
|
|
18
|
+
export interface ReadyRow {
|
|
19
|
+
id: string;
|
|
20
|
+
area: ReadyAreaId;
|
|
21
|
+
title: string;
|
|
22
|
+
status: ReadyStatus;
|
|
23
|
+
how: ReadyHow;
|
|
24
|
+
evidence: string[];
|
|
25
|
+
note: string;
|
|
26
|
+
next?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ReadyReport {
|
|
30
|
+
generatedAt: string;
|
|
31
|
+
generatedAtLabel: string;
|
|
32
|
+
root: string;
|
|
33
|
+
languages: string[];
|
|
34
|
+
rows: ReadyRow[];
|
|
35
|
+
counts: Record<ReadyStatus, number>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const AREA_LABELS: Record<ReadyAreaId, string> = {
|
|
39
|
+
"cold-start": "Cold start",
|
|
40
|
+
toolchain: "Toolchain",
|
|
41
|
+
verify: "Verify",
|
|
42
|
+
"lint-entropy": "Lint and entropy",
|
|
43
|
+
policy: "Policy",
|
|
44
|
+
standards: "Standards",
|
|
45
|
+
context: "Context",
|
|
46
|
+
reuse: "Reuse",
|
|
47
|
+
markers: "Markers",
|
|
48
|
+
harness: "Harness gates",
|
|
49
|
+
"side-effect-verbs": "Side-effect verbs",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const AREA_ORDER: ReadyAreaId[] = [
|
|
53
|
+
"cold-start",
|
|
54
|
+
"toolchain",
|
|
55
|
+
"verify",
|
|
56
|
+
"lint-entropy",
|
|
57
|
+
"policy",
|
|
58
|
+
"standards",
|
|
59
|
+
"context",
|
|
60
|
+
"reuse",
|
|
61
|
+
"markers",
|
|
62
|
+
"harness",
|
|
63
|
+
"side-effect-verbs",
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
export function countStatuses(rows: readonly ReadyRow[]): Record<ReadyStatus, number> {
|
|
67
|
+
const counts: Record<ReadyStatus, number> = {
|
|
68
|
+
pass: 0,
|
|
69
|
+
weak: 0,
|
|
70
|
+
missing: 0,
|
|
71
|
+
na: 0,
|
|
72
|
+
unknown: 0,
|
|
73
|
+
};
|
|
74
|
+
for (const row of rows) counts[row.status] += 1;
|
|
75
|
+
return counts;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function summarizeCounts(counts: Record<ReadyStatus, number>): string {
|
|
79
|
+
const parts: string[] = [];
|
|
80
|
+
for (const status of ["pass", "weak", "missing", "na", "unknown"] as const) {
|
|
81
|
+
if (counts[status] > 0) parts.push(`${counts[status]} ${status}`);
|
|
82
|
+
}
|
|
83
|
+
return parts.join(" · ") || "no rows";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Local-timezone stamp for filenames: 2026-04-08-143052 */
|
|
87
|
+
export function localTimestampSlug(date = new Date()): string {
|
|
88
|
+
const pad = (value: number) => String(value).padStart(2, "0");
|
|
89
|
+
return [
|
|
90
|
+
date.getFullYear(),
|
|
91
|
+
pad(date.getMonth() + 1),
|
|
92
|
+
pad(date.getDate()),
|
|
93
|
+
"-",
|
|
94
|
+
pad(date.getHours()),
|
|
95
|
+
pad(date.getMinutes()),
|
|
96
|
+
pad(date.getSeconds()),
|
|
97
|
+
].join("");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function localTimestampLabel(date = new Date()): string {
|
|
101
|
+
return date.toLocaleString(undefined, {
|
|
102
|
+
year: "numeric",
|
|
103
|
+
month: "short",
|
|
104
|
+
day: "numeric",
|
|
105
|
+
hour: "2-digit",
|
|
106
|
+
minute: "2-digit",
|
|
107
|
+
second: "2-digit",
|
|
108
|
+
timeZoneName: "short",
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function row(
|
|
113
|
+
partial: Omit<ReadyRow, "how" | "evidence"> & {
|
|
114
|
+
how?: ReadyHow;
|
|
115
|
+
evidence?: string[];
|
|
116
|
+
},
|
|
117
|
+
): ReadyRow {
|
|
118
|
+
return {
|
|
119
|
+
how: partial.how ?? "scan",
|
|
120
|
+
evidence: partial.evidence ?? [],
|
|
121
|
+
id: partial.id,
|
|
122
|
+
area: partial.area,
|
|
123
|
+
title: partial.title,
|
|
124
|
+
status: partial.status,
|
|
125
|
+
note: partial.note,
|
|
126
|
+
next: partial.next,
|
|
127
|
+
};
|
|
128
|
+
}
|