@flowriki/agent 0.1.0-beta.42 → 0.1.0-beta.44

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/cli.js CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  helperRunner,
15
15
  mirrorCheckTargets,
16
16
  openRunSession,
17
+ parseGherkin,
17
18
  prepareBrowsers,
18
19
  replayCase,
19
20
  replayFlow,
@@ -22,7 +23,7 @@ import {
22
23
  runtimeValue,
23
24
  start,
24
25
  status
25
- } from "./chunk-FKD5OAUV.js";
26
+ } from "./chunk-E24RRF2Q.js";
26
27
 
27
28
  // src/cli.ts
28
29
  import { parseArgs } from "util";
@@ -218,6 +219,31 @@ var ServiceClient = class {
218
219
  return this.call(`/api/agent/library?${query}`);
219
220
  }
220
221
  /** Test cases written, edited or linked in one commit; duplicates skipped. */
222
+ importPart(projectId, draftId, part) {
223
+ return this.call(
224
+ `/api/agent/import-drafts/${encodeURIComponent(draftId)}?projectId=${encodeURIComponent(projectId)}&part=${part}`
225
+ );
226
+ }
227
+ saveImportCases(projectId, draftId, body) {
228
+ return this.call(
229
+ `/api/agent/import-drafts/${encodeURIComponent(draftId)}/cases?projectId=${encodeURIComponent(projectId)}`,
230
+ { method: "POST", body: JSON.stringify(body) }
231
+ );
232
+ }
233
+ readCases(projectId, refs) {
234
+ const ids = refs.filter((r) => /^tc_/.test(r.trim()));
235
+ const names = refs.filter((r) => !/^tc_/.test(r.trim()));
236
+ const q = new URLSearchParams({ projectId, ...ids.length ? { ids: ids.join(",") } : {}, ...names.length ? { names: names.join("|") } : {} });
237
+ return this.call(`/api/agent/cases/read?${q}`);
238
+ }
239
+ /** Test cases written down — business steps, no flows yet — on Claude's
240
+ * working branch. */
241
+ writeCases(projectId, groups, changeId) {
242
+ return this.call(
243
+ `/api/agent/cases/batch?projectId=${encodeURIComponent(projectId)}`,
244
+ { method: "POST", body: JSON.stringify({ groups, ...changeId ? { changeId } : {} }) }
245
+ );
246
+ }
221
247
  saveLibrary(projectId, input) {
222
248
  return this.call(`/api/agent/library?projectId=${encodeURIComponent(projectId)}`, { method: "POST", body: JSON.stringify(input) });
223
249
  }
@@ -839,6 +865,11 @@ THE MODEL \u2014 get this right before recording anything:
839
865
  - A TEST CASE is the test. It strings flows together as rows (in the same
840
866
  browser, or a new one for a second person) and switches on the checks it is
841
867
  about. Build it with preview_case, then save_case \u2014 which runs it first.
868
+ - A test case can be WRITTEN BEFORE IT IS AUTOMATED: business steps (Given /
869
+ When / Then) and no flows \u2014 imported from a spreadsheet, or written with
870
+ write_test_cases. To automate one, record the flows its steps need, then
871
+ save_case with the SAME name in the SAME folder: that fills it, and its
872
+ business steps stay. Its Then lines are the checks to switch on.
842
873
  - SIGN-IN is shared setup: a flow or case that needs somebody signed in uses a
843
874
  sign-in profile from the catalog (start_flow's signIn), never typed login steps.
844
875
  None yet? Make it: record the login as a flow that ends with a check proving
@@ -874,6 +905,16 @@ as learned. Keep what you learn with write_knowledge (features/<name>.md,
874
905
  memory/decisions.md, a session summary), on Claude's branch for review \u2014
875
906
  never a password, key or token.
876
907
 
908
+ CONVERTING A SPREADSHEET: when asked to convert an import (imp_\u2026), read
909
+ every part with read_import_file, then save_import_cases in batches. Group
910
+ cases into features by product area (a sheet or section usually is one). Each
911
+ case is one scenario: name it after what it proves. Steps: preconditions and
912
+ setup as Given; ONE action per When / And \u2014 split steps that do several
913
+ things; each expected result as Then / And, after the action it follows.
914
+ Keep every case and every step's meaning, and its id from the file in row;
915
+ never invent a step or drop one. Rows that are not test cases (titles,
916
+ notes, empty) are left out. Say in note what you could not convert.
917
+
877
918
  HOW TO WORK:
878
919
  1. Call project_catalog. Reuse what exists \u2014 published, and on Claude's working
879
920
  branch \u2014 rather than writing it again.
@@ -1293,6 +1334,153 @@ ${page}`);
1293
1334
  return text(out);
1294
1335
  }
1295
1336
  },
1337
+ {
1338
+ name: "read_import_file",
1339
+ title: "Read a spreadsheet waiting to be converted",
1340
+ description: "Read one part of a spreadsheet the person is importing (its id is imp_\u2026): every sheet's rows, each as a JSON array of its cells, numbered as the spreadsheet numbers them. Part 1 says how many parts there are; read every one.",
1341
+ inputSchema: {
1342
+ type: "object",
1343
+ properties: { projectId: { type: "string" }, file: { type: "string", description: "The import's id, imp_\u2026" }, part: { type: "number", description: "Which part, from 1." } },
1344
+ required: ["file", "part"],
1345
+ additionalProperties: false
1346
+ },
1347
+ annotations: { readOnlyHint: true },
1348
+ run: async (args) => {
1349
+ const projectId = await projectFor(args["projectId"]);
1350
+ const got = await service.importPart(projectId, str(args["file"]) ?? "", Number(args["part"] ?? 1));
1351
+ return text(`${got.file} \u2014 part ${got.part} of ${got.of}${got.part < got.of ? " (read the rest too)" : " (the last)"}
1352
+
1353
+ ${got.text}`);
1354
+ }
1355
+ },
1356
+ {
1357
+ name: "save_import_cases",
1358
+ title: "Save the test cases a spreadsheet becomes",
1359
+ description: "Save test cases converted from a spreadsheet being imported (its id is imp_\u2026), in batches as you go: features, each with cases \u2014 name (the scenario it proves), steps as Given / When / Then / And lines, row (its id or row in the file), description (optional). They are shown to the person to import; nothing is written to the project until they do. done: true with the last batch; note: anything you could not convert, and why. restart: true replaces what was saved before.",
1360
+ inputSchema: {
1361
+ type: "object",
1362
+ properties: {
1363
+ projectId: { type: "string" },
1364
+ file: { type: "string", description: "The import's id, imp_\u2026" },
1365
+ features: {
1366
+ type: "array",
1367
+ items: {
1368
+ type: "object",
1369
+ properties: {
1370
+ feature: { type: "string" },
1371
+ cases: {
1372
+ type: "array",
1373
+ items: {
1374
+ type: "object",
1375
+ properties: { name: { type: "string" }, description: { type: "string" }, steps: { type: "array", items: { type: "string" } }, row: { type: "string" } },
1376
+ required: ["name", "steps"],
1377
+ additionalProperties: false
1378
+ }
1379
+ }
1380
+ },
1381
+ required: ["feature", "cases"],
1382
+ additionalProperties: false
1383
+ }
1384
+ },
1385
+ done: { type: "boolean" },
1386
+ note: { type: "string" },
1387
+ restart: { type: "boolean" }
1388
+ },
1389
+ required: ["file", "features"],
1390
+ additionalProperties: false
1391
+ },
1392
+ run: async (args) => {
1393
+ const projectId = await projectFor(args["projectId"]);
1394
+ const features = Array.isArray(args["features"]) ? args["features"] : [];
1395
+ const saved = await service.saveImportCases(projectId, str(args["file"]) ?? "", {
1396
+ features,
1397
+ ...args["done"] === true ? { done: true } : {},
1398
+ ...typeof args["note"] === "string" ? { note: args["note"] } : {},
1399
+ ...args["restart"] === true ? { restart: true } : {}
1400
+ });
1401
+ return text(`Saved ${saved.added} more \u2014 ${saved.total} test cases so far.${saved.done ? " Done: the person can now look them over and import them." : " Keep going, and pass done: true with the last batch."}`);
1402
+ }
1403
+ },
1404
+ {
1405
+ name: "read_test_cases",
1406
+ title: "Read test cases",
1407
+ description: "Read test cases by id (tc_\u2026) or exact name: their feature, business steps (Given / When / Then), whether they are automated, and the flows they run. Read a case's steps before automating it.",
1408
+ inputSchema: {
1409
+ type: "object",
1410
+ properties: { projectId: { type: "string" }, cases: { type: "array", items: { type: "string" }, description: "Ids or names." } },
1411
+ required: ["cases"],
1412
+ additionalProperties: false
1413
+ },
1414
+ annotations: { readOnlyHint: true },
1415
+ run: async (args) => {
1416
+ const projectId = await projectFor(args["projectId"]);
1417
+ const refs = Array.isArray(args["cases"]) ? args["cases"].map(String) : [];
1418
+ const { cases, missing } = await service.readCases(projectId, refs);
1419
+ const parts = cases.map((c) => [
1420
+ `${c.folder} \u203A ${c.name} (${c.id}) \u2014 ${c.automated ? `automated, rows: ${c.rows.join(" \u2192 ")}` : "not automated yet"}`,
1421
+ ...c.description ? [` ${c.description}`] : [],
1422
+ ...c.businessSteps.length ? c.businessSteps.map((s) => ` ${s.keyword} ${s.text}`) : [" (no business steps written)"]
1423
+ ].join("\n"));
1424
+ if (missing.length) parts.push(`Not found: ${missing.join(", ")} \u2014 project_catalog lists the test cases.`);
1425
+ return text(parts.join("\n\n") || "No test cases found.");
1426
+ }
1427
+ },
1428
+ {
1429
+ name: "write_test_cases",
1430
+ title: "Write test cases down, before automating them",
1431
+ description: "Write test cases as business steps \u2014 Given / When / Then \u2014 with no flows yet, grouped by feature (the case folder), on Claude's working branch for review. They show as not automated until a test case with the same name is saved into the same folder with save_case, which keeps these steps. A case its folder already has is skipped. Use it when asked to write, plan or list test cases; record and save_case only when asked to automate.",
1432
+ inputSchema: {
1433
+ type: "object",
1434
+ properties: {
1435
+ projectId: { type: "string" },
1436
+ features: {
1437
+ type: "array",
1438
+ description: 'One entry per feature (case folder), e.g. "Account \u2013 Sign up".',
1439
+ items: {
1440
+ type: "object",
1441
+ properties: {
1442
+ feature: { type: "string" },
1443
+ cases: {
1444
+ type: "array",
1445
+ items: {
1446
+ type: "object",
1447
+ properties: {
1448
+ name: { type: "string", description: 'The scenario it proves, e.g. "A taken email is refused".' },
1449
+ description: { type: "string" },
1450
+ steps: { type: "array", description: `Given / When / Then lines, e.g. "Given the sign-up form is open", "When I sign up with a taken email", "Then I see 'That email is taken'". And / But continue the line above.`, items: { type: "string" } }
1451
+ },
1452
+ required: ["name", "steps"],
1453
+ additionalProperties: false
1454
+ }
1455
+ }
1456
+ },
1457
+ required: ["feature", "cases"],
1458
+ additionalProperties: false
1459
+ }
1460
+ },
1461
+ branch: { type: "string", description: "Optional: the id of one of Claude's open branches to write to (list_my_branches). Default: the working branch." }
1462
+ },
1463
+ required: ["features"],
1464
+ additionalProperties: false
1465
+ },
1466
+ run: async (args) => {
1467
+ const projectId = await projectFor(args["projectId"]);
1468
+ const features = Array.isArray(args["features"]) ? args["features"] : [];
1469
+ const groups = features.map((f) => ({
1470
+ folder: String(f.feature ?? "").trim(),
1471
+ cases: (Array.isArray(f.cases) ? f.cases : []).map((c) => {
1472
+ const lines = Array.isArray(c.steps) ? c.steps.map(String) : [];
1473
+ const steps = parseGherkin(lines.join("\n"));
1474
+ if (!steps) throw new Error(`the steps of "${String(c.name ?? "")}" must each start with Given, When, Then, And or But`);
1475
+ return { name: String(c.name ?? "").trim(), ...typeof c.description === "string" ? { description: c.description } : {}, businessSteps: steps };
1476
+ })
1477
+ }));
1478
+ const done = await service.writeCases(projectId, groups, str(args["branch"]));
1479
+ catalogs.delete(projectId);
1480
+ const skipped = done.skipped.length ? ` Skipped ${done.skipped.length} the folder already had: ${done.skipped.map((s) => `"${s.name}"`).join(", ")}.` : "";
1481
+ return text(`Wrote ${done.created.length} test case${done.created.length === 1 ? "" : "s"}, not automated yet, on Claude's working branch ${done.branch}.${skipped}`);
1482
+ }
1483
+ },
1296
1484
  {
1297
1485
  name: "create_sign_in",
1298
1486
  title: "Make a sign-in profile",
package/dist/engine.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  resolveCheckouts,
10
10
  start,
11
11
  status
12
- } from "./chunk-FKD5OAUV.js";
12
+ } from "./chunk-E24RRF2Q.js";
13
13
  export {
14
14
  AGENT_VERSION,
15
15
  browsersReady,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowriki/agent",
3
- "version": "0.1.0-beta.42",
3
+ "version": "0.1.0-beta.44",
4
4
  "description": "Run Flowriki UI tests on your own computer. Connects this machine to your Flowriki account.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",