@nick848/fet 1.0.8 → 1.0.9

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/dist/cli/index.js CHANGED
@@ -179,7 +179,12 @@ function toGitNexusState(detection, previous) {
179
179
  setupHandoffPath: previous?.setupHandoffPath ?? null,
180
180
  setupHandoffUpdatedAt: previous?.setupHandoffUpdatedAt ?? null,
181
181
  handoffPath: previous?.handoffPath ?? null,
182
- handoffUpdatedAt: previous?.handoffUpdatedAt ?? null
182
+ handoffUpdatedAt: previous?.handoffUpdatedAt ?? null,
183
+ projectContextPath: previous?.projectContextPath ?? null,
184
+ projectContextUpdatedAt: previous?.projectContextUpdatedAt ?? null,
185
+ workflowContextPath: previous?.workflowContextPath ?? null,
186
+ workflowContextUpdatedAt: previous?.workflowContextUpdatedAt ?? null,
187
+ lastWorkflowGraphQuery: previous?.lastWorkflowGraphQuery ?? null
183
188
  };
184
189
  }
185
190
  async function inspectGitNexusGraph(projectRoot, env = process.env) {
@@ -248,7 +253,8 @@ function resolveGitNexusCommand(env) {
248
253
  const raw = env.FET_GITNEXUS_COMMAND?.trim() || env.FET_GITNEXUS_EXECUTABLE?.trim() || "gitnexus";
249
254
  const parts = splitCommand(raw);
250
255
  const [file = "gitnexus", ...args] = parts;
251
- return { file, args, label: raw };
256
+ const resolvedFile = process.platform === "win32" && raw === "gitnexus" ? "gitnexus.cmd" : file;
257
+ return { file: resolvedFile, args, label: raw };
252
258
  }
253
259
  function splitCommand(value) {
254
260
  const matches = value.match(/"[^"]+"|'[^']+'|\S+/g);
@@ -442,8 +448,313 @@ FET:END -->
442
448
  }
443
449
 
444
450
  // src/commands/graph.ts
445
- import { mkdir as mkdir4 } from "fs/promises";
451
+ import { mkdir as mkdir5 } from "fs/promises";
452
+ import { dirname as dirname6, join as join9 } from "path";
453
+
454
+ // src/graph-context.ts
455
+ import { mkdir as mkdir4, readdir, readFile as readFile5 } from "fs/promises";
446
456
  import { dirname as dirname5, join as join8 } from "path";
457
+ var MAX_SOURCE_CONTEXT = 8e3;
458
+ var MAX_GRAPH_OUTPUT = 2e4;
459
+ async function buildProjectGraphContext(ctx, state, trigger) {
460
+ if (!isGraphReadable(state)) {
461
+ return {
462
+ generated: false,
463
+ path: null,
464
+ query: null,
465
+ warnings: ["GitNexus graph exists check did not pass; project graph context was not generated."]
466
+ };
467
+ }
468
+ const query = "FET OpenSpec workflow architecture commands adapters graph integration project structure";
469
+ const goal = "Summarize the repository modules, workflow entry points, and likely insertion points for future FET/OpenSpec work.";
470
+ const graphQuery = await runGitNexus(["query", query, "--goal", goal, "--limit", "8"], { cwd: ctx.projectRoot });
471
+ const status = await runGitNexus(["status"], { cwd: ctx.projectRoot });
472
+ const warnings = commandWarnings([["gitnexus query", graphQuery], ["gitnexus status", status]]);
473
+ const relativePath = ".fet/graph-context/project.md";
474
+ await writeGraphContext(
475
+ join8(ctx.projectRoot, relativePath),
476
+ renderProjectContext({
477
+ trigger,
478
+ state,
479
+ query,
480
+ goal,
481
+ status: commandText(status),
482
+ graphOutput: commandText(graphQuery),
483
+ warnings
484
+ })
485
+ );
486
+ return {
487
+ generated: true,
488
+ path: relativePath,
489
+ query,
490
+ warnings
491
+ };
492
+ }
493
+ async function buildWorkflowGraphContext(ctx, options) {
494
+ const existing = await ctx.stateStore.getOrCreateGlobal();
495
+ if (!existing.graph?.gitnexus?.graphExists) {
496
+ return {
497
+ generated: false,
498
+ path: null,
499
+ query: null,
500
+ warnings: []
501
+ };
502
+ }
503
+ const state = await refreshGitNexusState(ctx);
504
+ if (!isGraphReadable(state)) {
505
+ return {
506
+ generated: false,
507
+ path: null,
508
+ query: null,
509
+ warnings: []
510
+ };
511
+ }
512
+ const sourceContext = await collectOpenSpecContext(ctx.projectRoot, options.changeId);
513
+ const query = buildWorkflowQuery(options, sourceContext);
514
+ const goal = workflowGoal(options.command);
515
+ const graphQuery = await runGitNexus(["query", query, "--goal", goal, "--limit", "8"], { cwd: ctx.projectRoot });
516
+ const detectChanges = shouldDetectChanges(options.command) ? await runGitNexus(["detect-changes", "--scope", "all"], { cwd: ctx.projectRoot }) : null;
517
+ const warnings = commandWarnings([
518
+ ["gitnexus query", graphQuery],
519
+ ...detectChanges ? [["gitnexus detect-changes", detectChanges]] : []
520
+ ]);
521
+ const relativePath = `.fet/graph-context/${sanitizePathPart(options.changeId ?? options.command)}.md`;
522
+ await writeGraphContext(
523
+ join8(ctx.projectRoot, relativePath),
524
+ renderWorkflowContext({
525
+ state,
526
+ command: options.command,
527
+ args: options.args,
528
+ changeId: options.changeId,
529
+ query,
530
+ goal,
531
+ sourceContext,
532
+ graphOutput: commandText(graphQuery),
533
+ detectChanges: detectChanges ? commandText(detectChanges) : null,
534
+ warnings
535
+ })
536
+ );
537
+ const global = await ctx.stateStore.getOrCreateGlobal();
538
+ global.graph ??= {};
539
+ global.graph.gitnexus = {
540
+ ...state,
541
+ workflowContextPath: relativePath,
542
+ workflowContextUpdatedAt: (/* @__PURE__ */ new Date()).toISOString(),
543
+ lastWorkflowGraphQuery: query
544
+ };
545
+ await ctx.stateStore.writeGlobal(global);
546
+ return {
547
+ generated: true,
548
+ path: relativePath,
549
+ query,
550
+ warnings
551
+ };
552
+ }
553
+ async function refreshGitNexusState(ctx) {
554
+ const global = await ctx.stateStore.getOrCreateGlobal();
555
+ global.graph ??= {};
556
+ const detection = await detectGitNexus();
557
+ const graph2 = await inspectGitNexusGraph(ctx.projectRoot);
558
+ const state = mergeGitNexusGraphInfo(toGitNexusState(detection, global.graph.gitnexus), graph2);
559
+ global.graph.gitnexus = state;
560
+ await ctx.stateStore.writeGlobal(global);
561
+ return state;
562
+ }
563
+ function isGraphReadable(state) {
564
+ return Boolean(state.installed && state.graphExists);
565
+ }
566
+ async function writeGraphContext(path, content) {
567
+ await mkdir4(dirname5(path), { recursive: true });
568
+ await atomicWrite(path, content);
569
+ }
570
+ function renderProjectContext(options) {
571
+ return `<!-- FET:MANAGED
572
+ schemaVersion: 1
573
+ generator: graph-context
574
+ scope: project
575
+ FET:END -->
576
+
577
+ # FET GitNexus Project Graph Context
578
+
579
+ Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
580
+ Trigger: ${options.trigger}
581
+
582
+ Use this file before broad repository scans in FET/OpenSpec work. Treat it as graph-derived context: confirm concrete behavior by reading the source files it points to.
583
+
584
+ ## Graph State
585
+
586
+ - Provider: GitNexus
587
+ - Installed: ${options.state.installed ? "yes" : "no"}
588
+ - Version: ${options.state.version ?? "unknown"}
589
+ - Graph path: ${options.state.graphPath ?? ".gitnexus"}
590
+ - Graph exists: ${options.state.graphExists ? "yes" : "no"}
591
+ - Last indexed at: ${options.state.lastIndexedAt ?? "unknown"}
592
+
593
+ ## GitNexus Status
594
+
595
+ \`\`\`text
596
+ ${clip(options.status, MAX_GRAPH_OUTPUT)}
597
+ \`\`\`
598
+
599
+ ## Project Query
600
+
601
+ - Query: ${options.query}
602
+ - Goal: ${options.goal}
603
+
604
+ \`\`\`text
605
+ ${clip(options.graphOutput, MAX_GRAPH_OUTPUT)}
606
+ \`\`\`
607
+ ${renderWarnings(options.warnings)}
608
+ `;
609
+ }
610
+ function renderWorkflowContext(options) {
611
+ return `<!-- FET:MANAGED
612
+ schemaVersion: 1
613
+ generator: graph-context
614
+ scope: workflow
615
+ FET:END -->
616
+
617
+ # FET GitNexus Workflow Graph Context
618
+
619
+ Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
620
+
621
+ Read this before editing or generating OpenSpec artifacts for this workflow. Use the graph output to narrow likely files, symbols, dependencies, and impact areas. If it conflicts with OpenSpec artifacts or source code, OpenSpec artifacts and source code win.
622
+
623
+ ## Workflow
624
+
625
+ - FET command: ${options.command}
626
+ - Args: ${options.args.length ? options.args.join(" ") : "(none)"}
627
+ - Change: ${options.changeId ?? "(none)"}
628
+ - Graph path: ${options.state.graphPath ?? ".gitnexus"}
629
+ - Last indexed at: ${options.state.lastIndexedAt ?? "unknown"}
630
+
631
+ ## OpenSpec Context Used For Query
632
+
633
+ \`\`\`text
634
+ ${clip(options.sourceContext || "(no change artifacts found yet)", MAX_SOURCE_CONTEXT)}
635
+ \`\`\`
636
+
637
+ ## GitNexus Query
638
+
639
+ - Query: ${options.query}
640
+ - Goal: ${options.goal}
641
+
642
+ \`\`\`text
643
+ ${clip(options.graphOutput, MAX_GRAPH_OUTPUT)}
644
+ \`\`\`
645
+ ${options.detectChanges ? `
646
+ ## GitNexus Change Impact
647
+
648
+ \`\`\`text
649
+ ${clip(options.detectChanges, MAX_GRAPH_OUTPUT)}
650
+ \`\`\`
651
+ ` : ""}
652
+ ${renderWarnings(options.warnings)}
653
+ `;
654
+ }
655
+ function renderWarnings(warnings) {
656
+ if (!warnings.length) {
657
+ return "";
658
+ }
659
+ return `
660
+ ## Warnings
661
+
662
+ ${warnings.map((warning) => `- ${warning}`).join("\n")}
663
+ `;
664
+ }
665
+ async function collectOpenSpecContext(projectRoot, changeId) {
666
+ if (!changeId) {
667
+ return "";
668
+ }
669
+ const changeRoot = join8(projectRoot, "openspec", "changes", changeId);
670
+ const chunks = [];
671
+ for (const file of ["proposal.md", "design.md", "tasks.md", "README.md"]) {
672
+ const content = await readOptional(join8(changeRoot, file));
673
+ if (content) {
674
+ chunks.push(`## ${file}
675
+ ${content}`);
676
+ }
677
+ }
678
+ const specsRoot = join8(changeRoot, "specs");
679
+ for (const spec of await listSpecFiles(specsRoot)) {
680
+ const content = await readOptional(spec.path);
681
+ if (content) {
682
+ chunks.push(`## ${spec.label}
683
+ ${content}`);
684
+ }
685
+ }
686
+ return clip(chunks.join("\n\n"), MAX_SOURCE_CONTEXT);
687
+ }
688
+ async function listSpecFiles(specsRoot) {
689
+ try {
690
+ const capabilities = await readdir(specsRoot, { withFileTypes: true });
691
+ return capabilities.filter((entry) => entry.isDirectory()).map((entry) => ({
692
+ path: join8(specsRoot, entry.name, "spec.md"),
693
+ label: `specs/${entry.name}/spec.md`
694
+ }));
695
+ } catch {
696
+ return [];
697
+ }
698
+ }
699
+ async function readOptional(path) {
700
+ try {
701
+ return await readFile5(path, "utf8");
702
+ } catch {
703
+ return null;
704
+ }
705
+ }
706
+ function buildWorkflowQuery(options, sourceContext) {
707
+ const artifactTerms = normalizeWhitespace(sourceContext).slice(0, 1200);
708
+ return normalizeWhitespace(
709
+ [
710
+ `FET OpenSpec ${options.command}`,
711
+ options.changeId ? `change ${options.changeId}` : "",
712
+ options.args.join(" "),
713
+ artifactTerms
714
+ ].join(" ")
715
+ );
716
+ }
717
+ function workflowGoal(command) {
718
+ if (command === "apply") {
719
+ return "Find implementation files, symbols, dependencies, and likely blast radius for the current OpenSpec tasks.";
720
+ }
721
+ if (command === "verify") {
722
+ return "Find affected flows and source areas that should be checked while verifying this OpenSpec change.";
723
+ }
724
+ if (["explore", "propose", "new", "continue", "ff"].includes(command)) {
725
+ return "Find relevant modules, entry points, and existing behavior to make OpenSpec artifacts precise.";
726
+ }
727
+ return "Find relevant repository context for this FET/OpenSpec workflow command.";
728
+ }
729
+ function shouldDetectChanges(command) {
730
+ return ["apply", "verify", "sync"].includes(command);
731
+ }
732
+ function commandText(result) {
733
+ const output = [result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n");
734
+ return output || `exit ${result.exitCode}`;
735
+ }
736
+ function commandWarnings(results) {
737
+ return results.filter(([, result]) => result.exitCode !== 0).map(([label, result]) => `${label} exited with ${result.exitCode}: ${firstLine(commandText(result))}`);
738
+ }
739
+ function firstLine(value) {
740
+ return value.trim().split(/\r?\n/)[0]?.trim() || "no output";
741
+ }
742
+ function clip(value, max) {
743
+ if (value.length <= max) {
744
+ return value;
745
+ }
746
+ return `${value.slice(0, max)}
747
+
748
+ [truncated ${value.length - max} characters]`;
749
+ }
750
+ function normalizeWhitespace(value) {
751
+ return value.replace(/\s+/g, " ").trim();
752
+ }
753
+ function sanitizePathPart(value) {
754
+ return value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "workflow";
755
+ }
756
+
757
+ // src/commands/graph.ts
447
758
  async function graphCommand(ctx, action, args = []) {
448
759
  switch (action) {
449
760
  case "status":
@@ -498,7 +809,7 @@ async function graphDoctorCommand(ctx) {
498
809
  }
499
810
  async function graphSetupCommand(ctx) {
500
811
  let result;
501
- const handoffPath = join8(ctx.projectRoot, ".fet", "graph-setup.md");
812
+ const handoffPath = join9(ctx.projectRoot, ".fet", "graph-setup.md");
502
813
  const installCommand = process.env.FET_GITNEXUS_INSTALL_COMMAND?.trim() || null;
503
814
  await withProjectLock(ctx.projectRoot, { command: "graph setup", cwd: ctx.cwd, fetVersion: ctx.fetVersion }, async () => {
504
815
  result = await refreshGraphState(ctx, { write: false });
@@ -534,7 +845,7 @@ async function graphSetupCommand(ctx) {
534
845
  }
535
846
  async function graphHandoffCommand(ctx) {
536
847
  let result;
537
- const handoffPath = join8(ctx.projectRoot, ".fet", "graph-handoff.md");
848
+ const handoffPath = join9(ctx.projectRoot, ".fet", "graph-handoff.md");
538
849
  await withProjectLock(ctx.projectRoot, { command: "graph handoff", cwd: ctx.cwd, fetVersion: ctx.fetVersion }, async () => {
539
850
  result = await refreshGraphState(ctx, { runStatus: true, write: false });
540
851
  await writeHandoffFile(handoffPath, renderGraphUsageHandoff(result.state, ctx.language));
@@ -581,11 +892,14 @@ async function graphAnalyzeCommand(ctx, mode, args) {
581
892
  });
582
893
  }
583
894
  const result = await refreshGraphState(ctx, { write: false });
895
+ const graphContext = await buildProjectGraphContext(ctx, result.state, `fet graph ${mode}`);
584
896
  const global = await ctx.stateStore.getOrCreateGlobal();
585
897
  global.graph ??= {};
586
898
  global.graph.gitnexus = {
587
899
  ...result.state,
588
- lastRefreshAt: (/* @__PURE__ */ new Date()).toISOString()
900
+ lastRefreshAt: (/* @__PURE__ */ new Date()).toISOString(),
901
+ projectContextPath: graphContext.path,
902
+ projectContextUpdatedAt: graphContext.generated ? (/* @__PURE__ */ new Date()).toISOString() : result.state.projectContextUpdatedAt ?? null
589
903
  };
590
904
  await ctx.stateStore.writeGlobal(global);
591
905
  ctx.output.result({
@@ -595,9 +909,10 @@ async function graphAnalyzeCommand(ctx, mode, args) {
595
909
  warnings: result.state.graphExists ? [] : [
596
910
  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
911
  ],
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"],
912
+ nextSteps: ctx.language === "en" ? ["Run fet graph status", "Read .fet/graph-context/project.md before broad scans", "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
913
  data: {
600
914
  gitnexus: global.graph.gitnexus,
915
+ graphContext,
601
916
  run: {
602
917
  command: run.command,
603
918
  stdout: run.stdout.trim(),
@@ -617,7 +932,7 @@ async function refreshGraphState(ctx, options = {}) {
617
932
  gitnexusStatus = await runGitNexus(["status"], { cwd: ctx.projectRoot });
618
933
  state = {
619
934
  ...state,
620
- lastStatus: firstLine(gitnexusStatus.stdout) || firstLine(gitnexusStatus.stderr) || `exit ${gitnexusStatus.exitCode}`
935
+ lastStatus: firstLine2(gitnexusStatus.stdout) || firstLine2(gitnexusStatus.stderr) || `exit ${gitnexusStatus.exitCode}`
621
936
  };
622
937
  }
623
938
  if (options.write ?? true) {
@@ -635,7 +950,7 @@ async function refreshGraphState(ctx, options = {}) {
635
950
  };
636
951
  }
637
952
  async function writeHandoffFile(path, content) {
638
- await mkdir4(dirname5(path), { recursive: true });
953
+ await mkdir5(dirname6(path), { recursive: true });
639
954
  await atomicWrite(path, content);
640
955
  }
641
956
  function renderGraphSetupHandoff(state, options) {
@@ -780,24 +1095,24 @@ FET:END -->
780
1095
  - \u6240\u6709\u751F\u6210\u4EA7\u7269\u4ECD\u5199\u5165\u6B63\u5E38\u7684 OpenSpec change \u76EE\u5F55\u3002
781
1096
  `;
782
1097
  }
783
- function firstLine(value) {
1098
+ function firstLine2(value) {
784
1099
  return value.trim().split(/\r?\n/)[0]?.trim() || null;
785
1100
  }
786
1101
 
787
1102
  // src/commands/init.ts
788
- import { readFile as readFile7, stat as stat4 } from "fs/promises";
789
- import { join as join11 } from "path";
1103
+ import { readFile as readFile8, stat as stat4 } from "fs/promises";
1104
+ import { join as join12 } from "path";
790
1105
 
791
1106
  // src/version.ts
792
1107
  import { existsSync, readFileSync } from "fs";
793
- import { dirname as dirname6, join as join9, parse } from "path";
1108
+ import { dirname as dirname7, join as join10, parse } from "path";
794
1109
  import { fileURLToPath } from "url";
795
1110
  var FET_VERSION = readPackageVersion();
796
1111
  function readPackageVersion() {
797
- let currentDir = dirname6(fileURLToPath(import.meta.url));
1112
+ let currentDir = dirname7(fileURLToPath(import.meta.url));
798
1113
  const root = parse(currentDir).root;
799
1114
  while (true) {
800
- const packageJsonPath = join9(currentDir, "package.json");
1115
+ const packageJsonPath = join10(currentDir, "package.json");
801
1116
  if (existsSync(packageJsonPath)) {
802
1117
  const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
803
1118
  if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
@@ -808,7 +1123,7 @@ function readPackageVersion() {
808
1123
  if (currentDir === root) {
809
1124
  throw new Error("\u65E0\u6CD5\u5B9A\u4F4D FET package.json");
810
1125
  }
811
- currentDir = dirname6(currentDir);
1126
+ currentDir = dirname7(currentDir);
812
1127
  }
813
1128
  }
814
1129
 
@@ -1259,18 +1574,18 @@ ${block}
1259
1574
  }
1260
1575
 
1261
1576
  // src/commands/update-context.ts
1262
- import { readFile as readFile6 } from "fs/promises";
1263
- import { join as join10 } from "path";
1577
+ import { readFile as readFile7 } from "fs/promises";
1578
+ import { join as join11 } from "path";
1264
1579
 
1265
1580
  // src/config/yaml.ts
1266
- import { readFile as readFile5 } from "fs/promises";
1581
+ import { readFile as readFile6 } from "fs/promises";
1267
1582
  import { parseDocument } from "yaml";
1268
1583
  async function mergeFetConfig(configPath, renderedFetYaml) {
1269
1584
  const fetDoc = parseDocument(renderedFetYaml);
1270
1585
  const nextFet = fetDoc.get("fet", true);
1271
1586
  let existing = "";
1272
1587
  try {
1273
- existing = await readFile5(configPath, "utf8");
1588
+ existing = await readFile6(configPath, "utf8");
1274
1589
  } catch {
1275
1590
  return renderedFetYaml;
1276
1591
  }
@@ -1294,14 +1609,14 @@ async function updateContextCommand(ctx) {
1294
1609
  }
1295
1610
  async function updateContextFiles(ctx) {
1296
1611
  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);
1612
+ const agentsPath = join11(ctx.projectRoot, "AGENTS.md");
1613
+ const configPath = join11(ctx.projectRoot, "openspec", "config.yaml");
1614
+ const claudePath = join11(ctx.projectRoot, "CLAUDE.md");
1615
+ const karpathyHandoffPath = join11(ctx.projectRoot, ".fet", "karpathy-guidelines.md");
1616
+ const karpathyCursorPath = join11(ctx.projectRoot, ".cursor", "rules", "karpathy-guidelines.mdc");
1617
+ const existingAgents = await readOptional2(agentsPath);
1618
+ const existingClaude = await readOptional2(claudePath);
1619
+ const existingKarpathyCursor = await readOptional2(karpathyCursorPath);
1305
1620
  const warnings = [...scan.warnings];
1306
1621
  if (existingAgents && hasInvalidManagedAutoRegion(existingAgents)) {
1307
1622
  throw new FetError({
@@ -1349,9 +1664,9 @@ async function updateContextFiles(ctx) {
1349
1664
  await ctx.stateStore.writeGlobal(state);
1350
1665
  return { warnings };
1351
1666
  }
1352
- async function readOptional(path) {
1667
+ async function readOptional2(path) {
1353
1668
  try {
1354
- return await readFile6(path, "utf8");
1669
+ return await readFile7(path, "utf8");
1355
1670
  } catch {
1356
1671
  return null;
1357
1672
  }
@@ -1359,7 +1674,7 @@ async function readOptional(path) {
1359
1674
 
1360
1675
  // src/commands/init.ts
1361
1676
  async function initCommand(ctx) {
1362
- const alreadyInitialized = await exists2(join11(ctx.projectRoot, "openspec", "config.yaml"));
1677
+ const alreadyInitialized = await exists2(join12(ctx.projectRoot, "openspec", "config.yaml"));
1363
1678
  let warnings = [];
1364
1679
  await withProjectLock(ctx.projectRoot, { command: "init", cwd: ctx.cwd, fetVersion: ctx.fetVersion }, async () => {
1365
1680
  const journal = createInitJournal(ctx.fetVersion);
@@ -1411,13 +1726,13 @@ async function initCommand(ctx) {
1411
1726
  });
1412
1727
  }
1413
1728
  async function ensureGitignore(ctx) {
1414
- const gitignorePath = join11(ctx.projectRoot, ".gitignore");
1415
- const existing = await readOptional2(gitignorePath);
1729
+ const gitignorePath = join12(ctx.projectRoot, ".gitignore");
1730
+ const existing = await readOptional3(gitignorePath);
1416
1731
  await atomicWrite(gitignorePath, mergeGitignore(existing));
1417
1732
  }
1418
- async function readOptional2(path) {
1733
+ async function readOptional3(path) {
1419
1734
  try {
1420
- return await readFile7(path, "utf8");
1735
+ return await readFile8(path, "utf8");
1421
1736
  } catch {
1422
1737
  return null;
1423
1738
  }
@@ -1432,8 +1747,8 @@ async function exists2(path) {
1432
1747
  }
1433
1748
 
1434
1749
  // src/commands/proxy.ts
1435
- import { readFile as readFile10 } from "fs/promises";
1436
- import { join as join13 } from "path";
1750
+ import { readFile as readFile11 } from "fs/promises";
1751
+ import { join as join14 } from "path";
1437
1752
 
1438
1753
  // src/state/project.ts
1439
1754
  import { execFile as execFile2 } from "child_process";
@@ -1462,8 +1777,8 @@ async function git(cwd, args) {
1462
1777
  }
1463
1778
 
1464
1779
  // src/state/store.ts
1465
- import { mkdir as mkdir5, readFile as readFile8 } from "fs/promises";
1466
- import { join as join12 } from "path";
1780
+ import { mkdir as mkdir6, readFile as readFile9 } from "fs/promises";
1781
+ import { join as join13 } from "path";
1467
1782
 
1468
1783
  // src/language.ts
1469
1784
  var DEFAULT_LANGUAGE = "zh-CN";
@@ -1581,7 +1896,7 @@ var StateStore = class {
1581
1896
  project;
1582
1897
  async readGlobal() {
1583
1898
  try {
1584
- const value = JSON.parse(await readFile8(this.globalPath(), "utf8"));
1899
+ const value = JSON.parse(await readFile9(this.globalPath(), "utf8"));
1585
1900
  assertGlobalState(value);
1586
1901
  return value;
1587
1902
  } catch (error) {
@@ -1596,13 +1911,13 @@ var StateStore = class {
1596
1911
  }
1597
1912
  async writeGlobal(state) {
1598
1913
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1599
- await mkdir5(join12(this.projectRoot, "openspec"), { recursive: true });
1914
+ await mkdir6(join13(this.projectRoot, "openspec"), { recursive: true });
1600
1915
  await atomicWrite(this.globalPath(), `${JSON.stringify(state, null, 2)}
1601
1916
  `);
1602
1917
  }
1603
1918
  async readChange(changeId) {
1604
1919
  try {
1605
- const value = JSON.parse(await readFile8(this.changePath(changeId), "utf8"));
1920
+ const value = JSON.parse(await readFile9(this.changePath(changeId), "utf8"));
1606
1921
  assertChangeState(value);
1607
1922
  return value;
1608
1923
  } catch (error) {
@@ -1617,15 +1932,15 @@ var StateStore = class {
1617
1932
  }
1618
1933
  async writeChange(state) {
1619
1934
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1620
- await mkdir5(join12(this.projectRoot, "openspec", "changes", state.changeId), { recursive: true });
1935
+ await mkdir6(join13(this.projectRoot, "openspec", "changes", state.changeId), { recursive: true });
1621
1936
  await atomicWrite(this.changePath(state.changeId), `${JSON.stringify(state, null, 2)}
1622
1937
  `);
1623
1938
  }
1624
1939
  globalPath() {
1625
- return join12(this.projectRoot, "openspec", "fet-state.json");
1940
+ return join13(this.projectRoot, "openspec", "fet-state.json");
1626
1941
  }
1627
1942
  changePath(changeId) {
1628
- return join12(this.projectRoot, "openspec", "changes", changeId, "fet-state.json");
1943
+ return join13(this.projectRoot, "openspec", "changes", changeId, "fet-state.json");
1629
1944
  }
1630
1945
  };
1631
1946
  function isNotFound(error) {
@@ -1633,11 +1948,11 @@ function isNotFound(error) {
1633
1948
  }
1634
1949
 
1635
1950
  // src/state/tasks.ts
1636
- import { readFile as readFile9 } from "fs/promises";
1951
+ import { readFile as readFile10 } from "fs/promises";
1637
1952
  async function readCompletedTaskIds(tasksPath) {
1638
1953
  let content;
1639
1954
  try {
1640
- content = await readFile9(tasksPath, "utf8");
1955
+ content = await readFile10(tasksPath, "utf8");
1641
1956
  } catch {
1642
1957
  return [];
1643
1958
  }
@@ -1668,71 +1983,76 @@ var phaseByCommand = {
1668
1983
  };
1669
1984
  async function proxyCommand(ctx, command, args) {
1670
1985
  const openSpecArgs = stripFetOptions(args);
1671
- await withProjectLock(
1672
- ctx.projectRoot,
1673
- { command, cwd: ctx.cwd, fetVersion: ctx.fetVersion },
1674
- async () => {
1675
- if (["sync", "archive", "bulk-archive"].includes(command)) {
1676
- await assertVerified(ctx);
1677
- }
1678
- const mapped = await mapOpenSpecCommand(ctx, command, openSpecArgs);
1679
- const targetChangeId = command === "archive" ? mapped.args[0] ?? ctx.changeId ?? null : ctx.changeId ?? null;
1680
- const changelogEntry = command === "archive" && targetChangeId ? await createChangelogEntry(ctx.projectRoot, targetChangeId) : null;
1681
- const result = await ctx.openSpec.run(mapped.command, mapped.args, { cwd: ctx.projectRoot, stdio: ctx.json ? "pipe" : "inherit" });
1682
- if (result.exitCode !== 0) {
1683
- throw new FetError({
1684
- code: "OPENSPEC_COMMAND_FAILED" /* OpenSpecCommandFailed */,
1685
- message: `OpenSpec ${command} \u6267\u884C\u5931\u8D25`,
1686
- details: result,
1687
- recoverable: true
1688
- });
1689
- }
1690
- if (changelogEntry) {
1691
- await appendChangelog(ctx.projectRoot, changelogEntry);
1692
- }
1693
- const inspection = await ctx.openSpec.inspectProject(ctx.projectRoot);
1694
- const state = await ctx.stateStore.getOrCreateGlobal();
1695
- state.openChangeIds = inspection.changes;
1696
- if (command === "archive") {
1697
- if (!state.activeChangeId || state.activeChangeId === targetChangeId || !inspection.changes.includes(state.activeChangeId)) {
1698
- state.activeChangeId = null;
1699
- }
1700
- state.verifyAuthorization = null;
1701
- } else {
1702
- if (ctx.changeId && inspection.changes.includes(ctx.changeId)) {
1703
- state.activeChangeId = ctx.changeId;
1704
- } else if (state.activeChangeId && !inspection.changes.includes(state.activeChangeId)) {
1705
- state.activeChangeId = inspection.changes.length === 1 ? inspection.changes[0] ?? null : null;
1706
- } else if (!state.activeChangeId && inspection.changes.length === 1) {
1707
- state.activeChangeId = inspection.changes[0] ?? null;
1708
- }
1709
- }
1710
- await ctx.stateStore.writeGlobal(state);
1711
- const changeId = ctx.changeId ?? state.activeChangeId;
1712
- if (changeId && inspection.changes.includes(changeId)) {
1713
- const changeInspection = await ctx.openSpec.inspectChange(ctx.projectRoot, changeId);
1714
- const changeState = await ctx.stateStore.getOrCreateChange(changeId, phaseByCommand[command] ?? "propose");
1715
- changeState.currentPhase = phaseByCommand[command] ?? changeState.currentPhase;
1716
- changeState.phases[changeState.currentPhase] = {
1717
- status: "done",
1718
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1719
- };
1720
- changeState.lastOpenSpecCommand = {
1721
- command: mapped.command,
1722
- args: mapped.args,
1723
- exitCode: result.exitCode,
1724
- ranAt: (/* @__PURE__ */ new Date()).toISOString()
1725
- };
1726
- changeState.tasks.completedIds = await readCompletedTaskIds(changeInspection.tasksPath);
1727
- changeState.tasks.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
1728
- await ctx.stateStore.writeChange(changeState);
1986
+ const runState = {};
1987
+ await withProjectLock(ctx.projectRoot, { command, cwd: ctx.cwd, fetVersion: ctx.fetVersion }, async () => {
1988
+ if (["sync", "archive", "bulk-archive"].includes(command)) {
1989
+ await assertVerified(ctx);
1990
+ }
1991
+ const mapped = await mapOpenSpecCommand(ctx, command, openSpecArgs);
1992
+ const mappedChangeId = extractChangeId(mapped.args);
1993
+ const targetChangeId = command === "archive" ? mapped.args[0] ?? ctx.changeId ?? mappedChangeId : ctx.changeId ?? mappedChangeId;
1994
+ runState.graphContext = await buildWorkflowGraphContext(ctx, {
1995
+ command,
1996
+ args: mapped.args,
1997
+ changeId: targetChangeId
1998
+ });
1999
+ const changelogEntry = command === "archive" && targetChangeId ? await createChangelogEntry(ctx.projectRoot, targetChangeId) : null;
2000
+ const result = await ctx.openSpec.run(mapped.command, mapped.args, { cwd: ctx.projectRoot, stdio: ctx.json ? "pipe" : "inherit" });
2001
+ if (result.exitCode !== 0) {
2002
+ throw new FetError({
2003
+ code: "OPENSPEC_COMMAND_FAILED" /* OpenSpecCommandFailed */,
2004
+ message: `OpenSpec ${command} failed.`,
2005
+ details: result,
2006
+ recoverable: true
2007
+ });
2008
+ }
2009
+ if (changelogEntry) {
2010
+ await appendChangelog(ctx.projectRoot, changelogEntry);
2011
+ }
2012
+ const inspection = await ctx.openSpec.inspectProject(ctx.projectRoot);
2013
+ const state = await ctx.stateStore.getOrCreateGlobal();
2014
+ state.openChangeIds = inspection.changes;
2015
+ if (command === "archive") {
2016
+ if (!state.activeChangeId || state.activeChangeId === targetChangeId || !inspection.changes.includes(state.activeChangeId)) {
2017
+ state.activeChangeId = null;
1729
2018
  }
2019
+ state.verifyAuthorization = null;
2020
+ } else if (ctx.changeId && inspection.changes.includes(ctx.changeId)) {
2021
+ state.activeChangeId = ctx.changeId;
2022
+ } else if (state.activeChangeId && !inspection.changes.includes(state.activeChangeId)) {
2023
+ state.activeChangeId = inspection.changes.length === 1 ? inspection.changes[0] ?? null : null;
2024
+ } else if (!state.activeChangeId && inspection.changes.length === 1) {
2025
+ state.activeChangeId = inspection.changes[0] ?? null;
1730
2026
  }
1731
- );
2027
+ await ctx.stateStore.writeGlobal(state);
2028
+ const changeId = ctx.changeId ?? state.activeChangeId;
2029
+ if (changeId && inspection.changes.includes(changeId)) {
2030
+ const changeInspection = await ctx.openSpec.inspectChange(ctx.projectRoot, changeId);
2031
+ const changeState = await ctx.stateStore.getOrCreateChange(changeId, phaseByCommand[command] ?? "propose");
2032
+ changeState.currentPhase = phaseByCommand[command] ?? changeState.currentPhase;
2033
+ changeState.phases[changeState.currentPhase] = {
2034
+ status: "done",
2035
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2036
+ };
2037
+ changeState.lastOpenSpecCommand = {
2038
+ command: mapped.command,
2039
+ args: mapped.args,
2040
+ exitCode: result.exitCode,
2041
+ ranAt: (/* @__PURE__ */ new Date()).toISOString()
2042
+ };
2043
+ changeState.tasks.completedIds = await readCompletedTaskIds(changeInspection.tasksPath);
2044
+ changeState.tasks.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
2045
+ await ctx.stateStore.writeChange(changeState);
2046
+ }
2047
+ });
2048
+ const graphContext = runState.graphContext;
1732
2049
  ctx.output.result({
1733
2050
  ok: true,
1734
2051
  command,
1735
- summary: `fet ${command} \u5B8C\u6210\u3002`
2052
+ summary: `fet ${command} completed.`,
2053
+ warnings: graphContext?.warnings,
2054
+ nextSteps: graphContext?.generated && graphContext.path ? [`Read ${graphContext.path} before broad source scans or implementation decisions.`] : void 0,
2055
+ data: graphContext ? { graphContext } : void 0
1736
2056
  });
1737
2057
  }
1738
2058
  async function createChangelogEntry(projectRoot, changeId) {
@@ -1742,10 +2062,12 @@ async function createChangelogEntry(projectRoot, changeId) {
1742
2062
  };
1743
2063
  }
1744
2064
  async function appendChangelog(projectRoot, entry) {
1745
- const changelogPath = join13(projectRoot, "CHANGELOG.md");
1746
- const existing = await readOptional3(changelogPath);
2065
+ const changelogPath = join14(projectRoot, "CHANGELOG.md");
2066
+ const existing = await readOptional4(changelogPath);
2067
+ const legacyContentLabel = "\u66F4\u65B0\u5185\u5BB9";
1747
2068
  const block = `updateTime: ${entry.updateTime}
1748
- \u66F4\u65B0\u5185\u5BB9:${entry.content}
2069
+ changeRequirement:${entry.content}
2070
+ ${legacyContentLabel}:${entry.content}
1749
2071
  `;
1750
2072
  const next = existing?.trimEnd() ? `${existing.trimEnd()}
1751
2073
 
@@ -1753,12 +2075,12 @@ ${block}` : block;
1753
2075
  await atomicWrite(changelogPath, next);
1754
2076
  }
1755
2077
  async function readChangeRequirement(projectRoot, changeId) {
1756
- const changeRoot = join13(projectRoot, "openspec", "changes", changeId);
1757
- const proposal = await readOptional3(join13(changeRoot, "proposal.md"));
2078
+ const changeRoot = join14(projectRoot, "openspec", "changes", changeId);
2079
+ const proposal = await readOptional4(join14(changeRoot, "proposal.md"));
1758
2080
  if (proposal) {
1759
2081
  return summarizeMarkdown(proposal);
1760
2082
  }
1761
- const readme = await readOptional3(join13(changeRoot, "README.md"));
2083
+ const readme = await readOptional4(join14(changeRoot, "README.md"));
1762
2084
  if (readme) {
1763
2085
  return summarizeMarkdown(readme);
1764
2086
  }
@@ -1766,11 +2088,11 @@ async function readChangeRequirement(projectRoot, changeId) {
1766
2088
  }
1767
2089
  function summarizeMarkdown(content) {
1768
2090
  const normalized = content.replace(/\r\n/g, "\n").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("<!--") && !line.startsWith("---")).join(" ");
1769
- return normalized || "\u672A\u63D0\u4F9B\u53D8\u66F4\u9700\u6C42";
2091
+ return normalized || "No change requirement found.";
1770
2092
  }
1771
- async function readOptional3(path) {
2093
+ async function readOptional4(path) {
1772
2094
  try {
1773
- return await readFile10(path, "utf8");
2095
+ return await readFile11(path, "utf8");
1774
2096
  } catch {
1775
2097
  return null;
1776
2098
  }
@@ -1792,7 +2114,7 @@ async function passthroughCommand(ctx, command, args) {
1792
2114
  if (result.exitCode !== 0) {
1793
2115
  throw new FetError({
1794
2116
  code: "OPENSPEC_COMMAND_FAILED" /* OpenSpecCommandFailed */,
1795
- message: `OpenSpec ${command} \u6267\u884C\u5931\u8D25`,
2117
+ message: `OpenSpec ${command} failed.`,
1796
2118
  details: result
1797
2119
  });
1798
2120
  }
@@ -1834,18 +2156,6 @@ async function mapOpenSpecCommand(ctx, command, args) {
1834
2156
  return { command: "new", args: args[0] === "change" ? args : ["change", ...args] };
1835
2157
  case "archive":
1836
2158
  return { command: "archive", args: [ctx.changeId ?? args[0] ?? await requireChangeId(ctx), ...args.slice(ctx.changeId ? 0 : 1)] };
1837
- /*
1838
- case "bulk-archive":
1839
- throw new FetError({
1840
- code: ErrorCode.InvalidArguments,
1841
- message: "OpenSpec 1.2.0 不提供 bulk-archive 顶层命令",
1842
- suggestedCommand: "逐个执行 fet archive --change <change-id>"
1843
- });
1844
- case "explore":
1845
- return { command: "explore", args: ctx.changeId ? ["--change", ctx.changeId, ...args] : args };
1846
- case "onboard":
1847
- return { command: "instructions", args: [] };
1848
- */
1849
2159
  default:
1850
2160
  return { command, args };
1851
2161
  }
@@ -1865,6 +2175,18 @@ async function withDefaultChange(ctx, args, allowWithArgs = false) {
1865
2175
  }
1866
2176
  return ["--change", await requireChangeId(ctx), ...args];
1867
2177
  }
2178
+ function extractChangeId(args) {
2179
+ for (let index = 0; index < args.length; index += 1) {
2180
+ const arg = args[index];
2181
+ if (arg === "--change") {
2182
+ return args[index + 1] ?? null;
2183
+ }
2184
+ if (arg?.startsWith("--change=")) {
2185
+ return arg.slice("--change=".length) || null;
2186
+ }
2187
+ }
2188
+ return null;
2189
+ }
1868
2190
  async function requireChangeId(ctx) {
1869
2191
  if (ctx.changeId) {
1870
2192
  return ctx.changeId;
@@ -1879,9 +2201,9 @@ async function requireChangeId(ctx) {
1879
2201
  }
1880
2202
  throw new FetError({
1881
2203
  code: "INVALID_ARGUMENTS" /* InvalidArguments */,
1882
- message: "\u8BE5\u547D\u4EE4\u9700\u8981\u660E\u786E\u7684 change",
2204
+ message: "No unambiguous OpenSpec change id was found.",
1883
2205
  details: { openChangeIds: inspection.changes },
1884
- suggestedCommand: "\u6DFB\u52A0 --change <change-id>"
2206
+ suggestedCommand: "Pass --change <change-id>."
1885
2207
  });
1886
2208
  }
1887
2209
  async function assertVerified(ctx) {
@@ -1890,7 +2212,7 @@ async function assertVerified(ctx) {
1890
2212
  if (!changeId) {
1891
2213
  throw new FetError({
1892
2214
  code: "INVALID_ARGUMENTS" /* InvalidArguments */,
1893
- message: "\u672A\u6307\u5B9A change\uFF0C\u65E0\u6CD5\u68C0\u67E5 verify \u72B6\u6001",
2215
+ message: "A change id is required before this command can check FET verification.",
1894
2216
  suggestedCommand: "fet verify --done --change <change-id>"
1895
2217
  });
1896
2218
  }
@@ -1899,7 +2221,7 @@ async function assertVerified(ctx) {
1899
2221
  if (!inspection.changes.includes(changeId)) {
1900
2222
  throw new FetError({
1901
2223
  code: "INVALID_ARGUMENTS" /* InvalidArguments */,
1902
- message: "\u6307\u5B9A\u7684 change \u4E0D\u5B58\u5728\u6216\u5DF2\u5F52\u6863",
2224
+ message: "The selected change does not exist in openspec/changes.",
1903
2225
  details: { changeId, openChangeIds: inspection.changes },
1904
2226
  suggestedCommand: "fet doctor"
1905
2227
  });
@@ -1907,7 +2229,7 @@ async function assertVerified(ctx) {
1907
2229
  if (change?.manualVerify?.status !== "declared_done") {
1908
2230
  throw new FetError({
1909
2231
  code: "STATE_CORRUPTED" /* StateCorrupted */,
1910
- message: "\u5F53\u524D change \u5C1A\u672A\u901A\u8FC7 FET verify",
2232
+ message: "This change has not been marked verified by FET.",
1911
2233
  details: { changeId },
1912
2234
  suggestedCommand: `fet verify --change ${changeId}`
1913
2235
  });
@@ -1916,8 +2238,8 @@ async function assertVerified(ctx) {
1916
2238
 
1917
2239
  // src/commands/verify.ts
1918
2240
  import { createHash } from "crypto";
1919
- import { mkdir as mkdir6, readFile as readFile11, stat as stat5 } from "fs/promises";
1920
- import { join as join14 } from "path";
2241
+ import { mkdir as mkdir7, readFile as readFile12, stat as stat5 } from "fs/promises";
2242
+ import { join as join15 } from "path";
1921
2243
  async function verifyCommand(ctx, options) {
1922
2244
  if (options.auto) {
1923
2245
  const scan = await ctx.scanner.scan(ctx.projectRoot, {});
@@ -1984,9 +2306,9 @@ async function verifyCommand(ctx, options) {
1984
2306
  async function writeInstructions(ctx, changeId) {
1985
2307
  await assertChangeExists(ctx, changeId);
1986
2308
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
1987
- const dir = join14(ctx.projectRoot, "openspec", "changes", changeId, ".fet");
1988
- const instructionsPath = join14(dir, "verify-instructions.md");
1989
- await mkdir6(dir, { recursive: true });
2309
+ const dir = join15(ctx.projectRoot, "openspec", "changes", changeId, ".fet");
2310
+ const instructionsPath = join15(dir, "verify-instructions.md");
2311
+ await mkdir7(dir, { recursive: true });
1990
2312
  await atomicWrite(instructionsPath, renderVerifyInstructions(changeId, generatedAt));
1991
2313
  const state = await ctx.stateStore.getOrCreateChange(changeId, "verify");
1992
2314
  state.currentPhase = "verify";
@@ -2002,7 +2324,7 @@ async function writeInstructions(ctx, changeId) {
2002
2324
  async function markDone(ctx, changeId) {
2003
2325
  await assertChangeExists(ctx, changeId);
2004
2326
  const declaredAt = (/* @__PURE__ */ new Date()).toISOString();
2005
- const instructionsPath = join14(ctx.projectRoot, "openspec", "changes", changeId, ".fet", "verify-instructions.md");
2327
+ const instructionsPath = join15(ctx.projectRoot, "openspec", "changes", changeId, ".fet", "verify-instructions.md");
2006
2328
  const instructions = await readInstructions(instructionsPath, changeId);
2007
2329
  const instructionsGeneratedAt = readFrontMatterValue(instructions, "generatedAt") ?? declaredAt;
2008
2330
  const state = await ctx.stateStore.getOrCreateChange(changeId, "verify");
@@ -2038,7 +2360,7 @@ async function assertChangeExists(ctx, changeId) {
2038
2360
  async function readInstructions(path, changeId) {
2039
2361
  try {
2040
2362
  await stat5(path);
2041
- const content = await readFile11(path, "utf8");
2363
+ const content = await readFile12(path, "utf8");
2042
2364
  const fileChangeId = readFrontMatterValue(content, "changeId");
2043
2365
  if (fileChangeId !== changeId) {
2044
2366
  throw new FetError({
@@ -2176,9 +2498,9 @@ function renderIdeModelPolicy(command, language = "zh-CN") {
2176
2498
  import { resolve } from "path";
2177
2499
 
2178
2500
  // src/adapters/codex/index.ts
2179
- import { mkdir as mkdir7, readFile as readFile12, stat as stat6 } from "fs/promises";
2501
+ import { mkdir as mkdir8, readFile as readFile13, stat as stat6 } from "fs/promises";
2180
2502
  import { homedir } from "os";
2181
- import { dirname as dirname7, join as join15 } from "path";
2503
+ import { dirname as dirname8, join as join16 } from "path";
2182
2504
 
2183
2505
  // src/adapters/commands.ts
2184
2506
  var FET_WORKFLOW_COMMANDS = [
@@ -3165,7 +3487,7 @@ var CodexAdapter = class {
3165
3487
  adapterVersion = 1;
3166
3488
  async detect(projectRoot) {
3167
3489
  return {
3168
- detected: await exists3(join15(projectRoot, ".codex")) || await exists3(join15(projectRoot, "AGENTS.md")),
3490
+ detected: await exists3(join16(projectRoot, ".codex")) || await exists3(join16(projectRoot, "AGENTS.md")),
3169
3491
  reason: "Codex adapter is available for projects that use AGENTS.md"
3170
3492
  };
3171
3493
  }
@@ -3204,7 +3526,7 @@ var CodexAdapter = class {
3204
3526
  if (existing && !existing.includes("FET:MANAGED") && force) {
3205
3527
  await createBackup(target);
3206
3528
  }
3207
- await mkdir7(dirname7(target), { recursive: true });
3529
+ await mkdir8(dirname8(target), { recursive: true });
3208
3530
  await atomicWrite(target, file.content);
3209
3531
  written.push(displayPath);
3210
3532
  }
@@ -3231,9 +3553,9 @@ var CodexAdapter = class {
3231
3553
  };
3232
3554
  function resolveTarget(projectRoot, file) {
3233
3555
  if (file.root === "codex-home") {
3234
- return join15(resolveCodexHome(), file.path);
3556
+ return join16(resolveCodexHome(), file.path);
3235
3557
  }
3236
- return join15(projectRoot, file.path);
3558
+ return join16(projectRoot, file.path);
3237
3559
  }
3238
3560
  function displayPathFor(file) {
3239
3561
  if (file.root === "codex-home") {
@@ -3242,11 +3564,11 @@ function displayPathFor(file) {
3242
3564
  return file.path;
3243
3565
  }
3244
3566
  function resolveCodexHome() {
3245
- return process.env.FET_CODEX_HOME ?? process.env.CODEX_HOME ?? join15(homedir(), ".codex");
3567
+ return process.env.FET_CODEX_HOME ?? process.env.CODEX_HOME ?? join16(homedir(), ".codex");
3246
3568
  }
3247
3569
  async function readExisting(path) {
3248
3570
  try {
3249
- return await readFile12(path, "utf8");
3571
+ return await readFile13(path, "utf8");
3250
3572
  } catch {
3251
3573
  return null;
3252
3574
  }
@@ -3261,8 +3583,8 @@ async function exists3(path) {
3261
3583
  }
3262
3584
 
3263
3585
  // src/adapters/cursor/index.ts
3264
- import { mkdir as mkdir8, readFile as readFile13, stat as stat7 } from "fs/promises";
3265
- import { dirname as dirname8, join as join16 } from "path";
3586
+ import { mkdir as mkdir9, readFile as readFile14, stat as stat7 } from "fs/promises";
3587
+ import { dirname as dirname9, join as join17 } from "path";
3266
3588
 
3267
3589
  // src/adapters/cursor/templates.ts
3268
3590
  function cursorSkillFiles(language = DEFAULT_LANGUAGE) {
@@ -3396,7 +3718,7 @@ var CursorAdapter = class {
3396
3718
  adapterVersion = 1;
3397
3719
  async detect(projectRoot) {
3398
3720
  return {
3399
- detected: await exists4(join16(projectRoot, ".cursor")),
3721
+ detected: await exists4(join17(projectRoot, ".cursor")),
3400
3722
  reason: "Cursor adapter is available for any project"
3401
3723
  };
3402
3724
  }
@@ -3413,7 +3735,7 @@ var CursorAdapter = class {
3413
3735
  const written = [];
3414
3736
  const skipped = [];
3415
3737
  for (const file of plan.files) {
3416
- const target = join16(projectRoot, file.path);
3738
+ const target = join17(projectRoot, file.path);
3417
3739
  const existing = await readExisting2(target);
3418
3740
  if (existing && !existing.includes("FET:MANAGED") && !force) {
3419
3741
  throw new FetError({
@@ -3426,7 +3748,7 @@ var CursorAdapter = class {
3426
3748
  if (existing && !existing.includes("FET:MANAGED") && force) {
3427
3749
  await createBackup(target);
3428
3750
  }
3429
- await mkdir8(dirname8(target), { recursive: true });
3751
+ await mkdir9(dirname9(target), { recursive: true });
3430
3752
  await atomicWrite(target, file.content);
3431
3753
  written.push(file.path);
3432
3754
  }
@@ -3436,7 +3758,7 @@ var CursorAdapter = class {
3436
3758
  const plan = await this.planInstall(projectRoot);
3437
3759
  const checks = [];
3438
3760
  for (const file of plan.files) {
3439
- const target = join16(projectRoot, file.path);
3761
+ const target = join17(projectRoot, file.path);
3440
3762
  const content = await readExisting2(target);
3441
3763
  const managed = Boolean(content?.includes("FET:MANAGED"));
3442
3764
  const versionMatches = Boolean(content?.includes(`adapterVersion: ${this.adapterVersion}`));
@@ -3452,7 +3774,7 @@ var CursorAdapter = class {
3452
3774
  };
3453
3775
  async function readExisting2(path) {
3454
3776
  try {
3455
- return await readFile13(path, "utf8");
3777
+ return await readFile14(path, "utf8");
3456
3778
  } catch {
3457
3779
  return null;
3458
3780
  }
@@ -3471,13 +3793,13 @@ import { execFile as execFile4 } from "child_process";
3471
3793
  import { promisify as promisify4 } from "util";
3472
3794
 
3473
3795
  // src/openspec/inspector.ts
3474
- import { readdir, stat as stat8 } from "fs/promises";
3475
- import { join as join17 } from "path";
3796
+ import { readdir as readdir2, stat as stat8 } from "fs/promises";
3797
+ import { join as join18 } from "path";
3476
3798
  async function inspectOpenSpecProject(projectRoot) {
3477
- const openspecPath = join17(projectRoot, "openspec");
3478
- const changesPath = join17(openspecPath, "changes");
3479
- const legacyArchivePath = join17(openspecPath, "archive");
3480
- const changesArchivePath = join17(changesPath, "archive");
3799
+ const openspecPath = join18(projectRoot, "openspec");
3800
+ const changesPath = join18(openspecPath, "changes");
3801
+ const legacyArchivePath = join18(openspecPath, "archive");
3802
+ const changesArchivePath = join18(changesPath, "archive");
3481
3803
  return {
3482
3804
  exists: await exists5(openspecPath),
3483
3805
  changes: await listDirectories(changesPath, { exclude: ["archive"] }),
@@ -3485,13 +3807,13 @@ async function inspectOpenSpecProject(projectRoot) {
3485
3807
  };
3486
3808
  }
3487
3809
  async function inspectOpenSpecChange(projectRoot, changeId) {
3488
- const changePath = join17(projectRoot, "openspec", "changes", changeId);
3489
- const tasksPath = join17(changePath, "tasks.md");
3490
- const specsPath = join17(changePath, "specs");
3810
+ const changePath = join18(projectRoot, "openspec", "changes", changeId);
3811
+ const tasksPath = join18(changePath, "tasks.md");
3812
+ const specsPath = join18(changePath, "specs");
3491
3813
  return {
3492
3814
  changeId,
3493
3815
  exists: await exists5(changePath),
3494
- hasProposal: await exists5(join17(changePath, "proposal.md")),
3816
+ hasProposal: await exists5(join18(changePath, "proposal.md")),
3495
3817
  hasTasks: await exists5(tasksPath),
3496
3818
  hasSpecs: await exists5(specsPath),
3497
3819
  tasksPath,
@@ -3500,7 +3822,7 @@ async function inspectOpenSpecChange(projectRoot, changeId) {
3500
3822
  }
3501
3823
  async function listDirectories(path, options = {}) {
3502
3824
  try {
3503
- const entries = await readdir(path, { withFileTypes: true });
3825
+ const entries = await readdir2(path, { withFileTypes: true });
3504
3826
  const excluded = new Set(options.exclude ?? []);
3505
3827
  return entries.filter((entry) => entry.isDirectory() && !excluded.has(entry.name)).map((entry) => entry.name);
3506
3828
  } catch {
@@ -3668,12 +3990,12 @@ function parseCommands(help) {
3668
3990
  }
3669
3991
 
3670
3992
  // src/scanner/package.ts
3671
- import { readFile as readFile14, stat as stat9 } from "fs/promises";
3672
- import { join as join18 } from "path";
3993
+ import { readFile as readFile15, stat as stat9 } from "fs/promises";
3994
+ import { join as join19 } from "path";
3673
3995
  import { parse as parse2 } from "yaml";
3674
3996
  async function readPackageJson(projectRoot) {
3675
3997
  try {
3676
- return JSON.parse(await readFile14(join18(projectRoot, "package.json"), "utf8"));
3998
+ return JSON.parse(await readFile15(join19(projectRoot, "package.json"), "utf8"));
3677
3999
  } catch {
3678
4000
  return null;
3679
4001
  }
@@ -3739,7 +4061,7 @@ function detectFramework(pkg) {
3739
4061
  }
3740
4062
  async function detectLanguage(projectRoot, pkg) {
3741
4063
  const deps = { ...pkg?.dependencies ?? {}, ...pkg?.devDependencies ?? {} };
3742
- if (deps.typescript || await exists6(join18(projectRoot, "tsconfig.json"))) {
4064
+ if (deps.typescript || await exists6(join19(projectRoot, "tsconfig.json"))) {
3743
4065
  return "typescript";
3744
4066
  }
3745
4067
  return "javascript";
@@ -3754,7 +4076,7 @@ async function detectWorkspaces(projectRoot, pkg) {
3754
4076
  return packageWorkspaces;
3755
4077
  }
3756
4078
  try {
3757
- const workspace = parse2(await readFile14(join18(projectRoot, "pnpm-workspace.yaml"), "utf8"));
4079
+ const workspace = parse2(await readFile15(join19(projectRoot, "pnpm-workspace.yaml"), "utf8"));
3758
4080
  return (workspace?.packages ?? []).map((path) => ({
3759
4081
  name: path,
3760
4082
  path,
@@ -3774,7 +4096,7 @@ async function detectLockManagers(projectRoot) {
3774
4096
  ];
3775
4097
  const found = [];
3776
4098
  for (const [file, manager] of lockFiles) {
3777
- if (await exists6(join18(projectRoot, file))) {
4099
+ if (await exists6(join19(projectRoot, file))) {
3778
4100
  found.push(manager);
3779
4101
  }
3780
4102
  }
@@ -3799,13 +4121,13 @@ async function exists6(path) {
3799
4121
  }
3800
4122
 
3801
4123
  // src/scanner/routes.ts
3802
- import { readdir as readdir2, stat as stat10 } from "fs/promises";
3803
- import { join as join19, relative, sep } from "path";
4124
+ import { readdir as readdir3, stat as stat10 } from "fs/promises";
4125
+ import { join as join20, relative, sep } from "path";
3804
4126
  async function scanRoutes(projectRoot) {
3805
4127
  const candidates = ["src/routes", "src/pages", "app", "pages"];
3806
4128
  const routes = [];
3807
4129
  for (const candidate of candidates) {
3808
- const root = join19(projectRoot, candidate);
4130
+ const root = join20(projectRoot, candidate);
3809
4131
  if (!await exists7(root)) {
3810
4132
  continue;
3811
4133
  }
@@ -3830,10 +4152,10 @@ function inferRoutePath(relativePath) {
3830
4152
  return `/${withoutIndex}`.replace(/\/+/g, "/");
3831
4153
  }
3832
4154
  async function listFiles(root) {
3833
- const entries = await readdir2(root, { withFileTypes: true });
4155
+ const entries = await readdir3(root, { withFileTypes: true });
3834
4156
  const files = [];
3835
4157
  for (const entry of entries) {
3836
- const path = join19(root, entry.name);
4158
+ const path = join20(root, entry.name);
3837
4159
  if (entry.isDirectory()) {
3838
4160
  files.push(...await listFiles(path));
3839
4161
  } else {