@nick848/fet 1.0.1 → 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/README.md +7 -4
- package/dist/cli/index.js +672 -49
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -182,7 +182,7 @@ function count(content, needle) {
|
|
|
182
182
|
|
|
183
183
|
// src/templates/agents-md.ts
|
|
184
184
|
function renderAgentsMd(scan) {
|
|
185
|
-
const
|
|
185
|
+
const commands = Object.entries(scan.commands).map(([name, command]) => `| ${name} | \`${command.command}\` | ${command.source} |`).join("\n");
|
|
186
186
|
const routes = scan.routes.map((route) => `| ${route.path} | ${route.source} | ${route.confidence}${route.inferred ? " inferred" : ""} |`).join("\n");
|
|
187
187
|
const workspaces = scan.project.workspaces.map((workspace) => `| ${workspace.name} | ${workspace.path} | ${workspace.source} |`).join("\n");
|
|
188
188
|
return `# Project Context
|
|
@@ -206,7 +206,7 @@ ${workspaces || "| root | . | inferred |"}
|
|
|
206
206
|
|
|
207
207
|
| Name | Command | Source |
|
|
208
208
|
|------|---------|--------|
|
|
209
|
-
${
|
|
209
|
+
${commands || "| [NEEDS LLM INPUT] | [NEEDS LLM INPUT] | [NEEDS LLM INPUT] |"}
|
|
210
210
|
|
|
211
211
|
## Structure
|
|
212
212
|
|
|
@@ -1081,12 +1081,13 @@ async function resolveChangeId(ctx) {
|
|
|
1081
1081
|
// src/cli/context.ts
|
|
1082
1082
|
import { resolve } from "path";
|
|
1083
1083
|
|
|
1084
|
-
// src/adapters/
|
|
1084
|
+
// src/adapters/codex/index.ts
|
|
1085
1085
|
import { mkdir as mkdir5, readFile as readFile9, stat as stat5 } from "fs/promises";
|
|
1086
|
+
import { homedir } from "os";
|
|
1086
1087
|
import { dirname as dirname5, join as join10 } from "path";
|
|
1087
1088
|
|
|
1088
|
-
// src/adapters/
|
|
1089
|
-
var
|
|
1089
|
+
// src/adapters/commands.ts
|
|
1090
|
+
var FET_WORKFLOW_COMMANDS = [
|
|
1090
1091
|
"explore",
|
|
1091
1092
|
"propose",
|
|
1092
1093
|
"new",
|
|
@@ -1099,8 +1100,629 @@ var commands = [
|
|
|
1099
1100
|
"bulk-archive",
|
|
1100
1101
|
"onboard"
|
|
1101
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
|
|
1102
1724
|
function cursorSkillFiles() {
|
|
1103
|
-
return
|
|
1725
|
+
return FET_ADAPTER_COMMANDS.map((command) => ({
|
|
1104
1726
|
path: `.cursor/skills/fet-${command}/SKILL.md`,
|
|
1105
1727
|
content: renderSkill(command)
|
|
1106
1728
|
}));
|
|
@@ -1131,12 +1753,13 @@ alwaysApply: false
|
|
|
1131
1753
|
};
|
|
1132
1754
|
}
|
|
1133
1755
|
function renderSkill(command) {
|
|
1756
|
+
const usage = command === "passthrough" ? "fet passthrough <openspec-command> [...args]" : `fet ${command}`;
|
|
1134
1757
|
return `<!-- FET:MANAGED
|
|
1135
1758
|
schemaVersion: 1
|
|
1136
1759
|
fetVersion: ${FET_VERSION}
|
|
1137
1760
|
generator: cursor-adapter
|
|
1138
1761
|
adapterVersion: 1
|
|
1139
|
-
command:
|
|
1762
|
+
command: ${usage}
|
|
1140
1763
|
FET:END -->
|
|
1141
1764
|
|
|
1142
1765
|
---
|
|
@@ -1150,7 +1773,7 @@ disable-model-invocation: true
|
|
|
1150
1773
|
\u8BF7\u5728\u7EC8\u7AEF\u4E2D\u6267\u884C\uFF1A
|
|
1151
1774
|
|
|
1152
1775
|
\`\`\`sh
|
|
1153
|
-
|
|
1776
|
+
${usage}
|
|
1154
1777
|
\`\`\`
|
|
1155
1778
|
|
|
1156
1779
|
\u6267\u884C\u524D\u8BF7\u786E\u8BA4\u5DF2\u9605\u8BFB AGENTS.md \u4E0E openspec/config.yaml\u3002
|
|
@@ -1163,7 +1786,7 @@ var CursorAdapter = class {
|
|
|
1163
1786
|
adapterVersion = 1;
|
|
1164
1787
|
async detect(projectRoot) {
|
|
1165
1788
|
return {
|
|
1166
|
-
detected: await
|
|
1789
|
+
detected: await exists4(join11(projectRoot, ".cursor")),
|
|
1167
1790
|
reason: "Cursor adapter is available for any project"
|
|
1168
1791
|
};
|
|
1169
1792
|
}
|
|
@@ -1180,8 +1803,8 @@ var CursorAdapter = class {
|
|
|
1180
1803
|
const written = [];
|
|
1181
1804
|
const skipped = [];
|
|
1182
1805
|
for (const file of plan.files) {
|
|
1183
|
-
const target =
|
|
1184
|
-
const existing = await
|
|
1806
|
+
const target = join11(projectRoot, file.path);
|
|
1807
|
+
const existing = await readExisting2(target);
|
|
1185
1808
|
if (existing && !existing.includes("FET:MANAGED") && !force) {
|
|
1186
1809
|
throw new FetError({
|
|
1187
1810
|
code: "TOOL_ADAPTER_CONFLICT" /* ToolAdapterConflict */,
|
|
@@ -1193,7 +1816,7 @@ var CursorAdapter = class {
|
|
|
1193
1816
|
if (existing && !existing.includes("FET:MANAGED") && force) {
|
|
1194
1817
|
await createBackup(target);
|
|
1195
1818
|
}
|
|
1196
|
-
await
|
|
1819
|
+
await mkdir6(dirname6(target), { recursive: true });
|
|
1197
1820
|
await atomicWrite(target, file.content);
|
|
1198
1821
|
written.push(file.path);
|
|
1199
1822
|
}
|
|
@@ -1203,8 +1826,8 @@ var CursorAdapter = class {
|
|
|
1203
1826
|
const plan = await this.planInstall(projectRoot);
|
|
1204
1827
|
const checks = [];
|
|
1205
1828
|
for (const file of plan.files) {
|
|
1206
|
-
const target =
|
|
1207
|
-
const content = await
|
|
1829
|
+
const target = join11(projectRoot, file.path);
|
|
1830
|
+
const content = await readExisting2(target);
|
|
1208
1831
|
const managed = Boolean(content?.includes("FET:MANAGED"));
|
|
1209
1832
|
const versionMatches = Boolean(content?.includes(`adapterVersion: ${this.adapterVersion}`));
|
|
1210
1833
|
checks.push({
|
|
@@ -1217,16 +1840,16 @@ var CursorAdapter = class {
|
|
|
1217
1840
|
return checks;
|
|
1218
1841
|
}
|
|
1219
1842
|
};
|
|
1220
|
-
async function
|
|
1843
|
+
async function readExisting2(path) {
|
|
1221
1844
|
try {
|
|
1222
|
-
return await
|
|
1845
|
+
return await readFile10(path, "utf8");
|
|
1223
1846
|
} catch {
|
|
1224
1847
|
return null;
|
|
1225
1848
|
}
|
|
1226
1849
|
}
|
|
1227
|
-
async function
|
|
1850
|
+
async function exists4(path) {
|
|
1228
1851
|
try {
|
|
1229
|
-
await
|
|
1852
|
+
await stat6(path);
|
|
1230
1853
|
return true;
|
|
1231
1854
|
} catch {
|
|
1232
1855
|
return false;
|
|
@@ -1238,28 +1861,28 @@ import { execFile as execFile3 } from "child_process";
|
|
|
1238
1861
|
import { promisify as promisify3 } from "util";
|
|
1239
1862
|
|
|
1240
1863
|
// src/openspec/inspector.ts
|
|
1241
|
-
import { readdir, stat as
|
|
1242
|
-
import { join as
|
|
1864
|
+
import { readdir, stat as stat7 } from "fs/promises";
|
|
1865
|
+
import { join as join12 } from "path";
|
|
1243
1866
|
async function inspectOpenSpecProject(projectRoot) {
|
|
1244
|
-
const openspecPath =
|
|
1245
|
-
const changesPath =
|
|
1246
|
-
const archivePath =
|
|
1867
|
+
const openspecPath = join12(projectRoot, "openspec");
|
|
1868
|
+
const changesPath = join12(openspecPath, "changes");
|
|
1869
|
+
const archivePath = join12(openspecPath, "archive");
|
|
1247
1870
|
return {
|
|
1248
|
-
exists: await
|
|
1871
|
+
exists: await exists5(openspecPath),
|
|
1249
1872
|
changes: await listDirectories(changesPath),
|
|
1250
1873
|
archived: await listDirectories(archivePath)
|
|
1251
1874
|
};
|
|
1252
1875
|
}
|
|
1253
1876
|
async function inspectOpenSpecChange(projectRoot, changeId) {
|
|
1254
|
-
const changePath =
|
|
1255
|
-
const tasksPath =
|
|
1256
|
-
const specsPath =
|
|
1877
|
+
const changePath = join12(projectRoot, "openspec", "changes", changeId);
|
|
1878
|
+
const tasksPath = join12(changePath, "tasks.md");
|
|
1879
|
+
const specsPath = join12(changePath, "specs");
|
|
1257
1880
|
return {
|
|
1258
1881
|
changeId,
|
|
1259
|
-
exists: await
|
|
1260
|
-
hasProposal: await
|
|
1261
|
-
hasTasks: await
|
|
1262
|
-
hasSpecs: await
|
|
1882
|
+
exists: await exists5(changePath),
|
|
1883
|
+
hasProposal: await exists5(join12(changePath, "proposal.md")),
|
|
1884
|
+
hasTasks: await exists5(tasksPath),
|
|
1885
|
+
hasSpecs: await exists5(specsPath),
|
|
1263
1886
|
tasksPath,
|
|
1264
1887
|
changePath
|
|
1265
1888
|
};
|
|
@@ -1272,9 +1895,9 @@ async function listDirectories(path) {
|
|
|
1272
1895
|
return [];
|
|
1273
1896
|
}
|
|
1274
1897
|
}
|
|
1275
|
-
async function
|
|
1898
|
+
async function exists5(path) {
|
|
1276
1899
|
try {
|
|
1277
|
-
await
|
|
1900
|
+
await stat7(path);
|
|
1278
1901
|
return true;
|
|
1279
1902
|
} catch {
|
|
1280
1903
|
return false;
|
|
@@ -1433,12 +2056,12 @@ function parseCommands(help) {
|
|
|
1433
2056
|
}
|
|
1434
2057
|
|
|
1435
2058
|
// src/scanner/package.ts
|
|
1436
|
-
import { readFile as
|
|
1437
|
-
import { join as
|
|
2059
|
+
import { readFile as readFile11, stat as stat8 } from "fs/promises";
|
|
2060
|
+
import { join as join13 } from "path";
|
|
1438
2061
|
import { parse as parse2 } from "yaml";
|
|
1439
2062
|
async function readPackageJson(projectRoot) {
|
|
1440
2063
|
try {
|
|
1441
|
-
return JSON.parse(await
|
|
2064
|
+
return JSON.parse(await readFile11(join13(projectRoot, "package.json"), "utf8"));
|
|
1442
2065
|
} catch {
|
|
1443
2066
|
return null;
|
|
1444
2067
|
}
|
|
@@ -1504,7 +2127,7 @@ function detectFramework(pkg) {
|
|
|
1504
2127
|
}
|
|
1505
2128
|
async function detectLanguage(projectRoot, pkg) {
|
|
1506
2129
|
const deps = { ...pkg?.dependencies ?? {}, ...pkg?.devDependencies ?? {} };
|
|
1507
|
-
if (deps.typescript || await
|
|
2130
|
+
if (deps.typescript || await exists6(join13(projectRoot, "tsconfig.json"))) {
|
|
1508
2131
|
return "typescript";
|
|
1509
2132
|
}
|
|
1510
2133
|
return "javascript";
|
|
@@ -1519,7 +2142,7 @@ async function detectWorkspaces(projectRoot, pkg) {
|
|
|
1519
2142
|
return packageWorkspaces;
|
|
1520
2143
|
}
|
|
1521
2144
|
try {
|
|
1522
|
-
const workspace = parse2(await
|
|
2145
|
+
const workspace = parse2(await readFile11(join13(projectRoot, "pnpm-workspace.yaml"), "utf8"));
|
|
1523
2146
|
return (workspace?.packages ?? []).map((path) => ({
|
|
1524
2147
|
name: path,
|
|
1525
2148
|
path,
|
|
@@ -1539,7 +2162,7 @@ async function detectLockManagers(projectRoot) {
|
|
|
1539
2162
|
];
|
|
1540
2163
|
const found = [];
|
|
1541
2164
|
for (const [file, manager] of lockFiles) {
|
|
1542
|
-
if (await
|
|
2165
|
+
if (await exists6(join13(projectRoot, file))) {
|
|
1543
2166
|
found.push(manager);
|
|
1544
2167
|
}
|
|
1545
2168
|
}
|
|
@@ -1554,9 +2177,9 @@ function normalizeWorkspaces(workspaces) {
|
|
|
1554
2177
|
function scriptCommand(packageManager, name) {
|
|
1555
2178
|
return packageManager === "npm" ? `npm run ${name}` : `${packageManager} ${name}`;
|
|
1556
2179
|
}
|
|
1557
|
-
async function
|
|
2180
|
+
async function exists6(path) {
|
|
1558
2181
|
try {
|
|
1559
|
-
await
|
|
2182
|
+
await stat8(path);
|
|
1560
2183
|
return true;
|
|
1561
2184
|
} catch {
|
|
1562
2185
|
return false;
|
|
@@ -1564,14 +2187,14 @@ async function exists5(path) {
|
|
|
1564
2187
|
}
|
|
1565
2188
|
|
|
1566
2189
|
// src/scanner/routes.ts
|
|
1567
|
-
import { readdir as readdir2, stat as
|
|
1568
|
-
import { join as
|
|
2190
|
+
import { readdir as readdir2, stat as stat9 } from "fs/promises";
|
|
2191
|
+
import { join as join14, relative, sep } from "path";
|
|
1569
2192
|
async function scanRoutes(projectRoot) {
|
|
1570
2193
|
const candidates = ["src/routes", "src/pages", "app", "pages"];
|
|
1571
2194
|
const routes = [];
|
|
1572
2195
|
for (const candidate of candidates) {
|
|
1573
|
-
const root =
|
|
1574
|
-
if (!await
|
|
2196
|
+
const root = join14(projectRoot, candidate);
|
|
2197
|
+
if (!await exists7(root)) {
|
|
1575
2198
|
continue;
|
|
1576
2199
|
}
|
|
1577
2200
|
for (const file of await listFiles(root)) {
|
|
@@ -1598,7 +2221,7 @@ async function listFiles(root) {
|
|
|
1598
2221
|
const entries = await readdir2(root, { withFileTypes: true });
|
|
1599
2222
|
const files = [];
|
|
1600
2223
|
for (const entry of entries) {
|
|
1601
|
-
const path =
|
|
2224
|
+
const path = join14(root, entry.name);
|
|
1602
2225
|
if (entry.isDirectory()) {
|
|
1603
2226
|
files.push(...await listFiles(path));
|
|
1604
2227
|
} else {
|
|
@@ -1607,9 +2230,9 @@ async function listFiles(root) {
|
|
|
1607
2230
|
}
|
|
1608
2231
|
return files;
|
|
1609
2232
|
}
|
|
1610
|
-
async function
|
|
2233
|
+
async function exists7(path) {
|
|
1611
2234
|
try {
|
|
1612
|
-
await
|
|
2235
|
+
await stat9(path);
|
|
1613
2236
|
return true;
|
|
1614
2237
|
} catch {
|
|
1615
2238
|
return false;
|
|
@@ -1741,7 +2364,7 @@ async function createCommandContext(command, options) {
|
|
|
1741
2364
|
stateStore: new StateStore(projectRoot, FET_VERSION, project),
|
|
1742
2365
|
openSpec: new DefaultOpenSpecAdapter(),
|
|
1743
2366
|
scanner: new ProjectScanner(),
|
|
1744
|
-
toolAdapters: [new CursorAdapter()]
|
|
2367
|
+
toolAdapters: [new CursorAdapter(), new CodexAdapter()]
|
|
1745
2368
|
};
|
|
1746
2369
|
}
|
|
1747
2370
|
|