@tangle-network/agent-runtime 0.301.0 → 0.306.2
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/dist/{activation-V6aaOlZV.js → activation-kaJzXM7Y.js} +3 -3
- package/dist/{activation-V6aaOlZV.js.map → activation-kaJzXM7Y.js.map} +1 -1
- package/dist/{activation-CRgEJutr.d.ts → activation-wZ-8vOjV.d.ts} +88 -13
- package/dist/agent.d.ts +2 -2
- package/dist/agent.js +2 -2
- package/dist/{bundle-C9v5TH_H.js → bundle-E6mbRX2P.js} +2 -2
- package/dist/{bundle-C9v5TH_H.js.map → bundle-E6mbRX2P.js.map} +1 -1
- package/dist/candidate-execution/index.d.ts +3 -26
- package/dist/candidate-execution/index.js +6 -6
- package/dist/{coordination-driver-vNrdQBSH.js → coordination-driver-Y2gf8pBT.js} +9 -16
- package/dist/coordination-driver-Y2gf8pBT.js.map +1 -0
- package/dist/{delegate-B-jFJcbW.js → delegate-B7bwEFQl.js} +2 -2
- package/dist/{delegate-B-jFJcbW.js.map → delegate-B7bwEFQl.js.map} +1 -1
- package/dist/durable.d.ts +3 -15
- package/dist/durable.js +158 -124
- package/dist/durable.js.map +1 -1
- package/dist/{graph-Dwv_hQB_.js → graph-VUwNFYff.js} +4 -5
- package/dist/graph-VUwNFYff.js.map +1 -0
- package/dist/{improvement-cycle-Cf37Kexd.js → improvement-cycle-DuIGusyA.js} +1129 -10
- package/dist/improvement-cycle-DuIGusyA.js.map +1 -0
- package/dist/{index-CxBVmgqL.d.ts → index-DflDZq_I.d.ts} +50 -688
- package/dist/index.d.ts +378 -12
- package/dist/index.js +223 -320
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +5 -5
- package/dist/intelligence.js +7 -8
- package/dist/intelligence.js.map +1 -1
- package/dist/kernel.d.ts +4 -4
- package/dist/kernel.js +9 -9
- package/dist/{loop-runner-bin-DiTNiJNs.d.ts → loop-runner-bin-DZfRUVWy.d.ts} +3 -3
- package/dist/{loop-runner-bin-C8MI8N1A.js → loop-runner-bin-Dp1V3P2W.js} +3 -3
- package/dist/{loop-runner-bin-C8MI8N1A.js.map → loop-runner-bin-Dp1V3P2W.js.map} +1 -1
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.d.ts +6 -35
- package/dist/mcp/index.js +311 -364
- package/dist/mcp/index.js.map +1 -1
- package/dist/model-policy-DydyWTQg.js.map +1 -1
- package/dist/{openai-tools-BeogJ6V8.d.ts → openai-tools-YO30D2s3.d.ts} +2 -2
- package/dist/{prepare-CGVNMb1o.js → prepare-CulUz6Wm.js} +7 -13
- package/dist/prepare-CulUz6Wm.js.map +1 -0
- package/dist/{profile-BetOB4_Q.js → profile-DY9lT3gH.js} +2 -2
- package/dist/{profile-BetOB4_Q.js.map → profile-DY9lT3gH.js.map} +1 -1
- package/dist/profiles.d.ts +2 -94
- package/dist/profiles.js +2 -451
- package/dist/profiles.js.map +1 -1
- package/dist/{protected-model-port-CuVHkhti.js → protected-model-port-BT2iYAsU.js} +4 -4
- package/dist/{protected-model-port-CuVHkhti.js.map → protected-model-port-BT2iYAsU.js.map} +1 -1
- package/dist/{protected-model-port-CAa0t1MT.d.ts → protected-model-port-C9QtEf3y.d.ts} +2 -2
- package/dist/{provision-supervisor-CS0fBIhp.js → provision-supervisor-CfK8fla-.js} +4 -3
- package/dist/{provision-supervisor-CS0fBIhp.js.map → provision-supervisor-CfK8fla-.js.map} +1 -1
- package/dist/{runtime-6VPSJVeg.js → runtime-D452tieK.js} +67 -469
- package/dist/runtime-D452tieK.js.map +1 -0
- package/dist/{search-method-wTTS7mGG.js → search-executor-D3AT8_hb.js} +239 -1132
- package/dist/search-executor-D3AT8_hb.js.map +1 -0
- package/dist/{server-BfHD5iLD.js → server-D1Tfxo-v.js} +5 -12
- package/dist/server-D1Tfxo-v.js.map +1 -0
- package/dist/{stream-agent-turn-B1ipFtOc.d.ts → stream-agent-turn-B1giFgOg.d.ts} +2 -2
- package/dist/{substrate-DKBMd0Dl.d.ts → substrate-L_sC7jCD.d.ts} +2 -2
- package/dist/{supervise-BJrEC2Mh.js → supervise-CtewGpxh.js} +14 -24
- package/dist/supervise-CtewGpxh.js.map +1 -0
- package/dist/{supervisor-DA_nvF-h.js → supervisor-DYKPbcZa.js} +488 -1407
- package/dist/supervisor-DYKPbcZa.js.map +1 -0
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +14 -14
- package/dist/tui/index.d.ts +1 -1
- package/dist/tui/index.js +1 -1
- package/dist/{types-jD9mNnJM.d.ts → types-DeuEkBhI.d.ts} +114 -258
- package/dist/{workspace-archive-pz8YJ1fr.d.ts → workspace-streams-CpSmt3zx.d.ts} +67 -3
- package/dist/{workspace-streams-D5gHJdTb.js → workspace-streams-DaQceg2p.js} +699 -2
- package/dist/workspace-streams-DaQceg2p.js.map +1 -0
- package/package.json +8 -8
- package/skills/generate-eval/SKILL.md +1 -1
- package/skills/profile-authoring/SKILL.md +2 -2
- package/dist/coordination-driver-vNrdQBSH.js.map +0 -1
- package/dist/graph-Dwv_hQB_.js.map +0 -1
- package/dist/improvement-cycle-Cf37Kexd.js.map +0 -1
- package/dist/prepare-CGVNMb1o.js.map +0 -1
- package/dist/runtime-6VPSJVeg.js.map +0 -1
- package/dist/search-method-wTTS7mGG.js.map +0 -1
- package/dist/server-BfHD5iLD.js.map +0 -1
- package/dist/supervise-BJrEC2Mh.js.map +0 -1
- package/dist/supervisor-DA_nvF-h.js.map +0 -1
- package/dist/workspace-streams-D5gHJdTb.js.map +0 -1
package/dist/profiles.js
CHANGED
|
@@ -1,324 +1,8 @@
|
|
|
1
1
|
import { i as ConfigError } from "./errors-DodWX-cb.js";
|
|
2
2
|
import { t as assertExecutableAgentProfile } from "./model-policy-DydyWTQg.js";
|
|
3
|
-
import { n as UI_LENSES, t as UI_FINDING_SEVERITIES } from "./substrate-B0TYNrXn.js";
|
|
4
3
|
import { t as coderTaskToPrompt } from "./coder-yhVWbdWc.js";
|
|
4
|
+
import { n as UI_LENSES, t as UI_FINDING_SEVERITIES } from "./substrate-B0TYNrXn.js";
|
|
5
5
|
import { agentProfileSchema } from "@tangle-network/agent-interface";
|
|
6
|
-
import { promises } from "node:fs";
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
//#region src/profiles/ui-auditor/slugify.ts
|
|
9
|
-
/**
|
|
10
|
-
*
|
|
11
|
-
* Shared slug helper used by both the in-process auditor client (for
|
|
12
|
-
* screenshot filenames) and the issue writer (for issue Markdown
|
|
13
|
-
* filenames). Lowercases, normalizes, strips non-alphanumeric, trims
|
|
14
|
-
* dashes, caps at 80 chars. Throws when the result is empty so callers
|
|
15
|
-
* never silently write to a name-less filename.
|
|
16
|
-
*
|
|
17
|
-
* @experimental
|
|
18
|
-
*/
|
|
19
|
-
/** @experimental */
|
|
20
|
-
function slugify(value, fieldName) {
|
|
21
|
-
const slug = value.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
22
|
-
if (slug.length === 0) throw new Error(`ui-auditor: ${fieldName} slugified to empty string. Provide a non-empty value (got ${JSON.stringify(value)}).`);
|
|
23
|
-
return slug;
|
|
24
|
-
}
|
|
25
|
-
//#endregion
|
|
26
|
-
//#region src/audit/issue-writer.ts
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
* UI-audit issue writer — pure I/O. Takes a workspace dir + `UiFinding[]`
|
|
30
|
-
* and emits:
|
|
31
|
-
* - `<workspace>/issues/NNN--<lens>--<slug>.md` — one self-contained
|
|
32
|
-
* GitHub-issue-ready Markdown per finding, with embedded screenshot
|
|
33
|
-
* references.
|
|
34
|
-
* - `<workspace>/registry.json` — finding index for dedup and audit
|
|
35
|
-
* resume across iterations.
|
|
36
|
-
* - `<workspace>/index.md` — human-readable rollup (severity / lens /
|
|
37
|
-
* route counts plus a sorted finding list).
|
|
38
|
-
*
|
|
39
|
-
* The writer is deterministic, idempotent for `appendFindings()`, and
|
|
40
|
-
* never invokes an LLM. It assigns the next monotonic id to a finding the
|
|
41
|
-
* caller did not pre-id.
|
|
42
|
-
*
|
|
43
|
-
* @experimental
|
|
44
|
-
*/
|
|
45
|
-
const SEVERITY_ORDER = {
|
|
46
|
-
critical: 0,
|
|
47
|
-
high: 1,
|
|
48
|
-
med: 2,
|
|
49
|
-
low: 3
|
|
50
|
-
};
|
|
51
|
-
function assertValidWorkspaceDir(dir) {
|
|
52
|
-
if (typeof dir !== "string" || dir.length === 0) throw new Error(`audit-writer: workspaceDir must be a non-empty string (got ${JSON.stringify(dir)})`);
|
|
53
|
-
if (!path.isAbsolute(dir)) throw new Error(`audit-writer: workspaceDir must be an absolute path (got ${JSON.stringify(dir)})`);
|
|
54
|
-
if (dir.split(path.sep).includes("..")) throw new Error(`audit-writer: workspaceDir must not contain '..' segments (got ${JSON.stringify(dir)})`);
|
|
55
|
-
}
|
|
56
|
-
/** Create the `issues/`, `screenshots/`, and `registry.json` scaffold in a new audit workspace. @experimental */
|
|
57
|
-
async function initAuditWorkspace(workspaceDir) {
|
|
58
|
-
assertValidWorkspaceDir(workspaceDir);
|
|
59
|
-
await promises.mkdir(path.join(workspaceDir, "issues"), { recursive: true });
|
|
60
|
-
await promises.mkdir(path.join(workspaceDir, "screenshots"), { recursive: true });
|
|
61
|
-
const regPath = path.join(workspaceDir, "registry.json");
|
|
62
|
-
try {
|
|
63
|
-
await promises.access(regPath);
|
|
64
|
-
} catch {
|
|
65
|
-
await promises.writeFile(regPath, JSON.stringify({
|
|
66
|
-
schemaVersion: 1,
|
|
67
|
-
findings: [],
|
|
68
|
-
routes: {}
|
|
69
|
-
}, null, 2));
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
/** Read and validate the `registry.json` from an audit workspace. @experimental */
|
|
73
|
-
async function readAuditRegistry(workspaceDir) {
|
|
74
|
-
assertValidWorkspaceDir(workspaceDir);
|
|
75
|
-
const regPath = path.join(workspaceDir, "registry.json");
|
|
76
|
-
const text = await promises.readFile(regPath, "utf8");
|
|
77
|
-
const parsed = JSON.parse(text);
|
|
78
|
-
if (!parsed || typeof parsed !== "object") throw new Error(`audit-writer: registry.json at ${regPath} is not a JSON object`);
|
|
79
|
-
const reg = parsed;
|
|
80
|
-
if (reg.schemaVersion !== 1 || !Array.isArray(reg.findings) || typeof reg.routes !== "object") throw new Error(`audit-writer: registry.json at ${regPath} has an unrecognized shape`);
|
|
81
|
-
return reg;
|
|
82
|
-
}
|
|
83
|
-
async function writeAuditRegistry(workspaceDir, reg) {
|
|
84
|
-
const regPath = path.join(workspaceDir, "registry.json");
|
|
85
|
-
const tmpPath = `${regPath}.tmp-${process.pid}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
86
|
-
await promises.writeFile(tmpPath, JSON.stringify(reg, null, 2));
|
|
87
|
-
await promises.rename(tmpPath, regPath);
|
|
88
|
-
}
|
|
89
|
-
const workspaceLocks = /* @__PURE__ */ new Map();
|
|
90
|
-
async function withWorkspaceLock(workspaceDir, fn) {
|
|
91
|
-
const key = path.resolve(workspaceDir);
|
|
92
|
-
const prev = workspaceLocks.get(key) ?? Promise.resolve();
|
|
93
|
-
let signalDone;
|
|
94
|
-
const done = new Promise((resolve) => {
|
|
95
|
-
signalDone = resolve;
|
|
96
|
-
});
|
|
97
|
-
workspaceLocks.set(key, done);
|
|
98
|
-
try {
|
|
99
|
-
await prev;
|
|
100
|
-
return await fn();
|
|
101
|
-
} finally {
|
|
102
|
-
signalDone();
|
|
103
|
-
if (workspaceLocks.get(key) === done) workspaceLocks.delete(key);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
function assertFindingShape(f, index) {
|
|
107
|
-
const where = `audit-writer: findings[${index}]`;
|
|
108
|
-
if (!UI_LENSES.includes(f.lens)) throw new Error(`${where}: invalid lens ${JSON.stringify(f.lens)}; one of ${UI_LENSES.join("|")}`);
|
|
109
|
-
if (!UI_FINDING_SEVERITIES.includes(f.severity)) throw new Error(`${where}: invalid severity ${JSON.stringify(f.severity)}; one of ${UI_FINDING_SEVERITIES.join("|")}`);
|
|
110
|
-
for (const field of [
|
|
111
|
-
"title",
|
|
112
|
-
"route",
|
|
113
|
-
"observation",
|
|
114
|
-
"impact",
|
|
115
|
-
"suggestedFix"
|
|
116
|
-
]) {
|
|
117
|
-
const v = f[field];
|
|
118
|
-
if (typeof v !== "string" || v.trim().length === 0) throw new Error(`${where}: ${field} must be a non-empty string`);
|
|
119
|
-
}
|
|
120
|
-
if (!Array.isArray(f.screenshots) || f.screenshots.length === 0) throw new Error(`${where}: screenshots must be a non-empty array`);
|
|
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) throw new Error(`${where}.screenshots[${i}].path must be a non-empty string`);
|
|
124
|
-
if (s.path.split(/[/\\]/).includes("..")) throw new Error(`${where}.screenshots[${i}].path must not contain '..' segments`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
function nextFindingId(reg) {
|
|
128
|
-
let max = 0;
|
|
129
|
-
for (const f of reg.findings) if (typeof f.id === "number" && f.id > max) max = f.id;
|
|
130
|
-
return max + 1;
|
|
131
|
-
}
|
|
132
|
-
function slugifyTitle(title) {
|
|
133
|
-
return slugify(title, "title");
|
|
134
|
-
}
|
|
135
|
-
function renderFinding(finding) {
|
|
136
|
-
if (!finding.id) throw new Error("audit-writer: cannot render a finding without an assigned id");
|
|
137
|
-
if (finding.screenshots.length === 0) throw new Error(`audit-writer: finding #${finding.id} has no screenshots`);
|
|
138
|
-
const id = finding.id;
|
|
139
|
-
const metaLines = [`> **Issue #${String(id).padStart(3, "0")}** · **Severity:** ${finding.severity} · **Lens:** ${finding.lens}`, `> **Route:** \`${finding.route}\`${finding.url ? ` · **URL:** ${finding.url}` : ""}`];
|
|
140
|
-
if (finding.viewport) metaLines.push(`> **Viewport:** ${finding.viewport}`);
|
|
141
|
-
if (finding.selector) metaLines.push(`> **Selector:** \`${finding.selector}\``);
|
|
142
|
-
if (finding.tags && finding.tags.length > 0) metaLines.push(`> **Tags:** ${finding.tags.map((t) => `\`${t}\``).join(", ")}`);
|
|
143
|
-
if (finding.similarTo && finding.similarTo.length > 0) metaLines.push(`> **Possible duplicates:** ${finding.similarTo.map((n) => `#${String(n).padStart(3, "0")}`).join(", ")}`);
|
|
144
|
-
metaLines.push(`> _Generated: ${finding.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()}_`);
|
|
145
|
-
const reproSteps = finding.reproSteps ?? `1. Open \`${finding.route}\`${finding.url ? ` (${finding.url})` : ""}\n2. Observe the area shown in the screenshot${finding.selector ? ` (selector: \`${finding.selector}\`)` : ""}.`;
|
|
146
|
-
const evidence = finding.screenshots.map((s) => ``).join("\n\n");
|
|
147
|
-
return [
|
|
148
|
-
`# [UI] ${finding.title}`,
|
|
149
|
-
"",
|
|
150
|
-
metaLines.join("\n"),
|
|
151
|
-
"",
|
|
152
|
-
"## Observation",
|
|
153
|
-
finding.observation.trim(),
|
|
154
|
-
"",
|
|
155
|
-
"## Why it matters",
|
|
156
|
-
finding.impact.trim(),
|
|
157
|
-
"",
|
|
158
|
-
"## Steps to reproduce",
|
|
159
|
-
reproSteps,
|
|
160
|
-
"",
|
|
161
|
-
"## Suggested fix",
|
|
162
|
-
finding.suggestedFix.trim(),
|
|
163
|
-
"",
|
|
164
|
-
"## Evidence",
|
|
165
|
-
evidence,
|
|
166
|
-
"",
|
|
167
|
-
"---",
|
|
168
|
-
`<sub>Generated by agent-runtime/ui-auditor · lens=\`${finding.lens}\` · severity=\`${finding.severity}\`</sub>`,
|
|
169
|
-
""
|
|
170
|
-
].join("\n");
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Append findings to a workspace, writing one Markdown file per finding
|
|
174
|
-
* and updating registry.json. Assigns monotonically increasing ids to
|
|
175
|
-
* findings that arrived without one.
|
|
176
|
-
*
|
|
177
|
-
* Findings already carrying an id that collides with the registry are
|
|
178
|
-
* rejected — callers must either freshly mint findings (id undefined) or
|
|
179
|
-
* use a separate update path. This protects against accidental overwrite.
|
|
180
|
-
*
|
|
181
|
-
* @experimental
|
|
182
|
-
*/
|
|
183
|
-
async function appendFindings(workspaceDir, findings) {
|
|
184
|
-
assertValidWorkspaceDir(workspaceDir);
|
|
185
|
-
for (let i = 0; i < findings.length; i += 1) {
|
|
186
|
-
const f = findings[i];
|
|
187
|
-
if (!f) throw new Error(`audit-writer: findings[${i}] is undefined`);
|
|
188
|
-
assertFindingShape(f, i);
|
|
189
|
-
}
|
|
190
|
-
return withWorkspaceLock(workspaceDir, async () => {
|
|
191
|
-
await initAuditWorkspace(workspaceDir);
|
|
192
|
-
const reg = await readAuditRegistry(workspaceDir);
|
|
193
|
-
const usedIds = /* @__PURE__ */ new Set();
|
|
194
|
-
for (const f of reg.findings) if (typeof f.id === "number") usedIds.add(f.id);
|
|
195
|
-
const written = [];
|
|
196
|
-
const files = [];
|
|
197
|
-
let nextId = nextFindingId(reg);
|
|
198
|
-
for (const incoming of findings) {
|
|
199
|
-
let id = incoming.id;
|
|
200
|
-
if (id !== void 0) {
|
|
201
|
-
if (usedIds.has(id)) throw new Error(`audit-writer: incoming finding id ${id} (title=${JSON.stringify(incoming.title)}) collides with an existing registry entry`);
|
|
202
|
-
} else {
|
|
203
|
-
id = nextId;
|
|
204
|
-
while (usedIds.has(id)) id += 1;
|
|
205
|
-
nextId = id + 1;
|
|
206
|
-
}
|
|
207
|
-
usedIds.add(id);
|
|
208
|
-
const createdAt = incoming.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
209
|
-
const persisted = {
|
|
210
|
-
...incoming,
|
|
211
|
-
id,
|
|
212
|
-
createdAt
|
|
213
|
-
};
|
|
214
|
-
const slug = slugifyTitle(persisted.title);
|
|
215
|
-
const fileName = `${String(id).padStart(3, "0")}--${persisted.lens}--${slug}.md`;
|
|
216
|
-
const filePathAbs = path.join(workspaceDir, "issues", fileName);
|
|
217
|
-
const filePathRel = `issues/${fileName}`;
|
|
218
|
-
await promises.writeFile(filePathAbs, renderFinding(persisted));
|
|
219
|
-
reg.findings.push(persisted);
|
|
220
|
-
written.push(persisted);
|
|
221
|
-
files.push(filePathRel);
|
|
222
|
-
}
|
|
223
|
-
await writeAuditRegistry(workspaceDir, reg);
|
|
224
|
-
return {
|
|
225
|
-
written,
|
|
226
|
-
files
|
|
227
|
-
};
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Record screenshots taken for a route in the registry, without filing a
|
|
232
|
-
* finding. Useful when the auditor wants to remember which captures
|
|
233
|
-
* exist for resume / dedup purposes.
|
|
234
|
-
*
|
|
235
|
-
* @experimental
|
|
236
|
-
*/
|
|
237
|
-
async function registerCaptures(workspaceDir, options) {
|
|
238
|
-
assertValidWorkspaceDir(workspaceDir);
|
|
239
|
-
if (typeof options.route !== "string" || options.route.trim().length === 0) throw new Error("audit-writer: registerCaptures: route must be a non-empty string");
|
|
240
|
-
return withWorkspaceLock(workspaceDir, async () => {
|
|
241
|
-
await initAuditWorkspace(workspaceDir);
|
|
242
|
-
const reg = await readAuditRegistry(workspaceDir);
|
|
243
|
-
const slot = reg.routes[options.route] ?? { captures: [] };
|
|
244
|
-
if (options.url) slot.url = options.url;
|
|
245
|
-
slot.captures = [...slot.captures, ...options.captures];
|
|
246
|
-
reg.routes[options.route] = slot;
|
|
247
|
-
await writeAuditRegistry(workspaceDir, reg);
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
/** Compute finding counts by severity, lens, and route from an `AuditRegistry`. @experimental */
|
|
251
|
-
function summarizeRegistry(reg) {
|
|
252
|
-
const bySeverity = {
|
|
253
|
-
critical: 0,
|
|
254
|
-
high: 0,
|
|
255
|
-
med: 0,
|
|
256
|
-
low: 0
|
|
257
|
-
};
|
|
258
|
-
const byLens = {};
|
|
259
|
-
const byRoute = {};
|
|
260
|
-
for (const f of reg.findings) {
|
|
261
|
-
bySeverity[f.severity] += 1;
|
|
262
|
-
byLens[f.lens] = (byLens[f.lens] ?? 0) + 1;
|
|
263
|
-
byRoute[f.route] = (byRoute[f.route] ?? 0) + 1;
|
|
264
|
-
}
|
|
265
|
-
return {
|
|
266
|
-
total: reg.findings.length,
|
|
267
|
-
bySeverity,
|
|
268
|
-
byLens,
|
|
269
|
-
byRoute
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Regenerate `<workspace>/index.md` from registry.json.
|
|
274
|
-
*
|
|
275
|
-
* @experimental
|
|
276
|
-
*/
|
|
277
|
-
async function writeAuditIndex(workspaceDir) {
|
|
278
|
-
assertValidWorkspaceDir(workspaceDir);
|
|
279
|
-
const reg = await readAuditRegistry(workspaceDir);
|
|
280
|
-
const summary = summarizeRegistry(reg);
|
|
281
|
-
const sorted = [...reg.findings].sort((a, b) => {
|
|
282
|
-
const sa = SEVERITY_ORDER[a.severity];
|
|
283
|
-
const sb = SEVERITY_ORDER[b.severity];
|
|
284
|
-
if (sa !== sb) return sa - sb;
|
|
285
|
-
return (a.id ?? 0) - (b.id ?? 0);
|
|
286
|
-
});
|
|
287
|
-
const lines = [];
|
|
288
|
-
lines.push("# UI audit index", "");
|
|
289
|
-
lines.push(`_Generated: ${(/* @__PURE__ */ new Date()).toISOString()}_`, "");
|
|
290
|
-
lines.push(`**Total findings:** ${summary.total}`, "");
|
|
291
|
-
lines.push("## By severity");
|
|
292
|
-
for (const s of [
|
|
293
|
-
"critical",
|
|
294
|
-
"high",
|
|
295
|
-
"med",
|
|
296
|
-
"low"
|
|
297
|
-
]) {
|
|
298
|
-
const n = summary.bySeverity[s] ?? 0;
|
|
299
|
-
if (n > 0) lines.push(`- **${s}** — ${n}`);
|
|
300
|
-
}
|
|
301
|
-
lines.push("", "## By lens");
|
|
302
|
-
const lensEntries = Object.entries(summary.byLens).map(([k, v]) => [k, v ?? 0]);
|
|
303
|
-
for (const [lens, n] of lensEntries.sort((a, b) => b[1] - a[1])) lines.push(`- **${lens}** — ${n}`);
|
|
304
|
-
lines.push("", "## By route");
|
|
305
|
-
const routeEntries = Object.entries(summary.byRoute).map(([k, v]) => [k, v ?? 0]);
|
|
306
|
-
for (const [route, n] of routeEntries.sort((a, b) => b[1] - a[1])) lines.push(`- \`${route}\` — ${n}`);
|
|
307
|
-
lines.push("", "## Findings", "");
|
|
308
|
-
for (const f of sorted) {
|
|
309
|
-
if (typeof f.id !== "number") continue;
|
|
310
|
-
const num = String(f.id).padStart(3, "0");
|
|
311
|
-
const slug = slugifyTitle(f.title);
|
|
312
|
-
const file = `issues/${num}--${f.lens}--${slug}.md`;
|
|
313
|
-
lines.push(`- [#${num} \`${f.severity}\` \`${f.lens}\` \`${f.route}\` — ${f.title}](${file})`);
|
|
314
|
-
}
|
|
315
|
-
lines.push("");
|
|
316
|
-
const out = path.join(workspaceDir, "index.md");
|
|
317
|
-
const body = lines.join("\n");
|
|
318
|
-
await promises.writeFile(out, body);
|
|
319
|
-
return out;
|
|
320
|
-
}
|
|
321
|
-
//#endregion
|
|
322
6
|
//#region src/profiles/researcher.ts
|
|
323
7
|
/**
|
|
324
8
|
* Opinionated preset for source-grounded research
|
|
@@ -952,139 +636,6 @@ function parseAuditorEvents(events) {
|
|
|
952
636
|
return out;
|
|
953
637
|
}
|
|
954
638
|
//#endregion
|
|
955
|
-
|
|
956
|
-
const ENVELOPE_BEGIN = "<<UI_AUDIT_TASK>>";
|
|
957
|
-
const ENVELOPE_END = "<<UI_AUDIT_TASK_END>>";
|
|
958
|
-
/** Wrap a `UiAuditTask` in a machine-readable envelope so iterations are self-describing. @experimental */
|
|
959
|
-
function encodeAuditTaskEnvelope(task) {
|
|
960
|
-
return `${ENVELOPE_BEGIN}${JSON.stringify(task)}${ENVELOPE_END}`;
|
|
961
|
-
}
|
|
962
|
-
/**
|
|
963
|
-
* Parse a task envelope back out of a prompt string. Returns undefined if
|
|
964
|
-
* the prompt does not contain a complete envelope OR if the payload is
|
|
965
|
-
* not valid JSON.
|
|
966
|
-
*
|
|
967
|
-
* @experimental
|
|
968
|
-
*/
|
|
969
|
-
function decodeAuditTaskEnvelope(prompt) {
|
|
970
|
-
const start = prompt.indexOf(ENVELOPE_BEGIN);
|
|
971
|
-
if (start === -1) return void 0;
|
|
972
|
-
const payloadStart = start + 17;
|
|
973
|
-
const end = prompt.indexOf(ENVELOPE_END, payloadStart);
|
|
974
|
-
if (end === -1) return void 0;
|
|
975
|
-
const payload = prompt.slice(payloadStart, end);
|
|
976
|
-
try {
|
|
977
|
-
const parsed = JSON.parse(payload);
|
|
978
|
-
if (!parsed || typeof parsed !== "object") return void 0;
|
|
979
|
-
const t = parsed;
|
|
980
|
-
if (typeof t.lens !== "string" || !Array.isArray(t.captures)) return void 0;
|
|
981
|
-
return t;
|
|
982
|
-
} catch {
|
|
983
|
-
return;
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
/** Produce the user message for one audit iteration: lens, captures to take, and the task envelope. @experimental */
|
|
987
|
-
function formatAuditorPrompt(task) {
|
|
988
|
-
const lines = [];
|
|
989
|
-
lines.push(`# UI audit iteration — lens: ${task.lens}`);
|
|
990
|
-
lines.push("");
|
|
991
|
-
if (task.productContext && task.productContext.trim().length > 0) {
|
|
992
|
-
lines.push("## Product context");
|
|
993
|
-
lines.push(task.productContext.trim());
|
|
994
|
-
lines.push("");
|
|
995
|
-
}
|
|
996
|
-
lines.push("## Captures to take");
|
|
997
|
-
task.captures.forEach((cap, i) => {
|
|
998
|
-
const detail = [
|
|
999
|
-
`viewport=${cap.viewport ? `${cap.viewport.width}x${cap.viewport.height}` : "1280x800 (default)"}`,
|
|
1000
|
-
cap.fullPage ? "fullPage=true" : null,
|
|
1001
|
-
cap.elementSelector ? `selector=\`${cap.elementSelector}\`` : null,
|
|
1002
|
-
cap.waitFor ? `waitFor=\`${cap.waitFor}\`` : null,
|
|
1003
|
-
cap.waitMs !== void 0 ? `waitMs=${cap.waitMs}` : null,
|
|
1004
|
-
cap.label ? `label=${cap.label}` : null
|
|
1005
|
-
].filter((s) => s !== null).join(" · ");
|
|
1006
|
-
lines.push(`${i + 1}. route=\`${cap.route}\` url=${cap.url} ${detail ? `(${detail})` : ""}`);
|
|
1007
|
-
});
|
|
1008
|
-
lines.push("");
|
|
1009
|
-
if (task.knownFindingIds && task.knownFindingIds.length > 0) {
|
|
1010
|
-
lines.push("## Known findings (link via similarTo, do not refile)");
|
|
1011
|
-
lines.push(task.knownFindingIds.map((n) => `#${String(n).padStart(3, "0")}`).join(", "));
|
|
1012
|
-
lines.push("");
|
|
1013
|
-
}
|
|
1014
|
-
lines.push("## Output format");
|
|
1015
|
-
lines.push("Emit a single JSON object with the shape `{ findings: UiFinding[], notes?: string }` where every finding has the fields enumerated in your system prompt. The screenshots field on each finding must reference the captures above by path. Do not emit findings outside the lens.");
|
|
1016
|
-
return lines.join("\n");
|
|
1017
|
-
}
|
|
1018
|
-
//#endregion
|
|
1019
|
-
//#region src/profiles/ui-auditor/validator.ts
|
|
1020
|
-
const GENERIC_TITLE_PATTERNS = [
|
|
1021
|
-
/^improve\s/i,
|
|
1022
|
-
/^fix\s/i,
|
|
1023
|
-
/^update\s/i,
|
|
1024
|
-
/^better\s/i,
|
|
1025
|
-
/^bad\s/i,
|
|
1026
|
-
/^make\s.+\sbetter/i,
|
|
1027
|
-
/\bUX\b\s*$/i,
|
|
1028
|
-
/\bUI\b\s*$/i
|
|
1029
|
-
];
|
|
1030
|
-
function isGenericTitle(title) {
|
|
1031
|
-
const t = title.trim();
|
|
1032
|
-
if (t.length < 16) return true;
|
|
1033
|
-
return GENERIC_TITLE_PATTERNS.some((re) => re.test(t));
|
|
1034
|
-
}
|
|
1035
|
-
/** Build a `Validator` that rejects off-lens findings and findings missing screenshot evidence. @experimental */
|
|
1036
|
-
function createUiAuditorValidator(task) {
|
|
1037
|
-
return { async validate(output) {
|
|
1038
|
-
const findings = output.findings;
|
|
1039
|
-
const captures = output.captures;
|
|
1040
|
-
const capturePaths = new Set(captures.map((c) => c.path));
|
|
1041
|
-
const offLens = findings.filter((f) => f.lens !== task.lens);
|
|
1042
|
-
if (offLens.length > 0) return {
|
|
1043
|
-
valid: false,
|
|
1044
|
-
score: 0,
|
|
1045
|
-
notes: `${offLens.length} finding(s) filed under wrong lens (expected ${task.lens}; got ${offLens.map((f) => f.lens).join(", ")})`,
|
|
1046
|
-
scores: { offLens: 0 }
|
|
1047
|
-
};
|
|
1048
|
-
const missingEvidence = findings.filter((f) => !Array.isArray(f.screenshots) || f.screenshots.length === 0);
|
|
1049
|
-
if (missingEvidence.length > 0) return {
|
|
1050
|
-
valid: false,
|
|
1051
|
-
score: 0,
|
|
1052
|
-
notes: `${missingEvidence.length} finding(s) have no screenshot evidence`,
|
|
1053
|
-
scores: { evidence: 0 }
|
|
1054
|
-
};
|
|
1055
|
-
const unresolvedShot = findings.filter((f) => f.screenshots.some((s) => !capturePaths.has(s.path)));
|
|
1056
|
-
if (unresolvedShot.length > 0) return {
|
|
1057
|
-
valid: false,
|
|
1058
|
-
score: 0,
|
|
1059
|
-
notes: `${unresolvedShot.length} finding(s) reference screenshot paths not captured this iteration`,
|
|
1060
|
-
scores: { evidence: 0 }
|
|
1061
|
-
};
|
|
1062
|
-
if (findings.length === 0) return {
|
|
1063
|
-
valid: true,
|
|
1064
|
-
score: .5,
|
|
1065
|
-
notes: "No findings reported. Neither a confident pass nor a failure.",
|
|
1066
|
-
scores: {
|
|
1067
|
-
specificity: 0,
|
|
1068
|
-
evidence: 1,
|
|
1069
|
-
titles: 1
|
|
1070
|
-
}
|
|
1071
|
-
};
|
|
1072
|
-
const specificity = findings.filter((f) => typeof f.selector === "string").length / findings.length;
|
|
1073
|
-
const titles = 1 - findings.filter((f) => isGenericTitle(f.title)).length / findings.length;
|
|
1074
|
-
const evidence = findings.filter((f) => Array.isArray(f.screenshots) && f.screenshots.length > 0 && f.screenshots.every((s) => capturePaths.has(s.path))).length / findings.length;
|
|
1075
|
-
return {
|
|
1076
|
-
valid: true,
|
|
1077
|
-
score: Number((.4 * specificity + .4 * evidence + .2 * titles).toFixed(4)),
|
|
1078
|
-
notes: `${findings.length} finding(s) — specificity=${specificity.toFixed(2)} evidence=${evidence.toFixed(2)} titles=${titles.toFixed(2)}`,
|
|
1079
|
-
scores: {
|
|
1080
|
-
specificity,
|
|
1081
|
-
evidence,
|
|
1082
|
-
titles
|
|
1083
|
-
}
|
|
1084
|
-
};
|
|
1085
|
-
} };
|
|
1086
|
-
}
|
|
1087
|
-
//#endregion
|
|
1088
|
-
export { LENS_BRIEFS, SHARED_AUDITOR_RULES, UI_FINDING_SEVERITIES, UI_LENSES, appendFindings, buildAuditorSystemPrompt, coderTaskToPrompt, createResearcherValidator, createUiAuditorValidator, decodeAuditTaskEnvelope, encodeAuditTaskEnvelope, formatAuditorPrompt, initAuditWorkspace, multiHarnessResearcherFanout, parseAuditorEvents, readAuditRegistry, registerCaptures, researcherProfile, summarizeRegistry, writeAuditIndex };
|
|
639
|
+
export { LENS_BRIEFS, SHARED_AUDITOR_RULES, UI_FINDING_SEVERITIES, UI_LENSES, buildAuditorSystemPrompt, coderTaskToPrompt, createResearcherValidator, multiHarnessResearcherFanout, parseAuditorEvents, researcherProfile };
|
|
1089
640
|
|
|
1090
641
|
//# sourceMappingURL=profiles.js.map
|