@nick848/fet 1.0.10 → 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-V4ZRBF5L.js.map
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-V4ZRBF5L.js";
5
+ } from "../chunk-J5WB4KAL.js";
6
6
 
7
7
  // src/cli/index.ts
8
8
  import { createInterface } from "readline/promises";
@@ -2254,6 +2254,195 @@ async function assertVerified(ctx) {
2254
2254
  }
2255
2255
  }
2256
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
+
2257
2446
  // src/commands/verify.ts
2258
2447
  import { createHash } from "crypto";
2259
2448
  import { mkdir as mkdir7, readFile as readFile12, stat as stat5 } from "fs/promises";
@@ -3921,14 +4110,14 @@ function exec(command, args) {
3921
4110
  }
3922
4111
 
3923
4112
  // src/openspec/runner.ts
3924
- import { spawn } from "child_process";
4113
+ import { spawn as spawn2 } from "child_process";
3925
4114
  async function runOpenSpec(executablePath, command, args, options) {
3926
4115
  const spawnCommand = executablePath === "npx openspec" ? "npx" : executablePath;
3927
4116
  const spawnArgs = executablePath === "npx openspec" ? ["openspec", command, ...args] : [command, ...args];
3928
4117
  return new Promise((resolve2, reject) => {
3929
4118
  const stdout = [];
3930
4119
  const stderr = [];
3931
- const child = spawn(spawnCommand, spawnArgs, {
4120
+ const child = spawn2(spawnCommand, spawnArgs, {
3932
4121
  cwd: options.cwd,
3933
4122
  stdio: options.stdio ?? "inherit",
3934
4123
  shell: process.platform === "win32"
@@ -4350,6 +4539,7 @@ async function createCommandContext(command, options) {
4350
4539
  var program = new Command();
4351
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");
4352
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));
4353
4543
  addGlobalOptions(program.command("update-context")).description("\u66F4\u65B0\u9879\u76EE\u4E0A\u4E0B\u6587").action(wrap("update-context", updateContextCommand));
4354
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));
4355
4545
  var graph = addGlobalOptions(program.command("graph").description("\u7BA1\u7406\u53EF\u9009\u7684 GitNexus \u4EE3\u7801\u56FE\u652F\u6301"));
@@ -4432,7 +4622,7 @@ function renderModelPolicyActionHint(policyMode, language) {
4432
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";
4433
4623
  }
4434
4624
  async function warnIfContextPlaceholdersRemain(ctx) {
4435
- if (["init", "update-context", "fill-context", "doctor"].includes(ctx.command)) {
4625
+ if (["init", "update", "update-context", "fill-context", "doctor"].includes(ctx.command)) {
4436
4626
  return;
4437
4627
  }
4438
4628
  const count2 = await countAgentsLlmPlaceholders(ctx.projectRoot);