@m8i-51/shoal 0.1.13 → 0.1.15
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/.env.example +6 -0
- package/framework/__tests__/coverage.test.ts +1 -0
- package/framework/__tests__/report.test.ts +1 -0
- package/framework/account-manager.ts +10 -5
- package/framework/coverage.ts +11 -0
- package/framework/persona-pack.ts +137 -0
- package/framework/product-discovery.ts +4 -2
- package/framework/trackers/asana.ts +13 -0
- package/framework/trackers/backlog.ts +14 -0
- package/framework/trackers/github.ts +18 -0
- package/framework/trackers/index.ts +9 -0
- package/framework/trackers/jira.ts +19 -0
- package/framework/trackers/notion.ts +16 -0
- package/framework/trackers/types.ts +1 -0
- package/framework/triage.ts +15 -5
- package/framework/types.ts +1 -0
- package/package.json +3 -2
- package/run.ts +62 -9
- package/web/dist/assets/index-riAs4l9D.js +85 -0
- package/web/dist/index.html +1 -1
- package/web/dist/assets/index-BgIAUEzL.js +0 -68
package/run.ts
CHANGED
|
@@ -17,7 +17,8 @@ import type { Tool } from "./framework/llm-client";
|
|
|
17
17
|
import { createMessageWithRetry, runAgentLoop, sleep, rateLimitRetries } from "./framework/agent-loop";
|
|
18
18
|
import { collectedFindings, initRunLog, saveRunLog, saveFinding, runLog } from "./framework/findings";
|
|
19
19
|
import { loadAgents, addAgent, retireAgent } from "./framework/agent-store";
|
|
20
|
-
import { updateCoverage, computeWeightedSummary } from "./framework/coverage";
|
|
20
|
+
import { updateCoverage, computeWeightedSummary, getLastRunPaths } from "./framework/coverage";
|
|
21
|
+
import { loadPersonaPack, formatPackForPrompt, type PersonaPack } from "./framework/persona-pack";
|
|
21
22
|
import { buildTrackers } from "./framework/trackers/index";
|
|
22
23
|
import {
|
|
23
24
|
setupObservation,
|
|
@@ -231,6 +232,10 @@ function makeExecutor(agentLog: AgentLog, scenarioOutcomes: ScenarioOutcome[], s
|
|
|
231
232
|
`**Regression:** #${original_issue_number} "${original_issue_title}" has reappeared.\n\n${body}\n\n---\n*This issue was auto-generated by an AI regression agent*`,
|
|
232
233
|
["regression", "feedback-agent"]
|
|
233
234
|
);
|
|
235
|
+
await trackers.commentOnIssue(
|
|
236
|
+
original_issue_number,
|
|
237
|
+
`⚠️ **Regression detected** by AI agent on ${new Date().toISOString().slice(0, 10)}\n\n${body}${url ? `\n\nNew issue: ${url}` : ""}`
|
|
238
|
+
);
|
|
234
239
|
const check: RegressionCheck = {
|
|
235
240
|
issueNumber: Number(original_issue_number),
|
|
236
241
|
issueTitle: String(original_issue_title),
|
|
@@ -248,6 +253,10 @@ function makeExecutor(agentLog: AgentLog, scenarioOutcomes: ScenarioOutcome[], s
|
|
|
248
253
|
const { original_issue_number, original_issue_title, note } = input as {
|
|
249
254
|
original_issue_number: number; original_issue_title: string; note: string;
|
|
250
255
|
};
|
|
256
|
+
await trackers.commentOnIssue(
|
|
257
|
+
original_issue_number,
|
|
258
|
+
`✅ **Verified as fixed** by AI agent on ${new Date().toISOString().slice(0, 10)}\n\n${note}`
|
|
259
|
+
);
|
|
251
260
|
agentLog.regressionChecks.push({
|
|
252
261
|
issueNumber: Number(original_issue_number),
|
|
253
262
|
issueTitle: String(original_issue_title),
|
|
@@ -304,6 +313,7 @@ async function runExplorer(
|
|
|
304
313
|
status: "completed",
|
|
305
314
|
iterations: 0,
|
|
306
315
|
actions: [],
|
|
316
|
+
visitedPaths: [],
|
|
307
317
|
issuesPosted: [],
|
|
308
318
|
regressionChecks: [],
|
|
309
319
|
error: null,
|
|
@@ -357,6 +367,7 @@ async function runRegressionAgent(
|
|
|
357
367
|
status: "completed",
|
|
358
368
|
iterations: 0,
|
|
359
369
|
actions: [],
|
|
370
|
+
visitedPaths: [],
|
|
360
371
|
issuesPosted: [],
|
|
361
372
|
regressionChecks: [],
|
|
362
373
|
error: null,
|
|
@@ -405,6 +416,16 @@ const PERSONA_DESIGNER_TOOLS: Anthropic.Tool[] = [
|
|
|
405
416
|
description: "Get a weighted summary of what has been explored across past runs. Use this to identify underrepresented lenses and perspectives before deciding whom to hire. / 過去のrunで何がどれだけ探索されたかの重み付きサマリーを取得する。採用方針の決定前に確認すること",
|
|
406
417
|
input_schema: { type: "object", properties: {}, required: [] },
|
|
407
418
|
},
|
|
419
|
+
{
|
|
420
|
+
name: "get_path_coverage",
|
|
421
|
+
description: "Get the list of URL paths visited in the previous run. Use this to identify unexplored areas of the app and recruit agents likely to visit NEW paths. / 前回のrunで訪れたURLパス一覧を取得する。未探索エリアを特定し、新しいパスを訪れる可能性の高いペルソナを採用するために使う",
|
|
422
|
+
input_schema: { type: "object", properties: {}, required: [] },
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
name: "get_persona_templates",
|
|
426
|
+
description: "Get the persona template pack defined for this project. Prefer these archetypes when adding agents — adapt names/details to fit the app context but keep the role intact. / このプロジェクト用に定義されたペルソナテンプレート一覧を取得する。エージェントを追加する際はまずこのテンプレートから選ぶこと",
|
|
427
|
+
input_schema: { type: "object", properties: {}, required: [] },
|
|
428
|
+
},
|
|
408
429
|
{
|
|
409
430
|
name: "get_open_issues",
|
|
410
431
|
description: "Get the titles and labels of currently open GitHub Issues (known problems). Use this to understand what is already known and recruit agents who are likely to explore DIFFERENT areas. / 現在オープンなGitHub Issueのタイトルとラベルを取得する。既知の問題を把握し、未探索領域を掘れるペルソナを採用するために使う",
|
|
@@ -448,6 +469,8 @@ async function runPersonaDesigner(
|
|
|
448
469
|
openIssues: { number: number | string; title: string; labels: string[] }[],
|
|
449
470
|
scenarios: Scenario[],
|
|
450
471
|
testAccounts: TestAccount[] = [],
|
|
472
|
+
lastRunPaths: { visitedPaths: string[]; runId: string } | null = null,
|
|
473
|
+
personaPack: PersonaPack | null = null,
|
|
451
474
|
): Promise<void> {
|
|
452
475
|
console.log("\n[persona-designer] starting...");
|
|
453
476
|
const messages: Anthropic.MessageParam[] = [
|
|
@@ -458,6 +481,14 @@ async function runPersonaDesigner(
|
|
|
458
481
|
? `\n[Available Test Accounts (one per role)]\n${testAccounts.map((a) => `- ${a.role}: ${a.email}`).join("\n")}\nWhen recruiting agents, match each persona's role to one of these accounts so they can operate with appropriate permissions.`
|
|
459
482
|
: "";
|
|
460
483
|
|
|
484
|
+
const pathCoverageStep = lastRunPaths
|
|
485
|
+
? "3. Call get_path_coverage to see which URL paths were visited last run — recruit agents whose role would naturally take them to DIFFERENT or unexplored paths"
|
|
486
|
+
: "3. (No previous run data yet — skip get_path_coverage)";
|
|
487
|
+
|
|
488
|
+
const personaTemplateStep = personaPack
|
|
489
|
+
? "2. Call get_persona_templates to get project-specific persona archetypes — prefer these over inventing new personas from scratch"
|
|
490
|
+
: "2. (No persona templates configured — invent personas that fit the app context)";
|
|
491
|
+
|
|
461
492
|
const systemPrompt = `You are the persona designer for "${productSpec.appName}".
|
|
462
493
|
You create and manage test agents that simulate real users of the app.
|
|
463
494
|
|
|
@@ -466,11 +497,13 @@ ${orgGuidance}${accountContext}
|
|
|
466
497
|
|
|
467
498
|
[Steps]
|
|
468
499
|
1. Call get_coverage to review which lenses and categories are underrepresented in past runs
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
4. Call
|
|
472
|
-
5.
|
|
473
|
-
6.
|
|
500
|
+
${personaTemplateStep}
|
|
501
|
+
${pathCoverageStep}
|
|
502
|
+
4. Call get_open_issues to understand what problems are already known — recruit agents likely to find DIFFERENT issues in unexplored areas
|
|
503
|
+
5. Call get_scenarios to see the user test scenarios generated for this run — about 70% of agents will be assigned a scenario, so recruit personas whose background fits those scenarios
|
|
504
|
+
6. Call get_agents to check the current agent roster
|
|
505
|
+
7. Add 2–3 agents with add_agent — balance between scenario-fit personas (step 5), underrepresented lenses (step 1), unexplored paths (step 3), and unexplored areas (step 4)${testAccounts.length > 0 ? "\n — assign each agent a role that matches one of the available test accounts" : ""}
|
|
506
|
+
8. If there are agents with old createdAt dates (oldest 1–2), retire them with retire_agent`;
|
|
474
507
|
|
|
475
508
|
try {
|
|
476
509
|
let iterations = 0;
|
|
@@ -494,6 +527,20 @@ ${orgGuidance}${accountContext}
|
|
|
494
527
|
if (toolUse.name === "get_coverage") {
|
|
495
528
|
result = computeWeightedSummary().formatted;
|
|
496
529
|
console.log(" [persona-designer] coverage summary fetched");
|
|
530
|
+
} else if (toolUse.name === "get_persona_templates") {
|
|
531
|
+
if (!personaPack) {
|
|
532
|
+
result = "(no persona templates configured — set SHOAL_PERSONAS env var or add personas.yaml to your project)";
|
|
533
|
+
} else {
|
|
534
|
+
result = formatPackForPrompt(personaPack);
|
|
535
|
+
}
|
|
536
|
+
console.log(` [persona-designer] persona templates fetched (${personaPack?.personas.length ?? 0})`);
|
|
537
|
+
} else if (toolUse.name === "get_path_coverage") {
|
|
538
|
+
if (!lastRunPaths || lastRunPaths.visitedPaths.length === 0) {
|
|
539
|
+
result = "(no path coverage data yet — this is the first run or no paths were recorded)";
|
|
540
|
+
} else {
|
|
541
|
+
result = `Paths visited in last run (${lastRunPaths.runId}):\n${lastRunPaths.visitedPaths.map((p) => `- ${p}`).join("\n")}\n\nRecruit agents whose role naturally takes them to paths NOT in this list.`;
|
|
542
|
+
}
|
|
543
|
+
console.log(` [persona-designer] path coverage fetched (${lastRunPaths?.visitedPaths.length ?? 0} paths)`);
|
|
497
544
|
} else if (toolUse.name === "get_open_issues") {
|
|
498
545
|
if (openIssues.length === 0) {
|
|
499
546
|
result = "(no open issues — either GitHub is not configured or there are no known issues yet)";
|
|
@@ -555,6 +602,7 @@ interface BrowserAgentLog {
|
|
|
555
602
|
status: "completed" | "error" | "iteration_limit";
|
|
556
603
|
iterations: number;
|
|
557
604
|
actions: BrowserAction[];
|
|
605
|
+
visitedPaths: string[];
|
|
558
606
|
feedbacksSaved: { title: string; category: string; findingId: string }[];
|
|
559
607
|
error: string | null;
|
|
560
608
|
}
|
|
@@ -690,6 +738,7 @@ async function executeBrowserTool(
|
|
|
690
738
|
await page.goto(`${BASE_URL}${navPath}`, { waitUntil: "networkidle" });
|
|
691
739
|
await page.waitForTimeout(3000);
|
|
692
740
|
screenshot = await takeScreenshot(page, `navigate_${navPath.replace(/\//g, "_")}`);
|
|
741
|
+
agentLog.visitedPaths.push(navPath);
|
|
693
742
|
resultText = `Navigated to ${navPath}`;
|
|
694
743
|
break;
|
|
695
744
|
}
|
|
@@ -870,6 +919,7 @@ async function runBrowserAgent(
|
|
|
870
919
|
status: "completed",
|
|
871
920
|
iterations: 0,
|
|
872
921
|
actions: [],
|
|
922
|
+
visitedPaths: [],
|
|
873
923
|
feedbacksSaved: [],
|
|
874
924
|
error: null,
|
|
875
925
|
};
|
|
@@ -1102,7 +1152,9 @@ async function main() {
|
|
|
1102
1152
|
}
|
|
1103
1153
|
|
|
1104
1154
|
// 4. HR agent
|
|
1105
|
-
|
|
1155
|
+
const lastRunPaths = getLastRunPaths();
|
|
1156
|
+
const personaPack = await loadPersonaPack();
|
|
1157
|
+
await runPersonaDesigner(productSpec, orgDesign.personaGuidance, openIssues, scenarios, testAccounts, lastRunPaths, personaPack);
|
|
1106
1158
|
|
|
1107
1159
|
// 5. load agents + closed issues
|
|
1108
1160
|
const allAgents = loadAgents();
|
|
@@ -1159,7 +1211,7 @@ async function main() {
|
|
|
1159
1211
|
browserAgents.forEach((a) => console.log(` - ${a.name} (${a.role})`));
|
|
1160
1212
|
|
|
1161
1213
|
await sleep(2000);
|
|
1162
|
-
await Promise.all(
|
|
1214
|
+
const browserLogs = await Promise.all(
|
|
1163
1215
|
browserAgents.map(async (agent) => {
|
|
1164
1216
|
const assignment = pickAssignment(dispatchIdx++, scenarios);
|
|
1165
1217
|
agentAssignments.set(agent.id, assignment);
|
|
@@ -1182,6 +1234,7 @@ async function main() {
|
|
|
1182
1234
|
}
|
|
1183
1235
|
})
|
|
1184
1236
|
);
|
|
1237
|
+
const allVisitedPaths = browserLogs.flatMap((log) => log.visitedPaths);
|
|
1185
1238
|
|
|
1186
1239
|
// 8. triage (API + browser findings)
|
|
1187
1240
|
await sleep(2000);
|
|
@@ -1199,7 +1252,7 @@ async function main() {
|
|
|
1199
1252
|
console.log(`\n[report] ${reportPath}`);
|
|
1200
1253
|
|
|
1201
1254
|
// 10. update coverage
|
|
1202
|
-
updateCoverage(runLog.runId, collectedFindings, agentAssignments);
|
|
1255
|
+
updateCoverage(runLog.runId, collectedFindings, agentAssignments, allVisitedPaths);
|
|
1203
1256
|
|
|
1204
1257
|
} finally {
|
|
1205
1258
|
await browser.close();
|