@nick848/fet 1.0.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README_en.md
CHANGED
|
@@ -42,6 +42,7 @@ Check the installation:
|
|
|
42
42
|
```sh
|
|
43
43
|
fet --version
|
|
44
44
|
fet --help
|
|
45
|
+
fet update
|
|
45
46
|
```
|
|
46
47
|
|
|
47
48
|
## Quick Start
|
|
@@ -100,6 +101,7 @@ fet init --lang en
|
|
|
100
101
|
| Command | Usage | Description |
|
|
101
102
|
|---------|-------|-------------|
|
|
102
103
|
| `fet init` | `fet init [--yes] [--lang <language>]` | Initialize FET and OpenSpec; generate context, state, Cursor integration, and Codex workflow guides. |
|
|
104
|
+
| `fet update` | `fet update` | Check whether FET is the latest published version and automatically upgrade when a newer version is available. |
|
|
103
105
|
| `fet update-context` | `fet update-context [--yes]` | Rescan the project and update FET-managed regions in `AGENTS.md` and `openspec/config.yaml`. |
|
|
104
106
|
| `fet fill-context` | `fet fill-context [--yes]` | Refresh IDE handoff commands that ask AI to replace `AGENTS.md` placeholders. |
|
|
105
107
|
| `fet doctor` | `fet doctor [--fix-lock]` | Diagnose OpenSpec, FET state, context files, tool integration, and lock files. |
|
|
@@ -15,6 +15,7 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
|
|
|
15
15
|
ErrorCode2["LockHeld"] = "LOCK_HELD";
|
|
16
16
|
ErrorCode2["UserCancelled"] = "USER_CANCELLED";
|
|
17
17
|
ErrorCode2["UnsafeScriptApprovalRequired"] = "UNSAFE_SCRIPT_APPROVAL_REQUIRED";
|
|
18
|
+
ErrorCode2["UpdateFailed"] = "UPDATE_FAILED";
|
|
18
19
|
ErrorCode2["ToolAdapterConflict"] = "TOOL_ADAPTER_CONFLICT";
|
|
19
20
|
ErrorCode2["ConfigInvalid"] = "CONFIG_INVALID";
|
|
20
21
|
ErrorCode2["FileSystemError"] = "FILE_SYSTEM_ERROR";
|
|
@@ -31,6 +32,7 @@ function exitCodeForError(code) {
|
|
|
31
32
|
return 3;
|
|
32
33
|
case "OPENSPEC_COMMAND_FAILED" /* OpenSpecCommandFailed */:
|
|
33
34
|
case "GRAPH_COMMAND_FAILED" /* GraphCommandFailed */:
|
|
35
|
+
case "UPDATE_FAILED" /* UpdateFailed */:
|
|
34
36
|
return 4;
|
|
35
37
|
case "OPENSPEC_STRUCTURE_UNKNOWN" /* OpenSpecStructureUnknown */:
|
|
36
38
|
case "STATE_SCHEMA_UNSUPPORTED" /* StateSchemaUnsupported */:
|
|
@@ -105,4 +107,4 @@ export {
|
|
|
105
107
|
FetError,
|
|
106
108
|
toFetError
|
|
107
109
|
};
|
|
108
|
-
//# sourceMappingURL=chunk-
|
|
110
|
+
//# sourceMappingURL=chunk-J5WB4KAL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors/codes.ts","../src/errors/fet-error.ts"],"sourcesContent":["export enum ErrorCode {\n Unknown = \"UNKNOWN\",\n InvalidArguments = \"INVALID_ARGUMENTS\",\n OpenSpecNotFound = \"OPENSPEC_NOT_FOUND\",\n OpenSpecUnsupportedVersion = \"OPENSPEC_UNSUPPORTED_VERSION\",\n OpenSpecCommandFailed = \"OPENSPEC_COMMAND_FAILED\",\n OpenSpecStructureUnknown = \"OPENSPEC_STRUCTURE_UNKNOWN\",\n GraphProviderNotFound = \"GRAPH_PROVIDER_NOT_FOUND\",\n GraphCommandFailed = \"GRAPH_COMMAND_FAILED\",\n StateSchemaUnsupported = \"STATE_SCHEMA_UNSUPPORTED\",\n StateCorrupted = \"STATE_CORRUPTED\",\n LockHeld = \"LOCK_HELD\",\n UserCancelled = \"USER_CANCELLED\",\n UnsafeScriptApprovalRequired = \"UNSAFE_SCRIPT_APPROVAL_REQUIRED\",\n UpdateFailed = \"UPDATE_FAILED\",\n ToolAdapterConflict = \"TOOL_ADAPTER_CONFLICT\",\n ConfigInvalid = \"CONFIG_INVALID\",\n FileSystemError = \"FILE_SYSTEM_ERROR\"\n}\n\nexport function exitCodeForError(code: ErrorCode): number {\n switch (code) {\n case ErrorCode.InvalidArguments:\n case ErrorCode.ConfigInvalid:\n return 2;\n case ErrorCode.OpenSpecNotFound:\n case ErrorCode.OpenSpecUnsupportedVersion:\n case ErrorCode.GraphProviderNotFound:\n return 3;\n case ErrorCode.OpenSpecCommandFailed:\n case ErrorCode.GraphCommandFailed:\n case ErrorCode.UpdateFailed:\n return 4;\n case ErrorCode.OpenSpecStructureUnknown:\n case ErrorCode.StateSchemaUnsupported:\n case ErrorCode.StateCorrupted:\n case ErrorCode.ToolAdapterConflict:\n case ErrorCode.FileSystemError:\n return 5;\n case ErrorCode.LockHeld:\n return 6;\n case ErrorCode.UserCancelled:\n return 7;\n case ErrorCode.UnsafeScriptApprovalRequired:\n return 8;\n case ErrorCode.Unknown:\n default:\n return 1;\n }\n}\n","import { ErrorCode, exitCodeForError } from \"./codes.js\";\n\nexport interface FetErrorOptions {\n code: ErrorCode;\n message: string;\n details?: unknown;\n recoverable?: boolean;\n suggestedCommand?: string;\n cause?: unknown;\n}\n\nexport class FetError extends Error {\n readonly code: ErrorCode;\n readonly exitCode: number;\n readonly details?: unknown;\n readonly recoverable: boolean;\n readonly suggestedCommand?: string;\n override readonly cause?: unknown;\n\n constructor(options: FetErrorOptions) {\n super(options.message);\n this.name = \"FetError\";\n this.code = options.code;\n this.exitCode = exitCodeForError(options.code);\n this.details = options.details;\n this.recoverable = options.recoverable ?? true;\n this.suggestedCommand = options.suggestedCommand;\n this.cause = options.cause;\n }\n\n toJSON() {\n return {\n code: this.code,\n exitCode: this.exitCode,\n message: this.message,\n details: this.details,\n recoverable: this.recoverable,\n suggestedCommand: this.suggestedCommand\n };\n }\n}\n\nexport function toFetError(error: unknown): FetError {\n if (error instanceof FetError) {\n return error;\n }\n\n if (error instanceof Error) {\n return new FetError({\n code: ErrorCode.Unknown,\n message: error.message,\n recoverable: false,\n cause: error\n });\n }\n\n return new FetError({\n code: ErrorCode.Unknown,\n message: \"Unknown error\",\n details: error,\n recoverable: false\n });\n}\n"],"mappings":";;;AAAO,IAAK,YAAL,kBAAKA,eAAL;AACL,EAAAA,WAAA,aAAU;AACV,EAAAA,WAAA,sBAAmB;AACnB,EAAAA,WAAA,sBAAmB;AACnB,EAAAA,WAAA,gCAA6B;AAC7B,EAAAA,WAAA,2BAAwB;AACxB,EAAAA,WAAA,8BAA2B;AAC3B,EAAAA,WAAA,2BAAwB;AACxB,EAAAA,WAAA,wBAAqB;AACrB,EAAAA,WAAA,4BAAyB;AACzB,EAAAA,WAAA,oBAAiB;AACjB,EAAAA,WAAA,cAAW;AACX,EAAAA,WAAA,mBAAgB;AAChB,EAAAA,WAAA,kCAA+B;AAC/B,EAAAA,WAAA,kBAAe;AACf,EAAAA,WAAA,yBAAsB;AACtB,EAAAA,WAAA,mBAAgB;AAChB,EAAAA,WAAA,qBAAkB;AAjBR,SAAAA;AAAA,GAAA;AAoBL,SAAS,iBAAiB,MAAyB;AACxD,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL;AACE,aAAO;AAAA,EACX;AACF;;;ACtCO,IAAM,WAAN,cAAuB,MAAM;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACS;AAAA,EAElB,YAAY,SAA0B;AACpC,UAAM,QAAQ,OAAO;AACrB,SAAK,OAAO;AACZ,SAAK,OAAO,QAAQ;AACpB,SAAK,WAAW,iBAAiB,QAAQ,IAAI;AAC7C,SAAK,UAAU,QAAQ;AACvB,SAAK,cAAc,QAAQ,eAAe;AAC1C,SAAK,mBAAmB,QAAQ;AAChC,SAAK,QAAQ,QAAQ;AAAA,EACvB;AAAA,EAEA,SAAS;AACP,WAAO;AAAA,MACL,MAAM,KAAK;AAAA,MACX,UAAU,KAAK;AAAA,MACf,SAAS,KAAK;AAAA,MACd,SAAS,KAAK;AAAA,MACd,aAAa,KAAK;AAAA,MAClB,kBAAkB,KAAK;AAAA,IACzB;AAAA,EACF;AACF;AAEO,SAAS,WAAW,OAA0B;AACnD,MAAI,iBAAiB,UAAU;AAC7B,WAAO;AAAA,EACT;AAEA,MAAI,iBAAiB,OAAO;AAC1B,WAAO,IAAI,SAAS;AAAA,MAClB;AAAA,MACA,SAAS,MAAM;AAAA,MACf,aAAa;AAAA,MACb,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,SAAO,IAAI,SAAS;AAAA,IAClB;AAAA,IACA,SAAS;AAAA,IACT,SAAS;AAAA,IACT,aAAa;AAAA,EACf,CAAC;AACH;","names":["ErrorCode"]}
|
package/dist/cli/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
FetError,
|
|
4
4
|
toFetError
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-J5WB4KAL.js";
|
|
6
6
|
|
|
7
7
|
// src/cli/index.ts
|
|
8
8
|
import { createInterface } from "readline/promises";
|
|
@@ -1989,6 +1989,7 @@ async function proxyCommand(ctx, command, args) {
|
|
|
1989
1989
|
await assertVerified(ctx);
|
|
1990
1990
|
}
|
|
1991
1991
|
const mapped = await mapOpenSpecCommand(ctx, command, openSpecArgs);
|
|
1992
|
+
await assertOpenSpecCommandSupported(ctx, mapped.command, command);
|
|
1992
1993
|
const mappedChangeId = extractChangeId(mapped.args);
|
|
1993
1994
|
const targetChangeId = command === "archive" ? mapped.args[0] ?? ctx.changeId ?? mappedChangeId : ctx.changeId ?? mappedChangeId;
|
|
1994
1995
|
runState.graphContext = await buildWorkflowGraphContext(ctx, {
|
|
@@ -2055,6 +2056,23 @@ async function proxyCommand(ctx, command, args) {
|
|
|
2055
2056
|
data: graphContext ? { graphContext } : void 0
|
|
2056
2057
|
});
|
|
2057
2058
|
}
|
|
2059
|
+
async function assertOpenSpecCommandSupported(ctx, openSpecCommand, fetCommand) {
|
|
2060
|
+
const capabilities = await ctx.openSpec.getCapabilities();
|
|
2061
|
+
if (capabilities.commands.includes(openSpecCommand)) {
|
|
2062
|
+
return;
|
|
2063
|
+
}
|
|
2064
|
+
throw new FetError({
|
|
2065
|
+
code: "OPENSPEC_UNSUPPORTED_VERSION" /* OpenSpecUnsupportedVersion */,
|
|
2066
|
+
message: `OpenSpec CLI ${capabilities.version} does not expose command "${openSpecCommand}" required by "fet ${fetCommand}". FET will not substitute another workflow command automatically.`,
|
|
2067
|
+
details: {
|
|
2068
|
+
openSpecVersion: capabilities.version,
|
|
2069
|
+
requiredCommand: openSpecCommand,
|
|
2070
|
+
availableCommands: capabilities.commands,
|
|
2071
|
+
supported: capabilities.supported
|
|
2072
|
+
},
|
|
2073
|
+
suggestedCommand: "Upgrade OpenSpec to a version that supports this command, then rerun FET. Try: npm install -g @fission-ai/openspec@latest && fet doctor. If your OpenSpec version intentionally removed this command, pause and choose a compatible FET workflow instead of running ff automatically."
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2058
2076
|
async function createChangelogEntry(projectRoot, changeId) {
|
|
2059
2077
|
return {
|
|
2060
2078
|
updateTime: formatLocalTimestamp(/* @__PURE__ */ new Date()),
|
|
@@ -2236,6 +2254,195 @@ async function assertVerified(ctx) {
|
|
|
2236
2254
|
}
|
|
2237
2255
|
}
|
|
2238
2256
|
|
|
2257
|
+
// src/commands/update.ts
|
|
2258
|
+
import { spawn } from "child_process";
|
|
2259
|
+
var DEFAULT_PACKAGE_NAME = "@nick848/fet";
|
|
2260
|
+
async function updateCommand(ctx) {
|
|
2261
|
+
const packageName = process.env.FET_UPDATE_PACKAGE_NAME ?? DEFAULT_PACKAGE_NAME;
|
|
2262
|
+
const npmExecutable = process.env.FET_UPDATE_NPM_EXECUTABLE ?? defaultNpmExecutable();
|
|
2263
|
+
const latestVersion = await resolveLatestVersion(packageName, npmExecutable);
|
|
2264
|
+
const currentVersion = ctx.fetVersion;
|
|
2265
|
+
if (compareVersions(currentVersion, latestVersion) >= 0) {
|
|
2266
|
+
ctx.output.result({
|
|
2267
|
+
ok: true,
|
|
2268
|
+
command: "update",
|
|
2269
|
+
summary: ctx.language === "en" ? `FET is already up to date (${currentVersion}).` : `FET \u5DF2\u662F\u6700\u65B0\u7248 (${currentVersion})\u3002`,
|
|
2270
|
+
data: {
|
|
2271
|
+
packageName,
|
|
2272
|
+
currentVersion,
|
|
2273
|
+
latestVersion,
|
|
2274
|
+
updated: false
|
|
2275
|
+
}
|
|
2276
|
+
});
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
if (!ctx.json) {
|
|
2280
|
+
ctx.output.info(
|
|
2281
|
+
ctx.language === "en" ? `Updating FET from ${currentVersion} to ${latestVersion}...` : `\u6B63\u5728\u5C06 FET \u4ECE ${currentVersion} \u5347\u7EA7\u5230 ${latestVersion}...`
|
|
2282
|
+
);
|
|
2283
|
+
}
|
|
2284
|
+
const installArgs = ["install", "-g", `${packageName}@latest`];
|
|
2285
|
+
const result = await runNpm(npmExecutable, installArgs, {
|
|
2286
|
+
cwd: ctx.cwd,
|
|
2287
|
+
stdio: ctx.json ? "pipe" : "inherit"
|
|
2288
|
+
});
|
|
2289
|
+
if (result.exitCode !== 0) {
|
|
2290
|
+
throw new FetError({
|
|
2291
|
+
code: "UPDATE_FAILED" /* UpdateFailed */,
|
|
2292
|
+
message: ctx.language === "en" ? "FET update failed." : "FET \u5347\u7EA7\u5931\u8D25\u3002",
|
|
2293
|
+
details: result,
|
|
2294
|
+
suggestedCommand: `${npmExecutable} ${installArgs.join(" ")}`
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
ctx.output.result({
|
|
2298
|
+
ok: true,
|
|
2299
|
+
command: "update",
|
|
2300
|
+
summary: ctx.language === "en" ? `FET updated from ${currentVersion} to ${latestVersion}.` : `FET \u5DF2\u4ECE ${currentVersion} \u5347\u7EA7\u5230 ${latestVersion}\u3002`,
|
|
2301
|
+
data: {
|
|
2302
|
+
packageName,
|
|
2303
|
+
currentVersion,
|
|
2304
|
+
latestVersion,
|
|
2305
|
+
updated: true,
|
|
2306
|
+
installCommand: `${npmExecutable} ${installArgs.join(" ")}`
|
|
2307
|
+
}
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
async function resolveLatestVersion(packageName, npmExecutable) {
|
|
2311
|
+
const override = process.env.FET_UPDATE_LATEST_VERSION?.trim();
|
|
2312
|
+
if (override) {
|
|
2313
|
+
return override;
|
|
2314
|
+
}
|
|
2315
|
+
const result = await runNpm(npmExecutable, ["view", packageName, "version", "--json"], {
|
|
2316
|
+
cwd: process.cwd(),
|
|
2317
|
+
stdio: "pipe"
|
|
2318
|
+
});
|
|
2319
|
+
if (result.exitCode !== 0) {
|
|
2320
|
+
throw new FetError({
|
|
2321
|
+
code: "UPDATE_FAILED" /* UpdateFailed */,
|
|
2322
|
+
message: "Unable to check the latest FET version from npm.",
|
|
2323
|
+
details: result,
|
|
2324
|
+
suggestedCommand: `${npmExecutable} view ${packageName} version`
|
|
2325
|
+
});
|
|
2326
|
+
}
|
|
2327
|
+
const version = parseNpmVersion(result.stdout ?? "");
|
|
2328
|
+
if (!version) {
|
|
2329
|
+
throw new FetError({
|
|
2330
|
+
code: "UPDATE_FAILED" /* UpdateFailed */,
|
|
2331
|
+
message: "npm returned an invalid FET version.",
|
|
2332
|
+
details: { stdout: result.stdout }
|
|
2333
|
+
});
|
|
2334
|
+
}
|
|
2335
|
+
return version;
|
|
2336
|
+
}
|
|
2337
|
+
function parseNpmVersion(stdout) {
|
|
2338
|
+
const trimmed = stdout.trim();
|
|
2339
|
+
if (!trimmed) {
|
|
2340
|
+
return null;
|
|
2341
|
+
}
|
|
2342
|
+
try {
|
|
2343
|
+
const parsed = JSON.parse(trimmed);
|
|
2344
|
+
return typeof parsed === "string" && parsed.length > 0 ? parsed : null;
|
|
2345
|
+
} catch {
|
|
2346
|
+
return trimmed;
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
function runNpm(command, args, options) {
|
|
2350
|
+
return new Promise((resolve2, reject) => {
|
|
2351
|
+
const stdout = [];
|
|
2352
|
+
const stderr = [];
|
|
2353
|
+
const child = spawn(command, args, {
|
|
2354
|
+
cwd: options.cwd,
|
|
2355
|
+
stdio: options.stdio,
|
|
2356
|
+
shell: process.platform === "win32"
|
|
2357
|
+
});
|
|
2358
|
+
child.stdout?.on("data", (chunk) => stdout.push(chunk));
|
|
2359
|
+
child.stderr?.on("data", (chunk) => stderr.push(chunk));
|
|
2360
|
+
child.on("error", (error) => {
|
|
2361
|
+
reject(
|
|
2362
|
+
new FetError({
|
|
2363
|
+
code: "UPDATE_FAILED" /* UpdateFailed */,
|
|
2364
|
+
message: "Unable to run npm for FET update.",
|
|
2365
|
+
details: { command, args },
|
|
2366
|
+
cause: error,
|
|
2367
|
+
suggestedCommand: `${command} ${args.join(" ")}`
|
|
2368
|
+
})
|
|
2369
|
+
);
|
|
2370
|
+
});
|
|
2371
|
+
child.on("close", (exitCode, signal) => {
|
|
2372
|
+
resolve2({
|
|
2373
|
+
command,
|
|
2374
|
+
args,
|
|
2375
|
+
exitCode: exitCode ?? 1,
|
|
2376
|
+
signal,
|
|
2377
|
+
stdout: stdout.length ? Buffer.concat(stdout).toString("utf8") : void 0,
|
|
2378
|
+
stderr: stderr.length ? Buffer.concat(stderr).toString("utf8") : void 0
|
|
2379
|
+
});
|
|
2380
|
+
});
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
function defaultNpmExecutable() {
|
|
2384
|
+
return process.platform === "win32" ? "npm.cmd" : "npm";
|
|
2385
|
+
}
|
|
2386
|
+
function compareVersions(left, right) {
|
|
2387
|
+
const leftVersion = parseVersion(left);
|
|
2388
|
+
const rightVersion = parseVersion(right);
|
|
2389
|
+
for (let index = 0; index < 3; index += 1) {
|
|
2390
|
+
const diff = leftVersion.main[index] - rightVersion.main[index];
|
|
2391
|
+
if (diff !== 0) {
|
|
2392
|
+
return Math.sign(diff);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
if (!leftVersion.prerelease.length && rightVersion.prerelease.length) {
|
|
2396
|
+
return 1;
|
|
2397
|
+
}
|
|
2398
|
+
if (leftVersion.prerelease.length && !rightVersion.prerelease.length) {
|
|
2399
|
+
return -1;
|
|
2400
|
+
}
|
|
2401
|
+
const length = Math.max(leftVersion.prerelease.length, rightVersion.prerelease.length);
|
|
2402
|
+
for (let index = 0; index < length; index += 1) {
|
|
2403
|
+
const leftPart = leftVersion.prerelease[index];
|
|
2404
|
+
const rightPart = rightVersion.prerelease[index];
|
|
2405
|
+
if (leftPart === void 0) {
|
|
2406
|
+
return -1;
|
|
2407
|
+
}
|
|
2408
|
+
if (rightPart === void 0) {
|
|
2409
|
+
return 1;
|
|
2410
|
+
}
|
|
2411
|
+
const diff = comparePrereleasePart(leftPart, rightPart);
|
|
2412
|
+
if (diff !== 0) {
|
|
2413
|
+
return diff;
|
|
2414
|
+
}
|
|
2415
|
+
}
|
|
2416
|
+
return 0;
|
|
2417
|
+
}
|
|
2418
|
+
function parseVersion(version) {
|
|
2419
|
+
const [withoutBuild] = version.trim().replace(/^v/i, "").split("+");
|
|
2420
|
+
const [mainValue = "", prereleaseValue = ""] = withoutBuild.split("-");
|
|
2421
|
+
const mainParts = mainValue.split(".").map((part) => Number.parseInt(part, 10));
|
|
2422
|
+
return {
|
|
2423
|
+
main: [
|
|
2424
|
+
Number.isFinite(mainParts[0]) ? mainParts[0] : 0,
|
|
2425
|
+
Number.isFinite(mainParts[1]) ? mainParts[1] : 0,
|
|
2426
|
+
Number.isFinite(mainParts[2]) ? mainParts[2] : 0
|
|
2427
|
+
],
|
|
2428
|
+
prerelease: prereleaseValue ? prereleaseValue.split(".") : []
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
function comparePrereleasePart(left, right) {
|
|
2432
|
+
const leftNumber = /^\d+$/.test(left) ? Number.parseInt(left, 10) : null;
|
|
2433
|
+
const rightNumber = /^\d+$/.test(right) ? Number.parseInt(right, 10) : null;
|
|
2434
|
+
if (leftNumber !== null && rightNumber !== null) {
|
|
2435
|
+
return Math.sign(leftNumber - rightNumber);
|
|
2436
|
+
}
|
|
2437
|
+
if (leftNumber !== null) {
|
|
2438
|
+
return -1;
|
|
2439
|
+
}
|
|
2440
|
+
if (rightNumber !== null) {
|
|
2441
|
+
return 1;
|
|
2442
|
+
}
|
|
2443
|
+
return left.localeCompare(right);
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2239
2446
|
// src/commands/verify.ts
|
|
2240
2447
|
import { createHash } from "crypto";
|
|
2241
2448
|
import { mkdir as mkdir7, readFile as readFile12, stat as stat5 } from "fs/promises";
|
|
@@ -2811,15 +3018,15 @@ After the command completes, report the GitNexus state, generated handoff files,
|
|
|
2811
3018
|
`;
|
|
2812
3019
|
}
|
|
2813
3020
|
function renderSlashPrompt(command, language) {
|
|
3021
|
+
if (command === "ff" || command === "propose") {
|
|
3022
|
+
return renderFastForwardSlashPrompt(command, language);
|
|
3023
|
+
}
|
|
2814
3024
|
if (language !== "en") {
|
|
2815
3025
|
return renderSlashPromptZh(command);
|
|
2816
3026
|
}
|
|
2817
3027
|
if (command === "continue") {
|
|
2818
3028
|
return renderContinueSlashPrompt(language);
|
|
2819
3029
|
}
|
|
2820
|
-
if (command === "ff" || command === "propose") {
|
|
2821
|
-
return renderFastForwardSlashPrompt(command, language);
|
|
2822
|
-
}
|
|
2823
3030
|
if (command === "explore") {
|
|
2824
3031
|
return renderExploreSlashPrompt(language);
|
|
2825
3032
|
}
|
|
@@ -3419,11 +3626,11 @@ Guardrails:
|
|
|
3419
3626
|
);
|
|
3420
3627
|
}
|
|
3421
3628
|
function renderFastForwardSlashPrompt(command, language) {
|
|
3422
|
-
const title = command === "propose" ? "Propose a new FET/OpenSpec change" : "Fast-forward FET/OpenSpec artifact creation";
|
|
3629
|
+
const title = language === "en" ? command === "propose" ? "Propose a new FET/OpenSpec change" : "Fast-forward FET/OpenSpec artifact creation" : command === "propose" ? "\u521B\u5EFA\u5E76\u8865\u9F50 FET/OpenSpec \u63D0\u6848\u4EA7\u7269" : "\u5FEB\u901F\u751F\u6210 FET/OpenSpec \u6240\u9700\u4EA7\u7269";
|
|
3423
3630
|
const commandLine = command === "propose" ? "fet propose <change-id-or-description>" : "fet ff --change <change-id>";
|
|
3424
3631
|
return renderManagedSlashPrompt(
|
|
3425
3632
|
`fet ${command} [...args]`,
|
|
3426
|
-
command === "propose" ? "Create a change and generate required OpenSpec artifacts" : "Generate required OpenSpec artifacts for a change",
|
|
3633
|
+
language === "en" ? command === "propose" ? "Create a change and generate required OpenSpec artifacts" : "Generate required OpenSpec artifacts for a change" : command === "propose" ? "\u521B\u5EFA\u5E76\u8865\u9F50 FET/OpenSpec \u63D0\u6848\u4EA7\u7269" : "\u5FEB\u901F\u751F\u6210 FET/OpenSpec \u6240\u9700\u4EA7\u7269",
|
|
3427
3634
|
`${title}.
|
|
3428
3635
|
|
|
3429
3636
|
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.
|
|
@@ -3438,6 +3645,7 @@ Steps:
|
|
|
3438
3645
|
\`\`\`
|
|
3439
3646
|
4. Follow the native output. If it asks for clarification, ask the user rather than inventing details.
|
|
3440
3647
|
5. If the native output includes artifact paths or templates to write, create only those files and preserve OpenSpec structure.
|
|
3648
|
+
6. If FET reports that the OpenSpec CLI does not expose the requested command, stop immediately. Do not run \`fet ff\`, \`openspec ff\`, \`openspec change\`, or any alternative workflow command unless the user explicitly chooses that fallback after seeing the error.
|
|
3441
3649
|
|
|
3442
3650
|
Artifact rules:
|
|
3443
3651
|
- Follow the instruction field from OpenSpec/FET for each artifact.
|
|
@@ -3449,7 +3657,11 @@ Output:
|
|
|
3449
3657
|
- Change id and location.
|
|
3450
3658
|
- Artifacts created.
|
|
3451
3659
|
- Current status.
|
|
3452
|
-
- Next recommended command, usually /prompts:fet-apply <change-id
|
|
3660
|
+
- Next recommended command, usually /prompts:fet-apply <change-id>.
|
|
3661
|
+
|
|
3662
|
+
Guardrails:
|
|
3663
|
+
- Do not substitute one FET/OpenSpec workflow command for another after a command-not-found or unsupported-version error.
|
|
3664
|
+
- If OpenSpec appears outdated or incompatible, report the detected version and suggest \`npm install -g @fission-ai/openspec@latest\` or \`fet doctor\`, then wait for the user's decision.`,
|
|
3453
3665
|
void 0,
|
|
3454
3666
|
language
|
|
3455
3667
|
);
|
|
@@ -3855,7 +4067,7 @@ async function findExecutable() {
|
|
|
3855
4067
|
const command = process.platform === "win32" ? "where.exe" : "which";
|
|
3856
4068
|
try {
|
|
3857
4069
|
const { stdout } = await exec(command, ["openspec"]);
|
|
3858
|
-
const first = stdout.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
4070
|
+
const first = stdout.split(/\r?\n/).map((line) => line.trim()).sort((left, right) => executablePreference(left) - executablePreference(right)).find(Boolean);
|
|
3859
4071
|
if (first) {
|
|
3860
4072
|
return first;
|
|
3861
4073
|
}
|
|
@@ -3872,6 +4084,12 @@ async function findExecutable() {
|
|
|
3872
4084
|
});
|
|
3873
4085
|
}
|
|
3874
4086
|
}
|
|
4087
|
+
function executablePreference(path) {
|
|
4088
|
+
if (process.platform === "win32" && path.toLowerCase().endsWith(".cmd")) {
|
|
4089
|
+
return 0;
|
|
4090
|
+
}
|
|
4091
|
+
return 1;
|
|
4092
|
+
}
|
|
3875
4093
|
async function readVersion(executablePath) {
|
|
3876
4094
|
const command = executablePath === "npx openspec" ? "npx" : executablePath;
|
|
3877
4095
|
const args = executablePath === "npx openspec" ? ["openspec", "--version"] : ["--version"];
|
|
@@ -3892,14 +4110,14 @@ function exec(command, args) {
|
|
|
3892
4110
|
}
|
|
3893
4111
|
|
|
3894
4112
|
// src/openspec/runner.ts
|
|
3895
|
-
import { spawn } from "child_process";
|
|
4113
|
+
import { spawn as spawn2 } from "child_process";
|
|
3896
4114
|
async function runOpenSpec(executablePath, command, args, options) {
|
|
3897
4115
|
const spawnCommand = executablePath === "npx openspec" ? "npx" : executablePath;
|
|
3898
4116
|
const spawnArgs = executablePath === "npx openspec" ? ["openspec", command, ...args] : [command, ...args];
|
|
3899
4117
|
return new Promise((resolve2, reject) => {
|
|
3900
4118
|
const stdout = [];
|
|
3901
4119
|
const stderr = [];
|
|
3902
|
-
const child =
|
|
4120
|
+
const child = spawn2(spawnCommand, spawnArgs, {
|
|
3903
4121
|
cwd: options.cwd,
|
|
3904
4122
|
stdio: options.stdio ?? "inherit",
|
|
3905
4123
|
shell: process.platform === "win32"
|
|
@@ -3986,7 +4204,10 @@ function parseCommands(help) {
|
|
|
3986
4204
|
"bulk-archive",
|
|
3987
4205
|
"onboard"
|
|
3988
4206
|
];
|
|
3989
|
-
return known.filter((command) =>
|
|
4207
|
+
return known.filter((command) => new RegExp(`\\b${escapeRegExp(command)}\\b`).test(help));
|
|
4208
|
+
}
|
|
4209
|
+
function escapeRegExp(value) {
|
|
4210
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3990
4211
|
}
|
|
3991
4212
|
|
|
3992
4213
|
// src/scanner/package.ts
|
|
@@ -4318,6 +4539,7 @@ async function createCommandContext(command, options) {
|
|
|
4318
4539
|
var program = new Command();
|
|
4319
4540
|
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");
|
|
4320
4541
|
addGlobalOptions(program.command("init")).description("\u521D\u59CB\u5316 FET + OpenSpec").action(wrap("init", initCommand));
|
|
4542
|
+
addGlobalOptions(program.command("update")).description("\u68C0\u67E5 FET \u662F\u5426\u4E3A\u6700\u65B0\u7248\uFF0C\u5E76\u5728\u9700\u8981\u65F6\u81EA\u52A8\u5347\u7EA7").action(wrap("update", updateCommand));
|
|
4321
4543
|
addGlobalOptions(program.command("update-context")).description("\u66F4\u65B0\u9879\u76EE\u4E0A\u4E0B\u6587").action(wrap("update-context", updateContextCommand));
|
|
4322
4544
|
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));
|
|
4323
4545
|
var graph = addGlobalOptions(program.command("graph").description("\u7BA1\u7406\u53EF\u9009\u7684 GitNexus \u4EE3\u7801\u56FE\u652F\u6301"));
|
|
@@ -4400,7 +4622,7 @@ function renderModelPolicyActionHint(policyMode, language) {
|
|
|
4400
4622
|
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";
|
|
4401
4623
|
}
|
|
4402
4624
|
async function warnIfContextPlaceholdersRemain(ctx) {
|
|
4403
|
-
if (["init", "update-context", "fill-context", "doctor"].includes(ctx.command)) {
|
|
4625
|
+
if (["init", "update", "update-context", "fill-context", "doctor"].includes(ctx.command)) {
|
|
4404
4626
|
return;
|
|
4405
4627
|
}
|
|
4406
4628
|
const count2 = await countAgentsLlmPlaceholders(ctx.projectRoot);
|