@nick848/fet 1.0.0 → 1.0.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
@@ -9,7 +9,7 @@ import { Command } from "commander";
9
9
 
10
10
  // src/commands/init.ts
11
11
  import { readFile as readFile5, stat as stat2 } from "fs/promises";
12
- import { join as join5 } from "path";
12
+ import { join as join6 } from "path";
13
13
 
14
14
  // src/fs/atomic-write.ts
15
15
  import { dirname } from "path";
@@ -116,7 +116,28 @@ async function writeInitJournal(projectRoot, journal) {
116
116
  }
117
117
 
118
118
  // src/version.ts
119
- var FET_VERSION = "0.3.0-dev";
119
+ import { existsSync, readFileSync } from "fs";
120
+ import { dirname as dirname4, join as join4, parse } from "path";
121
+ import { fileURLToPath } from "url";
122
+ var FET_VERSION = readPackageVersion();
123
+ function readPackageVersion() {
124
+ let currentDir = dirname4(fileURLToPath(import.meta.url));
125
+ const root = parse(currentDir).root;
126
+ while (true) {
127
+ const packageJsonPath = join4(currentDir, "package.json");
128
+ if (existsSync(packageJsonPath)) {
129
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
130
+ if (typeof packageJson.version === "string" && packageJson.version.length > 0) {
131
+ return packageJson.version;
132
+ }
133
+ throw new Error(`package.json \u7F3A\u5C11\u6709\u6548\u7684 version \u5B57\u6BB5: ${packageJsonPath}`);
134
+ }
135
+ if (currentDir === root) {
136
+ throw new Error("\u65E0\u6CD5\u5B9A\u4F4D FET package.json");
137
+ }
138
+ currentDir = dirname4(currentDir);
139
+ }
140
+ }
120
141
 
121
142
  // src/templates/markers.ts
122
143
  var AUTO_BEGIN = "<!-- FET:BEGIN AUTO -->";
@@ -161,7 +182,7 @@ function count(content, needle) {
161
182
 
162
183
  // src/templates/agents-md.ts
163
184
  function renderAgentsMd(scan) {
164
- const commands2 = Object.entries(scan.commands).map(([name, command]) => `| ${name} | \`${command.command}\` | ${command.source} |`).join("\n");
185
+ const commands = Object.entries(scan.commands).map(([name, command]) => `| ${name} | \`${command.command}\` | ${command.source} |`).join("\n");
165
186
  const routes = scan.routes.map((route) => `| ${route.path} | ${route.source} | ${route.confidence}${route.inferred ? " inferred" : ""} |`).join("\n");
166
187
  const workspaces = scan.project.workspaces.map((workspace) => `| ${workspace.name} | ${workspace.path} | ${workspace.source} |`).join("\n");
167
188
  return `# Project Context
@@ -185,7 +206,7 @@ ${workspaces || "| root | . | inferred |"}
185
206
 
186
207
  | Name | Command | Source |
187
208
  |------|---------|--------|
188
- ${commands2 || "| [NEEDS LLM INPUT] | [NEEDS LLM INPUT] | [NEEDS LLM INPUT] |"}
209
+ ${commands || "| [NEEDS LLM INPUT] | [NEEDS LLM INPUT] | [NEEDS LLM INPUT] |"}
189
210
 
190
211
  ## Structure
191
212
 
@@ -305,7 +326,7 @@ ${block}
305
326
 
306
327
  // src/commands/update-context.ts
307
328
  import { readFile as readFile4 } from "fs/promises";
308
- import { join as join4 } from "path";
329
+ import { join as join5 } from "path";
309
330
 
310
331
  // src/config/yaml.ts
311
332
  import { readFile as readFile3 } from "fs/promises";
@@ -339,8 +360,8 @@ async function updateContextCommand(ctx) {
339
360
  }
340
361
  async function updateContextFiles(ctx) {
341
362
  const scan = await ctx.scanner.scan(ctx.projectRoot, {});
342
- const agentsPath = join4(ctx.projectRoot, "AGENTS.md");
343
- const configPath = join4(ctx.projectRoot, "openspec", "config.yaml");
363
+ const agentsPath = join5(ctx.projectRoot, "AGENTS.md");
364
+ const configPath = join5(ctx.projectRoot, "openspec", "config.yaml");
344
365
  const existingAgents = await readOptional(agentsPath);
345
366
  const warnings = [...scan.warnings];
346
367
  if (existingAgents && hasInvalidManagedAutoRegion(existingAgents)) {
@@ -357,7 +378,7 @@ async function updateContextFiles(ctx) {
357
378
  code: "TOOL_ADAPTER_CONFLICT" /* ToolAdapterConflict */,
358
379
  message: "AGENTS.md \u5DF2\u5B58\u5728\u4E14\u4E0D\u5305\u542B FET \u6258\u7BA1\u533A\u57DF",
359
380
  details: { path: "AGENTS.md" },
360
- suggestedCommand: "\u786E\u8BA4\u53EF\u5907\u4EFD\u5E76\u66FF\u6362\u540E\u8FD0\u884C fet update-context --yes"
381
+ suggestedCommand: ctx.command === "init" ? "\u786E\u8BA4\u53EF\u5907\u4EFD\u5E76\u66FF\u6362\u540E\u8FD0\u884C fet init --yes" : "\u786E\u8BA4\u53EF\u5907\u4EFD\u5E76\u66FF\u6362\u540E\u8FD0\u884C fet update-context --yes"
361
382
  });
362
383
  }
363
384
  const backupPath = await createBackup(agentsPath);
@@ -386,7 +407,7 @@ async function readOptional(path) {
386
407
 
387
408
  // src/commands/init.ts
388
409
  async function initCommand(ctx) {
389
- const alreadyInitialized = await exists(join5(ctx.projectRoot, "openspec", "config.yaml"));
410
+ const alreadyInitialized = await exists(join6(ctx.projectRoot, "openspec", "config.yaml"));
390
411
  await withProjectLock(
391
412
  ctx.projectRoot,
392
413
  { command: "init", cwd: ctx.cwd, fetVersion: ctx.fetVersion },
@@ -431,7 +452,7 @@ async function initCommand(ctx) {
431
452
  });
432
453
  }
433
454
  async function ensureGitignore(ctx) {
434
- const gitignorePath = join5(ctx.projectRoot, ".gitignore");
455
+ const gitignorePath = join6(ctx.projectRoot, ".gitignore");
435
456
  const existing = await readOptional2(gitignorePath);
436
457
  await atomicWrite(gitignorePath, mergeGitignore(existing));
437
458
  }
@@ -453,17 +474,17 @@ async function exists(path) {
453
474
 
454
475
  // src/commands/doctor.ts
455
476
  import { stat as stat3 } from "fs/promises";
456
- import { join as join6 } from "path";
477
+ import { join as join7 } from "path";
457
478
  async function doctorCommand(ctx, options = {}) {
458
479
  const checks = [];
459
480
  checks.push(await checkOpenSpec(ctx));
460
481
  checks.push(await checkState(ctx));
461
- checks.push(await checkFile("agents", join6(ctx.projectRoot, "AGENTS.md"), "AGENTS.md \u7F3A\u5931", "fet update-context"));
462
- checks.push(await checkFile("config", join6(ctx.projectRoot, "openspec", "config.yaml"), "openspec/config.yaml \u7F3A\u5931", "fet init"));
482
+ checks.push(await checkFile("agents", join7(ctx.projectRoot, "AGENTS.md"), "AGENTS.md \u7F3A\u5931", "fet update-context"));
483
+ checks.push(await checkFile("config", join7(ctx.projectRoot, "openspec", "config.yaml"), "openspec/config.yaml \u7F3A\u5931", "fet init"));
463
484
  for (const adapter of ctx.toolAdapters) {
464
485
  checks.push(...await adapter.doctor(ctx.projectRoot));
465
486
  }
466
- const lockPath = join6(ctx.projectRoot, "openspec", ".fet.lock");
487
+ const lockPath = join7(ctx.projectRoot, "openspec", ".fet.lock");
467
488
  if (await exists2(lockPath)) {
468
489
  if (options.fixLock) {
469
490
  await clearLock(ctx.projectRoot);
@@ -537,7 +558,7 @@ async function git(cwd, args) {
537
558
 
538
559
  // src/state/store.ts
539
560
  import { mkdir as mkdir3, readFile as readFile6 } from "fs/promises";
540
- import { join as join7 } from "path";
561
+ import { join as join8 } from "path";
541
562
 
542
563
  // src/state/schema.ts
543
564
  var phases = ["explore", "propose", "implement", "verify", "sync", "archive"];
@@ -645,7 +666,7 @@ var StateStore = class {
645
666
  }
646
667
  async writeGlobal(state) {
647
668
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
648
- await mkdir3(join7(this.projectRoot, "openspec"), { recursive: true });
669
+ await mkdir3(join8(this.projectRoot, "openspec"), { recursive: true });
649
670
  await atomicWrite(this.globalPath(), `${JSON.stringify(state, null, 2)}
650
671
  `);
651
672
  }
@@ -666,15 +687,15 @@ var StateStore = class {
666
687
  }
667
688
  async writeChange(state) {
668
689
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
669
- await mkdir3(join7(this.projectRoot, "openspec", "changes", state.changeId), { recursive: true });
690
+ await mkdir3(join8(this.projectRoot, "openspec", "changes", state.changeId), { recursive: true });
670
691
  await atomicWrite(this.changePath(state.changeId), `${JSON.stringify(state, null, 2)}
671
692
  `);
672
693
  }
673
694
  globalPath() {
674
- return join7(this.projectRoot, "openspec", "fet-state.json");
695
+ return join8(this.projectRoot, "openspec", "fet-state.json");
675
696
  }
676
697
  changePath(changeId) {
677
- return join7(this.projectRoot, "openspec", "changes", changeId, "fet-state.json");
698
+ return join8(this.projectRoot, "openspec", "changes", changeId, "fet-state.json");
678
699
  }
679
700
  };
680
701
  function isNotFound(error) {
@@ -886,7 +907,7 @@ async function assertVerified(ctx) {
886
907
  // src/commands/verify.ts
887
908
  import { createHash } from "crypto";
888
909
  import { mkdir as mkdir4, readFile as readFile8, stat as stat4 } from "fs/promises";
889
- import { join as join8 } from "path";
910
+ import { join as join9 } from "path";
890
911
  async function verifyCommand(ctx, options) {
891
912
  if (options.auto) {
892
913
  const scan = await ctx.scanner.scan(ctx.projectRoot, {});
@@ -953,8 +974,8 @@ async function verifyCommand(ctx, options) {
953
974
  async function writeInstructions(ctx, changeId) {
954
975
  await assertChangeExists(ctx, changeId);
955
976
  const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
956
- const dir = join8(ctx.projectRoot, "openspec", "changes", changeId, ".fet");
957
- const instructionsPath = join8(dir, "verify-instructions.md");
977
+ const dir = join9(ctx.projectRoot, "openspec", "changes", changeId, ".fet");
978
+ const instructionsPath = join9(dir, "verify-instructions.md");
958
979
  await mkdir4(dir, { recursive: true });
959
980
  await atomicWrite(instructionsPath, renderVerifyInstructions(changeId, generatedAt));
960
981
  const state = await ctx.stateStore.getOrCreateChange(changeId, "verify");
@@ -971,7 +992,7 @@ async function writeInstructions(ctx, changeId) {
971
992
  async function markDone(ctx, changeId) {
972
993
  await assertChangeExists(ctx, changeId);
973
994
  const declaredAt = (/* @__PURE__ */ new Date()).toISOString();
974
- const instructionsPath = join8(ctx.projectRoot, "openspec", "changes", changeId, ".fet", "verify-instructions.md");
995
+ const instructionsPath = join9(ctx.projectRoot, "openspec", "changes", changeId, ".fet", "verify-instructions.md");
975
996
  const instructions = await readInstructions(instructionsPath, changeId);
976
997
  const instructionsGeneratedAt = readFrontMatterValue(instructions, "generatedAt") ?? declaredAt;
977
998
  const state = await ctx.stateStore.getOrCreateChange(changeId, "verify");
@@ -1060,12 +1081,13 @@ async function resolveChangeId(ctx) {
1060
1081
  // src/cli/context.ts
1061
1082
  import { resolve } from "path";
1062
1083
 
1063
- // src/adapters/cursor/index.ts
1084
+ // src/adapters/codex/index.ts
1064
1085
  import { mkdir as mkdir5, readFile as readFile9, stat as stat5 } from "fs/promises";
1065
- import { dirname as dirname4, join as join9 } from "path";
1086
+ import { homedir } from "os";
1087
+ import { dirname as dirname5, join as join10 } from "path";
1066
1088
 
1067
- // src/adapters/cursor/templates.ts
1068
- var commands = [
1089
+ // src/adapters/commands.ts
1090
+ var FET_WORKFLOW_COMMANDS = [
1069
1091
  "explore",
1070
1092
  "propose",
1071
1093
  "new",
@@ -1078,8 +1100,629 @@ var commands = [
1078
1100
  "bulk-archive",
1079
1101
  "onboard"
1080
1102
  ];
1103
+ var FET_ADAPTER_COMMANDS = [...FET_WORKFLOW_COMMANDS, "passthrough"];
1104
+
1105
+ // src/adapters/codex/templates.ts
1106
+ function codexGuideFile() {
1107
+ return {
1108
+ path: ".codex/fet/context.md",
1109
+ content: `<!-- FET:MANAGED
1110
+ schemaVersion: 1
1111
+ fetVersion: ${FET_VERSION}
1112
+ generator: codex-adapter
1113
+ adapterVersion: 1
1114
+ FET:END -->
1115
+
1116
+ # FET For Codex
1117
+
1118
+ Before doing FET or OpenSpec work in Codex, read:
1119
+
1120
+ - AGENTS.md
1121
+ - openspec/config.yaml
1122
+ - the active change files under openspec/changes/<change-id>/, when a change is selected
1123
+
1124
+ Use the terminal command \`fet <command>\` as the source of truth for workflow transitions. These files are Codex-readable guidance; they do not register native slash commands.
1125
+
1126
+ Command guides live in .codex/fet/commands/.
1127
+ `
1128
+ };
1129
+ }
1130
+ function codexCommandFiles() {
1131
+ return FET_ADAPTER_COMMANDS.map((command) => ({
1132
+ path: `.codex/fet/commands/${command}.md`,
1133
+ content: renderCommand(command)
1134
+ }));
1135
+ }
1136
+ function codexSlashPromptFiles() {
1137
+ return FET_ADAPTER_COMMANDS.map((command) => ({
1138
+ path: `prompts/fet-${command}.md`,
1139
+ content: renderSlashPrompt(command)
1140
+ }));
1141
+ }
1142
+ function renderCommand(command) {
1143
+ if (command === "passthrough") {
1144
+ return renderPassthroughCommand();
1145
+ }
1146
+ return `<!-- FET:MANAGED
1147
+ schemaVersion: 1
1148
+ fetVersion: ${FET_VERSION}
1149
+ generator: codex-adapter
1150
+ adapterVersion: 1
1151
+ command: fet ${command}
1152
+ FET:END -->
1153
+
1154
+ # fet ${command}
1155
+
1156
+ When the user asks Codex to run the FET ${command} workflow, first make sure the project context is loaded from AGENTS.md and openspec/config.yaml.
1157
+
1158
+ Then run:
1159
+
1160
+ \`\`\`sh
1161
+ fet ${command}
1162
+ \`\`\`
1163
+
1164
+ If the command needs a change id, pass it with \`--change <change-id>\` or use the active OpenSpec change from the user's request.
1165
+
1166
+ After the command completes, report the important next steps from the FET output and keep any generated OpenSpec artifacts in the normal project workflow.
1167
+ `;
1168
+ }
1169
+ function renderPassthroughCommand() {
1170
+ return `<!-- FET:MANAGED
1171
+ schemaVersion: 1
1172
+ fetVersion: ${FET_VERSION}
1173
+ generator: codex-adapter
1174
+ adapterVersion: 1
1175
+ command: fet passthrough <openspec-command>
1176
+ FET:END -->
1177
+
1178
+ # fet passthrough
1179
+
1180
+ When the user asks Codex to run an OpenSpec command that FET does not manage as a first-class workflow command, use FET passthrough instead of calling OpenSpec directly.
1181
+
1182
+ Then run:
1183
+
1184
+ \`\`\`sh
1185
+ fet passthrough <openspec-command> [...args]
1186
+ \`\`\`
1187
+
1188
+ This preserves the FET entry point while allowing access to unmanaged or newly added OpenSpec commands. Passthrough does not update FET lifecycle state.
1189
+ `;
1190
+ }
1191
+ function renderSlashPrompt(command) {
1192
+ if (command === "continue") {
1193
+ return renderContinueSlashPrompt();
1194
+ }
1195
+ if (command === "ff" || command === "propose") {
1196
+ return renderFastForwardSlashPrompt(command);
1197
+ }
1198
+ if (command === "explore") {
1199
+ return renderExploreSlashPrompt();
1200
+ }
1201
+ if (command === "new") {
1202
+ return renderNewSlashPrompt();
1203
+ }
1204
+ if (command === "apply") {
1205
+ return renderApplySlashPrompt();
1206
+ }
1207
+ if (command === "verify") {
1208
+ return renderVerifySlashPrompt();
1209
+ }
1210
+ if (command === "sync") {
1211
+ return renderSyncSlashPrompt();
1212
+ }
1213
+ if (command === "archive") {
1214
+ return renderArchiveSlashPrompt();
1215
+ }
1216
+ if (command === "bulk-archive") {
1217
+ return renderBulkArchiveSlashPrompt();
1218
+ }
1219
+ if (command === "onboard") {
1220
+ return renderOnboardSlashPrompt();
1221
+ }
1222
+ if (command === "passthrough") {
1223
+ return renderPassthroughSlashPrompt();
1224
+ }
1225
+ const usage = command === "passthrough" ? "fet passthrough <openspec-command> [...args]" : `fet ${command} [...args]`;
1226
+ const shellCommand = command === "passthrough" ? "fet passthrough $ARGUMENTS" : `fet ${command} $ARGUMENTS`;
1227
+ const description = command === "passthrough" ? "Run an unmanaged OpenSpec command through FET passthrough" : `Run the FET-managed OpenSpec ${command} workflow`;
1228
+ return `<!-- FET:MANAGED
1229
+ schemaVersion: 1
1230
+ fetVersion: ${FET_VERSION}
1231
+ generator: codex-adapter
1232
+ adapterVersion: 1
1233
+ command: ${usage}
1234
+ FET:END -->
1235
+
1236
+ ---
1237
+ description: ${description}
1238
+ argument-hint: command arguments
1239
+ ---
1240
+
1241
+ Use FET as the entry point for this OpenSpec workflow.
1242
+
1243
+ Before running the command, make sure the relevant project context is loaded from AGENTS.md and openspec/config.yaml. If a change id is needed and was not provided, infer it from the active FET/OpenSpec state when unambiguous; otherwise ask the user for the change id.
1244
+
1245
+ Run:
1246
+
1247
+ \`\`\`sh
1248
+ ${shellCommand}
1249
+ \`\`\`
1250
+
1251
+ After it completes, summarize the important FET output and next steps.
1252
+ `;
1253
+ }
1254
+ function renderNewSlashPrompt() {
1255
+ return renderManagedSlashPrompt(
1256
+ "fet new [...args]",
1257
+ "Create a new FET/OpenSpec change scaffold",
1258
+ `Create a new FET-managed OpenSpec change scaffold.
1259
+
1260
+ Input after the slash command should be a kebab-case change id or a short description.
1261
+
1262
+ Steps:
1263
+
1264
+ 1. Load project context from AGENTS.md and openspec/config.yaml.
1265
+ 2. If no input was provided, ask what change the user wants to build or fix.
1266
+ 3. Derive a kebab-case change id when the user provided a description.
1267
+ 4. Create the change through FET:
1268
+ \`\`\`sh
1269
+ fet new <change-id>
1270
+ \`\`\`
1271
+ 5. Show current artifact status:
1272
+ \`\`\`sh
1273
+ fet passthrough status --change <change-id>
1274
+ \`\`\`
1275
+ 6. Get the first ready artifact instructions:
1276
+ \`\`\`sh
1277
+ fet continue <first-ready-artifact-id> --change <change-id>
1278
+ \`\`\`
1279
+
1280
+ Guardrails:
1281
+ - Do not create artifact files in /prompts:fet-new.
1282
+ - If the change already exists, suggest /prompts:fet-continue <change-id>.
1283
+ - Show the change location and the next command to create the first artifact.`
1284
+ );
1285
+ }
1286
+ function renderApplySlashPrompt() {
1287
+ return renderManagedSlashPrompt(
1288
+ "fet apply [...args]",
1289
+ "Implement tasks from a FET/OpenSpec change",
1290
+ `Implement a FET-managed OpenSpec change.
1291
+
1292
+ Input after the slash command should identify the change, for example a change id or --change <change-id>.
1293
+
1294
+ Steps:
1295
+
1296
+ 1. Resolve the change id. If ambiguous, ask the user.
1297
+ 2. Get FET-managed apply instructions:
1298
+ \`\`\`sh
1299
+ fet apply --change <change-id> --json
1300
+ \`\`\`
1301
+ 3. Read all context files named by the instructions output. If JSON output is unavailable, read the files referenced by the terminal output and the artifacts under openspec/changes/<change-id>/.
1302
+ 4. If apply is blocked because required artifacts are missing, stop and suggest /prompts:fet-continue <change-id> or /prompts:fet-ff <change-id>.
1303
+ 5. Implement pending tasks one by one:
1304
+ - Keep code changes minimal and scoped to the task.
1305
+ - Follow proposal, specs, design, and tasks.
1306
+ - Mark each completed task checkbox in tasks.md from \`- [ ]\` to \`- [x]\`.
1307
+ - Pause and ask if a task is ambiguous or reveals a design conflict.
1308
+ 6. After completing or pausing, summarize completed tasks, remaining tasks, and blockers.
1309
+
1310
+ Guardrails:
1311
+ - Never skip reading OpenSpec artifacts before implementation.
1312
+ - Do not mark a task complete until the code change is actually done.
1313
+ - Do not run sync or archive from apply.`
1314
+ );
1315
+ }
1316
+ function renderVerifySlashPrompt() {
1317
+ return renderManagedSlashPrompt(
1318
+ "fet verify [...args]",
1319
+ "Verify a FET/OpenSpec change before sync or archive",
1320
+ `Verify a FET-managed OpenSpec change.
1321
+
1322
+ Input after the slash command should identify the change. If the user passes --done, declare verification complete only after checks have been performed or explicitly accepted by the user.
1323
+
1324
+ Steps:
1325
+
1326
+ 1. Resolve the change id. If ambiguous, ask the user.
1327
+ 2. Generate FET verification instructions:
1328
+ \`\`\`sh
1329
+ fet verify --change <change-id>
1330
+ \`\`\`
1331
+ 3. Read openspec/changes/<change-id>/.fet/verify-instructions.md.
1332
+ 4. Read available artifacts for the change: proposal.md, design.md, tasks.md, and delta specs.
1333
+ 5. Verify:
1334
+ - Completeness: tasks and required artifacts are present and checked off where appropriate.
1335
+ - Correctness: implementation evidence matches specs and proposal.
1336
+ - Coherence: code follows design decisions and project patterns.
1337
+ 6. Report critical issues, warnings, and suggestions with file references.
1338
+ 7. If there are no critical issues, or the user explicitly accepts the remaining risk, mark FET verification done:
1339
+ \`\`\`sh
1340
+ fet verify --done --change <change-id>
1341
+ \`\`\`
1342
+
1343
+ Guardrails:
1344
+ - Do not run --done before producing a verification assessment.
1345
+ - Treat incomplete tasks or missing required behavior as critical unless user explicitly accepts them.
1346
+ - Suggest /prompts:fet-sync <change-id> and /prompts:fet-archive <change-id> only after verification is done.`
1347
+ );
1348
+ }
1349
+ function renderSyncSlashPrompt() {
1350
+ return renderManagedSlashPrompt(
1351
+ "fet sync [...args]",
1352
+ "Sync delta specs and validate a FET/OpenSpec change",
1353
+ `Sync a FET-managed OpenSpec change.
1354
+
1355
+ Input after the slash command should identify the change.
1356
+
1357
+ Steps:
1358
+
1359
+ 1. Resolve the change id. If ambiguous, ask the user.
1360
+ 2. Confirm FET verification is complete or run /prompts:fet-verify <change-id> first.
1361
+ 3. Find delta specs under openspec/changes/<change-id>/specs/*/spec.md.
1362
+ 4. If delta specs exist, intelligently merge them into openspec/specs/<capability>/spec.md:
1363
+ - Add ADDED requirements.
1364
+ - Apply MODIFIED requirements without deleting unrelated existing scenarios.
1365
+ - Remove REMOVED requirements.
1366
+ - Apply RENAMED requirements.
1367
+ - Preserve main-spec content not mentioned in the delta.
1368
+ 5. If no delta specs exist, state that there is nothing to merge.
1369
+ 6. Run the FET sync gate and strict OpenSpec validation:
1370
+ \`\`\`sh
1371
+ fet sync --change <change-id>
1372
+ \`\`\`
1373
+ 7. Summarize updated capabilities and validation result.
1374
+
1375
+ Guardrails:
1376
+ - Read both delta and main specs before editing.
1377
+ - Make sync idempotent where possible.
1378
+ - If FET reports verify is not done, stop and run/ask for verification instead of bypassing the gate.`
1379
+ );
1380
+ }
1381
+ function renderArchiveSlashPrompt() {
1382
+ return renderManagedSlashPrompt(
1383
+ "fet archive [...args]",
1384
+ "Archive a verified FET/OpenSpec change",
1385
+ `Archive a FET-managed OpenSpec change.
1386
+
1387
+ Input after the slash command should identify the change.
1388
+
1389
+ Steps:
1390
+
1391
+ 1. Resolve the change id. If ambiguous, ask the user.
1392
+ 2. Check artifact and task status:
1393
+ \`\`\`sh
1394
+ fet passthrough status --change <change-id> --json
1395
+ \`\`\`
1396
+ 3. If tasks or required artifacts are incomplete, show the warning and ask whether to continue.
1397
+ 4. If delta specs exist, assess whether they have been synced. If not, recommend /prompts:fet-sync <change-id> before archiving.
1398
+ 5. Confirm FET verification is complete. If not, run or suggest /prompts:fet-verify <change-id>.
1399
+ 6. Archive through FET:
1400
+ \`\`\`sh
1401
+ fet archive --change <change-id>
1402
+ \`\`\`
1403
+ 7. Report archive result, sync status, and any warnings.
1404
+
1405
+ Guardrails:
1406
+ - Do not move change directories manually; use fet archive.
1407
+ - Do not bypass the FET verify gate.
1408
+ - Ask before archiving with incomplete tasks or unsynced delta specs.`
1409
+ );
1410
+ }
1411
+ function renderBulkArchiveSlashPrompt() {
1412
+ return renderManagedSlashPrompt(
1413
+ "fet bulk-archive [...args]",
1414
+ "Archive multiple FET/OpenSpec changes safely",
1415
+ `Bulk archive FET-managed OpenSpec changes.
1416
+
1417
+ Steps:
1418
+
1419
+ 1. List active changes:
1420
+ \`\`\`sh
1421
+ fet passthrough status --json
1422
+ \`\`\`
1423
+ 2. Ask the user which changes to archive. Do not auto-select.
1424
+ 3. For each selected change:
1425
+ - Check artifact/task status.
1426
+ - Confirm verification is done.
1427
+ - Recommend sync if delta specs are unsynced.
1428
+ - Run \`fet archive --change <change-id>\`.
1429
+ 4. If \`fet bulk-archive\` reports that the current OpenSpec version does not support a native bulk command, archive selected changes one by one through \`fet archive --change <change-id>\`.
1430
+ 5. Summarize successes, skipped changes, and failures.
1431
+
1432
+ Guardrails:
1433
+ - Never archive all changes without explicit user selection.
1434
+ - Do not bypass verify or warnings for individual changes.
1435
+ - Continue with remaining selected changes if one archive fails, then report the failure clearly.`
1436
+ );
1437
+ }
1438
+ function renderOnboardSlashPrompt() {
1439
+ return renderManagedSlashPrompt(
1440
+ "fet onboard [...args]",
1441
+ "Load FET/OpenSpec onboarding context",
1442
+ `Onboard the user or Codex into the FET/OpenSpec workflow for this project.
1443
+
1444
+ Steps:
1445
+
1446
+ 1. Read AGENTS.md and openspec/config.yaml.
1447
+ 2. Run FET onboarding:
1448
+ \`\`\`sh
1449
+ fet onboard $ARGUMENTS
1450
+ \`\`\`
1451
+ 3. Summarize:
1452
+ - Project context.
1453
+ - Available active changes.
1454
+ - Core commands: new, continue, ff, apply, verify, sync, archive.
1455
+ - Where Codex prompts live.
1456
+
1457
+ Guardrails:
1458
+ - Do not create or modify artifacts during onboard.
1459
+ - Use this command to orient the session, then suggest the next concrete FET command.`
1460
+ );
1461
+ }
1462
+ function renderPassthroughSlashPrompt() {
1463
+ return renderManagedSlashPrompt(
1464
+ "fet passthrough <openspec-command> [...args]",
1465
+ "Run an unmanaged OpenSpec command through FET",
1466
+ `Run an OpenSpec command that FET does not manage as a first-class workflow command.
1467
+
1468
+ Steps:
1469
+
1470
+ 1. Identify the OpenSpec command and arguments from the user's input.
1471
+ 2. Run it through FET:
1472
+ \`\`\`sh
1473
+ fet passthrough <openspec-command> [...args]
1474
+ \`\`\`
1475
+ 3. Report the output and whether FET lifecycle state was updated.
1476
+
1477
+ Guardrails:
1478
+ - Do not call openspec directly unless FET passthrough itself is unavailable.
1479
+ - Remember that passthrough does not update FET lifecycle state.
1480
+ - For managed workflows, prefer the specific FET prompt instead of passthrough.`
1481
+ );
1482
+ }
1483
+ function renderExploreSlashPrompt() {
1484
+ return renderManagedSlashPrompt(
1485
+ "fet explore [...args]",
1486
+ "Explore requirements for a FET/OpenSpec change",
1487
+ `Enter exploration mode for a FET-managed OpenSpec change.
1488
+
1489
+ Use this command for thinking and proposal shaping, not application implementation.
1490
+
1491
+ Input after the slash command may be a change id, a feature idea, or a problem description.
1492
+
1493
+ Steps:
1494
+
1495
+ 1. Load project context from AGENTS.md and openspec/config.yaml.
1496
+ 2. Check existing changes:
1497
+ \`\`\`sh
1498
+ fet passthrough status --json
1499
+ \`\`\`
1500
+ 3. If the input names an existing change, read its current artifacts under openspec/changes/<change-id>/.
1501
+ 4. If the input is a new idea and the user wants to capture it, derive a kebab-case change id and create the change:
1502
+ \`\`\`sh
1503
+ fet new <change-id>
1504
+ \`\`\`
1505
+ 5. Get proposal instructions through FET:
1506
+ \`\`\`sh
1507
+ fet explore --change <change-id>
1508
+ \`\`\`
1509
+ 6. If the user asks to generate or capture the proposal, create openspec/changes/<change-id>/proposal.md using the instruction/template/output path from FET/OpenSpec. Fill the proposal from the conversation and project context.
1510
+
1511
+ Guardrails:
1512
+ - Do not write application code in explore mode.
1513
+ - Ask a clarifying question if the proposal would otherwise be mostly guesswork.
1514
+ - Creating or updating OpenSpec artifacts is allowed when the user asks to capture the thinking.
1515
+ - After creating proposal.md, show the path and suggest /prompts:fet-continue <change-id> for the next artifact.`
1516
+ );
1517
+ }
1518
+ function renderContinueSlashPrompt() {
1519
+ return renderManagedSlashPrompt(
1520
+ "fet continue [...args]",
1521
+ "Create the next FET/OpenSpec artifact",
1522
+ `Continue a FET-managed OpenSpec change by creating exactly one ready artifact.
1523
+
1524
+ Input after the slash command should be a change id, optionally followed by an artifact id.
1525
+
1526
+ Steps:
1527
+
1528
+ 1. Load project context from AGENTS.md and openspec/config.yaml.
1529
+ 2. Resolve the change id. If it is missing and cannot be inferred unambiguously, ask the user.
1530
+ 3. Check status:
1531
+ \`\`\`sh
1532
+ fet passthrough status --change <change-id> --json
1533
+ \`\`\`
1534
+ 4. Pick the first artifact whose status is ready, unless the user specified an artifact id.
1535
+ 5. Get FET-managed instructions:
1536
+ \`\`\`sh
1537
+ fet continue <artifact-id> --change <change-id> --json
1538
+ \`\`\`
1539
+ 6. Parse the instructions output. Use its template, instruction, dependencies, and outputPath.
1540
+ 7. Read dependency files before writing.
1541
+ 8. Create the artifact file at outputPath. Do not copy context/rules wrapper text into the artifact; use those fields only as constraints.
1542
+ 9. Verify the file exists, then run:
1543
+ \`\`\`sh
1544
+ fet passthrough status --change <change-id>
1545
+ \`\`\`
1546
+
1547
+ Output:
1548
+ - State which artifact was created.
1549
+ - Show the file path.
1550
+ - Show the current status and what to run next.
1551
+
1552
+ Guardrails:
1553
+ - Create one artifact per invocation.
1554
+ - Never skip dependency order.
1555
+ - Ask the user if instructions are ambiguous enough that a useful artifact cannot be written.`
1556
+ );
1557
+ }
1558
+ function renderFastForwardSlashPrompt(command) {
1559
+ const title = command === "propose" ? "Propose a new FET/OpenSpec change" : "Fast-forward FET/OpenSpec artifact creation";
1560
+ return renderManagedSlashPrompt(
1561
+ `fet ${command} [...args]`,
1562
+ command === "propose" ? "Create a change and generate required OpenSpec artifacts" : "Generate required OpenSpec artifacts for a change",
1563
+ `${title}.
1564
+
1565
+ Input after the slash command may be a change id or a description of what the user wants to build.
1566
+
1567
+ Steps:
1568
+
1569
+ 1. Load project context from AGENTS.md and openspec/config.yaml.
1570
+ 2. Resolve or create the change:
1571
+ - If this is a new change, derive a kebab-case id and run \`fet new <change-id>\`.
1572
+ - If the change already exists, continue it instead of recreating it.
1573
+ 3. Check artifact status:
1574
+ \`\`\`sh
1575
+ fet passthrough status --change <change-id> --json
1576
+ \`\`\`
1577
+ 4. Loop until the change is apply-ready:
1578
+ - Pick the first artifact whose status is ready.
1579
+ - Run \`fet continue <artifact-id> --change <change-id> --json\`.
1580
+ - Parse template, instruction, dependencies, and outputPath.
1581
+ - Read dependency files.
1582
+ - Write the artifact file at outputPath.
1583
+ - Re-run \`fet passthrough status --change <change-id> --json\`.
1584
+ - Stop when all apply-required artifacts are done, or when no artifact is ready.
1585
+ 5. If context is unclear, ask one concise question, then continue.
1586
+
1587
+ Artifact rules:
1588
+ - Follow the instruction field from OpenSpec/FET for each artifact.
1589
+ - Use template as structure, filling it with concrete project-specific content.
1590
+ - Do not copy context/rules wrapper text into artifact files.
1591
+ - Verify each file exists after writing.
1592
+
1593
+ Output:
1594
+ - Change id and location.
1595
+ - Artifacts created.
1596
+ - Current status.
1597
+ - Next recommended command, usually /prompts:fet-apply <change-id>.`
1598
+ );
1599
+ }
1600
+ function renderManagedSlashPrompt(command, description, body) {
1601
+ return `<!-- FET:MANAGED
1602
+ schemaVersion: 1
1603
+ fetVersion: ${FET_VERSION}
1604
+ generator: codex-adapter
1605
+ adapterVersion: 1
1606
+ command: ${command}
1607
+ FET:END -->
1608
+
1609
+ ---
1610
+ description: ${description}
1611
+ argument-hint: command arguments
1612
+ ---
1613
+
1614
+ ${body}
1615
+ `;
1616
+ }
1617
+
1618
+ // src/adapters/codex/index.ts
1619
+ var CodexAdapter = class {
1620
+ tool = "codex";
1621
+ adapterVersion = 1;
1622
+ async detect(projectRoot) {
1623
+ return {
1624
+ detected: await exists3(join10(projectRoot, ".codex")) || await exists3(join10(projectRoot, "AGENTS.md")),
1625
+ reason: "Codex adapter is available for projects that use AGENTS.md"
1626
+ };
1627
+ }
1628
+ async planInstall(_projectRoot) {
1629
+ return {
1630
+ tool: this.tool,
1631
+ files: [
1632
+ ...[codexGuideFile(), ...codexCommandFiles()].map((file) => ({
1633
+ ...file,
1634
+ managed: true,
1635
+ root: "project"
1636
+ })),
1637
+ ...codexSlashPromptFiles().map((file) => ({
1638
+ ...file,
1639
+ managed: true,
1640
+ root: "codex-home"
1641
+ }))
1642
+ ]
1643
+ };
1644
+ }
1645
+ async install(projectRoot, plan, force = false) {
1646
+ const written = [];
1647
+ const skipped = [];
1648
+ for (const file of plan.files) {
1649
+ const target = resolveTarget(projectRoot, file);
1650
+ const displayPath = displayPathFor(file);
1651
+ const existing = await readExisting(target);
1652
+ if (existing && !existing.includes("FET:MANAGED") && !force) {
1653
+ throw new FetError({
1654
+ code: "TOOL_ADAPTER_CONFLICT" /* ToolAdapterConflict */,
1655
+ message: "Codex adapter file already exists and is not managed by FET",
1656
+ details: { path: displayPath },
1657
+ suggestedCommand: "fet init --yes"
1658
+ });
1659
+ }
1660
+ if (existing && !existing.includes("FET:MANAGED") && force) {
1661
+ await createBackup(target);
1662
+ }
1663
+ await mkdir5(dirname5(target), { recursive: true });
1664
+ await atomicWrite(target, file.content);
1665
+ written.push(displayPath);
1666
+ }
1667
+ return { tool: this.tool, written, skipped };
1668
+ }
1669
+ async doctor(projectRoot) {
1670
+ const plan = await this.planInstall(projectRoot);
1671
+ const checks = [];
1672
+ for (const file of plan.files) {
1673
+ const target = resolveTarget(projectRoot, file);
1674
+ const displayPath = displayPathFor(file);
1675
+ const content = await readExisting(target);
1676
+ const managed = Boolean(content?.includes("FET:MANAGED"));
1677
+ const versionMatches = Boolean(content?.includes(`adapterVersion: ${this.adapterVersion}`));
1678
+ checks.push({
1679
+ id: `codex:${displayPath}`,
1680
+ status: !content ? "warn" : managed && versionMatches ? "pass" : "warn",
1681
+ message: !content ? `${displayPath} is missing` : !managed ? `${displayPath} exists but is not managed by FET` : !versionMatches ? `${displayPath} adapterVersion is stale` : `${displayPath} is managed by FET`,
1682
+ suggestedCommand: !content || !managed || !versionMatches ? "fet init" : void 0
1683
+ });
1684
+ }
1685
+ return checks;
1686
+ }
1687
+ };
1688
+ function resolveTarget(projectRoot, file) {
1689
+ if (file.root === "codex-home") {
1690
+ return join10(resolveCodexHome(), file.path);
1691
+ }
1692
+ return join10(projectRoot, file.path);
1693
+ }
1694
+ function displayPathFor(file) {
1695
+ if (file.root === "codex-home") {
1696
+ return `$CODEX_HOME/${file.path.replaceAll("\\", "/")}`;
1697
+ }
1698
+ return file.path;
1699
+ }
1700
+ function resolveCodexHome() {
1701
+ return process.env.FET_CODEX_HOME ?? process.env.CODEX_HOME ?? join10(homedir(), ".codex");
1702
+ }
1703
+ async function readExisting(path) {
1704
+ try {
1705
+ return await readFile9(path, "utf8");
1706
+ } catch {
1707
+ return null;
1708
+ }
1709
+ }
1710
+ async function exists3(path) {
1711
+ try {
1712
+ await stat5(path);
1713
+ return true;
1714
+ } catch {
1715
+ return false;
1716
+ }
1717
+ }
1718
+
1719
+ // src/adapters/cursor/index.ts
1720
+ import { mkdir as mkdir6, readFile as readFile10, stat as stat6 } from "fs/promises";
1721
+ import { dirname as dirname6, join as join11 } from "path";
1722
+
1723
+ // src/adapters/cursor/templates.ts
1081
1724
  function cursorSkillFiles() {
1082
- return commands.map((command) => ({
1725
+ return FET_ADAPTER_COMMANDS.map((command) => ({
1083
1726
  path: `.cursor/skills/fet-${command}/SKILL.md`,
1084
1727
  content: renderSkill(command)
1085
1728
  }));
@@ -1110,12 +1753,13 @@ alwaysApply: false
1110
1753
  };
1111
1754
  }
1112
1755
  function renderSkill(command) {
1756
+ const usage = command === "passthrough" ? "fet passthrough <openspec-command> [...args]" : `fet ${command}`;
1113
1757
  return `<!-- FET:MANAGED
1114
1758
  schemaVersion: 1
1115
1759
  fetVersion: ${FET_VERSION}
1116
1760
  generator: cursor-adapter
1117
1761
  adapterVersion: 1
1118
- command: fet ${command}
1762
+ command: ${usage}
1119
1763
  FET:END -->
1120
1764
 
1121
1765
  ---
@@ -1129,7 +1773,7 @@ disable-model-invocation: true
1129
1773
  \u8BF7\u5728\u7EC8\u7AEF\u4E2D\u6267\u884C\uFF1A
1130
1774
 
1131
1775
  \`\`\`sh
1132
- fet ${command}
1776
+ ${usage}
1133
1777
  \`\`\`
1134
1778
 
1135
1779
  \u6267\u884C\u524D\u8BF7\u786E\u8BA4\u5DF2\u9605\u8BFB AGENTS.md \u4E0E openspec/config.yaml\u3002
@@ -1142,7 +1786,7 @@ var CursorAdapter = class {
1142
1786
  adapterVersion = 1;
1143
1787
  async detect(projectRoot) {
1144
1788
  return {
1145
- detected: await exists3(join9(projectRoot, ".cursor")),
1789
+ detected: await exists4(join11(projectRoot, ".cursor")),
1146
1790
  reason: "Cursor adapter is available for any project"
1147
1791
  };
1148
1792
  }
@@ -1159,8 +1803,8 @@ var CursorAdapter = class {
1159
1803
  const written = [];
1160
1804
  const skipped = [];
1161
1805
  for (const file of plan.files) {
1162
- const target = join9(projectRoot, file.path);
1163
- const existing = await readExisting(target);
1806
+ const target = join11(projectRoot, file.path);
1807
+ const existing = await readExisting2(target);
1164
1808
  if (existing && !existing.includes("FET:MANAGED") && !force) {
1165
1809
  throw new FetError({
1166
1810
  code: "TOOL_ADAPTER_CONFLICT" /* ToolAdapterConflict */,
@@ -1172,7 +1816,7 @@ var CursorAdapter = class {
1172
1816
  if (existing && !existing.includes("FET:MANAGED") && force) {
1173
1817
  await createBackup(target);
1174
1818
  }
1175
- await mkdir5(dirname4(target), { recursive: true });
1819
+ await mkdir6(dirname6(target), { recursive: true });
1176
1820
  await atomicWrite(target, file.content);
1177
1821
  written.push(file.path);
1178
1822
  }
@@ -1182,8 +1826,8 @@ var CursorAdapter = class {
1182
1826
  const plan = await this.planInstall(projectRoot);
1183
1827
  const checks = [];
1184
1828
  for (const file of plan.files) {
1185
- const target = join9(projectRoot, file.path);
1186
- const content = await readExisting(target);
1829
+ const target = join11(projectRoot, file.path);
1830
+ const content = await readExisting2(target);
1187
1831
  const managed = Boolean(content?.includes("FET:MANAGED"));
1188
1832
  const versionMatches = Boolean(content?.includes(`adapterVersion: ${this.adapterVersion}`));
1189
1833
  checks.push({
@@ -1196,16 +1840,16 @@ var CursorAdapter = class {
1196
1840
  return checks;
1197
1841
  }
1198
1842
  };
1199
- async function readExisting(path) {
1843
+ async function readExisting2(path) {
1200
1844
  try {
1201
- return await readFile9(path, "utf8");
1845
+ return await readFile10(path, "utf8");
1202
1846
  } catch {
1203
1847
  return null;
1204
1848
  }
1205
1849
  }
1206
- async function exists3(path) {
1850
+ async function exists4(path) {
1207
1851
  try {
1208
- await stat5(path);
1852
+ await stat6(path);
1209
1853
  return true;
1210
1854
  } catch {
1211
1855
  return false;
@@ -1217,28 +1861,28 @@ import { execFile as execFile3 } from "child_process";
1217
1861
  import { promisify as promisify3 } from "util";
1218
1862
 
1219
1863
  // src/openspec/inspector.ts
1220
- import { readdir, stat as stat6 } from "fs/promises";
1221
- import { join as join10 } from "path";
1864
+ import { readdir, stat as stat7 } from "fs/promises";
1865
+ import { join as join12 } from "path";
1222
1866
  async function inspectOpenSpecProject(projectRoot) {
1223
- const openspecPath = join10(projectRoot, "openspec");
1224
- const changesPath = join10(openspecPath, "changes");
1225
- const archivePath = join10(openspecPath, "archive");
1867
+ const openspecPath = join12(projectRoot, "openspec");
1868
+ const changesPath = join12(openspecPath, "changes");
1869
+ const archivePath = join12(openspecPath, "archive");
1226
1870
  return {
1227
- exists: await exists4(openspecPath),
1871
+ exists: await exists5(openspecPath),
1228
1872
  changes: await listDirectories(changesPath),
1229
1873
  archived: await listDirectories(archivePath)
1230
1874
  };
1231
1875
  }
1232
1876
  async function inspectOpenSpecChange(projectRoot, changeId) {
1233
- const changePath = join10(projectRoot, "openspec", "changes", changeId);
1234
- const tasksPath = join10(changePath, "tasks.md");
1235
- const specsPath = join10(changePath, "specs");
1877
+ const changePath = join12(projectRoot, "openspec", "changes", changeId);
1878
+ const tasksPath = join12(changePath, "tasks.md");
1879
+ const specsPath = join12(changePath, "specs");
1236
1880
  return {
1237
1881
  changeId,
1238
- exists: await exists4(changePath),
1239
- hasProposal: await exists4(join10(changePath, "proposal.md")),
1240
- hasTasks: await exists4(tasksPath),
1241
- hasSpecs: await exists4(specsPath),
1882
+ exists: await exists5(changePath),
1883
+ hasProposal: await exists5(join12(changePath, "proposal.md")),
1884
+ hasTasks: await exists5(tasksPath),
1885
+ hasSpecs: await exists5(specsPath),
1242
1886
  tasksPath,
1243
1887
  changePath
1244
1888
  };
@@ -1251,9 +1895,9 @@ async function listDirectories(path) {
1251
1895
  return [];
1252
1896
  }
1253
1897
  }
1254
- async function exists4(path) {
1898
+ async function exists5(path) {
1255
1899
  try {
1256
- await stat6(path);
1900
+ await stat7(path);
1257
1901
  return true;
1258
1902
  } catch {
1259
1903
  return false;
@@ -1412,12 +2056,12 @@ function parseCommands(help) {
1412
2056
  }
1413
2057
 
1414
2058
  // src/scanner/package.ts
1415
- import { readFile as readFile10, stat as stat7 } from "fs/promises";
1416
- import { join as join11 } from "path";
1417
- import { parse } from "yaml";
2059
+ import { readFile as readFile11, stat as stat8 } from "fs/promises";
2060
+ import { join as join13 } from "path";
2061
+ import { parse as parse2 } from "yaml";
1418
2062
  async function readPackageJson(projectRoot) {
1419
2063
  try {
1420
- return JSON.parse(await readFile10(join11(projectRoot, "package.json"), "utf8"));
2064
+ return JSON.parse(await readFile11(join13(projectRoot, "package.json"), "utf8"));
1421
2065
  } catch {
1422
2066
  return null;
1423
2067
  }
@@ -1483,7 +2127,7 @@ function detectFramework(pkg) {
1483
2127
  }
1484
2128
  async function detectLanguage(projectRoot, pkg) {
1485
2129
  const deps = { ...pkg?.dependencies ?? {}, ...pkg?.devDependencies ?? {} };
1486
- if (deps.typescript || await exists5(join11(projectRoot, "tsconfig.json"))) {
2130
+ if (deps.typescript || await exists6(join13(projectRoot, "tsconfig.json"))) {
1487
2131
  return "typescript";
1488
2132
  }
1489
2133
  return "javascript";
@@ -1498,7 +2142,7 @@ async function detectWorkspaces(projectRoot, pkg) {
1498
2142
  return packageWorkspaces;
1499
2143
  }
1500
2144
  try {
1501
- const workspace = parse(await readFile10(join11(projectRoot, "pnpm-workspace.yaml"), "utf8"));
2145
+ const workspace = parse2(await readFile11(join13(projectRoot, "pnpm-workspace.yaml"), "utf8"));
1502
2146
  return (workspace?.packages ?? []).map((path) => ({
1503
2147
  name: path,
1504
2148
  path,
@@ -1518,7 +2162,7 @@ async function detectLockManagers(projectRoot) {
1518
2162
  ];
1519
2163
  const found = [];
1520
2164
  for (const [file, manager] of lockFiles) {
1521
- if (await exists5(join11(projectRoot, file))) {
2165
+ if (await exists6(join13(projectRoot, file))) {
1522
2166
  found.push(manager);
1523
2167
  }
1524
2168
  }
@@ -1533,9 +2177,9 @@ function normalizeWorkspaces(workspaces) {
1533
2177
  function scriptCommand(packageManager, name) {
1534
2178
  return packageManager === "npm" ? `npm run ${name}` : `${packageManager} ${name}`;
1535
2179
  }
1536
- async function exists5(path) {
2180
+ async function exists6(path) {
1537
2181
  try {
1538
- await stat7(path);
2182
+ await stat8(path);
1539
2183
  return true;
1540
2184
  } catch {
1541
2185
  return false;
@@ -1543,14 +2187,14 @@ async function exists5(path) {
1543
2187
  }
1544
2188
 
1545
2189
  // src/scanner/routes.ts
1546
- import { readdir as readdir2, stat as stat8 } from "fs/promises";
1547
- import { join as join12, relative, sep } from "path";
2190
+ import { readdir as readdir2, stat as stat9 } from "fs/promises";
2191
+ import { join as join14, relative, sep } from "path";
1548
2192
  async function scanRoutes(projectRoot) {
1549
2193
  const candidates = ["src/routes", "src/pages", "app", "pages"];
1550
2194
  const routes = [];
1551
2195
  for (const candidate of candidates) {
1552
- const root = join12(projectRoot, candidate);
1553
- if (!await exists6(root)) {
2196
+ const root = join14(projectRoot, candidate);
2197
+ if (!await exists7(root)) {
1554
2198
  continue;
1555
2199
  }
1556
2200
  for (const file of await listFiles(root)) {
@@ -1577,7 +2221,7 @@ async function listFiles(root) {
1577
2221
  const entries = await readdir2(root, { withFileTypes: true });
1578
2222
  const files = [];
1579
2223
  for (const entry of entries) {
1580
- const path = join12(root, entry.name);
2224
+ const path = join14(root, entry.name);
1581
2225
  if (entry.isDirectory()) {
1582
2226
  files.push(...await listFiles(path));
1583
2227
  } else {
@@ -1586,9 +2230,9 @@ async function listFiles(root) {
1586
2230
  }
1587
2231
  return files;
1588
2232
  }
1589
- async function exists6(path) {
2233
+ async function exists7(path) {
1590
2234
  try {
1591
- await stat8(path);
2235
+ await stat9(path);
1592
2236
  return true;
1593
2237
  } catch {
1594
2238
  return false;
@@ -1720,7 +2364,7 @@ async function createCommandContext(command, options) {
1720
2364
  stateStore: new StateStore(projectRoot, FET_VERSION, project),
1721
2365
  openSpec: new DefaultOpenSpecAdapter(),
1722
2366
  scanner: new ProjectScanner(),
1723
- toolAdapters: [new CursorAdapter()]
2367
+ toolAdapters: [new CursorAdapter(), new CodexAdapter()]
1724
2368
  };
1725
2369
  }
1726
2370