@inflexa-ai/harness 0.23.0 → 0.24.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 (68) hide show
  1. package/README.md +1 -2
  2. package/dist/agents/conversation-agent.d.ts +0 -9
  3. package/dist/contracts/chat-parts.d.ts +29 -33
  4. package/dist/contracts/content-url.d.ts +7 -41
  5. package/dist/contracts/content-url.js +7 -48
  6. package/dist/contracts/index.d.ts +2 -2
  7. package/dist/contracts/index.js +1 -1
  8. package/dist/contracts/message.d.ts +1 -1
  9. package/dist/contracts/message.js +1 -1
  10. package/dist/contracts/part-registry.js +2 -3
  11. package/dist/contracts/schemas/chat-parts.d.ts +16 -53
  12. package/dist/contracts/schemas/chat-parts.js +15 -25
  13. package/dist/contracts/schemas/index.d.ts +1 -1
  14. package/dist/contracts/schemas/index.js +1 -1
  15. package/dist/execution/run-authorizer.d.ts +1 -1
  16. package/dist/execution/run-authorizer.js +1 -1
  17. package/dist/index.d.ts +1 -3
  18. package/dist/index.js +0 -2
  19. package/dist/lib/zod-issues.d.ts +2 -3
  20. package/dist/lib/zod-issues.js +2 -3
  21. package/dist/loop/__fixtures__/scripted-provider.js +3 -1
  22. package/dist/loop/run-agent.d.ts +5 -0
  23. package/dist/loop/run-agent.js +10 -1
  24. package/dist/loop/run-to-terminal.d.ts +1 -1
  25. package/dist/loop/run-to-terminal.js +1 -1
  26. package/dist/memory/card-builders.d.ts +0 -27
  27. package/dist/memory/card-builders.js +0 -75
  28. package/dist/memory/conversation-display-backfill.d.ts +2 -4
  29. package/dist/memory/conversation-display-backfill.js +3 -9
  30. package/dist/memory/conversation-display-storage.d.ts +3 -4
  31. package/dist/memory/conversation-display-storage.js +3 -4
  32. package/dist/memory/reconstruct-cards.d.ts +5 -7
  33. package/dist/memory/reconstruct-cards.js +6 -20
  34. package/dist/runtime/boot.js +2 -12
  35. package/dist/tools/report-session/preview-report.d.ts +2 -1
  36. package/dist/tools/report-session/preview-report.js +9 -1
  37. package/dist/tools/start-report-session.js +2 -2
  38. package/dist/workspace/paths.d.ts +0 -21
  39. package/dist/workspace/paths.js +0 -37
  40. package/package.json +1 -3
  41. package/dist/agents/report-builder.d.ts +0 -34
  42. package/dist/agents/report-builder.js +0 -34
  43. package/dist/execution/report-render.d.ts +0 -39
  44. package/dist/execution/report-render.js +0 -98
  45. package/dist/execution/report-runner.d.ts +0 -111
  46. package/dist/execution/report-runner.js +0 -230
  47. package/dist/prompts/report-builder.d.ts +0 -1
  48. package/dist/prompts/report-builder.js +0 -174
  49. package/dist/tools/iterate-report.d.ts +0 -227
  50. package/dist/tools/iterate-report.js +0 -883
  51. package/dist/tools/lib/preview-lock.d.ts +0 -20
  52. package/dist/tools/lib/preview-lock.js +0 -35
  53. package/dist/tools/lib/report-preflight.d.ts +0 -39
  54. package/dist/tools/lib/report-preflight.js +0 -283
  55. package/dist/tools/report/build-report.d.ts +0 -13
  56. package/dist/tools/report/build-report.js +0 -27
  57. package/dist/tools/report/index.d.ts +0 -15
  58. package/dist/tools/report/index.js +0 -15
  59. package/dist/tools/report/mint-preview-url.d.ts +0 -26
  60. package/dist/tools/report/mint-preview-url.js +0 -47
  61. package/dist/tools/report/preview-publisher.d.ts +0 -53
  62. package/dist/tools/report/preview-publisher.js +0 -45
  63. package/dist/tools/report/preview-snapshot.d.ts +0 -34
  64. package/dist/tools/report/preview-snapshot.js +0 -98
  65. package/dist/tools/report/submit-report.d.ts +0 -26
  66. package/dist/tools/report/submit-report.js +0 -148
  67. package/dist/tools/report/version-fs.d.ts +0 -15
  68. package/dist/tools/report/version-fs.js +0 -165
@@ -1,98 +0,0 @@
1
- /**
2
- * Report renderer — Node-side Nunjucks render of v{N}/report.html.j2 to v{N}/index.html.
3
- *
4
- * Stateless pure module: no harness imports, no env reads at import time,
5
- * safe to run standalone in tests.
6
- */
7
- import { readFile, writeFile } from "node:fs/promises";
8
- import { join } from "node:path";
9
- import nunjucks from "nunjucks";
10
- /**
11
- * Render `${versionDir}/report.html.j2` to `${versionDir}/index.html`.
12
- *
13
- * Loader resolution order: [versionDir, templatesDir]. `report.html.j2`
14
- * lives in versionDir; `extends "base.html.j2"` and `include "components/..."`
15
- * resolve from templatesDir. Autoescape is OFF — templates own escaping.
16
- *
17
- * `${templatesDir}/echarts-theme.json` is read, parsed, re-stringified, and
18
- * passed as `echarts_theme` (a JSON string) for safe inlining inside a
19
- * `<script>` block.
20
- *
21
- * Returns `{ ok: true, indexBytes }` on success. On failure, returns
22
- * `{ ok: false, indexBytes: 0, error }` with `error.kind`:
23
- * - "missing-template" — `report.html.j2` (or any `extends`/`include` target)
24
- * not found by either loader. Message identifies the missing path.
25
- * - "syntax" — Nunjucks parse error in any loaded template.
26
- * - "runtime" — anything else thrown during render or write.
27
- */
28
- export async function renderReport(opts) {
29
- const { versionDir, templatesDir } = opts;
30
- let echartsThemeJson;
31
- try {
32
- const raw = await readFile(join(templatesDir, "echarts-theme.json"), "utf8");
33
- echartsThemeJson = JSON.stringify(JSON.parse(raw));
34
- }
35
- catch (err) {
36
- const msg = err instanceof Error ? err.message : String(err);
37
- const kind = isNotFound(err) ? "missing-template" : "runtime";
38
- return {
39
- ok: false,
40
- indexBytes: 0,
41
- error: { kind, message: `failed to load echarts-theme.json: ${msg}` },
42
- };
43
- }
44
- const env = new nunjucks.Environment(new nunjucks.FileSystemLoader([versionDir, templatesDir], {
45
- noCache: true,
46
- }), { autoescape: false, throwOnUndefined: false });
47
- let html;
48
- try {
49
- html = env.render("report.html.j2", { echarts_theme: echartsThemeJson });
50
- }
51
- catch (err) {
52
- return { ok: false, indexBytes: 0, error: classifyNunjucksError(err) };
53
- }
54
- try {
55
- const indexPath = join(versionDir, "index.html");
56
- await writeFile(indexPath, html, "utf8");
57
- return { ok: true, indexBytes: Buffer.byteLength(html, "utf8") };
58
- }
59
- catch (err) {
60
- const msg = err instanceof Error ? err.message : String(err);
61
- return {
62
- ok: false,
63
- indexBytes: 0,
64
- error: { kind: "runtime", message: `failed to write index.html: ${msg}` },
65
- };
66
- }
67
- }
68
- function isNotFound(err) {
69
- if (!err || typeof err !== "object")
70
- return false;
71
- const code = err.code;
72
- return code === "ENOENT";
73
- }
74
- function classifyNunjucksError(err) {
75
- const message = err instanceof Error ? err.message : String(err);
76
- if (/template not found/i.test(message)) {
77
- return { kind: "missing-template", message };
78
- }
79
- // Nunjucks tags parse errors with "Template render error" + "expected" /
80
- // "unexpected" / "parseError" markers; the Template error carries lineno.
81
- const lineno = err?.lineno;
82
- const colno = err?.colno;
83
- const line = typeof lineno === "number" ? lineno : undefined;
84
- const isSyntax = /parseError|expected|unexpected token|unexpected end of/i.test(message) || /Template render error.*line \d+/i.test(message);
85
- if (isSyntax) {
86
- const snippet = extractSnippet(message, line, typeof colno === "number" ? colno : undefined);
87
- return snippet ? { kind: "syntax", message, line, snippet } : line !== undefined ? { kind: "syntax", message, line } : { kind: "syntax", message };
88
- }
89
- return line !== undefined ? { kind: "runtime", message, line } : { kind: "runtime", message };
90
- }
91
- function extractSnippet(message, line, col) {
92
- if (!line)
93
- return undefined;
94
- const m = message.split("\n").find((l) => l.trim().length > 0 && !/^\s*at\s/.test(l));
95
- if (!m)
96
- return undefined;
97
- return col !== undefined ? `${m} (col ${col})` : m;
98
- }
@@ -1,111 +0,0 @@
1
- /**
2
- * Report runner — orchestrates an iterative report build entirely in the
3
- * Cortex Node process. No sandbox, no Python.
4
- *
5
- * Lifecycle:
6
- * 1. Resolve version (latest + 1, or baseVersion + 1)
7
- * 2. Prepare version dir (mkdir, copy v{base}/report.html.j2, symlink ../assets)
8
- * 3. Construct the 4 custom report tools + the in-process versionFs surface;
9
- * all of them close over the iteration's preview-dir state and a single
10
- * `outcome: ReportOutcome | undefined` variable (the closure-state
11
- * closure-state capture — see design.md Decision #9, mirrors
12
- * `harness/tools/research/generate-plan.ts`'s
13
- * `PlannerOutcome` capture).
14
- * 4. Build the `report-builder` AgentDefinition with that tool roster and
15
- * drive it via `runAgent` + `passthroughStep`.
16
- * 5. Verify the closure-captured `outcome` is `{ ok: true, ... }` (else
17
- * classify the failure, roll back the version dir, return a structured
18
- * failure).
19
- * 6. Return success or a structured failure.
20
- *
21
- * Concurrency: per-previewId serialization via `withPreviewLock` so two
22
- * iterations on the same preview don't race on shared assets/.
23
- *
24
- * Billing: the report-builder's LLM calls flow through the `ChatProvider`
25
- * seam with `forSubAgent(session, REPORT_BUILDER_AGENT_ID)` — the provider's
26
- * lazy `resolveBilling` fires at the call site. No ALS, no fetch-patch
27
- * (Decision #6).
28
- *
29
- * Preview surface: the `preview_snapshot` and `mint_preview_url` tools mint
30
- * a content-server URL through the injected `PreviewPublisher` seam. The
31
- * managed publisher owns the short-lived host credential; the local default
32
- * returns "unavailable" so the report still builds without a hosted preview.
33
- */
34
- import type { Pool } from "pg";
35
- import { type RequestSession } from "../auth/types.js";
36
- import type { EmitFn } from "../loop/types.js";
37
- import type { ChatProvider } from "../providers/types.js";
38
- import type { PreviewPublisher } from "../tools/report/preview-publisher.js";
39
- import type { ChromeConfig } from "../lib/chrome.js";
40
- import type { Logger } from "../lib/logger.js";
41
- import type { UsageRecorder } from "../billing/usage-recorder.js";
42
- import type { AgentRunUsage } from "../loop/metrics.js";
43
- export interface ReportRunnerDeps {
44
- /** Operational logging seam; omitted falls back to no-op. */
45
- readonly logger?: Logger;
46
- readonly provider: ChatProvider;
47
- readonly pool: Pool;
48
- /** Model id — provenance / metric label; provider owns the wire model. */
49
- readonly model: string;
50
- /** Root templates dir; `report-html` is joined onto it. */
51
- readonly templatesDir: string;
52
- /** Skills root; the report-builder gets read-only `report-html` skill tools. */
53
- readonly skillsDir: string;
54
- readonly chrome: ChromeConfig;
55
- /** LLM usage-accounting seam for the report-builder loop; omitted falls back to the no-op recorder. */
56
- readonly usageRecorder?: UsageRecorder;
57
- }
58
- export interface ReportRunnerOptions {
59
- readonly resourceId: string;
60
- /** Absolute host root of the analysis's workspace tree (previews live inside it). */
61
- readonly workspaceRoot: string;
62
- readonly previews: PreviewPublisher;
63
- /** Preview group ID. Auto-generated by the tool layer if not provided. */
64
- readonly previewId: string;
65
- /** Base version to iterate from. Defaults to latest. */
66
- readonly baseVersion?: number;
67
- /**
68
- * Format the preview was built as. The tool boundary admits only `"html"`,
69
- * so no other value can reach a live iteration; the union stays wide because
70
- * previews persisted with a broader vocabulary must still parse. Read this
71
- * field as a record of what a preview is, not as a request for a renderer.
72
- */
73
- readonly format: "html" | "pdf";
74
- /** Prompt for the report-builder agent. */
75
- readonly prompt: string;
76
- /** Live chat session — derives the child agent's session via forSubAgent. */
77
- readonly session: RequestSession;
78
- /** Cancellation signal — propagates to the agent stream. */
79
- readonly signal: AbortSignal;
80
- /** Event sink — forwarded to the agent loop. */
81
- readonly emit: EmitFn;
82
- /**
83
- * The calling turn's usage accumulator (`ctx.turnUsage`), so the
84
- * report-builder loop's tokens land in the turn total its root reports.
85
- */
86
- readonly turnUsage?: AgentRunUsage;
87
- /**
88
- * The dispatching tool call's id (`ctx.invocationId`), which discriminates
89
- * the builder loop's usage record keys from those of a sibling iteration
90
- * dispatched in the same round — same frame, same call path, same
91
- * loop-local step names.
92
- */
93
- readonly invocationId?: string;
94
- }
95
- export type ReportRunnerResult = {
96
- ok: true;
97
- previewId: string;
98
- version: number;
99
- previewPath: string;
100
- durationMs: number;
101
- /** Caveats surfaced by the agent via submit_report. */
102
- notes: readonly string[];
103
- } | {
104
- ok: false;
105
- previewId: string;
106
- version: number;
107
- reason: string;
108
- errorKind: "render" | "submit" | "build" | "timeout" | "internal";
109
- durationMs: number;
110
- };
111
- export declare function runReportIteration(deps: ReportRunnerDeps, opts: ReportRunnerOptions): Promise<ReportRunnerResult>;
@@ -1,230 +0,0 @@
1
- /**
2
- * Report runner — orchestrates an iterative report build entirely in the
3
- * Cortex Node process. No sandbox, no Python.
4
- *
5
- * Lifecycle:
6
- * 1. Resolve version (latest + 1, or baseVersion + 1)
7
- * 2. Prepare version dir (mkdir, copy v{base}/report.html.j2, symlink ../assets)
8
- * 3. Construct the 4 custom report tools + the in-process versionFs surface;
9
- * all of them close over the iteration's preview-dir state and a single
10
- * `outcome: ReportOutcome | undefined` variable (the closure-state
11
- * closure-state capture — see design.md Decision #9, mirrors
12
- * `harness/tools/research/generate-plan.ts`'s
13
- * `PlannerOutcome` capture).
14
- * 4. Build the `report-builder` AgentDefinition with that tool roster and
15
- * drive it via `runAgent` + `passthroughStep`.
16
- * 5. Verify the closure-captured `outcome` is `{ ok: true, ... }` (else
17
- * classify the failure, roll back the version dir, return a structured
18
- * failure).
19
- * 6. Return success or a structured failure.
20
- *
21
- * Concurrency: per-previewId serialization via `withPreviewLock` so two
22
- * iterations on the same preview don't race on shared assets/.
23
- *
24
- * Billing: the report-builder's LLM calls flow through the `ChatProvider`
25
- * seam with `forSubAgent(session, REPORT_BUILDER_AGENT_ID)` — the provider's
26
- * lazy `resolveBilling` fires at the call site. No ALS, no fetch-patch
27
- * (Decision #6).
28
- *
29
- * Preview surface: the `preview_snapshot` and `mint_preview_url` tools mint
30
- * a content-server URL through the injected `PreviewPublisher` seam. The
31
- * managed publisher owns the short-lived host credential; the local default
32
- * returns "unavailable" so the report still builds without a hosted preview.
33
- */
34
- import { copyFile, mkdir, readdir, rm, stat, symlink } from "node:fs/promises";
35
- import { join } from "node:path";
36
- import { forSubAgent } from "../auth/types.js";
37
- import { runToTerminal } from "../loop/run-to-terminal.js";
38
- import { passthroughStep } from "../loop/run-step.js";
39
- import { previewVersionDir, previewDir, latestPreviewVersion } from "../workspace/paths.js";
40
- import { withPreviewLock } from "../tools/lib/preview-lock.js";
41
- import { createBuildReportTool, createSubmitReportTool, createMintPreviewUrlTool, createPreviewSnapshotTool, createVersionFsTools, } from "../tools/report/index.js";
42
- import { createReportBuilderAgent, REPORT_BUILDER_AGENT_ID, REPORT_BUILDER_SKILLS } from "../agents/report-builder.js";
43
- import { createSkillTools } from "../tools/sandbox/skills.js";
44
- import { createNoopLogger } from "../lib/console-logger.js";
45
- const REPORT_AGENT_MAX_STEPS = 75;
46
- export async function runReportIteration(deps, opts) {
47
- const logger = (deps.logger ?? createNoopLogger()).named("report-runner").with({ previewId: opts.previewId });
48
- const startTime = performance.now();
49
- return withPreviewLock(opts.previewId, async () => {
50
- // ── Resolve version directories ───────────────────────────────
51
- const previewRootAbs = join(opts.workspaceRoot, previewDir(opts.previewId));
52
- let existingVersions = [];
53
- try {
54
- existingVersions = (await readdir(previewRootAbs, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
55
- }
56
- catch {
57
- /* dir doesn't exist yet */
58
- }
59
- const latestVer = latestPreviewVersion(existingVersions);
60
- const baseVer = opts.baseVersion ?? latestVer;
61
- const newVersion = Math.max(latestVer, baseVer) + 1;
62
- const versionDirRel = previewVersionDir(opts.previewId, newVersion);
63
- const versionDirAbs = join(opts.workspaceRoot, versionDirRel);
64
- const assetsDirAbs = join(previewRootAbs, "assets");
65
- // ── Prepare directories ───────────────────────────────────────
66
- try {
67
- await mkdir(versionDirAbs, { recursive: true });
68
- await mkdir(assetsDirAbs, { recursive: true });
69
- if (baseVer > 0) {
70
- const baseTemplate = join(opts.workspaceRoot, previewVersionDir(opts.previewId, baseVer), "report.html.j2");
71
- try {
72
- await copyFile(baseTemplate, join(versionDirAbs, "report.html.j2"));
73
- }
74
- catch (err) {
75
- const code = err.code;
76
- if (code !== "ENOENT")
77
- throw err;
78
- // Base version has no template — agent will author from scratch.
79
- }
80
- }
81
- // Symlink assets/ → ../assets so templates can reference assets/foo.csv
82
- // and the content-server resolves them through the version-dir prefix.
83
- const assetsSymlink = join(versionDirAbs, "assets");
84
- try {
85
- await symlink("../assets", assetsSymlink, "dir");
86
- }
87
- catch (err) {
88
- const code = err.code;
89
- if (code !== "EEXIST")
90
- throw err;
91
- }
92
- }
93
- catch (err) {
94
- const msg = err instanceof Error ? err.message : String(err);
95
- return failure(opts.previewId, newVersion, `prepareVersionDir failed: ${msg}`, "internal", startTime);
96
- }
97
- // ── Closure-captured terminal-tool state (Decision #9) ─────────
98
- let outcome;
99
- const urlCell = { url: undefined, expiresAt: undefined };
100
- const templatesDir = join(deps.templatesDir, "report-html");
101
- const buildReportTool = createBuildReportTool({
102
- versionDir: versionDirAbs,
103
- templatesDir,
104
- });
105
- const submitReportTool = createSubmitReportTool({
106
- versionDir: versionDirAbs,
107
- assetsDir: assetsDirAbs,
108
- setOutcome: (v) => {
109
- outcome = v;
110
- },
111
- });
112
- const tools = [
113
- buildReportTool,
114
- submitReportTool,
115
- createMintPreviewUrlTool({
116
- resourceId: opts.resourceId,
117
- previewId: opts.previewId,
118
- currentVersion: newVersion,
119
- previews: opts.previews,
120
- urlCell,
121
- }),
122
- createPreviewSnapshotTool({
123
- resourceId: opts.resourceId,
124
- previewId: opts.previewId,
125
- currentVersion: newVersion,
126
- previews: opts.previews,
127
- urlCell,
128
- chrome: deps.chrome,
129
- logger,
130
- }),
131
- ...createVersionFsTools({ versionDir: versionDirAbs }),
132
- ...Object.values(createSkillTools({ skillsDir: deps.skillsDir, skills: [...REPORT_BUILDER_SKILLS] })),
133
- ];
134
- const agent = createReportBuilderAgent({ model: deps.model, tools });
135
- // The agent's runaway-guard takes precedence over the per-iteration cap.
136
- const cappedAgent = {
137
- ...agent,
138
- maxIterations: Math.min(agent.maxIterations, REPORT_AGENT_MAX_STEPS),
139
- };
140
- const childSession = forSubAgent(opts.session, REPORT_BUILDER_AGENT_ID);
141
- let agentError;
142
- try {
143
- await runToTerminal(cappedAgent, [{ role: "user", content: opts.prompt }], childSession, {
144
- provider: deps.provider,
145
- signal: opts.signal,
146
- emit: opts.emit,
147
- runStep: passthroughStep,
148
- resolved: () => outcome !== undefined,
149
- usageRecorder: deps.usageRecorder,
150
- // Fold the builder's calls into the turn total the root loop reports.
151
- turnUsage: opts.turnUsage,
152
- invocationId: opts.invocationId,
153
- }, {
154
- tools: [buildReportTool, submitReportTool],
155
- nudge: "You stopped without calling submit_report, so this report version " +
156
- "was not recorded. Build the report if needed, then call " +
157
- "submit_report now to finalize it.",
158
- });
159
- }
160
- catch (err) {
161
- agentError = err instanceof Error ? err.message : String(err);
162
- }
163
- // ── Phantom-success guard ──────────────────────────────────────
164
- // submit-report claims ok but index.html doesn't exist or is empty.
165
- let phantomSuccess = false;
166
- if (outcome?.ok === true && !agentError) {
167
- try {
168
- const indexStat = await stat(join(versionDirAbs, "index.html"));
169
- if (!indexStat.isFile() || indexStat.size === 0)
170
- phantomSuccess = true;
171
- }
172
- catch {
173
- phantomSuccess = true;
174
- }
175
- }
176
- if (!outcome || agentError || phantomSuccess) {
177
- logger.warn("report.agent.finish", { version: newVersion, submitted: outcome?.ok === true, phantom: phantomSuccess, agentError });
178
- const { reason, errorKind } = classifyFailure({
179
- phantomSuccess,
180
- agentError,
181
- outcome,
182
- });
183
- // Roll back the version dir (assets are shared — leave them alone).
184
- try {
185
- await rm(versionDirAbs, { recursive: true, force: true });
186
- }
187
- catch (err) {
188
- logger.error("rollback failed", { versionDirAbs, ...logger.errorFields(err) });
189
- }
190
- return failure(opts.previewId, newVersion, reason, errorKind, startTime);
191
- }
192
- const durationMs = Math.round(performance.now() - startTime);
193
- return {
194
- ok: true,
195
- previewId: opts.previewId,
196
- version: newVersion,
197
- previewPath: `v${newVersion}/index.html`,
198
- durationMs,
199
- notes: outcome.notes,
200
- };
201
- });
202
- }
203
- function failure(previewId, version, reason, errorKind, startTime) {
204
- return {
205
- ok: false,
206
- previewId,
207
- version,
208
- reason,
209
- errorKind,
210
- durationMs: Math.round(performance.now() - startTime),
211
- };
212
- }
213
- function classifyFailure(s) {
214
- if (s.phantomSuccess) {
215
- return {
216
- reason: "submit_report claimed ok but index.html is missing or empty",
217
- errorKind: "build",
218
- };
219
- }
220
- if (s.agentError) {
221
- return {
222
- reason: s.agentError,
223
- errorKind: /timeout/i.test(s.agentError) ? "timeout" : "submit",
224
- };
225
- }
226
- return {
227
- reason: "agent did not call submit_report — terminal outcome was never recorded",
228
- errorKind: "submit",
229
- };
230
- }
@@ -1 +0,0 @@
1
- export declare const reportBuilderPrompt = "# Report Builder\n\nYou build polished HTML reports from a structured brief using Jinja2 templating with the Inflexa Design Blueprint. The conversation agent has already done the analysis discovery, picked the data, and composed the prose \u2014 your job is to lay it out using the design system. Reports are light-themed, full-width, Space Grotesk + IBM Plex Mono typography with corner-accent cards and section textures.\n\n## CRITICAL \u2014 Read This First\n\n**Every iteration MUST terminate by calling `submit_report` and receiving `ok: true`. That is the only signal the runner acts on.**\n\nA response that ends without a successful `submit_report` is a **failure** \u2014 the runner rolls back the version directory. `build_report` returning `ok: true` does NOT ship the report. `preview_snapshot` returning a screenshot does NOT ship the report. Only `submit_report` does.\n\nThe brief you receive is **complete**:\n- Every asset you might need is already staged in `assets/` (the brief lists name, kind, size, columns, head rows, row count for each).\n- Every section's content is provided \u2014 narrative prose, metric values, asset names, chart specs.\n- You **cannot see the analysis tree.** There is no discovery to do, no files to find, no columns to peek at. `workspace_search`, `list_files`, and `file_stat` are disabled.\n\nIf a section references something not in the brief or in `assets/`, that's a real failure \u2014 stop and emit one short final message naming what's missing. Do NOT attempt to find it or fabricate substitutes.\n\n## Workflow\n\n1. **Read the brief.** Identify the title, audience, sections, staged assets, and any iteration-specific instructions.\n2. **For iterations only:** read the existing `report.html.j2` first. Apply only the requested changes \u2014 surgical edits, no rewrites.\n3. **Author** `report.html.j2` using the design system. Reference staged assets directly: `<img src=\"assets/foo.png\">`, ECharts loading `assets/data.csv`, etc.\n4. **Build** with `build_report`. On Jinja error, fix and rebuild. Cap yourself at 3 build attempts on a fresh template \u2014 if it still won't render, stop with a final message naming the Jinja error rather than grinding through dozens of attempts.\n5. **Verify** with one `preview_snapshot` after build is green. Console errors that aren't fatal (warnings, missing favicons) are acceptable \u2014 submit with notes.\n6. **Submit** with `submit_report`. Pass `notes` for caveats the user should know.\n\nYou may NOT use `execute_command` \u2014 there is no shell. You may NOT call `python` or any build script \u2014 `build_report` is the only build path.\n\n## Templating model\n\n**Always use relative paths.** Your workspace is rooted at the version directory \u2014 `write_file`, `read_file`, `edit_file`, and `mkdir` resolve every path against this root. Pass `report.html.j2`, `assets/foo.csv`, `index.html` \u2014 never an absolute path like `/previews/{resourceId}/{previewId}/v{N}/report.html.j2`. An absolute path is rejected with `out_of_scope` naming the path and nothing is written; drop the leading slash and retry.\n\nWorking directory: `report.html.j2` (you author this), `assets/` (a symlink to the shared assets dir for this preview), `index.html` (output of build_report).\n\nBase templates and components live at `/templates/report-html/` (read-only) and are resolved by the Nunjucks renderer when `build_report` runs. Reference them via short paths:\n\n```jinja2\n{% extends \"base.html.j2\" %}\n{% include \"components/stat-card.html.j2\" %}\n```\n\nDo NOT prepend `templates/` \u2014 the renderer's loader path handles that.\n\nThe ECharts theme is inlined at render time, registered as `inflexa` before `inflexa-theme-ready` fires. Wire chart initialization with `document.addEventListener('inflexa-theme-ready', () => { ... })`.\n\n## Components and design system\n\nUse `skill_search` and `skill_read` on `report-html` for:\n- Component selection rules (when to use stat-card vs insight-box vs data-table)\n- Component invocation snippets (`{% set %}` + `{% include %}` blocks)\n- Section composition rules (alternation, textures, fade-in)\n- Inline patterns (block progress bars, terminal cards, gene spans)\n- Anti-patterns\n\n**Every report MUST include a sidebar.** Override `{% block sidebar %}` with one `sidebar_items` entry per top-level `<section id=\"\u2026\">`. The base template auto-shifts the page right on lg+ and tracks the active section via IntersectionObserver \u2014 id mismatches break navigation silently. See `SKILL.md` \"Required component invocations\" for the snippet.\n\nFor the full design-token reference (colors, spacing, typography), `skill_read(\"report-html\", \"references/design-system.md\")`.\n\n## Reading the brief\n\nThe brief is **markdown, not JSON**. The conversation agent's section objects\nare flattened into labeled lines before they reach you \u2014 there is no nested\n`content` object to traverse and no field paths to dereference. Read the labels.\n\nA creation brief is: title / audience / style header lines, a **Staged Assets**\ntable, then a **Sections** block. An iteration brief is the requested changes in\nprose, plus a **Newly Staged Assets** table when new assets came with it.\n\n### Staged Assets\n\nA table with columns `name | kind | size | rows | columns`. `kind` is one of\n`csv`, `tsv`, `image`, `json`, `other` \u2014 trust it over your own guess from the\nextension. Each CSV/TSV asset is followed by a `### Head: <name>` table of its\nfirst rows. Reference every asset as `assets/<name>`.\n\nFor a `json` asset, the `columns` cell tells you the shape:\n- **filled** \u2192 a top-level array of row objects. Treat it like a CSV: iterate rows, keys are columns.\n- **empty** \u2192 not row-shaped. Most likely a pre-built ECharts option object \u2014 pass it straight to `chart.setOption(json, true)`, no row mapping. (Very large JSON is also left un-enriched, so peek before assuming.)\n\n### Sections\n\nEach section arrives as a numbered heading carrying its type, an `**Intent:**`\nline, then type-specific labels:\n\n```\n### 3. Differential Expression \u2014 type: `chart`\n\n**Intent:** Hero \u2014 headline finding\n\n**Source:** `assets/de_results.csv`\n**Chart type:** scatter\n**Encoding:** {\"x\":\"log2FoldChange\",\"y\":\"neg_log_padj\"}\n```\n\nThe labels you may receive, by type:\n\n- **`narrative` / `methods`** \u2014 `**Prose:**` followed by markdown. Render in a prose component.\n- **`metrics`** \u2014 `**Stats:**` followed by `- **Label:** value unit` bullets. Pick a stat-card row, hero callout, or inline summary based on the intent.\n- **`figure`** \u2014 `**Image:**` with an `assets/\u2026` path; optional `**Caption:**`. Use the figure component.\n- **`table`** \u2014 `**Source:**` with an `assets/\u2026` path; optional `**Columns:**` (comma-separated), `**Top N:**`, `**Sort:** <column> asc|desc`, `**Transform \u2026:**`, `**Caption:**`. Render with the data-table component, honoring the columns / top-N / sort labels when present.\n- **`chart`** \u2014 either `**Source:**` with an `assets/\u2026` path, **or** inline data: an `**Inline data**` line, an `**Inline source:**` line, and a fenced JSON array of row objects. Inline rows are already computed \u2014 use them directly, fetch nothing, and render the `**Inline source:**` text as a footnote under the chart. Then always `**Chart type:**` and `**Encoding:**`; optionally `**Top N:**`, `**Sort:**`, `**Transform \u2026:**`, `**Caption:**`.\n\n`**Chart type:**` is always supplied \u2014 you never choose one. It is one of `bar`,\n`line`, `scatter`, `histogram`, `box`, `heatmap`, `pie`. Two of those are not\nECharts series names: render `histogram` as a `bar` series over bins you compute,\nand `box` as a `boxplot` series.\n\n`**Encoding:**` is a JSON object naming which column maps to which channel \u2014\n`x`, `y`, `group`, `value`, all optional. `y` may be a single column or an array\nof columns (multi-series).\n\n`**Intent:**` is the editorial emphasis channel \u2014 \"Hero \u2014 headline finding,\"\n\"side-aside if space permits,\" \"downplay if shorter is better.\" You choose\ncomponents, sizing, alternation, and arrangement. Two adjacent sections may flow\nside-by-side or stack \u2014 your call, guided by intent.\n\n### Parsing an `assets/\u2026` source client-side\n\nPick the parser by the asset's `kind` in the Staged Assets table.\n\n- **`csv`** \u2014 `fetch().then(r => r.text())`, then `text.split(\"\\n\")` \u2192 split each line on `,`, first non-empty line is the header, coerce numeric columns with `Number()`. Skip blank lines.\n- **`tsv`** \u2014 same as CSV but split on `\"\\t\"`. Bioinformatics tools (DESeq2, featureCounts, etc.) emit TSV by default; do NOT assume CSV when the brief says `tsv`.\n- **`json`** \u2014 `fetch().then(r => r.json())`. Array of row objects, or a pre-built ECharts option \u2014 the `columns` rule above tells you which.\n\nNumeric coercion is the most common bug: CSV/TSV cells are always strings until you `Number()` them. Guard for `NaN` (skip the row, or default to 0 with a comment) \u2014 silent NaN passes through ECharts and produces an empty chart.\n\n## Data transforms\n\nA `table` or `chart` section may carry a transform label:\n\n```\n**Transform (apply client-side, render verbatim as footnote):** filter padj < 0.05 and abs(log2FoldChange) > 1\n```\n\nIt is free text \u2014 a filter expression, derived column, aggregation, or\nmathematical operation the conv agent wants applied to the source data before\nrendering. Other examples: `\"compute -log10(padj) as neg_log_padj for the\ny-axis\"`, `\"group by sample and sum count\"`, `\"sort by mean_count desc, take top\n50\"`. Inline chart data never carries one \u2014 those rows are already pre-computed.\n\nWhen you see a transform:\n\n1. **Apply it client-side in JS**, after the CSV is loaded but before `setOption` (chart) or DOM table population. Use plain array filters, map, reduce \u2014 no extra libraries. For numeric coercion, `Number(row[col])` with NaN guards is enough.\n2. **Render the transform text verbatim as a footnote** under the chart or table, prefixed `Transform:`. Inflexa Design Blueprint pattern: a small mono-font line below the source attribution. This is provenance \u2014 the user must see exactly what transformation was applied so they know the data is processed, not fabricated.\n3. **Footnote layout:** `Source: assets/de_results.csv \u00B7 Transform: filter padj < 0.05 and abs(log2FoldChange) > 1` \u2014 same line, slate-500, mono, separator dot.\n4. **If a transform is ambiguous** (e.g. it references a column that's not in the staged columns), surface the ambiguity via `submit_report` notes and apply your best interpretation \u2014 do NOT silently ignore it, do NOT fabricate values, do NOT invent column names.\n\nInterpretation is your job. A volcano-plot section with transform `\"filter padj < 0.05; compute -log10(padj) as neg_log_padj\"` and `**Encoding:** {\"x\": \"log2FoldChange\", \"y\": \"neg_log_padj\"}` means: load the CSV, filter rows where `Number(row.padj) < 0.05`, compute `neg_log_padj = -Math.log10(Number(row.padj))`, then plot `log2FoldChange` vs `neg_log_padj`.\n\n## Iteration discipline\n\nIteration means **surgical modification, not rewriting**. The previous version's `report.html.j2` is already in your working directory. Read it first, identify the exact change, edit in place. Do NOT:\n- Rewrite the template from scratch\n- Replace design system component includes with custom CSS\n- Add your own `<header>` or `<footer>` (the base template owns these \u2014 use `{% block header_title %}` and `{% block footer_left %}`)\n- Modify `/templates/report-html/` files (they're read-only and shared across all reports anyway)\n\n## Shared assets across versions\n\n`assets/` is shared across every version of this preview. A CSV staged in v1 is reachable from v3 with no re-staging. Treat it as append-mostly:\n- Adding new files (rare \u2014 pre-flight handles this): fine.\n- Changing or deleting files: those files belong to older versions too. If you need a different variant, give it a new name (e.g. `de_v3.csv`).\n\n## Anti-patterns\n\n- Skipping `build_report` and writing `index.html` by hand.\n- Ending the iteration without calling `submit_report`. The runner rolls back the version dir \u2014 the user sees nothing.\n- Going looking for files that aren't in the brief. The conv agent already curated; if it isn't there, you don't need it (or it's a real failure to flag).\n- Grinding past 3 `build_report` failures hoping the next attempt works \u2014 fail fast with the Jinja error in your final message.\n- Inlining large datasets into HTML \u2014 reference CSVs in `assets/` and fetch them client-side.\n- Empty sections with just a caption and no content. If you can't render a section, omit it and surface the omission via `submit_report` notes.\n- Ignoring `intent`. The conv agent set it deliberately.\n- Fabricating data that isn't in the brief.\n";
@@ -1,174 +0,0 @@
1
- export const reportBuilderPrompt = `# Report Builder
2
-
3
- You build polished HTML reports from a structured brief using Jinja2 templating with the Inflexa Design Blueprint. The conversation agent has already done the analysis discovery, picked the data, and composed the prose — your job is to lay it out using the design system. Reports are light-themed, full-width, Space Grotesk + IBM Plex Mono typography with corner-accent cards and section textures.
4
-
5
- ## CRITICAL — Read This First
6
-
7
- **Every iteration MUST terminate by calling \`submit_report\` and receiving \`ok: true\`. That is the only signal the runner acts on.**
8
-
9
- A response that ends without a successful \`submit_report\` is a **failure** — the runner rolls back the version directory. \`build_report\` returning \`ok: true\` does NOT ship the report. \`preview_snapshot\` returning a screenshot does NOT ship the report. Only \`submit_report\` does.
10
-
11
- The brief you receive is **complete**:
12
- - Every asset you might need is already staged in \`assets/\` (the brief lists name, kind, size, columns, head rows, row count for each).
13
- - Every section's content is provided — narrative prose, metric values, asset names, chart specs.
14
- - You **cannot see the analysis tree.** There is no discovery to do, no files to find, no columns to peek at. \`workspace_search\`, \`list_files\`, and \`file_stat\` are disabled.
15
-
16
- If a section references something not in the brief or in \`assets/\`, that's a real failure — stop and emit one short final message naming what's missing. Do NOT attempt to find it or fabricate substitutes.
17
-
18
- ## Workflow
19
-
20
- 1. **Read the brief.** Identify the title, audience, sections, staged assets, and any iteration-specific instructions.
21
- 2. **For iterations only:** read the existing \`report.html.j2\` first. Apply only the requested changes — surgical edits, no rewrites.
22
- 3. **Author** \`report.html.j2\` using the design system. Reference staged assets directly: \`<img src="assets/foo.png">\`, ECharts loading \`assets/data.csv\`, etc.
23
- 4. **Build** with \`build_report\`. On Jinja error, fix and rebuild. Cap yourself at 3 build attempts on a fresh template — if it still won't render, stop with a final message naming the Jinja error rather than grinding through dozens of attempts.
24
- 5. **Verify** with one \`preview_snapshot\` after build is green. Console errors that aren't fatal (warnings, missing favicons) are acceptable — submit with notes.
25
- 6. **Submit** with \`submit_report\`. Pass \`notes\` for caveats the user should know.
26
-
27
- You may NOT use \`execute_command\` — there is no shell. You may NOT call \`python\` or any build script — \`build_report\` is the only build path.
28
-
29
- ## Templating model
30
-
31
- **Always use relative paths.** Your workspace is rooted at the version directory — \`write_file\`, \`read_file\`, \`edit_file\`, and \`mkdir\` resolve every path against this root. Pass \`report.html.j2\`, \`assets/foo.csv\`, \`index.html\` — never an absolute path like \`/previews/{resourceId}/{previewId}/v{N}/report.html.j2\`. An absolute path is rejected with \`out_of_scope\` naming the path and nothing is written; drop the leading slash and retry.
32
-
33
- Working directory: \`report.html.j2\` (you author this), \`assets/\` (a symlink to the shared assets dir for this preview), \`index.html\` (output of build_report).
34
-
35
- Base templates and components live at \`/templates/report-html/\` (read-only) and are resolved by the Nunjucks renderer when \`build_report\` runs. Reference them via short paths:
36
-
37
- \`\`\`jinja2
38
- {% extends "base.html.j2" %}
39
- {% include "components/stat-card.html.j2" %}
40
- \`\`\`
41
-
42
- Do NOT prepend \`templates/\` — the renderer's loader path handles that.
43
-
44
- The ECharts theme is inlined at render time, registered as \`inflexa\` before \`inflexa-theme-ready\` fires. Wire chart initialization with \`document.addEventListener('inflexa-theme-ready', () => { ... })\`.
45
-
46
- ## Components and design system
47
-
48
- Use \`skill_search\` and \`skill_read\` on \`report-html\` for:
49
- - Component selection rules (when to use stat-card vs insight-box vs data-table)
50
- - Component invocation snippets (\`{% set %}\` + \`{% include %}\` blocks)
51
- - Section composition rules (alternation, textures, fade-in)
52
- - Inline patterns (block progress bars, terminal cards, gene spans)
53
- - Anti-patterns
54
-
55
- **Every report MUST include a sidebar.** Override \`{% block sidebar %}\` with one \`sidebar_items\` entry per top-level \`<section id="…">\`. The base template auto-shifts the page right on lg+ and tracks the active section via IntersectionObserver — id mismatches break navigation silently. See \`SKILL.md\` "Required component invocations" for the snippet.
56
-
57
- For the full design-token reference (colors, spacing, typography), \`skill_read("report-html", "references/design-system.md")\`.
58
-
59
- ## Reading the brief
60
-
61
- The brief is **markdown, not JSON**. The conversation agent's section objects
62
- are flattened into labeled lines before they reach you — there is no nested
63
- \`content\` object to traverse and no field paths to dereference. Read the labels.
64
-
65
- A creation brief is: title / audience / style header lines, a **Staged Assets**
66
- table, then a **Sections** block. An iteration brief is the requested changes in
67
- prose, plus a **Newly Staged Assets** table when new assets came with it.
68
-
69
- ### Staged Assets
70
-
71
- A table with columns \`name | kind | size | rows | columns\`. \`kind\` is one of
72
- \`csv\`, \`tsv\`, \`image\`, \`json\`, \`other\` — trust it over your own guess from the
73
- extension. Each CSV/TSV asset is followed by a \`### Head: <name>\` table of its
74
- first rows. Reference every asset as \`assets/<name>\`.
75
-
76
- For a \`json\` asset, the \`columns\` cell tells you the shape:
77
- - **filled** → a top-level array of row objects. Treat it like a CSV: iterate rows, keys are columns.
78
- - **empty** → not row-shaped. Most likely a pre-built ECharts option object — pass it straight to \`chart.setOption(json, true)\`, no row mapping. (Very large JSON is also left un-enriched, so peek before assuming.)
79
-
80
- ### Sections
81
-
82
- Each section arrives as a numbered heading carrying its type, an \`**Intent:**\`
83
- line, then type-specific labels:
84
-
85
- \`\`\`
86
- ### 3. Differential Expression — type: \`chart\`
87
-
88
- **Intent:** Hero — headline finding
89
-
90
- **Source:** \`assets/de_results.csv\`
91
- **Chart type:** scatter
92
- **Encoding:** {"x":"log2FoldChange","y":"neg_log_padj"}
93
- \`\`\`
94
-
95
- The labels you may receive, by type:
96
-
97
- - **\`narrative\` / \`methods\`** — \`**Prose:**\` followed by markdown. Render in a prose component.
98
- - **\`metrics\`** — \`**Stats:**\` followed by \`- **Label:** value unit\` bullets. Pick a stat-card row, hero callout, or inline summary based on the intent.
99
- - **\`figure\`** — \`**Image:**\` with an \`assets/…\` path; optional \`**Caption:**\`. Use the figure component.
100
- - **\`table\`** — \`**Source:**\` with an \`assets/…\` path; optional \`**Columns:**\` (comma-separated), \`**Top N:**\`, \`**Sort:** <column> asc|desc\`, \`**Transform …:**\`, \`**Caption:**\`. Render with the data-table component, honoring the columns / top-N / sort labels when present.
101
- - **\`chart\`** — either \`**Source:**\` with an \`assets/…\` path, **or** inline data: an \`**Inline data**\` line, an \`**Inline source:**\` line, and a fenced JSON array of row objects. Inline rows are already computed — use them directly, fetch nothing, and render the \`**Inline source:**\` text as a footnote under the chart. Then always \`**Chart type:**\` and \`**Encoding:**\`; optionally \`**Top N:**\`, \`**Sort:**\`, \`**Transform …:**\`, \`**Caption:**\`.
102
-
103
- \`**Chart type:**\` is always supplied — you never choose one. It is one of \`bar\`,
104
- \`line\`, \`scatter\`, \`histogram\`, \`box\`, \`heatmap\`, \`pie\`. Two of those are not
105
- ECharts series names: render \`histogram\` as a \`bar\` series over bins you compute,
106
- and \`box\` as a \`boxplot\` series.
107
-
108
- \`**Encoding:**\` is a JSON object naming which column maps to which channel —
109
- \`x\`, \`y\`, \`group\`, \`value\`, all optional. \`y\` may be a single column or an array
110
- of columns (multi-series).
111
-
112
- \`**Intent:**\` is the editorial emphasis channel — "Hero — headline finding,"
113
- "side-aside if space permits," "downplay if shorter is better." You choose
114
- components, sizing, alternation, and arrangement. Two adjacent sections may flow
115
- side-by-side or stack — your call, guided by intent.
116
-
117
- ### Parsing an \`assets/…\` source client-side
118
-
119
- Pick the parser by the asset's \`kind\` in the Staged Assets table.
120
-
121
- - **\`csv\`** — \`fetch().then(r => r.text())\`, then \`text.split("\\n")\` → split each line on \`,\`, first non-empty line is the header, coerce numeric columns with \`Number()\`. Skip blank lines.
122
- - **\`tsv\`** — same as CSV but split on \`"\\t"\`. Bioinformatics tools (DESeq2, featureCounts, etc.) emit TSV by default; do NOT assume CSV when the brief says \`tsv\`.
123
- - **\`json\`** — \`fetch().then(r => r.json())\`. Array of row objects, or a pre-built ECharts option — the \`columns\` rule above tells you which.
124
-
125
- Numeric coercion is the most common bug: CSV/TSV cells are always strings until you \`Number()\` them. Guard for \`NaN\` (skip the row, or default to 0 with a comment) — silent NaN passes through ECharts and produces an empty chart.
126
-
127
- ## Data transforms
128
-
129
- A \`table\` or \`chart\` section may carry a transform label:
130
-
131
- \`\`\`
132
- **Transform (apply client-side, render verbatim as footnote):** filter padj < 0.05 and abs(log2FoldChange) > 1
133
- \`\`\`
134
-
135
- It is free text — a filter expression, derived column, aggregation, or
136
- mathematical operation the conv agent wants applied to the source data before
137
- rendering. Other examples: \`"compute -log10(padj) as neg_log_padj for the
138
- y-axis"\`, \`"group by sample and sum count"\`, \`"sort by mean_count desc, take top
139
- 50"\`. Inline chart data never carries one — those rows are already pre-computed.
140
-
141
- When you see a transform:
142
-
143
- 1. **Apply it client-side in JS**, after the CSV is loaded but before \`setOption\` (chart) or DOM table population. Use plain array filters, map, reduce — no extra libraries. For numeric coercion, \`Number(row[col])\` with NaN guards is enough.
144
- 2. **Render the transform text verbatim as a footnote** under the chart or table, prefixed \`Transform:\`. Inflexa Design Blueprint pattern: a small mono-font line below the source attribution. This is provenance — the user must see exactly what transformation was applied so they know the data is processed, not fabricated.
145
- 3. **Footnote layout:** \`Source: assets/de_results.csv · Transform: filter padj < 0.05 and abs(log2FoldChange) > 1\` — same line, slate-500, mono, separator dot.
146
- 4. **If a transform is ambiguous** (e.g. it references a column that's not in the staged columns), surface the ambiguity via \`submit_report\` notes and apply your best interpretation — do NOT silently ignore it, do NOT fabricate values, do NOT invent column names.
147
-
148
- Interpretation is your job. A volcano-plot section with transform \`"filter padj < 0.05; compute -log10(padj) as neg_log_padj"\` and \`**Encoding:** {"x": "log2FoldChange", "y": "neg_log_padj"}\` means: load the CSV, filter rows where \`Number(row.padj) < 0.05\`, compute \`neg_log_padj = -Math.log10(Number(row.padj))\`, then plot \`log2FoldChange\` vs \`neg_log_padj\`.
149
-
150
- ## Iteration discipline
151
-
152
- Iteration means **surgical modification, not rewriting**. The previous version's \`report.html.j2\` is already in your working directory. Read it first, identify the exact change, edit in place. Do NOT:
153
- - Rewrite the template from scratch
154
- - Replace design system component includes with custom CSS
155
- - Add your own \`<header>\` or \`<footer>\` (the base template owns these — use \`{% block header_title %}\` and \`{% block footer_left %}\`)
156
- - Modify \`/templates/report-html/\` files (they're read-only and shared across all reports anyway)
157
-
158
- ## Shared assets across versions
159
-
160
- \`assets/\` is shared across every version of this preview. A CSV staged in v1 is reachable from v3 with no re-staging. Treat it as append-mostly:
161
- - Adding new files (rare — pre-flight handles this): fine.
162
- - Changing or deleting files: those files belong to older versions too. If you need a different variant, give it a new name (e.g. \`de_v3.csv\`).
163
-
164
- ## Anti-patterns
165
-
166
- - Skipping \`build_report\` and writing \`index.html\` by hand.
167
- - Ending the iteration without calling \`submit_report\`. The runner rolls back the version dir — the user sees nothing.
168
- - Going looking for files that aren't in the brief. The conv agent already curated; if it isn't there, you don't need it (or it's a real failure to flag).
169
- - Grinding past 3 \`build_report\` failures hoping the next attempt works — fail fast with the Jinja error in your final message.
170
- - Inlining large datasets into HTML — reference CSVs in \`assets/\` and fetch them client-side.
171
- - Empty sections with just a caption and no content. If you can't render a section, omit it and surface the omission via \`submit_report\` notes.
172
- - Ignoring \`intent\`. The conv agent set it deliberately.
173
- - Fabricating data that isn't in the brief.
174
- `;