@nick848/fet 1.0.9 → 1.0.10

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
@@ -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()),
@@ -2811,15 +2829,15 @@ After the command completes, report the GitNexus state, generated handoff files,
2811
2829
  `;
2812
2830
  }
2813
2831
  function renderSlashPrompt(command, language) {
2832
+ if (command === "ff" || command === "propose") {
2833
+ return renderFastForwardSlashPrompt(command, language);
2834
+ }
2814
2835
  if (language !== "en") {
2815
2836
  return renderSlashPromptZh(command);
2816
2837
  }
2817
2838
  if (command === "continue") {
2818
2839
  return renderContinueSlashPrompt(language);
2819
2840
  }
2820
- if (command === "ff" || command === "propose") {
2821
- return renderFastForwardSlashPrompt(command, language);
2822
- }
2823
2841
  if (command === "explore") {
2824
2842
  return renderExploreSlashPrompt(language);
2825
2843
  }
@@ -3419,11 +3437,11 @@ Guardrails:
3419
3437
  );
3420
3438
  }
3421
3439
  function renderFastForwardSlashPrompt(command, language) {
3422
- const title = command === "propose" ? "Propose a new FET/OpenSpec change" : "Fast-forward FET/OpenSpec artifact creation";
3440
+ 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
3441
  const commandLine = command === "propose" ? "fet propose <change-id-or-description>" : "fet ff --change <change-id>";
3424
3442
  return renderManagedSlashPrompt(
3425
3443
  `fet ${command} [...args]`,
3426
- command === "propose" ? "Create a change and generate required OpenSpec artifacts" : "Generate required OpenSpec artifacts for a change",
3444
+ 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
3445
  `${title}.
3428
3446
 
3429
3447
  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 +3456,7 @@ Steps:
3438
3456
  \`\`\`
3439
3457
  4. Follow the native output. If it asks for clarification, ask the user rather than inventing details.
3440
3458
  5. If the native output includes artifact paths or templates to write, create only those files and preserve OpenSpec structure.
3459
+ 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
3460
 
3442
3461
  Artifact rules:
3443
3462
  - Follow the instruction field from OpenSpec/FET for each artifact.
@@ -3449,7 +3468,11 @@ Output:
3449
3468
  - Change id and location.
3450
3469
  - Artifacts created.
3451
3470
  - Current status.
3452
- - Next recommended command, usually /prompts:fet-apply <change-id>.`,
3471
+ - Next recommended command, usually /prompts:fet-apply <change-id>.
3472
+
3473
+ Guardrails:
3474
+ - Do not substitute one FET/OpenSpec workflow command for another after a command-not-found or unsupported-version error.
3475
+ - 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
3476
  void 0,
3454
3477
  language
3455
3478
  );
@@ -3855,7 +3878,7 @@ async function findExecutable() {
3855
3878
  const command = process.platform === "win32" ? "where.exe" : "which";
3856
3879
  try {
3857
3880
  const { stdout } = await exec(command, ["openspec"]);
3858
- const first = stdout.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
3881
+ const first = stdout.split(/\r?\n/).map((line) => line.trim()).sort((left, right) => executablePreference(left) - executablePreference(right)).find(Boolean);
3859
3882
  if (first) {
3860
3883
  return first;
3861
3884
  }
@@ -3872,6 +3895,12 @@ async function findExecutable() {
3872
3895
  });
3873
3896
  }
3874
3897
  }
3898
+ function executablePreference(path) {
3899
+ if (process.platform === "win32" && path.toLowerCase().endsWith(".cmd")) {
3900
+ return 0;
3901
+ }
3902
+ return 1;
3903
+ }
3875
3904
  async function readVersion(executablePath) {
3876
3905
  const command = executablePath === "npx openspec" ? "npx" : executablePath;
3877
3906
  const args = executablePath === "npx openspec" ? ["openspec", "--version"] : ["--version"];
@@ -3986,7 +4015,10 @@ function parseCommands(help) {
3986
4015
  "bulk-archive",
3987
4016
  "onboard"
3988
4017
  ];
3989
- return known.filter((command) => help.includes(command));
4018
+ return known.filter((command) => new RegExp(`\\b${escapeRegExp(command)}\\b`).test(help));
4019
+ }
4020
+ function escapeRegExp(value) {
4021
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3990
4022
  }
3991
4023
 
3992
4024
  // src/scanner/package.ts