@shanepadgett/tau-agent 0.33.1 → 0.34.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.
Files changed (190) hide show
  1. package/docs/context.md +29 -20
  2. package/docs/extending-tau-agent.md +63 -24
  3. package/extensions/appshot/index.ts +112 -111
  4. package/extensions/aside/README.md +7 -0
  5. package/extensions/aside/index.ts +168 -0
  6. package/extensions/aside/panel.ts +83 -0
  7. package/extensions/branch/index.ts +38 -21
  8. package/extensions/cache-diagnostics/index.ts +70 -58
  9. package/extensions/checkpoint/README.md +9 -0
  10. package/extensions/checkpoint/checkpoint-budget.ts +79 -0
  11. package/extensions/checkpoint/checkpoint.ts +277 -0
  12. package/extensions/checkpoint/index.ts +102 -0
  13. package/extensions/checkpoint/messages.ts +169 -0
  14. package/extensions/checkpoint/prompt.ts +24 -0
  15. package/extensions/checkpoint/settings.ts +27 -0
  16. package/extensions/commit/commit-effort.ts +3 -3
  17. package/extensions/context/README.md +12 -8
  18. package/extensions/context/definitions.ts +178 -55
  19. package/extensions/context/index.ts +192 -160
  20. package/extensions/context/panel.ts +57 -37
  21. package/extensions/context/sync.ts +139 -89
  22. package/extensions/context/validation.ts +37 -25
  23. package/extensions/effort/README.md +1 -1
  24. package/extensions/effort/index.ts +3 -3
  25. package/extensions/effort/state.ts +3 -18
  26. package/extensions/explore/guidance.ts +6 -47
  27. package/extensions/explore/index.ts +38 -53
  28. package/extensions/explore/{ast/read → read}/hook.ts +12 -7
  29. package/extensions/explore/{ast/tools → tools}/ast-search.ts +15 -19
  30. package/extensions/explore/{ast/tools → tools}/context.ts +16 -23
  31. package/extensions/explore/{ast/tools → tools}/deps.ts +11 -15
  32. package/extensions/explore/{ast/tools → tools}/discover.ts +15 -17
  33. package/extensions/explore/{ast/tools → tools}/impact.ts +20 -23
  34. package/extensions/explore/{ast/tools → tools}/outline.ts +21 -24
  35. package/extensions/explore/{ast/tools → tools}/relationships.ts +23 -32
  36. package/extensions/explore/{ast/tools → tools}/render.ts +1 -1
  37. package/extensions/explore/{ast/tools → tools}/reverse-deps.ts +11 -15
  38. package/extensions/explore/{ast/tools → tools}/show.ts +16 -19
  39. package/extensions/explore/tools/target-params.ts +9 -0
  40. package/extensions/footer/index.ts +75 -56
  41. package/extensions/handoff/index.ts +8 -30
  42. package/extensions/image-gen/index.ts +46 -40
  43. package/extensions/patch/executor.ts +190 -135
  44. package/extensions/patch/matcher.ts +82 -48
  45. package/extensions/patch/parser.ts +149 -105
  46. package/extensions/patch/render.ts +32 -39
  47. package/extensions/patch/summary.ts +30 -26
  48. package/extensions/ready/README.md +7 -0
  49. package/extensions/ready/index.ts +58 -0
  50. package/extensions/ready/model.ts +128 -0
  51. package/extensions/ready/packs.ts +272 -0
  52. package/extensions/ready/render.ts +445 -0
  53. package/extensions/ready/scan.ts +760 -0
  54. package/extensions/reference/panel.ts +28 -16
  55. package/extensions/review/index.ts +63 -51
  56. package/extensions/review/panel.ts +5 -34
  57. package/extensions/runtime-context/context.ts +30 -26
  58. package/extensions/script-runner/README.md +2 -2
  59. package/extensions/script-runner/index.ts +122 -82
  60. package/extensions/silent-command-runner/index.ts +38 -23
  61. package/extensions/subagent/agents/context-sync.md +185 -33
  62. package/extensions/subagent/agents/scout.md +2 -2
  63. package/extensions/subagent/agents.ts +137 -84
  64. package/extensions/subagent/cmux-dashboard.ts +39 -30
  65. package/extensions/subagent/index.ts +79 -59
  66. package/extensions/subagent/resume.ts +1 -1
  67. package/extensions/subagent/run.ts +304 -193
  68. package/extensions/subagent/runtime.ts +781 -343
  69. package/extensions/tau/index.ts +47 -38
  70. package/extensions/tau-help/help.md +14 -6
  71. package/extensions/tool-loader/README.md +4 -2
  72. package/extensions/tool-loader/index.ts +77 -52
  73. package/extensions/web/index.ts +6 -3
  74. package/extensions/web/webfetch.ts +114 -86
  75. package/package.json +3 -3
  76. package/schemas/tau.schema.json +20 -33
  77. package/shared/bounded-text-result.ts +1 -0
  78. package/shared/checkpoint-visibility.ts +9 -0
  79. package/shared/events.ts +28 -10
  80. package/shared/isolated-session.ts +71 -29
  81. package/shared/model-effort.ts +9 -19
  82. package/shared/model-fallback/index.ts +66 -41
  83. package/shared/settings/load.ts +45 -38
  84. package/shared/temporary-output-store.ts +16 -10
  85. package/{extensions/explore → src}/ast/adapter.ts +11 -2
  86. package/{extensions/explore → src}/ast/engine.ts +54 -38
  87. package/{extensions/explore → src}/ast/format/ast-search.ts +15 -6
  88. package/{extensions/explore → src}/ast/format/context.ts +3 -6
  89. package/src/ast/format/deps.ts +94 -0
  90. package/{extensions/explore → src}/ast/format/discover.ts +2 -2
  91. package/{extensions/explore → src}/ast/format/impact.ts +9 -11
  92. package/{extensions/explore → src}/ast/format/outline.ts +2 -2
  93. package/{extensions/explore → src}/ast/format/relationships.ts +11 -7
  94. package/{extensions/explore → src}/ast/format/show.ts +1 -1
  95. package/src/ast/format/targeted-result.ts +20 -0
  96. package/src/ast/grammars/c_sharp.wasm +0 -0
  97. package/{extensions/explore → src}/ast/grammars/manifest.json +9 -12
  98. package/{extensions/explore → src}/ast/grammars/manifest.ts +0 -4
  99. package/{extensions/explore → src}/ast/graph/file-graph.ts +210 -98
  100. package/{extensions/explore → src}/ast/graph/relationships.ts +285 -72
  101. package/{extensions/explore → src}/ast/ir.ts +6 -0
  102. package/src/ast/languages/csharp-file-deps.ts +90 -0
  103. package/{extensions/explore → src}/ast/languages/csharp.ts +9 -40
  104. package/src/ast/languages/dotted-index.ts +131 -0
  105. package/{extensions/explore → src}/ast/languages/file-dep-util.ts +22 -13
  106. package/src/ast/languages/fixtures/sample.cs +53 -0
  107. package/src/ast/languages/fixtures/sample.cs.expected.json +156 -0
  108. package/src/ast/languages/fixtures/sample.go +37 -0
  109. package/src/ast/languages/fixtures/sample.go.expected.json +96 -0
  110. package/src/ast/languages/fixtures/sample.java +40 -0
  111. package/src/ast/languages/fixtures/sample.java.expected.json +126 -0
  112. package/src/ast/languages/fixtures/sample.kt +31 -0
  113. package/src/ast/languages/fixtures/sample.kt.expected.json +134 -0
  114. package/src/ast/languages/fixtures/sample.md +23 -0
  115. package/src/ast/languages/fixtures/sample.md.expected.json +44 -0
  116. package/src/ast/languages/fixtures/sample.rs +54 -0
  117. package/src/ast/languages/fixtures/sample.rs.expected.json +178 -0
  118. package/src/ast/languages/fixtures/sample.swift +45 -0
  119. package/src/ast/languages/fixtures/sample.swift.expected.json +147 -0
  120. package/src/ast/languages/fixtures/sample.ts +45 -0
  121. package/src/ast/languages/fixtures/sample.ts.expected.json +141 -0
  122. package/src/ast/languages/fixtures/sample.tsx +36 -0
  123. package/src/ast/languages/fixtures/sample.tsx.expected.json +59 -0
  124. package/{extensions/explore → src}/ast/languages/go-file-deps.ts +6 -0
  125. package/{extensions/explore → src}/ast/languages/go.ts +21 -3
  126. package/{extensions/explore → src}/ast/languages/java.ts +5 -38
  127. package/src/ast/languages/jvm-file-deps.ts +135 -0
  128. package/{extensions/explore → src}/ast/languages/kotlin.ts +5 -39
  129. package/{extensions/explore → src}/ast/languages/rust-file-deps.ts +3 -3
  130. package/{extensions/explore → src}/ast/languages/rust.ts +21 -13
  131. package/{extensions/explore → src}/ast/languages/swift-file-deps.ts +22 -15
  132. package/{extensions/explore → src}/ast/languages/swift.ts +9 -39
  133. package/{extensions/explore → src}/ast/languages/tree.ts +47 -0
  134. package/{extensions/explore → src}/ast/languages/typescript-file-deps.ts +108 -0
  135. package/{extensions/explore → src}/ast/languages/typescript-package-surface.ts +4 -3
  136. package/{extensions/explore → src}/ast/languages/typescript.ts +3 -5
  137. package/{extensions/explore → src}/ast/markdown.ts +72 -47
  138. package/{extensions/explore → src}/ast/queries/ast-search.ts +164 -146
  139. package/{extensions/explore → src}/ast/queries/composite-target.ts +1 -1
  140. package/{extensions/explore → src}/ast/queries/context.ts +116 -80
  141. package/{extensions/explore → src}/ast/queries/discover.ts +154 -105
  142. package/src/ast/queries/impact.ts +291 -0
  143. package/{extensions/explore → src}/ast/queries/outline.ts +4 -2
  144. package/{extensions/explore → src}/ast/queries/show.ts +46 -42
  145. package/{extensions/explore → src}/ast/query.ts +1 -1
  146. package/{extensions/explore → src}/ast/registry.ts +1 -2
  147. package/{extensions/explore → src}/ast/scan.ts +1 -1
  148. package/src/ast/session.ts +50 -0
  149. package/{extensions/explore → src}/ast/slice.ts +8 -0
  150. package/{extensions/explore → src/ast}/traverse.ts +32 -20
  151. package/src/file-injection/index.ts +547 -0
  152. package/src/image-generation/client.ts +50 -31
  153. package/src/image-generation/index.ts +177 -134
  154. package/src/index.ts +2 -0
  155. package/src/tool-loading/index.ts +58 -0
  156. package/extensions/context/evidence.ts +0 -517
  157. package/extensions/context/projection.ts +0 -141
  158. package/extensions/context/state.ts +0 -30
  159. package/extensions/context/write-scope.ts +0 -109
  160. package/extensions/explore/ast/format/deps.ts +0 -86
  161. package/extensions/explore/ast/grammars/odin.wasm +0 -0
  162. package/extensions/explore/ast/languages/csharp-file-deps.ts +0 -189
  163. package/extensions/explore/ast/languages/fixtures/sample.cs +0 -17
  164. package/extensions/explore/ast/languages/fixtures/sample.java +0 -21
  165. package/extensions/explore/ast/languages/fixtures/sample.kt +0 -15
  166. package/extensions/explore/ast/languages/fixtures/sample.odin +0 -28
  167. package/extensions/explore/ast/languages/fixtures/sample.rs +0 -31
  168. package/extensions/explore/ast/languages/fixtures/sample.swift +0 -25
  169. package/extensions/explore/ast/languages/jvm-file-deps.ts +0 -217
  170. package/extensions/explore/ast/languages/odin-file-deps.ts +0 -175
  171. package/extensions/explore/ast/languages/odin.ts +0 -407
  172. package/extensions/explore/ast/queries/impact.ts +0 -250
  173. package/extensions/explore/outline-injection.ts +0 -151
  174. package/extensions/explore/read/autoread.ts +0 -118
  175. package/extensions/working-memory/README.md +0 -17
  176. package/extensions/working-memory/checkpoint.ts +0 -242
  177. package/extensions/working-memory/index.ts +0 -341
  178. package/extensions/working-memory/memory.ts +0 -235
  179. package/extensions/working-memory/render.ts +0 -185
  180. package/extensions/working-memory/settings.ts +0 -38
  181. package/extensions/working-memory/state.ts +0 -158
  182. package/shared/autoread.ts +0 -234
  183. package/shared/context-messages.ts +0 -19
  184. package/shared/outline-injection.ts +0 -56
  185. /package/{extensions/explore → src}/ast/format/composite.ts +0 -0
  186. /package/{extensions/explore → src}/ast/grammars/kotlin.wasm +0 -0
  187. /package/{extensions/explore → src}/ast/grammars/swift.wasm +0 -0
  188. /package/{extensions/explore → src}/ast/identity.ts +0 -0
  189. /package/{extensions/explore → src}/ast/package-surface.ts +0 -0
  190. /package/{extensions/explore/ast/read/policy.ts → src/ast/read-policy.ts} +0 -0
@@ -0,0 +1,272 @@
1
+ export type PackCapability = "format" | "lint" | "types" | "dead" | "complexity" | "dup" | "boundaries";
2
+
3
+ export interface PackTool {
4
+ name: string;
5
+ /** Project-relative paths; any hit means the tool is configured. */
6
+ detectFiles: string[];
7
+ /** Substrings matched against package.json scripts / mise task bodies / CI text. */
8
+ detectText?: string[];
9
+ }
10
+
11
+ export interface LanguagePack {
12
+ id: string;
13
+ label: string;
14
+ /** Manifest or config files that activate this pack. */
15
+ detectFiles: string[];
16
+ capabilities: Partial<Record<PackCapability, { tools: PackTool[]; unsupportedReason?: string }>>;
17
+ }
18
+
19
+ /** TS/JS Node-oriented pack. Deno overlay is separate. */
20
+ const TYPESCRIPT_PACK: LanguagePack = {
21
+ id: "typescript",
22
+ label: "TypeScript / JavaScript",
23
+ detectFiles: ["package.json", "tsconfig.json", "tsconfig.base.json", "jsconfig.json"],
24
+ capabilities: {
25
+ format: {
26
+ tools: [
27
+ { name: "oxfmt", detectFiles: [".oxfmtrc.jsonc", ".oxfmtrc.json"] },
28
+ {
29
+ name: "prettier",
30
+ detectFiles: [
31
+ ".prettierrc",
32
+ ".prettierrc.json",
33
+ ".prettierrc.js",
34
+ ".prettierrc.cjs",
35
+ "prettier.config.js",
36
+ "prettier.config.cjs",
37
+ "prettier.config.mjs",
38
+ ],
39
+ detectText: ["prettier"],
40
+ },
41
+ { name: "biome", detectFiles: ["biome.json", "biome.jsonc"], detectText: ["biome"] },
42
+ { name: "dprint", detectFiles: ["dprint.json", "dprint.jsonc", ".dprint.json"], detectText: ["dprint"] },
43
+ ],
44
+ },
45
+ lint: {
46
+ tools: [
47
+ {
48
+ name: "oxlint",
49
+ detectFiles: [".oxlintrc.jsonc", ".oxlintrc.json", "oxlint.config.ts"],
50
+ detectText: ["oxlint"],
51
+ },
52
+ {
53
+ name: "eslint",
54
+ detectFiles: [
55
+ "eslint.config.js",
56
+ "eslint.config.mjs",
57
+ "eslint.config.cjs",
58
+ "eslint.config.ts",
59
+ ".eslintrc",
60
+ ".eslintrc.js",
61
+ ".eslintrc.cjs",
62
+ ".eslintrc.json",
63
+ ".eslintrc.yml",
64
+ ],
65
+ detectText: ["eslint"],
66
+ },
67
+ { name: "biome", detectFiles: ["biome.json", "biome.jsonc"], detectText: ["biome lint"] },
68
+ ],
69
+ },
70
+ types: {
71
+ tools: [{ name: "tsc", detectFiles: ["tsconfig.json", "tsconfig.base.json"], detectText: ["tsc", "tsgo"] }],
72
+ },
73
+ dead: {
74
+ tools: [
75
+ {
76
+ name: "fallow",
77
+ detectFiles: [".fallowrc.json", ".fallowrc.jsonc", "fallow.toml", ".fallow.toml"],
78
+ detectText: ["fallow"],
79
+ },
80
+ {
81
+ name: "knip",
82
+ detectFiles: ["knip.json", "knip.jsonc", ".knip.json", "knip.ts", "knip.config.ts"],
83
+ detectText: ["knip"],
84
+ },
85
+ ],
86
+ },
87
+ complexity: {
88
+ tools: [
89
+ {
90
+ name: "fallow health",
91
+ detectFiles: [".fallowrc.json", ".fallowrc.jsonc", "fallow.toml", ".fallow.toml"],
92
+ detectText: ["fallow health", "fallow --only health", "fallow --only dead-code,dupes,health"],
93
+ },
94
+ ],
95
+ },
96
+ dup: {
97
+ tools: [
98
+ {
99
+ name: "fallow dupes",
100
+ detectFiles: [".fallowrc.json", ".fallowrc.jsonc", "fallow.toml", ".fallow.toml"],
101
+ detectText: ["fallow", "dupes"],
102
+ },
103
+ { name: "jscpd", detectFiles: [".jscpd.json"], detectText: ["jscpd", "cpd"] },
104
+ ],
105
+ },
106
+ boundaries: {
107
+ tools: [
108
+ {
109
+ name: "fallow boundaries",
110
+ detectFiles: [".fallowrc.json", ".fallowrc.jsonc", "fallow.toml", ".fallow.toml"],
111
+ detectText: ["boundary"],
112
+ },
113
+ {
114
+ name: "dependency-cruiser",
115
+ detectFiles: [
116
+ ".dependency-cruiser.js",
117
+ ".dependency-cruiser.cjs",
118
+ ".dependency-cruiser.mjs",
119
+ ".dependency-cruiser.json",
120
+ ],
121
+ detectText: ["dependency-cruiser", "depcruise"],
122
+ },
123
+ ],
124
+ },
125
+ },
126
+ };
127
+
128
+ const DENO_PACK: LanguagePack = {
129
+ id: "deno",
130
+ label: "Deno",
131
+ detectFiles: ["deno.json", "deno.jsonc"],
132
+ capabilities: {
133
+ format: {
134
+ tools: [{ name: "deno fmt", detectFiles: ["deno.json", "deno.jsonc"], detectText: ["deno fmt"] }],
135
+ },
136
+ lint: {
137
+ tools: [{ name: "deno lint", detectFiles: ["deno.json", "deno.jsonc"], detectText: ["deno lint"] }],
138
+ },
139
+ types: {
140
+ tools: [{ name: "deno check", detectFiles: ["deno.json", "deno.jsonc"], detectText: ["deno check"] }],
141
+ },
142
+ dead: {
143
+ tools: [],
144
+ unsupportedReason: "No Knip-class whole-repo dead-code tool for Deno; deno lint covers locals only",
145
+ },
146
+ complexity: {
147
+ tools: [],
148
+ unsupportedReason: "No core Deno complexity gate; community lint plugins only",
149
+ },
150
+ dup: {
151
+ tools: [{ name: "jscpd", detectFiles: [".jscpd.json"], detectText: ["jscpd", "cpd"] }],
152
+ },
153
+ boundaries: {
154
+ tools: [
155
+ {
156
+ name: "deno-import-check",
157
+ detectFiles: [],
158
+ detectText: ["deno-import-check", "@cunarist/deno-import-check"],
159
+ },
160
+ ],
161
+ },
162
+ },
163
+ };
164
+
165
+ /** Thin packs: language present + types/build gate only for v1 ceiling honesty. */
166
+ const GO_PACK: LanguagePack = {
167
+ id: "go",
168
+ label: "Go",
169
+ detectFiles: ["go.mod"],
170
+ capabilities: {
171
+ format: {
172
+ tools: [{ name: "gofmt", detectFiles: ["go.mod"], detectText: ["gofmt", "gofumpt", "go fmt"] }],
173
+ },
174
+ lint: {
175
+ tools: [
176
+ {
177
+ name: "golangci-lint",
178
+ detectFiles: [".golangci.yml", ".golangci.yaml", ".golangci.toml", ".golangci.json"],
179
+ detectText: ["golangci-lint"],
180
+ },
181
+ ],
182
+ },
183
+ types: {
184
+ tools: [{ name: "go build/test", detectFiles: ["go.mod"], detectText: ["go build", "go test", "go vet"] }],
185
+ },
186
+ dead: {
187
+ tools: [{ name: "deadcode/unused", detectFiles: [], detectText: ["deadcode", "staticcheck", "unused"] }],
188
+ },
189
+ complexity: {
190
+ tools: [{ name: "gocyclo/cyclop", detectFiles: [], detectText: ["gocyclo", "cyclop", "gocognit"] }],
191
+ },
192
+ dup: {
193
+ tools: [{ name: "dupl", detectFiles: [], detectText: ["dupl"] }],
194
+ },
195
+ boundaries: {
196
+ tools: [{ name: "internal/depguard", detectFiles: [], detectText: ["depguard", "go-arch-lint"] }],
197
+ },
198
+ },
199
+ };
200
+
201
+ const RUST_PACK: LanguagePack = {
202
+ id: "rust",
203
+ label: "Rust",
204
+ detectFiles: ["Cargo.toml"],
205
+ capabilities: {
206
+ format: {
207
+ tools: [
208
+ {
209
+ name: "rustfmt",
210
+ detectFiles: ["rustfmt.toml", ".rustfmt.toml", "Cargo.toml"],
211
+ detectText: ["cargo fmt"],
212
+ },
213
+ ],
214
+ },
215
+ lint: {
216
+ tools: [
217
+ {
218
+ name: "clippy",
219
+ detectFiles: ["clippy.toml", ".clippy.toml", "Cargo.toml"],
220
+ detectText: ["cargo clippy"],
221
+ },
222
+ ],
223
+ },
224
+ types: {
225
+ tools: [
226
+ {
227
+ name: "cargo check",
228
+ detectFiles: ["Cargo.toml"],
229
+ detectText: ["cargo check", "cargo build", "cargo test"],
230
+ },
231
+ ],
232
+ },
233
+ dead: {
234
+ tools: [{ name: "dead_code/machete", detectFiles: [], detectText: ["cargo machete", "dead_code"] }],
235
+ },
236
+ complexity: {
237
+ tools: [],
238
+ unsupportedReason: "No dominant Rust complexity-budget tool in baseline pack",
239
+ },
240
+ dup: {
241
+ tools: [{ name: "jscpd", detectFiles: [".jscpd.json"], detectText: ["jscpd"] }],
242
+ },
243
+ boundaries: {
244
+ tools: [
245
+ { name: "cargo-deny", detectFiles: ["deny.toml"], detectText: ["cargo deny"] },
246
+ { name: "cargo-modules", detectFiles: [], detectText: ["cargo modules"] },
247
+ ],
248
+ },
249
+ },
250
+ };
251
+
252
+ export const LANGUAGE_PACKS: LanguagePack[] = [DENO_PACK, TYPESCRIPT_PACK, GO_PACK, RUST_PACK];
253
+
254
+ export const CAPABILITY_LABELS: Record<PackCapability, string> = {
255
+ format: "Format",
256
+ lint: "Lint",
257
+ types: "Types / build",
258
+ dead: "Dead / unused",
259
+ complexity: "Complexity",
260
+ dup: "Duplication",
261
+ boundaries: "Boundaries",
262
+ };
263
+
264
+ export const CAPABILITY_ORDER: PackCapability[] = [
265
+ "format",
266
+ "lint",
267
+ "types",
268
+ "dead",
269
+ "complexity",
270
+ "dup",
271
+ "boundaries",
272
+ ];
@@ -0,0 +1,445 @@
1
+ import {
2
+ AREA_LABELS,
3
+ AREA_ORDER,
4
+ type ReadyFormat,
5
+ type ReadyReport,
6
+ type ReadyRow,
7
+ type ReadyStatus,
8
+ summarizeCounts,
9
+ } from "./model.ts";
10
+
11
+ const STATUS_ORDER: ReadyStatus[] = ["missing", "weak", "unknown", "pass", "na"];
12
+
13
+ export function renderReadyReport(report: ReadyReport, format: ReadyFormat): string {
14
+ return format === "html" ? renderHtml(report) : renderMarkdown(report);
15
+ }
16
+
17
+ export function readyFileExtension(format: ReadyFormat): "md" | "html" {
18
+ return format === "html" ? "html" : "md";
19
+ }
20
+
21
+ function rowsByArea(report: ReadyReport): Map<string, ReadyRow[]> {
22
+ const map = new Map<string, ReadyRow[]>();
23
+ for (const area of AREA_ORDER) map.set(area, []);
24
+ for (const item of report.rows) {
25
+ const list = map.get(item.area) ?? [];
26
+ list.push(item);
27
+ map.set(item.area, list);
28
+ }
29
+ for (const list of map.values()) {
30
+ list.sort(
31
+ (a, b) => STATUS_ORDER.indexOf(a.status) - STATUS_ORDER.indexOf(b.status) || a.title.localeCompare(b.title),
32
+ );
33
+ }
34
+ return map;
35
+ }
36
+
37
+ function renderMarkdownItem(item: ReadyRow): string[] {
38
+ const lines = [
39
+ `### ${statusGlyph(item.status)} ${item.title}`,
40
+ "",
41
+ `- Status: **${item.status}** (${item.how})`,
42
+ `- ${item.note}`,
43
+ ];
44
+ if (item.evidence.length > 0) {
45
+ lines.push("- Evidence:");
46
+ for (const evidence of item.evidence) lines.push(` - \`${evidence}\``);
47
+ }
48
+ if (item.next) lines.push(`- Next: ${item.next}`);
49
+ lines.push("");
50
+ return lines;
51
+ }
52
+
53
+ function renderMarkdown(report: ReadyReport): string {
54
+ const lines: string[] = [
55
+ "# Agent readiness report",
56
+ "",
57
+ `- Generated: ${report.generatedAtLabel}`,
58
+ `- Root: \`${report.root}\``,
59
+ `- Languages: ${report.languages.length > 0 ? report.languages.join(", ") : "none detected"}`,
60
+ `- Summary: ${summarizeCounts(report.counts)}`,
61
+ "",
62
+ "Scan-only. No scores. Status is presence/heuristic evidence, not prose quality.",
63
+ "",
64
+ ];
65
+
66
+ const byArea = rowsByArea(report);
67
+ for (const area of AREA_ORDER) {
68
+ const rows = byArea.get(area) ?? [];
69
+ if (rows.length === 0) continue;
70
+ lines.push(`## ${AREA_LABELS[area]}`, "");
71
+ for (const item of rows) lines.push(...renderMarkdownItem(item));
72
+ }
73
+
74
+ return `${lines.join("\n").trimEnd()}\n`;
75
+ }
76
+
77
+ function renderHtml(report: ReadyReport): string {
78
+ const byArea = rowsByArea(report);
79
+ const sections: string[] = [];
80
+ for (const area of AREA_ORDER) {
81
+ const rows = byArea.get(area) ?? [];
82
+ if (rows.length === 0) continue;
83
+ const items = rows.map((item) => renderHtmlRow(item)).join("\n");
84
+ sections.push(`
85
+ <details class="area" open>
86
+ <summary><h2>${escapeHtml(AREA_LABELS[area])}</h2></summary>
87
+ <div class="rows">${items}</div>
88
+ </details>`);
89
+ }
90
+
91
+ const lang = report.languages.length > 0 ? escapeHtml(report.languages.join(", ")) : "none detected";
92
+ const summary = STATUS_ORDER.filter((status) => report.counts[status] > 0)
93
+ .map((status) => `<span class="count ${status}">${status} ${report.counts[status]}</span>`)
94
+ .join("");
95
+
96
+ return `<!DOCTYPE html>
97
+ <html lang="en">
98
+ <head>
99
+ <meta charset="utf-8"/>
100
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
101
+ <title>Agent readiness</title>
102
+ <script>
103
+ (() => {
104
+ const key = "ready-theme";
105
+ const stored = localStorage.getItem(key);
106
+ const theme =
107
+ stored === "light" || stored === "dark"
108
+ ? stored
109
+ : window.matchMedia("(prefers-color-scheme: light)").matches
110
+ ? "light"
111
+ : "dark";
112
+ document.documentElement.dataset.theme = theme;
113
+ })();
114
+ </script>
115
+ <style>
116
+ :root {
117
+ --pass: #4cc38a;
118
+ --weak: #f0b429;
119
+ --missing: #f2555a;
120
+ --na: #6b7280;
121
+ --unknown: #a78bfa;
122
+ --font: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
123
+ --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
124
+ }
125
+ html[data-theme="dark"] {
126
+ color-scheme: dark;
127
+ --bg: #0c0c0d;
128
+ --text: #ebebeb;
129
+ --muted: #8a8f98;
130
+ --faint: #5c6370;
131
+ --line: #2a2b2f;
132
+ }
133
+ html[data-theme="light"] {
134
+ color-scheme: light;
135
+ --bg: #ffffff;
136
+ --text: #1a1a1a;
137
+ --muted: #6b6f76;
138
+ --faint: #9b9fa8;
139
+ --line: #e6e6e6;
140
+ }
141
+ * { box-sizing: border-box; }
142
+ body {
143
+ margin: 0;
144
+ background: var(--bg);
145
+ color: var(--text);
146
+ font-family: var(--font);
147
+ font-size: 14px;
148
+ line-height: 1.5;
149
+ -webkit-font-smoothing: antialiased;
150
+ }
151
+ main {
152
+ max-width: 720px;
153
+ margin: 0 auto;
154
+ padding: 4rem 1.5rem 6rem;
155
+ }
156
+ header {
157
+ margin-bottom: 3.5rem;
158
+ }
159
+ h1 {
160
+ margin: 0 0 1.25rem;
161
+ font-size: 1.375rem;
162
+ font-weight: 560;
163
+ letter-spacing: -0.02em;
164
+ line-height: 1.2;
165
+ }
166
+ .meta {
167
+ display: grid;
168
+ gap: 0.35rem;
169
+ color: var(--muted);
170
+ font-size: 0.8125rem;
171
+ }
172
+ .meta span.label {
173
+ color: var(--faint);
174
+ display: inline-block;
175
+ min-width: 5.5rem;
176
+ }
177
+ .summary {
178
+ display: flex;
179
+ flex-wrap: wrap;
180
+ gap: 1rem 1.5rem;
181
+ margin-top: 1.5rem;
182
+ font-size: 0.8125rem;
183
+ font-variant-numeric: tabular-nums;
184
+ }
185
+ .count { color: var(--muted); }
186
+ .count.pass { color: var(--pass); }
187
+ .count.weak { color: var(--weak); }
188
+ .count.missing { color: var(--missing); }
189
+ .count.na { color: var(--na); }
190
+ .count.unknown { color: var(--unknown); }
191
+ .area { margin: 0 0 3.5rem; }
192
+ .area > summary {
193
+ list-style: none;
194
+ cursor: pointer;
195
+ user-select: none;
196
+ display: flex;
197
+ align-items: center;
198
+ gap: 0.65rem;
199
+ padding-bottom: 0.75rem;
200
+ margin-bottom: 1.5rem;
201
+ border-bottom: 1px solid var(--line);
202
+ }
203
+ .area > summary::-webkit-details-marker { display: none; }
204
+ .area > summary::before {
205
+ content: "";
206
+ width: 0.4rem;
207
+ height: 0.4rem;
208
+ border-right: 1.5px solid var(--muted);
209
+ border-bottom: 1.5px solid var(--muted);
210
+ transform: rotate(45deg);
211
+ transition: transform 120ms ease;
212
+ flex-shrink: 0;
213
+ }
214
+ .area[open] > summary::before {
215
+ transform: rotate(225deg);
216
+ translate: 0 0.1rem;
217
+ }
218
+ .area h2 {
219
+ margin: 0;
220
+ font-size: 1.125rem;
221
+ font-weight: 560;
222
+ letter-spacing: -0.015em;
223
+ color: var(--text);
224
+ }
225
+ .rows {
226
+ display: grid;
227
+ gap: 1.75rem;
228
+ }
229
+ .row { padding: 0; }
230
+ .row-head {
231
+ display: flex;
232
+ align-items: baseline;
233
+ justify-content: space-between;
234
+ gap: 1rem;
235
+ }
236
+ .row h3 {
237
+ margin: 0;
238
+ font-size: 0.9375rem;
239
+ font-weight: 510;
240
+ letter-spacing: -0.01em;
241
+ }
242
+ .status {
243
+ flex-shrink: 0;
244
+ font-size: 0.75rem;
245
+ font-weight: 500;
246
+ letter-spacing: 0.02em;
247
+ text-transform: lowercase;
248
+ }
249
+ .status.pass { color: var(--pass); }
250
+ .status.weak { color: var(--weak); }
251
+ .status.missing { color: var(--missing); }
252
+ .status.na { color: var(--na); }
253
+ .status.unknown { color: var(--unknown); }
254
+ .note {
255
+ margin: 0.4rem 0 0;
256
+ color: var(--muted);
257
+ font-size: 0.875rem;
258
+ max-width: 58ch;
259
+ }
260
+ .evidence {
261
+ margin: 0.65rem 0 0;
262
+ padding: 0;
263
+ list-style: none;
264
+ display: grid;
265
+ gap: 0.25rem;
266
+ }
267
+ .evidence li {
268
+ color: var(--faint);
269
+ font-size: 0.75rem;
270
+ font-family: var(--mono);
271
+ line-height: 1.4;
272
+ }
273
+ .next {
274
+ margin: 0.7rem 0 0;
275
+ color: var(--text);
276
+ font-size: 0.8125rem;
277
+ }
278
+ .next span.label {
279
+ color: var(--faint);
280
+ margin-right: 0.35rem;
281
+ }
282
+ code {
283
+ font-family: var(--mono);
284
+ font-size: 0.9em;
285
+ }
286
+ .dock {
287
+ position: fixed;
288
+ right: 1.25rem;
289
+ bottom: 1.25rem;
290
+ display: flex;
291
+ gap: 0.5rem;
292
+ z-index: 20;
293
+ }
294
+ .dock button,
295
+ .theme-toggle {
296
+ appearance: none;
297
+ margin: 0;
298
+ border: 1px solid var(--line);
299
+ width: 2.5rem;
300
+ height: 2.5rem;
301
+ border-radius: 999px;
302
+ display: grid;
303
+ place-items: center;
304
+ background: var(--bg);
305
+ color: var(--muted);
306
+ cursor: pointer;
307
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
308
+ }
309
+ .dock button:hover,
310
+ .theme-toggle:hover {
311
+ color: var(--text);
312
+ background: color-mix(in srgb, var(--bg) 88%, var(--text));
313
+ }
314
+ .dock button:focus-visible,
315
+ .theme-toggle:focus-visible {
316
+ outline: 1px solid var(--text);
317
+ outline-offset: 2px;
318
+ }
319
+ .dock svg,
320
+ .theme-toggle svg {
321
+ width: 1.05rem;
322
+ height: 1.05rem;
323
+ stroke: currentColor;
324
+ fill: none;
325
+ stroke-width: 1.75;
326
+ stroke-linecap: round;
327
+ stroke-linejoin: round;
328
+ }
329
+ .theme-toggle {
330
+ position: fixed;
331
+ top: 1.25rem;
332
+ right: 1.25rem;
333
+ z-index: 20;
334
+ }
335
+ html[data-theme="dark"] .theme-toggle .icon-moon { display: none; }
336
+ html[data-theme="light"] .theme-toggle .icon-sun { display: none; }
337
+ </style>
338
+ </head>
339
+ <body>
340
+ <button type="button" class="theme-toggle" id="theme-toggle" title="Toggle theme" aria-label="Toggle theme">
341
+ <svg class="icon-sun" viewBox="0 0 24 24" aria-hidden="true">
342
+ <circle cx="12" cy="12" r="4"/>
343
+ <path d="M12 2v2"/>
344
+ <path d="M12 20v2"/>
345
+ <path d="m4.93 4.93 1.41 1.41"/>
346
+ <path d="m17.66 17.66 1.41 1.41"/>
347
+ <path d="M2 12h2"/>
348
+ <path d="M20 12h2"/>
349
+ <path d="m6.34 17.66-1.41 1.41"/>
350
+ <path d="m19.07 4.93-1.41 1.41"/>
351
+ </svg>
352
+ <svg class="icon-moon" viewBox="0 0 24 24" aria-hidden="true">
353
+ <path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>
354
+ </svg>
355
+ </button>
356
+ <main>
357
+ <header>
358
+ <h1>Agent readiness</h1>
359
+ <div class="meta">
360
+ <div><span class="label">Generated</span>${escapeHtml(report.generatedAtLabel)}</div>
361
+ <div><span class="label">Root</span><code>${escapeHtml(report.root)}</code></div>
362
+ <div><span class="label">Languages</span>${lang}</div>
363
+ </div>
364
+ <div class="summary">${summary}</div>
365
+ </header>
366
+ ${sections.join("\n")}
367
+ </main>
368
+ <div class="dock" role="group" aria-label="Sections">
369
+ <button type="button" id="expand-all" title="Expand all" aria-label="Expand all">
370
+ <svg viewBox="0 0 24 24" aria-hidden="true">
371
+ <path d="m7 15 5 5 5-5"/>
372
+ <path d="m7 9 5-5 5 5"/>
373
+ </svg>
374
+ </button>
375
+ <button type="button" id="collapse-all" title="Collapse all" aria-label="Collapse all">
376
+ <svg viewBox="0 0 24 24" aria-hidden="true">
377
+ <path d="m7 20 5-5 5 5"/>
378
+ <path d="m7 4 5 5 5-5"/>
379
+ </svg>
380
+ </button>
381
+ </div>
382
+ <script>
383
+ (() => {
384
+ const sections = () => document.querySelectorAll("details.area");
385
+ document.getElementById("expand-all")?.addEventListener("click", () => {
386
+ for (const el of sections()) el.open = true;
387
+ });
388
+ document.getElementById("collapse-all")?.addEventListener("click", () => {
389
+ for (const el of sections()) el.open = false;
390
+ });
391
+
392
+ const root = document.documentElement;
393
+ const key = "ready-theme";
394
+ document.getElementById("theme-toggle")?.addEventListener("click", () => {
395
+ const next = root.dataset.theme === "light" ? "dark" : "light";
396
+ root.dataset.theme = next;
397
+ localStorage.setItem(key, next);
398
+ });
399
+ })();
400
+ </script>
401
+ </body>
402
+ </html>
403
+ `;
404
+ }
405
+
406
+ function renderHtmlRow(item: ReadyRow): string {
407
+ const evidence =
408
+ item.evidence.length === 0
409
+ ? ""
410
+ : `<ul class="evidence">${item.evidence.map((entry) => `<li>${escapeHtml(entry)}</li>`).join("")}</ul>`;
411
+ const next = item.next ? `<p class="next"><span class="label">Next</span>${escapeHtml(item.next)}</p>` : "";
412
+ return `<article class="row">
413
+ <div class="row-head">
414
+ <h3>${escapeHtml(item.title)}</h3>
415
+ <span class="status ${item.status}">${item.status}</span>
416
+ </div>
417
+ <p class="note">${escapeHtml(item.note)}</p>
418
+ ${evidence}
419
+ ${next}
420
+ </article>`;
421
+ }
422
+
423
+ function statusGlyph(status: ReadyStatus): string {
424
+ switch (status) {
425
+ case "pass":
426
+ return "[pass]";
427
+ case "weak":
428
+ return "[weak]";
429
+ case "missing":
430
+ return "[missing]";
431
+ case "na":
432
+ return "[n/a]";
433
+ case "unknown":
434
+ return "[?]";
435
+ }
436
+ }
437
+
438
+ function escapeHtml(value: string): string {
439
+ return value
440
+ .replaceAll("&", "&amp;")
441
+ .replaceAll("<", "&lt;")
442
+ .replaceAll(">", "&gt;")
443
+ .replaceAll('"', "&quot;")
444
+ .replaceAll("'", "&#39;");
445
+ }