@flowriki/agent 0.1.0-beta.39 → 0.1.0-beta.40

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.
@@ -6,7 +6,7 @@ import os from "os";
6
6
  import path from "path";
7
7
  import { createRequire } from "module";
8
8
  import { spawnSync } from "child_process";
9
- var AGENT_VERSION = "0.1.0-beta.39";
9
+ var AGENT_VERSION = "0.1.0-beta.40";
10
10
  async function prepareBrowsers() {
11
11
  if (browsersReady()) {
12
12
  return { ok: true, already: true, message: "chromium is already installed" };
@@ -1879,7 +1879,8 @@ var SourceVersionSchema = z5.object({
1879
1879
  v: z5.number().int().positive(),
1880
1880
  hash: z5.string().min(1),
1881
1881
  extractedText: z5.string(),
1882
- /** Where the original file is kept, for an upload. */
1882
+ /** Where an uploaded file WAS kept. Only text is kept since 2026-10-11;
1883
+ * read so older records still parse, and cleared at start-up. */
1883
1884
  originalKey: z5.string().optional(),
1884
1885
  mediaType: z5.string().optional(),
1885
1886
  at: z5.string(),
@@ -2022,37 +2023,6 @@ var ConversationSchema = z7.object({
2022
2023
  createdAt: z7.string(),
2023
2024
  updatedAt: z7.string()
2024
2025
  }).strict();
2025
- var INTEGRATION_PLATFORMS = ["jira", "linear", "github", "figma", "confluence"];
2026
- var IntegrationSchema = z7.object({
2027
- id: z7.string().regex(/^int_[A-Za-z0-9]+$/),
2028
- projectId: z7.string().min(1),
2029
- platform: z7.enum(INTEGRATION_PLATFORMS),
2030
- name: z7.string().min(1).max(120),
2031
- /** Where it is: a Jira or Confluence site, a GitHub repository, a Figma
2032
- * file, a Linear team. Never a credential. */
2033
- config: z7.object({
2034
- baseUrl: z7.string().url().optional(),
2035
- email: z7.string().optional(),
2036
- jql: z7.string().max(2e3).optional(),
2037
- projectKey: z7.string().max(40).optional(),
2038
- repository: z7.string().regex(/^[\w.-]+\/[\w.-]+$/).optional(),
2039
- fileKey: z7.string().max(120).optional(),
2040
- teamKey: z7.string().max(40).optional(),
2041
- spaceKey: z7.string().max(80).optional(),
2042
- pageIds: z7.array(z7.string().max(40)).max(200).optional(),
2043
- /** Raise bugs here when asked. */
2044
- bugs: z7.boolean().optional(),
2045
- /** Read items here as sources. */
2046
- sources: z7.boolean().optional()
2047
- }).strict(),
2048
- /** The vault reference for its token. */
2049
- tokenRef: z7.string().regex(/^[a-z][a-z0-9_]*$/),
2050
- status: z7.enum(["connected", "failing"]).default("connected"),
2051
- lastSyncAt: z7.string().nullable().default(null),
2052
- lastError: z7.string().nullable().default(null),
2053
- createdAt: z7.string(),
2054
- createdBy: z7.string().default("")
2055
- }).strict();
2056
2026
 
2057
2027
  // ../engine-playwright/dist/index.js
2058
2028
  import { createHmac, randomBytes as randomBytes2 } from "crypto";
@@ -9943,7 +9913,7 @@ function adapt(socket) {
9943
9913
  };
9944
9914
  }
9945
9915
  function dialWebSocket(opts) {
9946
- return () => new Promise((resolve, reject) => {
9916
+ return () => new Promise((resolve2, reject) => {
9947
9917
  const socket = new WebSocket(opts.url, {
9948
9918
  handshakeTimeout: opts.handshakeTimeoutMs ?? 1e4,
9949
9919
  ...opts.headers ? { headers: opts.headers } : {}
@@ -9957,7 +9927,7 @@ function dialWebSocket(opts) {
9957
9927
  };
9958
9928
  socket.once("open", () => {
9959
9929
  socket.off("error", failed);
9960
- resolve(adapt(socket));
9930
+ resolve2(adapt(socket));
9961
9931
  });
9962
9932
  socket.once("error", failed);
9963
9933
  });
@@ -10366,7 +10336,7 @@ var AgentLink = class {
10366
10336
  try {
10367
10337
  const socket = await this.opts.dial();
10368
10338
  this.socket = socket;
10369
- const closed = new Promise((resolve) => socket.onClose(resolve));
10339
+ const closed = new Promise((resolve2) => socket.onClose(resolve2));
10370
10340
  socket.onMessage((data) => void this.receive(data));
10371
10341
  const auth = {
10372
10342
  type: "auth",
@@ -11150,11 +11120,21 @@ var file = () => path34.join(flowrikiHome(), "assistant-folders.json");
11150
11120
  function readAll() {
11151
11121
  try {
11152
11122
  const parsed = JSON.parse(fs34.readFileSync(file(), "utf8"));
11153
- return parsed && typeof parsed === "object" ? parsed : {};
11123
+ if (!parsed || typeof parsed !== "object") return {};
11124
+ const out = {};
11125
+ for (const [id, value] of Object.entries(parsed)) {
11126
+ const list = typeof value === "string" ? [value] : Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
11127
+ if (list.length) out[id] = list;
11128
+ }
11129
+ return out;
11154
11130
  } catch {
11155
11131
  return {};
11156
11132
  }
11157
11133
  }
11134
+ function writeAll(all) {
11135
+ fs34.mkdirSync(path34.dirname(file()), { recursive: true, mode: 448 });
11136
+ fs34.writeFileSync(file(), JSON.stringify(all, null, 2), { mode: 384 });
11137
+ }
11158
11138
  function gitHead(folder) {
11159
11139
  try {
11160
11140
  const head = fs34.readFileSync(path34.join(folder, ".git", "HEAD"), "utf8").trim();
@@ -11170,7 +11150,7 @@ function gitHead(folder) {
11170
11150
  return { commit: null, branch: null };
11171
11151
  }
11172
11152
  }
11173
- function describeFolder(folder) {
11153
+ function describeFolder(folder, name = path34.basename(folder)) {
11174
11154
  const exists = (() => {
11175
11155
  try {
11176
11156
  return fs34.statSync(folder).isDirectory();
@@ -11178,26 +11158,46 @@ function describeFolder(folder) {
11178
11158
  return false;
11179
11159
  }
11180
11160
  })();
11181
- return { path: folder, exists, ...exists ? gitHead(folder) : { commit: null, branch: null } };
11161
+ return { path: folder, name, exists, ...exists ? gitHead(folder) : { commit: null, branch: null } };
11182
11162
  }
11183
- function folderFor(projectId) {
11184
- const folder = readAll()[projectId];
11185
- return folder ? describeFolder(folder) : null;
11163
+ function foldersFor(projectId) {
11164
+ const list = readAll()[projectId] ?? [];
11165
+ const base = (p) => path34.basename(p);
11166
+ return list.map((p) => describeFolder(p, list.filter((o) => base(o) === base(p)).length > 1 ? `${path34.basename(path34.dirname(p))}-${base(p)}` : base(p)));
11186
11167
  }
11187
- function setFolder(projectId, folder) {
11168
+ function resolve(folder) {
11169
+ const resolved = path34.resolve(folder.replace(/^~(?=$|\/)/, process.env["HOME"] ?? "~"));
11170
+ if (!path34.isAbsolute(resolved)) throw new Error("give the folder's full path");
11171
+ if (!describeFolder(resolved).exists) throw new Error(`there is no folder at ${resolved} on this computer`);
11172
+ if (resolved === path34.parse(resolved).root || resolved === process.env["HOME"]) throw new Error("link the project's own folder, not the whole disk or home folder");
11173
+ return resolved;
11174
+ }
11175
+ function addFolder(projectId, folder) {
11188
11176
  const all = readAll();
11189
- if (folder === null) {
11190
- delete all[projectId];
11191
- } else {
11192
- const resolved = path34.resolve(folder.replace(/^~(?=$|\/)/, process.env["HOME"] ?? "~"));
11193
- if (!path34.isAbsolute(resolved)) throw new Error("give the folder's full path");
11194
- if (!describeFolder(resolved).exists) throw new Error(`there is no folder at ${resolved} on this computer`);
11195
- if (resolved === path34.parse(resolved).root || resolved === process.env["HOME"]) throw new Error("link the project's own folder, not the whole disk or home folder");
11196
- all[projectId] = resolved;
11177
+ const resolved = resolve(folder);
11178
+ const list = all[projectId] ?? [];
11179
+ if (list.some((p) => p === resolved)) return foldersFor(projectId);
11180
+ if (list.some((p) => resolved.startsWith(`${p}${path34.sep}`) || p.startsWith(`${resolved}${path34.sep}`))) {
11181
+ throw new Error("that folder is inside one already linked, or holds one \u2014 link each checkout once");
11197
11182
  }
11198
- fs34.mkdirSync(path34.dirname(file()), { recursive: true, mode: 448 });
11199
- fs34.writeFileSync(file(), JSON.stringify(all, null, 2), { mode: 384 });
11200
- return folder === null ? null : folderFor(projectId);
11183
+ all[projectId] = [...list, resolved];
11184
+ writeAll(all);
11185
+ return foldersFor(projectId);
11186
+ }
11187
+ function removeFolder(projectId, folder) {
11188
+ const all = readAll();
11189
+ const list = (all[projectId] ?? []).filter((p) => p !== folder);
11190
+ if (list.length) all[projectId] = list;
11191
+ else delete all[projectId];
11192
+ writeAll(all);
11193
+ return foldersFor(projectId);
11194
+ }
11195
+ function setFolder(projectId, folder) {
11196
+ const all = readAll();
11197
+ if (folder === null) delete all[projectId];
11198
+ else all[projectId] = [resolve(folder)];
11199
+ writeAll(all);
11200
+ return foldersFor(projectId);
11201
11201
  }
11202
11202
  var MAX_PROMPT = 4e3;
11203
11203
  function assistantHandlers({ deps }) {
@@ -11226,28 +11226,29 @@ function assistantHandlers({ deps }) {
11226
11226
  if (!status2.installed) return { ok: false, reason: "failed", detail: status2.detail ?? "the assistant is not installed on this machine" };
11227
11227
  if (!status2.signedIn) return { ok: false, reason: "failed", detail: "the assistant is not signed in on this machine" };
11228
11228
  const askId = randomUUID2();
11229
- const linked = folderFor(project.id);
11229
+ const linked = foldersFor(project.id).filter((f) => f.exists).map((f) => f.path);
11230
11230
  const mode = command.args["mode"] === "ask" ? "ask" : command.args["mode"] === "do" ? "do" : void 0;
11231
11231
  const started = assistant.ask({
11232
11232
  askId,
11233
11233
  prompt,
11234
11234
  projectId: project.id,
11235
11235
  projectName: project.name,
11236
- ...linked?.exists ? { folder: linked.path } : {},
11236
+ ...linked.length ? { folders: linked } : {},
11237
11237
  ...mode ? { mode } : {},
11238
11238
  ...typeof sessionId === "string" ? { sessionId } : {},
11239
11239
  emit
11240
11240
  });
11241
11241
  return started.started ? { ok: true, result: { askId } } : { ok: false, reason: "busy", detail: started.detail ?? "the assistant is busy" };
11242
11242
  },
11243
- /** Which folder on this computer holds the project's code — read with no
11244
- * `path`, linked with a path, unlinked with null. */
11243
+ /** Which folders on this computer hold the project's code — read with
11244
+ * nothing, one more linked with `add`, one unlinked with `remove`.
11245
+ * `path` (one folder, or null for none) is what older dashboards send.
11246
+ * `folder` is answered too, for them: the first one. */
11245
11247
  "assistant.folder": async (command, project) => {
11246
11248
  try {
11247
- if (command.args["path"] === void 0) return { ok: true, result: { folder: folderFor(project.id) } };
11248
- const asked = command.args["path"];
11249
- const folder = setFolder(project.id, asked === null ? null : String(asked));
11250
- return { ok: true, result: { folder } };
11249
+ const { add, remove, path: asked } = command.args;
11250
+ const folders = typeof add === "string" ? addFolder(project.id, add) : typeof remove === "string" ? removeFolder(project.id, remove) : asked !== void 0 ? setFolder(project.id, asked === null ? null : String(asked)) : foldersFor(project.id);
11251
+ return { ok: true, result: { folders, folder: folders[0] ?? null } };
11251
11252
  } catch (error) {
11252
11253
  return { ok: false, reason: "failed", detail: error.message };
11253
11254
  }
@@ -11348,18 +11349,18 @@ var ASK_MODE_TOOLS = [
11348
11349
  "library_cases",
11349
11350
  "test_cycles",
11350
11351
  "bugs",
11351
- "list_my_branches",
11352
- "integrations"
11352
+ "list_my_branches"
11353
11353
  ];
11354
11354
  function buildAskArgs(opts) {
11355
11355
  const ask = opts.mode === "ask";
11356
+ const folders = opts.folders ?? [];
11356
11357
  const guidance = [
11357
11358
  `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. Start with project_brief: it is this project's memory.`,
11358
11359
  ask ? "ASK MODE: answer the question; change nothing. Cite what you rely on as [[testCase:TC-12]], [[source:src_x@v2]], [[requirement:req_x]], [[knowledge:features/checkout.md]], [[automation:tc_x]], [[cycle:cyc_x]], [[bug:BUG-3]] 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. End by writing a short summary of what you did with write_knowledge (memory/sessions/<date>-<topic>.md, append) so the next session knows.",
11359
- opts.folder ? `The project's code is in ${opts.folder}. Read it with Read, Grep and Glob when it helps; you cannot change it.` : ""
11360
+ 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.` : ""
11360
11361
  ].filter(Boolean).join("\n");
11361
11362
  const flowriki = ask ? ASK_MODE_TOOLS.map((t) => `mcp__${MCP_SERVER_NAME}__${t}`) : [`mcp__${MCP_SERVER_NAME}`];
11362
- const files = opts.folder ? ["Read", "Grep", "Glob"] : [];
11363
+ const files = folders.length ? ["Read", "Grep", "Glob"] : [];
11363
11364
  return [
11364
11365
  "-p",
11365
11366
  opts.prompt,
@@ -11373,7 +11374,7 @@ function buildAskArgs(opts) {
11373
11374
  // edits, no web.
11374
11375
  "--tools",
11375
11376
  files.join(","),
11376
- ...opts.folder ? ["--add-dir", opts.folder] : [],
11377
+ ...folders.flatMap((f) => ["--add-dir", f]),
11377
11378
  // Only these, and anything else is denied rather than asked about: there
11378
11379
  // is nobody at a terminal to answer.
11379
11380
  "--allowedTools",
@@ -11448,19 +11449,19 @@ function createClaudeCli(options) {
11448
11449
  let current = null;
11449
11450
  let cancelled = null;
11450
11451
  function capture(args, timeoutMs = 15e3) {
11451
- return new Promise((resolve) => {
11452
+ return new Promise((resolve2) => {
11452
11453
  let out = "", err = "", settled = false;
11453
11454
  const finish = (code, failed) => {
11454
11455
  if (settled) return;
11455
11456
  settled = true;
11456
11457
  clearTimeout(timer);
11457
- resolve({ code, out, err, failed });
11458
+ resolve2({ code, out, err, failed });
11458
11459
  };
11459
11460
  let child;
11460
11461
  try {
11461
11462
  child = spawn2(binary, args, { stdio: ["ignore", "pipe", "pipe"] });
11462
11463
  } catch {
11463
- return resolve({ code: null, out: "", err: "", failed: true });
11464
+ return resolve2({ code: null, out: "", err: "", failed: true });
11464
11465
  }
11465
11466
  const timer = setTimeout(() => {
11466
11467
  child.kill("SIGKILL");
@@ -11536,7 +11537,7 @@ function createClaudeCli(options) {
11536
11537
  projectId: ask.projectId,
11537
11538
  projectName: ask.projectName,
11538
11539
  ...ask.sessionId ? { sessionId: ask.sessionId } : {},
11539
- ...ask.folder ? { folder: ask.folder } : {},
11540
+ ...ask.folders?.length ? { folders: ask.folders } : {},
11540
11541
  ...ask.mode ? { mode: ask.mode } : {}
11541
11542
  }),
11542
11543
  // THE SCRATCH FOLDER, not the project: nothing here to read or write.
package/dist/cli.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  similarity,
24
24
  start,
25
25
  status
26
- } from "./chunk-7CH5XDEE.js";
26
+ } from "./chunk-T7I2US4Y.js";
27
27
 
28
28
  // src/cli.ts
29
29
  import { parseArgs } from "util";
@@ -261,7 +261,7 @@ var ServiceClient = class {
261
261
  updateBug(projectId, id, body) {
262
262
  return this.call(this.scoped(`/bugs/${encodeURIComponent(id)}`, projectId), { method: "PATCH", body: JSON.stringify(body) });
263
263
  }
264
- // ── Knowledge, sources, requirements, integrations ──────────────────────
264
+ // ── Knowledge, sources, requirements ──────────────────────
265
265
  knowledgePages(projectId) {
266
266
  return this.call(this.scoped("/knowledge", projectId));
267
267
  }
@@ -297,18 +297,6 @@ var ServiceClient = class {
297
297
  updateRequirement(projectId, id, body) {
298
298
  return this.call(this.scoped(`/requirements/${encodeURIComponent(id)}`, projectId), { method: "PATCH", body: JSON.stringify(body) });
299
299
  }
300
- refreshBug(projectId, id) {
301
- return this.call(this.scoped(`/bugs/${encodeURIComponent(id)}/refresh`, projectId), { method: "POST" });
302
- }
303
- integrations(projectId) {
304
- return this.call(this.scoped("/integrations", projectId));
305
- }
306
- syncIntegration(projectId, id) {
307
- return this.call(this.scoped(`/integrations/${encodeURIComponent(id)}/sync`, projectId), { method: "POST" });
308
- }
309
- pushBug(projectId, bugId, integrationId) {
310
- return this.call(this.scoped(`/bugs/${encodeURIComponent(bugId)}/push`, projectId), { method: "POST", body: JSON.stringify({ integrationId }) });
311
- }
312
300
  /** Gather Claude's open branches here into one. */
313
301
  gatherChanges(projectId, opts = {}) {
314
302
  return this.call(
@@ -884,13 +872,20 @@ same thing twice is harmless), and after save_case, link the automation to the
884
872
  test cases it proves with link_test_case.
885
873
 
886
874
  KNOWLEDGE: project_brief is the project's memory \u2014 read it first. Search
887
- everything (knowledge pages, sources, requirements, test cases) with
888
- knowledge_search; read a page with knowledge_read; read source documents with
889
- sources. Keep what you learn: write_knowledge writes product.md, glossary.md,
890
- features/<name>.md, memory/decisions.md or a session summary, on Claude's
891
- branch for review \u2014 never a password, key or token.
875
+ everything (knowledge pages, documents, requirements, test cases) with
876
+ knowledge_search; read a page with knowledge_read; read documents with
877
+ sources. product.md is "About the product": the person's own description of
878
+ the product, in their words, as long as they like \u2014 read it first. They
879
+ write it, often with you in a conversation beside it; propose text there and
880
+ let them decide, rather than writing product.md unless they ask you to.
881
+ A linked codebase folder is learned into
882
+ codebase/<folder>.md, which must start "# <folder>", then "Commit: <full sha>"
883
+ and "Learned: <yyyy-mm-dd>" lines: that is how the Knowledge screen shows it
884
+ as learned. Keep what you learn with write_knowledge (features/<name>.md,
885
+ memory/decisions.md, a session summary), on Claude's branch for review \u2014
886
+ never a password, key or token.
892
887
 
893
- FROM DOCUMENTS TO TESTS: sources are PRDs, CRs, Jira stories, Figma frames.
888
+ FROM DOCUMENTS TO TESTS: documents (sources) are PRDs, CRs, feature docs.
894
889
  requirements lists what was found in them and what the latest version changed
895
890
  (new / changed / removed). For a new requirement: write test cases that cover
896
891
  it (save_test_cases with covers: [requirement id]) \u2014 but first check
@@ -904,10 +899,6 @@ and helpers it needs, and anything that cannot be automated and why. Show it
904
899
  and wait for the person to approve before recording. Then record, save_case,
905
900
  and link_test_case.
906
901
 
907
- INTEGRATIONS: integrations lists Jira, Linear, GitHub, Figma and Confluence
908
- connections; sync_integration reads them as sources. push_bug raises a bug in
909
- a tracker \u2014 it reaches outside Flowriki, so only after the person says to.
910
-
911
902
  MANUAL TESTING: a test cycle is a set of library cases a person runs by hand
912
903
  ("Release 4.2 regression"); each case is marked passed, failed, blocked or
913
904
  skipped, step by step, with a bug raised from the step that broke. Read them
@@ -1547,33 +1538,33 @@ ${lines.join("\n")}`);
1547
1538
  {
1548
1539
  name: "project_brief",
1549
1540
  title: "Read the project's memory",
1550
- description: "The project's own knowledge, in one call: product.md, glossary.md, memory/decisions.md, the list of feature pages and session summaries, the codebase summary's header, and the sources and integrations it has. Call it first in every conversation.",
1541
+ description: "The project's own knowledge, in one call: About the product (product.md), memory/decisions.md, what each linked codebase folder was learned as, the list of feature pages and session summaries, and the documents it has. Call it first in every conversation.",
1551
1542
  inputSchema: { type: "object", properties: { projectId: { type: "string" } }, additionalProperties: false },
1552
1543
  annotations: { readOnlyHint: true },
1553
1544
  run: async (args) => {
1554
1545
  const projectId = await projectFor(args["projectId"]);
1555
- const [{ pages }, { sources }, { integrations }] = await Promise.all([
1546
+ const [{ pages }, { sources }] = await Promise.all([
1556
1547
  service.knowledgePages(projectId),
1557
- service.sources(projectId).catch(() => ({ sources: [] })),
1558
- service.integrations(projectId).catch(() => ({ integrations: [] }))
1548
+ service.sources(projectId).catch(() => ({ sources: [] }))
1559
1549
  ]);
1560
1550
  const read = async (path) => pages.some((p) => p.path === path) ? (await service.knowledgePage(projectId, path)).content : null;
1561
- const [product, glossary, decisions, codebase] = await Promise.all(["product.md", "glossary.md", "memory/decisions.md", "codebase.json"].map(read));
1551
+ const [product, glossary, decisions] = await Promise.all(["product.md", "glossary.md", "memory/decisions.md"].map(read));
1552
+ const codebases = pages.filter((p) => p.path.startsWith("codebase/"));
1553
+ const headers = await Promise.all(codebases.map(async (p) => `- ${p.path}: ${(await read(p.path))?.split("\n").filter((l) => l.trim()).slice(0, 3).join(" \xB7 ") ?? ""}`));
1554
+ const shown = /* @__PURE__ */ new Set(["product.md", "glossary.md", "memory/decisions.md", ...codebases.map((p) => p.path)]);
1562
1555
  const parts = [
1563
- product ? `## product.md
1564
- ${product}` : "No product.md yet \u2014 write one with write_knowledge once you understand the product.",
1565
- glossary ? `## glossary.md
1556
+ product ? `## About the product (product.md)
1557
+ ${product}` : "No About the product yet \u2014 the person writes it on the Knowledge screen; offer to help draft it.",
1558
+ glossary ? `## glossary.md (older page; terms now belong in product.md)
1566
1559
  ${glossary}` : "",
1567
1560
  decisions ? `## memory/decisions.md
1568
1561
  ${decisions}` : "",
1569
- codebase ? `## codebase.json (first 1500 characters)
1570
- ${codebase.slice(0, 1500)}` : "No codebase summary yet.",
1562
+ codebases.length ? `## Codebase, as learned
1563
+ ${headers.join("\n")}` : "No codebase learned yet.",
1571
1564
  `## Other pages
1572
- ${pages.filter((p) => !["product.md", "glossary.md", "memory/decisions.md", "codebase.json"].includes(p.path)).map((p) => `- ${p.path}`).join("\n") || "none"}`,
1573
- `## Sources
1574
- ${sources.map((s) => `- ${s.title} (id ${s.id}, v${s.version}, ${s.kind}) \u2014 requirements: ${s.requirements["new"] ?? 0} new, ${s.requirements["changed"] ?? 0} changed, ${s.requirements["active"] ?? 0} unchanged, ${s.requirements["removed"] ?? 0} removed`).join("\n") || "none"}`,
1575
- `## Integrations
1576
- ${integrations.map((i) => `- ${i.name} (${i.platform}, id ${i.id}, ${i.status}${i.lastSyncAt ? `, last read ${i.lastSyncAt}` : ""})`).join("\n") || "none"}`
1565
+ ${pages.filter((p) => !shown.has(p.path)).map((p) => `- ${p.path}`).join("\n") || "none"}`,
1566
+ `## Documents
1567
+ ${sources.map((s) => `- ${s.title} (id ${s.id}, v${s.version}) \u2014 requirements: ${s.requirements["new"] ?? 0} new, ${s.requirements["changed"] ?? 0} changed, ${s.requirements["active"] ?? 0} unchanged, ${s.requirements["removed"] ?? 0} removed`).join("\n") || "none"}`
1577
1568
  ];
1578
1569
  return text(parts.filter(Boolean).join("\n\n"));
1579
1570
  }
@@ -1595,7 +1586,7 @@ ${integrations.map((i) => `- ${i.name} (${i.platform}, id ${i.id}, ${i.status}${
1595
1586
  {
1596
1587
  name: "knowledge_read",
1597
1588
  title: "Read a knowledge page",
1598
- description: "Read one knowledge page by path (product.md, glossary.md, features/<name>.md, memory/\u2026, codebase.json, index.json).",
1589
+ description: "Read one knowledge page by path (product.md, codebase/<folder>.md, features/<name>.md, memory/\u2026).",
1599
1590
  inputSchema: { type: "object", properties: { projectId: { type: "string" }, path: { type: "string" } }, required: ["path"], additionalProperties: false },
1600
1591
  annotations: { readOnlyHint: true },
1601
1592
  run: async (args) => {
@@ -1607,7 +1598,7 @@ ${integrations.map((i) => `- ${i.name} (${i.platform}, id ${i.id}, ${i.status}${
1607
1598
  {
1608
1599
  name: "write_knowledge",
1609
1600
  title: "Write a knowledge page",
1610
- description: "Write what the project should remember, on Claude's working branch for review: product.md (what the app is, roles, environments), glossary.md, features/<name>.md (screens, rules, quirks), memory/decisions.md (decisions and conventions), memory/sessions/<yyyy-mm-dd>-<topic>.md (what a job did), codebase.json (the codebase summary: routes, screens, forms, roles, APIs, and the commit read). append: true adds to the end instead of replacing. Never a password, key or token \u2014 those are refused.",
1601
+ 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), 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.`,
1611
1602
  inputSchema: {
1612
1603
  type: "object",
1613
1604
  properties: { projectId: { type: "string" }, path: { type: "string" }, content: { type: "string" }, append: { type: "boolean" }, branch: { type: "string" } },
@@ -1761,46 +1752,6 @@ ${c.steps.map((st, i) => ` ${i + 1}. ${st.action}${st.expected ? ` \u2192 ${st
1761
1752
  return text(`Saved the ${args["approved"] === true ? "approved " : ""}automation plan for ${target.key} "${target.title}" on Claude's working branch ${saved.branch}.`);
1762
1753
  }
1763
1754
  },
1764
- {
1765
- name: "integrations",
1766
- title: "Read integrations",
1767
- description: "The project's connections to Jira, Linear, GitHub, Figma and Confluence: what each is, whether it works, when it was last read, and which take bugs.",
1768
- inputSchema: { type: "object", properties: { projectId: { type: "string" } }, additionalProperties: false },
1769
- annotations: { readOnlyHint: true },
1770
- run: async (args) => {
1771
- const projectId = await projectFor(args["projectId"]);
1772
- const { integrations } = await service.integrations(projectId);
1773
- if (!integrations.length) return text("No integrations. An owner connects them on the Knowledge screen.");
1774
- return text(integrations.map((i) => `${i.name} (${i.platform}, id ${i.id}) \u2014 ${i.status}${i.lastSyncAt ? `, last read ${i.lastSyncAt}` : ", never read"}${["jira", "linear", "github"].includes(i.platform) ? ", takes bugs" : ""}`).join("\n"));
1775
- }
1776
- },
1777
- {
1778
- name: "sync_integration",
1779
- title: "Read an integration's items as sources",
1780
- description: "Read what an integration holds (Jira stories, Linear and GitHub issues, Confluence pages, Figma frames) as sources. Items unchanged since the last read stay as they are.",
1781
- inputSchema: { type: "object", properties: { projectId: { type: "string" }, integrationId: { type: "string" } }, required: ["integrationId"], additionalProperties: false },
1782
- run: async (args) => {
1783
- const projectId = await projectFor(args["projectId"]);
1784
- const done = await service.syncIntegration(projectId, str(args["integrationId"]) ?? "");
1785
- return text(`Read ${done.items} item${done.items === 1 ? "" : "s"}: ${done.newSources} new, ${done.newVersions} changed, ${done.unchanged} unchanged. Requirements: ${done.requirements["added"] ?? 0} new, ${done.requirements["changed"] ?? 0} changed, ${done.requirements["removed"] ?? 0} removed.`);
1786
- }
1787
- },
1788
- {
1789
- name: "push_bug",
1790
- title: "Raise a bug in a tracker",
1791
- description: "Raise a Flowriki bug (BUG-n or its id) in Jira, Linear or GitHub through one of the project's integrations. This reaches outside Flowriki: only after the person has said to.",
1792
- inputSchema: { type: "object", properties: { projectId: { type: "string" }, bug: { type: "string" }, integrationId: { type: "string" } }, required: ["bug", "integrationId"], additionalProperties: false },
1793
- annotations: { destructiveHint: true },
1794
- run: async (args) => {
1795
- const projectId = await projectFor(args["projectId"]);
1796
- const ref = str(args["bug"]) ?? "";
1797
- const { bugs } = await service.bugs(projectId);
1798
- const bug = bugs.find((b) => b.id === ref || b.key.toLowerCase() === ref.toLowerCase());
1799
- if (!bug) return text(`No bug ${ref}.`);
1800
- const pushed = await service.pushBug(projectId, bug.id, str(args["integrationId"]) ?? "");
1801
- return text(`${bug.key} is now ${pushed.externalKey} in ${pushed.tracker}: ${pushed.url}`);
1802
- }
1803
- },
1804
1755
  {
1805
1756
  name: "test_cycles",
1806
1757
  title: "Read manual test cycles",
@@ -1964,7 +1915,7 @@ ${lines.join("\n")}`);
1964
1915
  {
1965
1916
  name: "save_bug",
1966
1917
  title: "Raise or update a bug",
1967
- description: "New: {title, description?, severity?: critical|major|minor|trivial, cycleId?, testCase?, step? (its number)} \u2014 found in a cycle, it is attached to that case's result there. Update: {bugId, title?, description?, severity?, status?: open|in-progress|fixed|verified|closed|wont-fix}. {bugId, refresh: true} reads a pushed bug's status back from its tracker.",
1918
+ description: "New: {title, description?, severity?: critical|major|minor|trivial, cycleId?, testCase?, step? (its number)} \u2014 found in a cycle, it is attached to that case's result there. Update: {bugId, title?, description?, severity?, status?: open|in-progress|fixed|verified|closed|wont-fix}.",
1968
1919
  inputSchema: {
1969
1920
  type: "object",
1970
1921
  properties: {
@@ -1976,18 +1927,13 @@ ${lines.join("\n")}`);
1976
1927
  status: { type: "string" },
1977
1928
  cycleId: { type: "string" },
1978
1929
  testCase: { type: "string" },
1979
- step: { type: "number" },
1980
- refresh: { type: "boolean" }
1930
+ step: { type: "number" }
1981
1931
  },
1982
1932
  additionalProperties: false
1983
1933
  },
1984
1934
  run: async (args) => {
1985
1935
  const projectId = await projectFor(args["projectId"]);
1986
1936
  const bugId = str(args["bugId"]);
1987
- if (bugId && args["refresh"] === true) {
1988
- const fresh = await service.refreshBug(projectId, bugId);
1989
- return text(`${fresh.key} is ${fresh.status} in its tracker.`);
1990
- }
1991
1937
  if (bugId) {
1992
1938
  const body2 = Object.fromEntries(["title", "description", "severity", "status"].filter((k) => typeof args[k] === "string").map((k) => [k, args[k]]));
1993
1939
  const saved = await service.updateBug(projectId, bugId, body2);
package/dist/engine.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  resolveCheckouts,
10
10
  start,
11
11
  status
12
- } from "./chunk-7CH5XDEE.js";
12
+ } from "./chunk-T7I2US4Y.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.39",
3
+ "version": "0.1.0-beta.40",
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",