flavor-code 1.3.12 → 1.3.15
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/README.md +426 -425
- package/README.zh-CN.md +423 -422
- package/dist/{app-Z7D7XPVE.js → app-DDYOKGCX.js} +134 -30
- package/dist/{chunk-EZNIDHFZ.js → chunk-TQXNF337.js} +1 -1
- package/dist/{chunk-QC5OYQTB.js → chunk-WB4IJXFT.js} +403 -123
- package/dist/cli-main.js +1036 -0
- package/dist/cli.js +48 -1007
- package/dist/desktop/astgraph-service.d.ts +34 -0
- package/dist/desktop/main.js +502 -299
- package/dist/desktop-renderer/assets/{index-CE6mw-at.js → index-BAHTmEUI.js} +6 -6
- package/dist/desktop-renderer/assets/index-ZZsUx916.css +1 -0
- package/dist/desktop-renderer/assets/{interactive-terminal-DcaRc1vO.js → interactive-terminal-Bn8G5Pig.js} +1 -1
- package/dist/desktop-renderer/index.html +2 -2
- package/dist/explain/service.d.ts +114 -0
- package/dist/sdk/index.js +2 -2
- package/dist/ui/commands.d.ts +6 -2
- package/dist/ui/session.d.ts +1 -0
- package/package.json +1 -1
- package/dist/desktop-renderer/assets/index-DHTr_Acb.css +0 -1
package/dist/desktop/main.js
CHANGED
|
@@ -4227,7 +4227,7 @@ ${summarizeReport(report2)}`).join("\n\n")
|
|
|
4227
4227
|
import { readFile as readFile27 } from "fs/promises";
|
|
4228
4228
|
import { createHash as createHash21, randomUUID as randomUUID25 } from "crypto";
|
|
4229
4229
|
import { homedir as homedir2, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
|
|
4230
|
-
import { basename as basename8, join as
|
|
4230
|
+
import { basename as basename8, join as join33, relative as relative23, resolve as resolve33, sep as sep23 } from "path";
|
|
4231
4231
|
|
|
4232
4232
|
// src/agent/subagents.ts
|
|
4233
4233
|
import { z as z10 } from "zod";
|
|
@@ -19684,6 +19684,232 @@ function formatReviewReport(report) {
|
|
|
19684
19684
|
return lines.join("\n");
|
|
19685
19685
|
}
|
|
19686
19686
|
|
|
19687
|
+
// src/explain/service.ts
|
|
19688
|
+
async function resolveExplainTarget(graph, query) {
|
|
19689
|
+
const trimmed = query.trim();
|
|
19690
|
+
if (trimmed.includes("#")) {
|
|
19691
|
+
const hit = await graph.relations(trimmed).catch(() => void 0);
|
|
19692
|
+
if (hit?.origin !== void 0) return { kind: "resolved", node: hit.origin };
|
|
19693
|
+
}
|
|
19694
|
+
const hits = await graph.search(trimmed, 10);
|
|
19695
|
+
if (hits.length === 0) return { kind: "not-found", query: trimmed };
|
|
19696
|
+
if (hits.length === 1) return { kind: "resolved", node: hits[0] };
|
|
19697
|
+
return { kind: "ambiguous", candidates: hits };
|
|
19698
|
+
}
|
|
19699
|
+
function explainCandidateLabel(candidate) {
|
|
19700
|
+
return candidate.id;
|
|
19701
|
+
}
|
|
19702
|
+
async function selectExplainCandidate(candidates, questions, signal) {
|
|
19703
|
+
const shown = candidates.slice(0, 3);
|
|
19704
|
+
const answers = await questions.ask([{
|
|
19705
|
+
header: "Symbol",
|
|
19706
|
+
question: "Multiple symbols match. Which one should /explain cover? (Pick one, or type a more exact name.)",
|
|
19707
|
+
options: [
|
|
19708
|
+
...shown.map((candidate) => ({
|
|
19709
|
+
label: explainCandidateLabel(candidate),
|
|
19710
|
+
description: `${candidate.kind} \xB7 ${candidate.filePath}:${candidate.startLine}-${candidate.endLine}`
|
|
19711
|
+
})),
|
|
19712
|
+
{ label: "Cancel", description: "Abort /explain without choosing." }
|
|
19713
|
+
]
|
|
19714
|
+
}], signal);
|
|
19715
|
+
const answer = (answers[0] ?? "").trim();
|
|
19716
|
+
if (answer === "Cancel" || answer === "") return { kind: "cancelled" };
|
|
19717
|
+
const picked = shown.find((candidate) => explainCandidateLabel(candidate) === answer);
|
|
19718
|
+
if (picked !== void 0) return { kind: "picked", node: picked };
|
|
19719
|
+
return { kind: "typed", query: answer };
|
|
19720
|
+
}
|
|
19721
|
+
var MAX_SOURCE_CHARS = 2e4;
|
|
19722
|
+
function buildExplainPrompt(input) {
|
|
19723
|
+
const source = input.anchorSource.length > MAX_SOURCE_CHARS ? `${input.anchorSource.slice(0, MAX_SOURCE_CHARS)}
|
|
19724
|
+
\u2026 (truncated)` : input.anchorSource;
|
|
19725
|
+
const line = (n) => `- ${n.name} \u2014 ${n.filePath}:${n.startLine}`;
|
|
19726
|
+
return [
|
|
19727
|
+
"You are a senior engineer onboarding a new team member. Explain the following code symbol clearly and concretely; never invent behavior that is not in the shown evidence.",
|
|
19728
|
+
`Answer in ${input.language}.`,
|
|
19729
|
+
"Structure the answer with short markdown sections:",
|
|
19730
|
+
"1. \u5B83\u662F\u505A\u4EC0\u4E48\u7684 \u2014 one paragraph, plain language, beginner first.",
|
|
19731
|
+
"2. \u5173\u952E\u5B9E\u73B0\u70B9 \u2014 2-5 bullets tied to concrete lines/branches in the source.",
|
|
19732
|
+
"3. \u8C01\u8C03\u7528\u5B83 / \u5B83\u8C03\u7528\u8C01 \u2014 use the relation lists; explain the data flow in one or two sentences.",
|
|
19733
|
+
"4. \u4E3A\u4EC0\u4E48\u8FD9\u6837\u5199 \u2014 infer from the recent commit subjects when possible; say '\u4E0D\u786E\u5B9A' instead of guessing.",
|
|
19734
|
+
"5. \u65B0\u4EBA\u6CE8\u610F\u4E8B\u9879 \u2014 pitfalls, guards or invariants worth knowing.",
|
|
19735
|
+
...input.focus === void 0 || input.focus.trim() === "" ? [] : [`The user specifically asks about: ${input.focus.trim()}`],
|
|
19736
|
+
"",
|
|
19737
|
+
`Symbol: ${input.anchor.id} (${input.anchor.kind}${input.anchor.signature ? `, signature: ${input.anchor.signature}` : ""})`,
|
|
19738
|
+
"",
|
|
19739
|
+
`Source (${input.anchor.filePath}:${input.anchor.startLine}-${input.anchor.endLine}):`,
|
|
19740
|
+
source,
|
|
19741
|
+
"",
|
|
19742
|
+
`Callers:
|
|
19743
|
+
${input.callers.map(line).join("\n") || "- (none)"}`,
|
|
19744
|
+
`Callees:
|
|
19745
|
+
${input.callees.map(line).join("\n") || "- (none)"}`,
|
|
19746
|
+
"",
|
|
19747
|
+
`Recent commits touching the file:
|
|
19748
|
+
${input.history.map((h) => `- ${h.date} ${h.author}: ${h.subject}`).join("\n") || "- (no history)"}`
|
|
19749
|
+
].join("\n");
|
|
19750
|
+
}
|
|
19751
|
+
async function explainWithModel(options, prompt, signal, onText) {
|
|
19752
|
+
const { adapter, model } = options.registry.get(options.modelId());
|
|
19753
|
+
let text = "";
|
|
19754
|
+
for await (const event of adapter.stream({
|
|
19755
|
+
model,
|
|
19756
|
+
messages: [{ role: "user", content: prompt }],
|
|
19757
|
+
tools: [],
|
|
19758
|
+
signal
|
|
19759
|
+
})) {
|
|
19760
|
+
if (event.type === "text") {
|
|
19761
|
+
text += event.text;
|
|
19762
|
+
onText?.(event.text);
|
|
19763
|
+
} else if (event.type === "error") throw new Error(`explain generation failed: ${event.error.message}`);
|
|
19764
|
+
else if (event.type === "done") break;
|
|
19765
|
+
}
|
|
19766
|
+
const cleaned = text.trim();
|
|
19767
|
+
if (cleaned === "") throw new Error("explain generation returned no text");
|
|
19768
|
+
return cleaned;
|
|
19769
|
+
}
|
|
19770
|
+
var HISTORY_LIMIT = 8;
|
|
19771
|
+
var MAX_RESOLVE_ROUNDS = 2;
|
|
19772
|
+
async function runExplain(deps, query, focus, signal) {
|
|
19773
|
+
if (query === void 0) {
|
|
19774
|
+
return "Usage: /explain <symbol | file.ts#symbol> [what to focus on]. Example: /explain src/order.ts#cancelOrder error handling";
|
|
19775
|
+
}
|
|
19776
|
+
if (!(await deps.graph.status()).available) {
|
|
19777
|
+
return "The code graph is not built yet \u2014 run /ast init, then try /explain again.";
|
|
19778
|
+
}
|
|
19779
|
+
let target = await resolveExplainTarget(deps.graph, query);
|
|
19780
|
+
for (let round = 1; round <= MAX_RESOLVE_ROUNDS; round += 1) {
|
|
19781
|
+
if (target.kind !== "ambiguous") break;
|
|
19782
|
+
if (deps.questions === void 0) {
|
|
19783
|
+
return `"${query}" is ambiguous. Use the exact node id, e.g. /explain ${target.candidates[0]?.id ?? "path/file.ts#name"}.`;
|
|
19784
|
+
}
|
|
19785
|
+
const selection = await selectExplainCandidate(target.candidates, deps.questions, signal);
|
|
19786
|
+
if (selection.kind === "cancelled") return "/explain cancelled \u2014 no symbol was selected.";
|
|
19787
|
+
if (selection.kind === "picked") {
|
|
19788
|
+
target = { kind: "resolved", node: selection.node };
|
|
19789
|
+
break;
|
|
19790
|
+
}
|
|
19791
|
+
target = await resolveExplainTarget(deps.graph, selection.query);
|
|
19792
|
+
if (round === MAX_RESOLVE_ROUNDS && target.kind === "ambiguous") {
|
|
19793
|
+
return `"${selection.query}" is still ambiguous. Pick one node id explicitly: /explain ${target.candidates[0]?.id ?? "path/file.ts#name"}`;
|
|
19794
|
+
}
|
|
19795
|
+
}
|
|
19796
|
+
if (target.kind === "not-found") {
|
|
19797
|
+
return `No matching symbol for "${target.query}" \u2014 check the name, or refresh the graph with /ast sync.`;
|
|
19798
|
+
}
|
|
19799
|
+
if (target.kind !== "resolved") {
|
|
19800
|
+
return `"${query}" is ambiguous. Pick one node id explicitly: /explain ${target.candidates[0]?.id ?? "path/file.ts#name"}`;
|
|
19801
|
+
}
|
|
19802
|
+
const anchor = target.node;
|
|
19803
|
+
signal.throwIfAborted();
|
|
19804
|
+
deps.notify?.(`Explaining ${anchor.id} \u2026`);
|
|
19805
|
+
const { callers, callees } = await deps.graph.relations(anchor.id, 1);
|
|
19806
|
+
const raw = await deps.readFile(anchor.filePath).catch(() => "");
|
|
19807
|
+
const lines = raw.split("\n");
|
|
19808
|
+
const anchorSource = lines.slice(Math.max(0, anchor.startLine - 1), anchor.endLine).join("\n") || raw;
|
|
19809
|
+
const history = deps.history === void 0 ? [] : await deps.history(anchor.filePath, HISTORY_LIMIT).catch(() => []);
|
|
19810
|
+
try {
|
|
19811
|
+
if (deps.onText !== void 0) deps.onText(`Explain ${anchor.id}
|
|
19812
|
+
|
|
19813
|
+
`);
|
|
19814
|
+
const explanation = await explainWithModel(
|
|
19815
|
+
{ registry: deps.registry, modelId: deps.modelId },
|
|
19816
|
+
buildExplainPrompt({
|
|
19817
|
+
anchor,
|
|
19818
|
+
anchorSource,
|
|
19819
|
+
callers: callers.map((n) => ({ name: n.name, filePath: n.filePath, startLine: n.startLine })),
|
|
19820
|
+
callees: callees.map((n) => ({ name: n.name, filePath: n.filePath, startLine: n.startLine })),
|
|
19821
|
+
history,
|
|
19822
|
+
...focus === void 0 ? {} : { focus },
|
|
19823
|
+
language: deps.language
|
|
19824
|
+
}),
|
|
19825
|
+
signal,
|
|
19826
|
+
deps.onText
|
|
19827
|
+
);
|
|
19828
|
+
return deps.onText === void 0 ? `Explain ${anchor.id}
|
|
19829
|
+
|
|
19830
|
+
${explanation}` : "";
|
|
19831
|
+
} catch (error) {
|
|
19832
|
+
if (signal.aborted) throw error;
|
|
19833
|
+
return `/explain failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
19834
|
+
}
|
|
19835
|
+
}
|
|
19836
|
+
|
|
19837
|
+
// src/desktop/astgraph-service.ts
|
|
19838
|
+
import { stat as stat10 } from "fs/promises";
|
|
19839
|
+
import { createRequire as nodeCreateRequire } from "module";
|
|
19840
|
+
import { join as join27, resolve as resolve26 } from "path";
|
|
19841
|
+
var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
|
|
19842
|
+
var DesktopAstGraphService = class {
|
|
19843
|
+
#path;
|
|
19844
|
+
constructor(workspace) {
|
|
19845
|
+
this.#path = join27(resolve26(workspace), ".flavor", "astgraph", "index.db");
|
|
19846
|
+
}
|
|
19847
|
+
async status() {
|
|
19848
|
+
if ((await stat10(this.#path).catch(() => void 0))?.isFile() !== true) {
|
|
19849
|
+
return { available: false, path: this.#path, files: 0, nodes: 0, edges: 0 };
|
|
19850
|
+
}
|
|
19851
|
+
return this.#read((db) => {
|
|
19852
|
+
const count = (table) => Number(db.prepare(`SELECT COUNT(*) AS count FROM ${table}`).get().count);
|
|
19853
|
+
const metadata = db.prepare("SELECT value FROM project_metadata WHERE key = 'last_index'").get();
|
|
19854
|
+
let indexedAt;
|
|
19855
|
+
try {
|
|
19856
|
+
const at = Number(JSON.parse(metadata?.value ?? "{}").at);
|
|
19857
|
+
if (Number.isFinite(at)) indexedAt = new Date(at).toISOString();
|
|
19858
|
+
} catch {
|
|
19859
|
+
}
|
|
19860
|
+
return { available: true, path: this.#path, files: count("files"), nodes: count("nodes"), edges: count("edges"), ...indexedAt === void 0 ? {} : { indexedAt } };
|
|
19861
|
+
});
|
|
19862
|
+
}
|
|
19863
|
+
async search(query, limit = 40) {
|
|
19864
|
+
const value = query.trim().slice(0, 200);
|
|
19865
|
+
if (value.length === 0 || !(await this.status()).available) return [];
|
|
19866
|
+
return this.#read((db) => db.prepare(`SELECT ${FIELDS} FROM nodes WHERE lower(name) LIKE ? OR lower(qualified_name) LIKE ? ORDER BY CASE WHEN lower(name) = ? THEN 0 ELSE 1 END, name LIMIT ?`).all(`%${value.toLowerCase()}%`, `%${value.toLowerCase()}%`, value.toLowerCase(), Math.min(Math.max(limit, 1), 100)).map(mapNode));
|
|
19867
|
+
}
|
|
19868
|
+
async relations(id, hops = 2) {
|
|
19869
|
+
if (!/^[A-Za-z0-9._:/#@+~-]{1,512}$/.test(id)) throw new Error("Invalid AST node id");
|
|
19870
|
+
if (!(await this.status()).available) return { callers: [], callees: [], impact: [] };
|
|
19871
|
+
return this.#read((db) => {
|
|
19872
|
+
const originRow = db.prepare(`SELECT ${FIELDS} FROM nodes WHERE id = ?`).get(id);
|
|
19873
|
+
const linked = (direction) => {
|
|
19874
|
+
const source = direction === "callers" ? "e.source" : "e.target";
|
|
19875
|
+
const predicate = direction === "callers" ? "e.target = ?" : "e.source = ?";
|
|
19876
|
+
return db.prepare(`SELECT DISTINCT n.${FIELDS.split(", ").join(", n.")} FROM edges e JOIN nodes n ON n.id = ${source} WHERE ${predicate} AND e.kind IN ('calls','imports') LIMIT 100`).all(id).map(mapNode);
|
|
19877
|
+
};
|
|
19878
|
+
const seen = /* @__PURE__ */ new Map();
|
|
19879
|
+
let frontier = [id];
|
|
19880
|
+
for (let hop = 1; hop <= Math.min(Math.max(hops, 1), 4) && frontier.length > 0 && seen.size < 200; hop += 1) {
|
|
19881
|
+
const next = [];
|
|
19882
|
+
for (const current of frontier) {
|
|
19883
|
+
const rows = db.prepare("SELECT source AS other FROM edges WHERE target = ? UNION SELECT target AS other FROM edges WHERE source = ? LIMIT 200").all(current, current);
|
|
19884
|
+
for (const row of rows) if (row.other !== id && !seen.has(row.other)) {
|
|
19885
|
+
seen.set(row.other, hop);
|
|
19886
|
+
next.push(row.other);
|
|
19887
|
+
}
|
|
19888
|
+
}
|
|
19889
|
+
frontier = next;
|
|
19890
|
+
}
|
|
19891
|
+
const impact = [...seen].flatMap(([nodeId, hop]) => {
|
|
19892
|
+
const row = db.prepare(`SELECT ${FIELDS} FROM nodes WHERE id = ?`).get(nodeId);
|
|
19893
|
+
return row === void 0 ? [] : [{ ...mapNode(row), hop }];
|
|
19894
|
+
});
|
|
19895
|
+
return { ...originRow === void 0 ? {} : { origin: mapNode(originRow) }, callers: linked("callers"), callees: linked("callees"), impact };
|
|
19896
|
+
});
|
|
19897
|
+
}
|
|
19898
|
+
#read(run) {
|
|
19899
|
+
const sqlite = nodeCreateRequire(import.meta.url)("node:sqlite");
|
|
19900
|
+
const db = new sqlite.DatabaseSync(this.#path, { readOnly: true });
|
|
19901
|
+
try {
|
|
19902
|
+
return run(db);
|
|
19903
|
+
} finally {
|
|
19904
|
+
db.close();
|
|
19905
|
+
}
|
|
19906
|
+
}
|
|
19907
|
+
};
|
|
19908
|
+
function mapNode(value) {
|
|
19909
|
+
const row = value;
|
|
19910
|
+
return { id: String(row.id), kind: String(row.kind), name: String(row.name), qualifiedName: String(row.qualified_name), filePath: String(row.file_path), language: String(row.language), startLine: Number(row.start_line), endLine: Number(row.end_line), ...row.signature == null ? {} : { signature: String(row.signature) } };
|
|
19911
|
+
}
|
|
19912
|
+
|
|
19687
19913
|
// src/git/tools.ts
|
|
19688
19914
|
import { z as z34 } from "zod";
|
|
19689
19915
|
var GitHistoryInput = z34.object({
|
|
@@ -19885,7 +20111,7 @@ function createTodoWriteTool() {
|
|
|
19885
20111
|
// src/tools/managed.ts
|
|
19886
20112
|
import { randomUUID as randomUUID18 } from "crypto";
|
|
19887
20113
|
import { link, lstat as lstat11, mkdir as mkdir20, readFile as readFile22, readdir as readdir9, unlink as unlink3, writeFile as writeFile12 } from "fs/promises";
|
|
19888
|
-
import { dirname as dirname21, join as
|
|
20114
|
+
import { dirname as dirname21, join as join28, resolve as resolve27 } from "path";
|
|
19889
20115
|
import { z as z37 } from "zod";
|
|
19890
20116
|
var MANAGED_TOOL_VERSION = 1;
|
|
19891
20117
|
var MAX_RECORD_BYTES = 256 * 1024;
|
|
@@ -19927,9 +20153,9 @@ var ManagedToolStore = class {
|
|
|
19927
20153
|
#entries = /* @__PURE__ */ new Map();
|
|
19928
20154
|
#diagnostics = [];
|
|
19929
20155
|
constructor(options) {
|
|
19930
|
-
this.#workspace =
|
|
19931
|
-
this.#projectRoot =
|
|
19932
|
-
this.#globalRoot =
|
|
20156
|
+
this.#workspace = resolve27(options.workspace);
|
|
20157
|
+
this.#projectRoot = join28(this.#workspace, ".flavor", "tools");
|
|
20158
|
+
this.#globalRoot = join28(resolve27(options.home), ".flavor-code", "tools");
|
|
19933
20159
|
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
19934
20160
|
}
|
|
19935
20161
|
get diagnostics() {
|
|
@@ -19976,7 +20202,7 @@ var ManagedToolStore = class {
|
|
|
19976
20202
|
version: MANAGED_TOOL_VERSION,
|
|
19977
20203
|
createdAt: this.#now()
|
|
19978
20204
|
});
|
|
19979
|
-
const path =
|
|
20205
|
+
const path = join28(this.directory(input.scope), filename(input.name));
|
|
19980
20206
|
const definition = compileDefinition(record3, input.scope, this.#workspace);
|
|
19981
20207
|
await writeExclusive(path, `${JSON.stringify(record3, void 0, 2)}
|
|
19982
20208
|
`);
|
|
@@ -20001,7 +20227,7 @@ var ManagedToolStore = class {
|
|
|
20001
20227
|
if (!parsed.success) return [];
|
|
20002
20228
|
const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
|
|
20003
20229
|
if (matches.length > 0) return matches.map((entry2) => entry2.path);
|
|
20004
|
-
return [
|
|
20230
|
+
return [join28(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
|
|
20005
20231
|
}
|
|
20006
20232
|
async #loadDirectory(scope2) {
|
|
20007
20233
|
const root = this.directory(scope2);
|
|
@@ -20014,7 +20240,7 @@ var ManagedToolStore = class {
|
|
|
20014
20240
|
return;
|
|
20015
20241
|
}
|
|
20016
20242
|
for (const name of names) {
|
|
20017
|
-
const path =
|
|
20243
|
+
const path = join28(root, name);
|
|
20018
20244
|
try {
|
|
20019
20245
|
const info = await lstat11(path);
|
|
20020
20246
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
|
|
@@ -20603,6 +20829,7 @@ var MVP_COMMANDS = [
|
|
|
20603
20829
|
"mcp",
|
|
20604
20830
|
"commit",
|
|
20605
20831
|
"review",
|
|
20832
|
+
"explain",
|
|
20606
20833
|
"ide",
|
|
20607
20834
|
"memory",
|
|
20608
20835
|
"remember",
|
|
@@ -20678,6 +20905,12 @@ function parseSlashCommand(input, dynamicCommands = [], skillCommands = [], mana
|
|
|
20678
20905
|
const focus = args.join(" ").trim();
|
|
20679
20906
|
return focus.length === 0 ? { name } : { name, focus };
|
|
20680
20907
|
}
|
|
20908
|
+
if (name === "explain") {
|
|
20909
|
+
const [query, ...focusParts] = args;
|
|
20910
|
+
if (query === void 0) return { name };
|
|
20911
|
+
const focus = focusParts.join(" ").trim();
|
|
20912
|
+
return focus.length === 0 ? { name, query } : { name, query, focus };
|
|
20913
|
+
}
|
|
20681
20914
|
if (name === "loop") {
|
|
20682
20915
|
const goal = args.join(" ").trim();
|
|
20683
20916
|
if (!goal) return { name: "invalid", command: name, message: "Use /loop <goal>." };
|
|
@@ -21281,6 +21514,14 @@ var FlavorSession = class {
|
|
|
21281
21514
|
this.#notice(await required(this.#services.gitCommit, "commit")(command.hint, signal));
|
|
21282
21515
|
} else if (command.name === "review") {
|
|
21283
21516
|
this.#notice(await required(this.#services.gitReview, "review")(command.focus, signal));
|
|
21517
|
+
} else if (command.name === "explain") {
|
|
21518
|
+
const result = await required(this.#services.explain, "explain")(
|
|
21519
|
+
command.query,
|
|
21520
|
+
command.focus,
|
|
21521
|
+
signal,
|
|
21522
|
+
(chunk) => this.#services.output({ type: "text", text: chunk })
|
|
21523
|
+
);
|
|
21524
|
+
if (result !== "") this.#notice(result);
|
|
21284
21525
|
} else if (command.name === "mcp") {
|
|
21285
21526
|
this.#notice(await this.#services.mcp(command, signal));
|
|
21286
21527
|
} else if (command.name === "pals") {
|
|
@@ -22317,7 +22558,7 @@ ${total}`;
|
|
|
22317
22558
|
// src/memory/store.ts
|
|
22318
22559
|
import { createHash as createHash17 } from "crypto";
|
|
22319
22560
|
import { readFile as readFile23, rm as rm12 } from "fs/promises";
|
|
22320
|
-
import { resolve as
|
|
22561
|
+
import { resolve as resolve28, join as join29, relative as relative19, sep as sep19 } from "path";
|
|
22321
22562
|
|
|
22322
22563
|
// src/memory/similarity.ts
|
|
22323
22564
|
function normalizeForSimilarity(value) {
|
|
@@ -22419,10 +22660,10 @@ var MemoryStore = class {
|
|
|
22419
22660
|
constructor(options) {
|
|
22420
22661
|
if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
|
|
22421
22662
|
if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
|
|
22422
|
-
this.workspace =
|
|
22423
|
-
this.#memoryRoot =
|
|
22424
|
-
this.path =
|
|
22425
|
-
this.#behaviorPath =
|
|
22663
|
+
this.workspace = resolve28(options.workspace);
|
|
22664
|
+
this.#memoryRoot = join29(this.workspace, ".flavor", "memory");
|
|
22665
|
+
this.path = join29(this.#memoryRoot, "MEMORY.md");
|
|
22666
|
+
this.#behaviorPath = join29(this.#memoryRoot, "behavior.json");
|
|
22426
22667
|
this.#maxEntries = options.maxEntries;
|
|
22427
22668
|
this.#maxEntryChars = options.maxEntryChars;
|
|
22428
22669
|
}
|
|
@@ -22645,7 +22886,7 @@ var MemoryStore = class {
|
|
|
22645
22886
|
}
|
|
22646
22887
|
#resolveContentPath(contentPath) {
|
|
22647
22888
|
if (!/^tasks\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.md$/.test(contentPath)) throw new Error("Invalid memory content path");
|
|
22648
|
-
const path =
|
|
22889
|
+
const path = resolve28(this.#memoryRoot, ...contentPath.split("/"));
|
|
22649
22890
|
const rel = relative19(this.#memoryRoot, path);
|
|
22650
22891
|
if (rel === ".." || rel.startsWith(`..${sep19}`)) throw new Error("Memory content path escapes the memory directory");
|
|
22651
22892
|
return path;
|
|
@@ -23105,12 +23346,12 @@ var MemoryReviewBridge = class {
|
|
|
23105
23346
|
|
|
23106
23347
|
// src/execution/docker.ts
|
|
23107
23348
|
import { spawn as spawn6 } from "child_process";
|
|
23108
|
-
import { relative as relative20, resolve as
|
|
23349
|
+
import { relative as relative20, resolve as resolve29, sep as sep20, win32 } from "path";
|
|
23109
23350
|
function buildDockerInvocation(options) {
|
|
23110
23351
|
const workspace = options.workspace;
|
|
23111
23352
|
const cwd = options.request.cwd;
|
|
23112
23353
|
const windows = /^[A-Za-z]:[\\/]/.test(workspace);
|
|
23113
|
-
const delta = windows ? win32.relative(workspace, cwd) : relative20(
|
|
23354
|
+
const delta = windows ? win32.relative(workspace, cwd) : relative20(resolve29(workspace), resolve29(cwd));
|
|
23114
23355
|
if (delta === ".." || delta.startsWith(`..${windows ? "\\" : sep20}`)) throw new Error("Docker cwd escapes workspace");
|
|
23115
23356
|
const containerCwd = delta.length === 0 ? "/workspace" : `/workspace/${delta.replaceAll("\\", "/")}`;
|
|
23116
23357
|
const args = [
|
|
@@ -23210,8 +23451,8 @@ function createExecutionEnvironment(workspace, config) {
|
|
|
23210
23451
|
}
|
|
23211
23452
|
|
|
23212
23453
|
// src/ide/client.ts
|
|
23213
|
-
import { readFile as readFile24, readdir as readdir10, stat as
|
|
23214
|
-
import { basename as basename7, join as
|
|
23454
|
+
import { readFile as readFile24, readdir as readdir10, stat as stat11 } from "fs/promises";
|
|
23455
|
+
import { basename as basename7, join as join30, relative as relative21, resolve as resolve30, sep as sep21 } from "path";
|
|
23215
23456
|
var FlavorIdeClient = class {
|
|
23216
23457
|
#workspace;
|
|
23217
23458
|
#home;
|
|
@@ -23222,8 +23463,8 @@ var FlavorIdeClient = class {
|
|
|
23222
23463
|
#sessionId;
|
|
23223
23464
|
#eventTail = Promise.resolve();
|
|
23224
23465
|
constructor(options) {
|
|
23225
|
-
this.#workspace =
|
|
23226
|
-
this.#home =
|
|
23466
|
+
this.#workspace = resolve30(options.workspace);
|
|
23467
|
+
this.#home = resolve30(options.home);
|
|
23227
23468
|
this.#environment = options.environment ?? process.env;
|
|
23228
23469
|
this.#fetch = options.fetch ?? fetch;
|
|
23229
23470
|
}
|
|
@@ -23307,7 +23548,7 @@ var FlavorIdeClient = class {
|
|
|
23307
23548
|
};
|
|
23308
23549
|
if (await this.#healthy(configured)) return configured;
|
|
23309
23550
|
}
|
|
23310
|
-
const directory =
|
|
23551
|
+
const directory = join30(this.#home, ".flavor-code", "ide");
|
|
23311
23552
|
let entries;
|
|
23312
23553
|
try {
|
|
23313
23554
|
entries = (await readdir10(directory)).filter((name) => name.endsWith(".lock"));
|
|
@@ -23315,9 +23556,9 @@ var FlavorIdeClient = class {
|
|
|
23315
23556
|
return void 0;
|
|
23316
23557
|
}
|
|
23317
23558
|
const candidates = (await Promise.all(entries.map(async (name) => {
|
|
23318
|
-
const path =
|
|
23559
|
+
const path = join30(directory, name);
|
|
23319
23560
|
try {
|
|
23320
|
-
const [contents, info] = await Promise.all([readFile24(path, "utf8"),
|
|
23561
|
+
const [contents, info] = await Promise.all([readFile24(path, "utf8"), stat11(path)]);
|
|
23321
23562
|
const lock = parseLockfile(contents);
|
|
23322
23563
|
return lock === void 0 ? void 0 : { lock, modified: info.mtimeMs };
|
|
23323
23564
|
} catch {
|
|
@@ -23421,8 +23662,8 @@ function parsePort(value) {
|
|
|
23421
23662
|
return Number.isInteger(port2) && port2 > 0 && port2 <= 65535 ? port2 : void 0;
|
|
23422
23663
|
}
|
|
23423
23664
|
function isWithin8(parent, child) {
|
|
23424
|
-
const base = normalize(
|
|
23425
|
-
const target = normalize(
|
|
23665
|
+
const base = normalize(resolve30(parent));
|
|
23666
|
+
const target = normalize(resolve30(child));
|
|
23426
23667
|
return target === base || target.startsWith(`${base}${sep21}`);
|
|
23427
23668
|
}
|
|
23428
23669
|
function normalize(path) {
|
|
@@ -23576,8 +23817,8 @@ function snapshot2(job) {
|
|
|
23576
23817
|
|
|
23577
23818
|
// src/terminal/service.ts
|
|
23578
23819
|
import { randomUUID as randomUUID20 } from "crypto";
|
|
23579
|
-
import { createRequire as
|
|
23580
|
-
import { isAbsolute as isAbsolute14, relative as relative22, resolve as
|
|
23820
|
+
import { createRequire as nodeCreateRequire2 } from "module";
|
|
23821
|
+
import { isAbsolute as isAbsolute14, relative as relative22, resolve as resolve31, sep as sep22 } from "path";
|
|
23581
23822
|
var TerminalService = class {
|
|
23582
23823
|
#root;
|
|
23583
23824
|
#factory;
|
|
@@ -23585,7 +23826,7 @@ var TerminalService = class {
|
|
|
23585
23826
|
#maxOutputChars;
|
|
23586
23827
|
#sessions = /* @__PURE__ */ new Map();
|
|
23587
23828
|
constructor(workspace, options = {}) {
|
|
23588
|
-
this.#root =
|
|
23829
|
+
this.#root = resolve31(workspace);
|
|
23589
23830
|
this.#factory = options.factory ?? defaultFactory;
|
|
23590
23831
|
this.#jobs = options.jobs;
|
|
23591
23832
|
this.#maxOutputChars = options.maxOutputChars ?? 2e5;
|
|
@@ -23690,7 +23931,7 @@ function snapshot3(record3) {
|
|
|
23690
23931
|
}
|
|
23691
23932
|
function defaultFactory(shell2, args, options) {
|
|
23692
23933
|
try {
|
|
23693
|
-
const pty =
|
|
23934
|
+
const pty = nodeCreateRequire2(import.meta.url)("node-pty");
|
|
23694
23935
|
return pty.spawn(shell2, args, { ...options, name: "xterm-256color" });
|
|
23695
23936
|
} catch (error) {
|
|
23696
23937
|
throw new Error(`Pseudo-terminal backend is unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -23703,7 +23944,7 @@ function cleanEnvironment(environment) {
|
|
|
23703
23944
|
return Object.fromEntries(Object.entries(environment).filter((entry2) => entry2[1] !== void 0));
|
|
23704
23945
|
}
|
|
23705
23946
|
function workspacePath(root, input) {
|
|
23706
|
-
const path =
|
|
23947
|
+
const path = resolve31(root, input);
|
|
23707
23948
|
const delta = relative22(root, path);
|
|
23708
23949
|
if (delta === ".." || delta.startsWith(`..${sep22}`) || isAbsolute14(delta)) throw new Error("Terminal cwd is outside the workspace");
|
|
23709
23950
|
return path;
|
|
@@ -23711,7 +23952,7 @@ function workspacePath(root, input) {
|
|
|
23711
23952
|
|
|
23712
23953
|
// src/tools/terminal.ts
|
|
23713
23954
|
import { z as z40 } from "zod";
|
|
23714
|
-
import { resolve as
|
|
23955
|
+
import { resolve as resolve32 } from "path";
|
|
23715
23956
|
var Id = z40.string().regex(/^term-[a-f0-9-]+$/);
|
|
23716
23957
|
var OpenInput = z40.object({ cwd: z40.string().optional(), shell: z40.string().optional(), args: z40.array(z40.string()).optional(), columns: z40.coerce.number().int().min(10).max(500).optional(), rows: z40.coerce.number().int().min(2).max(300).optional() });
|
|
23717
23958
|
var OptionalBoolean = z40.union([z40.boolean(), z40.string().refine((value) => value === "true" || value === "false")]);
|
|
@@ -23725,8 +23966,8 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
|
|
|
23725
23966
|
name: "TerminalOpen",
|
|
23726
23967
|
description: "Open a persistent interactive pseudo-terminal in the workspace",
|
|
23727
23968
|
inputSchema: OpenInput,
|
|
23728
|
-
paths: (input) => [
|
|
23729
|
-
permissions: (input) => ({ paths: [
|
|
23969
|
+
paths: (input) => [resolve32(workspace, input.cwd ?? ".")],
|
|
23970
|
+
permissions: (input) => ({ paths: [resolve32(workspace, input.cwd ?? ".")], command: input.shell ?? "system-shell", args: input.args ?? [], cwd: resolve32(workspace, input.cwd ?? ".") }),
|
|
23730
23971
|
execute: async (input, _signal, context) => terminals.open({
|
|
23731
23972
|
owner: owner(context),
|
|
23732
23973
|
...input.cwd === void 0 ? {} : { cwd: input.cwd },
|
|
@@ -23822,7 +24063,7 @@ function palSocketAddress(options) {
|
|
|
23822
24063
|
import { createHash as createHash20 } from "crypto";
|
|
23823
24064
|
import { Socket as Socket2 } from "net";
|
|
23824
24065
|
import { tmpdir as tmpdir3 } from "os";
|
|
23825
|
-
import { join as
|
|
24066
|
+
import { join as join32 } from "path";
|
|
23826
24067
|
import { spawn as spawn7 } from "child_process";
|
|
23827
24068
|
|
|
23828
24069
|
// src/pals/broker.ts
|
|
@@ -23835,16 +24076,16 @@ import { dirname as dirname23 } from "path";
|
|
|
23835
24076
|
import { randomBytes as randomBytes3, randomUUID as randomUUID21 } from "crypto";
|
|
23836
24077
|
import { chmod as chmod2, link as link2, mkdir as mkdir21, open as open12, readFile as readFile25, unlink as unlink4 } from "fs/promises";
|
|
23837
24078
|
import { homedir } from "os";
|
|
23838
|
-
import { join as
|
|
24079
|
+
import { join as join31 } from "path";
|
|
23839
24080
|
var PAL_AUTH_TOKEN_BYTES = 32;
|
|
23840
24081
|
var TOKEN_PATTERN = /^[a-f0-9]{64}$/;
|
|
23841
24082
|
async function loadOrCreatePalAuthToken(options = {}) {
|
|
23842
|
-
const directory =
|
|
23843
|
-
const tokenPath =
|
|
24083
|
+
const directory = join31(options.home ?? homedir(), ".flavor-code", "pals");
|
|
24084
|
+
const tokenPath = join31(directory, "auth-token");
|
|
23844
24085
|
await mkdir21(directory, { recursive: true, mode: 448 });
|
|
23845
24086
|
await chmod2(directory, 448);
|
|
23846
24087
|
const token = (options.random ?? randomBytes3)(PAL_AUTH_TOKEN_BYTES).toString("hex");
|
|
23847
|
-
const temporaryPath =
|
|
24088
|
+
const temporaryPath = join31(directory, `.auth-token-${process.pid}-${randomUUID21()}.tmp`);
|
|
23848
24089
|
let handle;
|
|
23849
24090
|
try {
|
|
23850
24091
|
handle = await open12(temporaryPath, "wx", 384);
|
|
@@ -23879,7 +24120,7 @@ function validatePalAuthToken(token) {
|
|
|
23879
24120
|
|
|
23880
24121
|
// src/pals/lifecycle.ts
|
|
23881
24122
|
import { randomUUID as randomUUID22 } from "crypto";
|
|
23882
|
-
import { mkdir as mkdir22, open as open13, readFile as readFile26, rename as rename11, stat as
|
|
24123
|
+
import { mkdir as mkdir22, open as open13, readFile as readFile26, rename as rename11, stat as stat12, unlink as unlink5 } from "fs/promises";
|
|
23883
24124
|
import { dirname as dirname22 } from "path";
|
|
23884
24125
|
function defaultProcessAlive(pid) {
|
|
23885
24126
|
if (!Number.isSafeInteger(pid) || pid <= 0) return true;
|
|
@@ -23911,7 +24152,7 @@ async function acquirePalFileLock(options) {
|
|
|
23911
24152
|
handle = await open13(options.path, "wx", 384);
|
|
23912
24153
|
} catch (error) {
|
|
23913
24154
|
if (error.code !== "EEXIST") throw error;
|
|
23914
|
-
const [contents, info] = await Promise.all([readFile26(options.path, "utf8").catch(() => ""),
|
|
24155
|
+
const [contents, info] = await Promise.all([readFile26(options.path, "utf8").catch(() => ""), stat12(options.path).catch(() => void 0)]);
|
|
23915
24156
|
if (await options.endpointLive()) return void 0;
|
|
23916
24157
|
const existing = parseMetadata(contents);
|
|
23917
24158
|
if (existing !== void 0 && (options.processAlive ?? defaultProcessAlive)(existing.pid)) return void 0;
|
|
@@ -23974,7 +24215,7 @@ function canConnect(address) {
|
|
|
23974
24215
|
}
|
|
23975
24216
|
function startupLockPath(address) {
|
|
23976
24217
|
const digest = createHash20("sha256").update(address, "utf8").digest("hex").slice(0, 24);
|
|
23977
|
-
return
|
|
24218
|
+
return join32(tmpdir3(), `flavor-code-pals-${digest}.startup.lock`);
|
|
23978
24219
|
}
|
|
23979
24220
|
async function spawnProductionBroker(address) {
|
|
23980
24221
|
const entry2 = process.argv[1];
|
|
@@ -24666,8 +24907,8 @@ var ApprovalBridge = class {
|
|
|
24666
24907
|
}
|
|
24667
24908
|
};
|
|
24668
24909
|
async function createProductionRuntime(options) {
|
|
24669
|
-
const workspace =
|
|
24670
|
-
const home =
|
|
24910
|
+
const workspace = resolve33(options.workspace ?? process.cwd());
|
|
24911
|
+
const home = resolve33(options.home ?? homedir2());
|
|
24671
24912
|
const environment = options.environment ?? process.env;
|
|
24672
24913
|
const ide = new FlavorIdeClient({ workspace, home, environment });
|
|
24673
24914
|
const ideReady = ide.initialize();
|
|
@@ -24752,9 +24993,28 @@ async function createProductionRuntime(options) {
|
|
|
24752
24993
|
const recovered = options.resumeSession === void 0 ? void 0 : await sessionStore.load(options.resumeSession === true ? void 0 : options.resumeSession);
|
|
24753
24994
|
let timelineState = recovered === void 0 ? createTranscriptState() : restoreTranscriptState(recovered.timeline.state);
|
|
24754
24995
|
const restoredTranscript = restoreTranscriptState(timelineState);
|
|
24996
|
+
const pendingTimelineStreams = [];
|
|
24997
|
+
const flushTimelineStreams = () => {
|
|
24998
|
+
for (const pending2 of pendingTimelineStreams.splice(0)) {
|
|
24999
|
+
timelineState = transcriptReducer(timelineState, {
|
|
25000
|
+
type: "session",
|
|
25001
|
+
event: { type: pending2.type, text: pending2.chunks.join("") }
|
|
25002
|
+
});
|
|
25003
|
+
}
|
|
25004
|
+
};
|
|
25005
|
+
const recordTimelineEvent = (event) => {
|
|
25006
|
+
if (event.type === "text" || event.type === "thinking") {
|
|
25007
|
+
const last = pendingTimelineStreams.at(-1);
|
|
25008
|
+
if (last?.type === event.type) last.chunks.push(event.text);
|
|
25009
|
+
else pendingTimelineStreams.push({ type: event.type, chunks: [event.text] });
|
|
25010
|
+
return;
|
|
25011
|
+
}
|
|
25012
|
+
flushTimelineStreams();
|
|
25013
|
+
timelineState = transcriptReducer(timelineState, { type: "session", event });
|
|
25014
|
+
};
|
|
24755
25015
|
let ideSessionId;
|
|
24756
25016
|
const emitOutput = (event) => {
|
|
24757
|
-
|
|
25017
|
+
recordTimelineEvent(event);
|
|
24758
25018
|
options.output(event);
|
|
24759
25019
|
if (ideSessionId !== void 0) ide.publishEvent(ideSessionId, event);
|
|
24760
25020
|
};
|
|
@@ -24891,8 +25151,8 @@ async function createProductionRuntime(options) {
|
|
|
24891
25151
|
const registeredProviders = registration.registered;
|
|
24892
25152
|
let effectiveLlm = registration.effectiveLlm;
|
|
24893
25153
|
const pluginHost = new PluginHost({
|
|
24894
|
-
globalPluginDirs: [
|
|
24895
|
-
projectPluginDirs: [
|
|
25154
|
+
globalPluginDirs: [join33(home, ".flavor-code", "plugins")],
|
|
25155
|
+
projectPluginDirs: [join33(workspace, ".flavor", "plugins")],
|
|
24896
25156
|
config,
|
|
24897
25157
|
// Existing project plugins (including astgraph and superharness) use Node.js
|
|
24898
25158
|
// built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
|
|
@@ -25000,7 +25260,7 @@ async function createProductionRuntime(options) {
|
|
|
25000
25260
|
const address = palSocketAddress({
|
|
25001
25261
|
platform: process.platform,
|
|
25002
25262
|
userScope: home,
|
|
25003
|
-
runtimeDir:
|
|
25263
|
+
runtimeDir: join33(tmpdir4(), `flavor-code-pals-${createHash21("sha256").update(home).digest("hex").slice(0, 16)}`)
|
|
25004
25264
|
});
|
|
25005
25265
|
const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
|
|
25006
25266
|
instanceId: collaboration.instanceId,
|
|
@@ -25111,8 +25371,8 @@ async function createProductionRuntime(options) {
|
|
|
25111
25371
|
diagnostics.push(`MCP servers could not start: ${message(error)}`);
|
|
25112
25372
|
});
|
|
25113
25373
|
const skills = new SkillRegistry({
|
|
25114
|
-
globalRoots: [
|
|
25115
|
-
projectRoots: [
|
|
25374
|
+
globalRoots: [join33(home, ".flavor-code", "skills")],
|
|
25375
|
+
projectRoots: [join33(workspace, ".flavor", "skills"), ...pluginSkillRoots],
|
|
25116
25376
|
authorizeResource: async () => true,
|
|
25117
25377
|
disabledNames: config.skills.disabled
|
|
25118
25378
|
});
|
|
@@ -25120,7 +25380,7 @@ async function createProductionRuntime(options) {
|
|
|
25120
25380
|
skillsReady.catch(() => void 0);
|
|
25121
25381
|
tools.push(createSkillTool(skills), createSkillResourceTool(skills));
|
|
25122
25382
|
if (options.extraTools !== void 0) tools.push(...options.extraTools);
|
|
25123
|
-
const flavor = await optionalText2(
|
|
25383
|
+
const flavor = await optionalText2(join33(workspace, "FLAVOR.md"));
|
|
25124
25384
|
const instructionBaseline = await workspaceInstructions.baseline();
|
|
25125
25385
|
let memoryContext;
|
|
25126
25386
|
if (memoryStore !== void 0) {
|
|
@@ -25171,27 +25431,30 @@ async function createProductionRuntime(options) {
|
|
|
25171
25431
|
};
|
|
25172
25432
|
}
|
|
25173
25433
|
let persistTail = Promise.resolve();
|
|
25174
|
-
const sessionDocument = () =>
|
|
25175
|
-
|
|
25176
|
-
|
|
25177
|
-
|
|
25178
|
-
|
|
25179
|
-
|
|
25180
|
-
|
|
25181
|
-
|
|
25182
|
-
|
|
25183
|
-
|
|
25184
|
-
|
|
25185
|
-
|
|
25186
|
-
|
|
25187
|
-
|
|
25188
|
-
|
|
25189
|
-
|
|
25190
|
-
|
|
25191
|
-
|
|
25192
|
-
|
|
25193
|
-
|
|
25194
|
-
|
|
25434
|
+
const sessionDocument = () => {
|
|
25435
|
+
flushTimelineStreams();
|
|
25436
|
+
return {
|
|
25437
|
+
version: SESSION_VERSION,
|
|
25438
|
+
sessionId,
|
|
25439
|
+
createdAt,
|
|
25440
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
25441
|
+
workspace: { path: workspace },
|
|
25442
|
+
conversation: storedConversation(harness.main.context.snapshot()),
|
|
25443
|
+
tasks: {
|
|
25444
|
+
...taskPlan === void 0 ? {} : { plan: taskPlan },
|
|
25445
|
+
...taskGraph === void 0 ? {} : { graph: taskGraph },
|
|
25446
|
+
states: { ...taskStates },
|
|
25447
|
+
results: { ...taskResults }
|
|
25448
|
+
},
|
|
25449
|
+
// Persist the resolved decision, not the harness snapshot: after logout the
|
|
25450
|
+
// harness may still hold the old login model, and resuming from that stale
|
|
25451
|
+
// value is what made the welcome card show a previous service.
|
|
25452
|
+
models: { main: mainModel, subagent: childModel },
|
|
25453
|
+
permissionMode: harness.permissionMode,
|
|
25454
|
+
memory: memoryLifecycle,
|
|
25455
|
+
timeline: { version: 1, state: timelineState }
|
|
25456
|
+
};
|
|
25457
|
+
};
|
|
25195
25458
|
let persistFailed = false;
|
|
25196
25459
|
const persist = () => {
|
|
25197
25460
|
persistTail = persistTail.catch(() => void 0).then(
|
|
@@ -25698,7 +25961,7 @@ async function createProductionRuntime(options) {
|
|
|
25698
25961
|
evolveService.toolDefinition(),
|
|
25699
25962
|
...mcpTools
|
|
25700
25963
|
];
|
|
25701
|
-
const loopFlavor = await optionalText2(
|
|
25964
|
+
const loopFlavor = await optionalText2(join33(input.workspace, "FLAVOR.md"));
|
|
25702
25965
|
const loopInstructionBaseline = await loopInstructions.baseline();
|
|
25703
25966
|
const loopEnvironment = createPromptEnvironment({
|
|
25704
25967
|
now: /* @__PURE__ */ new Date(),
|
|
@@ -26058,6 +26321,22 @@ async function createProductionRuntime(options) {
|
|
|
26058
26321
|
modelId: () => harness.subagentModelId,
|
|
26059
26322
|
notify: (message2) => emitOutput({ type: "notice", message: message2 })
|
|
26060
26323
|
}, focus, signal),
|
|
26324
|
+
explain: (query, focus, signal, onText) => runExplain({
|
|
26325
|
+
graph: new DesktopAstGraphService(workspace),
|
|
26326
|
+
readFile: async (path) => {
|
|
26327
|
+
const root = resolve33(workspace);
|
|
26328
|
+
const abs = resolve33(root, path);
|
|
26329
|
+
if (abs !== root && !abs.startsWith(root + sep23)) throw new Error(`path outside workspace: ${path}`);
|
|
26330
|
+
return readFile27(abs, "utf8");
|
|
26331
|
+
},
|
|
26332
|
+
history: async (path, limit) => fileHistory(workspace, path, limit),
|
|
26333
|
+
questions,
|
|
26334
|
+
registry,
|
|
26335
|
+
modelId: () => harness.subagentModelId,
|
|
26336
|
+
language,
|
|
26337
|
+
notify: (message2) => emitOutput({ type: "notice", message: message2 }),
|
|
26338
|
+
...onText === void 0 ? {} : { onText }
|
|
26339
|
+
}, query, focus, signal),
|
|
26061
26340
|
audit: async (toolFilter) => {
|
|
26062
26341
|
try {
|
|
26063
26342
|
const raw = await readFile27(auditLogger.path, "utf8");
|
|
@@ -26247,7 +26526,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
26247
26526
|
return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
|
|
26248
26527
|
}
|
|
26249
26528
|
try {
|
|
26250
|
-
const tokenStore = createFileTokenStore(
|
|
26529
|
+
const tokenStore = createFileTokenStore(join33(home, ".flavor-code", "auth.json"));
|
|
26251
26530
|
const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
|
|
26252
26531
|
await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
|
|
26253
26532
|
const oauth = new OAuthCallbackAuthProvider({
|
|
@@ -26294,7 +26573,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
26294
26573
|
}
|
|
26295
26574
|
},
|
|
26296
26575
|
async logout() {
|
|
26297
|
-
const tokenStore = createFileTokenStore(
|
|
26576
|
+
const tokenStore = createFileTokenStore(join33(home, ".flavor-code", "auth.json"));
|
|
26298
26577
|
const tokens2 = await tokenStore.load();
|
|
26299
26578
|
const hadCredentials = Object.keys(tokens2).length > 0 || effectiveLlm !== void 0;
|
|
26300
26579
|
if (!hadCredentials) {
|
|
@@ -26570,8 +26849,8 @@ async function workspaceFingerprint(workspace) {
|
|
|
26570
26849
|
let remaining = 5 * 1024 * 1024;
|
|
26571
26850
|
for (const name of untracked.stdout.split("\0").filter(Boolean).sort()) {
|
|
26572
26851
|
if (name === ".flavor/loops" || name.startsWith(".flavor/loops/")) continue;
|
|
26573
|
-
const path =
|
|
26574
|
-
const relativePath = relative23(
|
|
26852
|
+
const path = resolve33(workspace, name);
|
|
26853
|
+
const relativePath = relative23(resolve33(workspace), path);
|
|
26575
26854
|
if (relativePath === ".." || relativePath.startsWith(`..${sep23}`)) continue;
|
|
26576
26855
|
hash.update(name);
|
|
26577
26856
|
if (remaining <= 0) continue;
|
|
@@ -26789,7 +27068,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
|
|
|
26789
27068
|
const configured = { ...providers };
|
|
26790
27069
|
if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
|
|
26791
27070
|
if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
|
|
26792
|
-
const oauthTokenStore = createFileTokenStore(
|
|
27071
|
+
const oauthTokenStore = createFileTokenStore(join33(home, ".flavor-code", "auth.json"));
|
|
26793
27072
|
const registered = [];
|
|
26794
27073
|
let effectiveLlm;
|
|
26795
27074
|
for (const [name, provider] of Object.entries(configured)) {
|
|
@@ -27220,13 +27499,13 @@ function createMemoryTaskId() {
|
|
|
27220
27499
|
|
|
27221
27500
|
// src/desktop/runtime-controller.ts
|
|
27222
27501
|
import { createHash as createHash27 } from "crypto";
|
|
27223
|
-
import { mkdir as mkdir27, readFile as readFile35, readdir as readdir12, rm as rm15, stat as
|
|
27502
|
+
import { mkdir as mkdir27, readFile as readFile35, readdir as readdir12, rm as rm15, stat as stat15, writeFile as writeFile17 } from "fs/promises";
|
|
27224
27503
|
import { homedir as homedir4 } from "os";
|
|
27225
|
-
import { basename as basename9, join as
|
|
27504
|
+
import { basename as basename9, join as join43, resolve as resolve36 } from "path";
|
|
27226
27505
|
|
|
27227
27506
|
// src/d2c/workflow.ts
|
|
27228
27507
|
import { createHash as createHash22 } from "crypto";
|
|
27229
|
-
import { join as
|
|
27508
|
+
import { join as join34 } from "path";
|
|
27230
27509
|
import { z as z42 } from "zod";
|
|
27231
27510
|
|
|
27232
27511
|
// src/d2c/workflow-shared.ts
|
|
@@ -27372,7 +27651,7 @@ function reviewsFor(report, previous) {
|
|
|
27372
27651
|
function implementationDirectory(report) {
|
|
27373
27652
|
const source = report.implementation.source;
|
|
27374
27653
|
if (!/^https?:\/\//i.test(source)) return source;
|
|
27375
|
-
return
|
|
27654
|
+
return join34("src", "d2c-output", report.task);
|
|
27376
27655
|
}
|
|
27377
27656
|
function createWorkflow(report, framework) {
|
|
27378
27657
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -27496,7 +27775,7 @@ function applyQualityIssueDecision(workflow, issueId2, decision) {
|
|
|
27496
27775
|
};
|
|
27497
27776
|
}
|
|
27498
27777
|
function workflowPath(workspace, task) {
|
|
27499
|
-
return
|
|
27778
|
+
return join34(taskDir(workspace, task), "workflow.json");
|
|
27500
27779
|
}
|
|
27501
27780
|
async function readWorkflow(workspace, task) {
|
|
27502
27781
|
return (await readRecoverableFile(workflowPath(workspace, task), decodeWorkflow))?.value;
|
|
@@ -27750,7 +28029,7 @@ function confirmApiMapping(mapping, operationKey, operations) {
|
|
|
27750
28029
|
|
|
27751
28030
|
// src/d2c/modules.ts
|
|
27752
28031
|
import { readFile as readFile28 } from "fs/promises";
|
|
27753
|
-
import { isAbsolute as isAbsolute15, join as
|
|
28032
|
+
import { isAbsolute as isAbsolute15, join as join35, normalize as normalize2, posix, sep as sep24, win32 as win322 } from "path";
|
|
27754
28033
|
import { z as z43 } from "zod";
|
|
27755
28034
|
var ModuleSchema = z43.object({
|
|
27756
28035
|
id: z43.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
|
|
@@ -27769,13 +28048,13 @@ function safeSource(path) {
|
|
|
27769
28048
|
}
|
|
27770
28049
|
function d2cOutputDirectory(workspace, task) {
|
|
27771
28050
|
if (!D2C_TASK_PATTERN.test(task)) throw new Error(`Invalid D2C task: ${task}`);
|
|
27772
|
-
return
|
|
28051
|
+
return join35(workspace, "src", "d2c-output", task);
|
|
27773
28052
|
}
|
|
27774
28053
|
async function readD2cModules(workspace, task) {
|
|
27775
28054
|
const project = d2cOutputDirectory(workspace, task);
|
|
27776
28055
|
let raw;
|
|
27777
28056
|
try {
|
|
27778
|
-
raw = await readFile28(
|
|
28057
|
+
raw = await readFile28(join35(project, "d2c.modules.json"), "utf8");
|
|
27779
28058
|
} catch {
|
|
27780
28059
|
return [{ id: "page", label: task, sourceFiles: ["src/App.vue", "src/App.jsx"], keywords: [task] }];
|
|
27781
28060
|
}
|
|
@@ -27796,7 +28075,7 @@ async function readD2cModules(workspace, task) {
|
|
|
27796
28075
|
|
|
27797
28076
|
// src/d2c/integration.ts
|
|
27798
28077
|
import { mkdir as mkdir24, readFile as readFile29, writeFile as writeFile13 } from "fs/promises";
|
|
27799
|
-
import { join as
|
|
28078
|
+
import { join as join36 } from "path";
|
|
27800
28079
|
function record2(value) {
|
|
27801
28080
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
27802
28081
|
}
|
|
@@ -27932,7 +28211,7 @@ async def reset_acceptance(request: Request):
|
|
|
27932
28211
|
}
|
|
27933
28212
|
async function generateIntegrationArtifacts(projectDir, document, mappings, options = {}) {
|
|
27934
28213
|
if (mappings.some((mapping) => mapping.status === "needs-confirmation")) throw new Error("Confirm every uncertain OpenAPI mapping before generating integration code");
|
|
27935
|
-
const packagePath =
|
|
28214
|
+
const packagePath = join36(projectDir, "package.json");
|
|
27936
28215
|
let pkg;
|
|
27937
28216
|
try {
|
|
27938
28217
|
pkg = JSON.parse(await readFile29(packagePath, "utf8"));
|
|
@@ -27942,9 +28221,9 @@ async function generateIntegrationArtifacts(projectDir, document, mappings, opti
|
|
|
27942
28221
|
const scripts = options.pythonServer ? { ...record2(pkg.scripts) ?? {} } : { ...record2(pkg.scripts) ?? {}, mock: "node mock/server.mjs" };
|
|
27943
28222
|
const dependencies2 = options.pythonServer ? { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9" } : { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9", express: "^5.2.1" };
|
|
27944
28223
|
const selected = [...new Set(mappings.map((mapping) => mapping.operationKey))].map((key) => document.operations.find((operation) => operation.id === key)).filter((operation) => operation !== void 0);
|
|
27945
|
-
const apiDir =
|
|
27946
|
-
const mockDir =
|
|
27947
|
-
const serverDir =
|
|
28224
|
+
const apiDir = join36(projectDir, "src", "api");
|
|
28225
|
+
const mockDir = join36(projectDir, "mock");
|
|
28226
|
+
const serverDir = join36(projectDir, "server");
|
|
27948
28227
|
await Promise.all([
|
|
27949
28228
|
mkdir24(apiDir, { recursive: true }),
|
|
27950
28229
|
...options.pythonServer ? [] : [mkdir24(mockDir, { recursive: true })],
|
|
@@ -27954,23 +28233,23 @@ async function generateIntegrationArtifacts(projectDir, document, mappings, opti
|
|
|
27954
28233
|
if (options.pythonServer) files.push("server/main.py", "server/requirements.txt", "server/README.md", "server/flavor-runtime.json");
|
|
27955
28234
|
else files.push("mock/server.mjs");
|
|
27956
28235
|
await Promise.all([
|
|
27957
|
-
writeFile13(
|
|
28236
|
+
writeFile13(join36(apiDir, "http.js"), `import axios from "axios";
|
|
27958
28237
|
|
|
27959
28238
|
export const http = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || ${JSON.stringify(document.baseUrl ?? "")}, timeout: 15000 });
|
|
27960
28239
|
|
|
27961
28240
|
http.interceptors.response.use((response) => response, (error) => Promise.reject({ name: "ApiError", status: error.response?.status, message: error.response?.data?.message || error.message, cause: error }));
|
|
27962
28241
|
`),
|
|
27963
|
-
writeFile13(
|
|
27964
|
-
writeFile13(
|
|
28242
|
+
writeFile13(join36(apiDir, "d2c-api.js"), apiModule(selected)),
|
|
28243
|
+
writeFile13(join36(apiDir, "d2c-bindings.json"), `${JSON.stringify({ schema: 1, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), mappings }, null, 2)}
|
|
27965
28244
|
`),
|
|
27966
|
-
...options.pythonServer ? [] : [writeFile13(
|
|
28245
|
+
...options.pythonServer ? [] : [writeFile13(join36(mockDir, "server.mjs"), mockModule({ ...document, operations: selected }))],
|
|
27967
28246
|
writeFile13(packagePath, `${JSON.stringify({ ...pkg, scripts, dependencies: dependencies2 }, null, 2)}
|
|
27968
28247
|
`),
|
|
27969
28248
|
...options.pythonServer ? [
|
|
27970
|
-
writeFile13(
|
|
27971
|
-
writeFile13(
|
|
27972
|
-
writeFile13(
|
|
27973
|
-
writeFile13(
|
|
28249
|
+
writeFile13(join36(serverDir, "main.py"), pythonServerModule({ ...document, operations: selected })),
|
|
28250
|
+
writeFile13(join36(serverDir, "requirements.txt"), "fastapi>=0.116,<1\nuvicorn[standard]>=0.35,<1\nsqlalchemy>=2.0,<3\n"),
|
|
28251
|
+
writeFile13(join36(serverDir, "README.md"), "# Python API service\n\nThis is the real integration service, not a mock. Development defaults to SQLite through `DATABASE_URL=sqlite:///./data/app.db`. Set the same environment variable to a SQLAlchemy MySQL or PostgreSQL URL for deployment; keep schema changes in migrations instead of database-specific SQL. Flavor Code starts the service automatically for acceptance.\n"),
|
|
28252
|
+
writeFile13(join36(serverDir, "flavor-runtime.json"), `${JSON.stringify({
|
|
27974
28253
|
schema: 1,
|
|
27975
28254
|
kind: "python-fastapi",
|
|
27976
28255
|
cwd: "server",
|
|
@@ -27991,7 +28270,7 @@ http.interceptors.response.use((response) => response, (error) => Promise.reject
|
|
|
27991
28270
|
import { spawn as spawn8 } from "child_process";
|
|
27992
28271
|
import { access as access3, readFile as readFile30 } from "fs/promises";
|
|
27993
28272
|
import { createServer as createServer5 } from "net";
|
|
27994
|
-
import { join as
|
|
28273
|
+
import { join as join37 } from "path";
|
|
27995
28274
|
async function exists3(path) {
|
|
27996
28275
|
try {
|
|
27997
28276
|
await access3(path);
|
|
@@ -28099,7 +28378,7 @@ ${output.trim()}` : ""}`);
|
|
|
28099
28378
|
}
|
|
28100
28379
|
}
|
|
28101
28380
|
async function runPythonBackend(projectDir, options) {
|
|
28102
|
-
const serverDir =
|
|
28381
|
+
const serverDir = join37(projectDir, "server");
|
|
28103
28382
|
if (options.installDependencies !== false) await installPython(serverDir, options.signal);
|
|
28104
28383
|
const selectedPort = await port();
|
|
28105
28384
|
const url = `http://127.0.0.1:${selectedPort}`;
|
|
@@ -28145,7 +28424,7 @@ ${managed.output().slice(-4e3)}`);
|
|
|
28145
28424
|
};
|
|
28146
28425
|
}
|
|
28147
28426
|
async function runD2cMockServer(projectDir, options = {}) {
|
|
28148
|
-
const runtimePath =
|
|
28427
|
+
const runtimePath = join37(projectDir, "server", "flavor-runtime.json");
|
|
28149
28428
|
if (await exists3(runtimePath)) {
|
|
28150
28429
|
let runtime;
|
|
28151
28430
|
try {
|
|
@@ -28156,9 +28435,9 @@ async function runD2cMockServer(projectDir, options = {}) {
|
|
|
28156
28435
|
if (runtime.schema !== 1 || runtime.kind !== "python-fastapi") throw new Error("Unsupported E2E backend runtime manifest");
|
|
28157
28436
|
return runPythonBackend(projectDir, options);
|
|
28158
28437
|
}
|
|
28159
|
-
const serverPath =
|
|
28438
|
+
const serverPath = join37(projectDir, "mock", "server.mjs");
|
|
28160
28439
|
if (!await exists3(serverPath)) throw new Error(`D2C mock server is not generated at ${serverPath}`);
|
|
28161
|
-
if (options.installDependencies === false && !await exists3(
|
|
28440
|
+
if (options.installDependencies === false && !await exists3(join37(projectDir, "node_modules", "express", "package.json"))) {
|
|
28162
28441
|
throw new Error("D2C mock server dependencies are not installed");
|
|
28163
28442
|
}
|
|
28164
28443
|
if (options.installDependencies !== false) await install(projectDir, options.signal);
|
|
@@ -28200,8 +28479,8 @@ ${managed.output().slice(-4e3)}`);
|
|
|
28200
28479
|
|
|
28201
28480
|
// src/d2c/product.ts
|
|
28202
28481
|
import { createHash as createHash24, randomUUID as randomUUID27 } from "crypto";
|
|
28203
|
-
import { copyFile as copyFile2, mkdir as mkdir25, readFile as readFile32, rename as rename13, stat as
|
|
28204
|
-
import { join as
|
|
28482
|
+
import { copyFile as copyFile2, mkdir as mkdir25, readFile as readFile32, rename as rename13, stat as stat13, unlink as unlink7, writeFile as writeFile15 } from "fs/promises";
|
|
28483
|
+
import { join as join38 } from "path";
|
|
28205
28484
|
import { z as z44 } from "zod";
|
|
28206
28485
|
|
|
28207
28486
|
// src/e2e/prd-governance.ts
|
|
@@ -28356,13 +28635,13 @@ var PlanSchema2 = z44.object({
|
|
|
28356
28635
|
}).strict();
|
|
28357
28636
|
function d2cProductDirectory(workspace, task) {
|
|
28358
28637
|
if (!D2C_TASK_PATTERN.test(task)) throw new Error("Invalid D2C task name");
|
|
28359
|
-
return
|
|
28638
|
+
return join38(taskDir(workspace, task), "product");
|
|
28360
28639
|
}
|
|
28361
28640
|
function d2cProductPrototypeDirectory(workspace, task) {
|
|
28362
|
-
return
|
|
28641
|
+
return join38(d2cProductDirectory(workspace, task), "prototype");
|
|
28363
28642
|
}
|
|
28364
28643
|
function planPath(workspace, task) {
|
|
28365
|
-
return
|
|
28644
|
+
return join38(taskDir(workspace, task), "product-plan.json");
|
|
28366
28645
|
}
|
|
28367
28646
|
function resolveD2cProductTechnology(requirement, fallbackFramework = "vue") {
|
|
28368
28647
|
const frontendCandidates = [
|
|
@@ -28450,7 +28729,7 @@ function buildD2cProductOpenApi(task, modules) {
|
|
|
28450
28729
|
`;
|
|
28451
28730
|
}
|
|
28452
28731
|
async function fileTimestamp(path) {
|
|
28453
|
-
const info = await
|
|
28732
|
+
const info = await stat13(path).catch(() => void 0);
|
|
28454
28733
|
return info?.isFile() ? info.mtime.toISOString() : void 0;
|
|
28455
28734
|
}
|
|
28456
28735
|
async function fileHash(path) {
|
|
@@ -28545,16 +28824,16 @@ async function readD2cProductPlan(workspace, task) {
|
|
|
28545
28824
|
}
|
|
28546
28825
|
async function discoverD2cProductArtifacts(workspace, plan) {
|
|
28547
28826
|
const product = d2cProductDirectory(workspace, plan.task);
|
|
28548
|
-
const prdUpdatedAt = await fileTimestamp(
|
|
28549
|
-
const prdHash = await fileHash(
|
|
28550
|
-
const prototypeUpdatedAt = await fileTimestamp(
|
|
28551
|
-
const prototypeHash = await fileHash(
|
|
28552
|
-
const expectedManifest =
|
|
28827
|
+
const prdUpdatedAt = await fileTimestamp(join38(product, "prd.md"));
|
|
28828
|
+
const prdHash = await fileHash(join38(product, "prd.md"));
|
|
28829
|
+
const prototypeUpdatedAt = await fileTimestamp(join38(product, "prototype", "index.html"));
|
|
28830
|
+
const prototypeHash = await fileHash(join38(product, "prototype", "index.html"));
|
|
28831
|
+
const expectedManifest = join38(product, "prototype", "interaction-manifest.json");
|
|
28553
28832
|
let manifestUpdatedAt = await fileTimestamp(expectedManifest);
|
|
28554
28833
|
if (manifestUpdatedAt === void 0) {
|
|
28555
|
-
const misplacedManifest =
|
|
28834
|
+
const misplacedManifest = join38(product, "interaction-manifest.json");
|
|
28556
28835
|
if (await fileTimestamp(misplacedManifest) !== void 0) {
|
|
28557
|
-
await mkdir25(
|
|
28836
|
+
await mkdir25(join38(product, "prototype"), { recursive: true });
|
|
28558
28837
|
await copyFile2(misplacedManifest, expectedManifest);
|
|
28559
28838
|
manifestUpdatedAt = await fileTimestamp(expectedManifest);
|
|
28560
28839
|
}
|
|
@@ -28587,8 +28866,8 @@ async function readD2cProductPlanView(workspace, task) {
|
|
|
28587
28866
|
if (stored === void 0) return void 0;
|
|
28588
28867
|
const plan = await discoverD2cProductArtifacts(workspace, stored);
|
|
28589
28868
|
if (plan.revision !== stored.revision) await writeD2cProductPlan(workspace, plan);
|
|
28590
|
-
const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile32(
|
|
28591
|
-
const manifestPath =
|
|
28869
|
+
const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile32(join38(taskDir(workspace, task), plan.prd.path), "utf8");
|
|
28870
|
+
const manifestPath = join38(d2cProductDirectory(workspace, task), "prototype", "interaction-manifest.json");
|
|
28592
28871
|
const validationError = plan.phase === "design-generating" && await fileTimestamp(manifestPath) !== void 0 ? await validateAndNormalizePrototypeManifest(manifestPath) : void 0;
|
|
28593
28872
|
return {
|
|
28594
28873
|
plan,
|
|
@@ -28747,7 +29026,7 @@ function createAcceptanceEvidence(coverage, run, now = /* @__PURE__ */ new Date(
|
|
|
28747
29026
|
|
|
28748
29027
|
// src/e2e/delivery-run.ts
|
|
28749
29028
|
import { createHash as createHash26 } from "crypto";
|
|
28750
|
-
import { join as
|
|
29029
|
+
import { join as join39 } from "path";
|
|
28751
29030
|
import { z as z45 } from "zod";
|
|
28752
29031
|
var DELIVERY_NODE_IDS = ["requirement", "prd", "design", "d2c", "api", "acceptance", "delivery"];
|
|
28753
29032
|
var ArtifactSchema = z45.object({
|
|
@@ -28864,7 +29143,7 @@ function completeDeliveryNode(run, node, outputs, now = /* @__PURE__ */ new Date
|
|
|
28864
29143
|
return { ...run, nodes, updatedAt: timestamp2 };
|
|
28865
29144
|
}
|
|
28866
29145
|
function deliveryRunPath(workspace, task) {
|
|
28867
|
-
return
|
|
29146
|
+
return join39(taskDir(workspace, task), "delivery-run.json");
|
|
28868
29147
|
}
|
|
28869
29148
|
function decodeDeliveryRun(raw) {
|
|
28870
29149
|
return DeliveryRunSchema.parse(JSON.parse(raw));
|
|
@@ -28907,14 +29186,14 @@ async function updateDeliveryRun(workspace, task, expectedRevision, mutation) {
|
|
|
28907
29186
|
// src/skills/manager.ts
|
|
28908
29187
|
import { lstat as lstat13, mkdir as mkdir26, readFile as readFile34, readdir as readdir11, realpath as realpath13, rm as rm14, writeFile as writeFile16 } from "fs/promises";
|
|
28909
29188
|
import { homedir as homedir3 } from "os";
|
|
28910
|
-
import { isAbsolute as isAbsolute16, join as
|
|
29189
|
+
import { isAbsolute as isAbsolute16, join as join40, relative as relative24, resolve as resolve34, sep as sep25 } from "path";
|
|
28911
29190
|
import { stringify } from "yaml";
|
|
28912
29191
|
var SkillManager = class {
|
|
28913
29192
|
#workspace;
|
|
28914
29193
|
#home;
|
|
28915
29194
|
constructor(options) {
|
|
28916
|
-
this.#workspace =
|
|
28917
|
-
this.#home =
|
|
29195
|
+
this.#workspace = resolve34(options.workspace);
|
|
29196
|
+
this.#home = resolve34(options.home ?? homedir3());
|
|
28918
29197
|
}
|
|
28919
29198
|
async list() {
|
|
28920
29199
|
const { registry, disabled } = await this.#registry();
|
|
@@ -28930,7 +29209,7 @@ var SkillManager = class {
|
|
|
28930
29209
|
async create(draft) {
|
|
28931
29210
|
const value = parseDraft(draft);
|
|
28932
29211
|
const projectRoot = this.#projectRoot();
|
|
28933
|
-
const directory =
|
|
29212
|
+
const directory = join40(projectRoot, value.name);
|
|
28934
29213
|
await mkdir26(projectRoot, { recursive: true, mode: 448 });
|
|
28935
29214
|
try {
|
|
28936
29215
|
await mkdir26(directory, { mode: 448 });
|
|
@@ -28939,7 +29218,7 @@ var SkillManager = class {
|
|
|
28939
29218
|
throw error;
|
|
28940
29219
|
}
|
|
28941
29220
|
try {
|
|
28942
|
-
await writeFile16(
|
|
29221
|
+
await writeFile16(join40(directory, "SKILL.md"), renderSkill(value), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
28943
29222
|
await this.#persistEnabled(value.name, true);
|
|
28944
29223
|
return this.get(value.name);
|
|
28945
29224
|
} catch (error) {
|
|
@@ -28957,7 +29236,7 @@ var SkillManager = class {
|
|
|
28957
29236
|
throw error;
|
|
28958
29237
|
});
|
|
28959
29238
|
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`Project skill path is not a real directory: ${validName}`);
|
|
28960
|
-
const skillFile =
|
|
29239
|
+
const skillFile = join40(directory, "SKILL.md");
|
|
28961
29240
|
const fileInfo = await lstat13(skillFile);
|
|
28962
29241
|
if (!fileInfo.isFile() || fileInfo.isSymbolicLink()) throw new Error(`Project skill file is not a real file: ${validName}`);
|
|
28963
29242
|
await writeFile16(skillFile, renderSkill(value), { encoding: "utf8", mode: 384 });
|
|
@@ -28989,12 +29268,12 @@ var SkillManager = class {
|
|
|
28989
29268
|
async #registry() {
|
|
28990
29269
|
const loaded = await loadConfig({ cwd: this.#workspace, home: this.#home });
|
|
28991
29270
|
const [globalPluginRoots, projectPluginRoots] = await Promise.all([
|
|
28992
|
-
discoverPluginSkillRoots(
|
|
28993
|
-
discoverPluginSkillRoots(
|
|
29271
|
+
discoverPluginSkillRoots(join40(this.#home, ".flavor-code", "plugins")),
|
|
29272
|
+
discoverPluginSkillRoots(join40(this.#workspace, ".flavor", "plugins"))
|
|
28994
29273
|
]);
|
|
28995
29274
|
return {
|
|
28996
29275
|
registry: new SkillRegistry({
|
|
28997
|
-
globalRoots: [
|
|
29276
|
+
globalRoots: [join40(this.#home, ".flavor-code", "skills"), ...globalPluginRoots],
|
|
28998
29277
|
projectRoots: [this.#projectRoot(), ...projectPluginRoots]
|
|
28999
29278
|
}),
|
|
29000
29279
|
disabled: new Set(loaded.config.skills.disabled)
|
|
@@ -29010,10 +29289,10 @@ var SkillManager = class {
|
|
|
29010
29289
|
};
|
|
29011
29290
|
}
|
|
29012
29291
|
#projectRoot() {
|
|
29013
|
-
return
|
|
29292
|
+
return join40(this.#workspace, ".flavor", "skills");
|
|
29014
29293
|
}
|
|
29015
29294
|
#projectSkillDirectory(name) {
|
|
29016
|
-
return
|
|
29295
|
+
return join40(this.#projectRoot(), name);
|
|
29017
29296
|
}
|
|
29018
29297
|
};
|
|
29019
29298
|
function parseName(value) {
|
|
@@ -29054,11 +29333,11 @@ async function discoverPluginSkillRoots(directory) {
|
|
|
29054
29333
|
const roots = [];
|
|
29055
29334
|
for (const entry2 of entries) {
|
|
29056
29335
|
if (!entry2.isDirectory() || entry2.isSymbolicLink()) continue;
|
|
29057
|
-
const pluginRoot = await realpath13(
|
|
29336
|
+
const pluginRoot = await realpath13(join40(directory, entry2.name));
|
|
29058
29337
|
try {
|
|
29059
|
-
const manifest = PluginManifestSchema.parse(JSON.parse(await readFile34(
|
|
29338
|
+
const manifest = PluginManifestSchema.parse(JSON.parse(await readFile34(join40(pluginRoot, "flavor-plugin.json"), "utf8")));
|
|
29060
29339
|
for (const contribution of manifest.contributes.skillRoots) {
|
|
29061
|
-
const candidate =
|
|
29340
|
+
const candidate = resolve34(pluginRoot, contribution.path);
|
|
29062
29341
|
const info = await lstat13(candidate);
|
|
29063
29342
|
if (!info.isDirectory() || info.isSymbolicLink()) continue;
|
|
29064
29343
|
const physical = await realpath13(candidate);
|
|
@@ -29131,11 +29410,11 @@ async function createProjectMemoryManager(options) {
|
|
|
29131
29410
|
}
|
|
29132
29411
|
|
|
29133
29412
|
// src/mcp/config-manager.ts
|
|
29134
|
-
import { join as
|
|
29413
|
+
import { join as join41 } from "path";
|
|
29135
29414
|
var ProjectMcpConfigManager = class {
|
|
29136
29415
|
path;
|
|
29137
29416
|
constructor(workspace) {
|
|
29138
|
-
this.path =
|
|
29417
|
+
this.path = join41(workspace, ".flavor", "flavor.json");
|
|
29139
29418
|
}
|
|
29140
29419
|
async list() {
|
|
29141
29420
|
const document = await readRecoverableFile(this.path, (raw) => parseDocument2(this.path, raw));
|
|
@@ -29232,7 +29511,7 @@ function isRecord7(value) {
|
|
|
29232
29511
|
}
|
|
29233
29512
|
|
|
29234
29513
|
// src/desktop/model-config.ts
|
|
29235
|
-
import { join as
|
|
29514
|
+
import { join as join42 } from "path";
|
|
29236
29515
|
var DEFAULT_DESKTOP_MODELS = [
|
|
29237
29516
|
{
|
|
29238
29517
|
id: "anthropic:deepseek-v4-pro",
|
|
@@ -29253,7 +29532,7 @@ var DEFAULT_DESKTOP_MODELS = [
|
|
|
29253
29532
|
];
|
|
29254
29533
|
async function loadDesktopModels(workspace, home) {
|
|
29255
29534
|
try {
|
|
29256
|
-
const tokens2 = await createFileTokenStore(
|
|
29535
|
+
const tokens2 = await createFileTokenStore(join42(home, ".flavor-code", "auth.json")).load();
|
|
29257
29536
|
const pkceModels = [];
|
|
29258
29537
|
const knownPkce = /* @__PURE__ */ new Set();
|
|
29259
29538
|
for (const token of Object.values(tokens2)) {
|
|
@@ -29321,9 +29600,9 @@ function protocolLabel(type) {
|
|
|
29321
29600
|
|
|
29322
29601
|
// src/desktop/d2c-product-preview.ts
|
|
29323
29602
|
import { createReadStream as createReadStream2 } from "fs";
|
|
29324
|
-
import { realpath as realpath14, stat as
|
|
29603
|
+
import { realpath as realpath14, stat as stat14 } from "fs/promises";
|
|
29325
29604
|
import { createServer as createServer6 } from "http";
|
|
29326
|
-
import { extname as extname3, isAbsolute as isAbsolute17, relative as relative25, resolve as
|
|
29605
|
+
import { extname as extname3, isAbsolute as isAbsolute17, relative as relative25, resolve as resolve35, sep as sep26 } from "path";
|
|
29327
29606
|
var MIME_TYPES = {
|
|
29328
29607
|
".css": "text/css; charset=utf-8",
|
|
29329
29608
|
".gif": "image/gif",
|
|
@@ -29345,8 +29624,8 @@ function isInside(root, candidate) {
|
|
|
29345
29624
|
return delta === "" || !delta.startsWith(`..${sep26}`) && delta !== ".." && !isAbsolute17(delta);
|
|
29346
29625
|
}
|
|
29347
29626
|
async function startD2cProductPreview(rootDirectory) {
|
|
29348
|
-
const root = await realpath14(
|
|
29349
|
-
const rootInfo = await
|
|
29627
|
+
const root = await realpath14(resolve35(rootDirectory));
|
|
29628
|
+
const rootInfo = await stat14(root);
|
|
29350
29629
|
if (!rootInfo.isDirectory()) throw new Error("D2C product prototype directory is unavailable");
|
|
29351
29630
|
const server = createServer6(async (request, response2) => {
|
|
29352
29631
|
response2.setHeader("cache-control", "no-store");
|
|
@@ -29357,11 +29636,11 @@ async function startD2cProductPreview(rootDirectory) {
|
|
|
29357
29636
|
let pathname = decodeURIComponent(url.pathname);
|
|
29358
29637
|
if (pathname === "/") pathname = "/index.html";
|
|
29359
29638
|
if (pathname.includes("\0")) return sendNotFound(response2);
|
|
29360
|
-
const unresolved =
|
|
29639
|
+
const unresolved = resolve35(root, `.${pathname}`);
|
|
29361
29640
|
if (!isInside(root, unresolved)) return sendNotFound(response2);
|
|
29362
29641
|
const target = await realpath14(unresolved).catch(() => void 0);
|
|
29363
29642
|
if (target === void 0 || !isInside(root, target)) return sendNotFound(response2);
|
|
29364
|
-
const info = await
|
|
29643
|
+
const info = await stat14(target);
|
|
29365
29644
|
if (!info.isFile()) return sendNotFound(response2);
|
|
29366
29645
|
response2.writeHead(200, {
|
|
29367
29646
|
"content-length": info.size,
|
|
@@ -29398,33 +29677,33 @@ function pngDataUrl(png) {
|
|
|
29398
29677
|
}
|
|
29399
29678
|
async function detectD2cFramework(workspace, task) {
|
|
29400
29679
|
try {
|
|
29401
|
-
const pkg = JSON.parse(await readFile35(
|
|
29680
|
+
const pkg = JSON.parse(await readFile35(join43(d2cOutputDirectory(workspace, task), "package.json"), "utf8"));
|
|
29402
29681
|
return pkg.dependencies?.react === void 0 ? "vue" : "react";
|
|
29403
29682
|
} catch {
|
|
29404
29683
|
return "vue";
|
|
29405
29684
|
}
|
|
29406
29685
|
}
|
|
29407
29686
|
function integrationDirectory(workspace, task) {
|
|
29408
|
-
return
|
|
29687
|
+
return join43(workspace, ".flavor", "d2c", task, "integration");
|
|
29409
29688
|
}
|
|
29410
29689
|
var D2C_BACKEND_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".cfg", ".env", ".ini", ".js", ".json", ".lock", ".mjs", ".py", ".toml", ".txt", ".yaml", ".yml"]);
|
|
29411
29690
|
var D2C_BACKEND_IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".venv", "__pycache__", "data", "node_modules", "venv"]);
|
|
29412
29691
|
async function d2cBackendSourceFingerprint(project) {
|
|
29413
|
-
const runtimeManifest =
|
|
29414
|
-
const realBackend = (await
|
|
29415
|
-
const root = realBackend ?
|
|
29416
|
-
if ((await
|
|
29692
|
+
const runtimeManifest = join43(project, "server", "flavor-runtime.json");
|
|
29693
|
+
const realBackend = (await stat15(runtimeManifest).catch(() => void 0))?.isFile() === true;
|
|
29694
|
+
const root = realBackend ? join43(project, "server") : join43(project, "mock");
|
|
29695
|
+
if ((await stat15(root).catch(() => void 0))?.isDirectory() !== true) return void 0;
|
|
29417
29696
|
const files = [];
|
|
29418
29697
|
const visit = async (directory) => {
|
|
29419
29698
|
const entries = await readdir12(directory, { withFileTypes: true });
|
|
29420
29699
|
for (const entry2 of entries) {
|
|
29421
29700
|
if (entry2.isDirectory()) {
|
|
29422
|
-
if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(
|
|
29701
|
+
if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(join43(directory, entry2.name));
|
|
29423
29702
|
continue;
|
|
29424
29703
|
}
|
|
29425
29704
|
if (!entry2.isFile()) continue;
|
|
29426
29705
|
const extension2 = entry2.name.includes(".") ? `.${entry2.name.split(".").pop().toLowerCase()}` : "";
|
|
29427
|
-
if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(
|
|
29706
|
+
if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(join43(directory, entry2.name));
|
|
29428
29707
|
}
|
|
29429
29708
|
};
|
|
29430
29709
|
await visit(root);
|
|
@@ -29476,7 +29755,7 @@ var DesktopRuntimeController = class {
|
|
|
29476
29755
|
/** Backend source digest observed when the managed process was started. */
|
|
29477
29756
|
#d2cMockSourceFingerprints = /* @__PURE__ */ new Map();
|
|
29478
29757
|
constructor(options) {
|
|
29479
|
-
this.#home =
|
|
29758
|
+
this.#home = resolve36(options.home ?? homedir4());
|
|
29480
29759
|
this.#createRuntime = options.createRuntime ?? (async (runtimeOptions) => createProductionRuntime(runtimeOptions));
|
|
29481
29760
|
this.#createTerminalService = options.createTerminalService ?? ((workspace) => new TerminalService(workspace));
|
|
29482
29761
|
this.#listSessions = options.listSessions ?? (async (workspace) => {
|
|
@@ -29551,7 +29830,7 @@ var DesktopRuntimeController = class {
|
|
|
29551
29830
|
};
|
|
29552
29831
|
}
|
|
29553
29832
|
async openWorkspace(path) {
|
|
29554
|
-
const workspace =
|
|
29833
|
+
const workspace = resolve36(path);
|
|
29555
29834
|
if (workspace !== this.#workspace) {
|
|
29556
29835
|
await this.#disposeRuntime();
|
|
29557
29836
|
await this.#stopAllD2cPreviews();
|
|
@@ -29978,8 +30257,8 @@ var DesktopRuntimeController = class {
|
|
|
29978
30257
|
if (workflow === void 0 || !reviewProgress(workflow).complete || workflow.stage === "visual-review") {
|
|
29979
30258
|
throw new Error("Complete visual review before importing OpenAPI");
|
|
29980
30259
|
}
|
|
29981
|
-
const moduleManifest =
|
|
29982
|
-
const moduleManifestInfo = await
|
|
30260
|
+
const moduleManifest = join43(d2cOutputDirectory(workspace, task), "d2c.modules.json");
|
|
30261
|
+
const moduleManifestInfo = await stat15(moduleManifest).catch(() => void 0);
|
|
29983
30262
|
if (moduleManifestInfo === void 0 || !moduleManifestInfo.isFile()) {
|
|
29984
30263
|
throw new Error("D2C implementation must write d2c.modules.json before API mapping; ask the agent to split the project into modules first");
|
|
29985
30264
|
}
|
|
@@ -29991,8 +30270,8 @@ var DesktopRuntimeController = class {
|
|
|
29991
30270
|
await mkdir27(dir, { recursive: true });
|
|
29992
30271
|
const hash = createHash27("sha256").update(raw).digest("hex");
|
|
29993
30272
|
await Promise.all([
|
|
29994
|
-
writeFile17(
|
|
29995
|
-
writeFile17(
|
|
30273
|
+
writeFile17(join43(dir, "swagger.json"), raw),
|
|
30274
|
+
writeFile17(join43(dir, "openapi.normalized.json"), `${JSON.stringify(document, null, 2)}
|
|
29996
30275
|
`)
|
|
29997
30276
|
]);
|
|
29998
30277
|
const next = {
|
|
@@ -30016,7 +30295,7 @@ var DesktopRuntimeController = class {
|
|
|
30016
30295
|
const workspace = this.#workspace;
|
|
30017
30296
|
if (workspace === void 0) throw new Error("Open a project before importing OpenAPI");
|
|
30018
30297
|
await this.#assertProductPrdLocked(workspace, task);
|
|
30019
|
-
const info = await
|
|
30298
|
+
const info = await stat15(sourcePath);
|
|
30020
30299
|
if (!info.isFile() || info.size > 8 * 1024 * 1024) throw new Error("OpenAPI JSON must be a file no larger than 8 MiB");
|
|
30021
30300
|
return this.#storeD2cOpenApi(workspace, task, basename9(sourcePath), await readFile35(sourcePath, "utf8"), false);
|
|
30022
30301
|
}
|
|
@@ -30027,7 +30306,7 @@ var DesktopRuntimeController = class {
|
|
|
30027
30306
|
let workflow = await readWorkflow(workspace, task);
|
|
30028
30307
|
if (workflow?.openapi === void 0 && workflow !== void 0 && reviewProgress(workflow).complete && await readD2cProductPlanView(workspace, task) !== void 0) {
|
|
30029
30308
|
const modules = await readD2cModules(workspace, task);
|
|
30030
|
-
const productOpenApiPath =
|
|
30309
|
+
const productOpenApiPath = join43(taskDir(workspace, task), "product", "openapi.json");
|
|
30031
30310
|
const authored = await readFile35(productOpenApiPath, "utf8").catch(() => void 0);
|
|
30032
30311
|
const generated = authored === void 0;
|
|
30033
30312
|
const prepared = await this.#storeD2cOpenApi(
|
|
@@ -30040,7 +30319,7 @@ var DesktopRuntimeController = class {
|
|
|
30040
30319
|
return prepared;
|
|
30041
30320
|
}
|
|
30042
30321
|
if (workflow?.openapi === void 0 || workflow.mappings === void 0) return void 0;
|
|
30043
|
-
const document = JSON.parse(await readFile35(
|
|
30322
|
+
const document = JSON.parse(await readFile35(join43(integrationDirectory(workspace, task), "openapi.normalized.json"), "utf8"));
|
|
30044
30323
|
return { workflow, document, mappings: workflow.mappings };
|
|
30045
30324
|
}
|
|
30046
30325
|
async confirmD2cMapping(task, moduleId, operationKey) {
|
|
@@ -30107,7 +30386,7 @@ var DesktopRuntimeController = class {
|
|
|
30107
30386
|
].join("\n");
|
|
30108
30387
|
const delivery = await readDeliveryRun(workspace, task);
|
|
30109
30388
|
if (delivery?.nodes.api.status === "running") {
|
|
30110
|
-
const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile35(
|
|
30389
|
+
const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile35(join43(project, file)))));
|
|
30111
30390
|
const completed = await updateDeliveryRun(
|
|
30112
30391
|
workspace,
|
|
30113
30392
|
task,
|
|
@@ -30134,7 +30413,7 @@ var DesktopRuntimeController = class {
|
|
|
30134
30413
|
try {
|
|
30135
30414
|
const fingerprint2 = await d2cBackendSourceFingerprint(project);
|
|
30136
30415
|
if (fingerprint2 !== void 0) this.#d2cMockSourceFingerprints.set(task, fingerprint2);
|
|
30137
|
-
const environmentPath =
|
|
30416
|
+
const environmentPath = join43(project, ".env.local");
|
|
30138
30417
|
const existingEnvironment = await readFile35(environmentPath, "utf8").catch(() => "");
|
|
30139
30418
|
const environment = `${existingEnvironment.split(/\r?\n/).filter((line) => !/^\s*VITE_API_BASE_URL\s*=/.test(line)).join("\n").trim()}
|
|
30140
30419
|
VITE_API_BASE_URL=${running.url}
|
|
@@ -30215,7 +30494,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30215
30494
|
const readyPreview = this.#d2cPreviews.get(task);
|
|
30216
30495
|
const readyMock = this.#d2cMocks.get(task);
|
|
30217
30496
|
if (readyPreview === void 0 || readyMock === void 0) throw new Error("D2C preview or backend service is unavailable after synchronization");
|
|
30218
|
-
const manifestPath =
|
|
30497
|
+
const manifestPath = join43(taskDir(workspace, task), "design", "interaction-manifest.json");
|
|
30219
30498
|
const manifest = parseInteractionManifest(await readFile35(manifestPath, "utf8"));
|
|
30220
30499
|
const baseline = await this.#verifyAcceptanceBaseline(workspace, task);
|
|
30221
30500
|
const coverage = baseline === void 0 ? void 0 : verifyRequirementCoverage(baseline.approvedPrd.criteria, manifest);
|
|
@@ -30227,7 +30506,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30227
30506
|
await mkdir27(planDir, { recursive: true });
|
|
30228
30507
|
try {
|
|
30229
30508
|
const observations = await this.#observeD2cPages(manifest, readyPreview.url);
|
|
30230
|
-
const prd = await readFile35(
|
|
30509
|
+
const prd = await readFile35(join43(taskDir(workspace, task), "product", "prd.md"), "utf8").catch(() => void 0);
|
|
30231
30510
|
const apiContext = JSON.stringify({ openapi: workflow.openapi, mappings: workflow.mappings }, null, 2);
|
|
30232
30511
|
const plan = await this.#d2cJudge.planInteractions({ task, seed: manifest, observations, ...prd === void 0 ? {} : { prd }, apiContext });
|
|
30233
30512
|
executableManifest = mergeInteractionManifests(manifest, plan.manifest);
|
|
@@ -30238,9 +30517,9 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30238
30517
|
plannedScenarios: executableManifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
|
|
30239
30518
|
pageAnalyses: plan.pageAnalyses
|
|
30240
30519
|
};
|
|
30241
|
-
await writeFile17(
|
|
30520
|
+
await writeFile17(join43(planDir, "autonomous-interaction-plan.json"), `${JSON.stringify({ ...plan, manifest: executableManifest }, null, 2)}
|
|
30242
30521
|
`, { encoding: "utf8", mode: 384 });
|
|
30243
|
-
await rm15(
|
|
30522
|
+
await rm15(join43(planDir, "autonomous-interaction-diagnostic.json"), { force: true });
|
|
30244
30523
|
} catch (cause) {
|
|
30245
30524
|
const detail = message(cause);
|
|
30246
30525
|
review = {
|
|
@@ -30248,8 +30527,8 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30248
30527
|
plannedScenarios: manifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
|
|
30249
30528
|
warning: `\u591A\u6A21\u6001\u81EA\u4E3B\u89C4\u5212\u5931\u8D25\uFF0C\u5DF2\u7EE7\u7EED\u6267\u884C\u5DF2\u786E\u8BA4\u7684\u8BBE\u8BA1\u5951\u7EA6\uFF1A${detail}`
|
|
30250
30529
|
};
|
|
30251
|
-
await rm15(
|
|
30252
|
-
await writeFile17(
|
|
30530
|
+
await rm15(join43(planDir, "autonomous-interaction-plan.json"), { force: true });
|
|
30531
|
+
await writeFile17(join43(planDir, "autonomous-interaction-diagnostic.json"), `${JSON.stringify({
|
|
30253
30532
|
schema: 1,
|
|
30254
30533
|
stage: "planning",
|
|
30255
30534
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -30263,13 +30542,13 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30263
30542
|
let result = { ...rawResult, evidenceMode: review.mode };
|
|
30264
30543
|
if (coverage !== void 0) {
|
|
30265
30544
|
const evidence = createAcceptanceEvidence(coverage, result);
|
|
30266
|
-
await writeFile17(
|
|
30545
|
+
await writeFile17(join43(taskDir(workspace, task), "acceptance-evidence.json"), `${JSON.stringify({ ...evidence, baseline }, null, 2)}
|
|
30267
30546
|
`, { encoding: "utf8", mode: 384 });
|
|
30268
30547
|
if (!evidence.passed) result = { ...result, passed: false, failures: Math.max(1, result.failures) };
|
|
30269
30548
|
}
|
|
30270
30549
|
const dir = integrationDirectory(workspace, task);
|
|
30271
30550
|
await mkdir27(dir, { recursive: true });
|
|
30272
|
-
await writeFile17(
|
|
30551
|
+
await writeFile17(join43(dir, "interaction-results.json"), `${JSON.stringify(result, null, 2)}
|
|
30273
30552
|
`);
|
|
30274
30553
|
const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyInteractionRun(current, result));
|
|
30275
30554
|
return { workflow: next, result, review };
|
|
@@ -30282,7 +30561,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30282
30561
|
if (mock === void 0 || !mock.exited() || result.failures === 0) return result;
|
|
30283
30562
|
const output = mock.output().trim();
|
|
30284
30563
|
try {
|
|
30285
|
-
const dir =
|
|
30564
|
+
const dir = join43(d2cOutputDirectory(workspace, task), "mock-server.log");
|
|
30286
30565
|
await writeFile17(dir, `${output}
|
|
30287
30566
|
`);
|
|
30288
30567
|
} catch {
|
|
@@ -30377,7 +30656,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30377
30656
|
if (current === void 0) throw new Error("D2C product plan is unavailable");
|
|
30378
30657
|
if (current.plan.approvedPrd !== void 0) throw new Error("The approved PRD is immutable and cannot be edited");
|
|
30379
30658
|
if (current.plan.phase !== "prd-review") throw new Error("PRD sections can only be edited during PRD review");
|
|
30380
|
-
const path =
|
|
30659
|
+
const path = join43(taskDir(workspace, task), "product", "prd.md");
|
|
30381
30660
|
const updated = await updatePrdSectionFile(path, { sectionId, body, expectedHash });
|
|
30382
30661
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
30383
30662
|
await writeD2cProductPlan(workspace, {
|
|
@@ -30397,33 +30676,33 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30397
30676
|
async #assertProductPrdLocked(workspace, task) {
|
|
30398
30677
|
const current = await readD2cProductPlanView(workspace, task);
|
|
30399
30678
|
if (current?.plan.approvedPrd === void 0) return;
|
|
30400
|
-
await assertApprovedPrd(
|
|
30679
|
+
await assertApprovedPrd(join43(taskDir(workspace, task), "product", "prd.md"), current.plan.approvedPrd);
|
|
30401
30680
|
}
|
|
30402
30681
|
async #acceptancePaths(workspace, task) {
|
|
30403
|
-
const product =
|
|
30404
|
-
const openapi =
|
|
30682
|
+
const product = join43(taskDir(workspace, task), "product");
|
|
30683
|
+
const openapi = join43(product, "openapi.json");
|
|
30405
30684
|
return {
|
|
30406
|
-
prd:
|
|
30407
|
-
prototype:
|
|
30408
|
-
interaction:
|
|
30409
|
-
...await
|
|
30685
|
+
prd: join43(product, "prd.md"),
|
|
30686
|
+
prototype: join43(product, "prototype", "index.html"),
|
|
30687
|
+
interaction: join43(product, "prototype", "interaction-manifest.json"),
|
|
30688
|
+
...await stat15(openapi).catch(() => void 0) === void 0 ? {} : { openapi }
|
|
30410
30689
|
};
|
|
30411
30690
|
}
|
|
30412
30691
|
async #verifyAcceptanceBaseline(workspace, task) {
|
|
30413
30692
|
const product = await readD2cProductPlanView(workspace, task);
|
|
30414
30693
|
if (product?.plan.approvedPrd === void 0) return void 0;
|
|
30415
30694
|
await this.#assertProductPrdLocked(workspace, task);
|
|
30416
|
-
const baseline = JSON.parse(await readFile35(
|
|
30695
|
+
const baseline = JSON.parse(await readFile35(join43(taskDir(workspace, task), "acceptance-baseline.json"), "utf8"));
|
|
30417
30696
|
await verifyAcceptanceBaseline(await this.#acceptancePaths(workspace, task), baseline);
|
|
30418
|
-
const design =
|
|
30697
|
+
const design = join43(taskDir(workspace, task), "design");
|
|
30419
30698
|
await Promise.all([
|
|
30420
|
-
verifyAcceptanceArtifact(
|
|
30421
|
-
verifyAcceptanceArtifact(
|
|
30699
|
+
verifyAcceptanceArtifact(join43(design, "index.html"), baseline.artifacts.prototype, "runtime prototype"),
|
|
30700
|
+
verifyAcceptanceArtifact(join43(design, "interaction-manifest.json"), baseline.artifacts.interaction, "runtime interaction contract")
|
|
30422
30701
|
]);
|
|
30423
30702
|
const workflow = await readWorkflow(workspace, task);
|
|
30424
30703
|
if (workflow?.openapi !== void 0) {
|
|
30425
30704
|
await verifyAcceptanceArtifact(
|
|
30426
|
-
|
|
30705
|
+
join43(taskDir(workspace, task), "integration", "swagger.json"),
|
|
30427
30706
|
{ hash: workflow.openapi.hash },
|
|
30428
30707
|
"runtime OpenAPI contract"
|
|
30429
30708
|
);
|
|
@@ -30444,14 +30723,14 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30444
30723
|
throw new Error("D2C PRD is empty and cannot be accepted");
|
|
30445
30724
|
}
|
|
30446
30725
|
if (stage === "design" && accepted) {
|
|
30447
|
-
const manifest =
|
|
30726
|
+
const manifest = join43(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json");
|
|
30448
30727
|
const normalizedManifest = parseInteractionManifest(await readFile35(manifest, "utf8"));
|
|
30449
30728
|
if (current.plan.approvedPrd === void 0) throw new Error("Approve the PRD before confirming the design");
|
|
30450
30729
|
verifyRequirementCoverage(current.plan.approvedPrd.criteria, normalizedManifest);
|
|
30451
30730
|
await writeFile17(manifest, `${JSON.stringify(normalizedManifest, null, 2)}
|
|
30452
30731
|
`, { encoding: "utf8", mode: 384 });
|
|
30453
30732
|
const baseline = await captureAcceptanceBaseline(await this.#acceptancePaths(workspace, task), current.plan.approvedPrd);
|
|
30454
|
-
await writeFile17(
|
|
30733
|
+
await writeFile17(join43(taskDir(workspace, task), "acceptance-baseline.json"), `${JSON.stringify(baseline, null, 2)}
|
|
30455
30734
|
`, { encoding: "utf8", mode: 384 });
|
|
30456
30735
|
}
|
|
30457
30736
|
const plan = await writeD2cProductPlan(
|
|
@@ -30485,8 +30764,8 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30485
30764
|
const imported = await this.importD2cDesign(task, d2cProductPrototypeDirectory(workspace, task));
|
|
30486
30765
|
const delivery = await readDeliveryRun(workspace, task);
|
|
30487
30766
|
if (delivery !== void 0) {
|
|
30488
|
-
const prototype = await readFile35(
|
|
30489
|
-
const interaction = await readFile35(
|
|
30767
|
+
const prototype = await readFile35(join43(d2cProductPrototypeDirectory(workspace, task), "index.html"), "utf8");
|
|
30768
|
+
const interaction = await readFile35(join43(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json"), "utf8");
|
|
30490
30769
|
const completed = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "design", [
|
|
30491
30770
|
artifactRef("product/prototype/index.html", prototype),
|
|
30492
30771
|
artifactRef("product/prototype/interaction-manifest.json", interaction)
|
|
@@ -30570,10 +30849,10 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30570
30849
|
const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyQualityJudgment(current, judgment));
|
|
30571
30850
|
const qualityContent = `${JSON.stringify(judgment, null, 2)}
|
|
30572
30851
|
`;
|
|
30573
|
-
await writeFile17(
|
|
30852
|
+
await writeFile17(join43(taskDir(workspace, task), "quality-judge.json"), qualityContent, { encoding: "utf8", mode: 384 });
|
|
30574
30853
|
const delivery = await readDeliveryRun(workspace, task);
|
|
30575
30854
|
if (next.stage === "completed" && delivery?.nodes.acceptance.status === "running") {
|
|
30576
|
-
const interactionContent = await readFile35(
|
|
30855
|
+
const interactionContent = await readFile35(join43(integrationDirectory(workspace, task), "interaction-results.json"), "utf8");
|
|
30577
30856
|
const accepted = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "acceptance", [
|
|
30578
30857
|
artifactRef("integration/interaction-results.json", interactionContent),
|
|
30579
30858
|
artifactRef("quality-judge.json", qualityContent)
|
|
@@ -30755,14 +31034,14 @@ function desktopWindowChrome(platform = process.platform) {
|
|
|
30755
31034
|
|
|
30756
31035
|
// src/utils/crash-guard.ts
|
|
30757
31036
|
import { mkdirSync as mkdirSync3, writeFileSync } from "fs";
|
|
30758
|
-
import { join as
|
|
31037
|
+
import { join as join44 } from "path";
|
|
30759
31038
|
var installed = false;
|
|
30760
31039
|
function installCrashGuard(options = {}) {
|
|
30761
31040
|
if (installed) return;
|
|
30762
31041
|
installed = true;
|
|
30763
31042
|
const crashLogDir = () => {
|
|
30764
31043
|
try {
|
|
30765
|
-
return
|
|
31044
|
+
return join44((options.workspace ?? process.cwd)(), ".flavor");
|
|
30766
31045
|
} catch {
|
|
30767
31046
|
return process.cwd();
|
|
30768
31047
|
}
|
|
@@ -30771,7 +31050,7 @@ function installCrashGuard(options = {}) {
|
|
|
30771
31050
|
try {
|
|
30772
31051
|
const dir = crashLogDir();
|
|
30773
31052
|
mkdirSync3(dir, { recursive: true });
|
|
30774
|
-
const path =
|
|
31053
|
+
const path = join44(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
|
|
30775
31054
|
writeFileSync(path, [
|
|
30776
31055
|
`kind: ${kind}`,
|
|
30777
31056
|
`version: ${process.version}`,
|
|
@@ -30816,7 +31095,7 @@ ${reason.stack ?? ""}`;
|
|
|
30816
31095
|
|
|
30817
31096
|
// src/desktop/git-manager.ts
|
|
30818
31097
|
import { readFile as readFile36, rm as rm16 } from "fs/promises";
|
|
30819
|
-
import { isAbsolute as isAbsolute18, relative as relative26, resolve as
|
|
31098
|
+
import { isAbsolute as isAbsolute18, relative as relative26, resolve as resolve37 } from "path";
|
|
30820
31099
|
function failure(action, stderr, stdout) {
|
|
30821
31100
|
return new Error(`${action}\u5931\u8D25\uFF1A${(stderr.trim() || stdout.trim() || "\u672A\u77E5 Git \u9519\u8BEF").split("\n")[0]}`);
|
|
30822
31101
|
}
|
|
@@ -30909,13 +31188,13 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
|
|
|
30909
31188
|
return path.replaceAll("\\", "/");
|
|
30910
31189
|
}));
|
|
30911
31190
|
try {
|
|
30912
|
-
const tree = JSON.parse(await readFile36(
|
|
31191
|
+
const tree = JSON.parse(await readFile36(resolve37(workspace, ".flavor", "session-trees", sessionId, "tree.json"), "utf8"));
|
|
30913
31192
|
const leaf = tree.nodes?.find((node) => node.id === tree.leafId);
|
|
30914
31193
|
const parent = leaf?.parentId == null ? void 0 : tree.nodes?.find((node) => node.id === leaf.parentId);
|
|
30915
31194
|
if (leaf === void 0 || parent === void 0) return desktopGitReviewDiff(workspace, { scope: "last-turn", paths });
|
|
30916
31195
|
const load = async (id) => {
|
|
30917
31196
|
if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
|
|
30918
|
-
return JSON.parse(await readFile36(
|
|
31197
|
+
return JSON.parse(await readFile36(resolve37(workspace, ".flavor", "checkpoints", "manifests", `${id}.json`), "utf8"));
|
|
30919
31198
|
};
|
|
30920
31199
|
const [before, after] = await Promise.all([load(parent.checkpointId), load(leaf.checkpointId)]);
|
|
30921
31200
|
const oldFiles = new Map(before.files.map((file) => [file.path, file.digest]));
|
|
@@ -30924,7 +31203,7 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
|
|
|
30924
31203
|
const object = async (digest) => {
|
|
30925
31204
|
if (digest === void 0) return "";
|
|
30926
31205
|
if (!/^[a-f0-9]{64}$/.test(digest)) throw new Error("Invalid checkpoint object");
|
|
30927
|
-
const value = await readFile36(
|
|
31206
|
+
const value = await readFile36(resolve37(workspace, ".flavor", "checkpoints", "objects", digest));
|
|
30928
31207
|
return value.includes(0) ? "[binary content]" : value.toString("utf8");
|
|
30929
31208
|
};
|
|
30930
31209
|
const chunks = await Promise.all(changed.map(async (path) => wholeFileDiff(path, await object(oldFiles.get(path)), await object(newFiles.get(path)))));
|
|
@@ -30949,8 +31228,8 @@ async function assertListed(workspace, path) {
|
|
|
30949
31228
|
}
|
|
30950
31229
|
function safeWorkspacePath(workspace, path) {
|
|
30951
31230
|
if (isAbsolute18(path)) throw new Error("Git \u6587\u4EF6\u8DEF\u5F84\u5FC5\u987B\u662F\u9879\u76EE\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84");
|
|
30952
|
-
const root =
|
|
30953
|
-
const target =
|
|
31231
|
+
const root = resolve37(workspace);
|
|
31232
|
+
const target = resolve37(root, path);
|
|
30954
31233
|
const child = relative26(root, target);
|
|
30955
31234
|
if (child === "" || child.startsWith("..") || isAbsolute18(child)) throw new Error("\u62D2\u7EDD\u8BBF\u95EE\u9879\u76EE\u4E4B\u5916\u7684\u8DEF\u5F84");
|
|
30956
31235
|
return target;
|
|
@@ -30963,14 +31242,14 @@ function safeGitTarget(value) {
|
|
|
30963
31242
|
|
|
30964
31243
|
// src/desktop/worktree-manager.ts
|
|
30965
31244
|
import { randomUUID as randomUUID28 } from "crypto";
|
|
30966
|
-
import { mkdir as mkdir28, readFile as readFile37, readdir as readdir13, rm as rm17, stat as
|
|
30967
|
-
import { basename as basename10, isAbsolute as isAbsolute19, join as
|
|
31245
|
+
import { mkdir as mkdir28, readFile as readFile37, readdir as readdir13, rm as rm17, stat as stat16, writeFile as writeFile18 } from "fs/promises";
|
|
31246
|
+
import { basename as basename10, isAbsolute as isAbsolute19, join as join45, relative as relative27, resolve as resolve38, sep as sep27 } from "path";
|
|
30968
31247
|
var DesktopWorktreeManager = class {
|
|
30969
31248
|
#repository;
|
|
30970
31249
|
#storage;
|
|
30971
31250
|
constructor(options) {
|
|
30972
|
-
this.#repository =
|
|
30973
|
-
this.#storage =
|
|
31251
|
+
this.#repository = resolve38(options.repository);
|
|
31252
|
+
this.#storage = resolve38(options.storage);
|
|
30974
31253
|
}
|
|
30975
31254
|
async create(label) {
|
|
30976
31255
|
if (!await isGitRepository(this.#repository)) throw new Error("Task worktrees require a Git repository");
|
|
@@ -31010,8 +31289,8 @@ var DesktopWorktreeManager = class {
|
|
|
31010
31289
|
}
|
|
31011
31290
|
const values = await Promise.all(names.filter((name) => name.endsWith(".json")).map(async (name) => {
|
|
31012
31291
|
try {
|
|
31013
|
-
const stored = validateStored(JSON.parse(await readFile37(
|
|
31014
|
-
if ((await
|
|
31292
|
+
const stored = validateStored(JSON.parse(await readFile37(join45(this.#storage, name), "utf8")), this.#repository, this.#storage);
|
|
31293
|
+
if ((await stat16(stored.path).catch(() => void 0))?.isDirectory() !== true) return void 0;
|
|
31015
31294
|
const status = await git2(stored.path, ["status", "--porcelain"]);
|
|
31016
31295
|
const merged = await git2(this.#repository, ["merge-base", "--is-ancestor", stored.branch, "HEAD"]);
|
|
31017
31296
|
const { version: _version, ...item } = stored;
|
|
@@ -31051,7 +31330,7 @@ var DesktopWorktreeManager = class {
|
|
|
31051
31330
|
return this.#safeStoragePath(`${id}.json`);
|
|
31052
31331
|
}
|
|
31053
31332
|
#safeStoragePath(name) {
|
|
31054
|
-
const target =
|
|
31333
|
+
const target = resolve38(this.#storage, name);
|
|
31055
31334
|
const delta = relative27(this.#storage, target);
|
|
31056
31335
|
if (delta === "" || delta === ".." || delta.startsWith(`..${sep27}`) || isAbsolute19(delta)) throw new Error("Worktree path escapes storage");
|
|
31057
31336
|
return target;
|
|
@@ -31060,10 +31339,10 @@ var DesktopWorktreeManager = class {
|
|
|
31060
31339
|
function validateStored(value, repository, storage) {
|
|
31061
31340
|
if (typeof value !== "object" || value === null) throw new Error("Invalid worktree metadata");
|
|
31062
31341
|
const item = value;
|
|
31063
|
-
if (item.version !== 1 || item.mode !== "worktree" || typeof item.id !== "string" || typeof item.path !== "string" || typeof item.branch !== "string" || typeof item.createdAt !== "string" ||
|
|
31342
|
+
if (item.version !== 1 || item.mode !== "worktree" || typeof item.id !== "string" || typeof item.path !== "string" || typeof item.branch !== "string" || typeof item.createdAt !== "string" || resolve38(item.repository ?? "") !== repository) {
|
|
31064
31343
|
throw new Error("Invalid worktree metadata");
|
|
31065
31344
|
}
|
|
31066
|
-
const path =
|
|
31345
|
+
const path = resolve38(item.path);
|
|
31067
31346
|
const delta = relative27(storage, path);
|
|
31068
31347
|
if (delta === "" || delta === ".." || delta.startsWith(`..${sep27}`) || isAbsolute19(delta) || basename10(path) !== item.id) {
|
|
31069
31348
|
throw new Error("Worktree metadata escapes storage");
|
|
@@ -31072,17 +31351,17 @@ function validateStored(value, repository, storage) {
|
|
|
31072
31351
|
}
|
|
31073
31352
|
|
|
31074
31353
|
// src/desktop/workbench-service.ts
|
|
31075
|
-
import { readFile as readFile38, readdir as readdir14, stat as
|
|
31354
|
+
import { readFile as readFile38, readdir as readdir14, stat as stat17 } from "fs/promises";
|
|
31076
31355
|
import { homedir as homedir5 } from "os";
|
|
31077
|
-
import { join as
|
|
31356
|
+
import { join as join46, resolve as resolve39 } from "path";
|
|
31078
31357
|
var MAX_TEXT = 128e3;
|
|
31079
31358
|
var MAX_ITEMS = 200;
|
|
31080
31359
|
var DesktopWorkbenchService = class {
|
|
31081
31360
|
#workspace;
|
|
31082
31361
|
#home;
|
|
31083
31362
|
constructor(workspace, home = homedir5()) {
|
|
31084
|
-
this.#workspace =
|
|
31085
|
-
this.#home =
|
|
31363
|
+
this.#workspace = resolve39(workspace);
|
|
31364
|
+
this.#home = resolve39(home);
|
|
31086
31365
|
}
|
|
31087
31366
|
async inspect(sessionId) {
|
|
31088
31367
|
const [goals, instructions, permissionFiles, audit, context] = await Promise.all([
|
|
@@ -31107,11 +31386,11 @@ var DesktopWorkbenchService = class {
|
|
|
31107
31386
|
return url.toString().replace(/\/$/, input.trim().endsWith("/") ? "/" : "");
|
|
31108
31387
|
}
|
|
31109
31388
|
async #goals() {
|
|
31110
|
-
const root =
|
|
31389
|
+
const root = join46(this.#workspace, ".flavor", "goals");
|
|
31111
31390
|
const names = await readdir14(root).catch((error) => error.code === "ENOENT" ? [] : Promise.reject(error));
|
|
31112
31391
|
const values = await Promise.all(names.filter((name) => /^goal-[A-Za-z0-9._-]+\.json$/.test(name)).slice(0, MAX_ITEMS).map(async (name) => {
|
|
31113
31392
|
try {
|
|
31114
|
-
return GoalStateSchema.parse(JSON.parse(await boundedRead2(
|
|
31393
|
+
return GoalStateSchema.parse(JSON.parse(await boundedRead2(join46(root, name))));
|
|
31115
31394
|
} catch {
|
|
31116
31395
|
return void 0;
|
|
31117
31396
|
}
|
|
@@ -31121,7 +31400,7 @@ var DesktopWorkbenchService = class {
|
|
|
31121
31400
|
async #instructions() {
|
|
31122
31401
|
const names = ["FLAVOR.md", "AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
|
|
31123
31402
|
const items = await Promise.all(names.map(async (name) => {
|
|
31124
|
-
const path =
|
|
31403
|
+
const path = join46(this.#workspace, name);
|
|
31125
31404
|
try {
|
|
31126
31405
|
return { name, path, content: redactErrorText(await boundedRead2(path)) };
|
|
31127
31406
|
} catch (error) {
|
|
@@ -31133,10 +31412,10 @@ var DesktopWorkbenchService = class {
|
|
|
31133
31412
|
}
|
|
31134
31413
|
async #permissionFiles() {
|
|
31135
31414
|
const candidates = [
|
|
31136
|
-
...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path:
|
|
31137
|
-
{ tier: "user", path:
|
|
31138
|
-
{ tier: "project", path:
|
|
31139
|
-
{ tier: "local", path:
|
|
31415
|
+
...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path: resolve39(process.env.FLAVOR_MANAGED_PERMISSIONS), content: "" }] : [],
|
|
31416
|
+
{ tier: "user", path: join46(this.#home, ".flavor-code", "permissions.json"), content: "" },
|
|
31417
|
+
{ tier: "project", path: join46(this.#workspace, ".flavor", "permissions.json"), content: "" },
|
|
31418
|
+
{ tier: "local", path: join46(this.#workspace, ".flavor", "permissions.local.json"), content: "" }
|
|
31140
31419
|
];
|
|
31141
31420
|
const values = await Promise.all(candidates.map(async (item) => {
|
|
31142
31421
|
try {
|
|
@@ -31151,7 +31430,7 @@ var DesktopWorkbenchService = class {
|
|
|
31151
31430
|
async #audit() {
|
|
31152
31431
|
let raw;
|
|
31153
31432
|
try {
|
|
31154
|
-
raw = await boundedRead2(
|
|
31433
|
+
raw = await boundedRead2(join46(this.#workspace, ".flavor", "audit.jsonl"));
|
|
31155
31434
|
} catch (error) {
|
|
31156
31435
|
if (error.code === "ENOENT") return [];
|
|
31157
31436
|
throw error;
|
|
@@ -31166,7 +31445,7 @@ var DesktopWorkbenchService = class {
|
|
|
31166
31445
|
}).reverse();
|
|
31167
31446
|
}
|
|
31168
31447
|
async #context(sessionId) {
|
|
31169
|
-
const usage = await readJsonLines2(
|
|
31448
|
+
const usage = await readJsonLines2(join46(this.#workspace, ".flavor", "usage.jsonl"));
|
|
31170
31449
|
if (sessionId === void 0) return { visibility: [], usage };
|
|
31171
31450
|
try {
|
|
31172
31451
|
const document = await new SessionStore({ workspace: this.#workspace }).load(sessionId);
|
|
@@ -31177,7 +31456,7 @@ var DesktopWorkbenchService = class {
|
|
|
31177
31456
|
}
|
|
31178
31457
|
};
|
|
31179
31458
|
async function boundedRead2(path) {
|
|
31180
|
-
const info = await
|
|
31459
|
+
const info = await stat17(path);
|
|
31181
31460
|
if (!info.isFile()) throw new Error(`Expected a file: ${path}`);
|
|
31182
31461
|
if (info.size > MAX_TEXT) throw new Error(`Workbench file exceeds ${MAX_TEXT} bytes: ${path}`);
|
|
31183
31462
|
return readFile38(path, "utf8");
|
|
@@ -31204,82 +31483,6 @@ function redact2(value, key = "") {
|
|
|
31204
31483
|
return typeof value === "string" && value.length > MAX_TEXT ? `${value.slice(0, MAX_TEXT)}\u2026` : value;
|
|
31205
31484
|
}
|
|
31206
31485
|
|
|
31207
|
-
// src/desktop/astgraph-service.ts
|
|
31208
|
-
import { stat as stat17 } from "fs/promises";
|
|
31209
|
-
import { createRequire as nodeCreateRequire2 } from "module";
|
|
31210
|
-
import { join as join46, resolve as resolve39 } from "path";
|
|
31211
|
-
var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
|
|
31212
|
-
var DesktopAstGraphService = class {
|
|
31213
|
-
#path;
|
|
31214
|
-
constructor(workspace) {
|
|
31215
|
-
this.#path = join46(resolve39(workspace), ".flavor", "astgraph", "index.db");
|
|
31216
|
-
}
|
|
31217
|
-
async status() {
|
|
31218
|
-
if ((await stat17(this.#path).catch(() => void 0))?.isFile() !== true) {
|
|
31219
|
-
return { available: false, path: this.#path, files: 0, nodes: 0, edges: 0 };
|
|
31220
|
-
}
|
|
31221
|
-
return this.#read((db) => {
|
|
31222
|
-
const count = (table) => Number(db.prepare(`SELECT COUNT(*) AS count FROM ${table}`).get().count);
|
|
31223
|
-
const metadata = db.prepare("SELECT value FROM project_metadata WHERE key = 'last_index'").get();
|
|
31224
|
-
let indexedAt;
|
|
31225
|
-
try {
|
|
31226
|
-
const at = Number(JSON.parse(metadata?.value ?? "{}").at);
|
|
31227
|
-
if (Number.isFinite(at)) indexedAt = new Date(at).toISOString();
|
|
31228
|
-
} catch {
|
|
31229
|
-
}
|
|
31230
|
-
return { available: true, path: this.#path, files: count("files"), nodes: count("nodes"), edges: count("edges"), ...indexedAt === void 0 ? {} : { indexedAt } };
|
|
31231
|
-
});
|
|
31232
|
-
}
|
|
31233
|
-
async search(query, limit = 40) {
|
|
31234
|
-
const value = query.trim().slice(0, 200);
|
|
31235
|
-
if (value.length === 0 || !(await this.status()).available) return [];
|
|
31236
|
-
return this.#read((db) => db.prepare(`SELECT ${FIELDS} FROM nodes WHERE lower(name) LIKE ? OR lower(qualified_name) LIKE ? ORDER BY CASE WHEN lower(name) = ? THEN 0 ELSE 1 END, name LIMIT ?`).all(`%${value.toLowerCase()}%`, `%${value.toLowerCase()}%`, value.toLowerCase(), Math.min(Math.max(limit, 1), 100)).map(mapNode));
|
|
31237
|
-
}
|
|
31238
|
-
async relations(id, hops = 2) {
|
|
31239
|
-
if (!/^[A-Za-z0-9._:/#@+~-]{1,512}$/.test(id)) throw new Error("Invalid AST node id");
|
|
31240
|
-
if (!(await this.status()).available) return { callers: [], callees: [], impact: [] };
|
|
31241
|
-
return this.#read((db) => {
|
|
31242
|
-
const originRow = db.prepare(`SELECT ${FIELDS} FROM nodes WHERE id = ?`).get(id);
|
|
31243
|
-
const linked = (direction) => {
|
|
31244
|
-
const source = direction === "callers" ? "e.source" : "e.target";
|
|
31245
|
-
const predicate = direction === "callers" ? "e.target = ?" : "e.source = ?";
|
|
31246
|
-
return db.prepare(`SELECT DISTINCT n.${FIELDS.split(", ").join(", n.")} FROM edges e JOIN nodes n ON n.id = ${source} WHERE ${predicate} AND e.kind IN ('calls','imports') LIMIT 100`).all(id).map(mapNode);
|
|
31247
|
-
};
|
|
31248
|
-
const seen = /* @__PURE__ */ new Map();
|
|
31249
|
-
let frontier = [id];
|
|
31250
|
-
for (let hop = 1; hop <= Math.min(Math.max(hops, 1), 4) && frontier.length > 0 && seen.size < 200; hop += 1) {
|
|
31251
|
-
const next = [];
|
|
31252
|
-
for (const current of frontier) {
|
|
31253
|
-
const rows = db.prepare("SELECT source AS other FROM edges WHERE target = ? UNION SELECT target AS other FROM edges WHERE source = ? LIMIT 200").all(current, current);
|
|
31254
|
-
for (const row of rows) if (row.other !== id && !seen.has(row.other)) {
|
|
31255
|
-
seen.set(row.other, hop);
|
|
31256
|
-
next.push(row.other);
|
|
31257
|
-
}
|
|
31258
|
-
}
|
|
31259
|
-
frontier = next;
|
|
31260
|
-
}
|
|
31261
|
-
const impact = [...seen].flatMap(([nodeId, hop]) => {
|
|
31262
|
-
const row = db.prepare(`SELECT ${FIELDS} FROM nodes WHERE id = ?`).get(nodeId);
|
|
31263
|
-
return row === void 0 ? [] : [{ ...mapNode(row), hop }];
|
|
31264
|
-
});
|
|
31265
|
-
return { ...originRow === void 0 ? {} : { origin: mapNode(originRow) }, callers: linked("callers"), callees: linked("callees"), impact };
|
|
31266
|
-
});
|
|
31267
|
-
}
|
|
31268
|
-
#read(run) {
|
|
31269
|
-
const sqlite = nodeCreateRequire2(import.meta.url)("node:sqlite");
|
|
31270
|
-
const db = new sqlite.DatabaseSync(this.#path, { readOnly: true });
|
|
31271
|
-
try {
|
|
31272
|
-
return run(db);
|
|
31273
|
-
} finally {
|
|
31274
|
-
db.close();
|
|
31275
|
-
}
|
|
31276
|
-
}
|
|
31277
|
-
};
|
|
31278
|
-
function mapNode(value) {
|
|
31279
|
-
const row = value;
|
|
31280
|
-
return { id: String(row.id), kind: String(row.kind), name: String(row.name), qualifiedName: String(row.qualified_name), filePath: String(row.file_path), language: String(row.language), startLine: Number(row.start_line), endLine: Number(row.end_line), ...row.signature == null ? {} : { signature: String(row.signature) } };
|
|
31281
|
-
}
|
|
31282
|
-
|
|
31283
31486
|
// src/desktop/main.ts
|
|
31284
31487
|
installCrashGuard();
|
|
31285
31488
|
var moduleDirectory = dirname24(fileURLToPath4(import.meta.url));
|