@nick848/fet 1.1.1 → 1.1.3
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 +419 -2
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2004,6 +2004,30 @@ var phaseByCommand = {
|
|
|
2004
2004
|
onboard: "explore"
|
|
2005
2005
|
};
|
|
2006
2006
|
async function proxyCommand(ctx, command, args) {
|
|
2007
|
+
if (command === "propose" || command === "continue" || command === "ff") {
|
|
2008
|
+
await artifactWorkflowCommand(ctx, command, args);
|
|
2009
|
+
return;
|
|
2010
|
+
}
|
|
2011
|
+
if (command === "apply") {
|
|
2012
|
+
await applyWorkflowCommand(ctx, args);
|
|
2013
|
+
return;
|
|
2014
|
+
}
|
|
2015
|
+
if (command === "explore") {
|
|
2016
|
+
await exploreWorkflowCommand(ctx, args);
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
if (command === "sync") {
|
|
2020
|
+
await syncWorkflowCommand(ctx, args);
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2023
|
+
if (command === "bulk-archive") {
|
|
2024
|
+
await bulkArchiveWorkflowCommand(ctx, args);
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
if (command === "onboard") {
|
|
2028
|
+
await onboardWorkflowCommand(ctx);
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2007
2031
|
const openSpecArgs = stripFetOptions(args);
|
|
2008
2032
|
const runState = {};
|
|
2009
2033
|
await withProjectLock(ctx.projectRoot, { command, cwd: ctx.cwd, fetVersion: ctx.fetVersion }, async () => {
|
|
@@ -2078,6 +2102,381 @@ async function proxyCommand(ctx, command, args) {
|
|
|
2078
2102
|
data: graphContext ? { graphContext } : void 0
|
|
2079
2103
|
});
|
|
2080
2104
|
}
|
|
2105
|
+
async function applyWorkflowCommand(ctx, args) {
|
|
2106
|
+
const changeId = await requireChangeId(ctx);
|
|
2107
|
+
const runState = {};
|
|
2108
|
+
await withProjectLock(ctx.projectRoot, { command: "apply", cwd: ctx.cwd, fetVersion: ctx.fetVersion }, async () => {
|
|
2109
|
+
await assertOpenSpecCommandSupported(ctx, "status", "apply");
|
|
2110
|
+
await assertOpenSpecCommandSupported(ctx, "instructions", "apply");
|
|
2111
|
+
runState.graphContext = await buildWorkflowGraphContext(ctx, {
|
|
2112
|
+
command: "apply",
|
|
2113
|
+
args: ["tasks", "--change", changeId],
|
|
2114
|
+
changeId
|
|
2115
|
+
});
|
|
2116
|
+
const status = await readOpenSpecStatus(ctx, changeId);
|
|
2117
|
+
const tasks = status.artifacts?.find((artifact) => artifact.id === "tasks");
|
|
2118
|
+
const warnings = tasks && tasks.status !== "complete" && tasks.status !== "ready" ? [`OpenSpec reports tasks artifact as "${tasks.status}". Finish planning artifacts before implementation if tasks.md is missing.`] : void 0;
|
|
2119
|
+
const instructions = await runInstructions(ctx, changeId, "tasks");
|
|
2120
|
+
await syncWorkflowState(ctx, "apply", changeId, {
|
|
2121
|
+
openSpecCommand: "instructions",
|
|
2122
|
+
openSpecArgs: ["tasks", "--change", changeId],
|
|
2123
|
+
exitCode: instructions.exitCode,
|
|
2124
|
+
phaseStatus: "in_progress"
|
|
2125
|
+
});
|
|
2126
|
+
ctx.output.result({
|
|
2127
|
+
ok: true,
|
|
2128
|
+
command: "apply",
|
|
2129
|
+
summary: `fet apply prepared implementation instructions for change "${changeId}".`,
|
|
2130
|
+
warnings: [...runState.graphContext?.warnings ?? [], ...warnings ?? []],
|
|
2131
|
+
nextSteps: [
|
|
2132
|
+
`Read openspec/changes/${changeId}/tasks.md and the instructions output.`,
|
|
2133
|
+
"Implement pending tasks and update task checkboxes only after the work is done.",
|
|
2134
|
+
`Run fet verify --change ${changeId}`
|
|
2135
|
+
],
|
|
2136
|
+
data: {
|
|
2137
|
+
changeId,
|
|
2138
|
+
instructions: instructions.data,
|
|
2139
|
+
status,
|
|
2140
|
+
graphContext: runState.graphContext
|
|
2141
|
+
}
|
|
2142
|
+
});
|
|
2143
|
+
});
|
|
2144
|
+
}
|
|
2145
|
+
async function exploreWorkflowCommand(ctx, args) {
|
|
2146
|
+
const openSpecArgs = stripFetOptions(args);
|
|
2147
|
+
const changeId = ctx.changeId ?? (openSpecArgs[0] && isKebabId(openSpecArgs[0]) ? openSpecArgs[0] : null);
|
|
2148
|
+
const graphContext = await buildWorkflowGraphContext(ctx, {
|
|
2149
|
+
command: "explore",
|
|
2150
|
+
args: openSpecArgs,
|
|
2151
|
+
changeId
|
|
2152
|
+
});
|
|
2153
|
+
ctx.output.result({
|
|
2154
|
+
ok: true,
|
|
2155
|
+
command: "explore",
|
|
2156
|
+
summary: "fet explore is an IDE-guided workflow for shaping OpenSpec changes.",
|
|
2157
|
+
warnings: graphContext.warnings,
|
|
2158
|
+
nextSteps: [
|
|
2159
|
+
"Discuss the requirement, constraints, and acceptance criteria with the user.",
|
|
2160
|
+
changeId ? `Run fet continue --change ${changeId} when ready to create the next artifact.` : "Run fet propose <change-id-or-description> when ready to create a change."
|
|
2161
|
+
],
|
|
2162
|
+
data: { changeId, args: openSpecArgs, graphContext }
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
async function syncWorkflowCommand(ctx, args) {
|
|
2166
|
+
const changeId = await requireChangeId(ctx);
|
|
2167
|
+
const openSpecArgs = stripFetOptions(args);
|
|
2168
|
+
await withProjectLock(ctx.projectRoot, { command: "sync", cwd: ctx.cwd, fetVersion: ctx.fetVersion }, async () => {
|
|
2169
|
+
await assertVerifiedChange(ctx, changeId);
|
|
2170
|
+
await assertOpenSpecCommandSupported(ctx, "validate", "sync");
|
|
2171
|
+
const validateArgs = [changeId, "--type", "change", ...openSpecArgs.filter((arg) => arg !== changeId)];
|
|
2172
|
+
const result = await ctx.openSpec.run("validate", validateArgs, { cwd: ctx.projectRoot, stdio: ctx.json ? "pipe" : "inherit" });
|
|
2173
|
+
if (result.exitCode !== 0) {
|
|
2174
|
+
throw new FetError({
|
|
2175
|
+
code: "OPENSPEC_COMMAND_FAILED" /* OpenSpecCommandFailed */,
|
|
2176
|
+
message: "OpenSpec validate failed during fet sync.",
|
|
2177
|
+
details: result,
|
|
2178
|
+
recoverable: true
|
|
2179
|
+
});
|
|
2180
|
+
}
|
|
2181
|
+
await syncWorkflowState(ctx, "sync", changeId, {
|
|
2182
|
+
openSpecCommand: "validate",
|
|
2183
|
+
openSpecArgs: validateArgs,
|
|
2184
|
+
exitCode: result.exitCode,
|
|
2185
|
+
phaseStatus: "done"
|
|
2186
|
+
});
|
|
2187
|
+
ctx.output.result({
|
|
2188
|
+
ok: true,
|
|
2189
|
+
command: "sync",
|
|
2190
|
+
summary: `fet sync validated change "${changeId}". OpenSpec 1.3.x applies spec updates during archive.`,
|
|
2191
|
+
nextSteps: [`Run fet archive --change ${changeId} when you are ready to archive and update main specs.`],
|
|
2192
|
+
data: ctx.json ? { changeId, validate: result } : { changeId }
|
|
2193
|
+
});
|
|
2194
|
+
});
|
|
2195
|
+
}
|
|
2196
|
+
async function bulkArchiveWorkflowCommand(ctx, args) {
|
|
2197
|
+
const openSpecArgs = stripFetOptions(args);
|
|
2198
|
+
const targets = [.../* @__PURE__ */ new Set([...ctx.changeId ? [ctx.changeId] : [], ...openSpecArgs.filter((arg) => !arg.startsWith("-"))])];
|
|
2199
|
+
if (!targets.length) {
|
|
2200
|
+
throw new FetError({
|
|
2201
|
+
code: "INVALID_ARGUMENTS" /* InvalidArguments */,
|
|
2202
|
+
message: "fet bulk-archive requires explicit change ids with the current OpenSpec CLI.",
|
|
2203
|
+
suggestedCommand: "fet bulk-archive --change <change-id>"
|
|
2204
|
+
});
|
|
2205
|
+
}
|
|
2206
|
+
await withProjectLock(ctx.projectRoot, { command: "bulk-archive", cwd: ctx.cwd, fetVersion: ctx.fetVersion }, async () => {
|
|
2207
|
+
await assertOpenSpecCommandSupported(ctx, "archive", "bulk-archive");
|
|
2208
|
+
const archived = [];
|
|
2209
|
+
for (const changeId of targets) {
|
|
2210
|
+
await assertVerifiedChange(ctx, changeId);
|
|
2211
|
+
const changelogEntry = await createChangelogEntry(ctx.projectRoot, changeId);
|
|
2212
|
+
const result = await ctx.openSpec.run("archive", [changeId], { cwd: ctx.projectRoot, stdio: ctx.json ? "pipe" : "inherit" });
|
|
2213
|
+
if (result.exitCode !== 0) {
|
|
2214
|
+
throw new FetError({
|
|
2215
|
+
code: "OPENSPEC_COMMAND_FAILED" /* OpenSpecCommandFailed */,
|
|
2216
|
+
message: `OpenSpec archive failed for "${changeId}".`,
|
|
2217
|
+
details: result,
|
|
2218
|
+
recoverable: true
|
|
2219
|
+
});
|
|
2220
|
+
}
|
|
2221
|
+
await appendChangelog(ctx.projectRoot, changelogEntry);
|
|
2222
|
+
archived.push(changeId);
|
|
2223
|
+
}
|
|
2224
|
+
const inspection = await ctx.openSpec.inspectProject(ctx.projectRoot);
|
|
2225
|
+
const state = await ctx.stateStore.getOrCreateGlobal();
|
|
2226
|
+
state.openChangeIds = inspection.changes;
|
|
2227
|
+
if (state.activeChangeId && !inspection.changes.includes(state.activeChangeId)) {
|
|
2228
|
+
state.activeChangeId = null;
|
|
2229
|
+
}
|
|
2230
|
+
state.verifyAuthorization = null;
|
|
2231
|
+
await ctx.stateStore.writeGlobal(state);
|
|
2232
|
+
ctx.output.result({
|
|
2233
|
+
ok: true,
|
|
2234
|
+
command: "bulk-archive",
|
|
2235
|
+
summary: `fet bulk-archive archived ${archived.length} change(s) via OpenSpec archive.`,
|
|
2236
|
+
nextSteps: inspection.changes.length ? [`Remaining open changes: ${inspection.changes.join(", ")}`] : void 0,
|
|
2237
|
+
data: { archived, openChangeIds: inspection.changes }
|
|
2238
|
+
});
|
|
2239
|
+
});
|
|
2240
|
+
}
|
|
2241
|
+
async function onboardWorkflowCommand(ctx) {
|
|
2242
|
+
const inspection = await ctx.openSpec.inspectProject(ctx.projectRoot);
|
|
2243
|
+
const state = await ctx.stateStore.getOrCreateGlobal();
|
|
2244
|
+
state.openChangeIds = inspection.changes;
|
|
2245
|
+
if (state.activeChangeId && !inspection.changes.includes(state.activeChangeId)) {
|
|
2246
|
+
state.activeChangeId = inspection.changes.length === 1 ? inspection.changes[0] ?? null : null;
|
|
2247
|
+
}
|
|
2248
|
+
await ctx.stateStore.writeGlobal(state);
|
|
2249
|
+
ctx.output.result({
|
|
2250
|
+
ok: true,
|
|
2251
|
+
command: "onboard",
|
|
2252
|
+
summary: "fet onboard loaded local FET/OpenSpec workflow context.",
|
|
2253
|
+
nextSteps: [
|
|
2254
|
+
inspection.changes.length ? `Open changes: ${inspection.changes.join(", ")}` : "No open changes found. Run fet propose <change-id-or-description> to start one.",
|
|
2255
|
+
"Use fet continue to prepare planning artifacts, fet apply for implementation instructions, fet verify before archive."
|
|
2256
|
+
],
|
|
2257
|
+
data: { activeChangeId: state.activeChangeId, openChangeIds: inspection.changes, archivedChangeIds: inspection.archived }
|
|
2258
|
+
});
|
|
2259
|
+
}
|
|
2260
|
+
async function artifactWorkflowCommand(ctx, command, args) {
|
|
2261
|
+
const openSpecArgs = stripFetOptions(args);
|
|
2262
|
+
const runState = {};
|
|
2263
|
+
await withProjectLock(ctx.projectRoot, { command, cwd: ctx.cwd, fetVersion: ctx.fetVersion }, async () => {
|
|
2264
|
+
await assertOpenSpecCommandSupported(ctx, "status", command);
|
|
2265
|
+
await assertOpenSpecCommandSupported(ctx, "instructions", command);
|
|
2266
|
+
if (command === "propose") {
|
|
2267
|
+
await assertOpenSpecCommandSupported(ctx, "new", command);
|
|
2268
|
+
}
|
|
2269
|
+
const changeId = command === "propose" ? await ensureProposedChange(ctx, openSpecArgs) : await requireChangeId(ctx);
|
|
2270
|
+
const artifactId = command === "continue" ? await resolveArtifactId(ctx, changeId, openSpecArgs[0]) : await resolveArtifactId(ctx, changeId);
|
|
2271
|
+
runState.graphContext = await buildWorkflowGraphContext(ctx, {
|
|
2272
|
+
command,
|
|
2273
|
+
args: artifactId ? [artifactId, "--change", changeId] : ["--change", changeId],
|
|
2274
|
+
changeId
|
|
2275
|
+
});
|
|
2276
|
+
const instructions = await runInstructions(ctx, changeId, artifactId);
|
|
2277
|
+
await syncWorkflowState(ctx, command, changeId, {
|
|
2278
|
+
openSpecCommand: "instructions",
|
|
2279
|
+
openSpecArgs: [artifactId, "--change", changeId],
|
|
2280
|
+
exitCode: instructions.exitCode
|
|
2281
|
+
});
|
|
2282
|
+
const status = await readOpenSpecStatus(ctx, changeId);
|
|
2283
|
+
ctx.output.result({
|
|
2284
|
+
ok: true,
|
|
2285
|
+
command,
|
|
2286
|
+
summary: `fet ${command} prepared OpenSpec artifact "${artifactId}" for change "${changeId}".`,
|
|
2287
|
+
warnings: runState.graphContext?.warnings,
|
|
2288
|
+
nextSteps: [
|
|
2289
|
+
`Create or update openspec/changes/${changeId}/${resolveOutputPath(status, artifactId)}`,
|
|
2290
|
+
`Run fet passthrough status --change ${changeId}`,
|
|
2291
|
+
status.isComplete ? `Run fet apply --change ${changeId}` : `Run fet continue --change ${changeId}`
|
|
2292
|
+
],
|
|
2293
|
+
data: {
|
|
2294
|
+
changeId,
|
|
2295
|
+
artifactId,
|
|
2296
|
+
instructions: instructions.data,
|
|
2297
|
+
status,
|
|
2298
|
+
graphContext: runState.graphContext
|
|
2299
|
+
}
|
|
2300
|
+
});
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
async function ensureProposedChange(ctx, args) {
|
|
2304
|
+
if (ctx.changeId) {
|
|
2305
|
+
return ctx.changeId;
|
|
2306
|
+
}
|
|
2307
|
+
const existing = await ctx.openSpec.inspectProject(ctx.projectRoot);
|
|
2308
|
+
const explicit = args[0];
|
|
2309
|
+
if (!explicit) {
|
|
2310
|
+
throw new FetError({
|
|
2311
|
+
code: "INVALID_ARGUMENTS" /* InvalidArguments */,
|
|
2312
|
+
message: "A change id or short description is required for fet propose.",
|
|
2313
|
+
suggestedCommand: "fet propose <change-id-or-description>"
|
|
2314
|
+
});
|
|
2315
|
+
}
|
|
2316
|
+
const input = args.join(" ").trim();
|
|
2317
|
+
const changeId = isKebabId(input) ? input : toKebabId(input);
|
|
2318
|
+
if (!changeId) {
|
|
2319
|
+
throw new FetError({
|
|
2320
|
+
code: "INVALID_ARGUMENTS" /* InvalidArguments */,
|
|
2321
|
+
message: "Unable to derive a valid OpenSpec change id from the proposal input.",
|
|
2322
|
+
details: { input },
|
|
2323
|
+
suggestedCommand: "fet propose <kebab-case-change-id>"
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2326
|
+
if (existing.changes.includes(changeId)) {
|
|
2327
|
+
return changeId;
|
|
2328
|
+
}
|
|
2329
|
+
const createArgs = ["change", changeId];
|
|
2330
|
+
if (input && input !== changeId) {
|
|
2331
|
+
createArgs.push("--description", input);
|
|
2332
|
+
}
|
|
2333
|
+
const result = await ctx.openSpec.run("new", createArgs, { cwd: ctx.projectRoot, stdio: "pipe" });
|
|
2334
|
+
if (result.exitCode !== 0) {
|
|
2335
|
+
throw new FetError({
|
|
2336
|
+
code: "OPENSPEC_COMMAND_FAILED" /* OpenSpecCommandFailed */,
|
|
2337
|
+
message: "OpenSpec new change failed.",
|
|
2338
|
+
details: result,
|
|
2339
|
+
recoverable: true
|
|
2340
|
+
});
|
|
2341
|
+
}
|
|
2342
|
+
return changeId;
|
|
2343
|
+
}
|
|
2344
|
+
async function resolveArtifactId(ctx, changeId, requestedArtifactId) {
|
|
2345
|
+
const status = await readOpenSpecStatus(ctx, changeId);
|
|
2346
|
+
const artifacts = status.artifacts ?? [];
|
|
2347
|
+
if (requestedArtifactId) {
|
|
2348
|
+
const requested = artifacts.find((artifact) => artifact.id === requestedArtifactId);
|
|
2349
|
+
if (!requested) {
|
|
2350
|
+
throw new FetError({
|
|
2351
|
+
code: "INVALID_ARGUMENTS" /* InvalidArguments */,
|
|
2352
|
+
message: `OpenSpec artifact "${requestedArtifactId}" was not found for change "${changeId}".`,
|
|
2353
|
+
details: { changeId, requestedArtifactId, artifacts: artifacts.map((artifact) => artifact.id) },
|
|
2354
|
+
suggestedCommand: `fet passthrough status --change ${changeId} --json`
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
if (requested.status !== "ready" && requested.status !== "complete") {
|
|
2358
|
+
throw new FetError({
|
|
2359
|
+
code: "INVALID_ARGUMENTS" /* InvalidArguments */,
|
|
2360
|
+
message: `OpenSpec artifact "${requestedArtifactId}" is ${requested.status}, not ready.`,
|
|
2361
|
+
details: { changeId, requestedArtifactId, missingDeps: requested.missingDeps },
|
|
2362
|
+
suggestedCommand: `fet continue --change ${changeId}`
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2365
|
+
return requestedArtifactId;
|
|
2366
|
+
}
|
|
2367
|
+
const next = artifacts.find((artifact) => artifact.status === "ready");
|
|
2368
|
+
if (!next) {
|
|
2369
|
+
throw new FetError({
|
|
2370
|
+
code: "INVALID_ARGUMENTS" /* InvalidArguments */,
|
|
2371
|
+
message: `No ready OpenSpec artifact was found for change "${changeId}".`,
|
|
2372
|
+
details: { changeId, artifacts },
|
|
2373
|
+
suggestedCommand: status.isComplete ? `fet apply --change ${changeId}` : `fet passthrough status --change ${changeId} --json`
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
return next.id;
|
|
2377
|
+
}
|
|
2378
|
+
async function readOpenSpecStatus(ctx, changeId) {
|
|
2379
|
+
const result = await ctx.openSpec.run("status", ["--change", changeId, "--json"], { cwd: ctx.projectRoot, stdio: "pipe" });
|
|
2380
|
+
if (result.exitCode !== 0) {
|
|
2381
|
+
throw new FetError({
|
|
2382
|
+
code: "OPENSPEC_COMMAND_FAILED" /* OpenSpecCommandFailed */,
|
|
2383
|
+
message: "OpenSpec status failed.",
|
|
2384
|
+
details: result,
|
|
2385
|
+
recoverable: true
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
2388
|
+
try {
|
|
2389
|
+
return parseOpenSpecJson(result.stdout ?? "{}");
|
|
2390
|
+
} catch (error) {
|
|
2391
|
+
throw new FetError({
|
|
2392
|
+
code: "OPENSPEC_COMMAND_FAILED" /* OpenSpecCommandFailed */,
|
|
2393
|
+
message: "Unable to parse OpenSpec status JSON.",
|
|
2394
|
+
details: { stdout: result.stdout, stderr: result.stderr },
|
|
2395
|
+
cause: error
|
|
2396
|
+
});
|
|
2397
|
+
}
|
|
2398
|
+
}
|
|
2399
|
+
async function runInstructions(ctx, changeId, artifactId) {
|
|
2400
|
+
const args = [artifactId, "--change", changeId, ...ctx.json ? ["--json"] : []];
|
|
2401
|
+
const result = await ctx.openSpec.run("instructions", args, { cwd: ctx.projectRoot, stdio: "pipe" });
|
|
2402
|
+
if (result.stdout && !ctx.json) {
|
|
2403
|
+
process.stdout.write(result.stdout.endsWith("\n") ? result.stdout : `${result.stdout}
|
|
2404
|
+
`);
|
|
2405
|
+
}
|
|
2406
|
+
if (result.stderr && !ctx.json) {
|
|
2407
|
+
process.stderr.write(result.stderr.endsWith("\n") ? result.stderr : `${result.stderr}
|
|
2408
|
+
`);
|
|
2409
|
+
}
|
|
2410
|
+
if (result.exitCode !== 0) {
|
|
2411
|
+
throw new FetError({
|
|
2412
|
+
code: "OPENSPEC_COMMAND_FAILED" /* OpenSpecCommandFailed */,
|
|
2413
|
+
message: "OpenSpec instructions failed.",
|
|
2414
|
+
details: result,
|
|
2415
|
+
recoverable: true
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
if (!ctx.json) {
|
|
2419
|
+
return { exitCode: result.exitCode, data: null };
|
|
2420
|
+
}
|
|
2421
|
+
try {
|
|
2422
|
+
return { exitCode: result.exitCode, data: parseOpenSpecJson(result.stdout ?? "{}") };
|
|
2423
|
+
} catch (error) {
|
|
2424
|
+
throw new FetError({
|
|
2425
|
+
code: "OPENSPEC_COMMAND_FAILED" /* OpenSpecCommandFailed */,
|
|
2426
|
+
message: "Unable to parse OpenSpec instructions JSON.",
|
|
2427
|
+
details: { stdout: result.stdout, stderr: result.stderr },
|
|
2428
|
+
cause: error
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
async function syncWorkflowState(ctx, command, changeId, lastOpenSpecCommand) {
|
|
2433
|
+
const inspection = await ctx.openSpec.inspectProject(ctx.projectRoot);
|
|
2434
|
+
const state = await ctx.stateStore.getOrCreateGlobal();
|
|
2435
|
+
state.openChangeIds = inspection.changes;
|
|
2436
|
+
if (inspection.changes.includes(changeId)) {
|
|
2437
|
+
state.activeChangeId = changeId;
|
|
2438
|
+
}
|
|
2439
|
+
await ctx.stateStore.writeGlobal(state);
|
|
2440
|
+
if (!inspection.changes.includes(changeId)) {
|
|
2441
|
+
return;
|
|
2442
|
+
}
|
|
2443
|
+
const changeInspection = await ctx.openSpec.inspectChange(ctx.projectRoot, changeId);
|
|
2444
|
+
const changeState = await ctx.stateStore.getOrCreateChange(changeId, phaseByCommand[command] ?? "propose");
|
|
2445
|
+
const phase = phaseByCommand[command] ?? "propose";
|
|
2446
|
+
changeState.currentPhase = phase;
|
|
2447
|
+
changeState.phases[phase] = {
|
|
2448
|
+
status: lastOpenSpecCommand.phaseStatus ?? "in_progress",
|
|
2449
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2450
|
+
};
|
|
2451
|
+
changeState.lastOpenSpecCommand = {
|
|
2452
|
+
command: lastOpenSpecCommand.openSpecCommand,
|
|
2453
|
+
args: lastOpenSpecCommand.openSpecArgs,
|
|
2454
|
+
exitCode: lastOpenSpecCommand.exitCode,
|
|
2455
|
+
ranAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2456
|
+
};
|
|
2457
|
+
changeState.tasks.completedIds = await readCompletedTaskIds(changeInspection.tasksPath);
|
|
2458
|
+
changeState.tasks.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2459
|
+
await ctx.stateStore.writeChange(changeState);
|
|
2460
|
+
}
|
|
2461
|
+
function resolveOutputPath(status, artifactId) {
|
|
2462
|
+
return status.artifacts?.find((artifact) => artifact.id === artifactId)?.outputPath ?? `${artifactId}.md`;
|
|
2463
|
+
}
|
|
2464
|
+
function isKebabId(value) {
|
|
2465
|
+
return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
|
|
2466
|
+
}
|
|
2467
|
+
function toKebabId(value) {
|
|
2468
|
+
return value.trim().toLowerCase().replace(/['"]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2469
|
+
}
|
|
2470
|
+
function parseOpenSpecJson(stdout) {
|
|
2471
|
+
const trimmed = stdout.trim();
|
|
2472
|
+
const objectStart = trimmed.indexOf("{");
|
|
2473
|
+
const arrayStart = trimmed.indexOf("[");
|
|
2474
|
+
const starts = [objectStart, arrayStart].filter((index) => index >= 0);
|
|
2475
|
+
if (!starts.length) {
|
|
2476
|
+
return JSON.parse(trimmed || "{}");
|
|
2477
|
+
}
|
|
2478
|
+
return JSON.parse(trimmed.slice(Math.min(...starts)));
|
|
2479
|
+
}
|
|
2081
2480
|
async function assertOpenSpecCommandSupported(ctx, openSpecCommand, fetCommand) {
|
|
2082
2481
|
const capabilities = await ctx.openSpec.getCapabilities();
|
|
2083
2482
|
if (capabilities.commands.includes(openSpecCommand)) {
|
|
@@ -2256,6 +2655,9 @@ async function assertVerified(ctx) {
|
|
|
2256
2655
|
suggestedCommand: "fet verify --done --change <change-id>"
|
|
2257
2656
|
});
|
|
2258
2657
|
}
|
|
2658
|
+
await assertVerifiedChange(ctx, changeId);
|
|
2659
|
+
}
|
|
2660
|
+
async function assertVerifiedChange(ctx, changeId) {
|
|
2259
2661
|
const change = await ctx.stateStore.readChange(changeId);
|
|
2260
2662
|
const inspection = await ctx.openSpec.inspectProject(ctx.projectRoot);
|
|
2261
2663
|
if (!inspection.changes.includes(changeId)) {
|
|
@@ -2746,7 +3148,7 @@ var FET_WORKFLOW_COMMANDS = [
|
|
|
2746
3148
|
"onboard"
|
|
2747
3149
|
];
|
|
2748
3150
|
var FET_GRAPH_COMMANDS = ["graph-status", "graph-setup", "graph-init", "graph-refresh", "graph-doctor", "graph-handoff"];
|
|
2749
|
-
var FET_ADAPTER_COMMANDS = [...FET_WORKFLOW_COMMANDS, "fill-context", "passthrough", ...FET_GRAPH_COMMANDS];
|
|
3151
|
+
var FET_ADAPTER_COMMANDS = [...FET_WORKFLOW_COMMANDS, "update", "fill-context", "passthrough", ...FET_GRAPH_COMMANDS];
|
|
2750
3152
|
function renderFetAdapterUsage(command, args = "[...args]") {
|
|
2751
3153
|
if (command.startsWith("graph-")) {
|
|
2752
3154
|
const subcommand = command.slice("graph-".length);
|
|
@@ -4214,6 +4616,10 @@ var DefaultOpenSpecAdapter = class {
|
|
|
4214
4616
|
function parseCommands(help) {
|
|
4215
4617
|
const known = [
|
|
4216
4618
|
"init",
|
|
4619
|
+
"update",
|
|
4620
|
+
"list",
|
|
4621
|
+
"view",
|
|
4622
|
+
"change",
|
|
4217
4623
|
"explore",
|
|
4218
4624
|
"propose",
|
|
4219
4625
|
"new",
|
|
@@ -4224,7 +4630,18 @@ function parseCommands(help) {
|
|
|
4224
4630
|
"sync",
|
|
4225
4631
|
"archive",
|
|
4226
4632
|
"bulk-archive",
|
|
4227
|
-
"onboard"
|
|
4633
|
+
"onboard",
|
|
4634
|
+
"spec",
|
|
4635
|
+
"config",
|
|
4636
|
+
"schema",
|
|
4637
|
+
"validate",
|
|
4638
|
+
"show",
|
|
4639
|
+
"feedback",
|
|
4640
|
+
"completion",
|
|
4641
|
+
"status",
|
|
4642
|
+
"instructions",
|
|
4643
|
+
"templates",
|
|
4644
|
+
"schemas"
|
|
4228
4645
|
];
|
|
4229
4646
|
return known.filter((command) => new RegExp(`\\b${escapeRegExp(command)}\\b`).test(help));
|
|
4230
4647
|
}
|