flavor-code 1.3.11 → 1.3.13
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/agent/types.d.ts +3 -0
- package/dist/{app-L52CTVNR.js → app-2SBDQ6NS.js} +76 -201
- package/dist/{chunk-KKIBYDJI.js → chunk-2BF7CVWZ.js} +5 -7
- package/dist/{chunk-JZ5FVBET.js → chunk-EYCTP5JB.js} +1 -1
- package/dist/{chunk-IG4Z23CL.js → chunk-JFKRX2KG.js} +7 -0
- package/dist/{chunk-IGFN2DM5.js → chunk-W2D2MLY2.js} +705 -107
- package/dist/cli.js +6 -6
- package/dist/config/schema.d.ts +12 -0
- package/dist/desktop/astgraph-service.d.ts +34 -0
- package/dist/desktop/main.js +590 -282
- package/dist/desktop-renderer/assets/index-BiDVkW9t.js +154 -0
- package/dist/desktop-renderer/assets/{index-CDxzMJ9C.css → index-DHTr_Acb.css} +1 -1
- package/dist/desktop-renderer/assets/{interactive-terminal-CNgUWG7b.js → interactive-terminal-CKHHiGNG.js} +1 -1
- package/dist/desktop-renderer/index.html +2 -2
- package/dist/explain/service.d.ts +114 -0
- package/dist/{load-GKA7P6RJ.js → load-LTTKVPJZ.js} +1 -1
- package/dist/models/anthropic.d.ts +7 -0
- package/dist/models/openai.d.ts +3 -0
- package/dist/models/types.d.ts +19 -0
- package/dist/sdk/index.js +3 -3
- package/dist/ui/char-width.d.ts +12 -0
- package/dist/ui/commands.d.ts +6 -2
- package/dist/ui/session.d.ts +1 -0
- package/dist/ui/thinking-line.d.ts +42 -0
- package/dist/ui/transcript.d.ts +2 -0
- package/package.json +1 -1
- package/dist/desktop-renderer/assets/index-Bl5dmm02.js +0 -154
package/dist/desktop/main.js
CHANGED
|
@@ -209,6 +209,13 @@ var ProviderConfigSchema = z2.object({
|
|
|
209
209
|
cheapModel: z2.string().min(1).optional(),
|
|
210
210
|
models: z2.array(z2.string().min(1)).max(100).optional(),
|
|
211
211
|
maxOutputTokens: z2.number().int().positive().optional(),
|
|
212
|
+
// Extended-thinking budget in output tokens for the Anthropic protocol.
|
|
213
|
+
// Defaults to DEFAULT_THINKING_BUDGET; set to 0 to disable the request
|
|
214
|
+
// parameter. Provider thinking deltas are forwarded whenever they arrive.
|
|
215
|
+
thinkingBudget: z2.number().int().min(0).optional(),
|
|
216
|
+
// Reasoning effort requested from the OpenAI Responses protocol. Omitted by
|
|
217
|
+
// default so strict compatible gateways never see an unknown parameter.
|
|
218
|
+
thinkingEffort: z2.enum(["low", "medium", "high"]).optional(),
|
|
212
219
|
// Send the Claude Code client fingerprint (User-Agent/x-app) with Anthropic requests,
|
|
213
220
|
// for gateways that restrict the Anthropic protocol to Claude clients.
|
|
214
221
|
claudeClient: z2.boolean().optional(),
|
|
@@ -4220,7 +4227,7 @@ ${summarizeReport(report2)}`).join("\n\n")
|
|
|
4220
4227
|
import { readFile as readFile27 } from "fs/promises";
|
|
4221
4228
|
import { createHash as createHash21, randomUUID as randomUUID25 } from "crypto";
|
|
4222
4229
|
import { homedir as homedir2, release as osRelease, tmpdir as tmpdir4, version as osVersion } from "os";
|
|
4223
|
-
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";
|
|
4224
4231
|
|
|
4225
4232
|
// src/agent/subagents.ts
|
|
4226
4233
|
import { z as z10 } from "zod";
|
|
@@ -5874,7 +5881,12 @@ function providerValidMessages(input) {
|
|
|
5874
5881
|
const calls = original.toolCalls.filter((call) => call.id && call.name && availableResults.has(call.id));
|
|
5875
5882
|
calls.forEach((call) => announced.add(call.id));
|
|
5876
5883
|
if (!original.content && calls.length === 0) continue;
|
|
5877
|
-
output.push({
|
|
5884
|
+
output.push({
|
|
5885
|
+
role: "assistant",
|
|
5886
|
+
content: original.content,
|
|
5887
|
+
...calls.length === 0 ? {} : { toolCalls: calls.map((call) => ({ ...call })) },
|
|
5888
|
+
...original.thinkingBlocks === void 0 ? {} : { thinkingBlocks: original.thinkingBlocks.map((block) => ({ ...block })) }
|
|
5889
|
+
});
|
|
5878
5890
|
continue;
|
|
5879
5891
|
}
|
|
5880
5892
|
if (original.role === "tool") {
|
|
@@ -6597,6 +6609,7 @@ var AgentLoop = class {
|
|
|
6597
6609
|
}
|
|
6598
6610
|
let assistantText = "";
|
|
6599
6611
|
const collectedToolCalls = [];
|
|
6612
|
+
const collectedThinking = [];
|
|
6600
6613
|
let completed = false;
|
|
6601
6614
|
let reactiveRetried = false;
|
|
6602
6615
|
let attempt = 1;
|
|
@@ -6634,6 +6647,7 @@ var AgentLoop = class {
|
|
|
6634
6647
|
}
|
|
6635
6648
|
assistantText = "";
|
|
6636
6649
|
collectedToolCalls.length = 0;
|
|
6650
|
+
collectedThinking.length = 0;
|
|
6637
6651
|
completed = false;
|
|
6638
6652
|
let terminalError;
|
|
6639
6653
|
let providerError = false;
|
|
@@ -6654,6 +6668,10 @@ var AgentLoop = class {
|
|
|
6654
6668
|
assistantText += event.text;
|
|
6655
6669
|
accumulatedText += event.text;
|
|
6656
6670
|
yield event;
|
|
6671
|
+
} else if (event.type === "thinking") {
|
|
6672
|
+
yield event;
|
|
6673
|
+
} else if (event.type === "thinking-block") {
|
|
6674
|
+
collectedThinking.push({ text: event.text, ...event.signature === void 0 ? {} : { signature: event.signature } });
|
|
6657
6675
|
} else if (event.type === "tool-call") {
|
|
6658
6676
|
collectedToolCalls.push({ kind: "valid", id: event.id, name: event.name, input: event.input });
|
|
6659
6677
|
} else if (event.type === "invalid-tool-call") {
|
|
@@ -7001,7 +7019,8 @@ var AgentLoop = class {
|
|
|
7001
7019
|
const assistantMessage = {
|
|
7002
7020
|
role: "assistant",
|
|
7003
7021
|
content: assistantText,
|
|
7004
|
-
toolCalls: toolCalls.map(({ id, name, input }) => ({ id, name, input }))
|
|
7022
|
+
toolCalls: toolCalls.map(({ id, name, input }) => ({ id, name, input })),
|
|
7023
|
+
...collectedThinking.length === 0 ? {} : { thinkingBlocks: collectedThinking.map((block) => ({ ...block })) }
|
|
7005
7024
|
};
|
|
7006
7025
|
this.#options.context.appendMany([assistantMessage, ...stagedMessages]);
|
|
7007
7026
|
for (const { call, result } of stagedResults) {
|
|
@@ -13287,6 +13306,7 @@ var CLAUDE_CLIENT_HEADERS = {
|
|
|
13287
13306
|
"x-app": "cli"
|
|
13288
13307
|
};
|
|
13289
13308
|
var DEFAULT_MAX_OUTPUT_TOKENS = 32768;
|
|
13309
|
+
var DEFAULT_THINKING_BUDGET = 8192;
|
|
13290
13310
|
var MAX_CACHE_MARKERS = 4;
|
|
13291
13311
|
function collectCacheMarkers(system, messages) {
|
|
13292
13312
|
const markers = [];
|
|
@@ -13345,9 +13365,26 @@ function formatCacheUsage(model, snapshot4, shape) {
|
|
|
13345
13365
|
...shape === void 0 ? {} : { requestMessages: shape.messages, requestMarkers: shape.markers }
|
|
13346
13366
|
});
|
|
13347
13367
|
}
|
|
13368
|
+
function stripThinkingBlocks(messages) {
|
|
13369
|
+
for (const message2 of messages) {
|
|
13370
|
+
if (message2.role !== "assistant" || !Array.isArray(message2.content)) continue;
|
|
13371
|
+
const blocks = message2.content;
|
|
13372
|
+
if (!blocks.some((block) => block?.type === "thinking")) continue;
|
|
13373
|
+
message2.content = blocks.filter((block) => block?.type !== "thinking");
|
|
13374
|
+
}
|
|
13375
|
+
}
|
|
13376
|
+
function isThinkingParamRejected(error) {
|
|
13377
|
+
const status = error?.status;
|
|
13378
|
+
if (status !== void 0 && status !== 400) return false;
|
|
13379
|
+
const message2 = error instanceof Error ? error.message : String(error ?? "");
|
|
13380
|
+
return /\bthinking\b/iu.test(message2) && /(budget_tokens|expected|not supported|unsupported|unknown|invalid|extra inputs|unrecognized)/iu.test(message2);
|
|
13381
|
+
}
|
|
13348
13382
|
var AnthropicModelAdapter = class {
|
|
13349
13383
|
client;
|
|
13350
13384
|
maxOutputTokens;
|
|
13385
|
+
thinkingBudget;
|
|
13386
|
+
/** Set once an endpoint rejects the `thinking` parameter; later requests stop sending it. */
|
|
13387
|
+
#thinkingRejected = false;
|
|
13351
13388
|
debugUsage;
|
|
13352
13389
|
constructor(options) {
|
|
13353
13390
|
const maxOutputTokens = options.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS;
|
|
@@ -13355,6 +13392,7 @@ var AnthropicModelAdapter = class {
|
|
|
13355
13392
|
throw new Error("maxOutputTokens must be a positive integer");
|
|
13356
13393
|
}
|
|
13357
13394
|
this.maxOutputTokens = maxOutputTokens;
|
|
13395
|
+
this.thinkingBudget = options.thinkingBudget ?? DEFAULT_THINKING_BUDGET;
|
|
13358
13396
|
this.debugUsage = options.debugUsage ?? isEnvTruthy(process.env.FLAVOR_DEBUG_USAGE);
|
|
13359
13397
|
this.client = options.client ?? new Anthropic({
|
|
13360
13398
|
...options.apiKey === void 0 ? {} : { apiKey: options.apiKey },
|
|
@@ -13362,6 +13400,16 @@ var AnthropicModelAdapter = class {
|
|
|
13362
13400
|
...options.headers === void 0 ? {} : { defaultHeaders: options.headers }
|
|
13363
13401
|
});
|
|
13364
13402
|
}
|
|
13403
|
+
/**
|
|
13404
|
+
* Extended-thinking request parameter when enabled, otherwise undefined.
|
|
13405
|
+
* Anthropic requires the budget to fit inside max_tokens with a 1024 floor.
|
|
13406
|
+
*/
|
|
13407
|
+
#thinkingParam() {
|
|
13408
|
+
if (this.#thinkingRejected || this.thinkingBudget <= 0) return void 0;
|
|
13409
|
+
const budget = Math.min(this.thinkingBudget, this.maxOutputTokens - 1);
|
|
13410
|
+
if (budget < 1024) return void 0;
|
|
13411
|
+
return { type: "enabled", budget_tokens: budget };
|
|
13412
|
+
}
|
|
13365
13413
|
#logUsage(request, snapshot4, shape) {
|
|
13366
13414
|
const line = formatCacheUsage(request.model, snapshot4, shape);
|
|
13367
13415
|
if (this.debugUsage) {
|
|
@@ -13417,7 +13465,13 @@ var AnthropicModelAdapter = class {
|
|
|
13417
13465
|
}
|
|
13418
13466
|
messages.push({ role: "user", content: results });
|
|
13419
13467
|
} else if (message2.role === "assistant" && message2.toolCalls?.length) {
|
|
13468
|
+
const echoThinking = this.#thinkingRejected ? [] : (message2.thinkingBlocks ?? []).filter((block) => block.signature !== void 0);
|
|
13420
13469
|
const content = [
|
|
13470
|
+
...echoThinking.map((block) => ({
|
|
13471
|
+
type: "thinking",
|
|
13472
|
+
thinking: block.text,
|
|
13473
|
+
signature: block.signature
|
|
13474
|
+
})),
|
|
13421
13475
|
...modelContentText(message2.content) ? [{ type: "text", text: modelContentText(message2.content) }] : [],
|
|
13422
13476
|
...message2.toolCalls.map((call) => ({
|
|
13423
13477
|
type: "tool_use",
|
|
@@ -13479,18 +13533,56 @@ var AnthropicModelAdapter = class {
|
|
|
13479
13533
|
stream: true,
|
|
13480
13534
|
messages,
|
|
13481
13535
|
...system ? { system } : {},
|
|
13536
|
+
...(() => {
|
|
13537
|
+
const thinking = this.#thinkingParam();
|
|
13538
|
+
return thinking === void 0 ? {} : { thinking };
|
|
13539
|
+
})(),
|
|
13482
13540
|
tools: [...request.tools].sort((a, b) => a.name.localeCompare(b.name)).map((tool) => ({
|
|
13483
13541
|
name: tool.name,
|
|
13484
13542
|
description: tool.description,
|
|
13485
13543
|
input_schema: { ...tool.inputSchema, type: "object" }
|
|
13486
13544
|
}))
|
|
13487
13545
|
};
|
|
13488
|
-
|
|
13546
|
+
let stream;
|
|
13547
|
+
try {
|
|
13548
|
+
stream = await this.client.messages.create(body, { signal: request.signal });
|
|
13549
|
+
} catch (error) {
|
|
13550
|
+
if (this.thinkingBudget > 0 && !this.#thinkingRejected && isThinkingParamRejected(error)) {
|
|
13551
|
+
this.#thinkingRejected = true;
|
|
13552
|
+
delete body.thinking;
|
|
13553
|
+
stripThinkingBlocks(body.messages);
|
|
13554
|
+
stream = await this.client.messages.create(body, { signal: request.signal });
|
|
13555
|
+
} else {
|
|
13556
|
+
throw error;
|
|
13557
|
+
}
|
|
13558
|
+
}
|
|
13559
|
+
const thinkingBuffers = /* @__PURE__ */ new Map();
|
|
13489
13560
|
for await (const event of stream) {
|
|
13490
13561
|
if (event.type === "message_start") {
|
|
13491
13562
|
hasUsage = event.message?.usage !== void 0;
|
|
13492
13563
|
inputTokens = updateInputUsage(inputUsage, event.message?.usage);
|
|
13493
13564
|
outputTokens = event.message?.usage?.output_tokens ?? outputTokens;
|
|
13565
|
+
} else if (event.type === "content_block_start" && event.content_block?.type === "thinking" && event.index !== void 0) {
|
|
13566
|
+
thinkingBuffers.set(event.index, { text: "" });
|
|
13567
|
+
} else if (event.type === "content_block_delta" && event.delta?.type === "thinking_delta") {
|
|
13568
|
+
if (event.index !== void 0 && event.delta.thinking) {
|
|
13569
|
+
const pending2 = thinkingBuffers.get(event.index);
|
|
13570
|
+
if (pending2) pending2.text += event.delta.thinking;
|
|
13571
|
+
}
|
|
13572
|
+
if (event.delta.thinking) yield { type: "thinking", text: event.delta.thinking };
|
|
13573
|
+
} else if (event.type === "content_block_delta" && event.delta?.type === "signature_delta" && event.index !== void 0) {
|
|
13574
|
+
const pending2 = thinkingBuffers.get(event.index);
|
|
13575
|
+
if (pending2) pending2.signature = (pending2.signature ?? "") + (event.delta.signature ?? "");
|
|
13576
|
+
} else if (event.type === "content_block_stop" && event.index !== void 0 && thinkingBuffers.has(event.index)) {
|
|
13577
|
+
const pending2 = thinkingBuffers.get(event.index);
|
|
13578
|
+
if (pending2.text.length > 0 || pending2.signature !== void 0) {
|
|
13579
|
+
yield {
|
|
13580
|
+
type: "thinking-block",
|
|
13581
|
+
text: pending2.text,
|
|
13582
|
+
...pending2.signature === void 0 ? {} : { signature: pending2.signature }
|
|
13583
|
+
};
|
|
13584
|
+
}
|
|
13585
|
+
thinkingBuffers.delete(event.index);
|
|
13494
13586
|
} else if (event.type === "content_block_start" && event.content_block?.type === "tool_use" && event.index !== void 0 && event.content_block.id && event.content_block.name) {
|
|
13495
13587
|
pendingTools.set(event.index, {
|
|
13496
13588
|
id: event.content_block.id,
|
|
@@ -13693,12 +13785,14 @@ function formatOpenAIUsage(model, breakdown) {
|
|
|
13693
13785
|
var OpenAIModelAdapter = class {
|
|
13694
13786
|
client;
|
|
13695
13787
|
debugUsage;
|
|
13788
|
+
thinkingEffort;
|
|
13696
13789
|
constructor(options) {
|
|
13697
13790
|
this.client = options.client ?? new OpenAI({
|
|
13698
13791
|
...options.apiKey === void 0 ? {} : { apiKey: options.apiKey },
|
|
13699
13792
|
...options.baseURL === void 0 ? {} : { baseURL: options.baseURL }
|
|
13700
13793
|
});
|
|
13701
13794
|
this.debugUsage = options.debugUsage ?? isEnvTruthy(process.env.FLAVOR_DEBUG_USAGE);
|
|
13795
|
+
this.thinkingEffort = options.thinkingEffort;
|
|
13702
13796
|
}
|
|
13703
13797
|
#logUsage(model, breakdown) {
|
|
13704
13798
|
if (breakdown === void 0) return;
|
|
@@ -13720,6 +13814,7 @@ var OpenAIModelAdapter = class {
|
|
|
13720
13814
|
const body = {
|
|
13721
13815
|
model: request.model,
|
|
13722
13816
|
input: (await Promise.all(request.messages.map(toInput))).flat(),
|
|
13817
|
+
...this.thinkingEffort === void 0 ? {} : { reasoning: { effort: this.thinkingEffort } },
|
|
13723
13818
|
tools: [...request.tools].sort((a, b) => a.name.localeCompare(b.name)).map((tool) => ({
|
|
13724
13819
|
type: "function",
|
|
13725
13820
|
name: tool.name,
|
|
@@ -13752,6 +13847,8 @@ var OpenAIModelAdapter = class {
|
|
|
13752
13847
|
}
|
|
13753
13848
|
} else if (event.type === "response.output_text.delta" && event.delta) {
|
|
13754
13849
|
yield { type: "text", text: event.delta };
|
|
13850
|
+
} else if ((event.type === "response.reasoning_summary_text.delta" || event.type === "response.reasoning_text.delta") && event.delta) {
|
|
13851
|
+
yield { type: "thinking", text: event.delta };
|
|
13755
13852
|
} else if (event.type === "response.function_call_arguments.done" && event.output_index !== void 0 && event.name) {
|
|
13756
13853
|
const callId = callIds.get(event.output_index);
|
|
13757
13854
|
if (callId && !emittedCalls.has(event.output_index)) {
|
|
@@ -15301,6 +15398,13 @@ import { basename as basename5, dirname as dirname15, join as join23, relative a
|
|
|
15301
15398
|
import { randomUUID as randomUUID13 } from "crypto";
|
|
15302
15399
|
import { z as z27 } from "zod";
|
|
15303
15400
|
|
|
15401
|
+
// src/ui/thinking-line.ts
|
|
15402
|
+
var THINKING_MAX_STORED_CHARS = 4e3;
|
|
15403
|
+
function appendThinkingText(previous, delta) {
|
|
15404
|
+
const next = (previous ?? "") + delta;
|
|
15405
|
+
return next.length > THINKING_MAX_STORED_CHARS ? next.slice(-THINKING_MAX_STORED_CHARS) : next;
|
|
15406
|
+
}
|
|
15407
|
+
|
|
15304
15408
|
// src/ui/transcript.ts
|
|
15305
15409
|
function createTranscriptState() {
|
|
15306
15410
|
return { completed: [], nextId: 1 };
|
|
@@ -15397,6 +15501,9 @@ function transcriptReducer(state, action) {
|
|
|
15397
15501
|
if (event.type === "text") {
|
|
15398
15502
|
return { ...state, active: addText(withoutModelActivity(state.active), event.text) };
|
|
15399
15503
|
}
|
|
15504
|
+
if (event.type === "thinking") {
|
|
15505
|
+
return { ...state, active: attachThinking(state.active, event.text) };
|
|
15506
|
+
}
|
|
15400
15507
|
if (event.type === "tool-start") return upsertStatus({ ...state, active: withoutModelActivity(state.active) }, {
|
|
15401
15508
|
kind: "status",
|
|
15402
15509
|
id: `tool:${event.id}`,
|
|
@@ -15697,6 +15804,18 @@ function finishActive(state) {
|
|
|
15697
15804
|
...state.taskSnapshot === void 0 ? {} : { taskSnapshot: state.taskSnapshot }
|
|
15698
15805
|
};
|
|
15699
15806
|
}
|
|
15807
|
+
function attachThinking(turn, text) {
|
|
15808
|
+
const blocks = [...turn.blocks];
|
|
15809
|
+
const index = blocks.findIndex((block2) => block2.kind === "status" && block2.activity === "model" && block2.state === "running");
|
|
15810
|
+
if (index < 0) return turn;
|
|
15811
|
+
const block = blocks[index];
|
|
15812
|
+
if (block === void 0 || block.kind !== "status") return turn;
|
|
15813
|
+
blocks[index] = {
|
|
15814
|
+
...block,
|
|
15815
|
+
thinkingText: appendThinkingText(block.thinkingText, text)
|
|
15816
|
+
};
|
|
15817
|
+
return { ...turn, blocks };
|
|
15818
|
+
}
|
|
15700
15819
|
function withoutModelActivity(turn, id) {
|
|
15701
15820
|
const blocks = turn.blocks.filter((block) => block.kind !== "status" || block.activity !== "model" || id !== void 0 && block.id !== id);
|
|
15702
15821
|
if (blocks.length === turn.blocks.length) return turn;
|
|
@@ -19565,6 +19684,232 @@ function formatReviewReport(report) {
|
|
|
19565
19684
|
return lines.join("\n");
|
|
19566
19685
|
}
|
|
19567
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
|
+
|
|
19568
19913
|
// src/git/tools.ts
|
|
19569
19914
|
import { z as z34 } from "zod";
|
|
19570
19915
|
var GitHistoryInput = z34.object({
|
|
@@ -19766,7 +20111,7 @@ function createTodoWriteTool() {
|
|
|
19766
20111
|
// src/tools/managed.ts
|
|
19767
20112
|
import { randomUUID as randomUUID18 } from "crypto";
|
|
19768
20113
|
import { link, lstat as lstat11, mkdir as mkdir20, readFile as readFile22, readdir as readdir9, unlink as unlink3, writeFile as writeFile12 } from "fs/promises";
|
|
19769
|
-
import { dirname as dirname21, join as
|
|
20114
|
+
import { dirname as dirname21, join as join28, resolve as resolve27 } from "path";
|
|
19770
20115
|
import { z as z37 } from "zod";
|
|
19771
20116
|
var MANAGED_TOOL_VERSION = 1;
|
|
19772
20117
|
var MAX_RECORD_BYTES = 256 * 1024;
|
|
@@ -19808,9 +20153,9 @@ var ManagedToolStore = class {
|
|
|
19808
20153
|
#entries = /* @__PURE__ */ new Map();
|
|
19809
20154
|
#diagnostics = [];
|
|
19810
20155
|
constructor(options) {
|
|
19811
|
-
this.#workspace =
|
|
19812
|
-
this.#projectRoot =
|
|
19813
|
-
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");
|
|
19814
20159
|
this.#now = options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
19815
20160
|
}
|
|
19816
20161
|
get diagnostics() {
|
|
@@ -19857,7 +20202,7 @@ var ManagedToolStore = class {
|
|
|
19857
20202
|
version: MANAGED_TOOL_VERSION,
|
|
19858
20203
|
createdAt: this.#now()
|
|
19859
20204
|
});
|
|
19860
|
-
const path =
|
|
20205
|
+
const path = join28(this.directory(input.scope), filename(input.name));
|
|
19861
20206
|
const definition = compileDefinition(record3, input.scope, this.#workspace);
|
|
19862
20207
|
await writeExclusive(path, `${JSON.stringify(record3, void 0, 2)}
|
|
19863
20208
|
`);
|
|
@@ -19882,7 +20227,7 @@ var ManagedToolStore = class {
|
|
|
19882
20227
|
if (!parsed.success) return [];
|
|
19883
20228
|
const matches = this.#matching(parsed.data.name).filter((entry2) => parsed.data.scope === void 0 || entry2.scope === parsed.data.scope);
|
|
19884
20229
|
if (matches.length > 0) return matches.map((entry2) => entry2.path);
|
|
19885
|
-
return [
|
|
20230
|
+
return [join28(this.directory(parsed.data.scope ?? "project"), filename(parsed.data.name))];
|
|
19886
20231
|
}
|
|
19887
20232
|
async #loadDirectory(scope2) {
|
|
19888
20233
|
const root = this.directory(scope2);
|
|
@@ -19895,7 +20240,7 @@ var ManagedToolStore = class {
|
|
|
19895
20240
|
return;
|
|
19896
20241
|
}
|
|
19897
20242
|
for (const name of names) {
|
|
19898
|
-
const path =
|
|
20243
|
+
const path = join28(root, name);
|
|
19899
20244
|
try {
|
|
19900
20245
|
const info = await lstat11(path);
|
|
19901
20246
|
if (!info.isFile() || info.isSymbolicLink()) throw new Error("Managed tool record must be a regular file");
|
|
@@ -20484,6 +20829,7 @@ var MVP_COMMANDS = [
|
|
|
20484
20829
|
"mcp",
|
|
20485
20830
|
"commit",
|
|
20486
20831
|
"review",
|
|
20832
|
+
"explain",
|
|
20487
20833
|
"ide",
|
|
20488
20834
|
"memory",
|
|
20489
20835
|
"remember",
|
|
@@ -20559,6 +20905,12 @@ function parseSlashCommand(input, dynamicCommands = [], skillCommands = [], mana
|
|
|
20559
20905
|
const focus = args.join(" ").trim();
|
|
20560
20906
|
return focus.length === 0 ? { name } : { name, focus };
|
|
20561
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
|
+
}
|
|
20562
20914
|
if (name === "loop") {
|
|
20563
20915
|
const goal = args.join(" ").trim();
|
|
20564
20916
|
if (!goal) return { name: "invalid", command: name, message: "Use /loop <goal>." };
|
|
@@ -21162,6 +21514,14 @@ var FlavorSession = class {
|
|
|
21162
21514
|
this.#notice(await required(this.#services.gitCommit, "commit")(command.hint, signal));
|
|
21163
21515
|
} else if (command.name === "review") {
|
|
21164
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);
|
|
21165
21525
|
} else if (command.name === "mcp") {
|
|
21166
21526
|
this.#notice(await this.#services.mcp(command, signal));
|
|
21167
21527
|
} else if (command.name === "pals") {
|
|
@@ -22198,7 +22558,7 @@ ${total}`;
|
|
|
22198
22558
|
// src/memory/store.ts
|
|
22199
22559
|
import { createHash as createHash17 } from "crypto";
|
|
22200
22560
|
import { readFile as readFile23, rm as rm12 } from "fs/promises";
|
|
22201
|
-
import { resolve as
|
|
22561
|
+
import { resolve as resolve28, join as join29, relative as relative19, sep as sep19 } from "path";
|
|
22202
22562
|
|
|
22203
22563
|
// src/memory/similarity.ts
|
|
22204
22564
|
function normalizeForSimilarity(value) {
|
|
@@ -22300,10 +22660,10 @@ var MemoryStore = class {
|
|
|
22300
22660
|
constructor(options) {
|
|
22301
22661
|
if (!Number.isSafeInteger(options.maxEntries) || options.maxEntries < 1) throw new Error("maxEntries must be an integer of at least 1");
|
|
22302
22662
|
if (!Number.isSafeInteger(options.maxEntryChars) || options.maxEntryChars < 1) throw new Error("maxEntryChars must be an integer of at least 1");
|
|
22303
|
-
this.workspace =
|
|
22304
|
-
this.#memoryRoot =
|
|
22305
|
-
this.path =
|
|
22306
|
-
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");
|
|
22307
22667
|
this.#maxEntries = options.maxEntries;
|
|
22308
22668
|
this.#maxEntryChars = options.maxEntryChars;
|
|
22309
22669
|
}
|
|
@@ -22526,7 +22886,7 @@ var MemoryStore = class {
|
|
|
22526
22886
|
}
|
|
22527
22887
|
#resolveContentPath(contentPath) {
|
|
22528
22888
|
if (!/^tasks\/[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.md$/.test(contentPath)) throw new Error("Invalid memory content path");
|
|
22529
|
-
const path =
|
|
22889
|
+
const path = resolve28(this.#memoryRoot, ...contentPath.split("/"));
|
|
22530
22890
|
const rel = relative19(this.#memoryRoot, path);
|
|
22531
22891
|
if (rel === ".." || rel.startsWith(`..${sep19}`)) throw new Error("Memory content path escapes the memory directory");
|
|
22532
22892
|
return path;
|
|
@@ -22986,12 +23346,12 @@ var MemoryReviewBridge = class {
|
|
|
22986
23346
|
|
|
22987
23347
|
// src/execution/docker.ts
|
|
22988
23348
|
import { spawn as spawn6 } from "child_process";
|
|
22989
|
-
import { relative as relative20, resolve as
|
|
23349
|
+
import { relative as relative20, resolve as resolve29, sep as sep20, win32 } from "path";
|
|
22990
23350
|
function buildDockerInvocation(options) {
|
|
22991
23351
|
const workspace = options.workspace;
|
|
22992
23352
|
const cwd = options.request.cwd;
|
|
22993
23353
|
const windows = /^[A-Za-z]:[\\/]/.test(workspace);
|
|
22994
|
-
const delta = windows ? win32.relative(workspace, cwd) : relative20(
|
|
23354
|
+
const delta = windows ? win32.relative(workspace, cwd) : relative20(resolve29(workspace), resolve29(cwd));
|
|
22995
23355
|
if (delta === ".." || delta.startsWith(`..${windows ? "\\" : sep20}`)) throw new Error("Docker cwd escapes workspace");
|
|
22996
23356
|
const containerCwd = delta.length === 0 ? "/workspace" : `/workspace/${delta.replaceAll("\\", "/")}`;
|
|
22997
23357
|
const args = [
|
|
@@ -23091,8 +23451,8 @@ function createExecutionEnvironment(workspace, config) {
|
|
|
23091
23451
|
}
|
|
23092
23452
|
|
|
23093
23453
|
// src/ide/client.ts
|
|
23094
|
-
import { readFile as readFile24, readdir as readdir10, stat as
|
|
23095
|
-
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";
|
|
23096
23456
|
var FlavorIdeClient = class {
|
|
23097
23457
|
#workspace;
|
|
23098
23458
|
#home;
|
|
@@ -23103,8 +23463,8 @@ var FlavorIdeClient = class {
|
|
|
23103
23463
|
#sessionId;
|
|
23104
23464
|
#eventTail = Promise.resolve();
|
|
23105
23465
|
constructor(options) {
|
|
23106
|
-
this.#workspace =
|
|
23107
|
-
this.#home =
|
|
23466
|
+
this.#workspace = resolve30(options.workspace);
|
|
23467
|
+
this.#home = resolve30(options.home);
|
|
23108
23468
|
this.#environment = options.environment ?? process.env;
|
|
23109
23469
|
this.#fetch = options.fetch ?? fetch;
|
|
23110
23470
|
}
|
|
@@ -23188,7 +23548,7 @@ var FlavorIdeClient = class {
|
|
|
23188
23548
|
};
|
|
23189
23549
|
if (await this.#healthy(configured)) return configured;
|
|
23190
23550
|
}
|
|
23191
|
-
const directory =
|
|
23551
|
+
const directory = join30(this.#home, ".flavor-code", "ide");
|
|
23192
23552
|
let entries;
|
|
23193
23553
|
try {
|
|
23194
23554
|
entries = (await readdir10(directory)).filter((name) => name.endsWith(".lock"));
|
|
@@ -23196,9 +23556,9 @@ var FlavorIdeClient = class {
|
|
|
23196
23556
|
return void 0;
|
|
23197
23557
|
}
|
|
23198
23558
|
const candidates = (await Promise.all(entries.map(async (name) => {
|
|
23199
|
-
const path =
|
|
23559
|
+
const path = join30(directory, name);
|
|
23200
23560
|
try {
|
|
23201
|
-
const [contents, info] = await Promise.all([readFile24(path, "utf8"),
|
|
23561
|
+
const [contents, info] = await Promise.all([readFile24(path, "utf8"), stat11(path)]);
|
|
23202
23562
|
const lock = parseLockfile(contents);
|
|
23203
23563
|
return lock === void 0 ? void 0 : { lock, modified: info.mtimeMs };
|
|
23204
23564
|
} catch {
|
|
@@ -23302,8 +23662,8 @@ function parsePort(value) {
|
|
|
23302
23662
|
return Number.isInteger(port2) && port2 > 0 && port2 <= 65535 ? port2 : void 0;
|
|
23303
23663
|
}
|
|
23304
23664
|
function isWithin8(parent, child) {
|
|
23305
|
-
const base = normalize(
|
|
23306
|
-
const target = normalize(
|
|
23665
|
+
const base = normalize(resolve30(parent));
|
|
23666
|
+
const target = normalize(resolve30(child));
|
|
23307
23667
|
return target === base || target.startsWith(`${base}${sep21}`);
|
|
23308
23668
|
}
|
|
23309
23669
|
function normalize(path) {
|
|
@@ -23457,8 +23817,8 @@ function snapshot2(job) {
|
|
|
23457
23817
|
|
|
23458
23818
|
// src/terminal/service.ts
|
|
23459
23819
|
import { randomUUID as randomUUID20 } from "crypto";
|
|
23460
|
-
import { createRequire as
|
|
23461
|
-
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";
|
|
23462
23822
|
var TerminalService = class {
|
|
23463
23823
|
#root;
|
|
23464
23824
|
#factory;
|
|
@@ -23466,7 +23826,7 @@ var TerminalService = class {
|
|
|
23466
23826
|
#maxOutputChars;
|
|
23467
23827
|
#sessions = /* @__PURE__ */ new Map();
|
|
23468
23828
|
constructor(workspace, options = {}) {
|
|
23469
|
-
this.#root =
|
|
23829
|
+
this.#root = resolve31(workspace);
|
|
23470
23830
|
this.#factory = options.factory ?? defaultFactory;
|
|
23471
23831
|
this.#jobs = options.jobs;
|
|
23472
23832
|
this.#maxOutputChars = options.maxOutputChars ?? 2e5;
|
|
@@ -23571,7 +23931,7 @@ function snapshot3(record3) {
|
|
|
23571
23931
|
}
|
|
23572
23932
|
function defaultFactory(shell2, args, options) {
|
|
23573
23933
|
try {
|
|
23574
|
-
const pty =
|
|
23934
|
+
const pty = nodeCreateRequire2(import.meta.url)("node-pty");
|
|
23575
23935
|
return pty.spawn(shell2, args, { ...options, name: "xterm-256color" });
|
|
23576
23936
|
} catch (error) {
|
|
23577
23937
|
throw new Error(`Pseudo-terminal backend is unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -23584,7 +23944,7 @@ function cleanEnvironment(environment) {
|
|
|
23584
23944
|
return Object.fromEntries(Object.entries(environment).filter((entry2) => entry2[1] !== void 0));
|
|
23585
23945
|
}
|
|
23586
23946
|
function workspacePath(root, input) {
|
|
23587
|
-
const path =
|
|
23947
|
+
const path = resolve31(root, input);
|
|
23588
23948
|
const delta = relative22(root, path);
|
|
23589
23949
|
if (delta === ".." || delta.startsWith(`..${sep22}`) || isAbsolute14(delta)) throw new Error("Terminal cwd is outside the workspace");
|
|
23590
23950
|
return path;
|
|
@@ -23592,7 +23952,7 @@ function workspacePath(root, input) {
|
|
|
23592
23952
|
|
|
23593
23953
|
// src/tools/terminal.ts
|
|
23594
23954
|
import { z as z40 } from "zod";
|
|
23595
|
-
import { resolve as
|
|
23955
|
+
import { resolve as resolve32 } from "path";
|
|
23596
23956
|
var Id = z40.string().regex(/^term-[a-f0-9-]+$/);
|
|
23597
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() });
|
|
23598
23958
|
var OptionalBoolean = z40.union([z40.boolean(), z40.string().refine((value) => value === "true" || value === "false")]);
|
|
@@ -23606,8 +23966,8 @@ function createTerminalTools(terminals, workspace = process.cwd()) {
|
|
|
23606
23966
|
name: "TerminalOpen",
|
|
23607
23967
|
description: "Open a persistent interactive pseudo-terminal in the workspace",
|
|
23608
23968
|
inputSchema: OpenInput,
|
|
23609
|
-
paths: (input) => [
|
|
23610
|
-
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 ?? ".") }),
|
|
23611
23971
|
execute: async (input, _signal, context) => terminals.open({
|
|
23612
23972
|
owner: owner(context),
|
|
23613
23973
|
...input.cwd === void 0 ? {} : { cwd: input.cwd },
|
|
@@ -23703,7 +24063,7 @@ function palSocketAddress(options) {
|
|
|
23703
24063
|
import { createHash as createHash20 } from "crypto";
|
|
23704
24064
|
import { Socket as Socket2 } from "net";
|
|
23705
24065
|
import { tmpdir as tmpdir3 } from "os";
|
|
23706
|
-
import { join as
|
|
24066
|
+
import { join as join32 } from "path";
|
|
23707
24067
|
import { spawn as spawn7 } from "child_process";
|
|
23708
24068
|
|
|
23709
24069
|
// src/pals/broker.ts
|
|
@@ -23716,16 +24076,16 @@ import { dirname as dirname23 } from "path";
|
|
|
23716
24076
|
import { randomBytes as randomBytes3, randomUUID as randomUUID21 } from "crypto";
|
|
23717
24077
|
import { chmod as chmod2, link as link2, mkdir as mkdir21, open as open12, readFile as readFile25, unlink as unlink4 } from "fs/promises";
|
|
23718
24078
|
import { homedir } from "os";
|
|
23719
|
-
import { join as
|
|
24079
|
+
import { join as join31 } from "path";
|
|
23720
24080
|
var PAL_AUTH_TOKEN_BYTES = 32;
|
|
23721
24081
|
var TOKEN_PATTERN = /^[a-f0-9]{64}$/;
|
|
23722
24082
|
async function loadOrCreatePalAuthToken(options = {}) {
|
|
23723
|
-
const directory =
|
|
23724
|
-
const tokenPath =
|
|
24083
|
+
const directory = join31(options.home ?? homedir(), ".flavor-code", "pals");
|
|
24084
|
+
const tokenPath = join31(directory, "auth-token");
|
|
23725
24085
|
await mkdir21(directory, { recursive: true, mode: 448 });
|
|
23726
24086
|
await chmod2(directory, 448);
|
|
23727
24087
|
const token = (options.random ?? randomBytes3)(PAL_AUTH_TOKEN_BYTES).toString("hex");
|
|
23728
|
-
const temporaryPath =
|
|
24088
|
+
const temporaryPath = join31(directory, `.auth-token-${process.pid}-${randomUUID21()}.tmp`);
|
|
23729
24089
|
let handle;
|
|
23730
24090
|
try {
|
|
23731
24091
|
handle = await open12(temporaryPath, "wx", 384);
|
|
@@ -23760,7 +24120,7 @@ function validatePalAuthToken(token) {
|
|
|
23760
24120
|
|
|
23761
24121
|
// src/pals/lifecycle.ts
|
|
23762
24122
|
import { randomUUID as randomUUID22 } from "crypto";
|
|
23763
|
-
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";
|
|
23764
24124
|
import { dirname as dirname22 } from "path";
|
|
23765
24125
|
function defaultProcessAlive(pid) {
|
|
23766
24126
|
if (!Number.isSafeInteger(pid) || pid <= 0) return true;
|
|
@@ -23792,7 +24152,7 @@ async function acquirePalFileLock(options) {
|
|
|
23792
24152
|
handle = await open13(options.path, "wx", 384);
|
|
23793
24153
|
} catch (error) {
|
|
23794
24154
|
if (error.code !== "EEXIST") throw error;
|
|
23795
|
-
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)]);
|
|
23796
24156
|
if (await options.endpointLive()) return void 0;
|
|
23797
24157
|
const existing = parseMetadata(contents);
|
|
23798
24158
|
if (existing !== void 0 && (options.processAlive ?? defaultProcessAlive)(existing.pid)) return void 0;
|
|
@@ -23855,7 +24215,7 @@ function canConnect(address) {
|
|
|
23855
24215
|
}
|
|
23856
24216
|
function startupLockPath(address) {
|
|
23857
24217
|
const digest = createHash20("sha256").update(address, "utf8").digest("hex").slice(0, 24);
|
|
23858
|
-
return
|
|
24218
|
+
return join32(tmpdir3(), `flavor-code-pals-${digest}.startup.lock`);
|
|
23859
24219
|
}
|
|
23860
24220
|
async function spawnProductionBroker(address) {
|
|
23861
24221
|
const entry2 = process.argv[1];
|
|
@@ -24547,8 +24907,8 @@ var ApprovalBridge = class {
|
|
|
24547
24907
|
}
|
|
24548
24908
|
};
|
|
24549
24909
|
async function createProductionRuntime(options) {
|
|
24550
|
-
const workspace =
|
|
24551
|
-
const home =
|
|
24910
|
+
const workspace = resolve33(options.workspace ?? process.cwd());
|
|
24911
|
+
const home = resolve33(options.home ?? homedir2());
|
|
24552
24912
|
const environment = options.environment ?? process.env;
|
|
24553
24913
|
const ide = new FlavorIdeClient({ workspace, home, environment });
|
|
24554
24914
|
const ideReady = ide.initialize();
|
|
@@ -24772,8 +25132,8 @@ async function createProductionRuntime(options) {
|
|
|
24772
25132
|
const registeredProviders = registration.registered;
|
|
24773
25133
|
let effectiveLlm = registration.effectiveLlm;
|
|
24774
25134
|
const pluginHost = new PluginHost({
|
|
24775
|
-
globalPluginDirs: [
|
|
24776
|
-
projectPluginDirs: [
|
|
25135
|
+
globalPluginDirs: [join33(home, ".flavor-code", "plugins")],
|
|
25136
|
+
projectPluginDirs: [join33(workspace, ".flavor", "plugins")],
|
|
24777
25137
|
config,
|
|
24778
25138
|
// Existing project plugins (including astgraph and superharness) use Node.js
|
|
24779
25139
|
// built-ins. The Worker/vm sandbox deliberately blocks those imports, so it
|
|
@@ -24881,7 +25241,7 @@ async function createProductionRuntime(options) {
|
|
|
24881
25241
|
const address = palSocketAddress({
|
|
24882
25242
|
platform: process.platform,
|
|
24883
25243
|
userScope: home,
|
|
24884
|
-
runtimeDir:
|
|
25244
|
+
runtimeDir: join33(tmpdir4(), `flavor-code-pals-${createHash21("sha256").update(home).digest("hex").slice(0, 16)}`)
|
|
24885
25245
|
});
|
|
24886
25246
|
const baseCollaborationClient = collaboration.client ?? collaboration.createClient?.({
|
|
24887
25247
|
instanceId: collaboration.instanceId,
|
|
@@ -24992,8 +25352,8 @@ async function createProductionRuntime(options) {
|
|
|
24992
25352
|
diagnostics.push(`MCP servers could not start: ${message(error)}`);
|
|
24993
25353
|
});
|
|
24994
25354
|
const skills = new SkillRegistry({
|
|
24995
|
-
globalRoots: [
|
|
24996
|
-
projectRoots: [
|
|
25355
|
+
globalRoots: [join33(home, ".flavor-code", "skills")],
|
|
25356
|
+
projectRoots: [join33(workspace, ".flavor", "skills"), ...pluginSkillRoots],
|
|
24997
25357
|
authorizeResource: async () => true,
|
|
24998
25358
|
disabledNames: config.skills.disabled
|
|
24999
25359
|
});
|
|
@@ -25001,7 +25361,7 @@ async function createProductionRuntime(options) {
|
|
|
25001
25361
|
skillsReady.catch(() => void 0);
|
|
25002
25362
|
tools.push(createSkillTool(skills), createSkillResourceTool(skills));
|
|
25003
25363
|
if (options.extraTools !== void 0) tools.push(...options.extraTools);
|
|
25004
|
-
const flavor = await optionalText2(
|
|
25364
|
+
const flavor = await optionalText2(join33(workspace, "FLAVOR.md"));
|
|
25005
25365
|
const instructionBaseline = await workspaceInstructions.baseline();
|
|
25006
25366
|
let memoryContext;
|
|
25007
25367
|
if (memoryStore !== void 0) {
|
|
@@ -25579,7 +25939,7 @@ async function createProductionRuntime(options) {
|
|
|
25579
25939
|
evolveService.toolDefinition(),
|
|
25580
25940
|
...mcpTools
|
|
25581
25941
|
];
|
|
25582
|
-
const loopFlavor = await optionalText2(
|
|
25942
|
+
const loopFlavor = await optionalText2(join33(input.workspace, "FLAVOR.md"));
|
|
25583
25943
|
const loopInstructionBaseline = await loopInstructions.baseline();
|
|
25584
25944
|
const loopEnvironment = createPromptEnvironment({
|
|
25585
25945
|
now: /* @__PURE__ */ new Date(),
|
|
@@ -25939,6 +26299,22 @@ async function createProductionRuntime(options) {
|
|
|
25939
26299
|
modelId: () => harness.subagentModelId,
|
|
25940
26300
|
notify: (message2) => emitOutput({ type: "notice", message: message2 })
|
|
25941
26301
|
}, focus, signal),
|
|
26302
|
+
explain: (query, focus, signal, onText) => runExplain({
|
|
26303
|
+
graph: new DesktopAstGraphService(workspace),
|
|
26304
|
+
readFile: async (path) => {
|
|
26305
|
+
const root = resolve33(workspace);
|
|
26306
|
+
const abs = resolve33(root, path);
|
|
26307
|
+
if (abs !== root && !abs.startsWith(root + sep23)) throw new Error(`path outside workspace: ${path}`);
|
|
26308
|
+
return readFile27(abs, "utf8");
|
|
26309
|
+
},
|
|
26310
|
+
history: async (path, limit) => fileHistory(workspace, path, limit),
|
|
26311
|
+
questions,
|
|
26312
|
+
registry,
|
|
26313
|
+
modelId: () => harness.subagentModelId,
|
|
26314
|
+
language,
|
|
26315
|
+
notify: (message2) => emitOutput({ type: "notice", message: message2 }),
|
|
26316
|
+
...onText === void 0 ? {} : { onText }
|
|
26317
|
+
}, query, focus, signal),
|
|
25942
26318
|
audit: async (toolFilter) => {
|
|
25943
26319
|
try {
|
|
25944
26320
|
const raw = await readFile27(auditLogger.path, "utf8");
|
|
@@ -26128,7 +26504,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
26128
26504
|
return `Provider "${providerName}" is missing authorizationUrl, tokenUrl, or clientId.`;
|
|
26129
26505
|
}
|
|
26130
26506
|
try {
|
|
26131
|
-
const tokenStore = createFileTokenStore(
|
|
26507
|
+
const tokenStore = createFileTokenStore(join33(home, ".flavor-code", "auth.json"));
|
|
26132
26508
|
const credentialId = oauthCredentialId(oauthConfig.tokenUrl, oauthConfig.clientId);
|
|
26133
26509
|
await migrateLegacyOAuthToken(tokenStore, providerName, credentialId);
|
|
26134
26510
|
const oauth = new OAuthCallbackAuthProvider({
|
|
@@ -26151,8 +26527,12 @@ ${renderMemoryDocument(entries)}`;
|
|
|
26151
26527
|
};
|
|
26152
26528
|
registry.register(next.providerId, next.apiType === "anthropic" ? new AnthropicModelAdapter({
|
|
26153
26529
|
...adapterOptions,
|
|
26530
|
+
...providerConfig?.thinkingBudget === void 0 ? {} : { thinkingBudget: providerConfig.thinkingBudget },
|
|
26154
26531
|
...providerConfig?.claudeClient === true ? { headers: CLAUDE_CLIENT_HEADERS } : {}
|
|
26155
|
-
}) : new OpenAIModelAdapter(
|
|
26532
|
+
}) : new OpenAIModelAdapter({
|
|
26533
|
+
...adapterOptions,
|
|
26534
|
+
...providerConfig?.thinkingEffort === void 0 ? {} : { thinkingEffort: providerConfig.thinkingEffort }
|
|
26535
|
+
}));
|
|
26156
26536
|
effectiveLlm = next;
|
|
26157
26537
|
mainModel = `${next.providerId}:${next.defaultModel}`;
|
|
26158
26538
|
childModel = `${next.providerId}:${next.cheapModel}`;
|
|
@@ -26171,7 +26551,7 @@ ${renderMemoryDocument(entries)}`;
|
|
|
26171
26551
|
}
|
|
26172
26552
|
},
|
|
26173
26553
|
async logout() {
|
|
26174
|
-
const tokenStore = createFileTokenStore(
|
|
26554
|
+
const tokenStore = createFileTokenStore(join33(home, ".flavor-code", "auth.json"));
|
|
26175
26555
|
const tokens2 = await tokenStore.load();
|
|
26176
26556
|
const hadCredentials = Object.keys(tokens2).length > 0 || effectiveLlm !== void 0;
|
|
26177
26557
|
if (!hadCredentials) {
|
|
@@ -26447,8 +26827,8 @@ async function workspaceFingerprint(workspace) {
|
|
|
26447
26827
|
let remaining = 5 * 1024 * 1024;
|
|
26448
26828
|
for (const name of untracked.stdout.split("\0").filter(Boolean).sort()) {
|
|
26449
26829
|
if (name === ".flavor/loops" || name.startsWith(".flavor/loops/")) continue;
|
|
26450
|
-
const path =
|
|
26451
|
-
const relativePath = relative23(
|
|
26830
|
+
const path = resolve33(workspace, name);
|
|
26831
|
+
const relativePath = relative23(resolve33(workspace), path);
|
|
26452
26832
|
if (relativePath === ".." || relativePath.startsWith(`..${sep23}`)) continue;
|
|
26453
26833
|
hash.update(name);
|
|
26454
26834
|
if (remaining <= 0) continue;
|
|
@@ -26666,7 +27046,7 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
|
|
|
26666
27046
|
const configured = { ...providers };
|
|
26667
27047
|
if (configured.openai === void 0 && environment.OPENAI_API_KEY) configured.openai = { type: "openai", apiKey: environment.OPENAI_API_KEY };
|
|
26668
27048
|
if (configured.anthropic === void 0 && environment.ANTHROPIC_API_KEY) configured.anthropic = { type: "anthropic", apiKey: environment.ANTHROPIC_API_KEY };
|
|
26669
|
-
const oauthTokenStore = createFileTokenStore(
|
|
27049
|
+
const oauthTokenStore = createFileTokenStore(join33(home, ".flavor-code", "auth.json"));
|
|
26670
27050
|
const registered = [];
|
|
26671
27051
|
let effectiveLlm;
|
|
26672
27052
|
for (const [name, provider] of Object.entries(configured)) {
|
|
@@ -26738,8 +27118,12 @@ async function registerConfiguredAdapters(providers, registry, environment, diag
|
|
|
26738
27118
|
};
|
|
26739
27119
|
const adapter = apiProtocol === "anthropic" ? new AnthropicModelAdapter({
|
|
26740
27120
|
...adapterOptions,
|
|
27121
|
+
...runtimeProvider.thinkingBudget === void 0 ? {} : { thinkingBudget: runtimeProvider.thinkingBudget },
|
|
26741
27122
|
...runtimeProvider.claudeClient === true ? { headers: CLAUDE_CLIENT_HEADERS } : {}
|
|
26742
|
-
}) : new OpenAIModelAdapter(
|
|
27123
|
+
}) : new OpenAIModelAdapter({
|
|
27124
|
+
...adapterOptions,
|
|
27125
|
+
...runtimeProvider.thinkingEffort === void 0 ? {} : { thinkingEffort: runtimeProvider.thinkingEffort }
|
|
27126
|
+
});
|
|
26743
27127
|
const cacheProfile = resolveCacheProfile({ apiType: apiProtocol, baseURL: runtimeProvider.baseURL });
|
|
26744
27128
|
if (apiProtocol === "openai" && isDashScopeBaseURL(runtimeProvider.baseURL)) {
|
|
26745
27129
|
diagnostics.push(
|
|
@@ -27093,13 +27477,13 @@ function createMemoryTaskId() {
|
|
|
27093
27477
|
|
|
27094
27478
|
// src/desktop/runtime-controller.ts
|
|
27095
27479
|
import { createHash as createHash27 } from "crypto";
|
|
27096
|
-
import { mkdir as mkdir27, readFile as readFile35, readdir as readdir12, rm as rm15, stat as
|
|
27480
|
+
import { mkdir as mkdir27, readFile as readFile35, readdir as readdir12, rm as rm15, stat as stat15, writeFile as writeFile17 } from "fs/promises";
|
|
27097
27481
|
import { homedir as homedir4 } from "os";
|
|
27098
|
-
import { basename as basename9, join as
|
|
27482
|
+
import { basename as basename9, join as join43, resolve as resolve36 } from "path";
|
|
27099
27483
|
|
|
27100
27484
|
// src/d2c/workflow.ts
|
|
27101
27485
|
import { createHash as createHash22 } from "crypto";
|
|
27102
|
-
import { join as
|
|
27486
|
+
import { join as join34 } from "path";
|
|
27103
27487
|
import { z as z42 } from "zod";
|
|
27104
27488
|
|
|
27105
27489
|
// src/d2c/workflow-shared.ts
|
|
@@ -27245,7 +27629,7 @@ function reviewsFor(report, previous) {
|
|
|
27245
27629
|
function implementationDirectory(report) {
|
|
27246
27630
|
const source = report.implementation.source;
|
|
27247
27631
|
if (!/^https?:\/\//i.test(source)) return source;
|
|
27248
|
-
return
|
|
27632
|
+
return join34("src", "d2c-output", report.task);
|
|
27249
27633
|
}
|
|
27250
27634
|
function createWorkflow(report, framework) {
|
|
27251
27635
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -27369,7 +27753,7 @@ function applyQualityIssueDecision(workflow, issueId2, decision) {
|
|
|
27369
27753
|
};
|
|
27370
27754
|
}
|
|
27371
27755
|
function workflowPath(workspace, task) {
|
|
27372
|
-
return
|
|
27756
|
+
return join34(taskDir(workspace, task), "workflow.json");
|
|
27373
27757
|
}
|
|
27374
27758
|
async function readWorkflow(workspace, task) {
|
|
27375
27759
|
return (await readRecoverableFile(workflowPath(workspace, task), decodeWorkflow))?.value;
|
|
@@ -27623,7 +28007,7 @@ function confirmApiMapping(mapping, operationKey, operations) {
|
|
|
27623
28007
|
|
|
27624
28008
|
// src/d2c/modules.ts
|
|
27625
28009
|
import { readFile as readFile28 } from "fs/promises";
|
|
27626
|
-
import { isAbsolute as isAbsolute15, join as
|
|
28010
|
+
import { isAbsolute as isAbsolute15, join as join35, normalize as normalize2, posix, sep as sep24, win32 as win322 } from "path";
|
|
27627
28011
|
import { z as z43 } from "zod";
|
|
27628
28012
|
var ModuleSchema = z43.object({
|
|
27629
28013
|
id: z43.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/),
|
|
@@ -27642,13 +28026,13 @@ function safeSource(path) {
|
|
|
27642
28026
|
}
|
|
27643
28027
|
function d2cOutputDirectory(workspace, task) {
|
|
27644
28028
|
if (!D2C_TASK_PATTERN.test(task)) throw new Error(`Invalid D2C task: ${task}`);
|
|
27645
|
-
return
|
|
28029
|
+
return join35(workspace, "src", "d2c-output", task);
|
|
27646
28030
|
}
|
|
27647
28031
|
async function readD2cModules(workspace, task) {
|
|
27648
28032
|
const project = d2cOutputDirectory(workspace, task);
|
|
27649
28033
|
let raw;
|
|
27650
28034
|
try {
|
|
27651
|
-
raw = await readFile28(
|
|
28035
|
+
raw = await readFile28(join35(project, "d2c.modules.json"), "utf8");
|
|
27652
28036
|
} catch {
|
|
27653
28037
|
return [{ id: "page", label: task, sourceFiles: ["src/App.vue", "src/App.jsx"], keywords: [task] }];
|
|
27654
28038
|
}
|
|
@@ -27669,7 +28053,7 @@ async function readD2cModules(workspace, task) {
|
|
|
27669
28053
|
|
|
27670
28054
|
// src/d2c/integration.ts
|
|
27671
28055
|
import { mkdir as mkdir24, readFile as readFile29, writeFile as writeFile13 } from "fs/promises";
|
|
27672
|
-
import { join as
|
|
28056
|
+
import { join as join36 } from "path";
|
|
27673
28057
|
function record2(value) {
|
|
27674
28058
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
|
|
27675
28059
|
}
|
|
@@ -27805,7 +28189,7 @@ async def reset_acceptance(request: Request):
|
|
|
27805
28189
|
}
|
|
27806
28190
|
async function generateIntegrationArtifacts(projectDir, document, mappings, options = {}) {
|
|
27807
28191
|
if (mappings.some((mapping) => mapping.status === "needs-confirmation")) throw new Error("Confirm every uncertain OpenAPI mapping before generating integration code");
|
|
27808
|
-
const packagePath =
|
|
28192
|
+
const packagePath = join36(projectDir, "package.json");
|
|
27809
28193
|
let pkg;
|
|
27810
28194
|
try {
|
|
27811
28195
|
pkg = JSON.parse(await readFile29(packagePath, "utf8"));
|
|
@@ -27815,9 +28199,9 @@ async function generateIntegrationArtifacts(projectDir, document, mappings, opti
|
|
|
27815
28199
|
const scripts = options.pythonServer ? { ...record2(pkg.scripts) ?? {} } : { ...record2(pkg.scripts) ?? {}, mock: "node mock/server.mjs" };
|
|
27816
28200
|
const dependencies2 = options.pythonServer ? { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9" } : { ...record2(pkg.dependencies) ?? {}, axios: "^1.7.9", express: "^5.2.1" };
|
|
27817
28201
|
const selected = [...new Set(mappings.map((mapping) => mapping.operationKey))].map((key) => document.operations.find((operation) => operation.id === key)).filter((operation) => operation !== void 0);
|
|
27818
|
-
const apiDir =
|
|
27819
|
-
const mockDir =
|
|
27820
|
-
const serverDir =
|
|
28202
|
+
const apiDir = join36(projectDir, "src", "api");
|
|
28203
|
+
const mockDir = join36(projectDir, "mock");
|
|
28204
|
+
const serverDir = join36(projectDir, "server");
|
|
27821
28205
|
await Promise.all([
|
|
27822
28206
|
mkdir24(apiDir, { recursive: true }),
|
|
27823
28207
|
...options.pythonServer ? [] : [mkdir24(mockDir, { recursive: true })],
|
|
@@ -27827,23 +28211,23 @@ async function generateIntegrationArtifacts(projectDir, document, mappings, opti
|
|
|
27827
28211
|
if (options.pythonServer) files.push("server/main.py", "server/requirements.txt", "server/README.md", "server/flavor-runtime.json");
|
|
27828
28212
|
else files.push("mock/server.mjs");
|
|
27829
28213
|
await Promise.all([
|
|
27830
|
-
writeFile13(
|
|
28214
|
+
writeFile13(join36(apiDir, "http.js"), `import axios from "axios";
|
|
27831
28215
|
|
|
27832
28216
|
export const http = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || ${JSON.stringify(document.baseUrl ?? "")}, timeout: 15000 });
|
|
27833
28217
|
|
|
27834
28218
|
http.interceptors.response.use((response) => response, (error) => Promise.reject({ name: "ApiError", status: error.response?.status, message: error.response?.data?.message || error.message, cause: error }));
|
|
27835
28219
|
`),
|
|
27836
|
-
writeFile13(
|
|
27837
|
-
writeFile13(
|
|
28220
|
+
writeFile13(join36(apiDir, "d2c-api.js"), apiModule(selected)),
|
|
28221
|
+
writeFile13(join36(apiDir, "d2c-bindings.json"), `${JSON.stringify({ schema: 1, generatedAt: (/* @__PURE__ */ new Date()).toISOString(), mappings }, null, 2)}
|
|
27838
28222
|
`),
|
|
27839
|
-
...options.pythonServer ? [] : [writeFile13(
|
|
28223
|
+
...options.pythonServer ? [] : [writeFile13(join36(mockDir, "server.mjs"), mockModule({ ...document, operations: selected }))],
|
|
27840
28224
|
writeFile13(packagePath, `${JSON.stringify({ ...pkg, scripts, dependencies: dependencies2 }, null, 2)}
|
|
27841
28225
|
`),
|
|
27842
28226
|
...options.pythonServer ? [
|
|
27843
|
-
writeFile13(
|
|
27844
|
-
writeFile13(
|
|
27845
|
-
writeFile13(
|
|
27846
|
-
writeFile13(
|
|
28227
|
+
writeFile13(join36(serverDir, "main.py"), pythonServerModule({ ...document, operations: selected })),
|
|
28228
|
+
writeFile13(join36(serverDir, "requirements.txt"), "fastapi>=0.116,<1\nuvicorn[standard]>=0.35,<1\nsqlalchemy>=2.0,<3\n"),
|
|
28229
|
+
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"),
|
|
28230
|
+
writeFile13(join36(serverDir, "flavor-runtime.json"), `${JSON.stringify({
|
|
27847
28231
|
schema: 1,
|
|
27848
28232
|
kind: "python-fastapi",
|
|
27849
28233
|
cwd: "server",
|
|
@@ -27864,7 +28248,7 @@ http.interceptors.response.use((response) => response, (error) => Promise.reject
|
|
|
27864
28248
|
import { spawn as spawn8 } from "child_process";
|
|
27865
28249
|
import { access as access3, readFile as readFile30 } from "fs/promises";
|
|
27866
28250
|
import { createServer as createServer5 } from "net";
|
|
27867
|
-
import { join as
|
|
28251
|
+
import { join as join37 } from "path";
|
|
27868
28252
|
async function exists3(path) {
|
|
27869
28253
|
try {
|
|
27870
28254
|
await access3(path);
|
|
@@ -27972,7 +28356,7 @@ ${output.trim()}` : ""}`);
|
|
|
27972
28356
|
}
|
|
27973
28357
|
}
|
|
27974
28358
|
async function runPythonBackend(projectDir, options) {
|
|
27975
|
-
const serverDir =
|
|
28359
|
+
const serverDir = join37(projectDir, "server");
|
|
27976
28360
|
if (options.installDependencies !== false) await installPython(serverDir, options.signal);
|
|
27977
28361
|
const selectedPort = await port();
|
|
27978
28362
|
const url = `http://127.0.0.1:${selectedPort}`;
|
|
@@ -28018,7 +28402,7 @@ ${managed.output().slice(-4e3)}`);
|
|
|
28018
28402
|
};
|
|
28019
28403
|
}
|
|
28020
28404
|
async function runD2cMockServer(projectDir, options = {}) {
|
|
28021
|
-
const runtimePath =
|
|
28405
|
+
const runtimePath = join37(projectDir, "server", "flavor-runtime.json");
|
|
28022
28406
|
if (await exists3(runtimePath)) {
|
|
28023
28407
|
let runtime;
|
|
28024
28408
|
try {
|
|
@@ -28029,9 +28413,9 @@ async function runD2cMockServer(projectDir, options = {}) {
|
|
|
28029
28413
|
if (runtime.schema !== 1 || runtime.kind !== "python-fastapi") throw new Error("Unsupported E2E backend runtime manifest");
|
|
28030
28414
|
return runPythonBackend(projectDir, options);
|
|
28031
28415
|
}
|
|
28032
|
-
const serverPath =
|
|
28416
|
+
const serverPath = join37(projectDir, "mock", "server.mjs");
|
|
28033
28417
|
if (!await exists3(serverPath)) throw new Error(`D2C mock server is not generated at ${serverPath}`);
|
|
28034
|
-
if (options.installDependencies === false && !await exists3(
|
|
28418
|
+
if (options.installDependencies === false && !await exists3(join37(projectDir, "node_modules", "express", "package.json"))) {
|
|
28035
28419
|
throw new Error("D2C mock server dependencies are not installed");
|
|
28036
28420
|
}
|
|
28037
28421
|
if (options.installDependencies !== false) await install(projectDir, options.signal);
|
|
@@ -28073,8 +28457,8 @@ ${managed.output().slice(-4e3)}`);
|
|
|
28073
28457
|
|
|
28074
28458
|
// src/d2c/product.ts
|
|
28075
28459
|
import { createHash as createHash24, randomUUID as randomUUID27 } from "crypto";
|
|
28076
|
-
import { copyFile as copyFile2, mkdir as mkdir25, readFile as readFile32, rename as rename13, stat as
|
|
28077
|
-
import { join as
|
|
28460
|
+
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";
|
|
28461
|
+
import { join as join38 } from "path";
|
|
28078
28462
|
import { z as z44 } from "zod";
|
|
28079
28463
|
|
|
28080
28464
|
// src/e2e/prd-governance.ts
|
|
@@ -28229,13 +28613,13 @@ var PlanSchema2 = z44.object({
|
|
|
28229
28613
|
}).strict();
|
|
28230
28614
|
function d2cProductDirectory(workspace, task) {
|
|
28231
28615
|
if (!D2C_TASK_PATTERN.test(task)) throw new Error("Invalid D2C task name");
|
|
28232
|
-
return
|
|
28616
|
+
return join38(taskDir(workspace, task), "product");
|
|
28233
28617
|
}
|
|
28234
28618
|
function d2cProductPrototypeDirectory(workspace, task) {
|
|
28235
|
-
return
|
|
28619
|
+
return join38(d2cProductDirectory(workspace, task), "prototype");
|
|
28236
28620
|
}
|
|
28237
28621
|
function planPath(workspace, task) {
|
|
28238
|
-
return
|
|
28622
|
+
return join38(taskDir(workspace, task), "product-plan.json");
|
|
28239
28623
|
}
|
|
28240
28624
|
function resolveD2cProductTechnology(requirement, fallbackFramework = "vue") {
|
|
28241
28625
|
const frontendCandidates = [
|
|
@@ -28323,7 +28707,7 @@ function buildD2cProductOpenApi(task, modules) {
|
|
|
28323
28707
|
`;
|
|
28324
28708
|
}
|
|
28325
28709
|
async function fileTimestamp(path) {
|
|
28326
|
-
const info = await
|
|
28710
|
+
const info = await stat13(path).catch(() => void 0);
|
|
28327
28711
|
return info?.isFile() ? info.mtime.toISOString() : void 0;
|
|
28328
28712
|
}
|
|
28329
28713
|
async function fileHash(path) {
|
|
@@ -28418,16 +28802,16 @@ async function readD2cProductPlan(workspace, task) {
|
|
|
28418
28802
|
}
|
|
28419
28803
|
async function discoverD2cProductArtifacts(workspace, plan) {
|
|
28420
28804
|
const product = d2cProductDirectory(workspace, plan.task);
|
|
28421
|
-
const prdUpdatedAt = await fileTimestamp(
|
|
28422
|
-
const prdHash = await fileHash(
|
|
28423
|
-
const prototypeUpdatedAt = await fileTimestamp(
|
|
28424
|
-
const prototypeHash = await fileHash(
|
|
28425
|
-
const expectedManifest =
|
|
28805
|
+
const prdUpdatedAt = await fileTimestamp(join38(product, "prd.md"));
|
|
28806
|
+
const prdHash = await fileHash(join38(product, "prd.md"));
|
|
28807
|
+
const prototypeUpdatedAt = await fileTimestamp(join38(product, "prototype", "index.html"));
|
|
28808
|
+
const prototypeHash = await fileHash(join38(product, "prototype", "index.html"));
|
|
28809
|
+
const expectedManifest = join38(product, "prototype", "interaction-manifest.json");
|
|
28426
28810
|
let manifestUpdatedAt = await fileTimestamp(expectedManifest);
|
|
28427
28811
|
if (manifestUpdatedAt === void 0) {
|
|
28428
|
-
const misplacedManifest =
|
|
28812
|
+
const misplacedManifest = join38(product, "interaction-manifest.json");
|
|
28429
28813
|
if (await fileTimestamp(misplacedManifest) !== void 0) {
|
|
28430
|
-
await mkdir25(
|
|
28814
|
+
await mkdir25(join38(product, "prototype"), { recursive: true });
|
|
28431
28815
|
await copyFile2(misplacedManifest, expectedManifest);
|
|
28432
28816
|
manifestUpdatedAt = await fileTimestamp(expectedManifest);
|
|
28433
28817
|
}
|
|
@@ -28460,8 +28844,8 @@ async function readD2cProductPlanView(workspace, task) {
|
|
|
28460
28844
|
if (stored === void 0) return void 0;
|
|
28461
28845
|
const plan = await discoverD2cProductArtifacts(workspace, stored);
|
|
28462
28846
|
if (plan.revision !== stored.revision) await writeD2cProductPlan(workspace, plan);
|
|
28463
|
-
const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile32(
|
|
28464
|
-
const manifestPath =
|
|
28847
|
+
const prdMarkdown = plan.prd === void 0 ? void 0 : await readFile32(join38(taskDir(workspace, task), plan.prd.path), "utf8");
|
|
28848
|
+
const manifestPath = join38(d2cProductDirectory(workspace, task), "prototype", "interaction-manifest.json");
|
|
28465
28849
|
const validationError = plan.phase === "design-generating" && await fileTimestamp(manifestPath) !== void 0 ? await validateAndNormalizePrototypeManifest(manifestPath) : void 0;
|
|
28466
28850
|
return {
|
|
28467
28851
|
plan,
|
|
@@ -28620,7 +29004,7 @@ function createAcceptanceEvidence(coverage, run, now = /* @__PURE__ */ new Date(
|
|
|
28620
29004
|
|
|
28621
29005
|
// src/e2e/delivery-run.ts
|
|
28622
29006
|
import { createHash as createHash26 } from "crypto";
|
|
28623
|
-
import { join as
|
|
29007
|
+
import { join as join39 } from "path";
|
|
28624
29008
|
import { z as z45 } from "zod";
|
|
28625
29009
|
var DELIVERY_NODE_IDS = ["requirement", "prd", "design", "d2c", "api", "acceptance", "delivery"];
|
|
28626
29010
|
var ArtifactSchema = z45.object({
|
|
@@ -28737,7 +29121,7 @@ function completeDeliveryNode(run, node, outputs, now = /* @__PURE__ */ new Date
|
|
|
28737
29121
|
return { ...run, nodes, updatedAt: timestamp2 };
|
|
28738
29122
|
}
|
|
28739
29123
|
function deliveryRunPath(workspace, task) {
|
|
28740
|
-
return
|
|
29124
|
+
return join39(taskDir(workspace, task), "delivery-run.json");
|
|
28741
29125
|
}
|
|
28742
29126
|
function decodeDeliveryRun(raw) {
|
|
28743
29127
|
return DeliveryRunSchema.parse(JSON.parse(raw));
|
|
@@ -28780,14 +29164,14 @@ async function updateDeliveryRun(workspace, task, expectedRevision, mutation) {
|
|
|
28780
29164
|
// src/skills/manager.ts
|
|
28781
29165
|
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";
|
|
28782
29166
|
import { homedir as homedir3 } from "os";
|
|
28783
|
-
import { isAbsolute as isAbsolute16, join as
|
|
29167
|
+
import { isAbsolute as isAbsolute16, join as join40, relative as relative24, resolve as resolve34, sep as sep25 } from "path";
|
|
28784
29168
|
import { stringify } from "yaml";
|
|
28785
29169
|
var SkillManager = class {
|
|
28786
29170
|
#workspace;
|
|
28787
29171
|
#home;
|
|
28788
29172
|
constructor(options) {
|
|
28789
|
-
this.#workspace =
|
|
28790
|
-
this.#home =
|
|
29173
|
+
this.#workspace = resolve34(options.workspace);
|
|
29174
|
+
this.#home = resolve34(options.home ?? homedir3());
|
|
28791
29175
|
}
|
|
28792
29176
|
async list() {
|
|
28793
29177
|
const { registry, disabled } = await this.#registry();
|
|
@@ -28803,7 +29187,7 @@ var SkillManager = class {
|
|
|
28803
29187
|
async create(draft) {
|
|
28804
29188
|
const value = parseDraft(draft);
|
|
28805
29189
|
const projectRoot = this.#projectRoot();
|
|
28806
|
-
const directory =
|
|
29190
|
+
const directory = join40(projectRoot, value.name);
|
|
28807
29191
|
await mkdir26(projectRoot, { recursive: true, mode: 448 });
|
|
28808
29192
|
try {
|
|
28809
29193
|
await mkdir26(directory, { mode: 448 });
|
|
@@ -28812,7 +29196,7 @@ var SkillManager = class {
|
|
|
28812
29196
|
throw error;
|
|
28813
29197
|
}
|
|
28814
29198
|
try {
|
|
28815
|
-
await writeFile16(
|
|
29199
|
+
await writeFile16(join40(directory, "SKILL.md"), renderSkill(value), { encoding: "utf8", flag: "wx", mode: 384 });
|
|
28816
29200
|
await this.#persistEnabled(value.name, true);
|
|
28817
29201
|
return this.get(value.name);
|
|
28818
29202
|
} catch (error) {
|
|
@@ -28830,7 +29214,7 @@ var SkillManager = class {
|
|
|
28830
29214
|
throw error;
|
|
28831
29215
|
});
|
|
28832
29216
|
if (!info.isDirectory() || info.isSymbolicLink()) throw new Error(`Project skill path is not a real directory: ${validName}`);
|
|
28833
|
-
const skillFile =
|
|
29217
|
+
const skillFile = join40(directory, "SKILL.md");
|
|
28834
29218
|
const fileInfo = await lstat13(skillFile);
|
|
28835
29219
|
if (!fileInfo.isFile() || fileInfo.isSymbolicLink()) throw new Error(`Project skill file is not a real file: ${validName}`);
|
|
28836
29220
|
await writeFile16(skillFile, renderSkill(value), { encoding: "utf8", mode: 384 });
|
|
@@ -28862,12 +29246,12 @@ var SkillManager = class {
|
|
|
28862
29246
|
async #registry() {
|
|
28863
29247
|
const loaded = await loadConfig({ cwd: this.#workspace, home: this.#home });
|
|
28864
29248
|
const [globalPluginRoots, projectPluginRoots] = await Promise.all([
|
|
28865
|
-
discoverPluginSkillRoots(
|
|
28866
|
-
discoverPluginSkillRoots(
|
|
29249
|
+
discoverPluginSkillRoots(join40(this.#home, ".flavor-code", "plugins")),
|
|
29250
|
+
discoverPluginSkillRoots(join40(this.#workspace, ".flavor", "plugins"))
|
|
28867
29251
|
]);
|
|
28868
29252
|
return {
|
|
28869
29253
|
registry: new SkillRegistry({
|
|
28870
|
-
globalRoots: [
|
|
29254
|
+
globalRoots: [join40(this.#home, ".flavor-code", "skills"), ...globalPluginRoots],
|
|
28871
29255
|
projectRoots: [this.#projectRoot(), ...projectPluginRoots]
|
|
28872
29256
|
}),
|
|
28873
29257
|
disabled: new Set(loaded.config.skills.disabled)
|
|
@@ -28883,10 +29267,10 @@ var SkillManager = class {
|
|
|
28883
29267
|
};
|
|
28884
29268
|
}
|
|
28885
29269
|
#projectRoot() {
|
|
28886
|
-
return
|
|
29270
|
+
return join40(this.#workspace, ".flavor", "skills");
|
|
28887
29271
|
}
|
|
28888
29272
|
#projectSkillDirectory(name) {
|
|
28889
|
-
return
|
|
29273
|
+
return join40(this.#projectRoot(), name);
|
|
28890
29274
|
}
|
|
28891
29275
|
};
|
|
28892
29276
|
function parseName(value) {
|
|
@@ -28927,11 +29311,11 @@ async function discoverPluginSkillRoots(directory) {
|
|
|
28927
29311
|
const roots = [];
|
|
28928
29312
|
for (const entry2 of entries) {
|
|
28929
29313
|
if (!entry2.isDirectory() || entry2.isSymbolicLink()) continue;
|
|
28930
|
-
const pluginRoot = await realpath13(
|
|
29314
|
+
const pluginRoot = await realpath13(join40(directory, entry2.name));
|
|
28931
29315
|
try {
|
|
28932
|
-
const manifest = PluginManifestSchema.parse(JSON.parse(await readFile34(
|
|
29316
|
+
const manifest = PluginManifestSchema.parse(JSON.parse(await readFile34(join40(pluginRoot, "flavor-plugin.json"), "utf8")));
|
|
28933
29317
|
for (const contribution of manifest.contributes.skillRoots) {
|
|
28934
|
-
const candidate =
|
|
29318
|
+
const candidate = resolve34(pluginRoot, contribution.path);
|
|
28935
29319
|
const info = await lstat13(candidate);
|
|
28936
29320
|
if (!info.isDirectory() || info.isSymbolicLink()) continue;
|
|
28937
29321
|
const physical = await realpath13(candidate);
|
|
@@ -29004,11 +29388,11 @@ async function createProjectMemoryManager(options) {
|
|
|
29004
29388
|
}
|
|
29005
29389
|
|
|
29006
29390
|
// src/mcp/config-manager.ts
|
|
29007
|
-
import { join as
|
|
29391
|
+
import { join as join41 } from "path";
|
|
29008
29392
|
var ProjectMcpConfigManager = class {
|
|
29009
29393
|
path;
|
|
29010
29394
|
constructor(workspace) {
|
|
29011
|
-
this.path =
|
|
29395
|
+
this.path = join41(workspace, ".flavor", "flavor.json");
|
|
29012
29396
|
}
|
|
29013
29397
|
async list() {
|
|
29014
29398
|
const document = await readRecoverableFile(this.path, (raw) => parseDocument2(this.path, raw));
|
|
@@ -29105,7 +29489,7 @@ function isRecord7(value) {
|
|
|
29105
29489
|
}
|
|
29106
29490
|
|
|
29107
29491
|
// src/desktop/model-config.ts
|
|
29108
|
-
import { join as
|
|
29492
|
+
import { join as join42 } from "path";
|
|
29109
29493
|
var DEFAULT_DESKTOP_MODELS = [
|
|
29110
29494
|
{
|
|
29111
29495
|
id: "anthropic:deepseek-v4-pro",
|
|
@@ -29126,7 +29510,7 @@ var DEFAULT_DESKTOP_MODELS = [
|
|
|
29126
29510
|
];
|
|
29127
29511
|
async function loadDesktopModels(workspace, home) {
|
|
29128
29512
|
try {
|
|
29129
|
-
const tokens2 = await createFileTokenStore(
|
|
29513
|
+
const tokens2 = await createFileTokenStore(join42(home, ".flavor-code", "auth.json")).load();
|
|
29130
29514
|
const pkceModels = [];
|
|
29131
29515
|
const knownPkce = /* @__PURE__ */ new Set();
|
|
29132
29516
|
for (const token of Object.values(tokens2)) {
|
|
@@ -29194,9 +29578,9 @@ function protocolLabel(type) {
|
|
|
29194
29578
|
|
|
29195
29579
|
// src/desktop/d2c-product-preview.ts
|
|
29196
29580
|
import { createReadStream as createReadStream2 } from "fs";
|
|
29197
|
-
import { realpath as realpath14, stat as
|
|
29581
|
+
import { realpath as realpath14, stat as stat14 } from "fs/promises";
|
|
29198
29582
|
import { createServer as createServer6 } from "http";
|
|
29199
|
-
import { extname as extname3, isAbsolute as isAbsolute17, relative as relative25, resolve as
|
|
29583
|
+
import { extname as extname3, isAbsolute as isAbsolute17, relative as relative25, resolve as resolve35, sep as sep26 } from "path";
|
|
29200
29584
|
var MIME_TYPES = {
|
|
29201
29585
|
".css": "text/css; charset=utf-8",
|
|
29202
29586
|
".gif": "image/gif",
|
|
@@ -29218,8 +29602,8 @@ function isInside(root, candidate) {
|
|
|
29218
29602
|
return delta === "" || !delta.startsWith(`..${sep26}`) && delta !== ".." && !isAbsolute17(delta);
|
|
29219
29603
|
}
|
|
29220
29604
|
async function startD2cProductPreview(rootDirectory) {
|
|
29221
|
-
const root = await realpath14(
|
|
29222
|
-
const rootInfo = await
|
|
29605
|
+
const root = await realpath14(resolve35(rootDirectory));
|
|
29606
|
+
const rootInfo = await stat14(root);
|
|
29223
29607
|
if (!rootInfo.isDirectory()) throw new Error("D2C product prototype directory is unavailable");
|
|
29224
29608
|
const server = createServer6(async (request, response2) => {
|
|
29225
29609
|
response2.setHeader("cache-control", "no-store");
|
|
@@ -29230,11 +29614,11 @@ async function startD2cProductPreview(rootDirectory) {
|
|
|
29230
29614
|
let pathname = decodeURIComponent(url.pathname);
|
|
29231
29615
|
if (pathname === "/") pathname = "/index.html";
|
|
29232
29616
|
if (pathname.includes("\0")) return sendNotFound(response2);
|
|
29233
|
-
const unresolved =
|
|
29617
|
+
const unresolved = resolve35(root, `.${pathname}`);
|
|
29234
29618
|
if (!isInside(root, unresolved)) return sendNotFound(response2);
|
|
29235
29619
|
const target = await realpath14(unresolved).catch(() => void 0);
|
|
29236
29620
|
if (target === void 0 || !isInside(root, target)) return sendNotFound(response2);
|
|
29237
|
-
const info = await
|
|
29621
|
+
const info = await stat14(target);
|
|
29238
29622
|
if (!info.isFile()) return sendNotFound(response2);
|
|
29239
29623
|
response2.writeHead(200, {
|
|
29240
29624
|
"content-length": info.size,
|
|
@@ -29271,33 +29655,33 @@ function pngDataUrl(png) {
|
|
|
29271
29655
|
}
|
|
29272
29656
|
async function detectD2cFramework(workspace, task) {
|
|
29273
29657
|
try {
|
|
29274
|
-
const pkg = JSON.parse(await readFile35(
|
|
29658
|
+
const pkg = JSON.parse(await readFile35(join43(d2cOutputDirectory(workspace, task), "package.json"), "utf8"));
|
|
29275
29659
|
return pkg.dependencies?.react === void 0 ? "vue" : "react";
|
|
29276
29660
|
} catch {
|
|
29277
29661
|
return "vue";
|
|
29278
29662
|
}
|
|
29279
29663
|
}
|
|
29280
29664
|
function integrationDirectory(workspace, task) {
|
|
29281
|
-
return
|
|
29665
|
+
return join43(workspace, ".flavor", "d2c", task, "integration");
|
|
29282
29666
|
}
|
|
29283
29667
|
var D2C_BACKEND_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".cfg", ".env", ".ini", ".js", ".json", ".lock", ".mjs", ".py", ".toml", ".txt", ".yaml", ".yml"]);
|
|
29284
29668
|
var D2C_BACKEND_IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([".git", ".venv", "__pycache__", "data", "node_modules", "venv"]);
|
|
29285
29669
|
async function d2cBackendSourceFingerprint(project) {
|
|
29286
|
-
const runtimeManifest =
|
|
29287
|
-
const realBackend = (await
|
|
29288
|
-
const root = realBackend ?
|
|
29289
|
-
if ((await
|
|
29670
|
+
const runtimeManifest = join43(project, "server", "flavor-runtime.json");
|
|
29671
|
+
const realBackend = (await stat15(runtimeManifest).catch(() => void 0))?.isFile() === true;
|
|
29672
|
+
const root = realBackend ? join43(project, "server") : join43(project, "mock");
|
|
29673
|
+
if ((await stat15(root).catch(() => void 0))?.isDirectory() !== true) return void 0;
|
|
29290
29674
|
const files = [];
|
|
29291
29675
|
const visit = async (directory) => {
|
|
29292
29676
|
const entries = await readdir12(directory, { withFileTypes: true });
|
|
29293
29677
|
for (const entry2 of entries) {
|
|
29294
29678
|
if (entry2.isDirectory()) {
|
|
29295
|
-
if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(
|
|
29679
|
+
if (!D2C_BACKEND_IGNORED_DIRECTORIES.has(entry2.name)) await visit(join43(directory, entry2.name));
|
|
29296
29680
|
continue;
|
|
29297
29681
|
}
|
|
29298
29682
|
if (!entry2.isFile()) continue;
|
|
29299
29683
|
const extension2 = entry2.name.includes(".") ? `.${entry2.name.split(".").pop().toLowerCase()}` : "";
|
|
29300
|
-
if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(
|
|
29684
|
+
if (D2C_BACKEND_SOURCE_EXTENSIONS.has(extension2)) files.push(join43(directory, entry2.name));
|
|
29301
29685
|
}
|
|
29302
29686
|
};
|
|
29303
29687
|
await visit(root);
|
|
@@ -29349,7 +29733,7 @@ var DesktopRuntimeController = class {
|
|
|
29349
29733
|
/** Backend source digest observed when the managed process was started. */
|
|
29350
29734
|
#d2cMockSourceFingerprints = /* @__PURE__ */ new Map();
|
|
29351
29735
|
constructor(options) {
|
|
29352
|
-
this.#home =
|
|
29736
|
+
this.#home = resolve36(options.home ?? homedir4());
|
|
29353
29737
|
this.#createRuntime = options.createRuntime ?? (async (runtimeOptions) => createProductionRuntime(runtimeOptions));
|
|
29354
29738
|
this.#createTerminalService = options.createTerminalService ?? ((workspace) => new TerminalService(workspace));
|
|
29355
29739
|
this.#listSessions = options.listSessions ?? (async (workspace) => {
|
|
@@ -29424,7 +29808,7 @@ var DesktopRuntimeController = class {
|
|
|
29424
29808
|
};
|
|
29425
29809
|
}
|
|
29426
29810
|
async openWorkspace(path) {
|
|
29427
|
-
const workspace =
|
|
29811
|
+
const workspace = resolve36(path);
|
|
29428
29812
|
if (workspace !== this.#workspace) {
|
|
29429
29813
|
await this.#disposeRuntime();
|
|
29430
29814
|
await this.#stopAllD2cPreviews();
|
|
@@ -29851,8 +30235,8 @@ var DesktopRuntimeController = class {
|
|
|
29851
30235
|
if (workflow === void 0 || !reviewProgress(workflow).complete || workflow.stage === "visual-review") {
|
|
29852
30236
|
throw new Error("Complete visual review before importing OpenAPI");
|
|
29853
30237
|
}
|
|
29854
|
-
const moduleManifest =
|
|
29855
|
-
const moduleManifestInfo = await
|
|
30238
|
+
const moduleManifest = join43(d2cOutputDirectory(workspace, task), "d2c.modules.json");
|
|
30239
|
+
const moduleManifestInfo = await stat15(moduleManifest).catch(() => void 0);
|
|
29856
30240
|
if (moduleManifestInfo === void 0 || !moduleManifestInfo.isFile()) {
|
|
29857
30241
|
throw new Error("D2C implementation must write d2c.modules.json before API mapping; ask the agent to split the project into modules first");
|
|
29858
30242
|
}
|
|
@@ -29864,8 +30248,8 @@ var DesktopRuntimeController = class {
|
|
|
29864
30248
|
await mkdir27(dir, { recursive: true });
|
|
29865
30249
|
const hash = createHash27("sha256").update(raw).digest("hex");
|
|
29866
30250
|
await Promise.all([
|
|
29867
|
-
writeFile17(
|
|
29868
|
-
writeFile17(
|
|
30251
|
+
writeFile17(join43(dir, "swagger.json"), raw),
|
|
30252
|
+
writeFile17(join43(dir, "openapi.normalized.json"), `${JSON.stringify(document, null, 2)}
|
|
29869
30253
|
`)
|
|
29870
30254
|
]);
|
|
29871
30255
|
const next = {
|
|
@@ -29889,7 +30273,7 @@ var DesktopRuntimeController = class {
|
|
|
29889
30273
|
const workspace = this.#workspace;
|
|
29890
30274
|
if (workspace === void 0) throw new Error("Open a project before importing OpenAPI");
|
|
29891
30275
|
await this.#assertProductPrdLocked(workspace, task);
|
|
29892
|
-
const info = await
|
|
30276
|
+
const info = await stat15(sourcePath);
|
|
29893
30277
|
if (!info.isFile() || info.size > 8 * 1024 * 1024) throw new Error("OpenAPI JSON must be a file no larger than 8 MiB");
|
|
29894
30278
|
return this.#storeD2cOpenApi(workspace, task, basename9(sourcePath), await readFile35(sourcePath, "utf8"), false);
|
|
29895
30279
|
}
|
|
@@ -29900,7 +30284,7 @@ var DesktopRuntimeController = class {
|
|
|
29900
30284
|
let workflow = await readWorkflow(workspace, task);
|
|
29901
30285
|
if (workflow?.openapi === void 0 && workflow !== void 0 && reviewProgress(workflow).complete && await readD2cProductPlanView(workspace, task) !== void 0) {
|
|
29902
30286
|
const modules = await readD2cModules(workspace, task);
|
|
29903
|
-
const productOpenApiPath =
|
|
30287
|
+
const productOpenApiPath = join43(taskDir(workspace, task), "product", "openapi.json");
|
|
29904
30288
|
const authored = await readFile35(productOpenApiPath, "utf8").catch(() => void 0);
|
|
29905
30289
|
const generated = authored === void 0;
|
|
29906
30290
|
const prepared = await this.#storeD2cOpenApi(
|
|
@@ -29913,7 +30297,7 @@ var DesktopRuntimeController = class {
|
|
|
29913
30297
|
return prepared;
|
|
29914
30298
|
}
|
|
29915
30299
|
if (workflow?.openapi === void 0 || workflow.mappings === void 0) return void 0;
|
|
29916
|
-
const document = JSON.parse(await readFile35(
|
|
30300
|
+
const document = JSON.parse(await readFile35(join43(integrationDirectory(workspace, task), "openapi.normalized.json"), "utf8"));
|
|
29917
30301
|
return { workflow, document, mappings: workflow.mappings };
|
|
29918
30302
|
}
|
|
29919
30303
|
async confirmD2cMapping(task, moduleId, operationKey) {
|
|
@@ -29980,7 +30364,7 @@ var DesktopRuntimeController = class {
|
|
|
29980
30364
|
].join("\n");
|
|
29981
30365
|
const delivery = await readDeliveryRun(workspace, task);
|
|
29982
30366
|
if (delivery?.nodes.api.status === "running") {
|
|
29983
|
-
const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile35(
|
|
30367
|
+
const apiArtifacts = await Promise.all(generated.files.map(async (file) => artifactRef(file, await readFile35(join43(project, file)))));
|
|
29984
30368
|
const completed = await updateDeliveryRun(
|
|
29985
30369
|
workspace,
|
|
29986
30370
|
task,
|
|
@@ -30007,7 +30391,7 @@ var DesktopRuntimeController = class {
|
|
|
30007
30391
|
try {
|
|
30008
30392
|
const fingerprint2 = await d2cBackendSourceFingerprint(project);
|
|
30009
30393
|
if (fingerprint2 !== void 0) this.#d2cMockSourceFingerprints.set(task, fingerprint2);
|
|
30010
|
-
const environmentPath =
|
|
30394
|
+
const environmentPath = join43(project, ".env.local");
|
|
30011
30395
|
const existingEnvironment = await readFile35(environmentPath, "utf8").catch(() => "");
|
|
30012
30396
|
const environment = `${existingEnvironment.split(/\r?\n/).filter((line) => !/^\s*VITE_API_BASE_URL\s*=/.test(line)).join("\n").trim()}
|
|
30013
30397
|
VITE_API_BASE_URL=${running.url}
|
|
@@ -30088,7 +30472,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30088
30472
|
const readyPreview = this.#d2cPreviews.get(task);
|
|
30089
30473
|
const readyMock = this.#d2cMocks.get(task);
|
|
30090
30474
|
if (readyPreview === void 0 || readyMock === void 0) throw new Error("D2C preview or backend service is unavailable after synchronization");
|
|
30091
|
-
const manifestPath =
|
|
30475
|
+
const manifestPath = join43(taskDir(workspace, task), "design", "interaction-manifest.json");
|
|
30092
30476
|
const manifest = parseInteractionManifest(await readFile35(manifestPath, "utf8"));
|
|
30093
30477
|
const baseline = await this.#verifyAcceptanceBaseline(workspace, task);
|
|
30094
30478
|
const coverage = baseline === void 0 ? void 0 : verifyRequirementCoverage(baseline.approvedPrd.criteria, manifest);
|
|
@@ -30100,7 +30484,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30100
30484
|
await mkdir27(planDir, { recursive: true });
|
|
30101
30485
|
try {
|
|
30102
30486
|
const observations = await this.#observeD2cPages(manifest, readyPreview.url);
|
|
30103
|
-
const prd = await readFile35(
|
|
30487
|
+
const prd = await readFile35(join43(taskDir(workspace, task), "product", "prd.md"), "utf8").catch(() => void 0);
|
|
30104
30488
|
const apiContext = JSON.stringify({ openapi: workflow.openapi, mappings: workflow.mappings }, null, 2);
|
|
30105
30489
|
const plan = await this.#d2cJudge.planInteractions({ task, seed: manifest, observations, ...prd === void 0 ? {} : { prd }, apiContext });
|
|
30106
30490
|
executableManifest = mergeInteractionManifests(manifest, plan.manifest);
|
|
@@ -30111,9 +30495,9 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30111
30495
|
plannedScenarios: executableManifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
|
|
30112
30496
|
pageAnalyses: plan.pageAnalyses
|
|
30113
30497
|
};
|
|
30114
|
-
await writeFile17(
|
|
30498
|
+
await writeFile17(join43(planDir, "autonomous-interaction-plan.json"), `${JSON.stringify({ ...plan, manifest: executableManifest }, null, 2)}
|
|
30115
30499
|
`, { encoding: "utf8", mode: 384 });
|
|
30116
|
-
await rm15(
|
|
30500
|
+
await rm15(join43(planDir, "autonomous-interaction-diagnostic.json"), { force: true });
|
|
30117
30501
|
} catch (cause) {
|
|
30118
30502
|
const detail = message(cause);
|
|
30119
30503
|
review = {
|
|
@@ -30121,8 +30505,8 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30121
30505
|
plannedScenarios: manifest.pages.reduce((total, page) => total + page.scenarios.length, 0),
|
|
30122
30506
|
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}`
|
|
30123
30507
|
};
|
|
30124
|
-
await rm15(
|
|
30125
|
-
await writeFile17(
|
|
30508
|
+
await rm15(join43(planDir, "autonomous-interaction-plan.json"), { force: true });
|
|
30509
|
+
await writeFile17(join43(planDir, "autonomous-interaction-diagnostic.json"), `${JSON.stringify({
|
|
30126
30510
|
schema: 1,
|
|
30127
30511
|
stage: "planning",
|
|
30128
30512
|
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -30136,13 +30520,13 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30136
30520
|
let result = { ...rawResult, evidenceMode: review.mode };
|
|
30137
30521
|
if (coverage !== void 0) {
|
|
30138
30522
|
const evidence = createAcceptanceEvidence(coverage, result);
|
|
30139
|
-
await writeFile17(
|
|
30523
|
+
await writeFile17(join43(taskDir(workspace, task), "acceptance-evidence.json"), `${JSON.stringify({ ...evidence, baseline }, null, 2)}
|
|
30140
30524
|
`, { encoding: "utf8", mode: 384 });
|
|
30141
30525
|
if (!evidence.passed) result = { ...result, passed: false, failures: Math.max(1, result.failures) };
|
|
30142
30526
|
}
|
|
30143
30527
|
const dir = integrationDirectory(workspace, task);
|
|
30144
30528
|
await mkdir27(dir, { recursive: true });
|
|
30145
|
-
await writeFile17(
|
|
30529
|
+
await writeFile17(join43(dir, "interaction-results.json"), `${JSON.stringify(result, null, 2)}
|
|
30146
30530
|
`);
|
|
30147
30531
|
const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyInteractionRun(current, result));
|
|
30148
30532
|
return { workflow: next, result, review };
|
|
@@ -30155,7 +30539,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30155
30539
|
if (mock === void 0 || !mock.exited() || result.failures === 0) return result;
|
|
30156
30540
|
const output = mock.output().trim();
|
|
30157
30541
|
try {
|
|
30158
|
-
const dir =
|
|
30542
|
+
const dir = join43(d2cOutputDirectory(workspace, task), "mock-server.log");
|
|
30159
30543
|
await writeFile17(dir, `${output}
|
|
30160
30544
|
`);
|
|
30161
30545
|
} catch {
|
|
@@ -30250,7 +30634,7 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30250
30634
|
if (current === void 0) throw new Error("D2C product plan is unavailable");
|
|
30251
30635
|
if (current.plan.approvedPrd !== void 0) throw new Error("The approved PRD is immutable and cannot be edited");
|
|
30252
30636
|
if (current.plan.phase !== "prd-review") throw new Error("PRD sections can only be edited during PRD review");
|
|
30253
|
-
const path =
|
|
30637
|
+
const path = join43(taskDir(workspace, task), "product", "prd.md");
|
|
30254
30638
|
const updated = await updatePrdSectionFile(path, { sectionId, body, expectedHash });
|
|
30255
30639
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
30256
30640
|
await writeD2cProductPlan(workspace, {
|
|
@@ -30270,33 +30654,33 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30270
30654
|
async #assertProductPrdLocked(workspace, task) {
|
|
30271
30655
|
const current = await readD2cProductPlanView(workspace, task);
|
|
30272
30656
|
if (current?.plan.approvedPrd === void 0) return;
|
|
30273
|
-
await assertApprovedPrd(
|
|
30657
|
+
await assertApprovedPrd(join43(taskDir(workspace, task), "product", "prd.md"), current.plan.approvedPrd);
|
|
30274
30658
|
}
|
|
30275
30659
|
async #acceptancePaths(workspace, task) {
|
|
30276
|
-
const product =
|
|
30277
|
-
const openapi =
|
|
30660
|
+
const product = join43(taskDir(workspace, task), "product");
|
|
30661
|
+
const openapi = join43(product, "openapi.json");
|
|
30278
30662
|
return {
|
|
30279
|
-
prd:
|
|
30280
|
-
prototype:
|
|
30281
|
-
interaction:
|
|
30282
|
-
...await
|
|
30663
|
+
prd: join43(product, "prd.md"),
|
|
30664
|
+
prototype: join43(product, "prototype", "index.html"),
|
|
30665
|
+
interaction: join43(product, "prototype", "interaction-manifest.json"),
|
|
30666
|
+
...await stat15(openapi).catch(() => void 0) === void 0 ? {} : { openapi }
|
|
30283
30667
|
};
|
|
30284
30668
|
}
|
|
30285
30669
|
async #verifyAcceptanceBaseline(workspace, task) {
|
|
30286
30670
|
const product = await readD2cProductPlanView(workspace, task);
|
|
30287
30671
|
if (product?.plan.approvedPrd === void 0) return void 0;
|
|
30288
30672
|
await this.#assertProductPrdLocked(workspace, task);
|
|
30289
|
-
const baseline = JSON.parse(await readFile35(
|
|
30673
|
+
const baseline = JSON.parse(await readFile35(join43(taskDir(workspace, task), "acceptance-baseline.json"), "utf8"));
|
|
30290
30674
|
await verifyAcceptanceBaseline(await this.#acceptancePaths(workspace, task), baseline);
|
|
30291
|
-
const design =
|
|
30675
|
+
const design = join43(taskDir(workspace, task), "design");
|
|
30292
30676
|
await Promise.all([
|
|
30293
|
-
verifyAcceptanceArtifact(
|
|
30294
|
-
verifyAcceptanceArtifact(
|
|
30677
|
+
verifyAcceptanceArtifact(join43(design, "index.html"), baseline.artifacts.prototype, "runtime prototype"),
|
|
30678
|
+
verifyAcceptanceArtifact(join43(design, "interaction-manifest.json"), baseline.artifacts.interaction, "runtime interaction contract")
|
|
30295
30679
|
]);
|
|
30296
30680
|
const workflow = await readWorkflow(workspace, task);
|
|
30297
30681
|
if (workflow?.openapi !== void 0) {
|
|
30298
30682
|
await verifyAcceptanceArtifact(
|
|
30299
|
-
|
|
30683
|
+
join43(taskDir(workspace, task), "integration", "swagger.json"),
|
|
30300
30684
|
{ hash: workflow.openapi.hash },
|
|
30301
30685
|
"runtime OpenAPI contract"
|
|
30302
30686
|
);
|
|
@@ -30317,14 +30701,14 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30317
30701
|
throw new Error("D2C PRD is empty and cannot be accepted");
|
|
30318
30702
|
}
|
|
30319
30703
|
if (stage === "design" && accepted) {
|
|
30320
|
-
const manifest =
|
|
30704
|
+
const manifest = join43(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json");
|
|
30321
30705
|
const normalizedManifest = parseInteractionManifest(await readFile35(manifest, "utf8"));
|
|
30322
30706
|
if (current.plan.approvedPrd === void 0) throw new Error("Approve the PRD before confirming the design");
|
|
30323
30707
|
verifyRequirementCoverage(current.plan.approvedPrd.criteria, normalizedManifest);
|
|
30324
30708
|
await writeFile17(manifest, `${JSON.stringify(normalizedManifest, null, 2)}
|
|
30325
30709
|
`, { encoding: "utf8", mode: 384 });
|
|
30326
30710
|
const baseline = await captureAcceptanceBaseline(await this.#acceptancePaths(workspace, task), current.plan.approvedPrd);
|
|
30327
|
-
await writeFile17(
|
|
30711
|
+
await writeFile17(join43(taskDir(workspace, task), "acceptance-baseline.json"), `${JSON.stringify(baseline, null, 2)}
|
|
30328
30712
|
`, { encoding: "utf8", mode: 384 });
|
|
30329
30713
|
}
|
|
30330
30714
|
const plan = await writeD2cProductPlan(
|
|
@@ -30358,8 +30742,8 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30358
30742
|
const imported = await this.importD2cDesign(task, d2cProductPrototypeDirectory(workspace, task));
|
|
30359
30743
|
const delivery = await readDeliveryRun(workspace, task);
|
|
30360
30744
|
if (delivery !== void 0) {
|
|
30361
|
-
const prototype = await readFile35(
|
|
30362
|
-
const interaction = await readFile35(
|
|
30745
|
+
const prototype = await readFile35(join43(d2cProductPrototypeDirectory(workspace, task), "index.html"), "utf8");
|
|
30746
|
+
const interaction = await readFile35(join43(d2cProductPrototypeDirectory(workspace, task), "interaction-manifest.json"), "utf8");
|
|
30363
30747
|
const completed = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "design", [
|
|
30364
30748
|
artifactRef("product/prototype/index.html", prototype),
|
|
30365
30749
|
artifactRef("product/prototype/interaction-manifest.json", interaction)
|
|
@@ -30443,10 +30827,10 @@ VITE_API_BASE_URL=${running.url}
|
|
|
30443
30827
|
const next = await updateWorkflow(workspace, task, workflow.revision, (current) => applyQualityJudgment(current, judgment));
|
|
30444
30828
|
const qualityContent = `${JSON.stringify(judgment, null, 2)}
|
|
30445
30829
|
`;
|
|
30446
|
-
await writeFile17(
|
|
30830
|
+
await writeFile17(join43(taskDir(workspace, task), "quality-judge.json"), qualityContent, { encoding: "utf8", mode: 384 });
|
|
30447
30831
|
const delivery = await readDeliveryRun(workspace, task);
|
|
30448
30832
|
if (next.stage === "completed" && delivery?.nodes.acceptance.status === "running") {
|
|
30449
|
-
const interactionContent = await readFile35(
|
|
30833
|
+
const interactionContent = await readFile35(join43(integrationDirectory(workspace, task), "interaction-results.json"), "utf8");
|
|
30450
30834
|
const accepted = await updateDeliveryRun(workspace, task, delivery.revision, (run) => completeDeliveryNode(run, "acceptance", [
|
|
30451
30835
|
artifactRef("integration/interaction-results.json", interactionContent),
|
|
30452
30836
|
artifactRef("quality-judge.json", qualityContent)
|
|
@@ -30628,14 +31012,14 @@ function desktopWindowChrome(platform = process.platform) {
|
|
|
30628
31012
|
|
|
30629
31013
|
// src/utils/crash-guard.ts
|
|
30630
31014
|
import { mkdirSync as mkdirSync3, writeFileSync } from "fs";
|
|
30631
|
-
import { join as
|
|
31015
|
+
import { join as join44 } from "path";
|
|
30632
31016
|
var installed = false;
|
|
30633
31017
|
function installCrashGuard(options = {}) {
|
|
30634
31018
|
if (installed) return;
|
|
30635
31019
|
installed = true;
|
|
30636
31020
|
const crashLogDir = () => {
|
|
30637
31021
|
try {
|
|
30638
|
-
return
|
|
31022
|
+
return join44((options.workspace ?? process.cwd)(), ".flavor");
|
|
30639
31023
|
} catch {
|
|
30640
31024
|
return process.cwd();
|
|
30641
31025
|
}
|
|
@@ -30644,7 +31028,7 @@ function installCrashGuard(options = {}) {
|
|
|
30644
31028
|
try {
|
|
30645
31029
|
const dir = crashLogDir();
|
|
30646
31030
|
mkdirSync3(dir, { recursive: true });
|
|
30647
|
-
const path =
|
|
31031
|
+
const path = join44(dir, `crash-${(/* @__PURE__ */ new Date()).toISOString().replace(/[^0-9]/g, "")}-${process.pid}.log`);
|
|
30648
31032
|
writeFileSync(path, [
|
|
30649
31033
|
`kind: ${kind}`,
|
|
30650
31034
|
`version: ${process.version}`,
|
|
@@ -30689,7 +31073,7 @@ ${reason.stack ?? ""}`;
|
|
|
30689
31073
|
|
|
30690
31074
|
// src/desktop/git-manager.ts
|
|
30691
31075
|
import { readFile as readFile36, rm as rm16 } from "fs/promises";
|
|
30692
|
-
import { isAbsolute as isAbsolute18, relative as relative26, resolve as
|
|
31076
|
+
import { isAbsolute as isAbsolute18, relative as relative26, resolve as resolve37 } from "path";
|
|
30693
31077
|
function failure(action, stderr, stdout) {
|
|
30694
31078
|
return new Error(`${action}\u5931\u8D25\uFF1A${(stderr.trim() || stdout.trim() || "\u672A\u77E5 Git \u9519\u8BEF").split("\n")[0]}`);
|
|
30695
31079
|
}
|
|
@@ -30782,13 +31166,13 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
|
|
|
30782
31166
|
return path.replaceAll("\\", "/");
|
|
30783
31167
|
}));
|
|
30784
31168
|
try {
|
|
30785
|
-
const tree = JSON.parse(await readFile36(
|
|
31169
|
+
const tree = JSON.parse(await readFile36(resolve37(workspace, ".flavor", "session-trees", sessionId, "tree.json"), "utf8"));
|
|
30786
31170
|
const leaf = tree.nodes?.find((node) => node.id === tree.leafId);
|
|
30787
31171
|
const parent = leaf?.parentId == null ? void 0 : tree.nodes?.find((node) => node.id === leaf.parentId);
|
|
30788
31172
|
if (leaf === void 0 || parent === void 0) return desktopGitReviewDiff(workspace, { scope: "last-turn", paths });
|
|
30789
31173
|
const load = async (id) => {
|
|
30790
31174
|
if (!/^checkpoint-[A-Za-z0-9-]+$/.test(id)) throw new Error("Invalid checkpoint id");
|
|
30791
|
-
return JSON.parse(await readFile36(
|
|
31175
|
+
return JSON.parse(await readFile36(resolve37(workspace, ".flavor", "checkpoints", "manifests", `${id}.json`), "utf8"));
|
|
30792
31176
|
};
|
|
30793
31177
|
const [before, after] = await Promise.all([load(parent.checkpointId), load(leaf.checkpointId)]);
|
|
30794
31178
|
const oldFiles = new Map(before.files.map((file) => [file.path, file.digest]));
|
|
@@ -30797,7 +31181,7 @@ async function desktopLastTurnDiff(workspace, sessionId, paths = []) {
|
|
|
30797
31181
|
const object = async (digest) => {
|
|
30798
31182
|
if (digest === void 0) return "";
|
|
30799
31183
|
if (!/^[a-f0-9]{64}$/.test(digest)) throw new Error("Invalid checkpoint object");
|
|
30800
|
-
const value = await readFile36(
|
|
31184
|
+
const value = await readFile36(resolve37(workspace, ".flavor", "checkpoints", "objects", digest));
|
|
30801
31185
|
return value.includes(0) ? "[binary content]" : value.toString("utf8");
|
|
30802
31186
|
};
|
|
30803
31187
|
const chunks = await Promise.all(changed.map(async (path) => wholeFileDiff(path, await object(oldFiles.get(path)), await object(newFiles.get(path)))));
|
|
@@ -30822,8 +31206,8 @@ async function assertListed(workspace, path) {
|
|
|
30822
31206
|
}
|
|
30823
31207
|
function safeWorkspacePath(workspace, path) {
|
|
30824
31208
|
if (isAbsolute18(path)) throw new Error("Git \u6587\u4EF6\u8DEF\u5F84\u5FC5\u987B\u662F\u9879\u76EE\u5185\u7684\u76F8\u5BF9\u8DEF\u5F84");
|
|
30825
|
-
const root =
|
|
30826
|
-
const target =
|
|
31209
|
+
const root = resolve37(workspace);
|
|
31210
|
+
const target = resolve37(root, path);
|
|
30827
31211
|
const child = relative26(root, target);
|
|
30828
31212
|
if (child === "" || child.startsWith("..") || isAbsolute18(child)) throw new Error("\u62D2\u7EDD\u8BBF\u95EE\u9879\u76EE\u4E4B\u5916\u7684\u8DEF\u5F84");
|
|
30829
31213
|
return target;
|
|
@@ -30836,14 +31220,14 @@ function safeGitTarget(value) {
|
|
|
30836
31220
|
|
|
30837
31221
|
// src/desktop/worktree-manager.ts
|
|
30838
31222
|
import { randomUUID as randomUUID28 } from "crypto";
|
|
30839
|
-
import { mkdir as mkdir28, readFile as readFile37, readdir as readdir13, rm as rm17, stat as
|
|
30840
|
-
import { basename as basename10, isAbsolute as isAbsolute19, join as
|
|
31223
|
+
import { mkdir as mkdir28, readFile as readFile37, readdir as readdir13, rm as rm17, stat as stat16, writeFile as writeFile18 } from "fs/promises";
|
|
31224
|
+
import { basename as basename10, isAbsolute as isAbsolute19, join as join45, relative as relative27, resolve as resolve38, sep as sep27 } from "path";
|
|
30841
31225
|
var DesktopWorktreeManager = class {
|
|
30842
31226
|
#repository;
|
|
30843
31227
|
#storage;
|
|
30844
31228
|
constructor(options) {
|
|
30845
|
-
this.#repository =
|
|
30846
|
-
this.#storage =
|
|
31229
|
+
this.#repository = resolve38(options.repository);
|
|
31230
|
+
this.#storage = resolve38(options.storage);
|
|
30847
31231
|
}
|
|
30848
31232
|
async create(label) {
|
|
30849
31233
|
if (!await isGitRepository(this.#repository)) throw new Error("Task worktrees require a Git repository");
|
|
@@ -30883,8 +31267,8 @@ var DesktopWorktreeManager = class {
|
|
|
30883
31267
|
}
|
|
30884
31268
|
const values = await Promise.all(names.filter((name) => name.endsWith(".json")).map(async (name) => {
|
|
30885
31269
|
try {
|
|
30886
|
-
const stored = validateStored(JSON.parse(await readFile37(
|
|
30887
|
-
if ((await
|
|
31270
|
+
const stored = validateStored(JSON.parse(await readFile37(join45(this.#storage, name), "utf8")), this.#repository, this.#storage);
|
|
31271
|
+
if ((await stat16(stored.path).catch(() => void 0))?.isDirectory() !== true) return void 0;
|
|
30888
31272
|
const status = await git2(stored.path, ["status", "--porcelain"]);
|
|
30889
31273
|
const merged = await git2(this.#repository, ["merge-base", "--is-ancestor", stored.branch, "HEAD"]);
|
|
30890
31274
|
const { version: _version, ...item } = stored;
|
|
@@ -30924,7 +31308,7 @@ var DesktopWorktreeManager = class {
|
|
|
30924
31308
|
return this.#safeStoragePath(`${id}.json`);
|
|
30925
31309
|
}
|
|
30926
31310
|
#safeStoragePath(name) {
|
|
30927
|
-
const target =
|
|
31311
|
+
const target = resolve38(this.#storage, name);
|
|
30928
31312
|
const delta = relative27(this.#storage, target);
|
|
30929
31313
|
if (delta === "" || delta === ".." || delta.startsWith(`..${sep27}`) || isAbsolute19(delta)) throw new Error("Worktree path escapes storage");
|
|
30930
31314
|
return target;
|
|
@@ -30933,10 +31317,10 @@ var DesktopWorktreeManager = class {
|
|
|
30933
31317
|
function validateStored(value, repository, storage) {
|
|
30934
31318
|
if (typeof value !== "object" || value === null) throw new Error("Invalid worktree metadata");
|
|
30935
31319
|
const item = value;
|
|
30936
|
-
if (item.version !== 1 || item.mode !== "worktree" || typeof item.id !== "string" || typeof item.path !== "string" || typeof item.branch !== "string" || typeof item.createdAt !== "string" ||
|
|
31320
|
+
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) {
|
|
30937
31321
|
throw new Error("Invalid worktree metadata");
|
|
30938
31322
|
}
|
|
30939
|
-
const path =
|
|
31323
|
+
const path = resolve38(item.path);
|
|
30940
31324
|
const delta = relative27(storage, path);
|
|
30941
31325
|
if (delta === "" || delta === ".." || delta.startsWith(`..${sep27}`) || isAbsolute19(delta) || basename10(path) !== item.id) {
|
|
30942
31326
|
throw new Error("Worktree metadata escapes storage");
|
|
@@ -30945,17 +31329,17 @@ function validateStored(value, repository, storage) {
|
|
|
30945
31329
|
}
|
|
30946
31330
|
|
|
30947
31331
|
// src/desktop/workbench-service.ts
|
|
30948
|
-
import { readFile as readFile38, readdir as readdir14, stat as
|
|
31332
|
+
import { readFile as readFile38, readdir as readdir14, stat as stat17 } from "fs/promises";
|
|
30949
31333
|
import { homedir as homedir5 } from "os";
|
|
30950
|
-
import { join as
|
|
31334
|
+
import { join as join46, resolve as resolve39 } from "path";
|
|
30951
31335
|
var MAX_TEXT = 128e3;
|
|
30952
31336
|
var MAX_ITEMS = 200;
|
|
30953
31337
|
var DesktopWorkbenchService = class {
|
|
30954
31338
|
#workspace;
|
|
30955
31339
|
#home;
|
|
30956
31340
|
constructor(workspace, home = homedir5()) {
|
|
30957
|
-
this.#workspace =
|
|
30958
|
-
this.#home =
|
|
31341
|
+
this.#workspace = resolve39(workspace);
|
|
31342
|
+
this.#home = resolve39(home);
|
|
30959
31343
|
}
|
|
30960
31344
|
async inspect(sessionId) {
|
|
30961
31345
|
const [goals, instructions, permissionFiles, audit, context] = await Promise.all([
|
|
@@ -30980,11 +31364,11 @@ var DesktopWorkbenchService = class {
|
|
|
30980
31364
|
return url.toString().replace(/\/$/, input.trim().endsWith("/") ? "/" : "");
|
|
30981
31365
|
}
|
|
30982
31366
|
async #goals() {
|
|
30983
|
-
const root =
|
|
31367
|
+
const root = join46(this.#workspace, ".flavor", "goals");
|
|
30984
31368
|
const names = await readdir14(root).catch((error) => error.code === "ENOENT" ? [] : Promise.reject(error));
|
|
30985
31369
|
const values = await Promise.all(names.filter((name) => /^goal-[A-Za-z0-9._-]+\.json$/.test(name)).slice(0, MAX_ITEMS).map(async (name) => {
|
|
30986
31370
|
try {
|
|
30987
|
-
return GoalStateSchema.parse(JSON.parse(await boundedRead2(
|
|
31371
|
+
return GoalStateSchema.parse(JSON.parse(await boundedRead2(join46(root, name))));
|
|
30988
31372
|
} catch {
|
|
30989
31373
|
return void 0;
|
|
30990
31374
|
}
|
|
@@ -30994,7 +31378,7 @@ var DesktopWorkbenchService = class {
|
|
|
30994
31378
|
async #instructions() {
|
|
30995
31379
|
const names = ["FLAVOR.md", "AGENTS.md", "CLAUDE.md", "AGENTS.local.md", "CLAUDE.local.md"];
|
|
30996
31380
|
const items = await Promise.all(names.map(async (name) => {
|
|
30997
|
-
const path =
|
|
31381
|
+
const path = join46(this.#workspace, name);
|
|
30998
31382
|
try {
|
|
30999
31383
|
return { name, path, content: redactErrorText(await boundedRead2(path)) };
|
|
31000
31384
|
} catch (error) {
|
|
@@ -31006,10 +31390,10 @@ var DesktopWorkbenchService = class {
|
|
|
31006
31390
|
}
|
|
31007
31391
|
async #permissionFiles() {
|
|
31008
31392
|
const candidates = [
|
|
31009
|
-
...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path:
|
|
31010
|
-
{ tier: "user", path:
|
|
31011
|
-
{ tier: "project", path:
|
|
31012
|
-
{ tier: "local", path:
|
|
31393
|
+
...process.env.FLAVOR_MANAGED_PERMISSIONS ? [{ tier: "managed", path: resolve39(process.env.FLAVOR_MANAGED_PERMISSIONS), content: "" }] : [],
|
|
31394
|
+
{ tier: "user", path: join46(this.#home, ".flavor-code", "permissions.json"), content: "" },
|
|
31395
|
+
{ tier: "project", path: join46(this.#workspace, ".flavor", "permissions.json"), content: "" },
|
|
31396
|
+
{ tier: "local", path: join46(this.#workspace, ".flavor", "permissions.local.json"), content: "" }
|
|
31013
31397
|
];
|
|
31014
31398
|
const values = await Promise.all(candidates.map(async (item) => {
|
|
31015
31399
|
try {
|
|
@@ -31024,7 +31408,7 @@ var DesktopWorkbenchService = class {
|
|
|
31024
31408
|
async #audit() {
|
|
31025
31409
|
let raw;
|
|
31026
31410
|
try {
|
|
31027
|
-
raw = await boundedRead2(
|
|
31411
|
+
raw = await boundedRead2(join46(this.#workspace, ".flavor", "audit.jsonl"));
|
|
31028
31412
|
} catch (error) {
|
|
31029
31413
|
if (error.code === "ENOENT") return [];
|
|
31030
31414
|
throw error;
|
|
@@ -31039,7 +31423,7 @@ var DesktopWorkbenchService = class {
|
|
|
31039
31423
|
}).reverse();
|
|
31040
31424
|
}
|
|
31041
31425
|
async #context(sessionId) {
|
|
31042
|
-
const usage = await readJsonLines2(
|
|
31426
|
+
const usage = await readJsonLines2(join46(this.#workspace, ".flavor", "usage.jsonl"));
|
|
31043
31427
|
if (sessionId === void 0) return { visibility: [], usage };
|
|
31044
31428
|
try {
|
|
31045
31429
|
const document = await new SessionStore({ workspace: this.#workspace }).load(sessionId);
|
|
@@ -31050,7 +31434,7 @@ var DesktopWorkbenchService = class {
|
|
|
31050
31434
|
}
|
|
31051
31435
|
};
|
|
31052
31436
|
async function boundedRead2(path) {
|
|
31053
|
-
const info = await
|
|
31437
|
+
const info = await stat17(path);
|
|
31054
31438
|
if (!info.isFile()) throw new Error(`Expected a file: ${path}`);
|
|
31055
31439
|
if (info.size > MAX_TEXT) throw new Error(`Workbench file exceeds ${MAX_TEXT} bytes: ${path}`);
|
|
31056
31440
|
return readFile38(path, "utf8");
|
|
@@ -31077,82 +31461,6 @@ function redact2(value, key = "") {
|
|
|
31077
31461
|
return typeof value === "string" && value.length > MAX_TEXT ? `${value.slice(0, MAX_TEXT)}\u2026` : value;
|
|
31078
31462
|
}
|
|
31079
31463
|
|
|
31080
|
-
// src/desktop/astgraph-service.ts
|
|
31081
|
-
import { stat as stat17 } from "fs/promises";
|
|
31082
|
-
import { createRequire as nodeCreateRequire2 } from "module";
|
|
31083
|
-
import { join as join46, resolve as resolve39 } from "path";
|
|
31084
|
-
var FIELDS = "id, kind, name, qualified_name, file_path, language, start_line, end_line, signature";
|
|
31085
|
-
var DesktopAstGraphService = class {
|
|
31086
|
-
#path;
|
|
31087
|
-
constructor(workspace) {
|
|
31088
|
-
this.#path = join46(resolve39(workspace), ".flavor", "astgraph", "index.db");
|
|
31089
|
-
}
|
|
31090
|
-
async status() {
|
|
31091
|
-
if ((await stat17(this.#path).catch(() => void 0))?.isFile() !== true) {
|
|
31092
|
-
return { available: false, path: this.#path, files: 0, nodes: 0, edges: 0 };
|
|
31093
|
-
}
|
|
31094
|
-
return this.#read((db) => {
|
|
31095
|
-
const count = (table) => Number(db.prepare(`SELECT COUNT(*) AS count FROM ${table}`).get().count);
|
|
31096
|
-
const metadata = db.prepare("SELECT value FROM project_metadata WHERE key = 'last_index'").get();
|
|
31097
|
-
let indexedAt;
|
|
31098
|
-
try {
|
|
31099
|
-
const at = Number(JSON.parse(metadata?.value ?? "{}").at);
|
|
31100
|
-
if (Number.isFinite(at)) indexedAt = new Date(at).toISOString();
|
|
31101
|
-
} catch {
|
|
31102
|
-
}
|
|
31103
|
-
return { available: true, path: this.#path, files: count("files"), nodes: count("nodes"), edges: count("edges"), ...indexedAt === void 0 ? {} : { indexedAt } };
|
|
31104
|
-
});
|
|
31105
|
-
}
|
|
31106
|
-
async search(query, limit = 40) {
|
|
31107
|
-
const value = query.trim().slice(0, 200);
|
|
31108
|
-
if (value.length === 0 || !(await this.status()).available) return [];
|
|
31109
|
-
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));
|
|
31110
|
-
}
|
|
31111
|
-
async relations(id, hops = 2) {
|
|
31112
|
-
if (!/^[A-Za-z0-9._:/#@+~-]{1,512}$/.test(id)) throw new Error("Invalid AST node id");
|
|
31113
|
-
if (!(await this.status()).available) return { callers: [], callees: [], impact: [] };
|
|
31114
|
-
return this.#read((db) => {
|
|
31115
|
-
const originRow = db.prepare(`SELECT ${FIELDS} FROM nodes WHERE id = ?`).get(id);
|
|
31116
|
-
const linked = (direction) => {
|
|
31117
|
-
const source = direction === "callers" ? "e.source" : "e.target";
|
|
31118
|
-
const predicate = direction === "callers" ? "e.target = ?" : "e.source = ?";
|
|
31119
|
-
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);
|
|
31120
|
-
};
|
|
31121
|
-
const seen = /* @__PURE__ */ new Map();
|
|
31122
|
-
let frontier = [id];
|
|
31123
|
-
for (let hop = 1; hop <= Math.min(Math.max(hops, 1), 4) && frontier.length > 0 && seen.size < 200; hop += 1) {
|
|
31124
|
-
const next = [];
|
|
31125
|
-
for (const current of frontier) {
|
|
31126
|
-
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);
|
|
31127
|
-
for (const row of rows) if (row.other !== id && !seen.has(row.other)) {
|
|
31128
|
-
seen.set(row.other, hop);
|
|
31129
|
-
next.push(row.other);
|
|
31130
|
-
}
|
|
31131
|
-
}
|
|
31132
|
-
frontier = next;
|
|
31133
|
-
}
|
|
31134
|
-
const impact = [...seen].flatMap(([nodeId, hop]) => {
|
|
31135
|
-
const row = db.prepare(`SELECT ${FIELDS} FROM nodes WHERE id = ?`).get(nodeId);
|
|
31136
|
-
return row === void 0 ? [] : [{ ...mapNode(row), hop }];
|
|
31137
|
-
});
|
|
31138
|
-
return { ...originRow === void 0 ? {} : { origin: mapNode(originRow) }, callers: linked("callers"), callees: linked("callees"), impact };
|
|
31139
|
-
});
|
|
31140
|
-
}
|
|
31141
|
-
#read(run) {
|
|
31142
|
-
const sqlite = nodeCreateRequire2(import.meta.url)("node:sqlite");
|
|
31143
|
-
const db = new sqlite.DatabaseSync(this.#path, { readOnly: true });
|
|
31144
|
-
try {
|
|
31145
|
-
return run(db);
|
|
31146
|
-
} finally {
|
|
31147
|
-
db.close();
|
|
31148
|
-
}
|
|
31149
|
-
}
|
|
31150
|
-
};
|
|
31151
|
-
function mapNode(value) {
|
|
31152
|
-
const row = value;
|
|
31153
|
-
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) } };
|
|
31154
|
-
}
|
|
31155
|
-
|
|
31156
31464
|
// src/desktop/main.ts
|
|
31157
31465
|
installCrashGuard();
|
|
31158
31466
|
var moduleDirectory = dirname24(fileURLToPath4(import.meta.url));
|