@flowriki/agent 0.1.0-beta.60 → 0.1.0-beta.63

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.
@@ -656,6 +656,9 @@ import { z as z7 } from "zod";
656
656
  import { z as z8 } from "zod";
657
657
  import { z as z9 } from "zod";
658
658
  import { z as z10 } from "zod";
659
+ import { z as z11 } from "zod";
660
+ import { z as z12 } from "zod";
661
+ import { z as z13 } from "zod";
659
662
  var EngineSchema = z3.enum(["chromium", "firefox", "webkit", "android", "ios"]);
660
663
  var CapabilitySchema = z3.object({
661
664
  id: z3.string(),
@@ -1820,6 +1823,49 @@ function decodeEntities(text) {
1820
1823
  return out;
1821
1824
  }
1822
1825
  var nameKey = (name) => decodeEntities(name).toLowerCase().replace(/&/g, " and ").replace(/[^\p{L}\p{N}]+/gu, " ").trim();
1826
+ var PLATFORM_PART = "(web|android|ios)";
1827
+ var LEARNED_PATH = new RegExp(`^(explore\\/${PLATFORM_PART}\\.md|memory\\/sessions\\/[a-z0-9][a-z0-9-]{0,120}\\.md)$`);
1828
+ var isLearnedPath = (path8) => LEARNED_PATH.test(path8);
1829
+ var LearnedSchema = z11.object({
1830
+ id: z11.string().regex(/^lrn_[a-z0-9_-]+$/),
1831
+ projectId: z11.string().min(1),
1832
+ /** explore: the app as the assistant walked it, one per platform.
1833
+ * session: what one job learned. */
1834
+ kind: z11.enum(["explore", "session"]),
1835
+ /** Its page path — what it is read and searched by. */
1836
+ path: z11.string().refine(isLearnedPath, "not a learned page"),
1837
+ platform: z11.enum(["web", "android", "ios"]).optional(),
1838
+ content: z11.string().max(4e5),
1839
+ by: z11.string().max(200).default(""),
1840
+ /** new until a person acts on it: promoted into a written page, or
1841
+ * dismissed — kept out of the assistant's brief and search. */
1842
+ status: z11.enum(["new", "promoted", "dismissed"]).default("new"),
1843
+ createdAt: z11.string(),
1844
+ updatedAt: z11.string()
1845
+ }).strict();
1846
+ var RUN_MEMORY_WEEKS = 5;
1847
+ var Bucket = z12.object({
1848
+ week: z12.number().int(),
1849
+ runs: z12.number().int().nonnegative(),
1850
+ steps: z12.record(z12.string(), z12.unknown()),
1851
+ screens: z12.record(z12.string(), z12.unknown()),
1852
+ links: z12.record(z12.string(), z12.unknown())
1853
+ }).strict();
1854
+ var RunMemorySchema = z12.object({
1855
+ id: z12.string().regex(/^rm_/),
1856
+ projectId: z12.string().min(1),
1857
+ /** Filled from the runs already kept, once — after that, run by run. */
1858
+ backfilled: z12.boolean().default(false),
1859
+ buckets: z12.array(Bucket).max(RUN_MEMORY_WEEKS + 1),
1860
+ updatedAt: z12.number()
1861
+ }).strict();
1862
+ var WhatToDoSchema = z13.object({
1863
+ id: z13.string().regex(/^wtd_/),
1864
+ projectId: z13.string().min(1),
1865
+ /** Step ids skipped — "we do this differently". */
1866
+ skipped: z13.array(z13.string().min(1).max(80)).max(100),
1867
+ updatedAt: z13.string()
1868
+ }).strict();
1823
1869
 
1824
1870
  // ../engine-appium/dist/index.js
1825
1871
  import path5 from "path";
@@ -3765,6 +3811,7 @@ async function performSignIn(session, step, opts) {
3765
3811
  }
3766
3812
  return { ok: true, label, detail: "" };
3767
3813
  }
3814
+ var MOVES = /* @__PURE__ */ new Set(["click", "tap", "longPress", "press", "back", "navigate", "launchApp", "resetApp", "signIn", "signOut", "useHelper"]);
3768
3815
  async function runMobileSteps(session, steps, opts) {
3769
3816
  const results = [];
3770
3817
  let status = "passed";
@@ -3830,6 +3877,10 @@ async function runMobileSteps(session, steps, opts) {
3830
3877
  result.failureKind = e.kind ?? failureKind(e);
3831
3878
  result.detail = firstLine(e);
3832
3879
  }
3880
+ if (result.status === "passed" && MOVES.has(step.type)) {
3881
+ const at = await session.currentScreenName().catch(() => "");
3882
+ if (at) result.where = at;
3883
+ }
3833
3884
  const shots = opts.screenshots ?? "on-failure";
3834
3885
  if (opts.evidenceDir && (shots === "always" || shots === "on-failure" && result.status === "failed")) {
3835
3886
  const file = `step-${String((opts.stepOffset ?? 0) + results.length + 1).padStart(2, "0")}.png`;
@@ -68,7 +68,7 @@ import {
68
68
  textIndependent,
69
69
  validateActors,
70
70
  webCloudOf
71
- } from "./chunk-WQ22OUIQ.js";
71
+ } from "./chunk-IFUODU4W.js";
72
72
 
73
73
  // src/engine/machine.ts
74
74
  import fs from "fs";
@@ -76,7 +76,7 @@ import os from "os";
76
76
  import path from "path";
77
77
  import { createRequire } from "module";
78
78
  import { spawnSync } from "child_process";
79
- var AGENT_VERSION = "0.1.0-beta.60";
79
+ var AGENT_VERSION = "0.1.0-beta.63";
80
80
  async function prepareBrowsers() {
81
81
  if (browsersReady()) {
82
82
  return { ok: true, already: true, message: "chromium is already installed" };
@@ -136,7 +136,7 @@ async function checkMachine() {
136
136
  }
137
137
  } catch {
138
138
  }
139
- const { mobileDoctor: mobileDoctor2 } = await import("./dist-IBNVN2BD.js");
139
+ const { mobileDoctor: mobileDoctor2 } = await import("./dist-Z67YCHL5.js");
140
140
  return {
141
141
  agentVersion: AGENT_VERSION,
142
142
  node: process.versions.node,
@@ -3139,6 +3139,15 @@ async function checkAssertion(page, kind, value, timeoutMs = 3e3, assertion) {
3139
3139
  }
3140
3140
  fail(`expected ${sentence}`, last);
3141
3141
  }
3142
+ function whereOn(url) {
3143
+ try {
3144
+ const u = new URL(url);
3145
+ if (u.protocol === "about:") return "";
3146
+ return `${u.pathname}${u.hash.startsWith("#/") ? u.hash.split("?")[0] : ""}`;
3147
+ } catch {
3148
+ return "";
3149
+ }
3150
+ }
3142
3151
  async function applyAction(loc, step, value, ctx = {}) {
3143
3152
  switch (step.type) {
3144
3153
  case "fill":
@@ -3620,6 +3629,10 @@ async function runStepsIn(session, steps, opts) {
3620
3629
  result.foundBy = foundBy;
3621
3630
  }
3622
3631
  });
3632
+ try {
3633
+ result.where = whereOn(actionPage.url());
3634
+ } catch {
3635
+ }
3623
3636
  for (const a of step.proposals ?? []) {
3624
3637
  if (a.enabled === false) continue;
3625
3638
  const label = describeAssertion(a, step.name);
@@ -10169,7 +10182,8 @@ var ASK_MODE_TOOLS = [
10169
10182
  "knowledge_search",
10170
10183
  "knowledge_read",
10171
10184
  "read_test_cases",
10172
- "list_my_branches"
10185
+ "list_my_branches",
10186
+ "run_insights"
10173
10187
  ];
10174
10188
  function buildAskArgs(opts) {
10175
10189
  const ask = opts.mode === "ask";
@@ -10177,7 +10191,7 @@ function buildAskArgs(opts) {
10177
10191
  const brief = opts.brief?.trim();
10178
10192
  const guidance = [
10179
10193
  `You are the Flowriki assistant for the project "${opts.projectName}" (id ${opts.projectId}). Use the Flowriki tools, and pass projectId "${opts.projectId}" wherever a tool takes one. ` + (brief ? "The project brief below is this project's memory for this job: read it first." : "Start with project_brief: it is this project's memory."),
10180
- ask ? "ASK MODE: answer the question; change nothing. Cite what you rely on as [[testCase:<its id, tc_\u2026>]], [[knowledge:features/checkout.md]] or [[file:path/in/repo.ts]]." : "DO MODE: do what is asked, on Claude's working branch, for a person to review. Ask in the chat and wait before publishing, deleting, or sending anything to another system. Work quietly: the person sees what you build and your final answer, so keep words between tool calls to a minimum and end with a short result \u2014 what was saved, and anything that needs them. When you saved something, write a short summary with write_knowledge (memory/sessions/<date>-<topic>.md, append) so the next session knows.",
10194
+ ask ? "ASK MODE: answer the question; change nothing. Cite what you rely on as [[testCase:<its id, tc_\u2026>]], [[knowledge:features/checkout.md]] or [[file:path/in/repo.ts]]." : "DO MODE: do what is asked, on Claude's working branch, for a person to review. Ask in the chat and wait before publishing, deleting, or sending anything to another system. Work quietly: the person sees what you build and your final answer, so keep words between tool calls to a minimum and end with a short result \u2014 what was saved, and anything that needs them. When you learned something about the app the next job should know (a screen, a rule, a quirk, what failed and why), write it with write_knowledge (memory/sessions/<date>-<topic>.md, append) \u2014 Flowriki keeps it and gives it to the next job.",
10181
10195
  "Use only the tools you have: a tool is used by calling it, not by writing the call out as text. If Flowriki's tools are missing, say so in one sentence and stop.",
10182
10196
  folders.length ? `The project's code is in ${folders.join(" and ")}. Read it with Read, Grep and Glob when it helps; you cannot change it.` : "",
10183
10197
  brief ? `
package/dist/cli.js CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  runtimeValue,
25
25
  start,
26
26
  status
27
- } from "./chunk-BYFYSV5H.js";
27
+ } from "./chunk-QIZZ4HZ7.js";
28
28
  import {
29
29
  CLOUD_PROVIDERS,
30
30
  CLOUD_PROVIDER_NAME,
@@ -52,7 +52,7 @@ import {
52
52
  slugify,
53
53
  stepRefusedOn,
54
54
  targetFor
55
- } from "./chunk-WQ22OUIQ.js";
55
+ } from "./chunk-IFUODU4W.js";
56
56
 
57
57
  // src/cli.ts
58
58
  import { parseArgs } from "util";
@@ -266,6 +266,11 @@ var ServiceClient = class {
266
266
  if (!ASK_ID) return;
267
267
  await this.call(`/api/agent/jobs/draft?projectId=${encodeURIComponent(projectId)}`, { method: "POST", body: JSON.stringify({ draft }) });
268
268
  }
269
+ /** What runs taught us: failing steps, and the app's map. */
270
+ insights(projectId, document) {
271
+ const q = new URLSearchParams({ projectId, ...document ? { document } : {} });
272
+ return this.call(`/api/agent/insights?${q}`);
273
+ }
269
274
  /** The test case being built, as it stands, for the dashboard's job. */
270
275
  async reportCase(projectId, live) {
271
276
  if (!ASK_ID) return;
@@ -428,6 +433,22 @@ var STEP_TIMEOUT_MS = 1e4;
428
433
  var SECRET_WORDS = /password|passwd|pass[-_\s]?code|pwd|secret|otp|one[-_\s]?time[-_\s]?(code|pass)|security[-_\s]?(code|answer)|cvv|cvc|\bpin\b/i;
429
434
  var TYPED = "typed_";
430
435
  var withoutTyped = (steps) => steps.map((st) => st.secretRef?.startsWith(TYPED) ? (({ secretRef: _, ...rest }) => rest)(st) : st);
436
+ var SIGN_UP = /\b(sign[\s-]?up|register|registration|create (an |your )?account|join)\b/i;
437
+ function shapeOf(draft, typed) {
438
+ const fills = draft.steps.filter((st) => st.type === "fill" && !st.disabled);
439
+ const values = {};
440
+ for (const st of fills) {
441
+ const value = st.secret ? st.secretRef ? typed.get(st.secretRef) : void 0 : st.value;
442
+ if (st.name && typeof value === "string") values[st.name] = value;
443
+ }
444
+ const base = { flowName: draft.name, platform: draft.platform ?? "web", values };
445
+ const secret = fills.some((st) => st.secret);
446
+ const fixed = Object.values(values).every((v) => !v.includes("{{"));
447
+ if (!secret || !fixed || Object.keys(values).length !== fills.length) return { kind: null, ...base };
448
+ if (SIGN_UP.test(draft.name) || fills.length > 3) return { kind: "signup", ...base };
449
+ if (draft.signIn || draft.after?.length) return { kind: null, ...base };
450
+ return { kind: "login", ...base };
451
+ }
431
452
  var ACTIONS = [
432
453
  "click",
433
454
  "dblclick",
@@ -751,6 +772,14 @@ ${view}`;
751
772
  }
752
773
  if (this.liveCase) await this.sendCaseReport();
753
774
  }
775
+ /** What the last saved flow was — a login sign-in can be made from, a
776
+ * sign-up — with the values it typed. Taken once. */
777
+ lastSaved = null;
778
+ takeLastSaved() {
779
+ const saved = this.lastSaved;
780
+ this.lastSaved = null;
781
+ return saved;
782
+ }
754
783
  // ── The test case a recording is for ────────────────────────────────
755
784
  //
756
785
  // A PERSON THINKS IN TEST CASES. The flows are how the assistant makes one
@@ -1176,6 +1205,7 @@ Fix the step that failed (remove_step, then act again) and save again \u2014 or
1176
1205
  const note = others.length ? `
1177
1206
  Claude has ${others.length} other open branch${others.length === 1 ? "" : "es"} on this project \u2014 gather_branches puts them into this one, for one review.` : "";
1178
1207
  const after = draft.after?.length ? ` It starts after ${draft.after.map((a) => `"${a.name}"`).join(", ")}.` : "";
1208
+ this.lastSaved = { projectId: draft.projectId, ...shapeOf(draft, this.typed) };
1179
1209
  this.dropDraft(draft.projectId, draft.name);
1180
1210
  const live = this.liveCase;
1181
1211
  if (live && live.projectId === draft.projectId) {
@@ -1490,12 +1520,36 @@ THE MODEL \u2014 get this right before recording anything:
1490
1520
  write_test_cases. To automate one, record the flows its steps need, then
1491
1521
  save_case with the SAME name in the SAME folder: that fills it, and its
1492
1522
  business steps stay. Its Then lines are the checks to switch on.
1493
- - SIGN-IN is shared setup: a flow or case that needs somebody signed in uses a
1494
- sign-in profile from the catalog (start_flow's signIn), never typed login steps.
1495
- None yet? Make it: record the login as a flow that ends with a check proving
1496
- it worked (the account menu, a heading, the URL), then create_sign_in from it
1497
- and create_account for each person who signs in (a customer, an admin). The
1498
- login flow types into its fields; the account supplies the values.
1523
+ - AUTHENTICATION IS YOURS, END TO END. The person records nothing by hand and
1524
+ is not asked to set sign-in up. A flow or case that needs somebody signed in
1525
+ starts signed in with a sign-in profile (start_flow's signIn) \u2014 never typed
1526
+ login steps. Use one from project_catalog made on the SAME platform, with an
1527
+ account for the role. None yet? Make it, without asking:
1528
+ 1. A test account, in this order: credentials the person gave you; a demo
1529
+ login the app itself shows; or SIGN UP a dedicated test user yourself \u2014 a
1530
+ fixed address for the role (e.g. flowriki.customer@ the domain of the
1531
+ project's mailbox helper) and a strong password you invent (it is sealed
1532
+ in the vault). Sign it up once; it is reused by every run.
1533
+ 2. Record the login as a flow on that platform that ends with a check proving
1534
+ it worked (the account menu, a heading, the URL), then create_sign_in from
1535
+ it \u2014 one per platform per role, e.g. "Customer sign-in" \u2014 and
1536
+ create_account with the test user's values.
1537
+ 3. Only what you cannot create yourself \u2014 an admin, a role only staff can
1538
+ grant, a mailbox when sign-up must verify an email and the catalog has no
1539
+ mailbox helper \u2014 is asked of the person: once, saying exactly what you
1540
+ need, while you carry on with everything else.
1541
+ - AUTHENTICATION IS ALSO A PRODUCT AREA TO TEST. Asked to test sign-up,
1542
+ sign-in, accounts or security, cover it as a tester would \u2014 each its own
1543
+ test case, in folders such as "Account \u2013 Sign up", "Account \u2013 Sign in":
1544
+ sign up (valid; an email already taken; a weak password; required fields
1545
+ missing), sign in (valid; wrong password; unknown email; empty fields),
1546
+ password reset (request it, set a new one through the mailbox helper),
1547
+ sign out (and Back cannot return to a signed-in page), a signed-in page
1548
+ opened while signed out goes to sign-in, a session survives a reload,
1549
+ lockout or throttling after repeated wrong passwords (stop at the limit plus
1550
+ one), and roles (a customer cannot reach an admin page). Test the app's own
1551
+ behaviour only: no attacks, no load, no other systems, no real person's
1552
+ account.
1499
1553
  - HELPERS (an inbox for email codes, an authenticator, SMS, an API call) are made
1500
1554
  with create_helper when the catalog has none that fits.
1501
1555
  - SEVERAL ELEMENTS WITH THE SAME NAME (two "Proceed to Checkout" buttons, twelve
@@ -1505,8 +1559,9 @@ THE MODEL \u2014 get this right before recording anything:
1505
1559
  - PASSWORDS AND KEYS never go into a flow or a reply. Typed into a password
1506
1560
  field, a value is kept as a secret and saved to the project's vault; given to
1507
1561
  create_account or create_helper, it is sealed there too and never shown back.
1508
- Only use credentials the person gave you or that the app itself shows for
1509
- testing (a demo login) \u2014 never guess or reuse anyone's real password.
1562
+ Only use credentials the person gave you, that the app itself shows for
1563
+ testing (a demo login), or of a test user you signed up yourself \u2014 never
1564
+ guess or reuse anyone's real password.
1510
1565
  - FOLDERS do not nest. Name each one after the product area and what is in it,
1511
1566
  the way a tester would look for it \u2014 "Account \u2013 Sign up", "Account \u2013 Sign in",
1512
1567
  "Ordering \u2013 Pizza builder", "Checkout \u2013 Payment". Flows (features) and the
@@ -1693,6 +1748,33 @@ function authoringTools(service, session) {
1693
1748
  const lines = session.caseLinesFor(spec.name);
1694
1749
  return lines ? { ...spec, businessSteps: lines } : spec;
1695
1750
  };
1751
+ const signInFrom = async (shape) => {
1752
+ if (!shape?.kind) return "";
1753
+ const NAME = { web: "Web", android: "Android", ios: "iOS" };
1754
+ if (shape.kind === "signup") {
1755
+ return `
1756
+
1757
+ That signed up a fixed test user. Next: record the login as that user on ${NAME[shape.platform]} (a flow that types its email or username and password, then checks it is signed in) \u2014 saving it sets sign-in up for the project.`;
1758
+ }
1759
+ try {
1760
+ const catalog = await catalogOf(shape.projectId, true);
1761
+ const same = catalog.signIns.find((si) => (si.platform ?? "web") === shape.platform);
1762
+ if (same) return `
1763
+
1764
+ ${NAME[shape.platform]} already has the sign-in "${same.name}" (profileId ${same.profileId}) \u2014 use it with start_flow signIn.`;
1765
+ const made = await service.createSignIn(shape.projectId, { flow: shape.flowName, name: `${NAME[shape.platform]} sign-in` });
1766
+ const values = Object.fromEntries(made.fields.flatMap((f) => shape.values[f.label] !== void 0 ? [[f.label, shape.values[f.label]]] : []));
1767
+ const account = await service.createAccount(shape.projectId, { signIn: made.profileId, name: "Test user", values });
1768
+ catalogs.delete(shape.projectId);
1769
+ return `
1770
+
1771
+ This is a login, so it is now the project's sign-in on ${NAME[shape.platform]}: "${made.name}" (profileId ${made.profileId}) with the account "${account.name}" (accountId ${account.accountId}; its secrets are in the vault). Every flow that needs somebody signed in starts with start_flow signIn {"profileId": "${made.profileId}", "accountId": "${account.accountId}"}.`;
1772
+ } catch (error) {
1773
+ return `
1774
+
1775
+ It looks like a login, but sign-in could not be set up from it (${error.message.split("\n")[0]}) \u2014 create_sign_in and create_account can do it.`;
1776
+ }
1777
+ };
1696
1778
  const tools = [
1697
1779
  {
1698
1780
  name: "list_projects",
@@ -1780,6 +1862,33 @@ Helpers:${c.helpers.length ? "" : " none \u2014 create_helper makes one"}`,
1780
1862
  ].filter(Boolean).join("\n"));
1781
1863
  }
1782
1864
  },
1865
+ {
1866
+ name: "run_insights",
1867
+ title: "What runs taught us",
1868
+ description: "What the project's runs of the last 30 days show: the steps that fail most often \u2014 how often, whether they also pass sometimes (unreliable rather than broken), the last error \u2014 and the app's map: each page (web) or screen (Android) runs reached, what was done there, and which step leads where. Read it before healing a test, planning new ones, or answering what the project knows. document: one flow or test case, by name or id.",
1869
+ inputSchema: { type: "object", properties: { projectId: { type: "string" }, document: { type: "string" } }, additionalProperties: false },
1870
+ annotations: { readOnlyHint: true },
1871
+ run: async (args) => {
1872
+ const projectId = await projectFor(args["projectId"]);
1873
+ const r = await service.insights(projectId, str(args["document"]));
1874
+ if (!r.runs) return text("No runs in the last 30 days \u2014 nothing learned from runs yet.");
1875
+ const lines = [`From the last ${r.runs} run${r.runs === 1 ? "" : "s"} (30 days).`];
1876
+ lines.push(r.failing.length ? "\nSteps that failed, most often first:" : "\nNo step failed.");
1877
+ for (const f of r.failing.slice(0, 30)) {
1878
+ lines.push(`- ${f.document} (${f.documentKind}): ${f.step} \u2014 failed ${f.failures} of ${f.runs}${f.flaky ? ", passes sometimes (unreliable)" : ""}${f.lastKind ? `; last: ${f.lastKind}` : ""}${f.lastDetail ? ` \u2014 ${f.lastDetail}` : ""}`);
1879
+ }
1880
+ for (const m of r.maps) {
1881
+ lines.push(`
1882
+ ${m.platform === "web" ? "Web pages" : "Android screens"} runs reached:`);
1883
+ for (const sc of m.screens.slice(0, 30)) lines.push(`- ${sc.where} (${sc.visits}\xD7)${sc.actions.length ? `: ${sc.actions.join(", ")}` : ""}`);
1884
+ if (m.links.length) {
1885
+ lines.push("Which leads where:");
1886
+ for (const l of m.links.slice(0, 40)) lines.push(`- ${l.from} \u2192 ${l.to} by ${l.via} (${l.count}\xD7)`);
1887
+ }
1888
+ }
1889
+ return text(lines.join("\n"));
1890
+ }
1891
+ },
1783
1892
  {
1784
1893
  name: "start_case",
1785
1894
  title: "Start building a test case",
@@ -1995,10 +2104,13 @@ ${page}`);
1995
2104
  },
1996
2105
  additionalProperties: false
1997
2106
  },
1998
- run: async (args) => text(await session.save(str(args["title"]), {
1999
- ...str(args["branch"]) ? { changeId: str(args["branch"]) } : {},
2000
- ...args["newBranch"] === true ? { newBranch: true } : {}
2001
- }, { ...args["force"] === true ? { force: true } : {} }))
2107
+ run: async (args) => {
2108
+ const saved = await session.save(str(args["title"]), {
2109
+ ...str(args["branch"]) ? { changeId: str(args["branch"]) } : {},
2110
+ ...args["newBranch"] === true ? { newBranch: true } : {}
2111
+ }, { ...args["force"] === true ? { force: true } : {} });
2112
+ return text(saved + await signInFrom(session.takeLastSaved()));
2113
+ }
2002
2114
  },
2003
2115
  {
2004
2116
  name: "list_my_branches",
@@ -2458,7 +2570,7 @@ ${pages.filter((p) => !shown.has(p.path)).map((p) => `- ${p.path}`).join("\n") |
2458
2570
  {
2459
2571
  name: "write_knowledge",
2460
2572
  title: "Write a knowledge page",
2461
- description: `Write what the project should remember, on Claude's working branch for review: product.md (About the product \u2014 the person's own description; write it only when they ask) and product/<web|android|ios>.md (what is particular to that platform's app), explore/<web|android|ios>.md (what you saw walking that platform: "# Explored: <Platform>", "Explored: <yyyy-mm-dd>", "Environment: <name>", "Screens: <n>" lines, then Screens, Journeys, Rules observed, Vocabulary, Suggested features), codebase/<folder>.md (what a linked folder holds \u2014 starting with "# <folder>", "Commit: <sha>" and "Learned: <yyyy-mm-dd>" lines, then routes, screens, forms and their validation, roles, APIs, flags; summarised, never copied code), features/<name>.md (screens, rules, quirks), memory/decisions.md (decisions and conventions), memory/sessions/<yyyy-mm-dd>-<topic>.md (what a job did). append: true adds to the end instead of replacing. Never a password, key or token \u2014 those are refused.`,
2573
+ description: `Write what the project should remember, on Claude's working branch for review: product.md (About the product \u2014 the person's own description; write it only when they ask) and product/<web|android|ios>.md (what is particular to that platform's app), explore/<web|android|ios>.md (what you saw walking that platform: "# Explored: <Platform>", "Explored: <yyyy-mm-dd>", "Environment: <name>", "Screens: <n>" lines, then Screens, Journeys, Rules observed, Vocabulary, Suggested features), codebase/<folder>.md (what a linked folder holds \u2014 starting with "# <folder>", "Commit: <sha>" and "Learned: <yyyy-mm-dd>" lines, then routes, screens, forms and their validation, roles, APIs, flags; summarised, never copied code), features/<name>.md (screens, rules, quirks), memory/decisions.md (decisions and conventions), memory/sessions/<yyyy-mm-dd>-<topic>.md (what a job did \u2014 what you learned about the app that the next job should know). explore/\u2026 and memory/sessions/\u2026 are KEPT BY FLOWRIKI at once \u2014 no branch, nothing to publish \u2014 and given to the next job; the rest go on Claude's working branch for review. append: true adds to the end instead of replacing. Never a password, key or token \u2014 those are refused.`,
2462
2574
  inputSchema: {
2463
2575
  type: "object",
2464
2576
  properties: { projectId: { type: "string" }, path: { type: "string" }, content: { type: "string" }, append: { type: "boolean" }, branch: { type: "string" } },
@@ -2473,7 +2585,7 @@ ${pages.filter((p) => !shown.has(p.path)).map((p) => `- ${p.path}`).join("\n") |
2473
2585
  ...args["append"] === true ? { append: true } : {},
2474
2586
  ...str(args["branch"]) ? { changeId: str(args["branch"]) } : {}
2475
2587
  });
2476
- return text(`Wrote ${saved.path} (${saved.length} characters) on Claude's working branch ${saved.branch}.`);
2588
+ return text(saved.keptBy === "flowriki" ? `Kept ${saved.path} (${saved.length} characters) in the project's memory \u2014 the next job is given it; nothing to publish.` : `Wrote ${saved.path} (${saved.length} characters) on Claude's working branch ${saved.branch}.`);
2477
2589
  }
2478
2590
  },
2479
2591
  {
@@ -46,7 +46,7 @@ import {
46
46
  textOf,
47
47
  toolEnv,
48
48
  typesSecrets
49
- } from "./chunk-WQ22OUIQ.js";
49
+ } from "./chunk-IFUODU4W.js";
50
50
  export {
51
51
  AGENT_HOME,
52
52
  APPIUM_DIR,
package/dist/engine.js CHANGED
@@ -9,8 +9,8 @@ import {
9
9
  resolveCheckouts,
10
10
  start,
11
11
  status
12
- } from "./chunk-BYFYSV5H.js";
13
- import "./chunk-WQ22OUIQ.js";
12
+ } from "./chunk-QIZZ4HZ7.js";
13
+ import "./chunk-IFUODU4W.js";
14
14
  export {
15
15
  AGENT_VERSION,
16
16
  browsersReady,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowriki/agent",
3
- "version": "0.1.0-beta.60",
3
+ "version": "0.1.0-beta.63",
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",