@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 +264 -4
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
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 =
|
|
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();
|