@hasna/testers 0.0.12 → 0.0.13
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/dashboard/dist/assets/{index-DyXKnBM8.css → index-PT-52SEY.css} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/cli/index.js +2110 -1469
- package/dist/db/agents.d.ts +2 -0
- package/dist/db/agents.d.ts.map +1 -1
- package/dist/index.js +215 -117
- package/dist/lib/affected.d.ts +23 -0
- package/dist/lib/affected.d.ts.map +1 -0
- package/dist/lib/failure-pipeline.d.ts +20 -0
- package/dist/lib/failure-pipeline.d.ts.map +1 -0
- package/dist/lib/git-watch.d.ts +16 -0
- package/dist/lib/git-watch.d.ts.map +1 -0
- package/dist/lib/runner.d.ts.map +1 -1
- package/dist/mcp/index.js +314 -94
- package/dist/server/index.js +138 -13
- package/package.json +1 -1
- package/dist/cli/index.d.ts +0 -3
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/mcp/index.d.ts +0 -3
- package/dist/mcp/index.d.ts.map +0 -1
- /package/dashboard/dist/assets/{index-jNG_Nd_Q.js → index-FZ9gzLaz.js} +0 -0
package/dist/db/agents.d.ts
CHANGED
|
@@ -7,4 +7,6 @@ export declare function registerAgent(input: {
|
|
|
7
7
|
export declare function getAgent(id: string): Agent | null;
|
|
8
8
|
export declare function getAgentByName(name: string): Agent | null;
|
|
9
9
|
export declare function listAgents(): Agent[];
|
|
10
|
+
export declare function heartbeatAgent(id: string): Agent | null;
|
|
11
|
+
export declare function setAgentFocus(id: string, scenarioId: string | null): Agent | null;
|
|
10
12
|
//# sourceMappingURL=agents.d.ts.map
|
package/dist/db/agents.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/db/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,KAAK,EAGX,MAAM,mBAAmB,CAAC;AAG3B,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,KAAK,CA2BR;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAIjD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAIzD;AAED,wBAAgB,UAAU,IAAI,KAAK,EAAE,CAIpC"}
|
|
1
|
+
{"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../src/db/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,KAAK,EAGX,MAAM,mBAAmB,CAAC;AAG3B,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,KAAK,CA2BR;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAIjD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAIzD;AAED,wBAAgB,UAAU,IAAI,KAAK,EAAE,CAIpC;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,CAKvD;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,CAWjF"}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
3
7
|
var __export = (target, all) => {
|
|
4
8
|
for (var name in all)
|
|
5
9
|
__defProp(target, name, {
|
|
6
10
|
get: all[name],
|
|
7
11
|
enumerable: true,
|
|
8
12
|
configurable: true,
|
|
9
|
-
set: (
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
10
14
|
});
|
|
11
15
|
};
|
|
12
16
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -2722,6 +2726,214 @@ async function pushFailedRunToLogs(run, failedResults, scenarios) {
|
|
|
2722
2726
|
} catch {}
|
|
2723
2727
|
}
|
|
2724
2728
|
|
|
2729
|
+
// src/lib/todos-connector.ts
|
|
2730
|
+
import { Database as Database2 } from "bun:sqlite";
|
|
2731
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2732
|
+
import { join as join4 } from "path";
|
|
2733
|
+
import { homedir as homedir4 } from "os";
|
|
2734
|
+
init_types();
|
|
2735
|
+
function resolveTodosDbPath() {
|
|
2736
|
+
const envPath = process.env["TODOS_DB_PATH"];
|
|
2737
|
+
if (envPath)
|
|
2738
|
+
return envPath;
|
|
2739
|
+
return join4(homedir4(), ".todos", "todos.db");
|
|
2740
|
+
}
|
|
2741
|
+
function connectToTodos() {
|
|
2742
|
+
const dbPath = resolveTodosDbPath();
|
|
2743
|
+
if (!existsSync4(dbPath)) {
|
|
2744
|
+
throw new TodosConnectionError(`Todos database not found at ${dbPath}. Install @hasna/todos or set TODOS_DB_PATH.`);
|
|
2745
|
+
}
|
|
2746
|
+
const db2 = new Database2(dbPath, { readonly: true });
|
|
2747
|
+
db2.exec("PRAGMA foreign_keys = ON");
|
|
2748
|
+
return db2;
|
|
2749
|
+
}
|
|
2750
|
+
function pullTasks(options = {}) {
|
|
2751
|
+
const db2 = connectToTodos();
|
|
2752
|
+
try {
|
|
2753
|
+
let query = "SELECT id, short_id, title, description, status, priority, tags, project_id FROM tasks WHERE 1=1";
|
|
2754
|
+
const params = [];
|
|
2755
|
+
if (options.status) {
|
|
2756
|
+
query += " AND status = ?";
|
|
2757
|
+
params.push(options.status);
|
|
2758
|
+
} else {
|
|
2759
|
+
query += " AND status IN ('pending', 'in_progress')";
|
|
2760
|
+
}
|
|
2761
|
+
if (options.priority) {
|
|
2762
|
+
query += " AND priority = ?";
|
|
2763
|
+
params.push(options.priority);
|
|
2764
|
+
}
|
|
2765
|
+
if (options.projectName) {
|
|
2766
|
+
const project = db2.query("SELECT id FROM projects WHERE name = ?").get(options.projectName);
|
|
2767
|
+
if (project) {
|
|
2768
|
+
query += " AND project_id = ?";
|
|
2769
|
+
params.push(project.id);
|
|
2770
|
+
}
|
|
2771
|
+
}
|
|
2772
|
+
query += " ORDER BY CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END";
|
|
2773
|
+
const tasks = db2.query(query).all(...params);
|
|
2774
|
+
if (options.tags && options.tags.length > 0) {
|
|
2775
|
+
return tasks.filter((task) => {
|
|
2776
|
+
const taskTags = JSON.parse(task.tags || "[]");
|
|
2777
|
+
return options.tags.some((tag) => taskTags.includes(tag));
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
return tasks;
|
|
2781
|
+
} finally {
|
|
2782
|
+
db2.close();
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
function taskToScenarioInput(task, projectId) {
|
|
2786
|
+
const tags = JSON.parse(task.tags || "[]");
|
|
2787
|
+
const priority = ["low", "medium", "high", "critical"].includes(task.priority) ? task.priority : "medium";
|
|
2788
|
+
const steps = [];
|
|
2789
|
+
if (task.description) {
|
|
2790
|
+
const lines = task.description.split(`
|
|
2791
|
+
`);
|
|
2792
|
+
for (const line of lines) {
|
|
2793
|
+
const match = line.match(/^\s*\d+[\.\)]\s*(.+)/);
|
|
2794
|
+
if (match?.[1]) {
|
|
2795
|
+
steps.push(match[1].trim());
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
return {
|
|
2800
|
+
name: task.title.replace(/^(OPE\d+-\d+|[A-Z]+-\d+):\s*/, ""),
|
|
2801
|
+
description: task.description || task.title,
|
|
2802
|
+
steps,
|
|
2803
|
+
tags,
|
|
2804
|
+
priority,
|
|
2805
|
+
projectId,
|
|
2806
|
+
metadata: { todosTaskId: task.id, todosShortId: task.short_id }
|
|
2807
|
+
};
|
|
2808
|
+
}
|
|
2809
|
+
function importFromTodos(options = {}) {
|
|
2810
|
+
const tasks = pullTasks({
|
|
2811
|
+
projectName: options.projectName,
|
|
2812
|
+
tags: options.tags ?? ["qa", "test", "testing"],
|
|
2813
|
+
priority: options.priority
|
|
2814
|
+
});
|
|
2815
|
+
const existing = listScenarios({ projectId: options.projectId });
|
|
2816
|
+
const existingTodoIds = new Set(existing.filter((s) => s.metadata?.todosTaskId).map((s) => s.metadata.todosTaskId));
|
|
2817
|
+
let imported = 0;
|
|
2818
|
+
let skipped = 0;
|
|
2819
|
+
for (const task of tasks) {
|
|
2820
|
+
if (existingTodoIds.has(task.id)) {
|
|
2821
|
+
skipped++;
|
|
2822
|
+
continue;
|
|
2823
|
+
}
|
|
2824
|
+
const input = taskToScenarioInput(task, options.projectId);
|
|
2825
|
+
createScenario(input);
|
|
2826
|
+
imported++;
|
|
2827
|
+
}
|
|
2828
|
+
return { imported, skipped };
|
|
2829
|
+
}
|
|
2830
|
+
function markTodoDone(taskId) {
|
|
2831
|
+
const dbPath = resolveTodosDbPath();
|
|
2832
|
+
if (!existsSync4(dbPath))
|
|
2833
|
+
return false;
|
|
2834
|
+
const db2 = new Database2(dbPath);
|
|
2835
|
+
try {
|
|
2836
|
+
const task = db2.query("SELECT id, version FROM tasks WHERE id LIKE ? || '%'").get(taskId);
|
|
2837
|
+
if (!task)
|
|
2838
|
+
return false;
|
|
2839
|
+
db2.query("UPDATE tasks SET status = 'completed', completed_at = datetime('now'), version = version + 1, updated_at = datetime('now') WHERE id = ? AND version = ?").run(task.id, task.version);
|
|
2840
|
+
return true;
|
|
2841
|
+
} finally {
|
|
2842
|
+
db2.close();
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
// src/lib/failure-pipeline.ts
|
|
2847
|
+
async function createFailureTasks(run, failedResults, scenarios) {
|
|
2848
|
+
if (failedResults.length === 0)
|
|
2849
|
+
return { created: 0, skipped: 0 };
|
|
2850
|
+
const projectId = process.env["TESTERS_TODOS_PROJECT_ID"];
|
|
2851
|
+
if (!projectId)
|
|
2852
|
+
return { created: 0, skipped: 0 };
|
|
2853
|
+
let db2 = null;
|
|
2854
|
+
try {
|
|
2855
|
+
db2 = connectToTodos();
|
|
2856
|
+
} catch {
|
|
2857
|
+
return { created: 0, skipped: 0 };
|
|
2858
|
+
}
|
|
2859
|
+
const scenarioMap = new Map(scenarios.map((s) => [s.id, s]));
|
|
2860
|
+
let created = 0;
|
|
2861
|
+
let skipped = 0;
|
|
2862
|
+
try {
|
|
2863
|
+
for (const result of failedResults) {
|
|
2864
|
+
const scenario = scenarioMap.get(result.scenarioId);
|
|
2865
|
+
const title = `BUG: [testers] ${scenario?.name ?? result.scenarioId} failed`;
|
|
2866
|
+
const existing = db2.query("SELECT id FROM tasks WHERE title = ? AND status NOT IN ('completed', 'cancelled') LIMIT 1").get(title);
|
|
2867
|
+
if (existing) {
|
|
2868
|
+
skipped++;
|
|
2869
|
+
continue;
|
|
2870
|
+
}
|
|
2871
|
+
const id = crypto.randomUUID();
|
|
2872
|
+
const now2 = new Date().toISOString();
|
|
2873
|
+
const description = [
|
|
2874
|
+
`Test failure detected by open-testers.`,
|
|
2875
|
+
``,
|
|
2876
|
+
`**Run:** ${run.id}`,
|
|
2877
|
+
`**URL:** ${run.url}`,
|
|
2878
|
+
`**Scenario:** ${scenario?.name ?? result.scenarioId}`,
|
|
2879
|
+
`**Status:** ${result.status}`,
|
|
2880
|
+
result.error ? `**Error:** ${result.error}` : null,
|
|
2881
|
+
result.reasoning ? `**Reasoning:** ${result.reasoning.slice(0, 500)}` : null,
|
|
2882
|
+
`**Duration:** ${result.durationMs ? `${(result.durationMs / 1000).toFixed(1)}s` : "N/A"}`,
|
|
2883
|
+
`**Tokens:** ${result.tokensUsed ?? 0}`
|
|
2884
|
+
].filter(Boolean).join(`
|
|
2885
|
+
`);
|
|
2886
|
+
try {
|
|
2887
|
+
db2.query(`
|
|
2888
|
+
INSERT INTO tasks (id, short_id, title, description, status, priority, tags, project_id, version, created_at, updated_at)
|
|
2889
|
+
VALUES (?, ?, ?, ?, 'pending', 'high', ?, ?, 1, ?, ?)
|
|
2890
|
+
`).run(id, `BUG-${id.slice(0, 6)}`, title, description, JSON.stringify(["bug", "testers", "auto-created"]), projectId, now2, now2);
|
|
2891
|
+
created++;
|
|
2892
|
+
} catch {
|
|
2893
|
+
skipped++;
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
} finally {
|
|
2897
|
+
db2.close();
|
|
2898
|
+
}
|
|
2899
|
+
return { created, skipped };
|
|
2900
|
+
}
|
|
2901
|
+
async function notifyFailureToConversations(run, failedResults, scenarios) {
|
|
2902
|
+
const baseUrl = process.env["TESTERS_CONVERSATIONS_URL"];
|
|
2903
|
+
const space = process.env["TESTERS_CONVERSATIONS_SPACE"];
|
|
2904
|
+
if (!baseUrl || !space)
|
|
2905
|
+
return;
|
|
2906
|
+
const scenarioMap = new Map(scenarios.map((s) => [s.id, s]));
|
|
2907
|
+
const total = run.total;
|
|
2908
|
+
const failedCount = failedResults.length;
|
|
2909
|
+
const passedCount = run.passed;
|
|
2910
|
+
const failureLines = failedResults.slice(0, 5).map((r) => {
|
|
2911
|
+
const name = scenarioMap.get(r.scenarioId)?.name ?? r.scenarioId;
|
|
2912
|
+
const err = r.error ? ` \u2014 ${r.error.slice(0, 120)}` : "";
|
|
2913
|
+
return ` \u274C ${name}${err}`;
|
|
2914
|
+
});
|
|
2915
|
+
const extra = failedResults.length > 5 ? ` \u2026 and ${failedResults.length - 5} more` : "";
|
|
2916
|
+
const message = [
|
|
2917
|
+
`\uD83D\uDEA8 **Testers run failed** \u2014 ${failedCount}/${total} scenarios failed`,
|
|
2918
|
+
``,
|
|
2919
|
+
`**URL:** ${run.url}`,
|
|
2920
|
+
`**Run ID:** \`${run.id}\``,
|
|
2921
|
+
`**Pass rate:** ${passedCount}/${total}`,
|
|
2922
|
+
``,
|
|
2923
|
+
`**Failures:**`,
|
|
2924
|
+
...failureLines,
|
|
2925
|
+
extra
|
|
2926
|
+
].filter((l) => l !== "").join(`
|
|
2927
|
+
`);
|
|
2928
|
+
try {
|
|
2929
|
+
await fetch(`${baseUrl.replace(/\/$/, "")}/api/spaces/${encodeURIComponent(space)}/messages`, {
|
|
2930
|
+
method: "POST",
|
|
2931
|
+
headers: { "Content-Type": "application/json" },
|
|
2932
|
+
body: JSON.stringify({ content: message, from: "testers" })
|
|
2933
|
+
});
|
|
2934
|
+
} catch {}
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2725
2937
|
// src/lib/runner.ts
|
|
2726
2938
|
var eventHandler = null;
|
|
2727
2939
|
function onRunEvent(handler) {
|
|
@@ -2958,6 +3170,8 @@ async function runBatch(scenarios, options) {
|
|
|
2958
3170
|
if (finalRun.status === "failed") {
|
|
2959
3171
|
const failedResults = results.filter((r) => r.status === "failed" || r.status === "error");
|
|
2960
3172
|
pushFailedRunToLogs(finalRun, failedResults, scenarios).catch(() => {});
|
|
3173
|
+
createFailureTasks(finalRun, failedResults, scenarios).catch(() => {});
|
|
3174
|
+
notifyFailureToConversations(finalRun, failedResults, scenarios).catch(() => {});
|
|
2961
3175
|
}
|
|
2962
3176
|
return { run: finalRun, results };
|
|
2963
3177
|
}
|
|
@@ -3808,122 +4022,6 @@ function formatResultDetail(result, screenshots) {
|
|
|
3808
4022
|
return lines.join(`
|
|
3809
4023
|
`);
|
|
3810
4024
|
}
|
|
3811
|
-
// src/lib/todos-connector.ts
|
|
3812
|
-
import { Database as Database2 } from "bun:sqlite";
|
|
3813
|
-
import { existsSync as existsSync4 } from "fs";
|
|
3814
|
-
import { join as join4 } from "path";
|
|
3815
|
-
import { homedir as homedir4 } from "os";
|
|
3816
|
-
init_types();
|
|
3817
|
-
function resolveTodosDbPath() {
|
|
3818
|
-
const envPath = process.env["TODOS_DB_PATH"];
|
|
3819
|
-
if (envPath)
|
|
3820
|
-
return envPath;
|
|
3821
|
-
return join4(homedir4(), ".todos", "todos.db");
|
|
3822
|
-
}
|
|
3823
|
-
function connectToTodos() {
|
|
3824
|
-
const dbPath = resolveTodosDbPath();
|
|
3825
|
-
if (!existsSync4(dbPath)) {
|
|
3826
|
-
throw new TodosConnectionError(`Todos database not found at ${dbPath}. Install @hasna/todos or set TODOS_DB_PATH.`);
|
|
3827
|
-
}
|
|
3828
|
-
const db2 = new Database2(dbPath, { readonly: true });
|
|
3829
|
-
db2.exec("PRAGMA foreign_keys = ON");
|
|
3830
|
-
return db2;
|
|
3831
|
-
}
|
|
3832
|
-
function pullTasks(options = {}) {
|
|
3833
|
-
const db2 = connectToTodos();
|
|
3834
|
-
try {
|
|
3835
|
-
let query = "SELECT id, short_id, title, description, status, priority, tags, project_id FROM tasks WHERE 1=1";
|
|
3836
|
-
const params = [];
|
|
3837
|
-
if (options.status) {
|
|
3838
|
-
query += " AND status = ?";
|
|
3839
|
-
params.push(options.status);
|
|
3840
|
-
} else {
|
|
3841
|
-
query += " AND status IN ('pending', 'in_progress')";
|
|
3842
|
-
}
|
|
3843
|
-
if (options.priority) {
|
|
3844
|
-
query += " AND priority = ?";
|
|
3845
|
-
params.push(options.priority);
|
|
3846
|
-
}
|
|
3847
|
-
if (options.projectName) {
|
|
3848
|
-
const project = db2.query("SELECT id FROM projects WHERE name = ?").get(options.projectName);
|
|
3849
|
-
if (project) {
|
|
3850
|
-
query += " AND project_id = ?";
|
|
3851
|
-
params.push(project.id);
|
|
3852
|
-
}
|
|
3853
|
-
}
|
|
3854
|
-
query += " ORDER BY CASE priority WHEN 'critical' THEN 0 WHEN 'high' THEN 1 WHEN 'medium' THEN 2 WHEN 'low' THEN 3 END";
|
|
3855
|
-
const tasks = db2.query(query).all(...params);
|
|
3856
|
-
if (options.tags && options.tags.length > 0) {
|
|
3857
|
-
return tasks.filter((task) => {
|
|
3858
|
-
const taskTags = JSON.parse(task.tags || "[]");
|
|
3859
|
-
return options.tags.some((tag) => taskTags.includes(tag));
|
|
3860
|
-
});
|
|
3861
|
-
}
|
|
3862
|
-
return tasks;
|
|
3863
|
-
} finally {
|
|
3864
|
-
db2.close();
|
|
3865
|
-
}
|
|
3866
|
-
}
|
|
3867
|
-
function taskToScenarioInput(task, projectId) {
|
|
3868
|
-
const tags = JSON.parse(task.tags || "[]");
|
|
3869
|
-
const priority = ["low", "medium", "high", "critical"].includes(task.priority) ? task.priority : "medium";
|
|
3870
|
-
const steps = [];
|
|
3871
|
-
if (task.description) {
|
|
3872
|
-
const lines = task.description.split(`
|
|
3873
|
-
`);
|
|
3874
|
-
for (const line of lines) {
|
|
3875
|
-
const match = line.match(/^\s*\d+[\.\)]\s*(.+)/);
|
|
3876
|
-
if (match?.[1]) {
|
|
3877
|
-
steps.push(match[1].trim());
|
|
3878
|
-
}
|
|
3879
|
-
}
|
|
3880
|
-
}
|
|
3881
|
-
return {
|
|
3882
|
-
name: task.title.replace(/^(OPE\d+-\d+|[A-Z]+-\d+):\s*/, ""),
|
|
3883
|
-
description: task.description || task.title,
|
|
3884
|
-
steps,
|
|
3885
|
-
tags,
|
|
3886
|
-
priority,
|
|
3887
|
-
projectId,
|
|
3888
|
-
metadata: { todosTaskId: task.id, todosShortId: task.short_id }
|
|
3889
|
-
};
|
|
3890
|
-
}
|
|
3891
|
-
function importFromTodos(options = {}) {
|
|
3892
|
-
const tasks = pullTasks({
|
|
3893
|
-
projectName: options.projectName,
|
|
3894
|
-
tags: options.tags ?? ["qa", "test", "testing"],
|
|
3895
|
-
priority: options.priority
|
|
3896
|
-
});
|
|
3897
|
-
const existing = listScenarios({ projectId: options.projectId });
|
|
3898
|
-
const existingTodoIds = new Set(existing.filter((s) => s.metadata?.todosTaskId).map((s) => s.metadata.todosTaskId));
|
|
3899
|
-
let imported = 0;
|
|
3900
|
-
let skipped = 0;
|
|
3901
|
-
for (const task of tasks) {
|
|
3902
|
-
if (existingTodoIds.has(task.id)) {
|
|
3903
|
-
skipped++;
|
|
3904
|
-
continue;
|
|
3905
|
-
}
|
|
3906
|
-
const input = taskToScenarioInput(task, options.projectId);
|
|
3907
|
-
createScenario(input);
|
|
3908
|
-
imported++;
|
|
3909
|
-
}
|
|
3910
|
-
return { imported, skipped };
|
|
3911
|
-
}
|
|
3912
|
-
function markTodoDone(taskId) {
|
|
3913
|
-
const dbPath = resolveTodosDbPath();
|
|
3914
|
-
if (!existsSync4(dbPath))
|
|
3915
|
-
return false;
|
|
3916
|
-
const db2 = new Database2(dbPath);
|
|
3917
|
-
try {
|
|
3918
|
-
const task = db2.query("SELECT id, version FROM tasks WHERE id LIKE ? || '%'").get(taskId);
|
|
3919
|
-
if (!task)
|
|
3920
|
-
return false;
|
|
3921
|
-
db2.query("UPDATE tasks SET status = 'completed', completed_at = datetime('now'), version = version + 1, updated_at = datetime('now') WHERE id = ? AND version = ?").run(task.id, task.version);
|
|
3922
|
-
return true;
|
|
3923
|
-
} finally {
|
|
3924
|
-
db2.close();
|
|
3925
|
-
}
|
|
3926
|
-
}
|
|
3927
4025
|
// src/lib/scheduler.ts
|
|
3928
4026
|
init_types();
|
|
3929
4027
|
function parseCronField(field, min, max) {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Scenario } from "../types/index.js";
|
|
2
|
+
export interface FileMapping {
|
|
3
|
+
/** Glob pattern for source files (supports * and **) */
|
|
4
|
+
glob: string;
|
|
5
|
+
/** Tags of scenarios to run when this glob matches */
|
|
6
|
+
tags: string[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Given a set of changed file paths and all scenarios, return the scenarios
|
|
10
|
+
* relevant to those changes.
|
|
11
|
+
*
|
|
12
|
+
* Matching strategies (all applied, union of results):
|
|
13
|
+
* 1. Explicit glob → tag mappings: if a file matches a glob, scenarios with
|
|
14
|
+
* any of the mapped tags are included.
|
|
15
|
+
* 2. targetPath keyword: path segments of scenario.targetPath matched against
|
|
16
|
+
* file path components.
|
|
17
|
+
* 3. Tag keywords: scenario tags matched as substrings of file paths (>2 chars).
|
|
18
|
+
* 4. Name keywords: words in the scenario name matched against file paths (>3 chars).
|
|
19
|
+
*
|
|
20
|
+
* If filePaths is empty, all scenarios are returned (run everything).
|
|
21
|
+
*/
|
|
22
|
+
export declare function matchFilesToScenarios(filePaths: string[], scenarios: Scenario[], mappings?: FileMapping[]): Scenario[];
|
|
23
|
+
//# sourceMappingURL=affected.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"affected.d.ts","sourceRoot":"","sources":["../../src/lib/affected.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAElD,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAeD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,MAAM,EAAE,EACnB,SAAS,EAAE,QAAQ,EAAE,EACrB,QAAQ,GAAE,WAAW,EAAO,GAC3B,QAAQ,EAAE,CA+DZ"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Run, Result, Scenario } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Auto-create a todo task for each failed scenario in a run.
|
|
4
|
+
* Uses the todos DB directly (like todos-connector.ts does).
|
|
5
|
+
* No-op if TODOS_DB_PATH is not set and ~/.todos/todos.db does not exist.
|
|
6
|
+
* Controlled by TESTERS_TODOS_PROJECT_ID env var (which project to create tasks in).
|
|
7
|
+
*/
|
|
8
|
+
export declare function createFailureTasks(run: Run, failedResults: Result[], scenarios: Scenario[]): Promise<{
|
|
9
|
+
created: number;
|
|
10
|
+
skipped: number;
|
|
11
|
+
}>;
|
|
12
|
+
/**
|
|
13
|
+
* Post a failure notification to a conversations space.
|
|
14
|
+
* Controlled by:
|
|
15
|
+
* TESTERS_CONVERSATIONS_URL — base URL of the conversations service
|
|
16
|
+
* TESTERS_CONVERSATIONS_SPACE — space name/slug to post to
|
|
17
|
+
* No-op if either env var is missing.
|
|
18
|
+
*/
|
|
19
|
+
export declare function notifyFailureToConversations(run: Run, failedResults: Result[], scenarios: Scenario[]): Promise<void>;
|
|
20
|
+
//# sourceMappingURL=failure-pipeline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failure-pipeline.d.ts","sourceRoot":"","sources":["../../src/lib/failure-pipeline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAK/D;;;;;GAKG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,GAAG,EACR,aAAa,EAAE,MAAM,EAAE,EACvB,SAAS,EAAE,QAAQ,EAAE,GACpB,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAyE/C;AAID;;;;;;GAMG;AACH,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,GAAG,EACR,aAAa,EAAE,MAAM,EAAE,EACvB,SAAS,EAAE,QAAQ,EAAE,GACpB,OAAO,CAAC,IAAI,CAAC,CAwCf"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FileMapping } from "./affected.js";
|
|
2
|
+
import type { RunOptions } from "./runner.js";
|
|
3
|
+
export interface GitWatchOptions extends Omit<RunOptions, "url"> {
|
|
4
|
+
url: string;
|
|
5
|
+
dir?: string;
|
|
6
|
+
pollIntervalMs?: number;
|
|
7
|
+
mappings?: FileMapping[];
|
|
8
|
+
projectId?: string;
|
|
9
|
+
tags?: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Poll git for new commits and run affected scenarios when changes land.
|
|
13
|
+
* Designed for use in CI or as a long-running watcher.
|
|
14
|
+
*/
|
|
15
|
+
export declare function startGitWatcher(options: GitWatchOptions): Promise<void>;
|
|
16
|
+
//# sourceMappingURL=git-watch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-watch.d.ts","sourceRoot":"","sources":["../../src/lib/git-watch.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC;IAC9D,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAwBD;;;GAGG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAiF7E"}
|
package/dist/lib/runner.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/lib/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAc/D,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,YAAY,GAAG,YAAY,CAAC;CACtC;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EACA,gBAAgB,GAChB,eAAe,GACf,eAAe,GACf,gBAAgB,GAChB,qBAAqB,GACrB,cAAc,GACd,gBAAgB,GAChB,kBAAkB,GAClB,eAAe,GACf,0BAA0B,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AAIxD,wBAAgB,UAAU,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAEzD;AA+BD,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,MAAM,CAAC,CAiHjB;AAED,wBAAsB,QAAQ,CAC5B,SAAS,EAAE,QAAQ,EAAE,EACrB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAwI1C;AAED,wBAAsB,WAAW,CAC/B,OAAO,EAAE,UAAU,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GACnF,OAAO,CAAC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAuB1C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,OAAO,EAAE,UAAU,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GACnF;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAmF1C"}
|