@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.
- package/dist/{chunk-7CH5XDEE.js → chunk-T7I2US4Y.js} +72 -71
- package/dist/cli.js +34 -88
- package/dist/engine.js +1 -1
- package/package.json +1 -1
|
@@ -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.
|
|
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
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
|
11184
|
-
const
|
|
11185
|
-
|
|
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
|
|
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
|
-
|
|
11190
|
-
|
|
11191
|
-
|
|
11192
|
-
|
|
11193
|
-
|
|
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
|
-
|
|
11199
|
-
|
|
11200
|
-
return
|
|
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 =
|
|
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
|
|
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
|
|
11244
|
-
*
|
|
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
|
-
|
|
11248
|
-
const
|
|
11249
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
...
|
|
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((
|
|
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
|
-
|
|
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
|
|
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.
|
|
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-
|
|
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
|
|
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,
|
|
888
|
-
knowledge_search; read a page with knowledge_read; read
|
|
889
|
-
sources.
|
|
890
|
-
|
|
891
|
-
|
|
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,
|
|
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
|
|
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 }
|
|
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
|
|
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
|
|
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
|
-
|
|
1570
|
-
${
|
|
1562
|
+
codebases.length ? `## Codebase, as learned
|
|
1563
|
+
${headers.join("\n")}` : "No codebase learned yet.",
|
|
1571
1564
|
`## Other pages
|
|
1572
|
-
${pages.filter((p) => !
|
|
1573
|
-
`##
|
|
1574
|
-
${sources.map((s) => `- ${s.title} (id ${s.id}, v${s.version}
|
|
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,
|
|
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:
|
|
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}.
|
|
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
package/package.json
CHANGED