@vibeflow-tools/cli 0.5.0 → 0.6.0
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/CHANGELOG.md +53 -0
- package/dist/chunk-GEQ64RVF.js +7 -0
- package/dist/chunk-IUZWZWMW.js +7 -0
- package/dist/chunk-NRMCK2HM.js +7 -0
- package/dist/chunk-PCPVP4ZD.js +7 -0
- package/dist/chunk-XO547RIH.js +7 -0
- package/dist/cli/chunk-GEQ64RVF.js +7 -0
- package/dist/cli/chunk-IUZWZWMW.js +7 -0
- package/dist/cli/chunk-NRMCK2HM.js +7 -0
- package/dist/cli/chunk-PCPVP4ZD.js +7 -0
- package/dist/cli/chunk-XO547RIH.js +7 -0
- package/dist/cli/files-GOVXPMB5.js +2 -0
- package/dist/cli/files-LTK2TDC2.js +2 -0
- package/dist/cli/files-MEEV7E6V.js +2 -0
- package/dist/cli/files-R6YCYMTI.js +2 -0
- package/dist/cli/files-SUCOSQAO.js +2 -0
- package/dist/cli/index.js +384 -197
- package/dist/client/kanban-browser.js +23 -12
- package/dist/client/kanban.css +1 -1
- package/dist/client/overlay-browser.js +5 -4
- package/dist/files-GOVXPMB5.js +2 -0
- package/dist/files-LTK2TDC2.js +2 -0
- package/dist/files-MEEV7E6V.js +2 -0
- package/dist/files-R6YCYMTI.js +2 -0
- package/dist/files-SUCOSQAO.js +2 -0
- package/dist/index.js +384 -197
- package/package.json +7 -2
- package/dist/chunk-252RNKHM.js +0 -2
- package/dist/chunk-43MUGU6Z.js +0 -67
- package/dist/chunk-4Y62J4KR.js +0 -438
- package/dist/chunk-6ABTC7IA.js +0 -482
- package/dist/chunk-7K2VHAWF.js +0 -569
- package/dist/chunk-BKTZSHJS.js +0 -2
- package/dist/chunk-BQLZVQ4E.js +0 -2
- package/dist/chunk-F57OXWVU.js +0 -33
- package/dist/chunk-I2OSZY66.js +0 -803
- package/dist/chunk-JKC6MUZ4.js +0 -558
- package/dist/chunk-KKBXVPTJ.js +0 -2
- package/dist/chunk-LK3IGDR6.js +0 -438
- package/dist/chunk-MKOKIWTN.js +0 -569
- package/dist/chunk-MW4OS3IK.js +0 -438
- package/dist/chunk-NRPOXSY6.js +0 -568
- package/dist/chunk-NUM3G22J.js +0 -569
- package/dist/chunk-O564QSMS.js +0 -569
- package/dist/chunk-OAGEPYIT.js +0 -535
- package/dist/chunk-PHBHAIHA.js +0 -33
- package/dist/chunk-PK737AKV.js +0 -560
- package/dist/chunk-TWAAPROG.js +0 -822
- package/dist/chunk-XF2CNPE4.js +0 -803
- package/dist/cli/chunk-252RNKHM.js +0 -2
- package/dist/cli/chunk-43MUGU6Z.js +0 -67
- package/dist/cli/chunk-4Y62J4KR.js +0 -438
- package/dist/cli/chunk-6ABTC7IA.js +0 -482
- package/dist/cli/chunk-7K2VHAWF.js +0 -569
- package/dist/cli/chunk-BKTZSHJS.js +0 -2
- package/dist/cli/chunk-BQLZVQ4E.js +0 -2
- package/dist/cli/chunk-F57OXWVU.js +0 -33
- package/dist/cli/chunk-I2OSZY66.js +0 -803
- package/dist/cli/chunk-JKC6MUZ4.js +0 -558
- package/dist/cli/chunk-KKBXVPTJ.js +0 -2
- package/dist/cli/chunk-LK3IGDR6.js +0 -438
- package/dist/cli/chunk-MKOKIWTN.js +0 -569
- package/dist/cli/chunk-MW4OS3IK.js +0 -438
- package/dist/cli/chunk-NRPOXSY6.js +0 -568
- package/dist/cli/chunk-NUM3G22J.js +0 -569
- package/dist/cli/chunk-O564QSMS.js +0 -569
- package/dist/cli/chunk-OAGEPYIT.js +0 -535
- package/dist/cli/chunk-PHBHAIHA.js +0 -33
- package/dist/cli/chunk-PK737AKV.js +0 -560
- package/dist/cli/chunk-TWAAPROG.js +0 -822
- package/dist/cli/chunk-XF2CNPE4.js +0 -803
- package/dist/cli/files-27OYPA7W.js +0 -20
- package/dist/cli/files-2TK74THO.js +0 -22
- package/dist/cli/files-3RDDXUOS.js +0 -2
- package/dist/cli/files-7275M2PW.js +0 -20
- package/dist/cli/files-D3YPV7QT.js +0 -20
- package/dist/cli/files-FER4UZ4X.js +0 -22
- package/dist/cli/files-H4FRDKJV.js +0 -22
- package/dist/cli/files-IJZVMROA.js +0 -22
- package/dist/cli/files-IX5QZQHC.js +0 -2
- package/dist/cli/files-KH3UEFN7.js +0 -20
- package/dist/cli/files-LTDT5ZFT.js +0 -22
- package/dist/cli/files-O4WJLFMU.js +0 -2
- package/dist/cli/files-OYO6A6MZ.js +0 -22
- package/dist/cli/files-R6QHQBH4.js +0 -22
- package/dist/cli/files-UCALOYWZ.js +0 -22
- package/dist/cli/files-UWZP7P6B.js +0 -2
- package/dist/cli/files-VIDLQM7Y.js +0 -20
- package/dist/cli/files-X2RDLF3W.js +0 -22
- package/dist/cli/files-XVDNOAZB.js +0 -22
- package/dist/cli/workspace-NB6BACZA.js +0 -12
- package/dist/cli/workspace-X2NGGGTQ.js +0 -12
- package/dist/cli/workspace-X4QXECQQ.js +0 -12
- package/dist/files-27OYPA7W.js +0 -20
- package/dist/files-2TK74THO.js +0 -22
- package/dist/files-3RDDXUOS.js +0 -2
- package/dist/files-7275M2PW.js +0 -20
- package/dist/files-D3YPV7QT.js +0 -20
- package/dist/files-FER4UZ4X.js +0 -22
- package/dist/files-H4FRDKJV.js +0 -22
- package/dist/files-IJZVMROA.js +0 -22
- package/dist/files-IX5QZQHC.js +0 -2
- package/dist/files-KH3UEFN7.js +0 -20
- package/dist/files-LTDT5ZFT.js +0 -22
- package/dist/files-O4WJLFMU.js +0 -2
- package/dist/files-OYO6A6MZ.js +0 -22
- package/dist/files-R6QHQBH4.js +0 -22
- package/dist/files-UCALOYWZ.js +0 -22
- package/dist/files-UWZP7P6B.js +0 -2
- package/dist/files-VIDLQM7Y.js +0 -20
- package/dist/files-X2RDLF3W.js +0 -22
- package/dist/files-XVDNOAZB.js +0 -22
- package/dist/workspace-NB6BACZA.js +0 -12
- package/dist/workspace-X2NGGGTQ.js +0 -12
- package/dist/workspace-X4QXECQQ.js +0 -12
|
@@ -1,822 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/core/files.ts
|
|
4
|
-
import {
|
|
5
|
-
readdirSync as readdirSync2,
|
|
6
|
-
writeFileSync as writeFileSync2,
|
|
7
|
-
readFileSync as readFileSync2,
|
|
8
|
-
unlinkSync as unlinkSync2,
|
|
9
|
-
statSync,
|
|
10
|
-
mkdirSync as mkdirSync2,
|
|
11
|
-
existsSync as existsSync3
|
|
12
|
-
} from "fs";
|
|
13
|
-
import { join as join3, basename, isAbsolute } from "path";
|
|
14
|
-
|
|
15
|
-
// src/core/types.ts
|
|
16
|
-
var ANNOTATION_TAGS = [
|
|
17
|
-
"TODO",
|
|
18
|
-
"FEATURE",
|
|
19
|
-
"VARIANT",
|
|
20
|
-
"KEEP",
|
|
21
|
-
"QUESTION",
|
|
22
|
-
"CONTEXT"
|
|
23
|
-
];
|
|
24
|
-
var PROTO_DIR = ".proto";
|
|
25
|
-
var TASKS_DIR = "tasks";
|
|
26
|
-
var COMMENTS_DIR = "comments";
|
|
27
|
-
var FILES_DIR = "tasks/files";
|
|
28
|
-
var AGENTS_DIR = "agents";
|
|
29
|
-
var SCREENSHOTS_DIR = "tasks/screenshots";
|
|
30
|
-
var CONFIG_FILE = "config.json";
|
|
31
|
-
|
|
32
|
-
// src/core/tasks.ts
|
|
33
|
-
import {
|
|
34
|
-
existsSync as existsSync2,
|
|
35
|
-
readFileSync,
|
|
36
|
-
writeFileSync,
|
|
37
|
-
mkdirSync,
|
|
38
|
-
readdirSync,
|
|
39
|
-
unlinkSync,
|
|
40
|
-
renameSync
|
|
41
|
-
} from "fs";
|
|
42
|
-
import { join as join2, extname } from "path";
|
|
43
|
-
import { randomBytes } from "crypto";
|
|
44
|
-
|
|
45
|
-
// src/core/tasks-legacy.ts
|
|
46
|
-
import { existsSync } from "fs";
|
|
47
|
-
import { join } from "path";
|
|
48
|
-
var VALID_STATUSES = ["backlog", "todo", "in-progress", "review", "done"];
|
|
49
|
-
function parseFrontMatter(content) {
|
|
50
|
-
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
51
|
-
if (!match) return { frontMatter: {}, body: content.trim() };
|
|
52
|
-
const yaml = match[1];
|
|
53
|
-
const body = match[2].trim();
|
|
54
|
-
const frontMatter = {};
|
|
55
|
-
for (const line of yaml.split("\n")) {
|
|
56
|
-
const colonIdx = line.indexOf(":");
|
|
57
|
-
if (colonIdx === -1) continue;
|
|
58
|
-
const key = line.slice(0, colonIdx).trim();
|
|
59
|
-
let value = line.slice(colonIdx + 1).trim();
|
|
60
|
-
if (value.startsWith("'") && value.endsWith("'") || value.startsWith('"') && value.endsWith('"')) {
|
|
61
|
-
value = value.slice(1, -1);
|
|
62
|
-
}
|
|
63
|
-
frontMatter[key] = value;
|
|
64
|
-
}
|
|
65
|
-
return { frontMatter, body };
|
|
66
|
-
}
|
|
67
|
-
function parseTask(content) {
|
|
68
|
-
const { frontMatter, body } = parseFrontMatter(content);
|
|
69
|
-
if (!frontMatter.id || !frontMatter.selector) {
|
|
70
|
-
return null;
|
|
71
|
-
}
|
|
72
|
-
const titleMatch = body.match(/^#\s+(.+)/m);
|
|
73
|
-
const title = titleMatch ? titleMatch[1].trim() : "Untitled";
|
|
74
|
-
const description = body.replace(/^#\s+.+\n*/m, "").trim();
|
|
75
|
-
return {
|
|
76
|
-
id: frontMatter.id,
|
|
77
|
-
title,
|
|
78
|
-
description,
|
|
79
|
-
status: VALID_STATUSES.includes(frontMatter.status) ? frontMatter.status : "todo",
|
|
80
|
-
url: frontMatter.url || void 0,
|
|
81
|
-
selector: frontMatter.selector,
|
|
82
|
-
cssSelector: frontMatter.cssSelector || void 0,
|
|
83
|
-
file: frontMatter.file || void 0,
|
|
84
|
-
line: frontMatter.line ? Number(frontMatter.line) : void 0,
|
|
85
|
-
col: frontMatter.col ? Number(frontMatter.col) : void 0,
|
|
86
|
-
component: frontMatter.component || void 0,
|
|
87
|
-
type: frontMatter.type || void 0,
|
|
88
|
-
priority: frontMatter.priority || void 0,
|
|
89
|
-
reportBack: frontMatter.reportBack === "true" ? true : void 0,
|
|
90
|
-
created: frontMatter.created || (/* @__PURE__ */ new Date()).toISOString(),
|
|
91
|
-
updated: frontMatter.updated || void 0,
|
|
92
|
-
comments: [],
|
|
93
|
-
files: []
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// src/core/tasks-embeddings.ts
|
|
98
|
-
var EMBED_MODEL = "nomic-embed-text";
|
|
99
|
-
var DEFAULT_OLLAMA_URL = "http://localhost:11434";
|
|
100
|
-
var MAX_EMBED_CHARS = 500;
|
|
101
|
-
function taskToEmbedText(task) {
|
|
102
|
-
const parts = [task.title];
|
|
103
|
-
if (task.type) parts.push(task.type);
|
|
104
|
-
if (task.description) {
|
|
105
|
-
const cleaned = task.description.replace(/[|✅❌]/g, " ").replace(/\s+/g, " ").trim();
|
|
106
|
-
parts.push(cleaned);
|
|
107
|
-
}
|
|
108
|
-
return parts.join(" \u2014 ").slice(0, MAX_EMBED_CHARS);
|
|
109
|
-
}
|
|
110
|
-
function taskTitleEmbedText(task) {
|
|
111
|
-
return task.title.slice(0, MAX_EMBED_CHARS);
|
|
112
|
-
}
|
|
113
|
-
function taskDescriptionEmbedText(task) {
|
|
114
|
-
const cleaned = (task.description ?? "").replace(/[|✅❌]/g, " ").replace(/\s+/g, " ").trim();
|
|
115
|
-
return cleaned.slice(0, MAX_EMBED_CHARS);
|
|
116
|
-
}
|
|
117
|
-
function cosineSimilarity(a, b) {
|
|
118
|
-
if (!a.length || !b.length || a.length !== b.length) return 0;
|
|
119
|
-
let dot = 0;
|
|
120
|
-
let normA = 0;
|
|
121
|
-
let normB = 0;
|
|
122
|
-
for (let i = 0; i < a.length; i += 1) {
|
|
123
|
-
dot += a[i] * b[i];
|
|
124
|
-
normA += a[i] * a[i];
|
|
125
|
-
normB += b[i] * b[i];
|
|
126
|
-
}
|
|
127
|
-
if (normA === 0 || normB === 0) return 0;
|
|
128
|
-
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
129
|
-
}
|
|
130
|
-
function tokenize(text) {
|
|
131
|
-
return text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter(Boolean);
|
|
132
|
-
}
|
|
133
|
-
function bm25LikeScore(query, documents) {
|
|
134
|
-
const queryTerms = tokenize(query);
|
|
135
|
-
if (queryTerms.length === 0 || documents.length === 0) return documents.map(() => 0);
|
|
136
|
-
const docsTerms = documents.map((d) => tokenize(d));
|
|
137
|
-
const avgDocLen = docsTerms.reduce((acc, t) => acc + t.length, 0) / Math.max(1, docsTerms.length);
|
|
138
|
-
const k1 = 1.2;
|
|
139
|
-
const b = 0.75;
|
|
140
|
-
const df = /* @__PURE__ */ new Map();
|
|
141
|
-
for (const term of queryTerms) {
|
|
142
|
-
let seen = 0;
|
|
143
|
-
for (const doc of docsTerms) {
|
|
144
|
-
if (doc.includes(term)) seen += 1;
|
|
145
|
-
}
|
|
146
|
-
df.set(term, seen);
|
|
147
|
-
}
|
|
148
|
-
return docsTerms.map((doc) => {
|
|
149
|
-
if (doc.length === 0) return 0;
|
|
150
|
-
let score = 0;
|
|
151
|
-
for (const term of queryTerms) {
|
|
152
|
-
const freq = doc.filter((t) => t === term).length;
|
|
153
|
-
if (!freq) continue;
|
|
154
|
-
const docFreq = df.get(term) ?? 0;
|
|
155
|
-
const idf = Math.log(1 + (documents.length - docFreq + 0.5) / (docFreq + 0.5));
|
|
156
|
-
const tf = freq * (k1 + 1) / (freq + k1 * (1 - b + b * (doc.length / Math.max(1, avgDocLen))));
|
|
157
|
-
score += idf * tf;
|
|
158
|
-
}
|
|
159
|
-
return score;
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
function reciprocalRankFusion(ranks, k = 60) {
|
|
163
|
-
const fused = /* @__PURE__ */ new Map();
|
|
164
|
-
for (const rankMap of ranks) {
|
|
165
|
-
for (const [taskId, rank] of rankMap.entries()) {
|
|
166
|
-
const prev = fused.get(taskId) ?? 0;
|
|
167
|
-
fused.set(taskId, prev + 1 / (k + rank));
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return fused;
|
|
171
|
-
}
|
|
172
|
-
function buildRankMap(scores) {
|
|
173
|
-
const sorted = scores.slice().sort((a, b) => b.score - a.score).map((row) => row.taskId);
|
|
174
|
-
const rank = /* @__PURE__ */ new Map();
|
|
175
|
-
sorted.forEach((taskId, idx) => rank.set(taskId, idx + 1));
|
|
176
|
-
return rank;
|
|
177
|
-
}
|
|
178
|
-
function computeMetadataBoost(entry, options = {}) {
|
|
179
|
-
let boost = 1;
|
|
180
|
-
const statusBoost = options.statusBoost?.[entry.status];
|
|
181
|
-
const priorityBoost = entry.priority ? options.priorityBoost?.[entry.priority] : void 0;
|
|
182
|
-
if (statusBoost) boost *= statusBoost;
|
|
183
|
-
if (priorityBoost) boost *= priorityBoost;
|
|
184
|
-
const halfLife = options.recencyHalfLifeDays ?? 45;
|
|
185
|
-
const refDate = entry.updated ?? entry.created;
|
|
186
|
-
const ageDays = Math.max(0, (Date.now() - new Date(refDate).getTime()) / (1e3 * 60 * 60 * 24));
|
|
187
|
-
const recencyBoost = Math.exp(-Math.log(2) * ageDays / Math.max(1, halfLife));
|
|
188
|
-
boost *= 0.7 + 0.3 * recencyBoost;
|
|
189
|
-
if (options.pageScope) {
|
|
190
|
-
boost *= entry.url === options.pageScope ? 1.25 : 0.92;
|
|
191
|
-
}
|
|
192
|
-
return boost;
|
|
193
|
-
}
|
|
194
|
-
function rankHybridResults(args) {
|
|
195
|
-
const { entries, query, queryVectorTitle, queryVectorDescription, queryVectorCombined, options = {} } = args;
|
|
196
|
-
const docs = entries.map((e) => `${e.title} ${e.description}`.trim());
|
|
197
|
-
const lexical = bm25LikeScore(query, docs);
|
|
198
|
-
const denseScores = entries.map((entry) => {
|
|
199
|
-
const title = cosineSimilarity(queryVectorTitle, entry.vectors.title);
|
|
200
|
-
const description = cosineSimilarity(queryVectorDescription, entry.vectors.description);
|
|
201
|
-
const combined = cosineSimilarity(queryVectorCombined, entry.vectors.combined);
|
|
202
|
-
return {
|
|
203
|
-
taskId: entry.taskId,
|
|
204
|
-
score: combined * 0.5 + title * 0.35 + description * 0.15
|
|
205
|
-
};
|
|
206
|
-
});
|
|
207
|
-
const lexicalScores = entries.map((entry, idx) => ({ taskId: entry.taskId, score: lexical[idx] ?? 0 }));
|
|
208
|
-
const fused = reciprocalRankFusion([
|
|
209
|
-
buildRankMap(denseScores),
|
|
210
|
-
buildRankMap(lexicalScores)
|
|
211
|
-
]);
|
|
212
|
-
return entries.map((entry) => {
|
|
213
|
-
const base = fused.get(entry.taskId) ?? 0;
|
|
214
|
-
const boost = computeMetadataBoost(entry, options);
|
|
215
|
-
return {
|
|
216
|
-
taskId: entry.taskId,
|
|
217
|
-
score: base * boost
|
|
218
|
-
};
|
|
219
|
-
}).sort((a, b) => b.score - a.score);
|
|
220
|
-
}
|
|
221
|
-
async function generateEmbedding(text, ollamaUrl = DEFAULT_OLLAMA_URL, model = EMBED_MODEL) {
|
|
222
|
-
let res = await fetch(`${ollamaUrl}/api/embed`, {
|
|
223
|
-
method: "POST",
|
|
224
|
-
headers: { "Content-Type": "application/json" },
|
|
225
|
-
body: JSON.stringify({ model, input: text })
|
|
226
|
-
});
|
|
227
|
-
if (res.status === 404) {
|
|
228
|
-
res = await fetch(`${ollamaUrl}/api/embeddings`, {
|
|
229
|
-
method: "POST",
|
|
230
|
-
headers: { "Content-Type": "application/json" },
|
|
231
|
-
body: JSON.stringify({ model, prompt: text })
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
if (!res.ok) {
|
|
235
|
-
throw new OllamaUnavailableError(`Ollama embed error: ${res.status} ${res.statusText}`);
|
|
236
|
-
}
|
|
237
|
-
const data = await res.json();
|
|
238
|
-
const vec = data.embeddings?.[0] ?? data.embedding;
|
|
239
|
-
if (!vec || !Array.isArray(vec)) throw new Error("No embedding returned by Ollama");
|
|
240
|
-
return vec;
|
|
241
|
-
}
|
|
242
|
-
var OllamaUnavailableError = class extends Error {
|
|
243
|
-
constructor(message) {
|
|
244
|
-
super(message);
|
|
245
|
-
this.name = "OllamaUnavailableError";
|
|
246
|
-
}
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
// src/core/tasks.ts
|
|
250
|
-
function generateTaskId() {
|
|
251
|
-
return randomBytes(15).toString("hex");
|
|
252
|
-
}
|
|
253
|
-
function getTasksDir(projectDir) {
|
|
254
|
-
return join2(projectDir, PROTO_DIR, TASKS_DIR);
|
|
255
|
-
}
|
|
256
|
-
function migrateLegacyTaskAssetFolders(projectDir) {
|
|
257
|
-
const protoRoot = join2(projectDir, PROTO_DIR);
|
|
258
|
-
const legacyFilesDir = join2(protoRoot, "files");
|
|
259
|
-
const nextFilesDir = join2(protoRoot, FILES_DIR);
|
|
260
|
-
const legacyScreenshotsDir = join2(protoRoot, "screenshots");
|
|
261
|
-
const nextScreenshotsDir = join2(protoRoot, SCREENSHOTS_DIR);
|
|
262
|
-
if (existsSync2(legacyFilesDir)) {
|
|
263
|
-
mkdirSync(nextFilesDir, { recursive: true });
|
|
264
|
-
for (const entry of readdirSync(legacyFilesDir)) {
|
|
265
|
-
const from = join2(legacyFilesDir, entry);
|
|
266
|
-
const to = join2(nextFilesDir, entry);
|
|
267
|
-
if (existsSync2(to)) continue;
|
|
268
|
-
try {
|
|
269
|
-
renameSync(from, to);
|
|
270
|
-
} catch {
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
if (existsSync2(legacyScreenshotsDir)) {
|
|
275
|
-
mkdirSync(nextScreenshotsDir, { recursive: true });
|
|
276
|
-
for (const entry of readdirSync(legacyScreenshotsDir)) {
|
|
277
|
-
const from = join2(legacyScreenshotsDir, entry);
|
|
278
|
-
const to = join2(nextScreenshotsDir, entry);
|
|
279
|
-
if (existsSync2(to)) continue;
|
|
280
|
-
try {
|
|
281
|
-
renameSync(from, to);
|
|
282
|
-
} catch {
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
function ensureTaskDirs(projectDir) {
|
|
288
|
-
migrateLegacyTaskAssetFolders(projectDir);
|
|
289
|
-
mkdirSync(getTasksDir(projectDir), { recursive: true });
|
|
290
|
-
mkdirSync(join2(projectDir, PROTO_DIR, SCREENSHOTS_DIR), { recursive: true });
|
|
291
|
-
}
|
|
292
|
-
function getDateSubdir(isoDate) {
|
|
293
|
-
return isoDate.slice(0, 10);
|
|
294
|
-
}
|
|
295
|
-
function getTaskFilePath(projectDir, taskId, created) {
|
|
296
|
-
const tasksDir = getTasksDir(projectDir);
|
|
297
|
-
if (created) {
|
|
298
|
-
return join2(tasksDir, getDateSubdir(created), `${taskId}.json`);
|
|
299
|
-
}
|
|
300
|
-
return join2(tasksDir, `${taskId}.json`);
|
|
301
|
-
}
|
|
302
|
-
function findTaskFilePath(projectDir, taskId) {
|
|
303
|
-
const tasksDir = getTasksDir(projectDir);
|
|
304
|
-
if (!existsSync2(tasksDir)) return null;
|
|
305
|
-
for (const entry of readdirSync(tasksDir, { withFileTypes: true })) {
|
|
306
|
-
if (entry.isDirectory()) {
|
|
307
|
-
const candidate = join2(tasksDir, entry.name, `${taskId}.json`);
|
|
308
|
-
if (existsSync2(candidate)) return candidate;
|
|
309
|
-
} else if (entry.name === `${taskId}.json`) {
|
|
310
|
-
return join2(tasksDir, entry.name);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
return null;
|
|
314
|
-
}
|
|
315
|
-
function normalizeTask(raw) {
|
|
316
|
-
const normalizedType = (() => {
|
|
317
|
-
if (typeof raw.type !== "string") return void 0;
|
|
318
|
-
const t = raw.type.trim();
|
|
319
|
-
if (!t || t === "[object Object]") return void 0;
|
|
320
|
-
return t;
|
|
321
|
-
})();
|
|
322
|
-
return {
|
|
323
|
-
id: String(raw.id ?? ""),
|
|
324
|
-
title: String(raw.title ?? "Untitled"),
|
|
325
|
-
description: String(raw.description ?? ""),
|
|
326
|
-
status: VALID_STATUSES.includes(raw.status) ? raw.status : "todo",
|
|
327
|
-
url: raw.url ? String(raw.url) : void 0,
|
|
328
|
-
selector: (() => {
|
|
329
|
-
const sel = String(raw.selector ?? "/");
|
|
330
|
-
if (raw.file && !raw.cssSelector && sel.startsWith(String(raw.file))) {
|
|
331
|
-
return raw.url ? String(raw.url) : "/";
|
|
332
|
-
}
|
|
333
|
-
return sel;
|
|
334
|
-
})(),
|
|
335
|
-
cssSelector: raw.cssSelector && String(raw.cssSelector) !== String(raw.selector ?? "/") ? String(raw.cssSelector) : void 0,
|
|
336
|
-
file: raw.file ? String(raw.file) : void 0,
|
|
337
|
-
line: raw.line != null ? Number(raw.line) : void 0,
|
|
338
|
-
col: raw.col != null ? Number(raw.col) : void 0,
|
|
339
|
-
component: raw.component ? String(raw.component) : void 0,
|
|
340
|
-
type: normalizedType,
|
|
341
|
-
priority: raw.priority ? String(raw.priority) : void 0,
|
|
342
|
-
...raw.reportBack === true && { reportBack: true },
|
|
343
|
-
agent: raw.agent ? String(raw.agent) : void 0,
|
|
344
|
-
model: raw.model ? String(raw.model) : void 0,
|
|
345
|
-
author: raw.author ? String(raw.author) : void 0,
|
|
346
|
-
commit: raw.commit ? String(raw.commit) : void 0,
|
|
347
|
-
commits: Array.isArray(raw.commits) ? raw.commits.map((c) => ({
|
|
348
|
-
sha: String(c.sha ?? ""),
|
|
349
|
-
message: String(c.message ?? ""),
|
|
350
|
-
timestamp: String(c.timestamp ?? (/* @__PURE__ */ new Date()).toISOString())
|
|
351
|
-
})) : void 0,
|
|
352
|
-
created: String(raw.created ?? (/* @__PURE__ */ new Date()).toISOString()),
|
|
353
|
-
updated: raw.updated ? String(raw.updated) : void 0,
|
|
354
|
-
comments: Array.isArray(raw.comments) ? raw.comments.map((c) => ({
|
|
355
|
-
...c,
|
|
356
|
-
// Normalize legacy 'content' field → 'text' (some older agent comments used 'content')
|
|
357
|
-
text: c.text ?? c.content ?? ""
|
|
358
|
-
})) : [],
|
|
359
|
-
files: Array.isArray(raw.files) ? raw.files.map((f) => {
|
|
360
|
-
if (typeof f === "string") {
|
|
361
|
-
return { name: f, addedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
362
|
-
}
|
|
363
|
-
return {
|
|
364
|
-
name: String(f.name ?? ""),
|
|
365
|
-
addedAt: String(f.addedAt ?? (/* @__PURE__ */ new Date()).toISOString()),
|
|
366
|
-
linkedPath: f.linkedPath ? String(f.linkedPath) : void 0,
|
|
367
|
-
mimeType: f.mimeType ? String(f.mimeType) : void 0
|
|
368
|
-
};
|
|
369
|
-
}).filter((f) => f.name) : [],
|
|
370
|
-
screenshot: raw.screenshot ? String(raw.screenshot) : void 0
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
function writeTaskJson(projectDir, task) {
|
|
374
|
-
const dateDir = join2(getTasksDir(projectDir), getDateSubdir(task.created));
|
|
375
|
-
mkdirSync(dateDir, { recursive: true });
|
|
376
|
-
const filePath = join2(dateDir, `${task.id}.json`);
|
|
377
|
-
writeFileSync(filePath, JSON.stringify(task, null, 2), "utf-8");
|
|
378
|
-
}
|
|
379
|
-
function createTask(projectDir, input) {
|
|
380
|
-
const normalizedPriority = (() => {
|
|
381
|
-
const raw = (input.priority ?? "").trim().toLowerCase();
|
|
382
|
-
if (raw === "critical") return "Critical";
|
|
383
|
-
if (raw === "high") return "High";
|
|
384
|
-
if (raw === "low") return "Low";
|
|
385
|
-
return "Medium";
|
|
386
|
-
})();
|
|
387
|
-
const task = {
|
|
388
|
-
...input,
|
|
389
|
-
priority: normalizedPriority,
|
|
390
|
-
id: generateTaskId(),
|
|
391
|
-
created: (/* @__PURE__ */ new Date()).toISOString(),
|
|
392
|
-
comments: [],
|
|
393
|
-
files: []
|
|
394
|
-
};
|
|
395
|
-
writeTaskJson(projectDir, task);
|
|
396
|
-
queueAutoEmbeddingRefresh(projectDir, task);
|
|
397
|
-
return task;
|
|
398
|
-
}
|
|
399
|
-
function readTaskFile(filePath) {
|
|
400
|
-
try {
|
|
401
|
-
const content = readFileSync(filePath, "utf-8");
|
|
402
|
-
if (filePath.endsWith(".json")) {
|
|
403
|
-
const raw = JSON.parse(content);
|
|
404
|
-
if (!raw || typeof raw !== "object" || !("id" in raw)) return null;
|
|
405
|
-
return normalizeTask(raw);
|
|
406
|
-
}
|
|
407
|
-
return parseTask(content);
|
|
408
|
-
} catch {
|
|
409
|
-
return null;
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
function collectTaskFiles(projectDir) {
|
|
413
|
-
const tasksDir = getTasksDir(projectDir);
|
|
414
|
-
if (!existsSync2(tasksDir)) return [];
|
|
415
|
-
const results = [];
|
|
416
|
-
for (const entry of readdirSync(tasksDir, { withFileTypes: true })) {
|
|
417
|
-
if (entry.isDirectory()) {
|
|
418
|
-
const dateDir = join2(tasksDir, entry.name);
|
|
419
|
-
for (const file of readdirSync(dateDir)) {
|
|
420
|
-
if (extname(file) === ".json") {
|
|
421
|
-
const filePath = join2(dateDir, file);
|
|
422
|
-
const task = readTaskFile(filePath);
|
|
423
|
-
if (task) results.push({ task, filePath });
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
} else if (extname(entry.name) === ".json") {
|
|
427
|
-
const filePath = join2(tasksDir, entry.name);
|
|
428
|
-
const task = readTaskFile(filePath);
|
|
429
|
-
if (task) results.push({ task, filePath });
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
return results.sort((a, b) => {
|
|
433
|
-
const aDate = a.task.updated ?? a.task.created;
|
|
434
|
-
const bDate = b.task.updated ?? b.task.created;
|
|
435
|
-
return new Date(bDate).getTime() - new Date(aDate).getTime();
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
function listTasks(projectDir) {
|
|
439
|
-
return collectTaskFiles(projectDir).map(({ task }) => task);
|
|
440
|
-
}
|
|
441
|
-
function listTasksWithPaths(projectDir) {
|
|
442
|
-
return collectTaskFiles(projectDir).map(({ task, filePath }) => ({ ...task, filePath }));
|
|
443
|
-
}
|
|
444
|
-
function updateTask(projectDir, taskId, updates) {
|
|
445
|
-
const existingPath = findTaskFilePath(projectDir, taskId);
|
|
446
|
-
const task = existingPath ? readTaskFile(existingPath) : null;
|
|
447
|
-
if (!task) return null;
|
|
448
|
-
const updated = { ...task, ...updates, updated: (/* @__PURE__ */ new Date()).toISOString() };
|
|
449
|
-
writeTaskJson(projectDir, updated);
|
|
450
|
-
if (existingPath && existingPath !== getTaskFilePath(projectDir, taskId, updated.created)) {
|
|
451
|
-
try {
|
|
452
|
-
unlinkSync(existingPath);
|
|
453
|
-
} catch {
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
queueAutoEmbeddingRefresh(projectDir, updated);
|
|
457
|
-
return updated;
|
|
458
|
-
}
|
|
459
|
-
function deleteTask(projectDir, taskId) {
|
|
460
|
-
const filePath = findTaskFilePath(projectDir, taskId);
|
|
461
|
-
if (!filePath) return false;
|
|
462
|
-
unlinkSync(filePath);
|
|
463
|
-
return true;
|
|
464
|
-
}
|
|
465
|
-
function formatTaskForAgent(task, comments, files) {
|
|
466
|
-
return {
|
|
467
|
-
id: task.id,
|
|
468
|
-
status: task.status,
|
|
469
|
-
title: task.title,
|
|
470
|
-
description: task.description,
|
|
471
|
-
...task.url && { url: task.url },
|
|
472
|
-
selector: task.selector,
|
|
473
|
-
...task.file && { file: task.file },
|
|
474
|
-
...task.line != null && { line: task.line },
|
|
475
|
-
...task.col != null && { col: task.col },
|
|
476
|
-
...task.component && { component: task.component },
|
|
477
|
-
...task.type && { type: task.type },
|
|
478
|
-
...task.priority && { priority: task.priority },
|
|
479
|
-
...comments && comments.length > 0 && { structuredComments: comments },
|
|
480
|
-
...files && files.length > 0 && { linkedFiles: files },
|
|
481
|
-
...task.reportBack && { reportBack: true },
|
|
482
|
-
created: task.created
|
|
483
|
-
};
|
|
484
|
-
}
|
|
485
|
-
var embeddingWriteQueue = Promise.resolve();
|
|
486
|
-
function queueAutoEmbeddingRefresh(projectDir, task) {
|
|
487
|
-
if (process.env.NODE_ENV === "test" && process.env.PROTO_AUTO_EMBED !== "1") {
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
embeddingWriteQueue = embeddingWriteQueue.then(async () => {
|
|
491
|
-
try {
|
|
492
|
-
const [titleVec, descVec, combinedVec] = await Promise.all([
|
|
493
|
-
generateEmbedding(taskTitleEmbedText(task), DEFAULT_OLLAMA_URL, EMBED_MODEL),
|
|
494
|
-
generateEmbedding(taskDescriptionEmbedText(task), DEFAULT_OLLAMA_URL, EMBED_MODEL),
|
|
495
|
-
generateEmbedding(taskToEmbedText(task), DEFAULT_OLLAMA_URL, EMBED_MODEL)
|
|
496
|
-
]);
|
|
497
|
-
const current = readEmbeddingIndex(projectDir);
|
|
498
|
-
const byTaskId = new Map(current.entries.map((entry) => [entry.taskId, entry]));
|
|
499
|
-
byTaskId.set(task.id, {
|
|
500
|
-
taskId: task.id,
|
|
501
|
-
title: task.title,
|
|
502
|
-
description: task.description ?? "",
|
|
503
|
-
status: task.status,
|
|
504
|
-
priority: task.priority,
|
|
505
|
-
url: task.url,
|
|
506
|
-
created: task.created,
|
|
507
|
-
updated: task.updated,
|
|
508
|
-
vectors: {
|
|
509
|
-
title: titleVec,
|
|
510
|
-
description: descVec,
|
|
511
|
-
combined: combinedVec
|
|
512
|
-
}
|
|
513
|
-
});
|
|
514
|
-
writeEmbeddingIndex(projectDir, {
|
|
515
|
-
model: EMBED_MODEL,
|
|
516
|
-
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
517
|
-
entries: Array.from(byTaskId.values())
|
|
518
|
-
});
|
|
519
|
-
} catch (e) {
|
|
520
|
-
if (e instanceof OllamaUnavailableError) return;
|
|
521
|
-
const isNetworkError = e instanceof TypeError && /fetch failed|ECONNREFUSED|ENOTFOUND/i.test(String(e));
|
|
522
|
-
if (isNetworkError) return;
|
|
523
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
524
|
-
console.error(`[Proto] Background embedding error for task ${task.id}: ${msg}`);
|
|
525
|
-
}
|
|
526
|
-
}).catch((e) => {
|
|
527
|
-
if (e instanceof OllamaUnavailableError) return;
|
|
528
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
529
|
-
console.error(`[Proto] Embedding queue error: ${msg}`);
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
function getEmbeddingIndexPath(projectDir) {
|
|
533
|
-
return join2(projectDir, PROTO_DIR, "embeddings", "index.bin");
|
|
534
|
-
}
|
|
535
|
-
function readEmbeddingIndex(projectDir) {
|
|
536
|
-
const indexPath = getEmbeddingIndexPath(projectDir);
|
|
537
|
-
if (!existsSync2(indexPath)) {
|
|
538
|
-
return { model: EMBED_MODEL, generatedAt: "", entries: [] };
|
|
539
|
-
}
|
|
540
|
-
try {
|
|
541
|
-
const raw = JSON.parse(readFileSync(indexPath, "utf-8"));
|
|
542
|
-
return {
|
|
543
|
-
model: raw.model ?? EMBED_MODEL,
|
|
544
|
-
generatedAt: raw.generatedAt ?? "",
|
|
545
|
-
entries: Array.isArray(raw.entries) ? raw.entries : []
|
|
546
|
-
};
|
|
547
|
-
} catch {
|
|
548
|
-
return { model: EMBED_MODEL, generatedAt: "", entries: [] };
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
function writeEmbeddingIndex(projectDir, payload) {
|
|
552
|
-
const indexPath = getEmbeddingIndexPath(projectDir);
|
|
553
|
-
mkdirSync(join2(projectDir, PROTO_DIR, "embeddings"), { recursive: true });
|
|
554
|
-
writeFileSync(indexPath, JSON.stringify(payload), "utf-8");
|
|
555
|
-
}
|
|
556
|
-
async function searchTasksHybrid(projectDir, args, ollamaUrl = DEFAULT_OLLAMA_URL, model = EMBED_MODEL) {
|
|
557
|
-
const index = readEmbeddingIndex(projectDir);
|
|
558
|
-
if (index.entries.length === 0) return [];
|
|
559
|
-
const queryVector = await generateEmbedding(args.query, ollamaUrl, model);
|
|
560
|
-
const ranked = rankHybridResults({
|
|
561
|
-
entries: index.entries,
|
|
562
|
-
query: args.query,
|
|
563
|
-
queryVectorTitle: queryVector,
|
|
564
|
-
queryVectorDescription: queryVector,
|
|
565
|
-
queryVectorCombined: queryVector,
|
|
566
|
-
options: {
|
|
567
|
-
pageScope: args.pageScope,
|
|
568
|
-
statusBoost: {
|
|
569
|
-
"in-progress": 1.2,
|
|
570
|
-
review: 1.1,
|
|
571
|
-
todo: 1.05
|
|
572
|
-
},
|
|
573
|
-
priorityBoost: {
|
|
574
|
-
Critical: 1.35,
|
|
575
|
-
High: 1.2,
|
|
576
|
-
Medium: 1.05
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
});
|
|
580
|
-
const tasksById = new Map(listTasks(projectDir).map((t) => [t.id, t]));
|
|
581
|
-
const limit = Math.max(1, args.limit ?? 10);
|
|
582
|
-
return ranked.slice(0, limit).map((row) => ({ task: tasksById.get(row.taskId), score: row.score })).filter((row) => !!row.task);
|
|
583
|
-
}
|
|
584
|
-
function migrateTasksFromMd(projectDir) {
|
|
585
|
-
const tasksDir = getTasksDir(projectDir);
|
|
586
|
-
const commentsDir = join2(projectDir, PROTO_DIR, COMMENTS_DIR);
|
|
587
|
-
if (!existsSync2(tasksDir)) return 0;
|
|
588
|
-
const mdFiles = readdirSync(tasksDir).filter((f) => extname(f) === ".md");
|
|
589
|
-
let migrated = 0;
|
|
590
|
-
for (const mdFile of mdFiles) {
|
|
591
|
-
const mdPath = join2(tasksDir, mdFile);
|
|
592
|
-
try {
|
|
593
|
-
const legacyTask = parseTask(readFileSync(mdPath, "utf-8"));
|
|
594
|
-
if (!legacyTask) continue;
|
|
595
|
-
if (findTaskFilePath(projectDir, legacyTask.id)) {
|
|
596
|
-
unlinkSync(mdPath);
|
|
597
|
-
continue;
|
|
598
|
-
}
|
|
599
|
-
const commentsPath = join2(commentsDir, `${legacyTask.id}.json`);
|
|
600
|
-
let comments = [];
|
|
601
|
-
if (existsSync2(commentsPath)) {
|
|
602
|
-
try {
|
|
603
|
-
const raw = JSON.parse(readFileSync(commentsPath, "utf-8"));
|
|
604
|
-
if (Array.isArray(raw)) {
|
|
605
|
-
comments = raw.map((c, i) => ({
|
|
606
|
-
...c,
|
|
607
|
-
id: c.id || String(Date.now() + i).slice(-10)
|
|
608
|
-
}));
|
|
609
|
-
}
|
|
610
|
-
} catch {
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
const newTask = {
|
|
614
|
-
...legacyTask,
|
|
615
|
-
comments,
|
|
616
|
-
files: []
|
|
617
|
-
};
|
|
618
|
-
writeTaskJson(projectDir, newTask);
|
|
619
|
-
unlinkSync(mdPath);
|
|
620
|
-
migrated++;
|
|
621
|
-
} catch {
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
return migrated;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
// src/core/files.ts
|
|
628
|
-
var LINKED_MANIFEST = ".linked.json";
|
|
629
|
-
function getFilesDir(projectDir, taskId) {
|
|
630
|
-
return join3(projectDir, PROTO_DIR, FILES_DIR, taskId);
|
|
631
|
-
}
|
|
632
|
-
function ensureFilesDir(projectDir, taskId) {
|
|
633
|
-
mkdirSync2(getFilesDir(projectDir, taskId), { recursive: true });
|
|
634
|
-
}
|
|
635
|
-
function readLinked(projectDir, taskId) {
|
|
636
|
-
const manifestPath = join3(getFilesDir(projectDir, taskId), LINKED_MANIFEST);
|
|
637
|
-
if (!existsSync3(manifestPath)) return [];
|
|
638
|
-
try {
|
|
639
|
-
return JSON.parse(readFileSync2(manifestPath, "utf-8"));
|
|
640
|
-
} catch {
|
|
641
|
-
return [];
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
function getTaskFileRefs(projectDir, taskId) {
|
|
645
|
-
const filePath = findTaskFilePath(projectDir, taskId);
|
|
646
|
-
const task = filePath ? readTaskFile(filePath) : null;
|
|
647
|
-
if (!task?.files || task.files.length === 0) return [];
|
|
648
|
-
return task.files;
|
|
649
|
-
}
|
|
650
|
-
function setTaskFileRefs(projectDir, taskId, refs) {
|
|
651
|
-
updateTask(projectDir, taskId, { files: refs });
|
|
652
|
-
}
|
|
653
|
-
function migrateLegacyLinkedRefs(projectDir, taskId) {
|
|
654
|
-
const refs = getTaskFileRefs(projectDir, taskId);
|
|
655
|
-
const manifestPath = join3(getFilesDir(projectDir, taskId), LINKED_MANIFEST);
|
|
656
|
-
if (!existsSync3(manifestPath)) return refs;
|
|
657
|
-
const legacy = readLinked(projectDir, taskId);
|
|
658
|
-
if (legacy.length === 0) {
|
|
659
|
-
try {
|
|
660
|
-
unlinkSync2(manifestPath);
|
|
661
|
-
} catch {
|
|
662
|
-
}
|
|
663
|
-
return refs;
|
|
664
|
-
}
|
|
665
|
-
const next = refs.slice();
|
|
666
|
-
let added = false;
|
|
667
|
-
for (const entry of legacy) {
|
|
668
|
-
if (!next.find((f) => f.linkedPath === entry.path || f.name === entry.name && f.linkedPath)) {
|
|
669
|
-
next.push({
|
|
670
|
-
name: entry.name,
|
|
671
|
-
linkedPath: entry.path,
|
|
672
|
-
addedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
673
|
-
});
|
|
674
|
-
added = true;
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
if (added) {
|
|
678
|
-
setTaskFileRefs(projectDir, taskId, next);
|
|
679
|
-
}
|
|
680
|
-
try {
|
|
681
|
-
unlinkSync2(manifestPath);
|
|
682
|
-
} catch {
|
|
683
|
-
}
|
|
684
|
-
return next;
|
|
685
|
-
}
|
|
686
|
-
function listFiles(projectDir, taskId) {
|
|
687
|
-
const dir = getFilesDir(projectDir, taskId);
|
|
688
|
-
const refs = migrateLegacyLinkedRefs(projectDir, taskId);
|
|
689
|
-
const byName = /* @__PURE__ */ new Map();
|
|
690
|
-
for (const ref of refs) {
|
|
691
|
-
if (ref.linkedPath && existsSync3(ref.linkedPath)) {
|
|
692
|
-
const stat = statSync(ref.linkedPath);
|
|
693
|
-
byName.set(ref.name, {
|
|
694
|
-
name: ref.name,
|
|
695
|
-
size: stat.size,
|
|
696
|
-
url: `/api/tasks/${taskId}/files/${encodeURIComponent(ref.name)}`,
|
|
697
|
-
linkedPath: ref.linkedPath,
|
|
698
|
-
createdAt: stat.mtime.toISOString()
|
|
699
|
-
});
|
|
700
|
-
continue;
|
|
701
|
-
}
|
|
702
|
-
const uploadedPath = join3(dir, ref.name);
|
|
703
|
-
if (existsSync3(uploadedPath)) {
|
|
704
|
-
const stat = statSync(uploadedPath);
|
|
705
|
-
byName.set(ref.name, {
|
|
706
|
-
name: ref.name,
|
|
707
|
-
size: stat.size,
|
|
708
|
-
url: `/api/tasks/${taskId}/files/${encodeURIComponent(ref.name)}`,
|
|
709
|
-
createdAt: stat.mtime.toISOString()
|
|
710
|
-
});
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
if (existsSync3(dir)) {
|
|
714
|
-
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
715
|
-
if (!entry.isFile() || entry.name === LINKED_MANIFEST || byName.has(entry.name)) continue;
|
|
716
|
-
const fullPath = join3(dir, entry.name);
|
|
717
|
-
const stat = statSync(fullPath);
|
|
718
|
-
byName.set(entry.name, {
|
|
719
|
-
name: entry.name,
|
|
720
|
-
size: stat.size,
|
|
721
|
-
url: `/api/tasks/${taskId}/files/${encodeURIComponent(entry.name)}`,
|
|
722
|
-
createdAt: stat.mtime.toISOString()
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
return Array.from(byName.values());
|
|
727
|
-
}
|
|
728
|
-
function saveFile(projectDir, taskId, filename, data) {
|
|
729
|
-
const safe = basename(filename);
|
|
730
|
-
ensureFilesDir(projectDir, taskId);
|
|
731
|
-
writeFileSync2(join3(getFilesDir(projectDir, taskId), safe), data);
|
|
732
|
-
const refs = migrateLegacyLinkedRefs(projectDir, taskId);
|
|
733
|
-
if (!refs.find((f) => f.name === safe && !f.linkedPath)) {
|
|
734
|
-
refs.push({ name: safe, addedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
735
|
-
setTaskFileRefs(projectDir, taskId, refs);
|
|
736
|
-
}
|
|
737
|
-
return {
|
|
738
|
-
name: safe,
|
|
739
|
-
size: data.length,
|
|
740
|
-
url: `/api/tasks/${taskId}/files/${encodeURIComponent(safe)}`
|
|
741
|
-
};
|
|
742
|
-
}
|
|
743
|
-
function linkFile(projectDir, taskId, absolutePath) {
|
|
744
|
-
if (!isAbsolute(absolutePath)) return null;
|
|
745
|
-
if (!existsSync3(absolutePath)) return null;
|
|
746
|
-
const stat = statSync(absolutePath);
|
|
747
|
-
if (!stat.isFile()) return null;
|
|
748
|
-
const name = basename(absolutePath);
|
|
749
|
-
const refs = migrateLegacyLinkedRefs(projectDir, taskId);
|
|
750
|
-
if (!refs.find((f) => f.linkedPath === absolutePath)) {
|
|
751
|
-
refs.push({
|
|
752
|
-
name,
|
|
753
|
-
linkedPath: absolutePath,
|
|
754
|
-
addedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
755
|
-
});
|
|
756
|
-
setTaskFileRefs(projectDir, taskId, refs);
|
|
757
|
-
}
|
|
758
|
-
return {
|
|
759
|
-
name,
|
|
760
|
-
size: stat.size,
|
|
761
|
-
url: `/api/tasks/${taskId}/files/${encodeURIComponent(name)}`,
|
|
762
|
-
linkedPath: absolutePath
|
|
763
|
-
};
|
|
764
|
-
}
|
|
765
|
-
function deleteFile(projectDir, taskId, filename) {
|
|
766
|
-
const safe = basename(filename);
|
|
767
|
-
const refs = migrateLegacyLinkedRefs(projectDir, taskId);
|
|
768
|
-
const idx = refs.findIndex((f) => f.name === safe);
|
|
769
|
-
if (idx !== -1) {
|
|
770
|
-
const [removed] = refs.splice(idx, 1);
|
|
771
|
-
setTaskFileRefs(projectDir, taskId, refs);
|
|
772
|
-
if (removed && !removed.linkedPath) {
|
|
773
|
-
const uploadedPath = join3(getFilesDir(projectDir, taskId), safe);
|
|
774
|
-
if (existsSync3(uploadedPath)) unlinkSync2(uploadedPath);
|
|
775
|
-
}
|
|
776
|
-
return true;
|
|
777
|
-
}
|
|
778
|
-
const filePath = join3(getFilesDir(projectDir, taskId), safe);
|
|
779
|
-
if (!existsSync3(filePath)) return false;
|
|
780
|
-
unlinkSync2(filePath);
|
|
781
|
-
return true;
|
|
782
|
-
}
|
|
783
|
-
function getFilePath(projectDir, taskId, filename) {
|
|
784
|
-
const safe = basename(filename);
|
|
785
|
-
const linkedRef = migrateLegacyLinkedRefs(projectDir, taskId).find((f) => f.name === safe && f.linkedPath);
|
|
786
|
-
if (linkedRef?.linkedPath && existsSync3(linkedRef.linkedPath)) return linkedRef.linkedPath;
|
|
787
|
-
const filePath = join3(getFilesDir(projectDir, taskId), safe);
|
|
788
|
-
return existsSync3(filePath) ? filePath : null;
|
|
789
|
-
}
|
|
790
|
-
function getFileCount(projectDir, taskId) {
|
|
791
|
-
return listFiles(projectDir, taskId).length;
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
export {
|
|
795
|
-
ANNOTATION_TAGS,
|
|
796
|
-
PROTO_DIR,
|
|
797
|
-
AGENTS_DIR,
|
|
798
|
-
SCREENSHOTS_DIR,
|
|
799
|
-
CONFIG_FILE,
|
|
800
|
-
generateTaskId,
|
|
801
|
-
ensureTaskDirs,
|
|
802
|
-
getTaskFilePath,
|
|
803
|
-
findTaskFilePath,
|
|
804
|
-
createTask,
|
|
805
|
-
readTaskFile,
|
|
806
|
-
listTasks,
|
|
807
|
-
listTasksWithPaths,
|
|
808
|
-
updateTask,
|
|
809
|
-
deleteTask,
|
|
810
|
-
formatTaskForAgent,
|
|
811
|
-
searchTasksHybrid,
|
|
812
|
-
migrateTasksFromMd,
|
|
813
|
-
getFilesDir,
|
|
814
|
-
ensureFilesDir,
|
|
815
|
-
listFiles,
|
|
816
|
-
saveFile,
|
|
817
|
-
linkFile,
|
|
818
|
-
deleteFile,
|
|
819
|
-
getFilePath,
|
|
820
|
-
getFileCount
|
|
821
|
-
};
|
|
822
|
-
//# sourceMappingURL=chunk-TWAAPROG.js.map
|