@nick848/fet 1.1.0 → 1.1.2

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
@@ -5,7 +5,7 @@ import {
5
5
  } from "../chunk-J5WB4KAL.js";
6
6
 
7
7
  // src/cli/index.ts
8
- import { createInterface } from "readline/promises";
8
+ import { createInterface as createInterface2 } from "readline/promises";
9
9
  import { Command } from "commander";
10
10
 
11
11
  // src/commands/doctor.ts
@@ -1575,6 +1575,7 @@ ${block}
1575
1575
 
1576
1576
  // src/commands/update-context.ts
1577
1577
  import { readFile as readFile7 } from "fs/promises";
1578
+ import { createInterface } from "readline/promises";
1578
1579
  import { join as join11 } from "path";
1579
1580
 
1580
1581
  // src/config/yaml.ts
@@ -1627,7 +1628,7 @@ async function updateContextFiles(ctx) {
1627
1628
  });
1628
1629
  }
1629
1630
  if (existingAgents && !hasManagedAutoRegion(existingAgents)) {
1630
- if (!ctx.yes) {
1631
+ if (!ctx.yes && !await confirmInitCanReplaceUnmanagedAgents(ctx)) {
1631
1632
  throw new FetError({
1632
1633
  code: "TOOL_ADAPTER_CONFLICT" /* ToolAdapterConflict */,
1633
1634
  message: ctx.language === "en" ? "AGENTS.md already exists and does not contain a FET-managed region." : "AGENTS.md \u5DF2\u5B58\u5728\uFF0C\u4E14\u4E0D\u5305\u542B FET \u6258\u7BA1\u533A\u57DF\u3002",
@@ -1664,6 +1665,27 @@ async function updateContextFiles(ctx) {
1664
1665
  await ctx.stateStore.writeGlobal(state);
1665
1666
  return { warnings };
1666
1667
  }
1668
+ async function confirmInitCanReplaceUnmanagedAgents(ctx) {
1669
+ if (ctx.command !== "init" || ctx.json || !process.stdin.isTTY || !process.stderr.isTTY) {
1670
+ return false;
1671
+ }
1672
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
1673
+ try {
1674
+ const question = ctx.language === "en" ? "AGENTS.md already exists and is not managed by FET. Continue now with the same effect as fet init --yes? [y/N] " : "AGENTS.md \u5DF2\u5B58\u5728\u4E14\u4E0D\u7531 FET \u6258\u7BA1\u3002\u662F\u5426\u7EE7\u7EED\u6267\u884C\uFF0C\u6548\u679C\u7B49\u540C\u4E8E fet init --yes\uFF1F[y/N] ";
1675
+ const answer = (await rl.question(question)).trim().toLowerCase();
1676
+ if (answer === "y" || answer === "yes") {
1677
+ return true;
1678
+ }
1679
+ } finally {
1680
+ rl.close();
1681
+ }
1682
+ throw new FetError({
1683
+ code: "USER_CANCELLED" /* UserCancelled */,
1684
+ message: ctx.language === "en" ? "fet init cancelled so you can handle AGENTS.md manually." : "\u5DF2\u53D6\u6D88 fet init\uFF0C\u4F60\u53EF\u4EE5\u5148\u624B\u52A8\u5904\u7406 AGENTS.md\u3002",
1685
+ details: { path: "AGENTS.md" },
1686
+ suggestedCommand: ctx.language === "en" ? "Review AGENTS.md, then rerun fet init or fet init --yes." : "\u68C0\u67E5 AGENTS.md \u540E\uFF0C\u624B\u52A8\u91CD\u65B0\u8FD0\u884C fet init \u6216 fet init --yes\u3002"
1687
+ });
1688
+ }
1667
1689
  async function readOptional2(path) {
1668
1690
  try {
1669
1691
  return await readFile7(path, "utf8");
@@ -1982,6 +2004,10 @@ var phaseByCommand = {
1982
2004
  onboard: "explore"
1983
2005
  };
1984
2006
  async function proxyCommand(ctx, command, args) {
2007
+ if (command === "propose" || command === "continue" || command === "ff") {
2008
+ await artifactWorkflowCommand(ctx, command, args);
2009
+ return;
2010
+ }
1985
2011
  const openSpecArgs = stripFetOptions(args);
1986
2012
  const runState = {};
1987
2013
  await withProjectLock(ctx.projectRoot, { command, cwd: ctx.cwd, fetVersion: ctx.fetVersion }, async () => {
@@ -2056,6 +2082,225 @@ async function proxyCommand(ctx, command, args) {
2056
2082
  data: graphContext ? { graphContext } : void 0
2057
2083
  });
2058
2084
  }
2085
+ async function artifactWorkflowCommand(ctx, command, args) {
2086
+ const openSpecArgs = stripFetOptions(args);
2087
+ const runState = {};
2088
+ await withProjectLock(ctx.projectRoot, { command, cwd: ctx.cwd, fetVersion: ctx.fetVersion }, async () => {
2089
+ await assertOpenSpecCommandSupported(ctx, "status", command);
2090
+ await assertOpenSpecCommandSupported(ctx, "instructions", command);
2091
+ if (command === "propose") {
2092
+ await assertOpenSpecCommandSupported(ctx, "new", command);
2093
+ }
2094
+ const changeId = command === "propose" ? await ensureProposedChange(ctx, openSpecArgs) : await requireChangeId(ctx);
2095
+ const artifactId = command === "continue" ? await resolveArtifactId(ctx, changeId, openSpecArgs[0]) : await resolveArtifactId(ctx, changeId);
2096
+ runState.graphContext = await buildWorkflowGraphContext(ctx, {
2097
+ command,
2098
+ args: artifactId ? [artifactId, "--change", changeId] : ["--change", changeId],
2099
+ changeId
2100
+ });
2101
+ const instructions = await runInstructions(ctx, changeId, artifactId);
2102
+ await syncWorkflowState(ctx, command, changeId, {
2103
+ openSpecCommand: "instructions",
2104
+ openSpecArgs: [artifactId, "--change", changeId],
2105
+ exitCode: instructions.exitCode
2106
+ });
2107
+ const status = await readOpenSpecStatus(ctx, changeId);
2108
+ ctx.output.result({
2109
+ ok: true,
2110
+ command,
2111
+ summary: `fet ${command} prepared OpenSpec artifact "${artifactId}" for change "${changeId}".`,
2112
+ warnings: runState.graphContext?.warnings,
2113
+ nextSteps: [
2114
+ `Create or update openspec/changes/${changeId}/${resolveOutputPath(status, artifactId)}`,
2115
+ `Run fet passthrough status --change ${changeId}`,
2116
+ status.isComplete ? `Run fet apply --change ${changeId}` : `Run fet continue --change ${changeId}`
2117
+ ],
2118
+ data: {
2119
+ changeId,
2120
+ artifactId,
2121
+ instructions: instructions.data,
2122
+ status,
2123
+ graphContext: runState.graphContext
2124
+ }
2125
+ });
2126
+ });
2127
+ }
2128
+ async function ensureProposedChange(ctx, args) {
2129
+ if (ctx.changeId) {
2130
+ return ctx.changeId;
2131
+ }
2132
+ const existing = await ctx.openSpec.inspectProject(ctx.projectRoot);
2133
+ const explicit = args[0];
2134
+ if (!explicit) {
2135
+ throw new FetError({
2136
+ code: "INVALID_ARGUMENTS" /* InvalidArguments */,
2137
+ message: "A change id or short description is required for fet propose.",
2138
+ suggestedCommand: "fet propose <change-id-or-description>"
2139
+ });
2140
+ }
2141
+ const input = args.join(" ").trim();
2142
+ const changeId = isKebabId(input) ? input : toKebabId(input);
2143
+ if (!changeId) {
2144
+ throw new FetError({
2145
+ code: "INVALID_ARGUMENTS" /* InvalidArguments */,
2146
+ message: "Unable to derive a valid OpenSpec change id from the proposal input.",
2147
+ details: { input },
2148
+ suggestedCommand: "fet propose <kebab-case-change-id>"
2149
+ });
2150
+ }
2151
+ if (existing.changes.includes(changeId)) {
2152
+ return changeId;
2153
+ }
2154
+ const createArgs = ["change", changeId];
2155
+ if (input && input !== changeId) {
2156
+ createArgs.push("--description", input);
2157
+ }
2158
+ const result = await ctx.openSpec.run("new", createArgs, { cwd: ctx.projectRoot, stdio: "pipe" });
2159
+ if (result.exitCode !== 0) {
2160
+ throw new FetError({
2161
+ code: "OPENSPEC_COMMAND_FAILED" /* OpenSpecCommandFailed */,
2162
+ message: "OpenSpec new change failed.",
2163
+ details: result,
2164
+ recoverable: true
2165
+ });
2166
+ }
2167
+ return changeId;
2168
+ }
2169
+ async function resolveArtifactId(ctx, changeId, requestedArtifactId) {
2170
+ const status = await readOpenSpecStatus(ctx, changeId);
2171
+ const artifacts = status.artifacts ?? [];
2172
+ if (requestedArtifactId) {
2173
+ const requested = artifacts.find((artifact) => artifact.id === requestedArtifactId);
2174
+ if (!requested) {
2175
+ throw new FetError({
2176
+ code: "INVALID_ARGUMENTS" /* InvalidArguments */,
2177
+ message: `OpenSpec artifact "${requestedArtifactId}" was not found for change "${changeId}".`,
2178
+ details: { changeId, requestedArtifactId, artifacts: artifacts.map((artifact) => artifact.id) },
2179
+ suggestedCommand: `fet passthrough status --change ${changeId} --json`
2180
+ });
2181
+ }
2182
+ if (requested.status !== "ready" && requested.status !== "complete") {
2183
+ throw new FetError({
2184
+ code: "INVALID_ARGUMENTS" /* InvalidArguments */,
2185
+ message: `OpenSpec artifact "${requestedArtifactId}" is ${requested.status}, not ready.`,
2186
+ details: { changeId, requestedArtifactId, missingDeps: requested.missingDeps },
2187
+ suggestedCommand: `fet continue --change ${changeId}`
2188
+ });
2189
+ }
2190
+ return requestedArtifactId;
2191
+ }
2192
+ const next = artifacts.find((artifact) => artifact.status === "ready");
2193
+ if (!next) {
2194
+ throw new FetError({
2195
+ code: "INVALID_ARGUMENTS" /* InvalidArguments */,
2196
+ message: `No ready OpenSpec artifact was found for change "${changeId}".`,
2197
+ details: { changeId, artifacts },
2198
+ suggestedCommand: status.isComplete ? `fet apply --change ${changeId}` : `fet passthrough status --change ${changeId} --json`
2199
+ });
2200
+ }
2201
+ return next.id;
2202
+ }
2203
+ async function readOpenSpecStatus(ctx, changeId) {
2204
+ const result = await ctx.openSpec.run("status", ["--change", changeId, "--json"], { cwd: ctx.projectRoot, stdio: "pipe" });
2205
+ if (result.exitCode !== 0) {
2206
+ throw new FetError({
2207
+ code: "OPENSPEC_COMMAND_FAILED" /* OpenSpecCommandFailed */,
2208
+ message: "OpenSpec status failed.",
2209
+ details: result,
2210
+ recoverable: true
2211
+ });
2212
+ }
2213
+ try {
2214
+ return parseOpenSpecJson(result.stdout ?? "{}");
2215
+ } catch (error) {
2216
+ throw new FetError({
2217
+ code: "OPENSPEC_COMMAND_FAILED" /* OpenSpecCommandFailed */,
2218
+ message: "Unable to parse OpenSpec status JSON.",
2219
+ details: { stdout: result.stdout, stderr: result.stderr },
2220
+ cause: error
2221
+ });
2222
+ }
2223
+ }
2224
+ async function runInstructions(ctx, changeId, artifactId) {
2225
+ const args = [artifactId, "--change", changeId, ...ctx.json ? ["--json"] : []];
2226
+ const result = await ctx.openSpec.run("instructions", args, { cwd: ctx.projectRoot, stdio: "pipe" });
2227
+ if (result.stdout && !ctx.json) {
2228
+ process.stdout.write(result.stdout.endsWith("\n") ? result.stdout : `${result.stdout}
2229
+ `);
2230
+ }
2231
+ if (result.stderr && !ctx.json) {
2232
+ process.stderr.write(result.stderr.endsWith("\n") ? result.stderr : `${result.stderr}
2233
+ `);
2234
+ }
2235
+ if (result.exitCode !== 0) {
2236
+ throw new FetError({
2237
+ code: "OPENSPEC_COMMAND_FAILED" /* OpenSpecCommandFailed */,
2238
+ message: "OpenSpec instructions failed.",
2239
+ details: result,
2240
+ recoverable: true
2241
+ });
2242
+ }
2243
+ if (!ctx.json) {
2244
+ return { exitCode: result.exitCode, data: null };
2245
+ }
2246
+ try {
2247
+ return { exitCode: result.exitCode, data: parseOpenSpecJson(result.stdout ?? "{}") };
2248
+ } catch (error) {
2249
+ throw new FetError({
2250
+ code: "OPENSPEC_COMMAND_FAILED" /* OpenSpecCommandFailed */,
2251
+ message: "Unable to parse OpenSpec instructions JSON.",
2252
+ details: { stdout: result.stdout, stderr: result.stderr },
2253
+ cause: error
2254
+ });
2255
+ }
2256
+ }
2257
+ async function syncWorkflowState(ctx, command, changeId, lastOpenSpecCommand) {
2258
+ const inspection = await ctx.openSpec.inspectProject(ctx.projectRoot);
2259
+ const state = await ctx.stateStore.getOrCreateGlobal();
2260
+ state.openChangeIds = inspection.changes;
2261
+ if (inspection.changes.includes(changeId)) {
2262
+ state.activeChangeId = changeId;
2263
+ }
2264
+ await ctx.stateStore.writeGlobal(state);
2265
+ if (!inspection.changes.includes(changeId)) {
2266
+ return;
2267
+ }
2268
+ const changeInspection = await ctx.openSpec.inspectChange(ctx.projectRoot, changeId);
2269
+ const changeState = await ctx.stateStore.getOrCreateChange(changeId, phaseByCommand[command] ?? "propose");
2270
+ changeState.currentPhase = "propose";
2271
+ changeState.phases.propose = {
2272
+ status: "in_progress",
2273
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
2274
+ };
2275
+ changeState.lastOpenSpecCommand = {
2276
+ command: lastOpenSpecCommand.openSpecCommand,
2277
+ args: lastOpenSpecCommand.openSpecArgs,
2278
+ exitCode: lastOpenSpecCommand.exitCode,
2279
+ ranAt: (/* @__PURE__ */ new Date()).toISOString()
2280
+ };
2281
+ changeState.tasks.completedIds = await readCompletedTaskIds(changeInspection.tasksPath);
2282
+ changeState.tasks.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
2283
+ await ctx.stateStore.writeChange(changeState);
2284
+ }
2285
+ function resolveOutputPath(status, artifactId) {
2286
+ return status.artifacts?.find((artifact) => artifact.id === artifactId)?.outputPath ?? `${artifactId}.md`;
2287
+ }
2288
+ function isKebabId(value) {
2289
+ return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
2290
+ }
2291
+ function toKebabId(value) {
2292
+ return value.trim().toLowerCase().replace(/['"]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
2293
+ }
2294
+ function parseOpenSpecJson(stdout) {
2295
+ const trimmed = stdout.trim();
2296
+ const objectStart = trimmed.indexOf("{");
2297
+ const arrayStart = trimmed.indexOf("[");
2298
+ const starts = [objectStart, arrayStart].filter((index) => index >= 0);
2299
+ if (!starts.length) {
2300
+ return JSON.parse(trimmed || "{}");
2301
+ }
2302
+ return JSON.parse(trimmed.slice(Math.min(...starts)));
2303
+ }
2059
2304
  async function assertOpenSpecCommandSupported(ctx, openSpecCommand, fetCommand) {
2060
2305
  const capabilities = await ctx.openSpec.getCapabilities();
2061
2306
  if (capabilities.commands.includes(openSpecCommand)) {
@@ -4192,6 +4437,10 @@ var DefaultOpenSpecAdapter = class {
4192
4437
  function parseCommands(help) {
4193
4438
  const known = [
4194
4439
  "init",
4440
+ "update",
4441
+ "list",
4442
+ "view",
4443
+ "change",
4195
4444
  "explore",
4196
4445
  "propose",
4197
4446
  "new",
@@ -4202,7 +4451,18 @@ function parseCommands(help) {
4202
4451
  "sync",
4203
4452
  "archive",
4204
4453
  "bulk-archive",
4205
- "onboard"
4454
+ "onboard",
4455
+ "spec",
4456
+ "config",
4457
+ "schema",
4458
+ "validate",
4459
+ "show",
4460
+ "feedback",
4461
+ "completion",
4462
+ "status",
4463
+ "instructions",
4464
+ "templates",
4465
+ "schemas"
4206
4466
  ];
4207
4467
  return known.filter((command) => new RegExp(`\\b${escapeRegExp(command)}\\b`).test(help));
4208
4468
  }
@@ -4599,7 +4859,7 @@ async function handleModelPolicyRecommendation(ctx) {
4599
4859
  if (policyMode !== "confirm" || ctx.yes || ctx.json || !process.stdin.isTTY || !process.stderr.isTTY) {
4600
4860
  return;
4601
4861
  }
4602
- const rl = createInterface({ input: process.stdin, output: process.stderr });
4862
+ const rl = createInterface2({ input: process.stdin, output: process.stderr });
4603
4863
  try {
4604
4864
  const question = ctx.language === "en" ? "Continue anyway? [y/N] " : "\u4ECD\u7136\u7EE7\u7EED\uFF1F[y/N] ";
4605
4865
  const answer = (await rl.question(question)).trim().toLowerCase();