@tangle-network/agent-runtime 0.69.0 → 0.70.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/README.md +81 -13
- package/dist/agent.d.ts +4 -5
- package/dist/agent.js +3 -6
- package/dist/agent.js.map +1 -1
- package/dist/chunk-BYZCXQHF.js +474 -0
- package/dist/chunk-BYZCXQHF.js.map +1 -0
- package/dist/{chunk-G6U3GVK2.js → chunk-EDCVUZZC.js} +2 -2
- package/dist/{chunk-RTDI2DHD.js → chunk-L5ZFBVT6.js} +6 -6
- package/dist/chunk-L5ZFBVT6.js.map +1 -0
- package/dist/{chunk-NLRA6434.js → chunk-QXWGSDAQ.js} +716 -535
- package/dist/chunk-QXWGSDAQ.js.map +1 -0
- package/dist/{chunk-QZ6FA5LM.js → chunk-ZNQVMMR5.js} +3 -3
- package/dist/{coordination-Curpzeyc.d.ts → coordination-C7WxwHXq.d.ts} +8 -1
- package/dist/{delegates-CLFNAKyi.d.ts → delegates-DqAgo32T.d.ts} +105 -10
- package/dist/{improvement-adapter-BC4HhuAR.d.ts → improvement-adapter-BVuMragr.d.ts} +1 -1
- package/dist/index.d.ts +439 -100
- package/dist/index.js +583 -125
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +2 -3
- package/dist/{loop-runner-bin-B6dzNZC8.d.ts → loop-runner-bin-B0NeLTRd.d.ts} +4 -4
- package/dist/loop-runner-bin.d.ts +7 -10
- package/dist/loop-runner-bin.js +4 -7
- package/dist/loops.d.ts +3149 -16
- package/dist/loops.js +34 -63
- package/dist/mcp/bin.js +14 -16
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +8 -12
- package/dist/mcp/index.js +16 -19
- package/dist/mcp/index.js.map +1 -1
- package/dist/{openai-tools-CA2N3-Ak.d.ts → openai-tools-DPx9Gzvn.d.ts} +1 -1
- package/dist/profiles.d.ts +94 -6
- package/dist/profiles.js +320 -9
- package/dist/profiles.js.map +1 -1
- package/dist/{router-client-30Y_pca8.d.ts → router-client-C7kp_ECN.d.ts} +37 -1
- package/dist/{substrate-CUgk7F7s.d.ts → substrate-BoRXgvka.d.ts} +52 -1
- package/dist/{types-Crxftafi.d.ts → types-BYa2ZOAx.d.ts} +83 -2
- package/dist/{types-p8dWBIXL.d.ts → types-DJu6TBGp.d.ts} +2 -16
- package/dist/{worktree-fanout-DUiKPApb.d.ts → worktree-fanout-gNfl0Byj.d.ts} +9 -37
- package/package.json +9 -39
- package/dist/analyst-loop.d.ts +0 -52
- package/dist/analyst-loop.js +0 -11
- package/dist/analyst-loop.js.map +0 -1
- package/dist/audit.d.ts +0 -93
- package/dist/audit.js +0 -312
- package/dist/audit.js.map +0 -1
- package/dist/chunk-4B6U4CVQ.js +0 -15
- package/dist/chunk-4B6U4CVQ.js.map +0 -1
- package/dist/chunk-K4FQSEXQ.js +0 -214
- package/dist/chunk-K4FQSEXQ.js.map +0 -1
- package/dist/chunk-NLRA6434.js.map +0 -1
- package/dist/chunk-O2UPHN7X.js +0 -114
- package/dist/chunk-O2UPHN7X.js.map +0 -1
- package/dist/chunk-P5OKDSLB.js +0 -580
- package/dist/chunk-P5OKDSLB.js.map +0 -1
- package/dist/chunk-RTDI2DHD.js.map +0 -1
- package/dist/chunk-VLF5RHEQ.js +0 -143
- package/dist/chunk-VLF5RHEQ.js.map +0 -1
- package/dist/coder-2leJPOvC.d.ts +0 -52
- package/dist/improvement.d.ts +0 -208
- package/dist/improvement.js +0 -343
- package/dist/improvement.js.map +0 -1
- package/dist/local-harness-BE_h8szs.d.ts +0 -93
- package/dist/platform.d.ts +0 -255
- package/dist/platform.js +0 -229
- package/dist/platform.js.map +0 -1
- package/dist/run-loop-D3PwlG7J.d.ts +0 -112
- package/dist/runtime-hooks-C7JwKb9E.d.ts +0 -70
- package/dist/runtime.d.ts +0 -3120
- package/dist/runtime.js +0 -263
- package/dist/runtime.js.map +0 -1
- package/dist/topology.d.ts +0 -126
- package/dist/topology.js +0 -333
- package/dist/topology.js.map +0 -1
- package/dist/workflow.d.ts +0 -551
- package/dist/workflow.js +0 -1781
- package/dist/workflow.js.map +0 -1
- /package/dist/{chunk-G6U3GVK2.js.map → chunk-EDCVUZZC.js.map} +0 -0
- /package/dist/{chunk-QZ6FA5LM.js.map → chunk-ZNQVMMR5.js.map} +0 -0
package/dist/mcp/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/mcp/tools/checks.ts"],"sourcesContent":["/**\n * @experimental\n *\n * The trace-analyst KIND directory — the operator's lenses, as composable DATA.\n *\n * An analyst is not one question. A kind is ONE lens (completeness, correctness, policy, efficiency,\n * tool-use, …); each emits `AnalystFinding`s tagged by its `area`. The driver `list_analysts` to see\n * the menu, `run_analyst(kind, worker)` to apply a lens, and `define_analyst` to author a new one —\n * so at test time you compose the exact lenses a domain needs (maximum specificity), not one generic\n * reviewer. The kinds are data, the runner is generic, and the finding shape + firewall are reused\n * from agent-eval / the keystone — never re-derived.\n *\n * A kind here is a lightweight lens (`Check`); it is a deliberate SUBSET of agent-eval's full\n * `TraceAnalystKindSpec`, so a kind that needs the heavy agentic actor (sub-agent recursion, tools,\n * goldens) upgrades to `createTraceAnalystKind` without changing this directory's surface.\n */\n\nimport {\n type AnalystFinding,\n type AnalystSeverity,\n type EvidenceRef,\n makeFinding,\n} from '@tangle-network/agent-eval'\nimport { assertTraceDerivedFindings } from '../../runtime'\n\n// agent-eval's root entry exports the lift (`makeFinding`) + the `AnalystFinding`/`EvidenceRef` shapes\n// but NOT the raw-row validator / schema prompt (kind-factory-internal). We inline a minimal,\n// equivalent pair so a lens emits the SAME finding shape without reaching into an unstable internal.\nconst ANALYST_SEVERITIES = ['critical', 'high', 'medium', 'low', 'info'] as const\n\nconst FINDING_SCHEMA_PROMPT = [\n 'Each finding is a JSON object with these fields:',\n '- severity: one of \"critical\" | \"high\" | \"medium\" | \"low\" | \"info\"',\n '- claim: one-sentence statement',\n '- evidence_uri: REQUIRED, never blank — exactly one of \"span://<trace>/<span>\", \"artifact://<path>\",',\n ' or \"metric://<name>\"; ALWAYS cite a real id from the trace. No citable id ⇒ omit the finding.',\n '- evidence_excerpt?: a short quote from the cited evidence',\n '- confidence: number 0..1',\n '- rationale?: one sentence of reasoning',\n '- recommended_action?: a concrete imperative (\"Add ...\", \"Replace ...\", \"Stop ...\")',\n 'Emit an empty array when there is nothing to report. Never fabricate evidence.',\n].join('\\n')\n\ninterface RawRow {\n severity: AnalystSeverity\n claim: string\n evidence_uri: string\n confidence: number\n evidence_excerpt?: string\n rationale?: string\n recommended_action?: string\n subject?: string\n}\n\n/** Validate one raw finding row (the lightweight equivalent of agent-eval's `parseRawFinding`):\n * require a claim + a real trace evidence_uri; drop anything else. Returns null to discard. */\nfunction validateRawFinding(row: unknown): RawRow | null {\n if (!row || typeof row !== 'object') return null\n const r = row as Record<string, unknown>\n if (typeof r.claim !== 'string' || r.claim.length === 0) return null\n if (typeof r.evidence_uri !== 'string' || !/^(span|artifact|metric):\\/\\//.test(r.evidence_uri))\n return null\n const sev = (ANALYST_SEVERITIES as readonly string[]).includes(r.severity as string)\n ? (r.severity as AnalystSeverity)\n : 'medium'\n return {\n severity: sev,\n claim: r.claim,\n evidence_uri: r.evidence_uri,\n confidence: typeof r.confidence === 'number' ? r.confidence : 0.5,\n ...(typeof r.evidence_excerpt === 'string' ? { evidence_excerpt: r.evidence_excerpt } : {}),\n ...(typeof r.rationale === 'string' ? { rationale: r.rationale } : {}),\n ...(typeof r.recommended_action === 'string'\n ? { recommended_action: r.recommended_action }\n : {}),\n ...(typeof r.subject === 'string' ? { subject: r.subject } : {}),\n }\n}\n\n/** One lens — a composable analyst kind. Identity fields mirror `TraceAnalystKindSpec` so a kind is\n * upgradeable to the full agentic factory; `lookFor` is the lens question the actor applies. */\nexport interface Check {\n readonly id: string\n readonly description: string\n /** Coarse classification stamped on every finding this kind emits (the renderer groups by it). */\n readonly area: string\n readonly version: string\n /** The lens — what this analyst looks for in the trace. */\n readonly lookFor: string\n}\n\n/** The built-in lens directory. Domain-blind (about any agent trace); compose at test time. */\nexport const defaultChecks: Record<string, Check> = {\n completeness: {\n id: 'completeness',\n description: 'Required work the trace does not yet show done or verified.',\n area: 'failure-mode',\n version: '1',\n lookFor:\n 'every change the task requires that the trace does NOT yet show completed AND verified by a ' +\n 'tool result. One finding per missing/unverified requirement.',\n },\n correctness: {\n id: 'correctness',\n description: 'Tool calls that produced wrong, erroring, or contradicted results.',\n area: 'correctness',\n version: '1',\n lookFor:\n 'tool calls whose RESULT shows an error, a wrong value, or contradicts what the task required ' +\n '(e.g. set the wrong field, value did not take, an error was ignored).',\n },\n policy: {\n id: 'policy',\n description: 'Actions that violate a stated policy, constraint, or allow-list.',\n area: 'safety',\n version: '1',\n lookFor:\n 'actions in the trace that violate a policy/constraint stated in the task or system prompt ' +\n '(forbidden tool, missing approval, out-of-scope mutation, skipped precondition).',\n },\n efficiency: {\n id: 'efficiency',\n description: 'Wasted, redundant, or looping work.',\n area: 'cost',\n version: '1',\n lookFor:\n 'redundant or wasted actions — repeated identical calls, a stalled line retried the same way, ' +\n 'work that produced no progress toward the goal.',\n },\n 'tool-use': {\n id: 'tool-use',\n description: 'Malformed or misused tool calls.',\n area: 'tool-use',\n version: '1',\n lookFor:\n 'tool calls with malformed/invalid arguments, the wrong tool for the intent, or a tool used ' +\n 'against its contract — judged from the call + its result.',\n },\n}\n\n/** Lift validated raw rows into `AnalystFinding`s (agent-eval `makeFinding` stamps `finding_id`/\n * `produced_at`), then enforce the trace-derived firewall (selector ≠ judge). Pure — no LLM. */\nexport function liftFindings(kind: Check, rows: unknown[], producedAt: string): AnalystFinding[] {\n const findings: AnalystFinding[] = []\n for (const row of rows) {\n const raw = validateRawFinding(row)\n if (!raw) continue\n findings.push(\n makeFinding({\n analyst_id: kind.id,\n area: kind.area,\n severity: raw.severity,\n claim: raw.claim,\n confidence: raw.confidence,\n produced_at: producedAt,\n evidence_refs: evidenceRefs(raw.evidence_uri, raw.evidence_excerpt),\n ...(raw.rationale ? { rationale: raw.rationale } : {}),\n ...(raw.recommended_action ? { recommended_action: raw.recommended_action } : {}),\n ...(raw.subject ? { subject: raw.subject } : {}),\n metadata: { kind_version: kind.version },\n }),\n )\n }\n assertTraceDerivedFindings(findings) // throws if a finding cites judge/verdict/score evidence\n return findings\n}\n\n/** Map a raw `evidence_uri` (span:// | artifact:// | metric://<name>) to a typed `EvidenceRef`. A\n * metric ref carries the bare NAME (the firewall checks the metric name for judge/verdict/score —\n * so a finding that cites a judge metric is rejected as not trace-derived). */\nfunction evidenceRefs(uri: string, excerpt?: string): EvidenceRef[] {\n const scheme = uri.split('://', 1)[0]\n if (scheme === 'metric')\n return [\n { kind: 'metric', uri: uri.replace(/^metric:\\/\\//, ''), ...(excerpt ? { excerpt } : {}) },\n ]\n const kind: EvidenceRef['kind'] = scheme === 'span' ? 'span' : 'artifact'\n return [{ kind, uri, ...(excerpt ? { excerpt } : {}) }]\n}\n\n/** Render a worker's trace (tool calls + results) into the text an analyst lens reads. Generic over\n * the trace shape: a `{ messages }` conversation, a bare message array, else stringified. */\nexport function renderTrace(trace: unknown): string {\n const messages = Array.isArray(trace)\n ? trace\n : trace &&\n typeof trace === 'object' &&\n Array.isArray((trace as { messages?: unknown[] }).messages)\n ? (trace as { messages: unknown[] }).messages\n : undefined\n if (!messages) return JSON.stringify(trace ?? {}).slice(0, 8000)\n return messages\n .map((m) => {\n const r = m as {\n role?: string\n content?: unknown\n tool_calls?: Array<{ function?: { name?: string; arguments?: string } }>\n }\n if (r.role === 'tool') return `RESULT ${String(r.content).slice(0, 300)}`\n const calls = r.tool_calls\n ?.map((c) => `${c.function?.name}(${c.function?.arguments})`)\n .join(', ')\n return calls ? `CALL ${calls}` : `SAY ${String(r.content ?? '').slice(0, 200)}`\n })\n .join('\\n')\n .slice(0, 8000)\n}\n\nexport interface CheckRunnerOptions {\n routerBaseUrl: string\n routerKey: string\n model: string\n /** Test/override seam — replace the LLM call. Default: a router chat completion. */\n chat?: (system: string, user: string) => Promise<string>\n}\n\n/** Run ONE lens over a trace → findings. Generic over any kind: prompt = the lens + the agent-eval\n * finding schema; the model's JSON array is parsed (`parseRawFinding`), lifted, and firewalled. */\nexport async function runCheck(\n kind: Check,\n trace: unknown,\n opts: CheckRunnerOptions,\n producedAt: string,\n): Promise<AnalystFinding[]> {\n const sys =\n `You are a trace analyst applying ONE lens: look for ${kind.lookFor}\\n\\n` +\n `${FINDING_SCHEMA_PROMPT}\\n\\nReturn ONLY a fenced \\`\\`\\`json array of finding objects (possibly empty).`\n const user = `WORKER TRACE:\\n${renderTrace(trace)}\\n\\nApply your lens and emit the findings array.`\n const chat = opts.chat ?? defaultChat(opts)\n const content = await chat(sys, user)\n const match = content.match(/```(?:json)?\\s*([\\s\\S]*?)```/)\n let rows: unknown[] = []\n try {\n const parsed = JSON.parse((match?.[1] ?? content).trim())\n rows = Array.isArray(parsed)\n ? parsed\n : Array.isArray((parsed as { findings?: unknown[] })?.findings)\n ? (parsed as { findings: unknown[] }).findings\n : []\n } catch {\n rows = []\n }\n return liftFindings(kind, rows, producedAt)\n}\n\nfunction defaultChat(opts: CheckRunnerOptions): (system: string, user: string) => Promise<string> {\n return async (system, user) => {\n const res = await fetch(`${opts.routerBaseUrl.replace(/\\/$/, '')}/chat/completions`, {\n method: 'POST',\n headers: { 'content-type': 'application/json', authorization: `Bearer ${opts.routerKey}` },\n body: JSON.stringify({\n model: opts.model,\n messages: [\n { role: 'system', content: system },\n { role: 'user', content: user },\n ],\n temperature: 0.3,\n }),\n })\n if (!res.ok) throw new Error(`analyst router ${res.status}`)\n const data = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> }\n return data.choices?.[0]?.message?.content ?? ''\n }\n}\n\n/**\n * Build a `run_analyst` runner over a kind directory.\n * Returns findings, or a typed error for an unknown kind. `producedAt` is\n * passed in because replay-safe paths must not read `Date.now`.\n */\nexport function makeCheckRunner(\n kinds: Record<string, Check>,\n opts: CheckRunnerOptions,\n): (\n kindId: string,\n trace: unknown,\n producedAt: string,\n) => Promise<AnalystFinding[] | { error: string }> {\n return async (kindId, trace, producedAt) => {\n const kind = kinds[kindId]\n if (!kind)\n return {\n error: `unknown analyst kind ${JSON.stringify(kindId)} (have: ${Object.keys(kinds).join(', ')})`,\n }\n return runCheck(kind, trace, opts, producedAt)\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/mcp/tools/checks.ts"],"sourcesContent":["/**\n * @experimental\n *\n * The trace-analyst KIND directory — the operator's lenses, as composable DATA.\n *\n * An analyst is not one question. A kind is ONE lens (completeness, correctness, policy, efficiency,\n * tool-use, …); each emits `AnalystFinding`s tagged by its `area`. The driver `list_analysts` to see\n * the menu, `run_analyst(kind, worker)` to apply a lens, and `define_analyst` to author a new one —\n * so at test time you compose the exact lenses a domain needs (maximum specificity), not one generic\n * reviewer. The kinds are data, the runner is generic, and the finding shape + firewall are reused\n * from agent-eval / the keystone — never re-derived.\n *\n * A kind here is a lightweight lens (`Check`); it is a deliberate SUBSET of agent-eval's full\n * `TraceAnalystKindSpec`, so a kind that needs the heavy agentic actor (sub-agent recursion, tools,\n * goldens) upgrades to `createTraceAnalystKind` without changing this directory's surface.\n */\n\nimport {\n type AnalystFinding,\n type AnalystSeverity,\n type EvidenceRef,\n makeFinding,\n} from '@tangle-network/agent-eval'\nimport { assertTraceDerivedFindings } from '../../runtime'\n\n// agent-eval's root entry exports the lift (`makeFinding`) + the `AnalystFinding`/`EvidenceRef` shapes\n// but NOT the raw-row validator / schema prompt (kind-factory-internal). We inline a minimal,\n// equivalent pair so a lens emits the SAME finding shape without reaching into an unstable internal.\nconst ANALYST_SEVERITIES = ['critical', 'high', 'medium', 'low', 'info'] as const\n\nconst FINDING_SCHEMA_PROMPT = [\n 'Each finding is a JSON object with these fields:',\n '- severity: one of \"critical\" | \"high\" | \"medium\" | \"low\" | \"info\"',\n '- claim: one-sentence statement',\n '- evidence_uri: REQUIRED, never blank — exactly one of \"span://<trace>/<span>\", \"artifact://<path>\",',\n ' or \"metric://<name>\"; ALWAYS cite a real id from the trace. No citable id ⇒ omit the finding.',\n '- evidence_excerpt?: a short quote from the cited evidence',\n '- confidence: number 0..1',\n '- rationale?: one sentence of reasoning',\n '- recommended_action?: a concrete imperative (\"Add ...\", \"Replace ...\", \"Stop ...\")',\n 'Emit an empty array when there is nothing to report. Never fabricate evidence.',\n].join('\\n')\n\ninterface RawRow {\n severity: AnalystSeverity\n claim: string\n evidence_uri: string\n confidence: number\n evidence_excerpt?: string\n rationale?: string\n recommended_action?: string\n subject?: string\n}\n\n/** Validate one raw finding row (the lightweight equivalent of agent-eval's `parseRawFinding`):\n * require a claim + a real trace evidence_uri; drop anything else. Returns null to discard. */\nfunction validateRawFinding(row: unknown): RawRow | null {\n if (!row || typeof row !== 'object') return null\n const r = row as Record<string, unknown>\n if (typeof r.claim !== 'string' || r.claim.length === 0) return null\n if (typeof r.evidence_uri !== 'string' || !/^(span|artifact|metric):\\/\\//.test(r.evidence_uri))\n return null\n const sev = (ANALYST_SEVERITIES as readonly string[]).includes(r.severity as string)\n ? (r.severity as AnalystSeverity)\n : 'medium'\n return {\n severity: sev,\n claim: r.claim,\n evidence_uri: r.evidence_uri,\n confidence: typeof r.confidence === 'number' ? r.confidence : 0.5,\n ...(typeof r.evidence_excerpt === 'string' ? { evidence_excerpt: r.evidence_excerpt } : {}),\n ...(typeof r.rationale === 'string' ? { rationale: r.rationale } : {}),\n ...(typeof r.recommended_action === 'string'\n ? { recommended_action: r.recommended_action }\n : {}),\n ...(typeof r.subject === 'string' ? { subject: r.subject } : {}),\n }\n}\n\n/** One lens — a composable analyst kind. Identity fields mirror `TraceAnalystKindSpec` so a kind is\n * upgradeable to the full agentic factory; `lookFor` is the lens question the actor applies. */\nexport interface Check {\n readonly id: string\n readonly description: string\n /** Coarse classification stamped on every finding this kind emits (the renderer groups by it). */\n readonly area: string\n readonly version: string\n /** The lens — what this analyst looks for in the trace. */\n readonly lookFor: string\n}\n\n/** The built-in lens directory. Domain-blind (about any agent trace); compose at test time. */\nexport const defaultChecks: Record<string, Check> = {\n completeness: {\n id: 'completeness',\n description: 'Required work the trace does not yet show done or verified.',\n area: 'failure-mode',\n version: '1',\n lookFor:\n 'every change the task requires that the trace does NOT yet show completed AND verified by a ' +\n 'tool result. One finding per missing/unverified requirement.',\n },\n correctness: {\n id: 'correctness',\n description: 'Tool calls that produced wrong, erroring, or contradicted results.',\n area: 'correctness',\n version: '1',\n lookFor:\n 'tool calls whose RESULT shows an error, a wrong value, or contradicts what the task required ' +\n '(e.g. set the wrong field, value did not take, an error was ignored).',\n },\n policy: {\n id: 'policy',\n description: 'Actions that violate a stated policy, constraint, or allow-list.',\n area: 'safety',\n version: '1',\n lookFor:\n 'actions in the trace that violate a policy/constraint stated in the task or system prompt ' +\n '(forbidden tool, missing approval, out-of-scope mutation, skipped precondition).',\n },\n efficiency: {\n id: 'efficiency',\n description: 'Wasted, redundant, or looping work.',\n area: 'cost',\n version: '1',\n lookFor:\n 'redundant or wasted actions — repeated identical calls, a stalled line retried the same way, ' +\n 'work that produced no progress toward the goal.',\n },\n 'tool-use': {\n id: 'tool-use',\n description: 'Malformed or misused tool calls.',\n area: 'tool-use',\n version: '1',\n lookFor:\n 'tool calls with malformed/invalid arguments, the wrong tool for the intent, or a tool used ' +\n 'against its contract — judged from the call + its result.',\n },\n}\n\n/** Lift validated raw rows into `AnalystFinding`s (agent-eval `makeFinding` stamps `finding_id`/\n * `produced_at`), then enforce the trace-derived firewall (selector ≠ judge). Pure — no LLM. */\nexport function liftFindings(kind: Check, rows: unknown[], producedAt: string): AnalystFinding[] {\n const findings: AnalystFinding[] = []\n for (const row of rows) {\n const raw = validateRawFinding(row)\n if (!raw) continue\n findings.push(\n makeFinding({\n analyst_id: kind.id,\n area: kind.area,\n severity: raw.severity,\n claim: raw.claim,\n confidence: raw.confidence,\n produced_at: producedAt,\n evidence_refs: evidenceRefs(raw.evidence_uri, raw.evidence_excerpt),\n ...(raw.rationale ? { rationale: raw.rationale } : {}),\n ...(raw.recommended_action ? { recommended_action: raw.recommended_action } : {}),\n ...(raw.subject ? { subject: raw.subject } : {}),\n metadata: { kind_version: kind.version },\n }),\n )\n }\n assertTraceDerivedFindings(findings) // throws if a finding cites judge/verdict/score evidence\n return findings\n}\n\n/** Map a raw `evidence_uri` (span:// | artifact:// | metric://<name>) to a typed `EvidenceRef`. A\n * metric ref carries the bare NAME (the firewall checks the metric name for judge/verdict/score —\n * so a finding that cites a judge metric is rejected as not trace-derived). */\nfunction evidenceRefs(uri: string, excerpt?: string): EvidenceRef[] {\n const scheme = uri.split('://', 1)[0]\n if (scheme === 'metric')\n return [\n { kind: 'metric', uri: uri.replace(/^metric:\\/\\//, ''), ...(excerpt ? { excerpt } : {}) },\n ]\n const kind: EvidenceRef['kind'] = scheme === 'span' ? 'span' : 'artifact'\n return [{ kind, uri, ...(excerpt ? { excerpt } : {}) }]\n}\n\n/** Render a worker's trace (tool calls + results) into the text an analyst lens reads. Generic over\n * the trace shape: a `{ messages }` conversation, a bare message array, else stringified. */\nexport function renderTrace(trace: unknown): string {\n const messages = Array.isArray(trace)\n ? trace\n : trace &&\n typeof trace === 'object' &&\n Array.isArray((trace as { messages?: unknown[] }).messages)\n ? (trace as { messages: unknown[] }).messages\n : undefined\n if (!messages) return JSON.stringify(trace ?? {}).slice(0, 8000)\n return messages\n .map((m) => {\n const r = m as {\n role?: string\n content?: unknown\n tool_calls?: Array<{ function?: { name?: string; arguments?: string } }>\n }\n if (r.role === 'tool') return `RESULT ${String(r.content).slice(0, 300)}`\n const calls = r.tool_calls\n ?.map((c) => `${c.function?.name}(${c.function?.arguments})`)\n .join(', ')\n return calls ? `CALL ${calls}` : `SAY ${String(r.content ?? '').slice(0, 200)}`\n })\n .join('\\n')\n .slice(0, 8000)\n}\n\nexport interface CheckRunnerOptions {\n routerBaseUrl: string\n routerKey: string\n model: string\n /** Test/override seam — replace the LLM call. Default: a router chat completion. */\n chat?: (system: string, user: string) => Promise<string>\n}\n\n/** Run ONE lens over a trace → findings. Generic over any kind: prompt = the lens + the agent-eval\n * finding schema; the model's JSON array is parsed (`parseRawFinding`), lifted, and firewalled. */\nexport async function runCheck(\n kind: Check,\n trace: unknown,\n opts: CheckRunnerOptions,\n producedAt: string,\n): Promise<AnalystFinding[]> {\n const sys =\n `You are a trace analyst applying ONE lens: look for ${kind.lookFor}\\n\\n` +\n `${FINDING_SCHEMA_PROMPT}\\n\\nReturn ONLY a fenced \\`\\`\\`json array of finding objects (possibly empty).`\n const user = `WORKER TRACE:\\n${renderTrace(trace)}\\n\\nApply your lens and emit the findings array.`\n const chat = opts.chat ?? defaultChat(opts)\n const content = await chat(sys, user)\n const match = content.match(/```(?:json)?\\s*([\\s\\S]*?)```/)\n let rows: unknown[] = []\n try {\n const parsed = JSON.parse((match?.[1] ?? content).trim())\n rows = Array.isArray(parsed)\n ? parsed\n : Array.isArray((parsed as { findings?: unknown[] })?.findings)\n ? (parsed as { findings: unknown[] }).findings\n : []\n } catch {\n rows = []\n }\n return liftFindings(kind, rows, producedAt)\n}\n\nfunction defaultChat(opts: CheckRunnerOptions): (system: string, user: string) => Promise<string> {\n return async (system, user) => {\n const res = await fetch(`${opts.routerBaseUrl.replace(/\\/$/, '')}/chat/completions`, {\n method: 'POST',\n headers: { 'content-type': 'application/json', authorization: `Bearer ${opts.routerKey}` },\n body: JSON.stringify({\n model: opts.model,\n messages: [\n { role: 'system', content: system },\n { role: 'user', content: user },\n ],\n temperature: 0.3,\n }),\n })\n if (!res.ok) throw new Error(`analyst router ${res.status}`)\n const data = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> }\n return data.choices?.[0]?.message?.content ?? ''\n }\n}\n\n/**\n * Build a `run_analyst` runner over a kind directory.\n * Returns findings, or a typed error for an unknown kind. `producedAt` is\n * passed in because replay-safe paths must not read `Date.now`.\n */\nexport function makeCheckRunner(\n kinds: Record<string, Check>,\n opts: CheckRunnerOptions,\n): (\n kindId: string,\n trace: unknown,\n producedAt: string,\n) => Promise<AnalystFinding[] | { error: string }> {\n return async (kindId, trace, producedAt) => {\n const kind = kinds[kindId]\n if (!kind)\n return {\n error: `unknown analyst kind ${JSON.stringify(kindId)} (have: ${Object.keys(kinds).join(', ')})`,\n }\n return runCheck(kind, trace, opts, producedAt)\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/profiles.d.ts
CHANGED
|
@@ -1,11 +1,99 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { a as
|
|
4
|
-
export { b as UI_FINDING_SEVERITIES, c as UI_LENSES, d as UiFindingScreenshot, e as UiFindingSeverity } from './substrate-CUgk7F7s.js';
|
|
1
|
+
import { U as UiFinding, a as UiLens } from './substrate-BoRXgvka.js';
|
|
2
|
+
export { C as CoderTask, D as DEFAULT_CODER_SYSTEM_PROMPT, b as UI_FINDING_SEVERITIES, c as UI_LENSES, d as UiFindingScreenshot, e as UiFindingSeverity, f as coderProfile, g as coderTaskToPrompt } from './substrate-BoRXgvka.js';
|
|
3
|
+
import { S as SandboxClient, a as OutputAdapter, V as Validator, A as AgentRunSpec } from './types-BYa2ZOAx.js';
|
|
5
4
|
import { SandboxEvent } from '@tangle-network/sandbox';
|
|
6
5
|
import { AgentProfile } from '@tangle-network/agent-interface';
|
|
7
6
|
import '@tangle-network/agent-eval';
|
|
8
|
-
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @experimental
|
|
10
|
+
*
|
|
11
|
+
* UI-audit issue writer — pure I/O. Takes a workspace dir + `UiFinding[]`
|
|
12
|
+
* and emits:
|
|
13
|
+
* - `<workspace>/issues/NNN--<lens>--<slug>.md` — one self-contained
|
|
14
|
+
* GitHub-issue-ready Markdown per finding, with embedded screenshot
|
|
15
|
+
* references.
|
|
16
|
+
* - `<workspace>/registry.json` — finding index for dedup and audit
|
|
17
|
+
* resume across iterations.
|
|
18
|
+
* - `<workspace>/index.md` — human-readable rollup (severity / lens /
|
|
19
|
+
* route counts plus a sorted finding list).
|
|
20
|
+
*
|
|
21
|
+
* The writer is deterministic, idempotent for `appendFindings()`, and
|
|
22
|
+
* never invokes an LLM. It assigns the next monotonic id to a finding the
|
|
23
|
+
* caller did not pre-id.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** @experimental */
|
|
27
|
+
interface AuditRegistry {
|
|
28
|
+
schemaVersion: 1;
|
|
29
|
+
findings: UiFinding[];
|
|
30
|
+
/** Route → URL + captures sidecar; preserved across runs. */
|
|
31
|
+
routes: Record<string, {
|
|
32
|
+
url?: string;
|
|
33
|
+
captures: AuditRegistryCapture[];
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
/** @experimental */
|
|
37
|
+
interface AuditRegistryCapture {
|
|
38
|
+
file: string;
|
|
39
|
+
viewport?: string;
|
|
40
|
+
fullPage?: boolean;
|
|
41
|
+
elementSelector?: string;
|
|
42
|
+
capturedAt: string;
|
|
43
|
+
}
|
|
44
|
+
/** @experimental */
|
|
45
|
+
declare function initAuditWorkspace(workspaceDir: string): Promise<void>;
|
|
46
|
+
/** @experimental */
|
|
47
|
+
declare function readAuditRegistry(workspaceDir: string): Promise<AuditRegistry>;
|
|
48
|
+
/** @experimental */
|
|
49
|
+
interface AppendFindingsResult {
|
|
50
|
+
/** Findings with id + createdAt assigned, in input order. */
|
|
51
|
+
written: UiFinding[];
|
|
52
|
+
/** Workspace-relative path to each issue Markdown file, in input order. */
|
|
53
|
+
files: string[];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Append findings to a workspace, writing one Markdown file per finding
|
|
57
|
+
* and updating registry.json. Assigns monotonically increasing ids to
|
|
58
|
+
* findings that arrived without one.
|
|
59
|
+
*
|
|
60
|
+
* Findings already carrying an id that collides with the registry are
|
|
61
|
+
* rejected — callers must either freshly mint findings (id undefined) or
|
|
62
|
+
* use a separate update path. This protects against accidental overwrite.
|
|
63
|
+
*
|
|
64
|
+
* @experimental
|
|
65
|
+
*/
|
|
66
|
+
declare function appendFindings(workspaceDir: string, findings: readonly UiFinding[]): Promise<AppendFindingsResult>;
|
|
67
|
+
/** @experimental */
|
|
68
|
+
interface RegisterCapturesOptions {
|
|
69
|
+
route: string;
|
|
70
|
+
url?: string;
|
|
71
|
+
captures: readonly AuditRegistryCapture[];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Record screenshots taken for a route in the registry, without filing a
|
|
75
|
+
* finding. Useful when the auditor wants to remember which captures
|
|
76
|
+
* exist for resume / dedup purposes.
|
|
77
|
+
*
|
|
78
|
+
* @experimental
|
|
79
|
+
*/
|
|
80
|
+
declare function registerCaptures(workspaceDir: string, options: RegisterCapturesOptions): Promise<void>;
|
|
81
|
+
/** @experimental */
|
|
82
|
+
interface AuditIndex {
|
|
83
|
+
/** Total findings in the workspace. */
|
|
84
|
+
total: number;
|
|
85
|
+
bySeverity: Record<UiFinding['severity'], number>;
|
|
86
|
+
byLens: Partial<Record<UiLens, number>>;
|
|
87
|
+
byRoute: Record<string, number>;
|
|
88
|
+
}
|
|
89
|
+
/** @experimental */
|
|
90
|
+
declare function summarizeRegistry(reg: AuditRegistry): AuditIndex;
|
|
91
|
+
/**
|
|
92
|
+
* Regenerate `<workspace>/index.md` from registry.json.
|
|
93
|
+
*
|
|
94
|
+
* @experimental
|
|
95
|
+
*/
|
|
96
|
+
declare function writeAuditIndex(workspaceDir: string): Promise<string>;
|
|
9
97
|
|
|
10
98
|
/**
|
|
11
99
|
* @experimental
|
|
@@ -410,4 +498,4 @@ declare function formatAuditorPrompt(task: UiAuditTask): string;
|
|
|
410
498
|
/** @experimental */
|
|
411
499
|
declare function createUiAuditorValidator(task: UiAuditTask): Validator<UiAuditOutput>;
|
|
412
500
|
|
|
413
|
-
export { type BrowserContextHandle, type BrowserHandle, type InProcessUiAuditClientOptions, LENS_BRIEFS, type PageHandle, SHARED_AUDITOR_RULES, type UiAuditCapture, type UiAuditCaptureRequest, type UiAuditOutput, type UiAuditTask, type UiAuditViewport, type UiAuditorProfileOptions, UiFinding, type UiJudge, type UiJudgeInput, type UiJudgeOutput, type UiJudgeTokenUsage, UiLens, buildAuditorSystemPrompt, createInProcessUiAuditClient, createUiAuditorValidator, decodeAuditTaskEnvelope, encodeAuditTaskEnvelope, formatAuditorPrompt, parseAuditorEvents, uiAuditorProfile };
|
|
501
|
+
export { type AppendFindingsResult, type AuditIndex, type AuditRegistry, type AuditRegistryCapture, type BrowserContextHandle, type BrowserHandle, type InProcessUiAuditClientOptions, LENS_BRIEFS, type PageHandle, type RegisterCapturesOptions, SHARED_AUDITOR_RULES, type UiAuditCapture, type UiAuditCaptureRequest, type UiAuditOutput, type UiAuditTask, type UiAuditViewport, type UiAuditorProfileOptions, UiFinding, type UiJudge, type UiJudgeInput, type UiJudgeOutput, type UiJudgeTokenUsage, UiLens, appendFindings, buildAuditorSystemPrompt, createInProcessUiAuditClient, createUiAuditorValidator, decodeAuditTaskEnvelope, encodeAuditTaskEnvelope, formatAuditorPrompt, initAuditWorkspace, parseAuditorEvents, readAuditRegistry, registerCaptures, summarizeRegistry, uiAuditorProfile, writeAuditIndex };
|
package/dist/profiles.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
slugify
|
|
3
|
-
} from "./chunk-4B6U4CVQ.js";
|
|
4
1
|
import {
|
|
5
2
|
DEFAULT_CODER_SYSTEM_PROMPT,
|
|
6
3
|
coderProfile,
|
|
@@ -12,6 +9,314 @@ import {
|
|
|
12
9
|
} from "./chunk-WIR4HOOJ.js";
|
|
13
10
|
import "./chunk-DGUM43GV.js";
|
|
14
11
|
|
|
12
|
+
// src/audit/issue-writer.ts
|
|
13
|
+
import { promises as fs } from "fs";
|
|
14
|
+
import path from "path";
|
|
15
|
+
|
|
16
|
+
// src/profiles/ui-auditor/slugify.ts
|
|
17
|
+
function slugify(value, fieldName) {
|
|
18
|
+
const slug = value.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
19
|
+
if (slug.length === 0) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
`ui-auditor: ${fieldName} slugified to empty string. Provide a non-empty value (got ${JSON.stringify(value)}).`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
return slug;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/audit/issue-writer.ts
|
|
28
|
+
var SEVERITY_ORDER = {
|
|
29
|
+
critical: 0,
|
|
30
|
+
high: 1,
|
|
31
|
+
med: 2,
|
|
32
|
+
low: 3
|
|
33
|
+
};
|
|
34
|
+
function assertValidWorkspaceDir(dir) {
|
|
35
|
+
if (typeof dir !== "string" || dir.length === 0) {
|
|
36
|
+
throw new Error(
|
|
37
|
+
`audit-writer: workspaceDir must be a non-empty string (got ${JSON.stringify(dir)})`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (!path.isAbsolute(dir)) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`audit-writer: workspaceDir must be an absolute path (got ${JSON.stringify(dir)})`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (dir.split(path.sep).includes("..")) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`audit-writer: workspaceDir must not contain '..' segments (got ${JSON.stringify(dir)})`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async function initAuditWorkspace(workspaceDir) {
|
|
52
|
+
assertValidWorkspaceDir(workspaceDir);
|
|
53
|
+
await fs.mkdir(path.join(workspaceDir, "issues"), { recursive: true });
|
|
54
|
+
await fs.mkdir(path.join(workspaceDir, "screenshots"), { recursive: true });
|
|
55
|
+
const regPath = path.join(workspaceDir, "registry.json");
|
|
56
|
+
try {
|
|
57
|
+
await fs.access(regPath);
|
|
58
|
+
} catch {
|
|
59
|
+
const fresh = { schemaVersion: 1, findings: [], routes: {} };
|
|
60
|
+
await fs.writeFile(regPath, JSON.stringify(fresh, null, 2));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function readAuditRegistry(workspaceDir) {
|
|
64
|
+
assertValidWorkspaceDir(workspaceDir);
|
|
65
|
+
const regPath = path.join(workspaceDir, "registry.json");
|
|
66
|
+
const text = await fs.readFile(regPath, "utf8");
|
|
67
|
+
const parsed = JSON.parse(text);
|
|
68
|
+
if (!parsed || typeof parsed !== "object") {
|
|
69
|
+
throw new Error(`audit-writer: registry.json at ${regPath} is not a JSON object`);
|
|
70
|
+
}
|
|
71
|
+
const reg = parsed;
|
|
72
|
+
if (reg.schemaVersion !== 1 || !Array.isArray(reg.findings) || typeof reg.routes !== "object") {
|
|
73
|
+
throw new Error(`audit-writer: registry.json at ${regPath} has an unrecognized shape`);
|
|
74
|
+
}
|
|
75
|
+
return reg;
|
|
76
|
+
}
|
|
77
|
+
async function writeAuditRegistry(workspaceDir, reg) {
|
|
78
|
+
const regPath = path.join(workspaceDir, "registry.json");
|
|
79
|
+
const tmpPath = `${regPath}.tmp-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
80
|
+
await fs.writeFile(tmpPath, JSON.stringify(reg, null, 2));
|
|
81
|
+
await fs.rename(tmpPath, regPath);
|
|
82
|
+
}
|
|
83
|
+
var workspaceLocks = /* @__PURE__ */ new Map();
|
|
84
|
+
async function withWorkspaceLock(workspaceDir, fn) {
|
|
85
|
+
const key = path.resolve(workspaceDir);
|
|
86
|
+
const prev = workspaceLocks.get(key) ?? Promise.resolve();
|
|
87
|
+
let signalDone;
|
|
88
|
+
const done = new Promise((resolve) => {
|
|
89
|
+
signalDone = resolve;
|
|
90
|
+
});
|
|
91
|
+
workspaceLocks.set(key, done);
|
|
92
|
+
try {
|
|
93
|
+
await prev;
|
|
94
|
+
return await fn();
|
|
95
|
+
} finally {
|
|
96
|
+
signalDone();
|
|
97
|
+
if (workspaceLocks.get(key) === done) workspaceLocks.delete(key);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function assertFindingShape(f, index) {
|
|
101
|
+
const where = `audit-writer: findings[${index}]`;
|
|
102
|
+
if (!UI_LENSES.includes(f.lens)) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`${where}: invalid lens ${JSON.stringify(f.lens)}; one of ${UI_LENSES.join("|")}`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
if (!UI_FINDING_SEVERITIES.includes(f.severity)) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`${where}: invalid severity ${JSON.stringify(f.severity)}; one of ${UI_FINDING_SEVERITIES.join("|")}`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
for (const field of ["title", "route", "observation", "impact", "suggestedFix"]) {
|
|
113
|
+
const v = f[field];
|
|
114
|
+
if (typeof v !== "string" || v.trim().length === 0) {
|
|
115
|
+
throw new Error(`${where}: ${field} must be a non-empty string`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (!Array.isArray(f.screenshots) || f.screenshots.length === 0) {
|
|
119
|
+
throw new Error(`${where}: screenshots must be a non-empty array`);
|
|
120
|
+
}
|
|
121
|
+
for (let i = 0; i < f.screenshots.length; i += 1) {
|
|
122
|
+
const s = f.screenshots[i];
|
|
123
|
+
if (!s || typeof s.path !== "string" || s.path.length === 0) {
|
|
124
|
+
throw new Error(`${where}.screenshots[${i}].path must be a non-empty string`);
|
|
125
|
+
}
|
|
126
|
+
if (s.path.split(/[/\\]/).includes("..")) {
|
|
127
|
+
throw new Error(`${where}.screenshots[${i}].path must not contain '..' segments`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function nextFindingId(reg) {
|
|
132
|
+
let max = 0;
|
|
133
|
+
for (const f of reg.findings) {
|
|
134
|
+
if (typeof f.id === "number" && f.id > max) max = f.id;
|
|
135
|
+
}
|
|
136
|
+
return max + 1;
|
|
137
|
+
}
|
|
138
|
+
function slugifyTitle(title) {
|
|
139
|
+
return slugify(title, "title");
|
|
140
|
+
}
|
|
141
|
+
function renderFinding(finding) {
|
|
142
|
+
if (!finding.id) {
|
|
143
|
+
throw new Error("audit-writer: cannot render a finding without an assigned id");
|
|
144
|
+
}
|
|
145
|
+
if (finding.screenshots.length === 0) {
|
|
146
|
+
throw new Error(`audit-writer: finding #${finding.id} has no screenshots`);
|
|
147
|
+
}
|
|
148
|
+
const id = finding.id;
|
|
149
|
+
const numStr = String(id).padStart(3, "0");
|
|
150
|
+
const metaLines = [
|
|
151
|
+
`> **Issue #${numStr}** \xB7 **Severity:** ${finding.severity} \xB7 **Lens:** ${finding.lens}`,
|
|
152
|
+
`> **Route:** \`${finding.route}\`${finding.url ? ` \xB7 **URL:** ${finding.url}` : ""}`
|
|
153
|
+
];
|
|
154
|
+
if (finding.viewport) metaLines.push(`> **Viewport:** ${finding.viewport}`);
|
|
155
|
+
if (finding.selector) metaLines.push(`> **Selector:** \`${finding.selector}\``);
|
|
156
|
+
if (finding.tags && finding.tags.length > 0) {
|
|
157
|
+
metaLines.push(`> **Tags:** ${finding.tags.map((t) => `\`${t}\``).join(", ")}`);
|
|
158
|
+
}
|
|
159
|
+
if (finding.similarTo && finding.similarTo.length > 0) {
|
|
160
|
+
metaLines.push(
|
|
161
|
+
`> **Possible duplicates:** ${finding.similarTo.map((n) => `#${String(n).padStart(3, "0")}`).join(", ")}`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
metaLines.push(`> _Generated: ${finding.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()}_`);
|
|
165
|
+
const reproSteps = finding.reproSteps ?? `1. Open \`${finding.route}\`${finding.url ? ` (${finding.url})` : ""}
|
|
166
|
+
2. Observe the area shown in the screenshot${finding.selector ? ` (selector: \`${finding.selector}\`)` : ""}.`;
|
|
167
|
+
const evidence = finding.screenshots.map((s) => ``).join("\n\n");
|
|
168
|
+
return [
|
|
169
|
+
`# [UI] ${finding.title}`,
|
|
170
|
+
"",
|
|
171
|
+
metaLines.join("\n"),
|
|
172
|
+
"",
|
|
173
|
+
"## Observation",
|
|
174
|
+
finding.observation.trim(),
|
|
175
|
+
"",
|
|
176
|
+
"## Why it matters",
|
|
177
|
+
finding.impact.trim(),
|
|
178
|
+
"",
|
|
179
|
+
"## Steps to reproduce",
|
|
180
|
+
reproSteps,
|
|
181
|
+
"",
|
|
182
|
+
"## Suggested fix",
|
|
183
|
+
finding.suggestedFix.trim(),
|
|
184
|
+
"",
|
|
185
|
+
"## Evidence",
|
|
186
|
+
evidence,
|
|
187
|
+
"",
|
|
188
|
+
"---",
|
|
189
|
+
`<sub>Generated by agent-runtime/ui-auditor \xB7 lens=\`${finding.lens}\` \xB7 severity=\`${finding.severity}\`</sub>`,
|
|
190
|
+
""
|
|
191
|
+
].join("\n");
|
|
192
|
+
}
|
|
193
|
+
async function appendFindings(workspaceDir, findings) {
|
|
194
|
+
assertValidWorkspaceDir(workspaceDir);
|
|
195
|
+
for (let i = 0; i < findings.length; i += 1) {
|
|
196
|
+
const f = findings[i];
|
|
197
|
+
if (!f) throw new Error(`audit-writer: findings[${i}] is undefined`);
|
|
198
|
+
assertFindingShape(f, i);
|
|
199
|
+
}
|
|
200
|
+
return withWorkspaceLock(workspaceDir, async () => {
|
|
201
|
+
await initAuditWorkspace(workspaceDir);
|
|
202
|
+
const reg = await readAuditRegistry(workspaceDir);
|
|
203
|
+
const usedIds = /* @__PURE__ */ new Set();
|
|
204
|
+
for (const f of reg.findings) {
|
|
205
|
+
if (typeof f.id === "number") usedIds.add(f.id);
|
|
206
|
+
}
|
|
207
|
+
const written = [];
|
|
208
|
+
const files = [];
|
|
209
|
+
let nextId = nextFindingId(reg);
|
|
210
|
+
for (const incoming of findings) {
|
|
211
|
+
let id = incoming.id;
|
|
212
|
+
if (id !== void 0) {
|
|
213
|
+
if (usedIds.has(id)) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`audit-writer: incoming finding id ${id} (title=${JSON.stringify(incoming.title)}) collides with an existing registry entry`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
id = nextId;
|
|
220
|
+
while (usedIds.has(id)) id += 1;
|
|
221
|
+
nextId = id + 1;
|
|
222
|
+
}
|
|
223
|
+
usedIds.add(id);
|
|
224
|
+
const createdAt = incoming.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
225
|
+
const persisted = { ...incoming, id, createdAt };
|
|
226
|
+
const slug = slugifyTitle(persisted.title);
|
|
227
|
+
const fileName = `${String(id).padStart(3, "0")}--${persisted.lens}--${slug}.md`;
|
|
228
|
+
const filePathAbs = path.join(workspaceDir, "issues", fileName);
|
|
229
|
+
const filePathRel = `issues/${fileName}`;
|
|
230
|
+
await fs.writeFile(filePathAbs, renderFinding(persisted));
|
|
231
|
+
reg.findings.push(persisted);
|
|
232
|
+
written.push(persisted);
|
|
233
|
+
files.push(filePathRel);
|
|
234
|
+
}
|
|
235
|
+
await writeAuditRegistry(workspaceDir, reg);
|
|
236
|
+
return { written, files };
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
async function registerCaptures(workspaceDir, options) {
|
|
240
|
+
assertValidWorkspaceDir(workspaceDir);
|
|
241
|
+
if (typeof options.route !== "string" || options.route.trim().length === 0) {
|
|
242
|
+
throw new Error("audit-writer: registerCaptures: route must be a non-empty string");
|
|
243
|
+
}
|
|
244
|
+
return withWorkspaceLock(workspaceDir, async () => {
|
|
245
|
+
await initAuditWorkspace(workspaceDir);
|
|
246
|
+
const reg = await readAuditRegistry(workspaceDir);
|
|
247
|
+
const slot = reg.routes[options.route] ?? { captures: [] };
|
|
248
|
+
if (options.url) slot.url = options.url;
|
|
249
|
+
slot.captures = [...slot.captures, ...options.captures];
|
|
250
|
+
reg.routes[options.route] = slot;
|
|
251
|
+
await writeAuditRegistry(workspaceDir, reg);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
function summarizeRegistry(reg) {
|
|
255
|
+
const bySeverity = {
|
|
256
|
+
critical: 0,
|
|
257
|
+
high: 0,
|
|
258
|
+
med: 0,
|
|
259
|
+
low: 0
|
|
260
|
+
};
|
|
261
|
+
const byLens = {};
|
|
262
|
+
const byRoute = {};
|
|
263
|
+
for (const f of reg.findings) {
|
|
264
|
+
bySeverity[f.severity] += 1;
|
|
265
|
+
byLens[f.lens] = (byLens[f.lens] ?? 0) + 1;
|
|
266
|
+
byRoute[f.route] = (byRoute[f.route] ?? 0) + 1;
|
|
267
|
+
}
|
|
268
|
+
return { total: reg.findings.length, bySeverity, byLens, byRoute };
|
|
269
|
+
}
|
|
270
|
+
async function writeAuditIndex(workspaceDir) {
|
|
271
|
+
assertValidWorkspaceDir(workspaceDir);
|
|
272
|
+
const reg = await readAuditRegistry(workspaceDir);
|
|
273
|
+
const summary = summarizeRegistry(reg);
|
|
274
|
+
const sorted = [...reg.findings].sort((a, b) => {
|
|
275
|
+
const sa = SEVERITY_ORDER[a.severity];
|
|
276
|
+
const sb = SEVERITY_ORDER[b.severity];
|
|
277
|
+
if (sa !== sb) return sa - sb;
|
|
278
|
+
return (a.id ?? 0) - (b.id ?? 0);
|
|
279
|
+
});
|
|
280
|
+
const lines = [];
|
|
281
|
+
lines.push("# UI audit index", "");
|
|
282
|
+
lines.push(`_Generated: ${(/* @__PURE__ */ new Date()).toISOString()}_`, "");
|
|
283
|
+
lines.push(`**Total findings:** ${summary.total}`, "");
|
|
284
|
+
lines.push("## By severity");
|
|
285
|
+
for (const s of ["critical", "high", "med", "low"]) {
|
|
286
|
+
const n = summary.bySeverity[s] ?? 0;
|
|
287
|
+
if (n > 0) lines.push(`- **${s}** \u2014 ${n}`);
|
|
288
|
+
}
|
|
289
|
+
lines.push("", "## By lens");
|
|
290
|
+
const lensEntries = Object.entries(summary.byLens).map(([k, v]) => [
|
|
291
|
+
k,
|
|
292
|
+
v ?? 0
|
|
293
|
+
]);
|
|
294
|
+
for (const [lens, n] of lensEntries.sort((a, b) => b[1] - a[1])) {
|
|
295
|
+
lines.push(`- **${lens}** \u2014 ${n}`);
|
|
296
|
+
}
|
|
297
|
+
lines.push("", "## By route");
|
|
298
|
+
const routeEntries = Object.entries(summary.byRoute).map(([k, v]) => [
|
|
299
|
+
k,
|
|
300
|
+
v ?? 0
|
|
301
|
+
]);
|
|
302
|
+
for (const [route, n] of routeEntries.sort((a, b) => b[1] - a[1])) {
|
|
303
|
+
lines.push(`- \`${route}\` \u2014 ${n}`);
|
|
304
|
+
}
|
|
305
|
+
lines.push("", "## Findings", "");
|
|
306
|
+
for (const f of sorted) {
|
|
307
|
+
if (typeof f.id !== "number") continue;
|
|
308
|
+
const num = String(f.id).padStart(3, "0");
|
|
309
|
+
const slug = slugifyTitle(f.title);
|
|
310
|
+
const file = `issues/${num}--${f.lens}--${slug}.md`;
|
|
311
|
+
lines.push(`- [#${num} \`${f.severity}\` \`${f.lens}\` \`${f.route}\` \u2014 ${f.title}](${file})`);
|
|
312
|
+
}
|
|
313
|
+
lines.push("");
|
|
314
|
+
const out = path.join(workspaceDir, "index.md");
|
|
315
|
+
const body = lines.join("\n");
|
|
316
|
+
await fs.writeFile(out, body);
|
|
317
|
+
return out;
|
|
318
|
+
}
|
|
319
|
+
|
|
15
320
|
// src/profiles/ui-auditor/prompt.ts
|
|
16
321
|
var ENVELOPE_BEGIN = "<<UI_AUDIT_TASK>>";
|
|
17
322
|
var ENVELOPE_END = "<<UI_AUDIT_TASK_END>>";
|
|
@@ -163,14 +468,14 @@ function createInProcessUiAuditClient(options) {
|
|
|
163
468
|
try {
|
|
164
469
|
const page = await context.newPage();
|
|
165
470
|
const captures = [];
|
|
166
|
-
const
|
|
167
|
-
const
|
|
168
|
-
const shotsDir =
|
|
169
|
-
await
|
|
471
|
+
const fs2 = await import("fs/promises");
|
|
472
|
+
const path2 = await import("path");
|
|
473
|
+
const shotsDir = path2.join(options.workspaceDir, "screenshots");
|
|
474
|
+
await fs2.mkdir(shotsDir, { recursive: true });
|
|
170
475
|
for (const req of task.captures) {
|
|
171
476
|
signal.throwIfAborted();
|
|
172
477
|
const filename = captureFilename(req);
|
|
173
|
-
const absPath =
|
|
478
|
+
const absPath = path2.join(shotsDir, filename);
|
|
174
479
|
const relPath = `screenshots/${filename}`;
|
|
175
480
|
await captureOne(page, req, absPath, signal, navPolicy);
|
|
176
481
|
const vp = viewportOf(req);
|
|
@@ -545,6 +850,7 @@ export {
|
|
|
545
850
|
SHARED_AUDITOR_RULES,
|
|
546
851
|
UI_FINDING_SEVERITIES,
|
|
547
852
|
UI_LENSES,
|
|
853
|
+
appendFindings,
|
|
548
854
|
buildAuditorSystemPrompt,
|
|
549
855
|
coderProfile,
|
|
550
856
|
coderTaskToPrompt,
|
|
@@ -553,7 +859,12 @@ export {
|
|
|
553
859
|
decodeAuditTaskEnvelope,
|
|
554
860
|
encodeAuditTaskEnvelope,
|
|
555
861
|
formatAuditorPrompt,
|
|
862
|
+
initAuditWorkspace,
|
|
556
863
|
parseAuditorEvents,
|
|
557
|
-
|
|
864
|
+
readAuditRegistry,
|
|
865
|
+
registerCaptures,
|
|
866
|
+
summarizeRegistry,
|
|
867
|
+
uiAuditorProfile,
|
|
868
|
+
writeAuditIndex
|
|
558
869
|
};
|
|
559
870
|
//# sourceMappingURL=profiles.js.map
|