@nick848/fet 1.0.6 → 1.0.8
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 +79 -220
- package/README_en.md +196 -0
- package/dist/cli/index.js +1674 -998
- package/dist/cli/index.js.map +1 -1
- package/package.json +4 -2
package/dist/cli/index.js
CHANGED
|
@@ -8,9 +8,28 @@ import {
|
|
|
8
8
|
import { createInterface } from "readline/promises";
|
|
9
9
|
import { Command } from "commander";
|
|
10
10
|
|
|
11
|
-
// src/commands/
|
|
12
|
-
import { readFile as
|
|
13
|
-
import { join as
|
|
11
|
+
// src/commands/doctor.ts
|
|
12
|
+
import { readFile as readFile4, stat as stat3 } from "fs/promises";
|
|
13
|
+
import { join as join6 } from "path";
|
|
14
|
+
|
|
15
|
+
// src/context-placeholders.ts
|
|
16
|
+
import { readFile } from "fs/promises";
|
|
17
|
+
import { join } from "path";
|
|
18
|
+
var AGENTS_LLM_PLACEHOLDER_PATTERN = /\[NEEDS? LLM INPUT\]/g;
|
|
19
|
+
async function countAgentsLlmPlaceholders(projectRoot) {
|
|
20
|
+
try {
|
|
21
|
+
const content = await readFile(join(projectRoot, "AGENTS.md"), "utf8");
|
|
22
|
+
return [...content.matchAll(AGENTS_LLM_PLACEHOLDER_PATTERN)].length;
|
|
23
|
+
} catch {
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function renderAgentsPlaceholderWarning(count2, language = "zh-CN") {
|
|
28
|
+
if (language === "en") {
|
|
29
|
+
return `AGENTS.md still contains ${count2} LLM placeholder(s). Run fet fill-context first so your IDE AI can replace them. Continuing current command.`;
|
|
30
|
+
}
|
|
31
|
+
return `AGENTS.md \u4ECD\u6709 ${count2} \u4E2A LLM \u5360\u4F4D\u7B26\u3002\u5EFA\u8BAE\u5148\u8FD0\u884C fet fill-context\uFF0C\u8BA9 IDE AI \u8865\u9F50\u9879\u76EE\u4E0A\u4E0B\u6587\uFF1B\u5F53\u524D\u547D\u4EE4\u4F1A\u7EE7\u7EED\u6267\u884C\u3002`;
|
|
32
|
+
}
|
|
14
33
|
|
|
15
34
|
// src/fs/atomic-write.ts
|
|
16
35
|
import { dirname } from "path";
|
|
@@ -31,7 +50,7 @@ async function atomicWrite(targetPath, content) {
|
|
|
31
50
|
|
|
32
51
|
// src/fs/backup.ts
|
|
33
52
|
import { copyFile, stat } from "fs/promises";
|
|
34
|
-
import { basename, dirname as dirname2, join } from "path";
|
|
53
|
+
import { basename, dirname as dirname2, join as join2 } from "path";
|
|
35
54
|
async function createBackup(filePath) {
|
|
36
55
|
try {
|
|
37
56
|
await stat(filePath);
|
|
@@ -39,17 +58,17 @@ async function createBackup(filePath) {
|
|
|
39
58
|
return null;
|
|
40
59
|
}
|
|
41
60
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\..+$/, "").replace("T", "-");
|
|
42
|
-
const backupPath =
|
|
61
|
+
const backupPath = join2(dirname2(filePath), `${basename(filePath)}.fet-backup-${timestamp}`);
|
|
43
62
|
await copyFile(filePath, backupPath);
|
|
44
63
|
return backupPath;
|
|
45
64
|
}
|
|
46
65
|
|
|
47
66
|
// src/fs/lock.ts
|
|
48
67
|
import { hostname } from "os";
|
|
49
|
-
import { dirname as dirname3, join as
|
|
50
|
-
import { mkdir as mkdir2, open as open2, readFile, rm } from "fs/promises";
|
|
68
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
69
|
+
import { mkdir as mkdir2, open as open2, readFile as readFile2, rm } from "fs/promises";
|
|
51
70
|
async function withProjectLock(projectRoot, metadata, fn) {
|
|
52
|
-
const lockPath =
|
|
71
|
+
const lockPath = join3(projectRoot, "openspec", ".fet.lock");
|
|
53
72
|
const lock = {
|
|
54
73
|
pid: process.pid,
|
|
55
74
|
hostname: hostname(),
|
|
@@ -81,13 +100,13 @@ async function withProjectLock(projectRoot, metadata, fn) {
|
|
|
81
100
|
}
|
|
82
101
|
async function readExistingLock(lockPath) {
|
|
83
102
|
try {
|
|
84
|
-
return JSON.parse(await
|
|
103
|
+
return JSON.parse(await readFile2(lockPath, "utf8"));
|
|
85
104
|
} catch {
|
|
86
105
|
return { path: lockPath };
|
|
87
106
|
}
|
|
88
107
|
}
|
|
89
108
|
async function clearLock(projectRoot) {
|
|
90
|
-
const lockPath =
|
|
109
|
+
const lockPath = join3(projectRoot, "openspec", ".fet.lock");
|
|
91
110
|
try {
|
|
92
111
|
await rm(lockPath);
|
|
93
112
|
return true;
|
|
@@ -97,8 +116,8 @@ async function clearLock(projectRoot) {
|
|
|
97
116
|
}
|
|
98
117
|
|
|
99
118
|
// src/fs/journal.ts
|
|
100
|
-
import { join as
|
|
101
|
-
import { readFile as
|
|
119
|
+
import { join as join4 } from "path";
|
|
120
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
102
121
|
function createInitJournal(fetVersion) {
|
|
103
122
|
return {
|
|
104
123
|
schemaVersion: 1,
|
|
@@ -110,7 +129,7 @@ function createInitJournal(fetVersion) {
|
|
|
110
129
|
}
|
|
111
130
|
async function writeInitJournal(projectRoot, journal) {
|
|
112
131
|
await atomicWrite(
|
|
113
|
-
|
|
132
|
+
join4(projectRoot, "openspec", ".fet-init-journal.json"),
|
|
114
133
|
`${JSON.stringify(journal, null, 2)}
|
|
115
134
|
`
|
|
116
135
|
);
|
|
@@ -119,7 +138,7 @@ async function writeInitJournal(projectRoot, journal) {
|
|
|
119
138
|
// src/gitnexus.ts
|
|
120
139
|
import { execFile } from "child_process";
|
|
121
140
|
import { stat as stat2 } from "fs/promises";
|
|
122
|
-
import { join as
|
|
141
|
+
import { join as join5 } from "path";
|
|
123
142
|
import { promisify } from "util";
|
|
124
143
|
var execFileAsync = promisify(execFile);
|
|
125
144
|
var DEFAULT_GRAPH_PATH = ".gitnexus";
|
|
@@ -165,7 +184,7 @@ function toGitNexusState(detection, previous) {
|
|
|
165
184
|
}
|
|
166
185
|
async function inspectGitNexusGraph(projectRoot, env = process.env) {
|
|
167
186
|
const relative2 = env.FET_GITNEXUS_GRAPH_PATH?.trim() || DEFAULT_GRAPH_PATH;
|
|
168
|
-
const graphPath =
|
|
187
|
+
const graphPath = join5(projectRoot, relative2);
|
|
169
188
|
try {
|
|
170
189
|
const info = await stat2(graphPath);
|
|
171
190
|
return {
|
|
@@ -213,11 +232,17 @@ function mergeGitNexusGraphInfo(state, graph2) {
|
|
|
213
232
|
lastIndexedAt: graph2.lastIndexedAt
|
|
214
233
|
};
|
|
215
234
|
}
|
|
216
|
-
function renderGitNexusRecommendation(state) {
|
|
235
|
+
function renderGitNexusRecommendation(state, language = "zh-CN") {
|
|
236
|
+
if (language === "en") {
|
|
237
|
+
if (state.installed) {
|
|
238
|
+
return `Optional GitNexus detected (${state.version ?? "unknown"}). You can generate a code graph after init; future OpenSpec artifacts should prefer the graph when it is available.`;
|
|
239
|
+
}
|
|
240
|
+
return "Optional GitNexus code graph support is not installed. Consider installing GitNexus later to speed up OpenSpec artifact generation and improve code insertion context.";
|
|
241
|
+
}
|
|
217
242
|
if (state.installed) {
|
|
218
|
-
return
|
|
243
|
+
return `\u68C0\u6D4B\u5230\u53EF\u9009 GitNexus\uFF08${state.version ?? "unknown"}\uFF09\u3002\u521D\u59CB\u5316\u540E\u53EF\u4EE5\u751F\u6210\u4EE3\u7801\u56FE\uFF1B\u540E\u7EED OpenSpec \u4EA7\u7269\u5728\u56FE\u53EF\u7528\u65F6\u5E94\u4F18\u5148\u53C2\u8003\u5B83\u3002`;
|
|
219
244
|
}
|
|
220
|
-
return "
|
|
245
|
+
return "\u672A\u5B89\u88C5\u53EF\u9009\u7684 GitNexus \u4EE3\u7801\u56FE\u652F\u6301\u3002\u53EF\u4EE5\u7A0D\u540E\u5B89\u88C5 GitNexus\uFF0C\u4EE5\u52A0\u901F OpenSpec \u4EA7\u7269\u751F\u6210\u5E76\u6539\u5584\u4EE3\u7801\u63D2\u5165\u4E0A\u4E0B\u6587\u3002";
|
|
221
246
|
}
|
|
222
247
|
function resolveGitNexusCommand(env) {
|
|
223
248
|
const raw = env.FET_GITNEXUS_COMMAND?.trim() || env.FET_GITNEXUS_EXECUTABLE?.trim() || "gitnexus";
|
|
@@ -230,946 +255,1180 @@ function splitCommand(value) {
|
|
|
230
255
|
return (matches?.length ? matches : [value]).map((part) => part.replace(/^["']|["']$/g, ""));
|
|
231
256
|
}
|
|
232
257
|
|
|
233
|
-
// src/
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
258
|
+
// src/commands/doctor.ts
|
|
259
|
+
async function doctorCommand(ctx, options = {}) {
|
|
260
|
+
const checks = [];
|
|
261
|
+
checks.push(await checkOpenSpec(ctx));
|
|
262
|
+
checks.push(await checkState(ctx));
|
|
263
|
+
checks.push(await checkFile("agents", join6(ctx.projectRoot, "AGENTS.md"), "AGENTS.md \u7F3A\u5931", "fet update-context"));
|
|
264
|
+
checks.push(await checkFile("config", join6(ctx.projectRoot, "openspec", "config.yaml"), "openspec/config.yaml \u7F3A\u5931", "fet init"));
|
|
265
|
+
checks.push(await checkPlaceholders(ctx));
|
|
266
|
+
checks.push(await checkGitNexus(ctx));
|
|
267
|
+
for (const adapter of ctx.toolAdapters) {
|
|
268
|
+
checks.push(...await adapter.doctor(ctx.projectRoot));
|
|
269
|
+
}
|
|
270
|
+
const lockPath = join6(ctx.projectRoot, "openspec", ".fet.lock");
|
|
271
|
+
if (await exists(lockPath)) {
|
|
272
|
+
if (options.fixLock) {
|
|
273
|
+
await clearLock(ctx.projectRoot);
|
|
274
|
+
checks.push({ id: "lock", status: "pass", message: "\u5DF2\u6E05\u7406 openspec/.fet.lock" });
|
|
275
|
+
} else {
|
|
276
|
+
checks.push({ id: "lock", status: "warn", message: "\u5B58\u5728 openspec/.fet.lock", suggestedCommand: "fet doctor --fix-lock" });
|
|
252
277
|
}
|
|
253
|
-
currentDir = dirname4(currentDir);
|
|
254
278
|
}
|
|
279
|
+
const warnings = checks.filter((check) => check.status !== "pass").map((check) => check.message);
|
|
280
|
+
ctx.output.result({
|
|
281
|
+
ok: true,
|
|
282
|
+
command: "doctor",
|
|
283
|
+
summary: warnings.length ? `\u8BCA\u65AD\u5B8C\u6210\uFF0C\u53D1\u73B0 ${warnings.length} \u4E2A\u9700\u8981\u5173\u6CE8\u7684\u95EE\u9898\u3002` : "\u8BCA\u65AD\u5B8C\u6210\uFF0C\u672A\u53D1\u73B0\u660E\u663E\u95EE\u9898\u3002",
|
|
284
|
+
warnings,
|
|
285
|
+
data: checks
|
|
286
|
+
});
|
|
255
287
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
function hasInvalidManagedAutoRegion(content) {
|
|
267
|
-
const beginCount = count(content, AUTO_BEGIN);
|
|
268
|
-
const endCount = count(content, AUTO_END);
|
|
269
|
-
return beginCount !== endCount || beginCount > 1 || endCount > 1 || beginCount === 1 && content.indexOf(AUTO_BEGIN) > content.indexOf(AUTO_END);
|
|
270
|
-
}
|
|
271
|
-
function replaceManagedRegion(existing, generated) {
|
|
272
|
-
if (!existing) {
|
|
273
|
-
return generated;
|
|
274
|
-
}
|
|
275
|
-
const start = existing.indexOf(AUTO_BEGIN);
|
|
276
|
-
const end = existing.indexOf(AUTO_END);
|
|
277
|
-
if (start === -1 || end === -1 || end < start) {
|
|
278
|
-
return generated;
|
|
288
|
+
async function checkGitNexus(ctx) {
|
|
289
|
+
const global = await ctx.stateStore.readGlobal();
|
|
290
|
+
const state = mergeGitNexusGraphInfo(
|
|
291
|
+
toGitNexusState(await detectGitNexus(), global?.graph?.gitnexus),
|
|
292
|
+
await inspectGitNexusGraph(ctx.projectRoot)
|
|
293
|
+
);
|
|
294
|
+
if (global) {
|
|
295
|
+
global.graph ??= {};
|
|
296
|
+
global.graph.gitnexus = state;
|
|
297
|
+
await ctx.stateStore.writeGlobal(global);
|
|
279
298
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
299
|
+
return state.installed ? {
|
|
300
|
+
id: "gitnexus",
|
|
301
|
+
status: "pass",
|
|
302
|
+
message: ctx.language === "en" ? `GitNexus detected: ${state.executablePath ?? "gitnexus"} (${state.version ?? "unknown"}), graph ${state.graphExists ? "found" : "not found"}` : `\u68C0\u6D4B\u5230 GitNexus\uFF1A${state.executablePath ?? "gitnexus"}\uFF08${state.version ?? "unknown"}\uFF09\uFF0C\u4EE3\u7801\u56FE${state.graphExists ? "\u5DF2\u627E\u5230" : "\u672A\u627E\u5230"}`
|
|
303
|
+
} : {
|
|
304
|
+
id: "gitnexus",
|
|
305
|
+
status: "warn",
|
|
306
|
+
message: ctx.language === "en" ? "Optional GitNexus code graph support is not installed" : "\u5C1A\u672A\u5B89\u88C5\u53EF\u9009\u7684 GitNexus \u4EE3\u7801\u56FE\u652F\u6301",
|
|
307
|
+
suggestedCommand: ctx.language === "en" ? "Install GitNexus later if you want OpenSpec artifacts to prefer a repository graph" : "\u5982\u679C\u5E0C\u671B OpenSpec \u4EA7\u7269\u4F18\u5148\u53C2\u8003\u4ED3\u5E93\u4EE3\u7801\u56FE\uFF0C\u53EF\u4EE5\u7A0D\u540E\u5B89\u88C5 GitNexus"
|
|
308
|
+
};
|
|
287
309
|
}
|
|
288
|
-
function
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
310
|
+
async function checkOpenSpec(ctx) {
|
|
311
|
+
try {
|
|
312
|
+
const identity = await ctx.openSpec.resolveExecutable();
|
|
313
|
+
return { id: "openspec", status: "pass", message: `OpenSpec: ${identity.executablePath} (${identity.version})` };
|
|
314
|
+
} catch (error) {
|
|
315
|
+
return { id: "openspec", status: "fail", message: error instanceof Error ? error.message : "OpenSpec \u68C0\u6D4B\u5931\u8D25" };
|
|
293
316
|
}
|
|
294
|
-
return generatedSections.map((section) => {
|
|
295
|
-
const existing = existingSections.get(section.heading);
|
|
296
|
-
if (existing && LLM_PLACEHOLDER_PATTERN.test(section.body) && !LLM_PLACEHOLDER_PATTERN.test(existing.body)) {
|
|
297
|
-
return existing.raw.trim();
|
|
298
|
-
}
|
|
299
|
-
return section.raw.trim();
|
|
300
|
-
}).join("\n\n");
|
|
301
317
|
}
|
|
302
|
-
function
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
return
|
|
318
|
+
async function checkState(ctx) {
|
|
319
|
+
try {
|
|
320
|
+
const state = await ctx.stateStore.readGlobal();
|
|
321
|
+
return state ? { id: "state", status: "pass", message: "FET \u5168\u5C40\u72B6\u6001\u53EF\u8BFB\u53D6" } : { id: "state", status: "warn", message: "FET \u5168\u5C40\u72B6\u6001\u5C1A\u672A\u521D\u59CB\u5316", suggestedCommand: "fet init" };
|
|
322
|
+
} catch (error) {
|
|
323
|
+
return { id: "state", status: "fail", message: error instanceof Error ? error.message : "FET \u72B6\u6001\u8BFB\u53D6\u5931\u8D25" };
|
|
306
324
|
}
|
|
307
|
-
return matches.map((match, index) => {
|
|
308
|
-
const start = match.index ?? 0;
|
|
309
|
-
const end = matches[index + 1]?.index ?? content.length;
|
|
310
|
-
const raw = content.slice(start, end).trim();
|
|
311
|
-
const newline = raw.indexOf("\n");
|
|
312
|
-
const heading = newline === -1 ? raw.trim() : raw.slice(0, newline).trim();
|
|
313
|
-
const body = newline === -1 ? "" : raw.slice(newline + 1).trim();
|
|
314
|
-
return { heading, body, raw };
|
|
315
|
-
});
|
|
316
325
|
}
|
|
317
|
-
function
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
326
|
+
async function checkFile(id, path, missing, suggestedCommand) {
|
|
327
|
+
return await exists(path) ? { id, status: "pass", message: `${id} \u5B58\u5728` } : { id, status: "warn", message: missing, suggestedCommand };
|
|
328
|
+
}
|
|
329
|
+
async function checkPlaceholders(ctx) {
|
|
330
|
+
try {
|
|
331
|
+
await readFile4(join6(ctx.projectRoot, "AGENTS.md"), "utf8");
|
|
332
|
+
const count2 = await countAgentsLlmPlaceholders(ctx.projectRoot);
|
|
333
|
+
return count2 ? {
|
|
334
|
+
id: "context-placeholders",
|
|
335
|
+
status: "warn",
|
|
336
|
+
message: ctx.language === "en" ? `AGENTS.md has ${count2} LLM placeholder(s)` : `AGENTS.md \u4ECD\u6709 ${count2} \u4E2A LLM \u5360\u4F4D\u7B26`,
|
|
337
|
+
suggestedCommand: "fet fill-context"
|
|
338
|
+
} : {
|
|
339
|
+
id: "context-placeholders",
|
|
340
|
+
status: "pass",
|
|
341
|
+
message: ctx.language === "en" ? "AGENTS.md placeholders resolved" : "AGENTS.md \u5360\u4F4D\u7B26\u5DF2\u5904\u7406"
|
|
342
|
+
};
|
|
343
|
+
} catch {
|
|
344
|
+
return {
|
|
345
|
+
id: "context-placeholders",
|
|
346
|
+
status: "warn",
|
|
347
|
+
message: ctx.language === "en" ? "AGENTS.md missing" : "AGENTS.md \u7F3A\u5931",
|
|
348
|
+
suggestedCommand: "fet update-context"
|
|
349
|
+
};
|
|
322
350
|
}
|
|
323
|
-
return content.slice(start + AUTO_BEGIN.length, end).trim();
|
|
324
351
|
}
|
|
325
|
-
function
|
|
326
|
-
|
|
352
|
+
async function exists(path) {
|
|
353
|
+
try {
|
|
354
|
+
await stat3(path);
|
|
355
|
+
return true;
|
|
356
|
+
} catch {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
327
359
|
}
|
|
328
360
|
|
|
329
|
-
// src/
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
361
|
+
// src/commands/fill-context.ts
|
|
362
|
+
import { mkdir as mkdir3 } from "fs/promises";
|
|
363
|
+
import { dirname as dirname4, join as join7 } from "path";
|
|
364
|
+
async function fillContextCommand(ctx) {
|
|
365
|
+
await withProjectLock(ctx.projectRoot, { command: "fill-context", cwd: ctx.cwd, fetVersion: ctx.fetVersion }, async () => {
|
|
366
|
+
const handoffPath = join7(ctx.projectRoot, ".fet", "fill-context.md");
|
|
367
|
+
await mkdir3(dirname4(handoffPath), { recursive: true });
|
|
368
|
+
await atomicWrite(handoffPath, renderGenericHandoff(ctx.language));
|
|
369
|
+
for (const adapter of ctx.toolAdapters) {
|
|
370
|
+
const plan = await adapter.planInstall(ctx.projectRoot, ctx.language);
|
|
371
|
+
const result = await adapter.install(ctx.projectRoot, plan, ctx.yes);
|
|
372
|
+
const state = await ctx.stateStore.getOrCreateGlobal();
|
|
373
|
+
state.toolAdapters[adapter.tool] = {
|
|
374
|
+
adapterVersion: adapter.adapterVersion,
|
|
375
|
+
installed: true,
|
|
376
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
377
|
+
};
|
|
378
|
+
await ctx.stateStore.writeGlobal(state);
|
|
379
|
+
if (ctx.verbose) {
|
|
380
|
+
ctx.output.info(ctx.language === "en" ? `Updated ${adapter.tool} adapter` : `\u5DF2\u66F4\u65B0 ${adapter.tool} \u9002\u914D\u5668`, { written: result.written });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
const placeholders = await countAgentsLlmPlaceholders(ctx.projectRoot);
|
|
385
|
+
ctx.output.result({
|
|
386
|
+
ok: true,
|
|
387
|
+
command: "fill-context",
|
|
388
|
+
summary: ctx.language === "en" ? placeholders ? `Found ${placeholders} AGENTS.md placeholder(s). Use your IDE AI to fill them.` : "No AGENTS.md placeholders found. IDE fill-context commands were refreshed." : placeholders ? `\u53D1\u73B0 ${placeholders} \u4E2A AGENTS.md \u5360\u4F4D\u7B26\u3002\u8BF7\u4F7F\u7528 IDE AI \u8865\u9F50\u3002` : "\u672A\u53D1\u73B0 AGENTS.md \u5360\u4F4D\u7B26\uFF0C\u5DF2\u5237\u65B0 IDE fill-context \u547D\u4EE4\u3002",
|
|
389
|
+
nextSteps: ctx.language === "en" ? placeholders ? [
|
|
390
|
+
"Cursor: run /fet-fill-context",
|
|
391
|
+
"Codex: run /prompts:fet-fill-context",
|
|
392
|
+
"OpenCode or other IDEs: open .fet/fill-context.md or run fet fill-context for handoff instructions"
|
|
393
|
+
] : ["Run fet doctor to confirm project context health"] : placeholders ? [
|
|
394
|
+
"Cursor\uFF1A\u8FD0\u884C /fet-fill-context",
|
|
395
|
+
"Codex\uFF1A\u8FD0\u884C /prompts:fet-fill-context",
|
|
396
|
+
"OpenCode \u6216\u5176\u4ED6 IDE\uFF1A\u6253\u5F00 .fet/fill-context.md\uFF0C\u6216\u8FD0\u884C fet fill-context \u67E5\u770B\u4EA4\u63A5\u8BF4\u660E"
|
|
397
|
+
] : ["\u8FD0\u884C fet doctor \u786E\u8BA4\u9879\u76EE\u4E0A\u4E0B\u6587\u72B6\u6001"],
|
|
398
|
+
data: {
|
|
399
|
+
placeholders,
|
|
400
|
+
cursorCommand: "/fet-fill-context",
|
|
401
|
+
codexCommand: "/prompts:fet-fill-context"
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
function renderGenericHandoff(language) {
|
|
406
|
+
if (language === "en") {
|
|
407
|
+
return `<!-- FET:MANAGED
|
|
408
|
+
schemaVersion: 1
|
|
409
|
+
generator: fill-context
|
|
410
|
+
FET:END -->
|
|
362
411
|
|
|
363
|
-
|
|
364
|
-
|-------|--------|------------|
|
|
365
|
-
${routes || "| [NEEDS LLM INPUT] | [NEEDS LLM INPUT] | low |"}
|
|
412
|
+
# FET Fill Context
|
|
366
413
|
|
|
367
|
-
|
|
414
|
+
Use the IDE AI to complete FET-generated placeholders.
|
|
368
415
|
|
|
369
|
-
|
|
416
|
+
1. Read AGENTS.md and openspec/config.yaml.
|
|
417
|
+
2. Read .fet/karpathy-guidelines.md when it exists. For Codex, also read .codex/fet/karpathy-guidelines.md when it exists.
|
|
418
|
+
3. Inspect README files, package scripts, routes, tests, source layout, and project conventions.
|
|
419
|
+
4. Replace every \`[NEEDS LLM INPUT]\` or \`[NEED LLM INPUT]\` placeholder in AGENTS.md with concrete project-specific content.
|
|
420
|
+
5. Preserve FET managed markers.
|
|
421
|
+
6. Do not modify business code.
|
|
422
|
+
7. Run \`fet doctor\` and confirm no AGENTS.md placeholder warning remains.
|
|
423
|
+
`;
|
|
424
|
+
}
|
|
425
|
+
return `<!-- FET:MANAGED
|
|
426
|
+
schemaVersion: 1
|
|
427
|
+
generator: fill-context
|
|
428
|
+
FET:END -->
|
|
370
429
|
|
|
371
|
-
|
|
430
|
+
# FET \u586B\u5145\u4E0A\u4E0B\u6587
|
|
372
431
|
|
|
373
|
-
|
|
374
|
-
- For Codex, also read .codex/fet/karpathy-guidelines.md when present.
|
|
375
|
-
- Treat those guidelines as secondary to the user's latest request and explicit OpenSpec artifacts.
|
|
432
|
+
\u4F7F\u7528 IDE AI \u8865\u9F50 FET \u751F\u6210\u7684\u9879\u76EE\u4E0A\u4E0B\u6587\u5360\u4F4D\u7B26\u3002
|
|
376
433
|
|
|
377
|
-
|
|
434
|
+
1. \u9605\u8BFB AGENTS.md \u548C openspec/config.yaml\u3002
|
|
435
|
+
2. \u5982\u679C\u5B58\u5728 .fet/karpathy-guidelines.md\uFF0C\u8BF7\u4E00\u5E76\u9605\u8BFB\u3002\u5BF9 Codex\uFF0C\u5982\u679C\u5B58\u5728 .codex/fet/karpathy-guidelines.md\uFF0C\u4E5F\u8981\u9605\u8BFB\u3002
|
|
436
|
+
3. \u68C0\u67E5 README\u3001package scripts\u3001\u8DEF\u7531\u3001\u6D4B\u8BD5\u3001\u6E90\u7801\u7ED3\u6784\u548C\u9879\u76EE\u7EA6\u5B9A\u3002
|
|
437
|
+
4. \u5C06 AGENTS.md \u4E2D\u6BCF\u4E2A \`[NEEDS LLM INPUT]\` \u6216 \`[NEED LLM INPUT]\` \u5360\u4F4D\u7B26\u66FF\u6362\u4E3A\u5177\u4F53\u3001\u9879\u76EE\u76F8\u5173\u7684\u5185\u5BB9\u3002
|
|
438
|
+
5. \u4FDD\u7559 FET \u6258\u7BA1\u6807\u8BB0\u3002
|
|
439
|
+
6. \u4E0D\u8981\u4FEE\u6539\u4E1A\u52A1\u4EE3\u7801\u3002
|
|
440
|
+
7. \u8FD0\u884C \`fet doctor\`\uFF0C\u786E\u8BA4\u4E0D\u518D\u6709 AGENTS.md \u5360\u4F4D\u7B26\u8B66\u544A\u3002
|
|
441
|
+
`;
|
|
442
|
+
}
|
|
378
443
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
444
|
+
// src/commands/graph.ts
|
|
445
|
+
import { mkdir as mkdir4 } from "fs/promises";
|
|
446
|
+
import { dirname as dirname5, join as join8 } from "path";
|
|
447
|
+
async function graphCommand(ctx, action, args = []) {
|
|
448
|
+
switch (action) {
|
|
449
|
+
case "status":
|
|
450
|
+
await graphStatusCommand(ctx);
|
|
451
|
+
return;
|
|
452
|
+
case "doctor":
|
|
453
|
+
await graphDoctorCommand(ctx);
|
|
454
|
+
return;
|
|
455
|
+
case "setup":
|
|
456
|
+
await graphSetupCommand(ctx);
|
|
457
|
+
return;
|
|
458
|
+
case "handoff":
|
|
459
|
+
await graphHandoffCommand(ctx);
|
|
460
|
+
return;
|
|
461
|
+
case "init":
|
|
462
|
+
await graphAnalyzeCommand(ctx, "init", args);
|
|
463
|
+
return;
|
|
464
|
+
case "refresh":
|
|
465
|
+
await graphAnalyzeCommand(ctx, "refresh", args);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
391
468
|
}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
469
|
+
async function graphStatusCommand(ctx) {
|
|
470
|
+
const result = await refreshGraphState(ctx, { runStatus: true });
|
|
471
|
+
const warnings = result.state.installed ? [] : [
|
|
472
|
+
ctx.language === "en" ? "GitNexus is not installed. Run fet graph setup for installation handoff instructions." : "\u5C1A\u672A\u5B89\u88C5 GitNexus\u3002\u8FD0\u884C fet graph setup \u83B7\u53D6\u5B89\u88C5\u4EA4\u63A5\u8BF4\u660E\u3002"
|
|
473
|
+
];
|
|
474
|
+
ctx.output.result({
|
|
475
|
+
ok: true,
|
|
476
|
+
command: "graph status",
|
|
477
|
+
summary: ctx.language === "en" ? result.state.installed ? `GitNexus graph status checked. Graph ${result.state.graphExists ? "exists" : "does not exist"} at ${result.state.graphPath ?? ".gitnexus"}.` : "GitNexus is not installed. Graph support remains optional." : result.state.installed ? `\u5DF2\u68C0\u67E5 GitNexus \u4EE3\u7801\u56FE\u72B6\u6001\u3002\u4EE3\u7801\u56FE${result.state.graphExists ? "\u5B58\u5728" : "\u4E0D\u5B58\u5728"}\uFF0C\u8DEF\u5F84\u4E3A ${result.state.graphPath ?? ".gitnexus"}\u3002` : "\u5C1A\u672A\u5B89\u88C5 GitNexus\u3002\u4EE3\u7801\u56FE\u80FD\u529B\u4FDD\u6301\u53EF\u9009\u3002",
|
|
478
|
+
warnings,
|
|
479
|
+
nextSteps: result.state.installed && !result.state.graphExists ? [ctx.language === "en" ? "Run fet graph init to build the first GitNexus graph" : "\u8FD0\u884C fet graph init \u751F\u6210\u7B2C\u4E00\u4EFD GitNexus \u4EE3\u7801\u56FE"] : void 0,
|
|
480
|
+
data: result
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
async function graphDoctorCommand(ctx) {
|
|
484
|
+
const result = await refreshGraphState(ctx, { runStatus: true });
|
|
485
|
+
const warnings = [
|
|
486
|
+
...!result.state.installed ? [ctx.language === "en" ? "GitNexus is not installed." : "\u5C1A\u672A\u5B89\u88C5 GitNexus\u3002"] : [],
|
|
487
|
+
...result.state.installed && !result.state.graphExists ? [ctx.language === "en" ? "GitNexus is installed but no graph directory was found." : "\u5DF2\u5B89\u88C5 GitNexus\uFF0C\u4F46\u672A\u53D1\u73B0\u4EE3\u7801\u56FE\u76EE\u5F55\u3002"] : [],
|
|
488
|
+
...!result.state.handoffPath ? [ctx.language === "en" ? "Graph handoff instructions have not been generated." : "\u5C1A\u672A\u751F\u6210\u4EE3\u7801\u56FE\u4F7F\u7528\u4EA4\u63A5\u8BF4\u660E\u3002"] : []
|
|
489
|
+
];
|
|
490
|
+
ctx.output.result({
|
|
491
|
+
ok: true,
|
|
492
|
+
command: "graph doctor",
|
|
493
|
+
summary: ctx.language === "en" ? warnings.length ? `Graph doctor completed with ${warnings.length} warning(s).` : "Graph doctor completed without warnings." : warnings.length ? `\u4EE3\u7801\u56FE\u8BCA\u65AD\u5B8C\u6210\uFF0C\u53D1\u73B0 ${warnings.length} \u4E2A\u8B66\u544A\u3002` : "\u4EE3\u7801\u56FE\u8BCA\u65AD\u5B8C\u6210\uFF0C\u672A\u53D1\u73B0\u8B66\u544A\u3002",
|
|
494
|
+
warnings,
|
|
495
|
+
nextSteps: warnings.length ? ctx.language === "en" ? ["Run fet graph setup", "Run fet graph handoff", "Run fet graph init when GitNexus is installed"] : ["\u8FD0\u884C fet graph setup", "\u8FD0\u884C fet graph handoff", "\u5B89\u88C5 GitNexus \u540E\u8FD0\u884C fet graph init"] : void 0,
|
|
496
|
+
data: result
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
async function graphSetupCommand(ctx) {
|
|
500
|
+
let result;
|
|
501
|
+
const handoffPath = join8(ctx.projectRoot, ".fet", "graph-setup.md");
|
|
502
|
+
const installCommand = process.env.FET_GITNEXUS_INSTALL_COMMAND?.trim() || null;
|
|
503
|
+
await withProjectLock(ctx.projectRoot, { command: "graph setup", cwd: ctx.cwd, fetVersion: ctx.fetVersion }, async () => {
|
|
504
|
+
result = await refreshGraphState(ctx, { write: false });
|
|
505
|
+
await writeHandoffFile(handoffPath, renderGraphSetupHandoff(result.state, { installCommand, language: ctx.language }));
|
|
506
|
+
const global = await ctx.stateStore.getOrCreateGlobal();
|
|
507
|
+
global.graph ??= {};
|
|
508
|
+
global.graph.gitnexus = {
|
|
509
|
+
...result.state,
|
|
510
|
+
setupHandoffPath: ".fet/graph-setup.md",
|
|
511
|
+
setupHandoffUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
512
|
+
};
|
|
513
|
+
await ctx.stateStore.writeGlobal(global);
|
|
514
|
+
});
|
|
515
|
+
ctx.output.result({
|
|
516
|
+
ok: true,
|
|
517
|
+
command: "graph setup",
|
|
518
|
+
summary: ctx.language === "en" ? "GitNexus IDE-assisted setup playbook generated." : "\u5DF2\u751F\u6210 GitNexus IDE LLM \u8F85\u52A9\u5B89\u88C5\u4EFB\u52A1\u4E66\u3002",
|
|
519
|
+
warnings: result.state.installed ? [] : [
|
|
520
|
+
ctx.language === "en" ? "GitNexus is not installed. The playbook lets the current IDE LLM guide installation with user confirmation before risky commands." : "\u5C1A\u672A\u5B89\u88C5 GitNexus\u3002\u4EFB\u52A1\u4E66\u4F1A\u6307\u5BFC\u5F53\u524D IDE LLM \u63A8\u8FDB\u5B89\u88C5\uFF0C\u5E76\u5728\u9AD8\u98CE\u9669\u547D\u4EE4\u524D\u8BF7\u6C42\u7528\u6237\u786E\u8BA4\u3002"
|
|
521
|
+
],
|
|
522
|
+
nextSteps: result.state.installed ? [
|
|
523
|
+
ctx.language === "en" ? "Ask your IDE AI to read .fet/graph-setup.md and run the optional gitnexus setup flow if you want IDE/MCP integration" : "\u8BA9\u5F53\u524D IDE AI \u9605\u8BFB .fet/graph-setup.md\uFF0C\u5E76\u5728\u9700\u8981 IDE/MCP \u96C6\u6210\u65F6\u6309\u786E\u8BA4\u6D41\u7A0B\u8FD0\u884C gitnexus setup",
|
|
524
|
+
"fet graph init"
|
|
525
|
+
] : [
|
|
526
|
+
ctx.language === "en" ? "Ask your current IDE AI to read .fet/graph-setup.md and follow the installation playbook" : "\u8BA9\u5F53\u524D IDE AI \u9605\u8BFB .fet/graph-setup.md\uFF0C\u5E76\u6309\u5B89\u88C5\u4EFB\u52A1\u4E66\u63A8\u8FDB"
|
|
527
|
+
],
|
|
528
|
+
data: {
|
|
529
|
+
path: ".fet/graph-setup.md",
|
|
530
|
+
installCommandConfigured: Boolean(installCommand),
|
|
531
|
+
gitnexus: result.state
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
async function graphHandoffCommand(ctx) {
|
|
536
|
+
let result;
|
|
537
|
+
const handoffPath = join8(ctx.projectRoot, ".fet", "graph-handoff.md");
|
|
538
|
+
await withProjectLock(ctx.projectRoot, { command: "graph handoff", cwd: ctx.cwd, fetVersion: ctx.fetVersion }, async () => {
|
|
539
|
+
result = await refreshGraphState(ctx, { runStatus: true, write: false });
|
|
540
|
+
await writeHandoffFile(handoffPath, renderGraphUsageHandoff(result.state, ctx.language));
|
|
541
|
+
const global = await ctx.stateStore.getOrCreateGlobal();
|
|
542
|
+
global.graph ??= {};
|
|
543
|
+
global.graph.gitnexus = {
|
|
544
|
+
...result.state,
|
|
545
|
+
handoffPath: ".fet/graph-handoff.md",
|
|
546
|
+
handoffUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
547
|
+
};
|
|
548
|
+
await ctx.stateStore.writeGlobal(global);
|
|
549
|
+
});
|
|
550
|
+
ctx.output.result({
|
|
551
|
+
ok: true,
|
|
552
|
+
command: "graph handoff",
|
|
553
|
+
summary: ctx.language === "en" ? "GitNexus graph usage handoff generated." : "\u5DF2\u751F\u6210 GitNexus \u4EE3\u7801\u56FE\u4F7F\u7528\u4EA4\u63A5\u8BF4\u660E\u3002",
|
|
554
|
+
warnings: result.state.installed ? [] : [
|
|
555
|
+
ctx.language === "en" ? "GitNexus is not installed. The handoff still documents the fallback behavior." : "\u5C1A\u672A\u5B89\u88C5 GitNexus\u3002\u4EA4\u63A5\u8BF4\u660E\u4ECD\u4F1A\u8BB0\u5F55\u56DE\u9000\u884C\u4E3A\u3002"
|
|
556
|
+
],
|
|
557
|
+
nextSteps: ctx.language === "en" ? ["Cursor/Codex/OpenCode: read .fet/graph-handoff.md before broad repository scans"] : ["Cursor/Codex/OpenCode\uFF1A\u5927\u8303\u56F4\u626B\u63CF\u4ED3\u5E93\u524D\u5148\u9605\u8BFB .fet/graph-handoff.md"],
|
|
558
|
+
data: {
|
|
559
|
+
path: ".fet/graph-handoff.md",
|
|
560
|
+
gitnexus: result.state
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
async function graphAnalyzeCommand(ctx, mode, args) {
|
|
565
|
+
const detection = await detectGitNexus();
|
|
566
|
+
if (!detection.installed) {
|
|
567
|
+
throw new FetError({
|
|
568
|
+
code: "GRAPH_PROVIDER_NOT_FOUND" /* GraphProviderNotFound */,
|
|
569
|
+
message: ctx.language === "en" ? "GitNexus is not installed or is not available on PATH." : "\u5C1A\u672A\u5B89\u88C5 GitNexus\uFF0C\u6216 GitNexus \u4E0D\u5728 PATH \u4E2D\u3002",
|
|
570
|
+
details: { executable: detection.executablePath, error: detection.error },
|
|
571
|
+
suggestedCommand: "fet graph setup"
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
const run = await runGitNexus(["analyze", ...args], { cwd: ctx.projectRoot });
|
|
575
|
+
if (run.exitCode !== 0) {
|
|
576
|
+
throw new FetError({
|
|
577
|
+
code: "GRAPH_COMMAND_FAILED" /* GraphCommandFailed */,
|
|
578
|
+
message: ctx.language === "en" ? "GitNexus analyze failed." : "GitNexus analyze \u6267\u884C\u5931\u8D25\u3002",
|
|
579
|
+
details: { command: run.command.join(" "), exitCode: run.exitCode, stdout: run.stdout, stderr: run.stderr },
|
|
580
|
+
suggestedCommand: "fet graph doctor"
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
const result = await refreshGraphState(ctx, { write: false });
|
|
584
|
+
const global = await ctx.stateStore.getOrCreateGlobal();
|
|
585
|
+
global.graph ??= {};
|
|
586
|
+
global.graph.gitnexus = {
|
|
587
|
+
...result.state,
|
|
588
|
+
lastRefreshAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
589
|
+
};
|
|
590
|
+
await ctx.stateStore.writeGlobal(global);
|
|
591
|
+
ctx.output.result({
|
|
592
|
+
ok: true,
|
|
593
|
+
command: `graph ${mode}`,
|
|
594
|
+
summary: ctx.language === "en" ? mode === "init" ? "GitNexus graph initialized." : "GitNexus graph refreshed." : mode === "init" ? "\u5DF2\u521D\u59CB\u5316 GitNexus \u4EE3\u7801\u56FE\u3002" : "\u5DF2\u5237\u65B0 GitNexus \u4EE3\u7801\u56FE\u3002",
|
|
595
|
+
warnings: result.state.graphExists ? [] : [
|
|
596
|
+
ctx.language === "en" ? "GitNexus analyze completed, but the configured graph directory was not found." : "GitNexus analyze \u5DF2\u5B8C\u6210\uFF0C\u4F46\u672A\u53D1\u73B0\u914D\u7F6E\u7684\u4EE3\u7801\u56FE\u76EE\u5F55\u3002"
|
|
597
|
+
],
|
|
598
|
+
nextSteps: ctx.language === "en" ? ["Run fet graph status", "Use .fet/graph-handoff.md or generated IDE prompts to prefer graph context"] : ["\u8FD0\u884C fet graph status", "\u4F7F\u7528 .fet/graph-handoff.md \u6216\u751F\u6210\u7684 IDE \u63D0\u793A\uFF0C\u4F18\u5148\u53C2\u8003\u4EE3\u7801\u56FE\u4E0A\u4E0B\u6587"],
|
|
599
|
+
data: {
|
|
600
|
+
gitnexus: global.graph.gitnexus,
|
|
601
|
+
run: {
|
|
602
|
+
command: run.command,
|
|
603
|
+
stdout: run.stdout.trim(),
|
|
604
|
+
stderr: run.stderr.trim()
|
|
419
605
|
}
|
|
420
606
|
}
|
|
421
607
|
});
|
|
422
608
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
if (
|
|
431
|
-
|
|
432
|
-
|
|
609
|
+
async function refreshGraphState(ctx, options = {}) {
|
|
610
|
+
const global = await ctx.stateStore.getOrCreateGlobal();
|
|
611
|
+
global.graph ??= {};
|
|
612
|
+
const detection = await detectGitNexus();
|
|
613
|
+
const graph2 = await inspectGitNexusGraph(ctx.projectRoot);
|
|
614
|
+
let state = mergeGitNexusGraphInfo(toGitNexusState(detection, global.graph.gitnexus), graph2);
|
|
615
|
+
let gitnexusStatus = null;
|
|
616
|
+
if (options.runStatus && detection.installed) {
|
|
617
|
+
gitnexusStatus = await runGitNexus(["status"], { cwd: ctx.projectRoot });
|
|
618
|
+
state = {
|
|
619
|
+
...state,
|
|
620
|
+
lastStatus: firstLine(gitnexusStatus.stdout) || firstLine(gitnexusStatus.stderr) || `exit ${gitnexusStatus.exitCode}`
|
|
621
|
+
};
|
|
433
622
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
return `${existing.slice(0, start)}${block}${existing.slice(end + END.length)}`;
|
|
623
|
+
if (options.write ?? true) {
|
|
624
|
+
global.graph.gitnexus = state;
|
|
625
|
+
await ctx.stateStore.writeGlobal(global);
|
|
438
626
|
}
|
|
439
|
-
return
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
627
|
+
return {
|
|
628
|
+
state,
|
|
629
|
+
gitnexusStatus: gitnexusStatus ? {
|
|
630
|
+
exitCode: gitnexusStatus.exitCode,
|
|
631
|
+
command: gitnexusStatus.command,
|
|
632
|
+
stdout: gitnexusStatus.stdout.trim(),
|
|
633
|
+
stderr: gitnexusStatus.stderr.trim()
|
|
634
|
+
} : null
|
|
635
|
+
};
|
|
443
636
|
}
|
|
444
|
-
function
|
|
445
|
-
|
|
637
|
+
async function writeHandoffFile(path, content) {
|
|
638
|
+
await mkdir4(dirname5(path), { recursive: true });
|
|
639
|
+
await atomicWrite(path, content);
|
|
640
|
+
}
|
|
641
|
+
function renderGraphSetupHandoff(state, options) {
|
|
642
|
+
if (options.language === "en") {
|
|
643
|
+
return `<!-- FET:MANAGED
|
|
446
644
|
schemaVersion: 1
|
|
447
|
-
generator:
|
|
645
|
+
generator: graph-setup
|
|
448
646
|
FET:END -->
|
|
449
647
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
648
|
+
# FET GitNexus IDE Setup Playbook
|
|
649
|
+
|
|
650
|
+
This file is written for the current IDE LLM. Use it to help the user install and verify optional GitNexus graph support. GitNexus is optional; FET/OpenSpec workflows must continue to work when it is unavailable.
|
|
651
|
+
|
|
652
|
+
Current status:
|
|
653
|
+
|
|
654
|
+
- Installed: ${state.installed ? "yes" : "no"}
|
|
655
|
+
- Executable: ${state.executablePath ?? "gitnexus"}
|
|
656
|
+
- Version: ${state.version ?? "unknown"}
|
|
657
|
+
- Graph path: ${state.graphPath ?? ".gitnexus"}
|
|
658
|
+
- Graph exists: ${state.graphExists ? "yes" : "no"}
|
|
659
|
+
- Configured install command: ${options.installCommand ?? "none"}
|
|
660
|
+
|
|
661
|
+
IDE LLM setup flow:
|
|
454
662
|
|
|
455
|
-
|
|
663
|
+
1. Check the shell, operating system, package managers, and PATH. Run only read-only detection commands at first.
|
|
664
|
+
2. Run \`gitnexus --version\` or the executable shown above. If it succeeds, skip installation and continue to verification.
|
|
665
|
+
3. If GitNexus is missing and \`FET_GITNEXUS_INSTALL_COMMAND\` is configured, explain that command to the user and ask for approval before running it.
|
|
666
|
+
4. If no install command is configured, find the official GitNexus installation instructions or ask the user for the preferred install method. Do not invent an installer.
|
|
667
|
+
5. Before running any command that downloads software, installs globally, changes PATH, modifies user home files, or changes IDE/MCP configuration, show the exact command and wait for user approval.
|
|
668
|
+
6. After installation, verify with \`gitnexus --version\`.
|
|
669
|
+
7. If the user wants IDE/MCP integration, run \`gitnexus setup\` only after explaining what it may change and receiving approval.
|
|
670
|
+
8. Return to this project and run \`fet graph init\` to build the first graph.
|
|
671
|
+
9. Run \`fet graph handoff\` so future IDE AI work can prefer graph context before broad repository scans.
|
|
672
|
+
|
|
673
|
+
Guardrails:
|
|
674
|
+
|
|
675
|
+
- Do not block FET/OpenSpec commands when GitNexus is unavailable.
|
|
676
|
+
- Do not generate or modify application code during setup.
|
|
677
|
+
- Do not silently install software or modify global/user-level configuration.
|
|
678
|
+
- If installation fails, summarize the failing command, stderr, and the next manual step.
|
|
456
679
|
`;
|
|
457
|
-
}
|
|
458
|
-
function renderKarpathyFetHandoff() {
|
|
680
|
+
}
|
|
459
681
|
return `<!-- FET:MANAGED
|
|
460
682
|
schemaVersion: 1
|
|
461
|
-
generator:
|
|
683
|
+
generator: graph-setup
|
|
462
684
|
FET:END -->
|
|
463
685
|
|
|
464
|
-
#
|
|
686
|
+
# FET GitNexus IDE \u5B89\u88C5\u4EFB\u52A1\u4E66
|
|
465
687
|
|
|
466
|
-
|
|
467
|
-
`;
|
|
468
|
-
}
|
|
469
|
-
function renderManagedBlock(content) {
|
|
470
|
-
return `${BEGIN}
|
|
471
|
-
${content}
|
|
472
|
-
${END}`;
|
|
473
|
-
}
|
|
474
|
-
function renderKarpathyClaudeGuidelines() {
|
|
475
|
-
return `# Andrej Karpathy Inspired Coding Guidelines
|
|
688
|
+
\u672C\u6587\u6863\u5199\u7ED9\u5F53\u524D IDE LLM\u3002\u8BF7\u7528\u5B83\u5E2E\u52A9\u7528\u6237\u5B89\u88C5\u5E76\u9A8C\u8BC1\u53EF\u9009\u7684 GitNexus \u4EE3\u7801\u56FE\u80FD\u529B\u3002GitNexus \u662F\u53EF\u9009\u80FD\u529B\uFF1B\u5373\u4F7F\u4E0D\u53EF\u7528\uFF0CFET/OpenSpec \u4E3B\u6D41\u7A0B\u4E5F\u5FC5\u987B\u7EE7\u7EED\u53EF\u7528\u3002
|
|
476
689
|
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
function renderKarpathyGuidelinesBody() {
|
|
480
|
-
return `Source: ${KARPATHY_SKILLS_SOURCE}
|
|
690
|
+
\u5F53\u524D\u72B6\u6001\uFF1A
|
|
481
691
|
|
|
482
|
-
|
|
692
|
+
- \u5DF2\u5B89\u88C5\uFF1A${state.installed ? "\u662F" : "\u5426"}
|
|
693
|
+
- \u53EF\u6267\u884C\u6587\u4EF6\uFF1A${state.executablePath ?? "gitnexus"}
|
|
694
|
+
- \u7248\u672C\uFF1A${state.version ?? "unknown"}
|
|
695
|
+
- \u56FE\u8DEF\u5F84\uFF1A${state.graphPath ?? ".gitnexus"}
|
|
696
|
+
- \u56FE\u5DF2\u5B58\u5728\uFF1A${state.graphExists ? "\u662F" : "\u5426"}
|
|
697
|
+
- \u5DF2\u914D\u7F6E\u5B89\u88C5\u547D\u4EE4\uFF1A${options.installCommand ?? "\u65E0"}
|
|
483
698
|
|
|
484
|
-
|
|
699
|
+
IDE LLM \u5B89\u88C5\u6D41\u7A0B\uFF1A
|
|
485
700
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
701
|
+
1. \u5148\u68C0\u6D4B shell\u3001\u64CD\u4F5C\u7CFB\u7EDF\u3001\u5305\u7BA1\u7406\u5668\u548C PATH\u3002\u8D77\u6B65\u9636\u6BB5\u53EA\u8FD0\u884C\u53EA\u8BFB\u68C0\u6D4B\u547D\u4EE4\u3002
|
|
702
|
+
2. \u8FD0\u884C \`gitnexus --version\` \u6216\u4E0A\u65B9\u663E\u793A\u7684\u53EF\u6267\u884C\u6587\u4EF6\u3002\u5982\u679C\u6210\u529F\uFF0C\u8DF3\u8FC7\u5B89\u88C5\u5E76\u8FDB\u5165\u9A8C\u8BC1\u3002
|
|
703
|
+
3. \u5982\u679C GitNexus \u7F3A\u5931\u4E14\u5DF2\u914D\u7F6E \`FET_GITNEXUS_INSTALL_COMMAND\`\uFF0C\u5148\u5411\u7528\u6237\u89E3\u91CA\u8BE5\u547D\u4EE4\uFF0C\u518D\u7B49\u5F85\u7528\u6237\u6279\u51C6\u540E\u6267\u884C\u3002
|
|
704
|
+
4. \u5982\u679C\u6CA1\u6709\u914D\u7F6E\u5B89\u88C5\u547D\u4EE4\uFF0C\u67E5\u627E GitNexus \u5B98\u65B9\u5B89\u88C5\u8BF4\u660E\uFF0C\u6216\u8BE2\u95EE\u7528\u6237\u5E0C\u671B\u4F7F\u7528\u7684\u5B89\u88C5\u65B9\u5F0F\u3002\u4E0D\u8981\u81C6\u9020\u5B89\u88C5\u547D\u4EE4\u3002
|
|
705
|
+
5. \u4EFB\u4F55\u4F1A\u4E0B\u8F7D\u8F6F\u4EF6\u3001\u5168\u5C40\u5B89\u88C5\u3001\u4FEE\u6539 PATH\u3001\u5199\u5165\u7528\u6237\u76EE\u5F55\u6216\u4FEE\u6539 IDE/MCP \u914D\u7F6E\u7684\u547D\u4EE4\uFF0C\u6267\u884C\u524D\u90FD\u8981\u5C55\u793A\u5B8C\u6574\u547D\u4EE4\u5E76\u7B49\u5F85\u7528\u6237\u786E\u8BA4\u3002
|
|
706
|
+
6. \u5B89\u88C5\u540E\u8FD0\u884C \`gitnexus --version\` \u9A8C\u8BC1\u3002
|
|
707
|
+
7. \u5982\u679C\u7528\u6237\u9700\u8981 IDE/MCP \u96C6\u6210\uFF0C\u5148\u8BF4\u660E \`gitnexus setup\` \u53EF\u80FD\u4FEE\u6539\u7684\u5185\u5BB9\uFF0C\u83B7\u5F97\u786E\u8BA4\u540E\u518D\u8FD0\u884C\u3002
|
|
708
|
+
8. \u56DE\u5230\u672C\u9879\u76EE\u8FD0\u884C \`fet graph init\`\uFF0C\u751F\u6210\u7B2C\u4E00\u4EFD\u4EE3\u7801\u56FE\u3002
|
|
709
|
+
9. \u8FD0\u884C \`fet graph handoff\`\uFF0C\u8BA9\u540E\u7EED IDE AI \u5728\u5927\u8303\u56F4\u626B\u63CF\u524D\u4F18\u5148\u4F7F\u7528\u4EE3\u7801\u56FE\u4E0A\u4E0B\u6587\u3002
|
|
490
710
|
|
|
491
|
-
|
|
711
|
+
\u7EA6\u675F\uFF1A
|
|
492
712
|
|
|
493
|
-
-
|
|
494
|
-
-
|
|
495
|
-
-
|
|
496
|
-
-
|
|
713
|
+
- GitNexus \u4E0D\u53EF\u7528\u65F6\uFF0C\u4E0D\u8981\u963B\u585E FET/OpenSpec \u547D\u4EE4\u3002
|
|
714
|
+
- \u5B89\u88C5\u8FC7\u7A0B\u4E2D\u4E0D\u8981\u751F\u6210\u6216\u4FEE\u6539\u4E1A\u52A1\u4EE3\u7801\u3002
|
|
715
|
+
- \u4E0D\u8981\u9759\u9ED8\u5B89\u88C5\u8F6F\u4EF6\uFF0C\u4E5F\u4E0D\u8981\u9759\u9ED8\u4FEE\u6539\u5168\u5C40\u6216\u7528\u6237\u7EA7\u914D\u7F6E\u3002
|
|
716
|
+
- \u5982\u679C\u5B89\u88C5\u5931\u8D25\uFF0C\u6C47\u603B\u5931\u8D25\u547D\u4EE4\u3001stderr \u548C\u4E0B\u4E00\u6B65\u4EBA\u5DE5\u5904\u7406\u5EFA\u8BAE\u3002
|
|
717
|
+
`;
|
|
718
|
+
}
|
|
719
|
+
function renderGraphUsageHandoff(state, language) {
|
|
720
|
+
if (language === "en") {
|
|
721
|
+
return `<!-- FET:MANAGED
|
|
722
|
+
schemaVersion: 1
|
|
723
|
+
generator: graph-handoff
|
|
724
|
+
FET:END -->
|
|
497
725
|
|
|
498
|
-
|
|
726
|
+
# FET Graph Handoff
|
|
499
727
|
|
|
500
|
-
|
|
501
|
-
- Preserve existing style even when you personally prefer another pattern.
|
|
502
|
-
- Do not refactor nearby code, comments, or formatting unless the task requires it.
|
|
503
|
-
- Remove only dead imports, variables, or helpers made obsolete by your own change.
|
|
728
|
+
Use GitNexus graph context as an optional first pass before broad repository scans.
|
|
504
729
|
|
|
505
|
-
|
|
730
|
+
Current status:
|
|
506
731
|
|
|
507
|
-
-
|
|
508
|
-
-
|
|
509
|
-
-
|
|
510
|
-
-
|
|
732
|
+
- Installed: ${state.installed ? "yes" : "no"}
|
|
733
|
+
- Graph path: ${state.graphPath ?? ".gitnexus"}
|
|
734
|
+
- Graph exists: ${state.graphExists ? "yes" : "no"}
|
|
735
|
+
- Last indexed at: ${state.lastIndexedAt ?? "unknown"}
|
|
736
|
+
- Last status: ${state.lastStatus ?? "unknown"}
|
|
511
737
|
|
|
512
|
-
|
|
513
|
-
}
|
|
738
|
+
When graph context is available:
|
|
514
739
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
740
|
+
1. Use the graph to identify likely modules, dependencies, and insertion points.
|
|
741
|
+
2. Read only the concrete source files needed to confirm behavior.
|
|
742
|
+
3. Prefer OpenSpec artifacts and AGENTS.md over graph guesses when they conflict.
|
|
743
|
+
4. Fall back to normal repository inspection if the graph is missing, stale, or incomplete.
|
|
744
|
+
|
|
745
|
+
When producing OpenSpec artifacts:
|
|
746
|
+
|
|
747
|
+
- Use graph context to make proposal, design, specs, and tasks more precise.
|
|
748
|
+
- Avoid large repository scans when the graph already narrows the relevant area.
|
|
749
|
+
- Keep all generated artifacts in the normal OpenSpec change directory.
|
|
750
|
+
`;
|
|
751
|
+
}
|
|
752
|
+
return `<!-- FET:MANAGED
|
|
518
753
|
schemaVersion: 1
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
changeId: ${changeId}
|
|
522
|
-
purpose: manual-verify
|
|
523
|
-
---
|
|
754
|
+
generator: graph-handoff
|
|
755
|
+
FET:END -->
|
|
524
756
|
|
|
525
|
-
#
|
|
757
|
+
# FET \u4EE3\u7801\u56FE\u4EA4\u63A5\u8BF4\u660E
|
|
526
758
|
|
|
527
|
-
\
|
|
759
|
+
\u5728\u5927\u8303\u56F4\u626B\u63CF\u4ED3\u5E93\u524D\uFF0C\u4F18\u5148\u628A GitNexus \u4EE3\u7801\u56FE\u4E0A\u4E0B\u6587\u4F5C\u4E3A\u53EF\u9009\u7684\u7B2C\u4E00\u8F6E\u7EBF\u7D22\u3002
|
|
528
760
|
|
|
529
|
-
|
|
530
|
-
2. \u6309\u9879\u76EE\u7EA6\u5B9A\u8FD0\u884C lint\u3001typecheck\u3001test\u3002
|
|
531
|
-
3. \u68C0\u67E5\u672C\u6B21 change \u7684 \`tasks.md\` \u662F\u5426\u4E0E\u5B9E\u73B0\u72B6\u6001\u4E00\u81F4\u3002
|
|
761
|
+
\u5F53\u524D\u72B6\u6001\uFF1A
|
|
532
762
|
|
|
533
|
-
\
|
|
763
|
+
- \u5DF2\u5B89\u88C5\uFF1A${state.installed ? "\u662F" : "\u5426"}
|
|
764
|
+
- \u56FE\u8DEF\u5F84\uFF1A${state.graphPath ?? ".gitnexus"}
|
|
765
|
+
- \u56FE\u5DF2\u5B58\u5728\uFF1A${state.graphExists ? "\u662F" : "\u5426"}
|
|
766
|
+
- \u6700\u540E\u7D22\u5F15\u65F6\u95F4\uFF1A${state.lastIndexedAt ?? "unknown"}
|
|
767
|
+
- \u6700\u540E\u72B6\u6001\uFF1A${state.lastStatus ?? "unknown"}
|
|
534
768
|
|
|
535
|
-
|
|
536
|
-
fet verify --done --change ${changeId}
|
|
537
|
-
\`\`\`
|
|
538
|
-
`;
|
|
539
|
-
}
|
|
769
|
+
\u4EE3\u7801\u56FE\u4E0A\u4E0B\u6587\u53EF\u7528\u65F6\uFF1A
|
|
540
770
|
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
"openspec/fet-state.json",
|
|
546
|
-
"openspec/.fet.lock",
|
|
547
|
-
"openspec/.fet-init-journal.json",
|
|
548
|
-
"openspec/changes/*/fet-state.json",
|
|
549
|
-
"openspec/changes/*/.fet/",
|
|
550
|
-
".gitnexus/"
|
|
551
|
-
];
|
|
552
|
-
function mergeGitignore(existing) {
|
|
553
|
-
const block = `${BEGIN2}
|
|
554
|
-
${RULES.join("\n")}
|
|
555
|
-
${END2}`;
|
|
556
|
-
if (!existing || !existing.trim()) {
|
|
557
|
-
return `${block}
|
|
558
|
-
`;
|
|
559
|
-
}
|
|
560
|
-
const start = existing.indexOf(BEGIN2);
|
|
561
|
-
const end = existing.indexOf(END2);
|
|
562
|
-
if (start !== -1 && end !== -1 && end > start) {
|
|
563
|
-
return `${existing.slice(0, start)}${block}${existing.slice(end + END2.length)}`;
|
|
564
|
-
}
|
|
565
|
-
return `${existing.replace(/\s*$/, "")}
|
|
771
|
+
1. \u7528\u4EE3\u7801\u56FE\u8BC6\u522B\u53EF\u80FD\u76F8\u5173\u7684\u6A21\u5757\u3001\u4F9D\u8D56\u548C\u63D2\u5165\u70B9\u3002
|
|
772
|
+
2. \u53EA\u8BFB\u53D6\u9700\u8981\u786E\u8BA4\u884C\u4E3A\u7684\u5177\u4F53\u6E90\u7801\u6587\u4EF6\u3002
|
|
773
|
+
3. \u5F53\u4EE3\u7801\u56FE\u63A8\u65AD\u4E0E OpenSpec \u4EA7\u7269\u6216 AGENTS.md \u51B2\u7A81\u65F6\uFF0C\u4F18\u5148\u76F8\u4FE1 OpenSpec \u4EA7\u7269\u548C AGENTS.md\u3002
|
|
774
|
+
4. \u5982\u679C\u4EE3\u7801\u56FE\u7F3A\u5931\u3001\u8FC7\u671F\u6216\u4E0D\u5B8C\u6574\uFF0C\u56DE\u9000\u5230\u666E\u901A\u4ED3\u5E93\u68C0\u67E5\u3002
|
|
566
775
|
|
|
567
|
-
|
|
776
|
+
\u751F\u6210 OpenSpec \u4EA7\u7269\u65F6\uFF1A
|
|
777
|
+
|
|
778
|
+
- \u7528\u4EE3\u7801\u56FE\u4E0A\u4E0B\u6587\u8BA9 proposal\u3001design\u3001specs \u548C tasks \u66F4\u7CBE\u786E\u3002
|
|
779
|
+
- \u5F53\u4EE3\u7801\u56FE\u5DF2\u7ECF\u7F29\u5C0F\u76F8\u5173\u8303\u56F4\u65F6\uFF0C\u907F\u514D\u5927\u8303\u56F4\u4ED3\u5E93\u626B\u63CF\u3002
|
|
780
|
+
- \u6240\u6709\u751F\u6210\u4EA7\u7269\u4ECD\u5199\u5165\u6B63\u5E38\u7684 OpenSpec change \u76EE\u5F55\u3002
|
|
568
781
|
`;
|
|
569
782
|
}
|
|
783
|
+
function firstLine(value) {
|
|
784
|
+
return value.trim().split(/\r?\n/)[0]?.trim() || null;
|
|
785
|
+
}
|
|
570
786
|
|
|
571
|
-
// src/commands/
|
|
572
|
-
import { readFile as
|
|
573
|
-
import { join as
|
|
787
|
+
// src/commands/init.ts
|
|
788
|
+
import { readFile as readFile7, stat as stat4 } from "fs/promises";
|
|
789
|
+
import { join as join11 } from "path";
|
|
574
790
|
|
|
575
|
-
// src/
|
|
576
|
-
import {
|
|
577
|
-
import {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
let
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
791
|
+
// src/version.ts
|
|
792
|
+
import { existsSync, readFileSync } from "fs";
|
|
793
|
+
import { dirname as dirname6, join as join9, parse } from "path";
|
|
794
|
+
import { fileURLToPath } from "url";
|
|
795
|
+
var FET_VERSION = readPackageVersion();
|
|
796
|
+
function readPackageVersion() {
|
|
797
|
+
let currentDir = dirname6(fileURLToPath(import.meta.url));
|
|
798
|
+
const root = parse(currentDir).root;
|
|
799
|
+
while (true) {
|
|
800
|
+
const packageJsonPath = join9(currentDir, "package.json");
|
|
801
|
+
if (existsSync(packageJsonPath)) {
|
|
802
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
803
|
+
if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
|
|
804
|
+
return packageJson.version;
|
|
805
|
+
}
|
|
806
|
+
throw new Error(`package.json \u7F3A\u5C11\u6709\u6548\u7684 version \u5B57\u6BB5: ${packageJsonPath}`);
|
|
807
|
+
}
|
|
808
|
+
if (currentDir === root) {
|
|
809
|
+
throw new Error("\u65E0\u6CD5\u5B9A\u4F4D FET package.json");
|
|
810
|
+
}
|
|
811
|
+
currentDir = dirname6(currentDir);
|
|
586
812
|
}
|
|
587
|
-
const doc = parseDocument(existing || "{}");
|
|
588
|
-
doc.set("fet", nextFet);
|
|
589
|
-
return doc.toString();
|
|
590
813
|
}
|
|
591
814
|
|
|
592
|
-
// src/
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
var
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
} catch {
|
|
601
|
-
return 0;
|
|
602
|
-
}
|
|
815
|
+
// src/templates/markers.ts
|
|
816
|
+
var AUTO_BEGIN = "<!-- FET:BEGIN AUTO -->";
|
|
817
|
+
var AUTO_END = "<!-- FET:END AUTO -->";
|
|
818
|
+
var USER_BEGIN = "<!-- FET:BEGIN USER -->";
|
|
819
|
+
var USER_END = "<!-- FET:END USER -->";
|
|
820
|
+
var LLM_PLACEHOLDER_PATTERN = /\[NEEDS? LLM INPUT\]/;
|
|
821
|
+
function hasManagedAutoRegion(content) {
|
|
822
|
+
return count(content, AUTO_BEGIN) === 1 && count(content, AUTO_END) === 1 && content.indexOf(AUTO_BEGIN) < content.indexOf(AUTO_END);
|
|
603
823
|
}
|
|
604
|
-
function
|
|
605
|
-
|
|
824
|
+
function hasInvalidManagedAutoRegion(content) {
|
|
825
|
+
const beginCount = count(content, AUTO_BEGIN);
|
|
826
|
+
const endCount = count(content, AUTO_END);
|
|
827
|
+
return beginCount !== endCount || beginCount > 1 || endCount > 1 || beginCount === 1 && content.indexOf(AUTO_BEGIN) > content.indexOf(AUTO_END);
|
|
606
828
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
);
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
});
|
|
829
|
+
function replaceManagedRegion(existing, generated) {
|
|
830
|
+
if (!existing) {
|
|
831
|
+
return generated;
|
|
832
|
+
}
|
|
833
|
+
const start = existing.indexOf(AUTO_BEGIN);
|
|
834
|
+
const end = existing.indexOf(AUTO_END);
|
|
835
|
+
if (start === -1 || end === -1 || end < start) {
|
|
836
|
+
return generated;
|
|
837
|
+
}
|
|
838
|
+
const before = existing.slice(0, start);
|
|
839
|
+
const after = existing.slice(end + AUTO_END.length);
|
|
840
|
+
const existingAuto = extractAuto(existing);
|
|
841
|
+
const generatedAuto = extractAuto(generated);
|
|
842
|
+
return `${before}${AUTO_BEGIN}
|
|
843
|
+
${mergeAutoRegion(existingAuto, generatedAuto)}
|
|
844
|
+
${AUTO_END}${after}`;
|
|
624
845
|
}
|
|
625
|
-
|
|
626
|
-
const
|
|
627
|
-
const
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
const karpathyHandoffPath = join7(ctx.projectRoot, ".fet", "karpathy-guidelines.md");
|
|
631
|
-
const karpathyCursorPath = join7(ctx.projectRoot, ".cursor", "rules", "karpathy-guidelines.mdc");
|
|
632
|
-
const existingAgents = await readOptional(agentsPath);
|
|
633
|
-
const existingClaude = await readOptional(claudePath);
|
|
634
|
-
const existingKarpathyCursor = await readOptional(karpathyCursorPath);
|
|
635
|
-
const warnings = [...scan.warnings];
|
|
636
|
-
if (existingAgents && hasInvalidManagedAutoRegion(existingAgents)) {
|
|
637
|
-
throw new FetError({
|
|
638
|
-
code: "CONFIG_INVALID" /* ConfigInvalid */,
|
|
639
|
-
message: "AGENTS.md \u7684 FET \u6258\u7BA1\u6807\u8BB0\u635F\u574F\u6216\u91CD\u590D",
|
|
640
|
-
details: { path: "AGENTS.md" },
|
|
641
|
-
suggestedCommand: "\u624B\u52A8\u4FEE\u590D FET:BEGIN AUTO / FET:END AUTO \u6807\u8BB0\u540E\u91CD\u8BD5"
|
|
642
|
-
});
|
|
846
|
+
function mergeAutoRegion(existingAuto, generatedAuto) {
|
|
847
|
+
const generatedSections = splitMarkdownSections(generatedAuto);
|
|
848
|
+
const existingSections = new Map(splitMarkdownSections(existingAuto).map((section) => [sectionKey(section.heading), section]));
|
|
849
|
+
if (!generatedSections.length || !existingSections.size) {
|
|
850
|
+
return generatedAuto;
|
|
643
851
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
message: "AGENTS.md \u5DF2\u5B58\u5728\u4E14\u4E0D\u5305\u542B FET \u6258\u7BA1\u533A\u57DF",
|
|
649
|
-
details: { path: "AGENTS.md" },
|
|
650
|
-
suggestedCommand: ctx.command === "init" ? "\u786E\u8BA4\u53EF\u5907\u4EFD\u5E76\u66FF\u6362\u540E\u8FD0\u884C fet init --yes" : "\u786E\u8BA4\u53EF\u5907\u4EFD\u5E76\u66FF\u6362\u540E\u8FD0\u884C fet update-context --yes"
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
const backupPath = await createBackup(agentsPath);
|
|
654
|
-
if (backupPath) {
|
|
655
|
-
warnings.push(`\u5DF2\u5907\u4EFD\u975E\u6258\u7BA1 AGENTS.md \u5230 ${backupPath}`);
|
|
852
|
+
return generatedSections.map((section) => {
|
|
853
|
+
const existing = existingSections.get(sectionKey(section.heading));
|
|
854
|
+
if (existing && LLM_PLACEHOLDER_PATTERN.test(section.body) && !LLM_PLACEHOLDER_PATTERN.test(existing.body)) {
|
|
855
|
+
return existing.raw.trim();
|
|
656
856
|
}
|
|
857
|
+
return section.raw.trim();
|
|
858
|
+
}).join("\n\n");
|
|
859
|
+
}
|
|
860
|
+
function sectionKey(heading) {
|
|
861
|
+
const normalized = heading.replace(/^##\s+/, "").trim().toLowerCase();
|
|
862
|
+
const aliases = {
|
|
863
|
+
"project snapshot": "snapshot",
|
|
864
|
+
"\u9879\u76EE\u5FEB\u7167": "snapshot",
|
|
865
|
+
workspaces: "workspaces",
|
|
866
|
+
"\u5DE5\u4F5C\u533A": "workspaces",
|
|
867
|
+
commands: "commands",
|
|
868
|
+
"\u547D\u4EE4": "commands",
|
|
869
|
+
structure: "structure",
|
|
870
|
+
"\u7ED3\u6784": "structure",
|
|
871
|
+
routes: "routes",
|
|
872
|
+
"\u8DEF\u7531": "routes",
|
|
873
|
+
conventions: "conventions",
|
|
874
|
+
"\u7EA6\u5B9A": "conventions",
|
|
875
|
+
"ai work guidelines": "ai-guidelines",
|
|
876
|
+
"ai \u5DE5\u4F5C\u6307\u5357": "ai-guidelines",
|
|
877
|
+
"scanner metadata": "metadata",
|
|
878
|
+
"\u626B\u63CF\u5143\u6570\u636E": "metadata",
|
|
879
|
+
"notes for ai": "notes",
|
|
880
|
+
"\u7ED9 ai \u7684\u5907\u6CE8": "notes"
|
|
881
|
+
};
|
|
882
|
+
return aliases[normalized] ?? normalized;
|
|
883
|
+
}
|
|
884
|
+
function splitMarkdownSections(content) {
|
|
885
|
+
const matches = [...content.matchAll(/^## .+$/gm)];
|
|
886
|
+
if (!matches.length) {
|
|
887
|
+
return [];
|
|
657
888
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
889
|
+
return matches.map((match, index) => {
|
|
890
|
+
const start = match.index ?? 0;
|
|
891
|
+
const end = matches[index + 1]?.index ?? content.length;
|
|
892
|
+
const raw = content.slice(start, end).trim();
|
|
893
|
+
const newline = raw.indexOf("\n");
|
|
894
|
+
const heading = newline === -1 ? raw.trim() : raw.slice(0, newline).trim();
|
|
895
|
+
const body = newline === -1 ? "" : raw.slice(newline + 1).trim();
|
|
896
|
+
return { heading, body, raw };
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
function extractAuto(content) {
|
|
900
|
+
const start = content.indexOf(AUTO_BEGIN);
|
|
901
|
+
const end = content.indexOf(AUTO_END);
|
|
902
|
+
if (start === -1 || end === -1 || end < start) {
|
|
903
|
+
return content.trim();
|
|
670
904
|
}
|
|
671
|
-
|
|
672
|
-
state.context = {
|
|
673
|
-
agentsMdUpdatedAt: scan.generatedAt,
|
|
674
|
-
configUpdatedAt: scan.generatedAt,
|
|
675
|
-
scannerVersion: scan.scannerVersion
|
|
676
|
-
};
|
|
677
|
-
await ctx.stateStore.writeGlobal(state);
|
|
678
|
-
return { warnings };
|
|
905
|
+
return content.slice(start + AUTO_BEGIN.length, end).trim();
|
|
679
906
|
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
907
|
+
function count(content, needle) {
|
|
908
|
+
return content.split(needle).length - 1;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// src/templates/agents-md.ts
|
|
912
|
+
function renderAgentsMd(scan, language = "zh-CN") {
|
|
913
|
+
if (language === "en") {
|
|
914
|
+
return renderAgentsMdEn(scan);
|
|
685
915
|
}
|
|
916
|
+
return renderAgentsMdZh(scan);
|
|
686
917
|
}
|
|
918
|
+
function renderAgentsMdZh(scan) {
|
|
919
|
+
const commands = Object.entries(scan.commands).map(([name, command]) => `| ${name} | \`${command.command}\` | ${command.source} |`).join("\n");
|
|
920
|
+
const routes = scan.routes.map((route) => `| ${route.path} | ${route.source} | ${route.confidence}${route.inferred ? " inferred" : ""} |`).join("\n");
|
|
921
|
+
const workspaces = scan.project.workspaces.map((workspace) => `| ${workspace.name} | ${workspace.path} | ${workspace.source} |`).join("\n");
|
|
922
|
+
return `# \u9879\u76EE\u4E0A\u4E0B\u6587
|
|
687
923
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
const alreadyInitialized = await exists(join8(ctx.projectRoot, "openspec", "config.yaml"));
|
|
691
|
-
let warnings = [];
|
|
692
|
-
await withProjectLock(
|
|
693
|
-
ctx.projectRoot,
|
|
694
|
-
{ command: "init", cwd: ctx.cwd, fetVersion: ctx.fetVersion },
|
|
695
|
-
async () => {
|
|
696
|
-
const journal = createInitJournal(ctx.fetVersion);
|
|
697
|
-
await writeInitJournal(ctx.projectRoot, journal);
|
|
698
|
-
const identity = await ctx.openSpec.resolveExecutable();
|
|
699
|
-
if (!alreadyInitialized) {
|
|
700
|
-
const result = await ctx.openSpec.run("init", ["--tools", "none"], { cwd: ctx.projectRoot, stdio: "inherit" });
|
|
701
|
-
if (result.exitCode !== 0) {
|
|
702
|
-
process.exitCode = result.exitCode;
|
|
703
|
-
return;
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
const contextResult = await updateContextFiles(ctx);
|
|
707
|
-
warnings = contextResult.warnings;
|
|
708
|
-
await ensureGitignore(ctx);
|
|
709
|
-
const state = await ctx.stateStore.getOrCreateGlobal();
|
|
710
|
-
state.openspec = identity;
|
|
711
|
-
state.graph ??= {};
|
|
712
|
-
const gitnexus = mergeGitNexusGraphInfo(
|
|
713
|
-
toGitNexusState(await detectGitNexus(), state.graph.gitnexus),
|
|
714
|
-
await inspectGitNexusGraph(ctx.projectRoot)
|
|
715
|
-
);
|
|
716
|
-
if (!gitnexus.installed && !gitnexus.recommendationShownAt) {
|
|
717
|
-
warnings.push(renderGitNexusRecommendation(gitnexus));
|
|
718
|
-
gitnexus.recommendationShownAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
719
|
-
}
|
|
720
|
-
state.graph.gitnexus = gitnexus;
|
|
721
|
-
for (const adapter of ctx.toolAdapters) {
|
|
722
|
-
const plan = await adapter.planInstall(ctx.projectRoot);
|
|
723
|
-
const result = await adapter.install(ctx.projectRoot, plan, ctx.yes);
|
|
724
|
-
state.toolAdapters[adapter.tool] = {
|
|
725
|
-
adapterVersion: adapter.adapterVersion,
|
|
726
|
-
installed: true,
|
|
727
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
728
|
-
};
|
|
729
|
-
journal.steps.push(...result.written.map((path) => ({ operation: "write", path, status: "done" })));
|
|
730
|
-
}
|
|
731
|
-
journal.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
732
|
-
await writeInitJournal(ctx.projectRoot, journal);
|
|
733
|
-
await ctx.stateStore.writeGlobal(state);
|
|
734
|
-
}
|
|
735
|
-
);
|
|
736
|
-
ctx.output.result({
|
|
737
|
-
ok: true,
|
|
738
|
-
command: "init",
|
|
739
|
-
summary: "FET \u521D\u59CB\u5316\u5B8C\u6210\u3002",
|
|
740
|
-
warnings,
|
|
741
|
-
nextSteps: ["\u4F7F\u7528 fet propose/new \u521B\u5EFA OpenSpec change", "\u4F7F\u7528 fet doctor \u68C0\u67E5\u9879\u76EE\u72B6\u6001"]
|
|
742
|
-
});
|
|
743
|
-
}
|
|
744
|
-
async function ensureGitignore(ctx) {
|
|
745
|
-
const gitignorePath = join8(ctx.projectRoot, ".gitignore");
|
|
746
|
-
const existing = await readOptional2(gitignorePath);
|
|
747
|
-
await atomicWrite(gitignorePath, mergeGitignore(existing));
|
|
748
|
-
}
|
|
749
|
-
async function readOptional2(path) {
|
|
750
|
-
try {
|
|
751
|
-
return await readFile6(path, "utf8");
|
|
752
|
-
} catch {
|
|
753
|
-
return null;
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
async function exists(path) {
|
|
757
|
-
try {
|
|
758
|
-
await stat3(path);
|
|
759
|
-
return true;
|
|
760
|
-
} catch {
|
|
761
|
-
return false;
|
|
762
|
-
}
|
|
763
|
-
}
|
|
924
|
+
${AUTO_BEGIN}
|
|
925
|
+
## \u9879\u76EE\u5FEB\u7167
|
|
764
926
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
checks.push(await checkOpenSpec(ctx));
|
|
771
|
-
checks.push(await checkState(ctx));
|
|
772
|
-
checks.push(await checkFile("agents", join9(ctx.projectRoot, "AGENTS.md"), "AGENTS.md \u7F3A\u5931", "fet update-context"));
|
|
773
|
-
checks.push(await checkFile("config", join9(ctx.projectRoot, "openspec", "config.yaml"), "openspec/config.yaml \u7F3A\u5931", "fet init"));
|
|
774
|
-
checks.push(await checkPlaceholders(ctx.projectRoot));
|
|
775
|
-
checks.push(await checkGitNexus(ctx));
|
|
776
|
-
for (const adapter of ctx.toolAdapters) {
|
|
777
|
-
checks.push(...await adapter.doctor(ctx.projectRoot));
|
|
778
|
-
}
|
|
779
|
-
const lockPath = join9(ctx.projectRoot, "openspec", ".fet.lock");
|
|
780
|
-
if (await exists2(lockPath)) {
|
|
781
|
-
if (options.fixLock) {
|
|
782
|
-
await clearLock(ctx.projectRoot);
|
|
783
|
-
checks.push({ id: "lock", status: "pass", message: "\u5DF2\u6E05\u7406 openspec/.fet.lock" });
|
|
784
|
-
} else {
|
|
785
|
-
checks.push({ id: "lock", status: "warn", message: "\u5B58\u5728 openspec/.fet.lock", suggestedCommand: "fet doctor --fix-lock" });
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
const warnings = checks.filter((check) => check.status !== "pass").map((check) => check.message);
|
|
789
|
-
ctx.output.result({
|
|
790
|
-
ok: true,
|
|
791
|
-
command: "doctor",
|
|
792
|
-
summary: warnings.length ? `\u8BCA\u65AD\u5B8C\u6210\uFF0C\u53D1\u73B0 ${warnings.length} \u4E2A\u9700\u8981\u5173\u6CE8\u7684\u95EE\u9898\u3002` : "\u8BCA\u65AD\u5B8C\u6210\uFF0C\u672A\u53D1\u73B0\u660E\u663E\u95EE\u9898\u3002",
|
|
793
|
-
warnings,
|
|
794
|
-
data: checks
|
|
795
|
-
});
|
|
796
|
-
}
|
|
797
|
-
async function checkGitNexus(ctx) {
|
|
798
|
-
const global = await ctx.stateStore.readGlobal();
|
|
799
|
-
const state = mergeGitNexusGraphInfo(
|
|
800
|
-
toGitNexusState(await detectGitNexus(), global?.graph?.gitnexus),
|
|
801
|
-
await inspectGitNexusGraph(ctx.projectRoot)
|
|
802
|
-
);
|
|
803
|
-
if (global) {
|
|
804
|
-
global.graph ??= {};
|
|
805
|
-
global.graph.gitnexus = state;
|
|
806
|
-
await ctx.stateStore.writeGlobal(global);
|
|
807
|
-
}
|
|
808
|
-
return state.installed ? {
|
|
809
|
-
id: "gitnexus",
|
|
810
|
-
status: "pass",
|
|
811
|
-
message: `GitNexus detected: ${state.executablePath ?? "gitnexus"} (${state.version ?? "unknown"}), graph ${state.graphExists ? "found" : "not found"}`
|
|
812
|
-
} : {
|
|
813
|
-
id: "gitnexus",
|
|
814
|
-
status: "warn",
|
|
815
|
-
message: "Optional GitNexus code graph support is not installed",
|
|
816
|
-
suggestedCommand: "Install GitNexus later if you want OpenSpec artifacts to prefer a repository graph"
|
|
817
|
-
};
|
|
818
|
-
}
|
|
819
|
-
async function checkOpenSpec(ctx) {
|
|
820
|
-
try {
|
|
821
|
-
const identity = await ctx.openSpec.resolveExecutable();
|
|
822
|
-
return { id: "openspec", status: "pass", message: `OpenSpec: ${identity.executablePath} (${identity.version})` };
|
|
823
|
-
} catch (error) {
|
|
824
|
-
return { id: "openspec", status: "fail", message: error instanceof Error ? error.message : "OpenSpec \u68C0\u6D4B\u5931\u8D25" };
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
async function checkState(ctx) {
|
|
828
|
-
try {
|
|
829
|
-
const state = await ctx.stateStore.readGlobal();
|
|
830
|
-
return state ? { id: "state", status: "pass", message: "FET \u5168\u5C40\u72B6\u6001\u53EF\u8BFB\u53D6" } : { id: "state", status: "warn", message: "FET \u5168\u5C40\u72B6\u6001\u5C1A\u672A\u521D\u59CB\u5316", suggestedCommand: "fet init" };
|
|
831
|
-
} catch (error) {
|
|
832
|
-
return { id: "state", status: "fail", message: error instanceof Error ? error.message : "FET \u72B6\u6001\u8BFB\u53D6\u5931\u8D25" };
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
async function checkFile(id, path, missing, suggestedCommand) {
|
|
836
|
-
return await exists2(path) ? { id, status: "pass", message: `${id} \u5B58\u5728` } : { id, status: "warn", message: missing, suggestedCommand };
|
|
837
|
-
}
|
|
838
|
-
async function checkPlaceholders(projectRoot) {
|
|
839
|
-
try {
|
|
840
|
-
await readFile7(join9(projectRoot, "AGENTS.md"), "utf8");
|
|
841
|
-
const count2 = await countAgentsLlmPlaceholders(projectRoot);
|
|
842
|
-
return count2 ? {
|
|
843
|
-
id: "context-placeholders",
|
|
844
|
-
status: "warn",
|
|
845
|
-
message: `AGENTS.md has ${count2} LLM placeholder(s)`,
|
|
846
|
-
suggestedCommand: "fet fill-context"
|
|
847
|
-
} : { id: "context-placeholders", status: "pass", message: "AGENTS.md placeholders resolved" };
|
|
848
|
-
} catch {
|
|
849
|
-
return { id: "context-placeholders", status: "warn", message: "AGENTS.md missing", suggestedCommand: "fet update-context" };
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
async function exists2(path) {
|
|
853
|
-
try {
|
|
854
|
-
await stat4(path);
|
|
855
|
-
return true;
|
|
856
|
-
} catch {
|
|
857
|
-
return false;
|
|
858
|
-
}
|
|
859
|
-
}
|
|
927
|
+
- \u540D\u79F0\uFF1A${scan.project.name}
|
|
928
|
+
- \u5305\u7BA1\u7406\u5668\uFF1A${scan.project.packageManager}\uFF08${scan.project.packageManagerConfidence}\uFF09
|
|
929
|
+
- \u6846\u67B6\uFF1A${scan.project.framework.name}\uFF08${scan.project.framework.confidence}\uFF09
|
|
930
|
+
- \u8BED\u8A00\uFF1A${scan.project.language}
|
|
931
|
+
- Monorepo\uFF1A${scan.project.monorepo ? "\u662F" : "\u5426"}
|
|
860
932
|
|
|
861
|
-
|
|
862
|
-
import { mkdir as mkdir3 } from "fs/promises";
|
|
863
|
-
import { dirname as dirname5, join as join10 } from "path";
|
|
864
|
-
async function fillContextCommand(ctx) {
|
|
865
|
-
await withProjectLock(
|
|
866
|
-
ctx.projectRoot,
|
|
867
|
-
{ command: "fill-context", cwd: ctx.cwd, fetVersion: ctx.fetVersion },
|
|
868
|
-
async () => {
|
|
869
|
-
const handoffPath = join10(ctx.projectRoot, ".fet", "fill-context.md");
|
|
870
|
-
await mkdir3(dirname5(handoffPath), { recursive: true });
|
|
871
|
-
await atomicWrite(handoffPath, renderGenericHandoff());
|
|
872
|
-
for (const adapter of ctx.toolAdapters) {
|
|
873
|
-
const plan = await adapter.planInstall(ctx.projectRoot);
|
|
874
|
-
const result = await adapter.install(ctx.projectRoot, plan, ctx.yes);
|
|
875
|
-
const state = await ctx.stateStore.getOrCreateGlobal();
|
|
876
|
-
state.toolAdapters[adapter.tool] = {
|
|
877
|
-
adapterVersion: adapter.adapterVersion,
|
|
878
|
-
installed: true,
|
|
879
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
880
|
-
};
|
|
881
|
-
await ctx.stateStore.writeGlobal(state);
|
|
882
|
-
if (ctx.verbose) {
|
|
883
|
-
ctx.output.info(`Updated ${adapter.tool} adapter`, { written: result.written });
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
);
|
|
888
|
-
const placeholders = await countAgentsLlmPlaceholders(ctx.projectRoot);
|
|
889
|
-
ctx.output.result({
|
|
890
|
-
ok: true,
|
|
891
|
-
command: "fill-context",
|
|
892
|
-
summary: placeholders ? `Found ${placeholders} AGENTS.md placeholder(s). Use your IDE AI to fill them.` : "No AGENTS.md placeholders found. IDE fill-context commands were refreshed.",
|
|
893
|
-
nextSteps: placeholders ? [
|
|
894
|
-
"Cursor: run /fet-fill-context",
|
|
895
|
-
"Codex: run /prompts:fet-fill-context",
|
|
896
|
-
"OpenCode or other IDEs: open .fet/fill-context.md or run fet fill-context for handoff instructions"
|
|
897
|
-
] : ["Run fet doctor to confirm project context health"],
|
|
898
|
-
data: {
|
|
899
|
-
placeholders,
|
|
900
|
-
cursorCommand: "/fet-fill-context",
|
|
901
|
-
codexCommand: "/prompts:fet-fill-context"
|
|
902
|
-
}
|
|
903
|
-
});
|
|
904
|
-
}
|
|
905
|
-
function renderGenericHandoff() {
|
|
906
|
-
return `<!-- FET:MANAGED
|
|
907
|
-
schemaVersion: 1
|
|
908
|
-
generator: fill-context
|
|
909
|
-
FET:END -->
|
|
933
|
+
## \u5DE5\u4F5C\u533A
|
|
910
934
|
|
|
911
|
-
|
|
935
|
+
| \u540D\u79F0 | \u8DEF\u5F84 | \u6765\u6E90 |
|
|
936
|
+
|------|------|--------|
|
|
937
|
+
${workspaces || "| root | . | inferred |"}
|
|
912
938
|
|
|
913
|
-
|
|
939
|
+
## \u547D\u4EE4
|
|
914
940
|
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
941
|
+
| \u540D\u79F0 | \u547D\u4EE4 | \u6765\u6E90 |
|
|
942
|
+
|------|---------|--------|
|
|
943
|
+
${commands || "| [NEEDS LLM INPUT] | [NEEDS LLM INPUT] | [NEEDS LLM INPUT] |"}
|
|
944
|
+
|
|
945
|
+
## \u7ED3\u6784
|
|
946
|
+
|
|
947
|
+
[NEEDS LLM INPUT]
|
|
948
|
+
|
|
949
|
+
## \u8DEF\u7531
|
|
950
|
+
|
|
951
|
+
| \u8DEF\u7531 | \u6765\u6E90 | \u7F6E\u4FE1\u5EA6 |
|
|
952
|
+
|-------|--------|------------|
|
|
953
|
+
${routes || "| [NEEDS LLM INPUT] | [NEEDS LLM INPUT] | low |"}
|
|
954
|
+
|
|
955
|
+
## \u7EA6\u5B9A
|
|
956
|
+
|
|
957
|
+
[NEEDS LLM INPUT]
|
|
958
|
+
|
|
959
|
+
## AI \u5DE5\u4F5C\u6307\u5357
|
|
960
|
+
|
|
961
|
+
- \u4F7F\u7528 FET \u6258\u7BA1\u7684 IDE \u5DE5\u4F5C\u6D41\u65F6\uFF0C\u4F18\u5148\u53C2\u8003 .fet/karpathy-guidelines.md \u4E2D\u7684\u9879\u76EE\u7EA7\u6307\u5357\u3002
|
|
962
|
+
- \u5BF9 Codex\uFF0C\u8FD8\u5E94\u5728\u5B58\u5728\u65F6\u9605\u8BFB .codex/fet/karpathy-guidelines.md\u3002
|
|
963
|
+
- \u8FD9\u4E9B\u6307\u5357\u4F4E\u4E8E\u7528\u6237\u6700\u65B0\u8BF7\u6C42\u548C\u660E\u786E\u7684 OpenSpec \u4EA7\u7269\u3002
|
|
964
|
+
- \u9ED8\u8BA4\u4F7F\u7528\u4E2D\u6587\u4EA7\u51FA\u4EA4\u4E92\u4FE1\u606F\u3001\u8BA1\u5212\u6587\u6863\u548C\u5B9E\u73B0\u8BF4\u660E\uFF0C\u9664\u975E\u7528\u6237\u53E6\u6709\u660E\u786E\u8981\u6C42\u3002
|
|
965
|
+
|
|
966
|
+
## \u626B\u63CF\u5143\u6570\u636E
|
|
967
|
+
|
|
968
|
+
- \u751F\u6210\u65F6\u95F4\uFF1A${scan.generatedAt}
|
|
969
|
+
- FET \u7248\u672C\uFF1A${FET_VERSION}
|
|
970
|
+
- \u626B\u63CF\u5668\u7248\u672C\uFF1A${scan.scannerVersion}
|
|
971
|
+
- \u8B66\u544A\uFF1A${scan.warnings.length ? scan.warnings.join("; ") : "\u65E0"}
|
|
972
|
+
${AUTO_END}
|
|
973
|
+
|
|
974
|
+
${USER_BEGIN}
|
|
975
|
+
## \u7ED9 AI \u7684\u5907\u6CE8
|
|
976
|
+
|
|
977
|
+
[NEEDS LLM INPUT]
|
|
978
|
+
${USER_END}
|
|
922
979
|
`;
|
|
923
980
|
}
|
|
981
|
+
function renderAgentsMdEn(scan) {
|
|
982
|
+
const commands = Object.entries(scan.commands).map(([name, command]) => `| ${name} | \`${command.command}\` | ${command.source} |`).join("\n");
|
|
983
|
+
const routes = scan.routes.map((route) => `| ${route.path} | ${route.source} | ${route.confidence}${route.inferred ? " inferred" : ""} |`).join("\n");
|
|
984
|
+
const workspaces = scan.project.workspaces.map((workspace) => `| ${workspace.name} | ${workspace.path} | ${workspace.source} |`).join("\n");
|
|
985
|
+
return `# Project Context
|
|
924
986
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
const handoffPath = join11(ctx.projectRoot, ".fet", "graph-setup.md");
|
|
981
|
-
await withProjectLock(ctx.projectRoot, { command: "graph setup", cwd: ctx.cwd, fetVersion: ctx.fetVersion }, async () => {
|
|
982
|
-
result = await refreshGraphState(ctx, { write: false });
|
|
983
|
-
await writeHandoffFile(handoffPath, renderGraphSetupHandoff(result.state));
|
|
984
|
-
const global = await ctx.stateStore.getOrCreateGlobal();
|
|
985
|
-
global.graph ??= {};
|
|
986
|
-
global.graph.gitnexus = {
|
|
987
|
-
...result.state,
|
|
988
|
-
setupHandoffPath: ".fet/graph-setup.md",
|
|
989
|
-
setupHandoffUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
990
|
-
};
|
|
991
|
-
await ctx.stateStore.writeGlobal(global);
|
|
992
|
-
});
|
|
993
|
-
ctx.output.result({
|
|
994
|
-
ok: true,
|
|
995
|
-
command: "graph setup",
|
|
996
|
-
summary: "GitNexus setup handoff generated.",
|
|
997
|
-
warnings: result.state.installed ? [] : ["GitNexus is not installed. The handoff explains installation and IDE-assisted setup options."],
|
|
998
|
-
nextSteps: result.state.installed ? ["Run gitnexus setup if you want to configure IDE/MCP integrations", "Run fet graph init"] : ["Open .fet/graph-setup.md in your IDE AI"],
|
|
999
|
-
data: {
|
|
1000
|
-
path: ".fet/graph-setup.md",
|
|
1001
|
-
gitnexus: result.state
|
|
1002
|
-
}
|
|
1003
|
-
});
|
|
1004
|
-
}
|
|
1005
|
-
async function graphHandoffCommand(ctx) {
|
|
1006
|
-
let result;
|
|
1007
|
-
const handoffPath = join11(ctx.projectRoot, ".fet", "graph-handoff.md");
|
|
1008
|
-
await withProjectLock(ctx.projectRoot, { command: "graph handoff", cwd: ctx.cwd, fetVersion: ctx.fetVersion }, async () => {
|
|
1009
|
-
result = await refreshGraphState(ctx, { runStatus: true, write: false });
|
|
1010
|
-
await writeHandoffFile(handoffPath, renderGraphUsageHandoff(result.state));
|
|
1011
|
-
const global = await ctx.stateStore.getOrCreateGlobal();
|
|
1012
|
-
global.graph ??= {};
|
|
1013
|
-
global.graph.gitnexus = {
|
|
1014
|
-
...result.state,
|
|
1015
|
-
handoffPath: ".fet/graph-handoff.md",
|
|
1016
|
-
handoffUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1017
|
-
};
|
|
1018
|
-
await ctx.stateStore.writeGlobal(global);
|
|
1019
|
-
});
|
|
1020
|
-
ctx.output.result({
|
|
1021
|
-
ok: true,
|
|
1022
|
-
command: "graph handoff",
|
|
1023
|
-
summary: "GitNexus graph usage handoff generated.",
|
|
1024
|
-
warnings: result.state.installed ? [] : ["GitNexus is not installed. The handoff still documents the fallback behavior."],
|
|
1025
|
-
nextSteps: ["Cursor/Codex/OpenCode: read .fet/graph-handoff.md before broad repository scans"],
|
|
1026
|
-
data: {
|
|
1027
|
-
path: ".fet/graph-handoff.md",
|
|
1028
|
-
gitnexus: result.state
|
|
1029
|
-
}
|
|
1030
|
-
});
|
|
987
|
+
${AUTO_BEGIN}
|
|
988
|
+
## Project Snapshot
|
|
989
|
+
|
|
990
|
+
- Name: ${scan.project.name}
|
|
991
|
+
- Package Manager: ${scan.project.packageManager} (${scan.project.packageManagerConfidence})
|
|
992
|
+
- Framework: ${scan.project.framework.name} (${scan.project.framework.confidence})
|
|
993
|
+
- Language: ${scan.project.language}
|
|
994
|
+
- Monorepo: ${scan.project.monorepo ? "yes" : "no"}
|
|
995
|
+
|
|
996
|
+
## Workspaces
|
|
997
|
+
|
|
998
|
+
| Name | Path | Source |
|
|
999
|
+
|------|------|--------|
|
|
1000
|
+
${workspaces || "| root | . | inferred |"}
|
|
1001
|
+
|
|
1002
|
+
## Commands
|
|
1003
|
+
|
|
1004
|
+
| Name | Command | Source |
|
|
1005
|
+
|------|---------|--------|
|
|
1006
|
+
${commands || "| [NEEDS LLM INPUT] | [NEEDS LLM INPUT] | [NEEDS LLM INPUT] |"}
|
|
1007
|
+
|
|
1008
|
+
## Structure
|
|
1009
|
+
|
|
1010
|
+
[NEEDS LLM INPUT]
|
|
1011
|
+
|
|
1012
|
+
## Routes
|
|
1013
|
+
|
|
1014
|
+
| Route | Source | Confidence |
|
|
1015
|
+
|-------|--------|------------|
|
|
1016
|
+
${routes || "| [NEEDS LLM INPUT] | [NEEDS LLM INPUT] | low |"}
|
|
1017
|
+
|
|
1018
|
+
## Conventions
|
|
1019
|
+
|
|
1020
|
+
[NEEDS LLM INPUT]
|
|
1021
|
+
|
|
1022
|
+
## AI Work Guidelines
|
|
1023
|
+
|
|
1024
|
+
- Prefer the project-level Andrej Karpathy inspired guidelines in .fet/karpathy-guidelines.md when using FET-managed IDE workflows.
|
|
1025
|
+
- For Codex, also read .codex/fet/karpathy-guidelines.md when present.
|
|
1026
|
+
- Treat those guidelines as secondary to the user's latest request and explicit OpenSpec artifacts.
|
|
1027
|
+
|
|
1028
|
+
## Scanner Metadata
|
|
1029
|
+
|
|
1030
|
+
- Generated At: ${scan.generatedAt}
|
|
1031
|
+
- FET Version: ${FET_VERSION}
|
|
1032
|
+
- Scanner Version: ${scan.scannerVersion}
|
|
1033
|
+
- Warnings: ${scan.warnings.length ? scan.warnings.join("; ") : "none"}
|
|
1034
|
+
${AUTO_END}
|
|
1035
|
+
|
|
1036
|
+
${USER_BEGIN}
|
|
1037
|
+
## Notes For AI
|
|
1038
|
+
|
|
1039
|
+
[NEEDS LLM INPUT]
|
|
1040
|
+
${USER_END}
|
|
1041
|
+
`;
|
|
1031
1042
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
ok: true,
|
|
1061
|
-
command: `graph ${mode}`,
|
|
1062
|
-
summary: mode === "init" ? "GitNexus graph initialized." : "GitNexus graph refreshed.",
|
|
1063
|
-
warnings: result.state.graphExists ? [] : ["GitNexus analyze completed, but the configured graph directory was not found."],
|
|
1064
|
-
nextSteps: ["Run fet graph status", "Use .fet/graph-handoff.md or generated IDE prompts to prefer graph context"],
|
|
1065
|
-
data: {
|
|
1066
|
-
gitnexus: global.graph.gitnexus,
|
|
1067
|
-
run: {
|
|
1068
|
-
command: run.command,
|
|
1069
|
-
stdout: run.stdout.trim(),
|
|
1070
|
-
stderr: run.stderr.trim()
|
|
1043
|
+
|
|
1044
|
+
// src/templates/config-yaml.ts
|
|
1045
|
+
import { stringify } from "yaml";
|
|
1046
|
+
function renderFetConfig(scan, language = "zh-CN") {
|
|
1047
|
+
return stringify({
|
|
1048
|
+
fet: {
|
|
1049
|
+
schemaVersion: 1,
|
|
1050
|
+
generatedAt: scan.generatedAt,
|
|
1051
|
+
fetVersion: FET_VERSION,
|
|
1052
|
+
language,
|
|
1053
|
+
scannerVersion: scan.scannerVersion,
|
|
1054
|
+
project: {
|
|
1055
|
+
packageManager: scan.project.packageManager,
|
|
1056
|
+
packageManagerConfidence: scan.project.packageManagerConfidence,
|
|
1057
|
+
framework: scan.project.framework,
|
|
1058
|
+
language: scan.project.language,
|
|
1059
|
+
monorepo: scan.project.monorepo,
|
|
1060
|
+
workspaces: scan.project.workspaces
|
|
1061
|
+
},
|
|
1062
|
+
commands: scan.commands,
|
|
1063
|
+
validation: {
|
|
1064
|
+
monorepo: scan.project.monorepo,
|
|
1065
|
+
missing: {
|
|
1066
|
+
lint: "warn",
|
|
1067
|
+
typecheck: "warn",
|
|
1068
|
+
test: "warn"
|
|
1069
|
+
},
|
|
1070
|
+
workspaces: scan.project.workspaces
|
|
1071
1071
|
}
|
|
1072
1072
|
}
|
|
1073
1073
|
});
|
|
1074
1074
|
}
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
if (
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
...state,
|
|
1086
|
-
lastStatus: firstLine(gitnexusStatus.stdout) || firstLine(gitnexusStatus.stderr) || `exit ${gitnexusStatus.exitCode}`
|
|
1087
|
-
};
|
|
1075
|
+
|
|
1076
|
+
// src/templates/karpathy-skills.ts
|
|
1077
|
+
var KARPATHY_SKILLS_SOURCE = "https://github.com/forrestchang/andrej-karpathy-skills";
|
|
1078
|
+
var BEGIN = "<!-- FET:BEGIN ANDREJ-KARPATHY-SKILLS -->";
|
|
1079
|
+
var END = "<!-- FET:END ANDREJ-KARPATHY-SKILLS -->";
|
|
1080
|
+
function mergeKarpathyClaudeMd(existing, language = "zh-CN") {
|
|
1081
|
+
const block = renderManagedBlock(renderKarpathyClaudeGuidelines(language));
|
|
1082
|
+
if (!existing || !existing.trim()) {
|
|
1083
|
+
return `${block}
|
|
1084
|
+
`;
|
|
1088
1085
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1086
|
+
const start = existing.indexOf(BEGIN);
|
|
1087
|
+
const end = existing.indexOf(END);
|
|
1088
|
+
if (start !== -1 && end !== -1 && end > start) {
|
|
1089
|
+
return `${existing.slice(0, start)}${block}${existing.slice(end + END.length)}`;
|
|
1092
1090
|
}
|
|
1093
|
-
return {
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
command: gitnexusStatus.command,
|
|
1098
|
-
stdout: gitnexusStatus.stdout.trim(),
|
|
1099
|
-
stderr: gitnexusStatus.stderr.trim()
|
|
1100
|
-
} : null
|
|
1101
|
-
};
|
|
1091
|
+
return `${existing.replace(/\s*$/, "")}
|
|
1092
|
+
|
|
1093
|
+
${block}
|
|
1094
|
+
`;
|
|
1102
1095
|
}
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1096
|
+
function renderKarpathyCursorRule(language = "zh-CN") {
|
|
1097
|
+
return `<!-- FET:MANAGED
|
|
1098
|
+
schemaVersion: 1
|
|
1099
|
+
generator: karpathy-skills
|
|
1100
|
+
FET:END -->
|
|
1101
|
+
|
|
1102
|
+
---
|
|
1103
|
+
description: ${language === "en" ? "Andrej Karpathy inspired coding guidelines" : "\u53D7 Andrej Karpathy \u542F\u53D1\u7684\u7F16\u7801\u6307\u5357"}
|
|
1104
|
+
alwaysApply: true
|
|
1105
|
+
---
|
|
1106
|
+
|
|
1107
|
+
${renderKarpathyGuidelinesBody(language)}
|
|
1108
|
+
`;
|
|
1106
1109
|
}
|
|
1107
|
-
function
|
|
1110
|
+
function renderKarpathyFetHandoff(language = "zh-CN") {
|
|
1108
1111
|
return `<!-- FET:MANAGED
|
|
1109
1112
|
schemaVersion: 1
|
|
1110
|
-
generator:
|
|
1113
|
+
generator: karpathy-skills
|
|
1111
1114
|
FET:END -->
|
|
1112
1115
|
|
|
1113
|
-
#
|
|
1116
|
+
# ${language === "en" ? "Andrej Karpathy Inspired Coding Guidelines" : "\u53D7 Andrej Karpathy \u542F\u53D1\u7684\u7F16\u7801\u6307\u5357"}
|
|
1114
1117
|
|
|
1115
|
-
|
|
1118
|
+
${renderKarpathyGuidelinesBody(language)}
|
|
1119
|
+
`;
|
|
1120
|
+
}
|
|
1121
|
+
function renderManagedBlock(content) {
|
|
1122
|
+
return `${BEGIN}
|
|
1123
|
+
${content}
|
|
1124
|
+
${END}`;
|
|
1125
|
+
}
|
|
1126
|
+
function renderKarpathyClaudeGuidelines(language) {
|
|
1127
|
+
return `# ${language === "en" ? "Andrej Karpathy Inspired Coding Guidelines" : "\u53D7 Andrej Karpathy \u542F\u53D1\u7684\u7F16\u7801\u6307\u5357"}
|
|
1116
1128
|
|
|
1117
|
-
|
|
1129
|
+
${renderKarpathyGuidelinesBody(language)}`;
|
|
1130
|
+
}
|
|
1131
|
+
function renderKarpathyGuidelinesBody(language = "zh-CN") {
|
|
1132
|
+
if (language === "en") {
|
|
1133
|
+
return renderKarpathyGuidelinesBodyEn();
|
|
1134
|
+
}
|
|
1135
|
+
return `\u6765\u6E90\uFF1A${KARPATHY_SKILLS_SOURCE}
|
|
1118
1136
|
|
|
1119
|
-
|
|
1120
|
-
- Executable: ${state.executablePath ?? "gitnexus"}
|
|
1121
|
-
- Version: ${state.version ?? "unknown"}
|
|
1122
|
-
- Graph path: ${state.graphPath ?? ".gitnexus"}
|
|
1123
|
-
- Graph exists: ${state.graphExists ? "yes" : "no"}
|
|
1137
|
+
\u5C06\u8FD9\u4E9B\u9879\u76EE\u7EA7\u6307\u5357\u4E0E AGENTS.md\u3001OpenSpec \u4EA7\u7269\u548C\u7528\u6237\u6700\u65B0\u8BF7\u6C42\u4E00\u8D77\u4F7F\u7528\u3002
|
|
1124
1138
|
|
|
1125
|
-
|
|
1139
|
+
## \u7F16\u7801\u524D\u5148\u601D\u8003
|
|
1126
1140
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1141
|
+
- \u7F16\u8F91\u524D\u8BF4\u660E\u91CD\u8981\u5047\u8BBE\u3002
|
|
1142
|
+
- \u5F53\u6B67\u4E49\u4F1A\u6539\u53D8\u5B9E\u73B0\u65F6\uFF0C\u5148\u6F84\u6E05\u3002
|
|
1143
|
+
- \u4E3B\u52A8\u5448\u73B0\u53D6\u820D\uFF0C\u4E0D\u8981\u9759\u9ED8\u9009\u62E9\u9AD8\u98CE\u9669\u8DEF\u5F84\u3002
|
|
1144
|
+
- \u5F53\u66F4\u7B80\u5355\u7684\u65B9\u6848\u66F4\u9002\u5408\u8BF7\u6C42\u65F6\uFF0C\u660E\u786E\u63D0\u51FA\u3002
|
|
1131
1145
|
|
|
1132
|
-
|
|
1146
|
+
## \u7B80\u6D01\u4F18\u5148
|
|
1133
1147
|
|
|
1134
|
-
-
|
|
1135
|
-
-
|
|
1136
|
-
-
|
|
1137
|
-
|
|
1148
|
+
- \u7528\u6700\u5C0F\u4E14\u6E05\u6670\u7684\u6539\u52A8\u89E3\u51B3\u8BF7\u6C42\u7684\u95EE\u9898\u3002
|
|
1149
|
+
- \u907F\u514D\u731C\u6D4B\u6027\u529F\u80FD\u3001\u914D\u7F6E\u6216\u62BD\u8C61\u3002
|
|
1150
|
+
- \u4E0D\u8981\u4E3A\u4E00\u6B21\u6027\u4EE3\u7801\u521B\u5EFA\u62BD\u8C61\u3002
|
|
1151
|
+
- \u4F18\u5148\u5220\u9664\u81EA\u5DF1\u6539\u52A8\u5F15\u5165\u7684\u590D\u6742\u5EA6\uFF0C\u800C\u4E0D\u662F\u7EE7\u7EED\u5806\u7ED3\u6784\u3002
|
|
1152
|
+
|
|
1153
|
+
## \u7CBE\u51C6\u7F16\u8F91
|
|
1154
|
+
|
|
1155
|
+
- \u53EA\u4FEE\u6539\u76F4\u63A5\u670D\u52A1\u4E8E\u4EFB\u52A1\u7684\u6587\u4EF6\u548C\u884C\u3002
|
|
1156
|
+
- \u4FDD\u6301\u73B0\u6709\u98CE\u683C\uFF0C\u5373\u4FBF\u4F60\u4E2A\u4EBA\u66F4\u504F\u597D\u53E6\u4E00\u79CD\u6A21\u5F0F\u3002
|
|
1157
|
+
- \u9664\u975E\u4EFB\u52A1\u9700\u8981\uFF0C\u4E0D\u8981\u987A\u624B\u91CD\u6784\u9644\u8FD1\u4EE3\u7801\u3001\u6CE8\u91CA\u6216\u683C\u5F0F\u3002
|
|
1158
|
+
- \u53EA\u79FB\u9664\u56E0\u81EA\u5DF1\u6539\u52A8\u800C\u8FC7\u65F6\u7684\u6B7B\u5BFC\u5165\u3001\u53D8\u91CF\u6216 helper\u3002
|
|
1159
|
+
|
|
1160
|
+
## \u76EE\u6807\u9A71\u52A8\u6267\u884C
|
|
1161
|
+
|
|
1162
|
+
- \u628A\u6A21\u7CCA\u5DE5\u4F5C\u8F6C\u6210\u5177\u4F53\u6210\u529F\u6807\u51C6\u3002
|
|
1163
|
+
- \u5BF9 bug\uFF0C\u4F18\u5148\u51C6\u5907\u590D\u73B0\u6D4B\u8BD5\u6216\u6E05\u6670\u9A8C\u8BC1\uFF0C\u518D\u505A\u4FEE\u590D\u3002
|
|
1164
|
+
- \u5BF9\u591A\u6B65\u9AA4\u5DE5\u4F5C\uFF0C\u4FDD\u7559\u7B80\u77ED\u8BA1\u5212\u5E76\u9A8C\u8BC1\u6BCF\u4E2A\u6709\u610F\u4E49\u7684\u6B65\u9AA4\u3002
|
|
1165
|
+
- \u6301\u7EED\u8FED\u4EE3\uFF0C\u76F4\u5230\u8FBE\u5230\u6210\u529F\u6807\u51C6\u6216\u660E\u786E\u9047\u5230\u963B\u585E\u3002
|
|
1166
|
+
|
|
1167
|
+
\u8FD9\u4E9B\u6307\u5357\u5728\u975E\u5E73\u51E1\u5DE5\u4F5C\u4E2D\u523B\u610F\u504F\u5411\u8C28\u614E\u800C\u4E0D\u662F\u901F\u5EA6\u3002\u660E\u663E\u7684\u4E00\u884C\u4FEE\u590D\u53EF\u4EE5\u7075\u6D3B\u5224\u65AD\uFF0C\u4FDD\u6301\u8F7B\u91CF\u3002`;
|
|
1138
1168
|
}
|
|
1139
|
-
function
|
|
1140
|
-
return
|
|
1169
|
+
function renderKarpathyGuidelinesBodyEn() {
|
|
1170
|
+
return `Source: ${KARPATHY_SKILLS_SOURCE}
|
|
1171
|
+
|
|
1172
|
+
Use these project-level guidelines together with AGENTS.md, OpenSpec artifacts, and the user's latest request.
|
|
1173
|
+
|
|
1174
|
+
## Think Before Coding
|
|
1175
|
+
|
|
1176
|
+
- State important assumptions before editing.
|
|
1177
|
+
- Ask for clarification when ambiguity would change the implementation.
|
|
1178
|
+
- Surface tradeoffs instead of silently choosing a risky path.
|
|
1179
|
+
- Push back when a simpler approach better fits the request.
|
|
1180
|
+
|
|
1181
|
+
## Simplicity First
|
|
1182
|
+
|
|
1183
|
+
- Solve the requested problem with the smallest clear change.
|
|
1184
|
+
- Avoid speculative features, configuration, or abstraction.
|
|
1185
|
+
- Do not create abstractions for one-off code.
|
|
1186
|
+
- Prefer deleting complexity introduced by your own change over adding more structure.
|
|
1187
|
+
|
|
1188
|
+
## Precise Edits
|
|
1189
|
+
|
|
1190
|
+
- Touch only files and lines that directly serve the task.
|
|
1191
|
+
- Preserve existing style even when you personally prefer another pattern.
|
|
1192
|
+
- Do not refactor nearby code, comments, or formatting unless the task requires it.
|
|
1193
|
+
- Remove only dead imports, variables, or helpers made obsolete by your own change.
|
|
1194
|
+
|
|
1195
|
+
## Goal-Driven Execution
|
|
1196
|
+
|
|
1197
|
+
- Convert vague work into concrete success criteria.
|
|
1198
|
+
- For bugs, prefer a reproducing test or clear verification before the fix.
|
|
1199
|
+
- For multi-step work, keep a short plan and verify each meaningful step.
|
|
1200
|
+
- Continue iterating until the success criteria are met or a blocker is explicit.
|
|
1201
|
+
|
|
1202
|
+
These guidelines intentionally favor caution over speed for non-trivial work. For obvious one-line fixes, use judgment and stay lightweight.`;
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
// src/templates/verify-instructions.ts
|
|
1206
|
+
function renderVerifyInstructions(changeId, generatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
1207
|
+
return `---
|
|
1141
1208
|
schemaVersion: 1
|
|
1142
|
-
|
|
1143
|
-
|
|
1209
|
+
fetVersion: ${FET_VERSION}
|
|
1210
|
+
generatedAt: ${generatedAt}
|
|
1211
|
+
changeId: ${changeId}
|
|
1212
|
+
purpose: manual-verify
|
|
1213
|
+
---
|
|
1144
1214
|
|
|
1145
|
-
#
|
|
1215
|
+
# Verify Instructions
|
|
1146
1216
|
|
|
1147
|
-
|
|
1217
|
+
\u8BF7\u6309\u987A\u5E8F\u5B8C\u6210\u4EE5\u4E0B\u68C0\u67E5\uFF1A
|
|
1148
1218
|
|
|
1149
|
-
|
|
1219
|
+
1. \u8FD0\u884C OpenSpec \u89C4\u8303\u6821\u9A8C\uFF1A\`openspec verify\`
|
|
1220
|
+
2. \u6309\u9879\u76EE\u7EA6\u5B9A\u8FD0\u884C lint\u3001typecheck\u3001test\u3002
|
|
1221
|
+
3. \u68C0\u67E5\u672C\u6B21 change \u7684 \`tasks.md\` \u662F\u5426\u4E0E\u5B9E\u73B0\u72B6\u6001\u4E00\u81F4\u3002
|
|
1150
1222
|
|
|
1151
|
-
|
|
1152
|
-
- Graph path: ${state.graphPath ?? ".gitnexus"}
|
|
1153
|
-
- Graph exists: ${state.graphExists ? "yes" : "no"}
|
|
1154
|
-
- Last indexed at: ${state.lastIndexedAt ?? "unknown"}
|
|
1155
|
-
- Last status: ${state.lastStatus ?? "unknown"}
|
|
1223
|
+
\u5B8C\u6210\u540E\u8FD0\u884C\uFF1A
|
|
1156
1224
|
|
|
1157
|
-
|
|
1225
|
+
\`\`\`sh
|
|
1226
|
+
fet verify --done --change ${changeId}
|
|
1227
|
+
\`\`\`
|
|
1228
|
+
`;
|
|
1229
|
+
}
|
|
1158
1230
|
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1231
|
+
// src/templates/gitignore.ts
|
|
1232
|
+
var BEGIN2 = "# FET:BEGIN LOCAL STATE";
|
|
1233
|
+
var END2 = "# FET:END LOCAL STATE";
|
|
1234
|
+
var RULES = [
|
|
1235
|
+
"openspec/fet-state.json",
|
|
1236
|
+
"openspec/.fet.lock",
|
|
1237
|
+
"openspec/.fet-init-journal.json",
|
|
1238
|
+
"openspec/changes/*/fet-state.json",
|
|
1239
|
+
"openspec/changes/*/.fet/",
|
|
1240
|
+
".gitnexus/"
|
|
1241
|
+
];
|
|
1242
|
+
function mergeGitignore(existing) {
|
|
1243
|
+
const block = `${BEGIN2}
|
|
1244
|
+
${RULES.join("\n")}
|
|
1245
|
+
${END2}`;
|
|
1246
|
+
if (!existing || !existing.trim()) {
|
|
1247
|
+
return `${block}
|
|
1248
|
+
`;
|
|
1249
|
+
}
|
|
1250
|
+
const start = existing.indexOf(BEGIN2);
|
|
1251
|
+
const end = existing.indexOf(END2);
|
|
1252
|
+
if (start !== -1 && end !== -1 && end > start) {
|
|
1253
|
+
return `${existing.slice(0, start)}${block}${existing.slice(end + END2.length)}`;
|
|
1254
|
+
}
|
|
1255
|
+
return `${existing.replace(/\s*$/, "")}
|
|
1163
1256
|
|
|
1164
|
-
|
|
1257
|
+
${block}
|
|
1258
|
+
`;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// src/commands/update-context.ts
|
|
1262
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
1263
|
+
import { join as join10 } from "path";
|
|
1264
|
+
|
|
1265
|
+
// src/config/yaml.ts
|
|
1266
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
1267
|
+
import { parseDocument } from "yaml";
|
|
1268
|
+
async function mergeFetConfig(configPath, renderedFetYaml) {
|
|
1269
|
+
const fetDoc = parseDocument(renderedFetYaml);
|
|
1270
|
+
const nextFet = fetDoc.get("fet", true);
|
|
1271
|
+
let existing = "";
|
|
1272
|
+
try {
|
|
1273
|
+
existing = await readFile5(configPath, "utf8");
|
|
1274
|
+
} catch {
|
|
1275
|
+
return renderedFetYaml;
|
|
1276
|
+
}
|
|
1277
|
+
const doc = parseDocument(existing || "{}");
|
|
1278
|
+
doc.set("fet", nextFet);
|
|
1279
|
+
return doc.toString();
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// src/commands/update-context.ts
|
|
1283
|
+
async function updateContextCommand(ctx) {
|
|
1284
|
+
let contextResult = { warnings: [] };
|
|
1285
|
+
await withProjectLock(ctx.projectRoot, { command: "update-context", cwd: ctx.cwd, fetVersion: ctx.fetVersion }, async () => {
|
|
1286
|
+
contextResult = await updateContextFiles(ctx);
|
|
1287
|
+
});
|
|
1288
|
+
ctx.output.result({
|
|
1289
|
+
ok: true,
|
|
1290
|
+
command: "update-context",
|
|
1291
|
+
summary: ctx.language === "en" ? "Updated FET-managed regions in AGENTS.md and openspec/config.yaml." : "\u5DF2\u66F4\u65B0 AGENTS.md \u4E0E openspec/config.yaml \u4E2D\u7684 FET \u6258\u7BA1\u533A\u57DF\u3002",
|
|
1292
|
+
warnings: contextResult.warnings
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
async function updateContextFiles(ctx) {
|
|
1296
|
+
const scan = await ctx.scanner.scan(ctx.projectRoot, {});
|
|
1297
|
+
const agentsPath = join10(ctx.projectRoot, "AGENTS.md");
|
|
1298
|
+
const configPath = join10(ctx.projectRoot, "openspec", "config.yaml");
|
|
1299
|
+
const claudePath = join10(ctx.projectRoot, "CLAUDE.md");
|
|
1300
|
+
const karpathyHandoffPath = join10(ctx.projectRoot, ".fet", "karpathy-guidelines.md");
|
|
1301
|
+
const karpathyCursorPath = join10(ctx.projectRoot, ".cursor", "rules", "karpathy-guidelines.mdc");
|
|
1302
|
+
const existingAgents = await readOptional(agentsPath);
|
|
1303
|
+
const existingClaude = await readOptional(claudePath);
|
|
1304
|
+
const existingKarpathyCursor = await readOptional(karpathyCursorPath);
|
|
1305
|
+
const warnings = [...scan.warnings];
|
|
1306
|
+
if (existingAgents && hasInvalidManagedAutoRegion(existingAgents)) {
|
|
1307
|
+
throw new FetError({
|
|
1308
|
+
code: "CONFIG_INVALID" /* ConfigInvalid */,
|
|
1309
|
+
message: ctx.language === "en" ? "AGENTS.md FET managed markers are broken or duplicated." : "AGENTS.md \u7684 FET \u6258\u7BA1\u6807\u8BB0\u635F\u574F\u6216\u91CD\u590D\u3002",
|
|
1310
|
+
details: { path: "AGENTS.md" },
|
|
1311
|
+
suggestedCommand: ctx.language === "en" ? "Manually repair FET:BEGIN AUTO / FET:END AUTO markers, then rerun." : "\u624B\u52A8\u4FEE\u590D FET:BEGIN AUTO / FET:END AUTO \u6807\u8BB0\u540E\u91CD\u8BD5\u3002"
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
if (existingAgents && !hasManagedAutoRegion(existingAgents)) {
|
|
1315
|
+
if (!ctx.yes) {
|
|
1316
|
+
throw new FetError({
|
|
1317
|
+
code: "TOOL_ADAPTER_CONFLICT" /* ToolAdapterConflict */,
|
|
1318
|
+
message: ctx.language === "en" ? "AGENTS.md already exists and does not contain a FET-managed region." : "AGENTS.md \u5DF2\u5B58\u5728\uFF0C\u4E14\u4E0D\u5305\u542B FET \u6258\u7BA1\u533A\u57DF\u3002",
|
|
1319
|
+
details: { path: "AGENTS.md" },
|
|
1320
|
+
suggestedCommand: ctx.command === "init" ? ctx.language === "en" ? "Confirm it can be backed up and replaced, then run fet init --yes." : "\u786E\u8BA4\u53EF\u5907\u4EFD\u5E76\u66FF\u6362\u540E\u8FD0\u884C fet init --yes\u3002" : ctx.language === "en" ? "Confirm it can be backed up and replaced, then run fet update-context --yes." : "\u786E\u8BA4\u53EF\u5907\u4EFD\u5E76\u66FF\u6362\u540E\u8FD0\u884C fet update-context --yes\u3002"
|
|
1321
|
+
});
|
|
1322
|
+
}
|
|
1323
|
+
const backupPath = await createBackup(agentsPath);
|
|
1324
|
+
if (backupPath) {
|
|
1325
|
+
warnings.push(ctx.language === "en" ? `Backed up unmanaged AGENTS.md to ${backupPath}` : `\u5DF2\u5C06\u975E\u6258\u7BA1 AGENTS.md \u5907\u4EFD\u5230 ${backupPath}`);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
await atomicWrite(agentsPath, replaceManagedRegion(existingAgents, renderAgentsMd(scan, ctx.language)));
|
|
1329
|
+
await atomicWrite(configPath, await mergeFetConfig(configPath, renderFetConfig(scan, ctx.language)));
|
|
1330
|
+
await atomicWrite(claudePath, mergeKarpathyClaudeMd(existingClaude, ctx.language));
|
|
1331
|
+
await atomicWrite(karpathyHandoffPath, renderKarpathyFetHandoff(ctx.language));
|
|
1332
|
+
if (!existingKarpathyCursor || existingKarpathyCursor.includes("FET:MANAGED")) {
|
|
1333
|
+
await atomicWrite(karpathyCursorPath, renderKarpathyCursorRule(ctx.language));
|
|
1334
|
+
} else {
|
|
1335
|
+
warnings.push(
|
|
1336
|
+
ctx.language === "en" ? ".cursor/rules/karpathy-guidelines.mdc exists and is not managed by FET; leaving it unchanged." : ".cursor/rules/karpathy-guidelines.mdc \u5DF2\u5B58\u5728\u4E14\u4E0D\u7531 FET \u6258\u7BA1\uFF0C\u5DF2\u4FDD\u6301\u4E0D\u53D8\u3002"
|
|
1337
|
+
);
|
|
1338
|
+
}
|
|
1339
|
+
const placeholderCount = await countAgentsLlmPlaceholders(ctx.projectRoot);
|
|
1340
|
+
if (placeholderCount > 0) {
|
|
1341
|
+
warnings.push(renderAgentsPlaceholderWarning(placeholderCount, ctx.language));
|
|
1342
|
+
}
|
|
1343
|
+
const state = await ctx.stateStore.getOrCreateGlobal();
|
|
1344
|
+
state.context = {
|
|
1345
|
+
agentsMdUpdatedAt: scan.generatedAt,
|
|
1346
|
+
configUpdatedAt: scan.generatedAt,
|
|
1347
|
+
scannerVersion: scan.scannerVersion
|
|
1348
|
+
};
|
|
1349
|
+
await ctx.stateStore.writeGlobal(state);
|
|
1350
|
+
return { warnings };
|
|
1351
|
+
}
|
|
1352
|
+
async function readOptional(path) {
|
|
1353
|
+
try {
|
|
1354
|
+
return await readFile6(path, "utf8");
|
|
1355
|
+
} catch {
|
|
1356
|
+
return null;
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1165
1359
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1360
|
+
// src/commands/init.ts
|
|
1361
|
+
async function initCommand(ctx) {
|
|
1362
|
+
const alreadyInitialized = await exists2(join11(ctx.projectRoot, "openspec", "config.yaml"));
|
|
1363
|
+
let warnings = [];
|
|
1364
|
+
await withProjectLock(ctx.projectRoot, { command: "init", cwd: ctx.cwd, fetVersion: ctx.fetVersion }, async () => {
|
|
1365
|
+
const journal = createInitJournal(ctx.fetVersion);
|
|
1366
|
+
await writeInitJournal(ctx.projectRoot, journal);
|
|
1367
|
+
const identity = await ctx.openSpec.resolveExecutable();
|
|
1368
|
+
if (!alreadyInitialized) {
|
|
1369
|
+
const result = await ctx.openSpec.run("init", ["--tools", "none"], { cwd: ctx.projectRoot, stdio: "inherit" });
|
|
1370
|
+
if (result.exitCode !== 0) {
|
|
1371
|
+
process.exitCode = result.exitCode;
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
const contextResult = await updateContextFiles(ctx);
|
|
1376
|
+
warnings = contextResult.warnings;
|
|
1377
|
+
await ensureGitignore(ctx);
|
|
1378
|
+
const state = await ctx.stateStore.getOrCreateGlobal();
|
|
1379
|
+
state.openspec = identity;
|
|
1380
|
+
state.language = {
|
|
1381
|
+
current: ctx.language,
|
|
1382
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1383
|
+
};
|
|
1384
|
+
state.graph ??= {};
|
|
1385
|
+
const gitnexus = mergeGitNexusGraphInfo(toGitNexusState(await detectGitNexus(), state.graph.gitnexus), await inspectGitNexusGraph(ctx.projectRoot));
|
|
1386
|
+
if (!gitnexus.installed && !gitnexus.recommendationShownAt) {
|
|
1387
|
+
warnings.push(renderGitNexusRecommendation(gitnexus, ctx.language));
|
|
1388
|
+
gitnexus.recommendationShownAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1389
|
+
}
|
|
1390
|
+
state.graph.gitnexus = gitnexus;
|
|
1391
|
+
for (const adapter of ctx.toolAdapters) {
|
|
1392
|
+
const plan = await adapter.planInstall(ctx.projectRoot, ctx.language);
|
|
1393
|
+
const result = await adapter.install(ctx.projectRoot, plan, ctx.yes);
|
|
1394
|
+
state.toolAdapters[adapter.tool] = {
|
|
1395
|
+
adapterVersion: adapter.adapterVersion,
|
|
1396
|
+
installed: true,
|
|
1397
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1398
|
+
};
|
|
1399
|
+
journal.steps.push(...result.written.map((path) => ({ operation: "write", path, status: "done" })));
|
|
1400
|
+
}
|
|
1401
|
+
journal.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1402
|
+
await writeInitJournal(ctx.projectRoot, journal);
|
|
1403
|
+
await ctx.stateStore.writeGlobal(state);
|
|
1404
|
+
});
|
|
1405
|
+
ctx.output.result({
|
|
1406
|
+
ok: true,
|
|
1407
|
+
command: "init",
|
|
1408
|
+
summary: ctx.language === "en" ? "FET initialization completed." : "FET \u521D\u59CB\u5316\u5B8C\u6210\u3002",
|
|
1409
|
+
warnings,
|
|
1410
|
+
nextSteps: ctx.language === "en" ? ["Use fet propose/new to create an OpenSpec change", "Use fet doctor to check project health"] : ["\u4F7F\u7528 fet propose/new \u521B\u5EFA OpenSpec change", "\u4F7F\u7528 fet doctor \u68C0\u67E5\u9879\u76EE\u72B6\u6001"]
|
|
1411
|
+
});
|
|
1170
1412
|
}
|
|
1171
|
-
function
|
|
1172
|
-
|
|
1413
|
+
async function ensureGitignore(ctx) {
|
|
1414
|
+
const gitignorePath = join11(ctx.projectRoot, ".gitignore");
|
|
1415
|
+
const existing = await readOptional2(gitignorePath);
|
|
1416
|
+
await atomicWrite(gitignorePath, mergeGitignore(existing));
|
|
1417
|
+
}
|
|
1418
|
+
async function readOptional2(path) {
|
|
1419
|
+
try {
|
|
1420
|
+
return await readFile7(path, "utf8");
|
|
1421
|
+
} catch {
|
|
1422
|
+
return null;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
async function exists2(path) {
|
|
1426
|
+
try {
|
|
1427
|
+
await stat4(path);
|
|
1428
|
+
return true;
|
|
1429
|
+
} catch {
|
|
1430
|
+
return false;
|
|
1431
|
+
}
|
|
1173
1432
|
}
|
|
1174
1433
|
|
|
1175
1434
|
// src/commands/proxy.ts
|
|
@@ -1206,6 +1465,22 @@ async function git(cwd, args) {
|
|
|
1206
1465
|
import { mkdir as mkdir5, readFile as readFile8 } from "fs/promises";
|
|
1207
1466
|
import { join as join12 } from "path";
|
|
1208
1467
|
|
|
1468
|
+
// src/language.ts
|
|
1469
|
+
var DEFAULT_LANGUAGE = "zh-CN";
|
|
1470
|
+
function normalizeLanguage(value) {
|
|
1471
|
+
const normalized = value?.trim().toLowerCase();
|
|
1472
|
+
if (!normalized) {
|
|
1473
|
+
return DEFAULT_LANGUAGE;
|
|
1474
|
+
}
|
|
1475
|
+
if (["en", "en-us", "english"].includes(normalized)) {
|
|
1476
|
+
return "en";
|
|
1477
|
+
}
|
|
1478
|
+
return DEFAULT_LANGUAGE;
|
|
1479
|
+
}
|
|
1480
|
+
function languageInstruction(language) {
|
|
1481
|
+
return language === "en" ? "Use English for FET interaction messages, generated handoff documents, and IDE prompt output unless the user asks otherwise." : "\u9664\u975E\u7528\u6237\u53E6\u6709\u660E\u786E\u8981\u6C42\uFF0CFET \u7684\u4EA4\u4E92\u4FE1\u606F\u3001\u751F\u6210\u7684\u4EA4\u63A5\u6587\u6863\u548C IDE \u63D0\u793A\u4EA7\u51FA\u5747\u4F7F\u7528\u4E2D\u6587\u3002";
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1209
1484
|
// src/state/schema.ts
|
|
1210
1485
|
var phases = ["explore", "propose", "implement", "verify", "sync", "archive"];
|
|
1211
1486
|
function createGlobalState(fetVersion, project) {
|
|
@@ -1216,6 +1491,10 @@ function createGlobalState(fetVersion, project) {
|
|
|
1216
1491
|
createdAt: now,
|
|
1217
1492
|
updatedAt: now,
|
|
1218
1493
|
project,
|
|
1494
|
+
language: {
|
|
1495
|
+
current: DEFAULT_LANGUAGE,
|
|
1496
|
+
updatedAt: now
|
|
1497
|
+
},
|
|
1219
1498
|
openspec: null,
|
|
1220
1499
|
activeChangeId: null,
|
|
1221
1500
|
openChangeIds: [],
|
|
@@ -1239,9 +1518,7 @@ function createChangeState(fetVersion, changeId, phase) {
|
|
|
1239
1518
|
createdAt: now,
|
|
1240
1519
|
updatedAt: now,
|
|
1241
1520
|
currentPhase: phase,
|
|
1242
|
-
phases: Object.fromEntries(
|
|
1243
|
-
phases.map((item) => [item, { status: item === phase ? "in_progress" : "not_started" }])
|
|
1244
|
-
),
|
|
1521
|
+
phases: Object.fromEntries(phases.map((item) => [item, { status: item === phase ? "in_progress" : "not_started" }])),
|
|
1245
1522
|
tasks: {
|
|
1246
1523
|
source: "tasks.md",
|
|
1247
1524
|
completedIds: [],
|
|
@@ -1256,6 +1533,12 @@ function assertGlobalState(value) {
|
|
|
1256
1533
|
if (!isRecord(value) || value.schemaVersion !== 1) {
|
|
1257
1534
|
throw unsupportedSchema("\u5168\u5C40\u72B6\u6001 schema \u4E0D\u53D7\u652F\u6301");
|
|
1258
1535
|
}
|
|
1536
|
+
if (!isRecord(value.language)) {
|
|
1537
|
+
value.language = {
|
|
1538
|
+
current: DEFAULT_LANGUAGE,
|
|
1539
|
+
updatedAt: typeof value.updatedAt === "string" ? value.updatedAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1259
1542
|
if (typeof value.fetVersion !== "string" || !isRecord(value.project)) {
|
|
1260
1543
|
throw corruptedState("\u5168\u5C40\u72B6\u6001\u7F3A\u5C11\u5FC5\u586B\u5B57\u6BB5");
|
|
1261
1544
|
}
|
|
@@ -1537,14 +1820,16 @@ function stripFetOptions(args) {
|
|
|
1537
1820
|
async function mapOpenSpecCommand(ctx, command, args) {
|
|
1538
1821
|
switch (command) {
|
|
1539
1822
|
case "propose":
|
|
1540
|
-
case "continue":
|
|
1541
|
-
case "ff":
|
|
1542
|
-
case "apply":
|
|
1543
|
-
case "sync":
|
|
1544
1823
|
case "bulk-archive":
|
|
1545
1824
|
case "explore":
|
|
1546
1825
|
case "onboard":
|
|
1547
1826
|
return { command, args: withGlobalChange(ctx, args) };
|
|
1827
|
+
case "continue":
|
|
1828
|
+
case "ff":
|
|
1829
|
+
return { command, args: await withDefaultChange(ctx, args, true) };
|
|
1830
|
+
case "apply":
|
|
1831
|
+
case "sync":
|
|
1832
|
+
return { command, args: await withDefaultChange(ctx, args) };
|
|
1548
1833
|
case "new":
|
|
1549
1834
|
return { command: "new", args: args[0] === "change" ? args : ["change", ...args] };
|
|
1550
1835
|
case "archive":
|
|
@@ -1568,6 +1853,18 @@ async function mapOpenSpecCommand(ctx, command, args) {
|
|
|
1568
1853
|
function withGlobalChange(ctx, args) {
|
|
1569
1854
|
return ctx.changeId ? ["--change", ctx.changeId, ...args] : args;
|
|
1570
1855
|
}
|
|
1856
|
+
async function withDefaultChange(ctx, args, allowWithArgs = false) {
|
|
1857
|
+
if (ctx.changeId) {
|
|
1858
|
+
return ["--change", ctx.changeId, ...args];
|
|
1859
|
+
}
|
|
1860
|
+
if (args.includes("--change") || args.some((arg) => arg.startsWith("--change="))) {
|
|
1861
|
+
return args;
|
|
1862
|
+
}
|
|
1863
|
+
if (args.length > 0 && !allowWithArgs) {
|
|
1864
|
+
return args;
|
|
1865
|
+
}
|
|
1866
|
+
return ["--change", await requireChangeId(ctx), ...args];
|
|
1867
|
+
}
|
|
1571
1868
|
async function requireChangeId(ctx) {
|
|
1572
1869
|
if (ctx.changeId) {
|
|
1573
1870
|
return ctx.changeId;
|
|
@@ -1813,8 +2110,18 @@ function detectCurrentModel(env = process.env) {
|
|
|
1813
2110
|
function isHighCostModel(model) {
|
|
1814
2111
|
return HIGH_COST_MODEL_PATTERNS.some((pattern) => pattern.test(model));
|
|
1815
2112
|
}
|
|
2113
|
+
function getModelPolicyMode(env = process.env) {
|
|
2114
|
+
const value = env.FET_MODEL_POLICY?.trim().toLowerCase();
|
|
2115
|
+
if (value === "off" || env.FET_SKIP_MODEL_POLICY === "1") {
|
|
2116
|
+
return "off";
|
|
2117
|
+
}
|
|
2118
|
+
if (value === "warn") {
|
|
2119
|
+
return "warn";
|
|
2120
|
+
}
|
|
2121
|
+
return "confirm";
|
|
2122
|
+
}
|
|
1816
2123
|
function getCommandModelPolicyMismatch(command, env = process.env) {
|
|
1817
|
-
if (env
|
|
2124
|
+
if (getModelPolicyMode(env) === "off") {
|
|
1818
2125
|
return null;
|
|
1819
2126
|
}
|
|
1820
2127
|
const detected = detectCurrentModel(env);
|
|
@@ -1843,15 +2150,26 @@ function getCommandModelPolicyMismatch(command, env = process.env) {
|
|
|
1843
2150
|
}
|
|
1844
2151
|
return null;
|
|
1845
2152
|
}
|
|
1846
|
-
function formatModelPolicyMismatch(mismatch) {
|
|
1847
|
-
|
|
1848
|
-
|
|
2153
|
+
function formatModelPolicyMismatch(mismatch, language = "zh-CN") {
|
|
2154
|
+
if (language === "en") {
|
|
2155
|
+
const switchHint2 = mismatch.recommended === "high-cost" ? "Recommended models include GPT-5.5, GLM-5.1, GLM-5, Claude Opus, or Claude Sonnet." : "Recommended action: switch to a lower-cost model and reserve high-cost models for fet apply.";
|
|
2156
|
+
return `${mismatch.reason} Detected ${mismatch.detected.source}="${mismatch.detected.name}". ${switchHint2}`;
|
|
2157
|
+
}
|
|
2158
|
+
const switchHint = mismatch.recommended === "high-cost" ? "\u5EFA\u8BAE\u6A21\u578B\u5305\u62EC GPT-5.5\u3001GLM-5.1\u3001GLM-5\u3001Claude Opus \u6216 Claude Sonnet\u3002" : "\u5EFA\u8BAE\u5207\u6362\u5230\u4F4E\u6210\u672C\u6A21\u578B\uFF0C\u628A\u9AD8\u6210\u672C\u6A21\u578B\u7559\u7ED9 fet apply\u3002";
|
|
2159
|
+
const reason = mismatch.recommended === "high-cost" ? "fet apply \u5C5E\u4E8E\u5B9E\u65BD\u9636\u6BB5\uFF0C\u5EFA\u8BAE\u4F7F\u7528\u9AD8\u80FD\u529B/\u9AD8\u6210\u672C\u6A21\u578B\u3002" : `fet ${mismatch.command} \u4E0D\u662F\u5B9E\u65BD\u9636\u6BB5\uFF0C\u5EFA\u8BAE\u4F7F\u7528\u4F4E\u6210\u672C\u6A21\u578B\u3002`;
|
|
2160
|
+
return `${reason} \u68C0\u6D4B\u5230 ${mismatch.detected.source}="${mismatch.detected.name}"\u3002${switchHint}`;
|
|
1849
2161
|
}
|
|
1850
|
-
function renderIdeModelPolicy(command) {
|
|
2162
|
+
function renderIdeModelPolicy(command, language = "zh-CN") {
|
|
2163
|
+
if (language === "en") {
|
|
2164
|
+
if (command === "apply") {
|
|
2165
|
+
return "Model policy: this command is recommended to run with a high-capability/high-cost model such as GPT-5.5, GLM-5.1, GLM-5, Claude Opus, or Claude Sonnet. If the current IDE model is lower-cost, tell the user and ask whether to stop for a model switch or continue anyway. Continue only after the user chooses.";
|
|
2166
|
+
}
|
|
2167
|
+
return "Model policy: this command is recommended to run with a low-cost model. If the current IDE model is GPT-5.5, GLM-5.1, GLM-5, Claude Opus, Claude Sonnet, or another high-cost model, tell the user and ask whether to stop for a model switch or continue anyway. Continue only after the user chooses.";
|
|
2168
|
+
}
|
|
1851
2169
|
if (command === "apply") {
|
|
1852
|
-
return "
|
|
2170
|
+
return "\u6A21\u578B\u7B56\u7565\uFF1A\u8BE5\u547D\u4EE4\u5EFA\u8BAE\u4F7F\u7528\u9AD8\u80FD\u529B/\u9AD8\u6210\u672C\u6A21\u578B\u8FD0\u884C\uFF0C\u4F8B\u5982 GPT-5.5\u3001GLM-5.1\u3001GLM-5\u3001Claude Opus \u6216 Claude Sonnet\u3002\u82E5\u5F53\u524D IDE \u6A21\u578B\u80FD\u529B\u8F83\u4F4E\uFF0C\u8BF7\u544A\u77E5\u7528\u6237\u5E76\u8BE2\u95EE\u662F\u505C\u6B62\u540E\u5207\u6362\u6A21\u578B\uFF0C\u8FD8\u662F\u7EE7\u7EED\u6267\u884C\u5F53\u524D\u547D\u4EE4\uFF1B\u53EA\u6709\u5728\u7528\u6237\u9009\u62E9\u540E\u624D\u7EE7\u7EED\u3002";
|
|
1853
2171
|
}
|
|
1854
|
-
return "
|
|
2172
|
+
return "\u6A21\u578B\u7B56\u7565\uFF1A\u8BE5\u547D\u4EE4\u5EFA\u8BAE\u4F7F\u7528\u4F4E\u6210\u672C\u6A21\u578B\u8FD0\u884C\u3002\u82E5\u5F53\u524D IDE \u6A21\u578B\u662F GPT-5.5\u3001GLM-5.1\u3001GLM-5\u3001Claude Opus\u3001Claude Sonnet \u6216\u5176\u4ED6\u9AD8\u6210\u672C\u6A21\u578B\uFF0C\u8BF7\u544A\u77E5\u7528\u6237\u5E76\u8BE2\u95EE\u662F\u505C\u6B62\u540E\u5207\u6362\u6A21\u578B\uFF0C\u8FD8\u662F\u7EE7\u7EED\u6267\u884C\u5F53\u524D\u547D\u4EE4\uFF1B\u53EA\u6709\u5728\u7528\u6237\u9009\u62E9\u540E\u624D\u7EE7\u7EED\u3002";
|
|
1855
2173
|
}
|
|
1856
2174
|
|
|
1857
2175
|
// src/cli/context.ts
|
|
@@ -1890,17 +2208,12 @@ function renderFetAdapterUsage(command, args = "[...args]") {
|
|
|
1890
2208
|
}
|
|
1891
2209
|
|
|
1892
2210
|
// src/adapters/codex/templates.ts
|
|
1893
|
-
function codexGuideFile() {
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
schemaVersion: 1
|
|
1898
|
-
fetVersion: ${FET_VERSION}
|
|
1899
|
-
generator: codex-adapter
|
|
1900
|
-
adapterVersion: 1
|
|
1901
|
-
FET:END -->
|
|
2211
|
+
function codexGuideFile(language = DEFAULT_LANGUAGE) {
|
|
2212
|
+
const body = language === "en" ? `# FET For Codex
|
|
2213
|
+
|
|
2214
|
+
## Language
|
|
1902
2215
|
|
|
1903
|
-
|
|
2216
|
+
${languageInstruction(language)}
|
|
1904
2217
|
|
|
1905
2218
|
Before doing FET or OpenSpec work in Codex, read:
|
|
1906
2219
|
|
|
@@ -1914,25 +2227,53 @@ If GitNexus code graph context is available in the IDE or MCP tools, prefer it b
|
|
|
1914
2227
|
Use the terminal command \`fet <command>\` as the source of truth for workflow transitions. These files are Codex-readable guidance; they do not register native slash commands.
|
|
1915
2228
|
|
|
1916
2229
|
Command guides live in .codex/fet/commands/.
|
|
1917
|
-
`
|
|
2230
|
+
` : `# Codex \u7684 FET \u4F7F\u7528\u6307\u5357
|
|
2231
|
+
|
|
2232
|
+
## \u8BED\u8A00
|
|
2233
|
+
|
|
2234
|
+
${languageInstruction(language)}
|
|
2235
|
+
|
|
2236
|
+
\u5728 Codex \u4E2D\u6267\u884C FET \u6216 OpenSpec \u5DE5\u4F5C\u524D\uFF0C\u5148\u9605\u8BFB\uFF1A
|
|
2237
|
+
|
|
2238
|
+
- AGENTS.md
|
|
2239
|
+
- openspec/config.yaml
|
|
2240
|
+
- .codex/fet/karpathy-guidelines.md
|
|
2241
|
+
- \u5982\u679C\u5DF2\u9009\u62E9 change\uFF0C\u9605\u8BFB openspec/changes/<change-id>/ \u4E0B\u7684\u5F53\u524D\u4EA7\u7269
|
|
2242
|
+
|
|
2243
|
+
\u5982\u679C IDE \u6216 MCP \u5DE5\u5177\u4E2D\u53EF\u7528 GitNexus \u4EE3\u7801\u56FE\u4E0A\u4E0B\u6587\uFF0C\u5148\u7528\u5B83\u7F29\u5C0F\u4ED3\u5E93\u626B\u63CF\u8303\u56F4\uFF1B\u7528\u56FE\u8BC6\u522B\u76F8\u5173\u6A21\u5757\u3001\u4F9D\u8D56\u548C\u63D2\u5165\u70B9\uFF0C\u518D\u53EA\u8BFB\u53D6\u9700\u8981\u786E\u8BA4\u884C\u4E3A\u7684\u5177\u4F53\u6E90\u7801\u6587\u4EF6\u3002GitNexus \u4E0D\u53EF\u7528\u65F6\uFF0C\u6309\u666E\u901A FET/OpenSpec \u5DE5\u4F5C\u6D41\u7EE7\u7EED\u3002
|
|
2244
|
+
|
|
2245
|
+
\u5DE5\u4F5C\u6D41\u6D41\u8F6C\u4EE5\u7EC8\u7AEF\u547D\u4EE4 \`fet <command>\` \u4E3A\u51C6\u3002\u8FD9\u4E9B\u6587\u4EF6\u662F\u7ED9 Codex \u9605\u8BFB\u7684\u6307\u5BFC\uFF0C\u4E0D\u6CE8\u518C\u539F\u751F slash command\u3002
|
|
2246
|
+
|
|
2247
|
+
\u547D\u4EE4\u6307\u5357\u4F4D\u4E8E .codex/fet/commands/\u3002
|
|
2248
|
+
`;
|
|
2249
|
+
return {
|
|
2250
|
+
path: ".codex/fet/context.md",
|
|
2251
|
+
content: `<!-- FET:MANAGED
|
|
2252
|
+
schemaVersion: 1
|
|
2253
|
+
fetVersion: ${FET_VERSION}
|
|
2254
|
+
generator: codex-adapter
|
|
2255
|
+
adapterVersion: 1
|
|
2256
|
+
FET:END -->
|
|
2257
|
+
|
|
2258
|
+
${body}`
|
|
1918
2259
|
};
|
|
1919
2260
|
}
|
|
1920
|
-
function codexCommandFiles() {
|
|
2261
|
+
function codexCommandFiles(language = DEFAULT_LANGUAGE) {
|
|
1921
2262
|
return [
|
|
1922
|
-
codexKarpathyGuidelinesFile(),
|
|
2263
|
+
codexKarpathyGuidelinesFile(language),
|
|
1923
2264
|
...FET_ADAPTER_COMMANDS.map((command) => ({
|
|
1924
2265
|
path: `.codex/fet/commands/${command}.md`,
|
|
1925
|
-
content: renderCommand(command)
|
|
2266
|
+
content: renderCommand(command, language)
|
|
1926
2267
|
}))
|
|
1927
2268
|
];
|
|
1928
2269
|
}
|
|
1929
|
-
function codexSlashPromptFiles() {
|
|
2270
|
+
function codexSlashPromptFiles(language = DEFAULT_LANGUAGE) {
|
|
1930
2271
|
return FET_ADAPTER_COMMANDS.map((command) => ({
|
|
1931
2272
|
path: `prompts/fet-${command}.md`,
|
|
1932
|
-
content: renderSlashPrompt(command)
|
|
2273
|
+
content: renderSlashPrompt(command, language)
|
|
1933
2274
|
}));
|
|
1934
2275
|
}
|
|
1935
|
-
function codexKarpathyGuidelinesFile() {
|
|
2276
|
+
function codexKarpathyGuidelinesFile(language) {
|
|
1936
2277
|
return {
|
|
1937
2278
|
path: ".codex/fet/karpathy-guidelines.md",
|
|
1938
2279
|
content: `<!-- FET:MANAGED
|
|
@@ -1942,21 +2283,24 @@ generator: codex-adapter
|
|
|
1942
2283
|
adapterVersion: 1
|
|
1943
2284
|
FET:END -->
|
|
1944
2285
|
|
|
1945
|
-
# Andrej Karpathy Inspired Coding Guidelines
|
|
2286
|
+
# ${language === "en" ? "Andrej Karpathy Inspired Coding Guidelines" : "\u53D7 Andrej Karpathy \u542F\u53D1\u7684\u7F16\u7801\u6307\u5357"}
|
|
1946
2287
|
|
|
1947
|
-
${renderKarpathyGuidelinesBody()}
|
|
2288
|
+
${renderKarpathyGuidelinesBody(language)}
|
|
1948
2289
|
`
|
|
1949
2290
|
};
|
|
1950
2291
|
}
|
|
1951
|
-
function renderCommand(command) {
|
|
2292
|
+
function renderCommand(command, language) {
|
|
2293
|
+
if (language !== "en") {
|
|
2294
|
+
return renderCommandZh(command);
|
|
2295
|
+
}
|
|
1952
2296
|
if (command === "fill-context") {
|
|
1953
|
-
return renderFillContextCommand();
|
|
2297
|
+
return renderFillContextCommand(language);
|
|
1954
2298
|
}
|
|
1955
2299
|
if (command === "passthrough") {
|
|
1956
|
-
return renderPassthroughCommand();
|
|
2300
|
+
return renderPassthroughCommand(language);
|
|
1957
2301
|
}
|
|
1958
2302
|
if (command.startsWith("graph-")) {
|
|
1959
|
-
return renderGraphCommand(command);
|
|
2303
|
+
return renderGraphCommand(command, language);
|
|
1960
2304
|
}
|
|
1961
2305
|
const usage = renderFetAdapterUsage(command, "");
|
|
1962
2306
|
return `<!-- FET:MANAGED
|
|
@@ -1969,7 +2313,9 @@ FET:END -->
|
|
|
1969
2313
|
|
|
1970
2314
|
# ${usage}
|
|
1971
2315
|
|
|
1972
|
-
${renderIdeModelPolicy(command)}
|
|
2316
|
+
${renderIdeModelPolicy(command, language)}
|
|
2317
|
+
|
|
2318
|
+
${languageInstruction(language)}
|
|
1973
2319
|
|
|
1974
2320
|
When the user asks Codex to run the FET ${command} workflow, first make sure the project context is loaded from AGENTS.md and openspec/config.yaml.
|
|
1975
2321
|
|
|
@@ -1988,7 +2334,69 @@ If the command needs a change id, pass it with \`--change <change-id>\` or use t
|
|
|
1988
2334
|
After the command completes, report the important next steps from the FET output and keep any generated OpenSpec artifacts in the normal project workflow.
|
|
1989
2335
|
`;
|
|
1990
2336
|
}
|
|
1991
|
-
function
|
|
2337
|
+
function renderCommandZh(command) {
|
|
2338
|
+
const usage = renderFetAdapterUsage(command, command === "fill-context" ? "" : command === "passthrough" ? "<openspec-command> [...args]" : "");
|
|
2339
|
+
const title = commandTitleZh(command);
|
|
2340
|
+
if (command === "graph-setup") {
|
|
2341
|
+
return `<!-- FET:MANAGED
|
|
2342
|
+
schemaVersion: 1
|
|
2343
|
+
fetVersion: ${FET_VERSION}
|
|
2344
|
+
generator: codex-adapter
|
|
2345
|
+
adapterVersion: 1
|
|
2346
|
+
command: ${usage}
|
|
2347
|
+
FET:END -->
|
|
2348
|
+
|
|
2349
|
+
# ${usage}
|
|
2350
|
+
|
|
2351
|
+
${renderIdeModelPolicy(command, "zh-CN")}
|
|
2352
|
+
|
|
2353
|
+
${languageInstruction("zh-CN")}
|
|
2354
|
+
|
|
2355
|
+
\u7528\u4E8E\u6307\u5BFC\u5F53\u524D IDE LLM \u5728\u7528\u6237\u786E\u8BA4\u4E0B\u5B89\u88C5 GitNexus\u3002
|
|
2356
|
+
|
|
2357
|
+
\u5148\u8FD0\u884C\uFF1A
|
|
2358
|
+
|
|
2359
|
+
\`\`\`sh
|
|
2360
|
+
${usage}
|
|
2361
|
+
\`\`\`
|
|
2362
|
+
|
|
2363
|
+
\u7136\u540E\u9605\u8BFB .fet/graph-setup.md\uFF0C\u5E76\u4EE5\u5B83\u4F5C\u4E3A\u5B89\u88C5\u4EFB\u52A1\u4E66\u3002\u53EA\u8BFB\u68C0\u6D4B\u547D\u4EE4\u53EF\u4EE5\u76F4\u63A5\u8FD0\u884C\uFF1B\u4EFB\u4F55\u4F1A\u4E0B\u8F7D\u8F6F\u4EF6\u3001\u5168\u5C40\u5B89\u88C5\u3001\u4FEE\u6539 PATH\u3001\u5199\u5165\u7528\u6237\u7EA7\u6587\u4EF6\u6216\u4FEE\u6539 IDE/MCP \u914D\u7F6E\u7684\u547D\u4EE4\uFF0C\u6267\u884C\u524D\u90FD\u8981\u5C55\u793A\u5B8C\u6574\u547D\u4EE4\u5E76\u7B49\u5F85\u7528\u6237\u786E\u8BA4\u3002
|
|
2364
|
+
|
|
2365
|
+
\u5B89\u88C5\u540E\u7528 \`gitnexus --version\` \u9A8C\u8BC1\u3002\u5408\u9002\u65F6\u7EE7\u7EED\u8FD0\u884C \`fet graph init\` \u548C \`fet graph handoff\`\u3002\u5982\u679C\u5B89\u88C5\u5931\u8D25\uFF0C\u62A5\u544A\u5931\u8D25\u547D\u4EE4\u3001stderr \u548C\u4E0B\u4E00\u6B65\u4EBA\u5DE5\u5904\u7406\u5EFA\u8BAE\u3002
|
|
2366
|
+
`;
|
|
2367
|
+
}
|
|
2368
|
+
return `<!-- FET:MANAGED
|
|
2369
|
+
schemaVersion: 1
|
|
2370
|
+
fetVersion: ${FET_VERSION}
|
|
2371
|
+
generator: codex-adapter
|
|
2372
|
+
adapterVersion: 1
|
|
2373
|
+
command: ${usage}
|
|
2374
|
+
FET:END -->
|
|
2375
|
+
|
|
2376
|
+
# ${usage}
|
|
2377
|
+
|
|
2378
|
+
${renderIdeModelPolicy(command, "zh-CN")}
|
|
2379
|
+
|
|
2380
|
+
${languageInstruction("zh-CN")}
|
|
2381
|
+
|
|
2382
|
+
## \u7528\u9014
|
|
2383
|
+
|
|
2384
|
+
${title}
|
|
2385
|
+
|
|
2386
|
+
## \u6267\u884C\u65B9\u5F0F
|
|
2387
|
+
|
|
2388
|
+
\u5728\u6267\u884C\u524D\u9605\u8BFB AGENTS.md\u3001openspec/config.yaml\u3001.codex/fet/karpathy-guidelines.md\uFF0C\u4EE5\u53CA\u5F53\u524D change \u76EE\u5F55\u4E0B\u5DF2\u6709\u7684 OpenSpec \u4EA7\u7269\u3002
|
|
2389
|
+
|
|
2390
|
+
\`\`\`sh
|
|
2391
|
+
${usage}
|
|
2392
|
+
\`\`\`
|
|
2393
|
+
|
|
2394
|
+
\u5982\u679C\u547D\u4EE4\u9700\u8981 change id\uFF0C\u4F18\u5148\u4F7F\u7528\u7528\u6237\u8F93\u5165\u3001\`--change <change-id>\`\u3001FET active change \u6216\u552F\u4E00\u6253\u5F00\u7684 change\u3002\u5B58\u5728\u6B67\u4E49\u65F6\u5148\u8BE2\u95EE\u7528\u6237\u3002
|
|
2395
|
+
|
|
2396
|
+
\u6267\u884C\u5B8C\u6210\u540E\uFF0C\u7528\u4E2D\u6587\u603B\u7ED3\u5173\u952E\u8F93\u51FA\u3001\u751F\u6210\u6216\u66F4\u65B0\u7684\u6587\u4EF6\uFF0C\u4EE5\u53CA\u4E0B\u4E00\u6B65\u5EFA\u8BAE\u3002
|
|
2397
|
+
`;
|
|
2398
|
+
}
|
|
2399
|
+
function renderPassthroughCommand(language) {
|
|
1992
2400
|
return `<!-- FET:MANAGED
|
|
1993
2401
|
schemaVersion: 1
|
|
1994
2402
|
fetVersion: ${FET_VERSION}
|
|
@@ -1999,7 +2407,9 @@ FET:END -->
|
|
|
1999
2407
|
|
|
2000
2408
|
# fet passthrough
|
|
2001
2409
|
|
|
2002
|
-
${renderIdeModelPolicy("passthrough")}
|
|
2410
|
+
${renderIdeModelPolicy("passthrough", language)}
|
|
2411
|
+
|
|
2412
|
+
${languageInstruction(language)}
|
|
2003
2413
|
|
|
2004
2414
|
When the user asks Codex to run an OpenSpec command that FET does not manage as a first-class workflow command, use FET passthrough instead of calling OpenSpec directly.
|
|
2005
2415
|
|
|
@@ -2016,8 +2426,36 @@ fet passthrough <openspec-command> [...args]
|
|
|
2016
2426
|
This preserves the FET entry point while allowing access to unmanaged or newly added OpenSpec commands. Passthrough does not update FET lifecycle state.
|
|
2017
2427
|
`;
|
|
2018
2428
|
}
|
|
2019
|
-
function renderGraphCommand(command) {
|
|
2429
|
+
function renderGraphCommand(command, language) {
|
|
2020
2430
|
const usage = renderFetAdapterUsage(command, "");
|
|
2431
|
+
if (command === "graph-setup") {
|
|
2432
|
+
return `<!-- FET:MANAGED
|
|
2433
|
+
schemaVersion: 1
|
|
2434
|
+
fetVersion: ${FET_VERSION}
|
|
2435
|
+
generator: codex-adapter
|
|
2436
|
+
adapterVersion: 1
|
|
2437
|
+
command: ${usage}
|
|
2438
|
+
FET:END -->
|
|
2439
|
+
|
|
2440
|
+
# ${usage}
|
|
2441
|
+
|
|
2442
|
+
${renderIdeModelPolicy(command, language)}
|
|
2443
|
+
|
|
2444
|
+
${languageInstruction(language)}
|
|
2445
|
+
|
|
2446
|
+
Use this command to guide IDE-assisted GitNexus installation with user approval.
|
|
2447
|
+
|
|
2448
|
+
Run:
|
|
2449
|
+
|
|
2450
|
+
\`\`\`sh
|
|
2451
|
+
${usage}
|
|
2452
|
+
\`\`\`
|
|
2453
|
+
|
|
2454
|
+
Then read .fet/graph-setup.md and follow it as the source of truth. You may run read-only detection commands directly. Before downloading software, installing globally, changing PATH, writing user-level files, or modifying IDE/MCP configuration, show the exact command and wait for user approval.
|
|
2455
|
+
|
|
2456
|
+
After installation, verify \`gitnexus --version\`. If appropriate, continue with \`fet graph init\` and \`fet graph handoff\`. If installation fails, report the failing command, stderr, and next manual step.
|
|
2457
|
+
`;
|
|
2458
|
+
}
|
|
2021
2459
|
const subcommand = command.slice("graph-".length);
|
|
2022
2460
|
return `<!-- FET:MANAGED
|
|
2023
2461
|
schemaVersion: 1
|
|
@@ -2029,7 +2467,9 @@ FET:END -->
|
|
|
2029
2467
|
|
|
2030
2468
|
# ${usage}
|
|
2031
2469
|
|
|
2032
|
-
${renderIdeModelPolicy(command)}
|
|
2470
|
+
${renderIdeModelPolicy(command, language)}
|
|
2471
|
+
|
|
2472
|
+
${languageInstruction(language)}
|
|
2033
2473
|
|
|
2034
2474
|
When the user asks Codex to work with optional GitNexus graph support, use FET as the entry point.
|
|
2035
2475
|
|
|
@@ -2048,42 +2488,48 @@ For graph init or refresh, pass extra GitNexus analyze arguments only when the u
|
|
|
2048
2488
|
After the command completes, report the GitNexus state, generated handoff files, and next steps.
|
|
2049
2489
|
`;
|
|
2050
2490
|
}
|
|
2051
|
-
function renderSlashPrompt(command) {
|
|
2491
|
+
function renderSlashPrompt(command, language) {
|
|
2492
|
+
if (language !== "en") {
|
|
2493
|
+
return renderSlashPromptZh(command);
|
|
2494
|
+
}
|
|
2052
2495
|
if (command === "continue") {
|
|
2053
|
-
return renderContinueSlashPrompt();
|
|
2496
|
+
return renderContinueSlashPrompt(language);
|
|
2054
2497
|
}
|
|
2055
2498
|
if (command === "ff" || command === "propose") {
|
|
2056
|
-
return renderFastForwardSlashPrompt(command);
|
|
2499
|
+
return renderFastForwardSlashPrompt(command, language);
|
|
2057
2500
|
}
|
|
2058
2501
|
if (command === "explore") {
|
|
2059
|
-
return renderExploreSlashPrompt();
|
|
2502
|
+
return renderExploreSlashPrompt(language);
|
|
2060
2503
|
}
|
|
2061
2504
|
if (command === "new") {
|
|
2062
|
-
return renderNewSlashPrompt();
|
|
2505
|
+
return renderNewSlashPrompt(language);
|
|
2063
2506
|
}
|
|
2064
2507
|
if (command === "apply") {
|
|
2065
|
-
return renderApplySlashPrompt();
|
|
2508
|
+
return renderApplySlashPrompt(language);
|
|
2066
2509
|
}
|
|
2067
2510
|
if (command === "verify") {
|
|
2068
|
-
return renderVerifySlashPrompt();
|
|
2511
|
+
return renderVerifySlashPrompt(language);
|
|
2069
2512
|
}
|
|
2070
2513
|
if (command === "sync") {
|
|
2071
|
-
return renderSyncSlashPrompt();
|
|
2514
|
+
return renderSyncSlashPrompt(language);
|
|
2072
2515
|
}
|
|
2073
2516
|
if (command === "archive") {
|
|
2074
|
-
return renderArchiveSlashPrompt();
|
|
2517
|
+
return renderArchiveSlashPrompt(language);
|
|
2075
2518
|
}
|
|
2076
2519
|
if (command === "bulk-archive") {
|
|
2077
|
-
return renderBulkArchiveSlashPrompt();
|
|
2520
|
+
return renderBulkArchiveSlashPrompt(language);
|
|
2078
2521
|
}
|
|
2079
2522
|
if (command === "onboard") {
|
|
2080
|
-
return renderOnboardSlashPrompt();
|
|
2523
|
+
return renderOnboardSlashPrompt(language);
|
|
2081
2524
|
}
|
|
2082
2525
|
if (command === "fill-context") {
|
|
2083
|
-
return renderFillContextSlashPrompt();
|
|
2526
|
+
return renderFillContextSlashPrompt(language);
|
|
2084
2527
|
}
|
|
2085
2528
|
if (command === "passthrough") {
|
|
2086
|
-
return renderPassthroughSlashPrompt();
|
|
2529
|
+
return renderPassthroughSlashPrompt(language);
|
|
2530
|
+
}
|
|
2531
|
+
if (command === "graph-setup") {
|
|
2532
|
+
return renderGraphSetupSlashPrompt(language);
|
|
2087
2533
|
}
|
|
2088
2534
|
const usage = renderFetAdapterUsage(command);
|
|
2089
2535
|
const isGraph = command.startsWith("graph-");
|
|
@@ -2099,7 +2545,6 @@ FET:END -->
|
|
|
2099
2545
|
|
|
2100
2546
|
---
|
|
2101
2547
|
description: ${description}
|
|
2102
|
-
argument-hint: command arguments
|
|
2103
2548
|
---
|
|
2104
2549
|
|
|
2105
2550
|
Use FET as the entry point for this OpenSpec workflow.
|
|
@@ -2119,7 +2564,92 @@ ${shellCommand}
|
|
|
2119
2564
|
After it completes, summarize the important FET output and next steps.
|
|
2120
2565
|
`;
|
|
2121
2566
|
}
|
|
2122
|
-
function
|
|
2567
|
+
function renderSlashPromptZh(command) {
|
|
2568
|
+
if (command === "graph-setup") {
|
|
2569
|
+
return renderGraphSetupSlashPrompt("zh-CN");
|
|
2570
|
+
}
|
|
2571
|
+
const usage = renderFetAdapterUsage(command, command === "fill-context" ? "" : command === "passthrough" ? "<openspec-command> [...args]" : "[...args]");
|
|
2572
|
+
const argumentHint = command === "passthrough" ? "openspec-command [...args]" : void 0;
|
|
2573
|
+
const argumentHintLine = argumentHint ? `argument-hint: ${argumentHint}
|
|
2574
|
+
` : "";
|
|
2575
|
+
return `<!-- FET:MANAGED
|
|
2576
|
+
schemaVersion: 1
|
|
2577
|
+
fetVersion: ${FET_VERSION}
|
|
2578
|
+
generator: codex-adapter
|
|
2579
|
+
adapterVersion: 1
|
|
2580
|
+
command: ${usage}
|
|
2581
|
+
FET:END -->
|
|
2582
|
+
|
|
2583
|
+
---
|
|
2584
|
+
description: ${commandTitleZh(command)}
|
|
2585
|
+
${argumentHintLine}---
|
|
2586
|
+
|
|
2587
|
+
${renderIdeModelPolicy(command, "zh-CN")}
|
|
2588
|
+
|
|
2589
|
+
${languageInstruction("zh-CN")}
|
|
2590
|
+
|
|
2591
|
+
## \u76EE\u6807
|
|
2592
|
+
|
|
2593
|
+
${commandGoalZh(command)}
|
|
2594
|
+
|
|
2595
|
+
## \u6B65\u9AA4
|
|
2596
|
+
|
|
2597
|
+
1. \u9605\u8BFB AGENTS.md\u3001openspec/config.yaml \u548C .codex/fet/karpathy-guidelines.md\u3002
|
|
2598
|
+
2. \u5982\u679C\u5B58\u5728\u5F53\u524D OpenSpec change\uFF0C\u9605\u8BFB openspec/changes/<change-id>/ \u4E0B\u7684\u76F8\u5173\u4EA7\u7269\u3002
|
|
2599
|
+
3. \u8FD0\u884C FET \u547D\u4EE4\uFF1A
|
|
2600
|
+
\`\`\`sh
|
|
2601
|
+
${usage}
|
|
2602
|
+
\`\`\`
|
|
2603
|
+
4. \u6309 FET/OpenSpec \u8F93\u51FA\u7EE7\u7EED\uFF1B\u9700\u8981\u5199\u4EA7\u7269\u65F6\uFF0C\u53EA\u5199\u5230 OpenSpec \u6307\u5B9A\u8DEF\u5F84\uFF0C\u4E0D\u8981\u628A\u63D0\u793A\u5305\u88C5\u6587\u672C\u590D\u5236\u8FDB\u4EA7\u7269\u3002
|
|
2604
|
+
5. \u7528\u4E2D\u6587\u603B\u7ED3\u672C\u6B21\u52A8\u4F5C\u3001\u6587\u4EF6\u8DEF\u5F84\u3001\u72B6\u6001\u548C\u4E0B\u4E00\u6B65\u3002
|
|
2605
|
+
|
|
2606
|
+
## \u7EA6\u675F
|
|
2607
|
+
|
|
2608
|
+
- \u9ED8\u8BA4\u4F7F\u7528\u4E2D\u6587\u4EA7\u51FA\u3002
|
|
2609
|
+
- \u4E0D\u8981\u7ED5\u8FC7 FET \u76F4\u63A5\u8C03\u7528 openspec\uFF0C\u9664\u975E FET \u547D\u4EE4\u672C\u8EAB\u4E0D\u53EF\u7528\u3002
|
|
2610
|
+
- change \u4E0D\u660E\u786E\u65F6\u5148\u8BE2\u95EE\u7528\u6237\u3002
|
|
2611
|
+
${command === "fill-context" ? "- \u66FF\u6362 AGENTS.md \u4E2D\u6BCF\u4E2A `[NEEDS LLM INPUT]` \u6216 `[NEED LLM INPUT]` \u5360\u4F4D\u7B26\uFF0C\u4FDD\u7559 FET \u6258\u7BA1\u6807\u8BB0\uFF0C\u4E0D\u8981\u4FEE\u6539\u4E1A\u52A1\u4EE3\u7801\u3002\n" : ""}${command === "continue" ? "- \u4E00\u6B21\u53EA\u521B\u5EFA\u4E00\u4E2A ready artifact\uFF0C\u5E76\u5728\u5199\u5165\u524D\u9605\u8BFB\u4F9D\u8D56\u6587\u4EF6\u3002\n" : ""}${command === "apply" ? "- \u4E0D\u8981\u5728\u672A\u5B8C\u6210\u771F\u5B9E\u5B9E\u73B0\u524D\u52FE\u9009 tasks.md\uFF1B\u4E0D\u8981\u4ECE apply \u9636\u6BB5\u76F4\u63A5 sync \u6216 archive\u3002\n" : ""}`;
|
|
2612
|
+
}
|
|
2613
|
+
function commandTitleZh(command) {
|
|
2614
|
+
const titles = {
|
|
2615
|
+
explore: "\u63A2\u7D22 FET/OpenSpec \u9700\u6C42",
|
|
2616
|
+
propose: "\u521B\u5EFA\u5E76\u8865\u9F50 FET/OpenSpec \u63D0\u6848\u4EA7\u7269",
|
|
2617
|
+
new: "\u521B\u5EFA\u65B0\u7684 FET/OpenSpec change \u9AA8\u67B6",
|
|
2618
|
+
continue: "\u63A8\u8FDB\u5F53\u524D FET/OpenSpec change \u7684\u4E0B\u4E00\u4E2A\u4EA7\u7269",
|
|
2619
|
+
ff: "\u5FEB\u901F\u751F\u6210 FET/OpenSpec \u6240\u9700\u4EA7\u7269",
|
|
2620
|
+
apply: "\u5B9E\u65BD FET/OpenSpec change \u4E2D\u7684\u4EFB\u52A1",
|
|
2621
|
+
verify: "\u9A8C\u8BC1 FET/OpenSpec change",
|
|
2622
|
+
sync: "\u540C\u6B65 delta specs \u5230\u4E3B\u89C4\u8303",
|
|
2623
|
+
archive: "\u5F52\u6863\u5DF2\u9A8C\u8BC1\u7684 FET/OpenSpec change",
|
|
2624
|
+
"bulk-archive": "\u6279\u91CF\u5F52\u6863 FET/OpenSpec changes",
|
|
2625
|
+
onboard: "\u52A0\u8F7D FET/OpenSpec \u9879\u76EE\u4E0A\u4E0B\u6587",
|
|
2626
|
+
"fill-context": "\u586B\u5145 FET AGENTS.md \u5360\u4F4D\u7B26",
|
|
2627
|
+
passthrough: "\u901A\u8FC7 FET \u900F\u4F20\u672A\u63A5\u7BA1\u7684 OpenSpec \u547D\u4EE4",
|
|
2628
|
+
"graph-status": "\u67E5\u770B GitNexus \u4EE3\u7801\u56FE\u72B6\u6001",
|
|
2629
|
+
"graph-setup": "\u751F\u6210 GitNexus \u5B89\u88C5\u4EA4\u63A5\u8BF4\u660E",
|
|
2630
|
+
"graph-init": "\u521D\u59CB\u5316 GitNexus \u4EE3\u7801\u56FE",
|
|
2631
|
+
"graph-refresh": "\u5237\u65B0 GitNexus \u4EE3\u7801\u56FE",
|
|
2632
|
+
"graph-doctor": "\u8BCA\u65AD GitNexus \u4EE3\u7801\u56FE",
|
|
2633
|
+
"graph-handoff": "\u751F\u6210 GitNexus \u4EE3\u7801\u56FE\u4F7F\u7528\u8BF4\u660E"
|
|
2634
|
+
};
|
|
2635
|
+
return titles[command] ?? `\u8FD0\u884C FET ${command} \u5DE5\u4F5C\u6D41`;
|
|
2636
|
+
}
|
|
2637
|
+
function commandGoalZh(command) {
|
|
2638
|
+
if (command === "fill-context") {
|
|
2639
|
+
return "\u8865\u9F50 FET \u81EA\u52A8\u751F\u6210\u7684\u9879\u76EE\u4E0A\u4E0B\u6587\uFF0C\u8BA9\u540E\u7EED AI \u7F16\u7801\u548C OpenSpec \u5DE5\u4F5C\u6D41\u62E5\u6709\u7A33\u5B9A\u7684\u9879\u76EE\u4E8B\u5B9E\u3002";
|
|
2640
|
+
}
|
|
2641
|
+
if (command === "continue") {
|
|
2642
|
+
return "\u57FA\u4E8E active change \u6216\u7528\u6237\u6307\u5B9A\u7684 change\uFF0C\u521B\u5EFA\u4E0B\u4E00\u4E2A ready \u7684 OpenSpec \u89C4\u5212\u4EA7\u7269\u3002";
|
|
2643
|
+
}
|
|
2644
|
+
if (command === "apply") {
|
|
2645
|
+
return "\u8BFB\u53D6 OpenSpec \u4EA7\u7269\u5E76\u6309 tasks.md \u5B9E\u65BD\u4EE3\u7801\u53D8\u66F4\u3002";
|
|
2646
|
+
}
|
|
2647
|
+
if (command.startsWith("graph-")) {
|
|
2648
|
+
return "\u7BA1\u7406\u53EF\u9009\u7684 GitNexus \u4EE3\u7801\u56FE\uFF0C\u8BA9 AI \u5728\u5927\u8303\u56F4\u626B\u63CF\u524D\u4F18\u5148\u83B7\u5F97\u7ED3\u6784\u5316\u4EE3\u7801\u4E0A\u4E0B\u6587\u3002";
|
|
2649
|
+
}
|
|
2650
|
+
return commandTitleZh(command);
|
|
2651
|
+
}
|
|
2652
|
+
function renderFillContextCommand(language) {
|
|
2123
2653
|
return `<!-- FET:MANAGED
|
|
2124
2654
|
schemaVersion: 1
|
|
2125
2655
|
fetVersion: ${FET_VERSION}
|
|
@@ -2130,7 +2660,9 @@ FET:END -->
|
|
|
2130
2660
|
|
|
2131
2661
|
# fet fill-context
|
|
2132
2662
|
|
|
2133
|
-
${renderIdeModelPolicy("fill-context")}
|
|
2663
|
+
${renderIdeModelPolicy("fill-context", language)}
|
|
2664
|
+
|
|
2665
|
+
${languageInstruction(language)}
|
|
2134
2666
|
|
|
2135
2667
|
Use this command to complete FET-generated project context placeholders with Codex.
|
|
2136
2668
|
|
|
@@ -2147,7 +2679,7 @@ fet fill-context
|
|
|
2147
2679
|
Then read AGENTS.md and openspec/config.yaml, inspect the project, and replace every [NEEDS LLM INPUT] or [NEED LLM INPUT] placeholder in AGENTS.md with concrete project-specific content. Preserve FET managed markers and do not modify business code.
|
|
2148
2680
|
`;
|
|
2149
2681
|
}
|
|
2150
|
-
function renderFillContextSlashPrompt() {
|
|
2682
|
+
function renderFillContextSlashPrompt(language) {
|
|
2151
2683
|
return renderManagedSlashPrompt(
|
|
2152
2684
|
"fet fill-context",
|
|
2153
2685
|
"Fill FET AGENTS.md placeholders with Codex",
|
|
@@ -2179,10 +2711,12 @@ Steps:
|
|
|
2179
2711
|
Guardrails:
|
|
2180
2712
|
- Do not invent facts that cannot be inferred from the repo.
|
|
2181
2713
|
- Use [UNKNOWN] only when the repository does not contain enough evidence.
|
|
2182
|
-
- Keep generated context stable and useful for future AI coding sessions
|
|
2714
|
+
- Keep generated context stable and useful for future AI coding sessions.`,
|
|
2715
|
+
void 0,
|
|
2716
|
+
language
|
|
2183
2717
|
);
|
|
2184
2718
|
}
|
|
2185
|
-
function renderNewSlashPrompt() {
|
|
2719
|
+
function renderNewSlashPrompt(language) {
|
|
2186
2720
|
return renderManagedSlashPrompt(
|
|
2187
2721
|
"fet new [...args]",
|
|
2188
2722
|
"Create a new FET/OpenSpec change scaffold",
|
|
@@ -2211,20 +2745,22 @@ Steps:
|
|
|
2211
2745
|
Guardrails:
|
|
2212
2746
|
- Do not create artifact files in /prompts:fet-new.
|
|
2213
2747
|
- If the change already exists, suggest /prompts:fet-continue <change-id>.
|
|
2214
|
-
- Show the change location and the next command to create the first artifact
|
|
2748
|
+
- Show the change location and the next command to create the first artifact.`,
|
|
2749
|
+
void 0,
|
|
2750
|
+
language
|
|
2215
2751
|
);
|
|
2216
2752
|
}
|
|
2217
|
-
function renderApplySlashPrompt() {
|
|
2753
|
+
function renderApplySlashPrompt(language) {
|
|
2218
2754
|
return renderManagedSlashPrompt(
|
|
2219
2755
|
"fet apply [...args]",
|
|
2220
2756
|
"Implement tasks from a FET/OpenSpec change",
|
|
2221
2757
|
`Implement a FET-managed OpenSpec change.
|
|
2222
2758
|
|
|
2223
|
-
Input after the slash command
|
|
2759
|
+
Input after the slash command may identify the change, for example a change id or --change <change-id>. If omitted, use the active OpenSpec change when it is unambiguous.
|
|
2224
2760
|
|
|
2225
2761
|
Steps:
|
|
2226
2762
|
|
|
2227
|
-
1. Resolve the change id. If ambiguous, ask the user.
|
|
2763
|
+
1. Resolve the change id from the input, active FET/OpenSpec state, or the only open change. If ambiguous, ask the user.
|
|
2228
2764
|
2. Run the native OpenSpec apply flow through FET:
|
|
2229
2765
|
\`\`\`sh
|
|
2230
2766
|
fet apply --change <change-id> --json
|
|
@@ -2241,20 +2777,22 @@ Steps:
|
|
|
2241
2777
|
Guardrails:
|
|
2242
2778
|
- Never skip reading OpenSpec artifacts before implementation.
|
|
2243
2779
|
- Do not mark a task complete until the code change is actually done.
|
|
2244
|
-
- Do not run sync or archive from apply
|
|
2780
|
+
- Do not run sync or archive from apply.`,
|
|
2781
|
+
void 0,
|
|
2782
|
+
language
|
|
2245
2783
|
);
|
|
2246
2784
|
}
|
|
2247
|
-
function renderVerifySlashPrompt() {
|
|
2785
|
+
function renderVerifySlashPrompt(language) {
|
|
2248
2786
|
return renderManagedSlashPrompt(
|
|
2249
2787
|
"fet verify [...args]",
|
|
2250
2788
|
"Verify a FET/OpenSpec change before sync or archive",
|
|
2251
2789
|
`Verify a FET-managed OpenSpec change.
|
|
2252
2790
|
|
|
2253
|
-
Input after the slash command
|
|
2791
|
+
Input after the slash command may identify the change. If omitted, use the active OpenSpec change when it is unambiguous. If the user passes --done, declare verification complete only after checks have been performed or explicitly accepted by the user.
|
|
2254
2792
|
|
|
2255
2793
|
Steps:
|
|
2256
2794
|
|
|
2257
|
-
1. Resolve the change id. If ambiguous, ask the user.
|
|
2795
|
+
1. Resolve the change id from the input, active FET/OpenSpec state, or the only open change. If ambiguous, ask the user.
|
|
2258
2796
|
2. Generate FET verification instructions:
|
|
2259
2797
|
\`\`\`sh
|
|
2260
2798
|
fet verify --change <change-id>
|
|
@@ -2274,20 +2812,22 @@ Steps:
|
|
|
2274
2812
|
Guardrails:
|
|
2275
2813
|
- Do not run --done before producing a verification assessment.
|
|
2276
2814
|
- Treat incomplete tasks or missing required behavior as critical unless user explicitly accepts them.
|
|
2277
|
-
- Suggest /prompts:fet-sync <change-id> and /prompts:fet-archive <change-id> only after verification is done
|
|
2815
|
+
- Suggest /prompts:fet-sync <change-id> and /prompts:fet-archive <change-id> only after verification is done.`,
|
|
2816
|
+
void 0,
|
|
2817
|
+
language
|
|
2278
2818
|
);
|
|
2279
2819
|
}
|
|
2280
|
-
function renderSyncSlashPrompt() {
|
|
2820
|
+
function renderSyncSlashPrompt(language) {
|
|
2281
2821
|
return renderManagedSlashPrompt(
|
|
2282
2822
|
"fet sync [...args]",
|
|
2283
2823
|
"Sync delta specs and validate a FET/OpenSpec change",
|
|
2284
2824
|
`Sync a FET-managed OpenSpec change.
|
|
2285
2825
|
|
|
2286
|
-
Input after the slash command
|
|
2826
|
+
Input after the slash command may identify the change. If omitted, use the active OpenSpec change when it is unambiguous.
|
|
2287
2827
|
|
|
2288
2828
|
Steps:
|
|
2289
2829
|
|
|
2290
|
-
1. Resolve the change id. If ambiguous, ask the user.
|
|
2830
|
+
1. Resolve the change id from the input, active FET/OpenSpec state, or the only open change. If ambiguous, ask the user.
|
|
2291
2831
|
2. Confirm FET verification is complete or run /prompts:fet-verify <change-id> first.
|
|
2292
2832
|
3. Find delta specs under openspec/changes/<change-id>/specs/*/spec.md.
|
|
2293
2833
|
4. If delta specs exist, intelligently merge them into openspec/specs/<capability>/spec.md:
|
|
@@ -2306,20 +2846,22 @@ Steps:
|
|
|
2306
2846
|
Guardrails:
|
|
2307
2847
|
- Read both delta and main specs before editing.
|
|
2308
2848
|
- Make sync idempotent where possible.
|
|
2309
|
-
- If FET reports verify is not done, stop and run/ask for verification instead of bypassing the gate
|
|
2849
|
+
- If FET reports verify is not done, stop and run/ask for verification instead of bypassing the gate.`,
|
|
2850
|
+
void 0,
|
|
2851
|
+
language
|
|
2310
2852
|
);
|
|
2311
2853
|
}
|
|
2312
|
-
function renderArchiveSlashPrompt() {
|
|
2854
|
+
function renderArchiveSlashPrompt(language) {
|
|
2313
2855
|
return renderManagedSlashPrompt(
|
|
2314
2856
|
"fet archive [...args]",
|
|
2315
2857
|
"Archive a verified FET/OpenSpec change",
|
|
2316
2858
|
`Archive a FET-managed OpenSpec change.
|
|
2317
2859
|
|
|
2318
|
-
Input after the slash command
|
|
2860
|
+
Input after the slash command may identify the change. If omitted, use the active OpenSpec change when it is unambiguous.
|
|
2319
2861
|
|
|
2320
2862
|
Steps:
|
|
2321
2863
|
|
|
2322
|
-
1. Resolve the change id. If ambiguous, ask the user.
|
|
2864
|
+
1. Resolve the change id from the input, active FET/OpenSpec state, or the only open change. If ambiguous, ask the user.
|
|
2323
2865
|
2. Check artifact and task status:
|
|
2324
2866
|
\`\`\`sh
|
|
2325
2867
|
fet passthrough status --change <change-id> --json
|
|
@@ -2336,10 +2878,12 @@ Steps:
|
|
|
2336
2878
|
Guardrails:
|
|
2337
2879
|
- Do not move change directories manually; use fet archive.
|
|
2338
2880
|
- Do not bypass the FET verify gate.
|
|
2339
|
-
- Ask before archiving with incomplete tasks or unsynced delta specs
|
|
2881
|
+
- Ask before archiving with incomplete tasks or unsynced delta specs.`,
|
|
2882
|
+
void 0,
|
|
2883
|
+
language
|
|
2340
2884
|
);
|
|
2341
2885
|
}
|
|
2342
|
-
function renderBulkArchiveSlashPrompt() {
|
|
2886
|
+
function renderBulkArchiveSlashPrompt(language) {
|
|
2343
2887
|
return renderManagedSlashPrompt(
|
|
2344
2888
|
"fet bulk-archive [...args]",
|
|
2345
2889
|
"Archive multiple FET/OpenSpec changes safely",
|
|
@@ -2363,10 +2907,12 @@ Steps:
|
|
|
2363
2907
|
Guardrails:
|
|
2364
2908
|
- Never archive all changes without explicit user selection.
|
|
2365
2909
|
- Do not bypass verify or warnings for individual changes.
|
|
2366
|
-
- Continue with remaining selected changes if one archive fails, then report the failure clearly
|
|
2910
|
+
- Continue with remaining selected changes if one archive fails, then report the failure clearly.`,
|
|
2911
|
+
void 0,
|
|
2912
|
+
language
|
|
2367
2913
|
);
|
|
2368
2914
|
}
|
|
2369
|
-
function renderOnboardSlashPrompt() {
|
|
2915
|
+
function renderOnboardSlashPrompt(language) {
|
|
2370
2916
|
return renderManagedSlashPrompt(
|
|
2371
2917
|
"fet onboard [...args]",
|
|
2372
2918
|
"Load FET/OpenSpec onboarding context",
|
|
@@ -2377,7 +2923,7 @@ Steps:
|
|
|
2377
2923
|
1. Read AGENTS.md and openspec/config.yaml.
|
|
2378
2924
|
2. Run FET onboarding:
|
|
2379
2925
|
\`\`\`sh
|
|
2380
|
-
fet onboard
|
|
2926
|
+
fet onboard
|
|
2381
2927
|
\`\`\`
|
|
2382
2928
|
3. Summarize:
|
|
2383
2929
|
- Project context.
|
|
@@ -2387,10 +2933,12 @@ Steps:
|
|
|
2387
2933
|
|
|
2388
2934
|
Guardrails:
|
|
2389
2935
|
- Do not create or modify artifacts during onboard.
|
|
2390
|
-
- Use this command to orient the session, then suggest the next concrete FET command
|
|
2936
|
+
- Use this command to orient the session, then suggest the next concrete FET command.`,
|
|
2937
|
+
void 0,
|
|
2938
|
+
language
|
|
2391
2939
|
);
|
|
2392
2940
|
}
|
|
2393
|
-
function renderPassthroughSlashPrompt() {
|
|
2941
|
+
function renderPassthroughSlashPrompt(language) {
|
|
2394
2942
|
return renderManagedSlashPrompt(
|
|
2395
2943
|
"fet passthrough <openspec-command> [...args]",
|
|
2396
2944
|
"Run an unmanaged OpenSpec command through FET",
|
|
@@ -2408,10 +2956,68 @@ Steps:
|
|
|
2408
2956
|
Guardrails:
|
|
2409
2957
|
- Do not call openspec directly unless FET passthrough itself is unavailable.
|
|
2410
2958
|
- Remember that passthrough does not update FET lifecycle state.
|
|
2411
|
-
- For managed workflows, prefer the specific FET prompt instead of passthrough
|
|
2959
|
+
- For managed workflows, prefer the specific FET prompt instead of passthrough.`,
|
|
2960
|
+
"openspec-command [...args]",
|
|
2961
|
+
language
|
|
2962
|
+
);
|
|
2963
|
+
}
|
|
2964
|
+
function renderGraphSetupSlashPrompt(language) {
|
|
2965
|
+
if (language === "en") {
|
|
2966
|
+
return renderManagedSlashPrompt(
|
|
2967
|
+
"fet graph setup",
|
|
2968
|
+
"Guide IDE-assisted GitNexus installation with user approval",
|
|
2969
|
+
`Guide optional GitNexus installation for this project.
|
|
2970
|
+
|
|
2971
|
+
Steps:
|
|
2972
|
+
|
|
2973
|
+
1. Run:
|
|
2974
|
+
\`\`\`sh
|
|
2975
|
+
fet graph setup
|
|
2976
|
+
\`\`\`
|
|
2977
|
+
2. Read .fet/graph-setup.md and follow it as the source of truth.
|
|
2978
|
+
3. Run read-only detection commands as needed, such as checking the shell, PATH, package managers, and \`gitnexus --version\`.
|
|
2979
|
+
4. If GitNexus is missing, use the configured \`FET_GITNEXUS_INSTALL_COMMAND\` when present; otherwise find the official GitNexus installation instructions or ask the user for the preferred method.
|
|
2980
|
+
5. Before any command that downloads software, installs globally, changes PATH, writes user-level files, or modifies IDE/MCP settings, show the exact command and wait for user approval.
|
|
2981
|
+
6. Verify installation with \`gitnexus --version\`.
|
|
2982
|
+
7. If the user wants IDE/MCP integration, explain \`gitnexus setup\` and run it only after approval.
|
|
2983
|
+
8. Run \`fet graph init\` and then \`fet graph handoff\` when GitNexus is available.
|
|
2984
|
+
|
|
2985
|
+
Guardrails:
|
|
2986
|
+
- GitNexus is optional; do not block FET/OpenSpec workflows if installation fails.
|
|
2987
|
+
- Do not modify application code during setup.
|
|
2988
|
+
- Report the failing command, stderr, and next manual step if blocked.`,
|
|
2989
|
+
void 0,
|
|
2990
|
+
language
|
|
2991
|
+
);
|
|
2992
|
+
}
|
|
2993
|
+
return renderManagedSlashPrompt(
|
|
2994
|
+
"fet graph setup",
|
|
2995
|
+
"\u6307\u5BFC IDE LLM \u5728\u7528\u6237\u786E\u8BA4\u4E0B\u5B89\u88C5 GitNexus",
|
|
2996
|
+
`\u4E3A\u5F53\u524D\u9879\u76EE\u5F15\u5BFC\u53EF\u9009\u7684 GitNexus \u5B89\u88C5\u3002
|
|
2997
|
+
|
|
2998
|
+
\u6B65\u9AA4\uFF1A
|
|
2999
|
+
|
|
3000
|
+
1. \u8FD0\u884C\uFF1A
|
|
3001
|
+
\`\`\`sh
|
|
3002
|
+
fet graph setup
|
|
3003
|
+
\`\`\`
|
|
3004
|
+
2. \u9605\u8BFB .fet/graph-setup.md\uFF0C\u5E76\u4EE5\u5B83\u4F5C\u4E3A\u5B89\u88C5\u4EFB\u52A1\u4E66\u3002
|
|
3005
|
+
3. \u6309\u9700\u8FD0\u884C\u53EA\u8BFB\u68C0\u6D4B\u547D\u4EE4\uFF0C\u4F8B\u5982\u68C0\u67E5 shell\u3001PATH\u3001\u5305\u7BA1\u7406\u5668\u548C \`gitnexus --version\`\u3002
|
|
3006
|
+
4. \u5982\u679C GitNexus \u7F3A\u5931\uFF0C\u4F18\u5148\u4F7F\u7528\u5DF2\u914D\u7F6E\u7684 \`FET_GITNEXUS_INSTALL_COMMAND\`\uFF1B\u6CA1\u6709\u914D\u7F6E\u65F6\uFF0C\u67E5\u627E GitNexus \u5B98\u65B9\u5B89\u88C5\u8BF4\u660E\uFF0C\u6216\u8BE2\u95EE\u7528\u6237\u5E0C\u671B\u4F7F\u7528\u7684\u5B89\u88C5\u65B9\u5F0F\u3002
|
|
3007
|
+
5. \u4EFB\u4F55\u4F1A\u4E0B\u8F7D\u8F6F\u4EF6\u3001\u5168\u5C40\u5B89\u88C5\u3001\u4FEE\u6539 PATH\u3001\u5199\u5165\u7528\u6237\u7EA7\u6587\u4EF6\u6216\u4FEE\u6539 IDE/MCP \u8BBE\u7F6E\u7684\u547D\u4EE4\uFF0C\u6267\u884C\u524D\u90FD\u8981\u5C55\u793A\u5B8C\u6574\u547D\u4EE4\u5E76\u7B49\u5F85\u7528\u6237\u786E\u8BA4\u3002
|
|
3008
|
+
6. \u7528 \`gitnexus --version\` \u9A8C\u8BC1\u5B89\u88C5\u3002
|
|
3009
|
+
7. \u5982\u679C\u7528\u6237\u9700\u8981 IDE/MCP \u96C6\u6210\uFF0C\u5148\u8BF4\u660E \`gitnexus setup\`\uFF0C\u83B7\u5F97\u786E\u8BA4\u540E\u518D\u8FD0\u884C\u3002
|
|
3010
|
+
8. GitNexus \u53EF\u7528\u540E\u8FD0\u884C \`fet graph init\`\uFF0C\u518D\u8FD0\u884C \`fet graph handoff\`\u3002
|
|
3011
|
+
|
|
3012
|
+
\u7EA6\u675F\uFF1A
|
|
3013
|
+
- GitNexus \u662F\u53EF\u9009\u80FD\u529B\uFF1B\u5B89\u88C5\u5931\u8D25\u65F6\u4E0D\u8981\u963B\u585E FET/OpenSpec \u4E3B\u6D41\u7A0B\u3002
|
|
3014
|
+
- \u5B89\u88C5\u8FC7\u7A0B\u4E2D\u4E0D\u8981\u4FEE\u6539\u4E1A\u52A1\u4EE3\u7801\u3002
|
|
3015
|
+
- \u5982\u679C\u53D7\u963B\uFF0C\u62A5\u544A\u5931\u8D25\u547D\u4EE4\u3001stderr \u548C\u4E0B\u4E00\u6B65\u4EBA\u5DE5\u5904\u7406\u5EFA\u8BAE\u3002`,
|
|
3016
|
+
void 0,
|
|
3017
|
+
language
|
|
2412
3018
|
);
|
|
2413
3019
|
}
|
|
2414
|
-
function renderExploreSlashPrompt() {
|
|
3020
|
+
function renderExploreSlashPrompt(language) {
|
|
2415
3021
|
return renderManagedSlashPrompt(
|
|
2416
3022
|
"fet explore [...args]",
|
|
2417
3023
|
"Explore requirements for a FET/OpenSpec change",
|
|
@@ -2443,21 +3049,23 @@ Guardrails:
|
|
|
2443
3049
|
- Do not write application code in explore mode.
|
|
2444
3050
|
- Ask a clarifying question if the proposal would otherwise be mostly guesswork.
|
|
2445
3051
|
- Creating or updating OpenSpec artifacts is allowed when the user asks to capture the thinking.
|
|
2446
|
-
- After creating proposal.md, show the path and suggest /prompts:fet-continue <change-id> for the next artifact
|
|
3052
|
+
- After creating proposal.md, show the path and suggest /prompts:fet-continue <change-id> for the next artifact.`,
|
|
3053
|
+
void 0,
|
|
3054
|
+
language
|
|
2447
3055
|
);
|
|
2448
3056
|
}
|
|
2449
|
-
function renderContinueSlashPrompt() {
|
|
3057
|
+
function renderContinueSlashPrompt(language) {
|
|
2450
3058
|
return renderManagedSlashPrompt(
|
|
2451
3059
|
"fet continue [...args]",
|
|
2452
3060
|
"Create the next FET/OpenSpec artifact",
|
|
2453
3061
|
`Continue a FET-managed OpenSpec change by creating exactly one ready artifact.
|
|
2454
3062
|
|
|
2455
|
-
Input after the slash command
|
|
3063
|
+
Input after the slash command may be a change id, optionally followed by an artifact id. If omitted, use the active OpenSpec change and continue the next ready artifact.
|
|
2456
3064
|
|
|
2457
3065
|
Steps:
|
|
2458
3066
|
|
|
2459
3067
|
1. Load project context from AGENTS.md and openspec/config.yaml.
|
|
2460
|
-
2. Resolve the change id
|
|
3068
|
+
2. Resolve the change id from the input, active FET/OpenSpec state, or the only open change. If ambiguous, ask the user.
|
|
2461
3069
|
3. Check status:
|
|
2462
3070
|
\`\`\`sh
|
|
2463
3071
|
fet passthrough status --change <change-id> --json
|
|
@@ -2465,7 +3073,7 @@ Steps:
|
|
|
2465
3073
|
4. Pick the first artifact whose status is ready, unless the user specified an artifact id.
|
|
2466
3074
|
5. Run the native OpenSpec continue flow through FET:
|
|
2467
3075
|
\`\`\`sh
|
|
2468
|
-
fet continue
|
|
3076
|
+
fet continue [artifact-id] --change <change-id> --json
|
|
2469
3077
|
\`\`\`
|
|
2470
3078
|
6. Follow the native continue output. When it provides template, instruction, dependencies, and outputPath, use those fields.
|
|
2471
3079
|
7. Read dependency files before writing.
|
|
@@ -2483,10 +3091,12 @@ Output:
|
|
|
2483
3091
|
Guardrails:
|
|
2484
3092
|
- Create one artifact per invocation.
|
|
2485
3093
|
- Never skip dependency order.
|
|
2486
|
-
- Ask the user if instructions are ambiguous enough that a useful artifact cannot be written
|
|
3094
|
+
- Ask the user if instructions are ambiguous enough that a useful artifact cannot be written.`,
|
|
3095
|
+
void 0,
|
|
3096
|
+
language
|
|
2487
3097
|
);
|
|
2488
3098
|
}
|
|
2489
|
-
function renderFastForwardSlashPrompt(command) {
|
|
3099
|
+
function renderFastForwardSlashPrompt(command, language) {
|
|
2490
3100
|
const title = command === "propose" ? "Propose a new FET/OpenSpec change" : "Fast-forward FET/OpenSpec artifact creation";
|
|
2491
3101
|
const commandLine = command === "propose" ? "fet propose <change-id-or-description>" : "fet ff --change <change-id>";
|
|
2492
3102
|
return renderManagedSlashPrompt(
|
|
@@ -2494,7 +3104,7 @@ function renderFastForwardSlashPrompt(command) {
|
|
|
2494
3104
|
command === "propose" ? "Create a change and generate required OpenSpec artifacts" : "Generate required OpenSpec artifacts for a change",
|
|
2495
3105
|
`${title}.
|
|
2496
3106
|
|
|
2497
|
-
Input after the slash command may be a change id or a description of what the user wants to build.
|
|
3107
|
+
Input after the slash command may be a change id or a description of what the user wants to build. For ff, it may be omitted when the active OpenSpec change is unambiguous.
|
|
2498
3108
|
|
|
2499
3109
|
Steps:
|
|
2500
3110
|
|
|
@@ -2517,11 +3127,16 @@ Output:
|
|
|
2517
3127
|
- Change id and location.
|
|
2518
3128
|
- Artifacts created.
|
|
2519
3129
|
- Current status.
|
|
2520
|
-
- Next recommended command, usually /prompts:fet-apply <change-id
|
|
3130
|
+
- Next recommended command, usually /prompts:fet-apply <change-id>.`,
|
|
3131
|
+
void 0,
|
|
3132
|
+
language
|
|
2521
3133
|
);
|
|
2522
3134
|
}
|
|
2523
|
-
function renderManagedSlashPrompt(command, description, body) {
|
|
3135
|
+
function renderManagedSlashPrompt(command, description, body, argumentHint, language = DEFAULT_LANGUAGE) {
|
|
2524
3136
|
const policyCommand = command.split(/\s+/)[1] ?? command;
|
|
3137
|
+
const argumentHintLine = argumentHint ? `argument-hint: ${argumentHint}
|
|
3138
|
+
` : "";
|
|
3139
|
+
const graphContextInstruction = language === "en" ? "If GitNexus graph context is available, consult it before broad source scans and use it to narrow the files you read. If it is unavailable, continue normally." : "\u5982\u679C GitNexus \u4EE3\u7801\u56FE\u4E0A\u4E0B\u6587\u53EF\u7528\uFF0C\u5148\u7528\u5B83\u7F29\u5C0F\u6E90\u7801\u626B\u63CF\u8303\u56F4\uFF1B\u4E0D\u53EF\u7528\u65F6\u6309\u666E\u901A\u6D41\u7A0B\u7EE7\u7EED\u3002";
|
|
2525
3140
|
return `<!-- FET:MANAGED
|
|
2526
3141
|
schemaVersion: 1
|
|
2527
3142
|
fetVersion: ${FET_VERSION}
|
|
@@ -2532,12 +3147,13 @@ FET:END -->
|
|
|
2532
3147
|
|
|
2533
3148
|
---
|
|
2534
3149
|
description: ${description}
|
|
2535
|
-
|
|
2536
|
-
---
|
|
3150
|
+
${argumentHintLine}---
|
|
2537
3151
|
|
|
2538
|
-
${renderIdeModelPolicy(policyCommand)}
|
|
3152
|
+
${renderIdeModelPolicy(policyCommand, language)}
|
|
2539
3153
|
|
|
2540
|
-
|
|
3154
|
+
${languageInstruction(language)}
|
|
3155
|
+
|
|
3156
|
+
${graphContextInstruction}
|
|
2541
3157
|
|
|
2542
3158
|
${body}
|
|
2543
3159
|
`;
|
|
@@ -2553,16 +3169,16 @@ var CodexAdapter = class {
|
|
|
2553
3169
|
reason: "Codex adapter is available for projects that use AGENTS.md"
|
|
2554
3170
|
};
|
|
2555
3171
|
}
|
|
2556
|
-
async planInstall(_projectRoot) {
|
|
3172
|
+
async planInstall(_projectRoot, language) {
|
|
2557
3173
|
return {
|
|
2558
3174
|
tool: this.tool,
|
|
2559
3175
|
files: [
|
|
2560
|
-
...[codexGuideFile(), ...codexCommandFiles()].map((file) => ({
|
|
3176
|
+
...[codexGuideFile(language), ...codexCommandFiles(language)].map((file) => ({
|
|
2561
3177
|
...file,
|
|
2562
3178
|
managed: true,
|
|
2563
3179
|
root: "project"
|
|
2564
3180
|
})),
|
|
2565
|
-
...codexSlashPromptFiles().map((file) => ({
|
|
3181
|
+
...codexSlashPromptFiles(language).map((file) => ({
|
|
2566
3182
|
...file,
|
|
2567
3183
|
managed: true,
|
|
2568
3184
|
root: "codex-home"
|
|
@@ -2649,13 +3265,13 @@ import { mkdir as mkdir8, readFile as readFile13, stat as stat7 } from "fs/promi
|
|
|
2649
3265
|
import { dirname as dirname8, join as join16 } from "path";
|
|
2650
3266
|
|
|
2651
3267
|
// src/adapters/cursor/templates.ts
|
|
2652
|
-
function cursorSkillFiles() {
|
|
3268
|
+
function cursorSkillFiles(language = DEFAULT_LANGUAGE) {
|
|
2653
3269
|
return FET_ADAPTER_COMMANDS.map((command) => ({
|
|
2654
3270
|
path: `.cursor/skills/fet-${command}/SKILL.md`,
|
|
2655
|
-
content: renderSkill(command)
|
|
3271
|
+
content: renderSkill(command, language)
|
|
2656
3272
|
}));
|
|
2657
3273
|
}
|
|
2658
|
-
function cursorRuleFile() {
|
|
3274
|
+
function cursorRuleFile(language = DEFAULT_LANGUAGE) {
|
|
2659
3275
|
return {
|
|
2660
3276
|
path: ".cursor/rules/fet-context.mdc",
|
|
2661
3277
|
content: `<!-- FET:MANAGED
|
|
@@ -2666,22 +3282,24 @@ adapterVersion: 1
|
|
|
2666
3282
|
FET:END -->
|
|
2667
3283
|
|
|
2668
3284
|
---
|
|
2669
|
-
description: Load FET project context for implementation tasks
|
|
3285
|
+
description: ${language === "en" ? "Load FET project context for implementation tasks" : "\u4E3A\u5B9E\u73B0\u4EFB\u52A1\u52A0\u8F7D FET \u9879\u76EE\u4E0A\u4E0B\u6587"}
|
|
2670
3286
|
alwaysApply: false
|
|
2671
3287
|
---
|
|
2672
3288
|
|
|
3289
|
+
${languageInstruction(language)}
|
|
3290
|
+
|
|
2673
3291
|
\u5F53\u7528\u6237\u8BF7\u6C42\u4FEE\u6539\u9879\u76EE\u3001\u5B9E\u73B0 OpenSpec change\u3001\u8FD0\u884C FET \u5DE5\u4F5C\u6D41\u6216\u89E3\u91CA\u9879\u76EE\u7ED3\u6784\u65F6\uFF0C\u4F18\u5148\u9605\u8BFB\uFF1A
|
|
2674
3292
|
|
|
2675
3293
|
- AGENTS.md
|
|
2676
3294
|
- openspec/config.yaml
|
|
2677
|
-
- GitNexus
|
|
2678
|
-
- \u5F53\u524D change \u76EE\u5F55\u4E0B\u7684 OpenSpec \u89C4\u5212\u4EA7\u7269
|
|
3295
|
+
- \u53EF\u7528\u65F6\u7684 GitNexus \u4EE3\u7801\u56FE\u4E0A\u4E0B\u6587\u3002\u4F18\u5148\u7528\u5B83\u7F29\u5C0F\u8303\u56F4\uFF1B\u4E0D\u53EF\u7528\u65F6\u6309\u666E\u901A FET/OpenSpec \u5DE5\u4F5C\u6D41\u7EE7\u7EED\u3002
|
|
3296
|
+
- \u5F53\u524D change \u76EE\u5F55\u4E0B\u7684 OpenSpec \u89C4\u5212\u4EA7\u7269\u3002
|
|
2679
3297
|
|
|
2680
|
-
\u5982\u679C\u7528\u6237\u8F93\u5165\u7C7B\u4F3C \`/fet apply\` \u7684\u8BF7\u6C42\
|
|
3298
|
+
\u5982\u679C\u7528\u6237\u8F93\u5165\u7C7B\u4F3C \`/fet apply\` \u7684\u8BF7\u6C42\uFF0C\u8BF7\u628A\u5B83\u89C6\u4E3A\u5DE5\u4F5C\u6D41\u610F\u56FE\uFF0C\u5E76\u63D0\u793A\u6216\u6267\u884C\u5BF9\u5E94\u7684\u7EC8\u7AEF\u547D\u4EE4 \`fet <cmd>\`\u3002
|
|
2681
3299
|
`
|
|
2682
3300
|
};
|
|
2683
3301
|
}
|
|
2684
|
-
function renderSkill(command) {
|
|
3302
|
+
function renderSkill(command, language) {
|
|
2685
3303
|
const usage = renderFetAdapterUsage(command, command === "passthrough" ? "[...args]" : "");
|
|
2686
3304
|
if (command === "fill-context") {
|
|
2687
3305
|
return `<!-- FET:MANAGED
|
|
@@ -2694,22 +3312,52 @@ FET:END -->
|
|
|
2694
3312
|
|
|
2695
3313
|
---
|
|
2696
3314
|
name: fet-fill-context
|
|
2697
|
-
description: Fill FET AGENTS.md placeholders with Cursor AI
|
|
3315
|
+
description: ${language === "en" ? "Fill FET AGENTS.md placeholders with Cursor AI" : "\u4F7F\u7528 Cursor AI \u586B\u5145 FET AGENTS.md \u5360\u4F4D\u7B26"}
|
|
2698
3316
|
disable-model-invocation: false
|
|
2699
3317
|
---
|
|
2700
3318
|
|
|
2701
|
-
|
|
3319
|
+
${renderIdeModelPolicy(command, language)}
|
|
2702
3320
|
|
|
2703
|
-
${
|
|
3321
|
+
${languageInstruction(language)}
|
|
2704
3322
|
|
|
2705
|
-
|
|
3323
|
+
\u5982\u679C IDE \u547D\u4EE4\u9700\u8981\u5237\u65B0\uFF0C\u5148\u8FD0\u884C \`fet fill-context\`\u3002
|
|
2706
3324
|
|
|
2707
|
-
|
|
3325
|
+
\u7136\u540E\u9605\u8BFB\uFF1A
|
|
2708
3326
|
|
|
2709
3327
|
- AGENTS.md
|
|
2710
3328
|
- openspec/config.yaml
|
|
2711
3329
|
|
|
2712
|
-
|
|
3330
|
+
\u68C0\u67E5 README\u3001package scripts\u3001\u8DEF\u7531\u3001\u6D4B\u8BD5\u3001\u6E90\u7801\u7ED3\u6784\u548C\u73B0\u6709\u7EA6\u5B9A\u540E\uFF0C\u628A AGENTS.md \u4E2D\u6BCF\u4E2A \`[NEEDS LLM INPUT]\` \u6216 \`[NEED LLM INPUT]\` \u5360\u4F4D\u7B26\u66FF\u6362\u4E3A\u5177\u4F53\u3001\u7B80\u6D01\u3001\u9879\u76EE\u76F8\u5173\u7684\u5185\u5BB9\u3002\u4FDD\u7559 FET \u6258\u7BA1\u6807\u8BB0\uFF0C\u4E0D\u8981\u4FEE\u6539\u4E1A\u52A1\u4EE3\u7801\u3002
|
|
3331
|
+
`;
|
|
3332
|
+
}
|
|
3333
|
+
if (command === "graph-setup") {
|
|
3334
|
+
return `<!-- FET:MANAGED
|
|
3335
|
+
schemaVersion: 1
|
|
3336
|
+
fetVersion: ${FET_VERSION}
|
|
3337
|
+
generator: cursor-adapter
|
|
3338
|
+
adapterVersion: 1
|
|
3339
|
+
command: fet graph setup
|
|
3340
|
+
FET:END -->
|
|
3341
|
+
|
|
3342
|
+
---
|
|
3343
|
+
name: fet-graph-setup
|
|
3344
|
+
description: ${language === "en" ? "Guide IDE-assisted GitNexus installation with user approval" : "\u6307\u5BFC IDE LLM \u5728\u7528\u6237\u786E\u8BA4\u4E0B\u5B89\u88C5 GitNexus"}
|
|
3345
|
+
disable-model-invocation: false
|
|
3346
|
+
---
|
|
3347
|
+
|
|
3348
|
+
${renderIdeModelPolicy(command, language)}
|
|
3349
|
+
|
|
3350
|
+
${languageInstruction(language)}
|
|
3351
|
+
|
|
3352
|
+
${language === "en" ? `Run \`fet graph setup\`, then read .fet/graph-setup.md and follow it as an installation playbook.
|
|
3353
|
+
|
|
3354
|
+
You may run read-only detection commands without extra confirmation. Before any command that downloads software, installs globally, changes PATH, writes user-level config, or modifies IDE/MCP settings, show the exact command and wait for user approval.
|
|
3355
|
+
|
|
3356
|
+
After installation, verify with \`gitnexus --version\`, then run \`fet graph init\` and \`fet graph handoff\` when appropriate. If installation fails, summarize the failing command and next manual step.` : `\u5148\u8FD0\u884C \`fet graph setup\`\uFF0C\u518D\u9605\u8BFB .fet/graph-setup.md\uFF0C\u5E76\u628A\u5B83\u4F5C\u4E3A\u5B89\u88C5\u4EFB\u52A1\u4E66\u6267\u884C\u3002
|
|
3357
|
+
|
|
3358
|
+
\u53EA\u8BFB\u68C0\u6D4B\u547D\u4EE4\u53EF\u4EE5\u76F4\u63A5\u8FD0\u884C\u3002\u4EFB\u4F55\u4F1A\u4E0B\u8F7D\u8F6F\u4EF6\u3001\u5168\u5C40\u5B89\u88C5\u3001\u4FEE\u6539 PATH\u3001\u5199\u5165\u7528\u6237\u7EA7\u914D\u7F6E\u6216\u4FEE\u6539 IDE/MCP \u8BBE\u7F6E\u7684\u547D\u4EE4\uFF0C\u6267\u884C\u524D\u90FD\u8981\u5C55\u793A\u5B8C\u6574\u547D\u4EE4\u5E76\u7B49\u5F85\u7528\u6237\u786E\u8BA4\u3002
|
|
3359
|
+
|
|
3360
|
+
\u5B89\u88C5\u540E\u7528 \`gitnexus --version\` \u9A8C\u8BC1\uFF1B\u5408\u9002\u65F6\u7EE7\u7EED\u8FD0\u884C \`fet graph init\` \u548C \`fet graph handoff\`\u3002\u5982\u679C\u5B89\u88C5\u5931\u8D25\uFF0C\u6C47\u603B\u5931\u8D25\u547D\u4EE4\u548C\u4E0B\u4E00\u6B65\u4EBA\u5DE5\u5904\u7406\u5EFA\u8BAE\u3002`}
|
|
2713
3361
|
`;
|
|
2714
3362
|
}
|
|
2715
3363
|
return `<!-- FET:MANAGED
|
|
@@ -2722,15 +3370,15 @@ FET:END -->
|
|
|
2722
3370
|
|
|
2723
3371
|
---
|
|
2724
3372
|
name: fet-${command}
|
|
2725
|
-
description: Run FET-managed OpenSpec ${command} workflow from the terminal
|
|
3373
|
+
description: ${language === "en" ? `Run FET-managed OpenSpec ${command} workflow from the terminal` : `\u4ECE\u7EC8\u7AEF\u8FD0\u884C FET \u6258\u7BA1\u7684 OpenSpec ${command} \u5DE5\u4F5C\u6D41`}
|
|
2726
3374
|
disable-model-invocation: true
|
|
2727
3375
|
---
|
|
2728
3376
|
|
|
2729
|
-
${renderIdeModelPolicy(command)}
|
|
3377
|
+
${renderIdeModelPolicy(command, language)}
|
|
2730
3378
|
|
|
2731
|
-
|
|
3379
|
+
${languageInstruction(language)}
|
|
2732
3380
|
|
|
2733
|
-
\
|
|
3381
|
+
\u6B64\u6587\u4EF6\u91C7\u7528 Cursor Skill \u76EE\u5F55\u7ED3\u6784\uFF0C\u63D0\u4F9B \`/fet-${command}\` \u98CE\u683C\u7684\u5DE5\u4F5C\u6D41\u8BF4\u660E\uFF0C\u4E0D\u627F\u8BFA\u6CE8\u518C \`/fet ${command}\` \u8FD9\u79CD\u5E26\u7A7A\u683C\u7684\u539F\u751F slash command\u3002
|
|
2734
3382
|
|
|
2735
3383
|
\u8BF7\u5728\u7EC8\u7AEF\u4E2D\u6267\u884C\uFF1A
|
|
2736
3384
|
|
|
@@ -2738,7 +3386,7 @@ If GitNexus code graph context is available in Cursor, prefer it before broad re
|
|
|
2738
3386
|
${usage}
|
|
2739
3387
|
\`\`\`
|
|
2740
3388
|
|
|
2741
|
-
\u6267\u884C\u524D\u8BF7\u786E\u8BA4\u5DF2\u9605\u8BFB AGENTS.md \
|
|
3389
|
+
\u6267\u884C\u524D\u8BF7\u786E\u8BA4\u5DF2\u9605\u8BFB AGENTS.md \u548C openspec/config.yaml\u3002
|
|
2742
3390
|
`;
|
|
2743
3391
|
}
|
|
2744
3392
|
|
|
@@ -2752,10 +3400,10 @@ var CursorAdapter = class {
|
|
|
2752
3400
|
reason: "Cursor adapter is available for any project"
|
|
2753
3401
|
};
|
|
2754
3402
|
}
|
|
2755
|
-
async planInstall(_projectRoot) {
|
|
3403
|
+
async planInstall(_projectRoot, language) {
|
|
2756
3404
|
return {
|
|
2757
3405
|
tool: this.tool,
|
|
2758
|
-
files: [...cursorSkillFiles(), cursorRuleFile()].map((file) => ({
|
|
3406
|
+
files: [...cursorSkillFiles(language), cursorRuleFile(language)].map((file) => ({
|
|
2759
3407
|
...file,
|
|
2760
3408
|
managed: true
|
|
2761
3409
|
}))
|
|
@@ -2770,7 +3418,7 @@ var CursorAdapter = class {
|
|
|
2770
3418
|
if (existing && !existing.includes("FET:MANAGED") && !force) {
|
|
2771
3419
|
throw new FetError({
|
|
2772
3420
|
code: "TOOL_ADAPTER_CONFLICT" /* ToolAdapterConflict */,
|
|
2773
|
-
message: "Cursor \u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728\u4E14\u4E0D\
|
|
3421
|
+
message: "Cursor \u914D\u7F6E\u6587\u4EF6\u5DF2\u5B58\u5728\uFF0C\u4E14\u4E0D\u7531 FET \u7BA1\u7406\u3002",
|
|
2774
3422
|
details: { path: file.path },
|
|
2775
3423
|
suggestedCommand: "fet init --yes"
|
|
2776
3424
|
});
|
|
@@ -2795,7 +3443,7 @@ var CursorAdapter = class {
|
|
|
2795
3443
|
checks.push({
|
|
2796
3444
|
id: `cursor:${file.path}`,
|
|
2797
3445
|
status: !content ? "warn" : managed && versionMatches ? "pass" : "warn",
|
|
2798
|
-
message: !content ? `${file.path} \u7F3A\u5931` : !managed ? `${file.path} \u5B58\u5728\u4F46\u4E0D\
|
|
3446
|
+
message: !content ? `${file.path} \u7F3A\u5931` : !managed ? `${file.path} \u5DF2\u5B58\u5728\uFF0C\u4F46\u4E0D\u7531 FET \u7BA1\u7406` : !versionMatches ? `${file.path} adapterVersion \u5DF2\u8FC7\u671F` : `${file.path} \u5DF2\u5B58\u5728\u4E14\u7248\u672C\u5339\u914D`,
|
|
2799
3447
|
suggestedCommand: !content || !managed || !versionMatches ? "fet init" : void 0
|
|
2800
3448
|
});
|
|
2801
3449
|
}
|
|
@@ -3238,10 +3886,12 @@ var ProjectScanner = class {
|
|
|
3238
3886
|
|
|
3239
3887
|
// src/cli/output.ts
|
|
3240
3888
|
var OutputWriter = class {
|
|
3241
|
-
constructor(json) {
|
|
3889
|
+
constructor(json, language = "zh-CN") {
|
|
3242
3890
|
this.json = json;
|
|
3891
|
+
this.language = language;
|
|
3243
3892
|
}
|
|
3244
3893
|
json;
|
|
3894
|
+
language;
|
|
3245
3895
|
info(message, details) {
|
|
3246
3896
|
if (!this.json) {
|
|
3247
3897
|
process.stdout.write(`${message}${formatDetails(details)}
|
|
@@ -3254,10 +3904,8 @@ var OutputWriter = class {
|
|
|
3254
3904
|
`);
|
|
3255
3905
|
return;
|
|
3256
3906
|
}
|
|
3257
|
-
|
|
3258
|
-
process.stderr.write(`\u8B66\u544A\uFF1A${message}${formatDetails(details)}
|
|
3907
|
+
process.stderr.write(`${this.language === "en" ? "Warning" : "\u8B66\u544A"}\uFF1A${message}${formatDetails(details)}
|
|
3259
3908
|
`);
|
|
3260
|
-
}
|
|
3261
3909
|
}
|
|
3262
3910
|
error(error) {
|
|
3263
3911
|
if (this.json) {
|
|
@@ -3265,17 +3913,17 @@ var OutputWriter = class {
|
|
|
3265
3913
|
`);
|
|
3266
3914
|
return;
|
|
3267
3915
|
}
|
|
3268
|
-
process.stderr.write(
|
|
3916
|
+
process.stderr.write(`${this.language === "en" ? "FET cannot continue" : "FET \u65E0\u6CD5\u7EE7\u7EED"}\uFF1A${error.message}
|
|
3269
3917
|
`);
|
|
3270
3918
|
if (error.details !== void 0) {
|
|
3271
3919
|
process.stderr.write(`
|
|
3272
|
-
\u8BE6\u60C5\uFF1A
|
|
3920
|
+
${this.language === "en" ? "Details" : "\u8BE6\u60C5"}\uFF1A
|
|
3273
3921
|
${formatBlock(error.details)}
|
|
3274
3922
|
`);
|
|
3275
3923
|
}
|
|
3276
3924
|
if (error.suggestedCommand) {
|
|
3277
3925
|
process.stderr.write(`
|
|
3278
|
-
\u5EFA\u8BAE\uFF1A
|
|
3926
|
+
${this.language === "en" ? "Suggestion" : "\u5EFA\u8BAE"}\uFF1A
|
|
3279
3927
|
${error.suggestedCommand}
|
|
3280
3928
|
`);
|
|
3281
3929
|
}
|
|
@@ -3289,11 +3937,13 @@ ${formatBlock(error.details)}
|
|
|
3289
3937
|
process.stdout.write(`${result.summary}
|
|
3290
3938
|
`);
|
|
3291
3939
|
for (const warning of result.warnings ?? []) {
|
|
3292
|
-
process.stdout.write(
|
|
3940
|
+
process.stdout.write(`${this.language === "en" ? "Warning" : "\u8B66\u544A"}\uFF1A${warning}
|
|
3293
3941
|
`);
|
|
3294
3942
|
}
|
|
3295
3943
|
if (result.nextSteps?.length) {
|
|
3296
|
-
process.stdout.write(
|
|
3944
|
+
process.stdout.write(`
|
|
3945
|
+
${this.language === "en" ? "Next steps" : "\u4E0B\u4E00\u6B65"}\uFF1A
|
|
3946
|
+
`);
|
|
3297
3947
|
for (const step of result.nextSteps) {
|
|
3298
3948
|
process.stdout.write(` ${step}
|
|
3299
3949
|
`);
|
|
@@ -3318,7 +3968,10 @@ function formatBlock(details) {
|
|
|
3318
3968
|
async function createCommandContext(command, options) {
|
|
3319
3969
|
const projectRoot = resolve(options.cwd ?? process.cwd());
|
|
3320
3970
|
const project = await detectProjectIdentity(projectRoot);
|
|
3321
|
-
const
|
|
3971
|
+
const stateStore = new StateStore(projectRoot, FET_VERSION, project);
|
|
3972
|
+
const savedState = await stateStore.readGlobal();
|
|
3973
|
+
const language = normalizeLanguage(options.lang ?? savedState?.language.current);
|
|
3974
|
+
const output = new OutputWriter(Boolean(options.json), language);
|
|
3322
3975
|
return {
|
|
3323
3976
|
command,
|
|
3324
3977
|
cwd: projectRoot,
|
|
@@ -3328,9 +3981,11 @@ async function createCommandContext(command, options) {
|
|
|
3328
3981
|
verbose: Boolean(options.verbose),
|
|
3329
3982
|
yes: Boolean(options.yes),
|
|
3330
3983
|
changeId: options.change,
|
|
3984
|
+
language,
|
|
3985
|
+
explicitLanguage: options.lang !== void 0,
|
|
3331
3986
|
fetVersion: FET_VERSION,
|
|
3332
3987
|
output,
|
|
3333
|
-
stateStore
|
|
3988
|
+
stateStore,
|
|
3334
3989
|
openSpec: new DefaultOpenSpecAdapter(),
|
|
3335
3990
|
scanner: new ProjectScanner(),
|
|
3336
3991
|
toolAdapters: [new CursorAdapter(), new CodexAdapter()]
|
|
@@ -3339,25 +3994,33 @@ async function createCommandContext(command, options) {
|
|
|
3339
3994
|
|
|
3340
3995
|
// src/cli/index.ts
|
|
3341
3996
|
var program = new Command();
|
|
3342
|
-
program.name("fet").description("
|
|
3997
|
+
program.name("fet").description("\u56F4\u7ED5 OpenSpec \u7684\u524D\u7AEF\u5F00\u53D1\u5DE5\u4F5C\u6D41\u7F16\u6392\u5DE5\u5177\u3002").enablePositionalOptions().version(FET_VERSION).option("--cwd <path>", "\u6307\u5B9A\u9879\u76EE\u6839\u76EE\u5F55").option("--change <id>", "\u6307\u5B9A OpenSpec change").option("--lang <language>", "\u6307\u5B9A FET \u4EA4\u4E92\u4FE1\u606F\u548C\u751F\u6210\u4EA7\u7269\u8BED\u8A00\uFF0C\u9ED8\u8BA4 zh-CN").option("--yes", "\u5BF9\u4F4E\u98CE\u9669\u786E\u8BA4\u4F7F\u7528\u9ED8\u8BA4\u540C\u610F").option("--json", "\u8F93\u51FA\u673A\u5668\u53EF\u8BFB JSON").option("--verbose", "\u8F93\u51FA\u8BCA\u65AD\u7EC6\u8282").option("--no-color", "\u7981\u7528\u7EC8\u7AEF\u989C\u8272");
|
|
3343
3998
|
addGlobalOptions(program.command("init")).description("\u521D\u59CB\u5316 FET + OpenSpec").action(wrap("init", initCommand));
|
|
3344
3999
|
addGlobalOptions(program.command("update-context")).description("\u66F4\u65B0\u9879\u76EE\u4E0A\u4E0B\u6587").action(wrap("update-context", updateContextCommand));
|
|
3345
|
-
addGlobalOptions(program.command("fill-context")).description("
|
|
3346
|
-
var graph = addGlobalOptions(program.command("graph").description("
|
|
4000
|
+
addGlobalOptions(program.command("fill-context")).description("\u5237\u65B0 IDE \u586B\u5145 AGENTS.md \u5360\u4F4D\u7B26\u7684\u63D0\u793A\u6587\u4EF6").action(wrap("fill-context", fillContextCommand));
|
|
4001
|
+
var graph = addGlobalOptions(program.command("graph").description("\u7BA1\u7406\u53EF\u9009\u7684 GitNexus \u4EE3\u7801\u56FE\u652F\u6301"));
|
|
3347
4002
|
for (const action of ["status", "setup", "doctor", "handoff"]) {
|
|
3348
|
-
addGlobalOptions(graph.command(action).description(
|
|
4003
|
+
addGlobalOptions(graph.command(action).description(`\u8FD0\u884C fet graph ${action}`)).action(wrap("graph", (ctx) => graphCommand(ctx, action)));
|
|
3349
4004
|
}
|
|
3350
4005
|
for (const action of ["init", "refresh"]) {
|
|
3351
|
-
addGlobalOptions(
|
|
4006
|
+
addGlobalOptions(
|
|
4007
|
+
graph.command(`${action} [args...]`).description(`\u901A\u8FC7 GitNexus analyze \u6267\u884C graph ${action}`).allowUnknownOption(true).passThroughOptions()
|
|
4008
|
+
).action(wrap("graph", (ctx, args = []) => graphCommand(ctx, action, args)));
|
|
3352
4009
|
}
|
|
3353
4010
|
addGlobalOptions(program.command("doctor").description("\u8BCA\u65AD\u72B6\u6001\u3001\u914D\u7F6E\u4E0E\u4E00\u81F4\u6027").option("--fix-lock", "\u6E05\u7406 FET \u9501\u6587\u4EF6")).action(
|
|
3354
4011
|
wrap("doctor", (ctx, options) => doctorCommand(ctx, { fixLock: Boolean(options.fixLock) }))
|
|
3355
4012
|
);
|
|
3356
|
-
addGlobalOptions(program.command("verify").description("\u6700\u7EC8\u8D28\u91CF\u9A8C\u8BC1").option("--done", "\u58F0\u660E\u624B\u52A8\u9A8C\u8BC1\u5DF2\u5B8C\u6210").option("--auto", "\u751F\u6210\u6216\u6267\u884C\u81EA\u52A8\u9A8C\u8BC1\u8BA1\u5212")).action(
|
|
4013
|
+
addGlobalOptions(program.command("verify").description("\u6700\u7EC8\u8D28\u91CF\u9A8C\u8BC1").option("--done", "\u58F0\u660E\u624B\u52A8\u9A8C\u8BC1\u5DF2\u5B8C\u6210").option("--auto", "\u751F\u6210\u6216\u6267\u884C\u81EA\u52A8\u9A8C\u8BC1\u8BA1\u5212")).action(
|
|
4014
|
+
wrap("verify", verifyCommand)
|
|
4015
|
+
);
|
|
3357
4016
|
for (const command of ["explore", "propose", "new", "continue", "ff", "apply", "sync", "archive", "bulk-archive", "onboard"]) {
|
|
3358
|
-
addGlobalOptions(program.command(`${command} [args...]`).description(`\u4EE3\u7406\u6267\u884C openspec ${command}`).allowUnknownOption(true).passThroughOptions()).action(
|
|
4017
|
+
addGlobalOptions(program.command(`${command} [args...]`).description(`\u4EE3\u7406\u6267\u884C openspec ${command}`).allowUnknownOption(true).passThroughOptions()).action(
|
|
4018
|
+
wrap(command, (ctx, args = []) => proxyCommand(ctx, command, args))
|
|
4019
|
+
);
|
|
3359
4020
|
}
|
|
3360
|
-
addGlobalOptions(program.command("passthrough <command> [args...]").description("\u900F\u4F20\u6682\u672A\u63A5\u7BA1\u7684 OpenSpec \u547D\u4EE4").allowUnknownOption(true).passThroughOptions()).action(
|
|
4021
|
+
addGlobalOptions(program.command("passthrough <command> [args...]").description("\u900F\u4F20\u6682\u672A\u63A5\u7BA1\u7684 OpenSpec \u547D\u4EE4").allowUnknownOption(true).passThroughOptions()).action(
|
|
4022
|
+
wrap("passthrough", (ctx, command, args = []) => passthroughCommand(ctx, command, args))
|
|
4023
|
+
);
|
|
3361
4024
|
program.parseAsync(process.argv).catch((error) => {
|
|
3362
4025
|
const json = process.argv.includes("--json");
|
|
3363
4026
|
const output = new OutputWriter(json);
|
|
@@ -3371,7 +4034,7 @@ function wrap(command, handler) {
|
|
|
3371
4034
|
const opts = isCommandLike(maybeCommand) ? { ...maybeCommand.parent?.opts(), ...maybeCommand.opts() } : program.opts();
|
|
3372
4035
|
const ctx = await createCommandContext(command, { ...opts, ...extractGlobalOptions(args) });
|
|
3373
4036
|
try {
|
|
3374
|
-
await
|
|
4037
|
+
await handleModelPolicyRecommendation(ctx);
|
|
3375
4038
|
await warnIfContextPlaceholdersRemain(ctx);
|
|
3376
4039
|
await handler(ctx, ...args);
|
|
3377
4040
|
} catch (error) {
|
|
@@ -3381,45 +4044,53 @@ function wrap(command, handler) {
|
|
|
3381
4044
|
}
|
|
3382
4045
|
};
|
|
3383
4046
|
}
|
|
3384
|
-
async function
|
|
4047
|
+
async function handleModelPolicyRecommendation(ctx) {
|
|
3385
4048
|
const mismatch = getCommandModelPolicyMismatch(ctx.command);
|
|
3386
4049
|
if (!mismatch) {
|
|
3387
4050
|
return;
|
|
3388
4051
|
}
|
|
3389
|
-
const warning = formatModelPolicyMismatch(mismatch);
|
|
3390
|
-
|
|
3391
|
-
|
|
4052
|
+
const warning = formatModelPolicyMismatch(mismatch, ctx.language);
|
|
4053
|
+
const policyMode = getModelPolicyMode();
|
|
4054
|
+
ctx.output.warn(`${warning} ${renderModelPolicyActionHint(policyMode, ctx.language)}`);
|
|
4055
|
+
if (policyMode !== "confirm" || ctx.yes || ctx.json || !process.stdin.isTTY || !process.stderr.isTTY) {
|
|
3392
4056
|
return;
|
|
3393
4057
|
}
|
|
3394
4058
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
3395
4059
|
try {
|
|
3396
|
-
const
|
|
4060
|
+
const question = ctx.language === "en" ? "Continue anyway? [y/N] " : "\u4ECD\u7136\u7EE7\u7EED\uFF1F[y/N] ";
|
|
4061
|
+
const answer = (await rl.question(question)).trim().toLowerCase();
|
|
3397
4062
|
if (answer !== "y" && answer !== "yes") {
|
|
3398
4063
|
throw new FetError({
|
|
3399
4064
|
code: "USER_CANCELLED" /* UserCancelled */,
|
|
3400
|
-
message: "Command cancelled so you can switch IDE model.",
|
|
4065
|
+
message: ctx.language === "en" ? "Command cancelled so you can switch IDE model." : "\u547D\u4EE4\u5DF2\u53D6\u6D88\uFF0C\u4F60\u53EF\u4EE5\u5148\u5207\u6362 IDE \u6A21\u578B\u3002",
|
|
3401
4066
|
details: { command: ctx.command, detected: mismatch.detected, recommended: mismatch.recommended },
|
|
3402
|
-
suggestedCommand: `Switch IDE model, then rerun fet ${ctx.command}.`
|
|
4067
|
+
suggestedCommand: ctx.language === "en" ? `Switch IDE model, then rerun fet ${ctx.command}.` : `\u5207\u6362 IDE \u6A21\u578B\u540E\u91CD\u65B0\u8FD0\u884C fet ${ctx.command}\u3002`
|
|
3403
4068
|
});
|
|
3404
4069
|
}
|
|
3405
4070
|
} finally {
|
|
3406
4071
|
rl.close();
|
|
3407
4072
|
}
|
|
3408
4073
|
}
|
|
4074
|
+
function renderModelPolicyActionHint(policyMode, language) {
|
|
4075
|
+
if (policyMode === "confirm") {
|
|
4076
|
+
return language === "en" ? "Choose whether to continue this command or stop and switch models." : "\u8BF7\u9009\u62E9\u7EE7\u7EED\u6267\u884C\u672C\u547D\u4EE4\uFF0C\u6216\u505C\u6B62\u540E\u624B\u52A8\u5207\u6362\u6A21\u578B\u3002";
|
|
4077
|
+
}
|
|
4078
|
+
return language === "en" ? "This is advisory because FET_MODEL_POLICY=warn; the command will continue." : "\u5F53\u524D\u8BBE\u7F6E FET_MODEL_POLICY=warn\uFF0C\u8BE5\u63D0\u9192\u4EC5\u4F5C\u4E3A\u5EFA\u8BAE\uFF0C\u547D\u4EE4\u4F1A\u7EE7\u7EED\u6267\u884C\u3002";
|
|
4079
|
+
}
|
|
3409
4080
|
async function warnIfContextPlaceholdersRemain(ctx) {
|
|
3410
4081
|
if (["init", "update-context", "fill-context", "doctor"].includes(ctx.command)) {
|
|
3411
4082
|
return;
|
|
3412
4083
|
}
|
|
3413
4084
|
const count2 = await countAgentsLlmPlaceholders(ctx.projectRoot);
|
|
3414
4085
|
if (count2 > 0) {
|
|
3415
|
-
ctx.output.warn(renderAgentsPlaceholderWarning(count2));
|
|
4086
|
+
ctx.output.warn(renderAgentsPlaceholderWarning(count2, ctx.language));
|
|
3416
4087
|
}
|
|
3417
4088
|
}
|
|
3418
4089
|
function isCommandLike(value) {
|
|
3419
4090
|
return value instanceof Command;
|
|
3420
4091
|
}
|
|
3421
4092
|
function addGlobalOptions(command) {
|
|
3422
|
-
return command.option("--cwd <path>", "\u6307\u5B9A\u9879\u76EE\u6839\u76EE\u5F55").option("--change <id>", "\u6307\u5B9A OpenSpec change").option("--yes", "\u5BF9\u4F4E\u98CE\u9669\u786E\u8BA4\u4F7F\u7528\u9ED8\u8BA4\u540C\u610F").option("--json", "\u8F93\u51FA\u673A\u5668\u53EF\u8BFB JSON").option("--verbose", "\u8F93\u51FA\u8BCA\u65AD\u7EC6\u8282").option("--no-color", "\u7981\u7528\u7EC8\u7AEF\u989C\u8272");
|
|
4093
|
+
return command.option("--cwd <path>", "\u6307\u5B9A\u9879\u76EE\u6839\u76EE\u5F55").option("--change <id>", "\u6307\u5B9A OpenSpec change").option("--lang <language>", "\u6307\u5B9A FET \u4EA4\u4E92\u4FE1\u606F\u548C\u751F\u6210\u4EA7\u7269\u8BED\u8A00\uFF0C\u9ED8\u8BA4 zh-CN").option("--yes", "\u5BF9\u4F4E\u98CE\u9669\u786E\u8BA4\u4F7F\u7528\u9ED8\u8BA4\u540C\u610F").option("--json", "\u8F93\u51FA\u673A\u5668\u53EF\u8BFB JSON").option("--verbose", "\u8F93\u51FA\u8BCA\u65AD\u7EC6\u8282").option("--no-color", "\u7981\u7528\u7EC8\u7AEF\u989C\u8272");
|
|
3423
4094
|
}
|
|
3424
4095
|
function extractGlobalOptions(args) {
|
|
3425
4096
|
const values = args.flatMap((arg) => Array.isArray(arg) ? arg : []);
|
|
@@ -3439,6 +4110,11 @@ function extractGlobalOptions(args) {
|
|
|
3439
4110
|
index += 1;
|
|
3440
4111
|
} else if (value.startsWith("--change=")) {
|
|
3441
4112
|
options.change = value.slice("--change=".length);
|
|
4113
|
+
} else if (value === "--lang" && typeof values[index + 1] === "string") {
|
|
4114
|
+
options.lang = values[index + 1];
|
|
4115
|
+
index += 1;
|
|
4116
|
+
} else if (value.startsWith("--lang=")) {
|
|
4117
|
+
options.lang = value.slice("--lang=".length);
|
|
3442
4118
|
} else if (value === "--yes") {
|
|
3443
4119
|
options.yes = true;
|
|
3444
4120
|
} else if (value === "--json") {
|